@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
77 lines • 4.21 kB
JavaScript
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