@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
113 lines (99 loc) • 4.52 kB
text/typescript
import {
DataAvailabilityStatus,
ExecutionPayloadStatus,
IBeaconStateView,
StateHashTreeRootSource,
} from "@lodestar/state-transition";
import {ErrorAborted, Logger, byteArrayEquals} from "@lodestar/utils";
import {Metrics} from "../../metrics/index.js";
import {nextEventLoop} from "../../util/eventLoop.js";
import {BlockError, BlockErrorCode} from "../errors/index.js";
import {BlockProcessOpts} from "../options.js";
import {ValidatorMonitor} from "../validatorMonitor.js";
import {IBlockInput} from "./blockInput/index.js";
import {ImportBlockOpts} from "./types.js";
/**
* Verifies 1 or more blocks are fully valid running the full state transition; from a linear sequence of blocks.
*
* - Advance state to block's slot - per_slot_processing()
* - For each block:
* - STFN - per_block_processing()
* - Check state root matches
*/
export async function verifyBlocksStateTransitionOnly(
preState0: IBeaconStateView,
blocks: IBlockInput[],
dataAvailabilityStatuses: DataAvailabilityStatus[],
logger: Logger,
metrics: Metrics | null,
validatorMonitor: ValidatorMonitor | null,
signal: AbortSignal,
opts: BlockProcessOpts & ImportBlockOpts
): Promise<{postStates: IBeaconStateView[]; proposerBalanceDeltas: number[]; verifyStateTime: number}> {
const postStates: IBeaconStateView[] = [];
const proposerBalanceDeltas: number[] = [];
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
for (let i = 0; i < blocks.length; i++) {
const {validProposerSignature, validSignatures} = opts;
const block = blocks[i].getBlock();
const preState = i === 0 ? preState0 : postStates[i - 1];
const dataAvailabilityStatus = dataAvailabilityStatuses[i];
// STFN - per_slot_processing() + per_block_processing()
// NOTE: `regen.getPreState()` should have dialed forward the state already caching checkpoint states
const useBlsBatchVerify = !opts?.disableBlsBatchVerify;
const postState = preState.stateTransition(
block,
{
// NOTE: Assume valid for now while sending payload to execution engine in parallel
// Latter verifyBlocksInEpoch() will make sure that payload is indeed valid
executionPayloadStatus: ExecutionPayloadStatus.valid,
dataAvailabilityStatus,
// false because it's verified below with better error typing
verifyStateRoot: false,
// if block is trusted don't verify proposer or op signature
verifyProposer: !useBlsBatchVerify && !validSignatures && !validProposerSignature,
verifySignatures: !useBlsBatchVerify && !validSignatures,
dontTransferCache: false,
},
{metrics, validatorMonitor}
);
const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
source: StateHashTreeRootSource.blockTransition,
});
const stateRoot = postState.hashTreeRoot();
hashTreeRootTimer?.();
// Check state root matches
if (!byteArrayEquals(block.message.stateRoot, stateRoot)) {
throw new BlockError(block, {
code: BlockErrorCode.INVALID_STATE_ROOT,
root: postState.hashTreeRoot(),
expectedRoot: block.message.stateRoot,
preState,
postState,
});
}
postStates[i] = postState;
// For metric block profitability
const proposerIndex = block.message.proposerIndex;
proposerBalanceDeltas[i] = postState.getBalance(proposerIndex) - preState.getBalance(proposerIndex);
// If blocks are invalid in execution the main promise could resolve before this loop ends.
// In that case stop processing blocks and return early.
if (signal.aborted) {
throw new ErrorAborted("verifyBlockStateTransitionOnly");
}
// this avoids keeping our node busy processing blocks
if (i < blocks.length - 1) {
await nextEventLoop();
}
}
const verifyStateTime = Date.now();
if (blocks.length === 1 && opts.seenTimestampSec !== undefined) {
const slot = blocks[0].getBlock().message.slot;
const recvToValidation = verifyStateTime / 1000 - opts.seenTimestampSec;
const validationTime = recvToValidation - recvToValLatency;
metrics?.gossipBlock.stateTransition.recvToValidation.observe(recvToValidation);
metrics?.gossipBlock.stateTransition.validationTime.observe(validationTime);
logger.debug("Verified block state transition", {slot, recvToValLatency, recvToValidation, validationTime});
}
return {postStates, proposerBalanceDeltas, verifyStateTime};
}