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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions