Skip to content

Commit

Permalink
feat: support electra devnet-1 (#6892)
Browse files Browse the repository at this point in the history
* Update spec test version

* Use new max effective balance

* Relax loop breaking condition for `computeProposerIndex`

* fix remaining

* check-types & lint

* Skip invalid test

* Fix rebase + lint

* Remove early return statement in `computeProposers`

* Address comment

* Address comment
  • Loading branch information
ensi321 committed Jun 25, 2024
1 parent 85a85a9 commit 8e9edb4
Show file tree
Hide file tree
Showing 23 changed files with 88 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
capella,
deneb,
Wei,
electra,
} from "@lodestar/types";
import {
CachedBeaconStateAllForks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EpochTransitionCache,
BeaconStateAllForks,
beforeProcessEpoch,
CachedBeaconStateAltair,
} from "@lodestar/state-transition";
import * as epochFns from "@lodestar/state-transition/epoch";
import {ssz} from "@lodestar/types";
Expand Down Expand Up @@ -40,7 +41,10 @@ const epochTransitionFns: Record<string, EpochTransitionFn> = {
rewards_and_penalties: epochFns.processRewardsAndPenalties,
slashings: epochFns.processSlashings,
slashings_reset: epochFns.processSlashingsReset,
sync_committee_updates: epochFns.processSyncCommitteeUpdates as EpochTransitionFn,
sync_committee_updates: (state, _) => {
const fork = state.config.getForkSeq(state.slot);
epochFns.processSyncCommitteeUpdates(fork, state as CachedBeaconStateAltair);
},
historical_summaries_update: epochFns.processHistoricalSummariesUpdate as EpochTransitionFn,
pending_balance_deposits: epochFns.processPendingBalanceDeposits as EpochTransitionFn,
pending_consolidations: epochFns.processPendingConsolidations as EpochTransitionFn,
Expand Down
6 changes: 5 additions & 1 deletion packages/beacon-node/test/spec/presets/operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,13 @@ const operationFns: Record<string, BlockProcessFn<CachedBeaconStateAllForks>> =
blockFns.processWithdrawalRequest(ForkSeq.electra, state as CachedBeaconStateElectra, testCase.withdrawal_request);
},

deposit_request: (state, testCase: {deposit_request: electra.DepositRequest}) => {
blockFns.processDepositRequest(ForkSeq.electra, state as CachedBeaconStateElectra, testCase.deposit_request);
},

consolidation_request: (state, testCase: {consolidation_request: electra.ConsolidationRequest}) => {
blockFns.processConsolidationRequest(state as CachedBeaconStateElectra, testCase.consolidation_request);
},

};

export type BlockProcessFn<T extends CachedBeaconStateAllForks> = (state: T, testCase: any) => void;
Expand Down Expand Up @@ -151,6 +154,7 @@ const operations: TestRunnerFn<OperationsTestCase, BeaconStateAllForks> = (fork,
address_change: ssz.capella.SignedBLSToExecutionChange,
// Electra
withdrawal_request: ssz.electra.WithdrawalRequest,
deposit_request: ssz.electra.DepositRequest,
consolidation_request: ssz.electra.ConsolidationRequest,
},
shouldError: (testCase) => testCase.post === undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/test/spec/specTestVersioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util/downloadTests";
const __dirname = path.dirname(fileURLToPath(import.meta.url));

export const ethereumConsensusSpecsTests: DownloadTestsOptions = {
specVersion: "v1.5.0-alpha.2",
specVersion: "v1.5.0-alpha.3",
// Target directory is the host package root: 'packages/*/spec-tests'
outputDir: path.join(__dirname, "../../spec-tests"),
specTestsRepoUrl: "https://github.com/ethereum/consensus-spec-tests",
Expand Down
3 changes: 2 additions & 1 deletion packages/beacon-node/test/spec/utils/specTestIterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export const defaultSkipOpts: SkipOpts = {
/^capella\/light_client\/single_merkle_proof\/BeaconBlockBody.*/,
/^deneb\/light_client\/single_merkle_proof\/BeaconBlockBody.*/,
],
skippedTests: [],
// TODO Electra: Review this test in the next spec test release
skippedTests: [/incorrect_not_enough_consolidation_churn_available/],
skippedRunners: ["merkle_proof", "networking"],
};

Expand Down
8 changes: 7 additions & 1 deletion packages/config/src/forkConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
isForkExecution,
isForkBlobs,
} from "@lodestar/params";
import {Slot, allForks, Version, ssz} from "@lodestar/types";
import {Slot, allForks, Version, ssz, Epoch} from "@lodestar/types";
import {ChainConfig} from "../chainConfig/index.js";
import {ForkConfig, ForkInfo} from "./types.js";

Expand Down Expand Up @@ -80,6 +80,9 @@ export function createForkConfig(config: ChainConfig): ForkConfig {
// Fork convenience methods
getForkInfo(slot: Slot): ForkInfo {
const epoch = Math.floor(Math.max(slot, 0) / SLOTS_PER_EPOCH);
return this.getForkInfoFromEpoch(epoch);
},
getForkInfoFromEpoch(epoch: Epoch): ForkInfo {
// NOTE: forks must be sorted by descending epoch, latest fork first
for (const fork of forksDescendingEpochOrder) {
if (epoch >= fork.epoch) return fork;
Expand All @@ -92,6 +95,9 @@ export function createForkConfig(config: ChainConfig): ForkConfig {
getForkSeq(slot: Slot): ForkSeq {
return this.getForkInfo(slot).seq;
},
getForkSeqFromEpoch(epoch: Epoch): ForkSeq {
return this.getForkInfoFromEpoch(epoch).seq;
},
getForkVersion(slot: Slot): Version {
return this.getForkInfo(slot).version;
},
Expand Down
5 changes: 4 additions & 1 deletion packages/config/src/forkConfig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ export type ForkConfig = {

/** Get the hard-fork info for the active fork at `slot` */
getForkInfo(slot: Slot): ForkInfo;

/** Get the hard-fork info for the active fork at `epoch` */
getForkInfoFromEpoch(epoch: Epoch): ForkInfo;
/** Get the hard-fork name at a given slot */
getForkName(slot: Slot): ForkName;
/** Get the hard-fork sequence number at a given slot */
getForkSeq(slot: Slot): ForkSeq;
/** Get the hard-fork sequence number at a given epoch */
getForkSeqFromEpoch(epoch: Epoch): ForkSeq;
/** Get the hard-fork version at a given slot */
getForkVersion(slot: Slot): Version;
/** Get SSZ types by hard-fork */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export function processConsolidationRequest(
state: CachedBeaconStateElectra,
consolidationRequest: electra.ConsolidationRequest
): void {

// If the pending consolidations queue is full, consolidation requests are ignored
if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) {
return;
Expand All @@ -30,11 +29,11 @@ export function processConsolidationRequest(
}

// Verify that source != target, so a consolidation cannot be used as an exit.
if (sourceIndex === targetIndex){
if (sourceIndex === targetIndex) {
return;
}

const sourceValidator = state.validators.getReadonly(sourceIndex);
const sourceValidator = state.validators.get(sourceIndex);
const targetValidator = state.validators.getReadonly(targetIndex);
const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12);
const currentEpoch = state.epochCtx.epoch;
Expand Down Expand Up @@ -71,4 +70,4 @@ export function processConsolidationRequest(
targetIndex,
});
state.pendingConsolidations.push(pendingConsolidation);
}
}
2 changes: 1 addition & 1 deletion packages/state-transition/src/block/processOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function processOperations(
const stateElectra = state as CachedBeaconStateElectra;
const bodyElectra = body as electra.BeaconBlockBody;

for (const depositRequest of bodyElectra.executionPayload.depositReceipts) {
for (const depositRequest of bodyElectra.executionPayload.depositRequests) {
processDepositRequest(fork, stateElectra, depositRequest);
}

Expand Down
15 changes: 13 additions & 2 deletions packages/state-transition/src/cache/epochCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,12 @@ export class EpochCache {
// Allow to create CachedBeaconState for empty states, or no active validators
const proposers =
currentShuffling.activeIndices.length > 0
? computeProposers(currentProposerSeed, currentShuffling, effectiveBalanceIncrements)
? computeProposers(
config.getForkSeqFromEpoch(currentEpoch),
currentProposerSeed,
currentShuffling,
effectiveBalanceIncrements
)
: [];

const proposersNextEpoch: ProposersDeferred = {
Expand Down Expand Up @@ -571,7 +576,12 @@ export class EpochCache {
this.proposersPrevEpoch = this.proposers;

const currentProposerSeed = getSeed(state, this.currentShuffling.epoch, DOMAIN_BEACON_PROPOSER);
this.proposers = computeProposers(currentProposerSeed, this.currentShuffling, this.effectiveBalanceIncrements);
this.proposers = computeProposers(
this.config.getForkSeqFromEpoch(currEpoch),
currentProposerSeed,
this.currentShuffling,
this.effectiveBalanceIncrements
);

// Only pre-compute the seed since it's very cheap. Do the expensive computeProposers() call only on demand.
this.proposersNextEpoch = {computed: false, seed: getSeed(state, this.nextShuffling.epoch, DOMAIN_BEACON_PROPOSER)};
Expand Down Expand Up @@ -768,6 +778,7 @@ export class EpochCache {
getBeaconProposersNextEpoch(): ValidatorIndex[] {
if (!this.proposersNextEpoch.computed) {
const indexes = computeProposers(
this.config.getForkSeqFromEpoch(this.epoch + 1),
this.proposersNextEpoch.seed,
this.nextShuffling,
this.effectiveBalanceIncrements
Expand Down
2 changes: 1 addition & 1 deletion packages/state-transition/src/epoch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export function processEpoch(
const timer = metrics?.epochTransitionStepTime.startTimer({
step: EpochTransitionStep.processSyncCommitteeUpdates,
});
processSyncCommitteeUpdates(state as CachedBeaconStateAltair);
processSyncCommitteeUpdates(fork, state as CachedBeaconStateAltair);
timer?.();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {getCurrentEpoch} from "../util/epoch.js";
* For each eligible `deposit`, call `increaseBalance()`.
* Remove the processed deposits from `state.pendingBalanceDeposits`.
* Update `state.depositBalanceToConsume` for the next epoch
*
*
* TODO Electra: Update ssz library to support batch push to `pendingBalanceDeposits`
*/
export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import bls from "@chainsafe/bls";
import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD} from "@lodestar/params";
import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, ForkSeq} from "@lodestar/params";
import {ssz} from "@lodestar/types";
import {getNextSyncCommitteeIndices} from "../util/seed.js";
import {CachedBeaconStateAltair} from "../types.js";
Expand All @@ -10,14 +10,15 @@ import {CachedBeaconStateAltair} from "../types.js";
* PERF: Once every `EPOCHS_PER_SYNC_COMMITTEE_PERIOD`, do an expensive operation to compute the next committee.
* Calculating the next sync committee has a proportional cost to $VALIDATOR_COUNT
*/
export function processSyncCommitteeUpdates(state: CachedBeaconStateAltair): void {
export function processSyncCommitteeUpdates(fork: ForkSeq, state: CachedBeaconStateAltair): void {
const nextEpoch = state.epochCtx.epoch + 1;

if (nextEpoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD === 0) {
const activeValidatorIndices = state.epochCtx.nextShuffling.activeIndices;
const {effectiveBalanceIncrements} = state.epochCtx;

const nextSyncCommitteeIndices = getNextSyncCommitteeIndices(
fork,
state,
activeValidatorIndices,
effectiveBalanceIncrements
Expand Down
4 changes: 2 additions & 2 deletions packages/state-transition/src/signatureSets/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ForkSeq} from "@lodestar/params";
import {allForks, altair, capella, electra} from "@lodestar/types";
import {allForks, altair, capella} from "@lodestar/types";
import {ISignatureSet} from "../util/index.js";
import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js";
import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "../types.js";
import {getSyncCommitteeSignatureSet} from "../block/processSyncCommittee.js";
import {getProposerSlashingsSignatureSets} from "./proposerSlashings.js";
import {getAttesterSlashingsSignatureSets} from "./attesterSlashings.js";
Expand Down
1 change: 1 addition & 0 deletions packages/state-transition/src/slot/upgradeStateToAltair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function upgradeStateToAltair(statePhase0: CachedBeaconStatePhase0): Cach
stateAltair.inactivityScores = ssz.altair.InactivityScores.toViewDU(newZeroedArray(validatorCount));

const {syncCommittee, indices} = getNextSyncCommittee(
ForkSeq.altair,
stateAltair,
stateAltair.epochCtx.nextShuffling.activeIndices,
stateAltair.epochCtx.effectiveBalanceIncrements
Expand Down
2 changes: 2 additions & 0 deletions packages/state-transition/src/util/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ export function executionPayloadToPayloadHeader(
ssz.electra.DepositRequests.hashTreeRoot((payload as electra.ExecutionPayload).depositRequests);
(bellatrixPayloadFields as electra.ExecutionPayloadHeader).withdrawalRequestsRoot =
ssz.electra.WithdrawalRequests.hashTreeRoot((payload as electra.ExecutionPayload).withdrawalRequests);
(bellatrixPayloadFields as electra.ExecutionPayloadHeader).consolidationRequestsRoot =
ssz.electra.ConsolidationRequests.hashTreeRoot((payload as electra.ExecutionPayload).consolidationRequests);
}

return bellatrixPayloadFields;
Expand Down
8 changes: 6 additions & 2 deletions packages/state-transition/src/util/genesis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {EpochCacheImmutableData} from "../cache/epochCache.js";
import {processDeposit} from "../block/processDeposit.js";
import {increaseBalance} from "../index.js";
import {computeEpochAtSlot} from "./epoch.js";
import {getActiveValidatorIndices} from "./validator.js";
import {getActiveValidatorIndices, getValidatorMaxEffectiveBalance} from "./validator.js";
import {getTemporaryBlockHeader} from "./blockRoot.js";
import {newFilledArray} from "./array.js";
import {getNextSyncCommittee} from "./syncCommittee.js";
Expand Down Expand Up @@ -193,7 +193,10 @@ export function applyDeposits(
}

const balance = balancesArr[i];
const effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE);
const effectiveBalance = Math.min(
balance - (balance % EFFECTIVE_BALANCE_INCREMENT),
getValidatorMaxEffectiveBalance(validator.withdrawalCredentials)
);

validator.effectiveBalance = effectiveBalance;
epochCtx.effectiveBalanceIncrementsSet(i, effectiveBalance);
Expand Down Expand Up @@ -263,6 +266,7 @@ export function initializeBeaconStateFromEth1(

if (fork >= ForkSeq.altair) {
const {syncCommittee} = getNextSyncCommittee(
fork,
state,
activeValidatorIndices,
state.epochCtx.effectiveBalanceIncrements
Expand Down
19 changes: 14 additions & 5 deletions packages/state-transition/src/util/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
DOMAIN_SYNC_COMMITTEE,
EFFECTIVE_BALANCE_INCREMENT,
EPOCHS_PER_HISTORICAL_VECTOR,
ForkSeq,
MAX_EFFECTIVE_BALANCE,
MAX_EFFECTIVE_BALANCE_ELECTRA,
MIN_SEED_LOOKAHEAD,
SHUFFLE_ROUND_COUNT,
SLOTS_PER_EPOCH,
Expand All @@ -20,6 +22,7 @@ import {computeEpochAtSlot} from "./epoch.js";
* Compute proposer indices for an epoch
*/
export function computeProposers(
fork: ForkSeq,
epochSeed: Uint8Array,
shuffling: {epoch: Epoch; activeIndices: ArrayLike<ValidatorIndex>},
effectiveBalanceIncrements: EffectiveBalanceIncrements
Expand All @@ -29,6 +32,7 @@ export function computeProposers(
for (let slot = startSlot; slot < startSlot + SLOTS_PER_EPOCH; slot++) {
proposers.push(
computeProposerIndex(
fork,
effectiveBalanceIncrements,
shuffling.activeIndices,
digest(Buffer.concat([epochSeed, intToBytes(slot, 8)]))
Expand All @@ -44,6 +48,7 @@ export function computeProposers(
* SLOW CODE - 🐢
*/
export function computeProposerIndex(
fork: ForkSeq,
effectiveBalanceIncrements: EffectiveBalanceIncrements,
indices: ArrayLike<ValidatorIndex>,
seed: Uint8Array
Expand All @@ -54,7 +59,10 @@ export function computeProposerIndex(

// TODO: Inline outside this function
const MAX_RANDOM_BYTE = 2 ** 8 - 1;
const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT;
const MAX_EFFECTIVE_BALANCE_INCREMENT =
fork >= ForkSeq.electra
? MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT
: MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT;

let i = 0;
/* eslint-disable-next-line no-constant-condition */
Expand All @@ -73,9 +81,6 @@ export function computeProposerIndex(
return candidateIndex;
}
i += 1;
if (i === indices.length) {
return -1;
}
}
}

Expand All @@ -90,13 +95,17 @@ export function computeProposerIndex(
* SLOW CODE - 🐢
*/
export function getNextSyncCommitteeIndices(
fork: ForkSeq,
state: BeaconStateAllForks,
activeValidatorIndices: ArrayLike<ValidatorIndex>,
effectiveBalanceIncrements: EffectiveBalanceIncrements
): ValidatorIndex[] {
// TODO: Bechmark if it's necessary to inline outside of this function
const MAX_RANDOM_BYTE = 2 ** 8 - 1;
const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT;
const MAX_EFFECTIVE_BALANCE_INCREMENT =
fork >= ForkSeq.electra
? MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT
: MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT;

const epoch = computeEpochAtSlot(state.slot) + 1;

Expand Down
4 changes: 3 additions & 1 deletion packages/state-transition/src/util/syncCommittee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import bls from "@chainsafe/bls";
import {
BASE_REWARD_FACTOR,
EFFECTIVE_BALANCE_INCREMENT,
ForkSeq,
SLOTS_PER_EPOCH,
SYNC_COMMITTEE_SIZE,
SYNC_REWARD_WEIGHT,
Expand All @@ -19,11 +20,12 @@ import {getNextSyncCommitteeIndices} from "./seed.js";
* SLOW CODE - 🐢
*/
export function getNextSyncCommittee(
fork: ForkSeq,
state: BeaconStateAllForks,
activeValidatorIndices: ArrayLike<ValidatorIndex>,
effectiveBalanceIncrements: EffectiveBalanceIncrements
): {indices: ValidatorIndex[]; syncCommittee: altair.SyncCommittee} {
const indices = getNextSyncCommitteeIndices(state, activeValidatorIndices, effectiveBalanceIncrements);
const indices = getNextSyncCommitteeIndices(fork, state, activeValidatorIndices, effectiveBalanceIncrements);

// Using the index2pubkey cache is slower because it needs the serialized pubkey.
const pubkeys = indices.map((index) => state.validators.getReadonly(index).pubkey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function benchmarkAltairEpochSteps(stateOg: LazyValue<CachedBeaconStateAllForks>
id: `${stateId} - altair processSyncCommitteeUpdates`,
convergeFactor: 1 / 100, // Very unstable make it converge faster
beforeEach: () => stateOg.value.clone() as CachedBeaconStateAltair,
fn: (state) => processSyncCommitteeUpdates(state),
fn: (state) => processSyncCommitteeUpdates(ForkSeq.altair, state),
});

itBench<StateEpoch, StateEpoch>({
Expand Down
Loading

0 comments on commit 8e9edb4

Please sign in to comment.