Skip to content

Commit

Permalink
Merge 6750c0d into d86e26d
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech authored Dec 1, 2023
2 parents d86e26d + 6750c0d commit ed8f6d0
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 39 deletions.
1 change: 0 additions & 1 deletion packages/api/src/beacon/routes/beacon/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export type BlockHeaderResponse = {
};

export enum BroadcastValidation {
none = "none",
gossip = "gossip",
consensus = "consensus",
consensusAndEquivocation = "consensus_and_equivocation",
Expand Down
4 changes: 2 additions & 2 deletions packages/api/test/unit/beacon/testData/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ export const testData: GenericServerTestCases<Api> = {
res: undefined,
},
publishBlockV2: {
args: [ssz.phase0.SignedBeaconBlock.defaultValue(), {broadcastValidation: BroadcastValidation.none}],
args: [ssz.phase0.SignedBeaconBlock.defaultValue(), {broadcastValidation: BroadcastValidation.consensus}],
res: undefined,
},
publishBlindedBlock: {
args: [getDefaultBlindedBlock(64)],
res: undefined,
},
publishBlindedBlockV2: {
args: [getDefaultBlindedBlock(64), {broadcastValidation: BroadcastValidation.none}],
args: [getDefaultBlindedBlock(64), {broadcastValidation: BroadcastValidation.consensus}],
res: undefined,
},
getBlobSidecars: {
Expand Down
62 changes: 53 additions & 9 deletions packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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};
Expand Down Expand Up @@ -64,29 +68,69 @@ 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));
const blockLocallyProduced =
chain.producedBlockRoot.has(blockRoot) || chain.producedBlindedBlockRoot.has(blockRoot);
const valLogMeta = {broadcastValidation, blockRoot, 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);
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,
});
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);
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 {
Expand All @@ -102,7 +146,7 @@ export function getBeaconBlockApi({
if (chain.opts.broadcastValidationStrictness === "error") {
throw Error(message);
} else {
chain.logger.warn(message);
chain.logger.warn(message, valLogMeta);
}
}
}
Expand Down
59 changes: 36 additions & 23 deletions packages/beacon-node/src/chain/blocks/verifyBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -90,7 +90,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(
Expand All @@ -104,37 +111,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:
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 12 additions & 1 deletion packages/beacon-node/src/chain/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export type BlockProcessOpts = {
/**
* Override SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY
*/
safeSlotsToImportOptimistically: number;
safeSlotsToImportOptimistically?: number;
/**
* Assert progressive balances the same to EpochTransitionCache
*/
Expand All @@ -55,6 +55,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 = {
Expand Down

0 comments on commit ed8f6d0

Please sign in to comment.