feat(rpc)!: updated rpc api for blocks and checkpoints#22781
feat(rpc)!: updated rpc api for blocks and checkpoints#22781PhilWindle merged 9 commits intomerge-train/spartanfrom
Conversation
Implements the additive portion of the RPC refactor from the whimsical-babbage
plan. Follow-up work: migrate in-tree callers, remove old methods, rename new
methods to the spec (`getBlock`, `getCheckpoint`, `getBlockNumber(tip?)`, ...).
Store layer:
- Persist `feeAssetPriceModifier` on `CheckpointStorage` and thread through
`CheckpointData` / `data_source_base` rebuild path. Fixes the bug where
every confirmed checkpoint reported `0n` regardless of oracle input.
- Add `getCheckpointData`, `getCheckpointDataRange`, `getCheckpointNumberBySlot`,
and `getBlockDataWithCheckpointContext` to `L2BlockSource` (cheap metadata
paths wrapping existing KV reads).
Stdlib types/schemas (all purely additive):
- `BlockResponse<Opts>` + `BlockResponseSchema` with generic include narrowing.
- `CheckpointResponse<Opts>` + `CheckpointResponseSchema`.
- `L1PublishInfo` discriminated union + `l1PublishInfoFromL1PublishedData`.
- `ChainTip` (subtraction over `L2BlockTag`) and `ChainTips` alias.
- `CheckpointParameter`; extended `BlockParameter` to accept chain tips and
object variants (`{ number }`, `{ hash }`, `{ archive }`).
AztecNode interface / server:
- New methods: `getBlockResponse`, `getBlockResponses`, `getCheckpointResponse`,
`getCheckpointResponses`, `getBlockNumberForTip`, `getCheckpointNumberForTip`,
`getChainTips`. Added alongside existing methods (which are `@deprecated`)
so callers can migrate piecemeal — names will collapse to the spec in a
follow-up that also removes the old surface.
- Translation helpers in `block_response_helpers.ts`.
- Genesis special-case preserved via `buildGenesisBlockResponse`.
BREAKING CHANGE: consolidates `getBlock`/`getBlockByHash`/`getBlockByArchive`/ `getBlockHeader`/`getBlockHeaderByArchive` into a single `getBlock(param, options?)`. Similarly consolidates the checkpoint side into `getCheckpoint`/`getCheckpoints`. `getL2Tips` is renamed to `getChainTips` (with `proposedCheckpoint` omitted from the public surface). `getProvenBlockNumber`/`getCheckpointedBlockNumber` collapse into `getBlockNumber(tip?)` / `getCheckpointNumber(tip?)`. Implements plan Commits D + E (caller migration + old-method removal), plus fixes flagged during review: - `CheckpointResponse.blocks` no longer inherits `includeL1PublishInfo` / `includeAttestations` into the nested block type; only `includeTransactions` is forwarded. - `resolveBlockNumber` now handles every `BlockParameter` variant by routing through `resolveBlockParameter`, so object / chain-tip forms don't fall through to `block as BlockNumber` in `getBlockHashMembershipWitness`. - `ChainTips` omits `proposedCheckpoint` at both the type and schema level, matching the intended public surface. - Dropped the `?? '0'` fallback on `feeAssetPriceModifier` in `block_store` and the `as unknown as` branded-type casts in `block_response_helpers`. `L2BlockStream` still consumes the internal `L2BlockSource` shape, and `computeL2ToL1MembershipWitness` still needs an epoch->checkpoint-range lookup. To avoid a bigger refactor in the same commit, the interface keeps `getL2Tips`, `getBlockHeader`, `getCheckpointedBlocks`, and `getCheckpointsDataForEpoch` as TODO-flagged compat methods; PXE wraps its `AztecNode` in a thin adapter (`block_stream_source.ts`) that rebuilds `L2Block` / `PublishedCheckpoint` instances from the new response shapes. Follows-up on fb6753a.
…shim The deprecated `getBlockHeader` compat method on `AztecNode` was previously special-casing `BlockNumber.ZERO` by returning the initial header from the world-state synchronizer. The rename commit accidentally reduced it to a pass-through, which regresses the genesis case for existing callers (e2e_simple, base_wallet). Restore the pre-refactor behavior. Follows-up on 97d9b0a.
The getBlock unit tests still asserted the legacy L2Block return value. Update them to use `includeTransactions: true` so the request routes through `getL2Block`, and assert the projected `BlockResponse`.
776a730 to
635e8da
Compare
Proposal for new API methodsCurrent StateBlock Data Types/** Metadata only, no transaction data. */
type BlockData = {
header: BlockHeader;
archive: AppendOnlyTreeSnapshot;
blockHash: Fr;
checkpointNumber: CheckpointNumber;
indexWithinCheckpoint: IndexWithinCheckpoint;
};
/** Full block with transaction data. */
class L2Block {
archive: AppendOnlyTreeSnapshot;
header: BlockHeader;
body: Body;
checkpointNumber: CheckpointNumber;
indexWithinCheckpoint: IndexWithinCheckpoint;
}
/** Full block bundled with L1 checkpoint context. */
class CheckpointedL2Block {
checkpointNumber: CheckpointNumber;
block: L2Block;
l1: L1PublishedData; // { blockNumber: bigint, timestamp: bigint, blockHash: string }
attestations: CommitteeAttestation[];
}Checkpoint Data Types/** Full checkpoint with blocks and L1 context. Returned by getCheckpoints. */
class PublishedCheckpoint {
checkpoint: Checkpoint;
l1: L1PublishedData;
attestations: CommitteeAttestation[];
}
/** The checkpoint itself, containing full blocks. */
class Checkpoint {
archive: AppendOnlyTreeSnapshot;
header: CheckpointHeader;
blocks: L2Block[];
number: CheckpointNumber;
feeAssetPriceModifier: bigint;
}
/** Lightweight checkpoint metadata without blocks. Returned by getCheckpointsDataForEpoch. */
type CheckpointData = {
checkpointNumber: CheckpointNumber;
header: CheckpointHeader;
archive: AppendOnlyTreeSnapshot;
checkpointOutHash: Fr;
startBlock: BlockNumber;
blockCount: number;
attestations: CommitteeAttestation[];
l1: L1PublishedData;
};
/** L1 publication info, shared by checkpoint and block types. */
class L1PublishedData {
blockNumber: bigint;
timestamp: bigint;
blockHash: string;
}Block Parameter/** Currently: block number, block hash, or 'latest'. */
type BlockParameter = BlockNumber | BlockHash | 'latest';Chain Tipstype L2Tips = {
proposed: L2BlockId; // { number: BlockNumber, hash: string }
checkpointed: L2TipId; // { block: L2BlockId, checkpoint: CheckpointId }
proposedCheckpoint: L2TipId;
proven: L2TipId;
finalized: L2TipId;
};Current Methods (block/checkpoint related)
Proposal1. Unified Block Response TypeConsolidate type BlockResponse = {
header: BlockHeader;
archive: Fr;
hash: Fr;
checkpointNumber: CheckpointNumber;
indexWithinCheckpoint: IndexWithinCheckpoint;
/** Included when `includeTxs: true`. Undefined (not present) otherwise. */
body?: Body;
/** Included when `includeL1PublishInfo: true`. */
l1?: L1PublishInfo;
};
/** L1 publish info, present only when requested via includeL1PublishInfo. */
type L1PublishInfo =
| { published: false }
| { published: true; blockNumber: bigint; timestamp: bigint; blockHash: string };When 2. Unified Block ParameterExtend type ChainTip = 'proposed' | 'checkpointed' | 'proven' | 'finalized';
type BlockParameter =
| BlockNumber // by number: getBlock(10)
| BlockHash // by hash: getBlock(blockHash)
| ChainTip // by tip name: getBlock('proven')
| 'latest' // alias for 'proposed' (backwards compat)
| { number: BlockNumber } // explicit: getBlock({ number: 10 })
| { hash: BlockHash } // explicit: getBlock({ hash: '0x...' })
| { archive: Fr } // by archive root: getBlock({ archive: fr })
;
3. Block Include Optionstype BlockIncludeOptions = {
/** Include the block body (transaction data). Default: false. */
includeTxs?: boolean;
/** Include L1 publish info. Default: false. */
includeL1PublishInfo?: boolean;
};4. Consolidated
|
| Method | Replacement |
|---|---|
getBlockByHash(hash) |
getBlock(hash) or getBlock({ hash }) |
getBlockByArchive(archive) |
getBlock({ archive }) |
getBlockHeader(param) |
getBlock(param) (response always includes header) |
getBlockHeaderByArchive(archive) |
getBlock({ archive }) |
getCheckpointedBlocks(from, limit) |
getBlocks(from, limit, { includeL1PublishInfo: true }) (with limiting to last checkpointed block number) |
Replaced by consolidated getBlockNumber
| Method | Replacement |
|---|---|
getProvenBlockNumber() |
getBlockNumber('proven') |
getCheckpointedBlockNumber() |
getBlockNumber('checkpointed') |
Renamed
| Method | Replacement |
|---|---|
getL2Tips() |
getChainTips() |
Not used, candidates for removal
| Method | Notes |
|---|---|
getBlockByHash |
Zero usage in client or e2e tests |
getCheckpointsDataForEpoch(epoch) |
getCheckpoints(from, limit) with appropriate range for epoch (zero usage today) |
Unchanged (kept as-is, unrelated)
| Method | Reason |
|---|---|
getBlockHashMembershipWitness |
Distinct concern (archive tree witness) |
getWorldStateSyncStatus |
Distinct concern (sync status) |
getL2ToL1Messages |
Distinct concern (cross-chain) |
getL1ToL2MessageCheckpoint |
Distinct concern (cross-chain) |
Open Questions
- Naming:
BlockResponsevsBlockViewvsBlockInfovs something else?- Go with BlockResponse
- Naming:
includeTxsvsincludeBodyvsincludeTransactions?- includeTransactions
- Naming:
includeL1PublishInfovsincludeL1DatavsincludeCheckpointInfo?- includeL1PublishInfo
- Attestations:
CheckpointedL2Blockcurrently includesattestations. ShouldBlockResponseinclude those whenincludeL1PublishInfois set, or are attestations a separate concern?- Make attestations a separate concern
- Attestations: The current
PublishedCheckpointandCheckpointedL2Blockinclude attestations. These are dropped fromCheckpointResponse. Should they be available via a separate option or method?- Make them available via a separate option
- Restore 'latest' resolution in legacy getBlockHeader so it returns the initial header when no blocks have been mined. - Update PXE block_synchronizer and pxe tests to mock getBlock (returning a BlockResponse-shaped envelope) instead of the removed getBlockHeader paths, since BlockSynchronizer.doSync calls node.getBlock(0). - Add interface coverage for the deprecated getBlockHeader, getCheckpointedBlocks, and getL2Tips on AztecNode and for the new getBlockDataWithCheckpointContext, getCheckpointData, getCheckpointDataRange, getCheckpointNumberBySlot on Archiver.
PhilWindle
left a comment
There was a problem hiding this comment.
A big improvement. Can you make some issues for the follow up work?
Updates the archiver data store model so that we can account for more than a single proposed checkpoint. Under pipelining, it's possible we receive the proposal for the next checkpoint before we've managed to sync the previous one from L1, thus having two checkpoints unmined. Also fixes `addProposedCheckpoint` in the archiver so that it goes through the queue, instead of potentially injecting it in the middle of an L1 sync. Next steps is to review the archiver and node APIs that return checkpoints, and unify whether we return proposed checkpoints along mined checkpoints or not. Related to #22781. Fixes A-910
Implements the new JSON RPC API proposal for blocks and checkpoints. Next steps:
proposedCheckpoint)Fixes A-974