import { computeStartSlotAtEpoch } from "@lodestar/state-transition";
import { isGloasBeaconBlock } from "@lodestar/types";
import { toRootHex } from "@lodestar/utils";
import { BlockError, BlockErrorCode } from "../errors/index.js";
/**
 * Verifies some early cheap sanity checks on the block before running the full state transition.
 *
 * - Parent is known to the fork-choice
 * - Check skipped slots limit
 * - check_block_relevancy()
 *   - Block not in the future
 *   - Not genesis block
 *   - Block's slot is < Infinity
 *   - Not finalized slot
 *   - Not already known
 */
export function verifyBlocksSanityChecks(chain, blocks, opts) {
    if (blocks.length === 0) {
        throw Error("Empty partiallyVerifiedBlocks");
    }
    const relevantBlocks = [];
    const parentSlots = [];
    let parentBlock = null;
    for (const blockInput of blocks) {
        const block = blockInput.getBlock();
        const blockSlot = block.message.slot;
        const blockHash = toRootHex(chain.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message));
        if (chain.blacklistedBlocks.has(blockHash)) {
            // Blacklisting blocks via CLI flag only requires to set the block hash
            if (chain.blacklistedBlocks.get(blockHash) === null) {
                // Set actual slot observed when processing the block
                chain.blacklistedBlocks.set(blockHash, blockSlot);
            }
            throw new BlockError(block, { code: BlockErrorCode.BLACKLISTED_BLOCK });
        }
        if (chain.blacklistedBlocks.has(toRootHex(block.message.parentRoot))) {
            chain.blacklistedBlocks.set(blockHash, blockSlot);
            throw new BlockError(block, { code: BlockErrorCode.BLACKLISTED_BLOCK });
        }
        // Not genesis block
        // IGNORE if `partiallyVerifiedBlock.ignoreIfKnown`
        if (blockSlot === 0) {
            if (opts.ignoreIfKnown) {
                continue;
            }
            throw new BlockError(block, { code: BlockErrorCode.GENESIS_BLOCK });
        }
        // Not finalized slot
        // IGNORE if `partiallyVerifiedBlock.ignoreIfFinalized`
        const finalizedSlot = computeStartSlotAtEpoch(chain.forkChoice.getFinalizedCheckpoint().epoch);
        if (blockSlot <= finalizedSlot) {
            if (opts.ignoreIfFinalized) {
                continue;
            }
            throw new BlockError(block, { code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT, blockSlot, finalizedSlot });
        }
        const relevantLastBlock = relevantBlocks.at(-1);
        let parentBlockSlot;
        if (relevantLastBlock) {
            parentBlockSlot = relevantLastBlock.getBlock().message.slot;
        }
        else {
            // When importing a block segment, only the first NON-IGNORED block must be known to the fork-choice.
            const parentRoot = toRootHex(block.message.parentRoot);
            parentBlock = isGloasBeaconBlock(block.message)
                ? chain.forkChoice.getBlockHexAndBlockHash(parentRoot, toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash))
                : chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
            if (!parentBlock) {
                throw new BlockError(block, { code: BlockErrorCode.PARENT_UNKNOWN, parentRoot });
            }
            // Parent is known to the fork-choice
            parentBlockSlot = parentBlock.slot;
        }
        // Block not in the future, also checks for infinity
        const currentSlot = chain.clock.currentSlot;
        if (blockSlot > currentSlot) {
            throw new BlockError(block, { code: BlockErrorCode.FUTURE_SLOT, blockSlot, currentSlot });
        }
        // Not already known
        // IGNORE if `partiallyVerifiedBlock.ignoreIfKnown`
        if (chain.forkChoice.hasBlockHex(blockHash)) {
            if (opts.ignoreIfKnown) {
                continue;
            }
            throw new BlockError(block, { code: BlockErrorCode.ALREADY_KNOWN, root: blockHash });
        }
        // Block is relevant
        relevantBlocks.push(blockInput);
        parentSlots.push(parentBlockSlot);
    }
    // Just assert to be over cautious and for purposes to be more explicit for someone
    // going through the code segment
    if (parentBlock === null && relevantBlocks.length > 0) {
        throw Error(`Internal error, parentBlock should not be null for relevantBlocks=${relevantBlocks.length}`);
    }
    return { relevantBlocks, parentSlots, parentBlock };
}
//# sourceMappingURL=verifyBlocksSanityChecks.js.map