Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: rename caches and related functions to indicate finalized #6528

Draft
wants to merge 9 commits into
base: electra-fork
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 11 additions & 6 deletions packages/beacon-node/src/api/impl/beacon/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ export function getBeaconStateApi({
const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId);
const currentEpoch = getCurrentEpoch(state);
const {validators, balances} = state; // Get the validators sub tree once for all the loop
const {pubkey2index} = chain.getHeadState().epochCtx;
const {finalizedPubkey2index} = chain.getHeadState().epochCtx;

const validatorResponses: routes.beacon.ValidatorResponse[] = [];
if (filters?.id) {
for (const id of filters.id) {
const resp = getStateValidatorIndex(id, state, pubkey2index);
const resp = getStateValidatorIndex(id, state, finalizedPubkey2index);
if (resp.valid) {
const validatorIndex = resp.validatorIndex;
const validator = validators.getReadonly(validatorIndex);
Expand All @@ -111,7 +111,12 @@ export function getBeaconStateApi({
data: validatorResponses,
};
} else if (filters?.status) {
const validatorsByStatus = filterStateValidatorsByStatus(filters.status, state, pubkey2index, currentEpoch);
const validatorsByStatus = filterStateValidatorsByStatus(
filters.status,
state,
finalizedPubkey2index,
currentEpoch
);
return {
executionOptimistic,
finalized,
Expand All @@ -136,9 +141,9 @@ export function getBeaconStateApi({

async getStateValidator(stateId, validatorId) {
const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId);
const {pubkey2index} = chain.getHeadState().epochCtx;
const {finalizedPubkey2index} = chain.getHeadState().epochCtx;

const resp = getStateValidatorIndex(validatorId, state, pubkey2index);
const resp = getStateValidatorIndex(validatorId, state, finalizedPubkey2index);
if (!resp.valid) {
throw new ApiError(resp.code, resp.reason);
}
Expand Down Expand Up @@ -169,7 +174,7 @@ export function getBeaconStateApi({
}
balances.push({index: id, balance: state.balances.get(id)});
} else {
const index = headState.epochCtx.pubkey2index.get(id);
const index = headState.epochCtx.finalizedPubkey2index.get(id);
if (index != null && index <= state.validators.length) {
balances.push({index, balance: state.balances.get(index)});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,7 @@ export function getValidatorApi({

const filteredRegistrations = registrations.filter((registration) => {
const {pubkey} = registration.message;
const validatorIndex = headState.epochCtx.pubkey2index.get(pubkey);
const validatorIndex = headState.epochCtx.finalizedPubkey2index.get(pubkey);
if (validatorIndex === undefined) return false;

const validator = headState.validators.getReadonly(validatorIndex);
Expand Down
51 changes: 44 additions & 7 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ export class BeaconChain implements IBeaconChain {
readonly seenBlockAttesters = new SeenBlockAttesters();

// Global state caches
readonly pubkey2index: PubkeyIndexMap;
readonly index2pubkey: Index2PubkeyCache;
readonly finalizedPubkey2index: PubkeyIndexMap;
readonly finalizedIndex2pubkey: Index2PubkeyCache;

readonly beaconProposerCache: BeaconProposerCache;
readonly checkpointBalancesCache: CheckpointBalancesCache;
Expand Down Expand Up @@ -235,16 +235,16 @@ export class BeaconChain implements IBeaconChain {
? anchorState
: createCachedBeaconState(anchorState, {
config,
pubkey2index: new PubkeyIndexMap(),
index2pubkey: [],
finalizedPubkey2index: new PubkeyIndexMap(),
finalizedIndex2pubkey: [],
});
this.shufflingCache.processState(cachedState, cachedState.epochCtx.previousShuffling.epoch);
this.shufflingCache.processState(cachedState, cachedState.epochCtx.currentShuffling.epoch);
this.shufflingCache.processState(cachedState, cachedState.epochCtx.nextShuffling.epoch);

// Persist single global instance of state caches
this.pubkey2index = cachedState.epochCtx.pubkey2index;
this.index2pubkey = cachedState.epochCtx.index2pubkey;
this.finalizedPubkey2index = cachedState.epochCtx.finalizedPubkey2index;
this.finalizedIndex2pubkey = cachedState.epochCtx.finalizedIndex2pubkey;

const fileDataStore = opts.nHistoricalStatesFileDataStore ?? false;
const stateCache = this.opts.nHistoricalStates
Expand Down Expand Up @@ -585,7 +585,7 @@ export class BeaconChain implements IBeaconChain {
RegenCaller.produceBlock
);
const proposerIndex = state.epochCtx.getBeaconProposer(slot);
const proposerPubKey = state.epochCtx.index2pubkey[proposerIndex].toBytes();
const proposerPubKey = state.epochCtx.finalizedIndex2pubkey[proposerIndex].toBytes();

const {body, blobs, executionPayloadValue, shouldOverrideBuilder} = await produceBlockBody.call(
this,
Expand Down Expand Up @@ -927,6 +927,9 @@ export class BeaconChain implements IBeaconChain {
metrics.forkChoice.balancesLength.set(forkChoiceMetrics.balancesLength);
metrics.forkChoice.nodes.set(forkChoiceMetrics.nodes);
metrics.forkChoice.indices.set(forkChoiceMetrics.indices);

const headState = this.getHeadState();
metrics.headState.unfinalizedPubkeyCacheSize.set(headState.epochCtx.unfinalizedPubkey2index.size);
}

private onClockSlot(slot: Slot): void {
Expand Down Expand Up @@ -1015,6 +1018,40 @@ export class BeaconChain implements IBeaconChain {
if (headState) {
this.opPool.pruneAll(headBlock, headState);
}

const cpEpoch = cp.epoch;
const electraEpoch = headState?.config.ELECTRA_FORK_EPOCH ?? Infinity;

if (headState === null) {
this.logger.verbose("Head state is null");
} else if (cpEpoch >= electraEpoch) {
// Get the validator.length from the state at cpEpoch
// We are confident the last element in the list is from headEpoch
// Thus we query from the end of the list. (cpEpoch - headEpoch - 1) is negative number
const pivotValidatorIndex = headState.epochCtx.getValidatorCountAtEpoch(cpEpoch);

if (pivotValidatorIndex !== undefined) {
// Note EIP-6914 will break this logic
const newFinalizedValidators = headState.epochCtx.unfinalizedPubkey2index.filter(
(index, _pubkey) => index < pivotValidatorIndex
);

// Populate finalized pubkey cache and remove unfinalized pubkey cache
if (!newFinalizedValidators.isEmpty()) {
this.regen.updateUnfinalizedPubkeys(newFinalizedValidators);
}
}
}

// TODO-Electra: Deprecating eth1Data poll requires a check on a finalized checkpoint state.
// Will resolve this later
// if (cpEpoch >= (this.config.ELECTRA_FORK_EPOCH ?? Infinity)) {
// // finalizedState can be safely casted to Electra state since cp is already post-Electra
// if (finalizedState.eth1DepositIndex >= (finalizedState as CachedBeaconStateElectra).depositReceiptsStartIndex) {
// // Signal eth1 to stop polling eth1Data
// this.eth1.stopPollingEth1Data();
// }
// }
}

async updateBeaconProposerData(epoch: Epoch, proposers: ProposerPreparationData[]): Promise<void> {
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ export interface IBeaconChain {
readonly regen: IStateRegenerator;
readonly lightClientServer: LightClientServer;
readonly reprocessController: ReprocessController;
readonly pubkey2index: PubkeyIndexMap;
readonly index2pubkey: Index2PubkeyCache;
readonly finalizedPubkey2index: PubkeyIndexMap;
readonly finalizedIndex2pubkey: Index2PubkeyCache;

// Ops pool
readonly attestationPool: AttestationPool;
Expand Down
50 changes: 49 additions & 1 deletion packages/beacon-node/src/chain/regen/queued.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {toHexString} from "@chainsafe/ssz";
import {phase0, Slot, allForks, RootHex, Epoch} from "@lodestar/types";
import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition";
import {CachedBeaconStateAllForks, UnfinalizedPubkeyIndexMap, computeEpochAtSlot} from "@lodestar/state-transition";
import {Logger} from "@lodestar/utils";
import {routes} from "@lodestar/api";
import {CheckpointHex, toCheckpointHex} from "../stateCache/index.js";
Expand Down Expand Up @@ -194,6 +194,54 @@ export class QueuedStateRegenerator implements IStateRegenerator {
return this.checkpointStateCache.updatePreComputedCheckpoint(rootHex, epoch);
}

/**
* Remove `validators` from all unfinalized cache's epochCtx.UnfinalizedPubkey2Index,
* and add them to epochCtx.finalizedPubkey2index and epochCtx.finalizedIndex2pubkey
*/
updateUnfinalizedPubkeys(validators: UnfinalizedPubkeyIndexMap): void {
let numStatesUpdated = 0;
const states = this.stateCache.getStates();
const cpStates = this.checkpointStateCache.getStates();

// Add finalized pubkeys to all states.
const addTimer = this.metrics?.regenFnAddPubkeyTime.startTimer();

// We only need to add pubkeys to any one of the states since the finalized caches is shared globally across all states
const firstState = (states.next().value ?? cpStates.next().value) as CachedBeaconStateAllForks | undefined;

if (firstState !== undefined) {
firstState.epochCtx.addFinalizedPubkeys(validators, this.metrics?.epochCache ?? undefined);
} else {
this.logger.warn("Attempt to delete finalized pubkey from unfinalized pubkey cache. But no state is available");
}

addTimer?.();

// Delete finalized pubkeys from unfinalized pubkey cache for all states
const deleteTimer = this.metrics?.regenFnDeletePubkeyTime.startTimer();
const pubkeysToDelete = Array.from(validators.keys());

for (const s of states) {
s.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete);
numStatesUpdated++;
}

for (const s of cpStates) {
s.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete);
numStatesUpdated++;
}

// Since first state is consumed from the iterator. Will need to perform delete explicitly
if (firstState !== undefined) {
firstState?.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete);
numStatesUpdated++;
}

deleteTimer?.();

this.metrics?.regenFnNumStatesUpdated.observe(numStatesUpdated);
}

/**
* Get the state to run with `block`.
* - State after `block.parentRoot` dialed forward to block.slot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function computeTotalAttestationsRewardsAltair(
const {statuses} = transitionCache;
const {epochCtx, config} = state;
const validatorIndices = validatorIds
?.map((id) => (typeof id === "number" ? id : epochCtx.pubkey2index.get(id)))
?.map((id) => (typeof id === "number" ? id : epochCtx.finalizedPubkey2index.get(id)))
.filter((index) => index !== undefined); // Validator indices to include in the result

const inactivityPenaltyDenominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function computeSyncCommitteeRewards(

const altairBlock = block as altair.BeaconBlock;
const preStateAltair = preState as CachedBeaconStateAltair;
const {index2pubkey} = preStateAltair.epochCtx;
const {finalizedIndex2pubkey} = preStateAltair.epochCtx;

// Bound committeeIndices in case it goes beyond SYNC_COMMITTEE_SIZE just to be safe
const committeeIndices = preStateAltair.epochCtx.currentSyncCommitteeIndexed.validatorIndices.slice(
Expand Down Expand Up @@ -49,7 +49,8 @@ export async function computeSyncCommitteeRewards(
if (validatorIds !== undefined) {
const filtersSet = new Set(validatorIds);
return rewards.filter(
(reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex())
(reward) =>
filtersSet.has(reward.validatorIndex) || filtersSet.has(finalizedIndex2pubkey[reward.validatorIndex].toHex())
);
} else {
return rewards;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ export class FIFOBlockStateCache implements BlockStateCache {
}));
}

getStates(): IterableIterator<CachedBeaconStateAllForks> {
throw new Error("Method not implemented.");
}

/**
* For unit test only.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache {
}));
}

getStates(): IterableIterator<CachedBeaconStateAllForks> {
return this.cache.values();
}

/** ONLY FOR DEBUGGING PURPOSES. For spec tests on error */
dumpCheckpointKeys(): string[] {
return Array.from(this.cache.keys());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,10 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
});
}

getStates(): IterableIterator<CachedBeaconStateAllForks> {
throw new Error("Method not implemented.");
}

/** ONLY FOR DEBUGGING PURPOSES. For spec tests on error */
dumpCheckpointKeys(): string[] {
return Array.from(this.cache.keys());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class StateContextCache implements BlockStateCache {
this.maxStates = maxStates;
this.cache = new MapTracker(metrics?.stateCache);
if (metrics) {
this.metrics = metrics.stateCache;
this.metrics = {...metrics.stateCache, ...metrics.epochCache};
metrics.stateCache.size.addCollect(() => metrics.stateCache.size.set(this.cache.size));
}
}
Expand Down Expand Up @@ -128,6 +128,10 @@ export class StateContextCache implements BlockStateCache {
}));
}

getStates(): IterableIterator<CachedBeaconStateAllForks> {
return this.cache.values();
}

private deleteAllEpochItems(epoch: Epoch): void {
for (const rootHex of this.epochIndex.get(epoch) || []) {
this.cache.delete(rootHex);
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/src/chain/stateCache/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface BlockStateCache {
prune(headStateRootHex: RootHex): void;
deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
dumpSummary(): routes.lodestar.StateCacheItem[];
getStates(): IterableIterator<CachedBeaconStateAllForks>; // Expose beacon states stored in cache. Use with caution
}

/**
Expand Down Expand Up @@ -70,6 +71,7 @@ export interface CheckpointStateCache {
processState(blockRootHex: RootHex, state: CachedBeaconStateAllForks): Promise<number>;
clear(): void;
dumpSummary(): routes.lodestar.StateCacheItem[];
getStates(): IterableIterator<CachedBeaconStateAllForks>; // Expose beacon states stored in cache. Use with caution
}

export enum CacheItemType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ async function validateAggregateAndProof(
// by the validator with index aggregate_and_proof.aggregator_index.
// [REJECT] The aggregator signature, signed_aggregate_and_proof.signature, is valid.
// [REJECT] The signature of aggregate is valid.
const aggregator = chain.index2pubkey[aggregateAndProof.aggregatorIndex];
const aggregator = chain.finalizedIndex2pubkey[aggregateAndProof.aggregatorIndex];
const signingRoot = cachedAttData ? cachedAttData.signingRoot : getAttestationDataSigningRoot(chain.config, attData);
const indexedAttestationSignatureSet = createAggregateSignatureSetFromComponents(
indexedAttestation.attestingIndices.map((i) => chain.index2pubkey[i]),
indexedAttestation.attestingIndices.map((i) => chain.finalizedIndex2pubkey[i]),
signingRoot,
indexedAttestation.signature
);
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/src/chain/validation/attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,14 +424,14 @@ async function validateGossipAttestationNoSignatureCheck(
if (attestationOrCache.cache) {
// there could be up to 6% of cpu time to compute signing root if we don't clone the signature set
signatureSet = createSingleSignatureSetFromComponents(
chain.index2pubkey[validatorIndex],
chain.finalizedIndex2pubkey[validatorIndex],
attestationOrCache.cache.signingRoot,
signature
);
attDataRootHex = attestationOrCache.cache.attDataRootHex;
} else {
signatureSet = createSingleSignatureSetFromComponents(
chain.index2pubkey[validatorIndex],
chain.finalizedIndex2pubkey[validatorIndex],
getSigningRoot(),
signature
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function getContributionAndProofSignatureSet(
const signingData = signedContributionAndProof.message;
return {
type: SignatureSetType.single,
pubkey: epochCtx.index2pubkey[signedContributionAndProof.message.aggregatorIndex],
pubkey: epochCtx.finalizedIndex2pubkey[signedContributionAndProof.message.aggregatorIndex],
signingRoot: computeSigningRoot(ssz.altair.ContributionAndProof, signingData, domain),
signature: signedContributionAndProof.signature,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function getSyncCommitteeSignatureSet(

return {
type: SignatureSetType.single,
pubkey: state.epochCtx.index2pubkey[syncCommittee.validatorIndex],
pubkey: state.epochCtx.finalizedIndex2pubkey[syncCommittee.validatorIndex],
signingRoot: computeSigningRoot(ssz.Root, syncCommittee.beaconBlockRoot, domain),
signature: syncCommittee.signature,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function getSyncCommitteeSelectionProofSignatureSet(
};
return {
type: SignatureSetType.single,
pubkey: epochCtx.index2pubkey[contributionAndProof.aggregatorIndex],
pubkey: epochCtx.finalizedIndex2pubkey[contributionAndProof.aggregatorIndex],
signingRoot: computeSigningRoot(ssz.altair.SyncAggregatorSelectionData, signingData, domain),
signature: contributionAndProof.selectionProof,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export async function validateSyncCommitteeGossipContributionAndProof(
// > Checked in validateGossipSyncCommitteeExceptSig()

const participantPubkeys = syncCommitteeParticipantIndices.map(
(validatorIndex) => headState.epochCtx.index2pubkey[validatorIndex]
(validatorIndex) => headState.epochCtx.finalizedIndex2pubkey[validatorIndex]
);
const signatureSets = [
// [REJECT] The contribution_and_proof.selection_proof is a valid signature of the SyncAggregatorSelectionData
Expand Down Expand Up @@ -104,7 +104,7 @@ export async function validateSyncCommitteeGossipContributionAndProof(
/**
* Retrieve pubkeys in contribution aggregate using epochCtx:
* - currSyncCommitteeIndexes cache
* - index2pubkey cache
* - finalizedIndex2pubkey cache
*/
function getContributionIndices(
state: CachedBeaconStateAltair,
Expand Down