diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 6c28e020873b..6ab0b2867d96 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -147,6 +147,7 @@ export async function importBlock( for (const attestation of attestations) { try { + // TODO Electra: figure out how to reuse the attesting indices computed from state transition const indexedAttestation = postState.epochCtx.getIndexedAttestation(fork, attestation); const {target, beaconBlockRoot} = attestation.data; diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts index 9670bf6e0d9b..5e237828ef1f 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -136,7 +136,7 @@ const forkChoiceTest = const headState = chain.getHeadState(); const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(attestation.data)); chain.forkChoice.onAttestation( - headState.epochCtx.getIndexedAttestation(ForkSeq.phase0, attestation), + headState.epochCtx.getIndexedAttestation(ForkSeq[fork], attestation), attDataRootHex ); } diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index e4b206c66697..9cd1823e4dd4 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -2,6 +2,7 @@ import {toHexString} from "@chainsafe/ssz"; import {Slot, allForks, electra, phase0, ssz} from "@lodestar/types"; import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH, ForkSeq} from "@lodestar/params"; +import {assert} from "@lodestar/utils"; import {computeEpochAtSlot} from "../util/index.js"; import {CachedBeaconStatePhase0, CachedBeaconStateAllForks} from "../types.js"; import {isValidIndexedAttestation} from "./index.js"; @@ -89,9 +90,7 @@ export function validateAttestation( } if (fork >= ForkSeq.electra) { - if (data.index !== 0) { - throw new Error(`AttestationData.index must be zero: index=${data.index}`); - } + assert.equal(data.index, 0, `AttestationData.index must be zero: index=${data.index}`); const attestationElectra = attestation as electra.Attestation; const committeeBitsLength = attestationElectra.committeeBits.bitLen; @@ -101,7 +100,7 @@ export function validateAttestation( ); } - // TODO Electra: this should be obsolete soon when we switch to committeeIndices + // TODO Electra: this should be obsolete soon when the spec switches to committeeIndices const committeeIndices = attestationElectra.committeeBits.getTrueBitIndexes(); // Get total number of attestation participant of every committee specified @@ -109,11 +108,11 @@ export function validateAttestation( .map((committeeIndex) => epochCtx.getBeaconCommittee(data.slot, committeeIndex).length) .reduce((acc, committeeSize) => acc + committeeSize, 0); - if (attestationElectra.aggregationBits.bitLen !== participantCount) { - throw new Error( - `Attestation aggregation bits length does not match total number of committee participant aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${participantCount}` - ); - } + assert.equal( + attestationElectra.aggregationBits.bitLen, + participantCount, + `Attestation aggregation bits length does not match total number of committee participant aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${participantCount}` + ); } else { if (!(data.index < committeeCount)) { throw new Error( diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index 1defb036513d..3d73a58e509b 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -653,21 +653,17 @@ export class EpochCache { * Return the beacon committee at slot for index. */ getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array { - const slotCommittees = this.getShufflingAtSlot(slot).committees[slot % SLOTS_PER_EPOCH]; - if (index >= slotCommittees.length) { - throw new EpochCacheError({ - code: EpochCacheErrorCode.COMMITTEE_INDEX_OUT_OF_RANGE, - index, - maxIndex: slotCommittees.length, - }); - } - return slotCommittees[index]; + return this.getBeaconCommittees(slot, [index]); } /** * Return a single Uint32Array representing concatted committees of indices */ getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array { + if (indices.length === 0) { + throw new Error("Attempt to get committees without providing CommitteeIndex"); + } + const slotCommittees = this.getShufflingAtSlot(slot).committees[slot % SLOTS_PER_EPOCH]; const committees = []; @@ -682,6 +678,11 @@ export class EpochCache { committees.push(slotCommittees[index]); } + // Early return if only one index + if (committees.length === 1) { + return committees[0]; + } + // Create a new Uint32Array to flatten `committees` const totalLength = committees.reduce((acc, curr) => acc + curr.length, 0); const result = new Uint32Array(totalLength); @@ -807,13 +808,12 @@ export class EpochCache { // In Lodestar it usually means a list of validator indices of participants in a committee // In the spec it means a list of committee indices according to committeeBits // This `committeeIndices` refers to the latter + // TODO Electra: resolve the naming conflicts const committeeIndices = committeeBits.getTrueBitIndexes(); const validatorIndices = this.getBeaconCommittees(data.slot, committeeIndices); - const attestingIndices = new Set(aggregationBits.intersectValues(validatorIndices)); - - return Array.from(attestingIndices); + return aggregationBits.intersectValues(validatorIndices); } } diff --git a/packages/state-transition/src/signatureSets/indexedAttestation.ts b/packages/state-transition/src/signatureSets/indexedAttestation.ts index 724b186bee40..cb4706c645fe 100644 --- a/packages/state-transition/src/signatureSets/indexedAttestation.ts +++ b/packages/state-transition/src/signatureSets/indexedAttestation.ts @@ -41,6 +41,7 @@ export function getAttestationsSignatureSets( state: CachedBeaconStateAllForks, signedBlock: allForks.SignedBeaconBlock ): ISignatureSet[] { + // TODO: figure how to get attesting indices of an attestation once per block processing return signedBlock.message.body.attestations.map((attestation) => getIndexedAttestationSignatureSet( state,