Compare commits

..

7 Commits

Author SHA1 Message Date
tequ
fb5bc07bc9 wasmtime static link 2026-07-23 17:10:01 +09:00
tequ
0b8aadaf00 Add export for wasmtime dependency in action.yml 2026-07-23 16:44:08 +09:00
tequ
848aad1e5d run workflow 2026-07-23 16:35:48 +09:00
tequ
ca0144f811 clang-format 2026-07-23 16:16:17 +09:00
tequ
132a3a8520 Merge branch 'dev' into wasmtime-engine 2026-07-06 14:04:03 +09:00
tequ
0e8cedc7a9 feat(hook): add Wasmtime engine backend behind featureWasmtimeEngine amendment
Add Wasmtime v44.0.1 as a second WebAssembly execution engine, selectable at runtime via the new featureWasmtimeEngine Amendment. When the Amendment is enabled, hook execution and WASM validation switch from WasmEdge to Wasmtime.
sfHookInstructionCount changes semantics to fuel consumed (initial budget: 10,000,000,000 units); fee calculation is redefined accordingly.

New files:
- external/wasmtime/conanfile.py + conandata.yml — prebuilt C API binary package (macOS arm64, Linux x86_64; SHA256-pinned at v44.0.1)
- cmake/deps/Wasmtime.cmake — find_package(wasmtime REQUIRED)
- src/.../WasmtimeEngine.h/.cpp — IWasmEngine implementation

Build system changes:
- CMakeLists.txt, conanfile.py: with_wasmtime option (default on)
- cmake/RippledCore.cmake: link wasmtime::wasmtime alongside wasmedge::wasmedge

Protocol changes:
- features.macro: XRPL_FEATURE(WasmtimeEngine, Supported::yes, DefaultNo)
- WasmEngine.h: constexpr kWasmtimeInitialFuel = 10'000'000'000ULL (consensus-fixed)
- applyHook.cpp, SetHook.cpp, Change.cpp: engine selected by featureWasmtimeEngine

Implementation notes:
- Consensus-fixed config: consume_fuel=true, wasm_simd=true, relaxed_simd=false, reference_types=true, bulk_memory=true,  multi_value=false, nan_canonicalization=true
- Termination ABI: accept/rollback sets ExecState::terminated flag then returns a wasm_trap_t* to unwind the guest; execute() checks the flag before inspecting callErr to distinguish clean termination from errors
- Memory access workaround (ensureMemoryExported): Wasmtime lacks an index-based memory accessor equivalent to WasmEdge_CallingFrameGetMemoryInstance. The binary is patched before instantiation to inject a "memory" export when the module owns memory without exporting it, enabling wasmtime_caller_export_get() inside host callbacks.
2026-05-08 16:49:10 +09:00
tequ
e07b573753 refactor(hook): isolate WasmEdge behind IWasmEngine abstraction layer
Introduce an engine-agnostic abstraction so that <wasmedge/wasmedge.h> is included only in WasmEdgeEngine.cpp, enabling future execution engines to be plugged in without touching Hook API implementation code.

New files:
- include/xrpl/hook/WasmTypes.h     – GuestMemory, WasmValue, HostFunctionDecl
- src/.../hook/detail/WasmEngine.h  – IWasmEngine pure virtual interface, ExecutionResult, hookHostFunctionDecls()
- src/.../hook/detail/WasmEdgeEngine.h/.cpp – WasmEdge implementation; the sole consumer of wasmedge.h

Key changes:
- Macro.h: replace WasmEdge_* types with engine-agnostic WasmValue/GuestMemory; DEFINE_HOOK_FUNCTION now returns HostCallStatus via a thin wrapper
- applyHook.h: remove wasmedge.h include and HookExecutor class entirely
- applyHook.cpp: call makeWasmEdgeEngine()->execute() at the single call site
- SetHook.cpp, Change.cpp: call makeWasmEdgeEngine()->validate() for WASM validation instead of HookExecutor::validateWasm()

No behavioral change; no Amendment required.
2026-05-08 13:32:17 +09:00
308 changed files with 3860 additions and 50529 deletions

View File

@@ -1,40 +0,0 @@
# Export stream terminal follow-up tests
## Scope
Added regression coverage in `src/test/consensus/ConsensusExtensions_test.cpp`
for the LOW test-gap finding in `synthesis.md`:
- A tracked pending-latch origin that is absent from the next validated ledger
emits one empty `terminal: true` replacement snapshot. The test verifies the
retained owner/origin identity, validated-ledger cursor, empty share set,
internal retirement, and suppression of a duplicate terminal snapshot.
- A validated-ledger callback queued behind `exportStreamMutex_` is fenced by
`stopExportShareService()`. The test verifies that stop cannot return while
the stream lock is held and that the queued callback emits no snapshot after
the service flag is cleared.
No production files were changed. Deterministic accepted-edge/terminal lock
winner coverage was not added because there is no test seam between collector
admission and stream-lock acquisition; adding one would require production
instrumentation, while timing-only assertions would be flaky.
## Verification
- `x-format-changed`: passed.
- `x-quick-check`: passed for `ConsensusExtensions_test.cpp`.
- Narrow object build: passed for
`CMakeFiles/rippled.dir/src/test/consensus/ConsensusExtensions_test.cpp.o`.
- `/tmp/rippled-export-stream-tests --unittest=ripple.consensus.ConsensusExtensions`:
passed, 1 suite, 66 cases, 9,628 assertions, 0 failures.
The normal incremental `rippled` target was stopped after Ninja scheduled 321
steps from a stale build tree. A temporary binary was linked from existing
objects plus the newly compiled test object to execute the narrow suite without
a broad rebuild.
## Repository state
Work began at requested HEAD `018719fda`. A concurrent commit advanced the
shared branch to `572365baf` (`fix(export): enforce global live latch cap`)
before this follow-up was committed; that unrelated change was preserved.

View File

@@ -145,6 +145,11 @@ runs:
# Export wasmedge if not already exported
conan list wasmedge/0.11.2@xahaud/stable 2>/dev/null | (grep -q "not found" && exit 1 || exit 0) || \
conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable
# Export wasmtime if not already exported
conan list wasmtime/44.0.1@xahaud/stable 2>/dev/null | (grep -q "not found" && exit 1 || exit 0) || \
conan export external/wasmtime --version 44.0.1 --user xahaud --channel stable
- name: Install dependencies
shell: bash
env:

View File

@@ -40,7 +40,7 @@ jobs:
run: |
# Download install.sh
curl -o /tmp/wasienv-install.sh https://raw.githubusercontent.com/wasienv/wasienv/master/install.sh
# Replace /bin to /local/bin
sed -i 's|/bin|/local/bin|g' /tmp/wasienv-install.sh

View File

@@ -16,8 +16,6 @@ jobs:
generator: ./hook/generate_extern.sh
- target: hook/sfcodes.h
generator: bash ./hook/generate_sfcodes.sh
- target: hook/genesis/headers/sfcodes.h
generator: bash ./hook/generate_sfcodes.sh
- target: hook/tts.h
generator: ./hook/generate_tts.sh
- target: hook/ls_flags.h

7
.gitignore vendored
View File

@@ -127,12 +127,5 @@ bld.rippled/
generated
.vscode
# AI docs (local working documents)
.ai-docs/
# Local formal-methods workspace; kept as a separate repository and optionally
# symlinked here for navigation.
formal/lean/xahau_consensus
# Suggested in-tree build directory
/.build/

4
.testnet/.gitignore vendored
View File

@@ -1,4 +0,0 @@
output/
__pycache__/
scenarios/odd-cases/
scenarios/suite-experiments.yml

View File

@@ -1,29 +0,0 @@
"""Scenario: ConsensusEntropy amendment crashes non-supporting node.
Votes ConsensusEntropy accept on all nodes except n4, then waits for n4
to crash as the amendment activates without its support.
x-testnet run --scenario-script consensus_entropy_crash.py
"""
from helpers import CONSENSUS_ENTROPY_FEATURE
async def scenario(ctx, log):
await ctx.wait_for_ledger_close()
ctx.feature(CONSENSUS_ENTROPY_FEATURE, vetoed=False, exclude_nodes=[4])
log("Waiting for ConsensusEntropy to be voted for...")
await ctx.wait_for_feature(
CONSENSUS_ENTROPY_FEATURE,
check=lambda s: not s.get("vetoed"),
exclude_nodes=[4],
timeout=60,
)
log("Waiting for n4 to crash...")
op = await ctx.wait_for_nodes_down(nodes=[4], timeout=600)
ctx.assert_log("unsupported amendments activated", since=op.started, nodes=[4])
ctx.assert_exit_status(0, nodes=[4])
log("PASS: n4 shut down due to unsupported amendment")

View File

@@ -1,52 +0,0 @@
""":descr: entropy stays valid under transaction load"""
from __future__ import annotations
from helpers import require_entropy, get_entropy_tx, assert_valid_entropy
variants = [
{"label": "light", "min_txns": 5, "max_txns": 10},
{"label": "heavy", "min_txns": 50, "max_txns": 60},
{"label": "super_heavy", "min_txns": 90, "max_txns": 120},
]
async def scenario(ctx, log, *, min_txns=5, max_txns=10, **_):
await require_entropy(ctx, log)
gen = ctx.txn_generator(min_txns=min_txns, max_txns=max_txns)
await gen.start()
await gen.wait_until_ready()
log(f"Transaction generator ready ({min_txns}-{max_txns} txns/ledger)")
# Wait for pipeline warmup + a few txn-bearing ledgers.
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(10, node_id=0, timeout=120)
end_seq = ctx.validated_ledger_index(0)
log(f"Inspecting ledgers {start_seq + 1}{end_seq}")
digests = set()
total_user_txns = 0
for seq in range(start_seq + 1, end_seq + 1):
ce, user_txns = get_entropy_tx(ctx, seq)
digest, count = assert_valid_entropy(ce, seq, seen_digests=digests)
total_user_txns += len(user_txns)
log(
f" Ledger {seq}: EntropyCount={count} "
f"user_txns={len(user_txns)} Digest={digest[:16]}..."
)
await gen.stop()
log(
f"Verified {end_seq - start_seq} ledgers: {total_user_txns} user txns, "
f"all entropy valid and unique"
)
if total_user_txns == 0:
raise AssertionError("No user transactions were included in any ledger")
log("PASS")

View File

@@ -1,28 +0,0 @@
""":descr: healthy non-standalone testnet without UNLReport mints Tier 1 fallback"""
from __future__ import annotations
from helpers import require_entropy, get_entropy_tx, assert_consensus_fallback
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Non-standalone nodes require a ledger-anchored UNLReport before assigning
# validator_quorum / participant_aligned labels. Without it, the RNG pipeline
# may still collect commits/reveals, but injection must remain Tier 1.
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
log("Pipeline warmed up without UNLReport")
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(5, node_id=0, timeout=90)
end_seq = ctx.validated_ledger_index(0)
log(f"Inspecting ledgers {start_seq + 1} -> {end_seq}")
for seq in range(start_seq + 1, end_seq + 1):
ce, _ = get_entropy_tx(ctx, seq)
digest, count = assert_consensus_fallback(ce, seq)
log(f" Ledger {seq}: EntropyCount={count} Digest={digest[:16]}...")
log(f"Verified {end_seq - start_seq} ledgers: all consensus_fallback")
log("PASS")

View File

@@ -1,123 +0,0 @@
""":descr: compile, install, and invoke a Hook using the entropy_cr_* API"""
from __future__ import annotations
from helpers import require_entropy
ENTROPY_HOOK_C = r"""
#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 entropy_cr_dice(uint32_t sides, uint32_t min_tier);
extern int64_t entropy_cr_random(
uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
extern int64_t entropy_cr_status(void);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter) + 1)
#define ENTROPY_TIER(x) (((uint64_t)(x) >> 32U) & 0xFFU)
#define ENTROPY_COUNT(x) (((uint64_t)(x) >> 16U) & 0xFFFFU)
#define ENTROPY_DENOMINATOR(x) ((uint64_t)(x) & 0xFFFFU)
int64_t
hook(uint32_t reserved)
{
_g(1, 1);
int64_t status = entropy_cr_status();
if (status < 0)
return accept(0, 0, 10);
uint32_t tier = ENTROPY_TIER(status);
uint32_t count = ENTROPY_COUNT(status);
uint32_t denominator = ENTROPY_DENOMINATOR(status);
if (tier < 3 || count < 4 || denominator < count)
return accept(0, 0, 11);
int64_t die = entropy_cr_dice(6, 3);
if (die < 0 || die >= 6)
return accept(0, 0, 12);
uint8_t random_bytes[32];
for (int i = 0; GUARD(32), i < 32; ++i)
random_bytes[i] = 0;
if (entropy_cr_random((uint32_t)random_bytes, 32, 3) != 32)
return accept(0, 0, 13);
int nonzero = 0;
for (int i = 0; GUARD(32), i < 32; ++i)
if (random_bytes[i] != 0)
nonzero = 1;
if (!nonzero)
return accept(0, 0, 14);
return accept(0, 0, 0);
}
"""
def assert_success(result, operation):
meta = result.get("meta", result.get("metaData", {}))
tx_result = meta.get("TransactionResult", result.get("engine_result", ""))
if tx_result != "tesSUCCESS":
raise AssertionError(f"{operation} failed: {result}")
return meta
async def scenario(ctx, log):
await require_entropy(ctx, log)
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
await ctx.fund_accounts({"entropy_api": 1000})
account = ctx.account("entropy_api")
wasm = ctx.compile_hook(ENTROPY_HOOK_C, label="entropy-cr-api")
install = await ctx.submit_and_wait(
{
"TransactionType": "SetHook",
"Hooks": [
{
"Hook": {
"CreateCode": wasm.hex().upper(),
"HookOn": "0" * 64,
"HookNamespace": "0" * 64,
"HookApiVersion": 0,
"Flags": 1,
}
}
],
"Fee": "100000000",
},
account.wallet,
)
assert_success(install, "SetHook")
log("entropy_cr_* Hook installed")
invoke = await ctx.submit_and_wait(
{
"TransactionType": "Invoke",
"Fee": "1000000",
},
account.wallet,
)
meta = assert_success(invoke, "Invoke")
executions = meta.get("HookExecutions", [])
if len(executions) != 1:
raise AssertionError(f"Expected one HookExecution, got: {executions}")
execution = executions[0].get("HookExecution", {})
if execution.get("HookResult") != 3:
raise AssertionError(f"Hook did not ACCEPT: {execution}")
if "HookReturnCode" not in execution:
raise AssertionError(f"HookReturnCode missing from execution: {execution}")
return_code = execution["HookReturnCode"]
if str(return_code) != "0":
raise AssertionError(f"entropy_cr_* Hook check failed: {execution}")
log("entropy_cr_status, entropy_cr_dice, and entropy_cr_random passed")
log("PASS")

View File

@@ -1,162 +0,0 @@
""":descr: 5/6 validator_quorum, 4/6 participant_aligned (tier 2), recovery
Requires node_count: 6 (see suite.yml) — the smallest NON-degenerate Tier 2
size. At n=6: tier2 floor = 4, validator quorum = 5, validation quorum = 5. So
6/6, 5/6 present -> validator_quorum (EntropyTier=3)
4/6 present -> participant_aligned (EntropyTier=2, count 4) <-- the band
3/6 present -> consensus_fallback (EntropyTier=1)
n=5 has NO tier-2 band (tier2 == quorum == 4), which is why the existing
degradation smoke at 5 nodes only ever sees tier 3 / fallback.
KEY: the 4/6 window is BELOW the 80% validation quorum (5). The 4 survivors
keep CLOSING ledgers that carry tier-2 entropy, but those ledgers do NOT
validate until the network recovers — exactly the transition window Tier 2
serves. So validated_ledger_index() stalls; we instead inspect a surviving
node's CLOSED ledger (its LCL) directly, and cross-check the injection from the
cohort's logs.
"""
from __future__ import annotations
from helpers import (
require_entropy,
get_entropy_tx,
assert_participant_aligned,
assert_validator_quorum,
)
def _closed_entropy(result):
"""(seq, ConsensusEntropy tx) from a ctx.ledger('closed', transactions=True)
result, or (None, None) if the fetch returned no usable ledger.
Enforces the per-ledger invariant that an entropy-enabled closed ledger
carries EXACTLY ONE ConsensusEntropy pseudo-tx (mirroring get_entropy_tx):
a duplicate or missing injection raises here with a clear error instead of
being silently skipped and resurfacing later as a generic 'no tier-2 ledger'.
"""
if not result or not isinstance(result.get("ledger"), dict):
return None, None
led = result["ledger"]
try:
seq = int(led.get("ledger_index"))
except (TypeError, ValueError):
return None, None
ce = [
t
for t in led.get("transactions", [])
if isinstance(t, dict) and t.get("TransactionType") == "ConsensusEntropy"
]
if len(ce) != 1:
raise AssertionError(
f"Closed ledger {seq}: expected 1 ConsensusEntropy txn, got {len(ce)}"
)
return seq, ce[0]
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Baseline: healthy 6/6 produces validator_quorum entropy.
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
# --- 5/6: settles back to validator_quorum (5 present >= quorum 5) ---
val_before_drop = ctx.validated_ledger_index(0)
ctx.stop_node(5)
await ctx.wait_for_nodes_down(nodes=[5], timeout=30)
# Settle a few ledgers past the membership change. The ledger right at a
# validator drop can carry a transient consensus_fallback (tier 1, count 0,
# deterministic and by design) before the commit/reveal pipeline re-primes,
# so we do NOT assume any single post-drop ledger is already tier 3.
await ctx.wait_for_ledgers(4, node_id=0, timeout=90)
# 5/6 is at/above the 80% quorum (5), so steady state is validator_quorum.
# Scan the post-drop validated ledgers (all carry the 5-node cohort, so a
# tier-3 here has count == 5) and require at least one clean validator_quorum
# — EntropyTier=3, count >= quorum, non-zero digest — tolerating the
# transition fallback instead of depending on where the tip happened to land.
val_5of6 = ctx.validated_ledger_index(0)
t3_seq = None
for seq in range(val_5of6, val_before_drop, -1):
ce, _ = get_entropy_tx(ctx, seq)
tier = ce.get("EntropyTier")
log(f" 5/6 ledger {seq}: tier={tier} count={ce.get('EntropyCount')}")
if tier == 3:
assert_validator_quorum(ce, seq, min_count=5)
t3_seq = seq
break
if t3_seq is None:
raise AssertionError(
f"5/6: no validator_quorum (tier 3) entropy in post-drop validated "
f"ledgers {val_before_drop + 1}..{val_5of6}"
)
log(f"5/6: validator_quorum at validated seq {t3_seq}")
#@@start test-participant-aligned-window
# --- 4/6: participant_aligned (Tier 2) degraded window ---
ctx.stop_node(4)
await ctx.wait_for_nodes_down(nodes=[4], timeout=30)
# ~12s window: confirm tier-2 INJECTION from the cohort's logs, and that the
# round is NOT the below-quorum fallback path (which is what distinguishes
# the tier-2 band from the tier-1 fallback regime).
op = await ctx.sleep(12, name="tier2_window")
selected_t2 = ctx.search_logs(
r"RNG: entropy selected seq=\d+ tier=2 count=4",
within=op.window,
nodes=[0, 1, 2, 3],
)
log(f"4/6: 'entropy selected tier=2 count=4' logs: {selected_t2.count}")
if selected_t2.count == 0:
raise AssertionError(
"4/6 window injected no participant_aligned (tier 2) entropy: no "
"'RNG: entropy selected ... tier=2 count=4' on the surviving cohort"
)
ctx.assert_not_log(
r"STALLDIAG: rng-commit-timeout-below-quorum",
within=op.window,
nodes=[0, 1, 2, 3],
)
# Verify the on-ledger EntropyTier=2 DIRECTLY: validation is stalled (4 < 5),
# so sample the surviving cohort's CLOSED ledger (its LCL — built but not yet
# validated). At least one must be participant_aligned with EntropyCount=4.
tier2_on_ledger = 0
last_seq = None
for _ in range(5):
seq, ce = _closed_entropy(
ctx.ledger("closed", transactions=True, node_id=0)
)
if ce is not None and seq is not None and seq != last_seq:
last_seq = seq
tier = ce.get("EntropyTier")
count = ce.get("EntropyCount", -1)
log(f" closed ledger {seq}: tier={tier} count={count}")
if tier == 2:
assert_participant_aligned(ce, seq, expected_count=4)
tier2_on_ledger += 1
await ctx.sleep(3)
if tier2_on_ledger == 0:
raise AssertionError(
"no closed participant_aligned (tier 2) ledger observed during the "
"4/6 window (tier 2 was injected per logs, but not seen on a closed "
"ledger)"
)
log(f"4/6: {tier2_on_ledger} participant_aligned closed ledger(s) verified")
#@@end test-participant-aligned-window
# --- Recovery: liveness — validation resumes once quorum is restored ---
ctx.start_node(4)
ctx.start_node(5)
await ctx.wait_for_ledgers(1, node_id=0, timeout=120)
val_recovered = ctx.validated_ledger_index(0)
if not val_recovered or val_recovered <= val_5of6:
raise AssertionError(
f"Validated ledger did not advance after recovery "
f"({val_5of6} -> {val_recovered})"
)
log(f"Recovered: validated seq {val_5of6} -> {val_recovered}")
log("PASS")

View File

@@ -1,164 +0,0 @@
""":descr: 4/5 liveness, 3/5 fail-closed sub-quorum window, recovery"""
from __future__ import annotations
from helpers import (
require_entropy,
get_entropy_tx,
entropy_fields,
assert_consensus_fallback,
)
def _closed_entropy(result):
"""Return (seq, ConsensusEntropy tx) from a closed-ledger RPC result.
The 3/5 window is below validation quorum, so validated-ledger history is
expected to stall. Sampling a surviving node's closed ledger catches any
local LCL that advanced despite the sub-quorum condition.
"""
if not result or not isinstance(result.get("ledger"), dict):
return None, None
ledger = result["ledger"]
try:
seq = int(ledger.get("ledger_index"))
except (TypeError, ValueError):
return None, None
ce = [
tx
for tx in ledger.get("transactions", [])
if isinstance(tx, dict) and tx.get("TransactionType") == "ConsensusEntropy"
]
if len(ce) != 1:
raise AssertionError(
f"Closed ledger {seq}: expected 1 ConsensusEntropy txn, got {len(ce)}"
)
return seq, ce[0]
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Baseline: wait 1 ledger to confirm network is healthy.
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
# --- 4/5 liveness ---
ctx.stop_node(4)
await ctx.wait_for_nodes_down(nodes=[4], timeout=30)
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
log("4/5: liveness OK")
# Snapshot validated seq before dropping to 3/5.
val_before = ctx.validated_ledger_index(0)
# --- 3/5 degraded window ---
ctx.stop_node(3)
await ctx.wait_for_nodes_down(nodes=[3], timeout=30)
# 10s ≈ 3 rounds at 3s cadence.
await ctx.sleep(10)
val_after = ctx.validated_ledger_index(0)
log(f"3/5: validated ledger {val_before}{val_after}")
if val_after and val_before and val_after > val_before:
raise AssertionError(
f"3/5 sub-quorum window unexpectedly validated ledgers "
f"({val_before} -> {val_after})"
)
# If the surviving cohort exposes an advanced closed ledger despite being
# below validation quorum, it must fail closed to consensus_fallback. This
# keeps the entropy assertion live without pretending validated history
# should advance at 3/5.
degraded_fallback = 0
last_closed = None
for _ in range(5):
seq, ce = _closed_entropy(ctx.ledger("closed", transactions=True, node_id=0))
if seq and val_before and seq > val_before and seq != last_closed:
last_closed = seq
digest, count = assert_consensus_fallback(ce, seq)
degraded_fallback += 1
log(
f" 3/5 closed ledger {seq}: EntropyCount={count} "
f"Digest={digest[:16]}... FALLBACK"
)
await ctx.sleep(2)
log(f"3/5 closed-ledger fallback samples: {degraded_fallback}")
# Log checks tied to current transition mechanics:
# - commit-set SHAMap publication is the observable output of entering the
# commit sidecar phase
# - ConvergingCommit transition is the gateway out of seq=0-only behavior
# - rng-commit-timeout-below-quorum is the degraded-window fallback path
ctx.log_level("LedgerConsensus", "trace")
ctx.log_level("ConsensusExtensions", "trace")
op = await ctx.sleep(6, name="stall_window")
ctx.assert_not_log(
r"RNG: transitioned to ConvergingCommit", within=op.window, nodes=[0, 1, 2]
)
ctx.assert_not_log(
r"RNG: built commitSet SHAMap", within=op.window, nodes=[0, 1, 2]
)
gate_blocked = ctx.search_logs(
r"STALLDIAG: establish gate blocked reason=(pause|no-tx-consensus)",
within=op.window,
nodes=[0, 1, 2],
)
log(f"3/5: establish gate-blocked logs in 6s: {gate_blocked.count}")
below_quorum = ctx.search_logs(
r"STALLDIAG: rng-commit-timeout-below-quorum",
within=op.window,
nodes=[0, 1, 2],
)
log(f"3/5: RNG commit timeout below quorum logs in 6s: {below_quorum.count}")
# --- Recovery: restart nodes, verify ledger advancement ---
ctx.start_node(3)
ctx.start_node(4)
await ctx.wait_for_ledgers(1, node_id=0, timeout=120)
val_recovered = ctx.validated_ledger_index(0)
pre_recovery = max(v for v in [val_before, val_after] if v is not None)
log(f"Recovered: validated seq {pre_recovery}{val_recovered}")
if not val_recovered or val_recovered <= pre_recovery:
raise AssertionError(
f"Validated ledger did not advance after recovery "
f"({pre_recovery}{val_recovered})"
)
# Inspect post-recovery ledgers separately from the degraded window above.
# Once the network is back at quorum, validator-tier entropy is expected
# again (transitional fallback ledgers are fine) and must be quorum-met.
fallback_count = 0
validator_count = 0
for seq in range(pre_recovery + 1, val_recovered + 1):
ce, _ = get_entropy_tx(ctx, seq)
digest, entropy_count, is_fallback = entropy_fields(ce)
if is_fallback:
fallback_count += 1
else:
validator_count += 1
if entropy_count < 4:
raise AssertionError(
f"Ledger {seq}: validator entropy with sub-quorum "
f"EntropyCount={entropy_count} (need >= 4)"
)
log(
f" Ledger {seq}: EntropyCount={entropy_count} "
f"{'FALLBACK' if is_fallback else 'VALIDATOR'}"
)
log(
f"Entropy summary: {fallback_count} fallback, "
f"{validator_count} validator"
)
log("PASS")

View File

@@ -1,44 +0,0 @@
""":descr: drop 2 nodes (3/5 stall), restart both, verify recovery"""
from __future__ import annotations
from helpers import require_entropy
async def scenario(ctx, log):
await require_entropy(ctx, log)
await ctx.wait_for_ledgers(1, node_id=0, timeout=60)
log("Baseline OK")
# Drop 2 nodes → validation stall.
ctx.stop_node(3)
ctx.stop_node(4)
await ctx.wait_for_nodes_down(nodes=[3, 4], timeout=30)
info = ctx.rpc.server_info(node_id=0)
val_before = info.get("info", {}).get("validated_ledger", {}).get("seq", 0)
log(f"Stalled at validated seq {val_before}")
# Let it sit for a few rounds in degraded state.
await ctx.sleep(6)
# Bring both nodes back.
ctx.start_node(3)
ctx.start_node(4)
log("Restarted n3 and n4, waiting for recovery...")
# Recovery: wait for ANY validated ledger advance on n0.
await ctx.wait_for_ledger_close(node_id=0, timeout=60)
info = ctx.rpc.server_info(node_id=0)
val_after = info.get("info", {}).get("validated_ledger", {}).get("seq", 0)
log(f"Recovered: validated seq {val_before}{val_after}")
if val_after <= val_before:
raise AssertionError(
f"Validated ledger did not advance after recovery "
f"({val_before}{val_after})"
)
log("PASS")

View File

@@ -1,27 +0,0 @@
""":descr: all 5 nodes healthy, every ledger has valid unique quorum-met entropy"""
from __future__ import annotations
from helpers import require_entropy, get_entropy_tx, assert_valid_entropy
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Wait for the RNG pipeline to warm up past initial proposal/sidecar gossip.
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
log("Pipeline warmed up")
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(10, node_id=0, timeout=120)
end_seq = ctx.validated_ledger_index(0)
log(f"Inspecting ledgers {start_seq + 1}{end_seq}")
digests = set()
for seq in range(start_seq + 1, end_seq + 1):
ce, _ = get_entropy_tx(ctx, seq)
digest, count = assert_valid_entropy(ce, seq, seen_digests=digests)
log(f" Ledger {seq}: EntropyCount={count} Digest={digest[:16]}...")
log(f"Verified {end_seq - start_seq} ledgers: all quorum entropy, all unique")
log("PASS")

View File

@@ -1,108 +0,0 @@
defaults:
network:
node_count: 5
launcher: tmux
find_ports: true
slave_delay: 0.2
features:
- ConsensusEntropy
- Export
track_features:
- ConsensusEntropy
- Export
unl_report: true
log_levels:
TxQ: info
Protocol: debug
Peer: debug
LedgerConsensus: debug
ConsensusExtensions: debug
NetworkOPs: info
rc:
- rng_poll_ms=333
tests:
# --- CE + Export (post-validation shares, sidecar root convergence) ---
- name: steady_state_export_ce
script: .testnet/scenarios/export/steady_state_export.py
- name: retriable_export_ce
script: .testnet/scenarios/export/retriable_export.py
- name: export_degradation_ce
script: .testnet/scenarios/export/export_degradation.py
network:
rc:
- rng_poll_ms=333
- n3:no_export_sig=true
- n4:no_export_sig=true
- name: export_without_unl_report
script: .testnet/scenarios/export/export_without_unl_report.py
network:
features:
- Export
track_features:
- Export
unl_report: false
- name: export_no_veto_missing_observation
script: .testnet/scenarios/export/export_no_veto_missing_observation.py
network:
rc:
- rng_poll_ms=333
- n4:no_export_sig_hash=true
- name: export_share_subscription
script: .testnet/scenarios/export/export_share_subscription.py
- name: export_two_member_committee_recovery
script: .testnet/scenarios/export/export_committee_recovery.py
# CE + Export: 1 node suppressed, 4/5 = 80% quorum, should succeed
- name: export_ce_one_node_down
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: true
network:
rc:
- rng_poll_ms=333
- n4:no_export_sig=true
# --- Export only, no CE (80% active-view quorum) ---
- name: export_only_all_up
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: true
network:
features:
- Export
track_features:
- Export
- name: export_only_one_node_down
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: true
network:
features:
- Export
track_features:
- Export
rc:
- rng_poll_ms=333
- n4:no_export_sig=true
- name: export_only_two_nodes_down
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: false
network:
features:
- Export
track_features:
- Export
rc:
- rng_poll_ms=333
- n3:no_export_sig=true
- n4:no_export_sig=true

View File

@@ -1,126 +0,0 @@
""":descr: a 2-of-5 Export committee recovers without a new intent
The intent selects only validators n0 and n4, so qC is 2. Validator n4 is
stopped before admission: the network still validates the intent with 4/5, but
one selected share cannot form a witness. Restarting n4 must republish its share
for the same live latch and complete the witness without resubmitting Export.
"""
from __future__ import annotations
from export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
assert_export_latch,
bitmap_positions,
find_export_signature_witness,
find_export_txns,
require_export,
submit_direct_export,
wait_for_export_signature_witness,
)
async def scenario(ctx, log):
await require_export(ctx, log)
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
alice = ctx.account("alice")
bob = ctx.account("bob")
if not ctx.stop_node(4):
raise AssertionError("Failed to stop selected validator n4")
await ctx.wait_for_nodes_down(nodes=[4], timeout=30)
log("Stopped selected validator n4; selected committee is n0+n4")
current = ctx.validated_ledger_index(0)
result = await submit_direct_export(
ctx,
log,
{
"TransactionType": "Export",
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current + 1,
"LastLedgerSequence": current + EXPORT_RETRY_LEDGER_WINDOW,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
committee_node_ids=[0, 4],
)
if result.get("engine_result") != "tesSUCCESS":
raise AssertionError(f"Export intent failed: {result}")
origin = result.get("hash")
origin_seq = int(result.get("ledger_index"))
if not origin:
raise AssertionError(f"Validated Export missing hash: {result}")
assert_export_latch(
ctx,
alice.address,
log,
origin_hash=origin,
expect_witness=False,
)
selected = {0, 1}
await ctx.wait_for_ledger(origin_seq + 1, node_id=0, timeout=30)
if find_export_signature_witness(ctx, origin_seq + 1, origin):
raise AssertionError(
"Witness formed while one of two selected signers was down"
)
assert_export_latch(
ctx,
alice.address,
log,
origin_hash=origin,
expect_witness=False,
)
log("No witness with only one selected signer; original latch remains pending")
if not ctx.start_node(4):
raise AssertionError("Failed to restart selected validator n4")
await ctx.wait_for_nodes(
lambda node_id: ctx.rpc.server_info(node_id) is not None,
nodes=[4],
timeout=30,
poll_interval=0.5,
name="selected-export-validator-up",
)
log("Restarted selected validator n4")
witness = await wait_for_export_signature_witness(
ctx, log, origin, after_ledger=origin_seq
)
contributors = bitmap_positions(witness["EntropyContributors"])
if contributors != selected:
raise AssertionError(
f"Recovered witness contributors {contributors} != selected {selected}"
)
if len(witness["_WitnessSigners"]) != 2:
raise AssertionError(
f"Recovered witness has {len(witness['_WitnessSigners'])} signers, need 2"
)
witness_seq = int(witness["LedgerSequence"])
for seq in range(origin_seq + 1, witness_seq + 1):
if any(tx.get("Account") == alice.address for tx in find_export_txns(ctx, seq)):
raise AssertionError(
f"A second Export was submitted before recovery in ledger {seq}"
)
assert_export_latch(
ctx,
alice.address,
log,
origin_hash=origin,
expect_witness=True,
)
log(f"Same Export {origin} completed after selected validator recovery")
log("PASS")

View File

@@ -1,118 +0,0 @@
""":descr: Submit ttEXPORT with 2 nodes suppressing export signatures and
verify the admitted intent remains unwitnessed through its publication window.
Nodes 3 and 4 have runtime_config no_export_sig=true, so only 3/5 nodes
provide export signatures. With 80% quorum = ceil(5*0.8) = 4 required,
the export cannot reach quorum and no ExportSignatures witness may be recorded.
Flow:
1. Fund alice and bob
2. alice submits ttEXPORT with an explicit authority declaration
3. Only 3/5 post-validation shares become available (need 4)
4. Verify the publication window closes without a witness
5. Verify subsequent payment still works (sequence not permanently blocked)
"""
from __future__ import annotations
from export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
assert_export_latch,
require_export,
submit_direct_export,
wait_for_export_signature_witness,
)
async def scenario(ctx, log):
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
log("Nodes 3,4 have runtime_config no_export_sig=true (3/5 sigs, need 4)")
#@@start test-export-below-quorum-expiry
# --- Submit intent; only 3/5 validators release shares. ---
result = await submit_direct_export(
ctx,
log,
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
final_seq = result.get("ledger_index", ctx.validated_ledger_index(0))
origin_hash = result.get("hash")
engine_result = result.get("engine_result", "")
log(f"Export intent admitted at ledger {final_seq}, result: {engine_result}")
if engine_result != "tesSUCCESS":
raise AssertionError(f"Expected admitted intent, got {engine_result}")
if not origin_hash:
raise AssertionError(f"Validated Export missing hash: {result}")
await wait_for_export_signature_witness(
ctx,
log,
origin_hash,
after_ledger=final_seq,
expect_witness=False,
)
assert_export_latch(
ctx,
alice.address,
log,
expect_exists=True,
origin_hash=origin_hash,
expect_witness=False,
)
#@@end test-export-below-quorum-expiry
# --- Verify subsequent payment works regardless ---
log("Submitting payment from alice to bob...")
pay_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": bob.address,
"Amount": "1000000",
"Fee": "12",
},
alice.wallet,
timeout=30,
)
pay_engine = pay_result.get("engine_result", "")
log(f"Payment result: {pay_engine}")
if pay_engine != "tesSUCCESS":
raise AssertionError(
f"Payment failed after unwitnessed export: {pay_engine} "
f"-- sequence may be blocked"
)
log("Payment succeeded -- account not permanently blocked")
log("PASS")

View File

@@ -1,458 +0,0 @@
"""Shared helpers for Export scenario tests."""
from __future__ import annotations
import hashlib
import json
from xahaud_scripts.testnet.config import (
_decode_node_public_key,
_unl_report_index,
feature_name_to_hash,
)
EXPORT_RETRY_LEDGER_WINDOW = 5
EXPORT_PUBLICATION_LEDGER_WINDOW = 5
async def require_export(
ctx, log, *, require_unl_report=True, require_runtime_config=True
):
"""Wait for first ledger and assert Export is enabled.
Network-mode Export success requires a parent-ledger UNLReport-backed
active validator view. Most export scenarios seed that report in genesis;
assert it here so a success-path test cannot accidentally pass setup
without the condition Export::doApply requires. The no-UNLReport retry
scenario opts out deliberately.
The tracked export suite also uses XAHAUD_RUNTIME_TEST_CONFIG for polling
and fault-injection knobs. Default binaries reject the runtime_config RPC,
so check it up front rather than silently running without those knobs.
"""
await ctx.wait_for_ledger_close(timeout=120)
if require_runtime_config:
result = ctx.rpc.runtime_config(0)
if not result or result.get("error"):
raise AssertionError(
"Export suite requires a binary built with "
"xahaud_runtime_test_config=ON; runtime_config RPC returned "
f"{result}"
)
log("RuntimeConfig RPC active")
feature = ctx.feature_check(feature_name_to_hash("Export"), node_id=0)
if not feature or not feature.get("enabled", False):
raise AssertionError(f"Export not enabled: {feature}")
log("Export enabled")
if require_unl_report:
result = ctx.rpc.ledger_entry(0, _unl_report_index())
node = (result or {}).get("node", {})
active = node.get("ActiveValidators", [])
if node.get("LedgerEntryType") != "UNLReport" or not active:
raise AssertionError(
"Export success scenario requires a ledger UNLReport with "
f"ActiveValidators, got: {result}"
)
log(f"UNLReport active validators: {len(active)}")
def find_export_txns(ctx, seq):
"""Find Export transactions in a ledger.
Returns list of Export transaction dicts.
"""
result = ctx.ledger(seq, transactions=True)
if not result:
return []
txns = result.get("ledger", {}).get("transactions", [])
return [tx for tx in txns if tx.get("TransactionType") == "Export"]
def _validator_master_keys_by_node(ctx):
"""Return generated validator master keys keyed by testnet node id."""
network = json.loads((ctx.base_dir / "network.json").read_text())
return {
int(node["id"]): _decode_node_public_key(node["public_key"])
for node in network["nodes"]
}
def _export_committee_fields(master_keys):
"""Return the canonical roster and its protocol content digest."""
encoded = [bytes.fromhex(key) for key in master_keys]
if not encoded or len(encoded) > 32 or len(set(encoded)) != len(encoded):
raise AssertionError("Export committee requires 1..32 unique masters")
encoded.sort()
roster = b"".join(encoded)
preimage = b"ECM\0" + len(encoded).to_bytes(4, "big") + roster
digest = hashlib.sha512(preimage).digest()[:32]
return {
"ExportCommitteeHash": digest.hex().upper(),
"ExportCommittee": roster.hex().upper(),
}
def bitmap_positions(bitmap):
"""Return set positions from a witness contributor bitmap."""
raw = bytes.fromhex(bitmap) if isinstance(bitmap, str) else bytes(bitmap)
return {
byte_index * 8 + bit_index
for byte_index, byte in enumerate(raw)
for bit_index in range(8)
if byte & (1 << bit_index)
}
def export_authority(ctx, *, require_unl_report=True, committee_node_ids=None):
"""Build an account-owned committee declaration for a direct Export."""
ledger_result = ctx.ledger("validated") or {}
ledger = ledger_result.get("ledger", {})
ledger_hash = ledger_result.get("ledger_hash") or ledger.get("hash")
if not ledger_hash:
raise AssertionError(f"Validated ledger hash unavailable: {ledger_result}")
report = (
ctx.rpc.request(
0,
"ledger_entry",
{"index": _unl_report_index(), "ledger_hash": ledger_hash},
)
or {}
)
active = report.get("node", {}).get("ActiveValidators", [])
if not active:
if require_unl_report:
raise AssertionError(f"UNLReport active validators unavailable: {report}")
# The negative scenario still submits a structurally valid roster so
# source eligibility, rather than client construction, rejects it.
masters = _validator_master_keys_by_node(ctx)
selected_ids = (
sorted(masters) if committee_node_ids is None else committee_node_ids
)
return _export_committee_fields([masters[node_id] for node_id in selected_ids])
active_keys = set()
for entry in active:
validator = entry.get("ActiveValidator", entry)
key = validator.get("PublicKey")
if not key:
raise AssertionError(f"Malformed UNLReport validator entry: {entry}")
active_keys.add(key.upper())
active_keys = sorted(active_keys, key=bytes.fromhex)
if committee_node_ids is None:
selected_keys = active_keys
else:
masters = _validator_master_keys_by_node(ctx)
selected_keys = []
for node_id in committee_node_ids:
if node_id not in masters:
raise AssertionError(f"Unknown testnet validator node n{node_id}")
if masters[node_id] not in active_keys:
raise AssertionError(
f"Validator n{node_id} is absent from the active UNLReport"
)
selected_keys.append(masters[node_id])
if not selected_keys:
raise AssertionError("Export committee must select at least one validator")
return _export_committee_fields(selected_keys)
async def create_export_committee(
ctx, log, wallet, *, committee_node_ids=None
):
"""Create one immutable account-owned committee and return its fields."""
authority = export_authority(ctx, committee_node_ids=committee_node_ids)
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"ExportCommittee": authority["ExportCommittee"],
"Fee": "1000000",
},
wallet,
)
meta = result.get("meta", result.get("metaData", {}))
if meta.get("TransactionResult") != "tesSUCCESS":
raise AssertionError(f"Export committee setup failed: {result}")
log(f"Export committee created: {authority['ExportCommitteeHash']}")
return authority
def find_export_signature_witness(ctx, seq, origin_hash):
"""Find a later-ledger ExportSignatures witness for an Export origin."""
result = ctx.ledger(seq, transactions=True)
txns = (result or {}).get("ledger", {}).get("transactions", [])
for tx in txns:
if not isinstance(tx, dict):
continue
if tx.get("TransactionType") != "ExportSignatures":
continue
if tx.get("TransactionHash") == origin_hash:
return tx
return None
async def wait_for_export_signature_witness(
ctx,
log,
origin_hash,
*,
after_ledger,
max_ledgers=EXPORT_PUBLICATION_LEDGER_WINDOW,
expect_witness=True,
):
"""Wait through the publication window for an origin-keyed witness."""
scanned = after_ledger
target = after_ledger + max_ledgers
while scanned < target:
current = ctx.validated_ledger_index(0)
if current is None or current <= scanned:
await ctx.wait_for_ledger(scanned + 1, node_id=0, timeout=30)
current = ctx.validated_ledger_index(0)
if current is None or current <= scanned:
continue
scan_through = min(current, target)
for seq in range(scanned + 1, scan_through + 1):
witness = find_export_signature_witness(ctx, seq, origin_hash)
if witness:
if not expect_witness:
raise AssertionError(
f"Unexpected ExportSignatures witness in ledger {seq}"
)
log(f" ExportSignatures witness found in ledger {seq}")
return assert_export_witness(witness, origin_hash, seq, log)
scanned = scan_through
if expect_witness:
raise AssertionError(
f"No ExportSignatures witness for {origin_hash} within "
f"{max_ledgers} validated ledgers"
)
log(f" No witness observed through ledger {scanned}")
return None
async def wait_for_validated_transaction(
ctx, tx_hash, *, after_ledger, max_ledgers=EXPORT_RETRY_LEDGER_WINDOW
):
"""Resolve a raw non-tes submit result to validated transaction evidence."""
checked = after_ledger
target = after_ledger + max_ledgers
while True:
result = ctx.rpc.request(0, "tx", {"transaction": tx_hash}) or {}
if result.get("validated"):
return result
if checked >= target:
break
await ctx.wait_for_ledger(checked + 1, node_id=0, timeout=30)
current = ctx.validated_ledger_index(0)
checked = min(target, max(checked + 1, current or checked + 1))
raise AssertionError(f"Transaction {tx_hash} did not validate by ledger {target}")
async def submit_direct_export(
ctx,
log,
tx,
wallet,
*,
timeout=60,
max_rebases=2,
committee_node_ids=None,
):
"""Submit a direct Export, rebasing after a validated parent mismatch."""
for attempt in range(max_rebases + 1):
current = ctx.validated_ledger_index(0)
if current is None:
raise AssertionError("Validated ledger unavailable before Export")
candidate = dict(tx)
candidate.update(export_authority(ctx, committee_node_ids=committee_node_ids))
candidate["LastLedgerSequence"] = current + EXPORT_RETRY_LEDGER_WINDOW
result = await ctx.submit_and_wait(candidate, wallet, timeout=timeout)
if result.get("engine_result") != "tecEXPORT_COMMITTEE_UNAVAILABLE":
return result
tx_hash = result.get("hash") or result.get("tx_json", {}).get("hash")
if not tx_hash:
raise AssertionError(f"Committee eligibility failure missing tx hash: {result}")
validated = result
if not result.get("validated"):
validated = await wait_for_validated_transaction(
ctx, tx_hash, after_ledger=current
)
meta = validated.get("meta", {})
if meta.get("TransactionResult") != "tecEXPORT_COMMITTEE_UNAVAILABLE":
raise AssertionError(
f"Unexpected validated rebase result for {tx_hash}: {validated}"
)
log(f" Direct Export parent changed; rebasing attempt {attempt + 1}")
raise AssertionError(f"Direct Export parent changed more than {max_rebases} times")
def dst_param(address):
"""Encode an address as a HookParameter entry for the DST param."""
from xrpl.core.addresscodec import decode_classic_address
dst_hex = decode_classic_address(address).hex().upper()
return {
"HookParameter": {
"HookParameterName": "445354", # "DST"
"HookParameterValue": dst_hex,
}
}
def assert_hook_accepted(meta, log, *, expected_emits=1, expected_exports=None):
"""Assert hook executed with ACCEPT and expected emission counts.
Checks sfHookExecutions in transaction metadata.
Returns the hook execution entry for further inspection.
"""
hook_execs = meta.get("HookExecutions", [])
if not hook_execs:
raise AssertionError("No HookExecutions in metadata")
exec_entry = hook_execs[0].get("HookExecution", {})
hook_result = exec_entry.get("HookResult", -1)
emit_count = exec_entry.get("HookEmitCount", -1)
export_count = exec_entry.get("HookExportCount")
return_code = exec_entry.get("HookReturnCode", "")
log(
f" HookResult={hook_result} EmitCount={emit_count} "
f"ExportCount={export_count} ReturnCode={return_code}"
)
# HookResult 3 = ExitType::ACCEPT
if hook_result != 3:
raise AssertionError(
f"Hook did not ACCEPT: HookResult={hook_result} ReturnCode={return_code}"
)
if emit_count != expected_emits:
raise AssertionError(f"Expected {expected_emits} emits, got {emit_count}")
if expected_exports is not None and export_count != expected_exports:
raise AssertionError(f"Expected {expected_exports} exports, got {export_count}")
# ReturnCode 0 = success; non-zero = ASSERT line number in hook
if return_code and str(return_code) != "0":
raise AssertionError(
f"Hook returned error code {return_code} "
f"(likely ASSERT failure at that line)"
)
return exec_entry
def _signer_entries(witness):
entries = []
for entry in witness.get("ExportSigners", []):
signer = entry.get("ExportSigner", entry)
entries.append(signer)
return entries
def assert_export_witness(witness, origin_hash, ledger_seq, log):
"""Assert a later-ledger witness contains one ordered signature record."""
if witness.get("TransactionType") != "ExportSignatures":
raise AssertionError("Expected ExportSignatures witness")
if witness.get("TransactionHash") != origin_hash:
raise AssertionError("ExportSignatures origin binding mismatch")
if witness.get("LedgerSequence") != ledger_seq:
raise AssertionError("ExportSignatures ledger binding mismatch")
if witness.get("Signers"):
raise AssertionError("Witness must not contain ordinary Signers")
if witness.get("ExportedTxn", {}).get("Signers"):
raise AssertionError("Witness ExportedTxn must be unsigned")
contributors = witness.get("EntropyContributors")
if not contributors:
raise AssertionError("ExportSignatures missing contributor bitmap")
signers = _signer_entries(witness)
if not signers:
raise AssertionError("ExportSignatures has no ExportSigners")
if any(
not signer.get("SigningPubKey") or not signer.get("TxnSignature")
for signer in signers
):
raise AssertionError("ExportSignatures has a malformed ExportSigner")
contributor_count = sum(byte.bit_count() for byte in bytes.fromhex(contributors))
if contributor_count != len(signers):
raise AssertionError(
"ExportSignatures contributor bitmap and ordered signer count differ"
)
log(f" Witness signers: {len(signers)} validator(s)")
witness["_WitnessSigners"] = signers
return witness
def assert_export_latch(
ctx,
account_address,
log,
*,
expect_exists=True,
origin_hash=None,
expect_witness=None,
ledger_hash=None,
):
"""Assert Export latch exists (or doesn't) for the account."""
params = {"account": account_address, "ledger_index": "validated"}
if ledger_hash is not None:
del params["ledger_index"]
params["ledger_hash"] = ledger_hash
obj_result = ctx.rpc.request(0, "account_objects", params)
if not obj_result or obj_result.get("error"):
raise AssertionError(f"account_objects RPC failed: {obj_result}")
if obj_result.get("validated") is not True:
raise AssertionError(f"account_objects result is not validated: {obj_result}")
if ledger_hash is not None and obj_result.get("ledger_hash") != ledger_hash:
raise AssertionError(
"account_objects returned wrong ledger: "
f"expected {ledger_hash}, got {obj_result.get('ledger_hash')}"
)
all_objects = obj_result.get("account_objects", [])
export_latches = [
obj for obj in all_objects if obj.get("LedgerEntryType") == "ExportLatch"
]
log(f" Export latches: {len(export_latches)}")
if origin_hash is not None:
export_latches = [
latch
for latch in export_latches
if latch.get("TransactionHash") == origin_hash
]
if expect_exists and not export_latches:
raise AssertionError("Expected Export latch but none found")
if not expect_exists and export_latches:
raise AssertionError(
f"Expected no Export latches but found {len(export_latches)}"
)
for latch in export_latches:
if "Digest" not in latch:
raise AssertionError(
"ExportLatch missing signature-independent intent Digest"
)
if "TransactionHash" not in latch:
raise AssertionError("ExportLatch missing Export origin TransactionHash")
if "ExportCommitteeHash" not in latch:
raise AssertionError("ExportLatch missing ExportCommitteeHash")
if expect_witness is True and "ExportSignatureHash" not in latch:
raise AssertionError("ExportLatch missing ExportSignatureHash")
if expect_witness is False and "ExportSignatureHash" in latch:
raise AssertionError("Pending ExportLatch unexpectedly witnessed")
return export_latches

View File

@@ -1,96 +0,0 @@
""":descr: Export succeeds when quorum sidecar material exists but one active
validator withholds exportSigSetHash observation.
Node 4 has runtime_config no_export_sig_hash=true. It still attaches export
signatures, but it does not publish its exportSigSetHash in proposals. The
remaining 4/5 active validators can still align on the same export sidecar
hash, so the round must not retry/expire just because fullObservation is false.
"""
from __future__ import annotations
from export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
assert_export_latch,
require_export,
submit_direct_export,
wait_for_export_signature_witness,
)
async def scenario(ctx, log):
await require_export(ctx, log)
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
log("Node 4 withholds exportSigSetHash but still attaches export signatures")
export_start = ctx.mark("export-no-veto-submit-start")
result = await submit_direct_export(
ctx,
log,
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
final_seq = result.get("ledger_index", ctx.validated_ledger_index(0))
origin_hash = result.get("hash")
engine_result = result.get("engine_result", "")
log(f"Export completed at ledger {final_seq}, result: {engine_result}")
if engine_result != "tesSUCCESS":
raise AssertionError(f"Expected tesSUCCESS, got {engine_result}")
if not origin_hash:
raise AssertionError(f"Validated Export missing hash: {result}")
witness = await wait_for_export_signature_witness(
ctx, log, origin_hash, after_ledger=final_seq
)
signers = witness.get("_WitnessSigners", [])
if len(signers) < 4:
raise AssertionError(f"Expected at least 4 signers, got {len(signers)}")
log(f"Export signer count: {len(signers)}")
# The validated witness proves the missing observation did not veto the
# round. Pin the injected fault separately; the internal no-veto diagnostic
# may be flushed after witness observation and is not part of the contract.
withhold_logs = ctx.assert_log(
r"Export: withholding exportSigSetHash",
since=export_start,
)
log(f"Export sidecar hash withholding logs: {withhold_logs.count}")
assert_export_latch(
ctx,
alice.address,
log,
expect_exists=True,
origin_hash=origin_hash,
expect_witness=True,
)
log("PASS")

View File

@@ -1,129 +0,0 @@
""":descr: Test Export witness quorum behavior. Every valid intent is admitted;
enough selected validators produce a later witness, while a below-quorum intent
remains unwitnessed through its bounded publication window.
Parameterized via `expect_success` kwarg from suite.yml.
Flow:
1. Fund alice and bob
2. alice submits ttEXPORT
3. Verify the intent validates with tesSUCCESS
4. Verify a later witness exists only when the committee reaches quorum
5. Verify subsequent payment works regardless
"""
from __future__ import annotations
from export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
assert_export_latch,
require_export,
submit_direct_export,
wait_for_export_signature_witness,
)
async def scenario(ctx, log, expect_success=True):
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
outcome = "success" if expect_success else "failure (below quorum)"
log(f"Expecting export {outcome}")
# --- Submit ttEXPORT ---
result = await submit_direct_export(
ctx,
log,
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
final_seq = result.get("ledger_index", ctx.validated_ledger_index(0))
origin_hash = result.get("hash")
engine_result = result.get("engine_result", "")
log(f"Export at ledger {final_seq}, result: {engine_result}")
if engine_result != "tesSUCCESS":
raise AssertionError(f"Expected intent tesSUCCESS, got {engine_result}")
if not origin_hash:
raise AssertionError(f"Validated Export missing hash: {result}")
if expect_success:
await wait_for_export_signature_witness(
ctx, log, origin_hash, after_ledger=final_seq
)
assert_export_latch(
ctx,
alice.address,
log,
expect_exists=True,
origin_hash=origin_hash,
expect_witness=True,
)
log("Export succeeded as expected (active-view quorum reached)")
else:
await wait_for_export_signature_witness(
ctx,
log,
origin_hash,
after_ledger=final_seq,
expect_witness=False,
)
assert_export_latch(
ctx,
alice.address,
log,
expect_exists=True,
origin_hash=origin_hash,
expect_witness=False,
)
log("Intent remained unwitnessed as expected (below committee quorum)")
# --- Verify subsequent payment works ---
log("Submitting payment from alice to bob...")
pay_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": bob.address,
"Amount": "1000000",
"Fee": "12",
},
alice.wallet,
timeout=30,
)
pay_engine = pay_result.get("engine_result", "")
log(f"Payment result: {pay_engine}")
if pay_engine != "tesSUCCESS":
raise AssertionError(f"Payment failed: {pay_engine}")
log("Payment succeeded -- account not blocked")
log("PASS")

View File

@@ -1,226 +0,0 @@
""":descr: subscribe over a real WebSocket to post-validation Export shares
The subscriber opens before the Export is submitted. It proves the stream
publishes a quorum of independently attributable shares for the exact validated
origin and that those same signature records form the later ledger witness.
"""
from __future__ import annotations
import asyncio
import contextlib
import json
import websockets
from xahaud_scripts.testnet.config import _decode_node_public_key
from export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
assert_export_latch,
bitmap_positions,
export_authority,
require_export,
submit_direct_export,
wait_for_export_signature_witness,
)
def _witness_records(witness):
positions = sorted(bitmap_positions(witness["EntropyContributors"]))
signers = witness["_WitnessSigners"]
if len(positions) != len(signers):
raise AssertionError("Witness bitmap and signer count differ")
return {
(
position,
signer["SigningPubKey"].upper(),
signer["TxnSignature"].upper(),
)
for position, signer in zip(positions, signers, strict=True)
}
async def scenario(ctx, log):
await require_export(ctx, log)
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
alice = ctx.account("alice")
bob = ctx.account("bob")
network = json.loads((ctx.base_dir / "network.json").read_text())
node0 = next(node for node in network["nodes"] if int(node["id"]) == 0)
ws_url = f"ws://127.0.0.1:{node0['port_ws']}"
events = []
async with websockets.connect(ws_url, open_timeout=10) as websocket:
await websocket.send(
json.dumps(
{
"id": 1,
"command": "subscribe",
"streams": ["export_signatures"],
}
)
)
ack = json.loads(await asyncio.wait_for(websocket.recv(), timeout=10))
if ack.get("status") != "success":
raise AssertionError(f"export_signatures subscription failed: {ack}")
log("Subscribed to export_signatures over WebSocket")
async def receive_events():
while True:
event = json.loads(await websocket.recv())
if event.get("stream") == "export_signatures":
events.append(event)
reader = asyncio.create_task(receive_events())
try:
current = ctx.validated_ledger_index(0)
committee_size = (
len(bytes.fromhex(export_authority(ctx)["ExportCommittee"])) // 33
)
result = await submit_direct_export(
ctx,
log,
{
"TransactionType": "Export",
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current + 1,
"LastLedgerSequence": current + EXPORT_RETRY_LEDGER_WINDOW,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
)
if result.get("engine_result") != "tesSUCCESS":
raise AssertionError(f"Export failed: {result}")
origin = result.get("hash")
origin_seq = int(result.get("ledger_index"))
if not origin:
raise AssertionError(f"Validated Export missing hash: {result}")
origin_ledger = ctx.ledger(origin_seq) or {}
origin_hash = origin_ledger.get("ledger_hash") or origin_ledger.get(
"ledger", {}
).get("hash")
if not origin_hash:
raise AssertionError(
f"Validated origin ledger {origin_seq} missing hash"
)
assert_export_latch(
ctx,
alice.address,
log,
origin_hash=origin,
expect_witness=False,
)
selected = set(range(committee_size))
quorum = (4 * committee_size + 4) // 5
witness = await wait_for_export_signature_witness(
ctx, log, origin, after_ledger=origin_seq
)
expected_records = _witness_records(witness)
if len(expected_records) < quorum:
raise AssertionError(
f"Witness contains only {len(expected_records)} distinct records; "
f"need quorum {quorum}"
)
deadline = asyncio.get_running_loop().time() + 10
while asyncio.get_running_loop().time() < deadline:
matching = [
event for event in events if event.get("origin_txid") == origin
]
events_by_record = {}
for event in matching:
record = (
int(event["committee_position"]),
_decode_node_public_key(event["signing_key"]),
event["signature"].upper(),
)
events_by_record.setdefault(record, []).append(event)
if expected_records <= events_by_record.keys():
break
await asyncio.sleep(0.1)
else:
raise AssertionError(
"WebSocket stream did not publish every signature used by "
f"the witness: expected={expected_records}, "
f"observed={set(events_by_record)}"
)
unique_positions = set()
validated_hashes = {}
for event in matching:
if event.get("type") != "exportSignatureReceived":
raise AssertionError(f"Unexpected Export stream event: {event}")
if event.get("version") != 1:
raise AssertionError(f"Unexpected Export share version: {event}")
if event.get("owner") != alice.address:
raise AssertionError(f"Export stream owner mismatch: {event}")
if int(event.get("origin_ledger_seq", 0)) != origin_seq:
raise AssertionError(
f"Export stream origin sequence mismatch: {event}"
)
if event.get("origin_ledger_hash") != origin_hash:
raise AssertionError(f"Export stream origin hash mismatch: {event}")
position = int(event.get("committee_position", -1))
if position not in selected:
raise AssertionError(
f"Unselected validator streamed a share: {event}"
)
unique_positions.add(position)
witness_seq = int(witness["LedgerSequence"])
for event in matching:
ledger_index = event.get("ledger_index")
ledger_hash = event.get("ledger_hash")
if isinstance(ledger_index, bool) or not isinstance(ledger_index, int):
raise AssertionError(
f"Export stream event missing numeric ledger_index: {event}"
)
if not isinstance(ledger_hash, str) or not ledger_hash:
raise AssertionError(
f"Export stream event missing ledger_hash: {event}"
)
if ledger_index not in validated_hashes:
observed_ledger = ctx.ledger(ledger_index) or {}
validated_hashes[ledger_index] = observed_ledger.get(
"ledger_hash"
) or observed_ledger.get("ledger", {}).get("hash")
if ledger_hash != validated_hashes[ledger_index]:
raise AssertionError(
"Export stream cursor does not name the validated ledger: "
f"event={event}, expected_hash={validated_hashes[ledger_index]}"
)
for record in expected_records:
if not any(
origin_seq <= event["ledger_index"] < witness_seq
for event in events_by_record[record]
):
raise AssertionError(
"Witness signature lacked a pre-witness stream event with a "
f"validated cursor: record={record}, "
f"events={events_by_record[record]}"
)
log(
f"WebSocket exposed {len(unique_positions)} selected shares; "
f"witness used {len(expected_records)}"
)
finally:
reader.cancel()
with contextlib.suppress(asyncio.CancelledError):
await reader
log("PASS")

View File

@@ -1,97 +0,0 @@
""":descr: Export fails closed without a ledger-anchored UNLReport view.
Network-mode Export must not derive authority from a node-local trusted-config
view. An explicit parent binding still fails if that parent has no UNLReport,
and no Export latch is created.
"""
from __future__ import annotations
from export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
assert_export_latch,
export_authority,
require_export,
wait_for_validated_transaction,
)
async def scenario(ctx, log):
await require_export(ctx, log, require_unl_report=False)
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
log("UNLReport intentionally absent; export must not use local config view")
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Fee": "1000000",
**export_authority(ctx, require_unl_report=False),
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
engine_result = result.get("engine_result", "")
log(f"Export submit result: {engine_result}")
if engine_result == "tesSUCCESS":
raise AssertionError(
"Export should not succeed without a ledger-anchored UNLReport view"
)
if engine_result != "tecEXPORT_COMMITTEE_UNAVAILABLE":
raise AssertionError(
"Expected tecEXPORT_COMMITTEE_UNAVAILABLE without UNLReport view, "
f"got {engine_result}"
)
tx_hash = result.get("tx_json", {}).get("hash")
if not tx_hash:
raise AssertionError(f"Rejected Export missing tx hash: {result}")
validated = await wait_for_validated_transaction(
ctx, tx_hash, after_ledger=current_seq
)
meta = validated.get("meta", validated.get("metaData", {}))
if meta.get("TransactionResult") != "tecEXPORT_COMMITTEE_UNAVAILABLE":
raise AssertionError(f"Unexpected validated result: {validated}")
final_seq = validated.get("ledger_index")
final_ledger = ctx.ledger(final_seq) or {}
ledger_hash = final_ledger.get("ledger_hash") or final_ledger.get("ledger", {}).get(
"hash"
)
if not ledger_hash:
raise AssertionError(f"Validated failure ledger unavailable: {final_ledger}")
log(f"Export failure validated in ledger {final_seq}")
assert_export_latch(
ctx,
alice.address,
log,
expect_exists=False,
ledger_hash=ledger_hash,
)
log("PASS")

View File

@@ -1,110 +0,0 @@
""":descr: Submit ttEXPORT directly (no hook), verify the intent is admitted
and a later validated ledger records its signature witness. Then submit a
payment from the same account to verify sequence handling remains independent.
Flow:
1. Fund alice and bob
2. alice submits ttEXPORT with an explicit parent-universe declaration
3. Validation releases shares; a later ledger records ExportSignatures
4. alice submits a Payment to bob -> should succeed (sequence not blocked)
"""
from __future__ import annotations
from export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
assert_export_latch,
require_export,
submit_direct_export,
wait_for_export_signature_witness,
)
async def scenario(ctx, log):
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
# --- 1. Submit ttEXPORT ---
result = await submit_direct_export(
ctx,
log,
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
export_seq = result.get("ledger_index", ctx.validated_ledger_index(0))
origin_hash = result.get("hash")
engine_result = result.get("engine_result", "")
log(f"Export completed at ledger {export_seq}, result: {engine_result}")
if engine_result != "tesSUCCESS":
raise AssertionError(f"Expected tesSUCCESS for export, got {engine_result}")
if not origin_hash:
raise AssertionError(f"Validated Export missing hash: {result}")
await wait_for_export_signature_witness(
ctx, log, origin_hash, after_ledger=export_seq
)
assert_export_latch(
ctx,
alice.address,
log,
expect_exists=True,
origin_hash=origin_hash,
expect_witness=True,
)
# --- 2. Submit Payment from same account ---
log("Submitting payment from alice to bob...")
pay_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": bob.address,
"Amount": "1000000",
"Fee": "12",
},
alice.wallet,
timeout=30,
)
pay_engine = pay_result.get("engine_result", "")
log(f"Payment result: {pay_engine}")
if pay_engine != "tesSUCCESS":
raise AssertionError(f"Payment failed: {pay_engine}")
log(
f"Both transactions succeeded: "
f"Export at ledger {export_seq}, Payment at ledger {ctx.validated_ledger_index(0)}"
)
log("Sequence handling OK - export didn't block subsequent txns")
log("PASS")

View File

@@ -1,230 +0,0 @@
""":descr: install xport hook, trigger export, verify emitted ttEXPORT lifecycle
1. Fund alice (hook holder), bob (trigger), carol (export destination)
2. Install xport hook on alice
3. bob pays alice with DST=carol → hook calls xport() → emits ttEXPORT
4. Emitted ttEXPORT enters a validated ledger and releases signatures
5. Verify a later ledger records its ExportSignatures witness
"""
from __future__ import annotations
from export_helpers import (
require_export,
find_export_txns,
dst_param,
assert_hook_accepted,
assert_export_latch,
create_export_committee,
wait_for_export_signature_witness,
)
# C source for the xport hook — verbatim from src/test/app/Export_test_hooks.h
# On Payment to the hook account, exports a 1 XAH payment to the DST param.
XPORT_HOOK_C = r"""
#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 xport(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len, uint32_t committee_hash_ptr, uint32_t committee_hash_len, uint64_t callback_fee_drops);
extern int64_t xport_reserve(uint32_t count);
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t otxn_param(uint32_t write_ptr, uint32_t write_len, uint32_t name_ptr, uint32_t name_len);
extern int64_t otxn_type(void);
extern int64_t ledger_seq(void);
#define SBUF(x) (uint32_t)(x), sizeof(x)
#define ASSERT(x) if (!(x)) rollback((uint32_t)#x, sizeof(#x), __LINE__)
#define ttPAYMENT 0
#define tfCANONICAL 0x80000000UL
#define amAMOUNT 1
#define amFEE 8
#define atACCOUNT 1
#define atDESTINATION 3
#define ENCODE_TT(buf_out, tt) \
buf_out[0] = 0x12U; buf_out[1] = (tt >> 8) & 0xFFU; buf_out[2] = tt & 0xFFU; buf_out += 3;
#define ENCODE_FLAGS(buf_out, flags) \
buf_out[0] = 0x22U; buf_out[1] = (flags >> 24) & 0xFFU; buf_out[2] = (flags >> 16) & 0xFFU; \
buf_out[3] = (flags >> 8) & 0xFFU; buf_out[4] = flags & 0xFFU; buf_out += 5;
#define ENCODE_SEQUENCE(buf_out, seq) \
buf_out[0] = 0x24U; buf_out[1] = (seq >> 24) & 0xFFU; buf_out[2] = (seq >> 16) & 0xFFU; \
buf_out[3] = (seq >> 8) & 0xFFU; buf_out[4] = seq & 0xFFU; buf_out += 5;
#define ENCODE_FLS(buf_out, fls) \
buf_out[0] = 0x20U; buf_out[1] = 0x1AU; buf_out[2] = (fls >> 24) & 0xFFU; \
buf_out[3] = (fls >> 16) & 0xFFU; buf_out[4] = (fls >> 8) & 0xFFU; \
buf_out[5] = fls & 0xFFU; buf_out += 6;
#define ENCODE_LLS(buf_out, lls) \
buf_out[0] = 0x20U; buf_out[1] = 0x1BU; buf_out[2] = (lls >> 24) & 0xFFU; \
buf_out[3] = (lls >> 16) & 0xFFU; buf_out[4] = (lls >> 8) & 0xFFU; \
buf_out[5] = lls & 0xFFU; buf_out += 6;
#define ENCODE_DROPS(buf_out, drops, amt_type) \
buf_out[0] = 0x60U + amt_type; buf_out[1] = 0x40U + ((drops >> 56) & 0x3FU); \
buf_out[2] = (drops >> 48) & 0xFFU; buf_out[3] = (drops >> 40) & 0xFFU; \
buf_out[4] = (drops >> 32) & 0xFFU; buf_out[5] = (drops >> 24) & 0xFFU; \
buf_out[6] = (drops >> 16) & 0xFFU; buf_out[7] = (drops >> 8) & 0xFFU; \
buf_out[8] = drops & 0xFFU; buf_out += 9;
#define ENCODE_SIGNING_PUBKEY_EMPTY(buf_out) \
buf_out[0] = 0x73U; buf_out[1] = 0x00U; buf_out += 2;
#define ENCODE_ACCOUNT(buf_out, acc, acc_type) \
buf_out[0] = 0x80U + acc_type; buf_out[1] = 0x14U; \
for (int i = 0; i < 20; ++i) buf_out[2+i] = acc[i]; buf_out += 22;
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
int64_t hook(uint32_t reserved) {
_g(1, 1);
if (otxn_type() != ttPAYMENT)
return accept(0, 0, 0);
ASSERT(xport_reserve(1) == 1);
uint8_t dst[20];
int64_t dst_len = otxn_param(SBUF(dst), "DST", 3);
ASSERT(dst_len == 20);
uint8_t acc[20];
ASSERT(hook_account(SBUF(acc)) == 20);
uint32_t cls = (uint32_t)ledger_seq();
uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
uint8_t* buf = tx;
ENCODE_TT(buf, ttPAYMENT);
ENCODE_FLAGS(buf, tfCANONICAL);
ENCODE_SEQUENCE(buf, 0);
ENCODE_FLS(buf, cls + 1);
ENCODE_LLS(buf, cls + 5);
// sfTicketSequence = UINT32 field 41 = 0x20 0x29
buf[0] = 0x20U; buf[1] = 0x29U;
buf[2] = 0; buf[3] = 0; buf[4] = 0; buf[5] = 1;
buf += 6;
uint64_t drops = 1000000;
ENCODE_DROPS(buf, drops, amAMOUNT);
ENCODE_DROPS(buf, 10, amFEE);
ENCODE_SIGNING_PUBKEY_EMPTY(buf);
ENCODE_ACCOUNT(buf, acc, atACCOUNT);
ENCODE_ACCOUNT(buf, dst, atDESTINATION);
uint8_t hash[32];
static const uint8_t committee_hash[32] = { COMMITTEE_HASH_BYTES };
int64_t xport_result = xport(
SBUF(hash), (uint32_t)tx, buf - tx, SBUF(committee_hash), 0);
ASSERT(xport_result == 32);
return accept(0, 0, 0);
}
"""
async def scenario(ctx, log):
# Wait for network to start and amendments to activate
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 10000, "carol": 1000})
log("Accounts funded")
alice = ctx.account("alice")
carol = ctx.account("carol")
authority = await create_export_committee(ctx, log, alice.wallet)
committee_bytes = bytes.fromhex(authority["ExportCommitteeHash"])
committee_initializer = ", ".join(f"0x{byte:02X}" for byte in committee_bytes)
hook_source = XPORT_HOOK_C.replace("COMMITTEE_HASH_BYTES", committee_initializer)
# Compile and install xport hook on alice
wasm = ctx.compile_hook(hook_source, label="xport")
await ctx.submit_and_wait(
{
"TransactionType": "SetHook",
"Hooks": [
{
"Hook": {
"CreateCode": wasm.hex().upper(),
"HookOn": "0" * 64,
"HookNamespace": "0" * 64,
"HookApiVersion": 0,
"Flags": 1, # hsfOVERRIDE
}
}
],
"Fee": "100000000",
},
alice.wallet,
)
log(
f"Hook installed on alice ({alice.address[:12]}...) "
f"ledger {ctx.validated_ledger_index(0)}"
)
# --- Trigger ---
# bob pays alice → hook calls xport() → emits ttEXPORT
trigger_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": alice.address,
"Amount": "100000000",
"Fee": "1000000",
"HookParameters": [dst_param(carol.address)],
},
ctx.account("bob").wallet,
)
trigger_seq = ctx.validated_ledger_index(0)
log(f"Export triggered at ledger {trigger_seq}")
# xport() schedules a ttEXPORT through the emitted directory, but hook
# metadata reports it separately from ordinary HookEmissions.
trigger_meta = trigger_result.get("meta", {})
assert_hook_accepted(trigger_meta, log, expected_emits=0, expected_exports=1)
# --- Verify: check each ledger close for the Export transaction ---
max_ledgers = 10
for i in range(max_ledgers):
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
seq = ctx.validated_ledger_index(0)
exports = find_export_txns(ctx, seq)
if exports:
export_tx = exports[0]
meta = export_tx.get("meta", export_tx.get("metaData", {}))
result = meta.get("TransactionResult", "")
log(f"Ledger {seq}: Export txn found, result={result}")
if result != "tesSUCCESS":
raise AssertionError(f"Export did not succeed: {result}")
origin_hash = export_tx.get("hash")
if not origin_hash:
raise AssertionError(f"Export missing transaction hash: {export_tx}")
await wait_for_export_signature_witness(
ctx, log, origin_hash, after_ledger=seq
)
assert_export_latch(
ctx,
alice.address,
log,
expect_exists=True,
origin_hash=origin_hash,
expect_witness=True,
)
log("PASS")
return
log(f"Ledger {seq}: no Export txn yet")
raise AssertionError(
f"No Export transaction found after {max_ledgers} ledger closes"
)

View File

@@ -1,180 +0,0 @@
"""Shared helpers for ConsensusEntropy scenario tests."""
from __future__ import annotations
from xahaud_scripts.testnet.config import feature_name_to_hash
ZERO_DIGEST = "0" * 64
CONSENSUS_ENTROPY_FEATURE = feature_name_to_hash("ConsensusEntropy")
def feature_hash(name: str) -> str:
"""Return the amendment hash accepted by feature RPC."""
return feature_name_to_hash(name)
def feature_status(ctx, name: str, node_id=0):
"""Query a feature by amendment hash; feature RPC names are ambiguous."""
return ctx.feature_check(feature_hash(name), node_id=node_id)
def consensus_entropy_feature(ctx, node_id=0):
"""Query ConsensusEntropy by amendment hash."""
return feature_status(ctx, "ConsensusEntropy", node_id=node_id)
async def require_entropy(ctx, log):
"""Wait for first ledger and assert ConsensusEntropy is enabled."""
await ctx.wait_for_ledger_close(timeout=120)
feature = consensus_entropy_feature(ctx, node_id=0)
if not feature or not feature.get("enabled", False):
raise AssertionError(f"ConsensusEntropy not enabled: {feature}")
log("ConsensusEntropy enabled")
def get_entropy_tx(ctx, seq):
"""Fetch ledger and return (ce_tx, user_txns) or raise."""
result = ctx.ledger(seq, transactions=True)
if not result:
raise AssertionError(f"Ledger {seq}: fetch failed")
ledger = result.get("ledger")
if not isinstance(ledger, dict):
raise AssertionError(f"Ledger {seq}: fetch returned no ledger: {result}")
txns = ledger.get("transactions", [])
ce = [tx for tx in txns if tx.get("TransactionType") == "ConsensusEntropy"]
user = [tx for tx in txns if tx.get("TransactionType") != "ConsensusEntropy"]
if len(ce) != 1:
raise AssertionError(
f"Ledger {seq}: expected 1 ConsensusEntropy txn, got {len(ce)}"
)
return ce[0], user
def entropy_fields(ce_tx):
"""Return (digest, entropy_count, is_fallback) from a ConsensusEntropy tx.
consensus_fallback rounds carry a deterministic non-zero consensus-bound
digest with EntropyCount=0 and EntropyTier=1 (consensus_fallback).
Validator entropy has EntropyTier=3 (validator_quorum).
WARNING: is_fallback is ``tier != 3``, so it lumps participant_aligned
(Tier 2) in with fallback. It is only safe where no Tier 2 band exists
(e.g. 5-node networks, where tier2 == quorum). For band-aware scenarios use
the explicit assert_consensus_fallback / assert_participant_aligned /
assert_validator_quorum helpers, which check EntropyTier directly.
"""
digest = ce_tx.get("Digest", "")
entropy_count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier is not None:
is_fallback = tier != 3
else:
is_fallback = entropy_count == 0
return digest, entropy_count, is_fallback
def assert_participant_aligned(ce_tx, seq, expected_count=None):
"""Assert participant_aligned (Tier 2) entropy on a ConsensusEntropy tx.
Tier 2 is the sub-quorum band: the agreed reveal cohort is >= the
participant floor but < the 80% validator quorum, so it carries
EntropyTier=2 with a deterministic non-zero digest. NOTE entropy_fields()'s
is_fallback lumps tier 2 in with fallback (is_fallback = tier != 3), so the
tier must be checked EXPLICITLY here.
"""
digest = ce_tx.get("Digest", "")
count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier != 2:
raise AssertionError(
f"Ledger {seq}: expected EntropyTier==2 (participant_aligned), "
f"got {tier} (EntropyCount={count})"
)
if not digest or digest == ZERO_DIGEST:
raise AssertionError(
f"Ledger {seq}: participant_aligned digest must be non-zero, got "
f"{digest[:16]}..."
)
if expected_count is not None and count != expected_count:
raise AssertionError(
f"Ledger {seq}: participant_aligned EntropyCount must be "
f"{expected_count} (the surviving cohort), got {count}"
)
return digest, count
def assert_validator_quorum(ce_tx, seq, min_count=None):
"""Assert validator_quorum (Tier 3) entropy on a ConsensusEntropy tx:
EntropyTier=3, a deterministic non-zero digest, and (optionally)
EntropyCount >= min_count (the active quorum). The count can EXCEED the
quorum (e.g. a still-full 6/6 ledger caught at a 6->5 transition), so check
>=, not ==.
"""
digest = ce_tx.get("Digest", "")
count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier != 3:
raise AssertionError(
f"Ledger {seq}: expected EntropyTier==3 (validator_quorum), got "
f"{tier} (EntropyCount={count})"
)
if not digest or digest == ZERO_DIGEST:
raise AssertionError(
f"Ledger {seq}: validator_quorum digest must be non-zero, got "
f"{digest[:16]}..."
)
if min_count is not None and count < min_count:
raise AssertionError(
f"Ledger {seq}: validator_quorum EntropyCount={count} < quorum "
f"{min_count}"
)
return digest, count
def assert_consensus_fallback(ce_tx, seq):
"""Assert consensus_fallback (Tier 1) entropy on a ConsensusEntropy tx:
EntropyTier=1, EntropyCount=0, and a deterministic NON-zero digest.
"""
digest = ce_tx.get("Digest", "")
count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier != 1:
raise AssertionError(
f"Ledger {seq}: expected EntropyTier==1 (consensus_fallback), got "
f"{tier} (EntropyCount={count})"
)
if count != 0:
raise AssertionError(
f"Ledger {seq}: consensus_fallback EntropyCount must be 0, got "
f"{count}"
)
if not digest or digest == ZERO_DIGEST:
raise AssertionError(
f"Ledger {seq}: consensus_fallback digest must be non-zero, got "
f"{digest[:16]}..."
)
return digest, count
def assert_valid_entropy(ce_tx, seq, seen_digests=None):
"""Assert quorum-met validator entropy. Optionally check uniqueness."""
digest, entropy_count, is_fallback = entropy_fields(ce_tx)
if is_fallback or not digest or digest == ZERO_DIGEST:
raise AssertionError(f"Ledger {seq}: fallback/empty Digest")
if entropy_count < 4:
raise AssertionError(
f"Ledger {seq}: EntropyCount={entropy_count} < 4 (sub-quorum)"
)
if seen_digests is not None:
if digest in seen_digests:
raise AssertionError(f"Ledger {seq}: duplicate Digest {digest[:16]}...")
seen_digests.add(digest)
return digest, entropy_count

View File

@@ -1,90 +0,0 @@
defaults:
network:
node_count: 5
launcher: tmux
find_ports: true
slave_delay: 0.2
features:
- ConsensusEntropy
- Export
track_features:
- ConsensusEntropy
- Export
unl_report: true
log_levels:
TxQ: info
Protocol: debug
Peer: debug
LedgerConsensus: debug
ConsensusExtensions: debug
NetworkOPs: info
rc:
- rng_poll_ms=250
tests:
- name: latency_baseline_ce
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: false
- name: latency_baseline_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
- name: latency_proposal_delay_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
network:
rc:
- rng_poll_ms=250
- delay=100,jitter=25,msg=proposal
- name: latency_directed_pair_delay_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
network:
rc:
- rng_poll_ms=250
- n0->n2:delay=750,jitter=100,msg=proposal
- n2->n0:delay=750,jitter=100,msg=proposal
- name: latency_slow_minority_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
export_timeout: 120
network:
rc:
- rng_poll_ms=250
- n3->n0:delay=500,jitter=100,msg=proposal
- n3->n1:delay=500,jitter=100,msg=proposal
- n3->n2:delay=500,jitter=100,msg=proposal
- n4->n0:delay=500,jitter=100,msg=proposal
- n4->n1:delay=500,jitter=100,msg=proposal
- n4->n2:delay=500,jitter=100,msg=proposal
- n0->n3:delay=500,jitter=100,msg=proposal
- n1->n3:delay=500,jitter=100,msg=proposal
- n2->n3:delay=500,jitter=100,msg=proposal
- n0->n4:delay=500,jitter=100,msg=proposal
- n1->n4:delay=500,jitter=100,msg=proposal
- n2->n4:delay=500,jitter=100,msg=proposal
- name: latency_export_no_veto_with_delay
script: .testnet/scenarios/export/export_no_veto_missing_observation.py
network:
rc:
- rng_poll_ms=250
- delay=300,jitter=100,msg=proposal
- n4:no_export_sig_hash=true

View File

@@ -1,205 +0,0 @@
""":descr: measure CE/export behavior while RuntimeConfig injects latency/drop.
The suite supplies runtime fault injection through network.rc. This scenario
does not mutate RuntimeConfig itself; it observes what the launched network does
under that condition and logs enough counters to compare variants.
"""
from __future__ import annotations
from collections import Counter
import json
from export.export_helpers import (
EXPORT_RETRY_LEDGER_WINDOW,
require_export,
submit_direct_export,
wait_for_export_signature_witness,
)
from helpers import consensus_entropy_feature, get_entropy_tx
async def _require_runtime_config(ctx, log):
result = ctx.rpc.runtime_config(0)
if not result or result.get("error"):
raise AssertionError(
"Latency probe requires a binary built with "
"xahaud_runtime_test_config=ON; runtime_config RPC returned "
f"{result}"
)
log("RuntimeConfig RPC active")
async def _require_consensus_entropy(ctx, log):
feature = consensus_entropy_feature(ctx, node_id=0)
if not feature or not feature.get("enabled", False):
raise AssertionError(f"ConsensusEntropy not enabled: {feature}")
log("ConsensusEntropy enabled")
def _log_runtime_config(ctx, log):
for node_id in range(ctx.node_count):
cfg = ctx.rpc.runtime_config(node_id)
if cfg is None:
raise AssertionError(f"runtime_config RPC failed on node {node_id}")
log(
f"runtime_config n{node_id}: "
f"{json.dumps(cfg, sort_keys=True, separators=(',', ':'))}"
)
async def _submit_direct_export(ctx, log, *, timeout):
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
if current_seq is None:
raise AssertionError("validated ledger is not available before Export")
log(f"Submitting direct Export at validated ledger {current_seq}")
started = ctx.mark("latency-export-submit-start")
result = await submit_direct_export(
ctx,
log,
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + EXPORT_RETRY_LEDGER_WINDOW,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + 10,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=timeout,
)
engine_result = result.get("engine_result", "")
if engine_result != "tesSUCCESS":
raise AssertionError(f"Expected Export tesSUCCESS, got {engine_result}")
origin_hash = result.get("hash")
origin_seq = result.get("ledger_index", ctx.validated_ledger_index(0))
if not origin_hash:
raise AssertionError(f"Validated Export missing hash: {result}")
witness = await wait_for_export_signature_witness(
ctx, log, origin_hash, after_ledger=origin_seq
)
ended = ctx.mark("latency-export-submit-end")
elapsed = (ended.monotonic_ns - started.monotonic_ns) / 1_000_000_000
log(f"Export intent+witness result={engine_result} elapsed={elapsed:.3f}s")
signers = witness.get("_WitnessSigners", [])
log(f"Export signer count={len(signers)}")
return started, ended
def _summarize_logs(ctx, log, *, label, started, ended):
patterns = {
"rng_selected": r"RNG: entropy selected",
"rng_fallback": r"tier=1",
"rng_participant_aligned": r"tier=2",
"rng_validator_quorum": r"tier=3",
"export_quorum_timeout": r"Export: exportSigSet quorum alignment timeout",
"export_missing_observation_ignored": (
r"Export: missing exportSigSetHash observation ignored"
),
}
for name, pattern in patterns.items():
result = ctx.search_logs(pattern, since=started, until=ended, limit=500)
log(f"log_count {label}.{name}={result.count}")
async def scenario(
ctx,
log,
*,
warmup_ledgers=3,
ledgers=8,
submit_export=False,
export_timeout=90,
):
await ctx.wait_for_ledger_close(timeout=120)
await _require_runtime_config(ctx, log)
_log_runtime_config(ctx, log)
await _require_consensus_entropy(ctx, log)
if submit_export:
# require_export also asserts the UNLReport precondition for successful
# network-mode Export. Keep that explicit in perf runs so a missing
# report does not masquerade as a latency failure.
await require_export(ctx, log, require_runtime_config=False)
await ctx.wait_for_ledgers(warmup_ledgers, node_id=0, timeout=120)
warm_seq = ctx.validated_ledger_index(0)
log(f"Warmup complete at validated ledger {warm_seq}")
export_window = None
if submit_export:
export_window = await _submit_direct_export(ctx, log, timeout=export_timeout)
started = ctx.mark("latency-probe-start")
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(ledgers, node_id=0, timeout=max(120, ledgers * 30))
ended = ctx.mark("latency-probe-end")
end_seq = ctx.validated_ledger_index(0)
if start_seq is None or end_seq is None:
raise AssertionError("validated ledger index unavailable during probe")
elapsed = (ended.monotonic_ns - started.monotonic_ns) / 1_000_000_000
closed = max(0, end_seq - start_seq)
cadence = elapsed / closed if closed else 0.0
log(
f"Observed validated ledgers {start_seq + 1}..{end_seq} "
f"closed={closed} elapsed={elapsed:.3f}s cadence={cadence:.3f}s/ledger"
)
tiers: Counter[int] = Counter()
counts: Counter[int] = Counter()
missing_entropy = 0
for seq in range(start_seq + 1, end_seq + 1):
try:
ce, user_txns = get_entropy_tx(ctx, seq)
except AssertionError as exc:
missing_entropy += 1
log(f" Ledger {seq}: no ConsensusEntropy tx ({exc})")
continue
tier = ce.get("EntropyTier", -1)
count = ce.get("EntropyCount", -1)
tiers[tier] += 1
counts[count] += 1
log(
f" Ledger {seq}: tier={tier} count={count} "
f"user_txns={len(user_txns)} digest={ce.get('Digest', '')[:16]}..."
)
log(
"SUMMARY "
f"closed={closed} elapsed_s={elapsed:.3f} cadence_s={cadence:.3f} "
f"tiers={dict(sorted(tiers.items()))} "
f"counts={dict(sorted(counts.items()))} "
f"missing_entropy={missing_entropy}"
)
_summarize_logs(ctx, log, label="probe", started=started, ended=ended)
if export_window is not None:
_summarize_logs(
ctx,
log,
label="export",
started=export_window[0],
ended=export_window[1],
)
log("PASS")

View File

@@ -1,82 +0,0 @@
"""Mixed-binary rollout boundary for ConsensusEntropy.
Topology (launch with per-node binaries):
n0-n2 @export-rng feature-export-rng build, supports Export + ConsensusEntropy, UNL validators
n3-n5 @release 2026.6.21 mainnet release build, NO CE support, non-UNL trackers
What this proves:
Phase 1 CE inactive -> heterogeneous net is healthy. New validators emit
legacy 32-byte proposal positions, so old nodes parse them and track
validated ledgers.
Phase 2 CE activates -> the old @release nodes hit the upgrade boundary. Per
this branch's setAmendmentBlocked() (Change.cpp / LedgerMaster.cpp ->
NetworkOPs) they become amendment-blocked and DROP to CONNECTED, but
KEEP RUNNING (RPC stays up) -- they do NOT crash. This is the
"upgrade validators before activating" rollout invariant, observed.
Launch (fast dev check, CE enabled at genesis):
x-testnet run -n 6 \
--node-binary n0:@export-rng --node-binary n1:@export-rng --node-binary n2:@export-rng \
--node-binary n3:@release --node-binary n4:@release --node-binary n5:@release \
--feature @ConsensusEntropy \
--scenario-script .testnet/scenarios/rollout/mixed_binary_boundary.py --teardown
Launch (real transition, activates at the next flag ledger ~256; seed pre-satisfies the 1-min hold):
x-testnet run -n 6 <same --node-binary set> \
--seed-majority @ConsensusEntropy \
--scenario-script .testnet/scenarios/rollout/mixed_binary_boundary.py --teardown
"""
from helpers import CONSENSUS_ENTROPY_FEATURE
VALIDATORS = [0, 1, 2]
OLD_NODES = [3, 4, 5]
def _ce_enabled(ctx, node_id=0):
s = ctx.feature_check(CONSENSUS_ENTROPY_FEATURE, node_id=node_id)
return bool(s and s.get("enabled"))
def _blocked(ctx, nid):
info = ctx.rpc.server_info(nid) or {}
return bool(info.get("info", {}).get("amendment_blocked"))
async def scenario(ctx, log):
await ctx.wait_for_ledger_close(timeout=90)
if not _ce_enabled(ctx):
# Phase 1: with CE inactive the old @release trackers must stay in sync.
target = (await ctx.wait_for_ledgers(3, timeout=180)).result
for nid in OLD_NODES:
await ctx.wait_for_ledger(target, node_id=nid, timeout=120)
log(f"phase1 OK: old @release nodes tracked to ledger {target} (CE inactive)")
# Trigger phase 2: vote CE up on the new validators only.
ctx.feature(CONSENSUS_ENTROPY_FEATURE, vetoed=False, nodes=VALIDATORS)
log("voted ConsensusEntropy accept on n0-n2; awaiting activation...")
else:
log("CE enabled at genesis; skipping phase 1, checking the boundary directly")
await ctx.wait_for_feature(
CONSENSUS_ENTROPY_FEATURE,
check=lambda s: s.get("enabled"),
nodes=VALIDATORS,
timeout=1200,
)
log("ConsensusEntropy ENABLED on validators n0-n2")
# The upgrade boundary: old non-UNL nodes must become amendment-blocked...
await ctx.wait_for_nodes(
lambda nid: _blocked(ctx, nid), nodes=OLD_NODES, timeout=180
)
# ...report it in the log...
ctx.assert_log("server blocked", nodes=OLD_NODES)
# ...and still be alive (RPC responsive) -> blocked, not crashed.
for nid in OLD_NODES:
assert ctx.rpc.server_info(nid), f"n{nid} RPC unreachable (crashed?)"
log(
"PASS: n3-n5 (@release) amendment-blocked and STILL RUNNING -- upgrade boundary confirmed"
)

View File

@@ -1,369 +0,0 @@
"""Realistic rolling binary upgrade, then ConsensusEntropy activation.
Topology (ALL start on @release, a build with NO CE support):
n0-n4 5 UNL validators (quorum 4)
n5 tracker (non-UNL) -> WILL be upgraded; keeps working after activation
n6 tracker (non-UNL) -> NOT upgraded (the straggler); becomes
amendment-blocked, then protocol-isolated
TOPOLOGY-AWARE RESTARTS: the suite starts without fixed peers and this scenario
forms a directed ring over 127.0.0.1. A seven-node ring remains connected while
one node restarts and avoids macOS loopback-alias setup. After each restart we
restore the affected links and require every node to see both ring neighbours
before touching the next node.
Why --seed-majority is used (NOT a magic vote): the validators still VOTE the
amendment up (ctx.feature below). Seeding only pre-writes the sfMajorities record
with CloseTime=0 so the hold is already satisfied, collapsing activation to ONE
flag ledger. If the validators don't vote yes it is cleared (tfLostMajority). See
prepare_genesis_file() in testnet/config.py.
Arc: all-old healthy + mesh formed -> rolling-upgrade 5 validators + 1 tracker
(mesh re-forms between each) -> vote CE -> activation at the flag ledger ->
n5 (upgraded) keeps tracking, while every upgraded peer drops/rejects n6.
Run: x-testnet --rippled-path @release suite \\
.testnet/scenarios/rollout/rollout-suite.yml --stop-on-fail
The upgrade target is the immutable saved-binary alias `@export-rng-gate`.
"""
import asyncio
from helpers import CONSENSUS_ENTROPY_FEATURE
VALIDATORS = [0, 1, 2, 3, 4]
UPGRADED_TRACKER = 5
STRAGGLER_TRACKER = 6
ALL_NODES = VALIDATORS + [UPGRADED_TRACKER, STRAGGLER_TRACKER]
# Every node has its predecessor and successor in the ring.
MESH_MIN = 2
PROTOCOL = "XRPL/2.2"
CONSENSUS_ENTROPY_CAPABILITY = "xahau-consensus-entropy"
UPGRADE_BINARY = "@export-rng-gate"
RING_EDGES = {(nid, (nid + 1) % len(ALL_NODES)) for nid in ALL_NODES}
def _info(ctx, nid):
return (ctx.rpc.server_info(nid) or {}).get("info", {})
def _blocked(ctx, nid):
return bool(_info(ctx, nid).get("amendment_blocked"))
def _peers(ctx, nid):
return int(_info(ctx, nid).get("peers") or 0)
def _peer_public_keys(ctx, nid):
result = ctx.rpc.peers(nid) or []
return {peer.get("public_key") for peer in result if peer.get("public_key")}
def _assert_capability_matrix(ctx, log, *, upgraded, phase):
"""Assert per-edge handshake results from upgraded nodes.
Capable peers negotiate the CE token without changing the overlay protocol.
Old peers remain connected on XRPL/2.2 but cannot echo the token.
"""
upgraded = set(upgraded)
old = set(ALL_NODES) - upgraded
key_to_node = {}
for nid in ALL_NODES:
public_key = _info(ctx, nid).get("pubkey_node")
assert public_key, f"n{nid} server_info missing pubkey_node"
key_to_node[public_key] = nid
capable_edges = []
legacy_edges = []
for source in sorted(upgraded):
peers = ctx.rpc.peers(source)
assert peers is not None, f"n{source} peers RPC failed"
for peer in peers:
target = key_to_node.get(peer.get("public_key"))
if target is None:
continue
protocol = peer.get("protocol")
assert protocol == PROTOCOL, (
f"{phase}: n{source}->n{target} negotiated {protocol!r}, "
f"expected {PROTOCOL}"
)
capabilities = peer.get("capabilities") or {}
negotiated = bool(capabilities.get(CONSENSUS_ENTROPY_CAPABILITY))
expected = target in upgraded
assert negotiated == expected, (
f"{phase}: n{source}->n{target} capability "
f"{CONSENSUS_ENTROPY_CAPABILITY}={negotiated}, expected {expected}; "
f"peer={peer}"
)
(capable_edges if negotiated else legacy_edges).append(
f"n{source}->n{target}"
)
if len(upgraded) > 1:
assert capable_edges, f"{phase}: no upgraded-upgraded edge observed"
if old:
assert legacy_edges, f"{phase}: no upgraded-old edge observed"
log(
f"{phase}: {PROTOCOL} throughout; CE capability on "
f"{len(capable_edges)} upgraded-upgraded directed edges and off on "
f"{len(legacy_edges)} upgraded-old directed edges"
)
def _straggler_is_isolated(ctx):
"""The old process is alive, but no upgraded node has an active session to it."""
old_key = _info(ctx, STRAGGLER_TRACKER).get("pubkey_node")
if not old_key or _peers(ctx, STRAGGLER_TRACKER) != 0:
return False
return all(old_key not in _peer_public_keys(ctx, nid) for nid in ALL_NODES[:-1])
async def _wait_for_stable_straggler_isolation(ctx, log, *, timeout=180):
await ctx.wait_for(
lambda: _straggler_is_isolated(ctx),
timeout=timeout,
poll_interval=1,
name="legacy-straggler-isolated",
)
# Make the assertion survive PeerFinder's immediate reconnect cycle rather
# than observing only the instant between two attempts.
for _ in range(5):
await asyncio.sleep(1)
assert _straggler_is_isolated(ctx), (
f"n{STRAGGLER_TRACKER} regained an incompatible active session"
)
log(
f"n{STRAGGLER_TRACKER} remains RPC-alive but protocol-isolated: "
f"peers={_peers(ctx, STRAGGLER_TRACKER)}"
)
async def _wait_mesh(ctx, log, *, timeout=180):
"""Wait until every node sees both ring neighbours."""
await ctx.wait_for_nodes(
lambda x: _peers(ctx, x) >= MESH_MIN, nodes=ALL_NODES, timeout=timeout
)
peers = {nid: _peers(ctx, nid) for nid in ALL_NODES}
log(f"peer mesh healthy (>= {MESH_MIN} peers each): {peers}")
async def _restore_ring(ctx, log, *, timeout=90):
"""Restore missing ring links using localhost plus each node's peer port."""
await ctx.wait_for_nodes(
lambda nid: ctx.rpc.server_info(nid) is not None,
nodes=ALL_NODES,
timeout=min(timeout, 30),
)
current = ctx.topology_snapshot(nodes=ALL_NODES).outbound_edges
missing = RING_EDGES - current
for source, target in sorted(missing):
target_node = ctx._node_info(target)
result = ctx.rpc.connect(source, "127.0.0.1", target_node.port_peer)
assert result and result.get("status") == "success", (
f"failed to connect ring edge n{source}->n{target}: {result}"
)
await ctx.wait_for_topology(
RING_EDGES,
nodes=ALL_NODES,
exact=False,
timeout=timeout,
stable_for=1,
)
log(f"ring topology restored ({len(RING_EDGES)} directed edges)")
def _ledger_transactions(result):
"""Return the transactions array from a ledger RPC result."""
if not result:
return []
ledger = result.get("ledger") or {}
return ledger.get("transactions") or result.get("transactions") or []
def _tx_types(result):
"""Return TransactionType (or 'hash') for each tx in a ledger RPC result."""
types = []
for tx in _ledger_transactions(result):
if isinstance(tx, dict):
types.append(str(tx.get("TransactionType", "<unnamed>")))
else:
types.append("hash")
return types
def _rpc_summary(result):
"""Return a compact, log-safe summary of an RPC result."""
if result is None:
return "no response"
if result.get("error"):
return f"error={result.get('error')} message={result.get('error_message')}"
ledger = result.get("ledger") or {}
transactions = _ledger_transactions(result)
tx_types = _tx_types(result)
state = result.get("state") or []
entry_types = sorted(
{
str(entry.get("LedgerEntryType", "<unnamed>"))
for entry in state
if isinstance(entry, dict)
}
)
details = ["success"]
if ledger:
details.append(f"ledger={ledger.get('ledger_index')}")
details.append(f"txs={len(transactions)} types={tx_types}")
if state:
details.append(f"state={len(state)} types={entry_types}")
if result.get("account_data"):
details.append(f"account_seq={result['account_data'].get('Sequence')}")
if result.get("marker"):
details.append("marker=yes")
if not ledger and not transactions and not state and not result.get("account_data"):
details.append(f"keys={sorted(result)}")
return "; ".join(details)
async def _wait_for_entropy_ledger(ctx, log, *, node_id, timeout=180):
"""Wait until node_id's closed ledger expands with a ConsensusEntropy tx."""
deadline = asyncio.get_event_loop().time() + timeout
last = None
while asyncio.get_event_loop().time() < deadline:
info = _info(ctx, node_id)
seq = info.get("validated_ledger", {}).get("seq") or info.get("ledger_index")
if seq and seq != last:
last = seq
result = ctx.rpc.request(
node_id,
"ledger",
{
"ledger_index": seq,
"transactions": True,
"expand": True,
},
)
types = _tx_types(result)
log(
f"n{node_id} closed/validated ledger {seq} expanded: "
f"{_rpc_summary(result)}"
)
if "ConsensusEntropy" in types:
return seq, types
await asyncio.sleep(1)
raise TimeoutError(
f"n{node_id} did not close a ledger containing ConsensusEntropy "
f"within {timeout}s (last={last})"
)
async def scenario(ctx, log):
# 1. all-old net healthy AND the ring has formed before we touch it
await _restore_ring(ctx, log)
await ctx.wait_for_ledger_close(timeout=90)
base = (await ctx.wait_for_ledgers(2, timeout=120)).result
for nid in ALL_NODES:
await ctx.wait_for_ledger(base, node_id=nid, timeout=120)
await _wait_mesh(ctx, log, timeout=180)
log(f"all-old net healthy at ledger {base} (CE inactive)")
# 2. rolling-upgrade validators (then one tracker), one at a time. After each
# restart, restore and settle the ring BEFORE rolling the next, so we never
# leave a broken path in place while taking out another node.
upgraded = set()
for nid in [*VALIDATORS, UPGRADED_TRACKER]:
ref = next(v for v in VALIDATORS if v != nid)
role = "validator" if nid in VALIDATORS else "tracker"
log(f"rolling upgrade: n{nid} ({role}) -> {UPGRADE_BINARY}")
await ctx.restart_node_with_binary(nid, UPGRADE_BINARY, delay=3)
upgraded.add(nid)
await _restore_ring(ctx, log)
await _wait_mesh(ctx, log, timeout=180) # mesh re-formed before next roll
target = (await ctx.wait_for_ledgers(2, node_id=ref, timeout=180)).result
await ctx.wait_for_ledger(target, node_id=nid, timeout=180)
_assert_capability_matrix(
ctx,
log,
upgraded=upgraded,
phase=f"after upgrading n{nid}",
)
log(f"n{nid} rejoined; mesh re-formed; quorum advanced to {target}")
log(
f"validators + n{UPGRADED_TRACKER} on {UPGRADE_BINARY}; "
f"n{STRAGGLER_TRACKER} left on @release; CE still inactive"
)
# 3. vote CE up on the validators (real vote; the seed only pre-satisfied the hold)
gate_start = ctx.mark("ce-protocol-gate")
ctx.feature(CONSENSUS_ENTROPY_FEATURE, vetoed=False, nodes=VALIDATORS)
log("voted ConsensusEntropy accept on n0-n4; crossing the flag ledger...")
# 4. activation at the flag ledger
await ctx.wait_for_feature(
CONSENSUS_ENTROPY_FEATURE,
check=lambda s: s.get("enabled"),
nodes=VALIDATORS,
timeout=900,
)
log("ConsensusEntropy ENABLED on the upgraded quorum")
# 5. The amendment-blocked behavior remains visible on the old process, but
# the upgraded overlay now makes the incompatibility fail fast.
await ctx.wait_for_nodes(
lambda x: _blocked(ctx, x), nodes=[STRAGGLER_TRACKER], timeout=180
)
ctx.assert_log("server blocked", nodes=[STRAGGLER_TRACKER])
for nid in sorted(upgraded):
ctx.assert_log(
r"Peer protocol feature now required: xahau-consensus-entropy",
since=gate_start,
nodes=[nid],
)
for nid in (VALIDATORS[0], UPGRADED_TRACKER):
ctx.assert_log(
r"Missing required protocol feature xahau-consensus-entropy",
since=gate_start,
nodes=[nid],
)
await _wait_for_stable_straggler_isolation(ctx, log)
# Exercise both admission directions after the initial eviction. The RPC
# only schedules a connection attempt; the invariant is that neither
# attempt becomes an active peer session.
old_node = ctx._node_info(STRAGGLER_TRACKER)
new_node = ctx._node_info(VALIDATORS[0])
new_to_old = ctx.rpc.connect(UPGRADED_TRACKER, "127.0.0.1", old_node.port_peer)
old_to_new = ctx.rpc.connect(STRAGGLER_TRACKER, "127.0.0.1", new_node.port_peer)
log(
"forced reconnect attempts in both directions: "
f"new->old={new_to_old}; old->new={old_to_new}"
)
await _wait_for_stable_straggler_isolation(ctx, log, timeout=60)
assert not _blocked(ctx, UPGRADED_TRACKER), (
f"n{UPGRADED_TRACKER} (upgraded tracker) should NOT be amendment-blocked"
)
probe_ledger, ce_types = await _wait_for_entropy_ledger(
ctx, log, node_id=UPGRADED_TRACKER, timeout=180
)
log(
f"n{UPGRADED_TRACKER} has ConsensusEntropy at ledger {probe_ledger}: "
f"types={ce_types}"
)
target = (await ctx.wait_for_ledgers(2, node_id=VALIDATORS[0], timeout=180)).result
for nid in sorted(upgraded):
await ctx.wait_for_ledger(target, node_id=nid, timeout=180)
log(f"capable six-node component continued through ledger {target}")
snapshot = ctx._network.snapshot("ce-required-protocol-gate", keep_db=False)
log(f"captured protocol-gate and reconnect evidence at {snapshot}")
for nid in (UPGRADED_TRACKER, STRAGGLER_TRACKER):
assert ctx.rpc.server_info(nid), f"n{nid} RPC down (crashed?)"
log(
f"PASS: rolling upgrade preserved quorum 4 (mesh-gated); CE activated; "
f"n{UPGRADED_TRACKER} (upgraded tracker) still tracking; "
f"n{STRAGGLER_TRACKER} (@release straggler) amendment-blocked, still "
"running for RPC observation, and rejected from the peer protocol"
)

View File

@@ -1,43 +0,0 @@
# Rolling binary upgrade -> ConsensusEntropy activation.
#
# All nodes start on @release (set via `--rippled-path @release` on the CLI); the
# scenario rolling-restarts them to the immutable @export-rng-gate snapshot,
# then casts a REAL vote. The
# --seed-majority (majority_features) only pre-satisfies the amendment hold time
# so activation lands at ONE flag ledger instead of two -- it is NOT a vote, and
# is cleared (tfLostMajority) if the validators don't vote yes before the flag
# ledger. start_ledger gives runway for the 6 restarts + vote to finish before
# the first flag ledger (256), so the seed survives to a real vote.
#
# Run:
# x-testnet --rippled-path @release suite \
# .testnet/scenarios/rollout/rollout-suite.yml --stop-on-fail
#
# Suite runs auto-snapshot to .testnet/output/runs/ on failure (survives
# teardown), so `x-testnet logs-search --run latest/rolling_upgrade_ce ...` works.
defaults:
network:
node_count: 7 # n0-n4 validators, n5/n6 non-UNL trackers
validators: 5
quorum: 4 # 80%: a restart leaves exactly quorum on the peers up
fixed_peers: false # scenario forms a restart-safe ring on 127.0.0.1
find_ports: true
start_ledger: 210 # measured: the mesh-gated upgrade+vote is ~23 ledgers, so
# the vote lands ~233 with a ~23-ledger margin before flag
# ledger 256 (activation). Miss -> vote after 256 -> next
# flag is 512 (~13 min) or tfLostMajority clears the seed.
majority_features:
- ConsensusEntropy # pre-satisfy the hold ONLY (still needs a real vote)
track_features:
- ConsensusEntropy
log_levels:
LedgerConsensus: debug
NetworkOPs: info
# The scenario connects a seven-node ring through each node's distinct peer
# port. This remains connected while one node rolls and works on macOS without
# privileged 127.0.0.2+ aliases.
tests:
- name: rolling_upgrade_ce
script: .testnet/scenarios/rollout/rolling_upgrade.py

View File

@@ -1,63 +0,0 @@
defaults:
network:
node_count: 5
launcher: tmux
find_ports: true
slave_delay: 0.2
features:
- ConsensusEntropy
track_features:
- ConsensusEntropy
unl_report: true
log_levels:
TxQ: info
Protocol: debug
Peer: debug
LedgerConsensus: debug
ConsensusExtensions: debug
NetworkOPs: info
rc:
- rng_poll_ms=333
tests:
- name: steady_state_entropy
script: .testnet/scenarios/entropy/steady_state_entropy.py
- name: fallback_without_unl_report
script: .testnet/scenarios/entropy/fallback_without_unl_report.py
network:
unl_report: false
- name: steady_state_entropy_fast_start
script: .testnet/scenarios/entropy/steady_state_entropy.py
network:
env:
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"bootstrap_fast_start":true}}}'
- name: entropy_with_transactions
script: .testnet/scenarios/entropy/entropy_with_transactions.py
- name: hook_entropy_api
script: .testnet/scenarios/entropy/hook_entropy_api.py
- name: quorum_recovery_smoke
script: .testnet/scenarios/entropy/quorum_recovery_smoke.py
- name: quorum_degradation_smoke
script: .testnet/scenarios/entropy/quorum_degradation_smoke.py
network:
log_levels:
LedgerConsensus: trace
ConsensusExtensions: trace
# Tier 2 (participant_aligned) needs 6 nodes: n=5 has no band (tier2 ==
# quorum). At 6, the 4/6 window is the participant_aligned band.
- name: participant_aligned_smoke
script: .testnet/scenarios/entropy/participant_aligned_smoke.py
network:
node_count: 6
log_levels:
LedgerConsensus: trace
ConsensusExtensions: trace
# Export scenarios: see export-suite.yml

View File

@@ -205,6 +205,12 @@ It patches their CMake to correctly import its dependencies.
conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable
```
7. Export our [Conan recipe for Wasmtime](./external/wasmtime).
```
conan export external/wasmtime --version 44.0.1 --user xahaud --channel stable
```
Export our [Conan recipe for NuDB](./external/nudb).
It fixes some source files to add missing `#include`s.

View File

@@ -26,7 +26,7 @@ Loop: xrpld.app xrpld.nodestore
xrpld.app > xrpld.nodestore
Loop: xrpld.app xrpld.overlay
xrpld.overlay == xrpld.app
xrpld.overlay ~= xrpld.app
Loop: xrpld.app xrpld.peerfinder
xrpld.app > xrpld.peerfinder

View File

@@ -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
@@ -38,7 +36,6 @@ test.beast > xrpl.basics
test.conditions > xrpl.basics
test.conditions > xrpld.conditions
test.consensus > test.csf
test.consensus > test.jtx
test.consensus > test.toplevel
test.consensus > test.unit_test
test.consensus > xrpl.basics
@@ -46,9 +43,6 @@ test.consensus > xrpld.app
test.consensus > xrpld.consensus
test.consensus > xrpld.core
test.consensus > xrpld.ledger
test.consensus > xrpld.overlay
test.consensus > xrpld.shamap
test.consensus > xrpl.json
test.consensus > xrpl.protocol
test.core > test.jtx
test.core > test.toplevel
@@ -83,11 +77,6 @@ test.ledger > xrpld.app
test.ledger > xrpld.core
test.ledger > xrpld.ledger
test.ledger > xrpl.protocol
test.net > test.toplevel
test.net > xrpl.basics
test.net > xrpld.core
test.net > xrpld.net
test.net > xrpl.json
test.nodestore > test.jtx
test.nodestore > test.toplevel
test.nodestore > test.unit_test
@@ -211,7 +200,6 @@ xrpld.rpc > xrpld.core
xrpld.rpc > xrpld.ledger
xrpld.rpc > xrpld.nodestore
xrpld.rpc > xrpld.shamap
xrpld.rpc > xrpl.hook
xrpld.rpc > xrpl.json
xrpld.rpc > xrpl.protocol
xrpld.rpc > xrpl.resource

View File

@@ -21,11 +21,7 @@ endif()
project (xrpl)
set(Boost_NO_BOOST_CMAKE ON)
# Only sources that expose Git metadata should rebuild when HEAD changes.
set(git_info_sources
"${CMAKE_CURRENT_SOURCE_DIR}/src/xrpld/app/main/Main.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/xrpld/app/misc/NetworkOPs.cpp")
# make GIT_COMMIT_HASH define available to all sources
find_package(Git)
if(Git_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse HEAD
@@ -33,8 +29,7 @@ if(Git_FOUND)
if(gch)
set(GIT_COMMIT_HASH "${gch}")
message(STATUS gch: ${GIT_COMMIT_HASH})
set_property(SOURCE ${git_info_sources} APPEND PROPERTY
COMPILE_DEFINITIONS "GIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
endif()
execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse --abbrev-ref HEAD
@@ -42,8 +37,7 @@ if(Git_FOUND)
if(gb)
set(GIT_BRANCH "${gb}")
message(STATUS gb: ${GIT_BRANCH})
set_property(SOURCE ${git_info_sources} APPEND PROPERTY
COMPILE_DEFINITIONS "GIT_BRANCH=\"${GIT_BRANCH}\"")
add_definitions(-DGIT_BRANCH="${GIT_BRANCH}")
endif()
endif() #git
@@ -130,7 +124,8 @@ find_package(date REQUIRED)
find_package(xxHash REQUIRED)
find_package(magic_enum REQUIRED)
include(deps/WasmEdge)
include(deps/WasmEdge)
include(deps/Wasmtime)
if(TARGET nudb::core)
set(nudb nudb::core)
elseif(TARGET NuDB::nudb)

View File

@@ -2,7 +2,7 @@
**Note:** Throughout this README, references to "we" or "our" pertain to the community and contributors involved in the Xahau network. It does not imply a legal entity or a specific collection of individuals.
[Xahau](https://xahau.network/) is a decentralized cryptographic ledger that builds upon the robust foundation of the XRP Ledger. It inherits the XRP Ledger's Byzantine Fault Tolerant consensus algorithm under the normal XRPL assumptions about configured validator-list overlap, timing, and fault bounds, and enhances it with additional features and functionalities. Developers and users familiar with the XRP Ledger will find that most documentation and tutorials available on [xrpl.org](https://xrpl.org) are relevant and applicable to Xahau, including those related to running validators and managing validator keys. For Xahau specific documentation you can visit our [documentation](https://xahau.network/)
[Xahau](https://xahau.network/) is a decentralized cryptographic ledger that builds upon the robust foundation of the XRP Ledger. It inherits the XRP Ledger's Byzantine Fault Tolerant consensus algorithm and enhances it with additional features and functionalities. Developers and users familiar with the XRP Ledger will find that most documentation and tutorials available on [xrpl.org](https://xrpl.org) are relevant and applicable to Xahau, including those related to running validators and managing validator keys. For Xahau specific documentation you can visit our [documentation](https://xahau.network/)
## XAH
XAH is the public, counterparty-free asset native to Xahau and functions primarily as network gas. Transactions submitted to the Xahau network must supply an appropriate amount of XAH, to be burnt by the network as a fee, in order to be successfully included in a validated ledger. In addition, XAH also acts as a bridge currency within the Xahau DEX. XAH is traded on the open-market and is available for anyone to access. Xahau was created in 2023 with a supply of 600 million units of XAH.

View File

@@ -54,6 +54,7 @@ git checkout src/libxrpl/protocol/BuildInfo.cpp &&
sed -i s/\"0.0.0\"/\"$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)$(if [ -n "$4" ]; then echo "+$4"; fi)\"/g src/libxrpl/protocol/BuildInfo.cpp &&
conan export external/snappy --version 1.1.10 --user xahaud --channel stable &&
conan export external/soci --version 4.0.3 --user xahaud --channel stable &&
conan export external/wasmtime --version 44.0.1 --user xahaud --channel stable &&
cd release-build &&
# Install dependencies - tool_requires in conanfile.py handles glibc 2.28 compatibility
# for build tools (protoc, grpc plugins, b2) in HBB environment

View File

@@ -494,7 +494,7 @@
#
# Configure the maximum number of transactions to have in the job queue
#
# Must be a number between 100 and 1000, defaults to 1000
# Must be a number between 100 and 1000, defaults to 250
#
#
# [overlay]
@@ -593,7 +593,7 @@
# reaches or exceeds this number. After that the limit may still
# change, but will stay above the target. If consensus is not
# healthy, the limit will be clamped to this value or lower.
# Default: 1000.
# Default: 50.
#
# maximum_txn_in_ledger = <number>
#

View File

@@ -146,6 +146,8 @@ D686F2538F410C9D0D856788E98E3579595DAF7B38D38887F81ECAC934B06040 HooksUpdate1
3C43D9A973AA4443EF3FC38E42DD306160FBFFDAB901CD8BAA15D09F2597EB87 NonFungibleTokensV1
0285B7E5E08E1A8E4C15636F0591D87F73CB6A7B6452A932AD72BBC8E5D1CBE3 fixNFTokenDirV1
36799EA497B1369B170805C078AEFE6188345F9B3E324C21E9CA3FF574E3C3D6 fixNFTokenNegOffer
4C499D17719BB365B69010A436B64FD1A82AAB199FC1CEB06962EBD01059FB09 fixXahauV1
215181D23BF5C173314B5FDB9C872C92DE6CC918483727DE037C0C13E7E6EE9D fixXahauV2
0D8BF22FF7570D58598D1EF19EBB6E142AD46E59A223FD3816262FBB69345BEA Remit
7CA0426E7F411D39BB014E57CD9E08F61DE1750F0D41FCD428D9FB80BB7596B0 ZeroB2M
4B8466415FAB32FFA89D9DCBE166A42340115771DF611A7160F8D7439C87ECD8 fixNSDelete

View File

@@ -58,6 +58,7 @@ target_link_libraries(xrpl.imports.main
OpenSSL::Crypto
Ripple::boost
wasmedge::wasmedge
wasmtime::wasmtime
Ripple::opts
Ripple::syslibs
absl::random_random
@@ -165,9 +166,6 @@ if(xrpld)
if(tests)
target_compile_definitions(rippled PUBLIC ENABLE_TESTS)
endif()
if(xahaud_runtime_test_config)
target_compile_definitions(rippled PUBLIC XAHAUD_ENABLE_RUNTIME_TEST_CONFIG=1)
endif()
target_include_directories(rippled
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
@@ -183,21 +181,6 @@ if(xrpld)
"${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp"
)
target_sources(rippled PRIVATE ${sources})
set(HOOKS_TEST_DIR "" CACHE PATH "External hook Env-test directory")
if(NOT HOOKS_TEST_DIR AND DEFINED ENV{HOOKS_TEST_DIR})
set(HOOKS_TEST_DIR "$ENV{HOOKS_TEST_DIR}")
endif()
if(HOOKS_TEST_DIR)
file(GLOB_RECURSE hook_test_sources CONFIGURE_DEPENDS
"${HOOKS_TEST_DIR}/*_test.cpp"
)
if(hook_test_sources)
message(STATUS "Including external hook Env tests from ${HOOKS_TEST_DIR}")
target_sources(rippled PRIVATE ${hook_test_sources})
target_include_directories(rippled PRIVATE "${HOOKS_TEST_DIR}")
endif()
endif()
endif()
target_link_libraries(rippled

View File

@@ -22,9 +22,6 @@ target_compile_definitions (opts
$<$<BOOL:${beast_no_unit_test_inline}>:BEAST_NO_UNIT_TEST_INLINE=1>
$<$<BOOL:${beast_disable_autolink}>:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1>
$<$<BOOL:${single_io_service_thread}>:RIPPLE_SINGLE_IO_SERVICE_THREAD=1>
# Enhanced logging is enabled for Debug builds, or explicitly via
# -DBEAST_ENHANCED_LOGGING=ON for other build types.
$<$<OR:$<CONFIG:Debug>,$<BOOL:${BEAST_ENHANCED_LOGGING}>>:BEAST_ENHANCED_LOGGING=1>
$<$<BOOL:${voidstar}>:ENABLE_VOIDSTAR>)
target_compile_options (opts
INTERFACE

View File

@@ -12,13 +12,6 @@ option(xrpld "Build xrpld" ON)
option(tests "Build tests" ON)
option(xahaud_runtime_test_config
"Enable XAHAUD_RUNTIME_TEST_CONFIG env and runtime_config RPC fault-injection controls"
OFF)
# Conan 2 local opt-in:
# [conf]
# tools.cmake.cmaketoolchain:extra_variables={"xahaud_runtime_test_config":"ON"}
option(unity "Creates a build using UNITY support in cmake. This is the default" ON)
if(unity)
if(NOT is_ci)

View File

@@ -0,0 +1 @@
find_package(wasmtime REQUIRED)

View File

@@ -22,6 +22,7 @@ class Xrpl(ConanFile):
'unity': [True, False],
'xrpld': [True, False],
'with_wasmedge': [True, False],
'with_wasmtime': [True, False],
'tool_requires_b2': [True, False],
}
@@ -53,6 +54,7 @@ class Xrpl(ConanFile):
'unity': False,
'xrpld': False,
'with_wasmedge': True,
'with_wasmtime': True,
'tool_requires_b2': False,
'date/*:header_only': False,
@@ -121,6 +123,8 @@ class Xrpl(ConanFile):
if self.options.with_wasmedge:
self.requires('wasmedge/0.11.2@xahaud/stable')
if self.options.with_wasmtime:
self.requires('wasmtime/44.0.1@xahaud/stable')
if self.options.jemalloc:
self.requires('jemalloc/5.3.0')
if self.options.rocksdb:

25
external/wasmtime/conandata.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
sources:
"44.0.1":
Macos:
"armv8":
"gcc":
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-aarch64-macos-c-api.tar.xz"
sha256: "5da6d09fa6340db6d7afc8b73d189eff4973825c9a6fa5e814d2f9b1ddfd8998"
"x86_64":
"gcc":
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-macos-c-api.tar.xz"
sha256: ""
Linux:
"x86_64":
"gcc":
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-linux-c-api.tar.xz"
sha256: "fe9b4bfa87724054cebcc09b83a17d5ded66a465054b77c8de0ebc166ad78d7a"
"armv8":
"gcc":
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-aarch64-linux-c-api.tar.xz"
sha256: ""
Windows:
"x86_64":
"Visual Studio":
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-windows-c-api.zip"
sha256: ""

83
external/wasmtime/conanfile.py vendored Normal file
View File

@@ -0,0 +1,83 @@
from conan import ConanFile
from conan.tools.files import get, copy
from conan.errors import ConanInvalidConfiguration
import os
required_conan_version = ">=1.53.0"
class WasmtimeConan(ConanFile):
name = "wasmtime"
description = ("Wasmtime is a fast and secure runtime for WebAssembly. "
"It is a standalone wasm-only optimizing runtime for WebAssembly and WASI.")
license = "Apache-2.0"
url = "https://github.com/bytecodealliance/wasmtime"
homepage = "https://github.com/bytecodealliance/wasmtime"
topics = ("webassembly", "wasm", "wasi")
package_type = "static-library"
settings = "os", "arch", "compiler", "build_type"
@property
def _compiler_alias(self):
return {
"Visual Studio": "Visual Studio",
"msvc": "Visual Studio",
}.get(str(self.info.settings.compiler), "gcc")
def configure(self):
self.settings.compiler.rm_safe("libcxx")
self.settings.compiler.rm_safe("cppstd")
def validate(self):
try:
self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias]
except KeyError:
raise ConanInvalidConfiguration(
f"Binaries for this combination of version/os/arch/compiler are not available "
f"({self.version}/{self.settings.os}/{self.settings.arch}/{self._compiler_alias})"
)
entry = self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias]
if not entry.get("sha256"):
raise ConanInvalidConfiguration(
f"SHA256 not configured for {self.version}/{self.settings.os}/{self.settings.arch}/{self._compiler_alias}"
)
def package_id(self):
# Make binary compatible across compiler versions (since we're downloading prebuilt)
self.info.settings.rm_safe("compiler.version")
# Group compilers by their binary compatibility
compiler_name = str(self.info.settings.compiler)
if compiler_name in ["Visual Studio", "msvc"]:
self.info.settings.compiler = "Visual Studio"
else:
self.info.settings.compiler = "gcc"
def build(self):
# Download the prebuilt tarball
entry = self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias]
get(self, url=entry["url"], sha256=entry["sha256"],
destination=self.source_folder, strip_root=True)
def package(self):
copy(self, pattern="*.h", dst=os.path.join(self.package_folder, "include"),
src=os.path.join(self.source_folder, "include"), keep_path=True)
srclibdir = os.path.join(self.source_folder, "lib")
dstlibdir = os.path.join(self.package_folder, "lib")
if self.settings.os == "Windows":
copy(self, pattern="wasmtime.lib", src=srclibdir, dst=dstlibdir, keep_path=False)
else:
copy(self, pattern="libwasmtime.a", src=srclibdir, dst=dstlibdir, keep_path=False)
copy(self, pattern="LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"), keep_path=False)
def package_info(self):
self.cpp_info.libs = ["wasmtime"]
if self.settings.os == "Windows":
self.cpp_info.defines += ["WASM_API_EXTERN=", "WASI_API_EXTERN="]
self.cpp_info.system_libs += ["ws2_32", "bcrypt", "advapi32", "userenv", "ntdll", "shell32", "ole32"]
if self.settings.os in ["Linux", "FreeBSD"]:
self.cpp_info.system_libs += ["pthread", "dl", "m"]

View File

@@ -47,8 +47,5 @@
#define MEM_OVERLAP -43
#define TOO_MANY_STATE_MODIFICATIONS -44
#define TOO_MANY_NAMESPACES -45
#define EXPORT_FAILURE -46
#define TOO_MANY_EXPORTED_TXN -47
#define TOO_LITTLE_ENTROPY -48
#define HOOK_ERROR_CODES
#endif //HOOK_ERROR_CODES

View File

@@ -339,53 +339,6 @@ prepare(
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
xport_reserve(uint32_t count);
// callback_fee_drops: 0 omits third-party delivery permission; otherwise the
// exact Import fee authorized by the emitted Export intent, in native drops.
extern int64_t
xport(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len,
uint32_t committee_hash_ptr,
uint32_t committee_hash_len,
uint64_t callback_fee_drops);
extern int64_t
xport_cancel(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
/*
Consensus entropy APIs.
min_tier is a required fail-closed floor:
1 = consensus_fallback, 2 = participant_aligned,
3 = validator_quorum, 4 = validator_full.
entropy_cr_status returns a packed non-negative value:
bits 32..39 tier, 16..31 count, 0..15 denominator.
Check for a negative error before using the ENTROPY_* macros.
Classify tier before count/denominator arithmetic: fallback is tier 1
with count=denominator=0. Common policies are denominator-count <= 1,
5*count >= 4*denominator (use widened arithmetic), or count >= floor.
entropy_cr_dice/entropy_cr_random return TOO_LITTLE_ENTROPY if fresh
visible entropy is below
min_tier. Open-ledger and simulate execution are provisional previews;
final ordered execution may see a different entropy object.
*/
extern int64_t
entropy_cr_dice(uint32_t sides, uint32_t min_tier);
extern int64_t
entropy_cr_random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
extern int64_t
entropy_cr_status(void);
#ifdef __cplusplus
}
#endif

View File

@@ -41,34 +41,6 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
# Insert __attribute__((noduplicate)) before _g
sub(/[[:space:]]+_g/, " __attribute__((noduplicate)) _g", line);
}
if (line ~ /[[:space:]]+xport[[:space:]]*\(/) {
print "// callback_fee_drops: 0 omits third-party delivery permission; otherwise the";
print "// exact Import fee authorized by the emitted Export intent, in native drops.";
}
if (line ~ /[[:space:]]+entropy_cr_dice[[:space:]]*\(/) {
print "/*";
print " Consensus entropy APIs.";
print "";
print " min_tier is a required fail-closed floor:";
print " 1 = consensus_fallback, 2 = participant_aligned,";
print " 3 = validator_quorum, 4 = validator_full.";
print "";
print " entropy_cr_status returns a packed non-negative value:";
print " bits 32..39 tier, 16..31 count, 0..15 denominator.";
print " Check for a negative error before using the ENTROPY_* macros.";
print "";
print " Classify tier before count/denominator arithmetic: fallback is tier 1";
print " with count=denominator=0. Common policies are denominator-count <= 1,";
print " 5*count >= 4*denominator (use widened arithmetic), or count >= floor.";
print "";
print " entropy_cr_dice/entropy_cr_random return TOO_LITTLE_ENTROPY if fresh";
print " visible entropy is below";
print " min_tier. Open-ledger and simulate execution are provisional previews;";
print " final ordered execution may see a different entropy object.";
print "*/";
}
# printf("\n");

View File

@@ -348,24 +348,5 @@ util_verify(
uint32_t kread_len);
extern int64_t xpop_slot(uint32_t, uint32_t);
extern int64_t
xport_reserve(uint32_t count);
// callback_fee_drops: 0 omits third-party delivery permission; otherwise the
// exact Import fee authorized by the emitted Export intent, in native drops.
extern int64_t
xport(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len,
uint32_t committee_hash_ptr,
uint32_t committee_hash_len,
uint64_t callback_fee_drops);
extern int64_t
xport_cancel(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
#define HOOK_EXTERN
#endif // HOOK_EXTERN
#endif // HOOK_EXTERN

View File

@@ -47,4 +47,4 @@
#include "macro.h"
#include "types.h"
#endif
#endif

View File

@@ -607,37 +607,31 @@ int out_len = 0;\
#define PREPARE_PAYMENT_SIMPLE_SIZE 248U
#endif
#define PREPARE_PAYMENT_SIMPLE( \
buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw) \
{ \
uint8_t* buf_out = buf_out_master; \
uint8_t acc[20]; \
uint64_t drops_amount = (drops_amount_raw); \
uint32_t dest_tag = (dest_tag_raw); \
uint32_t src_tag = (src_tag_raw); \
uint32_t cls = (uint32_t)ledger_seq(); \
hook_account(SBUF(acc)); \
_01_02_ENCODE_TT(buf_out, ttPAYMENT); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS(buf_out, tfCANONICAL); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC(buf_out, src_tag); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE(buf_out, 0); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST(buf_out, dest_tag); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS(buf_out, cls + 1); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS(buf_out, cls + 5); /* uint32 | size 6 */ \
_06_01_ENCODE_DROPS_AMOUNT( \
buf_out, drops_amount); /* amount | size 9 */ \
uint8_t* fee_ptr = buf_out; \
_06_08_ENCODE_DROPS_FEE(buf_out, 0); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC(buf_out, acc); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST( \
buf_out, to_address); /* account | size 22 */ \
int64_t edlen = etxn_details( \
(uint32_t)buf_out, \
PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = \
etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
_06_08_ENCODE_DROPS_FEE(fee_ptr, fee); \
#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\
{\
uint8_t* buf_out = buf_out_master;\
uint8_t acc[20];\
uint64_t drops_amount = (drops_amount_raw);\
uint32_t dest_tag = (dest_tag_raw);\
uint32_t src_tag = (src_tag_raw);\
uint32_t cls = (uint32_t)ledger_seq();\
hook_account(SBUF(acc));\
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \
_06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \
uint8_t* fee_ptr = buf_out;\
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
}
#ifdef HAS_CALLBACK
@@ -645,35 +639,33 @@ int out_len = 0;\
#else
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 287
#endif
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE( \
buf_out_master, tlamt, to_address, dest_tag_raw, src_tag_raw) \
{ \
uint8_t* buf_out = buf_out_master; \
uint8_t acc[20]; \
uint32_t dest_tag = (dest_tag_raw); \
uint32_t src_tag = (src_tag_raw); \
uint32_t cls = (uint32_t)ledger_seq(); \
hook_account(SBUF(acc)); \
_01_02_ENCODE_TT(buf_out, ttPAYMENT); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS(buf_out, tfCANONICAL); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC(buf_out, src_tag); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE(buf_out, 0); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST(buf_out, dest_tag); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS(buf_out, cls + 1); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS(buf_out, cls + 5); /* uint32 | size 6 */ \
_06_01_ENCODE_TL_AMOUNT(buf_out, tlamt); /* amount | size 48 */ \
uint8_t* fee_ptr = buf_out; \
_06_08_ENCODE_DROPS_FEE(buf_out, 0); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC(buf_out, acc); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST( \
buf_out, to_address); /* account | size 22 */ \
etxn_details( \
(uint32_t)buf_out, \
PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = etxn_fee_base( \
buf_out_master, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); \
_06_08_ENCODE_DROPS_FEE(fee_ptr, fee); \
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE(buf_out_master, tlamt, to_address, dest_tag_raw, src_tag_raw)\
{\
uint8_t* buf_out = buf_out_master;\
uint8_t acc[20];\
uint32_t dest_tag = (dest_tag_raw);\
uint32_t src_tag = (src_tag_raw);\
uint32_t cls = (uint32_t)ledger_seq();\
hook_account(SBUF(acc));\
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \
_06_01_ENCODE_TL_AMOUNT (buf_out, tlamt ); /* amount | size 48 */ \
uint8_t* fee_ptr = buf_out;\
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); \
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
}
#endif

View File

@@ -3,31 +3,18 @@
#define sfCloseResolution ((16U << 16U) + 1U)
#define sfMethod ((16U << 16U) + 2U)
#define sfTransactionResult ((16U << 16U) + 3U)
#define sfScale ((16U << 16U) + 4U)
#define sfAssetScale ((16U << 16U) + 5U)
#define sfTickSize ((16U << 16U) + 16U)
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
#define sfHookResult ((16U << 16U) + 18U)
#define sfWasLockingChainSend ((16U << 16U) + 19U)
#define sfSidecarType ((16U << 16U) + 20U)
#define sfEntropyTier ((16U << 16U) + 21U)
#define sfLedgerEntryType ((1U << 16U) + 1U)
#define sfTransactionType ((1U << 16U) + 2U)
#define sfSignerWeight ((1U << 16U) + 3U)
#define sfTransferFee ((1U << 16U) + 4U)
#define sfTradingFee ((1U << 16U) + 5U)
#define sfDiscountedFee ((1U << 16U) + 6U)
#define sfVersion ((1U << 16U) + 16U)
#define sfHookStateChangeCount ((1U << 16U) + 17U)
#define sfHookEmitCount ((1U << 16U) + 18U)
#define sfHookExecutionIndex ((1U << 16U) + 19U)
#define sfHookApiVersion ((1U << 16U) + 20U)
#define sfHookStateScale ((1U << 16U) + 21U)
#define sfLedgerFixType ((1U << 16U) + 22U)
#define sfHookExportCount ((1U << 16U) + 98U)
#define sfEntropyCount ((1U << 16U) + 99U)
#define sfEntropyDenominator ((1U << 16U) + 100U)
#define sfExportCount ((1U << 16U) + 101U)
#define sfNetworkID ((2U << 16U) + 1U)
#define sfFlags ((2U << 16U) + 2U)
#define sfSourceTag ((2U << 16U) + 3U)
@@ -42,7 +29,6 @@
#define sfWalletSize ((2U << 16U) + 12U)
#define sfOwnerCount ((2U << 16U) + 13U)
#define sfDestinationTag ((2U << 16U) + 14U)
#define sfLastUpdateTime ((2U << 16U) + 15U)
#define sfHighQualityIn ((2U << 16U) + 16U)
#define sfHighQualityOut ((2U << 16U) + 17U)
#define sfLowQualityIn ((2U << 16U) + 18U)
@@ -74,15 +60,7 @@
#define sfBurnedNFTokens ((2U << 16U) + 44U)
#define sfHookStateCount ((2U << 16U) + 45U)
#define sfEmitGeneration ((2U << 16U) + 46U)
#define sfVoteWeight ((2U << 16U) + 48U)
#define sfLockCount ((2U << 16U) + 49U)
#define sfFirstNFTokenSequence ((2U << 16U) + 50U)
#define sfOracleDocumentID ((2U << 16U) + 51U)
#define sfStartTime ((2U << 16U) + 93U)
#define sfRepeatCount ((2U << 16U) + 94U)
#define sfDelaySeconds ((2U << 16U) + 95U)
#define sfXahauActivationLgrSeq ((2U << 16U) + 96U)
#define sfImportSequence ((2U << 16U) + 97U)
#define sfLockCount ((2U << 16U) + 47U)
#define sfRewardTime ((2U << 16U) + 98U)
#define sfRewardLgrFirst ((2U << 16U) + 99U)
#define sfRewardLgrLast ((2U << 16U) + 100U)
@@ -102,26 +80,12 @@
#define sfHookInstructionCount ((3U << 16U) + 17U)
#define sfHookReturnCode ((3U << 16U) + 18U)
#define sfReferenceCount ((3U << 16U) + 19U)
#define sfXChainClaimID ((3U << 16U) + 20U)
#define sfXChainAccountCreateCount ((3U << 16U) + 21U)
#define sfXChainAccountClaimCount ((3U << 16U) + 22U)
#define sfAssetPrice ((3U << 16U) + 23U)
#define sfMaximumAmount ((3U << 16U) + 24U)
#define sfOutstandingAmount ((3U << 16U) + 25U)
#define sfMPTAmount ((3U << 16U) + 26U)
#define sfIssuerNode ((3U << 16U) + 27U)
#define sfSubjectNode ((3U << 16U) + 28U)
#define sfExportNode ((3U << 16U) + 29U)
#define sfTouchCount ((3U << 16U) + 97U)
#define sfAccountIndex ((3U << 16U) + 98U)
#define sfAccountCount ((3U << 16U) + 99U)
#define sfRewardAccumulator ((3U << 16U) + 100U)
#define sfEmailHash ((4U << 16U) + 1U)
#define sfTakerPaysCurrency ((17U << 16U) + 1U)
#define sfTakerPaysIssuer ((17U << 16U) + 2U)
#define sfTakerGetsCurrency ((17U << 16U) + 3U)
#define sfTakerGetsIssuer ((17U << 16U) + 4U)
#define sfMPTokenIssuanceID ((21U << 16U) + 1U)
#define sfTakerPaysCurrency ((10U << 16U) + 1U)
#define sfTakerPaysIssuer ((10U << 16U) + 2U)
#define sfTakerGetsCurrency ((10U << 16U) + 3U)
#define sfTakerGetsIssuer ((10U << 16U) + 4U)
#define sfLedgerHash ((5U << 16U) + 1U)
#define sfParentHash ((5U << 16U) + 2U)
#define sfTransactionHash ((5U << 16U) + 3U)
@@ -135,8 +99,6 @@
#define sfEmitParentTxnID ((5U << 16U) + 11U)
#define sfEmitNonce ((5U << 16U) + 12U)
#define sfEmitHookHash ((5U << 16U) + 13U)
#define sfObjectID ((5U << 16U) + 14U)
#define sfAMMID ((5U << 16U) + 15U)
#define sfBookDirectory ((5U << 16U) + 16U)
#define sfInvoiceID ((5U << 16U) + 17U)
#define sfNickname ((5U << 16U) + 18U)
@@ -158,18 +120,6 @@
#define sfOfferID ((5U << 16U) + 34U)
#define sfEscrowID ((5U << 16U) + 35U)
#define sfURITokenID ((5U << 16U) + 36U)
#define sfDomainID ((5U << 16U) + 37U)
#define sfExportSignatureHash ((5U << 16U) + 38U)
#define sfExportCommitteeHash ((5U << 16U) + 39U)
#define sfManifestID ((5U << 16U) + 91U)
#define sfHookOnOutgoing ((5U << 16U) + 93U)
#define sfHookOnIncoming ((5U << 16U) + 94U)
#define sfCron ((5U << 16U) + 95U)
#define sfHookCanEmit ((5U << 16U) + 96U)
#define sfEmittedTxnID ((5U << 16U) + 97U)
#define sfGovernanceMarks ((5U << 16U) + 98U)
#define sfGovernanceFlags ((5U << 16U) + 99U)
#define sfNumber ((9U << 16U) + 1U)
#define sfAmount ((6U << 16U) + 1U)
#define sfBalance ((6U << 16U) + 2U)
#define sfLimitAmount ((6U << 16U) + 3U)
@@ -180,27 +130,12 @@
#define sfFee ((6U << 16U) + 8U)
#define sfSendMax ((6U << 16U) + 9U)
#define sfDeliverMin ((6U << 16U) + 10U)
#define sfAmount2 ((6U << 16U) + 11U)
#define sfBidMin ((6U << 16U) + 12U)
#define sfBidMax ((6U << 16U) + 13U)
#define sfMinimumOffer ((6U << 16U) + 16U)
#define sfRippleEscrow ((6U << 16U) + 17U)
#define sfDeliveredAmount ((6U << 16U) + 18U)
#define sfNFTokenBrokerFee ((6U << 16U) + 19U)
#define sfHookCallbackFee ((6U << 16U) + 20U)
#define sfLockedBalance ((6U << 16U) + 21U)
#define sfBaseFeeDrops ((6U << 16U) + 22U)
#define sfReserveBaseDrops ((6U << 16U) + 23U)
#define sfReserveIncrementDrops ((6U << 16U) + 24U)
#define sfLPTokenOut ((6U << 16U) + 25U)
#define sfLPTokenIn ((6U << 16U) + 26U)
#define sfEPrice ((6U << 16U) + 27U)
#define sfPrice ((6U << 16U) + 28U)
#define sfSignatureReward ((6U << 16U) + 29U)
#define sfMinAccountCreateAmount ((6U << 16U) + 30U)
#define sfLPTokenBalance ((6U << 16U) + 31U)
#define sfExportCallbackFee ((6U << 16U) + 32U)
#define sfTrustLineRewardAccumulator ((6U << 16U) + 99U)
#define sfPublicKey ((7U << 16U) + 1U)
#define sfMessageKey ((7U << 16U) + 2U)
#define sfSigningPubKey ((7U << 16U) + 3U)
@@ -226,18 +161,6 @@
#define sfHookParameterName ((7U << 16U) + 24U)
#define sfHookParameterValue ((7U << 16U) + 25U)
#define sfBlob ((7U << 16U) + 26U)
#define sfDIDDocument ((7U << 16U) + 27U)
#define sfData ((7U << 16U) + 28U)
#define sfAssetClass ((7U << 16U) + 29U)
#define sfProvider ((7U << 16U) + 30U)
#define sfMPTokenMetadata ((7U << 16U) + 31U)
#define sfCredentialType ((7U << 16U) + 32U)
#define sfEntropyContributors ((7U << 16U) + 33U)
#define sfExportCommittee ((7U << 16U) + 34U)
#define sfExportContributors ((7U << 16U) + 35U)
#define sfHookName ((7U << 16U) + 97U)
#define sfRemarkValue ((7U << 16U) + 98U)
#define sfRemarkName ((7U << 16U) + 99U)
#define sfAccount ((8U << 16U) + 1U)
#define sfOwner ((8U << 16U) + 2U)
#define sfDestination ((8U << 16U) + 3U)
@@ -247,32 +170,13 @@
#define sfRegularKey ((8U << 16U) + 8U)
#define sfNFTokenMinter ((8U << 16U) + 9U)
#define sfEmitCallback ((8U << 16U) + 10U)
#define sfHolder ((8U << 16U) + 11U)
#define sfHookAccount ((8U << 16U) + 16U)
#define sfOtherChainSource ((8U << 16U) + 18U)
#define sfOtherChainDestination ((8U << 16U) + 19U)
#define sfAttestationSignerAccount ((8U << 16U) + 20U)
#define sfAttestationRewardAccount ((8U << 16U) + 21U)
#define sfLockingChainDoor ((8U << 16U) + 22U)
#define sfIssuingChainDoor ((8U << 16U) + 23U)
#define sfSubject ((8U << 16U) + 24U)
#define sfInform ((8U << 16U) + 99U)
#define sfIndexes ((19U << 16U) + 1U)
#define sfHashes ((19U << 16U) + 2U)
#define sfAmendments ((19U << 16U) + 3U)
#define sfNFTokenOffers ((19U << 16U) + 4U)
#define sfHookNamespaces ((19U << 16U) + 5U)
#define sfCredentialIDs ((19U << 16U) + 6U)
#define sfURITokenIDs ((19U << 16U) + 99U)
#define sfPaths ((18U << 16U) + 1U)
#define sfBaseAsset ((26U << 16U) + 1U)
#define sfQuoteAsset ((26U << 16U) + 2U)
#define sfLockingChainIssue ((24U << 16U) + 1U)
#define sfIssuingChainIssue ((24U << 16U) + 2U)
#define sfAsset ((24U << 16U) + 3U)
#define sfAsset2 ((24U << 16U) + 4U)
#define sfClaimCurrency ((24U << 16U) + 5U)
#define sfXChainBridge ((25U << 16U) + 1U)
#define sfTransactionMetaData ((14U << 16U) + 2U)
#define sfCreatedNode ((14U << 16U) + 3U)
#define sfDeletedNode ((14U << 16U) + 4U)
@@ -287,33 +191,13 @@
#define sfEmitDetails ((14U << 16U) + 13U)
#define sfHook ((14U << 16U) + 14U)
#define sfSigner ((14U << 16U) + 16U)
#define sfExportSigner ((14U << 16U) + 17U)
#define sfMajority ((14U << 16U) + 18U)
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
#define sfHookExecution ((14U << 16U) + 21U)
#define sfHookDefinition ((14U << 16U) + 22U)
#define sfHookParameter ((14U << 16U) + 23U)
#define sfHookGrant ((14U << 16U) + 24U)
#define sfVoteEntry ((14U << 16U) + 25U)
#define sfAuctionSlot ((14U << 16U) + 26U)
#define sfAuthAccount ((14U << 16U) + 27U)
#define sfXChainClaimProofSig ((14U << 16U) + 28U)
#define sfXChainCreateAccountProofSig ((14U << 16U) + 29U)
#define sfXChainClaimAttestationCollectionElement ((14U << 16U) + 30U)
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
#define sfPriceData ((14U << 16U) + 32U)
#define sfCredential ((14U << 16U) + 33U)
#define sfExportedTxn ((14U << 16U) + 89U)
#define sfManifest ((14U << 16U) + 90U)
#define sfAmountEntry ((14U << 16U) + 91U)
#define sfMintURIToken ((14U << 16U) + 92U)
#define sfHookEmission ((14U << 16U) + 93U)
#define sfImportVLKey ((14U << 16U) + 94U)
#define sfActiveValidator ((14U << 16U) + 95U)
#define sfGenesisMint ((14U << 16U) + 96U)
#define sfRemark ((14U << 16U) + 97U)
#define sfHighReward ((14U << 16U) + 98U)
#define sfLowReward ((14U << 16U) + 99U)
#define sfSigners ((15U << 16U) + 3U)
#define sfSignerEntries ((15U << 16U) + 4U)
#define sfTemplate ((15U << 16U) + 5U)
@@ -323,23 +207,9 @@
#define sfMemos ((15U << 16U) + 9U)
#define sfNFTokens ((15U << 16U) + 10U)
#define sfHooks ((15U << 16U) + 11U)
#define sfVoteSlots ((15U << 16U) + 12U)
#define sfMajorities ((15U << 16U) + 16U)
#define sfDisabledValidators ((15U << 16U) + 17U)
#define sfHookExecutions ((15U << 16U) + 18U)
#define sfHookParameters ((15U << 16U) + 19U)
#define sfHookGrants ((15U << 16U) + 20U)
#define sfXChainClaimAttestations ((15U << 16U) + 21U)
#define sfXChainCreateAccountAttestations ((15U << 16U) + 22U)
#define sfExportSigners ((15U << 16U) + 23U)
#define sfPriceDataSeries ((15U << 16U) + 24U)
#define sfAuthAccounts ((15U << 16U) + 25U)
#define sfAuthorizeCredentials ((15U << 16U) + 26U)
#define sfUnauthorizeCredentials ((15U << 16U) + 27U)
#define sfAcceptedCredentials ((15U << 16U) + 28U)
#define sfAmounts ((15U << 16U) + 92U)
#define sfHookEmissions ((15U << 16U) + 93U)
#define sfImportVLKeys ((15U << 16U) + 94U)
#define sfActiveValidators ((15U << 16U) + 95U)
#define sfGenesisMints ((15U << 16U) + 96U)
#define sfRemarks ((15U << 16U) + 97U)
#define sfActiveValidators ((15U << 16U) + 95U)

View File

@@ -56,10 +56,6 @@ enum ltURI_TOKEN {
enum remarks {
lsfImmutable = 1,
};
enum ltEXPORT_LATCH {
lsfExportXpopSeen = 0x00000001,
lsfExportCanceled = 0x00000002,
};
enum ltMPTOKEN_ISSUANCE {
lsfMPTLocked = 0x00000001,
lsfMPTCanLock = 0x00000002,

View File

@@ -145,10 +145,6 @@ int out_len = 0;\
#define SUB_OFFSET(x) ((int32_t)(x >> 32))
#define SUB_LENGTH(x) ((int32_t)(x & 0xFFFFFFFFULL))
#define ENTROPY_TIER(x) (((uint64_t)(x) >> 32U) & 0xFFU)
#define ENTROPY_COUNT(x) (((uint64_t)(x) >> 16U) & 0xFFFFU)
#define ENTROPY_DENOMINATOR(x) ((uint64_t)(x) & 0xFFFFU)
#define BUFFER_EQUAL_20(buf1, buf2)\
(\
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
@@ -382,3 +378,4 @@ int out_len = 0;\
#endif

View File

@@ -9,8 +9,6 @@
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
#define sfHookResult ((16U << 16U) + 18U)
#define sfWasLockingChainSend ((16U << 16U) + 19U)
#define sfSidecarType ((16U << 16U) + 20U)
#define sfEntropyTier ((16U << 16U) + 21U)
#define sfLedgerEntryType ((1U << 16U) + 1U)
#define sfTransactionType ((1U << 16U) + 2U)
#define sfSignerWeight ((1U << 16U) + 3U)
@@ -24,10 +22,6 @@
#define sfHookApiVersion ((1U << 16U) + 20U)
#define sfHookStateScale ((1U << 16U) + 21U)
#define sfLedgerFixType ((1U << 16U) + 22U)
#define sfHookExportCount ((1U << 16U) + 98U)
#define sfEntropyCount ((1U << 16U) + 99U)
#define sfEntropyDenominator ((1U << 16U) + 100U)
#define sfExportCount ((1U << 16U) + 101U)
#define sfNetworkID ((2U << 16U) + 1U)
#define sfFlags ((2U << 16U) + 2U)
#define sfSourceTag ((2U << 16U) + 3U)
@@ -111,7 +105,6 @@
#define sfMPTAmount ((3U << 16U) + 26U)
#define sfIssuerNode ((3U << 16U) + 27U)
#define sfSubjectNode ((3U << 16U) + 28U)
#define sfExportNode ((3U << 16U) + 29U)
#define sfTouchCount ((3U << 16U) + 97U)
#define sfAccountIndex ((3U << 16U) + 98U)
#define sfAccountCount ((3U << 16U) + 99U)
@@ -159,9 +152,6 @@
#define sfEscrowID ((5U << 16U) + 35U)
#define sfURITokenID ((5U << 16U) + 36U)
#define sfDomainID ((5U << 16U) + 37U)
#define sfExportSignatureHash ((5U << 16U) + 38U)
#define sfExportCommitteeHash ((5U << 16U) + 39U)
#define sfManifestID ((5U << 16U) + 91U)
#define sfHookOnOutgoing ((5U << 16U) + 93U)
#define sfHookOnIncoming ((5U << 16U) + 94U)
#define sfCron ((5U << 16U) + 95U)
@@ -199,7 +189,6 @@
#define sfSignatureReward ((6U << 16U) + 29U)
#define sfMinAccountCreateAmount ((6U << 16U) + 30U)
#define sfLPTokenBalance ((6U << 16U) + 31U)
#define sfExportCallbackFee ((6U << 16U) + 32U)
#define sfTrustLineRewardAccumulator ((6U << 16U) + 99U)
#define sfPublicKey ((7U << 16U) + 1U)
#define sfMessageKey ((7U << 16U) + 2U)
@@ -232,9 +221,6 @@
#define sfProvider ((7U << 16U) + 30U)
#define sfMPTokenMetadata ((7U << 16U) + 31U)
#define sfCredentialType ((7U << 16U) + 32U)
#define sfEntropyContributors ((7U << 16U) + 33U)
#define sfExportCommittee ((7U << 16U) + 34U)
#define sfExportContributors ((7U << 16U) + 35U)
#define sfHookName ((7U << 16U) + 97U)
#define sfRemarkValue ((7U << 16U) + 98U)
#define sfRemarkName ((7U << 16U) + 99U)
@@ -287,7 +273,6 @@
#define sfEmitDetails ((14U << 16U) + 13U)
#define sfHook ((14U << 16U) + 14U)
#define sfSigner ((14U << 16U) + 16U)
#define sfExportSigner ((14U << 16U) + 17U)
#define sfMajority ((14U << 16U) + 18U)
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
@@ -303,8 +288,6 @@
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
#define sfPriceData ((14U << 16U) + 32U)
#define sfCredential ((14U << 16U) + 33U)
#define sfExportedTxn ((14U << 16U) + 89U)
#define sfManifest ((14U << 16U) + 90U)
#define sfAmountEntry ((14U << 16U) + 91U)
#define sfMintURIToken ((14U << 16U) + 92U)
#define sfHookEmission ((14U << 16U) + 93U)
@@ -331,7 +314,6 @@
#define sfHookGrants ((15U << 16U) + 20U)
#define sfXChainClaimAttestations ((15U << 16U) + 21U)
#define sfXChainCreateAccountAttestations ((15U << 16U) + 22U)
#define sfExportSigners ((15U << 16U) + 23U)
#define sfPriceDataSeries ((15U << 16U) + 24U)
#define sfAuthAccounts ((15U << 16U) + 25U)
#define sfAuthorizeCredentials ((15U << 16U) + 26U)

View File

@@ -61,8 +61,6 @@
#define ttNFTOKEN_MODIFY 70
#define ttPERMISSIONED_DOMAIN_SET 71
#define ttPERMISSIONED_DOMAIN_DELETE 72
#define ttEXPORT 90
#define ttMANIFEST_SET 91
#define ttCRON 92
#define ttCRON_SET 93
#define ttREMARKS_SET 94
@@ -76,5 +74,3 @@
#define ttUNL_MODIFY 102
#define ttEMIT_FAILURE 103
#define ttUNL_REPORT 104
#define ttCONSENSUS_ENTROPY 105
#define ttEXPORT_SIGNATURES 106

View File

@@ -115,10 +115,3 @@ enum AMMClawbackFlags : uint32_t {
enum BridgeModifyFlags : uint32_t {
tfClearAccountCreateAmount = 0x00010000,
};
enum ExportFlags : uint32_t {
// Lifecycle control: erase the named latch and forfeit any later callback.
tfExportEraseLatch = 0x00010000,
// Committee control: erase the named immutable committee object.
tfExportEraseCommittee = 0x00020000,
};

View File

@@ -15,8 +15,6 @@
#define uint256 std::string
#define featureHooksUpdate1 "1"
#define featureHooksUpdate2 "1"
#define featureExport "1"
#define featureConsensusEntropy "1"
#define fix20250131 "1"
#define fixGuardDepth32 "1"
namespace hook_api {
@@ -384,12 +382,9 @@ enum class hook_return_code : int64_t {
INVALID_KEY = -41, // user supplied key was not valid
NOT_A_STRING = -42, // nul terminator missing from a string argument
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 256 modified state
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state
// entires in the combined hook chains
TOO_MANY_NAMESPACES = -45,
EXPORT_FAILURE = -46,
TOO_MANY_EXPORTED_TXN = -47,
TOO_LITTLE_ENTROPY = -48,
TOO_MANY_NAMESPACES = -45
};
enum class ExitType : uint8_t {
@@ -403,7 +398,6 @@ const uint16_t max_state_modifications = 256;
const uint8_t max_slots = 255;
const uint8_t max_nonce = 255;
const uint8_t max_emit = 255;
const uint8_t max_export = 2;
const uint8_t max_params = 16;
const double fee_base_multiplier = 1.1f;
@@ -421,7 +415,6 @@ getImportWhitelist(Rules const& rules)
#undef HOOK_API_DEFINITION
#define int64_t 0x7EU
#define uint64_t 0x7EU
#define int32_t 0x7FU
#define uint32_t 0x7FU
@@ -438,7 +431,6 @@ getImportWhitelist(Rules const& rules)
#undef HOOK_API_DEFINITION
#undef HOOK_WRAP_PARAMS
#undef int64_t
#undef uint64_t
#undef int32_t
#undef uint32_t
#pragma pop_macro("HOOK_API_DEFINITION")
@@ -446,6 +438,10 @@ getImportWhitelist(Rules const& rules)
return whitelist;
}
#undef HOOK_API_DEFINITION
#undef I32
#undef I64
enum GuardRulesVersion : uint64_t {
GuardRuleFix20250131 = 0x00000001,
GuardRuleDepth32 = 0x00000002,

View File

@@ -10,6 +10,8 @@
* were then used.
*/
#include <xrpl/hook/WasmTypes.h>
#define LPAREN (
#define RPAREN )
#define COMMA ,
@@ -68,104 +70,90 @@
#define SEP_uint64_t LPAREN uint64_t COMMA
#define SEP_int64_t LPAREN int64_t COMMA
#define VAL_uint32_t WasmEdge_ValueGetI32(in[_stack++])
#define VAL_int32_t WasmEdge_ValueGetI32(in[_stack++])
#define VAL_uint64_t WasmEdge_ValueGetI64(in[_stack++])
#define VAL_int64_t WasmEdge_ValueGetI64(in[_stack++])
// VAL_* : extract typed value from WasmValue in[] array (engine-agnostic)
#define VAL_uint32_t (uint32_t) in[_stack++].asI32()
#define VAL_int32_t (int32_t) in[_stack++].asI32()
#define VAL_uint64_t (uint64_t) in[_stack++].asI64()
#define VAL_int64_t (int64_t) in[_stack++].asI64()
#define VAR_ASSIGN(T, V) T V = CAT(VAL_##T)
#define RET_uint32_t(return_code) WasmEdge_ValueGenI32(return_code)
#define RET_int32_t(return_code) WasmEdge_ValueGenI32(return_code)
#define RET_uint64_t(return_code) WasmEdge_ValueGenI64(return_code)
#define RET_int64_t(return_code) WasmEdge_ValueGenI64(return_code)
// RET_* : wrap a C return value into a WasmValue
#define RET_uint32_t(return_code) hook::WasmValue::i32((uint32_t)(return_code))
#define RET_int32_t(return_code) hook::WasmValue::i32((uint32_t)(return_code))
#define RET_uint64_t(return_code) hook::WasmValue::i64((uint64_t)(return_code))
#define RET_int64_t(return_code) hook::WasmValue::i64((uint64_t)(return_code))
#define RET_ASSIGN(T, return_code) CAT2(RET_, T(return_code))
#define TYP_uint32_t WasmEdge_ValType_I32
#define TYP_int32_t WasmEdge_ValType_I32
#define TYP_uint64_t WasmEdge_ValType_I64
#define TYP_int64_t WasmEdge_ValType_I64
// TYP_* : map C type to WasmValue::Kind (used by WasmEdgeEngine.cpp registry)
#define TYP_uint32_t hook::WasmValue::Kind::I32
#define TYP_int32_t hook::WasmValue::Kind::I32
#define TYP_uint64_t hook::WasmValue::Kind::I64
#define TYP_int64_t hook::WasmValue::Kind::I64
#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
#define UNSIGNED_TYPE(T) std::make_unsigned_t<T>
// DECLARE_HOOK_FUNCTION: forward-declares the impl function and the engine
// wrapper
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
hook::HookContext& hookCtx, \
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
COMMA __VA_ARGS__)); \
extern WasmEdge_Result WasmFunction##F( \
void* data_ptr, \
const WasmEdge_CallingFrameContext* frameCtx, \
const WasmEdge_Value* in, \
WasmEdge_Value* out); \
extern WasmEdge_ValType WasmFunctionParams##F[]; \
extern WasmEdge_ValType WasmFunctionResult##F[]; \
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
extern WasmEdge_String WasmFunctionName##F;
hook::GuestMemory& mem __VA_OPT__(COMMA __VA_ARGS__)); \
extern hook::HostCallStatus WasmFunction##F( \
void* userData, \
hook::GuestMemory& mem, \
hook::WasmValue const* in, \
size_t inLen, \
hook::WasmValue* out, \
size_t outLen);
// DEFINE_HOOK_FUNCTION: defines the engine-agnostic wrapper + the impl function
// body
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
WasmEdge_Result hook_api::WasmFunction##F( \
hook::HostCallStatus hook_api::WasmFunction##F( \
void* data_ptr, \
const WasmEdge_CallingFrameContext* frameCtx, \
const WasmEdge_Value* in, \
WasmEdge_Value* out) \
hook::GuestMemory& mem, \
hook::WasmValue const* in, \
size_t /*inLen*/, \
hook::WasmValue* out, \
size_t /*outLen*/) \
{ \
__VA_OPT__(int _stack = 0;) \
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
hook::HookContext* hookCtx = \
reinterpret_cast<hook::HookContext*>(data_ptr); \
auto const& return_code = hook_api::F( \
*hookCtx, \
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
*hookCtx, mem __VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
(std::get<hook_api::hook_return_code>(return_code) == \
RC_ROLLBACK || \
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
return WasmEdge_Result_Terminate; \
return hook::HostCallStatus::Terminate; \
out[0] = RET_ASSIGN( \
R, \
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
? std::get<UNSIGNED_TYPE(R)>(return_code) \
: R(std::get<hook_api::hook_return_code>(return_code))); \
return WasmEdge_Result_Success; \
return hook::HostCallStatus::Success; \
}; \
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
WASM_VAL_TYPE(R, dummy)}; \
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
WasmEdge_FunctionTypeCreate( \
WasmFunctionParams##F, \
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
WasmFunctionResult##F, \
1); \
WasmEdge_String hook_api::WasmFunctionName##F = \
WasmEdge_StringCreateByCString(#F); \
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> hook_api::F( \
hook::HookContext& hookCtx, \
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
COMMA __VA_ARGS__))
hook::GuestMemory& mem __VA_OPT__(COMMA __VA_ARGS__))
#define HOOK_SETUP() \
using enum hook_api::hook_return_code; \
try \
{ \
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
[[maybe_unused]] auto& view = applyCtx.view(); \
[[maybe_unused]] auto j = applyCtx.app.journal("View"); \
[[maybe_unused]] WasmEdge_MemoryInstanceContext* memoryCtx = \
WasmEdge_CallingFrameGetMemoryInstance(&frameCtx, 0); \
[[maybe_unused]] unsigned char* memory = \
WasmEdge_MemoryInstanceGetPointer(memoryCtx, 0, 0); \
[[maybe_unused]] const uint64_t memory_length = \
WasmEdge_MemoryInstanceGetPageSize(memoryCtx) * \
WasmEdge_kPageSize; \
[[maybe_unused]] auto& api = hookCtx.api(); \
if (!memoryCtx || !memory || !memory_length) \
#define HOOK_SETUP() \
using enum hook_api::hook_return_code; \
try \
{ \
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
[[maybe_unused]] auto& view = applyCtx.view(); \
[[maybe_unused]] auto j = applyCtx.app.journal("View"); \
[[maybe_unused]] unsigned char* memory = mem.base; \
[[maybe_unused]] const uint64_t memory_length = mem.size; \
[[maybe_unused]] auto& api = hookCtx.api(); \
if (!memory || !memory_length) \
return INTERNAL_ERROR;
#define HOOK_TEARDOWN() \
@@ -197,11 +185,10 @@
<< " bytes past end of wasm memory"; \
return OUT_OF_BOUNDS; \
} \
if (!WasmEdge_ResultOK(WasmEdge_MemoryInstanceSetData( \
memoryCtx, \
reinterpret_cast<const uint8_t*>(host_src_ptr), \
if (!mem.write( \
guest_dst_ptr, \
bytes_to_write))) \
reinterpret_cast<uint8_t const*>(host_src_ptr), \
static_cast<uint64_t>(bytes_to_write))) \
return INTERNAL_ERROR; \
bytes_written += bytes_to_write; \
}

View File

@@ -0,0 +1,109 @@
#ifndef RIPPLE_HOOK_WASMTYPES_H_INCLUDED
#define RIPPLE_HOOK_WASMTYPES_H_INCLUDED
#include <xrpl/basics/base_uint.h>
#include <cstdint>
#include <cstring>
#include <vector>
namespace hook {
struct GuestMemory
{
uint8_t* base;
uint64_t size;
inline bool
inBounds(uint64_t guestPtr, uint64_t len) const
{
if (len > 0 && guestPtr > (UINT64_MAX - len + 1))
return false;
return (guestPtr + len) <= size;
}
inline bool
write(uint64_t guestPtr, void const* src, uint64_t len)
{
if (!inBounds(guestPtr, len))
return false;
std::memcpy(base + guestPtr, src, len);
return true;
}
inline bool
read(uint64_t guestPtr, void* dst, uint64_t len) const
{
if (!inBounds(guestPtr, len))
return false;
std::memcpy(dst, base + guestPtr, len);
return true;
}
};
struct WasmValue
{
enum Kind : uint8_t { I32, I64 } kind;
union
{
uint32_t u32;
uint64_t u64;
};
static inline WasmValue
i32(uint32_t v)
{
WasmValue w;
w.kind = I32;
w.u32 = v;
return w;
}
static inline WasmValue
i64(uint64_t v)
{
WasmValue w;
w.kind = I64;
w.u64 = v;
return w;
}
inline uint32_t
asI32() const
{
return u32;
}
inline uint64_t
asI64() const
{
return u64;
}
};
enum class HostCallStatus {
Success,
Terminate,
Trap,
};
using HostFunctionFn = HostCallStatus (*)(
void* userData,
GuestMemory& mem,
WasmValue const* in,
size_t inLen,
WasmValue* out,
size_t outLen);
struct HostFunctionDecl
{
char const* name;
HostFunctionFn fn;
std::vector<WasmValue::Kind> params;
WasmValue::Kind result;
ripple::uint256 const* featureGate;
};
} // namespace hook
#endif // RIPPLE_HOOK_WASMTYPES_H_INCLUDED

View File

@@ -372,33 +372,3 @@ HOOK_API_DEFINITION(
HOOK_API_DEFINITION(
int64_t, prepare, (uint32_t, uint32_t, uint32_t, uint32_t),
featureHooksUpdate2)
// int64_t xport_reserve(uint32_t count);
HOOK_API_DEFINITION(
int64_t, xport_reserve, (uint32_t),
featureExport)
// int64_t xport(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len, uint32_t committee_hash_ptr, uint32_t committee_hash_len, uint64_t callback_fee_drops);
HOOK_API_DEFINITION(
int64_t, xport, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint64_t),
featureExport)
// int64_t xport_cancel(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
HOOK_API_DEFINITION(
int64_t, xport_cancel, (uint32_t, uint32_t, uint32_t),
featureExport)
// int64_t entropy_cr_dice(uint32_t sides, uint32_t min_tier);
HOOK_API_DEFINITION(
int64_t, entropy_cr_dice, (uint32_t, uint32_t),
featureConsensusEntropy)
// int64_t entropy_cr_random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
HOOK_API_DEFINITION(
int64_t, entropy_cr_random, (uint32_t, uint32_t, uint32_t),
featureConsensusEntropy)
// int64_t entropy_cr_status(void);
HOOK_API_DEFINITION(
int64_t, entropy_cr_status, (),
featureConsensusEntropy)

View File

@@ -1,2 +0,0 @@
---
DisableFormat: true

View File

@@ -27,7 +27,6 @@ enum MessageType
mtREPLAY_DELTA_RESPONSE = 60;
mtHAVE_TRANSACTIONS = 63;
mtTRANSACTIONS = 64;
mtEXPORT_SHARES = 65;
}
// token, iterations, target, challenge = issue demand for proof of work
@@ -37,7 +36,6 @@ enum MessageType
//------------------------------------------------------------------------------
/* Provides the current ephemeral key for a validator. */
//@@start opaque-self-verifying-overlay-artifact-precedent
message TMManifest
{
// A Manifest object in the Ripple serialization format.
@@ -51,7 +49,6 @@ message TMManifests
// The manifests sent when a peer first connects to another peer are `history`.
optional bool history = 2 [deprecated=true];
}
//@@end opaque-self-verifying-overlay-artifact-precedent
//------------------------------------------------------------------------------
@@ -121,15 +118,6 @@ message TMTransactions
repeated TMTransaction transactions = 1;
}
// Canonical post-validation Export signature contributions. Receivers enforce
// ExportLimits caps for entry count, each frame, aggregate frame payload, and
// encoded protobuf size. The encoded-size cap includes protobuf tags and
// length prefixes but excludes the overlay message header.
message TMExportShares
{
repeated bytes shares = 1;
}
enum NodeStatus
{
@@ -165,11 +153,7 @@ message TMStatusChange
message TMProposeSet
{
required uint32 proposeSeq = 1;
// Proposed transaction-set identity. Legacy/plain proposals carry the
// tx-set hash directly; ConsensusExtensions proposals carry a serialized
// ExtendedPosition whose first field is that tx-set hash, followed by
// signed RNG/Export sidecar fields.
required bytes currentTxHash = 2;
required bytes currentTxHash = 2; // the hash of the ledger we are proposing
required bytes nodePubKey = 3;
required uint32 closeTime = 4;
required bytes signature = 5; // signature of above fields
@@ -182,16 +166,6 @@ message TMProposeSet
// Number of hops traveled
optional uint32 hops = 12 [deprecated=true];
//@@start proposal-export-share-container-binding
// Canonical post-validation ExportShare frames authored by the proposing
// validator and re-advertised for anti-entropy. The proposal's
// ExtendedPosition includes a digest of this repeated field, so the exact
// frames are covered by the proposal signature. Each frame carries its
// version, owner, origin transaction, origin ledger sequence and hash,
// committee position, signing key, and multisign signature.
repeated bytes exportSignatures = 13;
//@@end proposal-export-share-container-binding
}
enum TxSetStatus
@@ -410,3 +384,4 @@ message TMHaveTransactions
{
repeated bytes hashes = 1;
}

View File

@@ -1,62 +0,0 @@
#ifndef RIPPLE_PROTOCOL_ENTROPY_TIER_H_INCLUDED
#define RIPPLE_PROTOCOL_ENTROPY_TIER_H_INCLUDED
#include <cstdint>
namespace ripple {
/// Which gate the ledger's entropy passed. Stored in sfEntropyTier (UINT8)
/// on the ttCONSENSUS_ENTROPY pseudo-transaction and the ConsensusEntropy
/// ledger entry.
///
/// EntropyCount says how many validators contributed; EntropyDenominator says
/// how many active validators were in the ledger-anchored view for that
/// non-fallback result; EntropyTier says which gate the result passed. Fallback
/// entropy carries count=0/denominator=0 because no validator-derived
/// denominator was accepted. Tier values are strength-ordered so consumers can
/// gate with a numeric comparison (tier >= required).
///
/// RESIDUAL BIAS — applies to fallback, participant_aligned, and
/// validator_quorum. This is a commit/reveal scheme: a validator can withhold
/// its reveal until after observing peers' reveals, choosing between two
/// outcomes (its contribution in vs. out) — up to one bit of influence per
/// withholder, and colluding withholders near a threshold can instead force a
/// downgrade to a lower tier. These tiers bound and *label* manipulation (it is
/// observable and limited); they are NOT bias-resistant against a colluding
/// validator minority. A hook that requires validator_full fails closed if any
/// active validator withholds, trading availability for no selective-
/// withholding slack.
enum EntropyTier : std::uint8_t {
/// No usable entropy (reserved; a fresh ConsensusEntropy entry should
/// always carry one of the tiers below).
entropyTierNone = 0,
/// Consensus-bound deterministic fallback: derived from already-agreed
/// round inputs (parent ledger hash, base tx set hash, sequence) under
/// HashPrefix::entropyFallback when no agreed reveal set reaches either
/// participant_aligned or validator_quorum. Unpredictable in practice but
/// user-influenceable via transaction submission — never suitable for
/// value-bearing outcomes.
entropyTierConsensusFallback = 1,
/// Participant-aligned sub-quorum entropy: the agreed reveal set aligned at
/// the tier-2 participant threshold — below the 80% validator quorum but at
/// or above the equivocation-intersection floor over the original
/// (pre-nUNL)
/// view. Weaker than validator_quorum; opt-in for hooks via min_tier.
entropyTierParticipantAligned = 2,
/// Validator commit/reveal entropy whose sidecar set passed the
/// active-validator-view quorum alignment gate.
entropyTierValidatorQuorum = 3,
/// Validator commit/reveal entropy with reveals from every validator in
/// the ledger-anchored active view. Any missing active validator downgrades
/// the tier, so hooks can require this to fail closed on selective
/// withholding.
entropyTierValidatorFull = 4,
};
} // namespace ripple
#endif

View File

@@ -1,131 +0,0 @@
#ifndef RIPPLE_PROTOCOL_EXPORTCOMMITTEE_H_INCLUDED
#define RIPPLE_PROTOCOL_EXPORTCOMMITTEE_H_INCLUDED
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/ExportLimits.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>
namespace ripple {
/** One canonical, network-neutral Export validator committee. */
struct ExportCommitteeProfile
{
std::vector<PublicKey> members;
std::size_t quorum;
std::optional<std::uint16_t>
position(PublicKey const& master) const
{
auto const it =
std::lower_bound(members.begin(), members.end(), master);
if (it == members.end() || *it != master)
return std::nullopt;
return static_cast<std::uint16_t>(std::distance(members.begin(), it));
}
};
/** Parse a strictly sorted sequence of compressed validator master keys. */
inline std::optional<ExportCommitteeProfile>
resolveExportCommittee(Slice roster)
{
constexpr std::size_t publicKeyBytes = 33;
if (roster.empty() || roster.size() % publicKeyBytes != 0)
return std::nullopt;
auto const count = roster.size() / publicKeyBytes;
if (count == 0 || count > ExportLimits::maxCommitteeMembers)
return std::nullopt;
std::vector<PublicKey> members;
members.reserve(count);
for (std::size_t offset = 0; offset < roster.size();
offset += publicKeyBytes)
{
Slice const encoded{roster.data() + offset, publicKeyBytes};
if (!publicKeyType(encoded))
return std::nullopt;
PublicKey member{encoded};
if (!members.empty() && !(members.back() < member))
return std::nullopt;
members.push_back(std::move(member));
}
return ExportCommitteeProfile{
std::move(members), ExportLimits::committeeQuorumThreshold(count)};
}
inline Blob
serializeExportCommittee(std::vector<PublicKey> members)
{
if (members.empty() || members.size() > ExportLimits::maxCommitteeMembers)
return {};
std::sort(members.begin(), members.end());
if (std::adjacent_find(members.begin(), members.end()) != members.end())
return {};
Blob roster;
roster.reserve(members.size() * 33);
for (auto const& member : members)
roster.insert(roster.end(), member.begin(), member.end());
return roster;
}
/** Canonicalize an unordered transaction roster into strict stored form. */
inline std::optional<Blob>
canonicalizeExportCommittee(Slice roster)
{
constexpr std::size_t publicKeyBytes = 33;
if (roster.empty() || roster.size() % publicKeyBytes != 0)
return std::nullopt;
auto const count = roster.size() / publicKeyBytes;
if (count == 0 || count > ExportLimits::maxCommitteeMembers)
return std::nullopt;
std::vector<PublicKey> members;
members.reserve(count);
for (std::size_t offset = 0; offset < roster.size();
offset += publicKeyBytes)
{
Slice const encoded{roster.data() + offset, publicKeyBytes};
if (!publicKeyType(encoded))
return std::nullopt;
members.emplace_back(encoded);
}
auto canonical = serializeExportCommittee(std::move(members));
if (canonical.empty())
return std::nullopt;
return canonical;
}
/** Content identity of one already-validated canonical roster. */
inline uint256
exportCommitteeHash(Slice roster)
{
auto const profile = resolveExportCommittee(roster);
if (!profile)
return {};
Serializer serialized;
serialized.add32(HashPrefix::exportCommittee);
serialized.add32(static_cast<std::uint32_t>(profile->members.size()));
serialized.addRaw(roster);
return serialized.getSHA512Half();
}
} // namespace ripple
#endif

View File

@@ -1,119 +0,0 @@
#ifndef RIPPLE_PROTOCOL_EXPORT_LIMITS_H_INCLUDED
#define RIPPLE_PROTOCOL_EXPORT_LIMITS_H_INCLUDED
#include <xrpl/protocol/ValidatorBitset.h>
#include <cstddef>
#include <cstdint>
namespace ripple {
// Export system caps.
//
// These limits bound the DoS surface of the export signature system:
// - Each selected validator signs once after exact source validation
// - Pending shares are re-advertised through bounded proposal/relay batches
// - Inbound signature processing involves crypto verification per sig
// - Durable and per-message caps bound scans, crypto, and sidecar leaves
struct ExportLimits
{
// Ordinary XRPL multisigning accepts at most 32 signers. An account that
// reserves a separate operator signer must select fewer validators.
static constexpr std::size_t maxCommitteeMembers = 32;
static constexpr std::size_t maxCommitteeRosterBytes =
maxCommitteeMembers * 33;
static constexpr std::size_t maxCommitteeContributorBytes =
validatorBitsetBytes(maxCommitteeMembers);
// Export-specific serialized payload bounds. The release target includes
// the canonical origin Memo appended after validation; the witness wraps
// that target plus the accepted committee signatures.
static constexpr std::size_t maxExportReleaseTargetBytes = 2'048;
static constexpr std::size_t maxExportWitnessBytes = 8'192;
// V1 witnesses require the standard 80% quorum of the intent-selected
// committee, rounded up. The intent selects members but cannot lower this
// threshold. Split quotient/remainder arithmetic avoids overflow while
// preserving ceil(memberCount * 0.8).
static constexpr std::size_t
committeeQuorumThreshold(std::size_t memberCount)
{
auto const quotient = memberCount / 5;
auto const remainder = memberCount % 5;
return quotient * 4 + (remainder * 4 + 4) / 5;
}
// Maximum exports a single hook execution may produce. Hook API ABI
// constant hook_api::max_export must stay equal.
static constexpr std::uint8_t maxExportsPerHook = 2;
// Maximum Export intents admitted in one ledger and maximum live latches
// owned by one account.
static constexpr std::uint8_t maxPendingExports = 8;
// Global pending-latch and per-validation scan bound. Witnessed or canceled
// latches release this signing-work slot while their account owner count
// and reserve continue to bound retained state. This is a provisional
// activation tuning value.
static constexpr std::uint16_t maxLiveExportLatches = 64;
// Maximum admission window requested through the mandatory outer
// LastLedgerSequence. This bounds how long an Export may remain queued
// before entering a ledger; it does not bound post-validation release.
static constexpr std::uint32_t maxAdmissionWindowLedgers = 5;
// Fixed source-ledger window for post-validation share publication and
// witness materialization, measured from the ledger that admits the
// intent. This is a provisional activation tuning value. Keeping it
// separate from the outer LastLedgerSequence prevents queue delay from
// consuming the publication window.
static constexpr std::uint32_t maxPublicationLedgers = 5;
// A fully-canonical secp256k1 signature is at most 72 bytes; Ed25519 is 64.
static constexpr std::size_t maxCanonicalExportSignatureBytes = 72;
// Export-signature sidecar leaves encode an origin, committee position,
// signing key, and signature in an STObject envelope. Keep this comfortably
// above the canonical encoding while bounding peer-supplied leaf bytes
// before parse/hash work.
static constexpr std::size_t maxExportSignatureSidecarBytes = 256;
// Post-validation relay framing. Values are deliberately conservative
// local tuning knobs and require measurement before activation; changing
// them does not change the canonical per-share format.
// version + AccountID + 2 hashes + ledger sequence + committee position +
// compressed public key + one-byte VL prefix + maximum signature.
static constexpr std::size_t maxSerializedExportShareBytes =
1 + 20 + 32 + 4 + 32 + 2 + 33 + 1 + maxCanonicalExportSignatureBytes;
static constexpr std::size_t maxExportSharesPerRelay = 32;
static constexpr std::size_t maxExportShareRelayPayloadBytes =
maxSerializedExportShareBytes * maxExportSharesPerRelay;
// Protobuf repeated-bytes framing is one tag byte plus a two-byte varint
// length for every maximum-size frame. This excludes the overlay header.
static constexpr std::size_t maxExportShareRelayMessageBytes =
maxExportShareRelayPayloadBytes + 3 * maxExportSharesPerRelay;
// Provisional activation fee schedule. These values intentionally price
// Export intents above ordinary transactions until production measurements
// can replace the conservative work and storage estimates.
//
// Witness bytes are estimated from the exact serialized target size, a
// fixed allowance for the witness envelope/origin stamp/contributor mask,
// and one bounded signer entry for every selected member. The accepted
// sidecar witness retains every valid committee contribution, not merely
// the minimum quorum subset.
static constexpr std::uint64_t feeWitnessFixedAllowanceBytes = 384;
static constexpr std::uint64_t feeWitnessSignerAllowanceBytes = 128;
static constexpr std::uint64_t feeSharePublicationRounds =
maxPublicationLedgers + 1;
static constexpr std::uint64_t feeWorkUnitsPerSharePublication = 1;
static constexpr std::uint64_t feeWorkUnitsPerWitnessSignature = 2;
static constexpr std::uint64_t feeWitnessChunkBytes = 256;
static constexpr std::uint64_t feeWorkUnitsPerWitnessChunk = 1;
static constexpr std::uint64_t feePermanentWitnessByteDrops = 1;
static constexpr std::uint64_t feeSurchargeRoundDrops = 1'000;
};
} // namespace ripple
#endif

View File

@@ -1,104 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
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_EXPORTORIGINMEMO_H_INCLUDED
#define RIPPLE_PROTOCOL_EXPORTORIGINMEMO_H_INCLUDED
#include <xrpl/basics/Expected.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
#include <optional>
#include <string_view>
namespace ripple::ExportOriginMemo {
inline constexpr std::string_view memoType = "xahau/export";
inline constexpr std::uint8_t version = 1;
inline constexpr std::uint8_t allowedFlags = 0;
inline constexpr std::size_t identityBytes = 42;
inline constexpr std::size_t releaseBytes = 78;
struct Origin
{
std::uint32_t sourceDomain;
std::uint32_t targetDomain;
uint256 transactionHash;
bool
operator==(Origin const&) const = default;
};
struct Anchor
{
LedgerIndex ledgerSequence;
uint256 ledgerHash;
bool
operator==(Anchor const&) const = default;
};
struct Stamp
{
Origin origin;
std::optional<Anchor> anchor;
bool
operator==(Stamp const&) const = default;
};
enum class Error {
reservedMemoPresent,
reservedMemoMissing,
reservedMemoPosition,
malformedMemo,
unsupportedVersion,
unsupportedFlags,
localChecks
};
/** Return true when any Memo reserves the Export protocol MemoType. */
bool
hasReservedMemo(STTx const& tx);
/** Append the canonical identity projection as the final Memo.
Existing Memos remain byte-identical and in their original order. This
function does not normalize transaction authorization fields; callers must
supply the canonical unsigned multisign base.
*/
Expected<STTx, Error>
identityForm(STTx const& base, Origin const& origin);
/** Append the canonical release projection as the final Memo. */
Expected<STTx, Error>
releaseForm(STTx const& base, Origin const& origin, Anchor const& anchor);
/** Parse one canonical final Export Memo. */
Expected<Stamp, Error>
parse(STTx const& tx);
/** Replace a canonical release projection with its identity projection. */
Expected<STTx, Error>
projectIdentity(STTx const& stamped);
} // namespace ripple::ExportOriginMemo
#endif

View File

@@ -1,142 +0,0 @@
#ifndef RIPPLE_PROTOCOL_EXPORTSHARE_H_INCLUDED
#define RIPPLE_PROTOCOL_EXPORTSHARE_H_INCLUDED
#include <xrpl/basics/Buffer.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ExportLimits.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/digest.h>
#include <cstdint>
#include <cstring>
#include <optional>
#include <stdexcept>
namespace ripple {
/** Canonical post-validation Export signature contribution.
The multisign signature authenticates the derived destination transaction.
The remaining fields are bounded lookup and release context which live
admission checks against the origin latch and local validated chain.
*/
struct ExportShare
{
static constexpr std::uint8_t currentVersion = 1;
std::uint8_t version{currentVersion};
AccountID owner;
uint256 originTxn;
LedgerIndex originLedgerSeq{0};
uint256 originLedgerHash;
std::uint16_t committeePosition{0};
PublicKey signingKey;
Buffer signature;
bool
hasCanonicalSignature() const
{
auto const keyType = publicKeyType(signingKey);
if (!keyType)
return false;
auto const sig = Slice{signature.data(), signature.size()};
if (*keyType == KeyType::secp256k1)
{
auto const canonicality = ecdsaCanonicality(sig);
return canonicality &&
*canonicality == ECDSACanonicality::fullyCanonical;
}
// Full Ed25519 canonicality is enforced by cryptographic admission.
// The framing layer can still reject every impossible wire length.
return *keyType == KeyType::ed25519 && signature.size() == 64;
}
bool
validShape() const
{
return version == currentVersion && owner != beast::zero &&
!originTxn.isZero() && originLedgerSeq != 0 &&
!originLedgerHash.isZero() &&
committeePosition < ExportLimits::maxCommitteeMembers &&
hasCanonicalSignature();
}
Serializer
serialize() const
{
if (!validShape())
throw std::invalid_argument("invalid ExportShare shape");
Serializer result;
result.add8(version);
result.addBitString(owner);
result.addBitString(originTxn);
result.add32(originLedgerSeq);
result.addBitString(originLedgerHash);
result.add16(committeePosition);
result.addRaw(signingKey.slice());
result.addVL(signature);
return result;
}
uint256
wireHash() const
{
// Raw-wire suppression only. Routing context such as committeePosition
// is checked against validated state before relay and is not
// authenticated by the destination multisignature.
auto const bytes = serialize();
return sha512Half(bytes.slice());
}
static std::optional<ExportShare>
parse(Slice bytes)
{
if (bytes.empty() ||
bytes.size() > ExportLimits::maxSerializedExportShareBytes)
return std::nullopt;
try
{
SerialIter sit{bytes};
auto const version = sit.get8();
auto const owner = sit.getBitString<160, detail::AccountIDTag>();
auto const originTxn = sit.get256();
auto const originLedgerSeq = sit.get32();
auto const originLedgerHash = sit.get256();
auto const committeePosition = sit.get16();
auto const keySlice = sit.getSlice(33);
if (!publicKeyType(keySlice))
return std::nullopt;
PublicKey const signingKey{keySlice};
auto signature = sit.getVLBuffer();
if (!sit.empty())
return std::nullopt;
ExportShare result{
version,
owner,
originTxn,
originLedgerSeq,
originLedgerHash,
committeePosition,
signingKey,
std::move(signature)};
if (!result.validShape())
return std::nullopt;
return result;
}
catch (std::exception const&)
{
return std::nullopt;
}
}
};
} // namespace ripple
#endif

View File

@@ -96,24 +96,6 @@ enum class HashPrefix : std::uint32_t {
/** Credentials signature */
credential = detail::make_hash_prefix('C', 'R', 'D'),
/** consensus extension sidecar object */
sidecar = detail::make_hash_prefix('S', 'C', 'R'),
/** consensus diagnostic observed participant set */
observedParticipants = detail::make_hash_prefix('O', 'B', 'P'),
/** consensus-bound fallback entropy digest (Tier 1: derived from
already-agreed round inputs when no agreed reveal set reaches an
accepted validator-participant tier; never to be confused with
validator entropy) */
entropyFallback = detail::make_hash_prefix('E', 'F', 'B'),
/** consensus entropy transaction-ordering salt */
entropyTxnOrder = detail::make_hash_prefix('E', 'T', 'O'),
/** immutable Export committee roster */
exportCommittee = detail::make_hash_prefix('E', 'C', 'M'),
};
template <class Hasher>

View File

@@ -454,15 +454,6 @@ getVLInfo(Json::Value const& xpop, beast::Journal const& j)
<< "Import: unl blob was not valid json (after base64 decoding)";
return {};
}
auto const isNonNegativeUInt = [](Json::Value const& value) {
return value.isUInt() || (value.isInt() && value.asInt() >= 0);
};
if (!list.isMember(jss::sequence) ||
!isNonNegativeUInt(list[jss::sequence]))
{
JLOG(j.warn()) << "Import: unl blob sequence was missing or negative";
return {};
}
auto const sequence = list[jss::sequence].asUInt();
auto const m = deserializeManifest(base64_decode(
xpop[jss::validation][jss::unl][jss::manifest].asString()));

View File

@@ -62,22 +62,6 @@ emittedDir() noexcept;
Keylet
emittedTxn(uint256 const& id) noexcept;
/** Locate an Export latch by its permanent source issuance identity.
The source Export transaction ID is content-addressed and cannot be reused,
including after AccountDelete.
*/
Keylet
exportLatch(AccountID const& account, uint256 const& originTxnHash) noexcept;
/** Locate an immutable Export committee owned by an account. */
Keylet
exportCommittee(AccountID const& account, uint256 const& digest) noexcept;
/** The fixed global directory of Export latches awaiting witness work. */
Keylet const&
pendingExports() noexcept;
Keylet
hookDefinition(uint256 const& hash) noexcept;
@@ -134,10 +118,6 @@ negativeUNL() noexcept;
Keylet const&
UNLReport() noexcept;
/** The (fixed) index of the object containing consensus-derived entropy. */
Keylet const&
consensusEntropy() noexcept;
/** The beginning of an order book */
struct book_t
{
@@ -392,10 +372,6 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
Keylet
permissionedDomain(uint256 const& domainID) noexcept;
Keylet
manifest(PublicKey const& pk) noexcept;
} // namespace keylet
// Everything below is deprecated and should be removed in favor of keylets:

View File

@@ -186,10 +186,6 @@ enum LedgerSpecificFlags {
// remarks
lsfImmutable = 1,
// ltEXPORT_LATCH
lsfExportXpopSeen = 0x00000001,
lsfExportCanceled = 0x00000002,
// ltMPTOKEN_ISSUANCE
lsfMPTLocked = 0x00000001, // Also used in ltMPTOKEN
lsfMPTCanLock = 0x00000002,

View File

@@ -43,19 +43,6 @@ std::size_t constexpr txMinSizeBytes = 10;
/** Largest legal byte size of a transaction. */
std::size_t constexpr txMaxSizeBytes = megabytes(1);
/** Largest network ID whose transactions retain the legacy canonical form.
Transactions on these networks must omit sfNetworkID. Transactions on
networks with larger IDs must include sfNetworkID matching the network.
*/
std::uint32_t constexpr maxNetworkIDWithoutTxField = 1024;
constexpr bool
requiresTxNetworkID(std::uint32_t networkID)
{
return networkID > maxNetworkIDWithoutTxField;
}
/** The maximum number of unfunded offers to delete at once */
std::size_t constexpr unfundedOfferRemoveLimit = 1000;

View File

@@ -1,21 +0,0 @@
#ifndef RIPPLE_PROTOCOL_SIDECAR_TYPE_H_INCLUDED
#define RIPPLE_PROTOCOL_SIDECAR_TYPE_H_INCLUDED
#include <cstdint>
namespace ripple {
/// Discriminator for sidecar set entries (SHAMap leaves used for
/// consensus extension data: RNG commit/reveal, export signatures).
///
/// Stored in sfSidecarType (UINT8) on each STObject entry.
/// Makes sidecar sets self-describing — no content-sniffing needed.
enum SidecarType : std::uint8_t {
sidecarRngCommit = 1,
sidecarRngReveal = 2,
sidecarExportSig = 3,
};
} // namespace ripple
#endif

View File

@@ -68,10 +68,7 @@ enum TELcodes : TERUnderlyingType {
telNON_LOCAL_EMITTED_TXN,
telIMPORT_VL_KEY_NOT_RECOGNISED,
telCAN_NOT_QUEUE_IMPORT,
// Keep test-only sentinels before appended branch-local TEL codes so their
// numeric values do not move.
telENV_RPC_FAILED,
telEXPORT_LATCH_REQUIRED,
};
//------------------------------------------------------------------------------
@@ -197,8 +194,6 @@ enum TEFcodes : TERUnderlyingType {
tefNONDIR_EMIT,
tefIMPORT_BLACKHOLED,
tefINVALID_LEDGER_FIX_TYPE,
tefPAST_MANIFEST_SEQ,
tefREVOKED_MANIFEST,
};
//------------------------------------------------------------------------------
@@ -368,7 +363,6 @@ enum TECcodes : TERUnderlyingType {
tecARRAY_TOO_LARGE = 197,
tecLOCKED = 198,
tecBAD_CREDENTIALS = 199,
tecEXPORT_COMMITTEE_UNAVAILABLE = 201,
tecLAST_POSSIBLE_ENTRY = 255,
};

View File

@@ -274,17 +274,6 @@ enum BridgeModifyFlags : uint32_t {
tfClearAccountCreateAmount = 0x00010000,
};
constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);
// Export flags:
enum ExportFlags : uint32_t {
// Lifecycle control: erase the named latch and forfeit any later callback.
tfExportEraseLatch = 0x00010000,
// Committee control: erase the named immutable committee object.
tfExportEraseCommittee = 0x00020000,
};
constexpr std::uint32_t tfExportMask =
~(tfUniversal | tfExportEraseLatch | tfExportEraseCommittee);
// clang-format on
} // namespace ripple

View File

@@ -176,6 +176,7 @@ private:
std::optional<STAmount> mDelivered;
std::optional<STArray> mHookExecutions;
std::optional<STArray> mHookEmissions;
STArray mNodes;
};

View File

@@ -1,158 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright 2026 Xahau
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_VALIDATORBITSET_H_INCLUDED
#define RIPPLE_PROTOCOL_VALIDATORBITSET_H_INCLUDED
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <utility>
namespace ripple {
class ValidatedValidatorBitset
{
Blob bitset_;
std::size_t memberCount_ = 0;
std::size_t selected_ = 0;
ValidatedValidatorBitset(
Slice bitset,
std::size_t memberCount,
std::size_t selected)
: bitset_(bitset.begin(), bitset.end())
, memberCount_(memberCount)
, selected_(selected)
{
}
public:
ValidatedValidatorBitset(ValidatedValidatorBitset const&) = default;
ValidatedValidatorBitset&
operator=(ValidatedValidatorBitset const&) = default;
ValidatedValidatorBitset(ValidatedValidatorBitset&& other) noexcept
: bitset_(std::move(other.bitset_))
, memberCount_(other.memberCount_)
, selected_(other.selected_)
{
other.bitset_.clear();
other.memberCount_ = 0;
other.selected_ = 0;
}
ValidatedValidatorBitset&
operator=(ValidatedValidatorBitset&& other) noexcept
{
if (this != &other)
{
bitset_ = std::move(other.bitset_);
memberCount_ = other.memberCount_;
selected_ = other.selected_;
other.bitset_.clear();
other.memberCount_ = 0;
other.selected_ = 0;
}
return *this;
}
/** Construct an owning, validated validator bitset. */
static std::optional<ValidatedValidatorBitset>
make(Slice bitset, std::size_t memberCount);
std::size_t
selected() const
{
return selected_;
}
bool
contains(std::size_t position) const
{
if (position >= memberCount_)
return false;
auto const byte = position / 8;
return (bitset_[byte] &
static_cast<std::uint8_t>(1u << (position % 8))) != 0;
}
};
constexpr std::size_t
validatorBitsetBytes(std::size_t memberCount)
{
return memberCount / 8 + (memberCount % 8 != 0);
}
/** Validate an LSB-first bitset over a fixed canonical validator universe.
A valid bitset has exactly enough bytes for the universe and leaves every
unused high bit in the final byte clear. The returned population can be
used by a caller's profile-specific admission and quorum rules.
*/
inline std::optional<ValidatedValidatorBitset>
validateValidatorBitset(Slice bitset, std::size_t memberCount)
{
return ValidatedValidatorBitset::make(bitset, memberCount);
}
inline std::optional<ValidatedValidatorBitset>
ValidatedValidatorBitset::make(Slice bitset, std::size_t memberCount)
{
if (bitset.size() != validatorBitsetBytes(memberCount))
return std::nullopt;
auto const remainder = memberCount % 8;
if (remainder != 0)
{
auto const allowed = static_cast<std::uint8_t>((1u << remainder) - 1u);
if ((bitset[bitset.size() - 1] & static_cast<std::uint8_t>(~allowed)) !=
0)
return std::nullopt;
}
std::size_t selected = 0;
for (auto const byte : bitset)
selected += std::popcount(byte);
return ValidatedValidatorBitset{bitset, memberCount, selected};
}
template <class Predicate>
Blob
makeValidatorBitset(std::size_t memberCount, Predicate&& selected)
{
Blob bitset(validatorBitsetBytes(memberCount), 0);
for (std::size_t i = 0; i < memberCount; ++i)
{
if (selected(i))
bitset[i / 8] |= static_cast<std::uint8_t>(1u << (i % 8));
}
return bitset;
}
} // namespace ripple
#endif

View File

@@ -34,7 +34,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(WasmtimeEngine, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
@@ -66,8 +66,6 @@ XRPL_FEATURE(AMM, Supported::no, VoteBehavior::DefaultNo
XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Export, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(ConsensusEntropy, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo)
@@ -91,6 +89,8 @@ XRPL_FIX (240819, Supported::yes, VoteBehavior::DefaultYe
XRPL_FIX (NSDelete, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (XahauV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (XahauV1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Import, Supported::yes, VoteBehavior::DefaultYes)
@@ -161,8 +161,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(fixXahauV2)
XRPL_RETIRE(fixXahauV1)
XRPL_RETIRE(MultiSign)
XRPL_RETIRE(TrustSetAuth)
XRPL_RETIRE(FeeEscalation)

View File

@@ -223,23 +223,6 @@ LEDGER_ENTRY(ltURI_TOKEN, 0x0055, URIToken, uri_token, ({
{sfPreviousTxnLgrSeq, soeREQUIRED},
}))
/** The ledger object which stores consensus-derived entropy.
\note This is a singleton: only one such object exists in the ledger.
\sa keylet::consensusEntropy
*/
LEDGER_ENTRY_DUPLICATE(ltCONSENSUS_ENTROPY, 0x0058, ConsensusEntropy, consensus_entropy, ({
{sfDigest, soeREQUIRED},
{sfEntropyCount, soeREQUIRED},
{sfEntropyDenominator, soeREQUIRED},
{sfEntropyContributors, soeREQUIRED},
{sfEntropyTier, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
}))
/** A ledger object which describes an account.
\sa keylet::account
@@ -274,15 +257,11 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfImportSequence, soeOPTIONAL},
{sfGovernanceFlags, soeOPTIONAL},
{sfGovernanceMarks, soeOPTIONAL},
//@@start account-index-optional-accountroot-field
{sfAccountIndex, soeOPTIONAL},
//@@end account-index-optional-accountroot-field
{sfTouchCount, soeOPTIONAL},
{sfHookStateScale, soeOPTIONAL},
{sfCron, soeOPTIONAL},
{sfAMMID, soeOPTIONAL},
{sfExportCount, soeOPTIONAL},
{sfManifestID, soeOPTIONAL},
}))
/** A ledger object which contains a list of object identifiers.
@@ -298,7 +277,6 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
{sfTakerGetsIssuer, soeOPTIONAL}, // order book directories
{sfExchangeRate, soeOPTIONAL}, // order book directories
{sfReferenceCount, soeOPTIONAL}, // for hook state directories
{sfExportCount, soeOPTIONAL}, // live latches on Export root
{sfIndexes, soeREQUIRED},
{sfRootIndex, soeREQUIRED},
{sfIndexNext, soeOPTIONAL},
@@ -439,9 +417,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
{sfXahauActivationLgrSeq, soeOPTIONAL},
//@@start account-count-optional-fees-field
{sfAccountCount, soeOPTIONAL},
//@@end account-count-optional-fees-field
{sfNetworkID, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL},
@@ -616,58 +592,6 @@ LEDGER_ENTRY(ltDID, 0x008D, DID, did, ({
{sfPreviousTxnLgrSeq, soeREQUIRED},
}))
//@@start export-latch-ledger-entry
/** A live lifecycle latch for an Export intent.
Created when an Export intent is admitted. It records witness and
proof-of-execution arrival in either order and is erased once both facts
exist. Account-owned (pays reserve).
sfDigest stores the canonical target signing-intent hash.
\sa keylet::exportLatch
*/
LEDGER_ENTRY(ltEXPORT_LATCH, 0x5374, ExportLatch, export_latch, ({
{sfAccount, soeREQUIRED},
{sfExportCallbackFee, soeOPTIONAL},
{sfTicketSequence, soeREQUIRED},
{sfTransactionHash, soeOPTIONAL},
{sfDigest, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},
{sfExportCommitteeHash, soeREQUIRED},
{sfExportSignatureHash, soeOPTIONAL},
{sfLastLedgerSequence, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
{sfExportNode, soeOPTIONAL},
}))
//@@end export-latch-ledger-entry
/** An immutable, account-owned Export validator committee. */
LEDGER_ENTRY(ltEXPORT_COMMITTEE, 0x4563, ExportCommittee, export_committee, ({
{sfAccount, soeREQUIRED},
{sfExportCommitteeHash, soeREQUIRED},
{sfExportCommittee, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
}))
LEDGER_ENTRY(ltMANIFEST, 0x004D, Manifest, manifest_entry, ({
{sfAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfSigningPubKey, soeOPTIONAL}, /* may be absent if the master is revoked */
{sfSequence, soeREQUIRED},
/* sfVersion defaults to 0 and is omitted from the signed payload when
absent. Storing it unconditionally would change the bytes and break
signature verification when the manifest is reconstructed. */
{sfVersion, soeOPTIONAL},
{sfDomain, soeOPTIONAL},
/* The signatures are mirrored so the object is a lossless copy of the
manifest and can be independently verified, and re-served to peers,
by any node reading it (see ManifestCache::applyLedger). */
{sfMasterSignature, soeREQUIRED},
{sfSignature, soeOPTIONAL}, /* absent if the master is revoked */
{sfManifestID, soeOPTIONAL}, /* pointer to the identical other copy on the other key */
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE

View File

@@ -42,8 +42,6 @@ TYPED_SFIELD(sfTickSize, UINT8, 16)
TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17)
TYPED_SFIELD(sfHookResult, UINT8, 18)
TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19)
TYPED_SFIELD(sfSidecarType, UINT8, 20)
TYPED_SFIELD(sfEntropyTier, UINT8, 21)
// 16-bit integers (common)
TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never)
@@ -61,10 +59,6 @@ TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
TYPED_SFIELD(sfHookStateScale, UINT16, 21)
TYPED_SFIELD(sfLedgerFixType, UINT16, 22)
TYPED_SFIELD(sfHookExportCount, UINT16, 98)
TYPED_SFIELD(sfEntropyCount, UINT16, 99)
TYPED_SFIELD(sfEntropyDenominator, UINT16, 100)
TYPED_SFIELD(sfExportCount, UINT16, 101)
// 32-bit integers (common)
TYPED_SFIELD(sfNetworkID, UINT32, 1)
@@ -159,7 +153,6 @@ TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::sMD_BaseTen|SFie
TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default)
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
TYPED_SFIELD(sfExportNode, UINT64, 29)
TYPED_SFIELD(sfTouchCount, UINT64, 97)
TYPED_SFIELD(sfAccountIndex, UINT64, 98)
TYPED_SFIELD(sfAccountCount, UINT64, 99)
@@ -217,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(sfExportSignatureHash, UINT256, 38)
TYPED_SFIELD(sfExportCommitteeHash, UINT256, 39)
TYPED_SFIELD(sfManifestID, UINT256, 91)
TYPED_SFIELD(sfHookOnOutgoing, UINT256, 93)
TYPED_SFIELD(sfHookOnIncoming, UINT256, 94)
TYPED_SFIELD(sfCron, UINT256, 95)
@@ -267,7 +257,6 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
TYPED_SFIELD(sfExportCallbackFee, AMOUNT, 32)
TYPED_SFIELD(sfTrustLineRewardAccumulator,AMOUNT, 99)
// variable length (common)
@@ -304,9 +293,6 @@ TYPED_SFIELD(sfAssetClass, VL, 29)
TYPED_SFIELD(sfProvider, VL, 30)
TYPED_SFIELD(sfMPTokenMetadata, VL, 31)
TYPED_SFIELD(sfCredentialType, VL, 32)
TYPED_SFIELD(sfEntropyContributors, VL, 33)
TYPED_SFIELD(sfExportCommittee, VL, 34)
TYPED_SFIELD(sfExportContributors, VL, 35)
TYPED_SFIELD(sfHookName, VL, 97)
TYPED_SFIELD(sfRemarkValue, VL, 98)
TYPED_SFIELD(sfRemarkName, VL, 99)
@@ -379,7 +365,7 @@ UNTYPED_SFIELD(sfHook, OBJECT, 14)
// inner object (uncommon)
UNTYPED_SFIELD(sfSigner, OBJECT, 16)
UNTYPED_SFIELD(sfExportSigner, OBJECT, 17)
// 17 unused
UNTYPED_SFIELD(sfMajority, OBJECT, 18)
UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19)
UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20)
@@ -396,8 +382,6 @@ UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30)
UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31)
UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
UNTYPED_SFIELD(sfExportedTxn, OBJECT, 89)
UNTYPED_SFIELD(sfManifest, OBJECT, 90)
UNTYPED_SFIELD(sfAmountEntry, OBJECT, 91)
UNTYPED_SFIELD(sfMintURIToken, OBJECT, 92)
UNTYPED_SFIELD(sfHookEmission, OBJECT, 93)
@@ -431,7 +415,7 @@ UNTYPED_SFIELD(sfHookParameters, ARRAY, 19)
UNTYPED_SFIELD(sfHookGrants, ARRAY, 20)
UNTYPED_SFIELD(sfXChainClaimAttestations, ARRAY, 21)
UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22)
UNTYPED_SFIELD(sfExportSigners, ARRAY, 23)
// 23 unused
UNTYPED_SFIELD(sfPriceDataSeries, ARRAY, 24)
UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25)
UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26)

View File

@@ -500,23 +500,6 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
{sfDomainID, soeREQUIRED},
}))
//@@start export-transaction-types
/* User-submittable Export. Its mutually exclusive forms create an intent,
control an existing latch, or manage an immutable committee object. */
TRANSACTION(ttEXPORT, 90, Export, ({
{sfExportedTxn, soeOPTIONAL},
{sfExportCallbackFee, soeOPTIONAL},
{sfTransactionHash, soeOPTIONAL},
{sfExportCommitteeHash, soeOPTIONAL},
{sfExportCommittee, soeOPTIONAL},
}))
//@@end export-transaction-types
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
makes the SOTemplate reject the format at construction. */
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
{sfManifest, soeREQUIRED},
}))
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
TRANSACTION(ttCRON, 92, Cron, ({
{sfOwner, soeREQUIRED},
@@ -623,20 +606,3 @@ TRANSACTION(ttUNL_REPORT, 104, UNLReport, ({
{sfActiveValidator, soeOPTIONAL},
{sfImportVLKey, soeOPTIONAL},
}))
TRANSACTION(ttCONSENSUS_ENTROPY, 105, ConsensusEntropy, ({
{sfLedgerSequence, soeREQUIRED},
{sfDigest, soeREQUIRED},
{sfEntropyCount, soeREQUIRED},
{sfEntropyDenominator, soeREQUIRED},
{sfEntropyContributors, soeREQUIRED},
{sfEntropyTier, soeREQUIRED},
}))
TRANSACTION(ttEXPORT_SIGNATURES, 106, ExportSignatures, ({
{sfLedgerSequence, soeREQUIRED},
{sfTransactionHash, soeREQUIRED},
{sfExportedTxn, soeREQUIRED},
{sfExportContributors, soeREQUIRED},
{sfExportSigners, soeREQUIRED},
}))

View File

@@ -350,7 +350,6 @@ JSS(hash_mismatches); // out: catalogue
JSS(have_header); // out: InboundLedger
JSS(have_state); // out: InboundLedger
JSS(have_transactions); // out: InboundLedger
JSS(hooks); // in/out: AccountInfo
JSS(high); // out: BookChanges
JSS(highest_sequence); // out: AccountInfo
JSS(highest_ticket); // out: AccountInfo
@@ -532,9 +531,6 @@ JSS(open_ledger_fee); // out: TxQ
JSS(open_ledger_level); // out: TxQ
JSS(oracles); // in: get_aggregate_price
JSS(oracle_document_id); // in: get_aggregate_price
JSS(origin_ledger_hash); // out: NetworkOPs
JSS(origin_ledger_seq); // out: NetworkOPs
JSS(origin_txid); // out: NetworkOPs
JSS(owner); // in: LedgerEntry, out: NetworkOPs
JSS(owner_funds); // in/out: Ledger, NetworkOPs, AcceptedLedgerTx
JSS(page_index);
@@ -656,7 +652,6 @@ JSS(state_now); // in: Subscribe
JSS(status); // error
JSS(stop); // in: LedgerCleaner
JSS(stop_history_tx_only); // in: Unsubscribe, stop history tx stream
JSS(stream); // out: NetworkOPs
JSS(streams); // in: Subscribe, Unsubscribe
JSS(strict); // in: AccountCurrencies, AccountInfo
JSS(sub_index); // in: LedgerEntry
@@ -738,7 +733,6 @@ JSS(TRANSACTION_FLAGS); // out: RPC server_definitions
JSS(TRANSACTION_FLAGS_INDICES); // out: RPC server_definitions
JSS(type_hex); // out: STPathSet
JSS(unl); // out: UnlList
JSS(committee_position); // out: NetworkOPs
JSS(unlimited); // out: Connection.h
JSS(uptime); // out: GetCounts
JSS(uri); // out: ValidatorSites

View File

@@ -31,7 +31,6 @@
#include <cassert>
#include <cstring>
#include <ctime>
#include <exception>
#include <fstream>
#include <functional>
#include <iostream>
@@ -352,18 +351,9 @@ Logs::format(
if (useLocalTime)
{
try
{
auto now = std::chrono::system_clock::now();
auto local = date::make_zoned(date::current_zone(), now);
output = date::format(fmt, local);
}
catch (std::exception const&)
{
// Enhanced logging should not make startup fatal if tzdb lookup is
// unavailable or misconfigured. Fall back to UTC formatting.
output = date::format(fmt, std::chrono::system_clock::now());
}
auto now = std::chrono::system_clock::now();
auto local = date::make_zoned(date::current_zone(), now);
output = date::format(fmt, local);
}
else
{

View File

@@ -56,9 +56,9 @@ getVersionString()
(versionString == std::string("0.") + std::string("0.0") ||
versionString == std::string("0.0.0+DEBUG")))
{
std::string y = std::string(&__DATE__[7]);
std::string y = std::string(__DATE__ + 7);
std::string d = std::string(
&__DATE__[4 + (__DATE__[4] == ' ' ? 1 : 0)],
__DATE__ + 4 + (__DATE__[4] == ' ' ? 1 : 0),
__DATE__[4] == ' ' ? 1 : 2);
std::string m;
switch (__DATE__[0])

View File

@@ -1,211 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
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/protocol/ExportOriginMemo.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/Serializer.h>
#include <algorithm>
#include <exception>
#include <iterator>
#include <string>
namespace ripple::ExportOriginMemo {
namespace {
Blob const&
memoTypeBytes()
{
static Blob const value(memoType.begin(), memoType.end());
return value;
}
bool
isReserved(STObject const& memo)
{
return memo.getFName() == sfMemo && memo.isFieldPresent(sfMemoType) &&
memo.getFieldVL(sfMemoType) == memoTypeBytes();
}
STObject
cloneObject(STTx const& tx)
{
Serializer serializer;
tx.add(serializer);
SerialIter sit{serializer.slice()};
STObject result{sfGeneric};
result.set(sit);
return result;
}
Blob
serialize(Origin const& origin, std::optional<Anchor> const& anchor)
{
//@@start export-origin-memo-wire-format
Serializer serializer;
serializer.add8(version);
serializer.add8(allowedFlags);
serializer.add32(origin.sourceDomain);
serializer.add32(origin.targetDomain);
serializer.addBitString(origin.transactionHash);
if (anchor)
{
serializer.add32(anchor->ledgerSequence);
serializer.addBitString(anchor->ledgerHash);
}
return serializer.getData();
//@@end export-origin-memo-wire-format
}
Expected<STTx, Error>
append(
STTx const& base,
Origin const& origin,
std::optional<Anchor> const& anchor)
{
if (hasReservedMemo(base))
return Unexpected(Error::reservedMemoPresent);
//@@start export-origin-memo-canonical-append
auto object = cloneObject(base);
STArray memos = object.isFieldPresent(sfMemos)
? object.getFieldArray(sfMemos)
: STArray{sfMemos};
STObject memo{sfMemo};
memo.setFieldVL(sfMemoType, memoTypeBytes());
memo.setFieldVL(sfMemoData, serialize(origin, anchor));
memos.emplace_back(std::move(memo));
object.setFieldArray(sfMemos, memos);
STTx result{std::move(object)};
std::string reason;
if (!passesLocalChecks(result, reason))
return Unexpected(Error::localChecks);
return result;
//@@end export-origin-memo-canonical-append
}
Expected<Stamp, Error>
parseData(Blob const& data)
{
if (data.size() != identityBytes && data.size() != releaseBytes)
return Unexpected(Error::malformedMemo);
try
{
SerialIter sit{makeSlice(data)};
if (sit.get8() != version)
return Unexpected(Error::unsupportedVersion);
if (sit.get8() != allowedFlags)
return Unexpected(Error::unsupportedFlags);
Stamp stamp{
.origin =
Origin{
.sourceDomain = sit.get32(),
.targetDomain = sit.get32(),
.transactionHash = sit.get256()},
.anchor = std::nullopt};
if (data.size() == releaseBytes)
stamp.anchor = Anchor{sit.get32(), sit.get256()};
if (!sit.empty())
return Unexpected(Error::malformedMemo);
return stamp;
}
catch (std::exception const&)
{
return Unexpected(Error::malformedMemo);
}
}
} // namespace
bool
hasReservedMemo(STTx const& tx)
{
if (!tx.isFieldPresent(sfMemos))
return false;
auto const& memos = tx.getFieldArray(sfMemos);
return std::any_of(memos.begin(), memos.end(), isReserved);
}
Expected<STTx, Error>
identityForm(STTx const& base, Origin const& origin)
{
return append(base, origin, std::nullopt);
}
Expected<STTx, Error>
releaseForm(STTx const& base, Origin const& origin, Anchor const& anchor)
{
return append(base, origin, anchor);
}
Expected<Stamp, Error>
parse(STTx const& tx)
{
std::string reason;
if (!passesLocalChecks(tx, reason))
return Unexpected(Error::localChecks);
if (!tx.isFieldPresent(sfMemos))
return Unexpected(Error::reservedMemoMissing);
auto const& memos = tx.getFieldArray(sfMemos);
auto found = memos.end();
for (auto it = memos.begin(); it != memos.end(); ++it)
{
if (!isReserved(*it))
continue;
if (found != memos.end())
return Unexpected(Error::reservedMemoPosition);
found = it;
}
if (found == memos.end())
return Unexpected(Error::reservedMemoMissing);
if (found != std::prev(memos.end()))
return Unexpected(Error::reservedMemoPosition);
if (found->isFieldPresent(sfMemoFormat) ||
!found->isFieldPresent(sfMemoData))
return Unexpected(Error::malformedMemo);
return parseData(found->getFieldVL(sfMemoData));
}
Expected<STTx, Error>
projectIdentity(STTx const& stamped)
{
auto parsed = parse(stamped);
if (!parsed)
return Unexpected(parsed.error());
auto object = cloneObject(stamped);
auto memos = object.getFieldArray(sfMemos);
memos.erase(std::prev(memos.end()));
if (memos.empty())
object.delField(sfMemos);
else
object.setFieldArray(sfMemos, memos);
return identityForm(STTx{std::move(object)}, parsed.value().origin);
}
} // namespace ripple::ExportOriginMemo

View File

@@ -74,9 +74,6 @@ enum class LedgerNameSpace : std::uint16_t {
HOOK_DEFINITION = 'D',
EMITTED_TXN = 'E',
EMITTED_DIR = 'F',
EXPORT_LATCH = 0x5374, // St
EXPORT_COMMITTEE = 0x4563, // Ec
EXPORT_PENDING_DIR = 0x4570, // Ep
NFTOKEN_OFFER = 'q',
NFTOKEN_BUY_OFFERS = 'h',
NFTOKEN_SELL_OFFERS = 'i',
@@ -84,7 +81,6 @@ enum class LedgerNameSpace : std::uint16_t {
IMPORT_VLSEQ = 'I',
UNL_REPORT = 'R',
CRON = 'L',
CONSENSUS_ENTROPY = 'X',
AMM = 'A',
BRIDGE = LEDGER_NAMESPACE2(0x01, 'H'),
XCHAIN_CLAIM_ID = 'Q',
@@ -93,7 +89,6 @@ enum class LedgerNameSpace : std::uint16_t {
ORACLE = LEDGER_NAMESPACE2(0x01, 'R'),
MPTOKEN_ISSUANCE = '~',
MPTOKEN = 't',
MANIFEST = 'M',
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
PERMISSIONED_DOMAIN = 'm',
@@ -193,34 +188,6 @@ emittedTxn(uint256 const& id) noexcept
return {ltEMITTED_TXN, indexHash(LedgerNameSpace::EMITTED_TXN, id)};
}
//@@start export-origin-keylet
Keylet
exportLatch(AccountID const& account, uint256 const& originTxnHash) noexcept
{
return {
ltEXPORT_LATCH,
indexHash(LedgerNameSpace::EXPORT_LATCH, account, originTxnHash)};
}
//@@end export-origin-keylet
Keylet
exportCommittee(AccountID const& account, uint256 const& digest) noexcept
{
return {
ltEXPORT_COMMITTEE,
indexHash(LedgerNameSpace::EXPORT_COMMITTEE, account, digest)};
}
//@@start export-pending-directory-keylet
Keylet const&
pendingExports() noexcept
{
static Keylet const ret{
ltDIR_NODE, indexHash(LedgerNameSpace::EXPORT_PENDING_DIR)};
return ret;
}
//@@end export-pending-directory-keylet
Keylet
hook(AccountID const& id) noexcept
{
@@ -579,14 +546,6 @@ cron(uint32_t timestamp, std::optional<AccountID> const& id)
return {ltCRON, uint256::fromVoid(h)};
}
Keylet const&
consensusEntropy() noexcept
{
static Keylet const ret{
ltCONSENSUS_ENTROPY, indexHash(LedgerNameSpace::CONSENSUS_ENTROPY)};
return ret;
}
Keylet
amm(Asset const& issue1, Asset const& issue2) noexcept
{
@@ -711,12 +670,6 @@ permissionedDomain(uint256 const& domainID) noexcept
return {ltPERMISSIONED_DOMAIN, domainID};
}
Keylet
manifest(PublicKey const& pk) noexcept
{
return {ltMANIFEST, indexHash(LedgerNameSpace::MANIFEST, pk.slice())};
}
} // namespace keylet
} // namespace ripple

View File

@@ -53,13 +53,6 @@ InnerObjectFormats::InnerObjectFormats()
{sfTxnSignature, soeREQUIRED},
});
add(sfExportSigner.jsonName,
sfExportSigner.getCode(),
{
{sfSigningPubKey, soeREQUIRED},
{sfTxnSignature, soeREQUIRED},
});
add(sfMajority.jsonName,
sfMajority.getCode(),
{
@@ -85,7 +78,6 @@ InnerObjectFormats::InnerObjectFormats()
{sfHookExecutionIndex, soeREQUIRED},
{sfHookStateChangeCount, soeREQUIRED},
{sfHookEmitCount, soeREQUIRED},
{sfHookExportCount, soeOPTIONAL},
{sfFlags, soeOPTIONAL}});
add(sfHookEmission.jsonName,

View File

@@ -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 == ttCONSENSUS_ENTROPY || tt == ttEXPORT_SIGNATURES;
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
}
} // namespace ripple

View File

@@ -124,7 +124,6 @@ transResults()
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
MAKE_ERROR(tecLOCKED, "Fund is locked."),
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
MAKE_ERROR(tecEXPORT_COMMITTEE_UNAVAILABLE, "Export committee is unavailable in the parent validator view."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -152,8 +151,6 @@ transResults()
MAKE_ERROR(tefNONDIR_EMIT, "An emitted txn was injected into the ledger without a corresponding directory entry."),
MAKE_ERROR(tefIMPORT_BLACKHOLED, "Cannot import keying because target account is blackholed."),
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(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
@@ -175,7 +172,6 @@ transResults()
MAKE_ERROR(telIMPORT_VL_KEY_NOT_RECOGNISED, "Import vl key was not recognized."),
MAKE_ERROR(telCAN_NOT_QUEUE_IMPORT, "Import transaction was not able to be directly applied and cannot be queued."),
MAKE_ERROR(telENV_RPC_FAILED, "Unit test RPC failure."),
MAKE_ERROR(telEXPORT_LATCH_REQUIRED, "The imported Export callback has no matching Export latch."),
MAKE_ERROR(temMALFORMED, "Malformed transaction."),
MAKE_ERROR(temBAD_AMM_TOKENS, "Malformed: Invalid LPTokens."),

View File

@@ -18,7 +18,6 @@
//==============================================================================
#include <test/jtx.h>
#include <xrpl/protocol/ExportCommittee.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/jss.h>
@@ -470,40 +469,6 @@ public:
env.close();
}
void
testExportCommitteeCleanup(FeatureBitset features)
{
using namespace jtx;
testcase("Export committee cleanup");
Env env{*this, features | featureExport};
Account const alice("alice");
Account const becky("becky");
env.fund(XRP(10000), alice, becky);
env.close();
auto const roster = serializeExportCommittee({alice.pk()});
auto const digest = exportCommitteeHash(makeSlice(roster));
Json::Value setup;
setup[jss::TransactionType] = "Export";
setup[jss::Account] = alice.human();
setup[sfExportCommittee.jsonName] = strHex(roster);
env(setup);
env.close();
auto const committeeKey = keylet::exportCommittee(alice.id(), digest);
BEAST_EXPECT(env.closed()->exists(committeeKey));
incLgrSeqForAccDel(env, alice);
auto const acctDelFee{drops(env.current()->fees().increment)};
env(acctdelete(alice, becky), fee(acctDelFee));
env.close();
BEAST_EXPECT(!env.closed()->exists(keylet::account(alice.id())));
BEAST_EXPECT(!env.closed()->exists(committeeKey));
}
void
testResurrection(FeatureBitset features)
{
@@ -1314,7 +1279,6 @@ public:
testBasics(features);
testDirectories(features);
testOwnedTypes(features);
testExportCommitteeCleanup(features);
testResurrection(features);
testAmendmentEnable(features);
testTooManyOffers(features);

View File

@@ -88,7 +88,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = fset(account, asfTshCollect);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -110,7 +112,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = acctdelete(account, bene);
// verify hooks fee
testRPCCall(env, tx, "200000");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "200000" : "200000";
testRPCCall(env, tx, feeResult);
}
static uint256
@@ -138,7 +142,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = check::cancel(account, checkId);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -161,7 +167,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = check::cash(dest, checkId, XRP(100));
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -183,7 +191,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = check::create(account, dest, XRP(100));
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -204,7 +214,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = reward::claim(account);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -226,7 +238,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = deposit::auth(account, authed);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -248,7 +262,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = cancel(account, account, seq1);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -270,7 +286,8 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = escrow(account, dest, XRP(10));
// verify hooks fee
std::string const feeResult = "16";
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
@@ -293,7 +310,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = finish(account, account, seq1);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -315,7 +334,9 @@ class BaseFee_test : public beast::unit_test::suite
account, import::loadXpop(ImportTCAccountSet::w_seed));
// verify hooks fee
testRPCCall(env, tx, "106");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "106" : "100";
testRPCCall(env, tx, feeResult);
}
void
@@ -336,7 +357,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = invoke::invoke(account);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "16";
testRPCCall(env, tx, feeResult);
}
void
@@ -358,7 +381,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = offer_cancel(account, offerSeq);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -381,7 +406,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = offer(account, USD(1000), XRP(1000));
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -403,7 +430,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = pay(account, dest, XRP(1));
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
static uint256
@@ -439,7 +468,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = paychan::claim(account, chan, reqBal, authAmt);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -463,7 +494,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = paychan::create(account, dest, XRP(10), settleDelay, pk);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -486,7 +519,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = paychan::fund(account, chan, XRP(1));
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -517,7 +552,9 @@ class BaseFee_test : public beast::unit_test::suite
hookParams[jss::HookParameter][jss::HookParameterValue] = "DEADBEEF";
// verify hooks fee
testRPCCall(env, tx, "73022");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "73022" : "73016";
testRPCCall(env, tx, feeResult);
}
void
@@ -539,7 +576,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = regkey(account, dest);
// verify hooks fee
testRPCCall(env, tx, "0");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "0" : "0";
testRPCCall(env, tx, feeResult);
}
void
@@ -562,7 +601,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = signers(account, 2, {{signer1, 1}, {signer2, 1}});
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -583,7 +624,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = ticket::create(account, 2);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -606,7 +649,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = trust(account, USD(1000));
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -630,7 +675,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = uritoken::burn(issuer, hexid);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -655,7 +702,9 @@ class BaseFee_test : public beast::unit_test::suite
tx[jss::Amount] = "1000000";
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -679,7 +728,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = uritoken::cancel(issuer, hexid);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -706,7 +757,8 @@ class BaseFee_test : public beast::unit_test::suite
tx[jss::Amount] = "1000000";
// verify hooks fee
std::string const feeResult = "16";
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
@@ -729,7 +781,9 @@ class BaseFee_test : public beast::unit_test::suite
auto tx = uritoken::mint(account, uri);
// verify hooks fee
testRPCCall(env, tx, "16");
std::string const feeResult =
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
testRPCCall(env, tx, feeResult);
}
void
@@ -773,6 +827,7 @@ public:
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeats(sa);
testWithFeats(sa - fixXahauV1);
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,785 +0,0 @@
// This file is generated by hookz build-test-hooks
#ifndef CONSENSUSENTROPY_TEST_WASM_INCLUDED
#define CONSENSUSENTROPY_TEST_WASM_INCLUDED
#include <map>
#include <stdint.h>
#include <string>
#include <vector>
namespace ripple {
namespace test {
inline std::map<std::string, std::vector<uint8_t>> consensusentropy_test_wasm =
{
/* ==== WASM: 0 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
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 entropy_cr_dice(uint32_t sides, uint32_t min_tier);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
int64_t hook(uint32_t r)
{
_g(1,1);
// A wide range makes this a useful byte-order known answer.
int64_t result = entropy_cr_dice(1000000, 3);
// negative means error
if (result < 0)
rollback(0, 0, result);
if (result >= 1000000)
rollback(0, 0, -1);
// return the entropy_cr_dice result as the accept code
return accept(0, 0, result);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x19U, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU,
0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x02U, 0x7FU,
0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x3CU, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U,
0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U, 0x6EU,
0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU,
0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U,
0x6BU, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U,
0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U,
0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U,
0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU,
0x42U, 0x01U, 0x40U, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U,
0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U,
0x03U, 0x10U, 0x01U, 0x22U, 0x01U, 0x21U, 0x02U, 0x02U, 0x40U,
0x20U, 0x01U, 0x42U, 0x00U, 0x59U, 0x04U, 0x40U, 0x42U, 0x7FU,
0x21U, 0x02U, 0x20U, 0x01U, 0x42U, 0xC0U, 0x84U, 0x3DU, 0x54U,
0x0DU, 0x01U, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U,
0x10U, 0x02U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U,
0x01U, 0x10U, 0x03U, 0x0BU,
}},
/* ==== WASM: 1 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
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 entropy_cr_random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
int64_t hook(uint32_t r)
{
_g(1,1);
uint8_t buf[32];
for (int i = 0; GUARD(32), i < 32; ++i)
buf[i] = 0;
int64_t result = entropy_cr_random((uint32_t)buf, 32, 3);
// Should return 32 (bytes written)
if (result != 32)
rollback(0, 0, result);
// Verify buffer is not all zeroes
int nonzero = 0;
for (int i = 0; GUARD(32), i < 32; ++i)
if (buf[i] != 0) nonzero = 1;
if (!nonzero)
rollback(0, 0, -2);
return accept(0, 0, 0);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x1AU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU,
0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
0x02U, 0x3EU, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x65U,
0x6EU, 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U,
0x5FU, 0x72U, 0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x02U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU,
0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U,
0x00U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U,
0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x80U,
0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU,
0x6BU, 0x00U, 0x04U, 0x0AU, 0xCEU, 0x01U, 0x01U, 0xCBU, 0x01U,
0x02U, 0x03U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U,
0x6BU, 0x22U, 0x01U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U,
0x10U, 0x00U, 0x1AU, 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U,
0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U,
0x03U, 0x40U, 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U,
0x21U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU,
0x41U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x01U, 0x01U, 0x01U, 0x01U,
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x20U, 0x00U,
0x41U, 0x01U, 0x6AU, 0x22U, 0x00U, 0x41U, 0x20U, 0x47U, 0x0DU,
0x00U, 0x0BU, 0x20U, 0x01U, 0x41U, 0x20U, 0x41U, 0x03U, 0x10U,
0x01U, 0x22U, 0x04U, 0x42U, 0x20U, 0x52U, 0x04U, 0x40U, 0x41U,
0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x10U, 0x02U, 0x1AU, 0x0BU,
0x41U, 0x99U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U,
0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U, 0x03U, 0x40U, 0x41U,
0x99U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U,
0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU, 0x2DU, 0x00U, 0x00U,
0x21U, 0x03U, 0x41U, 0x01U, 0x20U, 0x02U, 0x20U, 0x03U, 0x1BU,
0x21U, 0x02U, 0x20U, 0x00U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x00U,
0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU, 0x20U, 0x02U, 0x45U,
0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x7EU, 0x10U,
0x02U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U,
0x10U, 0x03U, 0x21U, 0x04U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU,
0x24U, 0x00U, 0x20U, 0x04U, 0x0BU,
}},
/* ==== WASM: 2 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
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 entropy_cr_dice(uint32_t sides, uint32_t min_tier);
int64_t hook(uint32_t r)
{
_g(1,1);
int64_t r1 = entropy_cr_dice(1000000, 3);
if (r1 < 0)
rollback(0, 0, r1);
int64_t r2 = entropy_cr_dice(1000000, 3);
if (r2 < 0)
rollback(0, 0, r2);
// consecutive calls should differ (rngCallCounter)
if (r1 == r2)
rollback(0, 0, -1);
return accept(0, 0, r1 | (r2 << 20));
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x19U, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU,
0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x02U, 0x7FU,
0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x3CU, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U,
0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U, 0x6EU,
0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU,
0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U,
0x6BU, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U,
0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U,
0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U,
0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU,
0x5EU, 0x01U, 0x5CU, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U,
0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U,
0x03U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x00U, 0x53U, 0x04U,
0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x02U,
0x1AU, 0x0BU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x03U, 0x10U,
0x01U, 0x22U, 0x02U, 0x42U, 0x00U, 0x53U, 0x04U, 0x40U, 0x41U,
0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x10U, 0x02U, 0x1AU, 0x0BU,
0x20U, 0x01U, 0x20U, 0x02U, 0x51U, 0x04U, 0x40U, 0x41U, 0x00U,
0x41U, 0x00U, 0x42U, 0x7FU, 0x10U, 0x02U, 0x1AU, 0x0BU, 0x41U,
0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x42U, 0x14U, 0x86U, 0x20U,
0x01U, 0x84U, 0x10U, 0x03U, 0x0BU,
}},
/* ==== WASM: 3 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_dice(uint32_t sides, uint32_t min_tier);
int64_t hook(uint32_t r)
{
_g(1,1);
int64_t result = entropy_cr_dice(0, 3);
// entropy_cr_dice(0) should return negative error code, pass it through
return accept(0, 0, result);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU,
0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x2DU, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U,
0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U, 0x6EU,
0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU,
0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U,
0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U,
0x01U, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
0x00U, 0x03U, 0x0AU, 0x17U, 0x01U, 0x15U, 0x00U, 0x41U, 0x01U,
0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U,
0x41U, 0x00U, 0x41U, 0x03U, 0x10U, 0x01U, 0x10U, 0x02U, 0x0BU,
}},
/* ==== WASM: 4 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_status(void);
#define ENTROPY_TIER(x) (((uint64_t)(x) >> 32U) & 0xFFU)
#define ENTROPY_COUNT(x) (((uint64_t)(x) >> 16U) & 0xFFFFU)
#define ENTROPY_DENOMINATOR(x) ((uint64_t)(x) & 0xFFFFU)
int64_t hook(uint32_t r)
{
_g(1,1);
int64_t status = entropy_cr_status();
if (status < 0)
return accept(0, 0, 13);
uint64_t expected =
((uint64_t)3 << 32U) | ((uint64_t)19 << 16U) | 20U;
if ((uint64_t)status != expected)
return accept(0, 0, 14);
uint32_t tier = ENTROPY_TIER(status);
uint32_t count = ENTROPY_COUNT(status);
uint32_t denominator = ENTROPY_DENOMINATOR(status);
if (tier != 3 || count != 19 || denominator != 20)
return accept(0, 0, 15);
// Common caller-side policies: tolerate one absent, require
// 4/5 participation, and require an absolute floor of 19.
if (tier < 2 || denominator - count > 1)
return accept(0, 0, 16);
if ((uint64_t)5 * count < (uint64_t)4 * denominator)
return accept(0, 0, 17);
if (count < 19)
return accept(0, 0, 18);
return accept(0, 0, 0);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x17U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U,
0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x2FU, 0x03U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x65U, 0x6EU, 0x74U, 0x72U,
0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU, 0x73U, 0x74U,
0x61U, 0x74U, 0x75U, 0x73U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U,
0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U,
0x01U, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
0x00U, 0x03U, 0x0AU, 0x2BU, 0x01U, 0x29U, 0x01U, 0x01U, 0x7EU,
0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U,
0x41U, 0x00U, 0x42U, 0x0DU, 0x42U, 0x0EU, 0x42U, 0x00U, 0x10U,
0x01U, 0x22U, 0x01U, 0x42U, 0x94U, 0x80U, 0xCCU, 0x80U, 0x30U,
0x52U, 0x1BU, 0x20U, 0x01U, 0x42U, 0x00U, 0x53U, 0x1BU, 0x10U,
0x02U, 0x0BU,
}},
/* ==== WASM: 5 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_dice(uint32_t sides, uint32_t min_tier);
extern int64_t entropy_cr_status(void);
#define ENTROPY_TIER(x) (((uint64_t)(x) >> 32U) & 0xFFU)
#define ENTROPY_COUNT(x) (((uint64_t)(x) >> 16U) & 0xFFFFU)
#define ENTROPY_DENOMINATOR(x) ((uint64_t)(x) & 0xFFFFU)
#define TOO_LITTLE_ENTROPY (-48)
int64_t hook(uint32_t r)
{
_g(1,1);
int64_t status = entropy_cr_status();
if (status < 0)
return accept(0, 0, 20);
if (ENTROPY_TIER(status) != 1 || ENTROPY_COUNT(status) != 0 ||
ENTROPY_DENOMINATOR(status) != 0)
return accept(0, 0, 21);
int64_t allowed = entropy_cr_dice(6, 1);
if (allowed < 0 || allowed > 5)
return accept(0, 0, 22);
if (entropy_cr_dice(6, 2) != TOO_LITTLE_ENTROPY)
return accept(0, 0, 23);
return accept(0, 0, 0);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x1DU, 0x05U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU,
0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U,
0x7FU, 0x01U, 0x7EU, 0x02U, 0x45U, 0x04U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x11U, 0x65U, 0x6EU, 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U,
0x5FU, 0x63U, 0x72U, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U, 0x75U,
0x73U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U,
0x6EU, 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U,
0x5FU, 0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U,
0x00U, 0x03U, 0x03U, 0x02U, 0x01U, 0x04U, 0x05U, 0x03U, 0x01U,
0x00U, 0x01U, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU,
0x6BU, 0x00U, 0x04U, 0x0AU, 0x54U, 0x01U, 0x52U, 0x01U, 0x01U,
0x7EU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U,
0x00U, 0x41U, 0x00U, 0x02U, 0x7EU, 0x42U, 0x14U, 0x10U, 0x01U,
0x22U, 0x01U, 0x42U, 0x00U, 0x53U, 0x0DU, 0x00U, 0x1AU, 0x42U,
0x15U, 0x20U, 0x01U, 0x42U, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0x1FU, 0x83U, 0x42U, 0x80U, 0x80U, 0x80U, 0x80U, 0x10U, 0x52U,
0x0DU, 0x00U, 0x1AU, 0x42U, 0x16U, 0x41U, 0x06U, 0x41U, 0x01U,
0x10U, 0x02U, 0x42U, 0x05U, 0x56U, 0x0DU, 0x00U, 0x1AU, 0x42U,
0x17U, 0x42U, 0x00U, 0x41U, 0x06U, 0x41U, 0x02U, 0x10U, 0x02U,
0x42U, 0x50U, 0x52U, 0x1BU, 0x0BU, 0x10U, 0x03U, 0x0BU,
}},
/* ==== WASM: 6 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_dice(uint32_t sides, uint32_t min_tier);
extern int64_t entropy_cr_random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
extern int64_t entropy_cr_status(void);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
#define TOO_LITTLE_ENTROPY (-48)
int64_t hook(uint32_t r)
{
_g(1,1);
uint64_t expected =
((uint64_t)3 << 32U) | ((uint64_t)19 << 16U) | 20U;
if ((uint64_t)entropy_cr_status() != expected)
return accept(0, 0, 40);
int64_t dice_result = entropy_cr_dice(6, 1);
if (dice_result != TOO_LITTLE_ENTROPY)
return accept(0, 0, 41);
uint8_t buf[32];
for (int i = 0; GUARD(32), i < 32; ++i)
buf[i] = 0xA5;
if (entropy_cr_random((uint32_t)buf, 32, 1) != TOO_LITTLE_ENTROPY)
return accept(0, 0, 42);
for (int i = 0; GUARD(32), i < 32; ++i)
if (buf[i] != 0xA5)
return accept(0, 0, 43);
return accept(0, 0, 0);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x24U, 0x06U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U,
0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U,
0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U,
0x7EU, 0x02U, 0x5DU, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U,
0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x11U,
0x65U, 0x6EU, 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U,
0x72U, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U, 0x75U, 0x73U, 0x00U,
0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U,
0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U,
0x0FU, 0x65U, 0x6EU, 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU,
0x63U, 0x72U, 0x5FU, 0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x03U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x65U, 0x6EU, 0x74U, 0x72U,
0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU, 0x72U, 0x61U,
0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x04U, 0x03U, 0x02U, 0x01U,
0x05U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x06U, 0x08U, 0x01U,
0x7FU, 0x01U, 0x41U, 0x80U, 0x80U, 0x04U, 0x0BU, 0x07U, 0x08U,
0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x05U, 0x0AU,
0xF6U, 0x01U, 0x01U, 0xF3U, 0x01U, 0x02U, 0x01U, 0x7FU, 0x01U,
0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, 0x6BU, 0x22U, 0x01U, 0x24U,
0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x02U,
0x7EU, 0x10U, 0x01U, 0x42U, 0x94U, 0x80U, 0xCCU, 0x80U, 0x30U,
0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x28U,
0x10U, 0x02U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0x06U, 0x41U, 0x01U,
0x10U, 0x03U, 0x42U, 0x50U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U,
0x41U, 0x00U, 0x42U, 0x29U, 0x10U, 0x02U, 0x0CU, 0x01U, 0x0BU,
0x41U, 0x98U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U,
0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U, 0x03U, 0x40U, 0x41U,
0x98U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U,
0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU, 0x41U, 0xA5U, 0x01U,
0x3AU, 0x00U, 0x00U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x20U, 0x00U, 0x41U, 0x01U,
0x6AU, 0x22U, 0x00U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU,
0x41U, 0x00U, 0x41U, 0x00U, 0x02U, 0x7EU, 0x42U, 0x2AU, 0x20U,
0x01U, 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x04U, 0x42U, 0x50U,
0x52U, 0x0DU, 0x00U, 0x1AU, 0x41U, 0x9CU, 0x80U, 0x80U, 0x80U,
0x78U, 0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x21U,
0x00U, 0x02U, 0x40U, 0x03U, 0x40U, 0x41U, 0x9CU, 0x80U, 0x80U,
0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x00U,
0x20U, 0x01U, 0x6AU, 0x2DU, 0x00U, 0x00U, 0x41U, 0xA5U, 0x01U,
0x47U, 0x0DU, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x20U, 0x00U, 0x41U, 0x01U,
0x6AU, 0x22U, 0x00U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU,
0x42U, 0x00U, 0x0CU, 0x01U, 0x0BU, 0x42U, 0x2BU, 0x0BU, 0x10U,
0x02U, 0x0BU, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU,
0x24U, 0x00U, 0x20U, 0x02U, 0x0BU,
}},
/* ==== WASM: 7 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_dice(uint32_t sides, uint32_t min_tier);
int64_t hook(uint32_t r)
{
_g(1,1);
int64_t result = entropy_cr_dice(6, 4);
return accept(0, 0, result);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU,
0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x2DU, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U,
0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U, 0x6EU,
0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU,
0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U,
0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U,
0x01U, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
0x00U, 0x03U, 0x0AU, 0x17U, 0x01U, 0x15U, 0x00U, 0x41U, 0x01U,
0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U,
0x41U, 0x06U, 0x41U, 0x04U, 0x10U, 0x01U, 0x10U, 0x02U, 0x0BU,
}},
/* ==== WASM: 8 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_dice(uint32_t sides, uint32_t min_tier);
int64_t hook(uint32_t r)
{
_g(1,1);
return accept(0, 0, entropy_cr_dice(6, 3));
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU,
0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x2DU, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U,
0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U, 0x6EU,
0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU,
0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U,
0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U,
0x01U, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
0x00U, 0x03U, 0x0AU, 0x17U, 0x01U, 0x15U, 0x00U, 0x41U, 0x01U,
0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U,
0x41U, 0x06U, 0x41U, 0x03U, 0x10U, 0x01U, 0x10U, 0x02U, 0x0BU,
}},
/* ==== WASM: 9 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
int64_t hook(uint32_t r)
{
_g(1,1);
uint8_t buf[32];
return accept(0, 0, entropy_cr_random((uint32_t)buf, 32, 3));
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
0x02U, 0x2FU, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x65U,
0x6EU, 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U,
0x5FU, 0x72U, 0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x01U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U,
0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U,
0x03U, 0x01U, 0x00U, 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U,
0x41U, 0x80U, 0x80U, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U,
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x03U, 0x0AU, 0x2FU, 0x01U,
0x2DU, 0x01U, 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, 0x6BU,
0x22U, 0x00U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U,
0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U,
0x20U, 0x41U, 0x03U, 0x10U, 0x01U, 0x10U, 0x02U, 0x21U, 0x01U,
0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x24U, 0x00U, 0x20U, 0x01U,
0x0BU,
}},
/* ==== WASM: 10 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_status(void);
int64_t hook(uint32_t r)
{
_g(1,1);
return accept(0, 0, entropy_cr_status());
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x17U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U,
0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x2FU, 0x03U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x65U, 0x6EU, 0x74U, 0x72U,
0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU, 0x73U, 0x74U,
0x61U, 0x74U, 0x75U, 0x73U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U,
0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U,
0x01U, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
0x00U, 0x03U, 0x0AU, 0x13U, 0x01U, 0x11U, 0x00U, 0x41U, 0x01U,
0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U,
0x10U, 0x01U, 0x10U, 0x02U, 0x0BU,
}},
/* ==== WASM: 11 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t dice(uint32_t sides, uint32_t min_tier);
int64_t hook(uint32_t r)
{
_g(1,1);
return accept(0, 0, dice(6, 3));
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU,
0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U,
0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, 0x69U,
0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U,
0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U,
0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U,
0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x03U,
0x0AU, 0x17U, 0x01U, 0x15U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U,
0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x06U,
0x41U, 0x03U, 0x10U, 0x01U, 0x10U, 0x02U, 0x0BU,
}},
/* ==== WASM: 12 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
int64_t hook(uint32_t r)
{
_g(1,1);
uint8_t buf[32];
return accept(0, 0, random((uint32_t)buf, 32, 3));
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
0x02U, 0x24U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x72U,
0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x01U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U,
0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U,
0x00U, 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U,
0x80U, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU,
0x6FU, 0x6BU, 0x00U, 0x03U, 0x0AU, 0x2FU, 0x01U, 0x2DU, 0x01U,
0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, 0x6BU, 0x22U, 0x00U,
0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU,
0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U, 0x20U, 0x41U,
0x03U, 0x10U, 0x01U, 0x10U, 0x02U, 0x21U, 0x01U, 0x20U, 0x00U,
0x41U, 0x20U, 0x6AU, 0x24U, 0x00U, 0x20U, 0x01U, 0x0BU,
}},
/* ==== WASM: 13 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_status(void);
int64_t hook(uint32_t r)
{
_g(1,1);
return accept(0, 0, entropy_status());
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x17U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U,
0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x2CU, 0x03U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x0EU, 0x65U, 0x6EU, 0x74U, 0x72U,
0x6FU, 0x70U, 0x79U, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U, 0x75U,
0x73U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U,
0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U,
0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U,
0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x03U, 0x0AU,
0x13U, 0x01U, 0x11U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U,
0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x01U, 0x10U,
0x02U, 0x0BU,
}},
/* ==== WASM: 14 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
#define TOO_LITTLE_ENTROPY (-48)
int64_t hook(uint32_t r)
{
_g(1,1);
uint8_t buf[32];
for (int i = 0; GUARD(32), i < 32; ++i)
buf[i] = 0xA5;
if (entropy_cr_random((uint32_t)buf, 32, 4) != TOO_LITTLE_ENTROPY)
return accept(0, 0, 30);
for (int i = 0; GUARD(32), i < 32; ++i)
if (buf[i] != 0xA5)
return accept(0, 0, 31);
return accept(0, 0, 0);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
0x02U, 0x2FU, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x65U,
0x6EU, 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U,
0x5FU, 0x72U, 0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x01U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U,
0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U,
0x03U, 0x01U, 0x00U, 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U,
0x41U, 0x80U, 0x80U, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U,
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x03U, 0x0AU, 0xC7U, 0x01U,
0x01U, 0xC4U, 0x01U, 0x02U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x23U,
0x00U, 0x41U, 0x20U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x00U, 0x41U,
0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x8DU, 0x80U,
0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, 0x41U,
0x00U, 0x21U, 0x00U, 0x03U, 0x40U, 0x41U, 0x8DU, 0x80U, 0x80U,
0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x00U,
0x20U, 0x01U, 0x6AU, 0x41U, 0xA5U, 0x01U, 0x3AU, 0x00U, 0x00U,
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
0x01U, 0x01U, 0x20U, 0x00U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x00U,
0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU, 0x41U, 0x00U, 0x41U,
0x00U, 0x02U, 0x7EU, 0x42U, 0x1EU, 0x20U, 0x01U, 0x41U, 0x20U,
0x41U, 0x04U, 0x10U, 0x01U, 0x42U, 0x50U, 0x52U, 0x0DU, 0x00U,
0x1AU, 0x41U, 0x92U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U,
0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U, 0x02U, 0x40U,
0x03U, 0x40U, 0x41U, 0x92U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U,
0x21U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU,
0x2DU, 0x00U, 0x00U, 0x41U, 0xA5U, 0x01U, 0x47U, 0x0DU, 0x01U,
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
0x01U, 0x01U, 0x20U, 0x00U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x00U,
0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU, 0x42U, 0x00U, 0x0CU,
0x01U, 0x0BU, 0x42U, 0x1FU, 0x0BU, 0x10U, 0x02U, 0x21U, 0x02U,
0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, 0x24U, 0x00U, 0x20U, 0x02U,
0x0BU,
}},
/* ==== WASM: 15 ==== */
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t entropy_cr_dice(uint32_t sides, uint32_t min_tier);
extern int64_t entropy_cr_random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier);
#define INVALID_ARGUMENT (-7)
int64_t hook(uint32_t r)
{
_g(1,1);
uint8_t buf[32];
int64_t bad_min_tier = entropy_cr_dice(6, 0);
if (bad_min_tier != INVALID_ARGUMENT)
return accept(0, 0, 100);
int64_t bad_high_tier = entropy_cr_dice(6, 5);
if (bad_high_tier != INVALID_ARGUMENT)
return accept(0, 0, 101);
int64_t bad_random_low = entropy_cr_random((uint32_t)buf, 32, 0);
if (bad_random_low != INVALID_ARGUMENT)
return accept(0, 0, 102);
int64_t bad_random_high = entropy_cr_random((uint32_t)buf, 32, 5);
if (bad_random_high != INVALID_ARGUMENT)
return accept(0, 0, 103);
// Failed calls must not consume the shared RNG call counter.
// The test pins the first valid draw as a known-answer vector.
return accept(0, 0, entropy_cr_dice(1000000, 4));
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x20U, 0x05U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU,
0x7EU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U,
0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x45U, 0x04U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U, 0x6EU, 0x74U, 0x72U,
0x6FU, 0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU, 0x64U, 0x69U,
0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U,
0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U,
0x65U, 0x6EU, 0x76U, 0x11U, 0x65U, 0x6EU, 0x74U, 0x72U, 0x6FU,
0x70U, 0x79U, 0x5FU, 0x63U, 0x72U, 0x5FU, 0x72U, 0x61U, 0x6EU,
0x64U, 0x6FU, 0x6DU, 0x00U, 0x03U, 0x03U, 0x02U, 0x01U, 0x04U,
0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU,
0x01U, 0x41U, 0x80U, 0x80U, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U,
0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x93U,
0x01U, 0x01U, 0x90U, 0x01U, 0x01U, 0x01U, 0x7EU, 0x23U, 0x00U,
0x41U, 0x20U, 0x6BU, 0x22U, 0x00U, 0x24U, 0x00U, 0x41U, 0x01U,
0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x02U, 0x7EU, 0x41U, 0x06U,
0x41U, 0x00U, 0x10U, 0x01U, 0x42U, 0x79U, 0x52U, 0x04U, 0x40U,
0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0xE4U, 0x00U, 0x10U, 0x02U,
0x0CU, 0x01U, 0x0BU, 0x41U, 0x06U, 0x41U, 0x05U, 0x10U, 0x01U,
0x42U, 0x79U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U,
0x42U, 0xE5U, 0x00U, 0x10U, 0x02U, 0x0CU, 0x01U, 0x0BU, 0x20U,
0x00U, 0x41U, 0x20U, 0x41U, 0x00U, 0x10U, 0x03U, 0x42U, 0x79U,
0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0xE6U,
0x00U, 0x10U, 0x02U, 0x0CU, 0x01U, 0x0BU, 0x20U, 0x00U, 0x41U,
0x20U, 0x41U, 0x05U, 0x10U, 0x03U, 0x42U, 0x79U, 0x52U, 0x04U,
0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0xE7U, 0x00U, 0x10U,
0x02U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
0xC0U, 0x84U, 0x3DU, 0x41U, 0x04U, 0x10U, 0x01U, 0x10U, 0x02U,
0x0BU, 0x21U, 0x01U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x24U,
0x00U, 0x20U, 0x01U, 0x0BU,
}},
};
}
} // namespace ripple
#endif

View File

@@ -4422,160 +4422,216 @@ struct Escrow_test : public beast::unit_test::suite
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
Env env{*this, features};
env.fund(XRP(10000), alice, bob, gw);
env.close();
env.trust(USD(1000000), alice);
env.trust(USD(1000000), bob);
env.close();
env(pay(gw, alice, USD(10000)));
env(pay(gw, bob, USD(10000)));
env.close();
// EscrowCancel - EscrowID
for (bool const withXahauV1 : {true, false})
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
auto const amend = withXahauV1 ? features : features - fixXahauV1;
Env env{*this, amend};
env.fund(XRP(10000), alice, bob, gw);
env.close();
env.trust(USD(1000000), alice);
env.trust(USD(1000000), bob);
env.close();
env(pay(gw, alice, USD(10000)));
env(pay(gw, bob, USD(10000)));
env.close();
// withXahauV1 - no OfferSequence
env(cancel(bob, alice),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
// EscrowCancel - EscrowID
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
if (withXahauV1)
{
// withXahauV1 - no OfferSequence
env(cancel(bob, alice),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
else
{
// !withXahauV1 - OfferSequence == 0
env(cancel(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
// EscrowCancel - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
env(cancel(bob, alice), fee(1500), ter(temMALFORMED));
env.close();
// EscrowCancel - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(cancel(bob, alice), fee(1500), ter(temMALFORMED));
env.close();
// EscrowCancel - EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
auto const seq = env.seq(alice);
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(cancel(bob, alice, seq),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
// EscrowCancel - EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
auto const seq = env.seq(alice);
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(cancel(bob, alice, seq),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
// EscrowCancel - EscrowID & OfferSequence 0
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
// withXahauV1 - OfferSequence 0 == temMALFORMED
env(cancel(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
// EscrowCancel - EscrowID & OfferSequence 0
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s),
fee(1500));
env.close();
// EscrowFinish - EscrowID
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
if (withXahauV1)
{
// withXahauV1 - OfferSequence 0 == temMALFORMED
env(cancel(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
else
{
// withXahauV1 - OfferSequence 0 == tesSUCCESS
env(cancel(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
}
// withXahauV1 - no OfferSequence
env(finish(bob, alice),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
// EscrowFinish - EscrowID
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
if (withXahauV1)
{
// withXahauV1 - no OfferSequence
env(finish(bob, alice),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
else
{
// !withXahauV1 - OfferSequence == 0
env(finish(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
// EscrowFinish - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
env(finish(bob, alice), fee(1500), ter(temMALFORMED));
env.close();
// EscrowFinish - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(finish(bob, alice), fee(1500), ter(temMALFORMED));
env.close();
// EscrowFinish- EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
auto const seq = env.seq(alice);
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(finish(bob, alice, seq),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
// EscrowFinish- EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
auto const seq = env.seq(alice);
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(finish(bob, alice, seq),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
// EscrowFinish- EscrowID & OfferSequence 0
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
// withXahauV1 - OfferSequence 0 == temMALFORMED
env(finish(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
// EscrowFinish- EscrowID & OfferSequence 0
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow(alice, bob, USD(1000)),
finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
if (withXahauV1)
{
// withXahauV1 - OfferSequence 0 == temMALFORMED
env(finish(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
else
{
// !withXahauV1 - OfferSequence 0 == tesSUCCESS
env(finish(bob, alice, 0),
escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
}
}
}

View File

@@ -1,292 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
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/ledger/OpenLedger.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/misc/ValidatorKeys.h>
#include <xrpld/app/tx/apply.h>
#include <xrpl/protocol/ExportCommittee.h>
#include <xrpl/protocol/ExportLimits.h>
#include <xrpl/protocol/ExportOriginMemo.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <vector>
namespace ripple {
namespace test {
struct ExportFee_test : public beast::unit_test::suite
{
static std::unique_ptr<Config>
exportTestConfig()
{
auto cfg = jtx::envconfig(jtx::validator, "");
cfg->NETWORK_ID = 21337;
return cfg;
}
void
seedUNLReportLedger(jtx::Env& env, PublicKey const& masterKey)
{
env.app().openLedger().modify(
[&](OpenView& view, beast::Journal) -> bool {
STTx tx(ttUNL_REPORT, [&](auto& obj) {
obj.setFieldU32(sfLedgerSequence, env.current()->seq());
auto active = std::make_unique<STObject>(sfActiveValidator);
active->setFieldVL(sfPublicKey, masterKey);
obj.set(std::move(active));
});
auto const txID = tx.getTransactionID();
auto serialized = std::make_shared<Serializer>();
tx.add(*serialized);
env.app().getHashRouter().setFlags(txID, SF_PRIVATE2);
view.rawTxInsert(txID, std::move(serialized), nullptr);
return true;
});
BEAST_EXPECT(env.close(
env.now() + std::chrono::seconds{5}, std::chrono::milliseconds{0}));
BEAST_EXPECT(env.le(keylet::UNLReport()));
}
static STObject
exportedPayment(
AccountID const& source,
AccountID const& destination,
LedgerIndex const first,
LedgerIndex const last)
{
STObject target(sfExportedTxn);
target.setFieldU16(sfTransactionType, ttPAYMENT);
target.setFieldU32(sfFlags, tfFullyCanonicalSig);
target.setFieldU32(sfSequence, 0);
target.setFieldU32(sfTicketSequence, 1);
target.setFieldU32(sfNetworkID, 1);
target.setFieldU32(sfFirstLedgerSequence, first);
target.setFieldU32(sfLastLedgerSequence, last);
target.setFieldAmount(sfAmount, XRPAmount{1'000'000});
target.setFieldAmount(sfFee, XRPAmount{10});
target.setFieldVL(sfSigningPubKey, Blob{});
target.setAccountID(sfAccount, source);
target.setAccountID(sfDestination, destination);
return target;
}
static Json::Value
exportIntent(
jtx::Account const& account,
STObject const& target,
LedgerIndex const last,
Blob const& committee)
{
Json::Value tx;
tx[jss::TransactionType] = jss::Export;
tx[jss::Account] = account.human();
tx[jss::LastLedgerSequence] = last;
tx[sfExportedTxn.jsonName] = target.getJson(JsonOptions::none);
tx[sfExportCommitteeHash.jsonName] =
to_string(exportCommitteeHash(makeSlice(committee)));
tx[sfExportCommittee.jsonName] = strHex(committee);
return tx;
}
static void
addHookParameterPayload(STObject& target, std::size_t count)
{
STArray parameters(sfHookParameters);
for (std::size_t i = 0; i < count; ++i)
{
STObject parameter(sfHookParameter);
parameter.setFieldVL(
sfHookParameterName, Blob{static_cast<std::uint8_t>(i + 1)});
parameter.setFieldVL(sfHookParameterValue, Blob(256, 0xA5));
parameters.emplace_back(std::move(parameter));
}
target.setFieldArray(sfHookParameters, std::move(parameters));
}
static XRPAmount
expectedFee(ReadView const& view, STTx const& tx, std::size_t committeeSize)
{
Serializer target;
tx.peekAtField(sfExportedTxn).downcast<STObject>().add(target);
auto const witnessBytes = target.size() +
ExportLimits::feeWitnessFixedAllowanceBytes +
committeeSize * ExportLimits::feeWitnessSignerAllowanceBytes;
auto const workUnits = committeeSize *
ExportLimits::feeSharePublicationRounds *
ExportLimits::feeWorkUnitsPerSharePublication +
committeeSize * ExportLimits::feeWorkUnitsPerWitnessSignature +
((witnessBytes + ExportLimits::feeWitnessChunkBytes - 1) /
ExportLimits::feeWitnessChunkBytes) *
ExportLimits::feeWorkUnitsPerWitnessChunk;
auto const unrounded = workUnits * view.fees().base.drops() +
witnessBytes * ExportLimits::feePermanentWitnessByteDrops;
auto const surcharge =
((unrounded + ExportLimits::feeSurchargeRoundDrops - 1) /
ExportLimits::feeSurchargeRoundDrops) *
ExportLimits::feeSurchargeRoundDrops;
return view.fees().base +
XRPAmount{static_cast<std::int64_t>(surcharge)};
}
void
testIntentFeeAndEnforcement(FeatureBitset const& features)
{
testcase("Export intent fee prices publication and witness work");
using namespace jtx;
Env env{*this, exportTestConfig(), features};
Account const alice{"alice"};
Account const carol{"carol"};
env.fund(XRP(10'000), alice, carol);
env.close();
auto const& validatorKeys = env.app().getValidatorKeys();
BEAST_EXPECT(validatorKeys.keys);
if (!validatorKeys.keys)
return;
auto const committee =
serializeExportCommittee({validatorKeys.keys->masterPublicKey});
seedUNLReportLedger(env, validatorKeys.keys->masterPublicKey);
auto const sequence = env.current()->seq();
auto const last = sequence + ExportLimits::maxAdmissionWindowLedgers;
auto const target =
exportedPayment(alice.id(), carol.id(), sequence + 1, last);
auto const intent = exportIntent(alice, target, last, committee);
auto const prepared = env.jt(intent);
auto const required = calculateBaseFee(*env.current(), *prepared.stx);
auto const expected = expectedFee(*env.current(), *prepared.stx, 1);
BEAST_EXPECT(required == expected);
// Any positive surcharge rounds to at least one provisional fee unit.
BEAST_EXPECT(
required >= env.current()->fees().base +
XRPAmount{static_cast<std::int64_t>(
ExportLimits::feeSurchargeRoundDrops)});
env(intent, fee(required - drops(1)), ter(telINSUF_FEE_P));
env(intent, fee(required), ter(tesSUCCESS));
env.close();
auto oversizedTarget = exportedPayment(
alice.id(), carol.id(), env.current()->seq() + 1, last + 1);
addHookParameterPayload(oversizedTarget, 8);
auto projectedTarget = oversizedTarget;
auto const projected = ExportOriginMemo::releaseForm(
STTx{std::move(projectedTarget)},
ExportOriginMemo::Origin{21337, 1, uint256{}},
ExportOriginMemo::Anchor{0, uint256{}});
BEAST_EXPECT(projected);
if (!projected)
return;
Serializer projectedBytes;
projected.value().add(projectedBytes);
BEAST_EXPECT(
projectedBytes.size() > ExportLimits::maxExportReleaseTargetBytes);
auto const oversizedIntent =
exportIntent(alice, oversizedTarget, last + 1, committee);
auto const oversizedFee =
calculateBaseFee(*env.current(), *env.jt(oversizedIntent).stx);
env(oversizedIntent, fee(oversizedFee), ter(temMALFORMED));
}
void
testFeeShape(FeatureBitset const& features)
{
testcase("Export fee scales by committee while admin stays ordinary");
using namespace jtx;
Env env{*this, exportTestConfig(), features};
Account const alice{"alice"};
Account const carol{"carol"};
env.fund(XRP(10'000), alice, carol);
env.close();
std::vector<PublicKey> members;
members.reserve(ExportLimits::maxCommitteeMembers);
for (std::size_t i = 0; i < ExportLimits::maxCommitteeMembers; ++i)
members.push_back(randomKeyPair(KeyType::secp256k1).first);
auto const largeCommittee = serializeExportCommittee(members);
auto const oneMember = serializeExportCommittee({members.front()});
Json::Value setup;
setup[jss::TransactionType] = jss::Export;
setup[jss::Account] = alice.human();
setup[sfExportCommittee.jsonName] = strHex(largeCommittee);
auto const setupFee =
calculateBaseFee(*env.current(), *env.jt(setup).stx);
BEAST_EXPECT(setupFee == env.current()->fees().base);
Json::Value oneMemberSetup = setup;
oneMemberSetup[sfExportCommittee.jsonName] = strHex(oneMember);
env(oneMemberSetup, fee(env.current()->fees().base), ter(tesSUCCESS));
env.close();
auto const sequence = env.current()->seq();
auto const last = sequence + ExportLimits::maxAdmissionWindowLedgers;
auto const target =
exportedPayment(alice.id(), carol.id(), sequence + 1, last);
auto const smallIntent = exportIntent(alice, target, last, oneMember);
auto const largeIntent =
exportIntent(alice, target, last, largeCommittee);
auto const smallFee =
calculateBaseFee(*env.current(), *env.jt(smallIntent).stx);
auto const largeFee =
calculateBaseFee(*env.current(), *env.jt(largeIntent).stx);
auto residentIntent = smallIntent;
residentIntent.removeMember(sfExportCommittee.jsonName);
auto const residentFee =
calculateBaseFee(*env.current(), *env.jt(residentIntent).stx);
BEAST_EXPECT(largeFee > smallFee);
BEAST_EXPECT(residentFee == smallFee);
BEAST_EXPECT(
largeFee ==
expectedFee(
*env.current(),
*env.jt(largeIntent).stx,
ExportLimits::maxCommitteeMembers));
}
void
run() override
{
auto const features = jtx::supported_amendments() | featureExport;
testIntentFeeAndEnforcement(features);
testFeeShape(features);
}
};
BEAST_DEFINE_TESTSUITE(ExportFee, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,455 +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 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/tx/detail/ExportLedgerOps.h>
#include <xrpld/ledger/Sandbox.h>
#include <xrpl/protocol/Protocol.h>
#include <set>
#include <vector>
namespace ripple {
namespace test {
struct ExportLatch_test : beast::unit_test::suite
{
static std::shared_ptr<SLE>
makeLatch(AccountID const& account, std::uint32_t ordinal)
{
uint256 const origin{ordinal + 1};
auto latch =
std::make_shared<SLE>(keylet::exportLatch(account, origin));
latch->setAccountID(sfAccount, account);
latch->setFieldU32(sfTicketSequence, 50'000 + ordinal);
latch->setFieldH256(sfTransactionHash, origin);
latch->setFieldH256(sfDigest, uint256{10'000 + ordinal});
latch->setFieldU32(sfLedgerSequence, 4'000'000 + ordinal);
latch->setFieldH256(sfExportCommitteeHash, uint256{20'000 + ordinal});
return latch;
}
void
testDirectoryLifecycle()
{
testcase("enhanced latch directory lifecycle");
using namespace jtx;
Account const alice{"alice"};
Env env{*this};
env.fund(XRP(10'000), alice);
env(ticket::create(alice, dirNodeMaxEntries));
env.close();
beast::Journal j{beast::Journal::getNullSink()};
Sandbox sb{env.closed().get(), tapNONE};
std::vector<Keylet> latches;
latches.reserve(dirNodeMaxEntries + 1);
for (std::uint32_t i = 0; i <= dirNodeMaxEntries; ++i)
{
auto latch = makeLatch(alice.id(), i);
latches.emplace_back(keylet::unchecked(latch->key()));
auto const ter =
ExportLedgerOps::insertPendingExportLatch(sb, sb, latch, j);
if (!BEAST_EXPECTS(
isTesSuccess(ter),
"ordinal=" + std::to_string(i) + " ter=" + transToken(ter)))
return;
}
auto const account = sb.read(keylet::account(alice.id()));
auto const pending = sb.read(keylet::pendingExports());
BEAST_EXPECT(account);
BEAST_EXPECT(pending);
if (!account || !pending)
return;
BEAST_EXPECT(account->getFieldU16(sfExportCount) == 33);
BEAST_EXPECT(account->getFieldU32(sfOwnerCount) == 65);
BEAST_EXPECT(pending->getFieldU16(sfExportCount) == 33);
BEAST_EXPECT(pending->getFieldV256(sfIndexes).size() == 32);
auto const first = sb.read(latches.front());
auto const last = sb.read(latches.back());
BEAST_EXPECT(first && first->getFieldU64(sfOwnerNode) == 1);
BEAST_EXPECT(first && first->isFieldPresent(sfExportNode));
BEAST_EXPECT(first && first->getFieldU64(sfExportNode) == 0);
BEAST_EXPECT(last && last->getFieldU64(sfOwnerNode) == 2);
BEAST_EXPECT(last && last->getFieldU64(sfExportNode) == 1);
if (!first || !last)
return;
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::removePendingExportLink(sb, latches.front(), j)));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::removePendingExportLink(sb, latches.back(), j)));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::removePendingExportLink(sb, latches.back(), j)));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::eraseExportLatch(sb, sb, latches.front(), j)));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::eraseExportLatch(sb, sb, latches[1], j)));
auto const afterAccount = sb.read(keylet::account(alice.id()));
auto const afterPending = sb.read(keylet::pendingExports());
BEAST_EXPECT(afterAccount->getFieldU16(sfExportCount) == 31);
BEAST_EXPECT(afterAccount->getFieldU32(sfOwnerCount) == 63);
BEAST_EXPECT(afterPending->getFieldU16(sfExportCount) == 30);
Sandbox reopened{&sb};
std::set<uint256> recovered;
forEachItem(
reopened,
keylet::pendingExports(),
[&](std::shared_ptr<SLE const> const& sle) {
recovered.insert(sle->key());
});
BEAST_EXPECT(recovered.size() == 30);
std::set<uint256> expected;
for (std::size_t i = 2; i + 1 < latches.size(); ++i)
expected.insert(latches[i].key);
BEAST_EXPECT(recovered == expected);
for (std::size_t i = 2; i < latches.size(); ++i)
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::eraseExportLatch(sb, sb, latches[i], j)));
auto const finalAccount = sb.read(keylet::account(alice.id()));
auto const finalPending = sb.read(keylet::pendingExports());
BEAST_EXPECT(finalAccount->getFieldU16(sfExportCount) == 0);
BEAST_EXPECT(finalAccount->getFieldU32(sfOwnerCount) == 32);
BEAST_EXPECT(finalPending);
BEAST_EXPECT(finalPending->getFieldU16(sfExportCount) == 0);
BEAST_EXPECT(finalPending->getFieldV256(sfIndexes).empty());
BEAST_EXPECT(finalPending->getFieldU64(sfIndexNext) == 0);
BEAST_EXPECT(finalPending->getFieldU64(sfIndexPrevious) == 0);
}
void
testWitnessedLatchReleasesGlobalPendingCap()
{
testcase("witnessed latch releases global pending cap");
using namespace jtx;
Account const alice{"alice"};
Env env{*this};
env.fund(XRP(10'000), alice);
env.close();
beast::Journal j{beast::Journal::getNullSink()};
Sandbox sb{env.closed().get(), tapNONE};
std::vector<Keylet> latches;
latches.reserve(ExportLimits::maxLiveExportLatches);
for (std::uint32_t i = 0; i < ExportLimits::maxLiveExportLatches; ++i)
{
auto latch = makeLatch(alice.id(), i);
latches.emplace_back(keylet::unchecked(latch->key()));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::insertPendingExportLatch(sb, sb, latch, j)));
}
auto const accountAtCap = sb.read(keylet::account(alice.id()));
auto const rootAtCap = sb.read(keylet::pendingExports());
BEAST_EXPECT(accountAtCap);
BEAST_EXPECT(rootAtCap);
if (!accountAtCap || !rootAtCap)
return;
BEAST_EXPECT(
rootAtCap->getFieldU16(sfExportCount) ==
ExportLimits::maxLiveExportLatches);
auto const ownerCountAtCap = accountAtCap->getFieldU32(sfOwnerCount);
auto replacement =
makeLatch(alice.id(), ExportLimits::maxLiveExportLatches);
auto const replacementKey = keylet::unchecked(replacement->key());
BEAST_EXPECT(
ExportLedgerOps::insertPendingExportLatch(sb, sb, replacement, j) ==
tecDIR_FULL);
auto const accountAfterRejection = sb.read(keylet::account(alice.id()));
auto const rootAfterRejection = sb.read(keylet::pendingExports());
BEAST_EXPECT(accountAfterRejection);
BEAST_EXPECT(rootAfterRejection);
if (!accountAfterRejection || !rootAfterRejection)
return;
BEAST_EXPECT(!sb.exists(replacementKey));
BEAST_EXPECT(
accountAfterRejection->getFieldU16(sfExportCount) ==
ExportLimits::maxLiveExportLatches);
BEAST_EXPECT(
accountAfterRejection->getFieldU32(sfOwnerCount) ==
ownerCountAtCap);
BEAST_EXPECT(
rootAfterRejection->getFieldU16(sfExportCount) ==
ExportLimits::maxLiveExportLatches);
uint256 const witnessHash{99'999};
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::recordExportWitness(
sb, sb, latches.front(), witnessHash, j)));
auto const witnessed = sb.read(latches.front());
auto const accountAfterWitness = sb.read(keylet::account(alice.id()));
auto const rootAfterWitness = sb.read(keylet::pendingExports());
BEAST_EXPECT(witnessed);
BEAST_EXPECT(accountAfterWitness);
BEAST_EXPECT(rootAfterWitness);
if (!witnessed || !accountAfterWitness || !rootAfterWitness)
return;
BEAST_EXPECT(!witnessed->isFieldPresent(sfExportNode));
BEAST_EXPECT(
witnessed->getFieldH256(sfExportSignatureHash) == witnessHash);
BEAST_EXPECT(
accountAfterWitness->getFieldU16(sfExportCount) ==
ExportLimits::maxLiveExportLatches);
BEAST_EXPECT(
accountAfterWitness->getFieldU32(sfOwnerCount) == ownerCountAtCap);
BEAST_EXPECT(
rootAfterWitness->getFieldU16(sfExportCount) ==
ExportLimits::maxLiveExportLatches - 1);
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::insertPendingExportLatch(sb, sb, replacement, j)));
BEAST_EXPECT(
sb.read(keylet::pendingExports())->getFieldU16(sfExportCount) ==
ExportLimits::maxLiveExportLatches);
BEAST_EXPECT(sb.exists(latches.front()));
}
void
testRetainedLatchAcceptsBothFactOrders()
{
testcase("retained latch accepts XPOP and witness in either order");
using namespace jtx;
Account const alice{"alice"};
Env env{*this};
env.fund(XRP(10'000), alice);
env.close();
beast::Journal j{beast::Journal::getNullSink()};
Sandbox sb{env.closed().get(), tapNONE};
auto const baselineAccount = sb.read(keylet::account(alice.id()));
BEAST_EXPECT(baselineAccount);
if (!baselineAccount)
return;
auto const baselineExportCount =
ExportLedgerOps::exportLatchCount(*baselineAccount);
auto const baselineOwnerCount =
baselineAccount->getFieldU32(sfOwnerCount);
auto const baselineReserve =
sb.fees().accountReserve(baselineOwnerCount);
auto xpopFirst = makeLatch(alice.id(), 0);
auto const xpopFirstKey = keylet::unchecked(xpopFirst->key());
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::insertPendingExportLatch(sb, sb, xpopFirst, j)));
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::controlExportLatch(
sb,
sb,
alice.id(),
xpopFirst->getFieldH256(sfTransactionHash),
false,
j)));
auto const canceled = sb.read(xpopFirstKey);
BEAST_EXPECT(canceled);
if (!canceled)
return;
auto const canceledFlags = canceled->isFieldPresent(sfFlags)
? canceled->getFieldU32(sfFlags)
: std::uint32_t{0};
BEAST_EXPECT((canceledFlags & lsfExportCanceled) != 0);
BEAST_EXPECT(!canceled->isFieldPresent(sfExportNode));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::recordExportXpop(sb, sb, xpopFirstKey, j)));
auto const afterXpop = sb.read(xpopFirstKey);
BEAST_EXPECT(afterXpop);
if (!afterXpop)
return;
auto const xpopFlags = afterXpop->getFieldU32(sfFlags);
BEAST_EXPECT((xpopFlags & lsfExportCanceled) != 0);
BEAST_EXPECT((xpopFlags & lsfExportXpopSeen) != 0);
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::recordExportWitness(
sb, sb, xpopFirstKey, uint256{99'999}, j)));
BEAST_EXPECT(!sb.exists(xpopFirstKey));
auto const accountAfterWitness = sb.read(keylet::account(alice.id()));
auto const rootAfterWitness = sb.read(keylet::pendingExports());
BEAST_EXPECT(accountAfterWitness);
BEAST_EXPECT(rootAfterWitness);
if (!accountAfterWitness || !rootAfterWitness)
return;
BEAST_EXPECT(
ExportLedgerOps::exportLatchCount(*accountAfterWitness) ==
baselineExportCount);
BEAST_EXPECT(
accountAfterWitness->getFieldU32(sfOwnerCount) ==
baselineOwnerCount);
BEAST_EXPECT(
sb.fees().accountReserve(accountAfterWitness->getFieldU32(
sfOwnerCount)) == baselineReserve);
BEAST_EXPECT(ExportLedgerOps::exportLatchCount(*rootAfterWitness) == 0);
auto witnessFirst = makeLatch(alice.id(), 1);
auto const witnessFirstKey = keylet::unchecked(witnessFirst->key());
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::insertPendingExportLatch(
sb, sb, witnessFirst, j)));
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::controlExportLatch(
sb,
sb,
alice.id(),
witnessFirst->getFieldH256(sfTransactionHash),
false,
j)));
// The accepted witness may already have been materialized before
// cancellation even when consensus orders its application afterward.
uint256 const witnessHash{100'000};
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::recordExportWitness(
sb, sb, witnessFirstKey, witnessHash, j)));
auto const afterWitness = sb.read(witnessFirstKey);
BEAST_EXPECT(afterWitness);
if (!afterWitness)
return;
auto const witnessFlags = afterWitness->getFieldU32(sfFlags);
BEAST_EXPECT((witnessFlags & lsfExportCanceled) != 0);
BEAST_EXPECT(
afterWitness->getFieldH256(sfExportSignatureHash) == witnessHash);
BEAST_EXPECT(!afterWitness->isFieldPresent(sfExportNode));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::recordExportXpop(sb, sb, witnessFirstKey, j)));
BEAST_EXPECT(!sb.exists(witnessFirstKey));
auto const accountAfterXpop = sb.read(keylet::account(alice.id()));
auto const rootAfterXpop = sb.read(keylet::pendingExports());
BEAST_EXPECT(accountAfterXpop);
BEAST_EXPECT(rootAfterXpop);
if (!accountAfterXpop || !rootAfterXpop)
return;
BEAST_EXPECT(
ExportLedgerOps::exportLatchCount(*accountAfterXpop) ==
baselineExportCount);
BEAST_EXPECT(
accountAfterXpop->getFieldU32(sfOwnerCount) == baselineOwnerCount);
BEAST_EXPECT(
sb.fees().accountReserve(accountAfterXpop->getFieldU32(
sfOwnerCount)) == baselineReserve);
BEAST_EXPECT(ExportLedgerOps::exportLatchCount(*rootAfterXpop) == 0);
}
void
testExplicitEraseAndExpiryRetention()
{
testcase("explicit erase and expiry retention");
using namespace jtx;
Account const alice{"alice"};
Env env{*this};
env.fund(XRP(10'000), alice);
env.close();
beast::Journal j{beast::Journal::getNullSink()};
Sandbox sb{env.closed().get(), tapNONE};
auto const baselineAccount = sb.read(keylet::account(alice.id()));
auto const baselinePending = sb.read(keylet::pendingExports());
BEAST_EXPECT(baselineAccount);
if (!baselineAccount)
return;
auto const baselineExportCount =
ExportLedgerOps::exportLatchCount(*baselineAccount);
auto const baselineOwnerCount =
baselineAccount->getFieldU32(sfOwnerCount);
auto const baselinePendingCount = baselinePending
? ExportLedgerOps::exportLatchCount(*baselinePending)
: std::uint16_t{0};
auto erased = makeLatch(alice.id(), 0);
auto const erasedOrigin = erased->getFieldH256(sfTransactionHash);
auto const erasedKey = keylet::exportLatch(alice.id(), erasedOrigin);
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::insertPendingExportLatch(sb, sb, erased, j)));
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::controlExportLatch(
sb, sb, alice.id(), erasedOrigin, true, j)));
BEAST_EXPECT(!sb.exists(erasedKey));
auto const afterEraseAccount = sb.read(keylet::account(alice.id()));
auto const afterErasePending = sb.read(keylet::pendingExports());
BEAST_EXPECT(afterEraseAccount);
BEAST_EXPECT(afterErasePending);
if (!afterEraseAccount || !afterErasePending)
return;
BEAST_EXPECT(
ExportLedgerOps::exportLatchCount(*afterEraseAccount) ==
baselineExportCount);
BEAST_EXPECT(
afterEraseAccount->getFieldU32(sfOwnerCount) == baselineOwnerCount);
BEAST_EXPECT(
ExportLedgerOps::exportLatchCount(*afterErasePending) ==
baselinePendingCount);
auto expired = makeLatch(alice.id(), 1);
expired->setFieldU32(sfLastLedgerSequence, 100);
auto const expiredOrigin = expired->getFieldH256(sfTransactionHash);
auto const expiredKey = keylet::exportLatch(alice.id(), expiredOrigin);
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::insertPendingExportLatch(sb, sb, expired, j)));
BEAST_EXPECT(isTesSuccess(
ExportLedgerOps::pruneExpiredExportLatches(sb, sb, 101, j)));
auto const retained = sb.read(expiredKey);
auto const afterExpiryAccount = sb.read(keylet::account(alice.id()));
auto const afterExpiryPending = sb.read(keylet::pendingExports());
BEAST_EXPECT(retained);
BEAST_EXPECT(afterExpiryAccount);
BEAST_EXPECT(afterExpiryPending);
if (!retained || !afterExpiryAccount || !afterExpiryPending)
return;
BEAST_EXPECT(!retained->isFieldPresent(sfExportNode));
BEAST_EXPECT((retained->getFieldU32(sfFlags) & lsfExportCanceled) != 0);
BEAST_EXPECT(
ExportLedgerOps::exportLatchCount(*afterExpiryAccount) ==
baselineExportCount + 1);
BEAST_EXPECT(
afterExpiryAccount->getFieldU32(sfOwnerCount) ==
baselineOwnerCount + 1);
BEAST_EXPECT(
ExportLedgerOps::exportLatchCount(*afterExpiryPending) ==
baselinePendingCount);
BEAST_EXPECT(isTesSuccess(ExportLedgerOps::controlExportLatch(
sb, sb, alice.id(), expiredOrigin, true, j)));
BEAST_EXPECT(!sb.exists(expiredKey));
}
void
run() override
{
testDirectoryLifecycle();
testWitnessedLatchReleasesGlobalPendingCap();
testRetainedLatchAcceptsBothFactOrders();
testExplicitEraseAndExpiryRetention();
}
};
BEAST_DEFINE_TESTSUITE(ExportLatch, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,881 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
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/tx/detail/ExportResultBuilder.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/ExportLimits.h>
#include <xrpl/protocol/ExportOriginMemo.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Seed.h>
#include <xrpl/protocol/Sign.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/digest.h>
#include <algorithm>
#include <array>
#include <cstring>
#include <optional>
namespace ripple {
namespace test {
namespace {
uint256
makeHash(char const* label)
{
return sha512Half(Slice(label, std::strlen(label)));
}
STTx
makeSTTx(STObject const& obj)
{
Serializer s;
obj.add(s);
SerialIter sit{s.slice()};
return STTx{std::ref(sit)};
}
STTx
makeExportedPayment(AccountID const& src, AccountID const& dst)
{
STObject obj(sfExportedTxn);
obj.setFieldU16(sfTransactionType, ttPAYMENT);
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
obj.setFieldU32(sfSequence, 0);
obj.setFieldU32(sfTicketSequence, 1);
obj.setFieldU32(sfFirstLedgerSequence, 2);
obj.setFieldU32(sfLastLedgerSequence, 6);
obj.setFieldAmount(sfAmount, XRPAmount{1000000});
obj.setFieldAmount(sfFee, XRPAmount{10});
obj.setFieldVL(sfSigningPubKey, Blob{});
obj.setAccountID(sfAccount, src);
obj.setAccountID(sfDestination, dst);
return makeSTTx(obj);
}
std::optional<STTx>
makeExportedPaymentWithSize(
AccountID const& src,
AccountID const& dst,
std::size_t const serializedSize)
{
auto const base = makeExportedPayment(src, dst);
for (std::size_t payloadBytes = 0; payloadBytes <= serializedSize;
++payloadBytes)
{
auto candidate = base;
STArray memos(sfMemos);
STObject memo(sfMemo);
memo.setFieldVL(sfMemoData, Blob(payloadBytes, 0xCC));
memos.emplace_back(std::move(memo));
candidate.setFieldArray(sfMemos, std::move(memos));
auto const size = candidate.getSerializer().size();
if (size == serializedSize)
return candidate;
if (size > serializedSize)
return std::nullopt;
}
return std::nullopt;
}
STTx
makeExportedPaymentChannelClaim(
AccountID const& src,
Blob const& channelSignature)
{
STObject obj(sfExportedTxn);
obj.setFieldU16(sfTransactionType, ttPAYCHAN_CLAIM);
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
obj.setFieldU32(sfSequence, 0);
obj.setFieldU32(sfTicketSequence, 1);
obj.setFieldU32(sfFirstLedgerSequence, 2);
obj.setFieldU32(sfLastLedgerSequence, 6);
obj.setFieldAmount(sfFee, XRPAmount{10});
obj.setFieldVL(sfSigningPubKey, Blob{});
obj.setAccountID(sfAccount, src);
obj.setFieldH256(sfChannel, makeHash("payment-channel"));
obj.setFieldVL(sfSignature, channelSignature);
return makeSTTx(obj);
}
ExportResultBuilder::PositionedSignatureSnapshot
positionedSignatures(ExportResultBuilder::SignatureSnapshot const& signatures)
{
ExportResultBuilder::PositionedSignatureSnapshot result;
std::uint16_t position = 0;
for (auto const& [key, signature] : signatures)
{
result.emplace(
position++,
ExportResultBuilder::PositionedSignature{key, signature});
}
return result;
}
std::pair<PublicKey, SecretKey>
deterministicKeyPair(char const* label)
{
return generateKeyPair(KeyType::secp256k1, generateSeed(label));
}
} // namespace
class ExportResultBuilder_test : public beast::unit_test::suite
{
public:
void
testAssemblesMultiSignedTransaction()
{
testcase("assembles multisigned transaction");
auto const signerA = randomKeyPair(KeyType::secp256k1);
auto const signerB = randomKeyPair(KeyType::secp256k1);
auto const innerTx = makeExportedPayment(
calcAccountID(signerA.first), calcAccountID(signerB.first));
ExportResultBuilder::SignatureSnapshot signatures;
signatures.emplace(
signerA.first,
ExportResultBuilder::signExportedTxn(
innerTx, signerA.first, signerA.second));
signatures.emplace(
signerB.first,
ExportResultBuilder::signExportedTxn(
innerTx, signerB.first, signerB.second));
auto const multiSigned =
ExportResultBuilder::buildMultiSignedExportedTxn(
innerTx, signatures);
BEAST_EXPECT(multiSigned.getFieldVL(sfSigningPubKey).empty());
BEAST_EXPECT(multiSigned.isFieldPresent(sfSigners));
auto const& signers = multiSigned.getFieldArray(sfSigners);
BEAST_EXPECT(signers.size() == 2);
if (signers.size() == 2)
{
BEAST_EXPECT(
signers[0].getAccountID(sfAccount) <
signers[1].getAccountID(sfAccount));
}
for (auto const& signer : signers)
{
auto const pkVL = signer.getFieldVL(sfSigningPubKey);
PublicKey const pk{makeSlice(pkVL)};
auto const sigVL = signer.getFieldVL(sfTxnSignature);
auto const signerAcctID = signer.getAccountID(sfAccount);
auto const sigData = buildMultiSigningData(innerTx, signerAcctID);
BEAST_EXPECT(verify(pk, sigData.slice(), makeSlice(sigVL)));
}
auto const signedTxHash =
multiSigned.getHash(HashPrefix::transactionID);
Serializer serialized;
multiSigned.add(serialized);
SerialIter sit(serialized.slice());
STTx signedTx{std::ref(sit)};
BEAST_EXPECT(signedTx.getTransactionID() == signedTxHash);
}
void
testBuildMultiSignedExportedTxnDirect()
{
testcase("builds multisigned exported transaction directly");
auto const signerA = randomKeyPair(KeyType::secp256k1);
auto const signerB = randomKeyPair(KeyType::secp256k1);
auto const dst = randomKeyPair(KeyType::secp256k1);
auto const innerTx = makeExportedPayment(
calcAccountID(signerA.first), calcAccountID(dst.first));
ExportResultBuilder::SignatureSnapshot signatures;
signatures.emplace(signerB.first, Buffer{});
signatures.emplace(
signerA.first,
ExportResultBuilder::signExportedTxn(
innerTx, signerA.first, signerA.second));
auto multiSigned = ExportResultBuilder::buildMultiSignedExportedTxn(
innerTx, signatures);
BEAST_EXPECT(multiSigned.getFieldVL(sfSigningPubKey).empty());
BEAST_EXPECT(multiSigned.isFieldPresent(sfSigners));
auto const& signers = multiSigned.getFieldArray(sfSigners);
BEAST_EXPECT(signers.size() == 1);
if (signers.size() == 1)
{
BEAST_EXPECT(
signers[0].getAccountID(sfAccount) ==
calcAccountID(signerA.first));
BEAST_EXPECT(
makeSlice(signers[0].getFieldVL(sfSigningPubKey)) ==
signerA.first.slice());
}
ExportResultBuilder::SignatureSnapshot none;
auto unsignedMulti =
ExportResultBuilder::buildMultiSignedExportedTxn(innerTx, none);
BEAST_EXPECT(unsignedMulti.getFieldVL(sfSigningPubKey).empty());
BEAST_EXPECT(!unsignedMulti.isFieldPresent(sfSigners));
}
void
testExportIntentHashIgnoresSignerSubset()
{
testcase("export intent hash ignores signer subset");
auto const src = randomKeyPair(KeyType::secp256k1);
auto const dst = randomKeyPair(KeyType::secp256k1);
auto const otherDst = randomKeyPair(KeyType::secp256k1);
auto const signerA = randomKeyPair(KeyType::secp256k1);
auto const signerB = randomKeyPair(KeyType::secp256k1);
auto const signerC = randomKeyPair(KeyType::secp256k1);
auto const innerTx = makeExportedPayment(
calcAccountID(src.first), calcAccountID(dst.first));
ExportResultBuilder::SignatureSnapshot subsetAB;
subsetAB.emplace(
signerA.first,
ExportResultBuilder::signExportedTxn(
innerTx, signerA.first, signerA.second));
subsetAB.emplace(
signerB.first,
ExportResultBuilder::signExportedTxn(
innerTx, signerB.first, signerB.second));
ExportResultBuilder::SignatureSnapshot subsetBC;
subsetBC.emplace(
signerB.first,
ExportResultBuilder::signExportedTxn(
innerTx, signerB.first, signerB.second));
subsetBC.emplace(
signerC.first,
ExportResultBuilder::signExportedTxn(
innerTx, signerC.first, signerC.second));
auto const signedAB =
makeSTTx(ExportResultBuilder::buildMultiSignedExportedTxn(
innerTx, subsetAB));
auto const signedBC =
makeSTTx(ExportResultBuilder::buildMultiSignedExportedTxn(
innerTx, subsetBC));
BEAST_EXPECT(
signedAB.getTransactionID() != signedBC.getTransactionID());
auto const intentHash = ExportResultBuilder::exportIntentHash(innerTx);
BEAST_EXPECT(
ExportResultBuilder::exportIntentHash(signedAB) == intentHash);
BEAST_EXPECT(
ExportResultBuilder::exportIntentHash(signedBC) == intentHash);
auto singleAuthorized = innerTx;
singleAuthorized.setFieldVL(sfSigningPubKey, signerA.first.slice());
singleAuthorized.setFieldVL(sfTxnSignature, Blob{1, 2, 3});
BEAST_EXPECT(
ExportResultBuilder::exportIntentHash(singleAuthorized) ==
intentHash);
auto const otherIntent = makeExportedPayment(
calcAccountID(src.first), calcAccountID(otherDst.first));
BEAST_EXPECT(
ExportResultBuilder::exportIntentHash(otherIntent) != intentHash);
Blob const claimSignatureA{1, 2, 3};
auto const claimA = makeExportedPaymentChannelClaim(
calcAccountID(src.first), claimSignatureA);
auto const claimB = makeExportedPaymentChannelClaim(
calcAccountID(src.first), Blob{4, 5, 6});
BEAST_EXPECT(
ExportResultBuilder::exportIntentHash(claimA) ==
ExportResultBuilder::exportIntentHash(claimB));
auto const normalizedClaim =
ExportResultBuilder::buildMultiSignedExportedTxn(claimA, {});
BEAST_EXPECT(
normalizedClaim.getFieldVL(sfSignature) == claimSignatureA);
}
void
testCapsSignerArray()
{
testcase("caps exported signer array");
auto const src = randomKeyPair(KeyType::secp256k1);
auto const dst = randomKeyPair(KeyType::secp256k1);
auto const innerTx = makeExportedPayment(
calcAccountID(src.first), calcAccountID(dst.first));
ExportResultBuilder::SignatureSnapshot signatures;
while (signatures.size() < STTx::maxMultiSigners() + 4)
{
auto const signer = randomKeyPair(KeyType::secp256k1);
signatures.emplace(
signer.first,
ExportResultBuilder::signExportedTxn(
innerTx, signer.first, signer.second));
}
auto const multiSigned =
ExportResultBuilder::buildMultiSignedExportedTxn(
innerTx, signatures);
BEAST_EXPECT(multiSigned.isFieldPresent(sfSigners));
if (multiSigned.isFieldPresent(sfSigners))
{
auto const& signers = multiSigned.getFieldArray(sfSigners);
BEAST_EXPECT(signers.size() == STTx::maxMultiSigners());
for (std::size_t i = 1; i < signers.size(); ++i)
{
BEAST_EXPECT(
signers[i - 1].getAccountID(sfAccount) <
signers[i].getAccountID(sfAccount));
}
}
}
void
testSignatureWitnessRoundTrip()
{
testcase("signature witness round trip");
auto const src = randomKeyPair(KeyType::secp256k1);
auto const dst = randomKeyPair(KeyType::secp256k1);
auto const innerTx = makeExportedPayment(
calcAccountID(src.first), calcAccountID(dst.first));
auto const exportTxHash = makeHash("outer-export-witness");
ExportResultBuilder::SignatureSnapshot signatures;
while (signatures.size() < STTx::maxMultiSigners())
{
auto const signer = randomKeyPair(KeyType::secp256k1);
signatures.emplace(
signer.first,
ExportResultBuilder::signExportedTxn(
innerTx, signer.first, signer.second));
}
auto const positioned = positionedSignatures(signatures);
Blob const contributors(
STTx::maxMultiSigners() / 8, std::uint8_t{0xFF});
auto witness = ExportResultBuilder::buildSignatureWitness(
exportTxHash, innerTx, positioned, STTx::maxMultiSigners(), 654);
BEAST_EXPECT(witness.getTxnType() == ttEXPORT_SIGNATURES);
BEAST_EXPECT(witness.getFieldU32(sfLedgerSequence) == 654);
BEAST_EXPECT(witness.getFieldH256(sfTransactionHash) == exportTxHash);
BEAST_EXPECT(witness.getFieldVL(sfExportContributors) == contributors);
BEAST_EXPECT(!witness.isFieldPresent(sfSigners));
auto const& exported =
witness.peekAtField(sfExportedTxn).downcast<STObject>();
BEAST_EXPECT(exported.getFieldU16(sfTransactionType) == ttPAYMENT);
BEAST_EXPECT(!exported.isFieldPresent(sfSigners));
BEAST_EXPECT(
witness.getFieldArray(sfExportSigners).size() ==
STTx::maxMultiSigners());
auto decoded = ExportResultBuilder::signaturesFromWitness(witness);
BEAST_EXPECT(decoded);
if (decoded)
{
BEAST_EXPECT(decoded->size() == STTx::maxMultiSigners());
std::uint16_t expectedPosition = 0;
for (auto const& [position, signature] : *decoded)
{
BEAST_EXPECT(position == expectedPosition++);
auto const it = signatures.find(signature.signingKey);
BEAST_EXPECT(it != signatures.end());
if (it != signatures.end())
BEAST_EXPECT(it->second == signature.signature);
}
}
}
void
testSparseSignatureWitnessRoundTrip()
{
testcase("sparse cross-byte signature witness round trip");
auto const src = deterministicKeyPair("d-attrib-source");
auto const dst = deterministicKeyPair("d-attrib-destination");
auto const signer0 = deterministicKeyPair("d-attrib-signer-0");
auto const signer7 = deterministicKeyPair("d-attrib-signer-7");
auto const signer9 = deterministicKeyPair("d-attrib-signer-9");
auto const innerTx = makeExportedPayment(
calcAccountID(src.first), calcAccountID(dst.first));
ExportResultBuilder::PositionedSignatureSnapshot signatures;
for (auto const& [position, signer] : std::array{
std::pair{std::uint16_t{0}, std::cref(signer0)},
std::pair{std::uint16_t{7}, std::cref(signer7)},
std::pair{std::uint16_t{9}, std::cref(signer9)}})
{
signatures.emplace(
position,
ExportResultBuilder::PositionedSignature{
signer.get().first,
ExportResultBuilder::signExportedTxn(
innerTx, signer.get().first, signer.get().second)});
}
auto const witness = ExportResultBuilder::buildSignatureWitness(
makeHash("d-attrib-export"), innerTx, signatures, 10, 654);
Blob const expectedContributors{0x81, 0x02};
BEAST_EXPECT(
witness.getFieldVL(sfExportContributors) == expectedContributors);
auto const serialized = witness.getSerializer();
BEAST_EXPECT(
strHex(serialized.slice()) ==
"12006A2400000000260000028E5316E18499CA512AC0B68C103C249FF20029D4"
"6CEFCCD8E6882A481768546DCBF368400000000000000073007023028102811400"
"00000000000000000000000000000000000000E0591200002280000000240000"
"0000201A00000002201B000000062029000000016140000000000F424068400000"
"000000000A7300811431C972A33313AC474C4996CF7463C8D5648AA1B283143A9"
"D59046AB0269024A1C12118FB57F514A891BEE1F017E011732102006E9F20ADB30"
"B695D059FEA08BF342FA6BD712433B769FD8D1A40098F4780F674463044022033"
"A39198530AD920FB0F21A041E4CFD2C9509C085B541BD1058F2B71F8D3F01A02"
"206B203D44035D8D653F5F146CF05EA769FB60ABC6246D3EC42CD5B14C541B583"
"3E1E0117321030D2368AFD1505BCFDCF91815E073871917B2E276CF1999E0B781"
"E9EC681B68D774473045022100824DDF6B8F117F53F11B35EFACC608676B3A0C2"
"19591A01E446BFAE747DAE6AC02205955CB02DAA217DE0E3617E0B75E66FE5D09"
"6326C4E754DDC46DFCEB992CC394E1E0117321028B8C98F69CFF73B9D66CC108"
"8161E10E9B31AE2C075BADC4656C27D10EDE23D774473045022100C4F6FC146A"
"2406E20E527572957265FB9EAA3C2F22D40D3B5924880D8E70797602202E54B5"
"C0FDDD58D45909F7B47FB5B90E9DE3DD57A1B167E4FDA10CF48E2BFD0FE1F1");
BEAST_EXPECT(
to_string(witness.getTransactionID()) ==
"17BEE8F0DF68D5226CAA778C9DF1B768618AC36ADB0CF6A49A62248173357EF8");
SerialIter iter{serialized.slice()};
STTx roundTripped{std::ref(iter)};
BEAST_EXPECT(
roundTripped.getFieldVL(sfExportContributors) ==
expectedContributors);
BEAST_EXPECT(
roundTripped.getTransactionID() == witness.getTransactionID());
auto const decoded =
ExportResultBuilder::signaturesFromWitness(roundTripped);
BEAST_EXPECT(decoded);
if (decoded)
{
BEAST_EXPECT(decoded->size() == signatures.size());
auto expected = signatures.begin();
for (auto const& [position, signature] : *decoded)
{
BEAST_EXPECT(expected != signatures.end());
if (expected == signatures.end())
break;
BEAST_EXPECT(position == expected->first);
BEAST_EXPECT(
signature.signingKey == expected->second.signingKey);
BEAST_EXPECT(signature.signature == expected->second.signature);
++expected;
}
BEAST_EXPECT(expected == signatures.end());
}
}
void
testDestinationSignerOrderIsIndependent()
{
testcase("destination signer order is independent of witness order");
auto const src = deterministicKeyPair("d-attrib-order-source");
auto const dst = deterministicKeyPair("d-attrib-order-destination");
auto const innerTx = makeExportedPayment(
calcAccountID(src.first), calcAccountID(dst.first));
std::array signers{
deterministicKeyPair("d-attrib-order-signer-0"),
deterministicKeyPair("d-attrib-order-signer-1"),
deterministicKeyPair("d-attrib-order-signer-2")};
std::sort(
signers.begin(),
signers.end(),
[](auto const& lhs, auto const& rhs) {
return calcAccountID(lhs.first) < calcAccountID(rhs.first);
});
ExportResultBuilder::PositionedSignatureSnapshot positioned;
std::array<std::uint16_t, 3> const positions{0, 7, 9};
for (std::size_t i = 0; i < signers.size(); ++i)
{
auto const& signer = signers[signers.size() - i - 1];
positioned.emplace(
positions[i],
ExportResultBuilder::PositionedSignature{
signer.first,
ExportResultBuilder::signExportedTxn(
innerTx, signer.first, signer.second)});
}
auto const witness = ExportResultBuilder::buildSignatureWitness(
makeHash("d-attrib-order-export"), innerTx, positioned, 10, 654);
auto const& witnessEntries = witness.getFieldArray(sfExportSigners);
BEAST_EXPECT(witnessEntries.size() == signers.size());
if (witnessEntries.size() == signers.size())
{
for (std::size_t i = 0; i < witnessEntries.size(); ++i)
{
auto const key = PublicKey{
makeSlice(witnessEntries[i].getFieldVL(sfSigningPubKey))};
BEAST_EXPECT(
calcAccountID(key) ==
calcAccountID(signers[signers.size() - i - 1].first));
}
}
auto const decoded =
ExportResultBuilder::signaturesFromWitness(witness);
BEAST_EXPECT(decoded);
if (!decoded)
return;
ExportResultBuilder::SignatureSnapshot destinationSignatures;
for (auto const& [_, signature] : *decoded)
destinationSignatures.emplace(
signature.signingKey, signature.signature);
auto const assembled = ExportResultBuilder::buildMultiSignedExportedTxn(
innerTx, destinationSignatures);
auto const& destinationEntries = assembled.getFieldArray(sfSigners);
BEAST_EXPECT(destinationEntries.size() == signers.size());
if (destinationEntries.size() == signers.size())
{
for (std::size_t i = 0; i < destinationEntries.size(); ++i)
{
BEAST_EXPECT(
destinationEntries[i].getAccountID(sfAccount) ==
calcAccountID(signers[i].first));
}
}
}
void
testRejectsMalformedWitnessSigners()
{
testcase("signature witness rejects malformed signer entries");
auto const src = randomKeyPair(KeyType::secp256k1);
auto const dst = randomKeyPair(KeyType::secp256k1);
auto const signer0 = randomKeyPair(KeyType::secp256k1);
auto const signer1 = randomKeyPair(KeyType::secp256k1);
auto const innerTx = makeExportedPayment(
calcAccountID(src.first), calcAccountID(dst.first));
auto const exportTxHash = makeHash("bad-witness-signer");
ExportResultBuilder::PositionedSignatureSnapshot signatures;
signatures.emplace(
0,
ExportResultBuilder::PositionedSignature{
signer0.first,
ExportResultBuilder::signExportedTxn(
innerTx, signer0.first, signer0.second)});
signatures.emplace(
1,
ExportResultBuilder::PositionedSignature{
signer1.first,
ExportResultBuilder::signExportedTxn(
innerTx, signer1.first, signer1.second)});
auto const witness = ExportResultBuilder::buildSignatureWitness(
exportTxHash, innerTx, signatures, 2, 654);
BEAST_EXPECT(ExportResultBuilder::signaturesFromWitness(witness));
using Mutator = void (*)(STArray&);
struct MalformedCase
{
char const* name;
Mutator mutate;
};
std::array<MalformedCase, 8> const malformedCases{{
{"missing public key",
[](STArray& entries) { entries[0].delField(sfSigningPubKey); }},
{"missing signature",
[](STArray& entries) { entries[0].delField(sfTxnSignature); }},
{"extra account",
[](STArray& entries) {
entries[0].setAccountID(sfAccount, AccountID{});
}},
{"wrong entry type",
[](STArray& entries) { entries[0].setFName(sfSigner); }},
{"malformed public key",
[](STArray& entries) {
entries[0].setFieldVL(sfSigningPubKey, Blob{0x02});
}},
{"empty signature",
[](STArray& entries) {
entries[0].setFieldVL(sfTxnSignature, Blob{});
}},
{"oversized signature",
[](STArray& entries) {
entries[0].setFieldVL(
sfTxnSignature,
Blob(
ExportLimits::maxCanonicalExportSignatureBytes + 1,
0x30));
}},
{"duplicate key",
[](STArray& entries) {
entries[1].setFieldVL(
sfSigningPubKey, entries[0].getFieldVL(sfSigningPubKey));
}},
}};
for (auto const& malformedCase : malformedCases)
{
auto malformed = witness;
auto& entries = malformed.peekFieldArray(sfExportSigners);
malformedCase.mutate(entries);
BEAST_EXPECTS(
!ExportResultBuilder::signaturesFromWitness(malformed),
malformedCase.name);
}
auto malformedBitmap = ExportResultBuilder::buildSignatureWitness(
exportTxHash, innerTx, signatures, 2, 654);
malformedBitmap.setFieldVL(sfExportContributors, Blob{0x00});
BEAST_EXPECT(
!ExportResultBuilder::signaturesFromWitness(malformedBitmap));
ExportResultBuilder::PositionedSignatureSnapshot oversized;
for (std::uint16_t position = 0;
position <= ExportLimits::maxCommitteeMembers;
++position)
{
auto const key = randomKeyPair(KeyType::secp256k1).first;
oversized.emplace(
position,
ExportResultBuilder::PositionedSignature{key, Buffer{0x01}});
}
except([&] {
ExportResultBuilder::buildSignatureWitness(
exportTxHash, innerTx, oversized, oversized.size(), 654);
});
}
void
testSerializedSizeInventory()
{
//@@start export-serialized-size-inventory
testcase("serialized size inventory");
auto const src = randomKeyPair(KeyType::secp256k1);
auto const dst = randomKeyPair(KeyType::secp256k1);
auto innerTx = makeExportedPayment(
calcAccountID(src.first), calcAccountID(dst.first));
innerTx.setFieldArray(sfMemos, STArray(sfMemos, 1));
STObject memo{sfMemo};
memo.setFieldVL(
sfMemoType,
Blob{
'x',
'a',
'h',
'a',
'u',
'-',
'e',
'x',
'p',
'o',
'r',
't',
'-',
'v',
'1'});
memo.setFieldVL(sfMemoData, Blob(45, 0xA5));
innerTx.peekFieldArray(sfMemos).emplace_back(std::move(memo));
std::array<std::uint8_t, 72> signatureBytes;
signatureBytes.fill(0x5A);
ExportResultBuilder::SignatureSnapshot signatures;
while (signatures.size() < STTx::maxMultiSigners())
{
auto const signer = randomKeyPair(KeyType::secp256k1);
signatures.emplace(
signer.first,
Buffer{signatureBytes.data(), signatureBytes.size()});
}
auto const multiSigned =
ExportResultBuilder::buildMultiSignedExportedTxn(
innerTx, signatures);
auto const positioned = positionedSignatures(signatures);
auto const witness = ExportResultBuilder::buildSignatureWitness(
makeHash("size-inventory-export"),
innerTx,
positioned,
STTx::maxMultiSigners(),
654);
auto const release = ExportOriginMemo::releaseForm(
innerTx,
ExportOriginMemo::Origin{21337, 0, makeHash("size-origin")},
ExportOriginMemo::Anchor{653, makeHash("size-ledger")});
BEAST_EXPECT(release);
if (!release)
return;
auto const releaseWitness = ExportResultBuilder::buildSignatureWitness(
makeHash("size-inventory-export"),
release.value(),
positioned,
STTx::maxMultiSigners(),
654);
auto const innerBytes = innerTx.getSerializer().size();
auto const multiSignedBytes = multiSigned.getSerializer().size();
auto const selfContainedWitnessBytes = witness.getSerializer().size();
auto const releaseWitnessBytes = releaseWitness.getSerializer().size();
auto const pricedWitnessBytes = innerBytes +
ExportLimits::feeWitnessFixedAllowanceBytes +
STTx::maxMultiSigners() *
ExportLimits::feeWitnessSignerAllowanceBytes;
constexpr std::size_t legacyShareBytes = 32 + 33 + 72;
log << "Export serialized-size inventory:\n"
<< " unsigned target + issuance Memo: " << innerBytes << "\n"
<< " 32-signer target: " << multiSignedBytes << "\n"
<< " self-contained 32-signer witness: "
<< selfContainedWitnessBytes << "\n"
<< " stamped self-contained witness: " << releaseWitnessBytes
<< "\n"
<< " one legacy share blob: " << legacyShareBytes << "\n"
<< " 32 legacy share blobs: "
<< legacyShareBytes * STTx::maxMultiSigners() << std::endl;
BEAST_EXPECT(signatures.size() == STTx::maxMultiSigners());
BEAST_EXPECT(!witness.isFieldPresent(sfSigners));
auto const& exported =
witness.peekAtField(sfExportedTxn).downcast<STObject>();
BEAST_EXPECT(!exported.isFieldPresent(sfSigners));
BEAST_EXPECT(
witness.getFieldArray(sfExportSigners).size() ==
STTx::maxMultiSigners());
Blob const contributors(
STTx::maxMultiSigners() / 8, std::uint8_t{0xFF});
BEAST_EXPECT(witness.getFieldVL(sfExportContributors) == contributors);
BEAST_EXPECT(innerBytes == 163);
BEAST_EXPECT(multiSignedBytes == 4453);
BEAST_EXPECT(selfContainedWitnessBytes == 3839);
BEAST_EXPECT(pricedWitnessBytes == 4643);
BEAST_EXPECT(releaseWitnessBytes > selfContainedWitnessBytes);
BEAST_EXPECT(pricedWitnessBytes >= releaseWitnessBytes);
BEAST_EXPECT(pricedWitnessBytes <= ExportLimits::maxExportWitnessBytes);
BEAST_EXPECT(legacyShareBytes * STTx::maxMultiSigners() == 4384);
STArray oversizedMemos(sfMemos);
STObject oversizedMemo(sfMemo);
oversizedMemo.setFieldVL(sfMemoData, Blob(6'000, 0xCC));
oversizedMemos.emplace_back(std::move(oversizedMemo));
auto oversizedTarget = innerTx;
oversizedTarget.setFieldArray(sfMemos, oversizedMemos);
except([&] {
ExportResultBuilder::buildSignatureWitness(
makeHash("oversized-witness"),
oversizedTarget,
positioned,
STTx::maxMultiSigners(),
654);
});
auto oversizedWitness = witness;
auto& oversizedExported = const_cast<STObject&>(
oversizedWitness.peekAtField(sfExportedTxn).downcast<STObject>());
oversizedExported.setFieldArray(sfMemos, std::move(oversizedMemos));
BEAST_EXPECT(
!ExportResultBuilder::signaturesFromWitness(oversizedWitness));
//@@end export-serialized-size-inventory
}
void
testReleaseTargetSizeBoundary()
{
testcase("release target serialized size boundary");
auto const src = randomKeyPair(KeyType::secp256k1);
auto const dst = randomKeyPair(KeyType::secp256k1);
auto const signer = randomKeyPair(KeyType::secp256k1);
auto const atLimit = makeExportedPaymentWithSize(
calcAccountID(src.first),
calcAccountID(dst.first),
ExportLimits::maxExportReleaseTargetBytes);
auto const overLimit = makeExportedPaymentWithSize(
calcAccountID(src.first),
calcAccountID(dst.first),
ExportLimits::maxExportReleaseTargetBytes + 1);
BEAST_EXPECT(atLimit);
BEAST_EXPECT(overLimit);
if (!atLimit || !overLimit)
return;
ExportResultBuilder::PositionedSignatureSnapshot signatures;
signatures.emplace(
0,
ExportResultBuilder::PositionedSignature{
signer.first,
ExportResultBuilder::signExportedTxn(
*atLimit, signer.first, signer.second)});
auto const witness = ExportResultBuilder::buildSignatureWitness(
makeHash("target-at-limit"), *atLimit, signatures, 1, 654);
BEAST_EXPECT(ExportResultBuilder::signaturesFromWitness(witness));
except([&] {
ExportResultBuilder::buildSignatureWitness(
makeHash("target-over-limit"), *overLimit, signatures, 1, 654);
});
auto oversizedWitness = witness;
auto& embedded = const_cast<STObject&>(
oversizedWitness.peekAtField(sfExportedTxn).downcast<STObject>());
embedded.setFieldArray(sfMemos, overLimit->getFieldArray(sfMemos));
Serializer embeddedBytes;
embedded.add(embeddedBytes);
BEAST_EXPECT(
embeddedBytes.size() ==
ExportLimits::maxExportReleaseTargetBytes + 1);
BEAST_EXPECT(
!ExportResultBuilder::signaturesFromWitness(oversizedWitness));
}
void
run() override
{
testAssemblesMultiSignedTransaction();
testBuildMultiSignedExportedTxnDirect();
testExportIntentHashIgnoresSignerSubset();
testCapsSignerArray();
testSignatureWitnessRoundTrip();
testSparseSignatureWitnessRoundTrip();
testDestinationSignerOrderIsIndependent();
testRejectsMalformedWitnessSigners();
testSerializedSizeInventory();
testReleaseTargetSizeBoundary();
}
};
BEAST_DEFINE_TESTSUITE(ExportResultBuilder, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,265 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
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 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/ExportSigCollector.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/digest.h>
namespace ripple {
namespace test {
namespace {
uint256
origin(std::uint32_t value)
{
Serializer s;
s.add32(value);
return sha512Half(s.slice());
}
PublicKey
publicKey(char const* hex)
{
auto const raw = strUnHex(hex);
return PublicKey{makeSlice(*raw)};
}
Buffer
signature(std::uint8_t value)
{
std::uint8_t bytes[] = {value, std::uint8_t(value + 1)};
return Buffer{bytes, sizeof(bytes)};
}
} // namespace
class ExportSigCollector_test : public beast::unit_test::suite
{
PublicKey const keyA_ = publicKey(
"0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C05"
"2");
PublicKey const keyB_ = randomKeyPair(KeyType::secp256k1).first;
static ExportSigCollector::Contribution
contribution(
ExportSigCollector::Position position,
PublicKey const& key,
std::uint8_t sig)
{
return {position, key, signature(sig)};
}
public:
void
testAdmissionAndConflict()
{
testcase("admission and absorbing conflict");
ExportSigCollector collector;
auto const w = origin(1);
BEAST_EXPECT(collector.registerOrigin(w, 9));
auto first = collector.beginAttributedAdmission(
w, contribution(7, keyA_, 1), 10);
BEAST_EXPECT(first.result == ExportSigCollector::BeginResult::verify);
BEAST_EXPECT(first.ticket.has_value());
BEAST_EXPECT(collector.fullUnionSnapshot().empty());
if (!first.ticket)
return;
auto accepted =
collector.admitContribution(std::move(*first.ticket), true, 10);
BEAST_EXPECT(
accepted.result == ExportSigCollector::AdmitResult::accepted);
auto snapshot = collector.fullUnionSnapshot();
BEAST_EXPECT(snapshot.at(w).size() == 1);
BEAST_EXPECT(snapshot.at(w).front().position == 7);
auto duplicate = collector.beginAttributedAdmission(
w, contribution(7, keyA_, 1), 11);
BEAST_EXPECT(
duplicate.result == ExportSigCollector::BeginResult::duplicate);
auto second = collector.beginAttributedAdmission(
w, contribution(7, keyB_, 2), 11);
auto third = collector.beginAttributedAdmission(
w, contribution(7, keyA_, 3), 11);
BEAST_EXPECT(second.ticket.has_value());
BEAST_EXPECT(third.result == ExportSigCollector::BeginResult::capacity);
if (!second.ticket)
return;
auto conflicted =
collector.admitContribution(std::move(*second.ticket), true, 11);
BEAST_EXPECT(
conflicted.result == ExportSigCollector::AdmitResult::conflicted);
BEAST_EXPECT(conflicted.priorContribution.has_value());
BEAST_EXPECT(conflicted.conflictingContribution.has_value());
BEAST_EXPECT(
collector.positionStatus(w, 7) ==
ExportSigCollector::PositionStatus::conflicted);
BEAST_EXPECT(collector.fullUnionSnapshot().empty());
BEAST_EXPECT(
collector.beginAttributedAdmission(w, contribution(7, keyA_, 4), 12)
.result == ExportSigCollector::BeginResult::conflicted);
}
void
testReservationAndOriginLifecycle()
{
testcase("verification reservations and origin registration");
ExportSigCollector collector;
auto const w = origin(2);
BEAST_EXPECT(collector.registerOrigin(w, 19));
auto invalid = collector.beginAttributedAdmission(
w, contribution(3, keyA_, 10), 20);
BEAST_EXPECT(invalid.ticket.has_value());
if (!invalid.ticket)
return;
BEAST_EXPECT(
collector.admitContribution(std::move(*invalid.ticket), false, 20)
.result == ExportSigCollector::AdmitResult::invalid);
auto valid = collector.beginAttributedAdmission(
w, contribution(3, keyA_, 11), 20);
BEAST_EXPECT(valid.ticket.has_value());
if (!valid.ticket)
return;
BEAST_EXPECT(
collector.admitContribution(std::move(*valid.ticket), true, 20)
.result == ExportSigCollector::AdmitResult::accepted);
auto other = collector.beginAttributedAdmission(
w, contribution(4, keyB_, 12), 20);
BEAST_EXPECT(other.ticket.has_value());
if (!other.ticket)
return;
BEAST_EXPECT(
collector.admitContribution(std::move(*other.ticket), true, 20)
.result == ExportSigCollector::AdmitResult::accepted);
BEAST_EXPECT(collector.registerOrigin(w, 21));
BEAST_EXPECT(
collector.positionStatus(w, 3) ==
ExportSigCollector::PositionStatus::unique);
BEAST_EXPECT(collector.fullUnionSnapshot().at(w).size() == 2);
// Idempotent registration refreshes the stale-cleanup cursor.
collector.cleanupStale(277);
BEAST_EXPECT(!collector.fullUnionSnapshot().empty());
collector.cleanupStale(278);
BEAST_EXPECT(collector.fullUnionSnapshot().empty());
}
void
testMalformedBoundaries()
{
testcase("malformed contribution boundaries");
ExportSigCollector collector;
auto good = contribution(0, keyA_, 1);
BEAST_EXPECT(
collector.beginAttributedAdmission(uint256{}, good).result ==
ExportSigCollector::BeginResult::malformed);
auto const w = origin(3);
BEAST_EXPECT(
collector.beginAttributedAdmission(w, good, 1).result ==
ExportSigCollector::BeginResult::unknownOrigin);
BEAST_EXPECT(collector.registerOrigin(w, 1));
good.position = ExportLimits::maxCommitteeMembers;
BEAST_EXPECT(
collector.beginAttributedAdmission(w, good, 1).result ==
ExportSigCollector::BeginResult::malformed);
good.position = 0;
good.signature = Buffer{};
BEAST_EXPECT(
collector.beginAttributedAdmission(w, good, 1).result ==
ExportSigCollector::BeginResult::malformed);
std::vector<std::uint8_t> oversized(
ExportLimits::maxCanonicalExportSignatureBytes + 1, 0xAB);
good.signature = Buffer{oversized.data(), oversized.size()};
BEAST_EXPECT(
collector.beginAttributedAdmission(w, good, 1).result ==
ExportSigCollector::BeginResult::malformed);
good = contribution(5, keyA_, 5);
auto abandoned = collector.beginAttributedAdmission(w, good, 2);
BEAST_EXPECT(abandoned.ticket.has_value());
if (!abandoned.ticket)
return;
BEAST_EXPECT(collector.cancelAdmission(std::move(*abandoned.ticket)));
BEAST_EXPECT(
collector.beginAttributedAdmission(w, good, 2).result ==
ExportSigCollector::BeginResult::verify);
auto expired =
collector.beginAttributedAdmission(w, contribution(6, keyA_, 6), 3);
BEAST_EXPECT(expired.ticket.has_value());
BEAST_EXPECT(
collector.beginAttributedAdmission(w, contribution(6, keyB_, 7), 5)
.result == ExportSigCollector::BeginResult::verify);
collector.clear(w);
BEAST_EXPECT(
collector.beginAttributedAdmission(w, good, 7).result ==
ExportSigCollector::BeginResult::unknownOrigin);
BEAST_EXPECT(collector.registerOrigin(w, 7));
}
void
testOriginRegistrationBounds()
{
testcase("origin registration bounds");
ExportSigCollector collector;
BEAST_EXPECT(!collector.registerOrigin(uint256{}, 1));
BEAST_EXPECT(!collector.registerOrigin(origin(100), 0));
bool registeredAll = true;
for (std::size_t i = 0; i < ExportSigCollector::maxTrackedOrigins; ++i)
registeredAll =
collector.registerOrigin(
origin(static_cast<std::uint32_t>(i + 1'000)), 1) &&
registeredAll;
BEAST_EXPECT(registeredAll);
BEAST_EXPECT(!collector.registerOrigin(origin(99'999), 1));
BEAST_EXPECT(collector.registerOrigin(origin(1'000), 2));
}
void
run() override
{
testAdmissionAndConflict();
testReservationAndOriginLifecycle();
testMalformedBoundaries();
testOriginRegistrationBounds();
}
};
BEAST_DEFINE_TESTSUITE(ExportSigCollector, app, ripple);
} // namespace test
} // namespace ripple

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