import { SYNC_COMMITTEE_SUBNET_SIZE } from "@lodestar/params";
import { isSyncCommitteeAggregator } from "@lodestar/state-transition";
import { GossipAction, SyncCommitteeError, SyncCommitteeErrorCode } from "../errors/index.js";
import { getContributionAndProofSignatureSet, getSyncCommitteeContributionSignatureSet, getSyncCommitteeSelectionProofSignatureSet, } from "./signatureSets/index.js";
import { validateGossipSyncCommitteeExceptSig } from "./syncCommittee.js";
/**
 * Spec v1.1.0-beta.2
 */
export async function validateSyncCommitteeGossipContributionAndProof(chain, signedContributionAndProof, skipValidationKnownParticipants = false) {
    const contributionAndProof = signedContributionAndProof.message;
    const { contribution, aggregatorIndex } = contributionAndProof;
    const { subcommitteeIndex, slot } = contribution;
    const headState = chain.getHeadState();
    validateGossipSyncCommitteeExceptSig(chain, headState, subcommitteeIndex, {
        slot,
        validatorIndex: contributionAndProof.aggregatorIndex,
    });
    // [IGNORE] The contribution's slot is for the current slot, i.e. contribution.slot == current_slot.
    // > Checked in validateGossipSyncCommitteeExceptSig()
    // [REJECT] The aggregator's validator index is in the declared subcommittee of the current sync committee
    // -- i.e. state.validators[contribution_and_proof.aggregator_index].pubkey in
    // get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index).
    // > Checked in validateGossipSyncCommitteeExceptSig()
    // _[IGNORE]_ A valid sync committee contribution with equal `slot`, `beacon_block_root` and `subcommittee_index` whose
    // `aggregation_bits` is non-strict superset has _not_ already been seen.
    if (!skipValidationKnownParticipants && chain.seenContributionAndProof.participantsKnown(contribution)) {
        throw new SyncCommitteeError(GossipAction.IGNORE, {
            code: SyncCommitteeErrorCode.SYNC_COMMITTEE_PARTICIPANTS_ALREADY_KNOWN,
        });
    }
    // [IGNORE] The sync committee contribution is the first valid contribution received for the aggregator with index
    // contribution_and_proof.aggregator_index for the slot contribution.slot and subcommittee index contribution.subcommittee_index.
    if (chain.seenContributionAndProof.isAggregatorKnown(slot, subcommitteeIndex, aggregatorIndex)) {
        throw new SyncCommitteeError(GossipAction.IGNORE, {
            code: SyncCommitteeErrorCode.SYNC_COMMITTEE_AGGREGATOR_ALREADY_KNOWN,
        });
    }
    // [REJECT] The contribution has participants -- that is, any(contribution.aggregation_bits)
    const syncCommitteeParticipantIndices = getContributionIndices(headState, contribution);
    if (syncCommitteeParticipantIndices.length === 0) {
        throw new SyncCommitteeError(GossipAction.REJECT, {
            code: SyncCommitteeErrorCode.NO_PARTICIPANT,
        });
    }
    // [REJECT] contribution_and_proof.selection_proof selects the validator as an aggregator for the slot --
    // i.e. is_sync_committee_aggregator(contribution_and_proof.selection_proof) returns True.
    if (!isSyncCommitteeAggregator(contributionAndProof.selectionProof)) {
        throw new SyncCommitteeError(GossipAction.REJECT, {
            code: SyncCommitteeErrorCode.INVALID_AGGREGATOR,
            aggregatorIndex: contributionAndProof.aggregatorIndex,
        });
    }
    // [REJECT] The aggregator's validator index is in the declared subcommittee of the current sync committee --
    // i.e. state.validators[contribution_and_proof.aggregator_index].pubkey in get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index).
    // > Checked in validateGossipSyncCommitteeExceptSig()
    const signatureSets = [
        // [REJECT] The contribution_and_proof.selection_proof is a valid signature of the SyncAggregatorSelectionData
        // derived from the contribution by the validator with index contribution_and_proof.aggregator_index.
        getSyncCommitteeSelectionProofSignatureSet(chain.config, headState, contributionAndProof),
        // [REJECT] The aggregator signature, signed_contribution_and_proof.signature, is valid.
        getContributionAndProofSignatureSet(chain.config, headState, signedContributionAndProof),
        // [REJECT] The aggregate signature is valid for the message beacon_block_root and aggregate pubkey derived from
        // the participation info in aggregation_bits for the subcommittee specified by the contribution.subcommittee_index.
        getSyncCommitteeContributionSignatureSet(chain.config, headState, contribution, syncCommitteeParticipantIndices),
    ];
    if (!(await chain.bls.verifySignatureSets(signatureSets, { batchable: true }))) {
        throw new SyncCommitteeError(GossipAction.REJECT, {
            code: SyncCommitteeErrorCode.INVALID_SIGNATURE,
        });
    }
    // no need to add to seenSyncCommittteeContributionCache here, gossip handler will do that
    chain.seenContributionAndProof.add(contributionAndProof, syncCommitteeParticipantIndices.length);
    return { syncCommitteeParticipantIndices };
}
/**
 * Retrieve pubkeys in contribution aggregate using epochCtx:
 * - currSyncCommitteeIndexes cache
 * - pubkeyCache
 */
function getContributionIndices(state, contribution) {
    const startIndex = contribution.subcommitteeIndex * SYNC_COMMITTEE_SUBNET_SIZE;
    const syncCommittee = state.epochCtx.getIndexedSyncCommittee(contribution.slot);
    // The bits in contribution.aggregationBits select validatorIndexes in the subcommittee starting at startIndex
    const subcommitteeValidatorIndices = syncCommittee.validatorIndices.slice(startIndex, startIndex + SYNC_COMMITTEE_SUBNET_SIZE);
    return contribution.aggregationBits.intersectValues(subcommitteeValidatorIndices);
}
//# sourceMappingURL=syncCommitteeContributionAndProof.js.map