@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
553 lines • 28.7 kB
JavaScript
import { KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH, KZG_COMMITMENTS_SUBTREE_INDEX, NUMBER_OF_COLUMNS, } from "@lodestar/params";
import { computeEpochAtSlot, computeStartSlotAtEpoch, getBlockHeaderProposerSignatureSetByHeaderSlot, getBlockHeaderProposerSignatureSetByParentStateSlot, } from "@lodestar/state-transition";
import { ssz } from "@lodestar/types";
import { byteArrayEquals, toRootHex, verifyMerkleBranch } from "@lodestar/utils";
import { getDataColumnSidecarSlot } from "../../util/dataColumns.js";
import { kzg } from "../../util/kzg.js";
import { DataColumnSidecarErrorCode, DataColumnSidecarGossipError, DataColumnSidecarValidationError, } from "../errors/dataColumnSidecarError.js";
import { GossipAction } from "../errors/gossipValidation.js";
import { RegenCaller } from "../regen/interface.js";
// SPEC FUNCTION
// https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.4/specs/fulu/p2p-interface.md#data_column_sidecar_subnet_id
export async function validateGossipFuluDataColumnSidecar(chain, dataColumnSidecar, gossipSubnet, metrics) {
const blockHeader = dataColumnSidecar.signedBlockHeader.message;
const blockRootHex = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader));
// 1) [REJECT] The sidecar is valid as verified by verify_data_column_sidecar
verifyFuluDataColumnSidecar(chain.config, dataColumnSidecar);
// 2) [REJECT] The sidecar is for the correct subnet -- i.e. compute_subnet_for_data_column_sidecar(sidecar.index) == subnet_id
if (computeSubnetForDataColumnSidecar(chain.config, dataColumnSidecar) !== gossipSubnet) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INVALID_SUBNET,
columnIndex: dataColumnSidecar.index,
gossipSubnet: gossipSubnet,
});
}
// 3) [IGNORE] The sidecar is not from a future slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
// -- i.e. validate that sidecar.slot <= current_slot (a client MAY queue future blocks
// for processing at the appropriate slot).
const currentSlotWithGossipDisparity = chain.clock.currentSlotWithGossipDisparity;
if (currentSlotWithGossipDisparity < blockHeader.slot) {
throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
code: DataColumnSidecarErrorCode.FUTURE_SLOT,
currentSlot: currentSlotWithGossipDisparity,
blockSlot: blockHeader.slot,
});
}
// 4) [IGNORE] The sidecar is from a slot greater than the latest finalized slot -- i.e. validate that
// sidecar.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint();
const finalizedSlot = computeStartSlotAtEpoch(finalizedCheckpoint.epoch);
if (blockHeader.slot <= finalizedSlot) {
throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
code: DataColumnSidecarErrorCode.WOULD_REVERT_FINALIZED_SLOT,
blockSlot: blockHeader.slot,
finalizedSlot,
});
}
// 6) [IGNORE] The sidecar's block's parent (defined by block_header.parent_root) has been seen (via gossip
// or non-gossip sources)
const parentRoot = toRootHex(blockHeader.parentRoot);
const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
if (parentBlock === null) {
// If fork choice does *not* consider the parent to be a descendant of the finalized block,
// then there are two more cases:
//
// 1. We have the parent stored in our database. Because fork-choice has confirmed the
// parent is *not* in our post-finalization DAG, all other blocks must be either
// pre-finalization or conflicting with finalization.
// 2. The parent is unknown to us, we probably want to download it since it might actually
// descend from the finalized root.
// (Non-Lighthouse): Since we prune all blocks non-descendant from finalized checking the `db.block` database won't be useful to guard
// against known bad fork blocks, so we throw PARENT_UNKNOWN for cases (1) and (2)
throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
code: DataColumnSidecarErrorCode.PARENT_UNKNOWN,
parentRoot,
slot: blockHeader.slot,
});
}
// 8) [REJECT] The sidecar is from a higher slot than the sidecar's block's parent
if (parentBlock.slot >= blockHeader.slot) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.NOT_LATER_THAN_PARENT,
parentSlot: parentBlock.slot,
slot: blockHeader.slot,
});
}
// getBlockSlotState also checks for whether the current finalized checkpoint is an ancestor of the block.
// As a result, we throw an IGNORE (whereas the spec says we should REJECT for this scenario).
// this is something we should change this in the future to make the code airtight to the spec.
// 7) [REJECT] The sidecar's block's parent passes validation.
const blockState = await chain.regen
.getBlockSlotState(parentBlock, blockHeader.slot, { dontTransferCache: true }, RegenCaller.validateGossipDataColumn)
.catch(() => {
throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
code: DataColumnSidecarErrorCode.PARENT_UNKNOWN,
parentRoot,
slot: blockHeader.slot,
});
});
// 13) [REJECT] The sidecar is proposed by the expected proposer_index for the block's slot in the context of the current
// shuffling (defined by block_header.parent_root/block_header.slot). If the proposer_index cannot
// immediately be verified against the expected shuffling, the sidecar MAY be queued for later processing
// while proposers for the block's branch are calculated -- in such a case do not REJECT, instead IGNORE
// this message.
const proposerIndex = blockHeader.proposerIndex;
const expectedProposerIndex = blockState.getBeaconProposer(blockHeader.slot);
if (proposerIndex !== expectedProposerIndex) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INCORRECT_PROPOSER,
actualProposerIndex: proposerIndex,
expectedProposerIndex,
});
}
// 5) [REJECT] The proposer signature of sidecar.signed_block_header, is valid with respect to the block_header.proposer_index pubkey.
const signature = dataColumnSidecar.signedBlockHeader.signature;
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockHeader.slot, blockRootHex, signature)) {
const signatureSet = getBlockHeaderProposerSignatureSetByParentStateSlot(chain.config, blockState.slot, dataColumnSidecar.signedBlockHeader);
if (!(await chain.bls.verifySignatureSets([signatureSet], {
// verify on main thread so that we only need to verify block proposer signature once per block
verifyOnMainThread: true,
}))) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
blockRoot: blockRootHex,
index: dataColumnSidecar.index,
slot: blockHeader.slot,
});
}
chain.seenBlockInputCache.markVerifiedProposerSignature(blockHeader.slot, blockRootHex, signature);
}
// 9) [REJECT] The current finalized_checkpoint is an ancestor of the sidecar's block
// -- i.e. get_checkpoint_block(store, block_header.parent_root, store.finalized_checkpoint.epoch)
// == store.finalized_checkpoint.root
// Handled by 7)
// 10) [REJECT] The sidecar's kzg_commitments field inclusion proof is valid as verified by
// verify_data_column_sidecar_inclusion_proof
// TODO: Can cache result on (commitments, proof, header) in the future
const timer = metrics?.peerDas.dataColumnSidecarInclusionProofVerificationTime.startTimer();
const valid = verifyDataColumnSidecarInclusionProof(dataColumnSidecar);
timer?.();
if (!valid) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID,
slot: dataColumnSidecar.signedBlockHeader.message.slot,
columnIndex: dataColumnSidecar.index,
});
}
// single data column is being verified here
const kzgProofTimer = metrics?.peerDas.dataColumnSidecarKzgProofsVerificationTime.startTimer();
// 11) [REJECT] The sidecar's column data is valid as verified by verify_data_column_sidecar_kzg_proofs
try {
await verifyDataColumnSidecarKzgProofs(dataColumnSidecar.kzgCommitments, Array.from({ length: dataColumnSidecar.column.length }, () => dataColumnSidecar.index), dataColumnSidecar.column, dataColumnSidecar.kzgProofs);
}
catch {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF,
slot: blockHeader.slot,
columnIndex: dataColumnSidecar.index,
});
}
finally {
kzgProofTimer?.();
}
// 12) [IGNORE] The sidecar is the first sidecar for the tuple (block_header.slot, block_header.proposer_index,
// sidecar.index) with valid header signature, sidecar inclusion proof, and kzg proof
// -- Handled in seenGossipBlockInput
}
// SPEC FUNCTION
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/p2p-interface.md#data_column_sidecar_subnet_id
export async function validateGossipGloasDataColumnSidecar(chain, payloadInput, dataColumnSidecar, gossipSubnet, metrics) {
const blockRootHex = toRootHex(dataColumnSidecar.beaconBlockRoot);
const block = chain.forkChoice.getBlockHexDefaultStatus(blockRootHex);
// [IGNORE] A valid block for the sidecar's `slot` has been seen.
if (block === null) {
throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
code: DataColumnSidecarErrorCode.BLOCK_UNKNOWN,
blockRoot: blockRootHex,
slot: dataColumnSidecar.slot,
});
}
// [REJECT] The sidecar slot matches the slot of the block with root beacon_block_root.
if (block.slot !== dataColumnSidecar.slot) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_SLOT,
columnIndex: dataColumnSidecar.index,
expected: block.slot,
actual: dataColumnSidecar.slot,
});
}
// [REJECT] The sidecar must pass verify_data_column_sidecar against the block commitments
const kzgCommitments = payloadInput.getBlobKzgCommitments();
verifyGloasDataColumnSidecar(dataColumnSidecar, kzgCommitments);
// [REJECT] The sidecar must be on the correct subnet
if (computeSubnetForDataColumnSidecar(chain.config, dataColumnSidecar) !== gossipSubnet) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INVALID_SUBNET,
columnIndex: dataColumnSidecar.index,
gossipSubnet,
});
}
// [REJECT] The sidecar kzg proofs must verify
const kzgProofTimer = metrics?.peerDas.dataColumnSidecarKzgProofsVerificationTime.startTimer();
try {
await verifyDataColumnSidecarKzgProofs(kzgCommitments, Array.from({ length: dataColumnSidecar.column.length }, () => dataColumnSidecar.index), dataColumnSidecar.column, dataColumnSidecar.kzgProofs);
}
catch {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF,
slot: dataColumnSidecar.slot,
columnIndex: dataColumnSidecar.index,
});
}
finally {
kzgProofTimer?.();
}
}
/**
* SPEC FUNCTION
* https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.4/specs/fulu/p2p-interface.md#verify_data_column_sidecar
*/
function verifyFuluDataColumnSidecar(config, dataColumnSidecar) {
if (dataColumnSidecar.index >= NUMBER_OF_COLUMNS) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INVALID_INDEX,
slot: dataColumnSidecar.signedBlockHeader.message.slot,
columnIndex: dataColumnSidecar.index,
});
}
if (dataColumnSidecar.kzgCommitments.length === 0) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.NO_COMMITMENTS,
slot: dataColumnSidecar.signedBlockHeader.message.slot,
columnIndex: dataColumnSidecar.index,
});
}
const epoch = computeEpochAtSlot(dataColumnSidecar.signedBlockHeader.message.slot);
const maxBlobsPerBlock = config.getMaxBlobsPerBlock(epoch);
if (dataColumnSidecar.kzgCommitments.length > maxBlobsPerBlock) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.TOO_MANY_KZG_COMMITMENTS,
slot: dataColumnSidecar.signedBlockHeader.message.slot,
columnIndex: dataColumnSidecar.index,
count: dataColumnSidecar.kzgCommitments.length,
limit: maxBlobsPerBlock,
});
}
if (dataColumnSidecar.column.length !== dataColumnSidecar.kzgCommitments.length ||
dataColumnSidecar.column.length !== dataColumnSidecar.kzgProofs.length) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.MISMATCHED_LENGTHS,
columnLength: dataColumnSidecar.column.length,
commitmentsLength: dataColumnSidecar.kzgCommitments.length,
proofsLength: dataColumnSidecar.kzgProofs.length,
});
}
}
/**
* SPEC FUNCTION
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/p2p-interface.md#modified-verify_data_column_sidecar
*/
function verifyGloasDataColumnSidecar(dataColumnSidecar, kzgCommitments) {
const slot = getDataColumnSidecarSlot(dataColumnSidecar);
if (dataColumnSidecar.index >= NUMBER_OF_COLUMNS) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.INVALID_INDEX,
slot,
columnIndex: dataColumnSidecar.index,
});
}
if (dataColumnSidecar.column.length === 0) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.NO_COMMITMENTS,
slot,
columnIndex: dataColumnSidecar.index,
});
}
if (dataColumnSidecar.column.length !== kzgCommitments.length ||
dataColumnSidecar.column.length !== dataColumnSidecar.kzgProofs.length) {
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
code: DataColumnSidecarErrorCode.MISMATCHED_LENGTHS,
columnLength: dataColumnSidecar.column.length,
commitmentsLength: kzgCommitments.length,
proofsLength: dataColumnSidecar.kzgProofs.length,
});
}
}
/**
* SPEC FUNCTION
* https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.4/specs/fulu/p2p-interface.md#verify_data_column_sidecar_kzg_proofs
*/
export async function verifyDataColumnSidecarKzgProofs(commitments, cellIndices, cells, proofs) {
let valid;
try {
valid = await kzg.asyncVerifyCellKzgProofBatch(commitments, cellIndices, cells, proofs);
}
catch (e) {
e.message = `Error on asyncVerifyCellKzgProofBatch: ${e.message}`;
throw e;
}
if (!valid) {
throw Error("Invalid verifyCellKzgProofBatch");
}
}
/**
* SPEC FUNCTION
* https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.4/specs/fulu/p2p-interface.md#verify_data_column_sidecar_inclusion_proof
*/
export function verifyDataColumnSidecarInclusionProof(dataColumnSidecar) {
return verifyMerkleBranch(ssz.deneb.BlobKzgCommitments.hashTreeRoot(dataColumnSidecar.kzgCommitments), dataColumnSidecar.kzgCommitmentsInclusionProof, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH, KZG_COMMITMENTS_SUBTREE_INDEX, dataColumnSidecar.signedBlockHeader.message.bodyRoot);
}
/**
* Validate a subset of fulu data column sidecars against a block
*
* Requires the block to be known to the node
*
* NOTE: chain is optional to skip signature verification. Helpful for testing purposes and so that can control whether
* signature gets checked depending on the reqresp method that is being checked
*/
export async function validateFuluBlockDataColumnSidecars(chain, blockSlot, blockRoot, blockBlobCount, dataColumnSidecars, metrics) {
metrics?.dataColumnSidecarProcessingRequests.inc(dataColumnSidecars.length);
const verificationTimer = metrics?.dataColumnSidecarGossipVerificationTime.startTimer();
try {
if (dataColumnSidecars.length === 0) {
return;
}
if (blockBlobCount === 0) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_COUNT,
slot: blockSlot,
expected: 0,
actual: dataColumnSidecars.length,
}, "Block has no blob commitments but data column sidecars were provided");
}
// Hash the first sidecar block header and compare the rest via (cheaper) equality
const firstSidecarSignedBlockHeader = dataColumnSidecars[0].signedBlockHeader;
const firstSidecarBlockHeader = firstSidecarSignedBlockHeader.message;
const firstBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(firstSidecarBlockHeader);
if (!byteArrayEquals(blockRoot, firstBlockRoot)) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_BLOCK,
slot: blockSlot,
columnIndex: 0,
expected: toRootHex(blockRoot),
actual: toRootHex(firstBlockRoot),
}, "DataColumnSidecar doesn't match corresponding block");
}
if (chain !== null) {
const rootHex = toRootHex(blockRoot);
const slot = firstSidecarSignedBlockHeader.message.slot;
const signature = firstSidecarSignedBlockHeader.signature;
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(slot, rootHex, signature)) {
const signatureSet = getBlockHeaderProposerSignatureSetByHeaderSlot(chain.config, firstSidecarSignedBlockHeader);
if (!(await chain.bls.verifySignatureSets([signatureSet], {
verifyOnMainThread: true,
}))) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
blockRoot: rootHex,
slot: blockSlot,
index: dataColumnSidecars[0].index,
});
}
chain.seenBlockInputCache.markVerifiedProposerSignature(slot, rootHex, signature);
}
}
const commitments = [];
const cellIndices = [];
const cells = [];
const proofs = [];
for (let i = 0; i < dataColumnSidecars.length; i++) {
const columnSidecar = dataColumnSidecars[i];
if (i !== 0 &&
!ssz.phase0.SignedBeaconBlockHeader.equals(firstSidecarSignedBlockHeader, columnSidecar.signedBlockHeader)) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_HEADER_ROOT,
slot: blockSlot,
expected: toRootHex(blockRoot),
actual: toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(columnSidecar.signedBlockHeader.message)),
});
}
if (columnSidecar.index >= NUMBER_OF_COLUMNS) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INVALID_INDEX,
slot: blockSlot,
columnIndex: columnSidecar.index,
}, "DataColumnSidecar has invalid index");
}
if (columnSidecar.column.length !== blockBlobCount) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_CELL_COUNT,
slot: blockSlot,
columnIndex: columnSidecar.index,
expected: blockBlobCount,
actual: columnSidecar.column.length,
});
}
if (columnSidecar.column.length !== columnSidecar.kzgCommitments.length) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT,
slot: blockSlot,
columnIndex: columnSidecar.index,
expected: columnSidecar.column.length,
actual: columnSidecar.kzgCommitments.length,
});
}
if (columnSidecar.column.length !== columnSidecar.kzgProofs.length) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_KZG_PROOF_COUNT,
slot: blockSlot,
columnIndex: columnSidecar.index,
expected: columnSidecar.column.length,
actual: columnSidecar.kzgProofs.length,
});
}
const inclusionProofTimer = metrics?.dataColumnSidecarInclusionProofVerificationTime.startTimer();
const validInclusionProof = verifyDataColumnSidecarInclusionProof(columnSidecar);
inclusionProofTimer?.();
if (!validInclusionProof) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID,
slot: blockSlot,
columnIndex: columnSidecar.index,
}, "DataColumnSidecar has invalid inclusion proof");
}
commitments.push(...columnSidecar.kzgCommitments);
cellIndices.push(...Array.from({ length: columnSidecar.column.length }, () => columnSidecar.index));
cells.push(...columnSidecar.column);
proofs.push(...columnSidecar.kzgProofs);
}
let reason;
// batch verification for the cases: downloadByRange and downloadByRoot
const kzgVerificationTimer = metrics?.kzgVerificationDataColumnBatchTime.startTimer();
try {
const valid = await kzg.asyncVerifyCellKzgProofBatch(commitments, cellIndices, cells, proofs);
if (!valid) {
reason = "Invalid KZG proof batch";
}
}
catch (e) {
reason = e.message;
}
finally {
kzgVerificationTimer?.();
}
if (reason !== undefined) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF_BATCH,
slot: blockSlot,
reason,
}, "DataColumnSidecar has invalid KZG proof batch");
}
metrics?.dataColumnSidecarProcessingSuccesses.inc();
}
finally {
verificationTimer?.();
}
}
/**
* Validate a subset of gloas data column sidecars against a block
* Gloas sidecars don't carry signed block headers, kzg commitments, or inclusion proofs
*/
export async function validateGloasBlockDataColumnSidecars(blockSlot, blockRoot, blockKzgCommitments, dataColumnSidecars, metrics) {
metrics?.dataColumnSidecarProcessingRequests.inc(dataColumnSidecars.length);
const verificationTimer = metrics?.dataColumnSidecarGossipVerificationTime.startTimer();
try {
if (dataColumnSidecars.length === 0) {
return;
}
if (blockKzgCommitments.length === 0) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_COUNT,
slot: blockSlot,
expected: 0,
actual: dataColumnSidecars.length,
}, "Block has no blob commitments but data column sidecars were provided");
}
const commitments = [];
const cellIndices = [];
const cells = [];
const proofs = [];
for (const columnSidecar of dataColumnSidecars) {
if (columnSidecar.slot !== blockSlot) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_SLOT,
columnIndex: columnSidecar.index,
expected: blockSlot,
actual: columnSidecar.slot,
});
}
if (!byteArrayEquals(columnSidecar.beaconBlockRoot, blockRoot)) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_BLOCK,
slot: blockSlot,
columnIndex: columnSidecar.index,
expected: toRootHex(blockRoot),
actual: toRootHex(columnSidecar.beaconBlockRoot),
});
}
if (columnSidecar.index >= NUMBER_OF_COLUMNS) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INVALID_INDEX,
slot: blockSlot,
columnIndex: columnSidecar.index,
}, "DataColumnSidecar has invalid index");
}
if (columnSidecar.column.length !== blockKzgCommitments.length) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_CELL_COUNT,
slot: blockSlot,
columnIndex: columnSidecar.index,
expected: blockKzgCommitments.length,
actual: columnSidecar.column.length,
});
}
if (columnSidecar.column.length !== columnSidecar.kzgProofs.length) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INCORRECT_KZG_PROOF_COUNT,
slot: blockSlot,
columnIndex: columnSidecar.index,
expected: columnSidecar.column.length,
actual: columnSidecar.kzgProofs.length,
});
}
commitments.push(...blockKzgCommitments);
cellIndices.push(...Array.from({ length: columnSidecar.column.length }, () => columnSidecar.index));
cells.push(...columnSidecar.column);
proofs.push(...columnSidecar.kzgProofs);
}
let reason;
// batch verification for the cases: downloadByRange and downloadByRoot
const kzgVerificationTimer = metrics?.kzgVerificationDataColumnBatchTime.startTimer();
try {
const valid = await kzg.asyncVerifyCellKzgProofBatch(commitments, cellIndices, cells, proofs);
if (!valid) {
reason = "Invalid KZG proof batch";
}
}
catch (e) {
reason = e.message;
}
finally {
kzgVerificationTimer?.();
}
if (reason !== undefined) {
throw new DataColumnSidecarValidationError({
code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF_BATCH,
slot: blockSlot,
reason,
}, "DataColumnSidecar has invalid KZG proof batch");
}
metrics?.dataColumnSidecarProcessingSuccesses.inc();
}
finally {
verificationTimer?.();
}
}
/**
* SPEC FUNCTION
* https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.4/specs/fulu/p2p-interface.md#compute_subnet_for_data_column_sidecar
*/
export function computeSubnetForDataColumnSidecar(config, columnSidecar) {
return columnSidecar.index % config.DATA_COLUMN_SIDECAR_SUBNET_COUNT;
}
//# sourceMappingURL=dataColumnSidecar.js.map