From 67a356ab6401093dad2ffc6a64b8a891e8bd379d Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Tue, 13 Aug 2024 15:13:18 +0700 Subject: [PATCH 1/2] fix: update cached balances in epoch transition --- .../src/block/processAttestationsAltair.ts | 3 ++- packages/state-transition/src/epoch/index.ts | 4 ++-- .../epoch/processEffectiveBalanceUpdates.ts | 6 +----- .../src/epoch/processPendingBalanceDeposits.ts | 18 +++++++++++++----- .../src/epoch/processPendingConsolidations.ts | 12 +++++++++--- .../src/epoch/processSyncCommitteeUpdates.ts | 5 ++--- 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/state-transition/src/block/processAttestationsAltair.ts b/packages/state-transition/src/block/processAttestationsAltair.ts index f3d4a3e16c21..046a23d7dc27 100644 --- a/packages/state-transition/src/block/processAttestationsAltair.ts +++ b/packages/state-transition/src/block/processAttestationsAltair.ts @@ -75,6 +75,7 @@ export function processAttestationsAltair( // For each participant, update their participation // In epoch processing, this participation info is used to calculate balance updates let totalBalanceIncrementsWithWeight = 0; + const validators = state.validators; for (const index of attestingIndices) { const flags = epochParticipation.get(index); @@ -104,7 +105,7 @@ export function processAttestationsAltair( // TODO: describe issue. Compute progressive target balances // When processing each attestation, increase the cummulative target balance. Only applies post-altair if ((flagsNewSet & TIMELY_TARGET) === TIMELY_TARGET) { - const validator = state.validators.getReadonly(index); + const validator = validators.getReadonly(index); if (!validator.slashed) { if (inCurrentEpoch) { epochCtx.currentTargetUnslashedBalanceIncrements += effectiveBalanceIncrements[index]; diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 21455521897b..85e7c348dad3 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -133,7 +133,7 @@ export function processEpoch( const timer = metrics?.epochTransitionStepTime.startTimer({ step: EpochTransitionStep.processPendingBalanceDeposits, }); - processPendingBalanceDeposits(stateElectra); + processPendingBalanceDeposits(stateElectra, cache); timer?.(); } @@ -141,7 +141,7 @@ export function processEpoch( const timer = metrics?.epochTransitionStepTime.startTimer({ step: EpochTransitionStep.processPendingConsolidations, }); - processPendingConsolidations(stateElectra); + processPendingConsolidations(stateElectra, cache); timer?.(); } } diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index f4fae6e41d4f..638867534b3a 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -40,11 +40,7 @@ export function processEffectiveBalanceUpdates( // update effective balances with hysteresis // epochTransitionCache.balances is set in processRewardsAndPenalties(), so it's recycled here for performance. - // It defaults to `state.balances.getAll()` to make Typescript happy and for spec tests - const balances = state.balances.getAll(); - - // TODO: (@matthewkeil) This was causing additional failures but should not. Check the EpochTransitionCache for why - // const balances = cache.balances ?? state.balances.getAll(); + const balances = cache.balances ?? state.balances.getAll(); for (let i = 0, len = balances.length; i < len; i++) { const balance = balances[i]; diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts index f89e938cc790..81da34349e53 100644 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -1,5 +1,5 @@ import {FAR_FUTURE_EPOCH} from "@lodestar/params"; -import {CachedBeaconStateElectra} from "../types.js"; +import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; import {increaseBalance} from "../util/balance.js"; import {getActivationExitChurnLimit} from "../util/validator.js"; import {getCurrentEpoch} from "../util/epoch.js"; @@ -13,16 +13,18 @@ import {getCurrentEpoch} from "../util/epoch.js"; * * TODO Electra: Update ssz library to support batch push to `pendingBalanceDeposits` */ -export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): void { +export function processPendingBalanceDeposits(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void { const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state.epochCtx)); const currentEpoch = getCurrentEpoch(state); let processedAmount = 0n; let nextDepositIndex = 0; const depositsToPostpone = []; + const validators = state.validators; + const cachedBalances = cache.balances; for (const deposit of state.pendingBalanceDeposits.getAllReadonly()) { const {amount, index: depositIndex} = deposit; - const validator = state.validators.get(depositIndex); + const validator = validators.getReadonly(depositIndex); // Validator is exiting, postpone the deposit until after withdrawable epoch if (validator.exitEpoch < FAR_FUTURE_EPOCH) { @@ -30,7 +32,10 @@ export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): depositsToPostpone.push(deposit); } else { // Deposited balance will never become active. Increase balance but do not consume churn - increaseBalance(state, deposit.index, Number(amount)); + increaseBalance(state, depositIndex, Number(amount)); + if (cachedBalances) { + cachedBalances[depositIndex] += Number(amount); + } } } else { // Validator is not exiting, attempt to process deposit @@ -39,7 +44,10 @@ export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): break; } else { // Deposit fits in the churn, process it. Increase balance and consume churn. - increaseBalance(state, deposit.index, Number(amount)); + increaseBalance(state, depositIndex, Number(amount)); + if (cachedBalances) { + cachedBalances[depositIndex] += Number(amount); + } processedAmount = processedAmount + amount; } } diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index e025a454f64e..d6f760a6b27a 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -1,4 +1,4 @@ -import {CachedBeaconStateElectra} from "../types.js"; +import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; import {decreaseBalance, increaseBalance} from "../util/balance.js"; import {getActiveBalance} from "../util/validator.js"; import {switchToCompoundingValidator} from "../util/electra.js"; @@ -16,12 +16,14 @@ import {switchToCompoundingValidator} from "../util/electra.js"; * Dequeue all processed consolidations from `state.pendingConsolidation` * */ -export function processPendingConsolidations(state: CachedBeaconStateElectra): void { +export function processPendingConsolidations(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void { let nextPendingConsolidation = 0; + const validators = state.validators; + const cachedBalances = cache.balances; for (const pendingConsolidation of state.pendingConsolidations.getAllReadonly()) { const {sourceIndex, targetIndex} = pendingConsolidation; - const sourceValidator = state.validators.getReadonly(sourceIndex); + const sourceValidator = validators.getReadonly(sourceIndex); if (sourceValidator.slashed) { nextPendingConsolidation++; @@ -37,6 +39,10 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v const activeBalance = getActiveBalance(state, sourceIndex); decreaseBalance(state, sourceIndex, activeBalance); increaseBalance(state, targetIndex, activeBalance); + if (cachedBalances) { + cachedBalances[sourceIndex] -= activeBalance; + cachedBalances[targetIndex] += activeBalance; + } nextPendingConsolidation++; } diff --git a/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts b/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts index aa22bb4f7bcf..df4e0364be17 100644 --- a/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts +++ b/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts @@ -23,11 +23,10 @@ export function processSyncCommitteeUpdates(fork: ForkSeq, state: CachedBeaconSt activeValidatorIndices, effectiveBalanceIncrements ); + const validators = state.validators; // Using the index2pubkey cache is slower because it needs the serialized pubkey. - const nextSyncCommitteePubkeys = nextSyncCommitteeIndices.map( - (index) => state.validators.getReadonly(index).pubkey - ); + const nextSyncCommitteePubkeys = nextSyncCommitteeIndices.map((index) => validators.getReadonly(index).pubkey); // Rotate syncCommittee in state state.currentSyncCommittee = state.nextSyncCommittee; From 3a80edd87a4b95c8095923521e69c40cb11e8a9a Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Tue, 13 Aug 2024 18:03:05 +0700 Subject: [PATCH 2/2] fix: more comments in processEffectiveBalanceUpdates() --- .../src/epoch/processEffectiveBalanceUpdates.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index 638867534b3a..fdbc99b9265e 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -39,7 +39,9 @@ export function processEffectiveBalanceUpdates( // update effective balances with hysteresis - // epochTransitionCache.balances is set in processRewardsAndPenalties(), so it's recycled here for performance. + // epochTransitionCache.balances is initialized in processRewardsAndPenalties() + // and updated in processPendingBalanceDeposits() and processPendingConsolidations() + // so it's recycled here for performance. const balances = cache.balances ?? state.balances.getAll(); for (let i = 0, len = balances.length; i < len; i++) {