UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

77 lines 4.21 kB
import { ExecutionPayloadStatus, StateHashTreeRootSource, stateTransition, } from "@lodestar/state-transition"; import { ErrorAborted } from "@lodestar/utils"; import { byteArrayEquals } from "../../util/bytes.js"; import { nextEventLoop } from "../../util/eventLoop.js"; import { BlockError, BlockErrorCode } from "../errors/index.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, blocks, dataAvailabilityStatuses, logger, metrics, validatorMonitor, signal, opts) { const postStates = []; const proposerBalanceDeltas = []; 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]; 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 = stateTransition(preState, 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, }, { 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.balances.get(proposerIndex) - preState.balances.get(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].block.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 }; } //# sourceMappingURL=verifyBlocksStateTransitionOnly.js.map