@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
297 lines • 14.7 kB
TypeScript
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