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
Dependencies
References
Summary
Implement the
FetchBlockRPC method from the UTxO RPCSyncServicespec, 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_cbortable 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_cborindex from their stack while retaining the ability to return raw transaction bytes.Spec coverage
The UTxO RPC
SyncServiceis entirely unimplemented in cardano-rpc today.FetchBlockis the foundational query of that service and a prerequisite forDumpHistory,FollowTip, andReadTip.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
FetchBlockRequesttakes a list ofBlockRef(slot + hash) and returnsAnyChainBlockentries.BlockRefcarries 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.
FetchBlockis the right primitive: callers who know the slot (from their own index, or fromFollowTipin 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.
BlockFetchis N2N only and not exposed over the local Unix socket.ChainSynccan serve full blocks, but only by streaming forward from an intersection point.After
MsgFindIntersectat point P, the follower is positioned at P; the firstMsgRequestNextreturns aRollBackto 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
NodeAccessrecord 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.protofrom the upstream UTxO RPC spec.For this issue, expose only
FetchBlock; leaveDumpHistory,FollowTip, andReadTipto follow-up issues.NodeAccess extension
Extend the
NodeAccessrecord (introduced in ADR-019) with a block-by-point lookup callback.The cardano-node implementation delegates to
ChainDB.getBlockComponent GetRawBlockfor 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.hswithfetchBlockMethod.Convert protobuf
BlockRefto a chain point, callNodeAccess, returnAnyChainBlockwithnative_bytespopulated.Whether to also populate the parsed
cardanooneof depends on conversion cost; recommendnative_bytesonly for v1 and let callers decode if they need structured fields.Server wiring
Register the new
SyncServicemethods alongside the existingQueryServiceandSubmitServicemethods inrunRpcServer.Behaviour for missing or unknown points
To decide during implementation:
BlockRef, with an absent block represented by an emptyAnyChainBlock), orThe 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).SyncServicemethods:DumpHistory,FollowTip,ReadTip.Acceptance criteria
proto/utxorpc/v1beta/sync/sync.protoadded,FetchBlockRPC exposed.buf generate proto.NodeAccess(ADR-019) exposes block-by-point lookup.fetchBlockMethodhandler implemented and registered inrunRpcServer.FetchBlock, verify the returned CBOR round-trips to the same transaction.cardano-rpc/README.mdupdated to markFetchBlockas supported in theSyncServicecoverage table.Dependencies
References
utxorpc/specrepo,proto/utxorpc/v1beta/sync/sync.proto