diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index 59fd922fc38e..4c3528bff638 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -78,6 +78,7 @@ export interface INetwork extends INetworkCorePublic { // ReqResp sendBeaconBlocksByRange(peerId: PeerIdStr, request: phase0.BeaconBlocksByRangeRequest): Promise; sendBeaconBlocksByRoot(peerId: PeerIdStr, request: BeaconBlocksByRootRequest): Promise; + sendBeaconBlocksByHead(peerId: PeerIdStr, request: fulu.BeaconBlocksByHeadRequest): Promise; sendBlobSidecarsByRange(peerId: PeerIdStr, request: deneb.BlobSidecarsByRangeRequest): Promise; sendBlobSidecarsByRoot(peerId: PeerIdStr, request: BlobSidecarsByRootRequest): Promise; sendDataColumnSidecarsByRange( diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 24e2fd632539..3fad5fb25dac 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -578,6 +578,18 @@ export class Network implements INetwork { ); } + async sendBeaconBlocksByHead( + peerId: PeerIdStr, + request: fulu.BeaconBlocksByHeadRequest + ): Promise { + return collectMaxResponseTypedWithBytes( + this.sendReqRespRequest(peerId, ReqRespMethod.BeaconBlocksByHead, [Version.V1], request), + Math.min(request.count, this.config.MAX_REQUEST_BLOCKS_DENEB), + responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByHead], + this.chain.serializedCache + ); + } + async sendLightClientBootstrap(peerId: PeerIdStr, request: Root): Promise { return collectExactOneTyped( this.sendReqRespRequest(peerId, ReqRespMethod.LightClientBootstrap, [Version.V1], request), diff --git a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts index 5aba7277cbac..62e75b74842d 100644 --- a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts +++ b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts @@ -286,6 +286,7 @@ export class ReqRespBeaconNode extends ReqResp { // instead of protocol version. This is not easily fixable with our current architecture. // See https://github.com/ChainSafe/lodestar/pull/8168 for more details. [protocols.StatusV2(fork, this.config), this.onStatus.bind(this)], + [protocols.BeaconBlocksByHead(fork, this.config), this.getHandler(ReqRespMethod.BeaconBlocksByHead)], [ protocols.DataColumnSidecarsByRoot(fork, this.config), this.getHandler(ReqRespMethod.DataColumnSidecarsByRoot), diff --git a/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByHead.ts b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByHead.ts new file mode 100644 index 000000000000..5d22e8fa2d20 --- /dev/null +++ b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByHead.ts @@ -0,0 +1,91 @@ +import {PeerId} from "@libp2p/interface"; +import {BeaconConfig} from "@lodestar/config"; +import {ForkName, GENESIS_EPOCH, GENESIS_SLOT, isForkPostDeneb} from "@lodestar/params"; +import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp"; +import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition"; +import {fulu} from "@lodestar/types"; +import {toRootHex} from "@lodestar/utils"; +import {IBeaconChain} from "../../../chain/index.js"; +import {getParentRootFromSignedBeaconBlockSerialized} from "../../../util/sszBytes.js"; +import {prettyPrintPeerId} from "../../util.js"; + +// See https://github.com/ethereum/consensus-specs/pull/5181 +export async function* onBeaconBlocksByHead( + request: fulu.BeaconBlocksByHeadRequest, + chain: IBeaconChain, + peerId: PeerId, + peerClient: string +): AsyncIterable { + const currentFork = chain.config.getForkName(chain.clock.currentSlot); + const {beaconRoot, count} = validateBeaconBlocksByHeadRequest(currentFork, chain.config, request); + + const requestedRootHex = toRootHex(beaconRoot); + let blockRootHex = requestedRootHex; + const minimumRequestEpoch = Math.max( + GENESIS_EPOCH, + chain.clock.currentEpoch - chain.config.MIN_EPOCHS_FOR_BLOCK_REQUESTS + ); + const minimumRequestSlot = computeStartSlotAtEpoch(minimumRequestEpoch); + + for (let blocksSent = 0; blocksSent < count; blocksSent++) { + const blockBytes = await chain.getSerializedBlockByRoot(blockRootHex); + if (!blockBytes) { + if (blocksSent === 0) { + throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, `Unknown block root ${requestedRootHex}`); + } + return; + } + + if (blockBytes.slot < minimumRequestSlot) { + if (blocksSent === 0) { + chain.logger.verbose("Peer requested unavailable block for BeaconBlocksByHead", { + peer: prettyPrintPeerId(peerId), + client: peerClient, + requestedRoot: requestedRootHex, + slot: blockBytes.slot, + minimumRequestSlot, + }); + } + return; + } + + yield { + data: blockBytes.block, + boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(blockBytes.slot)), + }; + + if (blockBytes.slot === GENESIS_SLOT) { + return; + } + + const parentRootHex = getParentRootFromSignedBeaconBlockSerialized(blockBytes.block); + if (parentRootHex === null) { + throw new ResponseError( + RespStatus.SERVER_ERROR, + `Invalid block bytes for root ${blockRootHex} slot ${blockBytes.slot}` + ); + } + blockRootHex = parentRootHex; + } +} + +export function validateBeaconBlocksByHeadRequest( + fork: ForkName, + config: BeaconConfig, + request: fulu.BeaconBlocksByHeadRequest +): fulu.BeaconBlocksByHeadRequest { + const {beaconRoot} = request; + let {count} = request; + + if (count < 1) { + throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1"); + } + + const maxRequestBlocks = isForkPostDeneb(fork) ? config.MAX_REQUEST_BLOCKS_DENEB : config.MAX_REQUEST_BLOCKS; + + if (count > maxRequestBlocks) { + count = maxRequestBlocks; + } + + return {beaconRoot, count}; +} diff --git a/packages/beacon-node/src/network/reqresp/handlers/index.ts b/packages/beacon-node/src/network/reqresp/handlers/index.ts index c5c6109741ea..e5537e3fa272 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/index.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/index.ts @@ -9,6 +9,7 @@ import { ExecutionPayloadEnvelopesByRootRequestType, } from "../../../util/types.js"; import {GetReqRespHandlerFn, ReqRespMethod} from "../types.js"; +import {onBeaconBlocksByHead} from "./beaconBlocksByHead.js"; import {onBeaconBlocksByRange} from "./beaconBlocksByRange.js"; import {onBeaconBlocksByRoot} from "./beaconBlocksByRoot.js"; import {onBlobSidecarsByRange} from "./blobSidecarsByRange.js"; @@ -47,6 +48,10 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh const body = BeaconBlocksByRootRequestType(fork, chain.config).deserialize(req.data); return onBeaconBlocksByRoot(body, chain); }, + [ReqRespMethod.BeaconBlocksByHead]: (req, peerId, peerClient) => { + const body = ssz.fulu.BeaconBlocksByHeadRequest.deserialize(req.data); + return onBeaconBlocksByHead(body, chain, peerId, peerClient); + }, [ReqRespMethod.BlobSidecarsByRoot]: (req) => { const fork = chain.config.getForkName(chain.clock.currentSlot); const body = BlobSidecarsByRootRequestType(fork, chain.config).deserialize(req.data); diff --git a/packages/beacon-node/src/network/reqresp/interface.ts b/packages/beacon-node/src/network/reqresp/interface.ts index 0a9d2f59b3ad..33ce4a7b795e 100644 --- a/packages/beacon-node/src/network/reqresp/interface.ts +++ b/packages/beacon-node/src/network/reqresp/interface.ts @@ -33,7 +33,7 @@ export enum RespStatus { */ SERVER_ERROR = 2, /** - * The responder does not have requested resource. The response payload adheres to the ErrorMessage schema (described below). Note: This response code is only valid as a response to BlocksByRange + * The responder does not have requested resource. The response payload adheres to the ErrorMessage schema. */ RESOURCE_UNAVAILABLE = 3, /** diff --git a/packages/beacon-node/src/network/reqresp/protocols.ts b/packages/beacon-node/src/network/reqresp/protocols.ts index 17dfc84b4910..063339dd58f0 100644 --- a/packages/beacon-node/src/network/reqresp/protocols.ts +++ b/packages/beacon-node/src/network/reqresp/protocols.ts @@ -70,6 +70,12 @@ export const BeaconBlocksByRootV2 = toProtocol({ contextBytesType: ContextBytesType.ForkDigest, }); +export const BeaconBlocksByHead = toProtocol({ + method: ReqRespMethod.BeaconBlocksByHead, + version: Version.V1, + contextBytesType: ContextBytesType.ForkDigest, +}); + export const BlobSidecarsByRange = toProtocol({ method: ReqRespMethod.BlobSidecarsByRange, version: Version.V1, diff --git a/packages/beacon-node/src/network/reqresp/rateLimit.ts b/packages/beacon-node/src/network/reqresp/rateLimit.ts index 924ece0d6902..fc5d4e626625 100644 --- a/packages/beacon-node/src/network/reqresp/rateLimit.ts +++ b/packages/beacon-node/src/network/reqresp/rateLimit.ts @@ -40,6 +40,10 @@ export const rateLimitQuotas: (fork: ForkName, config: BeaconConfig) => Record req.length), }, + [ReqRespMethod.BeaconBlocksByHead]: { + byPeer: {quota: config.MAX_REQUEST_BLOCKS_DENEB, quotaTimeMs: 10_000}, + getRequestCount: getRequestCountFn(fork, config, ReqRespMethod.BeaconBlocksByHead, (req) => req.count), + }, [ReqRespMethod.BlobSidecarsByRange]: { // Rationale: MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK byPeer: { diff --git a/packages/beacon-node/src/network/reqresp/score.ts b/packages/beacon-node/src/network/reqresp/score.ts index 7ed5e7dd1bec..f60bd5b01f20 100644 --- a/packages/beacon-node/src/network/reqresp/score.ts +++ b/packages/beacon-node/src/network/reqresp/score.ts @@ -46,6 +46,7 @@ export function onOutgoingReqRespError(e: RequestError, method: ReqRespMethod): return PeerAction.LowToleranceError; case ReqRespMethod.BeaconBlocksByRange: case ReqRespMethod.BeaconBlocksByRoot: + case ReqRespMethod.BeaconBlocksByHead: case ReqRespMethod.ExecutionPayloadEnvelopesByRoot: case ReqRespMethod.ExecutionPayloadEnvelopesByRange: return PeerAction.MidToleranceError; diff --git a/packages/beacon-node/src/network/reqresp/types.ts b/packages/beacon-node/src/network/reqresp/types.ts index b31cf1401de5..3435042e02db 100644 --- a/packages/beacon-node/src/network/reqresp/types.ts +++ b/packages/beacon-node/src/network/reqresp/types.ts @@ -42,6 +42,7 @@ export enum ReqRespMethod { Metadata = "metadata", BeaconBlocksByRange = "beacon_blocks_by_range", BeaconBlocksByRoot = "beacon_blocks_by_root", + BeaconBlocksByHead = "beacon_blocks_by_head", BlobSidecarsByRange = "blob_sidecars_by_range", BlobSidecarsByRoot = "blob_sidecars_by_root", DataColumnSidecarsByRange = "data_column_sidecars_by_range", @@ -62,6 +63,7 @@ export type RequestBodyByMethod = { [ReqRespMethod.Metadata]: null; [ReqRespMethod.BeaconBlocksByRange]: phase0.BeaconBlocksByRangeRequest; [ReqRespMethod.BeaconBlocksByRoot]: BeaconBlocksByRootRequest; + [ReqRespMethod.BeaconBlocksByHead]: fulu.BeaconBlocksByHeadRequest; [ReqRespMethod.BlobSidecarsByRange]: deneb.BlobSidecarsByRangeRequest; [ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequest; [ReqRespMethod.DataColumnSidecarsByRange]: fulu.DataColumnSidecarsByRangeRequest; @@ -82,6 +84,7 @@ type ResponseBodyByMethod = { // Do not matter [ReqRespMethod.BeaconBlocksByRange]: SignedBeaconBlock; [ReqRespMethod.BeaconBlocksByRoot]: SignedBeaconBlock; + [ReqRespMethod.BeaconBlocksByHead]: SignedBeaconBlock; [ReqRespMethod.BlobSidecarsByRange]: deneb.BlobSidecar; [ReqRespMethod.BlobSidecarsByRoot]: deneb.BlobSidecar; [ReqRespMethod.DataColumnSidecarsByRange]: DataColumnSidecar; @@ -111,6 +114,7 @@ export const requestSszTypeByMethod: ( [ReqRespMethod.BeaconBlocksByRange]: ssz.phase0.BeaconBlocksByRangeRequest, [ReqRespMethod.BeaconBlocksByRoot]: BeaconBlocksByRootRequestType(fork, config), + [ReqRespMethod.BeaconBlocksByHead]: ssz.fulu.BeaconBlocksByHeadRequest, [ReqRespMethod.BlobSidecarsByRange]: ssz.deneb.BlobSidecarsByRangeRequest, [ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequestType(fork, config), [ReqRespMethod.DataColumnSidecarsByRange]: ssz.fulu.DataColumnSidecarsByRangeRequest, @@ -142,6 +146,7 @@ export const responseSszTypeByMethod: {[K in ReqRespMethod]: ResponseTypeGetter< version === Version.V1 ? ssz.phase0.Metadata : version === Version.V2 ? ssz.altair.Metadata : ssz.fulu.Metadata, [ReqRespMethod.BeaconBlocksByRange]: blocksResponseType, [ReqRespMethod.BeaconBlocksByRoot]: blocksResponseType, + [ReqRespMethod.BeaconBlocksByHead]: (fork) => ssz[fork].SignedBeaconBlock, [ReqRespMethod.BlobSidecarsByRange]: () => ssz.deneb.BlobSidecar, [ReqRespMethod.BlobSidecarsByRoot]: () => ssz.deneb.BlobSidecar, [ReqRespMethod.LightClientBootstrap]: (fork) => sszTypesFor(onlyPostAltairFork(fork)).LightClientBootstrap, diff --git a/packages/beacon-node/test/unit/network/reqresp/handlers/beaconBlocksByHead.test.ts b/packages/beacon-node/test/unit/network/reqresp/handlers/beaconBlocksByHead.test.ts new file mode 100644 index 000000000000..7498290f42c9 --- /dev/null +++ b/packages/beacon-node/test/unit/network/reqresp/handlers/beaconBlocksByHead.test.ts @@ -0,0 +1,198 @@ +import {PeerId} from "@libp2p/interface"; +import {describe, expect, it, vi} from "vitest"; +import {createBeaconConfig} from "@lodestar/config"; +import {chainConfig} from "@lodestar/config/default"; +import {ForkName} from "@lodestar/params"; +import {RespStatus} from "@lodestar/reqresp"; +import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; +import {SignedBeaconBlock, ssz} from "@lodestar/types"; +import {toRootHex} from "@lodestar/utils"; +import {IBeaconChain} from "../../../../../src/chain/index.js"; +import {ZERO_HASH} from "../../../../../src/constants/index.js"; +import {onBeaconBlocksByHead} from "../../../../../src/network/reqresp/handlers/beaconBlocksByHead.js"; +import {ReqRespMethod, Version, responseSszTypeByMethod} from "../../../../../src/network/reqresp/types.js"; +import {getSlotFromSignedBeaconBlockSerialized} from "../../../../../src/util/sszBytes.js"; + +const config = createBeaconConfig( + { + ...chainConfig, + ALTAIR_FORK_EPOCH: 0, + BELLATRIX_FORK_EPOCH: 0, + CAPELLA_FORK_EPOCH: 0, + DENEB_FORK_EPOCH: 0, + ELECTRA_FORK_EPOCH: 0, + FULU_FORK_EPOCH: 0, + MIN_EPOCHS_FOR_BLOCK_REQUESTS: 1, + }, + ZERO_HASH +); +const crossForkConfig = createBeaconConfig( + { + ...chainConfig, + ALTAIR_FORK_EPOCH: 0, + BELLATRIX_FORK_EPOCH: 0, + CAPELLA_FORK_EPOCH: 0, + DENEB_FORK_EPOCH: 0, + ELECTRA_FORK_EPOCH: 0, + FULU_FORK_EPOCH: 2, + MIN_EPOCHS_FOR_BLOCK_REQUESTS: 1, + }, + ZERO_HASH +); +const peerId = {toString: () => "test-peer"} as PeerId; + +describe("onBeaconBlocksByHead", () => { + it("returns blocks from head to ancestors in descending slot order", async () => { + const blocks = createBlocks([10, 11, 12]); + const chain = createChain(blocks); + + const responses = await Array.fromAsync( + onBeaconBlocksByHead({beaconRoot: blocks[2].root, count: 2}, chain, peerId, "test-client") + ); + + expect(responses.map((response) => getSlotFromSignedBeaconBlockSerialized(response.data))).toEqual([12, 11]); + expect(responses.map((response) => response.boundary.fork)).toEqual([ForkName.fulu, ForkName.fulu]); + expect(responses.map((response) => response.data)).toEqual([blocks[2].bytes, blocks[1].bytes]); + }); + + it("stops before ancestors older than the minimum request epoch", async () => { + const currentEpoch = 5; + const minimumRequestSlot = computeStartSlotAtEpoch(currentEpoch - config.MIN_EPOCHS_FOR_BLOCK_REQUESTS); + const blocks = createBlocks([minimumRequestSlot - 1, minimumRequestSlot, minimumRequestSlot + 1]); + const chain = createChain(blocks, currentEpoch); + + const responses = await Array.fromAsync( + onBeaconBlocksByHead({beaconRoot: blocks[2].root, count: 3}, chain, peerId, "test-client") + ); + + expect(responses.map((response) => getSlotFromSignedBeaconBlockSerialized(response.data))).toEqual([ + minimumRequestSlot + 1, + minimumRequestSlot, + ]); + }); + + it("continues across the Fulu fork boundary", async () => { + const fuluStartSlot = computeStartSlotAtEpoch(crossForkConfig.FULU_FORK_EPOCH); + const blocks = createBlocks([fuluStartSlot - 1, fuluStartSlot], crossForkConfig); + const chain = createChain(blocks, crossForkConfig.FULU_FORK_EPOCH, crossForkConfig); + + const responses = await Array.fromAsync( + onBeaconBlocksByHead({beaconRoot: blocks[1].root, count: 2}, chain, peerId, "test-client") + ); + + expect(responses.map((response) => getSlotFromSignedBeaconBlockSerialized(response.data))).toEqual([ + fuluStartSlot, + fuluStartSlot - 1, + ]); + expect(responses.map((response) => response.boundary.fork)).toEqual([ForkName.fulu, ForkName.electra]); + }); + + it("caps responses to MAX_REQUEST_BLOCKS_DENEB", async () => { + const blocks = createBlocks(Array.from({length: config.MAX_REQUEST_BLOCKS_DENEB + 2}, (_, i) => i + 1)); + const chain = createChain(blocks); + const head = blocks.at(-1); + if (!head) { + throw Error("Expected at least one block"); + } + + const responses = await Array.fromAsync( + onBeaconBlocksByHead( + {beaconRoot: head.root, count: config.MAX_REQUEST_BLOCKS_DENEB + 10}, + chain, + peerId, + "test-client" + ) + ); + + expect(responses).toHaveLength(config.MAX_REQUEST_BLOCKS_DENEB); + }); + + it("returns no blocks when the requested root is older than the minimum request epoch", async () => { + const currentEpoch = 5; + const minimumRequestSlot = computeStartSlotAtEpoch(currentEpoch - config.MIN_EPOCHS_FOR_BLOCK_REQUESTS); + const blocks = createBlocks([minimumRequestSlot - 1, minimumRequestSlot, minimumRequestSlot + 1]); + const chain = createChain(blocks, currentEpoch); + + const responses = await Array.fromAsync( + onBeaconBlocksByHead({beaconRoot: blocks[0].root, count: 1}, chain, peerId, "test-client") + ); + + expect(responses).toEqual([]); + expect(chain.logger.verbose).toHaveBeenCalledOnce(); + }); + + it("rejects zero-count requests", async () => { + const blocks = createBlocks([10]); + const chain = createChain(blocks); + + await expect( + Array.fromAsync(onBeaconBlocksByHead({beaconRoot: blocks[0].root, count: 0}, chain, peerId, "test-client")) + ).rejects.toMatchObject({status: RespStatus.INVALID_REQUEST}); + expect(chain.getSerializedBlockByRoot).not.toHaveBeenCalled(); + }); + + it("returns ResourceUnavailable when the requested root is unknown", async () => { + const blocks = createBlocks([10]); + const chain = createChain(blocks); + const unknownRoot = new Uint8Array(32).fill(0xab); + + await expect( + Array.fromAsync(onBeaconBlocksByHead({beaconRoot: unknownRoot, count: 1}, chain, peerId, "test-client")) + ).rejects.toMatchObject({status: RespStatus.RESOURCE_UNAVAILABLE}); + }); + + it("decodes BlocksByHead responses with the block fork type", () => { + expect(responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByHead](ForkName.electra, Version.V1)).toBe( + ssz.electra.SignedBeaconBlock + ); + expect(responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByHead](ForkName.fulu, Version.V1)).toBe( + ssz.fulu.SignedBeaconBlock + ); + }); +}); + +type TestBlock = { + block: SignedBeaconBlock; + bytes: Uint8Array; + root: Uint8Array; + rootHex: string; + slot: number; +}; + +function createBlocks(slots: number[], testConfig = config): TestBlock[] { + let parentRoot: Uint8Array = ZERO_HASH; + const blocks: TestBlock[] = []; + + for (const slot of slots) { + const forkTypes = testConfig.getForkTypes(slot); + const block = forkTypes.SignedBeaconBlock.defaultValue(); + block.message.slot = slot; + block.message.parentRoot = parentRoot; + + const root = forkTypes.BeaconBlock.hashTreeRoot(block.message); + blocks.push({ + block, + bytes: forkTypes.SignedBeaconBlock.serialize(block), + root, + rootHex: toRootHex(root), + slot, + }); + parentRoot = root; + } + + return blocks; +} + +function createChain(blocks: TestBlock[], currentEpoch = 0, testConfig = config): IBeaconChain { + const blocksByRoot = new Map(blocks.map((block) => [block.rootHex, block])); + + return { + config: testConfig, + clock: {currentEpoch, currentSlot: computeStartSlotAtEpoch(currentEpoch)}, + getSerializedBlockByRoot: vi.fn(async (rootHex: string) => { + const block = blocksByRoot.get(rootHex); + return block ? {block: block.bytes, executionOptimistic: false, finalized: false, slot: block.slot} : null; + }), + logger: {verbose: vi.fn()}, + } as unknown as IBeaconChain; +} diff --git a/packages/reqresp/src/interface.ts b/packages/reqresp/src/interface.ts index 1664b84d916c..beda796b2b23 100644 --- a/packages/reqresp/src/interface.ts +++ b/packages/reqresp/src/interface.ts @@ -14,7 +14,7 @@ export enum RespStatus { */ SERVER_ERROR = 2, /** - * The responder does not have requested resource. The response payload adheres to the ErrorMessage schema (described below). Note: This response code is only valid as a response to BlocksByRange + * The responder does not have requested resource. The response payload adheres to the ErrorMessage schema. */ RESOURCE_UNAVAILABLE = 3, /** diff --git a/packages/types/src/fulu/sszTypes.ts b/packages/types/src/fulu/sszTypes.ts index 282d848f9f60..4f5556cf5875 100644 --- a/packages/types/src/fulu/sszTypes.ts +++ b/packages/types/src/fulu/sszTypes.ts @@ -97,6 +97,14 @@ export const DataColumnSidecarsByRangeRequest = new ContainerType( {typeName: "DataColumnSidecarsByRangeRequest", jsonCase: "eth2"} ); +export const BeaconBlocksByHeadRequest = new ContainerType( + { + beaconRoot: Root, + count: UintNum64, + }, + {typeName: "BeaconBlocksByHeadRequest", jsonCase: "eth2"} +); + // Explicit aliases for a few common types export const BeaconBlock = electraSsz.BeaconBlock; export const SignedBeaconBlock = electraSsz.SignedBeaconBlock; diff --git a/packages/types/src/fulu/types.ts b/packages/types/src/fulu/types.ts index 92e82db293c3..cd2c01c3bb97 100644 --- a/packages/types/src/fulu/types.ts +++ b/packages/types/src/fulu/types.ts @@ -19,6 +19,7 @@ export type ProposerLookahead = ValueOf; export type DataColumnsByRootIdentifier = ValueOf; export type DataColumnSidecarsByRangeRequest = ValueOf; +export type BeaconBlocksByHeadRequest = ValueOf; export type BeaconBlock = ValueOf; export type SignedBeaconBlock = ValueOf; export type BeaconState = ValueOf;