You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
smoldot verifies GRANDPA justifications internally but exposes none of them over JSON-RPC. We'd like JSON-RPC access to the raw SCALE-encoded GRANDPA justification for finalized blocks — both a live feed and an on-demand fetch for historical (mandatory) blocks.
The use case is a trustless bridge relayer running on a light client (no RPC infrastructure): it submits a finalized header + its GRANDPA justification to a target-chain pallet that re-verifies it on-chain. Everything else already works against smoldot (state_getReadProof, state_call, chain_getBlock, author_submitExtrinsic, finalized-head subscriptions) — the justification is the only missing piece. Full context: paritytech/parity-bridges-common#3270.
Current state (light-base v1.2.0; re-verified 2026-07-02 against main 386a1f9a — unchanged)
No JSON-RPC path to a justification exists:
grandpa_subscribeJustifications — not implemented.
grandpa_roundState — TODO stub (lib/src/json_rpc/methods.rs:426), answered with -32000 "Not implemented in smoldot yet".
chain_getBlock — always returns justifications: None (light-base/src/json_rpc_service/background.rs:5824-5827, "There's no way to verify the correctness of the justifications, consequently we always return an empty list").
The data, however, is already there and fetchable:
Decoded & verified in lib/src/finality/verify.rs (verify_justification); the verified bytes are then dropped when the sync service emits its Notification::Finalized (light-base/src/sync_service/substrate_compat.rs).
Carried by two protocols smoldot already speaks — the GRANDPA warp-sync proof (each fragment = header + justification of an authority-set change, lib/src/network/codec/grandpa_warp_sync.rs) and block requests with justifications (lib/src/network/codec/block_request.rs).
light-base already issues justification-bearing block requests itself: the sync service sets justifications in BlocksRequestFields when the state machine asks for it (substrate_compat.rs), and the public sync_service::block_query() / block_query_unknown_number() API accepts a justifications flag — every JSON-RPC caller merely passes false today. Warp sync is also re-armable after startup (ModeState::AwaitingWarp), not startup-only.
Full nodes retain mandatory-block justifications permanently to serve warp sync, so historical justifications are reliably fetchable (unlike pruned state).
So this is JSON-RPC plumbing of data smoldot already verifies / can fetch, not new networking or verification. For the on-demand path, the only genuinely new piece is verifying a historically fetched justification (against the authority set active at that height) before returning it — or explicitly returning it unverified for the consumer's own verifier, as the bridge use case re-verifies on-chain anyway.
Proposed API (one feature, two access patterns)
Live notification — when smoldot finalizes via a justification it just verified, surface the SCALE-encoded justification + consensus engine id ([u8; 4], e.g. FRNK). Preferably on the chainHead_v1_followfinalized event; a dedicated chainHead_v1_finalityProofs subscription is an alternative. Note: Report full finalized ancestry in chainHead Finalized events #3282 (merged) now reports the full per-block finalized ancestry in the finalized event — exactly the per-block shape a justification payload can attach to.
On-demand fetch — chainHead_v1_finalityProof(followSubscriptionId, blockHash) (or a grandpa_proveFinality-style call) returning the justification / warp-proof fragments for a requested finalized block, including historical mandatory blocks, via block-request / warp-sync under the hood (the sync_service::block_query() + BlocksRequestFields { justifications: true } machinery above).
Spec-side: this maps onto the open proposal paritytech/json-rpc-interface-spec#140 ("chainHead: Fetch block justifications"), which stalled on the question of a concrete use case — the bridge relayer above is that use case. We're happy to help drive the spec discussion there in parallel.
Requirements
Return raw SCALE-encoded justification bytes + engine id, submittable verbatim to an on-chain verifier.
On-demand variant must reach historical mandatory blocks.
Integrate with chainHead_v1_follow pinning where applicable.
Non-goals
BEEFY justifications (analogous, separate).
On-chain verification semantics (lives in the consumer).
Acceptance criteria
A consumer can obtain, live and on demand (incl. historical mandatory blocks), the SCALE-encoded GRANDPA justification + engine id for finalized blocks.
The justification suffices for an independent GRANDPA verifier to verify finality against the authority set.
Edited 2026-07-02: re-verified all citations against main 386a1f9a; added the existing internal on-demand machinery (block_query + BlocksRequestFields { justifications }), the #3282 attachment point, and the json-rpc-interface-spec#140 spec hook.
Summary
smoldot verifies GRANDPA justifications internally but exposes none of them over JSON-RPC. We'd like JSON-RPC access to the raw SCALE-encoded GRANDPA justification for finalized blocks — both a live feed and an on-demand fetch for historical (mandatory) blocks.
The use case is a trustless bridge relayer running on a light client (no RPC infrastructure): it submits a finalized header + its GRANDPA justification to a target-chain pallet that re-verifies it on-chain. Everything else already works against smoldot (
state_getReadProof,state_call,chain_getBlock,author_submitExtrinsic, finalized-head subscriptions) — the justification is the only missing piece. Full context: paritytech/parity-bridges-common#3270.Current state (
light-basev1.2.0; re-verified 2026-07-02 against main386a1f9a— unchanged)No JSON-RPC path to a justification exists:
grandpa_subscribeJustifications— not implemented.grandpa_roundState— TODO stub (lib/src/json_rpc/methods.rs:426), answered with-32000 "Not implemented in smoldot yet".chain_getBlock— always returnsjustifications: None(light-base/src/json_rpc_service/background.rs:5824-5827, "There's no way to verify the correctness of the justifications, consequently we always return an empty list").The data, however, is already there and fetchable:
lib/src/finality/verify.rs(verify_justification); the verified bytes are then dropped when the sync service emits itsNotification::Finalized(light-base/src/sync_service/substrate_compat.rs).lib/src/network/codec/grandpa_warp_sync.rs) and block requests with justifications (lib/src/network/codec/block_request.rs).justificationsinBlocksRequestFieldswhen the state machine asks for it (substrate_compat.rs), and the publicsync_service::block_query()/block_query_unknown_number()API accepts ajustificationsflag — every JSON-RPC caller merely passesfalsetoday. Warp sync is also re-armable after startup (ModeState::AwaitingWarp), not startup-only.So this is JSON-RPC plumbing of data smoldot already verifies / can fetch, not new networking or verification. For the on-demand path, the only genuinely new piece is verifying a historically fetched justification (against the authority set active at that height) before returning it — or explicitly returning it unverified for the consumer's own verifier, as the bridge use case re-verifies on-chain anyway.
Proposed API (one feature, two access patterns)
[u8; 4], e.g.FRNK). Preferably on thechainHead_v1_followfinalizedevent; a dedicatedchainHead_v1_finalityProofssubscription is an alternative. Note: Report full finalized ancestry in chainHead Finalized events #3282 (merged) now reports the full per-block finalized ancestry in thefinalizedevent — exactly the per-block shape a justification payload can attach to.chainHead_v1_finalityProof(followSubscriptionId, blockHash)(or agrandpa_proveFinality-style call) returning the justification / warp-proof fragments for a requested finalized block, including historical mandatory blocks, via block-request / warp-sync under the hood (thesync_service::block_query()+BlocksRequestFields { justifications: true }machinery above).Spec-side: this maps onto the open proposal paritytech/json-rpc-interface-spec#140 ("chainHead: Fetch block justifications"), which stalled on the question of a concrete use case — the bridge relayer above is that use case. We're happy to help drive the spec discussion there in parallel.
Requirements
chainHead_v1_followpinning where applicable.Non-goals
Acceptance criteria
References
chainHead_v1_follow— the natural attachment point for the live feed)Edited 2026-07-02: re-verified all citations against main
386a1f9a; added the existing internal on-demand machinery (block_query+BlocksRequestFields { justifications }), the #3282 attachment point, and the json-rpc-interface-spec#140 spec hook.