@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
127 lines • 6.4 kB
JavaScript
import { SYNC_COMMITTEE_SUBNET_COUNT, SYNC_COMMITTEE_SUBNET_SIZE } from "@lodestar/params";
import { isStatePostAltair } from "@lodestar/state-transition";
import { toRootHex } from "@lodestar/utils";
import { GossipAction, SyncCommitteeError, SyncCommitteeErrorCode } from "../errors/index.js";
import { getSyncCommitteeSignatureSet } from "./signatureSets/index.js";
/**
* Spec v1.1.0-alpha.8
*/
export async function validateGossipSyncCommittee(chain, syncCommittee, subnet) {
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, headState, syncCommittee) {
const prioritizeBls = true;
return validateSyncCommitteeSigOnly(chain, headState, syncCommittee, prioritizeBls);
}
/**
* Abstracted so it can be re-used in API validation.
*/
async function validateSyncCommitteeSigOnly(chain, headState, syncCommittee, prioritizeBls = false) {
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, headState, subnet, data) {
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, subnet, data) {
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 = [];
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;
}
//# sourceMappingURL=syncCommittee.js.map