Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
import {sleep} from "@lodestar/utils";
import {fromHexString, toHexString} from "@chainsafe/ssz";
import {BlockError, BlockErrorCode} from "../../../../chain/errors/index.js";
import {getBlockInput} from "../../../../chain/blocks/types.js";
import {OpSource} from "../../../../metrics/validatorMonitor.js";
import {NetworkEvent} from "../../../../network/index.js";
import {ApiModules, IS_OPTIMISTIC_TEMP} from "../../types.js";
Expand Down Expand Up @@ -186,14 +187,17 @@ export function getBeaconBlockApi({

metrics?.registerBeaconBlock(OpSource.api, seenTimestampSec, signedBlock.message);

// TODO EIP-4844: Will throw an error for blocks post EIP-4844
const blockInput = getBlockInput.preEIP4844(config, signedBlock);

await Promise.all([
// Send the block, regardless of whether or not it is valid. The API
// specification is very clear that this is the desired behaviour.
network.gossip.publishBeaconBlock(signedBlock),

chain.processBlock(signedBlock).catch((e) => {
chain.processBlock(blockInput).catch((e) => {
if (e instanceof BlockError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) {
network.events.emit(NetworkEvent.unknownBlockParent, signedBlock, network.peerId.toString());
network.events.emit(NetworkEvent.unknownBlockParent, blockInput, network.peerId.toString());
}
throw e;
}),
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/src/api/impl/lodestar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ export function getLodestarApi({

async getBlockProcessorQueueItems() {
return (chain as BeaconChain)["blockProcessor"].jobQueue.getItems().map((item) => {
const [blocks, opts] = item.args;
const [blockInputs, opts] = item.args;
return {
blockSlots: blocks.map((block) => block.message.slot),
blockSlots: blockInputs.map((blockInput) => blockInput.block.message.slot),
jobOpts: opts,
addedTimeMs: item.addedTimeMs,
};
Expand Down
4 changes: 3 additions & 1 deletion packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export async function importBlock(
fullyVerifiedBlock: FullyVerifiedBlock,
opts: ImportBlockOpts
): Promise<void> {
const {block, postState, parentBlockSlot, executionStatus} = fullyVerifiedBlock;
const {blockInput, postState, parentBlockSlot, executionStatus} = fullyVerifiedBlock;
const {block} = blockInput;
const pendingEvents = new PendingEvents(this.emitter);

// - Observe attestations
Expand Down Expand Up @@ -312,6 +313,7 @@ export async function importBlock(
// MUST happen before any other block is processed
// This adds the state necessary to process the next block
this.stateCache.add(postState);

await this.db.block.add(block);

// - head_tracker.register_block(block_root, parent_root, slot)
Expand Down
18 changes: 9 additions & 9 deletions packages/beacon-node/src/chain/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,29 @@ import type {BeaconChain} from "../chain.js";
import {verifyBlocksInEpoch} from "./verifyBlock.js";
import {importBlock} from "./importBlock.js";
import {assertLinearChainSegment} from "./utils/chainSegment.js";
import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
import {BlockInput, FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
import {verifyBlocksSanityChecks} from "./verifyBlocksSanityChecks.js";
export {ImportBlockOpts} from "./types.js";

const QUEUE_MAX_LENGHT = 256;
const QUEUE_MAX_LENGTH = 256;

/**
* BlockProcessor processes block jobs in a queued fashion, one after the other.
*/
export class BlockProcessor {
readonly jobQueue: JobItemQueue<[allForks.SignedBeaconBlock[], ImportBlockOpts], void>;
readonly jobQueue: JobItemQueue<[BlockInput[], ImportBlockOpts], void>;

constructor(chain: BeaconChain, metrics: IMetrics | null, opts: BlockProcessOpts, signal: AbortSignal) {
this.jobQueue = new JobItemQueue<[allForks.SignedBeaconBlock[], ImportBlockOpts], void>(
this.jobQueue = new JobItemQueue<[BlockInput[], ImportBlockOpts], void>(
(job, importOpts) => {
return processBlocks.call(chain, job, {...opts, ...importOpts});
},
{maxLength: QUEUE_MAX_LENGHT, signal},
{maxLength: QUEUE_MAX_LENGTH, signal},
metrics?.blockProcessorQueue ?? undefined
);
}

async processBlocksJob(job: allForks.SignedBeaconBlock[], opts: ImportBlockOpts = {}): Promise<void> {
async processBlocksJob(job: BlockInput[], opts: ImportBlockOpts = {}): Promise<void> {
await this.jobQueue.push(job, opts);
}
}
Expand All @@ -48,7 +48,7 @@ export class BlockProcessor {
*/
export async function processBlocks(
this: BeaconChain,
blocks: allForks.SignedBeaconBlock[],
blocks: BlockInput[],
opts: BlockProcessOpts & ImportBlockOpts
): Promise<void> {
if (blocks.length === 0) {
Expand Down Expand Up @@ -87,7 +87,7 @@ export async function processBlocks(
const {executionStatuses} = segmentExecStatus;
const fullyVerifiedBlocks = relevantBlocks.map(
(block, i): FullyVerifiedBlock => ({
block,
blockInput: block,
postState: postStates[i],
parentBlockSlot: parentSlots[i],
executionStatus: executionStatuses[i],
Expand All @@ -104,7 +104,7 @@ export async function processBlocks(
}
} catch (e) {
// above functions should only throw BlockError
const err = getBlockError(e, blocks[0]);
const err = getBlockError(e, blocks[0].block);

// TODO: De-duplicate with logic above
// ChainEvent.errorBlock
Expand Down
62 changes: 59 additions & 3 deletions packages/beacon-node/src/chain/blocks/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,60 @@
import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition";
import {MaybeValidExecutionStatus} from "@lodestar/fork-choice";
import {allForks, Slot} from "@lodestar/types";
import {allForks, eip4844, Slot} from "@lodestar/types";
import {ForkSeq} from "@lodestar/params";
import {IChainForkConfig} from "@lodestar/config";

export enum BlockInputType {
preEIP4844 = "preEIP4844",
postEIP4844 = "postEIP4844",
postEIP4844OldBlobs = "postEIP4844OldBlobs",
}

export type BlockInput =
| {type: BlockInputType.preEIP4844; block: allForks.SignedBeaconBlock}
| {type: BlockInputType.postEIP4844; block: allForks.SignedBeaconBlock; blobs: eip4844.BlobsSidecar}
| {type: BlockInputType.postEIP4844OldBlobs; block: allForks.SignedBeaconBlock};

export function blockRequiresBlobs(config: IChainForkConfig, blockSlot: Slot, clockSlot: Slot): boolean {
return (
config.getForkSeq(blockSlot) >= ForkSeq.eip4844 &&
// Only request blobs if they are recent enough
computeEpochAtSlot(blockSlot) >= computeEpochAtSlot(clockSlot) - config.MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS
);
}

export const getBlockInput = {
preEIP4844(config: IChainForkConfig, block: allForks.SignedBeaconBlock): BlockInput {
if (config.getForkSeq(block.message.slot) >= ForkSeq.eip4844) {
throw Error(`Post EIP4844 block slot ${block.message.slot}`);
}
return {
type: BlockInputType.preEIP4844,
block,
};
},

postEIP4844(config: IChainForkConfig, block: allForks.SignedBeaconBlock, blobs: eip4844.BlobsSidecar): BlockInput {
if (config.getForkSeq(block.message.slot) < ForkSeq.eip4844) {
throw Error(`Pre EIP4844 block slot ${block.message.slot}`);
}
return {
type: BlockInputType.postEIP4844,
block,
blobs,
};
},

postEIP4844OldBlobs(config: IChainForkConfig, block: allForks.SignedBeaconBlock): BlockInput {
if (config.getForkSeq(block.message.slot) < ForkSeq.eip4844) {
throw Error(`Pre EIP4844 block slot ${block.message.slot}`);
}
return {
type: BlockInputType.postEIP4844OldBlobs,
block,
};
},
};

export type ImportBlockOpts = {
/**
Expand Down Expand Up @@ -35,6 +89,8 @@ export type ImportBlockOpts = {
* Metadata: `true` if all the signatures including the proposer signature have been verified
*/
validSignatures?: boolean;
/** Set to true if already run `validateBlobsSidecar()` sucessfully on the blobs */
validBlobsSidecar?: boolean;
/** Seen timestamp seconds */
seenTimestampSec?: number;
};
Expand All @@ -43,7 +99,7 @@ export type ImportBlockOpts = {
* A wrapper around a `SignedBeaconBlock` that indicates that this block is fully verified and ready to import
*/
export type FullyVerifiedBlock = {
block: allForks.SignedBeaconBlock;
blockInput: BlockInput;
postState: CachedBeaconStateAllForks;
parentBlockSlot: Slot;
proposerBalanceDelta: number;
Expand Down
38 changes: 19 additions & 19 deletions packages/beacon-node/src/chain/blocks/utils/chainSegment.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import {IChainForkConfig} from "@lodestar/config";
import {allForks, ssz} from "@lodestar/types";
import {ssz} from "@lodestar/types";
import {BlockError, BlockErrorCode} from "../../errors/index.js";
import {BlockInput} from "../types.js";

/**
* Assert this chain segment of blocks is linear with slot numbers and hashes
*/
export function assertLinearChainSegment(config: IChainForkConfig, blocks: allForks.SignedBeaconBlock[]): void {
for (const [i, block] of blocks.entries()) {
const child = blocks[i + 1];
if (child !== undefined) {
// If this block has a child in this chain segment, ensure that its parent root matches
// the root of this block.
if (
!ssz.Root.equals(
config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message),
child.message.parentRoot
)
) {
throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS});
}
// Ensure that the slots are strictly increasing throughout the chain segment.
if (child.message.slot <= block.message.slot) {
throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_SLOTS});
}
export function assertLinearChainSegment(config: IChainForkConfig, blocks: BlockInput[]): void {
for (let i = 0; i < blocks.length - 1; i++) {
const block = blocks[i].block;
const child = blocks[i + 1].block;
// If this block has a child in this chain segment, ensure that its parent root matches
// the root of this block.
if (
!ssz.Root.equals(
config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message),
child.message.parentRoot
)
) {
throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS});
}
// Ensure that the slots are strictly increasing throughout the chain segment.
if (child.message.slot <= block.message.slot) {
throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_SLOTS});
}
}
}
9 changes: 5 additions & 4 deletions packages/beacon-node/src/chain/blocks/verifyBlock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition";
import {allForks, bellatrix} from "@lodestar/types";
import {bellatrix} from "@lodestar/types";
import {ForkName} from "@lodestar/params";
import {toHexString} from "@chainsafe/ssz";
import {ProtoBlock} from "@lodestar/fork-choice";
Expand All @@ -9,7 +9,7 @@ import {BlockError, BlockErrorCode} from "../errors/index.js";
import {BlockProcessOpts} from "../options.js";
import {RegenCaller} from "../regen/index.js";
import type {BeaconChain} from "../chain.js";
import {ImportBlockOpts} from "./types.js";
import {BlockInput, ImportBlockOpts} from "./types.js";
import {POS_PANDA_MERGE_TRANSITION_BANNER} from "./utils/pandaMergeTransitionBanner.js";
import {CAPELLA_OWL_BANNER} from "./utils/ownBanner.js";
import {verifyBlocksStateTransitionOnly} from "./verifyBlocksStateTransitionOnly.js";
Expand All @@ -30,13 +30,14 @@ import {verifyBlocksExecutionPayload, SegmentExecStatus} from "./verifyBlocksExe
export async function verifyBlocksInEpoch(
this: BeaconChain,
parentBlock: ProtoBlock,
blocks: allForks.SignedBeaconBlock[],
blocksImport: BlockInput[],
opts: BlockProcessOpts & ImportBlockOpts
): Promise<{
postStates: CachedBeaconStateAllForks[];
proposerBalanceDeltas: number[];
segmentExecStatus: SegmentExecStatus;
}> {
const blocks = blocksImport.map(({block}) => block);
if (blocks.length === 0) {
throw Error("Empty partiallyVerifiedBlocks");
}
Expand Down Expand Up @@ -69,7 +70,7 @@ export async function verifyBlocksInEpoch(
const [{postStates, proposerBalanceDeltas}, , segmentExecStatus] = await Promise.all([
// Run state transition only
// TODO: Ensure it yields to allow flushing to workers and engine API
verifyBlocksStateTransitionOnly(preState0, blocks, this.metrics, abortController.signal, opts),
verifyBlocksStateTransitionOnly(preState0, blocksImport, this.metrics, abortController.signal, opts),

// All signatures at once
verifyBlocksSignatures(this.bls, preState0, blocks, opts),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
import {IChainForkConfig} from "@lodestar/config";
import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {allForks, Slot} from "@lodestar/types";
import {Slot} from "@lodestar/types";
import {toHexString} from "@lodestar/utils";
import {IBeaconClock} from "../clock/interface.js";
import {BlockError, BlockErrorCode} from "../errors/index.js";
import {ImportBlockOpts} from "./types.js";
import {BlockInput, ImportBlockOpts} from "./types.js";

/**
* Verifies some early cheap sanity checks on the block before running the full state transition.
Expand All @@ -21,18 +21,19 @@ import {ImportBlockOpts} from "./types.js";
*/
export function verifyBlocksSanityChecks(
chain: {forkChoice: IForkChoice; clock: IBeaconClock; config: IChainForkConfig},
blocks: allForks.SignedBeaconBlock[],
blocks: BlockInput[],
opts: ImportBlockOpts
): {relevantBlocks: allForks.SignedBeaconBlock[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} {
): {relevantBlocks: BlockInput[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} {
if (blocks.length === 0) {
throw Error("Empty partiallyVerifiedBlocks");
}

const relevantBlocks: allForks.SignedBeaconBlock[] = [];
const relevantBlocks: BlockInput[] = [];
const parentSlots: Slot[] = [];
let parentBlock: ProtoBlock | null = null;

for (const block of blocks) {
for (const blockInput of blocks) {
const {block} = blockInput;
const blockSlot = block.message.slot;

// Not genesis block
Expand All @@ -59,7 +60,7 @@ export function verifyBlocksSanityChecks(
let parentBlockSlot: Slot;

if (relevantBlocks.length > 0) {
parentBlockSlot = relevantBlocks[relevantBlocks.length - 1].message.slot;
parentBlockSlot = relevantBlocks[relevantBlocks.length - 1].block.message.slot;
} else {
// When importing a block segment, only the first NON-IGNORED block must be known to the fork-choice.
const parentRoot = toHexString(block.message.parentRoot);
Expand Down Expand Up @@ -92,7 +93,7 @@ export function verifyBlocksSanityChecks(
}

// Block is relevant
relevantBlocks.push(block);
relevantBlocks.push(blockInput);
parentSlots.push(parentBlockSlot);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {CachedBeaconStateAllForks, stateTransition} from "@lodestar/state-transition";
import {allForks} from "@lodestar/types";
import {ErrorAborted, sleep} from "@lodestar/utils";
import {IMetrics} from "../../metrics/index.js";
import {BlockError, BlockErrorCode} from "../errors/index.js";
import {BlockProcessOpts} from "../options.js";
import {byteArrayEquals} from "../../util/bytes.js";
import {ImportBlockOpts} from "./types.js";
import {BlockInput, ImportBlockOpts} from "./types.js";

/**
* Verifies 1 or more blocks are fully valid running the full state transition; from a linear sequence of blocks.
Expand All @@ -17,7 +16,7 @@ import {ImportBlockOpts} from "./types.js";
*/
export async function verifyBlocksStateTransitionOnly(
preState0: CachedBeaconStateAllForks,
blocks: allForks.SignedBeaconBlock[],
blocks: BlockInput[],
metrics: IMetrics | null,
signal: AbortSignal,
opts: BlockProcessOpts & ImportBlockOpts
Expand All @@ -27,7 +26,7 @@ export async function verifyBlocksStateTransitionOnly(

for (let i = 0; i < blocks.length; i++) {
const {validProposerSignature, validSignatures} = opts;
const block = blocks[i];
const {block} = blocks[i];
const preState = i === 0 ? preState0 : postStates[i - 1];

// STFN - per_slot_processing() + per_block_processing()
Expand Down
5 changes: 3 additions & 2 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {CheckpointBalancesCache} from "./balancesCache.js";
import {AssembledBlockType, BlockType} from "./produceBlock/index.js";
import {BlockAttributes, produceBlockBody} from "./produceBlock/produceBlockBody.js";
import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js";
import {BlockInput} from "./blocks/types.js";

export class BeaconChain implements IBeaconChain {
readonly genesisTime: UintNum64;
Expand Down Expand Up @@ -379,11 +380,11 @@ export class BeaconChain implements IBeaconChain {
return block;
}

async processBlock(block: allForks.SignedBeaconBlock, opts?: ImportBlockOpts): Promise<void> {
async processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise<void> {
return await this.blockProcessor.processBlocksJob([block], opts);
}

async processChainSegment(blocks: allForks.SignedBeaconBlock[], opts?: ImportBlockOpts): Promise<void> {
async processChainSegment(blocks: BlockInput[], opts?: ImportBlockOpts): Promise<void> {
return await this.blockProcessor.processBlocksJob(blocks, opts);
}

Expand Down
Loading