UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

297 lines • 14.7 kB
import { ChainForkConfig } from "@lodestar/config"; import { ColumnIndex, DataColumnSidecar, RootHex, SignedBeaconBlock, Slot, deneb, fulu, gloas, phase0 } from "@lodestar/types"; import { LodestarError, Logger } from "@lodestar/utils"; import { DAType, IBlockInput } from "../../chain/blocks/blockInput/index.js"; import { PayloadEnvelopeInput } from "../../chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js"; import { SeenBlockInput } from "../../chain/seenCache/seenGossipBlockInput.js"; import { SeenPayloadEnvelopeInput } from "../../chain/seenCache/seenPayloadEnvelopeInput.js"; import { BeaconMetrics } from "../../metrics/metrics/beacon.js"; import { INetwork } from "../../network/index.js"; import { CustodyConfig } from "../../util/dataColumns.js"; import { PeerIdStr } from "../../util/peerId.js"; import { WarnResult } from "../../util/wrapError.js"; export type DownloadByRangeRequests = { blocksRequest?: phase0.BeaconBlocksByRangeRequest; blobsRequest?: deneb.BlobSidecarsByRangeRequest; columnsRequest?: fulu.DataColumnSidecarsByRangeRequest; envelopesRequest?: gloas.ExecutionPayloadEnvelopesByRangeRequest; /** * Post-Gloas only. Fetches the dangling-parent's payload envelope and/or its missing sampled * data columns by-root. Set by `Batch` for the first batch of a `SyncChain` after the first * block's parentRoot is known. */ parentPayloadRequest?: { blockRoot?: Uint8Array; columns?: ColumnIndex[]; envelopeBlockRoot?: Uint8Array; }; }; export type ParentPayloadCommitments = { blockRoot: Uint8Array; blockRootHex: RootHex; kzgCommitments: deneb.BlobKzgCommitments; }; export type DownloadByRangeResponses = { blocks?: SignedBeaconBlock[]; blobSidecars?: deneb.BlobSidecars; columnSidecars?: DataColumnSidecar[]; payloadEnvelopes?: gloas.SignedExecutionPayloadEnvelope[]; }; export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & { config: ChainForkConfig; network: INetwork; logger: Logger; peerIdStr: string; batchBlocks?: IBlockInput[]; /** Required when `parentPayloadRequest` is set; supplies the data needed to validate the parent's columns. */ parentPayloadCommitments?: ParentPayloadCommitments; peerDasMetrics?: BeaconMetrics["peerDas"] | null; }; export type CacheByRangeResponsesProps = { cache: SeenBlockInput; seenPayloadEnvelopeInputCache: SeenPayloadEnvelopeInput; peerIdStr: string; responses: ValidatedResponses; batchBlocks: IBlockInput[]; /** Raw envelopes downloaded in this batch, keyed by slot (from downloadByRange return) */ downloadedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null; /** Envelopes already wrapped from previous partial downloads on this batch */ existingPayloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null; /** Sampled/custody column indices for building PayloadEnvelopeInputs */ custodyConfig: Pick<CustodyConfig, "sampledColumns" | "custodyColumns">; seenTimestampSec: number; }; export type ValidatedBlock = { blockRoot: Uint8Array; block: SignedBeaconBlock; }; export type ValidatedBlobSidecars = { blockRoot: Uint8Array; blobSidecars: deneb.BlobSidecars; }; export type ValidatedColumnSidecars = { blockRoot: Uint8Array; columnSidecars: DataColumnSidecar[]; }; export type ValidatedResponses = { validatedBlocks?: ValidatedBlock[]; validatedBlobSidecars?: ValidatedBlobSidecars[]; validatedColumnSidecars?: ValidatedColumnSidecars[]; }; /** * Given existing cached batch block inputs and newly validated responses, update the cache with the new data */ export declare function cacheByRangeResponses({ cache, seenPayloadEnvelopeInputCache, peerIdStr, responses, batchBlocks, downloadedPayloadEnvelopes, existingPayloadEnvelopes, custodyConfig, seenTimestampSec }: CacheByRangeResponsesProps): { blocks: IBlockInput[]; payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null; }; export declare function downloadByRange({ config, network, peerIdStr, batchBlocks, blocksRequest, blobsRequest, columnsRequest, envelopesRequest, parentPayloadRequest, parentPayloadCommitments, peerDasMetrics }: DownloadAndCacheByRangeProps): Promise<WarnResult<{ responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null; }, DownloadByRangeError>>; /** * Should not be called directly. Only exported for unit testing purposes */ export declare function requestByRange({ network, peerIdStr, blocksRequest, blobsRequest, columnsRequest, envelopesRequest, parentPayloadRequest }: DownloadByRangeRequests & { network: INetwork; peerIdStr: PeerIdStr; }): Promise<DownloadByRangeResponses>; /** * Should not be called directly. Only exported for unit testing purposes */ export declare function validateResponses({ config, batchBlocks, blocksRequest, blobsRequest, columnsRequest, envelopesRequest, parentPayloadRequest, parentPayloadCommitments, blocks, blobSidecars, columnSidecars, payloadEnvelopes, peerDasMetrics }: DownloadByRangeRequests & DownloadByRangeResponses & { config: ChainForkConfig; batchBlocks?: IBlockInput[]; parentPayloadCommitments?: ParentPayloadCommitments; peerDasMetrics?: BeaconMetrics["peerDas"] | null; }): Promise<WarnResult<{ responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null; }, DownloadByRangeError>>; /** * Should not be called directly. Only exported for unit testing purposes * * - check all slots are within range of startSlot (inclusive) through startSlot + count (exclusive) * - don't have more than count number of blocks * - slots are in ascending order * - must allow for skip slots * - check is a chain of blocks where via parentRoot matches hashTreeRoot of block before */ export declare function validateBlockByRangeResponse(config: ChainForkConfig, blocksRequest: phase0.BeaconBlocksByRangeRequest, blocks: SignedBeaconBlock[]): WarnResult<ValidatedBlock[], DownloadByRangeError>; /** * Should not be called directly. Only exported for unit testing purposes. * This is used only in Deneb and Electra */ export declare function validateBlobsByRangeResponse(dataRequestBlocks: ValidatedBlock[], blobSidecars: deneb.BlobSidecars): Promise<ValidatedBlobSidecars[]>; /** * Should not be called directly. Only exported for unit testing purposes * * Spec states: * 1) must be within range [start_slot, start_slot + count] * 2) should respond with all columns in the range or and 3:ResourceUnavailable (and potentially get down-scored) * 3) must response with at least the sidecars of the first blob-carrying block that exists in the range * 4) must include all sidecars from each block from which there are blobs * 5) where they exists, sidecars must be sent in (slot, index) order * 6) clients may limit the number of sidecars in a response * 7) clients may stop responding mid-response if their view of fork-choice changes * * We will interpret the spec as follows * - Errors when validating: 1, 3, 5 * - Warnings when validating: 2, 4, 6, 7 * * For "warning" cases, where we get a partial response but sidecars are validated and correct with respect to the * blocks, then they will be kept. This loosening of the spec is to help ensure sync goes smoothly and we can find * the data needed in difficult network situations. * * Assume for the following two examples we request indices 5, 10, 15 for a range of slots 32-63 * * For slots where we receive no sidecars, example slot 45, but blobs exist we will stop validating subsequent * slots, 45-63. The next round of requests will get structured to pull the from the slot that had columns * missing to the end of the range for all columns indices that were requested for the current partially failed * request (slots 45-63 and indices 5, 10, 15). * * For slots where only some of the requested sidecars are received we will proceed with validation. For simplicity sake * we will assume that if we only get some indices back for a (or several) slot(s) that the indices we get will be * consistent. IE if a peer returns only index 5, they will most likely return that same index for subsequent slot * (index 5 for slots 34, 35, 36, etc). They will not likely return 5 on slot 34, 10 on slot 35, 15 on slot 36, etc. * This assumption makes the code simpler. For both cases the request for the next round will be structured correctly * to pull any missing column indices for whatever range remains. The simplification just leads to re-verification * of the columns but the number of columns downloaded will be the same regardless of if they are validated twice. * * validateColumnsByRangeResponse makes some assumptions about the data being passed in * blocks are: * - slotwise in order * - form a chain * - non-sparse response (any missing block is a skipped slot not a bad response) * - last block is last slot received */ export declare function validateColumnsByRangeResponse(config: ChainForkConfig, request: fulu.DataColumnSidecarsByRangeRequest, blocks: ValidatedBlock[], columnSidecars: DataColumnSidecar[], peerDasMetrics?: BeaconMetrics["peerDas"] | null): Promise<WarnResult<ValidatedColumnSidecars[], DownloadByRangeError>>; /** * Given a data request, return only the blocks and roots that correspond to the data request (sorted). Assumes that * cached have slots that are all before the current batch of downloaded blocks */ export declare function getBlocksForDataValidation(dataRequest: { startSlot: Slot; count: number; }, cached: IBlockInput[] | undefined, current: ValidatedBlock[] | undefined): ValidatedBlock[]; export declare enum DownloadByRangeErrorCode { MISSING_BLOCKS_RESPONSE = "DOWNLOAD_BY_RANGE_ERROR_MISSING_BLOCK_RESPONSE", MISSING_BLOBS_RESPONSE = "DOWNLOAD_BY_RANGE_ERROR_MISSING_BLOBS_RESPONSE", MISSING_COLUMNS_RESPONSE = "DOWNLOAD_BY_RANGE_ERROR_MISSING_COLUMNS_RESPONSE", /** Error at the reqresp layer */ REQ_RESP_ERROR = "DOWNLOAD_BY_RANGE_ERROR_REQ_RESP_ERROR", PARENT_ROOT_MISMATCH = "DOWNLOAD_BY_RANGE_ERROR_PARENT_ROOT_MISMATCH", EXTRA_BLOCKS = "DOWNLOAD_BY_RANGE_ERROR_EXTRA_BLOCKS", OUT_OF_RANGE_BLOCKS = "DOWNLOAD_BY_RANGE_OUT_OF_RANGE_BLOCKS", OUT_OF_ORDER_BLOCKS = "DOWNLOAD_BY_RANGE_OUT_OF_ORDER_BLOCKS", MISSING_BLOBS = "DOWNLOAD_BY_RANGE_ERROR_MISSING_BLOBS", OUT_OF_ORDER_BLOBS = "DOWNLOAD_BY_RANGE_ERROR_OUT_OF_ORDER_BLOBS", EXTRA_BLOBS = "DOWNLOAD_BY_RANGE_ERROR_EXTRA_BLOBS", MISSING_COLUMNS = "DOWNLOAD_BY_RANGE_ERROR_MISSING_COLUMNS", OVER_COLUMNS = "DOWNLOAD_BY_RANGE_ERROR_OVER_COLUMNS", EXTRA_COLUMNS = "DOWNLOAD_BY_RANGE_ERROR_EXTRA_COLUMNS", NO_COLUMNS_FOR_BLOCK = "DOWNLOAD_BY_RANGE_ERROR_NO_COLUMNS_FOR_BLOCK", DUPLICATE_COLUMN = "DOWNLOAD_BY_RANGE_ERROR_DUPLICATE_COLUMN", OUT_OF_ORDER_COLUMNS = "DOWNLOAD_BY_RANGE_OUT_OF_ORDER_COLUMNS", /** Cached block input type mismatches new data */ MISMATCH_BLOCK_FORK = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_FORK", MISMATCH_BLOCK_INPUT_TYPE = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_INPUT_TYPE", /** Envelope beaconBlockRoot does not match the block's root */ INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT", /** Block segment + envelopes failed chain-segment linearity / FULL-chain checks */ INVALID_CHAIN_SEGMENT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_CHAIN_SEGMENT" } export type DownloadByRangeErrorType = { code: DownloadByRangeErrorCode.MISSING_BLOCKS_RESPONSE | DownloadByRangeErrorCode.MISSING_BLOBS_RESPONSE | DownloadByRangeErrorCode.MISSING_COLUMNS_RESPONSE; blockStartSlot?: number; blockCount?: number; blobStartSlot?: number; blobCount?: number; columnStartSlot?: number; columnCount?: number; } | { code: DownloadByRangeErrorCode.OUT_OF_RANGE_BLOCKS; slot: number; } | { code: DownloadByRangeErrorCode.MISMATCH_BLOCK_FORK; slot: number; dataFork: string; blockFork: string; } | { code: DownloadByRangeErrorCode.OUT_OF_ORDER_BLOCKS; } | { code: DownloadByRangeErrorCode.REQ_RESP_ERROR; blockStartSlot?: number; blockCount?: number; blobStartSlot?: number; blobCount?: number; columnStartSlot?: number; columnCount?: number; reason: string; } | { code: DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH; slot: number; expected: string; actual: string; } | { code: DownloadByRangeErrorCode.EXTRA_BLOCKS; expected: number; actual: number; } | { code: DownloadByRangeErrorCode.MISSING_BLOBS; expected: number; actual: number; } | { code: DownloadByRangeErrorCode.OUT_OF_ORDER_BLOBS | DownloadByRangeErrorCode.OUT_OF_ORDER_COLUMNS; slot: number; } | { code: DownloadByRangeErrorCode.EXTRA_BLOBS; expected: number; actual: number; } | { code: DownloadByRangeErrorCode.OVER_COLUMNS; max: number; actual: number; } | { code: DownloadByRangeErrorCode.MISSING_COLUMNS; slot: Slot; blockRoot: string; missingIndices: string; } | { code: DownloadByRangeErrorCode.DUPLICATE_COLUMN; slot: Slot; index: number; } | { code: DownloadByRangeErrorCode.EXTRA_COLUMNS | DownloadByRangeErrorCode.NO_COLUMNS_FOR_BLOCK; slot: Slot; blockRoot: string; invalidIndices: string; } | { code: DownloadByRangeErrorCode.MISMATCH_BLOCK_INPUT_TYPE; slot: number; blockRoot: string; expected: DAType; actual: DAType; } | { code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT; slot: Slot; expected: string; actual: string; } | { code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT; slot: Slot; reason: string; }; export declare class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType> { } /** * Validates SignedExecutionPayloadEnvelopes received for a range request. * * Three categories of envelope slots: * - In-batch slot whose block we have: verify envelope.beaconBlockRoot matches. * - Dangling-parent envelope (only when `parentPayloadCommitments` is set, i.e. the first * batch of a `SyncChain`): keep if `envelope.beaconBlockRoot === parentPayloadCommitments.blockRoot`. * - Other "orphan" envelopes (e.g. unrelated slots): ignored. */ export declare function validateEnvelopesByRangeResponse(validatedBlocks: ValidatedBlock[], batchBlocks: IBlockInput[] | undefined, payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[], parentPayloadCommitments?: ParentPayloadCommitments): Map<Slot, gloas.SignedExecutionPayloadEnvelope>; //# sourceMappingURL=downloadByRange.d.ts.map