Repository navigation
fix(xrp): send jsonrpc 2.0 and an id in every XRP template - #200
Conversation
The XRP templates carried no id. The router fills a missing id with null, and blockdaemon's XRP gateway rejects "id":null with 400 -32600 Invalid Request. The latest-block fetch that verification and the block tracker share fails on every attempt, so blockdaemon has never been admitted as the gk8 XRP backup and tatum serves alone. rippled accepts the 2.0 envelope; checked against blockdaemon with every template. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
🤖 Spec pipeline started on PR open — running Phase 8 → 11. Results post here as comments. View run |
|
Phase 8 — smart-router boot + multi-node method probe Spec under test:
Headline for this PR's actual change: all five FAIL rows ( Method Probe Report — rippleGenerated: 2026-10-22T14:35:00Z PR #200 under testPR #200 prepends Verdict: NO REGRESSION. All five templates were A/B tested directly (old shape without
Parse-directive & verification runtime check (Step 3.5)
Overall VERIFY (mainnet): OK — every verification that actually ran passed (archive-pruning, 3/3); the two NOT_EXERCISED checks (chain-id, base-pruning) were independently confirmed correct via manual relay and are a test-harness config artifact (addon tagging on every node-url collapsed the plain tier), not a spec defect. Overall VERIFY (testnet): FAIL — see TESTNET_VERIFY section; this is reported as the mainnet-equivalent finding for completeness even though Step 3.5 as scoped here is the mainnet pass. Addon & extension coverage (Step 1c + probes)Spec declares zero addons (
No addons declared → Method probe table (Step 4, through router at localhost:3360)42 methods total. No 429s encountered at any point; unpaced mode used throughout.
CountsPASS=31 (28 plain PASS + 3 PASS-existence: channel_verify, manifest, vault_info) FAIL=3 (path_find, subscribe, unsubscribe) SKIP=2 (submit, submit_multisigned) UNPROBED=0 WARN=6 (nft_buy_offers, nft_sell_offers, channel_authorize, nft_info, nft_history, nfts_by_issuer) TIMEOUT=0 LOG_WARN=0 Log-scan findings (probe window)Clean. One benign debug-level line surfaced the "ERR" substring inside a JSON key name (
Testnet verification pass (Step 7)TESTNET_VERIFY: FAIL
Root cause appears to be the router auto-tagging any Empirical block time (Step 8)
TeardownBoth containers removed: |
|
Phase 9 — parallel reviewers (3 independent Three fresh-context reviewers each ran
All three reviewers independently concluded that PR #200's own diff is correct and introduces no regression. Every finding below is pre-existing in the base spec on Merged, deduplicated gaps
Explicitly cleared (checked and found correct, not gaps)
Reports on disk: |
|
Phase 10 — gap synthesis + fix pass (fix log) Consolidated the three Phase-9 reviews + the Phase-8 probe report into Outcome: no edits applied to
|
| # | Sev | Gap | Auto-decision | Why |
|---|---|---|---|---|
| C1 | CRITICAL | path_find enabled in the jsonrpc collection; XRPL docs say "WebSocket API only!" |
Do not disable | The disable survives the probe-only filter on documentation evidence, but the premise behind the CRITICAL rating does not hold: a Lava jsonrpc collection serves ws as well as http (this very collection carries the SUBSCRIBE/UNSUBSCRIBE directives), so path_find is reachable over ws in principle. The doc says it is unavailable over JSON-RPC HTTP, not that the node lacks it. Setting enabled: false would foreclose the ws path for every client. Two of three reviewers rated it below CRITICAL (R2: MINOR; R1 did not raise it). Pre-existing on main, unrelated to this PR. → follow-up ticket, re-probe once the router's ws relay is fixed. |
| M1 | MEDIUM | UNSUBSCRIBE's template carries an empty "streams":[], which live rippled always answers invalidParams |
Do not edit | Real and corroborated — a catalog survey confirms every other UNSUBSCRIBE template uses a %s placeholder the router substitutes ("params":["%s"]), while XRP's is a fixed empty array. But XRPL has no subscription-id to substitute, so the catalog pattern does not map cleanly onto {"streams":[...]}, and guessing the payload could break a directive that is currently inert rather than wrong. The path is also broken router-side regardless (see R1/R2 below). Pre-existing: this PR touched the line but the empty array predates it. → follow-up with the router team on the intended substitution. |
| M2 | MEDIUM | blocks_in_finalization_proof: 3 may misclassify XRPL's consensus finality (1 is the finality-typed value for BFT) |
Do not edit | Needs a human call on whether XRPL's UNL-quorum model belongs in the fast/instant-finality bucket or the probabilistic fallback. 3 is the conservative-safe direction (more confirmations, not fewer). Pre-existing and identical on main; changing a live chain's finalization parameters inside a template-id bugfix is exactly the silent-widening failure this pipeline guards against. |
| R1 | — | Testnet XRPT pruning verification fails 3/3 and excludes the sole testnet provider |
Router-side — no spec fix exists | The verifier reads the router's own implicit ledger-close ws push ({"closed":{"ledger":{…}}}) instead of its RPC reply, because the router shares one wss connection with no message-id correlation. Reproduces identically with and without this PR's id fields and regardless of which function_tag pruning references. → router team. |
| R2 | — | Chain tracker never invokes GET_BLOCK_BY_NUM for XRP |
Router-side — no spec fix exists | kind="block_hash" tracker requests never appear; the template and parser were manually confirmed correct through the router. Tracker scheduling logic, not spec content. → router team. |
| A1 | — | pruning references GET_BLOCK_BY_NUM rather than GET_EARLIEST_BLOCK |
Report-only | Catalog survey: 13 other spec entries use GET_BLOCK_BY_NUM for pruning — an established minority pattern, not a schema violation. Phase 8 and 10b both confirmed the archive-tier check passes on live providers as written. |
| A2 | — | SUBSCRIBE has no function_template while UNSUBSCRIBE does |
Not a gap | Verified correct convention by two independent reviewers. |
| S1 | — | Move nft_info/nft_history/nfts_by_issuer into a clio add-on |
Stripped (watch-list) | Rests only on unknownCmd disagreement between rotating public upstreams — exactly the free-tier evidence the disable filter rejects. Needs a node confirmed to run vanilla rippled without the Clio report-server, or a doc URL. |
Phase 10a — offline static gates re-run (all PASS)
| Gate | Result |
|---|---|
jq syntax |
exit 0 |
check_method_schema |
PASS — 42/42 APIs schema ok, 0 FAIL |
check_unused_fields |
PASS (no removed fields) — none of the 15 fields removed in #218 are present; envelope is the canonical { "proposal": { "specs": [ … ] } } |
check_hanging_api --base |
PASS (2 INFO rows: submit, submit_multisigned lack timeout_ms — pre-existing, the base fails identically) |
check_stateful --base |
PASS, no INFO |
check_disabled_count |
0 distinct methods disabled → <!-- disabled-count: 0 --> |
Disabled-API Justifications ledger: (none — no disabled APIs). Nothing in ripple.json carries enabled: false, before or after this phase.
Corroboration found for the PR's premise
A catalog-wide survey of every function_template in a jsonrpc collection returns 165/165 now carrying an id field, with none missing one. Before this PR, XRP's five were the only exceptions — confirming the PR description's claim that "XRP is the only JSON-RPC spec with templates that lack an id."
|
Phase 10b — smoke regression re-probe
|
| Probe | Phase 8 (:main) |
Phase 10b (:latest) |
Verdict |
|---|---|---|---|
GET_BLOCKNUM |
OK | OK — smartrouter_latest_block=107829584+ climbing, fetch_latest_success 12/12/12 |
no change |
chain-id verification |
NOT_EXERCISED at startup (manually confirmed network_id: 0) |
same — manual server_info relay → network_id: 0 |
no change |
account_channels |
PASS | PASS | no change |
account_currencies |
PASS | PASS | no change |
account_info |
PASS | PASS | no change |
account_lines |
PASS | PASS | no change |
account_nfts |
PASS | PASS | no change |
archive extension |
PASS | PASS — ledger_index: 32570 via lava-extension: archive returns the correct ledger |
no change |
Post-fix verdicts: PARSE: OK (GET_BLOCKNUM gauge > 0, no parse-signature hits; GET_BLOCK_BY_NUM NOT_EXERCISED, identical to Phase 8 — the tracker never schedules it, which is the router-side finding R2, not a regression) · VERIFY: OK (archive pruning 6/6 verified successfully) · ADDONS: 1 tested-ok / 0 failed / 0 not-testable.
No 429s; probe-window log scan found zero non-benign warn/error/fatal/panic lines. One transient boot-time connection reset by peer on the pruning verification retried successfully — the same blip Phase 8 saw on that upstream.
Because Phase 10 applied zero spec edits, this pass is strictly a reproduction check: it confirms every Phase-8 verdict holds on a second router image as well. No divergence from Phase 8 was observed. Container torn down.
|
Phase 11 — final reviewer verdict:
Per the skill's gate, a tally with any remaining CRITICAL or MEDIUM gap is CHANGES REQUESTED, and this run does not upgrade it. Reading that verdict correctly matters, though, because it splits cleanly in two:
So the CHANGES REQUESTED is against the long-standing state of PR-specific regression check — cleanAll five edited templates were A/B tested old-shape vs new-shape, both direct-to-upstream and relayed through the live router ( Disabled-API justification audit — cleanZero The 8 findings — all pre-existing on
|
| # | Sev | Finding | Disposition |
|---|---|---|---|
| 1 | CRITICAL | path_find enabled on the jsonrpc collection but documented "WebSocket API only!"; ripple_path_find already covers the HTTP case |
Needs a human call. The reviewer records the same counter-consideration Phase 10 did — the collection serves ws too, so the method is reachable over ws in principle, except the router's ws relay is itself broken (finding 4). Not fixable by editing this PR. |
| 2 | MEDIUM | UNSUBSCRIBE template's empty "streams":[] always returns invalidParams |
Follow-up with the router team on the intended substitution |
| 3 | MEDIUM | blocks_in_finalization_proof: 3 may misclassify XRPL's BFT finality |
Human policy call; 3 is the conservative direction |
| 4 | MEDIUM | Testnet XRPT pruning verification fails at startup, excluding the only testnet provider |
Router-side (ws multiplexing with no id correlation) |
| 5 | MEDIUM | Chain tracker never invokes GET_BLOCK_BY_NUM for XRP |
Router-side (tracker scheduling) |
| 6 | MINOR | account_tx's primary block_parsing tracks ledger_index_min rather than the latest bound |
Follow-up |
| 7 | MINOR | New in this pass — book_changes and simulate are newer public rippled methods absent from the spec. Unconfirmed: no API-doc path was supplied, so the mechanical doc-vs-spec diff could not run |
Re-review with real API docs supplied |
| 8 | MINOR | Informational (Clio-vs-rippled heterogeneity, channel_authorize signing disabled on public nodes) |
No action |
Recommendation
Merge PR #200 on its own merits, and open a separate tracking issue for findings 1–7 — split between a spec-policy decision (1, 3, 6, 7) and a smart-router ticket (2, 4, 5). Re-running /rerun-from 8 after the router's ws-relay fix would convert findings 4 and 5 into real signal rather than harness noise.
Full report on disk: docs/ripple/SPEC_REVIEW_GAPS_final.md. All cited evidence line numbers fall inside the 851-line file, so the reviewer reviewed current state (no stale-state re-dispatch needed).
|
Phase 12 — closing summary checklist (end of run span, Phases 8–11) Legend: Endpoints used (self-researched — both URL inputs were empty)
Every URL was validated over both transports before boot. The spec declares a Empirical block time
No Auto-decisions taken at STOP-and-ask gates (unattended run, conservative default)
Net effect: Run stats — this pipeline run only (Phases 8–11)These figures cover only Phases 8–11 in this run. Phases 1–7 never ran here — this is a resumable run against an already-committed spec, and Retry commands, if needed
|
Problem
blockdaemon, the gk8 XRP backup, fails verification on every attempt and has never been let in. tatum serves XRP alone.
The XRP templates in
ripple.jsonhave noid. When the router builds a request from a template, it fills a missing id withnull. blockdaemon's XRP gateway rejects"id":null:The failing call is the latest-block fetch (
GET_BLOCKNUM). Verification and the block tracker both use it. Skipping verifications in the node config doesn't help, because the tracker would fail the same way.Change
I added
"jsonrpc":"2.0","id":1to all 5 XRP templates. XRP is the only JSON-RPC spec with templates that lack an id.Checked
I sent each new template directly to blockdaemon's XRP endpoint:
server_info,ledgeratvalidatedandledgerat a given number all return results.ledger 32570returnslgrNotFound. That's expected: the 32570 check only applies to archive, and blockdaemon isn't configured for archive.Customer requests aren't affected; the router forwards the customer's own bytes.
A router-side fix (giving built requests a real id instead of
null) will follow separately.🤖 Generated with Claude Code