@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
171 lines (149 loc) • 6.73 kB
text/typescript
import {SYNC_COMMITTEE_SUBNET_COUNT, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params";
import {IBeaconStateView, isStatePostAltair} from "@lodestar/state-transition";
import {SubnetID, altair} from "@lodestar/types";
import {toRootHex} from "@lodestar/utils";
import {GossipAction, SyncCommitteeError, SyncCommitteeErrorCode} from "../errors/index.js";
import {IBeaconChain} from "../interface.js";
import {getSyncCommitteeSignatureSet} from "./signatureSets/index.js";
type IndexInSubcommittee = number;
/**
* Spec v1.1.0-alpha.8
*/
export async function validateGossipSyncCommittee(
chain: IBeaconChain,
syncCommittee: altair.SyncCommitteeMessage,
subnet: SubnetID
): Promise<{indicesInSubcommittee: IndexInSubcommittee[]}> {
const {slot, validatorIndex, beaconBlockRoot} = syncCommittee;
const messageRoot = toRootHex(beaconBlockRoot);
const headState = chain.getHeadState();
const indicesInSubcommittee = validateGossipSyncCommitteeExceptSig(chain, headState, subnet, syncCommittee);
// [IGNORE] The signature's slot is for the current slot, i.e. sync_committee_signature.slot == current_slot.
// > Checked in validateGossipSyncCommitteeExceptSig()
// [REJECT] The subnet_id is valid for the given validator, i.e. subnet_id in compute_subnets_for_sync_committee(state,
// sync_committee_message.validator_index). Note this validation implies the validator is part of the broader current
// sync committee along with the correct subcommittee.
// > Checked in validateGossipSyncCommitteeExceptSig()
// [IGNORE] There has been no other valid sync committee signature for the declared slot for the validator referenced
// by sync_committee_signature.validator_index.
const prevRoot = chain.seenSyncCommitteeMessages.get(slot, subnet, validatorIndex);
if (prevRoot) {
let shouldIgnore = false;
if (prevRoot === messageRoot) {
shouldIgnore = true;
} else {
const headRoot = chain.forkChoice.getHeadRoot();
chain.metrics?.gossipSyncCommittee.equivocationCount.inc();
if (messageRoot === headRoot) {
chain.metrics?.gossipSyncCommittee.equivocationToHeadCount.inc();
} else {
shouldIgnore = true;
}
}
if (shouldIgnore) {
throw new SyncCommitteeError(GossipAction.IGNORE, {
code: SyncCommitteeErrorCode.SYNC_COMMITTEE_MESSAGE_KNOWN,
validatorIndex,
slot,
prevRoot,
newRoot: messageRoot,
});
}
}
// [REJECT] The subnet_id is valid for the given validator, i.e. subnet_id in compute_subnets_for_sync_committee(state, sync_committee_signature.validator_index).
// Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee.
// > Checked in validateGossipSyncCommitteeExceptSig()
// [REJECT] The signature is valid for the message beacon_block_root for the validator referenced by validator_index.
await validateSyncCommitteeSigOnly(chain, headState, syncCommittee);
// Register this valid item as seen
chain.seenSyncCommitteeMessages.add(slot, subnet, validatorIndex, messageRoot);
return {indicesInSubcommittee};
}
export async function validateApiSyncCommittee(
chain: IBeaconChain,
headState: IBeaconStateView,
syncCommittee: altair.SyncCommitteeMessage
): Promise<void> {
const prioritizeBls = true;
return validateSyncCommitteeSigOnly(chain, headState, syncCommittee, prioritizeBls);
}
/**
* Abstracted so it can be re-used in API validation.
*/
async function validateSyncCommitteeSigOnly(
chain: IBeaconChain,
headState: IBeaconStateView,
syncCommittee: altair.SyncCommitteeMessage,
prioritizeBls = false
): Promise<void> {
const signatureSet = getSyncCommitteeSignatureSet(chain.config, headState, syncCommittee);
if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
throw new SyncCommitteeError(GossipAction.REJECT, {
code: SyncCommitteeErrorCode.INVALID_SIGNATURE,
});
}
}
/**
* Spec v1.1.0-alpha.8
*/
export function validateGossipSyncCommitteeExceptSig(
chain: IBeaconChain,
headState: IBeaconStateView,
subnet: SubnetID,
data: Pick<altair.SyncCommitteeMessage, "slot" | "validatorIndex">
): IndexInSubcommittee[] {
const {slot, validatorIndex} = data;
// [IGNORE] The signature's slot is for the current slot, i.e. sync_committee_signature.slot == current_slot.
// (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
if (!chain.clock.isCurrentSlotGivenGossipDisparity(slot)) {
throw new SyncCommitteeError(GossipAction.IGNORE, {
code: SyncCommitteeErrorCode.NOT_CURRENT_SLOT,
currentSlot: chain.clock.currentSlot,
slot,
});
}
// [REJECT] The subcommittee index is in the allowed range, i.e. contribution.subcommittee_index < SYNC_COMMITTEE_SUBNET_COUNT.
if (subnet >= SYNC_COMMITTEE_SUBNET_COUNT) {
throw new SyncCommitteeError(GossipAction.REJECT, {
code: SyncCommitteeErrorCode.INVALID_SUBCOMMITTEE_INDEX,
subcommitteeIndex: subnet,
});
}
// [REJECT] The subnet_id is valid for the given validator, i.e. subnet_id in compute_subnets_for_sync_committee(state, sync_committee_signature.validator_index).
// Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee.
const indicesInSubcommittee = getIndicesInSubcommittee(headState, subnet, data);
if (indicesInSubcommittee === null) {
throw new SyncCommitteeError(GossipAction.REJECT, {
code: SyncCommitteeErrorCode.VALIDATOR_NOT_IN_SYNC_COMMITTEE,
validatorIndex,
});
}
return indicesInSubcommittee;
}
/**
* Returns all IndexInSubcommittee positions of the given `subnet`.
* Returns `null` if not part of the sync committee or not part of the given `subnet`.
* A validator may appear multiple times in the same subcommittee.
*/
function getIndicesInSubcommittee(
headState: IBeaconStateView,
subnet: SubnetID,
data: Pick<altair.SyncCommitteeMessage, "slot" | "validatorIndex">
): IndexInSubcommittee[] | null {
if (!isStatePostAltair(headState)) {
return null;
}
const syncCommittee = headState.getIndexedSyncCommittee(data.slot);
const indexesInCommittee = syncCommittee.validatorIndexMap.get(data.validatorIndex);
if (indexesInCommittee === undefined) {
// Not part of the sync committee
return null;
}
const indices: IndexInSubcommittee[] = [];
for (const indexInCommittee of indexesInCommittee) {
if (Math.floor(indexInCommittee / SYNC_COMMITTEE_SUBNET_SIZE) === subnet) {
indices.push(indexInCommittee % SYNC_COMMITTEE_SUBNET_SIZE);
}
}
return indices.length > 0 ? indices : null;
}