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(config, bls, logger, metrics, preState0, blocks, indexedAttestationsByBlock, opts) {
    const isValidPromises = [];
    const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
    const currentSyncCommitteeIndexed = preState0.epochCtx.currentSyncCommitteeIndexed;
    // 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(config, currentSyncCommitteeIndexed, block, indexedAttestationsByBlock[i], {
                    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