diff --git a/packages/beacon-node/src/chain/blocks/types.ts b/packages/beacon-node/src/chain/blocks/types.ts index affbbbe5fd9e..1d62305642f7 100644 --- a/packages/beacon-node/src/chain/blocks/types.ts +++ b/packages/beacon-node/src/chain/blocks/types.ts @@ -128,7 +128,7 @@ export type ImportBlockOpts = { */ validSignatures?: boolean; /** Set to true if already run `validateBlobsSidecar()` sucessfully on the blobs */ - validBlobsSidecar?: boolean; + validBlobSidecars?: boolean; /** Seen timestamp seconds */ seenTimestampSec?: number; /** Set to true if persist block right at verification time */ diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts index 7fa5bb6fa405..c0ea81fbb173 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts @@ -5,7 +5,9 @@ import {Slot, deneb} from "@lodestar/types"; import {toHexString} from "@lodestar/utils"; import {IClock} from "../../util/clock.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; -import {validateBlobsSidecar} from "../validation/blobsSidecar.js"; +// TODO freetheblobs: disable the following exception once blockinput changes +/* eslint-disable @typescript-eslint/no-unused-vars */ +import {validateBlobSidecars} from "../validation/blobSidecar.js"; import {BlockInput, BlockInputType, ImportBlockOpts} from "./types.js"; /** @@ -126,7 +128,7 @@ function maybeValidateBlobs( // TODO Deneb: Make switch verify it's exhaustive switch (blockInput.type) { case BlockInputType.postDeneb: { - if (opts.validBlobsSidecar) { + if (opts.validBlobSidecars) { return DataAvailableStatus.available; } @@ -135,7 +137,8 @@ function maybeValidateBlobs( const {blobKzgCommitments} = (block as deneb.SignedBeaconBlock).message.body; const beaconBlockRoot = config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message); // TODO Deneb: This function throws un-typed errors - validateBlobsSidecar(blockSlot, beaconBlockRoot, blobKzgCommitments, blobs); + // TODO freetheblobs: enable the following validation once blockinput is migrated + // validateBlobSidecars(blockSlot, beaconBlockRoot, blobKzgCommitments, blobs); return DataAvailableStatus.available; } diff --git a/packages/beacon-node/src/chain/errors/blobsSidecarError.ts b/packages/beacon-node/src/chain/errors/blobSidecarError.ts similarity index 54% rename from packages/beacon-node/src/chain/errors/blobsSidecarError.ts rename to packages/beacon-node/src/chain/errors/blobSidecarError.ts index 628a2cdc0640..f0ad69167c19 100644 --- a/packages/beacon-node/src/chain/errors/blobsSidecarError.ts +++ b/packages/beacon-node/src/chain/errors/blobSidecarError.ts @@ -1,7 +1,8 @@ import {Slot} from "@lodestar/types"; import {GossipActionError} from "./gossipValidation.js"; -export enum BlobsSidecarErrorCode { +export enum BlobSidecarErrorCode { + INVALID_INDEX = "BLOBS_SIDECAR_ERROR_INVALID_INDEX", /** !bls.KeyValidate(block.body.blob_kzg_commitments[i]) */ INVALID_KZG = "BLOBS_SIDECAR_ERROR_INVALID_KZG", /** !verify_kzg_commitments_against_transactions(block.body.execution_payload.transactions, block.body.blob_kzg_commitments) */ @@ -14,11 +15,12 @@ export enum BlobsSidecarErrorCode { INVALID_KZG_PROOF = "BLOBS_SIDECAR_ERROR_INVALID_KZG_PROOF", } -export type BlobsSidecarErrorType = - | {code: BlobsSidecarErrorCode.INVALID_KZG; kzgIdx: number} - | {code: BlobsSidecarErrorCode.INVALID_KZG_TXS} - | {code: BlobsSidecarErrorCode.INCORRECT_SLOT; blockSlot: Slot; blobSlot: Slot} - | {code: BlobsSidecarErrorCode.INVALID_BLOB; blobIdx: number} - | {code: BlobsSidecarErrorCode.INVALID_KZG_PROOF}; +export type BlobSidecarErrorType = + | {code: BlobSidecarErrorCode.INVALID_INDEX; blobIdx: number; gossipIndex: number} + | {code: BlobSidecarErrorCode.INVALID_KZG; blobIdx: number} + | {code: BlobSidecarErrorCode.INVALID_KZG_TXS} + | {code: BlobSidecarErrorCode.INCORRECT_SLOT; blockSlot: Slot; blobSlot: Slot; blobIdx: number} + | {code: BlobSidecarErrorCode.INVALID_BLOB; blobIdx: number} + | {code: BlobSidecarErrorCode.INVALID_KZG_PROOF; blobIdx: number}; -export class BlobsSidecarError extends GossipActionError {} +export class BlobSidecarError extends GossipActionError {} diff --git a/packages/beacon-node/src/chain/errors/index.ts b/packages/beacon-node/src/chain/errors/index.ts index 9ce6c8109749..1bd8f8577305 100644 --- a/packages/beacon-node/src/chain/errors/index.ts +++ b/packages/beacon-node/src/chain/errors/index.ts @@ -1,6 +1,6 @@ export * from "./attestationError.js"; export * from "./attesterSlashingError.js"; -export * from "./blobsSidecarError.js"; +export * from "./blobSidecarError.js"; export * from "./blockError.js"; export * from "./gossipValidation.js"; export * from "./proposerSlashingError.js"; diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index a0989f30c41f..e7be64d0eecb 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -9,6 +9,7 @@ export enum RegenCaller { processBlock = "processBlock", produceBlock = "produceBlock", validateGossipBlock = "validateGossipBlock", + validateGossipBlob = "validateGossipBlob", precomputeEpoch = "precomputeEpoch", produceAttestationData = "produceAttestationData", processBlocksInEpoch = "processBlocksInEpoch", diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts new file mode 100644 index 000000000000..9f51b29b817e --- /dev/null +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -0,0 +1,198 @@ +import {ChainForkConfig} from "@lodestar/config"; +import {deneb, Root, Slot} from "@lodestar/types"; +import {toHex} from "@lodestar/utils"; +import {getBlobProposerSignatureSet, computeStartSlotAtEpoch} from "@lodestar/state-transition"; + +import {BlobSidecarError, BlobSidecarErrorCode} from "../errors/blobSidecarError.js"; +import {GossipAction} from "../errors/gossipValidation.js"; +import {ckzg} from "../../util/kzg.js"; +import {byteArrayEquals} from "../../util/bytes.js"; +import {IBeaconChain} from "../interface.js"; +import {RegenCaller} from "../regen/index.js"; + +// TODO: freetheblobs define blobs own gossip error +import {BlockGossipError, BlockErrorCode} from "../errors/index.js"; + +export async function validateGossipBlobSidecar( + config: ChainForkConfig, + chain: IBeaconChain, + signedBlob: deneb.SignedBlobSidecar, + gossipIndex: number +): Promise { + const blobSidecar = signedBlob.message; + const blobSlot = blobSidecar.slot; + + // [REJECT] The sidecar is for the correct topic -- i.e. sidecar.index matches the topic {index}. + if (blobSidecar.index !== gossipIndex) { + throw new BlobSidecarError(GossipAction.REJECT, { + code: BlobSidecarErrorCode.INVALID_INDEX, + blobIdx: blobSidecar.index, + gossipIndex, + }); + } + + // [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 < blobSlot) { + throw new BlockGossipError(GossipAction.IGNORE, { + code: BlockErrorCode.FUTURE_SLOT, + currentSlot: currentSlotWithGossipDisparity, + blockSlot: blobSlot, + }); + } + + // [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 (blobSlot <= finalizedSlot) { + throw new BlockGossipError(GossipAction.IGNORE, { + code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT, + blockSlot: blobSlot, + finalizedSlot, + }); + } + + // Check if the block is already known. We know it is post-finalization, so it is sufficient to check the fork choice. + // + // In normal operation this isn't necessary, however it is useful immediately after a + // reboot if the `observed_block_producers` cache is empty. In that case, without this + // check, we will load the parent and state from disk only to find out later that we + // already know this block. + const blockRoot = toHex(blobSidecar.blockRoot); + if (chain.forkChoice.getBlockHex(blockRoot) !== null) { + throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.ALREADY_KNOWN, root: blockRoot}); + } + + // TODO: freetheblobs - check for badblock + // TODO: freetheblobs - check that its first blob with valid signature + + // _[IGNORE]_ The blob's block's parent (defined by `sidecar.block_parent_root`) has been seen (via both + // gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is + // retrieved). + const parentRoot = toHex(blobSidecar.blockParentRoot); + const parentBlock = chain.forkChoice.getBlockHex(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 BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); + } + + // [REJECT] The blob is from a higher slot than its parent. + if (parentBlock.slot >= blobSlot) { + throw new BlockGossipError(GossipAction.IGNORE, { + code: BlockErrorCode.NOT_LATER_THAN_PARENT, + parentSlot: parentBlock.slot, + slot: blobSlot, + }); + } + + // 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. + // _[IGNORE]_ The blob's block's parent (defined by `sidecar.block_parent_root`) has been seen (via both + // gossip and non-gossip sources) // _[REJECT]_ The blob's block's parent (defined by `sidecar.block_parent_root`) passes validation + // The above validation will happen while importing + const blockState = await chain.regen + .getBlockSlotState(parentRoot, blobSlot, {dontTransferCache: true}, RegenCaller.validateGossipBlob) + .catch(() => { + throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); + }); + + // _[REJECT]_ The proposer signature, `signed_blob_sidecar.signature`, is valid with respect to the + // `sidecar.proposer_index` pubkey. + const signatureSet = getBlobProposerSignatureSet(blockState, signedBlob); + // Don't batch so verification is not delayed + if (!(await chain.bls.verifySignatureSets([signatureSet], {verifyOnMainThread: true}))) { + throw new BlockGossipError(GossipAction.REJECT, { + code: BlockErrorCode.PROPOSAL_SIGNATURE_INVALID, + }); + } + + // _[IGNORE]_ The sidecar is the only sidecar with valid signature received for the tuple + // `(sidecar.block_root, sidecar.index)` + // + // This is already taken care of by the way we group the blobs in getFullBlockInput helper + // but may be an error can be thrown there for this + + // _[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_parent_root`/`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 = blobSidecar.proposerIndex; + if (blockState.epochCtx.getBeaconProposer(blobSlot) !== proposerIndex) { + throw new BlockGossipError(GossipAction.REJECT, {code: BlockErrorCode.INCORRECT_PROPOSER, proposerIndex}); + } + + // blob, proof and commitment as a valid BLS G1 point gets verified in batch validation + validateBlobsAndProofs([blobSidecar.kzgCommitment], [blobSidecar.blob], [blobSidecar.kzgProof]); +} + +// https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/beacon-chain.md#validate_blobs_sidecar +export function validateBlobSidecars( + blockSlot: Slot, + blockRoot: Root, + expectedKzgCommitments: deneb.BlobKzgCommitments, + blobSidecars: deneb.BlobSidecars +): void { + // assert len(expected_kzg_commitments) == len(blobs) + if (expectedKzgCommitments.length !== blobSidecars.length) { + throw new Error( + `blobSidecars length to commitments length mismatch. Blob length: ${blobSidecars.length}, Expected commitments length ${expectedKzgCommitments.length}` + ); + } + + // No need to verify the aggregate proof of zero blobs + if (blobSidecars.length > 0) { + // Verify the blob slot and root matches + const blobs = []; + const proofs = []; + for (let index = 0; index < blobSidecars.length; index++) { + const blobSidecar = blobSidecars[index]; + if ( + blobSidecar.slot !== blockSlot || + !byteArrayEquals(blobSidecar.blockRoot, blockRoot) || + blobSidecar.index !== index || + !byteArrayEquals(expectedKzgCommitments[index], blobSidecar.kzgCommitment) + ) { + throw new Error( + `Invalid blob with slot=${blobSidecar.slot} blockRoot=${toHex(blockRoot)} index=${ + blobSidecar.index + } for the block root=${toHex(blockRoot)} slot=${blockSlot} index=${index}` + ); + } + blobs.push(blobSidecar.blob); + proofs.push(blobSidecar.kzgProof); + } + validateBlobsAndProofs(expectedKzgCommitments, blobs, proofs); + } +} + +function validateBlobsAndProofs( + expectedKzgCommitments: deneb.BlobKzgCommitments, + blobs: deneb.Blobs, + proofs: deneb.KZGProofs +): void { + // assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof) + let isProofValid: boolean; + try { + isProofValid = ckzg.verifyBlobKzgProofBatch(blobs, expectedKzgCommitments, proofs); + } catch (e) { + (e as Error).message = `Error on verifyBlobKzgProofBatch: ${(e as Error).message}`; + throw e; + } + if (!isProofValid) { + throw Error("Invalid verifyBlobKzgProofBatch"); + } +} diff --git a/packages/beacon-node/src/chain/validation/blobsSidecar.ts b/packages/beacon-node/src/chain/validation/blobsSidecar.ts deleted file mode 100644 index ad5eddb8c9a6..000000000000 --- a/packages/beacon-node/src/chain/validation/blobsSidecar.ts +++ /dev/null @@ -1,143 +0,0 @@ -import bls from "@chainsafe/bls"; -import {CoordType} from "@chainsafe/bls/types"; -import {deneb, Root, ssz} from "@lodestar/types"; -import {bytesToBigInt, toHex} from "@lodestar/utils"; -import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB} from "@lodestar/params"; -import {BlobsSidecarError, BlobsSidecarErrorCode} from "../errors/blobsSidecarError.js"; -import {GossipAction} from "../errors/gossipValidation.js"; -import {byteArrayEquals} from "../../util/bytes.js"; -import {ckzg} from "../../util/kzg.js"; - -const BLS_MODULUS = BigInt("52435875175126190479447740508185965837690552500527637822603658699938581184513"); - -export function validateGossipBlobsSidecar( - signedBlock: deneb.SignedBeaconBlock, - blobsSidecar: deneb.BlobsSidecar -): void { - const block = signedBlock.message; - - // Spec: https://github.com/ethereum/consensus-specs/blob/4cb6fd1c8c8f190d147d15b182c2510d0423ec61/specs/eip4844/p2p-interface.md#beacon_block_and_blobs_sidecar - // [REJECT] The KZG commitments of the blobs are all correctly encoded compressed BLS G1 Points. - // -- i.e. all(bls.KeyValidate(commitment) for commitment in block.body.blob_kzg_commitments) - const {blobKzgCommitments} = block.body; - for (let i = 0; i < blobKzgCommitments.length; i++) { - if (!blsKeyValidate(blobKzgCommitments[i])) { - throw new BlobsSidecarError(GossipAction.REJECT, {code: BlobsSidecarErrorCode.INVALID_KZG, kzgIdx: i}); - } - } - - // [IGNORE] the sidecar.beacon_block_slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) - // -- i.e. sidecar.beacon_block_slot == block.slot. - if (blobsSidecar.beaconBlockSlot !== block.slot) { - throw new BlobsSidecarError(GossipAction.IGNORE, { - code: BlobsSidecarErrorCode.INCORRECT_SLOT, - blobSlot: blobsSidecar.beaconBlockSlot, - blockSlot: block.slot, - }); - } - - // [REJECT] the sidecar.blobs are all well formatted, i.e. the BLSFieldElement in valid range (x < BLS_MODULUS). - for (let i = 0; i < blobsSidecar.blobs.length; i++) { - if (!blobIsValidRange(blobsSidecar.blobs[i])) { - throw new BlobsSidecarError(GossipAction.REJECT, {code: BlobsSidecarErrorCode.INVALID_BLOB, blobIdx: i}); - } - } - - // [REJECT] The KZG proof is a correctly encoded compressed BLS G1 Point - // -- i.e. blsKeyValidate(blobs_sidecar.kzg_aggregated_proof) - if (!blsKeyValidate(blobsSidecar.kzgAggregatedProof)) { - throw new BlobsSidecarError(GossipAction.REJECT, {code: BlobsSidecarErrorCode.INVALID_KZG_PROOF}); - } - - // [REJECT] The KZG commitments in the block are valid against the provided blobs sidecar. -- i.e. - // validate_blobs_sidecar(block.slot, hash_tree_root(block), block.body.blob_kzg_commitments, sidecar) - validateBlobsSidecar( - block.slot, - ssz.bellatrix.BeaconBlock.hashTreeRoot(block), - block.body.blobKzgCommitments, - blobsSidecar - ); -} - -// https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/beacon-chain.md#validate_blobs_sidecar -export function validateBlobsSidecar( - slot: number, - beaconBlockRoot: Root, - expectedKzgCommitments: deneb.KZGCommitment[], - blobsSidecar: deneb.BlobsSidecar -): void { - // assert slot == blobs_sidecar.beacon_block_slot - if (slot != blobsSidecar.beaconBlockSlot) { - throw new Error(`slot mismatch. Block slot: ${slot}, Blob slot ${blobsSidecar.beaconBlockSlot}`); - } - - // assert beacon_block_root == blobs_sidecar.beacon_block_root - if (!byteArrayEquals(beaconBlockRoot, blobsSidecar.beaconBlockRoot)) { - throw new Error( - `beacon block root mismatch. Block root: ${toHex(beaconBlockRoot)}, Blob root ${toHex( - blobsSidecar.beaconBlockRoot - )}` - ); - } - - // blobs = blobs_sidecar.blobs - // kzg_aggregated_proof = blobs_sidecar.kzg_aggregated_proof - const {blobs, kzgAggregatedProof} = blobsSidecar; - - // assert len(expected_kzg_commitments) == len(blobs) - if (expectedKzgCommitments.length !== blobs.length) { - throw new Error( - `blobs length to commitments length mismatch. Blob length: ${blobs.length}, Expected commitments length ${expectedKzgCommitments.length}` - ); - } - - // No need to verify the aggregate proof of zero blobs. Also c-kzg throws. - // https://github.com/dankrad/c-kzg/pull/12/files#r1025851956 - if (blobs.length > 0) { - // assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof) - let isProofValid: boolean; - try { - isProofValid = ckzg.verifyAggregateKzgProof(blobs, expectedKzgCommitments, kzgAggregatedProof); - } catch (e) { - // TODO DENEB: TEMP Nov17: May always throw error -- we need to fix Geth's KZG to match C-KZG and the trusted setup used here - (e as Error).message = `Error on verifyAggregateKzgProof: ${(e as Error).message}`; - throw e; - } - - // TODO DENEB: TEMP Nov17: May always throw error -- we need to fix Geth's KZG to match C-KZG and the trusted setup used here - if (!isProofValid) { - throw Error("Invalid AggregateKzgProof"); - } - } -} - -/** - * From https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-2.5 - * KeyValidate = valid, non-identity point that is in the correct subgroup - */ -function blsKeyValidate(g1Point: Uint8Array): boolean { - try { - bls.PublicKey.fromBytes(g1Point, CoordType.jacobian, true); - return true; - } catch (e) { - return false; - } -} - -/** - * ``` - * Blob = new ByteVectorType(BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB); - * ``` - * Check that each FIELD_ELEMENT as a uint256 < BLS_MODULUS - */ -function blobIsValidRange(blob: deneb.Blob): boolean { - for (let i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) { - const fieldElement = blob.subarray(i * BYTES_PER_FIELD_ELEMENT, (i + 1) * BYTES_PER_FIELD_ELEMENT); - const fieldElementBN = bytesToBigInt(fieldElement, "be"); - if (fieldElementBN >= BLS_MODULUS) { - return false; - } - } - - return true; -} diff --git a/packages/beacon-node/src/metrics/validatorMonitor.ts b/packages/beacon-node/src/metrics/validatorMonitor.ts index ee51f24bd114..5b0a5e1d5089 100644 --- a/packages/beacon-node/src/metrics/validatorMonitor.ts +++ b/packages/beacon-node/src/metrics/validatorMonitor.ts @@ -10,7 +10,7 @@ import { ParticipationFlags, } from "@lodestar/state-transition"; import {Logger, MapDef, MapDefMax, toHex} from "@lodestar/utils"; -import {RootHex, allForks, altair} from "@lodestar/types"; +import {RootHex, allForks, altair, deneb} from "@lodestar/types"; import {ChainConfig, ChainForkConfig} from "@lodestar/config"; import {ForkSeq, INTERVALS_PER_SLOT, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params"; import {Epoch, Slot, ValidatorIndex} from "@lodestar/types"; @@ -40,6 +40,7 @@ export type ValidatorMonitor = { registerLocalValidatorInSyncCommittee(index: number, untilEpoch: Epoch): void; registerValidatorStatuses(currentEpoch: Epoch, statuses: AttesterStatus[], balances?: number[]): void; registerBeaconBlock(src: OpSource, seenTimestampSec: Seconds, block: allForks.BeaconBlock): void; + registerBlobSidecar(src: OpSource, seenTimestampSec: Seconds, blob: deneb.BlobSidecar): void; registerImportedBlock(block: allForks.BeaconBlock, data: {proposerBalanceDelta: number}): void; onPoolSubmitUnaggregatedAttestation( seenTimestampSec: number, @@ -376,6 +377,10 @@ export function createValidatorMonitor( } }, + registerBlobSidecar(_src, _seenTimestampSec, _blob) { + //TODO: freetheblobs + }, + registerImportedBlock(block, {proposerBalanceDelta}) { const validator = validators.get(block.proposerIndex); if (validator) { diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index dc178e8ee246..6f1365a77c9c 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -11,7 +11,6 @@ import {JobItemQueue} from "../../util/queue/index.js"; export enum GossipType { beacon_block = "beacon_block", blob_sidecar = "blob_sidecar", - beacon_block_and_blobs_sidecar = "beacon_block_and_blobs_sidecar", beacon_aggregate_and_proof = "beacon_aggregate_and_proof", beacon_attestation = "beacon_attestation", voluntary_exit = "voluntary_exit", @@ -40,7 +39,6 @@ export interface IGossipTopic { export type GossipTopicTypeMap = { [GossipType.beacon_block]: {type: GossipType.beacon_block}; [GossipType.blob_sidecar]: {type: GossipType.blob_sidecar; index: number}; - [GossipType.beacon_block_and_blobs_sidecar]: {type: GossipType.beacon_block_and_blobs_sidecar}; [GossipType.beacon_aggregate_and_proof]: {type: GossipType.beacon_aggregate_and_proof}; [GossipType.beacon_attestation]: {type: GossipType.beacon_attestation; subnet: number}; [GossipType.voluntary_exit]: {type: GossipType.voluntary_exit}; @@ -71,7 +69,6 @@ export type SSZTypeOfGossipTopic = T extends {type: infer export type GossipTypeMap = { [GossipType.beacon_block]: allForks.SignedBeaconBlock; [GossipType.blob_sidecar]: deneb.SignedBlobSidecar; - [GossipType.beacon_block_and_blobs_sidecar]: deneb.SignedBeaconBlockAndBlobsSidecar; [GossipType.beacon_aggregate_and_proof]: phase0.SignedAggregateAndProof; [GossipType.beacon_attestation]: phase0.Attestation; [GossipType.voluntary_exit]: phase0.SignedVoluntaryExit; @@ -87,9 +84,6 @@ export type GossipTypeMap = { export type GossipFnByType = { [GossipType.beacon_block]: (signedBlock: allForks.SignedBeaconBlock) => Promise | void; [GossipType.blob_sidecar]: (signedBlobSidecar: deneb.SignedBlobSidecar) => Promise | void; - [GossipType.beacon_block_and_blobs_sidecar]: ( - signedBeaconBlockAndBlobsSidecar: deneb.SignedBeaconBlockAndBlobsSidecar - ) => Promise | void; [GossipType.beacon_aggregate_and_proof]: (aggregateAndProof: phase0.SignedAggregateAndProof) => Promise | void; [GossipType.beacon_attestation]: (attestation: phase0.Attestation) => Promise | void; [GossipType.voluntary_exit]: (voluntaryExit: phase0.SignedVoluntaryExit) => Promise | void; diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 8c5a35e74cfd..de1a571c3330 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -6,6 +6,7 @@ import { ForkSeq, SYNC_COMMITTEE_SUBNET_COUNT, isForkLightClient, + MAX_BLOBS_PER_BLOCK, } from "@lodestar/params"; import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js"; @@ -60,7 +61,6 @@ export function stringifyGossipTopic(forkDigestContext: ForkDigestContext, topic function stringifyGossipTopicType(topic: GossipTopic): string { switch (topic.type) { case GossipType.beacon_block: - case GossipType.beacon_block_and_blobs_sidecar: case GossipType.beacon_aggregate_and_proof: case GossipType.voluntary_exit: case GossipType.proposer_slashing: @@ -86,8 +86,6 @@ export function getGossipSSZType(topic: GossipTopic) { return ssz[topic.fork].SignedBeaconBlock; case GossipType.blob_sidecar: return ssz.deneb.SignedBlobSidecar; - case GossipType.beacon_block_and_blobs_sidecar: - return ssz.deneb.SignedBeaconBlockAndBlobsSidecar; case GossipType.beacon_aggregate_and_proof: return ssz.phase0.SignedAggregateAndProof; case GossipType.beacon_attestation: @@ -164,7 +162,6 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr: // Inline-d the parseGossipTopicType() function since spreading the resulting object x4 the time to parse a topicStr switch (gossipTypeStr) { case GossipType.beacon_block: - case GossipType.beacon_block_and_blobs_sidecar: case GossipType.beacon_aggregate_and_proof: case GossipType.voluntary_exit: case GossipType.proposer_slashing: @@ -208,18 +205,18 @@ export function getCoreTopicsAtFork( ): GossipTopicTypeMap[keyof GossipTopicTypeMap][] { // Common topics for all forks const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [ - // {type: GossipType.beacon_block}, // Handled below + {type: GossipType.beacon_block}, {type: GossipType.beacon_aggregate_and_proof}, {type: GossipType.voluntary_exit}, {type: GossipType.proposer_slashing}, {type: GossipType.attester_slashing}, ]; - // After Deneb only track beacon_block_and_blobs_sidecar topic - if (ForkSeq[fork] < ForkSeq.deneb) { - topics.push({type: GossipType.beacon_block}); - } else { - topics.push({type: GossipType.beacon_block_and_blobs_sidecar}); + // After Deneb also track blob_sidecar_{index} + if (ForkSeq[fork] >= ForkSeq.deneb) { + for (let index = 0; index < MAX_BLOBS_PER_BLOCK; index++) { + topics.push({type: GossipType.blob_sidecar, index}); + } } // capella @@ -265,7 +262,6 @@ function parseEncodingStr(encodingStr: string): GossipEncoding { export const gossipTopicIgnoreDuplicatePublishError: Record = { [GossipType.beacon_block]: true, [GossipType.blob_sidecar]: true, - [GossipType.beacon_block_and_blobs_sidecar]: true, [GossipType.beacon_aggregate_and_proof]: true, [GossipType.beacon_attestation]: true, [GossipType.voluntary_exit]: true, diff --git a/packages/beacon-node/src/network/processor/extractSlotRootFns.ts b/packages/beacon-node/src/network/processor/extractSlotRootFns.ts index 34b313e920b1..24fcfaae6cbc 100644 --- a/packages/beacon-node/src/network/processor/extractSlotRootFns.ts +++ b/packages/beacon-node/src/network/processor/extractSlotRootFns.ts @@ -4,7 +4,7 @@ import { getBlockRootFromSignedAggregateAndProofSerialized, getSlotFromAttestationSerialized, getSlotFromSignedAggregateAndProofSerialized, - getSlotFromSignedBeaconBlockAndBlobsSidecarSerialized, + getSlotFromSignedBlobSidecarSerialized, getSlotFromSignedBeaconBlockSerialized, } from "../../util/sszBytes.js"; import {GossipType} from "../gossip/index.js"; @@ -42,8 +42,8 @@ export function createExtractBlockSlotRootFns(): ExtractSlotRootFns { } return {slot}; }, - [GossipType.beacon_block_and_blobs_sidecar]: (data: Uint8Array): SlotOptionalRoot | null => { - const slot = getSlotFromSignedBeaconBlockAndBlobsSidecarSerialized(data); + [GossipType.blob_sidecar]: (data: Uint8Array): SlotOptionalRoot | null => { + const slot = getSlotFromSignedBlobSidecarSerialized(data); if (slot === null) { return null; diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 4399bda0b004..2ceafe09671c 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -35,7 +35,6 @@ import {NetworkEvent, NetworkEventBus} from "../events.js"; import {PeerAction} from "../peers/index.js"; import {validateLightClientFinalityUpdate} from "../../chain/validation/lightClientFinalityUpdate.js"; import {validateLightClientOptimisticUpdate} from "../../chain/validation/lightClientOptimisticUpdate.js"; -import {validateGossipBlobsSidecar} from "../../chain/validation/blobsSidecar.js"; import {BlockInput, BlockSource, getBlockInput} from "../../chain/blocks/types.js"; import {sszDeserialize} from "../gossip/topic.js"; import {INetworkCore} from "../core/index.js"; @@ -130,8 +129,8 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH .processBlock(blockInput, { // proposer signature already checked in validateBeaconBlock() validProposerSignature: true, - // blobsSidecar already checked in validateGossipBlobsSidecar() - validBlobsSidecar: true, + // blobSidecars already checked in validateGossipBlobSidecars() + validBlobSidecars: true, // It's critical to keep a good number of mesh peers. // To do that, the Gossip Job Wait Time should be consistently <3s to avoid the behavior penalties in gossip // Gossip Job Wait Time depends on the BLS Job Wait Time @@ -186,22 +185,6 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH // TODO DENEB: impl to be added on migration of blockinput }, - [GossipType.beacon_block_and_blobs_sidecar]: async ({serializedData}, topic, peerIdStr, seenTimestampSec) => { - const blockAndBlocks = sszDeserialize(topic, serializedData); - const {beaconBlock, blobsSidecar} = blockAndBlocks; - // TODO Deneb: Should throw for pre fork blocks? - if (config.getForkSeq(beaconBlock.message.slot) < ForkSeq.deneb) { - throw new GossipActionError(GossipAction.REJECT, {code: "PRE_DENEB_BLOCK"}); - } - - // Validate block + blob. Then forward, then handle both - // TODO DENEB: replace null with proper binary data for block and blobs separately - const blockInput = getBlockInput.postDeneb(config, beaconBlock, BlockSource.gossip, blobsSidecar, null); - await validateBeaconBlock(blockInput, topic.fork, peerIdStr, seenTimestampSec); - validateGossipBlobsSidecar(beaconBlock, blobsSidecar); - handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec); - }, - [GossipType.beacon_aggregate_and_proof]: async ({serializedData}, topic, _peer, seenTimestampSec) => { let validationResult: AggregateAndProofValidationResult; const signedAggregateAndProof = sszDeserialize(topic, serializedData); diff --git a/packages/beacon-node/src/network/processor/gossipQueues.ts b/packages/beacon-node/src/network/processor/gossipQueues.ts index 63abd16ad3a8..389ba7acca81 100644 --- a/packages/beacon-node/src/network/processor/gossipQueues.ts +++ b/packages/beacon-node/src/network/processor/gossipQueues.ts @@ -41,12 +41,6 @@ const gossipQueueOpts: { type: QueueType.FIFO, dropOpts: {type: DropType.count, count: 1}, }, - // TODO DENEB: What's a good queue max given that now blocks are much bigger? - [GossipType.beacon_block_and_blobs_sidecar]: { - maxLength: 32, - type: QueueType.FIFO, - dropOpts: {type: DropType.count, count: 1}, - }, // lighthoue has aggregate_queue 4096 and unknown_block_aggregate_queue 1024, we use single queue [GossipType.beacon_aggregate_and_proof]: { maxLength: 5120, diff --git a/packages/beacon-node/src/network/processor/index.ts b/packages/beacon-node/src/network/processor/index.ts index 9d5d05e7a672..dbd7c56cf9df 100644 --- a/packages/beacon-node/src/network/processor/index.ts +++ b/packages/beacon-node/src/network/processor/index.ts @@ -57,7 +57,6 @@ type WorkOpts = { const executeGossipWorkOrderObj: Record = { [GossipType.beacon_block]: {bypassQueue: true}, [GossipType.blob_sidecar]: {bypassQueue: true}, - [GossipType.beacon_block_and_blobs_sidecar]: {bypassQueue: true}, [GossipType.beacon_aggregate_and_proof]: {}, [GossipType.voluntary_exit]: {}, [GossipType.bls_to_execution_change]: {}, @@ -239,7 +238,7 @@ export class NetworkProcessor { } if ( slot === this.chain.clock.currentSlot && - (topicType === GossipType.beacon_block || topicType === GossipType.beacon_block_and_blobs_sidecar) + (topicType === GossipType.beacon_block || topicType === GossipType.blob_sidecar) ) { // in the worse case if the current slot block is not valid, this will be reset in the next slot this.isProcessingCurrentSlotBlock = true; diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 9604a5fb1ea4..46c28f7948fa 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -180,29 +180,28 @@ export function getSlotFromSignedBeaconBlockSerialized(data: Uint8Array): Slot | } /** - * 4 + 4 + SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK = 4 + 4 + (4 + 96) = 108 - * class SignedBeaconBlockAndBlobsSidecar(Container): - * beaconBlock: SignedBeaconBlock [offset - 4 bytes] - * blobsSidecar: BlobsSidecar, + * 4 + 96 = 100 + * ``` + * class SignedBlobSidecar(Container): + * message: BlobSidecar [fixed] + * signature: BLSSignature [fixed] + * + * class BlobSidecar(Container): + * blockRoot: Root [fixed - 32 bytes ], + * index: BlobIndex [fixed - 8 bytes ], + * slot: Slot [fixed - 8 bytes] + * ... + * ``` */ -/** - * Variable size. - * class BlobsSidecar(Container): - * beaconBlockRoot: Root, - * beaconBlockSlot: Slot, - * blobs: Blobs, - * kzgAggregatedProof: KZGProof, - */ -const SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK_AND_BLOBS_SIDECAR = - VARIABLE_FIELD_OFFSET + VARIABLE_FIELD_OFFSET + SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK; +const SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR = 32 + 8; -export function getSlotFromSignedBeaconBlockAndBlobsSidecarSerialized(data: Uint8Array): Slot | null { - if (data.length < SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK_AND_BLOBS_SIDECAR + SLOT_SIZE) { +export function getSlotFromSignedBlobSidecarSerialized(data: Uint8Array): Slot | null { + if (data.length < SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR + SLOT_SIZE) { return null; } - return getSlotFromOffset(data, SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK_AND_BLOBS_SIDECAR); + return getSlotFromOffset(data, SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR); } function getSlotFromOffset(data: Uint8Array, offset: number): Slot { diff --git a/packages/beacon-node/test/e2e/network/network.test.ts b/packages/beacon-node/test/e2e/network/network.test.ts index 5126796074df..13cdd6c20d71 100644 --- a/packages/beacon-node/test/e2e/network/network.test.ts +++ b/packages/beacon-node/test/e2e/network/network.test.ts @@ -131,11 +131,11 @@ function runTests(this: Mocha.Suite, {useWorker}: {useWorker: boolean}): void { await netA.subscribeGossipCoreTopics(); expect(await getTopics(netA)).deep.equals([ + "/eth2/18ae4ccb/beacon_block/ssz_snappy", "/eth2/18ae4ccb/beacon_aggregate_and_proof/ssz_snappy", "/eth2/18ae4ccb/voluntary_exit/ssz_snappy", "/eth2/18ae4ccb/proposer_slashing/ssz_snappy", "/eth2/18ae4ccb/attester_slashing/ssz_snappy", - "/eth2/18ae4ccb/beacon_block/ssz_snappy", ]); await netA.unsubscribeGossipCoreTopics(); diff --git a/packages/beacon-node/test/spec/presets/fork_choice.ts b/packages/beacon-node/test/spec/presets/fork_choice.ts index 3ebe850c75e2..5fae3acc3a5e 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.ts @@ -169,7 +169,7 @@ export const forkChoiceTest = try { await chain.processBlock(blockImport, { seenTimestampSec: tickTime, - validBlobsSidecar: true, + validBlobSidecars: true, importAttestations: AttestationImportOpt.Force, }); if (!isValid) throw Error("Expect error since this is a negative test"); diff --git a/packages/beacon-node/test/unit/network/gossip/topic.test.ts b/packages/beacon-node/test/unit/network/gossip/topic.test.ts index c2e6bb5c8015..eee3f2fa3ff9 100644 --- a/packages/beacon-node/test/unit/network/gossip/topic.test.ts +++ b/packages/beacon-node/test/unit/network/gossip/topic.test.ts @@ -21,12 +21,6 @@ describe("network / gossip / topic", function () { topicStr: "/eth2/46acb19a/blob_sidecar_1/ssz_snappy", }, ], - [GossipType.beacon_block_and_blobs_sidecar]: [ - { - topic: {type: GossipType.beacon_block_and_blobs_sidecar, fork: ForkName.deneb, encoding}, - topicStr: "/eth2/46acb19a/beacon_block_and_blobs_sidecar/ssz_snappy", - }, - ], [GossipType.beacon_aggregate_and_proof]: [ { topic: {type: GossipType.beacon_aggregate_and_proof, fork: ForkName.phase0, encoding}, diff --git a/packages/beacon-node/test/unit/util/kzg.test.ts b/packages/beacon-node/test/unit/util/kzg.test.ts index 797136a17abf..6b6b92bd645e 100644 --- a/packages/beacon-node/test/unit/util/kzg.test.ts +++ b/packages/beacon-node/test/unit/util/kzg.test.ts @@ -4,6 +4,7 @@ import {BYTES_PER_FIELD_ELEMENT, BLOB_TX_TYPE} from "@lodestar/params"; import {kzgCommitmentToVersionedHash} from "@lodestar/state-transition"; import {loadEthereumTrustedSetup, initCKZG, ckzg, FIELD_ELEMENTS_PER_BLOB_MAINNET} from "../../../src/util/kzg.js"; +import {validateBlobSidecars, validateGossipBlobSidecar} from "../../../src/chain/validation/blobSidecar.js"; import {getMockBeaconChain} from "../../utils/mocks/chain.js"; describe("C-KZG", async () => { @@ -68,7 +69,12 @@ describe("C-KZG", async () => { expect(signedBlobSidecars.length).to.equal(2); - // TODO DENEB: add full validation + // Full validation + validateBlobSidecars(slot, blockRoot, kzgCommitments, blobSidecars); + + signedBlobSidecars.forEach(async (signedBlobSidecar) => { + await validateGossipBlobSidecar(chain.config, chain, signedBlobSidecar, signedBlobSidecar.message.index); + }); }); }); diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index a3d9f54aab54..58b39dda82bf 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -11,7 +11,7 @@ import { getSlotFromSignedAggregateAndProofSerialized, getSignatureFromAttestationSerialized, getSlotFromSignedBeaconBlockSerialized, - getSlotFromSignedBeaconBlockAndBlobsSidecarSerialized, + getSlotFromSignedBlobSidecarSerialized, } from "../../../src/util/sszBytes.js"; describe("attestation SSZ serialized picking", () => { @@ -148,25 +148,20 @@ describe("signedBeaconBlock SSZ serialized picking", () => { }); }); -describe("signedBeaconBlockAndBlobsSidecar SSZ serialized picking", () => { - const testCases = [ - ssz.deneb.SignedBeaconBlockAndBlobsSidecar.defaultValue(), - signedBeaconBlockAndBlobsSidecarFromValues(1_000_000), - ]; +describe("signedBlobSidecar SSZ serialized picking", () => { + const testCases = [ssz.deneb.SignedBlobSidecar.defaultValue(), signedBlobSidecarFromValues(1_000_000)]; - for (const [i, signedBeaconBlockAndBlobsSidecar] of testCases.entries()) { - const bytes = ssz.deneb.SignedBeaconBlockAndBlobsSidecar.serialize(signedBeaconBlockAndBlobsSidecar); - it(`signedBeaconBlockAndBlobsSidecar ${i}`, () => { - expect(getSlotFromSignedBeaconBlockAndBlobsSidecarSerialized(bytes)).equals( - signedBeaconBlockAndBlobsSidecar.beaconBlock.message.slot - ); + for (const [i, signedBlobSidecar] of testCases.entries()) { + const bytes = ssz.deneb.SignedBlobSidecar.serialize(signedBlobSidecar); + it(`signedBlobSidecar ${i}`, () => { + expect(getSlotFromSignedBlobSidecarSerialized(bytes)).equals(signedBlobSidecar.message.slot); }); } - it("getSlotFromSignedBeaconBlockAndBlobsSidecarSerialized - invalid data", () => { - const invalidSlotDataSizes = [0, 50, 112]; + it("signedBlobSidecar - invalid data", () => { + const invalidSlotDataSizes = [0, 20, 38]; for (const size of invalidSlotDataSizes) { - expect(getSlotFromSignedBeaconBlockAndBlobsSidecarSerialized(Buffer.alloc(size))).to.be.null; + expect(getSlotFromSignedBlobSidecarSerialized(Buffer.alloc(size))).to.be.null; } }); }); @@ -205,8 +200,8 @@ function signedBeaconBlockFromValues(slot: Slot): phase0.SignedBeaconBlock { return signedBeaconBlock; } -function signedBeaconBlockAndBlobsSidecarFromValues(slot: Slot): deneb.SignedBeaconBlockAndBlobsSidecar { - const signedBeaconBlockAndBlobsSidecar = ssz.deneb.SignedBeaconBlockAndBlobsSidecar.defaultValue(); - signedBeaconBlockAndBlobsSidecar.beaconBlock.message.slot = slot; - return signedBeaconBlockAndBlobsSidecar; +function signedBlobSidecarFromValues(slot: Slot): deneb.SignedBlobSidecar { + const signedBlobSidecar = ssz.deneb.SignedBlobSidecar.defaultValue(); + signedBlobSidecar.message.slot = slot; + return signedBlobSidecar; } diff --git a/packages/beacon-node/test/utils/node/beacon.ts b/packages/beacon-node/test/utils/node/beacon.ts index bb9c4546d77d..6c62e22939d3 100644 --- a/packages/beacon-node/test/utils/node/beacon.ts +++ b/packages/beacon-node/test/utils/node/beacon.ts @@ -80,9 +80,9 @@ export async function getDevBeaconNode( await db.blockArchive.add(block); if (config.getForkSeq(GENESIS_SLOT) >= ForkSeq.deneb) { - const blobsSidecar = ssz.deneb.BlobsSidecar.defaultValue(); - blobsSidecar.beaconBlockRoot = config.getForkTypes(GENESIS_SLOT).BeaconBlock.hashTreeRoot(block.message); - await db.blobsSidecar.add(blobsSidecar); + const blobSidecars = ssz.deneb.BlobSidecars.defaultValue(); + const blockRoot = config.getForkTypes(GENESIS_SLOT).BeaconBlock.hashTreeRoot(block.message); + await db.blobSidecars.add({blobSidecars, slot: GENESIS_SLOT, blockRoot}); } } diff --git a/packages/types/src/deneb/sszTypes.ts b/packages/types/src/deneb/sszTypes.ts index 70101a0135ac..eb04bb1e7a38 100644 --- a/packages/types/src/deneb/sszTypes.ts +++ b/packages/types/src/deneb/sszTypes.ts @@ -54,6 +54,7 @@ export const BlindedBlob = Bytes32; export const BlindedBlobs = new ListCompositeType(BlindedBlob, MAX_BLOBS_PER_BLOCK); export const VersionedHash = Bytes32; export const BlobKzgCommitments = new ListCompositeType(KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK); +export const KZGProofs = new ListCompositeType(KZGProof, MAX_BLOBS_PER_BLOCK); // Constants diff --git a/packages/types/src/deneb/types.ts b/packages/types/src/deneb/types.ts index a4d2286af1e6..f91e94a68190 100644 --- a/packages/types/src/deneb/types.ts +++ b/packages/types/src/deneb/types.ts @@ -18,6 +18,7 @@ export type SignedBlindedBlobSidecar = ValueOf; export type BlobKzgCommitments = ValueOf; +export type KZGProofs = ValueOf; export type Polynomial = ValueOf; export type PolynomialAndCommitment = ValueOf; export type BLSFieldElement = ValueOf;