Skip to content

gRPC: Implement FetchBlock (UTxO RPC SyncService) in cardano-rpc #1216

@carbolymer

Description

@carbolymer

Summary

Implement the FetchBlock RPC method from the UTxO RPC SyncService spec, allowing clients to retrieve full block CBOR by chain point (slot + hash).
This depends on the node kernel access work proposed in ADR-019.

Motivation

External demand

Blockfrost currently depends on cardano-db-sync's tx_cbor table to serve raw transaction CBOR.
On mainnet that table alone is around 200 GB.
When the caller already knows the slot number (which db-sync's block table provides), the node itself can serve the block CBOR cheaply from its immutable DB.
This would let downstream services drop the tx_cbor index from their stack while retaining the ability to return raw transaction bytes.

Spec coverage

The UTxO RPC SyncService is entirely unimplemented in cardano-rpc today.
FetchBlock is the foundational query of that service and a prerequisite for DumpHistory, FollowTip, and ReadTip.

Architectural fit

The node already supports O(1) block-by-point lookup via ChainDB.getBlockComponent, which reads from the slot-indexed ImmutableDB (with VolatileDB fallback for recent blocks).
Only the wire surface and the in-process access path are missing.

Background

What the spec requires

FetchBlockRequest takes a list of BlockRef (slot + hash) and returns AnyChainBlock entries.
BlockRef carries slot, hash, height, and timestamp; the slot + hash pair is the minimum the node needs to locate a block.

Why ReadTx is out of scope

ReadTx (lookup by transaction hash alone) is intentionally not part of this work.
The node does not maintain a tx-hash to block-point index.
Implementing that would require building a new indexing subsystem, duplicating part of cardano-db-sync's role.
FetchBlock is the right primitive: callers who know the slot (from their own index, or from FollowTip in a later iteration) get the block and can extract the transaction client-side.

Why this depends on node kernel access

The N2C protocol bundle (ChainSync, LocalStateQuery, LocalTxSubmission, LocalTxMonitor) has no block-by-point lookup.

  • BlockFetch is N2N only and not exposed over the local Unix socket.
  • Local ChainSync can serve full blocks, but only by streaming forward from an intersection point.
    After MsgFindIntersect at point P, the follower is positioned at P; the first MsgRequestNext returns a RollBack to P, and subsequent calls roll forward.
    Getting the block at P over this protocol requires intersecting at P's predecessor, which the caller does not generally know.
    This makes random-access single-block lookup awkward and slow.
  • ChainDB.getBlockComponent :: BlockComponent blk b -> RealPoint blk -> m (Maybe b) gives the lookup directly, but is in-process only.
    It is not consumed by the diffusion layer or by cardano-api today.

ADR-019 introduces a NodeAccess record that gives cardano-rpc in-process access to consensus capabilities.
That is the natural place to expose block-by-point lookup.

Proposed approach

Proto additions

Add proto/utxorpc/v1beta/sync/sync.proto from the upstream UTxO RPC spec.
For this issue, expose only FetchBlock; leave DumpHistory, FollowTip, and ReadTip to follow-up issues.

NodeAccess extension

Extend the NodeAccess record (introduced in ADR-019) with a block-by-point lookup callback.
The cardano-node implementation delegates to ChainDB.getBlockComponent GetRawBlock for each requested point.
Returning raw CBOR keeps the path zero-copy from the immutable DB through to the gRPC response.

Handler

New module Cardano/Rpc/Server/Internal/UtxoRpc/Sync.hs with fetchBlockMethod.
Convert protobuf BlockRef to a chain point, call NodeAccess, return AnyChainBlock with native_bytes populated.
Whether to also populate the parsed cardano oneof depends on conversion cost; recommend native_bytes only for v1 and let callers decode if they need structured fields.

Server wiring

Register the new SyncService methods alongside the existing QueryService and SubmitService methods in runRpcServer.

Behaviour for missing or unknown points

To decide during implementation:

  • Per-entry response (one slot in the returned list per requested BlockRef, with an absent block represented by an empty AnyChainBlock), or
  • Whole-request error if any point is unknown.

The per-entry approach matches how the upstream spec describes batched queries; verify against reference implementations (Dolos, Dingo) before finalising.

Out of scope

  • ReadTx (see Background above).
  • Other SyncService methods: DumpHistory, FollowTip, ReadTip.
  • Any tx-hash to block-point index inside the node.

Acceptance criteria

  • proto/utxorpc/v1beta/sync/sync.proto added, FetchBlock RPC exposed.
  • Generated Haskell bindings regenerated via buf generate proto.
  • NodeAccess (ADR-019) exposes block-by-point lookup.
  • fetchBlockMethod handler implemented and registered in runRpcServer.
  • Integration test in cardano-testnet: submit a transaction, wait for confirmation, fetch the containing block via FetchBlock, verify the returned CBOR round-trips to the same transaction.
  • Behaviour for unknown points documented and tested.
  • cardano-rpc/README.md updated to mark FetchBlock as supported in the SyncService coverage table.

Dependencies

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions