diff --git a/lerna.json b/lerna.json index 4130f46a317b..ebae5dcafabe 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useNx": true, - "version": "1.12.0", + "version": "1.12.1", "stream": true, "command": { "version": { diff --git a/packages/api/package.json b/packages/api/package.json index 2f1c5953a673..99f5094b4d24 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -71,10 +71,10 @@ "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.6.1", "@chainsafe/ssz": "^0.14.0", - "@lodestar/config": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/config": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", "eventsource": "^2.0.2", "qs": "^6.11.1" }, diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index 2d887790dd39..53ebb93692dc 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -52,7 +52,6 @@ export type BlockHeaderResponse = { }; export enum BroadcastValidation { - none = "none", gossip = "gossip", consensus = "consensus", consensusAndEquivocation = "consensus_and_equivocation", diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index 5cb35540cf7a..54b2537648cb 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -59,7 +59,7 @@ export const testData: GenericServerTestCases = { res: undefined, }, publishBlockV2: { - args: [ssz.phase0.SignedBeaconBlock.defaultValue(), {broadcastValidation: BroadcastValidation.none}], + args: [ssz.phase0.SignedBeaconBlock.defaultValue(), {broadcastValidation: BroadcastValidation.consensus}], res: undefined, }, publishBlindedBlock: { @@ -67,7 +67,7 @@ export const testData: GenericServerTestCases = { res: undefined, }, publishBlindedBlockV2: { - args: [getDefaultBlindedBlock(64), {broadcastValidation: BroadcastValidation.none}], + args: [getDefaultBlindedBlock(64), {broadcastValidation: BroadcastValidation.consensus}], res: undefined, }, getBlobSidecars: { diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 083cc2410df1..1b8c9f607208 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -100,7 +100,7 @@ "@chainsafe/bls": "7.1.1", "@chainsafe/blst": "^0.2.9", "@chainsafe/discv5": "^5.1.0", - "@chainsafe/libp2p-gossipsub": "^10.1.0", + "@chainsafe/libp2p-gossipsub": "^10.1.1", "@chainsafe/libp2p-noise": "^13.0.1", "@chainsafe/persistent-merkle-tree": "^0.6.1", "@chainsafe/prometheus-gc-stats": "^1.0.0", @@ -119,18 +119,18 @@ "@libp2p/peer-id-factory": "^3.0.4", "@libp2p/prometheus-metrics": "^2.0.7", "@libp2p/tcp": "8.0.8", - "@lodestar/api": "^1.12.0", - "@lodestar/config": "^1.12.0", - "@lodestar/db": "^1.12.0", - "@lodestar/fork-choice": "^1.12.0", - "@lodestar/light-client": "^1.12.0", - "@lodestar/logger": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/reqresp": "^1.12.0", - "@lodestar/state-transition": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", - "@lodestar/validator": "^1.12.0", + "@lodestar/api": "^1.12.1", + "@lodestar/config": "^1.12.1", + "@lodestar/db": "^1.12.1", + "@lodestar/fork-choice": "^1.12.1", + "@lodestar/light-client": "^1.12.1", + "@lodestar/logger": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/reqresp": "^1.12.1", + "@lodestar/state-transition": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", + "@lodestar/validator": "^1.12.1", "@multiformats/multiaddr": "^12.1.3", "@types/datastore-level": "^3.0.0", "buffer-xor": "^2.0.2", 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 3b36674dd613..1b2d0f10a85b 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -4,6 +4,7 @@ import { computeTimeAtSlot, parseSignedBlindedBlockOrContents, reconstructFullBlockOrContents, + DataAvailableStatus, } from "@lodestar/state-transition"; import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import {sleep, toHex} from "@lodestar/utils"; @@ -15,6 +16,9 @@ import {BlockError, BlockErrorCode} from "../../../../chain/errors/index.js"; import {OpSource} from "../../../../metrics/validatorMonitor.js"; import {NetworkEvent} from "../../../../network/index.js"; import {ApiModules} from "../../types.js"; +import {validateGossipBlock} from "../../../../chain/validation/block.js"; +import {verifyBlocksInEpoch} from "../../../../chain/blocks/verifyBlock.js"; +import {BeaconChain} from "../../../../chain/chain.js"; import {resolveBlockId, toBeaconHeaderResponse} from "./utils.js"; type PublishBlockOpts = ImportBlockOpts & {broadcastValidation?: routes.beacon.BroadcastValidation}; @@ -64,29 +68,86 @@ export function getBeaconBlockApi({ // check what validations have been requested before broadcasting and publishing the block // TODO: add validation time to metrics - const broadcastValidation = opts.broadcastValidation ?? routes.beacon.BroadcastValidation.none; + const broadcastValidation = opts.broadcastValidation ?? routes.beacon.BroadcastValidation.gossip; // if block is locally produced, full or blinded, it already is 'consensus' validated as it went through // state transition to produce the stateRoot const slot = signedBlock.message.slot; + const fork = config.getForkName(slot); const blockRoot = toHex(chain.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(signedBlock.message)); + // bodyRoot should be the same to produced block + const bodyRoot = toHex(chain.config.getForkTypes(slot).BeaconBlockBody.hashTreeRoot(signedBlock.message.body)); const blockLocallyProduced = chain.producedBlockRoot.has(blockRoot) || chain.producedBlindedBlockRoot.has(blockRoot); - const valLogMeta = {broadcastValidation, blockRoot, blockLocallyProduced, slot}; + const valLogMeta = {broadcastValidation, blockRoot, bodyRoot, blockLocallyProduced, slot}; switch (broadcastValidation) { - case routes.beacon.BroadcastValidation.none: { - if (blockLocallyProduced) { - chain.logger.debug("No broadcast validation requested for the block", valLogMeta); - } else { - chain.logger.warn("No broadcast validation requested for the block", valLogMeta); + case routes.beacon.BroadcastValidation.gossip: { + if (!blockLocallyProduced) { + try { + await validateGossipBlock(config, chain, signedBlock, fork); + } catch (error) { + chain.logger.error("Gossip validations failed while publishing the block", valLogMeta, error as Error); + chain.persistInvalidSszValue( + chain.config.getForkTypes(slot).SignedBeaconBlock, + signedBlock, + "api_reject_gossip_failure" + ); + throw error; + } } + chain.logger.debug("Gossip checks validated while publishing the block", valLogMeta); break; } + + case routes.beacon.BroadcastValidation.consensusAndEquivocation: case routes.beacon.BroadcastValidation.consensus: { // check if this beacon node produced the block else run validations if (!blockLocallyProduced) { - // error or log warning that we support consensus val on blocks produced via this beacon node - const message = "Consensus validation not implemented yet for block not produced by this beacon node"; + const parentBlock = chain.forkChoice.getBlock(signedBlock.message.parentRoot); + if (parentBlock === null) { + network.events.emit(NetworkEvent.unknownBlockParent, { + blockInput: blockForImport, + peer: IDENTITY_PEER_ID, + }); + chain.persistInvalidSszValue( + chain.config.getForkTypes(slot).SignedBeaconBlock, + signedBlock, + "api_reject_parent_unknown" + ); + throw new BlockError(signedBlock, { + code: BlockErrorCode.PARENT_UNKNOWN, + parentRoot: toHexString(signedBlock.message.parentRoot), + }); + } + + try { + await verifyBlocksInEpoch.call( + chain as BeaconChain, + parentBlock, + [blockForImport], + [DataAvailableStatus.available], + { + ...opts, + verifyOnly: true, + skipVerifyBlockSignatures: true, + skipVerifyExecutionPayload: true, + } + ); + } catch (error) { + chain.logger.error("Consensus checks failed while publishing the block", valLogMeta, error as Error); + chain.persistInvalidSszValue( + chain.config.getForkTypes(slot).SignedBeaconBlock, + signedBlock, + "api_reject_consensus_failure" + ); + throw error; + } + } + + chain.logger.debug("Consensus validated while publishing block", valLogMeta); + + if (broadcastValidation === routes.beacon.BroadcastValidation.consensusAndEquivocation) { + const message = `Equivocation checks not yet implemented for broadcastValidation=${broadcastValidation}`; if (chain.opts.broadcastValidationStrictness === "error") { throw Error(message); } else { @@ -102,7 +163,7 @@ export function getBeaconBlockApi({ if (chain.opts.broadcastValidationStrictness === "error") { throw Error(message); } else { - chain.logger.warn(message); + chain.logger.warn(message, valLogMeta); } } } @@ -163,6 +224,8 @@ export function getBeaconBlockApi({ : undefined; const blobs = blobSidecars ? blobSidecars.map((blobSidecar) => blobSidecar.blob) : null; + chain.logger.debug("Assembling blinded block for publishing", {source, blockRoot, slot}); + const signedBlockOrContents = source === ProducedBlockSource.engine ? reconstructFullBlockOrContents({signedBlindedBlock, signedBlindedBlobSidecars}, {executionPayload, blobs}) diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index b14e1b3ade52..e41c4c97bc63 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -309,6 +309,9 @@ export function getValidatorApi({ }); 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); @@ -377,6 +380,9 @@ export function getValidatorApi({ executionPayloadValue, root: toHexString(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block)), }); + if (chain.opts.persistProducedBlocks) { + void chain.persistBlock(block, "produced_engine_block"); + } if (isForkBlobs(version)) { const blockHash = toHex((block as bellatrix.BeaconBlock).body.executionPayload.blockHash); const blobSidecars = chain.producedBlobSidecarsCache.get(blockHash); @@ -478,16 +484,17 @@ export function getValidatorApi({ delayMs, cutoffMs: BLOCK_PRODUCTION_RACE_CUTOFF_MS, timeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS, + slot, }); } ); if (blindedBlock instanceof Error) { // error here means race cutoff exceeded - logger.error("Failed to produce builder block", {}, blindedBlock); + logger.error("Failed to produce builder block", {slot}, blindedBlock); blindedBlock = null; } if (fullBlock instanceof Error) { - logger.error("Failed to produce execution block", {}, fullBlock); + logger.error("Failed to produce execution block", {slot}, fullBlock); fullBlock = null; } } else if (blindedBlockPromise !== null && fullBlockPromise === null) { @@ -535,23 +542,26 @@ export function getValidatorApi({ // winston logger doesn't like bigint enginePayloadValue: `${enginePayloadValue}`, builderPayloadValue: `${builderPayloadValue}`, + slot, }); } else if (fullBlock && !blindedBlock) { selectedSource = ProducedBlockSource.engine; logger.verbose("Selected engine block: no builder block produced", { // winston logger doesn't like bigint enginePayloadValue: `${enginePayloadValue}`, + slot, }); } else if (blindedBlock && !fullBlock) { selectedSource = ProducedBlockSource.builder; logger.verbose("Selected builder block: no engine block produced", { // winston logger doesn't like bigint builderPayloadValue: `${builderPayloadValue}`, + slot, }); } if (selectedSource === null) { - throw Error("Failed to produce engine or builder block"); + throw Error(`Failed to produce engine or builder block for slot=${slot}`); } if (selectedSource === ProducedBlockSource.engine) { diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 77c6b4832bbc..a273b2ba7d55 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -7,7 +7,7 @@ import { } from "@lodestar/state-transition"; import {bellatrix} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; -import {ProtoBlock} from "@lodestar/fork-choice"; +import {ProtoBlock, ExecutionStatus} from "@lodestar/fork-choice"; import {ChainForkConfig} from "@lodestar/config"; import {Logger} from "@lodestar/utils"; import {BlockError, BlockErrorCode} from "../errors/index.js"; @@ -89,7 +89,14 @@ export async function verifyBlocksInEpoch( // batch all I/O operations to reduce overhead const [segmentExecStatus, {postStates, proposerBalanceDeltas}] = await Promise.all([ // Execution payloads - verifyBlocksExecutionPayload(this, parentBlock, blocks, preState0, abortController.signal, opts), + opts.skipVerifyExecutionPayload !== true + ? verifyBlocksExecutionPayload(this, parentBlock, blocks, preState0, abortController.signal, opts) + : Promise.resolve({ + execAborted: null, + executionStatuses: blocks.map((_blk) => ExecutionStatus.Syncing), + mergeBlockFound: null, + } as SegmentExecStatus), + // Run state transition only // TODO: Ensure it yields to allow flushing to workers and engine API verifyBlocksStateTransitionOnly( @@ -103,37 +110,43 @@ export async function verifyBlocksInEpoch( ), // All signatures at once - verifyBlocksSignatures(this.bls, this.logger, this.metrics, preState0, blocks, opts), + opts.skipVerifyBlockSignatures !== true + ? verifyBlocksSignatures(this.bls, this.logger, this.metrics, preState0, blocks, opts) + : Promise.resolve(), // ideally we want to only persist blocks after verifying them however the reality is there are // rarely invalid blocks we'll batch all I/O operation here to reduce the overhead if there's // an error, we'll remove blocks not in forkchoice - opts.eagerPersistBlock ? writeBlockInputToDb.call(this, blocksInput) : Promise.resolve(), + opts.verifyOnly !== true && opts.eagerPersistBlock + ? writeBlockInputToDb.call(this, blocksInput) + : Promise.resolve(), ]); - if (segmentExecStatus.execAborted === null && segmentExecStatus.mergeBlockFound !== null) { - // merge block found and is fully valid = state transition + signatures + execution payload. - // TODO: Will this banner be logged during syncing? - logOnPowBlock(this.logger, this.config, segmentExecStatus.mergeBlockFound); - } + if (opts.verifyOnly !== true) { + if (segmentExecStatus.execAborted === null && segmentExecStatus.mergeBlockFound !== null) { + // merge block found and is fully valid = state transition + signatures + execution payload. + // TODO: Will this banner be logged during syncing? + logOnPowBlock(this.logger, this.config, segmentExecStatus.mergeBlockFound); + } - const fromFork = this.config.getForkName(parentBlock.slot); - const toFork = this.config.getForkName(blocks[blocks.length - 1].message.slot); + const fromFork = this.config.getForkName(parentBlock.slot); + const toFork = this.config.getForkName(blocks[blocks.length - 1].message.slot); - // If transition through toFork, note won't happen if ${toFork}_EPOCH = 0, will log double on re-org - if (toFork !== fromFork) { - switch (toFork) { - case ForkName.capella: - this.logger.info(CAPELLA_OWL_BANNER); - this.logger.info("Activating withdrawals", {epoch: this.config.CAPELLA_FORK_EPOCH}); - break; + // If transition through toFork, note won't happen if ${toFork}_EPOCH = 0, will log double on re-org + if (toFork !== fromFork) { + switch (toFork) { + case ForkName.capella: + this.logger.info(CAPELLA_OWL_BANNER); + this.logger.info("Activating withdrawals", {epoch: this.config.CAPELLA_FORK_EPOCH}); + break; - case ForkName.deneb: - this.logger.info(DENEB_BLOWFISH_BANNER); - this.logger.info("Activating blobs", {epoch: this.config.DENEB_FORK_EPOCH}); - break; + case ForkName.deneb: + this.logger.info(DENEB_BLOWFISH_BANNER); + this.logger.info("Activating blobs", {epoch: this.config.DENEB_FORK_EPOCH}); + break; - default: + default: + } } } diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index 88d56171c7a3..7f4edd14c618 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -19,7 +19,7 @@ import { } from "@lodestar/fork-choice"; import {ChainForkConfig} from "@lodestar/config"; import {ErrorAborted, Logger} from "@lodestar/utils"; -import {ForkSeq} from "@lodestar/params"; +import {ForkSeq, SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import {IExecutionEngine} from "../../execution/engine/interface.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; @@ -143,9 +143,10 @@ export async function verifyBlocksExecutionPayload( const lastBlock = blocks[blocks.length - 1]; const currentSlot = chain.clock.currentSlot; + const safeSlotsToImportOptimistically = opts.safeSlotsToImportOptimistically ?? SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY; let isOptimisticallySafe = parentBlock.executionStatus !== ExecutionStatus.PreMerge || - lastBlock.message.slot + opts.safeSlotsToImportOptimistically < currentSlot; + lastBlock.message.slot + safeSlotsToImportOptimistically < currentSlot; for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) { const block = blocks[blockIndex]; @@ -331,7 +332,9 @@ export async function verifyBlockExecutionPayload( // Check if the entire segment was deemed safe or, this block specifically itself if not in // the safeSlotsToImportOptimistically window of current slot, then we can import else // we need to throw and not import his block - if (!isOptimisticallySafe && block.message.slot + opts.safeSlotsToImportOptimistically >= currentSlot) { + const safeSlotsToImportOptimistically = + opts.safeSlotsToImportOptimistically ?? SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY; + if (!isOptimisticallySafe && block.message.slot + safeSlotsToImportOptimistically >= currentSlot) { const execError = new BlockError(block, { code: BlockErrorCode.EXECUTION_ENGINE_ERROR, execStatus: ExecutionPayloadStatus.UNSAFE_OPTIMISTIC_STATUS, diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 18c37a3ba437..56409dc89d21 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import {CompositeTypeAny, fromHexString, TreeView, Type} from "@chainsafe/ssz"; +import {CompositeTypeAny, fromHexString, toHexString, TreeView, Type} from "@chainsafe/ssz"; import { BeaconStateAllForks, CachedBeaconStateAllForks, @@ -25,6 +25,7 @@ import { deneb, Wei, bellatrix, + isBlindedBeaconBlock, } from "@lodestar/types"; import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; @@ -496,6 +497,17 @@ export class BeaconChain implements IBeaconChain { proposerPubKey, }); + // The hashtree root computed here for debug log will get cached and hence won't introduce additional delays + const bodyRoot = + blockType === BlockType.Full + ? this.config.getForkTypes(slot).BeaconBlockBody.hashTreeRoot(body) + : this.config.getBlindedForkTypes(slot).BeaconBlockBody.hashTreeRoot(body as allForks.BlindedBeaconBlockBody); + this.logger.debug("Computing block post state from the produced body", { + slot, + bodyRoot: toHexString(bodyRoot), + blockType, + }); + const block = { slot, proposerIndex, @@ -503,16 +515,20 @@ export class BeaconChain implements IBeaconChain { stateRoot: ZERO_HASH, body, } as AssembledBlockType; - block.stateRoot = computeNewStateRoot(this.metrics, state, block); + const blockRoot = + blockType === BlockType.Full + ? this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block) + : this.config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block as allForks.BlindedBeaconBlock); + const blockRootHex = toHex(blockRoot); // track the produced block for consensus broadcast validations - const blockRoot = this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block); - const blockRootHex = toHex(blockRoot); if (blockType === BlockType.Full) { + this.logger.debug("Setting executionPayload cache for produced block", {blockRootHex, slot, blockType}); this.producedBlockRoot.set(blockRootHex, (block as bellatrix.BeaconBlock).body.executionPayload ?? null); this.metrics?.blockProductionCaches.producedBlockRoot.set(this.producedBlockRoot.size); } else { + this.logger.debug("Tracking the produced blinded block", {blockRootHex, slot, blockType}); this.producedBlindedBlockRoot.add(blockRootHex); this.metrics?.blockProductionCaches.producedBlindedBlockRoot.set(this.producedBlindedBlockRoot.size); } @@ -622,21 +638,32 @@ export class BeaconChain implements IBeaconChain { return this.reprocessController.waitForBlockOfAttestation(slot, root); } + persistBlock(data: allForks.BeaconBlock | allForks.BlindedBeaconBlock, suffix?: string): void { + const slot = data.slot; + if (isBlindedBeaconBlock(data)) { + const sszType = this.config.getBlindedForkTypes(slot).BeaconBlock; + void this.persistSszObject("BlindedBeaconBlock", sszType.serialize(data), sszType.hashTreeRoot(data), suffix); + } else { + const sszType = this.config.getForkTypes(slot).BeaconBlock; + void this.persistSszObject("BeaconBlock", sszType.serialize(data), sszType.hashTreeRoot(data), suffix); + } + } + persistInvalidSszValue(type: Type, sszObject: T, suffix?: string): void { if (this.opts.persistInvalidSszObjects) { - void this.persistInvalidSszObject(type.typeName, type.serialize(sszObject), type.hashTreeRoot(sszObject), suffix); + void this.persistSszObject(type.typeName, type.serialize(sszObject), type.hashTreeRoot(sszObject), suffix); } } persistInvalidSszBytes(typeName: string, sszBytes: Uint8Array, suffix?: string): void { if (this.opts.persistInvalidSszObjects) { - void this.persistInvalidSszObject(typeName, sszBytes, sszBytes, suffix); + void this.persistSszObject(typeName, sszBytes, sszBytes, suffix); } } persistInvalidSszView(view: TreeView, suffix?: string): void { if (this.opts.persistInvalidSszObjects) { - void this.persistInvalidSszObject(view.type.typeName, view.serialize(), view.hashTreeRoot(), suffix); + void this.persistSszObject(view.type.typeName, view.serialize(), view.hashTreeRoot(), suffix); } } @@ -729,16 +756,12 @@ export class BeaconChain implements IBeaconChain { return {state: blockState, stateId: "block_state_any_epoch", shouldWarn: true}; } - private async persistInvalidSszObject( + private async persistSszObject( typeName: string, bytes: Uint8Array, root: Uint8Array, suffix?: string ): Promise { - if (!this.opts.persistInvalidSszObjects) { - return; - } - const now = new Date(); // yyyy-MM-dd const dateStr = now.toISOString().split("T")[0]; diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 8d6f7f419d7b..59beb122a212 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -156,6 +156,7 @@ export interface IBeaconChain { updateBeaconProposerData(epoch: Epoch, proposers: ProposerPreparationData[]): Promise; + persistBlock(data: allForks.BeaconBlock | allForks.BlindedBeaconBlock, suffix?: string): void; persistInvalidSszValue(type: Type, sszObject: T | Uint8Array, suffix?: string): void; persistInvalidSszBytes(type: string, sszBytes: Uint8Array, suffix?: string): void; /** Persist bad items to persistInvalidSszObjectsDir dir, for example invalid state, attestations etc. */ diff --git a/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts b/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts index f9911275b6ee..00309d322a11 100644 --- a/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts +++ b/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts @@ -166,10 +166,16 @@ export class AggregatedAttestationPool { } } - return attestationsByScore - .sort((a, b) => b.score - a.score) - .slice(0, MAX_ATTESTATIONS) - .map((attestation) => attestation.attestation); + const sortedAttestationsByScore = attestationsByScore.sort((a, b) => b.score - a.score); + const attestationsForBlock: phase0.Attestation[] = []; + for (const [i, attestationWithScore] of sortedAttestationsByScore.entries()) { + if (i >= MAX_ATTESTATIONS) { + break; + } + // attestations could be modified in this op pool, so we need to clone for block + attestationsForBlock.push(ssz.phase0.Attestation.clone(attestationWithScore.attestation)); + } + return attestationsForBlock; } /** diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index 9f826d1a2403..d71cd55df673 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -12,6 +12,7 @@ export type IChainOptions = BlockProcessOpts & LightClientServerOpts & { blsVerifyAllMainThread?: boolean; blsVerifyAllMultiThread?: boolean; + persistProducedBlocks?: boolean; persistInvalidSszObjects?: boolean; persistInvalidSszObjectsDir?: string; skipCreateStateCacheIfAvailable?: boolean; @@ -38,7 +39,7 @@ export type BlockProcessOpts = { /** * Override SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY */ - safeSlotsToImportOptimistically: number; + safeSlotsToImportOptimistically?: number; /** * Assert progressive balances the same to EpochTransitionCache */ @@ -53,6 +54,17 @@ export type BlockProcessOpts = { */ disableImportExecutionFcU?: boolean; emitPayloadAttributes?: boolean; + + /** + * Used to specify to specify to run verifications only and not + * to save the block or log transitions for e.g. doing + * broadcastValidation while publishing the block + */ + verifyOnly?: boolean; + /** Used to specify to skip execution payload validation */ + skipVerifyExecutionPayload?: boolean; + /** Used to specify to skip block signatures validation */ + skipVerifyBlockSignatures?: boolean; }; export type PoolOpts = { diff --git a/packages/cli/package.json b/packages/cli/package.json index 4089b1c2d5ed..2d58e12a987a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/lodestar", - "version": "1.12.0", + "version": "1.12.1", "description": "Command line interface for lodestar", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -65,17 +65,17 @@ "@libp2p/crypto": "^2.0.4", "@libp2p/peer-id": "^3.0.2", "@libp2p/peer-id-factory": "^3.0.4", - "@lodestar/api": "^1.12.0", - "@lodestar/beacon-node": "^1.12.0", - "@lodestar/config": "^1.12.0", - "@lodestar/db": "^1.12.0", - "@lodestar/light-client": "^1.12.0", - "@lodestar/logger": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/state-transition": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", - "@lodestar/validator": "^1.12.0", + "@lodestar/api": "^1.12.1", + "@lodestar/beacon-node": "^1.12.1", + "@lodestar/config": "^1.12.1", + "@lodestar/db": "^1.12.1", + "@lodestar/light-client": "^1.12.1", + "@lodestar/logger": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/state-transition": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", + "@lodestar/validator": "^1.12.1", "@multiformats/multiaddr": "^12.1.3", "@types/lockfile": "^1.0.2", "bip39": "^3.1.0", @@ -96,7 +96,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.12.0", + "@lodestar/test-utils": "^1.12.1", "@types/debug": "^4.1.7", "@types/expand-tilde": "^2.0.0", "@types/got": "^9.6.12", diff --git a/packages/cli/src/cmds/validator/handler.ts b/packages/cli/src/cmds/validator/handler.ts index 8537500684ca..fe14cedbcca1 100644 --- a/packages/cli/src/cmds/validator/handler.ts +++ b/packages/cli/src/cmds/validator/handler.ts @@ -169,6 +169,7 @@ export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Pr valProposerConfig, distributed: args.distributed, useProduceBlockV3: args.useProduceBlockV3, + broadcastValidation: parseBroadcastValidation(args.broadcastValidation), }, metrics ); @@ -268,3 +269,18 @@ function parseBuilderSelection(builderSelection?: string): routes.validator.Buil } return builderSelection as routes.validator.BuilderSelection; } + +function parseBroadcastValidation(broadcastValidation?: string): routes.beacon.BroadcastValidation | undefined { + if (broadcastValidation) { + switch (broadcastValidation) { + case "gossip": + case "consensus": + case "consensus_and_equivocation": + break; + default: + throw Error("Invalid input for broadcastValidation, check help"); + } + } + + return broadcastValidation as routes.beacon.BroadcastValidation; +} diff --git a/packages/cli/src/cmds/validator/options.ts b/packages/cli/src/cmds/validator/options.ts index d3af927deca6..a14e5530844d 100644 --- a/packages/cli/src/cmds/validator/options.ts +++ b/packages/cli/src/cmds/validator/options.ts @@ -47,6 +47,7 @@ export type IValidatorCliArgs = AccountValidatorArgs & "builder.selection"?: string; useProduceBlockV3?: boolean; + broadcastValidation?: string; importKeystores?: string[]; importKeystoresPassword?: string; @@ -250,6 +251,12 @@ export const validatorOptions: CliCommandOptions = { defaultDescription: `${defaultOptions.useProduceBlockV3}`, }, + broadcastValidation: { + type: "string", + description: "Validations to be run by beacon node for the signed block prior to publishing", + defaultDescription: `${defaultOptions.broadcastValidation}`, + }, + importKeystores: { alias: ["keystore"], // Backwards compatibility with old `validator import` cmdx description: "Path(s) to a directory or single file path to validator keystores, i.e. Launchpad validators", diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 359b77740b00..5aef805f61f2 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -7,6 +7,7 @@ export type ChainArgs = { "chain.blsVerifyAllMultiThread"?: boolean; "chain.blsVerifyAllMainThread"?: boolean; "chain.disableBlsBatchVerify"?: boolean; + "chain.persistProducedBlocks"?: boolean; "chain.persistInvalidSszObjects"?: boolean; // No need to define chain.persistInvalidSszObjects as part of ChainArgs // as this is defined as part of BeaconPaths @@ -32,6 +33,7 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { blsVerifyAllMultiThread: args["chain.blsVerifyAllMultiThread"], blsVerifyAllMainThread: args["chain.blsVerifyAllMainThread"], disableBlsBatchVerify: args["chain.disableBlsBatchVerify"], + persistProducedBlocks: args["chain.persistProducedBlocks"], persistInvalidSszObjects: args["chain.persistInvalidSszObjects"], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any persistInvalidSszObjectsDir: undefined as any, @@ -94,6 +96,13 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, + "chain.persistProducedBlocks": { + hidden: true, + type: "boolean", + description: "Persist produced blocks or not for debugging purpose", + group: "chain", + }, + "chain.persistInvalidSszObjects": { hidden: true, type: "boolean", diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index 4f5050d87daf..fe2433bb5030 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -21,6 +21,7 @@ describe("options / beaconNodeOptions", () => { "chain.blsVerifyAllMultiThread": true, "chain.blsVerifyAllMainThread": true, "chain.disableBlsBatchVerify": true, + "chain.persistProducedBlocks": true, "chain.persistInvalidSszObjects": true, "chain.proposerBoostEnabled": false, "chain.disableImportExecutionFcU": false, @@ -121,6 +122,7 @@ describe("options / beaconNodeOptions", () => { blsVerifyAllMultiThread: true, blsVerifyAllMainThread: true, disableBlsBatchVerify: true, + persistProducedBlocks: true, persistInvalidSszObjects: true, proposerBoostEnabled: false, disableImportExecutionFcU: false, diff --git a/packages/config/package.json b/packages/config/package.json index 7814c9a18778..93f60ebeef5e 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/config", - "version": "1.12.0", + "version": "1.12.1", "description": "Chain configuration required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -65,7 +65,7 @@ ], "dependencies": { "@chainsafe/ssz": "^0.14.0", - "@lodestar/params": "^1.12.0", - "@lodestar/types": "^1.12.0" + "@lodestar/params": "^1.12.1", + "@lodestar/types": "^1.12.1" } } diff --git a/packages/db/package.json b/packages/db/package.json index 961dadf19ecf..153960931e00 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/db", - "version": "1.12.0", + "version": "1.12.1", "description": "DB modules of Lodestar", "author": "ChainSafe Systems", "homepage": "https://github.com/ChainSafe/lodestar#readme", @@ -38,13 +38,13 @@ }, "dependencies": { "@chainsafe/ssz": "^0.14.0", - "@lodestar/config": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/config": "^1.12.1", + "@lodestar/utils": "^1.12.1", "@types/levelup": "^4.3.3", "it-all": "^3.0.2", "level": "^8.0.0" }, "devDependencies": { - "@lodestar/logger": "^1.12.0" + "@lodestar/logger": "^1.12.1" } } diff --git a/packages/flare/package.json b/packages/flare/package.json index dc4157415bd8..deef9bb34d38 100644 --- a/packages/flare/package.json +++ b/packages/flare/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/flare", - "version": "1.12.0", + "version": "1.12.1", "description": "Beacon chain debugging tool", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -60,12 +60,12 @@ "dependencies": { "@chainsafe/bls": "7.1.1", "@chainsafe/bls-keygen": "^0.3.0", - "@lodestar/api": "^1.12.0", - "@lodestar/config": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/state-transition": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/api": "^1.12.1", + "@lodestar/config": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/state-transition": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", "source-map-support": "^0.5.21", "yargs": "^17.7.1" }, diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index 5789c0109dd9..c1b71d818b52 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": "./lib/index.js", "types": "./lib/index.d.ts", @@ -39,11 +39,11 @@ }, "dependencies": { "@chainsafe/ssz": "^0.14.0", - "@lodestar/config": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/state-transition": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0" + "@lodestar/config": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/state-transition": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1" }, "keywords": [ "ethereum", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index c6068fa1b4d9..d145cdb40708 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -67,12 +67,12 @@ "@chainsafe/bls": "7.1.1", "@chainsafe/persistent-merkle-tree": "^0.6.1", "@chainsafe/ssz": "^0.14.0", - "@lodestar/api": "^1.12.0", - "@lodestar/config": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/state-transition": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/api": "^1.12.1", + "@lodestar/config": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/state-transition": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", "mitt": "^3.0.0", "strict-event-emitter-types": "^2.0.0" }, diff --git a/packages/logger/package.json b/packages/logger/package.json index cc26cb7123c7..82a503652006 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -63,13 +63,13 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@lodestar/utils": "^1.12.0", + "@lodestar/utils": "^1.12.1", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.7.1", "winston-transport": "^4.5.0" }, "devDependencies": { - "@lodestar/test-utils": "^1.12.0", + "@lodestar/test-utils": "^1.12.1", "@types/triple-beam": "^1.3.2", "rimraf": "^4.4.1", "triple-beam": "^1.3.0" diff --git a/packages/params/package.json b/packages/params/package.json index e3cacfc45b60..805b5bdb0fc1 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/params", - "version": "1.12.0", + "version": "1.12.1", "description": "Chain parameters required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", diff --git a/packages/prover/package.json b/packages/prover/package.json index f4476ab32d14..cf117f46a346 100644 --- a/packages/prover/package.json +++ b/packages/prover/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -69,13 +69,13 @@ "@ethereumjs/tx": "^4.1.2", "@ethereumjs/util": "^8.0.6", "@ethereumjs/vm": "^6.4.2", - "@lodestar/api": "^1.12.0", - "@lodestar/config": "^1.12.0", - "@lodestar/light-client": "^1.12.0", - "@lodestar/logger": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/api": "^1.12.1", + "@lodestar/config": "^1.12.1", + "@lodestar/light-client": "^1.12.1", + "@lodestar/logger": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", "ethereum-cryptography": "^1.2.0", "find-up": "^6.3.0", "http-proxy": "^1.18.1", @@ -84,7 +84,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.12.0", + "@lodestar/test-utils": "^1.12.1", "@types/http-proxy": "^1.17.10", "@types/yargs": "^17.0.24", "axios": "^1.3.4", diff --git a/packages/reqresp/package.json b/packages/reqresp/package.json index 4e8923e6cf60..641bdb1a060e 100644 --- a/packages/reqresp/package.json +++ b/packages/reqresp/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -56,9 +56,9 @@ "dependencies": { "@chainsafe/fast-crc32c": "^4.1.1", "@libp2p/interface": "^0.1.2", - "@lodestar/config": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/config": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/utils": "^1.12.1", "it-all": "^3.0.2", "it-pipe": "^3.0.1", "snappy": "^7.2.2", @@ -67,8 +67,8 @@ "uint8arraylist": "^2.4.3" }, "devDependencies": { - "@lodestar/logger": "^1.12.0", - "@lodestar/types": "^1.12.0", + "@lodestar/logger": "^1.12.1", + "@lodestar/types": "^1.12.1", "libp2p": "0.46.12" }, "peerDependencies": { diff --git a/packages/spec-test-util/package.json b/packages/spec-test-util/package.json index 79324d813caa..6bd90a959a3f 100644 --- a/packages/spec-test-util/package.json +++ b/packages/spec-test-util/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/spec-test-util", - "version": "1.12.0", + "version": "1.12.1", "description": "Spec test suite generator from yaml test files", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -45,7 +45,7 @@ "blockchain" ], "dependencies": { - "@lodestar/utils": "^1.12.0", + "@lodestar/utils": "^1.12.1", "async-retry": "^1.3.3", "axios": "^1.3.4", "chai": "^4.3.7", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index f743861f54ec..4d6a0f9cfffe 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -63,10 +63,10 @@ "@chainsafe/persistent-merkle-tree": "^0.6.1", "@chainsafe/persistent-ts": "^0.19.1", "@chainsafe/ssz": "^0.14.0", - "@lodestar/config": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/config": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", "bigint-buffer": "^1.1.5", "buffer-xor": "^2.0.2" }, diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index b03a5f7c68d7..de0996d028f6 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@lodestar/test-utils", "private": true, - "version": "1.12.0", + "version": "1.12.1", "description": "Test utilities reused across other packages", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -61,7 +61,7 @@ "blockchain" ], "dependencies": { - "@lodestar/utils": "^1.12.0", + "@lodestar/utils": "^1.12.1", "axios": "^1.3.4", "chai": "^4.3.7", "mocha": "^10.2.0", diff --git a/packages/types/package.json b/packages/types/package.json index e5e6d4fd5e25..0bc556078a73 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": { ".": { @@ -68,7 +68,7 @@ "types": "lib/index.d.ts", "dependencies": { "@chainsafe/ssz": "^0.14.0", - "@lodestar/params": "^1.12.0" + "@lodestar/params": "^1.12.1" }, "keywords": [ "ethereum", diff --git a/packages/utils/package.json b/packages/utils/package.json index 457218fa4f4c..b2238b651b83 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.12.0", + "version": "1.12.1", "type": "module", "exports": "./lib/index.js", "files": [ diff --git a/packages/validator/package.json b/packages/validator/package.json index 8e659c94bd9e..788a7a797a36 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/validator", - "version": "1.12.0", + "version": "1.12.1", "description": "A Typescript implementation of the validator client", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -50,13 +50,13 @@ "dependencies": { "@chainsafe/bls": "7.1.1", "@chainsafe/ssz": "^0.14.0", - "@lodestar/api": "^1.12.0", - "@lodestar/config": "^1.12.0", - "@lodestar/db": "^1.12.0", - "@lodestar/params": "^1.12.0", - "@lodestar/state-transition": "^1.12.0", - "@lodestar/types": "^1.12.0", - "@lodestar/utils": "^1.12.0", + "@lodestar/api": "^1.12.1", + "@lodestar/config": "^1.12.1", + "@lodestar/db": "^1.12.1", + "@lodestar/params": "^1.12.1", + "@lodestar/state-transition": "^1.12.1", + "@lodestar/types": "^1.12.1", + "@lodestar/utils": "^1.12.1", "bigint-buffer": "^1.1.5", "strict-event-emitter-types": "^2.0.0" }, diff --git a/packages/validator/src/services/block.ts b/packages/validator/src/services/block.ts index c9eeadb06630..55b7c4cd74ce 100644 --- a/packages/validator/src/services/block.ts +++ b/packages/validator/src/services/block.ts @@ -70,7 +70,7 @@ export class BlockProposingService { private readonly clock: IClock, private readonly validatorStore: ValidatorStore, private readonly metrics: Metrics | null, - private readonly opts: {useProduceBlockV3: boolean} + private readonly opts: {useProduceBlockV3: boolean; broadcastValidation: routes.beacon.BroadcastValidation} ) { this.dutiesService = new BlockDutiesService( config, @@ -144,7 +144,7 @@ export class BlockProposingService { this.logger.debug("Produced block", {...debugLogCtx, ...blockContents.debugLogCtx}); this.metrics?.blocksProduced.inc(); - const signedBlockPromise = this.validatorStore.signBlock(pubkey, blockContents.block, slot); + 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)) @@ -158,7 +158,9 @@ export class BlockProposingService { signedBlobs = undefined; } - await this.publishBlockWrapper(signedBlock, signedBlobs).catch((e: Error) => { + await this.publishBlockWrapper(signedBlock, signedBlobs, { + broadcastValidation: this.opts.broadcastValidation, + }).catch((e: Error) => { this.metrics?.blockProposingErrors.inc({error: "publish"}); throw extendError(e, "Failed to publish block"); }); @@ -172,22 +174,29 @@ export class BlockProposingService { private publishBlockWrapper = async ( signedBlock: allForks.FullOrBlindedSignedBeaconBlock, - signedBlobSidecars?: allForks.FullOrBlindedSignedBlobSidecar[] + signedBlobSidecars?: allForks.FullOrBlindedSignedBlobSidecar[], + opts: {broadcastValidation?: routes.beacon.BroadcastValidation} = {} ): Promise => { if (signedBlobSidecars === undefined) { ApiError.assert( isBlindedBeaconBlock(signedBlock.message) - ? await this.api.beacon.publishBlindedBlock(signedBlock as allForks.SignedBlindedBeaconBlock) - : await this.api.beacon.publishBlockV2(signedBlock as allForks.SignedBeaconBlock) + ? await this.api.beacon.publishBlindedBlockV2(signedBlock as allForks.SignedBlindedBeaconBlock, opts) + : await this.api.beacon.publishBlockV2(signedBlock as allForks.SignedBeaconBlock, opts) ); } else { ApiError.assert( isBlindedBeaconBlock(signedBlock.message) - ? await this.api.beacon.publishBlindedBlock({ - signedBlindedBlock: signedBlock, - signedBlindedBlobSidecars: signedBlobSidecars, - } as allForks.SignedBlindedBlockContents) - : await this.api.beacon.publishBlockV2({signedBlock, signedBlobSidecars} as allForks.SignedBlockContents) + ? await this.api.beacon.publishBlindedBlockV2( + { + signedBlindedBlock: signedBlock, + signedBlindedBlobSidecars: signedBlobSidecars, + } as allForks.SignedBlindedBlockContents, + opts + ) + : await this.api.beacon.publishBlockV2( + {signedBlock, signedBlobSidecars} as allForks.SignedBlockContents, + opts + ) ); } }; diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index 42979f7c71e8..e2736a09754a 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -41,6 +41,7 @@ import {PubkeyHex} from "../types.js"; import {externalSignerPostSignature, SignableMessageType, SignableMessage} from "../util/externalSignerClient.js"; import {Metrics} from "../metrics.js"; import {isValidatePubkeyHex} from "../util/format.js"; +import {LoggerVc} from "../util/logger.js"; import {IndicesService} from "./indices.js"; import {DoppelgangerService} from "./doppelgangerService.js"; @@ -126,6 +127,8 @@ export const defaultOptions = { builderAliasSelection: routes.validator.BuilderSelection.MaxProfit, // turn it off by default, turn it back on once other clients support v3 api useProduceBlockV3: false, + // spec asks for gossip validation by default + broadcastValidation: routes.beacon.BroadcastValidation.gossip, }; /** @@ -351,7 +354,8 @@ export class ValidatorStore { async signBlock( pubkey: BLSPubkey, blindedOrFull: allForks.FullOrBlindedBeaconBlock, - currentSlot: Slot + currentSlot: Slot, + logger?: LoggerVc ): Promise { // Make sure the block slot is not higher than the current slot to avoid potential attacks. if (blindedOrFull.slot > currentSlot) { @@ -367,8 +371,14 @@ export class ValidatorStore { // Don't use `computeSigningRoot()` here to compute the objectRoot in typesafe function blindedOrFullBlockHashTreeRoot() const signingRoot = ssz.phase0.SigningData.hashTreeRoot({objectRoot: blockRoot, domain}); + logger?.debug("Signing the block proposal", { + slot: signingSlot, + blockRoot: toHexString(blockRoot), + signingRoot: toHexString(signingRoot), + }); + try { - await this.slashingProtection.checkAndInsertBlockProposal(pubkey, {slot: blindedOrFull.slot, signingRoot}); + await this.slashingProtection.checkAndInsertBlockProposal(pubkey, {slot: signingSlot, signingRoot}); } catch (e) { this.metrics?.slashingProtectionBlockError.inc(); throw e; diff --git a/packages/validator/src/validator.ts b/packages/validator/src/validator.ts index 4b81aee44de5..307aefc2abbf 100644 --- a/packages/validator/src/validator.ts +++ b/packages/validator/src/validator.ts @@ -57,6 +57,7 @@ export type ValidatorOptions = { valProposerConfig?: ValidatorProposerConfig; distributed?: boolean; useProduceBlockV3?: boolean; + broadcastValidation?: routes.beacon.BroadcastValidation; }; // TODO: Extend the timeout, and let it be customizable @@ -208,6 +209,7 @@ export class Validator { const blockProposingService = new BlockProposingService(config, loggerVc, api, clock, validatorStore, metrics, { useProduceBlockV3: opts.useProduceBlockV3 ?? defaultOptions.useProduceBlockV3, + broadcastValidation: opts.broadcastValidation ?? defaultOptions.broadcastValidation, }); const attestationService = new AttestationService( @@ -284,13 +286,19 @@ export class Validator { await assertEqualGenesis(opts, genesis); logger.info("Verified connected beacon node and validator have the same genesisValidatorRoot"); - const {useProduceBlockV3, valProposerConfig} = opts; + const { + useProduceBlockV3 = defaultOptions.useProduceBlockV3, + broadcastValidation = defaultOptions.broadcastValidation, + valProposerConfig, + } = opts; const defaultBuilderSelection = valProposerConfig?.defaultConfig.builder?.selection ?? defaultOptions.builderSelection; const strictFeeRecipientCheck = valProposerConfig?.defaultConfig.strictFeeRecipientCheck ?? false; const suggestedFeeRecipient = valProposerConfig?.defaultConfig.feeRecipient ?? defaultOptions.suggestedFeeRecipient; + logger.info("Initializing validator", { useProduceBlockV3, + broadcastValidation, defaultBuilderSelection, suggestedFeeRecipient, strictFeeRecipientCheck, diff --git a/packages/validator/test/unit/services/block.test.ts b/packages/validator/test/unit/services/block.test.ts index 0a533b140a9c..0c78fdc82eec 100644 --- a/packages/validator/test/unit/services/block.test.ts +++ b/packages/validator/test/unit/services/block.test.ts @@ -6,7 +6,7 @@ import {createChainForkConfig} from "@lodestar/config"; import {config as mainnetConfig} from "@lodestar/config/default"; import {sleep} from "@lodestar/utils"; import {ssz} from "@lodestar/types"; -import {HttpStatusCode} from "@lodestar/api"; +import {HttpStatusCode, routes} from "@lodestar/api"; import {ForkName} from "@lodestar/params"; import {BlockProposingService} from "../../../src/services/block.js"; import {ValidatorStore} from "../../../src/services/validatorStore.js"; @@ -52,6 +52,7 @@ describe("BlockDutiesService", function () { // use produceBlockV3 const blockService = new BlockProposingService(config, loggerVc, api, clock, validatorStore, null, { useProduceBlockV3: true, + broadcastValidation: routes.beacon.BroadcastValidation.consensus, }); const signedBlock = ssz.phase0.SignedBeaconBlock.defaultValue(); @@ -78,6 +79,9 @@ describe("BlockDutiesService", function () { // Must have submitted the block received on signBlock() expect(api.beacon.publishBlockV2.callCount).to.equal(1, "publishBlock() must be called once"); - expect(api.beacon.publishBlockV2.getCall(0).args).to.deep.equal([signedBlock], "wrong publishBlock() args"); + expect(api.beacon.publishBlockV2.getCall(0).args).to.deep.equal( + [signedBlock, {broadcastValidation: routes.beacon.BroadcastValidation.consensus}], + "wrong publishBlock() args" + ); }); }); diff --git a/yarn.lock b/yarn.lock index 8c78c64bd89c..0e6a8f3757b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -565,13 +565,13 @@ resolved "https://registry.yarnpkg.com/@chainsafe/is-ip/-/is-ip-2.0.2.tgz#7311e7403f11d8c5cfa48111f56fcecaac37c9f6" integrity sha512-ndGqEMG1W5WkGagaqOZHpPU172AGdxr+LD15sv3WIUvT5oCFUrG1Y0CW/v2Egwj4JXEvSibaIIIqImsm98y1nA== -"@chainsafe/libp2p-gossipsub@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@chainsafe/libp2p-gossipsub/-/libp2p-gossipsub-10.1.0.tgz#29c2e3da2bbf1dc68ae171c5ac777bce9ca88c2c" - integrity sha512-mOVYJAvxYRkh2HeggNFW/7ukEccQDVEI9LPhvlnJk7gnJhyJJ6mhZxUAaytfp3v3qTkmeBRnEL0eJOQBm+MoOA== +"@chainsafe/libp2p-gossipsub@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@chainsafe/libp2p-gossipsub/-/libp2p-gossipsub-10.1.1.tgz#906aa2a67efb5fea0bacc6721ef4e7ee4e353d7e" + integrity sha512-nou65zlGaUIPwlUq7ceEVpszJX4tBWRRanppYaKsJk7rbDeIKRJQla2duATGOI3fwj1+pGSlDQuF2zG7P0VJQw== dependencies: "@libp2p/crypto" "^2.0.0" - "@libp2p/interface" "^0.1.0" + "@libp2p/interface" "^0.1.4" "@libp2p/interface-internal" "^0.1.0" "@libp2p/logger" "^3.0.0" "@libp2p/peer-id" "^3.0.0" @@ -1686,6 +1686,20 @@ p-defer "^4.0.0" uint8arraylist "^2.4.3" +"@libp2p/interface@^0.1.4": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@libp2p/interface/-/interface-0.1.6.tgz#1328cf6086f02c499183489ccb143fe9c159e871" + integrity sha512-Lzc5cS/hXuoXhuAbVIxJIHLCYmfPcbU0vVgrpMoiP1Qb2Q3ETU4A46GB8s8mWXgSU6tr9RcqerUqzFYD6+OAag== + dependencies: + "@multiformats/multiaddr" "^12.1.5" + abortable-iterator "^5.0.1" + it-pushable "^3.2.0" + it-stream-types "^2.0.1" + multiformats "^12.0.1" + p-defer "^4.0.0" + race-signal "^1.0.0" + uint8arraylist "^2.4.3" + "@libp2p/keychain@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@libp2p/keychain/-/keychain-3.0.4.tgz#94d04a592ea18d83ebed6d6d8457e9aa8cc72e91" @@ -11704,6 +11718,11 @@ quick-lru@^5.1.1: resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +race-signal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/race-signal/-/race-signal-1.0.2.tgz#e42379fba0cec4ee8dab7c9bbbd4aa6e0d14c25f" + integrity sha512-o3xNv0iTcIDQCXFlF6fPAMEBRjFxssgGoRqLbg06m+AdzEXXLUmoNOoUHTVz2NoBI8hHwKFKoC6IqyNtWr2bww== + rambda@^7.4.0: version "7.5.0" resolved "https://registry.yarnpkg.com/rambda/-/rambda-7.5.0.tgz#1865044c59bc0b16f63026c6e5a97e4b1bbe98fe"