import {CompositeViewDU} from "@chainsafe/ssz"; import {ForkSeq} from "@lodestar/params"; import {ssz} from "@lodestar/types"; import {getAttestationParticipationStatus} from "../block/processAttestationsAltair.js"; import {getCachedBeaconState} from "../cache/stateCache.js"; import {CachedBeaconStateAltair, CachedBeaconStatePhase0} from "../types.js"; import {RootCache, newZeroedArray} from "../util/index.js"; import {getNextSyncCommittee} from "../util/syncCommittee.js"; import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js"; /** * Upgrade a state from phase0 to altair. */ export function upgradeStateToAltair(statePhase0: CachedBeaconStatePhase0): CachedBeaconStateAltair { const {config} = statePhase0; // Get underlying node and cast phase0 tree to altair tree // // A phase0 BeaconState tree can be safely casted to an altair BeaconState tree because: // - Deprecated fields are replaced by new fields at the exact same indexes // - All new fields are appended at the end // // So by just setting all new fields to some value, all the old nodes are dropped // // phase0 | op | altair // ----------------------------- | ---- | ------------ // genesis_time | - | genesis_time // genesis_validators_root | - | genesis_validators_root // slot | - | slot // fork | - | fork // latest_block_header | - | latest_block_header // block_roots | - | block_roots // state_roots | - | state_roots // historical_roots | - | historical_roots // eth1_data | - | eth1_data // eth1_data_votes | - | eth1_data_votes // eth1_deposit_index | - | eth1_deposit_index // validators | - | validators // balances | - | balances // randao_mixes | - | randao_mixes // slashings | - | slashings // previous_epoch_attestations | diff | previous_epoch_participation // current_epoch_attestations | diff | current_epoch_participation // justification_bits | - | justification_bits // previous_justified_checkpoint | - | previous_justified_checkpoint // current_justified_checkpoint | - | current_justified_checkpoint // finalized_checkpoint | - | finalized_checkpoint // - | new | inactivity_scores // - | new | current_sync_committee // - | new | next_sync_committee const statePhase0Node = ssz.phase0.BeaconState.commitViewDU(statePhase0); const stateAltairView = ssz.altair.BeaconState.getViewDU(statePhase0Node); // Attach existing BeaconStateCache from statePhase0 to new stateAltairView object const stateAltair = getCachedBeaconState(stateAltairView, statePhase0); stateAltair.fork = ssz.phase0.Fork.toViewDU({ previousVersion: statePhase0.fork.currentVersion, currentVersion: config.ALTAIR_FORK_VERSION, epoch: statePhase0.epochCtx.epoch, }); const validatorCount = statePhase0.validators.length; const emptyEpochParticipationView = ssz.altair.EpochParticipation.toViewDU(newZeroedArray(validatorCount)); const emptyEpochParticipationNode = ssz.altair.EpochParticipation.commitViewDU(emptyEpochParticipationView); stateAltair.previousEpochParticipation = emptyEpochParticipationView; // Cloned instance with same immutable Node stateAltair.currentEpochParticipation = ssz.altair.EpochParticipation.getViewDU(emptyEpochParticipationNode); stateAltair.inactivityScores = ssz.altair.InactivityScores.toViewDU(newZeroedArray(validatorCount)); const {syncCommittee, indices} = getNextSyncCommittee( ForkSeq.altair, stateAltair, stateAltair.epochCtx.nextActiveIndices, stateAltair.epochCtx.effectiveBalanceIncrements ); const syncCommitteeView = ssz.altair.SyncCommittee.toViewDU(syncCommittee); stateAltair.currentSyncCommittee = syncCommitteeView; stateAltair.nextSyncCommittee = syncCommitteeView; stateAltair.epochCtx.setSyncCommitteesIndexed(indices); const pendingAttesations = statePhase0.previousEpochAttestations; translateParticipation(stateAltair, pendingAttesations); // Commit new added fields ViewDU to the root node stateAltair.commit(); // Clear cache to ensure the cache of phase0 fields is not used by new altair fields // [15] previous_epoch_attestations -> previous_epoch_participation // [16] current_epoch_attestations -> current_epoch_participation // // TODO: This could only drop the caches of index 15,16. However this would couple this code tightly with SSZ ViewDU // internals. If the cache is not cleared, consuming the ViewDU instance could break in strange ways. // biome-ignore lint/complexity/useLiteralKeys: It is a protected attribute stateAltair["clearCache"](); // TODO: describe issue. Compute progressive target balances // // Note: in EpochCache.afterProcessEpoch previousTargetUnslashedBalanceIncrements is overwritten, // currentTargetUnslashedBalanceIncrements is rotated to previousTargetUnslashedBalanceIncrements // // Here target balance is computed in full, which is slightly less performant than doing so in the loop // above but gurantees consistency with EpochCache.createFromState(). Note execution order below: // ``` // processEpoch() // epochCtx.afterProcessEpoch() // if (...) upgradeStateToAltair() // ``` const previousEpoch = stateAltair.epochCtx.epoch - 1; stateAltair.epochCtx.previousTargetUnslashedBalanceIncrements = sumTargetUnslashedBalanceIncrements( stateAltair.previousEpochParticipation.getAll(), previousEpoch, stateAltair.validators.getAllReadonlyValues() ); return stateAltair; } /** * Translate_participation in https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/fork.md */ function translateParticipation( state: CachedBeaconStateAltair, pendingAttesations: CompositeViewDU ): void { const {epochCtx} = state; const rootCache = new RootCache(state); const epochParticipation = state.previousEpochParticipation; for (const attestation of pendingAttesations.getAllReadonly()) { const data = attestation.data; const attestationFlags = getAttestationParticipationStatus( ForkSeq.altair, data, attestation.inclusionDelay, epochCtx.epoch, rootCache, null ); const committeeIndices = epochCtx.getBeaconCommittee(data.slot, data.index); const attestingIndices = attestation.aggregationBits.intersectValues(committeeIndices); for (const index of attestingIndices) { // ParticipationFlags type uses option {setBitwiseOR: true}, .set() does a |= operation epochParticipation.set(index, attestationFlags); } } }