@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
90 lines • 6 kB
JavaScript
import { SYNC_COMMITTEE_SUBNET_SIZE } from "@lodestar/params";
import { isStatePostAltair, 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) {
if (!isStatePostAltair(state)) {
throw new Error("Expected Altair state for sync committee contribution");
}
const startIndex = contribution.subcommitteeIndex * SYNC_COMMITTEE_SUBNET_SIZE;
const syncCommittee = state.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