UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

81 lines 4.08 kB
import { getBlockSignatureSets } from "@lodestar/state-transition"; import { nextEventLoop } from "../../util/eventLoop.js"; import { BlockError, BlockErrorCode } from "../errors/blockError.js"; /** * Verifies 1 or more block's signatures from a group of blocks in the same epoch. * getBlockSignatureSets() guarantees to return the correct signingRoots as long as all blocks belong in the same * epoch as `preState0`. Otherwise the shufflings won't be correct. * * Since all data is known in advance all signatures are verified at once in parallel. */ export async function verifyBlocksSignatures(bls, logger, metrics, preState0, blocks, opts) { const isValidPromises = []; const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000); // Verifies signatures after running state transition, so all SyncCommittee signed roots are known at this point. // We must ensure block.slot <= state.slot before running getAllBlockSignatureSets(). // NOTE: If in the future multiple blocks signatures are verified at once, all blocks must be in the same epoch // so the attester and proposer shufflings are correct. for (const [i, block] of blocks.entries()) { // Use [i] to make clear that the index has to be correct to blame the right block below on BlockError() isValidPromises[i] = opts.validSignatures ? // Skip all signature verification Promise.resolve(true) : // // Verify signatures per block to track which block is invalid bls.verifySignatureSets(getBlockSignatureSets(preState0, block, { skipProposerSignature: opts.validProposerSignature, })); // getBlockSignatureSets() takes 45ms in benchmarks for 2022Q2 mainnet blocks (100 sigs). When syncing a 32 blocks // segments it will block the event loop for 1400 ms, which is too much. This call will allow the event loop to // yield, which will cause one block's state transition to run. However, the tradeoff is okay and doesn't slow sync if ((i + 1) % 8 === 0) { await nextEventLoop(); } } // `rejectFirstInvalidResolveAllValid()` returns on isValid result with its index const res = await rejectFirstInvalidResolveAllValid(isValidPromises); if (!res.allValid) { throw new BlockError(blocks[res.index], { code: BlockErrorCode.INVALID_SIGNATURE, state: preState0 }); } const verifySignaturesTime = Date.now(); if (blocks.length === 1 && opts.seenTimestampSec !== undefined) { const recvToValidation = verifySignaturesTime / 1000 - opts.seenTimestampSec; const validationTime = recvToValidation - recvToValLatency; metrics?.gossipBlock.signatureVerification.recvToValidation.observe(recvToValidation); metrics?.gossipBlock.signatureVerification.validationTime.observe(validationTime); logger.debug("Verified block signatures", { slot: blocks[0].message.slot, recvToValLatency, recvToValidation, validationTime, }); } return { verifySignaturesTime }; } /** * From an array of promises that resolve a boolean isValid * - if all valid, await all and return * - if one invalid, abort immediately and return index of invalid */ export function rejectFirstInvalidResolveAllValid(isValidPromises) { return new Promise((resolve, reject) => { let validCount = 0; for (let i = 0; i < isValidPromises.length; i++) { isValidPromises[i] .then((isValid) => { if (isValid) { if (++validCount >= isValidPromises.length) { resolve({ allValid: true }); } } else { resolve({ allValid: false, index: i }); } }) .catch((e) => { reject(e); }); } }); } //# sourceMappingURL=verifyBlocksSignatures.js.map