diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76d62ae576be..43ceee898d85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -271,9 +271,9 @@ jobs: key: ${{ runner.os }}-node-${{ matrix.node }}-${{ github.sha }} fail-on-cache-miss: true - name: Install Chrome browser - run: npx @puppeteer/browsers install chrome + run: npx @puppeteer/browsers install chromedriver@latest --path /tmp - name: Install Firefox browser - run: npx @puppeteer/browsers install firefox + run: npx @puppeteer/browsers install firefox@latest --path /tmp - name: Browser tests run: | export DISPLAY=':99.0' diff --git a/package.json b/package.json index 158ac2affe68..8e6dad1fdea2 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "@types/sinon-chai": "^3.2.9", "@typescript-eslint/eslint-plugin": "6.7.2", "@typescript-eslint/parser": "6.7.2", - "@vitest/coverage-v8": "^1.0.1", - "@vitest/browser": "^1.0.1", + "@vitest/coverage-v8": "^1.1.0", + "@vitest/browser": "^1.1.0", "c8": "^8.0.1", "chai": "^4.3.8", "chai-as-promised": "^7.1.1", @@ -97,17 +97,18 @@ "ts-node": "^10.9.1", "typescript": "^5.2.2", "typescript-docs-verifier": "^2.5.0", - "vite-plugin-node-polyfills": "^0.17.0", - "vite-plugin-top-level-await": "^1.3.1", - "vitest": "^1.0.2", + "vite-plugin-node-polyfills": "^0.18.0", + "vite-plugin-top-level-await": "^1.4.1", + "vitest": "^1.1.0", "vitest-when": "^0.3.0", "wait-port": "^1.1.0", - "webdriverio": "^8.24.12", + "webdriverio": "^8.27.0", "webpack": "^5.88.2" }, "resolutions": { "dns-over-http-resolver": "^2.1.1", "chai": "^4.3.10", - "loupe": "^2.3.6" + "loupe": "^2.3.6", + "vite": "^5.0.0" } } diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index 53ebb93692dc..b56006fe4191 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -1,17 +1,7 @@ import {ContainerType} from "@chainsafe/ssz"; import {ForkName} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; -import { - phase0, - allForks, - Slot, - Root, - ssz, - RootHex, - deneb, - isSignedBlockContents, - isSignedBlindedBlockContents, -} from "@lodestar/types"; +import {phase0, allForks, Slot, Root, ssz, RootHex, deneb, isSignedBlockContents} from "@lodestar/types"; import { RoutesData, @@ -30,10 +20,7 @@ import { import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; import {parseAcceptHeader, writeAcceptHeader} from "../../../utils/acceptHeader.js"; import {ApiClientResponse, ResponseFormat} from "../../../interfaces.js"; -import { - allForksSignedBlockContentsReqSerializer, - allForksSignedBlindedBlockContentsReqSerializer, -} from "../../../utils/routes.js"; +import {allForksSignedBlockContentsReqSerializer} from "../../../utils/routes.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -207,7 +194,7 @@ export type Api = { * Publish a signed blinded block by submitting it to the mev relay and patching in the block * transactions beacon node gets in response. */ - publishBlindedBlock(blindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents): Promise< + publishBlindedBlock(blindedBlock: allForks.SignedBlindedBeaconBlock): Promise< ApiClientResponse< { [HttpStatusCode.OK]: void; @@ -218,7 +205,7 @@ export type Api = { >; publishBlindedBlockV2( - blindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents, + blindedBlockOrContents: allForks.SignedBlindedBeaconBlock, opts: {broadcastValidation?: BroadcastValidation} ): Promise< ApiClientResponse< @@ -315,16 +302,9 @@ export function getReqSerializers(config: ChainForkConfig): ReqSerializers config.getBlindedForkTypes(data.message.slot).SignedBeaconBlock; - const AllForksSignedBlindedBlockOrContents: TypeJson = { - toJson: (data) => - isSignedBlindedBlockContents(data) - ? allForksSignedBlindedBlockContentsReqSerializer(getSignedBlindedBeaconBlockType).toJson(data) - : getSignedBlindedBeaconBlockType(data).toJson(data), - - fromJson: (data) => - (data as {signed_blinded_block: unknown}).signed_blinded_block !== undefined - ? allForksSignedBlindedBlockContentsReqSerializer(getSignedBlindedBeaconBlockType).fromJson(data) - : getSignedBlindedBeaconBlockType(data as allForks.SignedBlindedBeaconBlock).fromJson(data), + const AllForksSignedBlindedBlock: TypeJson = { + toJson: (data) => getSignedBlindedBeaconBlockType(data).toJson(data), + fromJson: (data) => getSignedBlindedBeaconBlockType(data as allForks.SignedBlindedBeaconBlock).fromJson(data), }; return { @@ -353,14 +333,14 @@ export function getReqSerializers(config: ChainForkConfig): ReqSerializers ({ - body: AllForksSignedBlindedBlockOrContents.toJson(item), + body: AllForksSignedBlindedBlock.toJson(item), query: {broadcast_validation: broadcastValidation}, }), parseReq: ({body, query}) => [ - AllForksSignedBlindedBlockOrContents.fromJson(body), + AllForksSignedBlindedBlock.fromJson(body), {broadcastValidation: query.broadcast_validation as BroadcastValidation}, ], schema: { diff --git a/packages/api/src/beacon/routes/validator.ts b/packages/api/src/beacon/routes/validator.ts index f5ae20937a0e..f7a731783681 100644 --- a/packages/api/src/beacon/routes/validator.ts +++ b/packages/api/src/beacon/routes/validator.ts @@ -1,5 +1,5 @@ import {ContainerType, fromHexString, toHexString, Type} from "@chainsafe/ssz"; -import {ForkName, ForkBlobs, isForkBlobs, isForkExecution, ForkPreBlobs} from "@lodestar/params"; +import {ForkName, ForkBlobs, isForkBlobs, isForkExecution, ForkPreBlobs, ForkExecution} from "@lodestar/params"; import { allForks, altair, @@ -37,7 +37,7 @@ import { TypeJson, } from "../../utils/index.js"; import {fromU64Str, fromGraffitiHex, toU64Str, U64Str, toGraffitiHex} from "../../utils/serdes.js"; -import {allForksBlockContentsResSerializer, allForksBlindedBlockContentsResSerializer} from "../../utils/routes.js"; +import {allForksBlockContentsResSerializer} from "../../utils/routes.js"; import {ExecutionOptimistic} from "./beacon/block.js"; export enum BuilderSelection { @@ -59,14 +59,14 @@ export type ProduceBlockOrContentsRes = {executionPayloadValue: Wei; consensusBl | {data: allForks.BeaconBlock; version: ForkPreBlobs} | {data: allForks.BlockContents; version: ForkBlobs} ); -export type ProduceBlindedBlockOrContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Gwei} & ( - | {data: allForks.BlindedBeaconBlock; version: ForkPreBlobs} - | {data: allForks.BlindedBlockContents; version: ForkBlobs} -); +export type ProduceBlindedBlockRes = {executionPayloadValue: Wei; consensusBlockValue: Gwei} & { + data: allForks.BlindedBeaconBlock; + version: ForkExecution; +}; export type ProduceFullOrBlindedBlockOrContentsRes = | (ProduceBlockOrContentsRes & {executionPayloadBlinded: false}) - | (ProduceBlindedBlockOrContentsRes & {executionPayloadBlinded: true}); + | (ProduceBlindedBlockRes & {executionPayloadBlinded: true}); // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -287,7 +287,7 @@ export type Api = { ): Promise< ApiClientResponse< { - [HttpStatusCode.OK]: ProduceBlindedBlockOrContentsRes; + [HttpStatusCode.OK]: ProduceBlindedBlockRes; }, HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE > @@ -721,13 +721,11 @@ export function getReturnTypes(): ReturnTypes { isForkBlobs(fork) ? allForksBlockContentsResSerializer(fork) : ssz[fork].BeaconBlock ) ) as TypeJson; - const produceBlindedBlockOrContents = WithBlockValues( - WithVersion((fork: ForkName) => - isForkBlobs(fork) - ? allForksBlindedBlockContentsResSerializer(fork) - : ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock + const produceBlindedBlock = WithBlockValues( + WithVersion( + (fork: ForkName) => ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock ) - ) as TypeJson; + ) as TypeJson; return { getAttesterDuties: WithDependentRootExecutionOptimistic(ArrayOf(AttesterDuty)), @@ -741,7 +739,7 @@ export function getReturnTypes(): ReturnTypes { if (data.executionPayloadBlinded) { return { execution_payload_blinded: true, - ...(produceBlindedBlockOrContents.toJson(data) as Record), + ...(produceBlindedBlock.toJson(data) as Record), }; } else { return { @@ -752,13 +750,13 @@ export function getReturnTypes(): ReturnTypes { }, fromJson: (data) => { if ((data as {execution_payload_blinded: true}).execution_payload_blinded) { - return {executionPayloadBlinded: true, ...produceBlindedBlockOrContents.fromJson(data)}; + return {executionPayloadBlinded: true, ...produceBlindedBlock.fromJson(data)}; } else { return {executionPayloadBlinded: false, ...produceBlockOrContents.fromJson(data)}; } }, }, - produceBlindedBlock: produceBlindedBlockOrContents, + produceBlindedBlock, produceAttestationData: ContainerData(ssz.phase0.AttestationData), produceSyncCommitteeContribution: ContainerData(ssz.altair.SyncCommitteeContribution), diff --git a/packages/api/src/beacon/server/validator.ts b/packages/api/src/beacon/server/validator.ts index 6bf446e05a16..5d6c22557060 100644 --- a/packages/api/src/beacon/server/validator.ts +++ b/packages/api/src/beacon/server/validator.ts @@ -4,6 +4,28 @@ import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; import {ServerApi} from "../../interfaces.js"; export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - // All routes return JSON, use a server auto-generator - return getGenericJsonServer, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); + const reqSerializers = getReqSerializers(); + const returnTypes = getReturnTypes(); + + // Most of routes return JSON, use a server auto-generator + const serverRoutes = getGenericJsonServer, ReqTypes>( + {routesData, getReturnTypes, getReqSerializers}, + config, + api + ); + return { + ...serverRoutes, + produceBlockV3: { + ...serverRoutes.produceBlockV3, + handler: async (req, res) => { + const response = await api.produceBlockV3(...reqSerializers.produceBlockV3.parseReq(req)); + void res.header("Eth-Consensus-Version", response.version); + void res.header("Eth-Execution-Payload-Blinded", response.executionPayloadBlinded); + void res.header("Eth-Execution-Payload-Value", response.executionPayloadValue); + void res.header("Eth-Consensus-Block-Value", response.consensusBlockValue); + + return returnTypes.produceBlockV3.toJson(response); + }, + }, + }; } diff --git a/packages/api/src/builder/routes.ts b/packages/api/src/builder/routes.ts index 0136f1deeac4..6f5a55f0dcff 100644 --- a/packages/api/src/builder/routes.ts +++ b/packages/api/src/builder/routes.ts @@ -34,7 +34,7 @@ export type Api = { HttpStatusCode.NOT_FOUND | HttpStatusCode.BAD_REQUEST > >; - submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlockOrContents): Promise< + submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise< ApiClientResponse< { [HttpStatusCode.OK]: { diff --git a/packages/api/src/utils/routes.ts b/packages/api/src/utils/routes.ts index 213a561efd58..77d177f7b24c 100644 --- a/packages/api/src/utils/routes.ts +++ b/packages/api/src/utils/routes.ts @@ -11,12 +11,14 @@ export function allForksSignedBlockContentsReqSerializer( return { toJson: (data) => ({ signed_block: blockSerializer(data.signedBlock).toJson(data.signedBlock), - signed_blob_sidecars: ssz.deneb.SignedBlobSidecars.toJson(data.signedBlobSidecars), + kzg_proofs: ssz.deneb.KZGProofs.toJson(data.kzgProofs), + blobs: ssz.deneb.Blobs.toJson(data.blobs), }), - fromJson: (data: {signed_block: unknown; signed_blob_sidecars: unknown}) => ({ + fromJson: (data: {signed_block: unknown; kzg_proofs: unknown; blobs: unknown}) => ({ signedBlock: blockSerializer(data.signed_block as allForks.SignedBeaconBlock).fromJson(data.signed_block), - signedBlobSidecars: ssz.deneb.SignedBlobSidecars.fromJson(data.signed_blob_sidecars), + kzgProofs: ssz.deneb.KZGProofs.fromJson(data.kzg_proofs), + blobs: ssz.deneb.Blobs.fromJson(data.blobs), }), }; } @@ -25,44 +27,13 @@ export function allForksBlockContentsResSerializer(fork: ForkBlobs): TypeJson ({ block: (ssz.allForks[fork].BeaconBlock as allForks.AllForksSSZTypes["BeaconBlock"]).toJson(data.block), - blob_sidecars: ssz.deneb.BlobSidecars.toJson(data.blobSidecars), + kzg_proofs: ssz.deneb.KZGProofs.toJson(data.kzgProofs), + blobs: ssz.deneb.Blobs.toJson(data.blobs), }), - fromJson: (data: {block: unknown; blob_sidecars: unknown}) => ({ + fromJson: (data: {block: unknown; blob_sidecars: unknown; kzg_proofs: unknown; blobs: unknown}) => ({ block: ssz.allForks[fork].BeaconBlock.fromJson(data.block), - blobSidecars: ssz.deneb.BlobSidecars.fromJson(data.blob_sidecars), - }), - }; -} - -export function allForksSignedBlindedBlockContentsReqSerializer( - blockSerializer: (data: allForks.SignedBlindedBeaconBlock) => TypeJson -): TypeJson { - return { - toJson: (data) => ({ - signed_blinded_block: blockSerializer(data.signedBlindedBlock).toJson(data.signedBlindedBlock), - signed_blinded_blob_sidecars: ssz.deneb.SignedBlindedBlobSidecars.toJson(data.signedBlindedBlobSidecars), - }), - - fromJson: (data: {signed_blinded_block: unknown; signed_blinded_blob_sidecars: unknown}) => ({ - signedBlindedBlock: blockSerializer(data.signed_blinded_block as allForks.SignedBlindedBeaconBlock).fromJson( - data.signed_blinded_block - ), - signedBlindedBlobSidecars: ssz.deneb.SignedBlindedBlobSidecars.fromJson(data.signed_blinded_blob_sidecars), - }), - }; -} - -export function allForksBlindedBlockContentsResSerializer(fork: ForkBlobs): TypeJson { - return { - toJson: (data) => ({ - blinded_block: (ssz.allForksBlinded[fork].BeaconBlock as allForks.AllForksBlindedSSZTypes["BeaconBlock"]).toJson( - data.blindedBlock - ), - blinded_blob_sidecars: ssz.deneb.BlindedBlobSidecars.toJson(data.blindedBlobSidecars), - }), - fromJson: (data: {blinded_block: unknown; blinded_blob_sidecars: unknown}) => ({ - blindedBlock: ssz.allForksBlinded[fork].BeaconBlock.fromJson(data.blinded_block), - blindedBlobSidecars: ssz.deneb.BlindedBlobSidecars.fromJson(data.blinded_blob_sidecars), + kzgProofs: ssz.deneb.KZGProofs.fromJson(data.kzg_proofs), + blobs: ssz.deneb.Blobs.fromJson(data.blobs), }), }; } diff --git a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts index ef24724a34b1..89565426426e 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -1,16 +1,13 @@ import {fromHexString, toHexString} from "@chainsafe/ssz"; import {routes, ServerApi, ResponseFormat} from "@lodestar/api"; -import { - computeTimeAtSlot, - parseSignedBlindedBlockOrContents, - reconstructFullBlockOrContents, -} from "@lodestar/state-transition"; +import {computeTimeAtSlot, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import {sleep, toHex} from "@lodestar/utils"; import {allForks, deneb, isSignedBlockContents, ProducedBlockSource} from "@lodestar/types"; import {BlockSource, getBlockInput, ImportBlockOpts, BlockInput} from "../../../../chain/blocks/types.js"; import {promiseAllMaybeAsync} from "../../../../util/promises.js"; import {isOptimisticBlock} from "../../../../util/forkChoice.js"; +import {computeBlobSidecars} from "../../../../util/blobs.js"; import {BlockError, BlockErrorCode} from "../../../../chain/errors/index.js"; import {OpSource} from "../../../../metrics/validatorMonitor.js"; import {NetworkEvent} from "../../../../network/index.js"; @@ -45,22 +42,23 @@ export function getBeaconBlockApi({ opts: PublishBlockOpts = {} ) => { const seenTimestampSec = Date.now() / 1000; - let blockForImport: BlockInput, signedBlock: allForks.SignedBeaconBlock, signedBlobs: deneb.SignedBlobSidecars; + let blockForImport: BlockInput, signedBlock: allForks.SignedBeaconBlock, blobSidecars: deneb.BlobSidecars; if (isSignedBlockContents(signedBlockOrContents)) { - ({signedBlock, signedBlobSidecars: signedBlobs} = signedBlockOrContents); + ({signedBlock} = signedBlockOrContents); + blobSidecars = computeBlobSidecars(config, signedBlock, signedBlockOrContents); blockForImport = getBlockInput.postDeneb( config, signedBlock, BlockSource.api, - signedBlobs.map((sblob) => sblob.message), + blobSidecars, // don't bundle any bytes for block and blobs null, - signedBlobs.map(() => null) + blobSidecars.map(() => null) ); } else { signedBlock = signedBlockOrContents; - signedBlobs = []; + blobSidecars = []; // TODO: Once API supports submitting data as SSZ, replace null with blockBytes blockForImport = getBlockInput.preDeneb(config, signedBlock, BlockSource.api, null); } @@ -188,18 +186,15 @@ export function getBeaconBlockApi({ } throw e; }), - ...signedBlobs.map((signedBlob) => () => network.publishBlobSidecar(signedBlob)), + ...blobSidecars.map((blobSidecar) => () => network.publishBlobSidecar(blobSidecar)), ]; await promiseAllMaybeAsync(publishPromises); }; const publishBlindedBlock: ServerApi["publishBlindedBlock"] = async ( - signedBlindedBlockOrContents, + signedBlindedBlock, opts: PublishBlockOpts = {} ) => { - const {signedBlindedBlock, signedBlindedBlobSidecars} = - parseSignedBlindedBlockOrContents(signedBlindedBlockOrContents); - const slot = signedBlindedBlock.message.slot; const blockRoot = toHex( chain.config @@ -210,27 +205,31 @@ export function getBeaconBlockApi({ // Either the payload/blobs are cached from i) engine locally or ii) they are from the builder // // executionPayload can be null or a real payload in locally produced so check for presence of root - const source = chain.producedBlockRoot.has(blockRoot) ? ProducedBlockSource.engine : ProducedBlockSource.builder; - - const executionPayload = chain.producedBlockRoot.get(blockRoot) ?? null; - const blobSidecars = executionPayload - ? chain.producedBlobSidecarsCache.get(toHex(executionPayload.blockHash)) - : undefined; - const blobs = blobSidecars ? blobSidecars.map((blobSidecar) => blobSidecar.blob) : null; - - chain.logger.debug("Assembling blinded block for publishing", {source, blockRoot, slot}); + const executionPayload = chain.producedBlockRoot.get(blockRoot); + if (executionPayload !== undefined) { + const source = ProducedBlockSource.engine; + chain.logger.debug("Reconstructing signedBlockOrContents", {blockRoot, slot, source}); + + const contents = executionPayload + ? chain.producedContentsCache.get(toHex(executionPayload.blockHash)) ?? null + : null; + const signedBlockOrContents = reconstructFullBlockOrContents(signedBlindedBlock, {executionPayload, contents}); + + chain.logger.info("Publishing assembled block", {blockRoot, slot, source}); + return publishBlock(signedBlockOrContents, opts); + } else { + const source = ProducedBlockSource.builder; + chain.logger.debug("Reconstructing signedBlockOrContents", {blockRoot, slot, source}); - const signedBlockOrContents = - source === ProducedBlockSource.engine - ? reconstructFullBlockOrContents({signedBlindedBlock, signedBlindedBlobSidecars}, {executionPayload, blobs}) - : await reconstructBuilderBlockOrContents(chain, signedBlindedBlockOrContents); + const signedBlockOrContents = await reconstructBuilderBlockOrContents(chain, signedBlindedBlock); - // the full block is published by relay and it's possible that the block is already known to us - // by gossip - // - // see: https://github.com/ChainSafe/lodestar/issues/5404 - chain.logger.info("Publishing assembled block", {blockRoot, slot, source}); - return publishBlock(signedBlockOrContents, {...opts, ignoreIfKnown: true}); + // the full block is published by relay and it's possible that the block is already known to us + // by gossip + // + // see: https://github.com/ChainSafe/lodestar/issues/5404 + chain.logger.info("Publishing assembled block", {blockRoot, slot, source}); + return publishBlock(signedBlockOrContents, {...opts, ignoreIfKnown: true}); + } }; return { @@ -424,13 +423,13 @@ export function getBeaconBlockApi({ async function reconstructBuilderBlockOrContents( chain: ApiModules["chain"], - signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents + signedBlindedBlock: allForks.SignedBlindedBeaconBlock ): Promise { const executionBuilder = chain.executionBuilder; if (!executionBuilder) { throw Error("exeutionBuilder required to publish SignedBlindedBeaconBlock"); } - const signedBlockOrContents = await executionBuilder.submitBlindedBlock(signedBlindedBlockOrContents); + const signedBlockOrContents = await executionBuilder.submitBlindedBlock(signedBlindedBlock); return signedBlockOrContents; } diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 8f92fa483908..3f813f32a3fd 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -9,7 +9,6 @@ import { computeEpochAtSlot, getCurrentSlot, beaconBlockToBlinded, - blobSidecarsToBlinded, } from "@lodestar/state-transition"; import { GENESIS_SLOT, @@ -31,11 +30,11 @@ import { allForks, BLSSignature, isBlindedBeaconBlock, - isBlindedBlockContents, + isBlockContents, phase0, } from "@lodestar/types"; import {ExecutionStatus} from "@lodestar/fork-choice"; -import {toHex, racePromisesWithCutoff, RaceEvent} from "@lodestar/utils"; +import {toHex, racePromisesWithCutoff, RaceEvent, gweiToWei} from "@lodestar/utils"; import {AttestationError, AttestationErrorCode, GossipAction, SyncCommitteeError} from "../../../chain/errors/index.js"; import {validateApiAggregateAndProof} from "../../../chain/validation/index.js"; import {ZERO_HASH} from "../../../constants/index.js"; @@ -280,7 +279,7 @@ export function getValidatorApi({ ); } - const produceBlindedBlockOrContents = async function produceBlindedBlockOrContents( + const produceBuilderBlindedBlock = async function produceBuilderBlindedBlock( slot: Slot, randaoReveal: BLSSignature, graffiti: string, @@ -288,7 +287,12 @@ export function getValidatorApi({ { skipHeadChecksAndUpdate, }: Omit & {skipHeadChecksAndUpdate?: boolean} = {} - ): Promise { + ): Promise { + const version = config.getForkName(slot); + if (!isForkExecution(version)) { + throw Error(`Invalid fork=${version} for produceBuilderBlindedBlock`); + } + const source = ProducedBlockSource.builder; metrics?.blockProductionRequests.inc({source}); @@ -329,31 +333,17 @@ export function getValidatorApi({ root: toHexString(config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block)), }); - const version = config.getForkName(block.slot); if (chain.opts.persistProducedBlocks) { void chain.persistBlock(block, "produced_builder_block"); } - if (isForkBlobs(version)) { - const blockHash = toHex((block as bellatrix.BlindedBeaconBlock).body.executionPayloadHeader.blockHash); - const blindedBlobSidecars = chain.producedBlindedBlobSidecarsCache.get(blockHash); - if (blindedBlobSidecars === undefined) { - throw Error("blobSidecars missing in cache"); - } - return { - data: {blindedBlock: block, blindedBlobSidecars} as allForks.BlindedBlockContents, - version, - executionPayloadValue, - consensusBlockValue, - }; - } else { - return {data: block, version, executionPayloadValue, consensusBlockValue}; - } + + return {data: block, version, executionPayloadValue, consensusBlockValue}; } finally { if (timer) timer({source}); } }; - const produceFullBlockOrContents = async function produceFullBlockOrContents( + const produceEngineFullBlockOrContents = async function produceEngineFullBlockOrContents( slot: Slot, randaoReveal: BLSSignature, graffiti: string, @@ -407,12 +397,13 @@ export function getValidatorApi({ } if (isForkBlobs(version)) { const blockHash = toHex((block as bellatrix.BeaconBlock).body.executionPayload.blockHash); - const blobSidecars = chain.producedBlobSidecarsCache.get(blockHash); - if (blobSidecars === undefined) { - throw Error("blobSidecars missing in cache"); + const contents = chain.producedContentsCache.get(blockHash); + if (contents === undefined) { + throw Error("contents missing in cache"); } + return { - data: {block, blobSidecars} as allForks.BlockContents, + data: {block, ...contents} as allForks.BlockContents, version, executionPayloadValue, consensusBlockValue, @@ -460,12 +451,12 @@ export function getValidatorApi({ // Start calls for building execution and builder blocks const blindedBlockPromise = isBuilderEnabled ? // can't do fee recipient checks as builder bid doesn't return feeRecipient as of now - produceBlindedBlockOrContents(slot, randaoReveal, graffiti, { + produceBuilderBlindedBlock(slot, randaoReveal, graffiti, { feeRecipient, // skip checking and recomputing head in these individual produce calls skipHeadChecksAndUpdate: true, }).catch((e) => { - logger.error("produceBlindedBlockOrContents failed to produce block", {slot}, e); + logger.error("produceBuilderBlindedBlock failed to produce block", {slot}, e); return null; }) : null; @@ -481,13 +472,13 @@ export function getValidatorApi({ !isBuilderEnabled || builderSelection !== routes.validator.BuilderSelection.BuilderOnly ? // TODO deneb: builderSelection needs to be figured out if to be done beacon side // || builderSelection !== BuilderSelection.BuilderOnly - produceFullBlockOrContents(slot, randaoReveal, graffiti, { + produceEngineFullBlockOrContents(slot, randaoReveal, graffiti, { feeRecipient, strictFeeRecipientCheck, // skip checking and recomputing head in these individual produce calls skipHeadChecksAndUpdate: true, }).catch((e) => { - logger.error("produceFullBlockOrContents failed to produce block", {slot}, e); + logger.error("produceEngineFullBlockOrContents failed to produce block", {slot}, e); return null; }) : null; @@ -497,7 +488,7 @@ export function getValidatorApi({ // reference index of promises in the race const promisesOrder = [ProducedBlockSource.builder, ProducedBlockSource.engine]; [blindedBlock, fullBlock] = await racePromisesWithCutoff< - routes.validator.ProduceBlockOrContentsRes | routes.validator.ProduceBlindedBlockOrContentsRes | null + routes.validator.ProduceBlockOrContentsRes | routes.validator.ProduceBlindedBlockRes | null >( [blindedBlockPromise, fullBlockPromise], BLOCK_PRODUCTION_RACE_CUTOFF_MS, @@ -541,8 +532,8 @@ export function getValidatorApi({ const consensusBlockValueBuilder = blindedBlock?.consensusBlockValue ?? BigInt(0); const consensusBlockValueEngine = fullBlock?.consensusBlockValue ?? BigInt(0); - const blockValueBuilder = builderPayloadValue + consensusBlockValueBuilder; - const blockValueEngine = enginePayloadValue + consensusBlockValueEngine; + const blockValueBuilder = builderPayloadValue + gweiToWei(consensusBlockValueBuilder); // Total block value is in wei + const blockValueEngine = enginePayloadValue + gweiToWei(consensusBlockValueEngine); // Total block value is in wei let selectedSource: ProducedBlockSource | null = null; @@ -607,7 +598,7 @@ export function getValidatorApi({ executionPayloadBlinded: false; }; } else { - return {...blindedBlock, executionPayloadBlinded: true} as routes.validator.ProduceBlindedBlockOrContentsRes & { + return {...blindedBlock, executionPayloadBlinded: true} as routes.validator.ProduceBlindedBlockRes & { executionPayloadBlinded: true; }; } @@ -618,7 +609,7 @@ export function getValidatorApi({ randaoReveal, graffiti ) { - const producedData = await produceFullBlockOrContents(slot, randaoReveal, graffiti); + const producedData = await produceEngineFullBlockOrContents(slot, randaoReveal, graffiti); if (isForkBlobs(producedData.version)) { throw Error(`Invalid call to produceBlock for deneb+ fork=${producedData.version}`); } else { @@ -628,45 +619,35 @@ export function getValidatorApi({ } }; - const produceBlindedBlock: ServerApi["produceBlindedBlock"] = - async function produceBlindedBlock(slot, randaoReveal, graffiti) { - const producedData = await produceBlockV3(slot, randaoReveal, graffiti); - let blindedProducedData: routes.validator.ProduceBlindedBlockOrContentsRes; - - if (isForkBlobs(producedData.version)) { - if (isBlindedBlockContents(producedData.data as allForks.FullOrBlindedBlockContents)) { - blindedProducedData = producedData as routes.validator.ProduceBlindedBlockOrContentsRes; - } else { - // - const {block, blobSidecars} = producedData.data as allForks.BlockContents; - const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); - const blindedBlobSidecars = blobSidecarsToBlinded(blobSidecars); - - blindedProducedData = { - ...producedData, - data: {blindedBlock, blindedBlobSidecars}, - } as routes.validator.ProduceBlindedBlockOrContentsRes; - } + const produceEngineOrBuilderBlindedBlock: ServerApi["produceBlindedBlock"] = + async function produceEngineOrBuilderBlindedBlock(slot, randaoReveal, graffiti) { + const {data, executionPayloadValue, consensusBlockValue, version} = await produceBlockV3( + slot, + randaoReveal, + graffiti + ); + if (!isForkExecution(version)) { + throw Error(`Invalid fork=${version} for produceEngineOrBuilderBlindedBlock`); + } + const executionPayloadBlinded = true; + + if (isBlockContents(data)) { + const {block} = data; + const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); + return {executionPayloadValue, consensusBlockValue, data: blindedBlock, executionPayloadBlinded, version}; + } else if (isBlindedBeaconBlock(data)) { + return {executionPayloadValue, consensusBlockValue, data, executionPayloadBlinded, version}; } else { - if (isBlindedBeaconBlock(producedData.data)) { - blindedProducedData = producedData as routes.validator.ProduceBlindedBlockOrContentsRes; - } else { - const block = producedData.data; - const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); - blindedProducedData = { - ...producedData, - data: blindedBlock, - } as routes.validator.ProduceBlindedBlockOrContentsRes; - } + const blindedBlock = beaconBlockToBlinded(config, data as allForks.AllForksExecution["BeaconBlock"]); + return {executionPayloadValue, consensusBlockValue, data: blindedBlock, executionPayloadBlinded, version}; } - return blindedProducedData; }; return { produceBlock, - produceBlockV2: produceFullBlockOrContents, + produceBlockV2: produceEngineFullBlockOrContents, produceBlockV3, - produceBlindedBlock, + produceBlindedBlock: produceEngineOrBuilderBlindedBlock, async produceAttestationData(committeeIndex, slot) { notWhileSyncing(); diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index feaddfbad39d..12b43359fa4e 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -7,7 +7,6 @@ import { computeStartSlotAtEpoch, isStateValidatorsNodesPopulated, RootCache, - kzgCommitmentToVersionedHash, } from "@lodestar/state-transition"; import {routes} from "@lodestar/api"; import {ForkChoiceError, ForkChoiceErrorCode, EpochDifference, AncestorStatus} from "@lodestar/fork-choice"; @@ -16,6 +15,7 @@ import {ZERO_HASH_HEX} from "../../constants/index.js"; import {toCheckpointHex} from "../stateCache/index.js"; import {isOptimisticBlock} from "../../util/forkChoice.js"; import {isQueueErrorAborted} from "../../util/queue/index.js"; +import {kzgCommitmentToVersionedHash} from "../../util/blobs.js"; import {ChainEvent, ReorgEventData} from "../emitter.js"; import {REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC} from "../reprocess.js"; import type {BeaconChain} from "../chain.js"; diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index 01d04e9a876a..5dbe104c9541 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -5,7 +5,6 @@ import { isExecutionBlockBodyType, isMergeTransitionBlock as isMergeTransitionBlockFn, isExecutionEnabled, - kzgCommitmentToVersionedHash, } from "@lodestar/state-transition"; import {bellatrix, allForks, Slot, deneb} from "@lodestar/types"; import { @@ -24,6 +23,7 @@ import {ForkSeq, SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import {IExecutionEngine} from "../../execution/engine/interface.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; import {IClock} from "../../util/clock.js"; +import {kzgCommitmentToVersionedHash} from "../../util/blobs.js"; import {BlockProcessOpts} from "../options.js"; import {ExecutionPayloadStatus} from "../../execution/engine/interface.js"; import {IEth1ForBlockProduction} from "../../eth1/index.js"; diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index e093b4240828..5e38cf23f5de 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -88,7 +88,6 @@ import {CheckpointStateCache} from "./stateCache/stateContextCheckpointsCache.js * allow some margin if the node overloads. */ const DEFAULT_MAX_CACHED_PRODUCED_ROOTS = 4; -const DEFAULT_MAX_CACHED_BLOB_SIDECARS = 4; export class BeaconChain implements IBeaconChain { readonly genesisTime: UintNum64; @@ -138,8 +137,7 @@ export class BeaconChain implements IBeaconChain { readonly checkpointBalancesCache: CheckpointBalancesCache; readonly shufflingCache: ShufflingCache; /** Map keyed by executionPayload.blockHash of the block for those blobs */ - readonly producedBlobSidecarsCache = new Map(); - readonly producedBlindedBlobSidecarsCache = new Map(); + readonly producedContentsCache = new Map(); // Cache payload from the local execution so that produceBlindedBlock or produceBlockV3 and // send and get signed/published blinded versions which beacon can assemble into full before @@ -554,32 +552,9 @@ export class BeaconChain implements IBeaconChain { // publishing the blinded block's full version if (blobs.type === BlobsResultType.produced) { // body is of full type here - const blockHash = blobs.blockHash; - const blobSidecars = blobs.blobSidecars.map((blobSidecar) => ({ - ...blobSidecar, - blockRoot, - slot, - blockParentRoot: parentBlockRoot, - proposerIndex, - })); - - this.producedBlobSidecarsCache.set(blockHash, blobSidecars); - this.metrics?.blockProductionCaches.producedBlobSidecarsCache.set(this.producedBlobSidecarsCache.size); - } else if (blobs.type === BlobsResultType.blinded) { - // body is of blinded type here - const blockHash = blobs.blockHash; - const blindedBlobSidecars = blobs.blobSidecars.map((blindedBlobSidecar) => ({ - ...blindedBlobSidecar, - blockRoot, - slot, - blockParentRoot: parentBlockRoot, - proposerIndex, - })); - - this.producedBlindedBlobSidecarsCache.set(blockHash, blindedBlobSidecars); - this.metrics?.blockProductionCaches.producedBlindedBlobSidecarsCache.set( - this.producedBlindedBlobSidecarsCache.size - ); + const {blockHash, contents} = blobs; + this.producedContentsCache.set(blockHash, contents); + this.metrics?.blockProductionCaches.producedContentsCache.set(this.producedContentsCache.size); } return {block, executionPayloadValue, consensusBlockValue: proposerReward}; @@ -595,14 +570,14 @@ export class BeaconChain implements IBeaconChain { * kzg_aggregated_proof=compute_proof_from_blobs(blobs), * ) */ - getBlobSidecars(beaconBlock: deneb.BeaconBlock): deneb.BlobSidecars { + getContents(beaconBlock: deneb.BeaconBlock): deneb.Contents { const blockHash = toHex(beaconBlock.body.executionPayload.blockHash); - const blobSidecars = this.producedBlobSidecarsCache.get(blockHash); - if (!blobSidecars) { - throw Error(`No blobSidecars for executionPayload.blockHash ${blockHash}`); + const contents = this.producedContentsCache.get(blockHash); + if (!contents) { + throw Error(`No contents for executionPayload.blockHash ${blockHash}`); } - return blobSidecars; + return contents; } async processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise { @@ -884,19 +859,8 @@ export class BeaconChain implements IBeaconChain { this.metrics?.blockProductionCaches.producedBlindedBlockRoot.set(this.producedBlindedBlockRoot.size); if (this.config.getForkSeq(slot) >= ForkSeq.deneb) { - pruneSetToMax( - this.producedBlobSidecarsCache, - this.opts.maxCachedBlobSidecars ?? DEFAULT_MAX_CACHED_BLOB_SIDECARS - ); - this.metrics?.blockProductionCaches.producedBlobSidecarsCache.set(this.producedBlobSidecarsCache.size); - - pruneSetToMax( - this.producedBlindedBlobSidecarsCache, - this.opts.maxCachedBlobSidecars ?? DEFAULT_MAX_CACHED_BLOB_SIDECARS - ); - this.metrics?.blockProductionCaches.producedBlindedBlobSidecarsCache.set( - this.producedBlindedBlobSidecarsCache.size - ); + pruneSetToMax(this.producedContentsCache, this.opts.maxCachedProducedRoots ?? DEFAULT_MAX_CACHED_PRODUCED_ROOTS); + this.metrics?.blockProductionCaches.producedContentsCache.set(this.producedContentsCache.size); } const metrics = this.metrics; diff --git a/packages/beacon-node/src/chain/errors/blobSidecarError.ts b/packages/beacon-node/src/chain/errors/blobSidecarError.ts index e242cbcb11ba..38a125bbb21e 100644 --- a/packages/beacon-node/src/chain/errors/blobSidecarError.ts +++ b/packages/beacon-node/src/chain/errors/blobSidecarError.ts @@ -21,6 +21,7 @@ export enum BlobSidecarErrorCode { PARENT_UNKNOWN = "BLOB_SIDECAR_ERROR_PARENT_UNKNOWN", NOT_LATER_THAN_PARENT = "BLOB_SIDECAR_ERROR_NOT_LATER_THAN_PARENT", PROPOSAL_SIGNATURE_INVALID = "BLOB_SIDECAR_ERROR_PROPOSAL_SIGNATURE_INVALID", + INCLUSION_PROOF_INVALID = "BLOB_SIDECAR_ERROR_INCLUSION_PROOF_INVALID", INCORRECT_PROPOSER = "BLOB_SIDECAR_ERROR_INCORRECT_PROPOSER", } @@ -37,6 +38,7 @@ export type BlobSidecarErrorType = | {code: BlobSidecarErrorCode.PARENT_UNKNOWN; parentRoot: RootHex} | {code: BlobSidecarErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot} | {code: BlobSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID} + | {code: BlobSidecarErrorCode.INCLUSION_PROOF_INVALID} | {code: BlobSidecarErrorCode.INCORRECT_PROPOSER; proposerIndex: ValidatorIndex}; export class BlobSidecarGossipError extends GossipActionError {} diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index a209ec36276e..880a5e86071a 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -109,9 +109,8 @@ export interface IBeaconChain { readonly beaconProposerCache: BeaconProposerCache; readonly checkpointBalancesCache: CheckpointBalancesCache; - readonly producedBlobSidecarsCache: Map; + readonly producedContentsCache: Map; readonly producedBlockRoot: Map; - readonly producedBlindedBlobSidecarsCache: Map; readonly shufflingCache: ShufflingCache; readonly producedBlindedBlockRoot: Set; readonly opts: IChainOptions; @@ -153,7 +152,7 @@ export interface IBeaconChain { */ getBlockByRoot(root: RootHex): Promise<{block: allForks.SignedBeaconBlock; executionOptimistic: boolean} | null>; - getBlobSidecars(beaconBlock: deneb.BeaconBlock): deneb.BlobSidecars; + getContents(beaconBlock: deneb.BeaconBlock): deneb.Contents; produceBlock( blockAttributes: BlockAttributes diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 4c1bab7eedef..3c2bec223eca 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -35,10 +35,7 @@ import {PayloadId, IExecutionEngine, IExecutionBuilder, PayloadAttributes} from import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js"; import {IEth1ForBlockProduction} from "../../eth1/index.js"; import {numToQuantity} from "../../eth1/provider/utils.js"; -import { - validateBlobsAndKzgCommitments, - validateBlindedBlobsAndKzgCommitments, -} from "./validateBlobsAndKzgCommitments.js"; +import {validateBlobsAndKzgCommitments} from "./validateBlobsAndKzgCommitments.js"; // Time to provide the EL to generate a payload from new payload id const PAYLOAD_GENERATION_TIME_MS = 500; @@ -90,8 +87,8 @@ export enum BlobsResultType { export type BlobsResult = | {type: BlobsResultType.preDeneb} - | {type: BlobsResultType.produced; blobSidecars: deneb.BlobSidecars; blockHash: RootHex} - | {type: BlobsResultType.blinded; blobSidecars: deneb.BlindedBlobSidecars; blockHash: RootHex}; + | {type: BlobsResultType.produced; contents: deneb.Contents; blockHash: RootHex} + | {type: BlobsResultType.blinded}; export async function produceBlockBody( this: BeaconChain, @@ -247,35 +244,14 @@ export async function produceBlockBody( }); if (ForkSeq[fork] >= ForkSeq.deneb) { - const {blindedBlobsBundle} = builderRes; - if (blindedBlobsBundle === undefined) { - throw Error(`Invalid builder getHeader response for fork=${fork}, missing blindedBlobsBundle`); - } - - // validate blindedBlobsBundle - if (this.opts.sanityCheckExecutionEngineBlobs) { - validateBlindedBlobsAndKzgCommitments(builderRes.header, blindedBlobsBundle); + const {blobKzgCommitments} = builderRes; + if (blobKzgCommitments === undefined) { + throw Error(`Invalid builder getHeader response for fork=${fork}, missing blobKzgCommitments`); } - (blockBody as deneb.BlindedBeaconBlockBody).blobKzgCommitments = blindedBlobsBundle.commitments; - const blockHash = toHex(builderRes.header.blockHash); - - const blobSidecars = Array.from({length: blindedBlobsBundle.blobRoots.length}, (_v, index) => { - const blobRoot = blindedBlobsBundle.blobRoots[index]; - const commitment = blindedBlobsBundle.commitments[index]; - const proof = blindedBlobsBundle.proofs[index]; - const blindedBlobSidecar = { - index, - blobRoot, - kzgProof: proof, - kzgCommitment: commitment, - }; - // Other fields will be injected after postState is calculated - return blindedBlobSidecar; - }) as deneb.BlindedBlobSidecars; - blobsResult = {type: BlobsResultType.blinded, blobSidecars, blockHash}; - - Object.assign(logMeta, {blobs: blindedBlobsBundle.commitments.length}); + (blockBody as deneb.BlindedBeaconBlockBody).blobKzgCommitments = blobKzgCommitments; + blobsResult = {type: BlobsResultType.blinded}; + Object.assign(logMeta, {blobs: blobKzgCommitments.length}); } else { blobsResult = {type: BlobsResultType.preDeneb}; } @@ -348,23 +324,10 @@ export async function produceBlockBody( (blockBody as deneb.BeaconBlockBody).blobKzgCommitments = blobsBundle.commitments; const blockHash = toHex(executionPayload.blockHash); + const contents = {kzgProofs: blobsBundle.proofs, blobs: blobsBundle.blobs}; + blobsResult = {type: BlobsResultType.produced, contents, blockHash}; - const blobSidecars = Array.from({length: blobsBundle.blobs.length}, (_v, index) => { - const blob = blobsBundle.blobs[index]; - const commitment = blobsBundle.commitments[index]; - const proof = blobsBundle.proofs[index]; - const blobSidecar = { - index, - blob, - kzgProof: proof, - kzgCommitment: commitment, - }; - // Other fields will be injected after postState is calculated - return blobSidecar; - }) as deneb.BlobSidecars; - blobsResult = {type: BlobsResultType.produced, blobSidecars, blockHash}; - - Object.assign(logMeta, {blobs: blobSidecars.length}); + Object.assign(logMeta, {blobs: blobsBundle.commitments.length}); } else { blobsResult = {type: BlobsResultType.preDeneb}; } @@ -518,7 +481,7 @@ async function prepareExecutionPayloadHeader( ): Promise<{ header: allForks.ExecutionPayloadHeader; executionPayloadValue: Wei; - blindedBlobsBundle?: deneb.BlindedBlobsBundle; + blobKzgCommitments?: deneb.BlobKzgCommitments; }> { if (!chain.executionBuilder) { throw Error("executionBuilder required"); diff --git a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts index 0d00d0c8bd72..54e90672d189 100644 --- a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts +++ b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts @@ -1,4 +1,4 @@ -import {allForks, deneb} from "@lodestar/types"; +import {allForks} from "@lodestar/types"; import {BlobsBundle} from "../../execution/index.js"; /** @@ -13,15 +13,3 @@ export function validateBlobsAndKzgCommitments(payload: allForks.ExecutionPayloa ); } } - -export function validateBlindedBlobsAndKzgCommitments( - payload: allForks.ExecutionPayloadHeader, - blindedBlobsBundle: deneb.BlindedBlobsBundle -): void { - // sanity-check that the KZG commitments match the blobs (as produced by the execution engine) - if (blindedBlobsBundle.blobRoots.length !== blindedBlobsBundle.commitments.length) { - throw Error( - `BlindedBlobs bundle blobs len ${blindedBlobsBundle.blobRoots.length} != commitments len ${blindedBlobsBundle.commitments.length}` - ); - } -} diff --git a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts index 1257feb844b1..8b767975c112 100644 --- a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts +++ b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts @@ -2,6 +2,7 @@ import {toHexString} from "@chainsafe/ssz"; import {deneb, RootHex, ssz, allForks} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {pruneSetToMax} from "@lodestar/utils"; +import {BLOBSIDECAR_FIXED_SIZE} from "@lodestar/params"; import { BlockInput, @@ -12,11 +13,11 @@ import { GossipedInputType, } from "../blocks/types.js"; -export type GossipedBlockInput = +type GossipedBlockInput = | {type: GossipedInputType.block; signedBlock: allForks.SignedBeaconBlock; blockBytes: Uint8Array | null} - | {type: GossipedInputType.blob; signedBlob: deneb.SignedBlobSidecar; blobBytes: Uint8Array | null}; + | {type: GossipedInputType.blob; blobSidecar: deneb.BlobSidecar; blobBytes: Uint8Array | null}; -export type BlockInputCacheType = { +type BlockInputCacheType = { block?: allForks.SignedBeaconBlock; blockBytes?: Uint8Array | null; blobsCache: BlobsCache; @@ -26,8 +27,6 @@ export type BlockInputCacheType = { }; const MAX_GOSSIPINPUT_CACHE = 5; -// ssz.deneb.BlobSidecars.elementType.fixedSize, 131256 is size for mainnet preset; -const BLOBSIDECAR_FIXED_SIZE = ssz.deneb.BlobSidecars.elementType.fixedSize ?? 131256; /** * SeenGossipBlockInput tracks and caches the live blobs and blocks on the network to solve data availability @@ -68,13 +67,14 @@ export class SeenGossipBlockInput { blockCache.block = signedBlock; blockCache.blockBytes = blockBytes; } else { - const {signedBlob, blobBytes} = gossipedInput; - blockHex = toHexString(signedBlob.message.blockRoot); + const {blobSidecar, blobBytes} = gossipedInput; + const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobSidecar.signedBlockHeader.message); + blockHex = toHexString(blockRoot); blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(); // TODO: freetheblobs check if its the same blob or a duplicate and throw/take actions - blockCache.blobsCache.set(signedBlob.message.index, { - blobSidecar: signedBlob.message, + blockCache.blobsCache.set(blobSidecar.index, { + blobSidecar, // easily splice out the unsigned message as blob is a fixed length type blobBytes: blobBytes?.slice(0, BLOBSIDECAR_FIXED_SIZE) ?? null, }); diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts index b5aab323c269..29aa4e763380 100644 --- a/packages/beacon-node/src/chain/validation/blobSidecar.ts +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -1,7 +1,8 @@ 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 {deneb, Root, Slot, ssz} from "@lodestar/types"; +import {toHex, verifyMerkleBranch} from "@lodestar/utils"; +import {computeStartSlotAtEpoch, getBlockHeaderProposerSignatureSet} from "@lodestar/state-transition"; +import {KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, KZG_COMMITMENT_SUBTREE_INDEX0} from "@lodestar/params"; import {BlobSidecarGossipError, BlobSidecarErrorCode} from "../errors/blobSidecarError.js"; import {GossipAction} from "../errors/gossipValidation.js"; @@ -13,11 +14,10 @@ import {RegenCaller} from "../regen/index.js"; export async function validateGossipBlobSidecar( config: ChainForkConfig, chain: IBeaconChain, - signedBlob: deneb.SignedBlobSidecar, + blobSidecar: deneb.BlobSidecar, gossipIndex: number ): Promise { - const blobSidecar = signedBlob.message; - const blobSlot = blobSidecar.slot; + const blobSlot = blobSidecar.signedBlockHeader.message.slot; // [REJECT] The sidecar is for the correct topic -- i.e. sidecar.index matches the topic {index}. if (blobSidecar.index !== gossipIndex) { @@ -58,9 +58,10 @@ export async function validateGossipBlobSidecar( // 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 BlobSidecarGossipError(GossipAction.IGNORE, {code: BlobSidecarErrorCode.ALREADY_KNOWN, root: blockRoot}); + const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobSidecar.signedBlockHeader.message); + const blockHex = toHex(blockRoot); + if (chain.forkChoice.getBlockHex(blockHex) !== null) { + throw new BlobSidecarGossipError(GossipAction.IGNORE, {code: BlobSidecarErrorCode.ALREADY_KNOWN, root: blockHex}); } // TODO: freetheblobs - check for badblock @@ -69,7 +70,7 @@ export async function validateGossipBlobSidecar( // _[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 parentRoot = toHex(blobSidecar.signedBlockHeader.message.parentRoot); 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, @@ -97,18 +98,16 @@ export async function validateGossipBlobSidecar( // 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 + // [IGNORE] The block's parent (defined by 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). + // [REJECT] The block's parent (defined by block.parent_root) passes validation. const blockState = await chain.regen - .getBlockSlotState(parentRoot, blobSlot, {dontTransferCache: true}, RegenCaller.validateGossipBlob) + .getBlockSlotState(parentRoot, blobSlot, {dontTransferCache: true}, RegenCaller.validateGossipBlock) .catch(() => { throw new BlobSidecarGossipError(GossipAction.IGNORE, {code: BlobSidecarErrorCode.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); + // [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey. + const signatureSet = getBlockHeaderProposerSignatureSet(blockState, blobSidecar.signedBlockHeader); // Don't batch so verification is not delayed if (!(await chain.bls.verifySignatureSets([signatureSet], {verifyOnMainThread: true}))) { throw new BlobSidecarGossipError(GossipAction.REJECT, { @@ -116,6 +115,13 @@ export async function validateGossipBlobSidecar( }); } + // verify if the blob inclusion proof is correct + if (!validateInclusionProof(config, blobSidecar)) { + throw new BlobSidecarGossipError(GossipAction.REJECT, { + code: BlobSidecarErrorCode.INCLUSION_PROOF_INVALID, + }); + } + // _[IGNORE]_ The sidecar is the only sidecar with valid signature received for the tuple // `(sidecar.block_root, sidecar.index)` // @@ -127,7 +133,7 @@ export async function validateGossipBlobSidecar( // 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; + const proposerIndex = blobSidecar.signedBlockHeader.message.proposerIndex; if (blockState.epochCtx.getBeaconProposer(blobSlot) !== proposerIndex) { throw new BlobSidecarGossipError(GossipAction.REJECT, { code: BlobSidecarErrorCode.INCORRECT_PROPOSER, @@ -168,16 +174,18 @@ export function validateBlobSidecars( const proofs = []; for (let index = 0; index < blobSidecars.length; index++) { const blobSidecar = blobSidecars[index]; + const blobBlockHeader = blobSidecar.signedBlockHeader.message; + const blobBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobBlockHeader); if ( - blobSidecar.slot !== blockSlot || - !byteArrayEquals(blobSidecar.blockRoot, blockRoot) || + blobBlockHeader.slot !== blockSlot || + !byteArrayEquals(blobBlockRoot, blockRoot) || blobSidecar.index !== index || !byteArrayEquals(expectedKzgCommitments[index], blobSidecar.kzgCommitment) ) { throw new Error( - `Invalid blob with slot=${blobSidecar.slot} blockRoot=${toHex(blockRoot)} index=${ + `Invalid blob with slot=${blobBlockHeader.slot} blobBlockRoot=${toHex(blobBlockRoot)} index=${ blobSidecar.index - } for the block root=${toHex(blockRoot)} slot=${blockSlot} index=${index}` + } for the block blockRoot=${toHex(blockRoot)} slot=${blockSlot} index=${index}` ); } blobs.push(blobSidecar.blob); @@ -207,3 +215,13 @@ function validateBlobsAndProofs( throw Error("Invalid verifyBlobKzgProofBatch"); } } + +function validateInclusionProof(config: ChainForkConfig, blobSidecar: deneb.BlobSidecar): boolean { + return verifyMerkleBranch( + ssz.deneb.KZGCommitment.hashTreeRoot(blobSidecar.kzgCommitment), + blobSidecar.kzgCommitmentInclusionProof, + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, + KZG_COMMITMENT_SUBTREE_INDEX0 + blobSidecar.index, + blobSidecar.signedBlockHeader.message.bodyRoot + ); +} diff --git a/packages/beacon-node/src/db/repositories/blobSidecars.ts b/packages/beacon-node/src/db/repositories/blobSidecars.ts index 576a03df9e61..e5750ed31b58 100644 --- a/packages/beacon-node/src/db/repositories/blobSidecars.ts +++ b/packages/beacon-node/src/db/repositories/blobSidecars.ts @@ -2,6 +2,7 @@ import {ValueOf, ContainerType} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {Db, Repository} from "@lodestar/db"; import {ssz} from "@lodestar/types"; + import {Bucket, getBucketNameByValue} from "../buckets.js"; export const blobSidecarsWrapperSsz = new ContainerType( @@ -14,10 +15,7 @@ export const blobSidecarsWrapperSsz = new ContainerType( ); export type BlobSidecarsWrapper = ValueOf; - export const BLOB_SIDECARS_IN_WRAPPER_INDEX = 44; -// ssz.deneb.BlobSidecars.elementType.fixedSize; -export const BLOBSIDECAR_FIXED_SIZE = 131256; /** * blobSidecarsWrapper by block root (= hash_tree_root(SignedBeaconBlock.message)) diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 20b7d4751c81..c47e8471f199 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -1,10 +1,6 @@ import {byteArrayEquals, toHexString} from "@chainsafe/ssz"; import {allForks, bellatrix, Slot, Root, BLSPubkey, ssz, deneb, Wei} from "@lodestar/types"; -import { - parseSignedBlindedBlockOrContents, - parseExecutionPayloadAndBlobsBundle, - reconstructFullBlockOrContents, -} from "@lodestar/state-transition"; +import {parseExecutionPayloadAndBlobsBundle, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {Logger} from "@lodestar/logger"; import {getClient, Api as BuilderApi} from "@lodestar/api/builder"; @@ -110,26 +106,23 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { ): Promise<{ header: allForks.ExecutionPayloadHeader; executionPayloadValue: Wei; - blindedBlobsBundle?: deneb.BlindedBlobsBundle; + blobKzgCommitments?: deneb.BlobKzgCommitments; }> { const res = await this.api.getHeader(slot, parentHash, proposerPubKey); ApiError.assert(res, "execution.builder.getheader"); const {header, value: executionPayloadValue} = res.response.data.message; - const {blindedBlobsBundle} = res.response.data.message as deneb.BuilderBid; - return {header, executionPayloadValue, blindedBlobsBundle}; + const {blobKzgCommitments} = res.response.data.message as deneb.BuilderBid; + return {header, executionPayloadValue, blobKzgCommitments}; } async submitBlindedBlock( - signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents + signedBlindedBlock: allForks.SignedBlindedBeaconBlock ): Promise { - const res = await this.api.submitBlindedBlock(signedBlindedBlockOrContents); + const res = await this.api.submitBlindedBlock(signedBlindedBlock); ApiError.assert(res, "execution.builder.submitBlindedBlock"); const {data} = res.response; const {executionPayload, blobsBundle} = parseExecutionPayloadAndBlobsBundle(data); - const {signedBlindedBlock, signedBlindedBlobSidecars} = - parseSignedBlindedBlockOrContents(signedBlindedBlockOrContents); - // some validations for execution payload const expectedTransactionsRoot = signedBlindedBlock.message.body.executionPayloadHeader.transactionsRoot; const actualTransactionsRoot = ssz.bellatrix.Transactions.hashTreeRoot(executionPayload.transactions); @@ -141,7 +134,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { ); } - const blobs = blobsBundle ? blobsBundle.blobs : null; - return reconstructFullBlockOrContents({signedBlindedBlock, signedBlindedBlobSidecars}, {executionPayload, blobs}); + const contents = blobsBundle ? {blobs: blobsBundle.blobs, kzgProofs: blobsBundle.proofs} : null; + return reconstructFullBlockOrContents(signedBlindedBlock, {executionPayload, contents}); } } diff --git a/packages/beacon-node/src/execution/builder/interface.ts b/packages/beacon-node/src/execution/builder/interface.ts index e9a2cabb69ef..8754a3616610 100644 --- a/packages/beacon-node/src/execution/builder/interface.ts +++ b/packages/beacon-node/src/execution/builder/interface.ts @@ -25,9 +25,7 @@ export interface IExecutionBuilder { ): Promise<{ header: allForks.ExecutionPayloadHeader; executionPayloadValue: Wei; - blindedBlobsBundle?: deneb.BlindedBlobsBundle; + blobKzgCommitments?: deneb.BlobKzgCommitments; }>; - submitBlindedBlock( - signedBlock: allForks.SignedBlindedBeaconBlockOrContents - ): Promise; + submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise; } diff --git a/packages/beacon-node/src/execution/engine/mock.ts b/packages/beacon-node/src/execution/engine/mock.ts index 83a5ea3a7ed6..5779713435a5 100644 --- a/packages/beacon-node/src/execution/engine/mock.ts +++ b/packages/beacon-node/src/execution/engine/mock.ts @@ -1,5 +1,4 @@ import crypto from "node:crypto"; -import {kzgCommitmentToVersionedHash} from "@lodestar/state-transition"; import {bellatrix, deneb, RootHex, ssz} from "@lodestar/types"; import {fromHex, toHex} from "@lodestar/utils"; import { @@ -12,6 +11,7 @@ import { } from "@lodestar/params"; import {ZERO_HASH_HEX} from "../../constants/index.js"; import {ckzg} from "../../util/kzg.js"; +import {kzgCommitmentToVersionedHash} from "../../util/blobs.js"; import {quantityToNum} from "../../eth1/provider/utils.js"; import { EngineApiRpcParamTypes, diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 04ceceb9b452..9366174ef6c6 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -161,13 +161,9 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { name: "beacon_blinded_blockroot_produced_cache_total", help: "Count of cached produced blinded block roots", }), - producedBlobSidecarsCache: register.gauge({ - name: "beacon_blobsidecars_produced_cache_total", - help: "Count of cached produced blob sidecars", - }), - producedBlindedBlobSidecarsCache: register.gauge({ - name: "beacon_blinded_blobsidecars_produced_cache_total", - help: "Count of cached produced blinded blob sidecars", + producedContentsCache: register.gauge({ + name: "beacon_contents_produced_cache_total", + help: "Count of cached produced blob contents", }), }, diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index 8e9013487a06..600f96193296 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -70,7 +70,7 @@ export type SSZTypeOfGossipTopic = T extends {type: infer export type GossipTypeMap = { [GossipType.beacon_block]: allForks.SignedBeaconBlock; - [GossipType.blob_sidecar]: deneb.SignedBlobSidecar; + [GossipType.blob_sidecar]: deneb.BlobSidecar; [GossipType.beacon_aggregate_and_proof]: phase0.SignedAggregateAndProof; [GossipType.beacon_attestation]: phase0.Attestation; [GossipType.voluntary_exit]: phase0.SignedVoluntaryExit; @@ -85,7 +85,7 @@ export type GossipTypeMap = { export type GossipFnByType = { [GossipType.beacon_block]: (signedBlock: allForks.SignedBeaconBlock) => Promise | void; - [GossipType.blob_sidecar]: (signedBlobSidecar: deneb.SignedBlobSidecar) => Promise | void; + [GossipType.blob_sidecar]: (blobSidecar: deneb.BlobSidecar) => 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 de1a571c3330..c5cd68ffa1de 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -85,7 +85,7 @@ export function getGossipSSZType(topic: GossipTopic) { // beacon_block is updated in altair to support the updated SignedBeaconBlock type return ssz[topic.fork].SignedBeaconBlock; case GossipType.blob_sidecar: - return ssz.deneb.SignedBlobSidecar; + return ssz.deneb.BlobSidecar; case GossipType.beacon_aggregate_and_proof: return ssz.phase0.SignedAggregateAndProof; case GossipType.beacon_attestation: diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index 047263d15022..9531c8529acf 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -44,7 +44,7 @@ export interface INetwork extends INetworkCorePublic { // Gossip publishBeaconBlock(signedBlock: allForks.SignedBeaconBlock): Promise; - publishBlobSidecar(signedBlobSidecar: deneb.SignedBlobSidecar): Promise; + publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise; publishBeaconAggregateAndProof(aggregateAndProof: phase0.SignedAggregateAndProof): Promise; publishBeaconAttestation(attestation: phase0.Attestation, subnet: number): Promise; publishVoluntaryExit(voluntaryExit: phase0.SignedVoluntaryExit): Promise; diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index d2571a2a92e0..200bd4fd3a8d 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -288,14 +288,14 @@ export class Network implements INetwork { }); } - async publishBlobSidecar(signedBlobSidecar: deneb.SignedBlobSidecar): Promise { - const fork = this.config.getForkName(signedBlobSidecar.message.slot); - const index = signedBlobSidecar.message.index; - return this.publishGossip( - {type: GossipType.blob_sidecar, fork, index}, - signedBlobSidecar, - {ignoreDuplicatePublishError: true} - ); + async publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise { + const slot = blobSidecar.signedBlockHeader.message.slot; + const fork = this.config.getForkName(slot); + const index = blobSidecar.index; + + return this.publishGossip({type: GossipType.blob_sidecar, fork, index}, blobSidecar, { + ignoreDuplicatePublishError: true, + }); } async publishBeaconAggregateAndProof(aggregateAndProof: phase0.SignedAggregateAndProof): Promise { diff --git a/packages/beacon-node/src/network/processor/extractSlotRootFns.ts b/packages/beacon-node/src/network/processor/extractSlotRootFns.ts index 24fcfaae6cbc..d31cb3e2d7f9 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, - getSlotFromSignedBlobSidecarSerialized, + getSlotFromBlobSidecarSerialized, getSlotFromSignedBeaconBlockSerialized, } from "../../util/sszBytes.js"; import {GossipType} from "../gossip/index.js"; @@ -43,7 +43,7 @@ export function createExtractBlockSlotRootFns(): ExtractSlotRootFns { return {slot}; }, [GossipType.blob_sidecar]: (data: Uint8Array): SlotOptionalRoot | null => { - const slot = getSlotFromSignedBlobSidecarSerialized(data); + const slot = getSlotFromBlobSidecarSerialized(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 627533b54e1d..831b18670add 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -178,20 +178,23 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler } async function validateBeaconBlob( - signedBlob: deneb.SignedBlobSidecar, + blobSidecar: deneb.BlobSidecar, blobBytes: Uint8Array, gossipIndex: number, peerIdStr: string, seenTimestampSec: number ): Promise { - const slot = signedBlob.message.slot; - const blockHex = prettyBytes(signedBlob.message.blockRoot); + const blobBlockHeader = blobSidecar.signedBlockHeader.message; + const slot = blobBlockHeader.slot; + const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobBlockHeader); + const blockHex = prettyBytes(blockRoot); + const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec); const recvToVal = Date.now() / 1000 - seenTimestampSec; const {blockInput, blockInputMeta} = chain.seenGossipBlockInput.getGossipBlockInput(config, { type: GossipedInputType.blob, - signedBlob, + blobSidecar, blobBytes, }); @@ -208,7 +211,7 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler }); try { - await validateGossipBlobSidecar(config, chain, signedBlob, gossipIndex); + await validateGossipBlobSidecar(config, chain, blobSidecar, gossipIndex); return blockInput; } catch (e) { if (e instanceof BlobSidecarGossipError) { @@ -219,7 +222,11 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler } if (e.action === GossipAction.REJECT) { - chain.persistInvalidSszValue(ssz.deneb.SignedBlobSidecar, signedBlob, `gossip_reject_slot_${slot}`); + chain.persistInvalidSszValue( + ssz.deneb.BlobSidecar, + blobSidecar, + `gossip_reject_slot_${slot}_index_${blobSidecar.index}` + ); } } @@ -317,11 +324,17 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler seenTimestampSec, }: GossipHandlerParamGeneric) => { const {serializedData} = gossipData; - const signedBlob = sszDeserialize(topic, serializedData); - if (config.getForkSeq(signedBlob.message.slot) < ForkSeq.deneb) { + const blobSidecar = sszDeserialize(topic, serializedData); + if (config.getForkSeq(blobSidecar.signedBlockHeader.message.slot) < ForkSeq.deneb) { throw new GossipActionError(GossipAction.REJECT, {code: "PRE_DENEB_BLOCK"}); } - const blockInput = await validateBeaconBlob(signedBlob, serializedData, topic.index, peerIdStr, seenTimestampSec); + const blockInput = await validateBeaconBlob( + blobSidecar, + serializedData, + topic.index, + peerIdStr, + seenTimestampSec + ); if (blockInput !== null) { // TODO DENEB: // diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts index 10e7071f4fdb..41d3e901c41d 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts @@ -78,7 +78,9 @@ export function matchBlockWithBlobs( const blobSidecars: deneb.BlobSidecar[] = []; let blobSidecar: deneb.BlobSidecar; - while ((blobSidecar = allBlobSidecars[blobSideCarIndex])?.slot === block.data.message.slot) { + while ( + (blobSidecar = allBlobSidecars[blobSideCarIndex])?.signedBlockHeader.message.slot === block.data.message.slot + ) { blobSidecars.push(blobSidecar); lastMatchedSlot = block.data.message.slot; blobSideCarIndex++; @@ -111,14 +113,14 @@ export function matchBlockWithBlobs( if ( allBlobSidecars[blobSideCarIndex] !== undefined && // If there are no blobs, the blobs request can give 1 block outside the requested range - allBlobSidecars[blobSideCarIndex].slot <= endSlot + allBlobSidecars[blobSideCarIndex].signedBlockHeader.message.slot <= endSlot ) { throw Error( `Unmatched blobSidecars, blocks=${allBlocks.length}, blobs=${ allBlobSidecars.length } lastMatchedSlot=${lastMatchedSlot}, pending blobSidecars slots=${allBlobSidecars .slice(blobSideCarIndex) - .map((blb) => blb.slot) + .map((blb) => blb.signedBlockHeader.message.slot) .join(",")}` ); } diff --git a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts index 2cd852492220..e3655cd90c6f 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts @@ -1,10 +1,10 @@ -import {GENESIS_SLOT, MAX_REQUEST_BLOCKS_DENEB} from "@lodestar/params"; +import {GENESIS_SLOT, MAX_REQUEST_BLOCKS_DENEB, BLOBSIDECAR_FIXED_SIZE} from "@lodestar/params"; import {ResponseError, ResponseOutgoing, RespStatus} from "@lodestar/reqresp"; import {deneb, Slot} from "@lodestar/types"; import {fromHex} from "@lodestar/utils"; import {IBeaconChain} from "../../../chain/index.js"; import {IBeaconDb} from "../../../db/index.js"; -import {BLOB_SIDECARS_IN_WRAPPER_INDEX, BLOBSIDECAR_FIXED_SIZE} from "../../../db/repositories/blobSidecars.js"; +import {BLOB_SIDECARS_IN_WRAPPER_INDEX} from "../../../db/repositories/blobSidecars.js"; export async function* onBlobSidecarsByRange( request: deneb.BlobSidecarsByRangeRequest, diff --git a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts index 3bb162d019e3..6aa16a0c2629 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts @@ -1,9 +1,10 @@ import {ResponseError, ResponseOutgoing, RespStatus} from "@lodestar/reqresp"; +import {BLOBSIDECAR_FIXED_SIZE} from "@lodestar/params"; import {deneb, RootHex} from "@lodestar/types"; import {toHex, fromHex} from "@lodestar/utils"; import {IBeaconChain} from "../../../chain/index.js"; import {IBeaconDb} from "../../../db/index.js"; -import {BLOB_SIDECARS_IN_WRAPPER_INDEX, BLOBSIDECAR_FIXED_SIZE} from "../../../db/repositories/blobSidecars.js"; +import {BLOB_SIDECARS_IN_WRAPPER_INDEX} from "../../../db/repositories/blobSidecars.js"; export async function* onBlobSidecarsByRoot( requestBody: deneb.BlobSidecarsByRootRequest, diff --git a/packages/beacon-node/src/util/blobs.ts b/packages/beacon-node/src/util/blobs.ts new file mode 100644 index 000000000000..bbad27f684ed --- /dev/null +++ b/packages/beacon-node/src/util/blobs.ts @@ -0,0 +1,48 @@ +import SHA256 from "@chainsafe/as-sha256"; +import {Tree} from "@chainsafe/persistent-merkle-tree"; +import {VERSIONED_HASH_VERSION_KZG, KZG_COMMITMENT_GINDEX0, ForkName} from "@lodestar/params"; +import {deneb, ssz, allForks} from "@lodestar/types"; +import {ChainForkConfig} from "@lodestar/config"; +import {signedBlockToSignedHeader} from "@lodestar/state-transition"; + +type VersionHash = Uint8Array; + +export function kzgCommitmentToVersionedHash(kzgCommitment: deneb.KZGCommitment): VersionHash { + const hash = SHA256.digest(kzgCommitment); + // Equivalent to `VERSIONED_HASH_VERSION_KZG + hash(kzg_commitment)[1:]` + hash[0] = VERSIONED_HASH_VERSION_KZG; + return hash; +} + +export function computeInclusionProof( + fork: ForkName, + body: allForks.BeaconBlockBody, + index: number +): deneb.KzgCommitmentInclusionProof { + const bodyView = (ssz[fork].BeaconBlockBody as allForks.AllForksSSZTypes["BeaconBlockBody"]).toView(body); + const commitmentGindex = KZG_COMMITMENT_GINDEX0 + index; + return new Tree(bodyView.node).getSingleProof(BigInt(commitmentGindex)); +} + +export function computeBlobSidecars( + config: ChainForkConfig, + signedBlock: allForks.SignedBeaconBlock, + contents: deneb.Contents & {kzgCommitmentInclusionProofs?: deneb.KzgCommitmentInclusionProof[]} +): deneb.BlobSidecars { + const blobKzgCommitments = (signedBlock as deneb.SignedBeaconBlock).message.body.blobKzgCommitments; + if (blobKzgCommitments === undefined) { + throw Error("Invalid block with missing blobKzgCommitments for computeBlobSidecars"); + } + + const signedBlockHeader = signedBlockToSignedHeader(config, signedBlock); + const fork = config.getForkName(signedBlockHeader.message.slot); + + return blobKzgCommitments.map((kzgCommitment, index) => { + const blob = contents.blobs[index]; + const kzgProof = contents.kzgProofs[index]; + const kzgCommitmentInclusionProof = + contents.kzgCommitmentInclusionProofs?.[index] ?? computeInclusionProof(fork, signedBlock.message.body, index); + + return {index, blob, kzgCommitment, kzgProof, signedBlockHeader, kzgCommitmentInclusionProof}; + }); +} diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 0c258df35041..cd12c4bd9c18 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -1,6 +1,7 @@ import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz"; import {BLSSignature, RootHex, Slot} from "@lodestar/types"; import {toHex} from "@lodestar/utils"; +import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB} from "@lodestar/params"; export type BlockRootHex = RootHex; export type AttDataBase64 = string; @@ -180,23 +181,18 @@ export function getSlotFromSignedBeaconBlockSerialized(data: Uint8Array): Slot | } /** - * 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] - * ... - * ``` + * index: BlobIndex [fixed - 8 bytes ], + * blob: Blob, BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB + * kzgCommitment: Bytes48, + * kzgProof: Bytes48, + * signedBlockHeader: + * slot: 8 bytes */ -const SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR = 32 + 8; +const SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR = 8 + BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB + 48 + 48; -export function getSlotFromSignedBlobSidecarSerialized(data: Uint8Array): Slot | null { +export function getSlotFromBlobSidecarSerialized(data: Uint8Array): Slot | null { if (data.length < SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR + SLOT_SIZE) { return null; } diff --git a/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/post-merge.sh index dee850740370..fbf9dcaaf929 100755 --- a/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/post-merge.sh @@ -5,4 +5,4 @@ currentDir=$(pwd) . $scriptDir/common-setup.sh -docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --name custom-execution --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --dataDir /data/ethereumjs --gethGenesis /data/genesis.json --rpc --rpcEngine --jwt-secret /data/jwtsecret --logLevel debug --isSingleNode +docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --name custom-execution --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --dataDir /data/ethereumjs --gethGenesis /data/genesis.json --rpc --rpcEngineAddr 0.0.0.0 --rpcAddr 0.0.0.0 --rpcEngine --jwt-secret /data/jwtsecret --logLevel debug --isSingleNode diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts index 0ab7b3b363b5..47d72c1226e1 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -1,7 +1,7 @@ import path from "node:path"; import {expect} from "chai"; import {toHexString} from "@chainsafe/ssz"; -import {BeaconStateAllForks, isExecutionStateType} from "@lodestar/state-transition"; +import {BeaconStateAllForks, isExecutionStateType, signedBlockToSignedHeader} from "@lodestar/state-transition"; import {InputType} from "@lodestar/spec-test-util"; import {CheckpointWithHex, ForkChoice} from "@lodestar/fork-choice"; import {phase0, allForks, bellatrix, ssz, RootHex, deneb} from "@lodestar/types"; @@ -10,6 +10,7 @@ import {createBeaconConfig} from "@lodestar/config"; import {ACTIVE_PRESET, ForkSeq, isForkBlobs} from "@lodestar/params"; import {BeaconChain} from "../../../src/chain/index.js"; import {ClockEvent} from "../../../src/util/clock.js"; +import {computeInclusionProof} from "../../../src/util/blobs.js"; import {createCachedBeaconStateTest} from "../../utils/cachedBeaconState.js"; import {testLogger} from "../../utils/logger.js"; import {getConfig} from "../../utils/config.js"; @@ -195,20 +196,14 @@ const forkChoiceTest = throw Error("Invalid blobs or proofs lengths"); } - const blockRoot = config - .getForkTypes(signedBlock.message.slot) - .BeaconBlock.hashTreeRoot(signedBlock.message); const blobSidecars: deneb.BlobSidecars = blobs.map((blob, index) => { return { - blockRoot, index, - slot, blob, - // proofs isn't undefined here but typescript(check types) can't figure it out - kzgProof: (proofs ?? [])[index], kzgCommitment: commitments[index], - blockParentRoot: signedBlock.message.parentRoot, - proposerIndex: signedBlock.message.proposerIndex, + kzgProof: (proofs ?? [])[index], + signedBlockHeader: signedBlockToSignedHeader(config, signedBlock), + kzgCommitmentInclusionProof: computeInclusionProof(fork, signedBlock.message.body, index), }; }); diff --git a/packages/beacon-node/test/spec/specTestVersioning.ts b/packages/beacon-node/test/spec/specTestVersioning.ts index 3f1aad878e65..20125520321d 100644 --- a/packages/beacon-node/test/spec/specTestVersioning.ts +++ b/packages/beacon-node/test/spec/specTestVersioning.ts @@ -15,7 +15,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const ethereumConsensusSpecsTests: DownloadTestsOptions = { - specVersion: "v1.4.0-beta.2-hotfix", + specVersion: "v1.4.0-beta.5", // Target directory is the host package root: 'packages/*/spec-tests' outputDir: path.join(__dirname, "../../spec-tests"), specTestsRepoUrl: "https://github.com/ethereum/consensus-spec-tests", diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index a9310d53ac81..084d3d00fd48 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -64,6 +64,7 @@ export const defaultSkipOpts: SkipOpts = { "capella/light_client/single_merkle_proof/BeaconBlockBody", "deneb/light_client/single_merkle_proof/BeaconBlockBody", ], + skippedRunners: ["merkle_proof"], }; /** diff --git a/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts b/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts index 05919efdbcbf..c389e1b81e70 100644 --- a/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts +++ b/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts @@ -72,6 +72,7 @@ describe("SeenGossipBlockInput", () => { ], ], ]; + // lets start from a random slot to build cases let slot = 7456; for (const testCase of testCases) { @@ -86,10 +87,15 @@ describe("SeenGossipBlockInput", () => { ssz.deneb.KZGCommitment.defaultValue() ); - const blockRoot = ssz.deneb.BeaconBlock.hashTreeRoot(signedBlock.message); - const signedBlobSidecars = Array.from({length: numBlobs}, (_val, index) => { - const message = {...ssz.deneb.BlobSidecar.defaultValue(), index, blockRoot, slot}; - return {message, signature: ssz.BLSSignature.defaultValue()}; + // create a dummy signed block header with matching body root + const bodyRoot = ssz.deneb.BeaconBlockBody.hashTreeRoot(signedBlock.message.body); + const signedBlockHeader = ssz.phase0.SignedBeaconBlockHeader.defaultValue(); + signedBlockHeader.message.slot = signedBlock.message.slot; + signedBlockHeader.message.bodyRoot = bodyRoot; + + const blobSidecars = Array.from({length: numBlobs}, (_val, index) => { + const message = {...ssz.deneb.BlobSidecar.defaultValue(), signedBlockHeader, index}; + return message; }); for (const testEvent of events) { @@ -114,11 +120,12 @@ describe("SeenGossipBlockInput", () => { } } else { const index = parseInt(inputEvent.split("blob")[1] ?? "0"); - const signedBlob = signedBlobSidecars[index]; - expect(signedBlob).not.equal(undefined); + const blobSidecar = blobSidecars[index]; + expect(blobSidecar).not.equal(undefined); + const blockInputRes = seenGossipBlockInput.getGossipBlockInput(config, { type: GossipedInputType.blob, - signedBlob, + blobSidecar, blobBytes: null, }); diff --git a/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts b/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts index 189327a6a5ab..56fb64104744 100644 --- a/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts +++ b/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts @@ -30,16 +30,21 @@ describe("beaconBlocksMaybeBlobsByRange", () => { rangeRequest.count = 1; const block1 = ssz.deneb.SignedBeaconBlock.defaultValue(); + const blockheader1 = ssz.phase0.SignedBeaconBlockHeader.defaultValue(); + blockheader1.message.slot = 1; block1.message.slot = 1; block1.message.body.blobKzgCommitments.push(ssz.deneb.KZGCommitment.defaultValue()); const blobSidecar1 = ssz.deneb.BlobSidecar.defaultValue(); - blobSidecar1.slot = 1; + blobSidecar1.signedBlockHeader = blockheader1; const block2 = ssz.deneb.SignedBeaconBlock.defaultValue(); block2.message.slot = 2; + const blockheader2 = ssz.phase0.SignedBeaconBlockHeader.defaultValue(); + blockheader2.message.slot = 2; + block2.message.body.blobKzgCommitments.push(ssz.deneb.KZGCommitment.defaultValue()); const blobSidecar2 = ssz.deneb.BlobSidecar.defaultValue(); - blobSidecar2.slot = 2; + blobSidecar2.signedBlockHeader = blockheader2; const block3 = ssz.deneb.SignedBeaconBlock.defaultValue(); block3.message.slot = 3; @@ -47,13 +52,18 @@ describe("beaconBlocksMaybeBlobsByRange", () => { const block4 = ssz.deneb.SignedBeaconBlock.defaultValue(); block4.message.slot = 4; + const blockheader4 = ssz.phase0.SignedBeaconBlockHeader.defaultValue(); + blockheader4.message.slot = 4; + // two blobsidecars block4.message.body.blobKzgCommitments.push(ssz.deneb.KZGCommitment.defaultValue()); block4.message.body.blobKzgCommitments.push(ssz.deneb.KZGCommitment.defaultValue()); const blobSidecar41 = ssz.deneb.BlobSidecar.defaultValue(); - blobSidecar41.slot = 4; + + blobSidecar41.signedBlockHeader = blockheader4; + const blobSidecar42 = ssz.deneb.BlobSidecar.defaultValue(); - blobSidecar42.slot = 4; + blobSidecar42.signedBlockHeader = blockheader4; blobSidecar42.index = 1; // Array of testcases which are array of matched blocks with/without (if empty) sidecars diff --git a/packages/beacon-node/test/unit/util/kzg.test.ts b/packages/beacon-node/test/unit/util/kzg.test.ts index 5bcaf1071cf6..b50bd2c0f1a7 100644 --- a/packages/beacon-node/test/unit/util/kzg.test.ts +++ b/packages/beacon-node/test/unit/util/kzg.test.ts @@ -1,7 +1,8 @@ import {describe, it, expect, afterEach, beforeAll} from "vitest"; import {bellatrix, deneb, ssz} from "@lodestar/types"; import {BYTES_PER_FIELD_ELEMENT, BLOB_TX_TYPE} from "@lodestar/params"; -import {kzgCommitmentToVersionedHash} from "@lodestar/state-transition"; +import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config"; +import {computeBlobSidecars, kzgCommitmentToVersionedHash} from "../../../src/util/blobs.js"; import {loadEthereumTrustedSetup, initCKZG, ckzg, FIELD_ELEMENTS_PER_BLOB_MAINNET} from "../../../src/util/kzg.js"; import {validateBlobSidecars, validateGossipBlobSidecar} from "../../../src/chain/validation/blobSidecar.js"; import {getMockedBeaconChain} from "../../__mocks__/mockedBeaconChain.js"; @@ -30,8 +31,18 @@ describe("C-KZG", async () => { expect(ckzg.verifyBlobKzgProofBatch(blobs, commitments, proofs)).toBe(true); }); + /* eslint-disable @typescript-eslint/naming-convention */ it("BlobSidecars", async () => { - const chain = getMockedBeaconChain(); + const chainConfig = createChainForkConfig({ + ...defaultChainConfig, + ALTAIR_FORK_EPOCH: 0, + BELLATRIX_FORK_EPOCH: 0, + DENEB_FORK_EPOCH: 0, + }); + const genesisValidatorsRoot = Buffer.alloc(32, 0xaa); + const config = createBeaconConfig(chainConfig, genesisValidatorsRoot); + + const chain = getMockedBeaconChain({config}); afterEachCallbacks.push(() => chain.close()); const slot = 0; @@ -45,34 +56,17 @@ describe("C-KZG", async () => { signedBeaconBlock.message.body.blobKzgCommitments.push(kzgCommitment); } const blockRoot = ssz.deneb.BeaconBlock.hashTreeRoot(signedBeaconBlock.message); + const kzgProofs = blobs.map((blob, index) => ckzg.computeBlobKzgProof(blob, kzgCommitments[index])); + const blobSidecars: deneb.BlobSidecars = computeBlobSidecars(chain.config, signedBeaconBlock, {blobs, kzgProofs}); - const blobSidecars: deneb.BlobSidecars = blobs.map((blob, index) => { - return { - blockRoot, - index, - slot, - blob, - kzgProof: ckzg.computeBlobKzgProof(blob, kzgCommitments[index]), - kzgCommitment: kzgCommitments[index], - blockParentRoot: Buffer.alloc(32), - proposerIndex: 0, - }; - }); - - const signedBlobSidecars: deneb.SignedBlobSidecar[] = blobSidecars.map((blobSidecar) => { - const signedBlobSidecar = ssz.deneb.SignedBlobSidecar.defaultValue(); - signedBlobSidecar.message = blobSidecar; - return signedBlobSidecar; - }); - - expect(signedBlobSidecars.length).toBe(2); + expect(blobSidecars.length).toBe(2); // Full validation validateBlobSidecars(slot, blockRoot, kzgCommitments, blobSidecars); - signedBlobSidecars.forEach(async (signedBlobSidecar) => { + blobSidecars.forEach(async (blobSidecar) => { try { - await validateGossipBlobSidecar(chain.config, chain, signedBlobSidecar, signedBlobSidecar.message.index); + await validateGossipBlobSidecar(chain.config, chain, blobSidecar, blobSidecar.index); } catch (error) { // We expect some error from here // console.log(error); diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index 2ffaa98e6cfe..bb5fc67a7ce6 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, - getSlotFromSignedBlobSidecarSerialized, + getSlotFromBlobSidecarSerialized, } from "../../../src/util/sszBytes.js"; describe("attestation SSZ serialized picking", () => { @@ -146,20 +146,20 @@ describe("signedBeaconBlock SSZ serialized picking", () => { }); }); -describe("signedBlobSidecar SSZ serialized picking", () => { - const testCases = [ssz.deneb.SignedBlobSidecar.defaultValue(), signedBlobSidecarFromValues(1_000_000)]; +describe("BlobSidecar SSZ serialized picking", () => { + const testCases = [ssz.deneb.BlobSidecar.defaultValue(), blobSidecarFromValues(1_000_000)]; - for (const [i, signedBlobSidecar] of testCases.entries()) { - const bytes = ssz.deneb.SignedBlobSidecar.serialize(signedBlobSidecar); - it(`signedBlobSidecar ${i}`, () => { - expect(getSlotFromSignedBlobSidecarSerialized(bytes)).toBe(signedBlobSidecar.message.slot); + for (const [i, blobSidecar] of testCases.entries()) { + const bytes = ssz.deneb.BlobSidecar.serialize(blobSidecar); + it(`blobSidecar ${i}`, () => { + expect(getSlotFromBlobSidecarSerialized(bytes)).toBe(blobSidecar.signedBlockHeader.message.slot); }); } - it("signedBlobSidecar - invalid data", () => { + it("blobSidecar - invalid data", () => { const invalidSlotDataSizes = [0, 20, 38]; for (const size of invalidSlotDataSizes) { - expect(getSlotFromSignedBlobSidecarSerialized(Buffer.alloc(size))).toBeNull(); + expect(getSlotFromBlobSidecarSerialized(Buffer.alloc(size))).toBeNull(); } }); }); @@ -198,8 +198,8 @@ function signedBeaconBlockFromValues(slot: Slot): phase0.SignedBeaconBlock { return signedBeaconBlock; } -function signedBlobSidecarFromValues(slot: Slot): deneb.SignedBlobSidecar { - const signedBlobSidecar = ssz.deneb.SignedBlobSidecar.defaultValue(); - signedBlobSidecar.message.slot = slot; - return signedBlobSidecar; +function blobSidecarFromValues(slot: Slot): deneb.BlobSidecar { + const blobSidecar = ssz.deneb.BlobSidecar.defaultValue(); + blobSidecar.signedBlockHeader.message.slot = slot; + return blobSidecar; } diff --git a/packages/cli/test/utils/runUtils.ts b/packages/cli/test/utils/runUtils.ts index 9fd53a03c67a..8d2846d6e664 100644 --- a/packages/cli/test/utils/runUtils.ts +++ b/packages/cli/test/utils/runUtils.ts @@ -17,20 +17,14 @@ export function findApiToken(dirpath: string): string { } export function expectDeepEquals(a: T, b: T, message: string): void { - try { - expect(a).toEqual(b); - } catch (e) { - expect.fail(message); - } + expect(a).toEqualWithMessage(b, message); } /** * Similar to `expectDeepEquals` but only checks presence of all elements in array, irrespective of their order. */ export function expectDeepEqualsUnordered(a: T[], b: T[], message: string): void { - try { - expect(a.sort()).toEqual(b.sort()); - } catch (e) { - expect.fail(message); - } + expect(a).toEqualWithMessage(expect.arrayContaining(b), message); + expect(b).toEqualWithMessage(expect.arrayContaining(a), message); + expect(a).toHaveLength(b.length); } diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 3d784356ae0f..e0623537d7f0 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -90,6 +90,7 @@ export const { FIELD_ELEMENTS_PER_BLOB, MAX_BLOB_COMMITMENTS_PER_BLOCK, MAX_BLOBS_PER_BLOCK, + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, } = activePreset; //////////// @@ -235,3 +236,10 @@ export const INTERVALS_PER_SLOT = 3; export const BYTES_PER_FIELD_ELEMENT = 32; export const BLOB_TX_TYPE = 0x03; export const VERSIONED_HASH_VERSION_KZG = 0x01; + +// ssz.deneb.BeaconBlockBody.getPathInfo(['blobKzgCommitments',0]).gindex +export const KZG_COMMITMENT_GINDEX0 = ACTIVE_PRESET === PresetName.minimal ? 864 : 221184; +export const KZG_COMMITMENT_SUBTREE_INDEX0 = KZG_COMMITMENT_GINDEX0 - 2 ** KZG_COMMITMENT_INCLUSION_PROOF_DEPTH; + +// ssz.deneb.BlobSidecars.elementType.fixedSize +export const BLOBSIDECAR_FIXED_SIZE = ACTIVE_PRESET === PresetName.minimal ? 131672 : 131928; diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index f29b1668ac44..9b591103edf5 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -115,4 +115,5 @@ export const mainnetPreset: BeaconPreset = { FIELD_ELEMENTS_PER_BLOB: 4096, MAX_BLOB_COMMITMENTS_PER_BLOCK: 4096, MAX_BLOBS_PER_BLOCK: 6, + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 17, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 34d690045117..ad86cbf89e61 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -119,7 +119,8 @@ export const minimalPreset: BeaconPreset = { // DENEB /////////// // https://github.com/ethereum/consensus-specs/blob/dev/presets/minimal/eip4844.yaml - FIELD_ELEMENTS_PER_BLOB: 4, + FIELD_ELEMENTS_PER_BLOB: 4096, MAX_BLOB_COMMITMENTS_PER_BLOCK: 16, MAX_BLOBS_PER_BLOCK: 6, + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 9, }; diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index 67d258bdd0c9..3c5ba6381131 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -81,6 +81,7 @@ export type BeaconPreset = { FIELD_ELEMENTS_PER_BLOB: number; MAX_BLOB_COMMITMENTS_PER_BLOCK: number; MAX_BLOBS_PER_BLOCK: number; + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: number; }; /** @@ -165,6 +166,7 @@ export const beaconPresetTypes: BeaconPresetTypes = { FIELD_ELEMENTS_PER_BLOB: "number", MAX_BLOB_COMMITMENTS_PER_BLOCK: "number", MAX_BLOBS_PER_BLOCK: "number", + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: "number", }; type BeaconPresetTypes = { diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index dfd57990e694..06fb4bae000c 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -8,7 +8,7 @@ import {loadConfigYaml} from "../yaml.js"; // Not e2e, but slow. Run with e2e tests /** https://github.com/ethereum/consensus-specs/releases */ -const specConfigCommit = "v1.4.0-beta.2"; +const specConfigCommit = "v1.4.0-beta.5"; describe("Ensure config is synced", function () { vi.setConfig({testTimeout: 60 * 1000}); diff --git a/packages/state-transition/src/signatureSets/proposer.ts b/packages/state-transition/src/signatureSets/proposer.ts index a00bcacc7c99..135ac7ed5c7a 100644 --- a/packages/state-transition/src/signatureSets/proposer.ts +++ b/packages/state-transition/src/signatureSets/proposer.ts @@ -1,5 +1,5 @@ -import {DOMAIN_BEACON_PROPOSER, DOMAIN_BLOB_SIDECAR} from "@lodestar/params"; -import {allForks, isBlindedBeaconBlock, isBlindedBlobSidecar, ssz} from "@lodestar/types"; +import {DOMAIN_BEACON_PROPOSER} from "@lodestar/params"; +import {allForks, isBlindedBeaconBlock, phase0, ssz} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; import {ISignatureSet, SignatureSetType, verifySignatureSet} from "../util/signatureSets.js"; import {CachedBeaconStateAllForks} from "../types.js"; @@ -17,7 +17,7 @@ export function getBlockProposerSignatureSet( signedBlock: allForks.FullOrBlindedSignedBeaconBlock ): ISignatureSet { const {config, epochCtx} = state; - const domain = state.config.getDomain(state.slot, DOMAIN_BEACON_PROPOSER, signedBlock.message.slot); + const domain = config.getDomain(state.slot, DOMAIN_BEACON_PROPOSER, signedBlock.message.slot); const blockType = isBlindedBeaconBlock(signedBlock.message) ? config.getBlindedForkTypes(signedBlock.message.slot).BeaconBlock @@ -31,19 +31,17 @@ export function getBlockProposerSignatureSet( }; } -export function getBlobProposerSignatureSet( +export function getBlockHeaderProposerSignatureSet( state: CachedBeaconStateAllForks, - signedBlob: allForks.FullOrBlindedSignedBlobSidecar + signedBlockHeader: phase0.SignedBeaconBlockHeader ): ISignatureSet { const {config, epochCtx} = state; - const domain = config.getDomain(state.slot, DOMAIN_BLOB_SIDECAR, signedBlob.message.slot); - - const blockType = isBlindedBlobSidecar(signedBlob.message) ? ssz.deneb.BlindedBlobSidecar : ssz.deneb.BlobSidecar; + const domain = config.getDomain(state.slot, DOMAIN_BEACON_PROPOSER, signedBlockHeader.message.slot); return { type: SignatureSetType.single, - pubkey: epochCtx.index2pubkey[signedBlob.message.proposerIndex], - signingRoot: computeSigningRoot(blockType, signedBlob.message, domain), - signature: signedBlob.signature, + pubkey: epochCtx.index2pubkey[signedBlockHeader.message.proposerIndex], + signingRoot: computeSigningRoot(ssz.phase0.BeaconBlockHeader, signedBlockHeader.message, domain), + signature: signedBlockHeader.signature, }; } diff --git a/packages/state-transition/src/util/blindedBlock.ts b/packages/state-transition/src/util/blindedBlock.ts index 8c271e7fec81..5b6cf42d3cef 100644 --- a/packages/state-transition/src/util/blindedBlock.ts +++ b/packages/state-transition/src/util/blindedBlock.ts @@ -1,24 +1,9 @@ import {ChainForkConfig} from "@lodestar/config"; import {ForkSeq} from "@lodestar/params"; -import { - allForks, - phase0, - Root, - deneb, - ssz, - isBlindedBeaconBlock, - isBlindedBlobSidecar, - isSignedBlindedBlockContents, - isExecutionPayloadAndBlobsBundle, -} from "@lodestar/types"; +import {allForks, phase0, Root, deneb, isBlindedBeaconBlock, isExecutionPayloadAndBlobsBundle} from "@lodestar/types"; import {executionPayloadToPayloadHeader} from "./execution.js"; -type ParsedSignedBlindedBlockOrContents = { - signedBlindedBlock: allForks.SignedBlindedBeaconBlock; - signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars | null; -}; - export function blindedOrFullBlockHashTreeRoot( config: ChainForkConfig, blindedOrFull: allForks.FullOrBlindedBeaconBlock @@ -30,17 +15,6 @@ export function blindedOrFullBlockHashTreeRoot( config.getForkTypes(blindedOrFull.slot).BeaconBlock.hashTreeRoot(blindedOrFull); } -export function blindedOrFullBlobSidecarHashTreeRoot( - config: ChainForkConfig, - blindedOrFull: allForks.FullOrBlindedBlobSidecar -): Root { - return isBlindedBlobSidecar(blindedOrFull) - ? // Blinded - config.getBlobsForkTypes(blindedOrFull.slot).BlindedBlobSidecar.hashTreeRoot(blindedOrFull) - : // Full - config.getBlobsForkTypes(blindedOrFull.slot).BlobSidecar.hashTreeRoot(blindedOrFull); -} - export function blindedOrFullBlockToHeader( config: ChainForkConfig, blindedOrFull: allForks.FullOrBlindedBeaconBlock @@ -70,13 +44,6 @@ export function beaconBlockToBlinded( return blindedBlock; } -export function blobSidecarsToBlinded(blobSidecars: deneb.BlobSidecars): deneb.BlindedBlobSidecars { - return blobSidecars.map((blobSidecar) => { - const blobRoot = ssz.deneb.Blob.hashTreeRoot(blobSidecar.blob); - return {...blobSidecar, blobRoot} as deneb.BlindedBlobSidecar; - }); -} - export function signedBlindedBlockToFull( signedBlindedBlock: allForks.SignedBlindedBeaconBlock, executionPayload: allForks.ExecutionPayload | null @@ -100,33 +67,6 @@ export function signedBlindedBlockToFull( return signedBlock; } -export function signedBlindedBlobSidecarsToFull( - signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars, - blobs: deneb.Blobs -): deneb.SignedBlobSidecars { - const signedBlobSidecars = signedBlindedBlobSidecars.map((signedBlindedBlobSidecar, index) => { - const signedBlobSidecar = { - ...signedBlindedBlobSidecar, - message: {...signedBlindedBlobSidecar.message, blob: blobs[index]}, - }; - delete (signedBlobSidecar.message as {blobRoot?: deneb.BlindedBlob}).blobRoot; - return signedBlobSidecar; - }); - return signedBlobSidecars; -} - -export function parseSignedBlindedBlockOrContents( - signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents -): ParsedSignedBlindedBlockOrContents { - if (isSignedBlindedBlockContents(signedBlindedBlockOrContents)) { - const signedBlindedBlock = signedBlindedBlockOrContents.signedBlindedBlock; - const signedBlindedBlobSidecars = signedBlindedBlockOrContents.signedBlindedBlobSidecars; - return {signedBlindedBlock, signedBlindedBlobSidecars}; - } else { - return {signedBlindedBlock: signedBlindedBlockOrContents, signedBlindedBlobSidecars: null}; - } -} - export function parseExecutionPayloadAndBlobsBundle( data: allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle ): {executionPayload: allForks.ExecutionPayload; blobsBundle: deneb.BlobsBundle | null} { @@ -141,27 +81,23 @@ export function parseExecutionPayloadAndBlobsBundle( } export function reconstructFullBlockOrContents( - {signedBlindedBlock, signedBlindedBlobSidecars}: ParsedSignedBlindedBlockOrContents, - {executionPayload, blobs}: {executionPayload: allForks.ExecutionPayload | null; blobs: deneb.Blobs | null} + signedBlindedBlock: allForks.SignedBlindedBeaconBlock, + { + executionPayload, + contents, + }: { + executionPayload: allForks.ExecutionPayload | null; + contents: deneb.Contents | null; + } ): allForks.SignedBeaconBlockOrContents { const signedBlock = signedBlindedBlockToFull(signedBlindedBlock, executionPayload); - if (signedBlindedBlobSidecars !== null) { + if (contents !== null) { if (executionPayload === null) { throw Error("Missing locally produced executionPayload for deneb+ publishBlindedBlock"); } - if (blobs === null) { - throw Error("Missing blobs from the local execution cache"); - } - if (blobs.length !== signedBlindedBlobSidecars.length) { - throw Error( - `Length mismatch signedBlindedBlobSidecars=${signedBlindedBlobSidecars.length} blobs=${blobs.length}` - ); - } - const signedBlobSidecars = signedBlindedBlobSidecarsToFull(signedBlindedBlobSidecars, blobs); - - return {signedBlock, signedBlobSidecars} as allForks.SignedBeaconBlockOrContents; + return {signedBlock, ...contents} as allForks.SignedBeaconBlockOrContents; } else { return signedBlock as allForks.SignedBeaconBlockOrContents; } diff --git a/packages/state-transition/src/util/blobs.ts b/packages/state-transition/src/util/blobs.ts deleted file mode 100644 index 8b6ea84362c4..000000000000 --- a/packages/state-transition/src/util/blobs.ts +++ /dev/null @@ -1,12 +0,0 @@ -import SHA256 from "@chainsafe/as-sha256"; -import {VERSIONED_HASH_VERSION_KZG} from "@lodestar/params"; -import {deneb} from "@lodestar/types"; - -type VersionHash = Uint8Array; - -export function kzgCommitmentToVersionedHash(kzgCommitment: deneb.KZGCommitment): VersionHash { - const hash = SHA256.digest(kzgCommitment); - // Equivalent to `VERSIONED_HASH_VERSION_KZG + hash(kzg_commitment)[1:]` - hash[0] = VERSIONED_HASH_VERSION_KZG; - return hash; -} diff --git a/packages/state-transition/src/util/blockRoot.ts b/packages/state-transition/src/util/blockRoot.ts index 7aa5de52cdfe..1e1df38ef4fe 100644 --- a/packages/state-transition/src/util/blockRoot.ts +++ b/packages/state-transition/src/util/blockRoot.ts @@ -54,3 +54,15 @@ export function blockToHeader(config: ChainForkConfig, block: allForks.BeaconBlo bodyRoot: config.getForkTypes(block.slot).BeaconBlockBody.hashTreeRoot(block.body), }; } + +export function signedBlockToSignedHeader( + config: ChainForkConfig, + signedBlock: allForks.SignedBeaconBlock +): phase0.SignedBeaconBlockHeader { + const message = blockToHeader(config, signedBlock.message); + const signature = signedBlock.signature; + return { + message, + signature, + }; +} diff --git a/packages/state-transition/src/util/index.ts b/packages/state-transition/src/util/index.ts index bbc9bf8a8654..3f2e91da9a77 100644 --- a/packages/state-transition/src/util/index.ts +++ b/packages/state-transition/src/util/index.ts @@ -4,7 +4,6 @@ export * from "./attestation.js"; export * from "./attesterStatus.js"; export * from "./balance.js"; export * from "./blindedBlock.js"; -export * from "./blobs.js"; export * from "./capella.js"; export * from "./execution.js"; export * from "./blockRoot.js"; diff --git a/packages/types/package.json b/packages/types/package.json index 5c7a6599d890..42dffc436875 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -61,7 +61,9 @@ "check-types": "tsc", "lint": "eslint --color --ext .ts src/ test/", "lint:fix": "yarn run lint --fix", - "test:unit": "mocha 'test/**/*.test.ts'", + "test:unit": "yarn test:constants:minimal && yarn test:constants:mainnet && mocha 'test/unit/**/*.test.ts'", + "test:constants:minimal": "LODESTAR_PRESET=minimal mocha 'test/constants/**/*.test.ts'", + "test:constants:mainnet": "LODESTAR_PRESET=mainnet mocha 'test/constants/**/*.test.ts'", "test:browsers": "yarn karma start karma.config.cjs", "check-readme": "typescript-docs-verifier" }, diff --git a/packages/types/src/allForks/sszTypes.ts b/packages/types/src/allForks/sszTypes.ts index 463e5c57bd0d..7174bc52e89c 100644 --- a/packages/types/src/allForks/sszTypes.ts +++ b/packages/types/src/allForks/sszTypes.ts @@ -155,7 +155,6 @@ export const allForksLightClient = { export const allForksBlobs = { deneb: { BlobSidecar: deneb.BlobSidecar, - BlindedBlobSidecar: deneb.BlindedBlobSidecar, ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle, }, }; diff --git a/packages/types/src/allForks/types.ts b/packages/types/src/allForks/types.ts index 01c597b8a245..59768a5a3308 100644 --- a/packages/types/src/allForks/types.ts +++ b/packages/types/src/allForks/types.ts @@ -68,31 +68,17 @@ export type FullOrBlindedBeaconBlockBody = BeaconBlockBody | BlindedBeaconBlockB export type FullOrBlindedBeaconBlock = BeaconBlock | BlindedBeaconBlock; export type FullOrBlindedSignedBeaconBlock = SignedBeaconBlock | SignedBlindedBeaconBlock; -export type FullOrBlindedBlobSidecar = deneb.BlobSidecar | deneb.BlindedBlobSidecar; -export type FullOrBlindedSignedBlobSidecar = deneb.SignedBlobSidecar | deneb.SignedBlindedBlobSidecar; - -export type FullOrBlindedBlobSidecars = deneb.BlobSidecars | deneb.BlindedBlobSidecars; -export type BlockContents = {block: BeaconBlock; blobSidecars: deneb.BlobSidecars}; +export type BlockContents = {block: BeaconBlock; kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs}; export type SignedBlockContents = { signedBlock: SignedBeaconBlock; - signedBlobSidecars: deneb.SignedBlobSidecars; + kzgProofs: deneb.KZGProofs; + blobs: deneb.Blobs; }; -export type BlindedBlockContents = { - blindedBlock: BlindedBeaconBlock; - blindedBlobSidecars: deneb.BlindedBlobSidecars; -}; -export type SignedBlindedBlockContents = { - signedBlindedBlock: SignedBlindedBeaconBlock; - signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars; -}; - -export type FullOrBlindedBlockContents = BlockContents | BlindedBlockContents; -export type FullOrBlindedBeaconBlockOrContents = FullOrBlindedBeaconBlock | FullOrBlindedBlockContents; export type BeaconBlockOrContents = BeaconBlock | BlockContents; -export type BlindedBeaconBlockOrContents = BlindedBeaconBlock | BlindedBlockContents; export type SignedBeaconBlockOrContents = SignedBeaconBlock | SignedBlockContents; -export type SignedBlindedBeaconBlockOrContents = SignedBlindedBeaconBlock | SignedBlindedBlockContents; + +export type FullOrBlindedBeaconBlockOrContents = BeaconBlockOrContents | BlindedBeaconBlock; export type BuilderBid = bellatrix.BuilderBid | capella.BuilderBid | deneb.BuilderBid; export type SignedBuilderBid = bellatrix.SignedBuilderBid | capella.SignedBuilderBid | deneb.SignedBuilderBid; @@ -308,6 +294,5 @@ export type AllForksLightClientSSZTypes = { export type AllForksBlobsSSZTypes = { BlobSidecar: AllForksTypeOf; - BlindedBlobSidecar: AllForksTypeOf; ExecutionPayloadAndBlobsBundle: AllForksTypeOf; }; diff --git a/packages/types/src/deneb/sszTypes.ts b/packages/types/src/deneb/sszTypes.ts index 96509d1d898b..b39e5f6281e1 100644 --- a/packages/types/src/deneb/sszTypes.ts +++ b/packages/types/src/deneb/sszTypes.ts @@ -8,6 +8,7 @@ import { BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH, EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, } from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; @@ -15,20 +16,8 @@ import {ssz as altairSsz} from "../altair/index.js"; import {ssz as bellatrixSsz} from "../bellatrix/index.js"; import {ssz as capellaSsz} from "../capella/index.js"; -const { - UintNum64, - Slot, - Root, - BLSSignature, - UintBn64, - UintBn256, - Bytes32, - Bytes48, - Bytes96, - BLSPubkey, - BlobIndex, - ValidatorIndex, -} = primitiveSsz; +const {UintNum64, Slot, Root, BLSSignature, UintBn64, UintBn256, Bytes32, Bytes48, Bytes96, BLSPubkey, BlobIndex} = + primitiveSsz; // Polynomial commitments // https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/polynomial-commitments.md @@ -124,31 +113,22 @@ export const SignedBeaconBlock = new ContainerType( {typeName: "SignedBeaconBlock", jsonCase: "eth2"} ); +export const KzgCommitmentInclusionProof = new VectorCompositeType(Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH); + export const BlobSidecar = new ContainerType( { - blockRoot: Root, index: BlobIndex, - slot: Slot, - blockParentRoot: Root, - proposerIndex: ValidatorIndex, blob: Blob, kzgCommitment: KZGCommitment, kzgProof: KZGProof, + signedBlockHeader: phase0Ssz.SignedBeaconBlockHeader, + kzgCommitmentInclusionProof: KzgCommitmentInclusionProof, }, {typeName: "BlobSidecar", jsonCase: "eth2"} ); export const BlobSidecars = new ListCompositeType(BlobSidecar, MAX_BLOB_COMMITMENTS_PER_BLOCK); -export const SignedBlobSidecar = new ContainerType( - { - message: BlobSidecar, - signature: BLSSignature, - }, - {typeName: "SignedBlobSidecar", jsonCase: "eth2"} -); -export const SignedBlobSidecars = new ListCompositeType(SignedBlobSidecar, MAX_BLOB_COMMITMENTS_PER_BLOCK); - export const BlobsBundle = new ContainerType( { commitments: BlobKzgCommitments, @@ -158,35 +138,6 @@ export const BlobsBundle = new ContainerType( {typeName: "BlobsBundle", jsonCase: "eth2"} ); -export const BlindedBlobSidecar = new ContainerType( - { - blockRoot: Root, - index: BlobIndex, - slot: Slot, - blockParentRoot: Root, - proposerIndex: ValidatorIndex, - blobRoot: BlindedBlob, - kzgCommitment: KZGCommitment, - kzgProof: KZGProof, - }, - {typeName: "BlindedBlobSidecar", jsonCase: "eth2"} -); - -export const BlindedBlobSidecars = new ListCompositeType(BlindedBlobSidecar, MAX_BLOB_COMMITMENTS_PER_BLOCK); - -export const SignedBlindedBlobSidecar = new ContainerType( - { - message: BlindedBlobSidecar, - signature: BLSSignature, - }, - {typeName: "SignedBlindedBlobSidecar", jsonCase: "eth2"} -); - -export const SignedBlindedBlobSidecars = new ListCompositeType( - SignedBlindedBlobSidecar, - MAX_BLOB_COMMITMENTS_PER_BLOCK -); - export const BlindedBeaconBlockBody = new ContainerType( { ...altairSsz.BeaconBlockBody.fields, @@ -213,19 +164,10 @@ export const SignedBlindedBeaconBlock = new ContainerType( {typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"} ); -export const BlindedBlobsBundle = new ContainerType( - { - commitments: BlobKzgCommitments, - proofs: KZGProofs, - blobRoots: BlindedBlobs, - }, - {typeName: "BlindedBlobsBundle", jsonCase: "eth2"} -); - export const BuilderBid = new ContainerType( { header: ExecutionPayloadHeader, - blindedBlobsBundle: BlindedBlobsBundle, + blobKzgCommitments: BlobKzgCommitments, value: UintBn256, pubkey: BLSPubkey, }, diff --git a/packages/types/src/deneb/types.ts b/packages/types/src/deneb/types.ts index 1d6eb5fca5aa..0921ae2428e7 100644 --- a/packages/types/src/deneb/types.ts +++ b/packages/types/src/deneb/types.ts @@ -1,4 +1,5 @@ import {ValueOf} from "@chainsafe/ssz"; +import {BlockContents} from "../allForks/types.js"; import * as ssz from "./sszTypes.js"; export type KZGProof = ValueOf; @@ -6,19 +7,12 @@ export type KZGCommitment = ValueOf; export type Blob = ValueOf; export type Blobs = ValueOf; -export type BlindedBlob = ValueOf; -export type BlindedBlobs = ValueOf; export type BlobSidecar = ValueOf; export type BlobSidecars = ValueOf; -export type BlindedBlobSidecar = ValueOf; -export type BlindedBlobSidecars = ValueOf; -export type SignedBlobSidecar = ValueOf; -export type SignedBlobSidecars = ValueOf; -export type SignedBlindedBlobSidecar = ValueOf; -export type SignedBlindedBlobSidecars = ValueOf; export type ExecutionPayloadAndBlobsBundle = ValueOf; export type BlobsBundle = ValueOf; +export type KzgCommitmentInclusionProof = ValueOf; export type BlobKzgCommitments = ValueOf; export type KZGProofs = ValueOf; export type BLSFieldElement = ValueOf; @@ -42,7 +36,6 @@ export type SignedBlindedBeaconBlock = ValueOf; export type BuilderBid = ValueOf; export type SignedBuilderBid = ValueOf; export type SSEPayloadAttributes = ValueOf; @@ -53,3 +46,6 @@ export type LightClientUpdate = ValueOf; export type LightClientFinalityUpdate = ValueOf; export type LightClientOptimisticUpdate = ValueOf; export type LightClientStore = ValueOf; + +export type ProducedBlobSidecars = Omit; +export type Contents = Omit; diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index 0b9bee97d17a..781738c3dbad 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -5,21 +5,15 @@ import { FullOrBlindedBeaconBlockBody, FullOrBlindedExecutionPayload, ExecutionPayloadHeader, - FullOrBlindedBlobSidecar, - FullOrBlindedSignedBlobSidecar, BlindedBeaconBlockBody, BlindedBeaconBlock, BlockContents, - SignedBlindedBlockContents, SignedBlindedBeaconBlock, - BlindedBlockContents, SignedBlockContents, SignedBeaconBlock, - SignedBlindedBeaconBlockOrContents, ExecutionPayload, ExecutionPayloadAndBlobsBundle, } from "../allForks/types.js"; -import {ts as deneb} from "../deneb/index.js"; export function isBlindedExecution(payload: FullOrBlindedExecutionPayload): payload is ExecutionPayloadHeader { // we just check transactionsRoot for determinging as it the base field @@ -42,32 +36,12 @@ export function isBlindedSignedBeaconBlock( return (signedBlock as SignedBlindedBeaconBlock).message.body.executionPayloadHeader !== undefined; } -export function isBlindedBlobSidecar(blob: FullOrBlindedBlobSidecar): blob is deneb.BlindedBlobSidecar { - return (blob as deneb.BlindedBlobSidecar).blobRoot !== undefined; -} - -export function isBlindedSignedBlobSidecar( - blob: FullOrBlindedSignedBlobSidecar -): blob is deneb.SignedBlindedBlobSidecar { - return (blob as deneb.SignedBlindedBlobSidecar).message.blobRoot !== undefined; -} - export function isBlockContents(data: FullOrBlindedBeaconBlockOrContents): data is BlockContents { - return (data as BlockContents).blobSidecars !== undefined; + return (data as BlockContents).kzgProofs !== undefined; } export function isSignedBlockContents(data: SignedBeaconBlock | SignedBlockContents): data is SignedBlockContents { - return (data as SignedBlockContents).signedBlobSidecars !== undefined; -} - -export function isBlindedBlockContents(data: FullOrBlindedBeaconBlockOrContents): data is BlindedBlockContents { - return (data as BlindedBlockContents).blindedBlobSidecars !== undefined; -} - -export function isSignedBlindedBlockContents( - data: SignedBlindedBeaconBlockOrContents -): data is SignedBlindedBlockContents { - return (data as SignedBlindedBlockContents).signedBlindedBlobSidecars !== undefined; + return (data as SignedBlockContents).kzgProofs !== undefined; } export function isExecutionPayloadAndBlobsBundle( diff --git a/packages/types/test/constants/blobs.test.ts b/packages/types/test/constants/blobs.test.ts new file mode 100644 index 000000000000..42dce38e8636 --- /dev/null +++ b/packages/types/test/constants/blobs.test.ts @@ -0,0 +1,25 @@ +import {expect} from "chai"; +import * as constants from "@lodestar/params"; +import {ssz} from "../../src/index.js"; + +// NOTE: This test is here and not in lodestar-params, to prevent lodestar-params depending on SSZ +// Since lodestar-params and lodestar-types are in the same mono-repo, running this test here is enough +// guarantee that these constants are correct. + +describe(`${constants.ACTIVE_PRESET}/ blobs pre-computed constants`, () => { + const BLOBSIDECAR_FIXED_SIZE = ssz.deneb.BlobSidecars.elementType.fixedSize; + const KZG_COMMITMENT_GINDEX0 = Number(ssz.deneb.BeaconBlockBody.getPathInfo(["blobKzgCommitments", 0]).gindex); + const KZG_COMMITMENT_SUBTREE_INDEX0 = KZG_COMMITMENT_GINDEX0 - 2 ** constants.KZG_COMMITMENT_INCLUSION_PROOF_DEPTH; + + const correctConstants = { + BLOBSIDECAR_FIXED_SIZE, + KZG_COMMITMENT_GINDEX0, + KZG_COMMITMENT_SUBTREE_INDEX0, + }; + + for (const [key, expectedValue] of Object.entries(correctConstants)) { + it(key, () => { + expect((constants as unknown as Record)[key]).to.equal(expectedValue); + }); + } +}); diff --git a/packages/types/test/unit/constants.test.ts b/packages/types/test/constants/lightclient.test.ts similarity index 94% rename from packages/types/test/unit/constants.test.ts rename to packages/types/test/constants/lightclient.test.ts index 09cbec8bf1b5..3d8652ccc44c 100644 --- a/packages/types/test/unit/constants.test.ts +++ b/packages/types/test/constants/lightclient.test.ts @@ -6,7 +6,7 @@ import {ssz} from "../../src/index.js"; // Since lodestar-params and lodestar-types are in the same mono-repo, running this test here is enough // guarantee that these constants are correct. -describe("Lightclient pre-computed constants", () => { +describe(`${constants.ACTIVE_PRESET}/ Lightclient pre-computed constants`, () => { const FINALIZED_ROOT_GINDEX = bnToNum(ssz.altair.BeaconState.getPathInfo(["finalizedCheckpoint", "root"]).gindex); const FINALIZED_ROOT_DEPTH = floorlog2(FINALIZED_ROOT_GINDEX); const FINALIZED_ROOT_INDEX = FINALIZED_ROOT_GINDEX % 2 ** FINALIZED_ROOT_DEPTH; diff --git a/packages/utils/src/ethConversion.ts b/packages/utils/src/ethConversion.ts new file mode 100644 index 000000000000..7aa8fa0cc63c --- /dev/null +++ b/packages/utils/src/ethConversion.ts @@ -0,0 +1,12 @@ +export const ETH_TO_GWEI = BigInt(10 ** 9); +export const GWEI_TO_WEI = BigInt(10 ** 9); +export const ETH_TO_WEI = ETH_TO_GWEI * GWEI_TO_WEI; + +type EthNumeric = bigint; + +/** + * Convert gwei to wei. + */ +export function gweiToWei(gwei: EthNumeric): EthNumeric { + return gwei * GWEI_TO_WEI; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 55cde2ccae21..fcff789f9c56 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -20,3 +20,4 @@ export * from "./url.js"; export * from "./verifyMerkleBranch.js"; export * from "./promise.js"; export * from "./waitFor.js"; +export * from "./ethConversion.js"; diff --git a/packages/validator/src/services/block.ts b/packages/validator/src/services/block.ts index d65ce2616fb5..14c4f83a6282 100644 --- a/packages/validator/src/services/block.ts +++ b/packages/validator/src/services/block.ts @@ -4,15 +4,14 @@ import { Slot, BLSSignature, allForks, - isBlindedBeaconBlock, + isBlindedSignedBeaconBlock, ProducedBlockSource, deneb, isBlockContents, - isBlindedBlockContents, } from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; -import {ForkPreBlobs, ForkBlobs, ForkSeq} from "@lodestar/params"; -import {extendError, prettyBytes} from "@lodestar/utils"; +import {ForkPreBlobs, ForkBlobs, ForkSeq, ForkExecution} from "@lodestar/params"; +import {ETH_TO_GWEI, ETH_TO_WEI, extendError, gweiToWei, prettyBytes} from "@lodestar/utils"; import {Api, ApiError, routes} from "@lodestar/api"; import {IClock, LoggerVc} from "../util/index.js"; import {PubkeyHex} from "../types.js"; @@ -21,38 +20,33 @@ import {formatBigDecimal} from "../util/format.js"; import {ValidatorStore} from "./validatorStore.js"; import {BlockDutiesService, GENESIS_SLOT} from "./blockDuties.js"; -const ETH_TO_WEI = BigInt("1000000000000000000"); // display upto 5 decimal places const MAX_DECIMAL_FACTOR = BigInt("100000"); // The following combination of blocks and blobs can be produced // i) a full block pre deneb // ii) a full block and full blobs post deneb -// iii) a blinded block pre deneb as a result of beacon/execution race -// iv) a blinded block + blinded blobs as a result of beacon/execution race +// iii) a blinded block post bellatrix type FullOrBlindedBlockWithContents = | { version: ForkPreBlobs; block: allForks.BeaconBlock; - blobs: null; + contents: null; executionPayloadBlinded: false; } | { version: ForkBlobs; block: allForks.BeaconBlock; - blobs: deneb.BlobSidecars; + contents: { + kzgProofs: deneb.KZGProofs; + blobs: deneb.Blobs; + }; executionPayloadBlinded: false; } | { - version: ForkPreBlobs; + version: ForkExecution; block: allForks.BlindedBeaconBlock; - blobs: null; - executionPayloadBlinded: true; - } - | { - version: ForkBlobs; - block: allForks.BlindedBeaconBlock; - blobs: deneb.BlindedBlobSidecars; + contents: null; executionPayloadBlinded: true; }; @@ -132,38 +126,30 @@ export class BlockProposingService { this.metrics?.proposerStepCallProduceBlock.observe(this.clock.secFromSlot(slot)); const produceBlockFn = this.opts.useProduceBlockV3 ? this.produceBlockWrapper : this.produceBlockV2Wrapper; - const blockContents = await produceBlockFn(this.config, slot, randaoReveal, graffiti, { + const produceOpts = { feeRecipient, strictFeeRecipientCheck, builderSelection, - }).catch((e: Error) => { - this.metrics?.blockProposingErrors.inc({error: "produce"}); - throw extendError(e, "Failed to produce block"); - }); + }; + const blockContents = await produceBlockFn(this.config, slot, randaoReveal, graffiti, produceOpts).catch( + (e: Error) => { + this.metrics?.blockProposingErrors.inc({error: "produce"}); + throw extendError(e, "Failed to produce block"); + } + ); this.logger.debug("Produced block", {...debugLogCtx, ...blockContents.debugLogCtx}); this.metrics?.blocksProduced.inc(); - const signedBlockPromise = this.validatorStore.signBlock(pubkey, blockContents.block, slot, this.logger); - const signedBlobPromises = - blockContents.blobs !== null - ? blockContents.blobs.map((blob) => this.validatorStore.signBlob(pubkey, blob, slot)) - : undefined; - let signedBlock: allForks.FullOrBlindedSignedBeaconBlock, - signedBlobs: allForks.FullOrBlindedSignedBlobSidecar[] | undefined; - if (signedBlobPromises !== undefined) { - [signedBlock, ...signedBlobs] = await Promise.all([signedBlockPromise, ...signedBlobPromises]); - } else { - signedBlock = await signedBlockPromise; - signedBlobs = undefined; - } + const signedBlock = await this.validatorStore.signBlock(pubkey, blockContents.block, slot); - await this.publishBlockWrapper(signedBlock, signedBlobs, { - broadcastValidation: this.opts.broadcastValidation, - }).catch((e: Error) => { + const {broadcastValidation} = this.opts; + const publishOpts = {broadcastValidation}; + await this.publishBlockWrapper(signedBlock, blockContents.contents, publishOpts).catch((e: Error) => { this.metrics?.blockProposingErrors.inc({error: "publish"}); throw extendError(e, "Failed to publish block"); }); + this.metrics?.proposerStepCallPublishBlock.observe(this.clock.secFromSlot(slot)); this.metrics?.blocksPublished.inc(); this.logger.info("Published block", {...logCtx, graffiti, ...blockContents.debugLogCtx}); @@ -174,30 +160,22 @@ export class BlockProposingService { private publishBlockWrapper = async ( signedBlock: allForks.FullOrBlindedSignedBeaconBlock, - signedBlobSidecars?: allForks.FullOrBlindedSignedBlobSidecar[], + contents: {kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs} | null, opts: {broadcastValidation?: routes.beacon.BroadcastValidation} = {} ): Promise => { - if (signedBlobSidecars === undefined) { - ApiError.assert( - isBlindedBeaconBlock(signedBlock.message) - ? await this.api.beacon.publishBlindedBlockV2(signedBlock as allForks.SignedBlindedBeaconBlock, opts) - : await this.api.beacon.publishBlockV2(signedBlock as allForks.SignedBeaconBlock, opts) - ); + if (isBlindedSignedBeaconBlock(signedBlock)) { + if (contents !== null) { + this.logger.warn( + "Ignoring contents while publishing blinded block - publishing beacon should assemble it from its local cache or builder" + ); + } + ApiError.assert(await this.api.beacon.publishBlindedBlockV2(signedBlock, opts)); } else { - ApiError.assert( - isBlindedBeaconBlock(signedBlock.message) - ? await this.api.beacon.publishBlindedBlockV2( - { - signedBlindedBlock: signedBlock, - signedBlindedBlobSidecars: signedBlobSidecars, - } as allForks.SignedBlindedBlockContents, - opts - ) - : await this.api.beacon.publishBlockV2( - {signedBlock, signedBlobSidecars} as allForks.SignedBlockContents, - opts - ) - ); + if (contents === null) { + ApiError.assert(await this.api.beacon.publishBlockV2(signedBlock, opts)); + } else { + ApiError.assert(await this.api.beacon.publishBlockV2({...contents, signedBlock}, opts)); + } } }; @@ -220,9 +198,9 @@ export class BlockProposingService { source: response.executionPayloadBlinded ? ProducedBlockSource.builder : ProducedBlockSource.engine, // winston logger doesn't like bigint executionPayloadValue: `${formatBigDecimal(response.executionPayloadValue, ETH_TO_WEI, MAX_DECIMAL_FACTOR)} ETH`, - consensusBlockValue: `${formatBigDecimal(response.consensusBlockValue, ETH_TO_WEI, MAX_DECIMAL_FACTOR)} ETH`, + consensusBlockValue: `${formatBigDecimal(response.consensusBlockValue, ETH_TO_GWEI, MAX_DECIMAL_FACTOR)} ETH`, totalBlockValue: `${formatBigDecimal( - response.executionPayloadValue + response.consensusBlockValue, + response.executionPayloadValue + gweiToWei(response.consensusBlockValue), ETH_TO_WEI, MAX_DECIMAL_FACTOR )} ETH`, @@ -270,28 +248,18 @@ function parseProduceBlockResponse( debugLogCtx: Record ): FullOrBlindedBlockWithContents & DebugLogCtx { if (response.executionPayloadBlinded) { - if (isBlindedBlockContents(response.data)) { - return { - block: response.data.blindedBlock, - blobs: response.data.blindedBlobSidecars, - version: response.version, - executionPayloadBlinded: true, - debugLogCtx, - } as FullOrBlindedBlockWithContents & DebugLogCtx; - } else { - return { - block: response.data, - blobs: null, - version: response.version, - executionPayloadBlinded: true, - debugLogCtx, - } as FullOrBlindedBlockWithContents & DebugLogCtx; - } + return { + block: response.data, + contents: null, + version: response.version, + executionPayloadBlinded: true, + debugLogCtx, + } as FullOrBlindedBlockWithContents & DebugLogCtx; } else { if (isBlockContents(response.data)) { return { block: response.data.block, - blobs: response.data.blobSidecars, + contents: {blobs: response.data.blobs, kzgProofs: response.data.kzgProofs}, version: response.version, executionPayloadBlinded: false, debugLogCtx, @@ -299,7 +267,7 @@ function parseProduceBlockResponse( } else { return { block: response.data, - blobs: null, + contents: null, version: response.version, executionPayloadBlinded: false, debugLogCtx, diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index e2736a09754a..698de1dc7053 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -7,7 +7,6 @@ import { computeDomain, ZERO_HASH, blindedOrFullBlockHashTreeRoot, - blindedOrFullBlobSidecarHashTreeRoot, } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; import { @@ -20,7 +19,6 @@ import { DOMAIN_SYNC_COMMITTEE, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, DOMAIN_APPLICATION_BUILDER, - DOMAIN_BLOB_SIDECAR, } from "@lodestar/params"; import { allForks, @@ -395,37 +393,6 @@ export class ValidatorStore { } as allForks.FullOrBlindedSignedBeaconBlock; } - async signBlob( - pubkey: BLSPubkey, - blindedOrFull: allForks.FullOrBlindedBlobSidecar, - currentSlot: Slot - ): Promise { - // Make sure the block slot is not higher than the current slot to avoid potential attacks. - if (blindedOrFull.slot > currentSlot) { - throw Error(`Not signing block with slot ${blindedOrFull.slot} greater than current slot ${currentSlot}`); - } - - // Duties are filtered before-hard by doppelganger-safe, this assert should never throw - this.assertDoppelgangerSafe(pubkey); - - const signingSlot = blindedOrFull.slot; - const domain = this.config.getDomain(signingSlot, DOMAIN_BLOB_SIDECAR); - const blobRoot = blindedOrFullBlobSidecarHashTreeRoot(this.config, blindedOrFull); - // Don't use `computeSigningRoot()` here to compute the objectRoot in typesafe function blindedOrFullBlockHashTreeRoot() - const signingRoot = ssz.phase0.SigningData.hashTreeRoot({objectRoot: blobRoot, domain}); - - // Slashing protection is not required as blobs are binded to blocks which are already protected - const signableMessage: SignableMessage = { - type: SignableMessageType.BLOB, - data: blindedOrFull, - }; - - return { - message: blindedOrFull, - signature: await this.getSignature(pubkey, signingRoot, signingSlot, signableMessage), - } as allForks.FullOrBlindedSignedBlobSidecar; - } - async signRandao(pubkey: BLSPubkey, slot: Slot): Promise { const signingSlot = slot; const domain = this.config.getDomain(slot, DOMAIN_RANDAO); diff --git a/packages/validator/src/util/externalSignerClient.ts b/packages/validator/src/util/externalSignerClient.ts index 2716533e536f..90c6e1f464c8 100644 --- a/packages/validator/src/util/externalSignerClient.ts +++ b/packages/validator/src/util/externalSignerClient.ts @@ -15,7 +15,6 @@ export enum SignableMessageType { AGGREGATE_AND_PROOF = "AGGREGATE_AND_PROOF", ATTESTATION = "ATTESTATION", BLOCK_V2 = "BLOCK_V2", - BLOB = "BLOB", DEPOSIT = "DEPOSIT", RANDAO_REVEAL = "RANDAO_REVEAL", VOLUNTARY_EXIT = "VOLUNTARY_EXIT", @@ -65,7 +64,6 @@ export type SignableMessage = | {type: SignableMessageType.AGGREGATE_AND_PROOF; data: phase0.AggregateAndProof} | {type: SignableMessageType.ATTESTATION; data: phase0.AttestationData} | {type: SignableMessageType.BLOCK_V2; data: allForks.FullOrBlindedBeaconBlock} - | {type: SignableMessageType.BLOB; data: allForks.FullOrBlindedBlobSidecar} | {type: SignableMessageType.DEPOSIT; data: ValueOf} | {type: SignableMessageType.RANDAO_REVEAL; data: {epoch: Epoch}} | {type: SignableMessageType.VOLUNTARY_EXIT; data: phase0.VoluntaryExit} @@ -88,7 +86,6 @@ const requiresForkInfo: Record = { [SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF]: true, [SignableMessageType.VALIDATOR_REGISTRATION]: false, [SignableMessageType.BLS_TO_EXECUTION_CHANGE]: true, - [SignableMessageType.BLOB]: true, }; type Web3SignerSerializedRequest = { @@ -232,9 +229,5 @@ function serializerSignableMessagePayload(config: BeaconConfig, payload: Signabl case SignableMessageType.BLS_TO_EXECUTION_CHANGE: return {BLS_TO_EXECUTION_CHANGE: ssz.capella.BLSToExecutionChange.toJson(payload.data)}; - - case SignableMessageType.BLOB: - // TODO DENEB: freetheblobs - throw Error("web3signer for blob signing not yet implemented"); } } diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 37908afaf86c..1431f4f3c56e 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -210,5 +210,6 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record&1) echo $OUTPUT -MATCH=("warning") - -# There are few yarn warnings we can't find a fix for. Excluding those. -# TODO: Keep checking occasionally if the warnings are fixed upstream. -EXCLUDE=("Pattern \[\".*\"\] is trying to unpack in the same destination") -ARGS=() - -for m in "${MATCH[@]}"; do ARGS+=(-e "$m"); done -for e in "${EXCLUDE[@]}"; do ARGS+=(--exclude "$e"); done -COMMAND="grep -qi ${ARGS[@]}" - -echo "Running $COMMAND" - # grep the output for 'warning' -if echo "$OUTPUT" | ${COMMAND}; then +if echo "$OUTPUT" | grep -qi 'warning'; then echo "There were warnings in yarn install --check-files" exit 1 else diff --git a/scripts/vitest/customMatchers.ts b/scripts/vitest/customMatchers.ts index ef569dd23686..227c0a2c0c76 100644 --- a/scripts/vitest/customMatchers.ts +++ b/scripts/vitest/customMatchers.ts @@ -42,7 +42,7 @@ expect.extend({ toBeWithMessage(received: unknown, expected: unknown, message: string) { if (Object.is(received, expected)) { return { - message: () => "Expected value is truthy", + message: () => "Received value is the same as expected value", pass: true, }; } @@ -50,25 +50,27 @@ expect.extend({ return { pass: false, message: () => message, + actual: received, + expected, }; }, toSatisfy(received: unknown, func: (received: unknown) => boolean) { if (func(received)) { return { - message: () => "Expected value satisfied the condition", + message: () => "Received value satisfied the condition", pass: true, }; } return { pass: false, - message: () => "Expected value did not satisfy the condition", + message: () => "Received value did not satisfy the condition", }; }, toEqualWithMessage(received: unknown, expected: unknown, message: string) { if (this.equals(received, expected)) { return { - message: () => "Expected value is truthy", + message: () => "Received value equals expected value", pass: true, }; } @@ -76,6 +78,8 @@ expect.extend({ return { pass: false, message: () => message, + actual: received, + expected, }; }, }); diff --git a/yarn.lock b/yarn.lock index 39664352cf9b..291225ebedf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2721,7 +2721,7 @@ estree-walker "^2.0.2" magic-string "^0.30.3" -"@rollup/plugin-virtual@^3.0.1": +"@rollup/plugin-virtual@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz#17e17eeecb4c9fa1c0a6e72c9e5f66382fddbb82" integrity sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A== @@ -2933,74 +2933,74 @@ resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@swc/core-darwin-arm64@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.93.tgz#aefd94625451988286bebccb1c072bae0a36bcdb" - integrity sha512-gEKgk7FVIgltnIfDO6GntyuQBBlAYg5imHpRgLxB1zSI27ijVVkksc6QwISzFZAhKYaBWIsFSVeL9AYSziAF7A== - -"@swc/core-darwin-x64@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.93.tgz#18409c6effdf508ddf1ebccfa77d35aaa6cd72f0" - integrity sha512-ZQPxm/fXdDQtn3yrYSL/gFfA8OfZ5jTi33yFQq6vcg/Y8talpZ+MgdSlYM0FkLrZdMTYYTNFiuBQuuvkA+av+Q== - -"@swc/core-linux-arm-gnueabihf@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.93.tgz#23a97bc94a8b2f23fb6cc4bc9d8936899e5eeff5" - integrity sha512-OYFMMI2yV+aNe3wMgYhODxHdqUB/jrK0SEMHHS44GZpk8MuBXEF+Mcz4qjkY5Q1EH7KVQqXb/gVWwdgTHpjM2A== - -"@swc/core-linux-arm64-gnu@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.93.tgz#7a17406a7cf76a959a617626d5ee2634ae9afa26" - integrity sha512-BT4dT78odKnJMNiq5HdjBsv29CiIdcCcImAPxeFqAeFw1LL6gh9nzI8E96oWc+0lVT5lfhoesCk4Qm7J6bty8w== - -"@swc/core-linux-arm64-musl@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.93.tgz#a30be7780090afefd3b8706398418cbe1d23db49" - integrity sha512-yH5fWEl1bktouC0mhh0Chuxp7HEO4uCtS/ly1Vmf18gs6wZ8DOOkgAEVv2dNKIryy+Na++ljx4Ym7C8tSJTrLw== - -"@swc/core-linux-x64-gnu@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.93.tgz#41e903fd82e059952d16051b442cbe65ee5b8cb3" - integrity sha512-OFUdx64qvrGJhXKEyxosHxgoUVgba2ztYh7BnMiU5hP8lbI8G13W40J0SN3CmFQwPP30+3oEbW7LWzhKEaYjlg== - -"@swc/core-linux-x64-musl@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.93.tgz#0866807545c44eac9b3254b374310ad5e1c573f9" - integrity sha512-4B8lSRwEq1XYm6xhxHhvHmKAS7pUp1Q7E33NQ2TlmFhfKvCOh86qvThcjAOo57x8DRwmpvEVrqvpXtYagMN6Ig== - -"@swc/core-win32-arm64-msvc@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.93.tgz#c72411dea2fd4f62a832f71a6e15424d849e7610" - integrity sha512-BHShlxtkven8ZjjvZ5QR6sC5fZCJ9bMujEkiha6W4cBUTY7ce7qGFyHmQd+iPC85d9kD/0cCiX/Xez8u0BhO7w== - -"@swc/core-win32-ia32-msvc@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.93.tgz#05c2b031b976af4ef81f5073ee114254678a5d5d" - integrity sha512-nEwNWnz4JzYAK6asVvb92yeylfxMYih7eMQOnT7ZVlZN5ba9WF29xJ6kcQKs9HRH6MvWhz9+wRgv3FcjlU6HYA== - -"@swc/core-win32-x64-msvc@1.3.93": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.93.tgz#f8748b3fd1879f13084b1b0814edf328c662935c" - integrity sha512-jibQ0zUr4kwJaQVwgmH+svS04bYTPnPw/ZkNInzxS+wFAtzINBYcU8s2PMWbDb2NGYiRSEeoSGyAvS9H+24JFA== - -"@swc/core@^1.3.10": - version "1.3.93" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.93.tgz#be4282aa44deffb0e5081a2613bac00335600630" - integrity sha512-690GRr1wUGmGYZHk7fUduX/JUwViMF2o74mnZYIWEcJaCcd9MQfkhsxPBtjeg6tF+h266/Cf3RPYhsFBzzxXcA== +"@swc/core-darwin-arm64@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.101.tgz#9ffdc0e77c31b20877fa7405c82905e0c76738d0" + integrity sha512-mNFK+uHNPRXSnfTOG34zJOeMl2waM4hF4a2NY7dkMXrPqw9CoJn4MwTXJcyMiSz1/BnNjjTCHF3Yhj0jPxmkzQ== + +"@swc/core-darwin-x64@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.101.tgz#e50130e21e3cfd3029fd6cea43e8309b58ad9fa6" + integrity sha512-B085j8XOx73Fg15KsHvzYWG262bRweGr3JooO1aW5ec5pYbz5Ew9VS5JKYS03w2UBSxf2maWdbPz2UFAxg0whw== + +"@swc/core-linux-arm-gnueabihf@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.101.tgz#8cd36328e794b3c42b6c8e578bb1f42e59ba0231" + integrity sha512-9xLKRb6zSzRGPqdz52Hy5GuB1lSjmLqa0lST6MTFads3apmx4Vgs8Y5NuGhx/h2I8QM4jXdLbpqQlifpzTlSSw== + +"@swc/core-linux-arm64-gnu@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.101.tgz#d15e3885eb13a1512ba62f00ce4f5bb19f710a0c" + integrity sha512-oE+r1lo7g/vs96Weh2R5l971dt+ZLuhaUX+n3BfDdPxNHfObXgKMjO7E+QS5RbGjv/AwiPCxQmbdCp/xN5ICJA== + +"@swc/core-linux-arm64-musl@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.101.tgz#851d4cc1079b091fee36f5f64335232210749d7a" + integrity sha512-OGjYG3H4BMOTnJWJyBIovCez6KiHF30zMIu4+lGJTCrxRI2fAjGLml3PEXj8tC3FMcud7U2WUn6TdG0/te2k6g== + +"@swc/core-linux-x64-gnu@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.101.tgz#3a2a7c584db2e05a798e28361440424914563fa3" + integrity sha512-/kBMcoF12PRO/lwa8Z7w4YyiKDcXQEiLvM+S3G9EvkoKYGgkkz4Q6PSNhF5rwg/E3+Hq5/9D2R+6nrkF287ihg== + +"@swc/core-linux-x64-musl@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.101.tgz#45d1d53945994f08e93703b8de24ccac88538d0c" + integrity sha512-kDN8lm4Eew0u1p+h1l3JzoeGgZPQ05qDE0czngnjmfpsH2sOZxVj1hdiCwS5lArpy7ktaLu5JdRnx70MkUzhXw== + +"@swc/core-win32-arm64-msvc@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.101.tgz#b2610b8354e5fbca7cc5be3f728e61b046227fa8" + integrity sha512-9Wn8TTLWwJKw63K/S+jjrZb9yoJfJwCE2RV5vPCCWmlMf3U1AXj5XuWOLUX+Rp2sGKau7wZKsvywhheWm+qndQ== + +"@swc/core-win32-ia32-msvc@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.101.tgz#c919175bb4cd5e9fcfa56fbd3708167c1d445c68" + integrity sha512-onO5KvICRVlu2xmr4//V2je9O2XgS1SGKpbX206KmmjcJhXN5EYLSxW9qgg+kgV5mip+sKTHTAu7IkzkAtElYA== + +"@swc/core-win32-x64-msvc@1.3.101": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.101.tgz#17743fe425caffc596fde5965c9c4cf9a48aa26a" + integrity sha512-T3GeJtNQV00YmiVw/88/nxJ/H43CJvFnpvBHCVn17xbahiVUOPOduh3rc9LgAkKiNt/aV8vU3OJR+6PhfMR7UQ== + +"@swc/core@^1.3.100": + version "1.3.101" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.101.tgz#4e8f1583094a73c410e48a0bebdeccdc6c66d4a5" + integrity sha512-w5aQ9qYsd/IYmXADAnkXPGDMTqkQalIi+kfFf/MHRKTpaOL7DHjMXwPp/n8hJ0qNjRvchzmPtOqtPBiER50d8A== dependencies: "@swc/counter" "^0.1.1" "@swc/types" "^0.1.5" optionalDependencies: - "@swc/core-darwin-arm64" "1.3.93" - "@swc/core-darwin-x64" "1.3.93" - "@swc/core-linux-arm-gnueabihf" "1.3.93" - "@swc/core-linux-arm64-gnu" "1.3.93" - "@swc/core-linux-arm64-musl" "1.3.93" - "@swc/core-linux-x64-gnu" "1.3.93" - "@swc/core-linux-x64-musl" "1.3.93" - "@swc/core-win32-arm64-msvc" "1.3.93" - "@swc/core-win32-ia32-msvc" "1.3.93" - "@swc/core-win32-x64-msvc" "1.3.93" + "@swc/core-darwin-arm64" "1.3.101" + "@swc/core-darwin-x64" "1.3.101" + "@swc/core-linux-arm-gnueabihf" "1.3.101" + "@swc/core-linux-arm64-gnu" "1.3.101" + "@swc/core-linux-arm64-musl" "1.3.101" + "@swc/core-linux-x64-gnu" "1.3.101" + "@swc/core-linux-x64-musl" "1.3.101" + "@swc/core-win32-arm64-msvc" "1.3.101" + "@swc/core-win32-ia32-msvc" "1.3.101" + "@swc/core-win32-x64-msvc" "1.3.101" "@swc/counter@^0.1.1": version "0.1.2" @@ -3670,19 +3670,19 @@ "@typescript-eslint/types" "6.7.2" eslint-visitor-keys "^3.4.1" -"@vitest/browser@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@vitest/browser/-/browser-1.0.1.tgz#d908e8015c5a449e0db28636c6afb969a8be9fcf" - integrity sha512-zKJvgfZMzahaFrIS5fbYnP2We+KRPJQUfog4mjOCOOVpLbk5DWtDD15XPYKaIY2IydD0ir0aCPrlcKlWGrcNww== +"@vitest/browser@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitest/browser/-/browser-1.1.0.tgz#b3c3e06d04506309a1e163103e1f65ee1391c262" + integrity sha512-59Uwoiw/zAQPmqgIKrzev8HNfeNlD8Q/nDyP9Xqg1D3kaM0tcOT/wk5RnZFW5f0JdguK0c1+vSeOPUSrOja1hQ== dependencies: estree-walker "^3.0.3" magic-string "^0.30.5" sirv "^2.0.3" -"@vitest/coverage-v8@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-1.0.1.tgz#b1249ca6e8f2617e56c7a15caa546e8b1abae4c7" - integrity sha512-Z4a7ig4VjUCT/P+LRB3IZrBRXb9xWRUM8rSBH9cKgfrU1Oe01/K2WJKtGshOnQwXZoSfQtwCGpbnHmB/qJwjcw== +"@vitest/coverage-v8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-1.1.0.tgz#bc0bbb99fcb608f72794701a86302ff3aabbc125" + integrity sha512-kHQRk70vTdXAyQY2C0vKOHPyQD/R6IUzcGdO4vCuyr4alE5Yg1+Sk2jSdjlIrTTXdcNEs+ReWVM09mmSFJpzyQ== dependencies: "@ampproject/remapping" "^2.2.1" "@bcoe/v8-coverage" "^0.2.3" @@ -3698,57 +3698,57 @@ test-exclude "^6.0.0" v8-to-istanbul "^9.2.0" -"@vitest/expect@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.0.2.tgz#7fc5ee3fe0e649f5a5e3df1a9744efe0163d1237" - integrity sha512-mAIo/8uddSWkjQMLFcjqZP3WmkwvvN0OtlyZIu33jFnwme3vZds8m8EDMxtj+Uzni2DwtPfHNjJcTM8zTV1f4A== +"@vitest/expect@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.1.0.tgz#f58eef7de090ad65f30bb93ec54fa9f94c9d1d5d" + integrity sha512-9IE2WWkcJo2BR9eqtY5MIo3TPmS50Pnwpm66A6neb2hvk/QSLfPXBz2qdiwUOQkwyFuuXEUj5380CbwfzW4+/w== dependencies: - "@vitest/spy" "1.0.2" - "@vitest/utils" "1.0.2" + "@vitest/spy" "1.1.0" + "@vitest/utils" "1.1.0" chai "^4.3.10" -"@vitest/runner@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.0.2.tgz#aad21c03fdcd1f380564fad37be7d5a2feb2f733" - integrity sha512-ZcHJXPT2kg/9Hc4fNkCbItlsgZSs3m4vQbxB8LCSdzpbG85bExCmSvu6K9lWpMNdoKfAr1Jn0BwS9SWUcGnbTQ== +"@vitest/runner@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.1.0.tgz#b3bf60f4a78f4324ca09811dd0f87b721a96b534" + integrity sha512-zdNLJ00pm5z/uhbWF6aeIJCGMSyTyWImy3Fcp9piRGvueERFlQFbUwCpzVce79OLm2UHk9iwaMSOaU9jVHgNVw== dependencies: - "@vitest/utils" "1.0.2" + "@vitest/utils" "1.1.0" p-limit "^5.0.0" pathe "^1.1.1" -"@vitest/snapshot@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.0.2.tgz#df11b066c9593e3539640a41f38452a6b5889da1" - integrity sha512-9ClDz2/aV5TfWA4reV7XR9p+hE0e7bifhwxlURugj3Fw0YXeTFzHmKCNEHd6wOIFMfthbGGwhlq7TOJ2jDO4/g== +"@vitest/snapshot@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.1.0.tgz#b9924e4303382b43bb2c31061b173e69a6fb3437" + integrity sha512-5O/wyZg09V5qmNmAlUgCBqflvn2ylgsWJRRuPrnHEfDNT6tQpQ8O1isNGgo+VxofISHqz961SG3iVvt3SPK/QQ== dependencies: magic-string "^0.30.5" pathe "^1.1.1" pretty-format "^29.7.0" -"@vitest/spy@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.0.2.tgz#c28205427e77e589e3f0e6017f55d1c5b9defee3" - integrity sha512-YlnHmDntp+zNV3QoTVFI5EVHV0AXpiThd7+xnDEbWnD6fw0TH/J4/+3GFPClLimR39h6nA5m0W4Bjm5Edg4A/A== +"@vitest/spy@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.1.0.tgz#7f40697e4fc217ac8c3cc89a865d1751b263f561" + integrity sha512-sNOVSU/GE+7+P76qYo+VXdXhXffzWZcYIPQfmkiRxaNCSPiLANvQx5Mx6ZURJ/ndtEkUJEpvKLXqAYTKEY+lTg== dependencies: tinyspy "^2.2.0" -"@vitest/utils@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.0.2.tgz#fbc483a62d13a02fa4e2b470fbf565fdd616a242" - integrity sha512-GPQkGHAnFAP/+seSbB9pCsj339yRrMgILoI5H2sPevTLCYgBq0VRjF8QSllmnQyvf0EontF6KUIt2t5s2SmqoQ== +"@vitest/utils@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.1.0.tgz#d177a5f41bdb484bbb43c8d73a77ca782df068b5" + integrity sha512-z+s510fKmYz4Y41XhNs3vcuFTFhcij2YF7F8VQfMEYAAUfqQh0Zfg7+w9xdgFGhPf3tX3TicAe+8BDITk6ampQ== dependencies: diff-sequences "^29.6.3" loupe "^2.3.7" pretty-format "^29.7.0" -"@wdio/config@8.24.12": - version "8.24.12" - resolved "https://registry.yarnpkg.com/@wdio/config/-/config-8.24.12.tgz#07d30aafcf0ef476e9930623b9c8e0f986943d00" - integrity sha512-3HW7qG1rIHzOIybV6oHR1CqLghsN0G3Xzs90ZciGL8dYhtcLtYCHwuWmBw4mkaB5xViU4AmZDuj7ChiG8Cr6Qw== +"@wdio/config@8.27.0": + version "8.27.0" + resolved "https://registry.yarnpkg.com/@wdio/config/-/config-8.27.0.tgz#c738d8108b5161cf3f80bb34d0e1f4d700b1a9ce" + integrity sha512-zYM5daeiBVVAbQj0ASymAt0RUsocLVIwKiUHNa8gg/1GsZnztGjetXExSp1gXlxtMVM5xWUSKjh6ceFK79gWDQ== dependencies: "@wdio/logger" "8.24.12" - "@wdio/types" "8.24.12" - "@wdio/utils" "8.24.12" + "@wdio/types" "8.27.0" + "@wdio/utils" "8.27.0" decamelize "^6.0.0" deepmerge-ts "^5.0.0" glob "^10.2.2" @@ -3786,21 +3786,21 @@ dependencies: "@types/node" "^20.1.0" -"@wdio/types@8.24.12": - version "8.24.12" - resolved "https://registry.yarnpkg.com/@wdio/types/-/types-8.24.12.tgz#c7a182ecc7effdd8ed7ea1967567a84da2c89100" - integrity sha512-SaD3OacDiW06DvSgAQ7sDBbpiI9qZRg7eoVYeBg3uSGVtUq84vTETRhhV7D6xTC00IqZu+mmN2TY5/q+7Gqy7w== +"@wdio/types@8.27.0": + version "8.27.0" + resolved "https://registry.yarnpkg.com/@wdio/types/-/types-8.27.0.tgz#ef2e3a9ae083f08ee5fe5bf9e5dfc70cc55cebcb" + integrity sha512-LbP9FKh8r0uW9/dKhTIUCC1Su8PsP9TmzGKXkWt6/IMacgJiB/zW3u1CgyaLw9lG0UiQORHGoeJX9zB2HZAh4w== dependencies: "@types/node" "^20.1.0" -"@wdio/utils@8.24.12": - version "8.24.12" - resolved "https://registry.yarnpkg.com/@wdio/utils/-/utils-8.24.12.tgz#4d4e03d62728b181f44c05584f3988659c6c7a38" - integrity sha512-uzwZyBVgqz0Wz1KL3aOUaQsxT8TNkzxti4NNTSMrU256qAPqc/n75rB7V73QASapCMpy70mZZTsuPgQYYj4ytQ== +"@wdio/utils@8.27.0": + version "8.27.0" + resolved "https://registry.yarnpkg.com/@wdio/utils/-/utils-8.27.0.tgz#6cb9b29649b4e301a959a8e8aea831edec635d55" + integrity sha512-4BY+JBQssVn003P5lA289uDMie3LtGinHze5btkcW9timB6VaU+EeZS4eKTPC0pziizLhteVvXYxv3YTpeeRfA== dependencies: "@puppeteer/browsers" "^1.6.0" "@wdio/logger" "8.24.12" - "@wdio/types" "8.24.12" + "@wdio/types" "8.27.0" decamelize "^6.0.0" deepmerge-ts "^5.1.0" edgedriver "^5.3.5" @@ -4948,15 +4948,6 @@ buffer-indexof-polyfill@~1.0.0: resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== -"buffer-polyfill@npm:buffer@^6.0.3", buffer@^6.0.3: - name buffer-polyfill - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz" @@ -4986,6 +4977,15 @@ buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.7.1: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + name buffer-polyfill + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffers@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" @@ -6318,10 +6318,10 @@ devtools-protocol@0.0.1147663: resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz#4ec5610b39a6250d1f87e6b9c7e16688ed0ac78e" integrity sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ== -devtools-protocol@^0.0.1233178: - version "0.0.1233178" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1233178.tgz#dfc83cdc487c0cae8f059047293be9d6267a19f9" - integrity sha512-jmMfyaqlzddwmDaSR1AQ+5ek+f7rupZdxKuPdkRcoxrZoF70Idg/4dTgXA08TLPmwAwB54gh49Wm2l/gRM0eUg== +devtools-protocol@^0.0.1237913: + version "0.0.1237913" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1237913.tgz#ac0208ff0cbe9c53646753576b5c1d788e3caa38" + integrity sha512-Pxtmz2ZIqBkpU82HaIdsvCQBG94yTC4xajrEsWx9p38QKEfBCJktSazsHkrjf9j3dVVNPhg5LR21F6KWeXpjiQ== dezalgo@^1.0.4: version "1.0.4" @@ -15125,7 +15125,7 @@ uuid@^3.3.2, uuid@^3.3.3: resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^9.0.0: +uuid@^9.0.0, uuid@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== @@ -15193,10 +15193,10 @@ vary@^1: resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vite-node@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.0.2.tgz#5e6096e31b851f245ccbd353bf3939130dfd0224" - integrity sha512-h7BbMJf46fLvFW/9Ygo3snkIBEHFh6fHpB4lge98H5quYrDhPFeI3S0LREz328uqPWSnii2yeJXktQ+Pmqk5BQ== +vite-node@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.1.0.tgz#0ebcb7398692e378954786dfba28e905e28a76b4" + integrity sha512-jV48DDUxGLEBdHCQvxL1mEh7+naVy+nhUUUaPAZLd3FJgXuxQiewHcfeZebbJ6onDqNGkP4r3MhQ342PRlG81Q== dependencies: cac "^6.7.14" debug "^4.3.4" @@ -15204,24 +15204,22 @@ vite-node@1.0.2: picocolors "^1.0.0" vite "^5.0.0" -vite-plugin-node-polyfills@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.17.0.tgz#1044a4955174ddaeedbc3679b179e1efac9da006" - integrity sha512-iPmPn7376e5u6QvoTSJa16hf5Q0DFwHFXJk2uYpsNlmI3JdPms7hWyh55o+OysJ5jo9J5XPhLC9sMOYifwFd1w== +vite-plugin-node-polyfills@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.18.0.tgz#2ad147960f7a35dbbb1c9f9c1ae928bd0f438c1e" + integrity sha512-zkdLD3gpOhLFyxYRMJ5apk0RcODhomuS3XQgExowiX8naoc251JfcP3toqnfDlMdF0xuPYahre/H38xAcq8ApA== dependencies: "@rollup/plugin-inject" "^5.0.5" - buffer-polyfill "npm:buffer@^6.0.3" node-stdlib-browser "^1.2.0" - process "^0.11.10" -vite-plugin-top-level-await@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.3.1.tgz#7e7293be01489b508692627529c0a3b3218a23a3" - integrity sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ== +vite-plugin-top-level-await@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.4.1.tgz#607dfe084157550fa33df18062b99ceea774cd9c" + integrity sha512-hogbZ6yT7+AqBaV6lK9JRNvJDn4/IJvHLu6ET06arNfo0t2IsyCaon7el9Xa8OumH+ESuq//SDf8xscZFE0rWw== dependencies: - "@rollup/plugin-virtual" "^3.0.1" - "@swc/core" "^1.3.10" - uuid "^9.0.0" + "@rollup/plugin-virtual" "^3.0.2" + "@swc/core" "^1.3.100" + uuid "^9.0.1" vite@^5.0.0: version "5.0.6" @@ -15239,16 +15237,16 @@ vitest-when@^0.3.0: resolved "https://registry.yarnpkg.com/vitest-when/-/vitest-when-0.3.0.tgz#663d4274f1e7302bd24ec00dda8269d20b2eff04" integrity sha512-wYfmzd+GkvdNNhbeb/40PnKpetUP5I7qxvdbu1OAXRXaLrnLfSrJTa/dMIbqqrc8SA0vhonpw5p0RHDXwhDM1Q== -vitest@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.0.2.tgz#a7c3bf41bd5ef8c1c781c98c84a749d26b31f944" - integrity sha512-F3NVwwpXfRSDnJmyv+ALPwSRVt0zDkRRE18pwUHSUPXAlWQ47rY1dc99ziMW5bBHyqwK2ERjMisLNoef64qk9w== - dependencies: - "@vitest/expect" "1.0.2" - "@vitest/runner" "1.0.2" - "@vitest/snapshot" "1.0.2" - "@vitest/spy" "1.0.2" - "@vitest/utils" "1.0.2" +vitest@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.1.0.tgz#47ba67c564aa137b53b0197d2a992908e7f5b04d" + integrity sha512-oDFiCrw7dd3Jf06HoMtSRARivvyjHJaTxikFxuqJjO76U436PqlVw1uLn7a8OSPrhSfMGVaRakKpA2lePdw79A== + dependencies: + "@vitest/expect" "1.1.0" + "@vitest/runner" "1.1.0" + "@vitest/snapshot" "1.1.0" + "@vitest/spy" "1.1.0" + "@vitest/utils" "1.1.0" acorn-walk "^8.3.0" cac "^6.7.14" chai "^4.3.10" @@ -15263,7 +15261,7 @@ vitest@^1.0.2: tinybench "^2.5.1" tinypool "^0.8.1" vite "^5.0.0" - vite-node "1.0.2" + vite-node "1.1.0" why-is-node-running "^2.2.2" vm-browserify@^1.0.1: @@ -15523,40 +15521,40 @@ web3@^4.0.3: web3-utils "^4.0.3" web3-validator "^1.0.2" -webdriver@8.24.12: - version "8.24.12" - resolved "https://registry.yarnpkg.com/webdriver/-/webdriver-8.24.12.tgz#fd443550f2fa25498af8d6a7a1261dc3d6c4f462" - integrity sha512-03DQIClHoaAqTsmDkxGwo4HwHfkn9LzJ1wfNyUerzKg8DnyXeiT6ILqj6EXLfsvh5zddU2vhYGLFXSerPgkuOQ== +webdriver@8.27.0: + version "8.27.0" + resolved "https://registry.yarnpkg.com/webdriver/-/webdriver-8.27.0.tgz#27e936a03c08b2d72ed6bd01a6a46f8189ef0abf" + integrity sha512-n1IA+rR3u84XxU9swiKUM06BkEC0GDimfZkBML57cny+utQOUbdM/mBpqCUnkWX/RBz/p2EfHdKNyOs3/REaog== dependencies: "@types/node" "^20.1.0" "@types/ws" "^8.5.3" - "@wdio/config" "8.24.12" + "@wdio/config" "8.27.0" "@wdio/logger" "8.24.12" "@wdio/protocols" "8.24.12" - "@wdio/types" "8.24.12" - "@wdio/utils" "8.24.12" + "@wdio/types" "8.27.0" + "@wdio/utils" "8.27.0" deepmerge-ts "^5.1.0" got "^12.6.1" ky "^0.33.0" ws "^8.8.0" -webdriverio@^8.24.12: - version "8.24.12" - resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-8.24.12.tgz#05a2107ae8a3927e1a01503a05fc2050fa4e06bd" - integrity sha512-Ddu0NNRMVkTzRzqvm3m0wt2eLUn+Plz2Cj+1QXDnVpddYJvk9J3elZC2hqNyscEtecQ+h2y3r36OcJqkl9jPag== +webdriverio@^8.27.0: + version "8.27.0" + resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-8.27.0.tgz#4068b0164ab66bfb62d6eb6b8d97df2d140922d5" + integrity sha512-Qh5VCiBjEmxnmXcL1QEFoDzFqTtaWKrXriuU5G0yHKCModGAt2G7IHTkAok3CpmkVJfZpEvY630aP1MvgDtFhw== dependencies: "@types/node" "^20.1.0" - "@wdio/config" "8.24.12" + "@wdio/config" "8.27.0" "@wdio/logger" "8.24.12" "@wdio/protocols" "8.24.12" "@wdio/repl" "8.24.12" - "@wdio/types" "8.24.12" - "@wdio/utils" "8.24.12" + "@wdio/types" "8.27.0" + "@wdio/utils" "8.27.0" archiver "^6.0.0" aria-query "^5.0.0" css-shorthand-properties "^1.1.1" css-value "^0.0.1" - devtools-protocol "^0.0.1233178" + devtools-protocol "^0.0.1237913" grapheme-splitter "^1.0.2" import-meta-resolve "^4.0.0" is-plain-obj "^4.1.0" @@ -15568,7 +15566,7 @@ webdriverio@^8.24.12: resq "^1.9.1" rgb2hex "0.2.5" serialize-error "^11.0.1" - webdriver "8.24.12" + webdriver "8.27.0" webidl-conversions@^3.0.0: version "3.0.1"