Skip to content

Expose GRANDPA finality justifications over the JSON-RPC interface #3288

Description

@bkontur

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-base v1.2.0; re-verified 2026-07-02 against main 386a1f9a — unchanged)

No JSON-RPC path to a justification exists:

  • grandpa_subscribeJustificationsnot implemented.
  • grandpa_roundState — TODO stub (lib/src/json_rpc/methods.rs:426), answered with -32000 "Not implemented in smoldot yet".
  • chain_getBlockalways 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)

  1. 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_follow finalized 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.
  2. On-demand fetchchainHead_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.
  • Cover every mandatory / authority-set-change block, ideally arbitrary recent finalized blocks too.
  • 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.
  • Documented in the JSON-RPC API reference.

References


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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions