diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index db5c7549964..26bf8ede697 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -110,33 +110,31 @@ export async function importBlock( if (!opts.skipImportingAttestations && blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT) { const attestations = block.message.body.attestations; const rootCache = new RootCache(postState); - const parentSlot = chain.forkChoice.getBlock(block.message.parentRoot)?.slot; const invalidAttestationErrorsByCode = new Map(); for (const attestation of attestations) { try { const indexedAttestation = postState.epochCtx.getIndexedAttestation(attestation); - const targetEpoch = attestation.data.target.epoch; + const {target, slot, beaconBlockRoot} = attestation.data; const attDataRoot = toHexString(ssz.phase0.AttestationData.hashTreeRoot(indexedAttestation.data)); chain.seenAggregatedAttestations.add( - targetEpoch, + target.epoch, attDataRoot, {aggregationBits: attestation.aggregationBits, trueBitCount: indexedAttestation.attestingIndices.length}, true ); // Duplicated logic from fork-choice onAttestation validation logic. // Attestations outside of this range will be dropped as Errors, so no need to import - if (targetEpoch <= currentEpoch && targetEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT) { + if (target.epoch <= currentEpoch && target.epoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT) { chain.forkChoice.onAttestation(indexedAttestation, attDataRoot); } // Note: To avoid slowing down sync, only register attestations within FORK_CHOICE_ATT_EPOCH_LIMIT chain.seenBlockAttesters.addIndices(blockEpoch, indexedAttestation.attestingIndices); - if (parentSlot !== undefined) { - chain.metrics?.registerAttestationInBlock(indexedAttestation, parentSlot, rootCache); - } + const correctHead = ssz.Root.equals(rootCache.getBlockRootAtSlot(slot), beaconBlockRoot); + chain.metrics?.registerAttestationInBlock(indexedAttestation, parentBlockSlot, correctHead); // don't want to log the processed attestations here as there are so many attestations and it takes too much disc space, // users may want to keep more log files instead of unnecessary processed attestations log diff --git a/packages/beacon-node/src/metrics/validatorMonitor.ts b/packages/beacon-node/src/metrics/validatorMonitor.ts index 748ec3ba019..aa140f2d7ae 100644 --- a/packages/beacon-node/src/metrics/validatorMonitor.ts +++ b/packages/beacon-node/src/metrics/validatorMonitor.ts @@ -1,9 +1,9 @@ -import {computeEpochAtSlot, IAttesterStatus, parseAttesterFlags, RootCache} from "@lodestar/state-transition"; +import {computeEpochAtSlot, IAttesterStatus, parseAttesterFlags} from "@lodestar/state-transition"; import {ILogger} from "@lodestar/utils"; import {allForks} from "@lodestar/types"; import {IChainForkConfig} from "@lodestar/config"; import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {Epoch, Slot, ValidatorIndex, ssz} from "@lodestar/types"; +import {Epoch, Slot, ValidatorIndex} from "@lodestar/types"; import {IndexedAttestation, SignedAggregateAndProof} from "@lodestar/types/phase0"; import {ILodestarMetrics} from "./metrics/lodestar.js"; @@ -40,7 +40,7 @@ export interface IValidatorMonitor { signedAggregateAndProof: SignedAggregateAndProof, indexedAttestation: IndexedAttestation ): void; - registerAttestationInBlock(indexedAttestation: IndexedAttestation, parentSlot: Slot, rootCache: RootCache): void; + registerAttestationInBlock(indexedAttestation: IndexedAttestation, parentSlot: Slot, correctHead: boolean): void; scrapeMetrics(slotClock: Slot): void; } @@ -380,13 +380,13 @@ export function createValidatorMonitor( }, // Register that the `indexed_attestation` was included in a *valid* `BeaconBlock`. - registerAttestationInBlock(indexedAttestation, parentSlot, rootCache): void { + registerAttestationInBlock(indexedAttestation, parentSlot, correctHead): void { const data = indexedAttestation.data; // optimal inclusion distance, not to count skipped slots between data.slot and blockSlot const inclusionDistance = Math.max(parentSlot - data.slot, 0) + 1; const delay = inclusionDistance - MIN_ATTESTATION_INCLUSION_DELAY; const epoch = computeEpochAtSlot(data.slot); - let correctHead: boolean | null = null; + for (const index of indexedAttestation.attestingIndices) { const validator = validators.get(index); if (validator) { @@ -404,9 +404,6 @@ export function createValidatorMonitor( summary.attestationMinBlockInclusionDistance = inclusionDistance; } - if (correctHead === null) { - correctHead = ssz.Root.equals(rootCache.getBlockRootAtSlot(data.slot), data.beaconBlockRoot); - } summary.attestationCorrectHead = correctHead; }); diff --git a/packages/state-transition/test/perf/util.ts b/packages/state-transition/test/perf/util.ts index a8ccfa7519c..6bd29f8b0d9 100644 --- a/packages/state-transition/test/perf/util.ts +++ b/packages/state-transition/test/perf/util.ts @@ -60,6 +60,7 @@ export const perfStateId = `${numValidators} vs - 7PWei`; /** Cache interop secret keys */ const secretKeyByModIndex = new Map(); const epoch = 23638; +export const perfStateEpoch = epoch; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types export function getPubkeys(vc = numValidators) { diff --git a/packages/state-transition/test/perf/util/rootCache.test.ts b/packages/state-transition/test/perf/util/rootCache.test.ts new file mode 100644 index 00000000000..aa5867f4214 --- /dev/null +++ b/packages/state-transition/test/perf/util/rootCache.test.ts @@ -0,0 +1,34 @@ +import {itBench} from "@dapplion/benchmark"; +import {generatePerfTestCachedStatePhase0, perfStateId, perfStateEpoch} from "../util.js"; +import {State} from "../types.js"; +import {computeStartSlotAtEpoch, getBlockRootAtSlot, RootCache} from "../../../src/util/index.js"; + +const slot = computeStartSlotAtEpoch(perfStateEpoch) - 1; + +describe("RootCache.getBlockRootAtSlot", () => { + itBench({ + id: `RootCache.getBlockRootAtSlot - ${perfStateId}`, + before: () => new RootCache(generatePerfTestCachedStatePhase0()), + beforeEach: (rootCache) => rootCache, + fn: (rootCache) => { + for (let i = 0; i <= 100; i++) { + rootCache.getBlockRootAtSlot(slot); + } + }, + runsFactor: 100, + }); +}); + +describe("RootCache.getBlockRootAtSlot", () => { + itBench({ + id: `state getBlockRootAtSlot - ${perfStateId}`, + before: () => generatePerfTestCachedStatePhase0() as State, + beforeEach: (state) => state, + fn: (state) => { + for (let i = 0; i <= 100; i++) { + getBlockRootAtSlot(state, slot); + } + }, + runsFactor: 100, + }); +});