From f5a9d9c70af3049712f2f8f0f1c00dcab81cc7a7 Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 20 Feb 2024 02:51:21 +0800 Subject: [PATCH 01/41] feat: implement EIP-6110 (#6042) * Add immutable in the dependencies * Initial change to pubkeyCache * Added todos * Moved unfinalized cache to epochCache * Move populating finalized cache to afterProcessEpoch * Specify unfinalized cache during state cloning * Move from unfinalized to finalized cache in afterProcessEpoch * Confused myself * Clean up * Change logic * Fix cloning issue * Clean up redundant code * Add CarryoverData in epochCtx.createFromState * Fix typo * Update usage of pubkeyCache * Update pubkeyCache usage * Fix lint * Fix lint * Add 6110 to ChainConfig * Add 6110 to BeaconPreset * Define 6110 fork and container * Add V6110 api to execution engine * Update test * Add depositReceiptsRoot to process_execution_payload * State transitioning to EIP6110 * State transitioning to EIP6110 * Light client change in EIP-6110 * Update tests * produceBlock * Refactor processDeposit to match the spec * Implement processDepositReceipt * Implement 6110 fork guard for pubkeyCache * Handle changes in eth1 deposit * Update eth1 deposit test * Fix typo * Lint * Remove embarassing comments * Address comments * Modify applyDeposit signature * Update packages/state-transition/src/cache/epochCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Update packages/state-transition/src/cache/epochCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Update packages/state-transition/src/cache/pubkeyCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Remove old code * Rename fields in epochCache and immutableData * Remove CarryoverData * Move isAfter6110 from var to method * Fix cyclic import * Fix operations spec runner * Fix for spec test * Fix spec test * state.depositReceiptsStartIndex to BigInt * getDeposit requires cached state * default depositReceiptsStartIndex value in genesis * Fix pubkeyCache bug * newUnfinalizedPubkeyIndexMap in createCachedBeaconState * Lint * Pass epochCache instead of pubkey2IndexFn in apis * Address comments * Add unit test on pubkey cache cloning * Add unfinalizedPubkeyCacheSize to metrics * Add unfinalizedPubkeyCacheSize to metrics * Clean up code * Add besu to el-interop * Add 6110 genesis file * Template for sim test * Add unit test for getEth1DepositCount * Update sim test * Update besudocker * Finish beacon api calls in sim test * Update epochCache.createFromState() * Fix bug unfinalized validators are not finalized * Add sim test to run a few blocks * Lint * Merge branch 'unstable' into 611 * Add more check to sim test * Update besu docker image instruction * Update sim test with correct tx * Address comment + cleanup * Clean up code * Properly handle promise rejection * Lint * Update packages/beacon-node/src/execution/engine/types.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Update comments * Accept type undefined in ExecutionPayloadBodyRpc * Update comment and semantic * Remove if statement when adding finalized validator * Comment on repeated insert on finalized cache * rename createFromState * Add comment on getPubkey() * Stash change to reduce diffs * Stash change to reduce diffs * Lint * addFinalizedPubkey on finalized checkpoint * Update comment * Use OrderedMap for unfinalized cache * Pull out logic of deleting pubkeys for batch op * Add updateUnfinalizedPubkeys in regen * Update updateUnfinalizedPubkeys logic * Add comment * Add metrics for state context caches * Address comment * Address comment * Deprecate eth1Data polling when condition is reached * Fix conflicts * Fix sim test * Lint * Fix type * Fix test * Fix test * Lint * Update packages/light-client/src/spec/utils.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Fix spec test * Address comments * Improve cache logic on checkpoint finalized * Update sim test according to new cache logic * Update comment * Lint * Finalized pubkey cache only update once per checkpoint * Add perf test for updateUnfinalizedPubkeys * Add perf test for updateUnfinalizedPubkeys * Tweak params for perf test * Freeze besu docker image version for 6110 * Add benchmark result * Use Map instead of OrderedMap. Update benchmark * Minor optimization * Minor optimization * Add memory test for immutable.js * Update test * Reduce code duplication * Lint * Remove try/catch in updateUnfinalizedPubkeys * Introduce EpochCache metric * Add historicalValidatorLengths * Polish code * Migrate state-transition unit tests to vitest * Fix calculation of pivot index * `historicalValidatorLengths` only activate post 6110 * Update sim test * Lint * Update packages/state-transition/src/cache/epochCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Improve readability on historicalValidatorLengths * Update types * Fix calculation * Add eth1data poll todo * Add epochCache.getValidatorCountAtEpoch * Add todo * Add getStateIterator for state cache * Partial commit * Update perf test * updateUnfinalizedPubkeys directly modify states from regen * Update sim test. Lint * Add todo * some improvements and a fix for effectiveBalanceIncrements fork safeness * rename eip6110 to elctra * fix electra-interop.test.ts --------- Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Co-authored-by: gajinder lint and tsc small cleanup fix rebase issue --- packages/beacon-node/test/sim/electra-interop.test.ts | 2 +- .../unit/chain/opPools/aggregatedAttestationPool.test.ts | 4 ++-- .../beacon-node/test/unit/executionEngine/http.test.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/beacon-node/test/sim/electra-interop.test.ts b/packages/beacon-node/test/sim/electra-interop.test.ts index ab3f1ae6b2c1..29483b249c85 100644 --- a/packages/beacon-node/test/sim/electra-interop.test.ts +++ b/packages/beacon-node/test/sim/electra-interop.test.ts @@ -173,7 +173,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { blockHash: dataToBytes(newPayloadBlockHash, 32), receiptsRoot: dataToBytes("0x79ee3424eb720a3ad4b1c5a372bb8160580cbe4d893778660f34213c685627a9", 32), blobGasUsed: 0n, - exits: [], + withdrawalRequests: [], }; const parentBeaconBlockRoot = dataToBytes("0x0000000000000000000000000000000000000000000000000000000000000000", 32); const payloadResult = await executionEngine.notifyNewPayload( diff --git a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts index 3c248ad4d194..71dfd63830c1 100644 --- a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts @@ -62,9 +62,9 @@ describe("AggregatedAttestationPool", function () { epochParticipation[committee[i]] = 0b000; } } - (originalState as CachedBeaconStateAltair).previousEpochParticipation = + (originalState as unknown as CachedBeaconStateAltair).previousEpochParticipation = ssz.altair.EpochParticipation.toViewDU(epochParticipation); - (originalState as CachedBeaconStateAltair).currentEpochParticipation = + (originalState as unknown as CachedBeaconStateAltair).currentEpochParticipation = ssz.altair.EpochParticipation.toViewDU(epochParticipation); originalState.commit(); let altairState: CachedBeaconStateAllForks; diff --git a/packages/beacon-node/test/unit/executionEngine/http.test.ts b/packages/beacon-node/test/unit/executionEngine/http.test.ts index 29bced11b236..5721b9ce5ad1 100644 --- a/packages/beacon-node/test/unit/executionEngine/http.test.ts +++ b/packages/beacon-node/test/unit/executionEngine/http.test.ts @@ -189,7 +189,7 @@ describe("ExecutionEngine / http", () => { }, ], depositReceipts: null, // depositReceipts is null pre-electra - exits: null, + withdrawalRequests: null, }, null, // null returned for missing blocks { @@ -199,7 +199,7 @@ describe("ExecutionEngine / http", () => { ], withdrawals: null, // withdrawals is null pre-capella depositReceipts: null, // depositReceipts is null pre-electra - exits: null, + withdrawalRequests: null, }, ], }; @@ -248,7 +248,7 @@ describe("ExecutionEngine / http", () => { }, ], depositReceipts: null, // depositReceipts is null pre-electra - exits: null, + withdrawalRequests: null, }, null, // null returned for missing blocks { @@ -258,7 +258,7 @@ describe("ExecutionEngine / http", () => { ], withdrawals: null, // withdrawals is null pre-capella depositReceipts: null, // depositReceipts is null pre-electra - exits: null, + withdrawalRequests: null, }, ], }; From 6aad4cd927e55f29bb67202c54a8d8efd2a254f5 Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 20 Feb 2024 02:51:21 +0800 Subject: [PATCH 02/41] feat: implement EIP-6110 (#6042) * Add immutable in the dependencies * Initial change to pubkeyCache * Added todos * Moved unfinalized cache to epochCache * Move populating finalized cache to afterProcessEpoch * Specify unfinalized cache during state cloning * Move from unfinalized to finalized cache in afterProcessEpoch * Confused myself * Clean up * Change logic * Fix cloning issue * Clean up redundant code * Add CarryoverData in epochCtx.createFromState * Fix typo * Update usage of pubkeyCache * Update pubkeyCache usage * Fix lint * Fix lint * Add 6110 to ChainConfig * Add 6110 to BeaconPreset * Define 6110 fork and container * Add V6110 api to execution engine * Update test * Add depositReceiptsRoot to process_execution_payload * State transitioning to EIP6110 * State transitioning to EIP6110 * Light client change in EIP-6110 * Update tests * produceBlock * Refactor processDeposit to match the spec * Implement processDepositReceipt * Implement 6110 fork guard for pubkeyCache * Handle changes in eth1 deposit * Update eth1 deposit test * Fix typo * Lint * Remove embarassing comments * Address comments * Modify applyDeposit signature * Update packages/state-transition/src/cache/epochCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Update packages/state-transition/src/cache/epochCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Update packages/state-transition/src/cache/pubkeyCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Remove old code * Rename fields in epochCache and immutableData * Remove CarryoverData * Move isAfter6110 from var to method * Fix cyclic import * Fix operations spec runner * Fix for spec test * Fix spec test * state.depositReceiptsStartIndex to BigInt * getDeposit requires cached state * default depositReceiptsStartIndex value in genesis * Fix pubkeyCache bug * newUnfinalizedPubkeyIndexMap in createCachedBeaconState * Lint * Pass epochCache instead of pubkey2IndexFn in apis * Address comments * Add unit test on pubkey cache cloning * Add unfinalizedPubkeyCacheSize to metrics * Add unfinalizedPubkeyCacheSize to metrics * Clean up code * Add besu to el-interop * Add 6110 genesis file * Template for sim test * Add unit test for getEth1DepositCount * Update sim test * Update besudocker * Finish beacon api calls in sim test * Update epochCache.createFromState() * Fix bug unfinalized validators are not finalized * Add sim test to run a few blocks * Lint * Merge branch 'unstable' into 611 * Add more check to sim test * Update besu docker image instruction * Update sim test with correct tx * Address comment + cleanup * Clean up code * Properly handle promise rejection * Lint * Update packages/beacon-node/src/execution/engine/types.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Update comments * Accept type undefined in ExecutionPayloadBodyRpc * Update comment and semantic * Remove if statement when adding finalized validator * Comment on repeated insert on finalized cache * rename createFromState * Add comment on getPubkey() * Stash change to reduce diffs * Stash change to reduce diffs * Lint * addFinalizedPubkey on finalized checkpoint * Update comment * Use OrderedMap for unfinalized cache * Pull out logic of deleting pubkeys for batch op * Add updateUnfinalizedPubkeys in regen * Update updateUnfinalizedPubkeys logic * Add comment * Add metrics for state context caches * Address comment * Address comment * Deprecate eth1Data polling when condition is reached * Fix conflicts * Fix sim test * Lint * Fix type * Fix test * Fix test * Lint * Update packages/light-client/src/spec/utils.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Fix spec test * Address comments * Improve cache logic on checkpoint finalized * Update sim test according to new cache logic * Update comment * Lint * Finalized pubkey cache only update once per checkpoint * Add perf test for updateUnfinalizedPubkeys * Add perf test for updateUnfinalizedPubkeys * Tweak params for perf test * Freeze besu docker image version for 6110 * Add benchmark result * Use Map instead of OrderedMap. Update benchmark * Minor optimization * Minor optimization * Add memory test for immutable.js * Update test * Reduce code duplication * Lint * Remove try/catch in updateUnfinalizedPubkeys * Introduce EpochCache metric * Add historicalValidatorLengths * Polish code * Migrate state-transition unit tests to vitest * Fix calculation of pivot index * `historicalValidatorLengths` only activate post 6110 * Update sim test * Lint * Update packages/state-transition/src/cache/epochCache.ts Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> * Improve readability on historicalValidatorLengths * Update types * Fix calculation * Add eth1data poll todo * Add epochCache.getValidatorCountAtEpoch * Add todo * Add getStateIterator for state cache * Partial commit * Update perf test * updateUnfinalizedPubkeys directly modify states from regen * Update sim test. Lint * Add todo * some improvements and a fix for effectiveBalanceIncrements fork safeness * rename eip6110 to elctra * fix electra-interop.test.ts --------- Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Co-authored-by: gajinder lint and tsc small cleanup --- packages/beacon-node/src/metrics/metrics/lodestar.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/beacon-node/src/metrics/metrics/lodestar.ts b/packages/beacon-node/src/metrics/metrics/lodestar.ts index cfa4145bfdcc..fd3962aba0f7 100644 --- a/packages/beacon-node/src/metrics/metrics/lodestar.ts +++ b/packages/beacon-node/src/metrics/metrics/lodestar.ts @@ -1211,16 +1211,16 @@ export function createLodestarMetrics( help: "Histogram of cloned count per state every time state.clone() is called", buckets: [1, 2, 5, 10, 50, 250], }), - stateSerializeDuration: register.histogram({ - name: "lodestar_cp_state_cache_state_serialize_seconds", - help: "Histogram of time to serialize state to db", - buckets: [0.1, 0.5, 1, 2, 3, 4], - }), numStatesUpdated: register.histogram({ name: "lodestar_cp_state_cache_state_updated_count", help: "Histogram of number of state cache items updated every time removing and adding pubkeys to pubkey cache", buckets: [1, 2, 5, 10, 50, 250], }), + stateSerializeDuration: register.histogram({ + name: "lodestar_cp_state_cache_state_serialize_seconds", + help: "Histogram of time to serialize state to db", + buckets: [0.1, 0.5, 1, 2, 3, 4], + }), statePruneFromMemoryCount: register.gauge({ name: "lodestar_cp_state_cache_state_prune_from_memory_count", help: "Total number of states pruned from memory", From 62c76dff21d7fe89540a51b236764c6ea6176fad Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 13 Mar 2024 15:52:49 +0800 Subject: [PATCH 03/41] Add presets --- packages/params/src/index.ts | 5 ++++ packages/params/src/presets/mainnet.ts | 13 ++++++--- packages/params/src/presets/minimal.ts | 13 ++++++--- packages/params/src/types.ts | 12 +++++++- .../src/block/slashValidator.ts | 2 +- packages/state-transition/src/epoch/index.ts | 2 +- .../perf/block/processWithdrawals.test.ts | 2 +- packages/types/src/phase0/sszTypes.ts | 2 +- packages/validator/src/util/params.ts | 5 ++++ .../test/unit/utils/interopConfigs.ts | 28 ++++++++++++++++--- 10 files changed, 67 insertions(+), 17 deletions(-) diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index b261e07959d0..04015ed5d7df 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -44,7 +44,12 @@ export const { HYSTERESIS_DOWNWARD_MULTIPLIER, HYSTERESIS_UPWARD_MULTIPLIER, MIN_DEPOSIT_AMOUNT, + MIN_ACTIVATION_BALANCE, MAX_EFFECTIVE_BALANCE, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + MAX_CONSOLIDATIONS, EFFECTIVE_BALANCE_INCREMENT, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH, diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index 5343966a43f4..8f6aa3dcba09 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -26,8 +26,8 @@ export const mainnetPreset: BeaconPreset = { // --------------------------------------------------------------- // 2**0 * 10**9 (= 1,000,000,000) Gwei MIN_DEPOSIT_AMOUNT: 1000000000, - // 2**5 * 10**9 (= 32,000,000,000) Gwei - MAX_EFFECTIVE_BALANCE: 32000000000, + // 2**11 * 10**9 (= 2,048,000,000,000) Gwei + MAX_EFFECTIVE_BALANCE: 2048000000000, // 2**0 * 10**9 (= 1,000,000,000) Gwei EFFECTIVE_BALANCE_INCREMENT: 1000000000, @@ -69,8 +69,8 @@ export const mainnetPreset: BeaconPreset = { PROPOSER_REWARD_QUOTIENT: 8, // 2**26 (= 67,108,864) INACTIVITY_PENALTY_QUOTIENT: 67108864, - // 2**7 (= 128) (lower safety margin at Phase 0 genesis) - MIN_SLASHING_PENALTY_QUOTIENT: 128, + // 2**16 (= 65536) + MIN_SLASHING_PENALTY_QUOTIENT: 65536, PROPORTIONAL_SLASHING_MULTIPLIER: 1, // Max operations per block @@ -124,4 +124,9 @@ export const mainnetPreset: BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, + MIN_ACTIVATION_BALANCE: 32000000000, + PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, + PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728, + PENDING_CONSOLIDATIONS_LIMIT: 262144, + MAX_CONSOLIDATIONS: 1, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index e4938d501a51..8989c25fcdf6 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -26,8 +26,8 @@ export const minimalPreset: BeaconPreset = { // --------------------------------------------------------------- // 2**0 * 10**9 (= 1,000,000,000) Gwei MIN_DEPOSIT_AMOUNT: 1000000000, - // 2**5 * 10**9 (= 32,000,000,000) Gwei - MAX_EFFECTIVE_BALANCE: 32000000000, + // 2**11 * 10**9 (= 2,048,000,000,000) Gwei + MAX_EFFECTIVE_BALANCE: 2048000000000, // 2**0 * 10**9 (= 1,000,000,000) Gwei EFFECTIVE_BALANCE_INCREMENT: 1000000000, @@ -69,8 +69,8 @@ export const minimalPreset: BeaconPreset = { PROPOSER_REWARD_QUOTIENT: 8, // [customized] 2**25 (= 33,554,432) INACTIVITY_PENALTY_QUOTIENT: 33554432, - // [customized] 2**6 (= 64) - MIN_SLASHING_PENALTY_QUOTIENT: 64, + // [customized] 2**16 (= 65536) + MIN_SLASHING_PENALTY_QUOTIENT: 65536, // [customized] 2 (lower safety margin than Phase 0 genesis but different than mainnet config for testing) PROPORTIONAL_SLASHING_MULTIPLIER: 2, @@ -125,4 +125,9 @@ export const minimalPreset: BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, + MIN_ACTIVATION_BALANCE: 32000000000, + PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, + PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64, + PENDING_CONSOLIDATIONS_LIMIT: 64, + MAX_CONSOLIDATIONS: 1, }; diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index e5b85a9e2224..776c47406079 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -17,7 +17,7 @@ export type BeaconPreset = { // Gwei Values MIN_DEPOSIT_AMOUNT: number; - MAX_EFFECTIVE_BALANCE: number; + MIN_ACTIVATION_BALANCE: number; EFFECTIVE_BALANCE_INCREMENT: number; // Time parameters @@ -88,6 +88,11 @@ export type BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: number; MAX_ATTESTER_SLASHINGS_ELECTRA: number; MAX_ATTESTATIONS_ELECTRA: number; + MAX_EFFECTIVE_BALANCE: number; + PENDING_BALANCE_DEPOSITS_LIMIT: number; + PENDING_PARTIAL_WITHDRAWALS_LIMIT: number; + PENDING_CONSOLIDATIONS_LIMIT: number; + MAX_CONSOLIDATIONS: number; }; /** @@ -179,6 +184,11 @@ export const beaconPresetTypes: BeaconPresetTypes = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: "number", MAX_ATTESTER_SLASHINGS_ELECTRA: "number", MAX_ATTESTATIONS_ELECTRA: "number", + MIN_ACTIVATION_BALANCE: "number", + PENDING_BALANCE_DEPOSITS_LIMIT: "number", + PENDING_PARTIAL_WITHDRAWALS_LIMIT: "number", + PENDING_CONSOLIDATIONS_LIMIT: "number", + MAX_CONSOLIDATIONS: "number", }; type BeaconPresetTypes = { diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index 9f3eb2947644..59d65953cd04 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -41,7 +41,7 @@ export function slashValidator( // state.slashings is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because: // - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset() // - max slashed validators per epoch is SLOTS_PER_EPOCH * MAX_ATTESTER_SLASHINGS * MAX_VALIDATORS_PER_COMMITTEE which is 32 * 2 * 2048 = 131072 on mainnet - // - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 + // - with that and 2048_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 // - we don't need to compute the total slashings from state.slashings, it's handled by totalSlashingsByIncrement in EpochCache const slashingIndex = epoch % EPOCHS_PER_SLASHINGS_VECTOR; state.slashings.set(slashingIndex, (state.slashings.get(slashingIndex) ?? 0) + effectiveBalance); diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index b55ebe291fb9..dfc6ff11c74f 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -76,7 +76,7 @@ export function processEpoch( // state.slashings is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because: // - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset() // - max slashed validators per epoch is SLOTS_PER_EPOCH * MAX_ATTESTER_SLASHINGS * MAX_VALIDATORS_PER_COMMITTEE which is 32 * 2 * 2048 = 131072 on mainnet - // - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 + // - with that and 2048_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 if (maxValidatorsPerStateSlashing > maxSafeValidators) { throw new Error("Lodestar does not support this network, parameters don't fit number value inside state.slashings"); } diff --git a/packages/state-transition/test/perf/block/processWithdrawals.test.ts b/packages/state-transition/test/perf/block/processWithdrawals.test.ts index 997f401d32ce..d7136d224e7a 100644 --- a/packages/state-transition/test/perf/block/processWithdrawals.test.ts +++ b/packages/state-transition/test/perf/block/processWithdrawals.test.ts @@ -9,7 +9,7 @@ import {getExpectedWithdrawalsTestData, WithdrawalOpts} from "../../utils/capell // having BLS withdrawal credential prefix as that validator probe is wasted. // // Best case: -// All Validator have balances > MAX_EFFECTIVE_BALANCE and ETH1 withdrawal credential prefix set +// All Validator have balances > MAX_EFFECTIVE_BALANCE and ETH1 withdrawal credential prefix set // TODO Electra: Not true anymore // // Worst case: // All balances are low enough or withdrawal credential not set diff --git a/packages/types/src/phase0/sszTypes.ts b/packages/types/src/phase0/sszTypes.ts index 9eb2a13e5fae..ffa3483c3fb8 100644 --- a/packages/types/src/phase0/sszTypes.ts +++ b/packages/types/src/phase0/sszTypes.ts @@ -236,7 +236,7 @@ export const RandaoMixes = new VectorCompositeType(Bytes32, EPOCHS_PER_HISTORICA * This is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because: * - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset() * - max slashed validators per epoch is SLOTS_PER_EPOCH * MAX_ATTESTER_SLASHINGS * MAX_VALIDATORS_PER_COMMITTEE which is 32 * 2 * 2048 = 131072 on mainnet - * - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 + * - with that and 2048_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 * - we don't need to compute the total slashings from state.slashings, it's handled by totalSlashingsByIncrement in EpochCache */ export const Slashings = new VectorBasicType(UintNum64, EPOCHS_PER_SLASHINGS_VECTOR); diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index ff1c8c0fdc25..ad48ee4fd241 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -227,5 +227,10 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Wed, 13 Mar 2024 16:00:44 +0800 Subject: [PATCH 04/41] Update config --- packages/config/README.md | 2 +- packages/config/src/chainConfig/configs/mainnet.ts | 8 ++++---- packages/config/src/chainConfig/configs/minimal.ts | 8 ++++---- packages/config/src/chainConfig/types.ts | 2 ++ packages/validator/test/unit/utils/interopConfigs.ts | 8 ++++---- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/config/README.md b/packages/config/README.md index 46ce14bac5dc..b64726d5ba94 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -54,7 +54,7 @@ import {createChainConfig, ChainConfig, chainConfigFromJson} from "@lodestar/con // example config let chainConfigObj: Record = { // phase0 - MIN_PER_EPOCH_CHURN_LIMIT: 4, + MIN_PER_EPOCH_CHURN_LIMIT: 128000000000, CHURN_LIMIT_QUOTIENT: 65536, MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 128, MIN_GENESIS_TIME: 1621504614, diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 6d1fd9b75952..a5a7dc9c0c3a 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -74,10 +74,10 @@ export const chainConfig: ChainConfig = { INACTIVITY_SCORE_RECOVERY_RATE: 16, // 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000, - // 2**2 (= 4) - MIN_PER_EPOCH_CHURN_LIMIT: 4, - // 2**3 (= 8) - MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8, + // 2**7 * 10**9 (= 128,000,000,000) + MIN_PER_EPOCH_CHURN_LIMIT: 128000000000, + // 2**8 * 10**9 (= 256,000,000,000) + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 256000000000, // 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536, diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index 44a28ca36ec7..c5f6dda82954 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -70,10 +70,10 @@ export const chainConfig: ChainConfig = { INACTIVITY_SCORE_RECOVERY_RATE: 16, // 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000, - // 2**2 (= 4) - MIN_PER_EPOCH_CHURN_LIMIT: 2, - // [customized] - MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 4, + // 2**6 * 10**9 (= 64,000,000,000) + MIN_PER_EPOCH_CHURN_LIMIT: 64000000000, + // 2**7 * 10**9 (= 128,000,000,000) + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 128000000000, // [customized] scale queue churn at much lower validator counts for testing CHURN_LIMIT_QUOTIENT: 32, diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 0d306aa6545a..c35d6af21808 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -72,6 +72,8 @@ export type ChainConfig = { // Networking MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: number; + + // Churn limits }; export const chainConfigTypes: SpecTypes = { diff --git a/packages/validator/test/unit/utils/interopConfigs.ts b/packages/validator/test/unit/utils/interopConfigs.ts index e2ba696dde5d..26c452e581e7 100644 --- a/packages/validator/test/unit/utils/interopConfigs.ts +++ b/packages/validator/test/unit/utils/interopConfigs.ts @@ -23,7 +23,7 @@ export const lightHouseRopstenConfig = { INACTIVITY_SCORE_BIAS: "4", INACTIVITY_SCORE_RECOVERY_RATE: "16", EJECTION_BALANCE: "16000000000", - MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", CHURN_LIMIT_QUOTIENT: "65536", PROPOSER_SCORE_BOOST: "40", DEPOSIT_CHAIN_ID: "3", @@ -148,7 +148,7 @@ export const prysmRopstenConfig = { MIN_EPOCHS_TO_INACTIVITY_PENALTY: "4", MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "100000", MIN_GENESIS_TIME: "1653318000", - MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", MIN_SEED_LOOKAHEAD: "1", MIN_SLASHING_PENALTY_QUOTIENT: "128", MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: "64", @@ -241,7 +241,7 @@ export const tekuRopstenConfig = { DOMAIN_RANDAO: "0x02000000", EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: "256", EPOCHS_PER_HISTORICAL_VECTOR: "65536", - MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: "64", TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: "16", EPOCHS_PER_ETH1_VOTING_PERIOD: "64", @@ -364,7 +364,7 @@ export const nimbusRopstenConfig = { INACTIVITY_SCORE_BIAS: "4", INACTIVITY_SCORE_RECOVERY_RATE: "16", EJECTION_BALANCE: "16000000000", - MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", CHURN_LIMIT_QUOTIENT: "65536", PROPOSER_SCORE_BOOST: "40", DEPOSIT_CHAIN_ID: "3", From 2e0ba78ddc48027c49a5a2716053ba7668d1dd9b Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 13 Mar 2024 16:29:59 +0800 Subject: [PATCH 05/41] Add necessary containers --- packages/types/src/electra/sszTypes.ts | 83 +++++++++++++++++++++++++- packages/types/src/electra/types.ts | 9 +++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 53195f30de11..5d5be4ca0315 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -24,8 +24,11 @@ import {ssz as altairSsz} from "../altair/index.js"; import {ssz as bellatrixSsz} from "../bellatrix/index.js"; import {ssz as capellaSsz} from "../capella/index.js"; import {ssz as denebSsz} from "../deneb/index.js"; +import { MAX_CONSOLIDATIONS, PENDING_BALANCE_DEPOSITS_LIMIT, PENDING_CONSOLIDATIONS_LIMIT, PENDING_PARTIAL_WITHDRAWALS_LIMIT } from "@lodestar/params"; const { + Epoch, + Gwei, UintNum64, Slot, Root, @@ -148,6 +151,40 @@ export const ExecutionPayloadHeader = new ContainerType( {typeName: "ExecutionPayloadHeader", jsonCase: "eth2"} ); +export const Consolidation = new ContainerType( + { + sourceIndex: ValidatorIndex, + targetIndex: ValidatorIndex, + epoch: Epoch, + }, + {typeName: "Consolidation", jsonCase: "eth2"} +); + +export const SignedConsolidation = new ContainerType( + { + message: Consolidation, + signature: BLSSignature, + }, + {typeName: "SignedConsolidation", jsonCase: "eth2"} +); + +export const Consolidation = new ContainerType( + { + sourceIndex: ValidatorIndex, + targetIndex: ValidatorIndex, + epoch: Epoch, + }, + {typeName: "Consolidation", jsonCase: "eth2"} +); + +export const SignedConsolidation = new ContainerType( + { + message: Consolidation, + signature: BLSSignature, + }, + {typeName: "SignedConsolidation", jsonCase: "eth2"} +); + // We have to preserve Fields ordering while changing the type of ExecutionPayload export const BeaconBlockBody = new ContainerType( { @@ -163,6 +200,7 @@ export const BeaconBlockBody = new ContainerType( executionPayload: ExecutionPayload, // Modified in ELECTRA blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges, blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments, + consolidations: new ListCompositeType(SignedConsolidation, MAX_CONSOLIDATIONS), // [New in Electra] }, {typeName: "BeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true} ); @@ -243,8 +281,32 @@ export const ExecutionPayloadAndBlobsBundle = new ContainerType( {typeName: "ExecutionPayloadAndBlobsBundle", jsonCase: "eth2"} ); -// We don't spread deneb.BeaconState fields since we need to replace -// latestExecutionPayloadHeader and we cannot keep order doing that +export const PendingBalanceDeposit = new ContainerType( + { + index: ValidatorIndex, + amount: Gwei, + }, + {typeName: "PendingBalanceDeposit", jsonCase: "eth2"} +); + +export const PartialWithdrawal = new ContainerType( + { + index: ValidatorIndex, + amount: Gwei, + withdrawableEpoch: Epoch, + }, + {typeName: "PartialWithdrawal", jsonCase: "eth2"} +); + +export const PendingConsolidation = new ContainerType( + { + sourceIndex: ValidatorIndex, + targetIndex: ValidatorIndex, + }, + {typeName: "PendingConsolidation", jsonCase: "eth2"} +); + +// In EIP-7251, we spread deneb fields as new fields are appended at the end export const BeaconState = new ContainerType( { genesisTime: UintNum64, @@ -288,6 +350,14 @@ export const BeaconState = new ContainerType( // Deep history valid from Capella onwards historicalSummaries: capellaSsz.BeaconState.fields.historicalSummaries, depositReceiptsStartIndex: UintBn64, // New in ELECTRA + depositBalanceToConsume: Gwei, // [New in Electra] + exitBalanceToConsume: Gwei, // [New in Electra] + earliestExitEpoch: Epoch, // [New in Electra] + consolidationBalanceToConsume: Gwei, // [New in Electra] + earliestConsolidationEpoch: Epoch, // [New in Electra] + pendingBalanceDeposits: new ListCompositeType(PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT), // [New in Electra] + pendingPartialWithdrawals: new ListCompositeType(PartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT), // [New in Electra] + pendingConsolidations: new ListCompositeType(PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT), // [New in Electra] }, {typeName: "BeaconState", jsonCase: "eth2"} ); @@ -367,3 +437,12 @@ export const SSEPayloadAttributes = new ContainerType( }, {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} ); + +export const ExecutionLayerWithdrawRequest = new ContainerType( + { + sourceAddress: ExecutionAddress, + validatorPubkey: BLSPubkey, + amount: Gwei, + }, + {typeName: "ExecutionLayerWithdrawRequest", jsonCase: "eth2"} +); \ No newline at end of file diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index d1d0109e6ae6..399e918160b7 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -42,3 +42,12 @@ export type LightClientUpdate = ValueOf; export type LightClientFinalityUpdate = ValueOf; export type LightClientOptimisticUpdate = ValueOf; export type LightClientStore = ValueOf; + +export type Consolidation = ValueOf; +export type SignedConsolidation = ValueOf; + +export type PendingBalanceDeposit = ValueOf; +export type PartialWithdrawal = ValueOf; +export type PendingConsolidation = ValueOf; + +export type ExecutionLayerWithdrawRequest = ValueOf; \ No newline at end of file From 4879368f9b210813ae73d22bb6885b361218255e Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 13 Mar 2024 17:12:16 +0800 Subject: [PATCH 06/41] Update presets --- packages/params/src/presets/mainnet.ts | 12 ++++++++---- packages/params/src/presets/minimal.ts | 12 ++++++++---- packages/params/src/types.ts | 11 ++++++++++- .../state-transition/src/block/slashValidator.ts | 2 +- packages/state-transition/src/epoch/index.ts | 2 +- packages/types/src/phase0/sszTypes.ts | 2 +- packages/validator/src/util/params.ts | 2 ++ .../validator/test/unit/utils/interopConfigs.ts | 16 ++++++++++++---- 8 files changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index 8f6aa3dcba09..a81030bb2e1c 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -26,8 +26,8 @@ export const mainnetPreset: BeaconPreset = { // --------------------------------------------------------------- // 2**0 * 10**9 (= 1,000,000,000) Gwei MIN_DEPOSIT_AMOUNT: 1000000000, - // 2**11 * 10**9 (= 2,048,000,000,000) Gwei - MAX_EFFECTIVE_BALANCE: 2048000000000, + // 2**5 * 10**9 (= 32,000,000,000) Gwei + MAX_EFFECTIVE_BALANCE: 32000000000, // 2**0 * 10**9 (= 1,000,000,000) Gwei EFFECTIVE_BALANCE_INCREMENT: 1000000000, @@ -69,8 +69,8 @@ export const mainnetPreset: BeaconPreset = { PROPOSER_REWARD_QUOTIENT: 8, // 2**26 (= 67,108,864) INACTIVITY_PENALTY_QUOTIENT: 67108864, - // 2**16 (= 65536) - MIN_SLASHING_PENALTY_QUOTIENT: 65536, + // 2**7 (= 128) (lower safety margin at Phase 0 genesis) + MIN_SLASHING_PENALTY_QUOTIENT: 128, PROPORTIONAL_SLASHING_MULTIPLIER: 1, // Max operations per block @@ -124,6 +124,10 @@ export const mainnetPreset: BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, + // 2**11 * 10**9 (= 2,048,000,000,000) Gwei + MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, + // 2**16 (= 65536) + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 65536, MIN_ACTIVATION_BALANCE: 32000000000, PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728, diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 8989c25fcdf6..4ea70d1fd555 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -26,8 +26,8 @@ export const minimalPreset: BeaconPreset = { // --------------------------------------------------------------- // 2**0 * 10**9 (= 1,000,000,000) Gwei MIN_DEPOSIT_AMOUNT: 1000000000, - // 2**11 * 10**9 (= 2,048,000,000,000) Gwei - MAX_EFFECTIVE_BALANCE: 2048000000000, + // 2**5 * 10**9 (= 32,000,000,000) Gwei + MAX_EFFECTIVE_BALANCE: 32000000000, // 2**0 * 10**9 (= 1,000,000,000) Gwei EFFECTIVE_BALANCE_INCREMENT: 1000000000, @@ -69,8 +69,8 @@ export const minimalPreset: BeaconPreset = { PROPOSER_REWARD_QUOTIENT: 8, // [customized] 2**25 (= 33,554,432) INACTIVITY_PENALTY_QUOTIENT: 33554432, - // [customized] 2**16 (= 65536) - MIN_SLASHING_PENALTY_QUOTIENT: 65536, + // [customized] 2**6 (= 64) + MIN_SLASHING_PENALTY_QUOTIENT: 64, // [customized] 2 (lower safety margin than Phase 0 genesis but different than mainnet config for testing) PROPORTIONAL_SLASHING_MULTIPLIER: 2, @@ -125,6 +125,10 @@ export const minimalPreset: BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, + // 2**11 * 10**9 (= 2,048,000,000,000) Gwei + MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, + // 2**16 (= 65536) + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 65536, MIN_ACTIVATION_BALANCE: 32000000000, PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64, diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index 776c47406079..aabb00c623ec 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -17,7 +17,7 @@ export type BeaconPreset = { // Gwei Values MIN_DEPOSIT_AMOUNT: number; - MIN_ACTIVATION_BALANCE: number; + MAX_EFFECTIVE_BALANCE: number; EFFECTIVE_BALANCE_INCREMENT: number; // Time parameters @@ -85,10 +85,17 @@ export type BeaconPreset = { // ELECTRA MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: number; +<<<<<<< HEAD MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: number; MAX_ATTESTER_SLASHINGS_ELECTRA: number; MAX_ATTESTATIONS_ELECTRA: number; MAX_EFFECTIVE_BALANCE: number; +======= + MAX_EXECUTION_LAYER_EXITS: number; + MAX_EFFECTIVE_BALANCE_ELECTRA: number; + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: number; + MIN_ACTIVATION_BALANCE: number; +>>>>>>> 90ac864d5a4 (Update presets) PENDING_BALANCE_DEPOSITS_LIMIT: number; PENDING_PARTIAL_WITHDRAWALS_LIMIT: number; PENDING_CONSOLIDATIONS_LIMIT: number; @@ -184,6 +191,8 @@ export const beaconPresetTypes: BeaconPresetTypes = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: "number", MAX_ATTESTER_SLASHINGS_ELECTRA: "number", MAX_ATTESTATIONS_ELECTRA: "number", + MAX_EFFECTIVE_BALANCE_ELECTRA: "number", + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: "number", MIN_ACTIVATION_BALANCE: "number", PENDING_BALANCE_DEPOSITS_LIMIT: "number", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "number", diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index 59d65953cd04..190a22218ed8 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -41,7 +41,7 @@ export function slashValidator( // state.slashings is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because: // - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset() // - max slashed validators per epoch is SLOTS_PER_EPOCH * MAX_ATTESTER_SLASHINGS * MAX_VALIDATORS_PER_COMMITTEE which is 32 * 2 * 2048 = 131072 on mainnet - // - with that and 2048_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 + // - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE or 2048_000_000_000 MAX_EFFECTIVE_BALANCE_ELECTRA, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 // - we don't need to compute the total slashings from state.slashings, it's handled by totalSlashingsByIncrement in EpochCache const slashingIndex = epoch % EPOCHS_PER_SLASHINGS_VECTOR; state.slashings.set(slashingIndex, (state.slashings.get(slashingIndex) ?? 0) + effectiveBalance); diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index dfc6ff11c74f..6bf9bb9bc6e7 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -76,7 +76,7 @@ export function processEpoch( // state.slashings is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because: // - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset() // - max slashed validators per epoch is SLOTS_PER_EPOCH * MAX_ATTESTER_SLASHINGS * MAX_VALIDATORS_PER_COMMITTEE which is 32 * 2 * 2048 = 131072 on mainnet - // - with that and 2048_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 + // - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE or 2048_000_000_000 MAX_EFFECTIVE_BALANCE_ELECTRA, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 if (maxValidatorsPerStateSlashing > maxSafeValidators) { throw new Error("Lodestar does not support this network, parameters don't fit number value inside state.slashings"); } diff --git a/packages/types/src/phase0/sszTypes.ts b/packages/types/src/phase0/sszTypes.ts index ffa3483c3fb8..4a04701b789d 100644 --- a/packages/types/src/phase0/sszTypes.ts +++ b/packages/types/src/phase0/sszTypes.ts @@ -236,7 +236,7 @@ export const RandaoMixes = new VectorCompositeType(Bytes32, EPOCHS_PER_HISTORICA * This is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because: * - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset() * - max slashed validators per epoch is SLOTS_PER_EPOCH * MAX_ATTESTER_SLASHINGS * MAX_VALIDATORS_PER_COMMITTEE which is 32 * 2 * 2048 = 131072 on mainnet - * - with that and 2048_000_000_000 MAX_EFFECTIVE_BALANCE, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 + * - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE or 2048_000_000_000 MAX_EFFECTIVE_BALANCE_ELECTRA, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 * - we don't need to compute the total slashings from state.slashings, it's handled by totalSlashingsByIncrement in EpochCache */ export const Slashings = new VectorBasicType(UintNum64, EPOCHS_PER_SLASHINGS_VECTOR); diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index ad48ee4fd241..45a6432ee335 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -227,6 +227,8 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Wed, 13 Mar 2024 17:28:15 +0800 Subject: [PATCH 07/41] Update config --- packages/config/README.md | 2 +- packages/config/src/chainConfig/configs/mainnet.ts | 9 +++++++-- packages/config/src/chainConfig/configs/minimal.ts | 8 ++++++-- packages/config/src/chainConfig/types.ts | 3 ++- packages/validator/test/unit/utils/interopConfigs.ts | 12 ++++++++---- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/config/README.md b/packages/config/README.md index b64726d5ba94..46ce14bac5dc 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -54,7 +54,7 @@ import {createChainConfig, ChainConfig, chainConfigFromJson} from "@lodestar/con // example config let chainConfigObj: Record = { // phase0 - MIN_PER_EPOCH_CHURN_LIMIT: 128000000000, + MIN_PER_EPOCH_CHURN_LIMIT: 4, CHURN_LIMIT_QUOTIENT: 65536, MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 128, MIN_GENESIS_TIME: 1621504614, diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index a5a7dc9c0c3a..084a439b47c8 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -74,8 +74,8 @@ export const chainConfig: ChainConfig = { INACTIVITY_SCORE_RECOVERY_RATE: 16, // 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000, - // 2**7 * 10**9 (= 128,000,000,000) - MIN_PER_EPOCH_CHURN_LIMIT: 128000000000, + // 2**2 (= 4) + MIN_PER_EPOCH_CHURN_LIMIT: 4, // 2**8 * 10**9 (= 256,000,000,000) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 256000000000, // 2**16 (= 65,536) @@ -102,4 +102,9 @@ export const chainConfig: ChainConfig = { // Deneb // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, + + + // Electra + // 2**7 * 10**9 (= 128,000,000,000) + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000, }; diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index c5f6dda82954..764d9b7744c1 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -70,8 +70,8 @@ export const chainConfig: ChainConfig = { INACTIVITY_SCORE_RECOVERY_RATE: 16, // 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000, - // 2**6 * 10**9 (= 64,000,000,000) - MIN_PER_EPOCH_CHURN_LIMIT: 64000000000, + // 2**2 (= 4) + MIN_PER_EPOCH_CHURN_LIMIT: 2, // 2**7 * 10**9 (= 128,000,000,000) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 128000000000, // [customized] scale queue churn at much lower validator counts for testing @@ -99,4 +99,8 @@ export const chainConfig: ChainConfig = { // Deneb // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, + + // Electra + // 2**6 * 10**9 (= 64,000,000,000) + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000, }; diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index c35d6af21808..bbb734583de3 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -58,6 +58,7 @@ export type ChainConfig = { MIN_PER_EPOCH_CHURN_LIMIT: number; MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: number; CHURN_LIMIT_QUOTIENT: number; + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: number; // Fork choice PROPOSER_SCORE_BOOST: number; @@ -73,7 +74,6 @@ export type ChainConfig = { // Networking MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: number; - // Churn limits }; export const chainConfigTypes: SpecTypes = { @@ -122,6 +122,7 @@ export const chainConfigTypes: SpecTypes = { MIN_PER_EPOCH_CHURN_LIMIT: "number", MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: "number", CHURN_LIMIT_QUOTIENT: "number", + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: "number", // Fork choice PROPOSER_SCORE_BOOST: "number", diff --git a/packages/validator/test/unit/utils/interopConfigs.ts b/packages/validator/test/unit/utils/interopConfigs.ts index cf8417f385a2..05a3749bf7d7 100644 --- a/packages/validator/test/unit/utils/interopConfigs.ts +++ b/packages/validator/test/unit/utils/interopConfigs.ts @@ -23,7 +23,8 @@ export const lightHouseRopstenConfig = { INACTIVITY_SCORE_BIAS: "4", INACTIVITY_SCORE_RECOVERY_RATE: "16", EJECTION_BALANCE: "16000000000", - MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", + MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: "128000000000", CHURN_LIMIT_QUOTIENT: "65536", PROPOSER_SCORE_BOOST: "40", DEPOSIT_CHAIN_ID: "3", @@ -150,7 +151,8 @@ export const prysmRopstenConfig = { MIN_EPOCHS_TO_INACTIVITY_PENALTY: "4", MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "100000", MIN_GENESIS_TIME: "1653318000", - MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", + MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: "128000000000", MIN_SEED_LOOKAHEAD: "1", MIN_SLASHING_PENALTY_QUOTIENT: "128", MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: "64", @@ -245,7 +247,8 @@ export const tekuRopstenConfig = { DOMAIN_RANDAO: "0x02000000", EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: "256", EPOCHS_PER_HISTORICAL_VECTOR: "65536", - MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", + MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: "128000000000", MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: "64", TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: "16", EPOCHS_PER_ETH1_VOTING_PERIOD: "64", @@ -370,7 +373,8 @@ export const nimbusRopstenConfig = { INACTIVITY_SCORE_BIAS: "4", INACTIVITY_SCORE_RECOVERY_RATE: "16", EJECTION_BALANCE: "16000000000", - MIN_PER_EPOCH_CHURN_LIMIT: "128000000000", + MIN_PER_EPOCH_CHURN_LIMIT: "4", + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: "128000000000", CHURN_LIMIT_QUOTIENT: "65536", PROPOSER_SCORE_BOOST: "40", DEPOSIT_CHAIN_ID: "3", From 9ff94c56f55a9b002097d6ac6b25388bbe328bcb Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 13 Mar 2024 17:53:33 +0800 Subject: [PATCH 08/41] Add todo comments --- packages/params/src/index.ts | 13 ++++++++----- .../src/block/initiateValidatorExit.ts | 2 +- .../state-transition/src/block/processOperations.ts | 2 ++ .../state-transition/src/block/slashValidator.ts | 7 +++++-- packages/state-transition/src/cache/epochCache.ts | 4 ++-- .../src/cache/epochTransitionCache.ts | 6 +++--- packages/state-transition/src/epoch/index.ts | 5 +++++ .../src/epoch/processEffectiveBalanceUpdates.ts | 2 +- .../src/epoch/processRegistryUpdates.ts | 8 +++++++- packages/state-transition/src/util/epoch.ts | 4 ++++ packages/state-transition/src/util/genesis.ts | 4 ++-- .../state-transition/test/perf/analyzeEpochs.ts | 3 +++ 12 files changed, 43 insertions(+), 17 deletions(-) diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 04015ed5d7df..32a5d6b3cd12 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -44,12 +44,7 @@ export const { HYSTERESIS_DOWNWARD_MULTIPLIER, HYSTERESIS_UPWARD_MULTIPLIER, MIN_DEPOSIT_AMOUNT, - MIN_ACTIVATION_BALANCE, MAX_EFFECTIVE_BALANCE, - PENDING_BALANCE_DEPOSITS_LIMIT, - PENDING_PARTIAL_WITHDRAWALS_LIMIT, - PENDING_CONSOLIDATIONS_LIMIT, - MAX_CONSOLIDATIONS, EFFECTIVE_BALANCE_INCREMENT, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH, @@ -99,6 +94,14 @@ export const { MAX_BLOBS_PER_BLOCK, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, + MAX_EFFECTIVE_BALANCE_ELECTRA, + MIN_ACTIVATION_BALANCE, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA, + MAX_CONSOLIDATIONS, + MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD, MAX_ATTESTER_SLASHINGS_ELECTRA, diff --git a/packages/state-transition/src/block/initiateValidatorExit.ts b/packages/state-transition/src/block/initiateValidatorExit.ts index e34d4dda7002..ae91ab0f2589 100644 --- a/packages/state-transition/src/block/initiateValidatorExit.ts +++ b/packages/state-transition/src/block/initiateValidatorExit.ts @@ -46,6 +46,6 @@ export function initiateValidatorExit( } // set validator exit epoch and withdrawable epoch - validator.exitEpoch = epochCtx.exitQueueEpoch; + validator.exitEpoch = epochCtx.exitQueueEpoch; // TODO Electra: calculate exit epoch validator.withdrawableEpoch = epochCtx.exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; } diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index b4c8975f23bd..b4f267dd5e36 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -70,5 +70,7 @@ export function processOperations( for (const depositReceipt of (body as electra.BeaconBlockBody).executionPayload.depositReceipts) { processDepositReceipt(fork, state as CachedBeaconStateElectra, depositReceipt); } + // TODO Electra: add process_execution_layer_withdraw_request + // TODO Electra: add process_consolidation } } diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index 190a22218ed8..95f59645f2ce 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -6,6 +6,7 @@ import { MIN_SLASHING_PENALTY_QUOTIENT, MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR, MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX, + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA, PROPOSER_REWARD_QUOTIENT, PROPOSER_WEIGHT, TIMELY_TARGET_FLAG_INDEX, @@ -51,8 +52,10 @@ export function slashValidator( fork === ForkSeq.phase0 ? MIN_SLASHING_PENALTY_QUOTIENT : fork === ForkSeq.altair - ? MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR - : MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX; + ? MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR + : fork === ForkSeq.bellatrix + ? MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX + : MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA; decreaseBalance(state, slashedIndex, Math.floor(effectiveBalance / minSlashingPenaltyQuotient)); // apply proposer and whistleblower rewards diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index ade519719fc0..6dc9742e3d31 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -309,7 +309,7 @@ export class EpochCache { const nextEpoch = currentEpoch + 1; let totalActiveBalanceIncrements = 0; - let exitQueueEpoch = computeActivationExitEpoch(currentEpoch); + let exitQueueEpoch = computeActivationExitEpoch(currentEpoch); // TODO Electra: New exit epoch calculation let exitQueueChurn = 0; const validators = state.validators.getAllReadonlyValues(); @@ -588,7 +588,7 @@ export class EpochCache { ); // Maybe advance exitQueueEpoch at the end of the epoch if there haven't been any exists for a while - const exitQueueEpoch = computeActivationExitEpoch(currEpoch); + const exitQueueEpoch = computeActivationExitEpoch(currEpoch); // TODO Electra: New exit epoch calculation if (exitQueueEpoch > this.exitQueueEpoch) { this.exitQueueEpoch = exitQueueEpoch; this.exitQueueChurn = 0; diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index dc4edf26e084..4a8fa7286d6c 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -89,7 +89,7 @@ export interface EpochTransitionCache { /** * Indices of validators that just joined and will be eligible for the active queue. * ``` - * v.activationEligibilityEpoch === FAR_FUTURE_EPOCH && v.effectiveBalance === MAX_EFFECTIVE_BALANCE + * v.activationEligibilityEpoch === FAR_FUTURE_EPOCH && v.effectiveBalance >= MAX_EFFECTIVE_BALANCE * ``` * All validators in indicesEligibleForActivationQueue get activationEligibilityEpoch set. So it can only include * validators that have just joined the registry through a valid full deposit(s). @@ -243,12 +243,12 @@ export function beforeProcessEpoch( // def is_eligible_for_activation_queue(validator: Validator) -> bool: // return ( // validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH - // and validator.effective_balance == MAX_EFFECTIVE_BALANCE + // and validator.effective_balance >= MAX_EFFECTIVE_BALANCE # [Modified in Electra] // ) // ``` if ( validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH && - validator.effectiveBalance === MAX_EFFECTIVE_BALANCE + validator.effectiveBalance >= MAX_EFFECTIVE_BALANCE ) { indicesEligibleForActivationQueue.push(i); } diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 6bf9bb9bc6e7..0df2012978bb 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -120,6 +120,11 @@ export function processEpoch( processEth1DataReset(state, cache); + if (fork >= ForkSeq.electra) { + // TODO Electra: processPendingBalanceDeposits + // TODO Electra: processPendingConsolidations + } + { const timer = metrics?.epochTransitionStepTime.startTimer({ step: EpochTransitionStep.processEffectiveBalanceUpdates, diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index 5f1df35b7215..87a74f0bde86 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -49,7 +49,7 @@ export function processEffectiveBalanceUpdates(state: CachedBeaconStateAllForks, // Too small. Check effectiveBalance < MAX_EFFECTIVE_BALANCE to prevent unnecessary updates (effectiveBalance < MAX_EFFECTIVE_BALANCE && effectiveBalance < balance - UPWARD_THRESHOLD) ) { - effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE); + effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE); // TODO Electra: Minimum is now dictated by EFFECTIVE_BALANCE_LIMIT instead of MAX_EB // Update the state tree // Should happen rarely, so it's fine to update the tree const validator = validators.get(i); diff --git a/packages/state-transition/src/epoch/processRegistryUpdates.ts b/packages/state-transition/src/epoch/processRegistryUpdates.ts index 0591f982d1d5..99dcc7d23c2f 100644 --- a/packages/state-transition/src/epoch/processRegistryUpdates.ts +++ b/packages/state-transition/src/epoch/processRegistryUpdates.ts @@ -1,6 +1,7 @@ import {computeActivationExitEpoch} from "../util/index.js"; import {initiateValidatorExit} from "../block/index.js"; import {EpochTransitionCache, CachedBeaconStateAllForks} from "../types.js"; +import { ForkSeq } from "@lodestar/params"; /** * Update validator registry for validators that activate + exit @@ -16,7 +17,7 @@ import {EpochTransitionCache, CachedBeaconStateAllForks} from "../types.js"; * - indicesEligibleForActivationQueue: 0 * - indicesToEject: 0 */ -export function processRegistryUpdates(state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { +export function processRegistryUpdates(fork: ForkSeq = ForkSeq.phase0, state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { const {epochCtx} = state; // Get the validators sub tree once for all the loop @@ -31,6 +32,11 @@ export function processRegistryUpdates(state: CachedBeaconStateAllForks, cache: initiateValidatorExit(state, validators.get(index)); } + // TODO Electra: New logic to set validator.activation_epoch + if (fork >= ForkSeq.electra) { + + } + // set new activation eligibilities for (const index of cache.indicesEligibleForActivationQueue) { validators.get(index).activationEligibilityEpoch = epochCtx.epoch + 1; diff --git a/packages/state-transition/src/util/epoch.ts b/packages/state-transition/src/util/epoch.ts index b7febf45c110..4da1b221da7d 100644 --- a/packages/state-transition/src/util/epoch.ts +++ b/packages/state-transition/src/util/epoch.ts @@ -39,6 +39,10 @@ export function computeActivationExitEpoch(epoch: Epoch): Epoch { return epoch + 1 + MAX_SEED_LOOKAHEAD; } +/** + * TODO Electra: compute_exit_epoch_and_update_churn() + */ + /** * Return the current epoch of the given state. */ diff --git a/packages/state-transition/src/util/genesis.ts b/packages/state-transition/src/util/genesis.ts index 6c700436d7f6..797ca7cc1aee 100644 --- a/packages/state-transition/src/util/genesis.ts +++ b/packages/state-transition/src/util/genesis.ts @@ -180,12 +180,12 @@ 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), MAX_EFFECTIVE_BALANCE); // TODO Electra: Double check if we use MIN_ACTIVATION_BALANCE validator.effectiveBalance = effectiveBalance; epochCtx.effectiveBalanceIncrementsSet(i, effectiveBalance); - if (validator.effectiveBalance === MAX_EFFECTIVE_BALANCE) { + if (validator.effectiveBalance >= MAX_EFFECTIVE_BALANCE) { validator.activationEligibilityEpoch = GENESIS_EPOCH; validator.activationEpoch = GENESIS_EPOCH; activatedValidatorCount++; diff --git a/packages/state-transition/test/perf/analyzeEpochs.ts b/packages/state-transition/test/perf/analyzeEpochs.ts index df072a75902b..3da7791b31e4 100644 --- a/packages/state-transition/test/perf/analyzeEpochs.ts +++ b/packages/state-transition/test/perf/analyzeEpochs.ts @@ -156,6 +156,9 @@ async function analyzeEpochs(network: NetworkName, fromEpoch?: number): Promise< // processRegistryUpdates: function of registry updates // processSlashingsAllForks: function of process.indicesToSlash // processSlashingsReset: free + // -- electra + // processPendingBalanceDeposits: - + // processPendingConsolidations: - // -- altair // processInactivityUpdates: - // processParticipationFlagUpdates: - From 6ea4e2db5aa39909c830d6eb2668b84362fdfcc2 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 25 Mar 2024 17:29:40 +0800 Subject: [PATCH 09/41] Update constants and params --- packages/config/src/chainConfig/configs/mainnet.ts | 4 ++-- packages/config/src/chainConfig/configs/minimal.ts | 4 ++-- packages/config/src/chainConfig/types.ts | 4 ++-- packages/params/src/index.ts | 5 ++++- packages/params/src/presets/mainnet.ts | 4 +++- packages/params/src/presets/minimal.ts | 4 +++- packages/params/src/types.ts | 4 ++++ packages/validator/src/util/params.ts | 2 ++ 8 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 084a439b47c8..9a6b611fdb59 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -105,6 +105,6 @@ export const chainConfig: ChainConfig = { // Electra - // 2**7 * 10**9 (= 128,000,000,000) - MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000, + // 2**8 * 10**9 (= 256,000,000,000) + MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000, }; diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index 764d9b7744c1..775ae42307eb 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -101,6 +101,6 @@ export const chainConfig: ChainConfig = { MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, // Electra - // 2**6 * 10**9 (= 64,000,000,000) - MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000, + // 2**7 * 10**9 (= 128,000,000,000) + MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000, }; diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index bbb734583de3..0c474b27dcf1 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -58,7 +58,7 @@ export type ChainConfig = { MIN_PER_EPOCH_CHURN_LIMIT: number; MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: number; CHURN_LIMIT_QUOTIENT: number; - MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: number; + MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: number; // Fork choice PROPOSER_SCORE_BOOST: number; @@ -122,7 +122,7 @@ export const chainConfigTypes: SpecTypes = { MIN_PER_EPOCH_CHURN_LIMIT: "number", MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: "number", CHURN_LIMIT_QUOTIENT: "number", - MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: "number", + MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: "number", // Fork choice PROPOSER_SCORE_BOOST: "number", diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 32a5d6b3cd12..49e7ff1e19f8 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -106,6 +106,8 @@ export const { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD, MAX_ATTESTER_SLASHINGS_ELECTRA, MAX_ATTESTATIONS_ELECTRA, + WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA, + MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD, } = activePreset; //////////// @@ -127,6 +129,7 @@ export const JUSTIFICATION_BITS_LENGTH = 4; // Since the prefixes are just 1 byte, we define and use them as number export const BLS_WITHDRAWAL_PREFIX = 0; export const ETH1_ADDRESS_WITHDRAWAL_PREFIX = 1; +export const COMPOUNDING_WITHDRAWAL_PREFIX = 2; // Domain types @@ -141,7 +144,7 @@ export const DOMAIN_SYNC_COMMITTEE = Uint8Array.from([7, 0, 0, 0]); export const DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = Uint8Array.from([8, 0, 0, 0]); export const DOMAIN_CONTRIBUTION_AND_PROOF = Uint8Array.from([9, 0, 0, 0]); export const DOMAIN_BLS_TO_EXECUTION_CHANGE = Uint8Array.from([10, 0, 0, 0]); -export const DOMAIN_BLOB_SIDECAR = Uint8Array.from([11, 0, 0, 0]); +export const DOMAIN_CONSOLIDATION = Uint8Array.from([11, 0, 0, 0]); // Application specific domains diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index a81030bb2e1c..3c356bb12127 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -127,10 +127,12 @@ export const mainnetPreset: BeaconPreset = { // 2**11 * 10**9 (= 2,048,000,000,000) Gwei MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, // 2**16 (= 65536) - MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 65536, + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096, MIN_ACTIVATION_BALANCE: 32000000000, PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728, PENDING_CONSOLIDATIONS_LIMIT: 262144, MAX_CONSOLIDATIONS: 1, + WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096, + MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 4ea70d1fd555..3b7f7184eabe 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -128,10 +128,12 @@ export const minimalPreset: BeaconPreset = { // 2**11 * 10**9 (= 2,048,000,000,000) Gwei MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, // 2**16 (= 65536) - MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 65536, + MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096, MIN_ACTIVATION_BALANCE: 32000000000, PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64, PENDING_CONSOLIDATIONS_LIMIT: 64, MAX_CONSOLIDATIONS: 1, + WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096, + MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8, }; diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index aabb00c623ec..3cdc33d9c38e 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -100,6 +100,8 @@ export type BeaconPreset = { PENDING_PARTIAL_WITHDRAWALS_LIMIT: number; PENDING_CONSOLIDATIONS_LIMIT: number; MAX_CONSOLIDATIONS: number; + WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: number; + MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: number; }; /** @@ -198,6 +200,8 @@ export const beaconPresetTypes: BeaconPresetTypes = { PENDING_PARTIAL_WITHDRAWALS_LIMIT: "number", PENDING_CONSOLIDATIONS_LIMIT: "number", MAX_CONSOLIDATIONS: "number", + WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: "number", + MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: "number", }; type BeaconPresetTypes = { diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 45a6432ee335..26466f28c7b4 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -234,5 +234,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Mon, 25 Mar 2024 22:34:18 +0800 Subject: [PATCH 10/41] Impl new process withdrawal --- .../chain/produceBlock/produceBlockBody.ts | 11 +++- packages/state-transition/src/block/index.ts | 14 ++++- .../src/block/processWithdrawals.ts | 63 +++++++++++++++---- packages/state-transition/src/epoch/index.ts | 2 +- .../src/epoch/processRegistryUpdates.ts | 2 +- packages/state-transition/src/util/capella.ts | 49 ++++++++++++++- .../perf/block/processWithdrawals.test.ts | 3 +- .../unit/block/processWithdrawals.test.ts | 4 +- packages/validator/src/util/params.ts | 1 + 9 files changed, 130 insertions(+), 19 deletions(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index c1c1d5eaa447..ce36b73dadb7 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -11,11 +11,13 @@ import { capella, deneb, Wei, + electra, } from "@lodestar/types"; import { CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix, + CachedBeaconStateElectra, CachedBeaconStateExecutions, computeEpochAtSlot, computeTimeAtSlot, @@ -555,7 +557,7 @@ function preparePayloadAttributes( suggestedFeeRecipient: feeRecipient, }; - if (ForkSeq[fork] >= ForkSeq.capella) { + if (ForkSeq[fork] === ForkSeq.capella || ForkSeq[fork] === ForkSeq.electra) { (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( prepareState as CachedBeaconStateCapella ).withdrawals; @@ -565,6 +567,13 @@ function preparePayloadAttributes( (payloadAttributes as deneb.SSEPayloadAttributes["payloadAttributes"]).parentBeaconBlockRoot = parentBlockRoot; } + if (ForkSeq[fork] >= ForkSeq.electra) { + (payloadAttributes as electra.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( + prepareState as CachedBeaconStateElectra + ).withdrawals; + } + + return payloadAttributes; } diff --git a/packages/state-transition/src/block/index.ts b/packages/state-transition/src/block/index.ts index b235f7ca24ef..5e50aac48abd 100644 --- a/packages/state-transition/src/block/index.ts +++ b/packages/state-transition/src/block/index.ts @@ -1,5 +1,5 @@ import {ForkSeq} from "@lodestar/params"; -import {allForks, altair, capella} from "@lodestar/types"; +import {allForks, altair, capella, electra} from "@lodestar/types"; import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js"; import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix} from "../types.js"; import {processExecutionPayload} from "./processExecutionPayload.js"; @@ -12,6 +12,7 @@ import {processBlobKzgCommitments} from "./processBlobKzgCommitments.js"; import {BlockExternalData, DataAvailableStatus} from "./externalData.js"; import {processWithdrawals} from "./processWithdrawals.js"; import {ProcessBlockOpts} from "./types.js"; +import { CachedBeaconStateElectra } from "../../lib/types.js"; // Spec tests export { @@ -45,12 +46,21 @@ export function processBlock( const fullOrBlindedPayload = getFullOrBlindedPayload(block); // TODO Deneb: Allow to disable withdrawals for interop testing // https://github.com/ethereum/consensus-specs/blob/b62c9e877990242d63aa17a2a59a49bc649a2f2e/specs/eip4844/beacon-chain.md#disabling-withdrawals - if (fork >= ForkSeq.capella) { + if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { processWithdrawals( + fork, state as CachedBeaconStateCapella, fullOrBlindedPayload as capella.FullOrBlindedExecutionPayload ); } + if (fork >= ForkSeq.electra) { + processWithdrawals( + fork, + state as CachedBeaconStateElectra, + fullOrBlindedPayload as capella.FullOrBlindedExecutionPayload + ); + } + processExecutionPayload(fork, state as CachedBeaconStateBellatrix, block.body, externalData); } diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index ddea73c27a26..4d46d3466cf3 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -4,16 +4,21 @@ import { MAX_EFFECTIVE_BALANCE, MAX_WITHDRAWALS_PER_PAYLOAD, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP, + ForkSeq, + MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD, + FAR_FUTURE_EPOCH, + MIN_ACTIVATION_BALANCE, } from "@lodestar/params"; -import {CachedBeaconStateCapella} from "../types.js"; -import {decreaseBalance, hasEth1WithdrawalCredential, isCapellaPayloadHeader} from "../util/index.js"; +import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; +import {decreaseBalance, getValidatorExcessBalance, hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, isCapellaPayloadHeader, isFullyWithdrawableValidator, isPartiallyWithdrawableValidator} from "../util/index.js"; export function processWithdrawals( - state: CachedBeaconStateCapella, + fork: ForkSeq, + state: CachedBeaconStateCapella | CachedBeaconStateElectra, payload: capella.FullOrBlindedExecutionPayload ): void { - const {withdrawals: expectedWithdrawals} = getExpectedWithdrawals(state); + const {withdrawals: expectedWithdrawals} = getExpectedWithdrawals(fork, state); const numWithdrawals = expectedWithdrawals.length; if (isCapellaPayloadHeader(payload)) { @@ -43,6 +48,11 @@ export function processWithdrawals( decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount)); } + if (fork >= ForkSeq.electra) { + const stateElectra = state as CachedBeaconStateElectra; + // TODO Electra: stateElectra.pendingPartialWithdrawals need to implement slicing mechanism for ListCompositeTreeViewDU + } + // Update the nextWithdrawalIndex if (expectedWithdrawals.length > 0) { const latestWithdrawal = expectedWithdrawals[expectedWithdrawals.length - 1]; @@ -62,18 +72,46 @@ export function processWithdrawals( } } -export function getExpectedWithdrawals(state: CachedBeaconStateCapella): { +export function getExpectedWithdrawals(fork: ForkSeq, state: CachedBeaconStateCapella | CachedBeaconStateElectra): { withdrawals: capella.Withdrawal[]; sampledValidators: number; + partialWithdrawalsCount: number; } { const epoch = state.epochCtx.epoch; let withdrawalIndex = state.nextWithdrawalIndex; const {validators, balances, nextWithdrawalValidatorIndex} = state; const bound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP); - let n = 0; const withdrawals: capella.Withdrawal[] = []; + + if (fork >= ForkSeq.electra) { + const stateElectra = state as CachedBeaconStateElectra; + + for (const withdrawal of stateElectra.pendingPartialWithdrawals.getAllReadonly()) { + if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD) { + break; + } + + const validator = validators.getReadonly(withdrawal.index); + + if (validator.exitEpoch === FAR_FUTURE_EPOCH && balances.get(withdrawalIndex) > MIN_ACTIVATION_BALANCE) { + const balanceOverMinActivationBalance = BigInt(balances.get(withdrawalIndex) - MIN_ACTIVATION_BALANCE); + const withdrawableBalance = balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount; + withdrawals.push({ + index: withdrawalIndex, + validatorIndex: withdrawal.index, + address: validator.withdrawalCredentials.slice(12), + amount: withdrawableBalance, + }) + withdrawalIndex++; + } + } + + } + + const partialWithdrawalsCount = withdrawals.length; + let n = 0; // Just run a bounded loop max iterating over all withdrawals // however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD for (n = 0; n < bound; n++) { @@ -82,13 +120,16 @@ export function getExpectedWithdrawals(state: CachedBeaconStateCapella): { // It's most likely for validators to not have set eth1 credentials, than having 0 balance const validator = validators.getReadonly(validatorIndex); - if (!hasEth1WithdrawalCredential(validator.withdrawalCredentials)) { + if ((fork === ForkSeq.capella || fork === ForkSeq.deneb) && !hasEth1WithdrawalCredential(validator.withdrawalCredentials)) { + continue; + } + if (fork === ForkSeq.electra && (!hasEth1WithdrawalCredential(validator.withdrawalCredentials) && !hasCompoundingWithdrawalCredential(validator.withdrawalCredentials))) { continue; } const balance = balances.get(validatorIndex); - if (balance > 0 && validator.withdrawableEpoch <= epoch) { + if (isFullyWithdrawableValidator(fork, validator, balance, epoch)) { withdrawals.push({ index: withdrawalIndex, validatorIndex, @@ -96,12 +137,12 @@ export function getExpectedWithdrawals(state: CachedBeaconStateCapella): { amount: BigInt(balance), }); withdrawalIndex++; - } else if (validator.effectiveBalance === MAX_EFFECTIVE_BALANCE && balance > MAX_EFFECTIVE_BALANCE) { + } else if (isPartiallyWithdrawableValidator(fork, validator, balance, epoch)) { withdrawals.push({ index: withdrawalIndex, validatorIndex, address: validator.withdrawalCredentials.slice(12), - amount: BigInt(balance - MAX_EFFECTIVE_BALANCE), + amount: BigInt(getValidatorExcessBalance(validator.withdrawalCredentials, balance)), }); withdrawalIndex++; } @@ -112,5 +153,5 @@ export function getExpectedWithdrawals(state: CachedBeaconStateCapella): { } } - return {withdrawals, sampledValidators: n}; + return {withdrawals, sampledValidators: n, partialWithdrawalsCount}; } diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 0df2012978bb..405862cf3cfa 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -100,7 +100,7 @@ export function processEpoch( // processRewardsAndPenalties(state, cache); { const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.processRegistryUpdates}); - processRegistryUpdates(state, cache); + processRegistryUpdates(fork, state, cache); timer?.(); } diff --git a/packages/state-transition/src/epoch/processRegistryUpdates.ts b/packages/state-transition/src/epoch/processRegistryUpdates.ts index 99dcc7d23c2f..b1bdfb31e42a 100644 --- a/packages/state-transition/src/epoch/processRegistryUpdates.ts +++ b/packages/state-transition/src/epoch/processRegistryUpdates.ts @@ -17,7 +17,7 @@ import { ForkSeq } from "@lodestar/params"; * - indicesEligibleForActivationQueue: 0 * - indicesToEject: 0 */ -export function processRegistryUpdates(fork: ForkSeq = ForkSeq.phase0, state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { +export function processRegistryUpdates(fork: ForkSeq, state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { const {epochCtx} = state; // Get the validators sub tree once for all the loop diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index dfedc2778c75..d205b24d93ce 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -1,4 +1,10 @@ -import {ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params"; +import {COMPOUNDING_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX, ForkSeq, MAX_EFFECTIVE_BALANCE, MAX_EFFECTIVE_BALANCE_ELECTRA, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; + +type ValidatorCredential = { + effectiveBalance: number, + withdrawableEpoch: number, + withdrawalCredentials: Uint8Array +} /** * https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential @@ -6,3 +12,44 @@ import {ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params"; export function hasEth1WithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return withdrawalCredentials[0] === ETH1_ADDRESS_WITHDRAWAL_PREFIX; } + +export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { + return withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX; +} + +export function getValidatorExcessBalance(withdrawalCredentials: Uint8Array, validatorBalance: number): number { + // Compounding withdrawal credential only available since Electra + if (hasCompoundingWithdrawalCredential(withdrawalCredentials)) { + return validatorBalance - MAX_EFFECTIVE_BALANCE_ELECTRA; + } else if (hasEth1WithdrawalCredential(withdrawalCredentials)) { + return validatorBalance - MIN_ACTIVATION_BALANCE; + } + + return 0; +} + +export function isFullyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorCredential, balance: number, epoch: number) { + const {withdrawableEpoch, withdrawalCredentials: withdrawalCredential} = validatorCredential; + if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { + return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; + } + + if (fork === ForkSeq.electra) { + return (hasCompoundingWithdrawalCredential(withdrawalCredential) || hasEth1WithdrawalCredential(withdrawalCredential)) && withdrawableEpoch <= epoch && balance > 0; + } + + return false; +} + +export function isPartiallyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorCredential, balance: number, epoch: number) { + const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; + if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { + return hasEth1WithdrawalCredential(withdrawalCredential) && effectiveBalance === MAX_EFFECTIVE_BALANCE && balance > MAX_EFFECTIVE_BALANCE; + } + + if (fork === ForkSeq.electra) { + return (hasCompoundingWithdrawalCredential(withdrawalCredential) || hasEth1WithdrawalCredential(withdrawalCredential)) && getValidatorExcessBalance(withdrawalCredential, balance) > 0; + } + + return false; +} \ No newline at end of file diff --git a/packages/state-transition/test/perf/block/processWithdrawals.test.ts b/packages/state-transition/test/perf/block/processWithdrawals.test.ts index d7136d224e7a..0edd2b39dc02 100644 --- a/packages/state-transition/test/perf/block/processWithdrawals.test.ts +++ b/packages/state-transition/test/perf/block/processWithdrawals.test.ts @@ -3,6 +3,7 @@ import {CachedBeaconStateCapella} from "../../../src/index.js"; import {getExpectedWithdrawals} from "../../../src/block/processWithdrawals.js"; import {numValidators} from "../util.js"; import {getExpectedWithdrawalsTestData, WithdrawalOpts} from "../../utils/capella.js"; +import { ForkSeq } from "@lodestar/params"; // PERF: Fixed cost for MAX_WITHDRAWALS_PER_PAYLOAD probes // + cost 'proportional' to $VALIDATOR_COUNT with balances under MAX_EFFECTIVE_BALANCE or @@ -69,7 +70,7 @@ describe("getExpectedWithdrawals", () => { return opts.cache ? state : state.clone(true); }, fn: (state) => { - const {sampledValidators} = getExpectedWithdrawals(state); + const {sampledValidators} = getExpectedWithdrawals(ForkSeq.capella, state); // TODO Electra: Do test for electra if (sampledValidators !== opts.sampled) { throw Error(`Wrong sampledValidators ${sampledValidators} != ${opts.sampled}`); } diff --git a/packages/state-transition/test/unit/block/processWithdrawals.test.ts b/packages/state-transition/test/unit/block/processWithdrawals.test.ts index 2841da635472..5aefec40dcec 100644 --- a/packages/state-transition/test/unit/block/processWithdrawals.test.ts +++ b/packages/state-transition/test/unit/block/processWithdrawals.test.ts @@ -3,6 +3,7 @@ import {getExpectedWithdrawals} from "../../../src/block/processWithdrawals.js"; import {numValidators} from "../../perf/util.js"; import {getExpectedWithdrawalsTestData, WithdrawalOpts} from "../../utils/capella.js"; import {beforeValue} from "../../utils/beforeValue.js"; +import { ForkSeq } from "@lodestar/params"; describe("getExpectedWithdrawals", () => { const vc = numValidators; @@ -36,8 +37,9 @@ describe("getExpectedWithdrawals", () => { // Clone true to drop cache const state = beforeValue(() => getExpectedWithdrawalsTestData(vc, opts).clone(true)); + // TODO Electra: Add test for electra it(`getExpectedWithdrawals ${vc} ${caseID}`, () => { - const {sampledValidators, withdrawals} = getExpectedWithdrawals(state.value); + const {sampledValidators, withdrawals} = getExpectedWithdrawals(ForkSeq.capella, state.value); expect(sampledValidators).toBe(opts.sampled); expect(withdrawals.length).toBe(opts.withdrawals); }); diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 26466f28c7b4..ae5fcf234e90 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -236,5 +236,6 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Tue, 26 Mar 2024 16:22:53 +0800 Subject: [PATCH 11/41] Add withdrawaRequests to payload --- packages/types/src/electra/sszTypes.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 5d5be4ca0315..b358ff8d3f8f 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -133,6 +133,15 @@ export const ExecutionLayerWithdrawalRequests = new ListCompositeType( MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD ); +export const ExecutionLayerWithdrawRequest = new ContainerType( + { + sourceAddress: ExecutionAddress, + validatorPubkey: BLSPubkey, + amount: Gwei, + }, + {typeName: "ExecutionLayerWithdrawRequest", jsonCase: "eth2"} +); + export const ExecutionPayload = new ContainerType( { ...denebSsz.ExecutionPayload.fields, @@ -209,6 +218,7 @@ export const BeaconBlock = new ContainerType( { ...denebSsz.BeaconBlock.fields, body: BeaconBlockBody, // Modified in ELECTRA + body: BeaconBlockBody, // Modified in ELECTRA }, {typeName: "BeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true} ); @@ -437,12 +447,3 @@ export const SSEPayloadAttributes = new ContainerType( }, {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} ); - -export const ExecutionLayerWithdrawRequest = new ContainerType( - { - sourceAddress: ExecutionAddress, - validatorPubkey: BLSPubkey, - amount: Gwei, - }, - {typeName: "ExecutionLayerWithdrawRequest", jsonCase: "eth2"} -); \ No newline at end of file From 1e98f41613691324bc9d2a5213c2896696798eb6 Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 26 Mar 2024 16:24:01 +0800 Subject: [PATCH 12/41] Add processConsolidation --- .../src/block/processConsolidation.ts | 100 ++++++++++++++++++ .../src/block/processOperations.ts | 14 ++- .../src/signatureSets/consolidation.ts | 39 +++++++ .../src/signatureSets/index.ts | 16 ++- 4 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 packages/state-transition/src/block/processConsolidation.ts create mode 100644 packages/state-transition/src/signatureSets/consolidation.ts diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts new file mode 100644 index 000000000000..5d77e70c8da4 --- /dev/null +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -0,0 +1,100 @@ +import {toHexString} from "@chainsafe/ssz"; +import {electra, ssz} from "@lodestar/types"; +import {FAR_FUTURE_EPOCH, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params"; +import {verifyConsolidationSignature} from "../signatureSets/index.js"; + +import {CachedBeaconStateElectra} from "../types.js"; +import { isActiveValidator } from "../util/validator.js"; +import { hasExecutionWithdrawalCredential } from "../util/capella.js"; + +export function processConsolidation( + state: CachedBeaconStateElectra, + signedConsolidation: electra.SignedConsolidation, +): void { + + + assertValidConsolidation(state, signedConsolidation); + + // Initiate source validator exit and append pending consolidation + const {sourceIndex, targetIndex} = signedConsolidation.message; + const activeBalance = 0; // TODO Electra: get_active_balance() + const sourceValidator = state.validators.get(sourceIndex); + + const exitEpoch = 0; // TODO Electra: compute_consolidation_epoch_and_update_churn + sourceValidator.exitEpoch = exitEpoch; + sourceValidator.withdrawableEpoch = exitEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; + + const pendingConsolidation = ssz.electra.PendingConsolidation.toViewDU({ + sourceIndex, + targetIndex + }); + state.pendingConsolidations.push(pendingConsolidation); + +} + +function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsolidation: electra.SignedConsolidation) { + // If the pending consolidations queue is full, no consolidations are allowed in the block + if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) { + throw new Error(`Pending consolidation queue is full`); + } + + // If there is too little available consolidation churn limit, no consolidations are allowed in the block + // assert get_consolidation_churn_limit(state) > MIN_ACTIVATION_BALANCE + + + const consolidation = signedConsolidation.message; + const {sourceIndex, targetIndex} = consolidation; + + // Verify that source != target, so a consolidation cannot be used as an exit. + if (sourceIndex === targetIndex) { + throw new Error(`Consolidation source and target index cannot be the same: sourceIndex=${sourceIndex} targetIndex=${targetIndex}`); + } + + + const sourceValidator = state.validators.getReadonly(sourceIndex); + const targetValidator = state.validators.getReadonly(targetIndex); + const currentEpoch = state.epochCtx.epoch; + + // Verify the source and the target are active + if (!isActiveValidator(sourceValidator, currentEpoch)) { + throw new Error(`Consolidation source validator is not active: sourceIndex=${sourceIndex}`); + } + + if (!isActiveValidator(targetValidator, currentEpoch)) { + throw new Error(`Consolidation target validator is not active: targetIndex=${targetIndex}`); + } + + // Verify exits for source and target have not been initiated + if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH){ + throw new Error(`Consolidation source validator has initialized exit: sourceIndex=${sourceIndex}`); + } + if (targetValidator.exitEpoch !== FAR_FUTURE_EPOCH){ + throw new Error(`Consolidation target validator has initialized exit: targetIndex=${targetIndex}`); + } + + // Consolidations must specify an epoch when they become valid; they are not valid before then + if (currentEpoch < consolidation.epoch) { + throw new Error(`Consolidation epoch is after the current epoch: consolidationEpoch=${consolidation.epoch} currentEpoch=${currentEpoch}`); + } + + // Verify the source and the target have Execution layer withdrawal credentials + if (!hasExecutionWithdrawalCredential(sourceValidator.withdrawalCredentials)) { + throw new Error(`Consolidation source validator does not have execution withdrawal credentials: sourceIndex=${sourceIndex}`); + } + if (!hasExecutionWithdrawalCredential(targetValidator.withdrawalCredentials)) { + throw new Error(`Consolidation target validator does not have execution withdrawal credentials: targetIndex=${targetIndex}`); + } + + // Verify the same withdrawal address + const sourceWithdrawalAddress = toHexString(sourceValidator.withdrawalCredentials.slice(1)); + const targetWithdrawalAddress = toHexString(targetValidator.withdrawalCredentials.slice(1)); + + if (sourceWithdrawalAddress !== targetWithdrawalAddress) { + throw new Error(`Consolidation source and target withdrawal address are different: source: ${sourceWithdrawalAddress} target: ${targetWithdrawalAddress}`); + } + + // Verify consolidation is signed by the source and the target + if (!verifyConsolidationSignature(state, signedConsolidation)) { + throw new Error("Consolidation not valid"); + } +} \ No newline at end of file diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index b4f267dd5e36..4b3705bf89a2 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -12,6 +12,7 @@ import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWit import {processBlsToExecutionChange} from "./processBlsToExecutionChange.js"; import {processDepositReceipt} from "./processDepositReceipt.js"; import {ProcessBlockOpts} from "./types.js"; +import { processConsolidation } from "./processConsolidation.js"; export { processProposerSlashing, @@ -67,10 +68,15 @@ export function processOperations( } if (fork >= ForkSeq.electra) { - for (const depositReceipt of (body as electra.BeaconBlockBody).executionPayload.depositReceipts) { - processDepositReceipt(fork, state as CachedBeaconStateElectra, depositReceipt); + const stateElectra = state as CachedBeaconStateElectra; + const bodyElectra = body as electra.BeaconBlockBody; + for (const depositReceipt of bodyElectra.executionPayload.depositReceipts) { + processDepositReceipt(fork, stateElectra, depositReceipt); + } + + + for (const consolidation of bodyElectra.consolidations) { + processConsolidation(stateElectra, consolidation); } - // TODO Electra: add process_execution_layer_withdraw_request - // TODO Electra: add process_consolidation } } diff --git a/packages/state-transition/src/signatureSets/consolidation.ts b/packages/state-transition/src/signatureSets/consolidation.ts new file mode 100644 index 000000000000..7334a360affd --- /dev/null +++ b/packages/state-transition/src/signatureSets/consolidation.ts @@ -0,0 +1,39 @@ +import {DOMAIN_CONSOLIDATION, ForkName} from "@lodestar/params"; +import {electra, ssz} from "@lodestar/types"; + +import {computeSigningRoot, createAggregateSignatureSetFromComponents, ISignatureSet, SignatureSetType, verifySignatureSet} from "../util/index.js"; +import {CachedBeaconStateElectra} from "../types.js"; + +export function verifyConsolidationSignature( + state: CachedBeaconStateElectra, + signedConsolidation: electra.SignedConsolidation +): boolean { + return verifySignatureSet(getConsolidationSignatureSet(state, signedConsolidation)); +} + +/** + * Extract signatures to allow validating all block signatures at once + */ +export function getConsolidationSignatureSet( + state: CachedBeaconStateElectra, + signedConsolidation: electra.SignedConsolidation, +): ISignatureSet { + const {config} = state; + const {index2pubkey} = state.epochCtx; // TODO Electra: Use 6110 pubkey cache + const {sourceIndex, targetIndex} = signedConsolidation.message; + const sourcePubkey = index2pubkey[sourceIndex]; + const targetPubkey = index2pubkey[targetIndex]; + + // signatureFork for signing domain is fixed + const signatureFork = ForkName.phase0; + const domain = config.getDomainAtFork(signatureFork, DOMAIN_CONSOLIDATION); + const signingRoot = computeSigningRoot(ssz.electra.Consolidation, signedConsolidation.message, domain); + + return createAggregateSignatureSetFromComponents([sourcePubkey, targetPubkey], signingRoot, signedConsolidation.signature); +} + +export function getConsolidationSignatureSets(state: CachedBeaconStateElectra, signedBlock: electra.SignedBeaconBlock): ISignatureSet[] { + return signedBlock.message.body.consolidations.map((consolidation) => + getConsolidationSignatureSet(state, consolidation) + ); +} \ No newline at end of file diff --git a/packages/state-transition/src/signatureSets/index.ts b/packages/state-transition/src/signatureSets/index.ts index 05e4ad4b197a..a6bdc7f478bd 100644 --- a/packages/state-transition/src/signatureSets/index.ts +++ b/packages/state-transition/src/signatureSets/index.ts @@ -1,7 +1,7 @@ import {ForkSeq} from "@lodestar/params"; -import {allForks, altair, capella} from "@lodestar/types"; +import {allForks, altair, capella, electra} from "@lodestar/types"; import {ISignatureSet} from "../util/index.js"; -import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "../types.js"; +import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js"; import {getSyncCommitteeSignatureSet} from "../block/processSyncCommittee.js"; import {getProposerSlashingsSignatureSets} from "./proposerSlashings.js"; import {getAttesterSlashingsSignatureSets} from "./attesterSlashings.js"; @@ -10,6 +10,7 @@ import {getBlockProposerSignatureSet} from "./proposer.js"; import {getRandaoRevealSignatureSet} from "./randao.js"; import {getVoluntaryExitsSignatureSets} from "./voluntaryExits.js"; import {getBlsToExecutionChangeSignatureSets} from "./blsToExecutionChange.js"; +import {getConsolidationSignatureSets} from "./consolidation.js"; export * from "./attesterSlashings.js"; export * from "./indexedAttestation.js"; @@ -18,6 +19,7 @@ export * from "./proposerSlashings.js"; export * from "./randao.js"; export * from "./voluntaryExits.js"; export * from "./blsToExecutionChange.js"; +export * from "./consolidation.js"; /** * Includes all signatures on the block (except the deposit signatures) for verification. @@ -69,5 +71,15 @@ export function getBlockSignatureSets( } } + if (fork >= ForkSeq.electra) { + const consolidationSignatureSets = getConsolidationSignatureSets( + state as CachedBeaconStateElectra, + signedBlock as electra.SignedBeaconBlock + ); + if (consolidationSignatureSets.length > 0) { + signatureSets.push(...consolidationSignatureSets); + } + } + return signatureSets; } From df3f37672753e8949bfed29a28c9a9ab9fe6453c Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 26 Mar 2024 16:25:38 +0800 Subject: [PATCH 13/41] Add process withdraw request --- packages/state-transition/src/block/index.ts | 3 +- .../processExecutionLayerWithdrawRequest.ts | 81 +++++++++++++++++++ .../src/block/processOperations.ts | 7 +- packages/state-transition/src/util/capella.ts | 20 ++--- 4 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts diff --git a/packages/state-transition/src/block/index.ts b/packages/state-transition/src/block/index.ts index 5e50aac48abd..cc463e53cd37 100644 --- a/packages/state-transition/src/block/index.ts +++ b/packages/state-transition/src/block/index.ts @@ -1,7 +1,7 @@ import {ForkSeq} from "@lodestar/params"; import {allForks, altair, capella, electra} from "@lodestar/types"; import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js"; -import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix} from "../types.js"; +import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix, CachedBeaconStateElectra} from "../types.js"; import {processExecutionPayload} from "./processExecutionPayload.js"; import {processSyncAggregate} from "./processSyncCommittee.js"; import {processBlockHeader} from "./processBlockHeader.js"; @@ -12,7 +12,6 @@ import {processBlobKzgCommitments} from "./processBlobKzgCommitments.js"; import {BlockExternalData, DataAvailableStatus} from "./externalData.js"; import {processWithdrawals} from "./processWithdrawals.js"; import {ProcessBlockOpts} from "./types.js"; -import { CachedBeaconStateElectra } from "../../lib/types.js"; // Spec tests export { diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts new file mode 100644 index 000000000000..4b3a6c67d4cb --- /dev/null +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -0,0 +1,81 @@ +import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; +import {digest} from "@chainsafe/as-sha256"; +import {Epoch, capella, electra, phase0, ssz} from "@lodestar/types"; +import {BLS_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT} from "@lodestar/params"; +import {verifyBlsToExecutionChangeSignature} from "../signatureSets/index.js"; + +import {CachedBeaconStateElectra} from "../types.js"; +import { hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, hasExecutionWithdrawalCredential } from "../util/capella.js"; +import { isActiveValidator } from "../util/validator.js"; +import { initiateValidatorExit } from "./initiateValidatorExit.js"; + +export function processExecutionLayerWithdrawRequest( + state: CachedBeaconStateElectra, + executionLayerWithdrawRequest: electra.ExecutionLayerWithdrawRequest, +): void { + + const amount = Number(executionLayerWithdrawRequest.amount); + const {pendingPartialWithdrawals, validators, epochCtx} = state; + const {pubkey2index, config} = epochCtx; // TODO Electra: Use finalized+unfinalized pubkey cache from 6110 + const isFullExitRequest = amount === 0; + + // If partial withdrawal queue is full, only full exits are processed + if (pendingPartialWithdrawals.length >= PENDING_PARTIAL_WITHDRAWALS_LIMIT && !isFullExitRequest) { + return; + } + + const validatorIndex = pubkey2index.get(executionLayerWithdrawRequest.validatorPubkey); + + if (validatorIndex === undefined) { + throw new Error(`Can't find validator index from ExecutionLayerWithdrawRequest : pubkey=${toHexString(executionLayerWithdrawRequest.validatorPubkey)}`); + } + + const validator = state.validators.getReadonly(validatorIndex); + + if (!isValidValidator(validator, executionLayerWithdrawRequest.sourceAddress, state)) { + return; + } + + if (!isFullExitRequest && !hasCompoundingWithdrawalCredential(validator.withdrawalCredentials)) { + return; + } + + // TODO Electra: Consider caching pendingPartialWithdrawals + const pendingBalanceToWithdraw = state.pendingPartialWithdrawals.getAllReadonly().filter(item => item.index === validatorIndex).reduce((total, item) => total + Number(item.amount), 0); + const validatorBalance = state.balances.get(validatorIndex); + + // only exit validator if it has no pending withdrawals in the queue + if (isFullExitRequest && pendingBalanceToWithdraw === 0) { + initiateValidatorExit(state, validator); + } else if (validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw) { + const amountToWithdraw = Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount); + const exitQueueEpoch = 0; // TODO Electra: compute_exit_epoch_and_update_churn() + const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; + + const pendingPartialWithdrawal = ssz.electra.PartialWithdrawal.toViewDU({ + index: validatorIndex, + amount: BigInt(amount), + withdrawableEpoch, + }); + + state.pendingPartialWithdrawals.push(pendingPartialWithdrawal); + } + +} + +function isValidValidator(validator: phase0.Validator, sourceAddress: Uint8Array, state: CachedBeaconStateElectra): boolean { + const {withdrawalCredentials} = validator; + const addressStr = toHexString(withdrawalCredentials.slice(12)); + const sourceAddressStr = toHexString(sourceAddress); + const {epoch: currentEpoch, config} = state.epochCtx; + + return hasExecutionWithdrawalCredential(withdrawalCredentials) + && + addressStr === sourceAddressStr + && + isActiveValidator(validator, currentEpoch) + && + validator.exitEpoch === FAR_FUTURE_EPOCH + && + currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD; +} \ No newline at end of file diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 4b3705bf89a2..7615f4ff2af3 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -1,5 +1,5 @@ import {allForks, capella, electra} from "@lodestar/types"; -import {ForkSeq} from "@lodestar/params"; +import {ForkSeq, MAX_DEPOSITS} from "@lodestar/params"; import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; import {getEth1DepositCount} from "../util/deposit.js"; @@ -12,6 +12,7 @@ import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWit import {processBlsToExecutionChange} from "./processBlsToExecutionChange.js"; import {processDepositReceipt} from "./processDepositReceipt.js"; import {ProcessBlockOpts} from "./types.js"; +import { processExecutionLayerWithdrawRequest } from "./processExecutionLayerWithdrawRequest.js"; import { processConsolidation } from "./processConsolidation.js"; export { @@ -75,6 +76,10 @@ export function processOperations( } + for (const withdrawRequest of bodyElectra.executionPayload.withdrawaRequests) { + processExecutionLayerWithdrawRequest(stateElectra, withdrawRequest); + } + for (const consolidation of bodyElectra.consolidations) { processConsolidation(stateElectra, consolidation); } diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index d205b24d93ce..baa472a7fb5d 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -1,10 +1,8 @@ import {COMPOUNDING_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX, ForkSeq, MAX_EFFECTIVE_BALANCE, MAX_EFFECTIVE_BALANCE_ELECTRA, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; +import {phase0} from "@lodestar/types"; + +type ValidatorInfo = Pick; -type ValidatorCredential = { - effectiveBalance: number, - withdrawableEpoch: number, - withdrawalCredentials: Uint8Array -} /** * https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential @@ -17,6 +15,10 @@ export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8A return withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX; } +export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { + return hasCompoundingWithdrawalCredential(withdrawalCredentials) || hasEth1WithdrawalCredential(withdrawalCredentials); +} + export function getValidatorExcessBalance(withdrawalCredentials: Uint8Array, validatorBalance: number): number { // Compounding withdrawal credential only available since Electra if (hasCompoundingWithdrawalCredential(withdrawalCredentials)) { @@ -28,27 +30,27 @@ export function getValidatorExcessBalance(withdrawalCredentials: Uint8Array, val return 0; } -export function isFullyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorCredential, balance: number, epoch: number) { +export function isFullyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorInfo, balance: number, epoch: number) { const {withdrawableEpoch, withdrawalCredentials: withdrawalCredential} = validatorCredential; if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; } if (fork === ForkSeq.electra) { - return (hasCompoundingWithdrawalCredential(withdrawalCredential) || hasEth1WithdrawalCredential(withdrawalCredential)) && withdrawableEpoch <= epoch && balance > 0; + return hasExecutionWithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; } return false; } -export function isPartiallyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorCredential, balance: number, epoch: number) { +export function isPartiallyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorInfo, balance: number, epoch: number) { const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { return hasEth1WithdrawalCredential(withdrawalCredential) && effectiveBalance === MAX_EFFECTIVE_BALANCE && balance > MAX_EFFECTIVE_BALANCE; } if (fork === ForkSeq.electra) { - return (hasCompoundingWithdrawalCredential(withdrawalCredential) || hasEth1WithdrawalCredential(withdrawalCredential)) && getValidatorExcessBalance(withdrawalCredential, balance) > 0; + return hasExecutionWithdrawalCredential(withdrawalCredential) && getValidatorExcessBalance(withdrawalCredential, balance) > 0; } return false; From be5fd014614d25ba87dadabc84c1fdcc5158223f Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 26 Mar 2024 16:26:02 +0800 Subject: [PATCH 14/41] Update deposit and withdrawal flow --- .../chain/produceBlock/produceBlockBody.ts | 2 + .../src/block/processDeposit.ts | 50 ++++++++++++++----- .../src/block/processWithdrawals.ts | 5 +- packages/state-transition/src/epoch/index.ts | 2 +- .../epoch/processEffectiveBalanceUpdates.ts | 15 +++++- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index ce36b73dadb7..b3aa078bf2e9 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -559,6 +559,7 @@ function preparePayloadAttributes( if (ForkSeq[fork] === ForkSeq.capella || ForkSeq[fork] === ForkSeq.electra) { (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( + ForkSeq[fork], prepareState as CachedBeaconStateCapella ).withdrawals; } @@ -569,6 +570,7 @@ function preparePayloadAttributes( if (ForkSeq[fork] >= ForkSeq.electra) { (payloadAttributes as electra.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( + ForkSeq[fork], prepareState as CachedBeaconStateElectra ).withdrawals; } diff --git a/packages/state-transition/src/block/processDeposit.ts b/packages/state-transition/src/block/processDeposit.ts index fbefafdc7fbf..2fc144f56098 100644 --- a/packages/state-transition/src/block/processDeposit.ts +++ b/packages/state-transition/src/block/processDeposit.ts @@ -16,7 +16,7 @@ import {DepositData} from "@lodestar/types/lib/phase0/types.js"; import {DepositReceipt} from "@lodestar/types/lib/electra/types.js"; import {ZERO_HASH} from "../constants/index.js"; import {computeDomain, computeSigningRoot, increaseBalance} from "../util/index.js"; -import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "../types.js"; +import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js"; /** * Process a Deposit operation. Potentially adds a new validator to the registry. Mutates the validators and balances @@ -94,7 +94,7 @@ function addValidatorToRegistry( ): void { const {validators, epochCtx} = state; // add validator and balance entries - const effectiveBalance = Math.min(amount - (amount % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE); + const effectiveBalance = fork < ForkSeq.electra ? Math.min(amount - (amount % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE) : 0; validators.push( ssz.phase0.Validator.toViewDU({ pubkey, @@ -107,14 +107,14 @@ function addValidatorToRegistry( slashed: false, }) ); - state.balances.push(amount); - const validatorIndex = validators.length - 1; - // Updating here is better than updating at once on epoch transition - // - Simplify genesis fn applyDeposits(): effectiveBalanceIncrements is populated immediately - // - Keep related code together to reduce risk of breaking this cache - // - Should have equal performance since it sets a value in a flat array - epochCtx.effectiveBalanceIncrementsSet(validatorIndex, effectiveBalance); + const validatorIndex = validators.length - 1; + // TODO Electra: Review this + // Updating here is better than updating at once on epoch transition + // - Simplify genesis fn applyDeposits(): effectiveBalanceIncrements is populated immediately + // - Keep related code together to reduce risk of breaking this cache + // - Should have equal performance since it sets a value in a flat array + epochCtx.effectiveBalanceIncrementsSet(validatorIndex, effectiveBalance); // now that there is a new validator, update the epoch context with the new pubkey epochCtx.addPubkey(validatorIndex, pubkey); @@ -125,8 +125,34 @@ function addValidatorToRegistry( stateAltair.inactivityScores.push(0); - // add participation caches - stateAltair.previousEpochParticipation.push(0); - stateAltair.currentEpochParticipation.push(0); + // add participation caches + stateAltair.previousEpochParticipation.push(0); + stateAltair.currentEpochParticipation.push(0); + } + + if (fork < ForkSeq.electra) { + state.balances.push(amount); + } else if (fork >= ForkSeq.electra) { + state.balances.push(0); + const stateElectra = state as CachedBeaconStateElectra; + const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ + index: validatorIndex, + amount: BigInt(amount), + }); + stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); + } + } else { + + if (fork < ForkSeq.electra) { + // increase balance by deposit amount right away pre-electra + increaseBalance(state, cachedIndex, amount); + } else if (fork >= ForkSeq.electra) { + const stateElectra = state as CachedBeaconStateElectra; + const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ + index: cachedIndex, + amount: BigInt(amount), + }); + stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); + } } } diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 4d46d3466cf3..3705295c03d1 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -11,13 +11,14 @@ import { } from "@lodestar/params"; import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; -import {decreaseBalance, getValidatorExcessBalance, hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, isCapellaPayloadHeader, isFullyWithdrawableValidator, isPartiallyWithdrawableValidator} from "../util/index.js"; +import {decreaseBalance, getValidatorExcessBalance, hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, hasExecutionWithdrawalCredential, isCapellaPayloadHeader, isFullyWithdrawableValidator, isPartiallyWithdrawableValidator} from "../util/index.js"; export function processWithdrawals( fork: ForkSeq, state: CachedBeaconStateCapella | CachedBeaconStateElectra, payload: capella.FullOrBlindedExecutionPayload ): void { + // const {withdrawals: expectedWithdrawals, partialWithdrawalsCount} = getExpectedWithdrawals(fork, state); const {withdrawals: expectedWithdrawals} = getExpectedWithdrawals(fork, state); const numWithdrawals = expectedWithdrawals.length; @@ -123,7 +124,7 @@ export function getExpectedWithdrawals(fork: ForkSeq, state: CachedBeaconStateCa if ((fork === ForkSeq.capella || fork === ForkSeq.deneb) && !hasEth1WithdrawalCredential(validator.withdrawalCredentials)) { continue; } - if (fork === ForkSeq.electra && (!hasEth1WithdrawalCredential(validator.withdrawalCredentials) && !hasCompoundingWithdrawalCredential(validator.withdrawalCredentials))) { + if (fork === ForkSeq.electra && !hasExecutionWithdrawalCredential(validator.withdrawalCredentials)) { continue; } diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 405862cf3cfa..027972b0861a 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -129,7 +129,7 @@ export function processEpoch( const timer = metrics?.epochTransitionStepTime.startTimer({ step: EpochTransitionStep.processEffectiveBalanceUpdates, }); - processEffectiveBalanceUpdates(state, cache); + processEffectiveBalanceUpdates(fork, state, cache); timer?.(); } diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index 87a74f0bde86..a08f9743809d 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -5,9 +5,12 @@ import { HYSTERESIS_QUOTIENT, HYSTERESIS_UPWARD_MULTIPLIER, MAX_EFFECTIVE_BALANCE, + MAX_EFFECTIVE_BALANCE_ELECTRA, + MIN_ACTIVATION_BALANCE, TIMELY_TARGET_FLAG_INDEX, } from "@lodestar/params"; import {EpochTransitionCache, CachedBeaconStateAllForks, BeaconStateAltair} from "../types.js"; +import { hasCompoundingWithdrawalCredential } from "../util/capella.js"; /** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; @@ -21,7 +24,7 @@ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; * - On normal mainnet conditions 0 validators change their effective balance * - In case of big innactivity event a medium portion of validators may have their effectiveBalance updated */ -export function processEffectiveBalanceUpdates(state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { +export function processEffectiveBalanceUpdates(fork: ForkSeq, state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { const HYSTERESIS_INCREMENT = EFFECTIVE_BALANCE_INCREMENT / HYSTERESIS_QUOTIENT; const DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER; const UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER; @@ -42,6 +45,7 @@ export function processEffectiveBalanceUpdates(state: CachedBeaconStateAllForks, // PERF: It's faster to access to get() every single element (4ms) than to convert to regular array then loop (9ms) let effectiveBalanceIncrement = effectiveBalanceIncrements[i]; let effectiveBalance = effectiveBalanceIncrement * EFFECTIVE_BALANCE_INCREMENT; + let effectiveBalanceLimit; if ( // Too big @@ -49,10 +53,17 @@ export function processEffectiveBalanceUpdates(state: CachedBeaconStateAllForks, // Too small. Check effectiveBalance < MAX_EFFECTIVE_BALANCE to prevent unnecessary updates (effectiveBalance < MAX_EFFECTIVE_BALANCE && effectiveBalance < balance - UPWARD_THRESHOLD) ) { - effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE); // TODO Electra: Minimum is now dictated by EFFECTIVE_BALANCE_LIMIT instead of MAX_EB // Update the state tree // Should happen rarely, so it's fine to update the tree const validator = validators.get(i); + + if (fork < ForkSeq.electra) { + effectiveBalanceLimit = MAX_EFFECTIVE_BALANCE; + } else { // Electra or after + effectiveBalanceLimit = hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) ? MAX_EFFECTIVE_BALANCE_ELECTRA : MIN_ACTIVATION_BALANCE; + } + + effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), effectiveBalanceLimit); validator.effectiveBalance = effectiveBalance; // Also update the fast cached version const newEffectiveBalanceIncrement = Math.floor(effectiveBalance / EFFECTIVE_BALANCE_INCREMENT); From 5e8be924cf3c8720f86b24226a94d68fdd5accaf Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 26 Mar 2024 17:11:39 +0800 Subject: [PATCH 15/41] epoch processing --- .../src/block/slashValidator.ts | 6 +++- packages/state-transition/src/epoch/index.ts | 11 ++++-- .../epoch/processPendingBalanceDeposits.ts | 33 +++++++++++++++++ .../src/epoch/processPendingConsolidations.ts | 36 +++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 packages/state-transition/src/epoch/processPendingBalanceDeposits.ts create mode 100644 packages/state-transition/src/epoch/processPendingConsolidations.ts diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index 95f59645f2ce..3989a105efc0 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -12,6 +12,7 @@ import { TIMELY_TARGET_FLAG_INDEX, WEIGHT_DENOMINATOR, WHISTLEBLOWER_REWARD_QUOTIENT, + WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA, } from "@lodestar/params"; import {decreaseBalance, increaseBalance} from "../util/index.js"; @@ -59,7 +60,10 @@ export function slashValidator( decreaseBalance(state, slashedIndex, Math.floor(effectiveBalance / minSlashingPenaltyQuotient)); // apply proposer and whistleblower rewards - const whistleblowerReward = Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT); + const whistleblowerReward = + fork <= ForkSeq.deneb + ? Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT) + : Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA); const proposerReward = fork === ForkSeq.phase0 ? Math.floor(whistleblowerReward / PROPOSER_REWARD_QUOTIENT) diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 027972b0861a..4c0d40703ea0 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -11,6 +11,7 @@ import { CachedBeaconStateAltair, CachedBeaconStatePhase0, EpochTransitionCache, + CachedBeaconStateElectra, } from "../types.js"; import {BeaconStateTransitionMetrics} from "../metrics.js"; import {processEffectiveBalanceUpdates} from "./processEffectiveBalanceUpdates.js"; @@ -27,6 +28,8 @@ import {processRewardsAndPenalties} from "./processRewardsAndPenalties.js"; import {processSlashings} from "./processSlashings.js"; import {processSlashingsReset} from "./processSlashingsReset.js"; import {processSyncCommitteeUpdates} from "./processSyncCommitteeUpdates.js"; +import { processPendingBalanceDeposits } from "./processPendingBalanceDeposits.js"; +import { processPendingConsolidations } from "./processPendingConsolidations.js"; // For spec tests export {getRewardsAndPenalties} from "./processRewardsAndPenalties.js"; @@ -45,6 +48,8 @@ export { processParticipationFlagUpdates, processSyncCommitteeUpdates, processHistoricalSummariesUpdate, + processPendingBalanceDeposits, + processPendingConsolidations, }; export {computeUnrealizedCheckpoints} from "./computeUnrealizedCheckpoints.js"; @@ -121,8 +126,10 @@ export function processEpoch( processEth1DataReset(state, cache); if (fork >= ForkSeq.electra) { - // TODO Electra: processPendingBalanceDeposits - // TODO Electra: processPendingConsolidations + const stateElectra = state as CachedBeaconStateElectra + // TODO Electra: Add timer + processPendingBalanceDeposits(stateElectra); + processPendingConsolidations(stateElectra); } { diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts new file mode 100644 index 000000000000..682012e7dee1 --- /dev/null +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -0,0 +1,33 @@ +import {CachedBeaconStateElectra} from "../types.js"; +import { increaseBalance } from "../util/balance.js"; + +/** + * TODO Electra: jdoc + */ +export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): void { + const availableForProcessing = state.depositBalanceToConsume + 0n; // TODO Electra: get_activation_exit_churn_limit + let processedAmount = 0n; + let nextDepositIndex = 0; + + for (const deposit of state.pendingBalanceDeposits.getAllReadonly()) { + const {amount} = deposit + if (processedAmount + amount > availableForProcessing) { + break; + } + increaseBalance(state, deposit.index, Number(amount)); + processedAmount = processedAmount + amount; + nextDepositIndex ++; + } + + // TODO Electra: Impl slicing for ssz + const remainingPendingBalanceDeposits = []; + // const remainingPendingBalanceDeposits = state.pendingBalanceDeposits.slice() + // state.pendingBalanceDeposits = remainingPendingBalanceDeposits + + + if (remainingPendingBalanceDeposits.length === 0) { + state.depositBalanceToConsume = 0n; + } else { + state.depositBalanceToConsume = availableForProcessing - processedAmount; + } +} diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts new file mode 100644 index 000000000000..ee2f76c5a079 --- /dev/null +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -0,0 +1,36 @@ +import {EPOCHS_PER_ETH1_VOTING_PERIOD} from "@lodestar/params"; +import {ssz} from "@lodestar/types"; +import {EpochTransitionCache, CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; +import { decreaseBalance, increaseBalance } from "../util/balance.js"; + +/** + * TODO Electra: jdoc + */ +export function processPendingConsolidations(state: CachedBeaconStateElectra): void { + let nextPendingConsolidation = 0; + + for (const pendingConsolidation of state.pendingConsolidations.getAllReadonly()) { + const {sourceIndex, targetIndex} = pendingConsolidation; + const sourceValidator = state.validators.getReadonly(sourceIndex); + + if (sourceValidator.slashed) { + nextPendingConsolidation ++; + continue; + } + + if (sourceValidator.withdrawableEpoch > state.epochCtx.epoch){ + break; + } + + // Move active balance to target. Excess balance is withdrawable. + const activeBalance = 0; // TODO Electra: get_active_balance() + decreaseBalance(state, sourceIndex, activeBalance); + increaseBalance(state, targetIndex, activeBalance); + // TODO Electra: set_compounding_withdrawal_credentials() + nextPendingConsolidation++; + } + + // TODO Electra: impl slicing for ssz + // const remainingPendingConsolidations = []; + // state.pendingConsolidations =remainingPendingConsolidations +} From 3d06e163d2c12e244f53a33497ab989a8546474b Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 26 Mar 2024 18:08:10 +0800 Subject: [PATCH 16/41] Implement churn limits --- .../config/src/chainConfig/configs/mainnet.ts | 2 ++ .../config/src/chainConfig/configs/minimal.ts | 2 ++ packages/config/src/chainConfig/types.ts | 2 ++ .../src/block/processConsolidation.ts | 7 ++-- .../processExecutionLayerWithdrawRequest.ts | 3 +- .../epoch/processPendingBalanceDeposits.ts | 3 +- .../src/epoch/processPendingConsolidations.ts | 13 +++++--- packages/state-transition/src/util/epoch.ts | 33 ++++++++++++++++--- .../state-transition/src/util/validator.ts | 20 +++++++++-- packages/validator/src/util/params.ts | 1 + 10 files changed, 72 insertions(+), 14 deletions(-) diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 9a6b611fdb59..075af1701c70 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -107,4 +107,6 @@ export const chainConfig: ChainConfig = { // Electra // 2**8 * 10**9 (= 256,000,000,000) MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000, + // 2*7 * 10**9 (= 128,000,000,000) + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000, }; diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index 775ae42307eb..c699026f4a32 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -103,4 +103,6 @@ export const chainConfig: ChainConfig = { // Electra // 2**7 * 10**9 (= 128,000,000,000) MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000, + // 2**6 * 10**9 (= 64,000,000,000) + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000, }; diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 0c474b27dcf1..2f624e54e1b0 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -59,6 +59,7 @@ export type ChainConfig = { MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: number; CHURN_LIMIT_QUOTIENT: number; MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: number; + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: number; // Fork choice PROPOSER_SCORE_BOOST: number; @@ -123,6 +124,7 @@ export const chainConfigTypes: SpecTypes = { MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: "number", CHURN_LIMIT_QUOTIENT: "number", MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: "number", + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: "number", // Fork choice PROPOSER_SCORE_BOOST: "number", diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index 5d77e70c8da4..ae2239e21d55 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -1,10 +1,10 @@ import {toHexString} from "@chainsafe/ssz"; import {electra, ssz} from "@lodestar/types"; -import {FAR_FUTURE_EPOCH, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params"; import {verifyConsolidationSignature} from "../signatureSets/index.js"; import {CachedBeaconStateElectra} from "../types.js"; -import { isActiveValidator } from "../util/validator.js"; +import { getConsolidationChurnLimit, isActiveValidator } from "../util/validator.js"; import { hasExecutionWithdrawalCredential } from "../util/capella.js"; export function processConsolidation( @@ -40,6 +40,9 @@ function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsoli // If there is too little available consolidation churn limit, no consolidations are allowed in the block // assert get_consolidation_churn_limit(state) > MIN_ACTIVATION_BALANCE + if (getConsolidationChurnLimit(state) <= MIN_ACTIVATION_BALANCE) { + throw new Error(`Consolidation churn limit too low. consolidationChurnLimit=${getConsolidationChurnLimit(state)}`); + } const consolidation = signedConsolidation.message; diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index 4b3a6c67d4cb..6e6552567c38 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -8,6 +8,7 @@ import {CachedBeaconStateElectra} from "../types.js"; import { hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, hasExecutionWithdrawalCredential } from "../util/capella.js"; import { isActiveValidator } from "../util/validator.js"; import { initiateValidatorExit } from "./initiateValidatorExit.js"; +import { computeExitEpochAndUpdateChurn } from "../util/epoch.js"; export function processExecutionLayerWithdrawRequest( state: CachedBeaconStateElectra, @@ -49,7 +50,7 @@ export function processExecutionLayerWithdrawRequest( initiateValidatorExit(state, validator); } else if (validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw) { const amountToWithdraw = Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount); - const exitQueueEpoch = 0; // TODO Electra: compute_exit_epoch_and_update_churn() + const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, BigInt(amountToWithdraw)); const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; const pendingPartialWithdrawal = ssz.electra.PartialWithdrawal.toViewDU({ diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts index 682012e7dee1..e636e50f0b13 100644 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -1,11 +1,12 @@ import {CachedBeaconStateElectra} from "../types.js"; import { increaseBalance } from "../util/balance.js"; +import { getActivationExitChurnLimit } from "../util/validator.js"; /** * TODO Electra: jdoc */ export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): void { - const availableForProcessing = state.depositBalanceToConsume + 0n; // TODO Electra: get_activation_exit_churn_limit + const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state)); let processedAmount = 0n; let nextDepositIndex = 0; diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index ee2f76c5a079..e68387fa82ac 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -1,7 +1,8 @@ -import {EPOCHS_PER_ETH1_VOTING_PERIOD} from "@lodestar/params"; -import {ssz} from "@lodestar/types"; +import {COMPOUNDING_WITHDRAWAL_PREFIX, EPOCHS_PER_ETH1_VOTING_PERIOD} from "@lodestar/params"; +import {phase0, ssz} from "@lodestar/types"; import {EpochTransitionCache, CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; import { decreaseBalance, increaseBalance } from "../util/balance.js"; +import { hasEth1WithdrawalCredential } from "../util/capella.js"; /** * TODO Electra: jdoc @@ -26,11 +27,15 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v const activeBalance = 0; // TODO Electra: get_active_balance() decreaseBalance(state, sourceIndex, activeBalance); increaseBalance(state, targetIndex, activeBalance); - // TODO Electra: set_compounding_withdrawal_credentials() + + const targetValidator = state.validators.get(targetIndex); + if (hasEth1WithdrawalCredential(targetValidator.withdrawalCredentials)) { + targetValidator.withdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; + } nextPendingConsolidation++; } // TODO Electra: impl slicing for ssz // const remainingPendingConsolidations = []; // state.pendingConsolidations =remainingPendingConsolidations -} +} \ No newline at end of file diff --git a/packages/state-transition/src/util/epoch.ts b/packages/state-transition/src/util/epoch.ts index 4da1b221da7d..f41aec550795 100644 --- a/packages/state-transition/src/util/epoch.ts +++ b/packages/state-transition/src/util/epoch.ts @@ -1,5 +1,7 @@ import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, GENESIS_EPOCH, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {allForks, Epoch, Slot, SyncPeriod} from "@lodestar/types"; +import {allForks, electra, Epoch, Gwei, Slot, SyncPeriod} from "@lodestar/types"; +import { getActivationExitChurnLimit } from "./validator"; +import { CachedBeaconStateElectra } from "../types"; /** * Return the epoch number at the given slot. @@ -39,9 +41,32 @@ export function computeActivationExitEpoch(epoch: Epoch): Epoch { return epoch + 1 + MAX_SEED_LOOKAHEAD; } -/** - * TODO Electra: compute_exit_epoch_and_update_churn() - */ +export function computeExitEpochAndUpdateChurn(state: CachedBeaconStateElectra, exitBalance: Gwei) { + const earliestExitEpoch = computeActivationExitEpoch(state.epochCtx.epoch); + const perEpochChurn = getActivationExitChurnLimit(state); + + // New epoch for exits. + if (state.earliestExitEpoch < earliestExitEpoch) { + state.earliestExitEpoch = earliestExitEpoch; + state.exitBalanceToConsume = BigInt(perEpochChurn); + } + + if (exitBalance <= state.exitBalanceToConsume) { + // Exit fits in the current earliest epoch. + state.exitBalanceToConsume -= exitBalance; + } else { + // Exit doesn't fit in the current earliest epoch. + const balanceToProcess = exitBalance - state.exitBalanceToConsume; + const additionalEpochs = balanceToProcess / BigInt(perEpochChurn); + const remainder = balanceToProcess % BigInt(perEpochChurn); + + state.earliestExitEpoch += Number(additionalEpochs); + state.exitBalanceToConsume = BigInt(perEpochChurn) - remainder; + + } + + return state.earliestExitEpoch; +} /** * Return the current epoch of the given state. diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 99f1e6fa0b19..9b4ab40642f3 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -1,8 +1,8 @@ import {Epoch, phase0, ValidatorIndex} from "@lodestar/types"; import {intDiv} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; -import {ForkSeq} from "@lodestar/params"; -import {BeaconStateAllForks} from "../types.js"; +import {EFFECTIVE_BALANCE_INCREMENT, ForkSeq} from "@lodestar/params"; +import {BeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; /** * Check if [[validator]] is active @@ -44,6 +44,22 @@ export function getActivationChurnLimit(config: ChainForkConfig, fork: ForkSeq, } } +export function getActivationExitChurnLimit(state: CachedBeaconStateElectra): number { + return Math.min(state.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, getActivationExitConsolidationChurnLimit(state)); +} + +export function getConsolidationChurnLimit(state: CachedBeaconStateElectra): number { + return getActivationExitConsolidationChurnLimit(state) - getActivationExitChurnLimit(state); +} + +export function getActivationExitConsolidationChurnLimit(state: CachedBeaconStateElectra): number { + const churnLimitByTotalActiveBalance = Math.floor((state.epochCtx.totalActiveBalanceIncrements / state.config.CHURN_LIMIT_QUOTIENT) * EFFECTIVE_BALANCE_INCREMENT); // TODO Electra: verify calculation + + const churn = Math.max(churnLimitByTotalActiveBalance, state.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_ELECTRA); + + return churn - churn % EFFECTIVE_BALANCE_INCREMENT; +} + export function getChurnLimit(config: ChainForkConfig, activeValidatorCount: number): number { return Math.max(config.MIN_PER_EPOCH_CHURN_LIMIT, intDiv(activeValidatorCount, config.CHURN_LIMIT_QUOTIENT)); } diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index ae5fcf234e90..891d6e8f7ec5 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -237,5 +237,6 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Tue, 26 Mar 2024 18:15:59 +0800 Subject: [PATCH 17/41] Lint --- .../chain/produceBlock/produceBlockBody.ts | 1 - .../config/src/chainConfig/configs/mainnet.ts | 1 - packages/config/src/chainConfig/types.ts | 1 - packages/state-transition/src/block/index.ts | 9 ++- .../src/block/processConsolidation.ts | 45 +++++++------ .../src/block/processDeposit.ts | 1 - .../processExecutionLayerWithdrawRequest.ts | 64 +++++++++++-------- .../src/block/processOperations.ts | 4 +- .../src/block/processWithdrawals.ts | 28 +++++--- .../src/block/slashValidator.ts | 2 +- .../state-transition/src/cache/epochCache.ts | 2 +- packages/state-transition/src/epoch/index.ts | 6 +- .../epoch/processEffectiveBalanceUpdates.ts | 15 +++-- .../epoch/processPendingBalanceDeposits.ts | 9 ++- .../src/epoch/processPendingConsolidations.ts | 15 ++--- .../src/epoch/processRegistryUpdates.ts | 9 ++- .../src/signatureSets/consolidation.ts | 26 ++++++-- packages/state-transition/src/util/capella.ts | 42 +++++++++--- packages/state-transition/src/util/epoch.ts | 9 ++- .../state-transition/src/util/validator.ts | 15 +++-- .../perf/block/processWithdrawals.test.ts | 2 +- .../unit/block/processWithdrawals.test.ts | 2 +- packages/types/src/electra/sszTypes.ts | 4 ++ packages/types/src/electra/types.ts | 2 +- 24 files changed, 198 insertions(+), 116 deletions(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index b3aa078bf2e9..e0af4a7281b7 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -575,7 +575,6 @@ function preparePayloadAttributes( ).withdrawals; } - return payloadAttributes; } diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 075af1701c70..7d0488b31969 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -103,7 +103,6 @@ export const chainConfig: ChainConfig = { // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, - // Electra // 2**8 * 10**9 (= 256,000,000,000) MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000, diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 2f624e54e1b0..da36cde49d03 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -74,7 +74,6 @@ export type ChainConfig = { // Networking MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: number; - }; export const chainConfigTypes: SpecTypes = { diff --git a/packages/state-transition/src/block/index.ts b/packages/state-transition/src/block/index.ts index cc463e53cd37..2d5ebe053f47 100644 --- a/packages/state-transition/src/block/index.ts +++ b/packages/state-transition/src/block/index.ts @@ -1,7 +1,12 @@ import {ForkSeq} from "@lodestar/params"; -import {allForks, altair, capella, electra} from "@lodestar/types"; +import {allForks, altair, capella} from "@lodestar/types"; import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js"; -import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix, CachedBeaconStateElectra} from "../types.js"; +import { + CachedBeaconStateAllForks, + CachedBeaconStateCapella, + CachedBeaconStateBellatrix, + CachedBeaconStateElectra, +} from "../types.js"; import {processExecutionPayload} from "./processExecutionPayload.js"; import {processSyncAggregate} from "./processSyncCommittee.js"; import {processBlockHeader} from "./processBlockHeader.js"; diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index ae2239e21d55..18d191cba369 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -4,15 +4,13 @@ import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} import {verifyConsolidationSignature} from "../signatureSets/index.js"; import {CachedBeaconStateElectra} from "../types.js"; -import { getConsolidationChurnLimit, isActiveValidator } from "../util/validator.js"; -import { hasExecutionWithdrawalCredential } from "../util/capella.js"; +import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; +import {hasExecutionWithdrawalCredential} from "../util/capella.js"; export function processConsolidation( state: CachedBeaconStateElectra, - signedConsolidation: electra.SignedConsolidation, + signedConsolidation: electra.SignedConsolidation ): void { - - assertValidConsolidation(state, signedConsolidation); // Initiate source validator exit and append pending consolidation @@ -21,21 +19,20 @@ export function processConsolidation( const sourceValidator = state.validators.get(sourceIndex); const exitEpoch = 0; // TODO Electra: compute_consolidation_epoch_and_update_churn - sourceValidator.exitEpoch = exitEpoch; + sourceValidator.exitEpoch = exitEpoch; sourceValidator.withdrawableEpoch = exitEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; const pendingConsolidation = ssz.electra.PendingConsolidation.toViewDU({ sourceIndex, - targetIndex + targetIndex, }); state.pendingConsolidations.push(pendingConsolidation); - } -function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsolidation: electra.SignedConsolidation) { +function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsolidation: electra.SignedConsolidation): void { // If the pending consolidations queue is full, no consolidations are allowed in the block if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) { - throw new Error(`Pending consolidation queue is full`); + throw new Error("Pending consolidation queue is full"); } // If there is too little available consolidation churn limit, no consolidations are allowed in the block @@ -44,16 +41,16 @@ function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsoli throw new Error(`Consolidation churn limit too low. consolidationChurnLimit=${getConsolidationChurnLimit(state)}`); } - const consolidation = signedConsolidation.message; const {sourceIndex, targetIndex} = consolidation; // Verify that source != target, so a consolidation cannot be used as an exit. if (sourceIndex === targetIndex) { - throw new Error(`Consolidation source and target index cannot be the same: sourceIndex=${sourceIndex} targetIndex=${targetIndex}`); + throw new Error( + `Consolidation source and target index cannot be the same: sourceIndex=${sourceIndex} targetIndex=${targetIndex}` + ); } - const sourceValidator = state.validators.getReadonly(sourceIndex); const targetValidator = state.validators.getReadonly(targetIndex); const currentEpoch = state.epochCtx.epoch; @@ -68,24 +65,30 @@ function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsoli } // Verify exits for source and target have not been initiated - if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH){ + if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH) { throw new Error(`Consolidation source validator has initialized exit: sourceIndex=${sourceIndex}`); } - if (targetValidator.exitEpoch !== FAR_FUTURE_EPOCH){ + if (targetValidator.exitEpoch !== FAR_FUTURE_EPOCH) { throw new Error(`Consolidation target validator has initialized exit: targetIndex=${targetIndex}`); } // Consolidations must specify an epoch when they become valid; they are not valid before then if (currentEpoch < consolidation.epoch) { - throw new Error(`Consolidation epoch is after the current epoch: consolidationEpoch=${consolidation.epoch} currentEpoch=${currentEpoch}`); + throw new Error( + `Consolidation epoch is after the current epoch: consolidationEpoch=${consolidation.epoch} currentEpoch=${currentEpoch}` + ); } // Verify the source and the target have Execution layer withdrawal credentials if (!hasExecutionWithdrawalCredential(sourceValidator.withdrawalCredentials)) { - throw new Error(`Consolidation source validator does not have execution withdrawal credentials: sourceIndex=${sourceIndex}`); + throw new Error( + `Consolidation source validator does not have execution withdrawal credentials: sourceIndex=${sourceIndex}` + ); } if (!hasExecutionWithdrawalCredential(targetValidator.withdrawalCredentials)) { - throw new Error(`Consolidation target validator does not have execution withdrawal credentials: targetIndex=${targetIndex}`); + throw new Error( + `Consolidation target validator does not have execution withdrawal credentials: targetIndex=${targetIndex}` + ); } // Verify the same withdrawal address @@ -93,11 +96,13 @@ function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsoli const targetWithdrawalAddress = toHexString(targetValidator.withdrawalCredentials.slice(1)); if (sourceWithdrawalAddress !== targetWithdrawalAddress) { - throw new Error(`Consolidation source and target withdrawal address are different: source: ${sourceWithdrawalAddress} target: ${targetWithdrawalAddress}`); + throw new Error( + `Consolidation source and target withdrawal address are different: source: ${sourceWithdrawalAddress} target: ${targetWithdrawalAddress}` + ); } // Verify consolidation is signed by the source and the target if (!verifyConsolidationSignature(state, signedConsolidation)) { throw new Error("Consolidation not valid"); } -} \ No newline at end of file +} diff --git a/packages/state-transition/src/block/processDeposit.ts b/packages/state-transition/src/block/processDeposit.ts index 2fc144f56098..5097bd721ca3 100644 --- a/packages/state-transition/src/block/processDeposit.ts +++ b/packages/state-transition/src/block/processDeposit.ts @@ -142,7 +142,6 @@ function addValidatorToRegistry( stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); } } else { - if (fork < ForkSeq.electra) { // increase balance by deposit amount right away pre-electra increaseBalance(state, cachedIndex, amount); diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index 6e6552567c38..ba4ad29fca56 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -1,26 +1,30 @@ -import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; -import {digest} from "@chainsafe/as-sha256"; -import {Epoch, capella, electra, phase0, ssz} from "@lodestar/types"; -import {BLS_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT} from "@lodestar/params"; -import {verifyBlsToExecutionChangeSignature} from "../signatureSets/index.js"; +import {toHexString} from "@chainsafe/ssz"; +import { electra, phase0, ssz} from "@lodestar/types"; +import { + FAR_FUTURE_EPOCH, + MIN_ACTIVATION_BALANCE, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, +} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; -import { hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, hasExecutionWithdrawalCredential } from "../util/capella.js"; -import { isActiveValidator } from "../util/validator.js"; -import { initiateValidatorExit } from "./initiateValidatorExit.js"; -import { computeExitEpochAndUpdateChurn } from "../util/epoch.js"; +import { + hasCompoundingWithdrawalCredential, + hasExecutionWithdrawalCredential, +} from "../util/capella.js"; +import {isActiveValidator} from "../util/validator.js"; +import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; +import {initiateValidatorExit} from "./initiateValidatorExit.js"; export function processExecutionLayerWithdrawRequest( state: CachedBeaconStateElectra, - executionLayerWithdrawRequest: electra.ExecutionLayerWithdrawRequest, + executionLayerWithdrawRequest: electra.ExecutionLayerWithdrawRequest ): void { - const amount = Number(executionLayerWithdrawRequest.amount); const {pendingPartialWithdrawals, validators, epochCtx} = state; const {pubkey2index, config} = epochCtx; // TODO Electra: Use finalized+unfinalized pubkey cache from 6110 const isFullExitRequest = amount === 0; - // If partial withdrawal queue is full, only full exits are processed + // If partial withdrawal queue is full, only full exits are processed if (pendingPartialWithdrawals.length >= PENDING_PARTIAL_WITHDRAWALS_LIMIT && !isFullExitRequest) { return; } @@ -28,7 +32,11 @@ export function processExecutionLayerWithdrawRequest( const validatorIndex = pubkey2index.get(executionLayerWithdrawRequest.validatorPubkey); if (validatorIndex === undefined) { - throw new Error(`Can't find validator index from ExecutionLayerWithdrawRequest : pubkey=${toHexString(executionLayerWithdrawRequest.validatorPubkey)}`); + throw new Error( + `Can't find validator index from ExecutionLayerWithdrawRequest : pubkey=${toHexString( + executionLayerWithdrawRequest.validatorPubkey + )}` + ); } const validator = state.validators.getReadonly(validatorIndex); @@ -42,7 +50,10 @@ export function processExecutionLayerWithdrawRequest( } // TODO Electra: Consider caching pendingPartialWithdrawals - const pendingBalanceToWithdraw = state.pendingPartialWithdrawals.getAllReadonly().filter(item => item.index === validatorIndex).reduce((total, item) => total + Number(item.amount), 0); + const pendingBalanceToWithdraw = state.pendingPartialWithdrawals + .getAllReadonly() + .filter((item) => item.index === validatorIndex) + .reduce((total, item) => total + Number(item.amount), 0); const validatorBalance = state.balances.get(validatorIndex); // only exit validator if it has no pending withdrawals in the queue @@ -61,22 +72,23 @@ export function processExecutionLayerWithdrawRequest( state.pendingPartialWithdrawals.push(pendingPartialWithdrawal); } - } -function isValidValidator(validator: phase0.Validator, sourceAddress: Uint8Array, state: CachedBeaconStateElectra): boolean { +function isValidValidator( + validator: phase0.Validator, + sourceAddress: Uint8Array, + state: CachedBeaconStateElectra +): boolean { const {withdrawalCredentials} = validator; const addressStr = toHexString(withdrawalCredentials.slice(12)); const sourceAddressStr = toHexString(sourceAddress); const {epoch: currentEpoch, config} = state.epochCtx; - return hasExecutionWithdrawalCredential(withdrawalCredentials) - && - addressStr === sourceAddressStr - && - isActiveValidator(validator, currentEpoch) - && - validator.exitEpoch === FAR_FUTURE_EPOCH - && - currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD; -} \ No newline at end of file + return ( + hasExecutionWithdrawalCredential(withdrawalCredentials) && + addressStr === sourceAddressStr && + isActiveValidator(validator, currentEpoch) && + validator.exitEpoch === FAR_FUTURE_EPOCH && + currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD + ); +} diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 7615f4ff2af3..35bcb259c282 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -12,8 +12,8 @@ import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWit import {processBlsToExecutionChange} from "./processBlsToExecutionChange.js"; import {processDepositReceipt} from "./processDepositReceipt.js"; import {ProcessBlockOpts} from "./types.js"; -import { processExecutionLayerWithdrawRequest } from "./processExecutionLayerWithdrawRequest.js"; -import { processConsolidation } from "./processConsolidation.js"; +import {processExecutionLayerWithdrawRequest} from "./processExecutionLayerWithdrawRequest.js"; +import {processConsolidation} from "./processConsolidation.js"; export { processProposerSlashing, diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 3705295c03d1..7da97c1ab7f7 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -1,7 +1,6 @@ import {byteArrayEquals, toHexString} from "@chainsafe/ssz"; import {ssz, capella} from "@lodestar/types"; import { - MAX_EFFECTIVE_BALANCE, MAX_WITHDRAWALS_PER_PAYLOAD, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP, ForkSeq, @@ -11,7 +10,15 @@ import { } from "@lodestar/params"; import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; -import {decreaseBalance, getValidatorExcessBalance, hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, hasExecutionWithdrawalCredential, isCapellaPayloadHeader, isFullyWithdrawableValidator, isPartiallyWithdrawableValidator} from "../util/index.js"; +import { + decreaseBalance, + getValidatorExcessBalance, + hasEth1WithdrawalCredential, + hasExecutionWithdrawalCredential, + isCapellaPayloadHeader, + isFullyWithdrawableValidator, + isPartiallyWithdrawableValidator, +} from "../util/index.js"; export function processWithdrawals( fork: ForkSeq, @@ -73,7 +80,10 @@ export function processWithdrawals( } } -export function getExpectedWithdrawals(fork: ForkSeq, state: CachedBeaconStateCapella | CachedBeaconStateElectra): { +export function getExpectedWithdrawals( + fork: ForkSeq, + state: CachedBeaconStateCapella | CachedBeaconStateElectra +): { withdrawals: capella.Withdrawal[]; sampledValidators: number; partialWithdrawalsCount: number; @@ -83,7 +93,6 @@ export function getExpectedWithdrawals(fork: ForkSeq, state: CachedBeaconStateCa const {validators, balances, nextWithdrawalValidatorIndex} = state; const bound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP); - const withdrawals: capella.Withdrawal[] = []; if (fork >= ForkSeq.electra) { @@ -98,17 +107,17 @@ export function getExpectedWithdrawals(fork: ForkSeq, state: CachedBeaconStateCa if (validator.exitEpoch === FAR_FUTURE_EPOCH && balances.get(withdrawalIndex) > MIN_ACTIVATION_BALANCE) { const balanceOverMinActivationBalance = BigInt(balances.get(withdrawalIndex) - MIN_ACTIVATION_BALANCE); - const withdrawableBalance = balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount; + const withdrawableBalance = + balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount; withdrawals.push({ index: withdrawalIndex, validatorIndex: withdrawal.index, address: validator.withdrawalCredentials.slice(12), amount: withdrawableBalance, - }) + }); withdrawalIndex++; } } - } const partialWithdrawalsCount = withdrawals.length; @@ -121,7 +130,10 @@ export function getExpectedWithdrawals(fork: ForkSeq, state: CachedBeaconStateCa // It's most likely for validators to not have set eth1 credentials, than having 0 balance const validator = validators.getReadonly(validatorIndex); - if ((fork === ForkSeq.capella || fork === ForkSeq.deneb) && !hasEth1WithdrawalCredential(validator.withdrawalCredentials)) { + if ( + (fork === ForkSeq.capella || fork === ForkSeq.deneb) && + !hasEth1WithdrawalCredential(validator.withdrawalCredentials) + ) { continue; } if (fork === ForkSeq.electra && !hasExecutionWithdrawalCredential(validator.withdrawalCredentials)) { diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index 3989a105efc0..471ef1f13830 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -60,7 +60,7 @@ export function slashValidator( decreaseBalance(state, slashedIndex, Math.floor(effectiveBalance / minSlashingPenaltyQuotient)); // apply proposer and whistleblower rewards - const whistleblowerReward = + const whistleblowerReward = fork <= ForkSeq.deneb ? Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT) : Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA); diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index 6dc9742e3d31..f34638c75c53 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -588,7 +588,7 @@ export class EpochCache { ); // Maybe advance exitQueueEpoch at the end of the epoch if there haven't been any exists for a while - const exitQueueEpoch = computeActivationExitEpoch(currEpoch); // TODO Electra: New exit epoch calculation + const exitQueueEpoch = computeActivationExitEpoch(currEpoch); // TODO Electra: New exit epoch calculation if (exitQueueEpoch > this.exitQueueEpoch) { this.exitQueueEpoch = exitQueueEpoch; this.exitQueueChurn = 0; diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 4c0d40703ea0..3c20fd68753c 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -28,8 +28,8 @@ import {processRewardsAndPenalties} from "./processRewardsAndPenalties.js"; import {processSlashings} from "./processSlashings.js"; import {processSlashingsReset} from "./processSlashingsReset.js"; import {processSyncCommitteeUpdates} from "./processSyncCommitteeUpdates.js"; -import { processPendingBalanceDeposits } from "./processPendingBalanceDeposits.js"; -import { processPendingConsolidations } from "./processPendingConsolidations.js"; +import {processPendingBalanceDeposits} from "./processPendingBalanceDeposits.js"; +import {processPendingConsolidations} from "./processPendingConsolidations.js"; // For spec tests export {getRewardsAndPenalties} from "./processRewardsAndPenalties.js"; @@ -126,7 +126,7 @@ export function processEpoch( processEth1DataReset(state, cache); if (fork >= ForkSeq.electra) { - const stateElectra = state as CachedBeaconStateElectra + const stateElectra = state as CachedBeaconStateElectra; // TODO Electra: Add timer processPendingBalanceDeposits(stateElectra); processPendingConsolidations(stateElectra); diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index a08f9743809d..db249db4f226 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -10,7 +10,7 @@ import { TIMELY_TARGET_FLAG_INDEX, } from "@lodestar/params"; import {EpochTransitionCache, CachedBeaconStateAllForks, BeaconStateAltair} from "../types.js"; -import { hasCompoundingWithdrawalCredential } from "../util/capella.js"; +import {hasCompoundingWithdrawalCredential} from "../util/capella.js"; /** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; @@ -24,7 +24,11 @@ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; * - On normal mainnet conditions 0 validators change their effective balance * - In case of big innactivity event a medium portion of validators may have their effectiveBalance updated */ -export function processEffectiveBalanceUpdates(fork: ForkSeq, state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { +export function processEffectiveBalanceUpdates( + fork: ForkSeq, + state: CachedBeaconStateAllForks, + cache: EpochTransitionCache +): void { const HYSTERESIS_INCREMENT = EFFECTIVE_BALANCE_INCREMENT / HYSTERESIS_QUOTIENT; const DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER; const UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER; @@ -59,8 +63,11 @@ export function processEffectiveBalanceUpdates(fork: ForkSeq, state: CachedBeaco if (fork < ForkSeq.electra) { effectiveBalanceLimit = MAX_EFFECTIVE_BALANCE; - } else { // Electra or after - effectiveBalanceLimit = hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) ? MAX_EFFECTIVE_BALANCE_ELECTRA : MIN_ACTIVATION_BALANCE; + } else { + // Electra or after + effectiveBalanceLimit = hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) + ? MAX_EFFECTIVE_BALANCE_ELECTRA + : MIN_ACTIVATION_BALANCE; } effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), effectiveBalanceLimit); diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts index e636e50f0b13..b7bd453fd986 100644 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -1,6 +1,6 @@ import {CachedBeaconStateElectra} from "../types.js"; -import { increaseBalance } from "../util/balance.js"; -import { getActivationExitChurnLimit } from "../util/validator.js"; +import {increaseBalance} from "../util/balance.js"; +import {getActivationExitChurnLimit} from "../util/validator.js"; /** * TODO Electra: jdoc @@ -11,20 +11,19 @@ export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): let nextDepositIndex = 0; for (const deposit of state.pendingBalanceDeposits.getAllReadonly()) { - const {amount} = deposit + const {amount} = deposit; if (processedAmount + amount > availableForProcessing) { break; } increaseBalance(state, deposit.index, Number(amount)); processedAmount = processedAmount + amount; - nextDepositIndex ++; + nextDepositIndex++; } // TODO Electra: Impl slicing for ssz const remainingPendingBalanceDeposits = []; // const remainingPendingBalanceDeposits = state.pendingBalanceDeposits.slice() // state.pendingBalanceDeposits = remainingPendingBalanceDeposits - if (remainingPendingBalanceDeposits.length === 0) { state.depositBalanceToConsume = 0n; diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index e68387fa82ac..71cd79b8ecbf 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -1,8 +1,7 @@ -import {COMPOUNDING_WITHDRAWAL_PREFIX, EPOCHS_PER_ETH1_VOTING_PERIOD} from "@lodestar/params"; -import {phase0, ssz} from "@lodestar/types"; -import {EpochTransitionCache, CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; -import { decreaseBalance, increaseBalance } from "../util/balance.js"; -import { hasEth1WithdrawalCredential } from "../util/capella.js"; +import {COMPOUNDING_WITHDRAWAL_PREFIX} from "@lodestar/params"; +import { CachedBeaconStateElectra} from "../types.js"; +import {decreaseBalance, increaseBalance} from "../util/balance.js"; +import {hasEth1WithdrawalCredential} from "../util/capella.js"; /** * TODO Electra: jdoc @@ -15,11 +14,11 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v const sourceValidator = state.validators.getReadonly(sourceIndex); if (sourceValidator.slashed) { - nextPendingConsolidation ++; + nextPendingConsolidation++; continue; } - if (sourceValidator.withdrawableEpoch > state.epochCtx.epoch){ + if (sourceValidator.withdrawableEpoch > state.epochCtx.epoch) { break; } @@ -38,4 +37,4 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v // TODO Electra: impl slicing for ssz // const remainingPendingConsolidations = []; // state.pendingConsolidations =remainingPendingConsolidations -} \ No newline at end of file +} diff --git a/packages/state-transition/src/epoch/processRegistryUpdates.ts b/packages/state-transition/src/epoch/processRegistryUpdates.ts index b1bdfb31e42a..cf9ca8d29527 100644 --- a/packages/state-transition/src/epoch/processRegistryUpdates.ts +++ b/packages/state-transition/src/epoch/processRegistryUpdates.ts @@ -1,7 +1,7 @@ +import {ForkSeq} from "@lodestar/params"; import {computeActivationExitEpoch} from "../util/index.js"; import {initiateValidatorExit} from "../block/index.js"; import {EpochTransitionCache, CachedBeaconStateAllForks} from "../types.js"; -import { ForkSeq } from "@lodestar/params"; /** * Update validator registry for validators that activate + exit @@ -17,7 +17,11 @@ import { ForkSeq } from "@lodestar/params"; * - indicesEligibleForActivationQueue: 0 * - indicesToEject: 0 */ -export function processRegistryUpdates(fork: ForkSeq, state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void { +export function processRegistryUpdates( + fork: ForkSeq, + state: CachedBeaconStateAllForks, + cache: EpochTransitionCache +): void { const {epochCtx} = state; // Get the validators sub tree once for all the loop @@ -34,7 +38,6 @@ export function processRegistryUpdates(fork: ForkSeq, state: CachedBeaconStateAl // TODO Electra: New logic to set validator.activation_epoch if (fork >= ForkSeq.electra) { - } // set new activation eligibilities diff --git a/packages/state-transition/src/signatureSets/consolidation.ts b/packages/state-transition/src/signatureSets/consolidation.ts index 7334a360affd..0aef47d417a5 100644 --- a/packages/state-transition/src/signatureSets/consolidation.ts +++ b/packages/state-transition/src/signatureSets/consolidation.ts @@ -1,7 +1,12 @@ import {DOMAIN_CONSOLIDATION, ForkName} from "@lodestar/params"; import {electra, ssz} from "@lodestar/types"; -import {computeSigningRoot, createAggregateSignatureSetFromComponents, ISignatureSet, SignatureSetType, verifySignatureSet} from "../util/index.js"; +import { + computeSigningRoot, + createAggregateSignatureSetFromComponents, + ISignatureSet, + verifySignatureSet, +} from "../util/index.js"; import {CachedBeaconStateElectra} from "../types.js"; export function verifyConsolidationSignature( @@ -16,7 +21,7 @@ export function verifyConsolidationSignature( */ export function getConsolidationSignatureSet( state: CachedBeaconStateElectra, - signedConsolidation: electra.SignedConsolidation, + signedConsolidation: electra.SignedConsolidation ): ISignatureSet { const {config} = state; const {index2pubkey} = state.epochCtx; // TODO Electra: Use 6110 pubkey cache @@ -28,12 +33,19 @@ export function getConsolidationSignatureSet( const signatureFork = ForkName.phase0; const domain = config.getDomainAtFork(signatureFork, DOMAIN_CONSOLIDATION); const signingRoot = computeSigningRoot(ssz.electra.Consolidation, signedConsolidation.message, domain); - - return createAggregateSignatureSetFromComponents([sourcePubkey, targetPubkey], signingRoot, signedConsolidation.signature); + + return createAggregateSignatureSetFromComponents( + [sourcePubkey, targetPubkey], + signingRoot, + signedConsolidation.signature + ); } -export function getConsolidationSignatureSets(state: CachedBeaconStateElectra, signedBlock: electra.SignedBeaconBlock): ISignatureSet[] { - return signedBlock.message.body.consolidations.map((consolidation) => +export function getConsolidationSignatureSets( + state: CachedBeaconStateElectra, + signedBlock: electra.SignedBeaconBlock +): ISignatureSet[] { + return signedBlock.message.body.consolidations.map((consolidation) => getConsolidationSignatureSet(state, consolidation) ); -} \ No newline at end of file +} diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index baa472a7fb5d..599535d981fc 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -1,9 +1,15 @@ -import {COMPOUNDING_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX, ForkSeq, MAX_EFFECTIVE_BALANCE, MAX_EFFECTIVE_BALANCE_ELECTRA, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; +import { + COMPOUNDING_WITHDRAWAL_PREFIX, + ETH1_ADDRESS_WITHDRAWAL_PREFIX, + ForkSeq, + MAX_EFFECTIVE_BALANCE, + MAX_EFFECTIVE_BALANCE_ELECTRA, + MIN_ACTIVATION_BALANCE, +} from "@lodestar/params"; import {phase0} from "@lodestar/types"; type ValidatorInfo = Pick; - /** * https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential */ @@ -16,9 +22,11 @@ export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8A } export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { - return hasCompoundingWithdrawalCredential(withdrawalCredentials) || hasEth1WithdrawalCredential(withdrawalCredentials); + return ( + hasCompoundingWithdrawalCredential(withdrawalCredentials) || hasEth1WithdrawalCredential(withdrawalCredentials) + ); } - + export function getValidatorExcessBalance(withdrawalCredentials: Uint8Array, validatorBalance: number): number { // Compounding withdrawal credential only available since Electra if (hasCompoundingWithdrawalCredential(withdrawalCredentials)) { @@ -30,7 +38,12 @@ export function getValidatorExcessBalance(withdrawalCredentials: Uint8Array, val return 0; } -export function isFullyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorInfo, balance: number, epoch: number) { +export function isFullyWithdrawableValidator( + fork: ForkSeq, + validatorCredential: ValidatorInfo, + balance: number, + epoch: number +): boolean { const {withdrawableEpoch, withdrawalCredentials: withdrawalCredential} = validatorCredential; if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; @@ -43,15 +56,26 @@ export function isFullyWithdrawableValidator(fork: ForkSeq, validatorCredential: return false; } -export function isPartiallyWithdrawableValidator(fork: ForkSeq, validatorCredential: ValidatorInfo, balance: number, epoch: number) { +export function isPartiallyWithdrawableValidator( + fork: ForkSeq, + validatorCredential: ValidatorInfo, + balance: number, +): boolean { const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { - return hasEth1WithdrawalCredential(withdrawalCredential) && effectiveBalance === MAX_EFFECTIVE_BALANCE && balance > MAX_EFFECTIVE_BALANCE; + return ( + hasEth1WithdrawalCredential(withdrawalCredential) && + effectiveBalance === MAX_EFFECTIVE_BALANCE && + balance > MAX_EFFECTIVE_BALANCE + ); } if (fork === ForkSeq.electra) { - return hasExecutionWithdrawalCredential(withdrawalCredential) && getValidatorExcessBalance(withdrawalCredential, balance) > 0; + return ( + hasExecutionWithdrawalCredential(withdrawalCredential) && + getValidatorExcessBalance(withdrawalCredential, balance) > 0 + ); } return false; -} \ No newline at end of file +} diff --git a/packages/state-transition/src/util/epoch.ts b/packages/state-transition/src/util/epoch.ts index f41aec550795..03eccd3467e8 100644 --- a/packages/state-transition/src/util/epoch.ts +++ b/packages/state-transition/src/util/epoch.ts @@ -1,7 +1,7 @@ import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, GENESIS_EPOCH, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {allForks, electra, Epoch, Gwei, Slot, SyncPeriod} from "@lodestar/types"; -import { getActivationExitChurnLimit } from "./validator"; -import { CachedBeaconStateElectra } from "../types"; +import {allForks, Epoch, Gwei, Slot, SyncPeriod} from "@lodestar/types"; +import {CachedBeaconStateElectra} from "../types.js"; +import {getActivationExitChurnLimit} from "./validator.js"; /** * Return the epoch number at the given slot. @@ -41,7 +41,7 @@ export function computeActivationExitEpoch(epoch: Epoch): Epoch { return epoch + 1 + MAX_SEED_LOOKAHEAD; } -export function computeExitEpochAndUpdateChurn(state: CachedBeaconStateElectra, exitBalance: Gwei) { +export function computeExitEpochAndUpdateChurn(state: CachedBeaconStateElectra, exitBalance: Gwei): number { const earliestExitEpoch = computeActivationExitEpoch(state.epochCtx.epoch); const perEpochChurn = getActivationExitChurnLimit(state); @@ -62,7 +62,6 @@ export function computeExitEpochAndUpdateChurn(state: CachedBeaconStateElectra, state.earliestExitEpoch += Number(additionalEpochs); state.exitBalanceToConsume = BigInt(perEpochChurn) - remainder; - } return state.earliestExitEpoch; diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 9b4ab40642f3..e7dea793023e 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -45,7 +45,10 @@ export function getActivationChurnLimit(config: ChainForkConfig, fork: ForkSeq, } export function getActivationExitChurnLimit(state: CachedBeaconStateElectra): number { - return Math.min(state.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, getActivationExitConsolidationChurnLimit(state)); + return Math.min( + state.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, + getActivationExitConsolidationChurnLimit(state) + ); } export function getConsolidationChurnLimit(state: CachedBeaconStateElectra): number { @@ -53,13 +56,15 @@ export function getConsolidationChurnLimit(state: CachedBeaconStateElectra): num } export function getActivationExitConsolidationChurnLimit(state: CachedBeaconStateElectra): number { - const churnLimitByTotalActiveBalance = Math.floor((state.epochCtx.totalActiveBalanceIncrements / state.config.CHURN_LIMIT_QUOTIENT) * EFFECTIVE_BALANCE_INCREMENT); // TODO Electra: verify calculation + const churnLimitByTotalActiveBalance = Math.floor( + (state.epochCtx.totalActiveBalanceIncrements / state.config.CHURN_LIMIT_QUOTIENT) * EFFECTIVE_BALANCE_INCREMENT + ); // TODO Electra: verify calculation - const churn = Math.max(churnLimitByTotalActiveBalance, state.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_ELECTRA); + const churn = Math.max(churnLimitByTotalActiveBalance, state.config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA); - return churn - churn % EFFECTIVE_BALANCE_INCREMENT; + return churn - (churn % EFFECTIVE_BALANCE_INCREMENT); } - + export function getChurnLimit(config: ChainForkConfig, activeValidatorCount: number): number { return Math.max(config.MIN_PER_EPOCH_CHURN_LIMIT, intDiv(activeValidatorCount, config.CHURN_LIMIT_QUOTIENT)); } diff --git a/packages/state-transition/test/perf/block/processWithdrawals.test.ts b/packages/state-transition/test/perf/block/processWithdrawals.test.ts index 0edd2b39dc02..66d624b39bfd 100644 --- a/packages/state-transition/test/perf/block/processWithdrawals.test.ts +++ b/packages/state-transition/test/perf/block/processWithdrawals.test.ts @@ -1,9 +1,9 @@ import {itBench} from "@dapplion/benchmark"; +import {ForkSeq} from "@lodestar/params"; import {CachedBeaconStateCapella} from "../../../src/index.js"; import {getExpectedWithdrawals} from "../../../src/block/processWithdrawals.js"; import {numValidators} from "../util.js"; import {getExpectedWithdrawalsTestData, WithdrawalOpts} from "../../utils/capella.js"; -import { ForkSeq } from "@lodestar/params"; // PERF: Fixed cost for MAX_WITHDRAWALS_PER_PAYLOAD probes // + cost 'proportional' to $VALIDATOR_COUNT with balances under MAX_EFFECTIVE_BALANCE or diff --git a/packages/state-transition/test/unit/block/processWithdrawals.test.ts b/packages/state-transition/test/unit/block/processWithdrawals.test.ts index 5aefec40dcec..7b708d108a7b 100644 --- a/packages/state-transition/test/unit/block/processWithdrawals.test.ts +++ b/packages/state-transition/test/unit/block/processWithdrawals.test.ts @@ -1,9 +1,9 @@ import {describe, it, expect} from "vitest"; +import {ForkSeq} from "@lodestar/params"; import {getExpectedWithdrawals} from "../../../src/block/processWithdrawals.js"; import {numValidators} from "../../perf/util.js"; import {getExpectedWithdrawalsTestData, WithdrawalOpts} from "../../utils/capella.js"; import {beforeValue} from "../../utils/beforeValue.js"; -import { ForkSeq } from "@lodestar/params"; describe("getExpectedWithdrawals", () => { const vc = numValidators; diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index b358ff8d3f8f..8be941ae5688 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -17,6 +17,10 @@ import { MAX_ATTESTATIONS_ELECTRA, MAX_ATTESTER_SLASHINGS_ELECTRA, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD, + MAX_CONSOLIDATIONS, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, } from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 399e918160b7..7c1d06fa168b 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -50,4 +50,4 @@ export type PendingBalanceDeposit = ValueOf; export type PartialWithdrawal = ValueOf; export type PendingConsolidation = ValueOf; -export type ExecutionLayerWithdrawRequest = ValueOf; \ No newline at end of file +export type ExecutionLayerWithdrawRequest = ValueOf; From 1f473fc7127f4327bf190dc1d6860f52b4824ab4 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 1 Apr 2024 17:40:56 +0800 Subject: [PATCH 18/41] lint --- .../src/block/processConsolidation.ts | 5 +- .../src/block/processDeposit.ts | 71 +++++++++---------- .../processExecutionLayerWithdrawRequest.ts | 15 ++-- .../src/block/processOperations.ts | 3 +- .../src/block/processWithdrawals.ts | 2 +- .../src/block/slashValidator.ts | 8 +-- .../src/epoch/processPendingConsolidations.ts | 2 +- packages/state-transition/src/util/capella.ts | 2 +- packages/types/src/electra/sszTypes.ts | 19 ----- 9 files changed, 50 insertions(+), 77 deletions(-) diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index 18d191cba369..d04e38c1aa45 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -29,7 +29,10 @@ export function processConsolidation( state.pendingConsolidations.push(pendingConsolidation); } -function assertValidConsolidation(state: CachedBeaconStateElectra, signedConsolidation: electra.SignedConsolidation): void { +function assertValidConsolidation( + state: CachedBeaconStateElectra, + signedConsolidation: electra.SignedConsolidation +): void { // If the pending consolidations queue is full, no consolidations are allowed in the block if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) { throw new Error("Pending consolidation queue is full"); diff --git a/packages/state-transition/src/block/processDeposit.ts b/packages/state-transition/src/block/processDeposit.ts index 5097bd721ca3..1b9cb71eb488 100644 --- a/packages/state-transition/src/block/processDeposit.ts +++ b/packages/state-transition/src/block/processDeposit.ts @@ -80,8 +80,17 @@ export function applyDeposit( } addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, amount); } else { - // increase balance by deposit amount - increaseBalance(state, cachedIndex, amount); + if (fork < ForkSeq.electra) { + // increase balance by deposit amount right away pre-electra + increaseBalance(state, cachedIndex, amount); + } else if (fork >= ForkSeq.electra) { + const stateElectra = state as CachedBeaconStateElectra; + const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ + index: cachedIndex, + amount: BigInt(amount), + }); + stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); + } } } @@ -94,7 +103,8 @@ function addValidatorToRegistry( ): void { const {validators, epochCtx} = state; // add validator and balance entries - const effectiveBalance = fork < ForkSeq.electra ? Math.min(amount - (amount % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE) : 0; + const effectiveBalance = + fork < ForkSeq.electra ? Math.min(amount - (amount % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE) : 0; validators.push( ssz.phase0.Validator.toViewDU({ pubkey, @@ -108,13 +118,13 @@ function addValidatorToRegistry( }) ); - const validatorIndex = validators.length - 1; - // TODO Electra: Review this - // Updating here is better than updating at once on epoch transition - // - Simplify genesis fn applyDeposits(): effectiveBalanceIncrements is populated immediately - // - Keep related code together to reduce risk of breaking this cache - // - Should have equal performance since it sets a value in a flat array - epochCtx.effectiveBalanceIncrementsSet(validatorIndex, effectiveBalance); + const validatorIndex = validators.length - 1; + // TODO Electra: Review this + // Updating here is better than updating at once on epoch transition + // - Simplify genesis fn applyDeposits(): effectiveBalanceIncrements is populated immediately + // - Keep related code together to reduce risk of breaking this cache + // - Should have equal performance since it sets a value in a flat array + epochCtx.effectiveBalanceIncrementsSet(validatorIndex, effectiveBalance); // now that there is a new validator, update the epoch context with the new pubkey epochCtx.addPubkey(validatorIndex, pubkey); @@ -125,33 +135,20 @@ function addValidatorToRegistry( stateAltair.inactivityScores.push(0); - // add participation caches - stateAltair.previousEpochParticipation.push(0); - stateAltair.currentEpochParticipation.push(0); - } + // add participation caches + stateAltair.previousEpochParticipation.push(0); + stateAltair.currentEpochParticipation.push(0); + } - if (fork < ForkSeq.electra) { - state.balances.push(amount); - } else if (fork >= ForkSeq.electra) { - state.balances.push(0); - const stateElectra = state as CachedBeaconStateElectra; - const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ - index: validatorIndex, - amount: BigInt(amount), - }); - stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); - } - } else { - if (fork < ForkSeq.electra) { - // increase balance by deposit amount right away pre-electra - increaseBalance(state, cachedIndex, amount); - } else if (fork >= ForkSeq.electra) { - const stateElectra = state as CachedBeaconStateElectra; - const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ - index: cachedIndex, - amount: BigInt(amount), - }); - stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); - } + if (fork < ForkSeq.electra) { + state.balances.push(amount); + } else if (fork >= ForkSeq.electra) { + state.balances.push(0); + const stateElectra = state as CachedBeaconStateElectra; + const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ + index: validatorIndex, + amount: BigInt(amount), + }); + stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); } } diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index ba4ad29fca56..ab62c529dbe8 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -1,16 +1,9 @@ import {toHexString} from "@chainsafe/ssz"; -import { electra, phase0, ssz} from "@lodestar/types"; -import { - FAR_FUTURE_EPOCH, - MIN_ACTIVATION_BALANCE, - PENDING_PARTIAL_WITHDRAWALS_LIMIT, -} from "@lodestar/params"; +import {electra, phase0, ssz} from "@lodestar/types"; +import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; -import { - hasCompoundingWithdrawalCredential, - hasExecutionWithdrawalCredential, -} from "../util/capella.js"; +import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/capella.js"; import {isActiveValidator} from "../util/validator.js"; import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; import {initiateValidatorExit} from "./initiateValidatorExit.js"; @@ -39,7 +32,7 @@ export function processExecutionLayerWithdrawRequest( ); } - const validator = state.validators.getReadonly(validatorIndex); + const validator = validators.getReadonly(validatorIndex); if (!isValidValidator(validator, executionLayerWithdrawRequest.sourceAddress, state)) { return; diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 35bcb259c282..96c033fa81f3 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -1,5 +1,5 @@ import {allForks, capella, electra} from "@lodestar/types"; -import {ForkSeq, MAX_DEPOSITS} from "@lodestar/params"; +import {ForkSeq} from "@lodestar/params"; import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; import {getEth1DepositCount} from "../util/deposit.js"; @@ -75,7 +75,6 @@ export function processOperations( processDepositReceipt(fork, stateElectra, depositReceipt); } - for (const withdrawRequest of bodyElectra.executionPayload.withdrawaRequests) { processExecutionLayerWithdrawRequest(stateElectra, withdrawRequest); } diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 7da97c1ab7f7..9bfd1a25d0ff 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -150,7 +150,7 @@ export function getExpectedWithdrawals( amount: BigInt(balance), }); withdrawalIndex++; - } else if (isPartiallyWithdrawableValidator(fork, validator, balance, epoch)) { + } else if (isPartiallyWithdrawableValidator(fork, validator, balance)) { withdrawals.push({ index: withdrawalIndex, validatorIndex, diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index 471ef1f13830..e2210f7a4d48 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -53,10 +53,10 @@ export function slashValidator( fork === ForkSeq.phase0 ? MIN_SLASHING_PENALTY_QUOTIENT : fork === ForkSeq.altair - ? MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR - : fork === ForkSeq.bellatrix - ? MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX - : MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA; + ? MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR + : fork === ForkSeq.bellatrix + ? MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX + : MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA; decreaseBalance(state, slashedIndex, Math.floor(effectiveBalance / minSlashingPenaltyQuotient)); // apply proposer and whistleblower rewards diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 71cd79b8ecbf..76f3dd766549 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -1,5 +1,5 @@ import {COMPOUNDING_WITHDRAWAL_PREFIX} from "@lodestar/params"; -import { CachedBeaconStateElectra} from "../types.js"; +import {CachedBeaconStateElectra} from "../types.js"; import {decreaseBalance, increaseBalance} from "../util/balance.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index 599535d981fc..9ecb4e2e7d12 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -59,7 +59,7 @@ export function isFullyWithdrawableValidator( export function isPartiallyWithdrawableValidator( fork: ForkSeq, validatorCredential: ValidatorInfo, - balance: number, + balance: number ): boolean { const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 8be941ae5688..bfb0e54db919 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -28,7 +28,6 @@ import {ssz as altairSsz} from "../altair/index.js"; import {ssz as bellatrixSsz} from "../bellatrix/index.js"; import {ssz as capellaSsz} from "../capella/index.js"; import {ssz as denebSsz} from "../deneb/index.js"; -import { MAX_CONSOLIDATIONS, PENDING_BALANCE_DEPOSITS_LIMIT, PENDING_CONSOLIDATIONS_LIMIT, PENDING_PARTIAL_WITHDRAWALS_LIMIT } from "@lodestar/params"; const { Epoch, @@ -181,23 +180,6 @@ export const SignedConsolidation = new ContainerType( {typeName: "SignedConsolidation", jsonCase: "eth2"} ); -export const Consolidation = new ContainerType( - { - sourceIndex: ValidatorIndex, - targetIndex: ValidatorIndex, - epoch: Epoch, - }, - {typeName: "Consolidation", jsonCase: "eth2"} -); - -export const SignedConsolidation = new ContainerType( - { - message: Consolidation, - signature: BLSSignature, - }, - {typeName: "SignedConsolidation", jsonCase: "eth2"} -); - // We have to preserve Fields ordering while changing the type of ExecutionPayload export const BeaconBlockBody = new ContainerType( { @@ -222,7 +204,6 @@ export const BeaconBlock = new ContainerType( { ...denebSsz.BeaconBlock.fields, body: BeaconBlockBody, // Modified in ELECTRA - body: BeaconBlockBody, // Modified in ELECTRA }, {typeName: "BeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true} ); From ff30b74aab261de7f090fd7c1b7577daa4d0b6b4 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 1 Apr 2024 18:08:40 +0800 Subject: [PATCH 19/41] Update state-transition utils --- .../src/block/processWithdrawals.ts | 4 +- packages/state-transition/src/util/capella.ts | 37 +++++++------------ .../state-transition/src/util/validator.ts | 18 ++++++++- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 9bfd1a25d0ff..30fd92453334 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -12,7 +12,7 @@ import { import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; import { decreaseBalance, - getValidatorExcessBalance, + getValidatorMaxEffectiveBalance, hasEth1WithdrawalCredential, hasExecutionWithdrawalCredential, isCapellaPayloadHeader, @@ -155,7 +155,7 @@ export function getExpectedWithdrawals( index: withdrawalIndex, validatorIndex, address: validator.withdrawalCredentials.slice(12), - amount: BigInt(getValidatorExcessBalance(validator.withdrawalCredentials, balance)), + amount: BigInt(balance - getValidatorMaxEffectiveBalance(validator.withdrawalCredentials)), }); withdrawalIndex++; } diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index 9ecb4e2e7d12..1ed02307b637 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -7,6 +7,7 @@ import { MIN_ACTIVATION_BALANCE, } from "@lodestar/params"; import {phase0} from "@lodestar/types"; +import { getValidatorMaxEffectiveBalance } from "./validator"; type ValidatorInfo = Pick; @@ -27,17 +28,6 @@ export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Arr ); } -export function getValidatorExcessBalance(withdrawalCredentials: Uint8Array, validatorBalance: number): number { - // Compounding withdrawal credential only available since Electra - if (hasCompoundingWithdrawalCredential(withdrawalCredentials)) { - return validatorBalance - MAX_EFFECTIVE_BALANCE_ELECTRA; - } else if (hasEth1WithdrawalCredential(withdrawalCredentials)) { - return validatorBalance - MIN_ACTIVATION_BALANCE; - } - - return 0; -} - export function isFullyWithdrawableValidator( fork: ForkSeq, validatorCredential: ValidatorInfo, @@ -62,20 +52,19 @@ export function isPartiallyWithdrawableValidator( balance: number ): boolean { const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; - if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { - return ( - hasEth1WithdrawalCredential(withdrawalCredential) && - effectiveBalance === MAX_EFFECTIVE_BALANCE && - balance > MAX_EFFECTIVE_BALANCE - ); - } - if (fork === ForkSeq.electra) { - return ( - hasExecutionWithdrawalCredential(withdrawalCredential) && - getValidatorExcessBalance(withdrawalCredential, balance) > 0 - ); + if (fork < ForkSeq.capella) { + throw new Error(`Unsupported fork`); } - return false; + const validatorMaxEffectiveBalance = (fork === ForkSeq.capella || fork === ForkSeq.deneb) ? MAX_EFFECTIVE_BALANCE : getValidatorMaxEffectiveBalance(withdrawalCredential); + const hasMaxEffectiveBalance = effectiveBalance === validatorMaxEffectiveBalance; + const hasExcessBalance = balance > validatorMaxEffectiveBalance; + + return ( + hasEth1WithdrawalCredential(withdrawalCredential) && + hasMaxEffectiveBalance && + hasExcessBalance + ); + } diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index e7dea793023e..1a939c993a73 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -1,8 +1,9 @@ import {Epoch, phase0, ValidatorIndex} from "@lodestar/types"; import {intDiv} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; -import {EFFECTIVE_BALANCE_INCREMENT, ForkSeq} from "@lodestar/params"; +import {EFFECTIVE_BALANCE_INCREMENT, ForkSeq, MAX_EFFECTIVE_BALANCE_ELECTRA, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; import {BeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; +import { hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential } from "./capella.js"; /** * Check if [[validator]] is active @@ -68,3 +69,18 @@ export function getActivationExitConsolidationChurnLimit(state: CachedBeaconStat export function getChurnLimit(config: ChainForkConfig, activeValidatorCount: number): number { return Math.max(config.MIN_PER_EPOCH_CHURN_LIMIT, intDiv(activeValidatorCount, config.CHURN_LIMIT_QUOTIENT)); } + +export function getValidatorMaxEffectiveBalance(withdrawalCredentials: Uint8Array): number { + // Compounding withdrawal credential only available since Electra + if (hasCompoundingWithdrawalCredential(withdrawalCredentials)) { + return MAX_EFFECTIVE_BALANCE_ELECTRA; + } else { + return MIN_ACTIVATION_BALANCE; + } +} + +export function getActiveBalance(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex) { + const validatorMaxEffectiveBalance = getValidatorMaxEffectiveBalance(state.validators.getReadonly(validatorIndex).withdrawalCredentials); + + return Math.min(state.balances.get(validatorIndex), validatorMaxEffectiveBalance); +} \ No newline at end of file From 16e18e139b1d05a1ca59eff741e3ab431b3fcee4 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 1 Apr 2024 18:42:13 +0800 Subject: [PATCH 20/41] processExecutionLayerWithdrawRequest --- .../src/block/processExecutionLayerWithdrawRequest.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index ab62c529dbe8..2978881e02ee 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -49,8 +49,13 @@ export function processExecutionLayerWithdrawRequest( .reduce((total, item) => total + Number(item.amount), 0); const validatorBalance = state.balances.get(validatorIndex); + // If pending balance is non zero, ignore + if (isFullExitRequest && pendingBalanceToWithdraw > 0) { + return; + } + // only exit validator if it has no pending withdrawals in the queue - if (isFullExitRequest && pendingBalanceToWithdraw === 0) { + if (isFullExitRequest) { initiateValidatorExit(state, validator); } else if (validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw) { const amountToWithdraw = Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount); From cf27a34efdffb27f11663101d3c8561e3f30e32e Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 1 Apr 2024 18:55:37 +0800 Subject: [PATCH 21/41] processConsolidation --- .../src/block/processConsolidation.ts | 7 +++--- .../src/epoch/processPendingConsolidations.ts | 3 ++- packages/state-transition/src/util/epoch.ts | 25 +++++++++++++++++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index d04e38c1aa45..ee5967615c77 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -4,8 +4,9 @@ import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} import {verifyConsolidationSignature} from "../signatureSets/index.js"; import {CachedBeaconStateElectra} from "../types.js"; -import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; +import {getActiveBalance, getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; import {hasExecutionWithdrawalCredential} from "../util/capella.js"; +import { computeConsolidationEpochAndUpdateChurn } from "../util/epoch.js"; export function processConsolidation( state: CachedBeaconStateElectra, @@ -15,10 +16,10 @@ export function processConsolidation( // Initiate source validator exit and append pending consolidation const {sourceIndex, targetIndex} = signedConsolidation.message; - const activeBalance = 0; // TODO Electra: get_active_balance() + const activeBalance = getActiveBalance(state, sourceIndex); const sourceValidator = state.validators.get(sourceIndex); - const exitEpoch = 0; // TODO Electra: compute_consolidation_epoch_and_update_churn + const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(activeBalance)); sourceValidator.exitEpoch = exitEpoch; sourceValidator.withdrawableEpoch = exitEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 76f3dd766549..e6cb2d4368ce 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -2,6 +2,7 @@ import {COMPOUNDING_WITHDRAWAL_PREFIX} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; import {decreaseBalance, increaseBalance} from "../util/balance.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; +import { getActiveBalance } from "../util/validator.js"; /** * TODO Electra: jdoc @@ -23,7 +24,7 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v } // Move active balance to target. Excess balance is withdrawable. - const activeBalance = 0; // TODO Electra: get_active_balance() + const activeBalance = getActiveBalance(state, sourceIndex); decreaseBalance(state, sourceIndex, activeBalance); increaseBalance(state, targetIndex, activeBalance); diff --git a/packages/state-transition/src/util/epoch.ts b/packages/state-transition/src/util/epoch.ts index 03eccd3467e8..972ea6597a5f 100644 --- a/packages/state-transition/src/util/epoch.ts +++ b/packages/state-transition/src/util/epoch.ts @@ -1,7 +1,7 @@ import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, GENESIS_EPOCH, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; import {allForks, Epoch, Gwei, Slot, SyncPeriod} from "@lodestar/types"; import {CachedBeaconStateElectra} from "../types.js"; -import {getActivationExitChurnLimit} from "./validator.js"; +import {getActivationExitChurnLimit, getConsolidationChurnLimit} from "./validator.js"; /** * Return the epoch number at the given slot. @@ -60,13 +60,34 @@ export function computeExitEpochAndUpdateChurn(state: CachedBeaconStateElectra, const additionalEpochs = balanceToProcess / BigInt(perEpochChurn); const remainder = balanceToProcess % BigInt(perEpochChurn); - state.earliestExitEpoch += Number(additionalEpochs); + state.earliestExitEpoch += Number(additionalEpochs) + 1; state.exitBalanceToConsume = BigInt(perEpochChurn) - remainder; } return state.earliestExitEpoch; } +export function computeConsolidationEpochAndUpdateChurn(state: CachedBeaconStateElectra, consolidationBalance: Gwei): number { + const earliestConsolidationEpoch = computeActivationExitEpoch(state.epochCtx.epoch); + const perEpochConsolidationChurn = getConsolidationChurnLimit(state); + + // New epoch for consolidations + if (state.earliestConsolidationEpoch < earliestConsolidationEpoch) { + state.earliestConsolidationEpoch = earliestConsolidationEpoch; + state.consolidationBalanceToConsume = BigInt(perEpochConsolidationChurn); + } else { + // Consolidation doesn't fit in the current earliest epoch. + const balanceToProcess = consolidationBalance - state.consolidationBalanceToConsume; + const additionalEpochs = balanceToProcess / BigInt(perEpochConsolidationChurn); + const remainder = balanceToProcess % BigInt(perEpochConsolidationChurn); + + state.earliestConsolidationEpoch += Number(additionalEpochs) + 1; + state.exitBalanceToConsume = BigInt(perEpochConsolidationChurn) - remainder; + } + + return state.earliestConsolidationEpoch; +} + /** * Return the current epoch of the given state. */ From 284b925f345acca5a50b27d841a542fca3620f90 Mon Sep 17 00:00:00 2001 From: NC Date: Sat, 6 Apr 2024 23:51:24 +0800 Subject: [PATCH 22/41] queueExcessActiveBalance --- .../src/block/processDeposit.ts | 9 +- .../processExecutionLayerWithdrawRequest.ts | 2 +- .../epoch/processEffectiveBalanceUpdates.ts | 2 +- .../src/epoch/processPendingConsolidations.ts | 4 +- .../src/slot/upgradeStateToElectra.ts | 12 +++ packages/state-transition/src/util/capella.ts | 66 +------------- packages/state-transition/src/util/electra.ts | 88 +++++++++++++++++++ packages/state-transition/src/util/index.ts | 1 + .../state-transition/src/util/validator.ts | 2 +- 9 files changed, 117 insertions(+), 69 deletions(-) create mode 100644 packages/state-transition/src/util/electra.ts diff --git a/packages/state-transition/src/block/processDeposit.ts b/packages/state-transition/src/block/processDeposit.ts index 1b9cb71eb488..d408770e2a6a 100644 --- a/packages/state-transition/src/block/processDeposit.ts +++ b/packages/state-transition/src/block/processDeposit.ts @@ -15,7 +15,7 @@ import { import {DepositData} from "@lodestar/types/lib/phase0/types.js"; import {DepositReceipt} from "@lodestar/types/lib/electra/types.js"; import {ZERO_HASH} from "../constants/index.js"; -import {computeDomain, computeSigningRoot, increaseBalance} from "../util/index.js"; +import {computeDomain, computeSigningRoot, hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, increaseBalance, switchToCompoundingValidator} from "../util/index.js"; import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js"; /** @@ -90,6 +90,13 @@ export function applyDeposit( amount: BigInt(amount), }); stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); + + if (hasCompoundingWithdrawalCredential(withdrawalCredentials) + && hasEth1WithdrawalCredential(validators.getReadonly(cachedIndex).withdrawalCredentials) + // TODO Electra: is_valid_deposit_signature + ) { + switchToCompoundingValidator(stateElectra, cachedIndex); + } } } } diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index 2978881e02ee..f2c584667671 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -3,7 +3,7 @@ import {electra, phase0, ssz} from "@lodestar/types"; import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; -import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/capella.js"; +import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js"; import {isActiveValidator} from "../util/validator.js"; import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; import {initiateValidatorExit} from "./initiateValidatorExit.js"; diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index db249db4f226..e83d2545ef50 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -10,7 +10,7 @@ import { TIMELY_TARGET_FLAG_INDEX, } from "@lodestar/params"; import {EpochTransitionCache, CachedBeaconStateAllForks, BeaconStateAltair} from "../types.js"; -import {hasCompoundingWithdrawalCredential} from "../util/capella.js"; +import {hasCompoundingWithdrawalCredential} from "../util/electra.js"; /** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index e6cb2d4368ce..0627930a5cd3 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -3,6 +3,7 @@ import {CachedBeaconStateElectra} from "../types.js"; import {decreaseBalance, increaseBalance} from "../util/balance.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; import { getActiveBalance } from "../util/validator.js"; +import { switchToCompoundingValidator } from "../util/electra.js"; /** * TODO Electra: jdoc @@ -22,7 +23,8 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v if (sourceValidator.withdrawableEpoch > state.epochCtx.epoch) { break; } - + // Churn any target excess active balance of target and raise its max + switchToCompoundingValidator(state, targetIndex); // Move active balance to target. Excess balance is withdrawable. const activeBalance = getActiveBalance(state, sourceIndex); decreaseBalance(state, sourceIndex, activeBalance); diff --git a/packages/state-transition/src/slot/upgradeStateToElectra.ts b/packages/state-transition/src/slot/upgradeStateToElectra.ts index f41c37af94aa..1a94344546f1 100644 --- a/packages/state-transition/src/slot/upgradeStateToElectra.ts +++ b/packages/state-transition/src/slot/upgradeStateToElectra.ts @@ -2,6 +2,7 @@ import {ssz} from "@lodestar/types"; import {UNSET_DEPOSIT_RECEIPTS_START_INDEX} from "@lodestar/params"; import {CachedBeaconStateDeneb} from "../types.js"; import {CachedBeaconStateElectra, getCachedBeaconState} from "../cache/stateCache.js"; +import { hasCompoundingWithdrawalCredential, queueExcessActiveBalance } from "../util/electra.js"; /** * Upgrade a state from Capella to Deneb. @@ -24,6 +25,17 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache // default value of depositReceiptsStartIndex is UNSET_DEPOSIT_RECEIPTS_START_INDEX stateElectra.depositReceiptsStartIndex = UNSET_DEPOSIT_RECEIPTS_START_INDEX; + // [EIP-7251]: Ensure early adopters of compounding credentials go through the activation churn + const validatorsArr = stateElectra.validators.getAllReadonly(); + + for (let i = 0; i < validatorsArr.length; i++) { + const withdrawalCredential = validatorsArr[i].withdrawalCredentials; + if (hasCompoundingWithdrawalCredential(withdrawalCredential)) { + queueExcessActiveBalance(stateElectra, i); + } + } + + // Commit new added fields ViewDU to the root node stateElectra.commit(); // Clear cache to ensure the cache of deneb fields is not used by new ELECTRA fields diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index 1ed02307b637..2e90d44cc744 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -1,70 +1,8 @@ -import { - COMPOUNDING_WITHDRAWAL_PREFIX, - ETH1_ADDRESS_WITHDRAWAL_PREFIX, - ForkSeq, - MAX_EFFECTIVE_BALANCE, - MAX_EFFECTIVE_BALANCE_ELECTRA, - MIN_ACTIVATION_BALANCE, -} from "@lodestar/params"; -import {phase0} from "@lodestar/types"; -import { getValidatorMaxEffectiveBalance } from "./validator"; - -type ValidatorInfo = Pick; +import { ETH1_ADDRESS_WITHDRAWAL_PREFIX } from "@lodestar/params/"; /** * https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential */ export function hasEth1WithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return withdrawalCredentials[0] === ETH1_ADDRESS_WITHDRAWAL_PREFIX; -} - -export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { - return withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX; -} - -export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { - return ( - hasCompoundingWithdrawalCredential(withdrawalCredentials) || hasEth1WithdrawalCredential(withdrawalCredentials) - ); -} - -export function isFullyWithdrawableValidator( - fork: ForkSeq, - validatorCredential: ValidatorInfo, - balance: number, - epoch: number -): boolean { - const {withdrawableEpoch, withdrawalCredentials: withdrawalCredential} = validatorCredential; - if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { - return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; - } - - if (fork === ForkSeq.electra) { - return hasExecutionWithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; - } - - return false; -} - -export function isPartiallyWithdrawableValidator( - fork: ForkSeq, - validatorCredential: ValidatorInfo, - balance: number -): boolean { - const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; - - if (fork < ForkSeq.capella) { - throw new Error(`Unsupported fork`); - } - - const validatorMaxEffectiveBalance = (fork === ForkSeq.capella || fork === ForkSeq.deneb) ? MAX_EFFECTIVE_BALANCE : getValidatorMaxEffectiveBalance(withdrawalCredential); - const hasMaxEffectiveBalance = effectiveBalance === validatorMaxEffectiveBalance; - const hasExcessBalance = balance > validatorMaxEffectiveBalance; - - return ( - hasEth1WithdrawalCredential(withdrawalCredential) && - hasMaxEffectiveBalance && - hasExcessBalance - ); - -} +} \ No newline at end of file diff --git a/packages/state-transition/src/util/electra.ts b/packages/state-transition/src/util/electra.ts new file mode 100644 index 000000000000..62598445b511 --- /dev/null +++ b/packages/state-transition/src/util/electra.ts @@ -0,0 +1,88 @@ +import { + COMPOUNDING_WITHDRAWAL_PREFIX, + ETH1_ADDRESS_WITHDRAWAL_PREFIX, + ForkSeq, + MAX_EFFECTIVE_BALANCE, + MAX_EFFECTIVE_BALANCE_ELECTRA, + MIN_ACTIVATION_BALANCE, +} from "@lodestar/params"; +import {ValidatorIndex, phase0, ssz} from "@lodestar/types"; +import { getValidatorMaxEffectiveBalance } from "./validator"; +import { hasEth1WithdrawalCredential } from "./capella"; +import { CachedBeaconStateElectra } from "../types"; + +type ValidatorInfo = Pick; + +export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { + return withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX; +} + +export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { + return ( + hasCompoundingWithdrawalCredential(withdrawalCredentials) || hasEth1WithdrawalCredential(withdrawalCredentials) + ); +} + +export function isFullyWithdrawableValidator( + fork: ForkSeq, + validatorCredential: ValidatorInfo, + balance: number, + epoch: number +): boolean { + const {withdrawableEpoch, withdrawalCredentials: withdrawalCredential} = validatorCredential; + if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { + return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; + } + + if (fork === ForkSeq.electra) { + return hasExecutionWithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; + } + + return false; +} + +export function isPartiallyWithdrawableValidator( + fork: ForkSeq, + validatorCredential: ValidatorInfo, + balance: number +): boolean { + const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; + + if (fork < ForkSeq.capella) { + throw new Error(`Unsupported fork`); + } + + const validatorMaxEffectiveBalance = (fork === ForkSeq.capella || fork === ForkSeq.deneb) ? MAX_EFFECTIVE_BALANCE : getValidatorMaxEffectiveBalance(withdrawalCredential); + const hasMaxEffectiveBalance = effectiveBalance === validatorMaxEffectiveBalance; + const hasExcessBalance = balance > validatorMaxEffectiveBalance; + + return ( + hasEth1WithdrawalCredential(withdrawalCredential) && + hasMaxEffectiveBalance && + hasExcessBalance + ); + +} + +export function switchToCompoundingValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void { + const validator = state.validators.get(index); + + if (hasEth1WithdrawalCredential(validator.withdrawalCredentials)) { + validator.withdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; + queueExcessActiveBalance(state, index); + } +} + +export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: ValidatorIndex): void { + const balance = state.balances.get(index); + if (balance > MIN_ACTIVATION_BALANCE) { + const excessBalance = balance - MIN_ACTIVATION_BALANCE; + state.balances.set(index, MIN_ACTIVATION_BALANCE); + + const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ + index, + amount: BigInt(excessBalance), + }); + state.pendingBalanceDeposits.push(pendingBalanceDeposit); + } +} \ No newline at end of file diff --git a/packages/state-transition/src/util/index.ts b/packages/state-transition/src/util/index.ts index ba998f65b254..27c24590e408 100644 --- a/packages/state-transition/src/util/index.ts +++ b/packages/state-transition/src/util/index.ts @@ -24,3 +24,4 @@ export * from "./syncCommittee.js"; export * from "./validator.js"; export * from "./weakSubjectivity.js"; export * from "./deposit.js"; +export * from "./electra.js"; \ No newline at end of file diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 1a939c993a73..205492d14dd7 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -3,7 +3,7 @@ import {intDiv} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; import {EFFECTIVE_BALANCE_INCREMENT, ForkSeq, MAX_EFFECTIVE_BALANCE_ELECTRA, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; import {BeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; -import { hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential } from "./capella.js"; +import { hasCompoundingWithdrawalCredential} from "./electra.js"; /** * Check if [[validator]] is active From f15a42c19ddfb05cb63f12cf1bb30f9fc8f48d6c Mon Sep 17 00:00:00 2001 From: NC Date: Sun, 7 Apr 2024 10:12:44 +0800 Subject: [PATCH 23/41] isValidDepositSignature --- .../src/block/processDeposit.ts | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/state-transition/src/block/processDeposit.ts b/packages/state-transition/src/block/processDeposit.ts index d408770e2a6a..351a5407dd70 100644 --- a/packages/state-transition/src/block/processDeposit.ts +++ b/packages/state-transition/src/block/processDeposit.ts @@ -17,6 +17,7 @@ import {DepositReceipt} from "@lodestar/types/lib/electra/types.js"; import {ZERO_HASH} from "../constants/index.js"; import {computeDomain, computeSigningRoot, hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, increaseBalance, switchToCompoundingValidator} from "../util/index.js"; import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js"; +import { BeaconConfig } from "@lodestar/config"; /** * Process a Deposit operation. Potentially adds a new validator to the registry. Mutates the validators and balances @@ -59,26 +60,10 @@ export function applyDeposit( const cachedIndex = epochCtx.getValidatorIndex(pubkey); if (cachedIndex === undefined || !Number.isSafeInteger(cachedIndex) || cachedIndex >= validators.length) { - // verify the deposit signature (proof of posession) which is not checked by the deposit contract - const depositMessage = { - pubkey, - withdrawalCredentials, - amount, - }; - // fork-agnostic domain since deposits are valid across forks - const domain = computeDomain(DOMAIN_DEPOSIT, config.GENESIS_FORK_VERSION, ZERO_HASH); - const signingRoot = computeSigningRoot(ssz.phase0.DepositMessage, depositMessage, domain); - try { - // Pubkeys must be checked for group + inf. This must be done only once when the validator deposit is processed - const publicKey = bls.PublicKey.fromBytes(pubkey, CoordType.affine, true); - const signature = bls.Signature.fromBytes(deposit.signature, CoordType.affine, true); - if (!signature.verify(publicKey, signingRoot)) { - return; - } - } catch (e) { - return; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature + + if (isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature)) { + addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, amount); } - addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, amount); } else { if (fork < ForkSeq.electra) { // increase balance by deposit amount right away pre-electra @@ -93,7 +78,7 @@ export function applyDeposit( if (hasCompoundingWithdrawalCredential(withdrawalCredentials) && hasEth1WithdrawalCredential(validators.getReadonly(cachedIndex).withdrawalCredentials) - // TODO Electra: is_valid_deposit_signature + && isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature) ) { switchToCompoundingValidator(stateElectra, cachedIndex); } @@ -159,3 +144,24 @@ function addValidatorToRegistry( stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); } } + +function isValidDepositSignature(config: BeaconConfig, pubkey: Uint8Array, withdrawalCredentials: Uint8Array, amount: number, depositSignature: Uint8Array): boolean { + // verify the deposit signature (proof of posession) which is not checked by the deposit contract + const depositMessage = { + pubkey, + withdrawalCredentials, + amount, + }; + // fork-agnostic domain since deposits are valid across forks + const domain = computeDomain(DOMAIN_DEPOSIT, config.GENESIS_FORK_VERSION, ZERO_HASH); + const signingRoot = computeSigningRoot(ssz.phase0.DepositMessage, depositMessage, domain); + try { + // Pubkeys must be checked for group + inf. This must be done only once when the validator deposit is processed + const publicKey = bls.PublicKey.fromBytes(pubkey, CoordType.affine, true); + const signature = bls.Signature.fromBytes(depositSignature, CoordType.affine, true); + + return signature.verify(publicKey, signingRoot); + } catch (e) { + return false; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature + } +} \ No newline at end of file From ca157c6f13027f466ea880846faa9d878cbde70e Mon Sep 17 00:00:00 2001 From: NC Date: Sun, 7 Apr 2024 10:37:06 +0800 Subject: [PATCH 24/41] Add jsdoc and timer for new processEpoch functions --- packages/state-transition/src/epoch/index.ts | 16 +++++++++++++--- .../src/epoch/processPendingBalanceDeposits.ts | 6 +++++- .../src/epoch/processPendingConsolidations.ts | 15 ++++++++++----- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 3c20fd68753c..179346828b5d 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -70,6 +70,8 @@ export enum EpochTransitionStep { processEffectiveBalanceUpdates = "processEffectiveBalanceUpdates", processParticipationFlagUpdates = "processParticipationFlagUpdates", processSyncCommitteeUpdates = "processSyncCommitteeUpdates", + processPendingBalanceDeposits = "processPendingBalanceDeposits", + processPendingConsolidations = "processPendingConsolidations", } export function processEpoch( @@ -127,9 +129,17 @@ export function processEpoch( if (fork >= ForkSeq.electra) { const stateElectra = state as CachedBeaconStateElectra; - // TODO Electra: Add timer - processPendingBalanceDeposits(stateElectra); - processPendingConsolidations(stateElectra); + { + const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.processPendingBalanceDeposits}); + processPendingBalanceDeposits(stateElectra); + timer?.(); + } + + { + const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.processPendingConsolidations}); + processPendingConsolidations(stateElectra); + timer?.(); + } } { diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts index b7bd453fd986..c24f5e81348b 100644 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -3,7 +3,11 @@ import {increaseBalance} from "../util/balance.js"; import {getActivationExitChurnLimit} from "../util/validator.js"; /** - * TODO Electra: jdoc + * Starting from Electra: + * Process pending balance deposits from state subject to churn limit. + * For each eligible `deposit`, call `increaseBalance()`. + * Remove the processed deposits from `state.pendingBalanceDeposits`. + * Update `state.depositBalanceToConsume` for the next epoch */ export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): void { const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state)); diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 0627930a5cd3..51d62f1a7e0c 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -6,7 +6,16 @@ import { getActiveBalance } from "../util/validator.js"; import { switchToCompoundingValidator } from "../util/electra.js"; /** - * TODO Electra: jdoc + * Starting from Electra: + * Process every `pendingConsolidation` in `state.pendingConsolidations`. + * Churn limit was applied when enqueueing so we don't care about the limit here + * + * For each valid `pendingConsolidation`, update withdrawal credential of target + * validator to compounding, decrease balance of source validator and increase balance + * of target validator. + * + * Set `state.pendingConsolidation` to empty list at the end + * */ export function processPendingConsolidations(state: CachedBeaconStateElectra): void { let nextPendingConsolidation = 0; @@ -30,10 +39,6 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v decreaseBalance(state, sourceIndex, activeBalance); increaseBalance(state, targetIndex, activeBalance); - const targetValidator = state.validators.get(targetIndex); - if (hasEth1WithdrawalCredential(targetValidator.withdrawalCredentials)) { - targetValidator.withdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; - } nextPendingConsolidation++; } From 16914f3d01f404892452659384fd4fc543d7f168 Mon Sep 17 00:00:00 2001 From: NC Date: Sun, 7 Apr 2024 10:44:26 +0800 Subject: [PATCH 25/41] Lint --- .../src/block/processConsolidation.ts | 4 +-- .../src/block/processDeposit.ts | 29 ++++++++++++----- packages/state-transition/src/epoch/index.ts | 8 +++-- .../epoch/processPendingBalanceDeposits.ts | 4 +-- .../src/epoch/processPendingConsolidations.ts | 17 +++++----- .../src/slot/upgradeStateToElectra.ts | 3 +- packages/state-transition/src/util/capella.ts | 4 +-- packages/state-transition/src/util/electra.ts | 31 +++++++------------ packages/state-transition/src/util/epoch.ts | 7 +++-- packages/state-transition/src/util/index.ts | 2 +- .../state-transition/src/util/validator.ts | 17 +++++++--- 11 files changed, 71 insertions(+), 55 deletions(-) diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index ee5967615c77..20d28cc2b18f 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -5,8 +5,8 @@ import {verifyConsolidationSignature} from "../signatureSets/index.js"; import {CachedBeaconStateElectra} from "../types.js"; import {getActiveBalance, getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; -import {hasExecutionWithdrawalCredential} from "../util/capella.js"; -import { computeConsolidationEpochAndUpdateChurn } from "../util/epoch.js"; +import {hasExecutionWithdrawalCredential} from "../util/electra.js"; +import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; export function processConsolidation( state: CachedBeaconStateElectra, diff --git a/packages/state-transition/src/block/processDeposit.ts b/packages/state-transition/src/block/processDeposit.ts index 351a5407dd70..676d78e27578 100644 --- a/packages/state-transition/src/block/processDeposit.ts +++ b/packages/state-transition/src/block/processDeposit.ts @@ -14,10 +14,17 @@ import { import {DepositData} from "@lodestar/types/lib/phase0/types.js"; import {DepositReceipt} from "@lodestar/types/lib/electra/types.js"; +import {BeaconConfig} from "@lodestar/config"; import {ZERO_HASH} from "../constants/index.js"; -import {computeDomain, computeSigningRoot, hasCompoundingWithdrawalCredential, hasEth1WithdrawalCredential, increaseBalance, switchToCompoundingValidator} from "../util/index.js"; +import { + computeDomain, + computeSigningRoot, + hasCompoundingWithdrawalCredential, + hasEth1WithdrawalCredential, + increaseBalance, + switchToCompoundingValidator, +} from "../util/index.js"; import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js"; -import { BeaconConfig } from "@lodestar/config"; /** * Process a Deposit operation. Potentially adds a new validator to the registry. Mutates the validators and balances @@ -60,7 +67,6 @@ export function applyDeposit( const cachedIndex = epochCtx.getValidatorIndex(pubkey); if (cachedIndex === undefined || !Number.isSafeInteger(cachedIndex) || cachedIndex >= validators.length) { - if (isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature)) { addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, amount); } @@ -76,9 +82,10 @@ export function applyDeposit( }); stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); - if (hasCompoundingWithdrawalCredential(withdrawalCredentials) - && hasEth1WithdrawalCredential(validators.getReadonly(cachedIndex).withdrawalCredentials) - && isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature) + if ( + hasCompoundingWithdrawalCredential(withdrawalCredentials) && + hasEth1WithdrawalCredential(validators.getReadonly(cachedIndex).withdrawalCredentials) && + isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature) ) { switchToCompoundingValidator(stateElectra, cachedIndex); } @@ -145,7 +152,13 @@ function addValidatorToRegistry( } } -function isValidDepositSignature(config: BeaconConfig, pubkey: Uint8Array, withdrawalCredentials: Uint8Array, amount: number, depositSignature: Uint8Array): boolean { +function isValidDepositSignature( + config: BeaconConfig, + pubkey: Uint8Array, + withdrawalCredentials: Uint8Array, + amount: number, + depositSignature: Uint8Array +): boolean { // verify the deposit signature (proof of posession) which is not checked by the deposit contract const depositMessage = { pubkey, @@ -164,4 +177,4 @@ function isValidDepositSignature(config: BeaconConfig, pubkey: Uint8Array, withd } catch (e) { return false; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature } -} \ No newline at end of file +} diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 179346828b5d..6e736fdae2cc 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -130,13 +130,17 @@ export function processEpoch( if (fork >= ForkSeq.electra) { const stateElectra = state as CachedBeaconStateElectra; { - const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.processPendingBalanceDeposits}); + const timer = metrics?.epochTransitionStepTime.startTimer({ + step: EpochTransitionStep.processPendingBalanceDeposits, + }); processPendingBalanceDeposits(stateElectra); timer?.(); } { - const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.processPendingConsolidations}); + const timer = metrics?.epochTransitionStepTime.startTimer({ + step: EpochTransitionStep.processPendingConsolidations, + }); processPendingConsolidations(stateElectra); timer?.(); } diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts index c24f5e81348b..7b063d5daaba 100644 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -3,8 +3,8 @@ import {increaseBalance} from "../util/balance.js"; import {getActivationExitChurnLimit} from "../util/validator.js"; /** - * Starting from Electra: - * Process pending balance deposits from state subject to churn limit. + * Starting from Electra: + * Process pending balance deposits from state subject to churn limit and depsoitBalanceToConsume. * For each eligible `deposit`, call `increaseBalance()`. * Remove the processed deposits from `state.pendingBalanceDeposits`. * Update `state.depositBalanceToConsume` for the next epoch diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 51d62f1a7e0c..0d02fdbdedd9 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -1,21 +1,20 @@ -import {COMPOUNDING_WITHDRAWAL_PREFIX} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; import {decreaseBalance, increaseBalance} from "../util/balance.js"; -import {hasEth1WithdrawalCredential} from "../util/capella.js"; -import { getActiveBalance } from "../util/validator.js"; -import { switchToCompoundingValidator } from "../util/electra.js"; +import {getActiveBalance} from "../util/validator.js"; +import {switchToCompoundingValidator} from "../util/electra.js"; /** * Starting from Electra: * Process every `pendingConsolidation` in `state.pendingConsolidations`. * Churn limit was applied when enqueueing so we don't care about the limit here - * + * However we only process consolidations up to current epoch + * * For each valid `pendingConsolidation`, update withdrawal credential of target * validator to compounding, decrease balance of source validator and increase balance - * of target validator. - * - * Set `state.pendingConsolidation` to empty list at the end - * + * of target validator. + * + * Dequeue all processed consolidations from `state.pendingConsolidation` + * */ export function processPendingConsolidations(state: CachedBeaconStateElectra): void { let nextPendingConsolidation = 0; diff --git a/packages/state-transition/src/slot/upgradeStateToElectra.ts b/packages/state-transition/src/slot/upgradeStateToElectra.ts index 1a94344546f1..326b9087aa6a 100644 --- a/packages/state-transition/src/slot/upgradeStateToElectra.ts +++ b/packages/state-transition/src/slot/upgradeStateToElectra.ts @@ -2,7 +2,7 @@ import {ssz} from "@lodestar/types"; import {UNSET_DEPOSIT_RECEIPTS_START_INDEX} from "@lodestar/params"; import {CachedBeaconStateDeneb} from "../types.js"; import {CachedBeaconStateElectra, getCachedBeaconState} from "../cache/stateCache.js"; -import { hasCompoundingWithdrawalCredential, queueExcessActiveBalance } from "../util/electra.js"; +import {hasCompoundingWithdrawalCredential, queueExcessActiveBalance} from "../util/electra.js"; /** * Upgrade a state from Capella to Deneb. @@ -35,7 +35,6 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache } } - // Commit new added fields ViewDU to the root node stateElectra.commit(); // Clear cache to ensure the cache of deneb fields is not used by new ELECTRA fields diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index 2e90d44cc744..5f65f6d3bfa3 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -1,8 +1,8 @@ -import { ETH1_ADDRESS_WITHDRAWAL_PREFIX } from "@lodestar/params/"; +import {ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params/"; /** * https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential */ export function hasEth1WithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return withdrawalCredentials[0] === ETH1_ADDRESS_WITHDRAWAL_PREFIX; -} \ No newline at end of file +} diff --git a/packages/state-transition/src/util/electra.ts b/packages/state-transition/src/util/electra.ts index 62598445b511..96e22abda22a 100644 --- a/packages/state-transition/src/util/electra.ts +++ b/packages/state-transition/src/util/electra.ts @@ -1,15 +1,8 @@ -import { - COMPOUNDING_WITHDRAWAL_PREFIX, - ETH1_ADDRESS_WITHDRAWAL_PREFIX, - ForkSeq, - MAX_EFFECTIVE_BALANCE, - MAX_EFFECTIVE_BALANCE_ELECTRA, - MIN_ACTIVATION_BALANCE, -} from "@lodestar/params"; +import {COMPOUNDING_WITHDRAWAL_PREFIX, ForkSeq, MAX_EFFECTIVE_BALANCE, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; import {ValidatorIndex, phase0, ssz} from "@lodestar/types"; -import { getValidatorMaxEffectiveBalance } from "./validator"; -import { hasEth1WithdrawalCredential } from "./capella"; -import { CachedBeaconStateElectra } from "../types"; +import {CachedBeaconStateElectra} from "../types.js"; +import {getValidatorMaxEffectiveBalance} from "./validator.js"; +import {hasEth1WithdrawalCredential} from "./capella.js"; type ValidatorInfo = Pick; @@ -49,19 +42,17 @@ export function isPartiallyWithdrawableValidator( const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; if (fork < ForkSeq.capella) { - throw new Error(`Unsupported fork`); + throw new Error("Unsupported fork"); } - const validatorMaxEffectiveBalance = (fork === ForkSeq.capella || fork === ForkSeq.deneb) ? MAX_EFFECTIVE_BALANCE : getValidatorMaxEffectiveBalance(withdrawalCredential); + const validatorMaxEffectiveBalance = + fork === ForkSeq.capella || fork === ForkSeq.deneb + ? MAX_EFFECTIVE_BALANCE + : getValidatorMaxEffectiveBalance(withdrawalCredential); const hasMaxEffectiveBalance = effectiveBalance === validatorMaxEffectiveBalance; const hasExcessBalance = balance > validatorMaxEffectiveBalance; - return ( - hasEth1WithdrawalCredential(withdrawalCredential) && - hasMaxEffectiveBalance && - hasExcessBalance - ); - + return hasEth1WithdrawalCredential(withdrawalCredential) && hasMaxEffectiveBalance && hasExcessBalance; } export function switchToCompoundingValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void { @@ -85,4 +76,4 @@ export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: }); state.pendingBalanceDeposits.push(pendingBalanceDeposit); } -} \ No newline at end of file +} diff --git a/packages/state-transition/src/util/epoch.ts b/packages/state-transition/src/util/epoch.ts index 972ea6597a5f..f719284bc2f5 100644 --- a/packages/state-transition/src/util/epoch.ts +++ b/packages/state-transition/src/util/epoch.ts @@ -67,7 +67,10 @@ export function computeExitEpochAndUpdateChurn(state: CachedBeaconStateElectra, return state.earliestExitEpoch; } -export function computeConsolidationEpochAndUpdateChurn(state: CachedBeaconStateElectra, consolidationBalance: Gwei): number { +export function computeConsolidationEpochAndUpdateChurn( + state: CachedBeaconStateElectra, + consolidationBalance: Gwei +): number { const earliestConsolidationEpoch = computeActivationExitEpoch(state.epochCtx.epoch); const perEpochConsolidationChurn = getConsolidationChurnLimit(state); @@ -87,7 +90,7 @@ export function computeConsolidationEpochAndUpdateChurn(state: CachedBeaconState return state.earliestConsolidationEpoch; } - + /** * Return the current epoch of the given state. */ diff --git a/packages/state-transition/src/util/index.ts b/packages/state-transition/src/util/index.ts index 27c24590e408..6a839fbe103d 100644 --- a/packages/state-transition/src/util/index.ts +++ b/packages/state-transition/src/util/index.ts @@ -24,4 +24,4 @@ export * from "./syncCommittee.js"; export * from "./validator.js"; export * from "./weakSubjectivity.js"; export * from "./deposit.js"; -export * from "./electra.js"; \ No newline at end of file +export * from "./electra.js"; diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 205492d14dd7..3fbc57c43b89 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -1,9 +1,14 @@ import {Epoch, phase0, ValidatorIndex} from "@lodestar/types"; import {intDiv} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; -import {EFFECTIVE_BALANCE_INCREMENT, ForkSeq, MAX_EFFECTIVE_BALANCE_ELECTRA, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; +import { + EFFECTIVE_BALANCE_INCREMENT, + ForkSeq, + MAX_EFFECTIVE_BALANCE_ELECTRA, + MIN_ACTIVATION_BALANCE, +} from "@lodestar/params"; import {BeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; -import { hasCompoundingWithdrawalCredential} from "./electra.js"; +import {hasCompoundingWithdrawalCredential} from "./electra.js"; /** * Check if [[validator]] is active @@ -79,8 +84,10 @@ export function getValidatorMaxEffectiveBalance(withdrawalCredentials: Uint8Arra } } -export function getActiveBalance(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex) { - const validatorMaxEffectiveBalance = getValidatorMaxEffectiveBalance(state.validators.getReadonly(validatorIndex).withdrawalCredentials); +export function getActiveBalance(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex): number { + const validatorMaxEffectiveBalance = getValidatorMaxEffectiveBalance( + state.validators.getReadonly(validatorIndex).withdrawalCredentials + ); return Math.min(state.balances.get(validatorIndex), validatorMaxEffectiveBalance); -} \ No newline at end of file +} From ef15bc2f7b804ff784a201f7c3df3937f4a1749c Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 12 Apr 2024 18:10:14 +0800 Subject: [PATCH 26/41] Update maxEB --- .../test/spec/presets/operations.test.ts | 4 +- packages/params/src/index.ts | 1 + .../src/block/processConsolidation.ts | 7 ++- .../processExecutionLayerWithdrawRequest.ts | 45 +++++++++---------- .../src/block/processOperations.ts | 2 +- .../src/block/processVoluntaryExit.ts | 13 ++++-- .../src/block/processWithdrawals.ts | 2 +- .../state-transition/src/util/validator.ts | 7 +++ 8 files changed, 46 insertions(+), 35 deletions(-) diff --git a/packages/beacon-node/test/spec/presets/operations.test.ts b/packages/beacon-node/test/spec/presets/operations.test.ts index 2c4ae22475a5..a96d38298542 100644 --- a/packages/beacon-node/test/spec/presets/operations.test.ts +++ b/packages/beacon-node/test/spec/presets/operations.test.ts @@ -11,7 +11,7 @@ import { import * as blockFns from "@lodestar/state-transition/block"; import {ssz, phase0, altair, bellatrix, capella, electra} from "@lodestar/types"; import {InputType} from "@lodestar/spec-test-util"; -import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; +import {ACTIVE_PRESET, ForkName, ForkSeq} from "@lodestar/params"; import {createCachedBeaconStateTest} from "../../utils/cachedBeaconState.js"; import {expectEqualBeaconState, inputTypeSszTreeViewDU} from "../utils/expectEqualBeaconState.js"; @@ -88,7 +88,7 @@ const operationFns: Record> = }, withdrawals: (state, testCase: {execution_payload: capella.ExecutionPayload}) => { - blockFns.processWithdrawals(state as CachedBeaconStateCapella, testCase.execution_payload); + blockFns.processWithdrawals(ForkSeq.capella, state as CachedBeaconStateCapella, testCase.execution_payload); }, }; diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 49e7ff1e19f8..dd6d2f607b27 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -263,3 +263,4 @@ export const BLOBSIDECAR_FIXED_SIZE = ACTIVE_PRESET === PresetName.minimal ? 131 // Electra Misc export const UNSET_DEPOSIT_RECEIPTS_START_INDEX = 2n ** 64n - 1n; +export const FULL_EXIT_REQUEST_AMOUNT = 0; diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index 20d28cc2b18f..3231ee585df1 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -16,10 +16,9 @@ export function processConsolidation( // Initiate source validator exit and append pending consolidation const {sourceIndex, targetIndex} = signedConsolidation.message; - const activeBalance = getActiveBalance(state, sourceIndex); const sourceValidator = state.validators.get(sourceIndex); - const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(activeBalance)); + const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(sourceValidator.effectiveBalance)); sourceValidator.exitEpoch = exitEpoch; sourceValidator.withdrawableEpoch = exitEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; @@ -96,8 +95,8 @@ function assertValidConsolidation( } // Verify the same withdrawal address - const sourceWithdrawalAddress = toHexString(sourceValidator.withdrawalCredentials.slice(1)); - const targetWithdrawalAddress = toHexString(targetValidator.withdrawalCredentials.slice(1)); + const sourceWithdrawalAddress = toHexString(sourceValidator.withdrawalCredentials.slice(12)); + const targetWithdrawalAddress = toHexString(targetValidator.withdrawalCredentials.slice(12)); if (sourceWithdrawalAddress !== targetWithdrawalAddress) { throw new Error( diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index f2c584667671..75faceadac8e 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -1,10 +1,10 @@ import {toHexString} from "@chainsafe/ssz"; import {electra, phase0, ssz} from "@lodestar/types"; -import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT, FULL_EXIT_REQUEST_AMOUNT} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js"; -import {isActiveValidator} from "../util/validator.js"; +import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; import {initiateValidatorExit} from "./initiateValidatorExit.js"; @@ -15,7 +15,7 @@ export function processExecutionLayerWithdrawRequest( const amount = Number(executionLayerWithdrawRequest.amount); const {pendingPartialWithdrawals, validators, epochCtx} = state; const {pubkey2index, config} = epochCtx; // TODO Electra: Use finalized+unfinalized pubkey cache from 6110 - const isFullExitRequest = amount === 0; + const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT; // If partial withdrawal queue is full, only full exits are processed if (pendingPartialWithdrawals.length >= PENDING_PARTIAL_WITHDRAWALS_LIMIT && !isFullExitRequest) { @@ -38,38 +38,37 @@ export function processExecutionLayerWithdrawRequest( return; } - if (!isFullExitRequest && !hasCompoundingWithdrawalCredential(validator.withdrawalCredentials)) { - return; - } - // TODO Electra: Consider caching pendingPartialWithdrawals - const pendingBalanceToWithdraw = state.pendingPartialWithdrawals - .getAllReadonly() - .filter((item) => item.index === validatorIndex) - .reduce((total, item) => total + Number(item.amount), 0); + const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(state, validatorIndex); const validatorBalance = state.balances.get(validatorIndex); - // If pending balance is non zero, ignore - if (isFullExitRequest && pendingBalanceToWithdraw > 0) { + if (isFullExitRequest) { + // only exit validator if it has no pending withdrawals in the queue + if (pendingBalanceToWithdraw === 0) { + initiateValidatorExit(state, validator); + } else { + // Full request can't be fulfilled because still has pending withdrawals. + // TODO Electra: add log here + } return; - } + } - // only exit validator if it has no pending withdrawals in the queue - if (isFullExitRequest) { - initiateValidatorExit(state, validator); - } else if (validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw) { - const amountToWithdraw = Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount); - const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, BigInt(amountToWithdraw)); + const hasSufficientEffectiveBalance = validator.effectiveBalance >= MIN_ACTIVATION_BALANCE; + const hasExcessBalance = validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw; + + // Only allow partial withdrawals with compounding withdrawal credentials + if (hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) && hasSufficientEffectiveBalance && hasExcessBalance) { + const amountToWithdraw = BigInt(Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount)); + const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, amountToWithdraw); const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; const pendingPartialWithdrawal = ssz.electra.PartialWithdrawal.toViewDU({ index: validatorIndex, - amount: BigInt(amount), + amount: amountToWithdraw, withdrawableEpoch, }); - state.pendingPartialWithdrawals.push(pendingPartialWithdrawal); - } + } } function isValidValidator( diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 96c033fa81f3..db28156967e8 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -54,7 +54,7 @@ export function processOperations( } for (const voluntaryExit of body.voluntaryExits) { - processVoluntaryExit(state, voluntaryExit, opts.verifySignatures); + processVoluntaryExit(fork, state, voluntaryExit, opts.verifySignatures); } if (fork >= ForkSeq.electra) { for (const elWithdrawalRequest of (body as electra.BeaconBlockBody).executionPayload.withdrawalRequests) { diff --git a/packages/state-transition/src/block/processVoluntaryExit.ts b/packages/state-transition/src/block/processVoluntaryExit.ts index 80982623a447..ceea27cb33ef 100644 --- a/packages/state-transition/src/block/processVoluntaryExit.ts +++ b/packages/state-transition/src/block/processVoluntaryExit.ts @@ -1,7 +1,7 @@ -import {FAR_FUTURE_EPOCH} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params"; import {phase0} from "@lodestar/types"; -import {isActiveValidator} from "../util/index.js"; -import {CachedBeaconStateAllForks} from "../types.js"; +import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/index.js"; +import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; import {verifyVoluntaryExitSignature} from "../signatureSets/index.js"; import {initiateValidatorExit} from "./index.js"; @@ -11,11 +11,12 @@ import {initiateValidatorExit} from "./index.js"; * PERF: Work depends on number of VoluntaryExit per block. On regular networks the average is 0 / block. */ export function processVoluntaryExit( + fork: ForkSeq, state: CachedBeaconStateAllForks, signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true ): void { - if (!isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature)) { + if (!isValidVoluntaryExit(fork, state, signedVoluntaryExit, verifySignature)) { throw Error("Invalid voluntary exit"); } @@ -24,6 +25,7 @@ export function processVoluntaryExit( } export function isValidVoluntaryExit( + fork: ForkSeq, state: CachedBeaconStateAllForks, signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true @@ -32,6 +34,7 @@ export function isValidVoluntaryExit( const voluntaryExit = signedVoluntaryExit.message; const validator = state.validators.get(voluntaryExit.validatorIndex); const currentEpoch = epochCtx.epoch; + const isAfterElectra = fork >= ForkSeq.electra; return ( // verify the validator is active @@ -42,6 +45,8 @@ export function isValidVoluntaryExit( currentEpoch >= voluntaryExit.epoch && // verify the validator had been active long enough currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD && + // only exit validator if it has no pending withdrawals in the queue (post-Electra only) + isAfterElectra ? getPendingBalanceToWithdraw(state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) === 0 : true && // verify signature (!verifySignature || verifyVoluntaryExitSignature(state, signedVoluntaryExit)) ); diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 30fd92453334..77d968b7acbd 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -105,7 +105,7 @@ export function getExpectedWithdrawals( const validator = validators.getReadonly(withdrawal.index); - if (validator.exitEpoch === FAR_FUTURE_EPOCH && balances.get(withdrawalIndex) > MIN_ACTIVATION_BALANCE) { + if (validator.exitEpoch === FAR_FUTURE_EPOCH && validator.effectiveBalance >= MIN_ACTIVATION_BALANCE && balances.get(withdrawalIndex) > MIN_ACTIVATION_BALANCE) { const balanceOverMinActivationBalance = BigInt(balances.get(withdrawalIndex) - MIN_ACTIVATION_BALANCE); const withdrawableBalance = balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount; diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 3fbc57c43b89..ba87f0d04dd4 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -91,3 +91,10 @@ export function getActiveBalance(state: CachedBeaconStateElectra, validatorIndex return Math.min(state.balances.get(validatorIndex), validatorMaxEffectiveBalance); } + +export function getPendingBalanceToWithdraw(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex): number { + return state.pendingPartialWithdrawals + .getAllReadonly() + .filter((item) => item.index === validatorIndex) + .reduce((total, item) => total + Number(item.amount), 0); +} \ No newline at end of file From 5c16ff3f2e9b0962a35585591550a3a172e4629d Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 12 Apr 2024 21:37:08 +0800 Subject: [PATCH 27/41] update voluntary exit --- .../src/block/processConsolidation.ts | 2 +- .../processExecutionLayerWithdrawRequest.ts | 21 +++++++++++++----- .../src/block/processVoluntaryExit.ts | 22 ++++++++++++++----- .../src/block/processWithdrawals.ts | 6 ++++- .../state-transition/src/util/validator.ts | 2 +- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index 3231ee585df1..cacc6d0e08d6 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -4,7 +4,7 @@ import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} import {verifyConsolidationSignature} from "../signatureSets/index.js"; import {CachedBeaconStateElectra} from "../types.js"; -import {getActiveBalance, getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; +import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; import {hasExecutionWithdrawalCredential} from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index 75faceadac8e..5e466a35a974 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -1,6 +1,11 @@ import {toHexString} from "@chainsafe/ssz"; import {electra, phase0, ssz} from "@lodestar/types"; -import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT, FULL_EXIT_REQUEST_AMOUNT} from "@lodestar/params"; +import { + FAR_FUTURE_EPOCH, + MIN_ACTIVATION_BALANCE, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + FULL_EXIT_REQUEST_AMOUNT, +} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js"; @@ -51,14 +56,20 @@ export function processExecutionLayerWithdrawRequest( // TODO Electra: add log here } return; - } + } const hasSufficientEffectiveBalance = validator.effectiveBalance >= MIN_ACTIVATION_BALANCE; const hasExcessBalance = validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw; // Only allow partial withdrawals with compounding withdrawal credentials - if (hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) && hasSufficientEffectiveBalance && hasExcessBalance) { - const amountToWithdraw = BigInt(Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount)); + if ( + hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) && + hasSufficientEffectiveBalance && + hasExcessBalance + ) { + const amountToWithdraw = BigInt( + Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount) + ); const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, amountToWithdraw); const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; @@ -68,7 +79,7 @@ export function processExecutionLayerWithdrawRequest( withdrawableEpoch, }); state.pendingPartialWithdrawals.push(pendingPartialWithdrawal); - } + } } function isValidValidator( diff --git a/packages/state-transition/src/block/processVoluntaryExit.ts b/packages/state-transition/src/block/processVoluntaryExit.ts index ceea27cb33ef..d1e5947ae409 100644 --- a/packages/state-transition/src/block/processVoluntaryExit.ts +++ b/packages/state-transition/src/block/processVoluntaryExit.ts @@ -16,7 +16,12 @@ export function processVoluntaryExit( signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true ): void { - if (!isValidVoluntaryExit(fork, state, signedVoluntaryExit, verifySignature)) { + const isAfterElectra = fork >= ForkSeq.electra; + if ( + (!isAfterElectra && !isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature)) || + (isAfterElectra && + !isValidVoluntaryExitElectra(state as CachedBeaconStateElectra, signedVoluntaryExit, verifySignature)) + ) { throw Error("Invalid voluntary exit"); } @@ -25,7 +30,6 @@ export function processVoluntaryExit( } export function isValidVoluntaryExit( - fork: ForkSeq, state: CachedBeaconStateAllForks, signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true @@ -34,7 +38,6 @@ export function isValidVoluntaryExit( const voluntaryExit = signedVoluntaryExit.message; const validator = state.validators.get(voluntaryExit.validatorIndex); const currentEpoch = epochCtx.epoch; - const isAfterElectra = fork >= ForkSeq.electra; return ( // verify the validator is active @@ -45,9 +48,18 @@ export function isValidVoluntaryExit( currentEpoch >= voluntaryExit.epoch && // verify the validator had been active long enough currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD && - // only exit validator if it has no pending withdrawals in the queue (post-Electra only) - isAfterElectra ? getPendingBalanceToWithdraw(state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) === 0 : true && // verify signature (!verifySignature || verifyVoluntaryExitSignature(state, signedVoluntaryExit)) ); } + +function isValidVoluntaryExitElectra( + state: CachedBeaconStateElectra, + signedVoluntaryExit: phase0.SignedVoluntaryExit, + verifySignature = true +): boolean { + const isValidPreElectra = isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature); + + // only exit validator if it has no pending withdrawals in the queue (post-Electra only) + return isValidPreElectra && getPendingBalanceToWithdraw(state, signedVoluntaryExit.message.validatorIndex) === 0; +} diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 77d968b7acbd..634b3fc4571e 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -105,7 +105,11 @@ export function getExpectedWithdrawals( const validator = validators.getReadonly(withdrawal.index); - if (validator.exitEpoch === FAR_FUTURE_EPOCH && validator.effectiveBalance >= MIN_ACTIVATION_BALANCE && balances.get(withdrawalIndex) > MIN_ACTIVATION_BALANCE) { + if ( + validator.exitEpoch === FAR_FUTURE_EPOCH && + validator.effectiveBalance >= MIN_ACTIVATION_BALANCE && + balances.get(withdrawalIndex) > MIN_ACTIVATION_BALANCE + ) { const balanceOverMinActivationBalance = BigInt(balances.get(withdrawalIndex) - MIN_ACTIVATION_BALANCE); const withdrawableBalance = balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount; diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index ba87f0d04dd4..e95a13bf5d44 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -97,4 +97,4 @@ export function getPendingBalanceToWithdraw(state: CachedBeaconStateElectra, val .getAllReadonly() .filter((item) => item.index === validatorIndex) .reduce((total, item) => total + Number(item.amount), 0); -} \ No newline at end of file +} From 2a967fe984910f0793219372683ae0e01d54ee19 Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 12 Apr 2024 21:48:31 +0800 Subject: [PATCH 28/41] Fix config --- packages/config/src/chainConfig/configs/mainnet.ts | 4 ++-- packages/config/src/chainConfig/configs/minimal.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 7d0488b31969..741ddc99f8cd 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -76,8 +76,8 @@ export const chainConfig: ChainConfig = { EJECTION_BALANCE: 16000000000, // 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4, - // 2**8 * 10**9 (= 256,000,000,000) - MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 256000000000, + // 2**3 (= 8) + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8, // 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536, diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index c699026f4a32..26f49cc3e47d 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -72,8 +72,8 @@ export const chainConfig: ChainConfig = { EJECTION_BALANCE: 16000000000, // 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 2, - // 2**7 * 10**9 (= 128,000,000,000) - MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 128000000000, + // [customized] + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 4, // [customized] scale queue churn at much lower validator counts for testing CHURN_LIMIT_QUOTIENT: 32, From 2349afaa836400630d5c87102ac7523a7aecd462 Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 17 Apr 2024 17:29:55 +0800 Subject: [PATCH 29/41] Update initiateValidatorExit --- .../src/block/initiateValidatorExit.ts | 36 +++++++++++-------- .../processExecutionLayerWithdrawRequest.ts | 3 +- .../src/block/processVoluntaryExit.ts | 2 +- .../src/block/slashValidator.ts | 2 +- .../state-transition/src/cache/epochCache.ts | 2 ++ .../src/epoch/processRegistryUpdates.ts | 2 +- 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/state-transition/src/block/initiateValidatorExit.ts b/packages/state-transition/src/block/initiateValidatorExit.ts index ae91ab0f2589..a64323745fd7 100644 --- a/packages/state-transition/src/block/initiateValidatorExit.ts +++ b/packages/state-transition/src/block/initiateValidatorExit.ts @@ -1,7 +1,8 @@ import {CompositeViewDU} from "@chainsafe/ssz"; -import {FAR_FUTURE_EPOCH} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params"; import {ssz} from "@lodestar/types"; -import {CachedBeaconStateAllForks} from "../types.js"; +import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; +import { computeExitEpochAndUpdateChurn } from "../util/epoch.js"; /** * Initiate the exit of the validator with index ``index``. @@ -24,6 +25,7 @@ import {CachedBeaconStateAllForks} from "../types.js"; * Forcing consumers to pass the SubTree of `validator` directly mitigates this issue. */ export function initiateValidatorExit( + fork: ForkSeq, state: CachedBeaconStateAllForks, validator: CompositeViewDU ): void { @@ -34,18 +36,24 @@ export function initiateValidatorExit( return; } - // Limits the number of validators that can exit on each epoch. - // Expects all state.validators to follow this rule, i.e. no validator.exitEpoch is greater than exitQueueEpoch. - // If there the churnLimit is reached at this current exitQueueEpoch, advance epoch and reset churn. - if (epochCtx.exitQueueChurn >= epochCtx.churnLimit) { - epochCtx.exitQueueEpoch += 1; - epochCtx.exitQueueChurn = 1; // = 1 to account for this validator with exitQueueEpoch + if (fork < ForkSeq.electra) { + // Limits the number of validators that can exit on each epoch. + // Expects all state.validators to follow this rule, i.e. no validator.exitEpoch is greater than exitQueueEpoch. + // If there the churnLimit is reached at this current exitQueueEpoch, advance epoch and reset churn. + if (epochCtx.exitQueueChurn >= epochCtx.churnLimit) { + epochCtx.exitQueueEpoch += 1; + epochCtx.exitQueueChurn = 1; // = 1 to account for this validator with exitQueueEpoch + } else { + // Add this validator to the current exitQueueEpoch churn + epochCtx.exitQueueChurn += 1; + } + + // set validator exit epoch + validator.exitEpoch = epochCtx.exitQueueEpoch; } else { - // Add this validator to the current exitQueueEpoch churn - epochCtx.exitQueueChurn += 1; + // set validator exit epoch + // Note we don't use epochCtx.exitQueueChurn and exitQueueEpoch anymore + validator.exitEpoch = computeExitEpochAndUpdateChurn(state as CachedBeaconStateElectra, BigInt(validator.effectiveBalance)); } - - // set validator exit epoch and withdrawable epoch - validator.exitEpoch = epochCtx.exitQueueEpoch; // TODO Electra: calculate exit epoch - validator.withdrawableEpoch = epochCtx.exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; + validator.withdrawableEpoch = validator.exitEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; } diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index 5e466a35a974..c902f558272d 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -5,6 +5,7 @@ import { MIN_ACTIVATION_BALANCE, PENDING_PARTIAL_WITHDRAWALS_LIMIT, FULL_EXIT_REQUEST_AMOUNT, + ForkSeq, } from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; @@ -50,7 +51,7 @@ export function processExecutionLayerWithdrawRequest( if (isFullExitRequest) { // only exit validator if it has no pending withdrawals in the queue if (pendingBalanceToWithdraw === 0) { - initiateValidatorExit(state, validator); + initiateValidatorExit(ForkSeq.electra, state, validator); } else { // Full request can't be fulfilled because still has pending withdrawals. // TODO Electra: add log here diff --git a/packages/state-transition/src/block/processVoluntaryExit.ts b/packages/state-transition/src/block/processVoluntaryExit.ts index d1e5947ae409..810da2f021a6 100644 --- a/packages/state-transition/src/block/processVoluntaryExit.ts +++ b/packages/state-transition/src/block/processVoluntaryExit.ts @@ -26,7 +26,7 @@ export function processVoluntaryExit( } const validator = state.validators.get(signedVoluntaryExit.message.validatorIndex); - initiateValidatorExit(state, validator); + initiateValidatorExit(fork, state, validator); } export function isValidVoluntaryExit( diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index e2210f7a4d48..cf68920b0977 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -33,7 +33,7 @@ export function slashValidator( const validator = state.validators.get(slashedIndex); // TODO: Bellatrix initiateValidatorExit validators.update() with the one below - initiateValidatorExit(state, validator); + initiateValidatorExit(fork, state, validator); validator.slashed = true; validator.withdrawableEpoch = Math.max(validator.withdrawableEpoch, epoch + EPOCHS_PER_SLASHINGS_VECTOR); diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index f34638c75c53..e61fe751f3dd 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -181,6 +181,7 @@ export class EpochCache { * initiateValidatorExit(). This value may vary on each fork of the state. * * NOTE: Changes block to block + * NOTE: No longer used by initiateValidatorExit post-electra */ exitQueueEpoch: Epoch; /** @@ -188,6 +189,7 @@ export class EpochCache { * initiateValidatorExit(). This value may vary on each fork of the state. * * NOTE: Changes block to block + * NOTE: No longer used by initiateValidatorExit post-electra */ exitQueueChurn: number; diff --git a/packages/state-transition/src/epoch/processRegistryUpdates.ts b/packages/state-transition/src/epoch/processRegistryUpdates.ts index cf9ca8d29527..14ae1cfaa531 100644 --- a/packages/state-transition/src/epoch/processRegistryUpdates.ts +++ b/packages/state-transition/src/epoch/processRegistryUpdates.ts @@ -33,7 +33,7 @@ export function processRegistryUpdates( for (const index of cache.indicesToEject) { // set validator exit epoch and withdrawable epoch // TODO: Figure out a way to quickly set properties on the validators tree - initiateValidatorExit(state, validators.get(index)); + initiateValidatorExit(fork, state, validators.get(index)); } // TODO Electra: New logic to set validator.activation_epoch From 5646fd514bd2a1db016da55aca96701e5405408e Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 17 Apr 2024 17:59:26 +0800 Subject: [PATCH 30/41] Remove churn limit in processRegistryUpdates --- packages/state-transition/src/cache/epochCache.ts | 4 ++-- .../src/cache/epochTransitionCache.ts | 4 ++-- .../src/epoch/processRegistryUpdates.ts | 15 +++++++++------ packages/state-transition/src/util/genesis.ts | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index e61fe751f3dd..804f606ab76a 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -311,7 +311,7 @@ export class EpochCache { const nextEpoch = currentEpoch + 1; let totalActiveBalanceIncrements = 0; - let exitQueueEpoch = computeActivationExitEpoch(currentEpoch); // TODO Electra: New exit epoch calculation + let exitQueueEpoch = computeActivationExitEpoch(currentEpoch); let exitQueueChurn = 0; const validators = state.validators.getAllReadonlyValues(); @@ -590,7 +590,7 @@ export class EpochCache { ); // Maybe advance exitQueueEpoch at the end of the epoch if there haven't been any exists for a while - const exitQueueEpoch = computeActivationExitEpoch(currEpoch); // TODO Electra: New exit epoch calculation + const exitQueueEpoch = computeActivationExitEpoch(currEpoch); if (exitQueueEpoch > this.exitQueueEpoch) { this.exitQueueEpoch = exitQueueEpoch; this.exitQueueChurn = 0; diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index 4a8fa7286d6c..537d84f3cdbb 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -1,6 +1,6 @@ import {Epoch, ValidatorIndex} from "@lodestar/types"; import {intDiv} from "@lodestar/utils"; -import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MAX_EFFECTIVE_BALANCE} from "@lodestar/params"; +import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MAX_EFFECTIVE_BALANCE, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; import { AttesterStatus, @@ -248,7 +248,7 @@ export function beforeProcessEpoch( // ``` if ( validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH && - validator.effectiveBalance >= MAX_EFFECTIVE_BALANCE + validator.effectiveBalance >= MIN_ACTIVATION_BALANCE ) { indicesEligibleForActivationQueue.push(i); } diff --git a/packages/state-transition/src/epoch/processRegistryUpdates.ts b/packages/state-transition/src/epoch/processRegistryUpdates.ts index 14ae1cfaa531..d9ff7bd63d0b 100644 --- a/packages/state-transition/src/epoch/processRegistryUpdates.ts +++ b/packages/state-transition/src/epoch/processRegistryUpdates.ts @@ -36,18 +36,21 @@ export function processRegistryUpdates( initiateValidatorExit(fork, state, validators.get(index)); } - // TODO Electra: New logic to set validator.activation_epoch - if (fork >= ForkSeq.electra) { - } - // set new activation eligibilities for (const index of cache.indicesEligibleForActivationQueue) { validators.get(index).activationEligibilityEpoch = epochCtx.epoch + 1; } const finalityEpoch = state.finalizedCheckpoint.epoch; - // dequeue validators for activation up to churn limit - for (const index of cache.indicesEligibleForActivation.slice(0, epochCtx.activationChurnLimit)) { + let indicesEligibleForActivation; + if (fork < ForkSeq.electra) { + // dequeue validators for activation up to churn limit + indicesEligibleForActivation = cache.indicesEligibleForActivation.slice(0, epochCtx.activationChurnLimit) + } else { + // no churn limit post-electra + indicesEligibleForActivation = cache.indicesEligibleForActivation; + } + for (const index of indicesEligibleForActivation) { const validator = validators.get(index); // placement in queue is finalized if (validator.activationEligibilityEpoch > finalityEpoch) { diff --git a/packages/state-transition/src/util/genesis.ts b/packages/state-transition/src/util/genesis.ts index 797ca7cc1aee..537edc7f976f 100644 --- a/packages/state-transition/src/util/genesis.ts +++ b/packages/state-transition/src/util/genesis.ts @@ -180,7 +180,7 @@ export function applyDeposits( } const balance = balancesArr[i]; - const effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE); // TODO Electra: Double check if we use MIN_ACTIVATION_BALANCE + const effectiveBalance = Math.min(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE); validator.effectiveBalance = effectiveBalance; epochCtx.effectiveBalanceIncrementsSet(i, effectiveBalance); From ffb5ad19886cb3bb9273e7b95790e7bd1472935c Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Wed, 1 May 2024 16:33:48 +0800 Subject: [PATCH 31/41] Fix conflict --- .../src/block/processExecutionLayerWithdrawalRequest.ts | 5 +++-- packages/state-transition/src/block/processOperations.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts index cfe719bf57f3..165da38492cb 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts @@ -1,6 +1,6 @@ import {CompositeViewDU} from "@chainsafe/ssz"; import {electra, ssz} from "@lodestar/types"; -import {ETH1_ADDRESS_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH} from "@lodestar/params"; +import {ETH1_ADDRESS_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params"; import {isActiveValidator} from "../util/index.js"; import {CachedBeaconStateElectra} from "../types.js"; @@ -12,6 +12,7 @@ const FULL_EXIT_REQUEST_AMOUNT = 0; * otherwise silent ignore. */ export function processExecutionLayerWithdrawalRequest( + fork: ForkSeq, state: CachedBeaconStateElectra, withdrawalRequest: electra.ExecutionLayerWithdrawalRequest ): void { @@ -23,7 +24,7 @@ export function processExecutionLayerWithdrawalRequest( return; } - initiateValidatorExit(state, validator); + initiateValidatorExit(fork, state, validator); } else { // partial withdral request add codeblock } diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index db28156967e8..187aaaad3c3a 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -58,7 +58,7 @@ export function processOperations( } if (fork >= ForkSeq.electra) { for (const elWithdrawalRequest of (body as electra.BeaconBlockBody).executionPayload.withdrawalRequests) { - processExecutionLayerWithdrawalRequest(state as CachedBeaconStateElectra, elWithdrawalRequest); + processExecutionLayerWithdrawalRequest(fork ,state as CachedBeaconStateElectra, elWithdrawalRequest); } } @@ -75,7 +75,7 @@ export function processOperations( processDepositReceipt(fork, stateElectra, depositReceipt); } - for (const withdrawRequest of bodyElectra.executionPayload.withdrawaRequests) { + for (const withdrawRequest of bodyElectra.executionPayload.withdrawalRequests) { processExecutionLayerWithdrawRequest(stateElectra, withdrawRequest); } From bb29c548bdb6e2672a9526979580c9a9bcc0f358 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Wed, 1 May 2024 16:45:36 +0800 Subject: [PATCH 32/41] Add MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD --- packages/params/src/presets/minimal.ts | 2 +- packages/params/src/types.ts | 5 ----- .../block/processExecutionLayerWithdrawRequest.ts | 14 +++++++------- .../src/block/processOperations.ts | 6 +++--- packages/types/src/electra/sszTypes.ts | 5 +++-- packages/types/src/electra/types.ts | 2 +- 6 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 3b7f7184eabe..5984a5bf3089 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -122,7 +122,7 @@ export const minimalPreset: BeaconPreset = { // ELECTRA MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4, - MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16, + MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, // 2**11 * 10**9 (= 2,048,000,000,000) Gwei diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index 3cdc33d9c38e..e83679133b26 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -85,17 +85,12 @@ export type BeaconPreset = { // ELECTRA MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: number; -<<<<<<< HEAD MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: number; MAX_ATTESTER_SLASHINGS_ELECTRA: number; MAX_ATTESTATIONS_ELECTRA: number; - MAX_EFFECTIVE_BALANCE: number; -======= - MAX_EXECUTION_LAYER_EXITS: number; MAX_EFFECTIVE_BALANCE_ELECTRA: number; MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: number; MIN_ACTIVATION_BALANCE: number; ->>>>>>> 90ac864d5a4 (Update presets) PENDING_BALANCE_DEPOSITS_LIMIT: number; PENDING_PARTIAL_WITHDRAWALS_LIMIT: number; PENDING_CONSOLIDATIONS_LIMIT: number; diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts index c902f558272d..d23234dab98a 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts @@ -14,11 +14,11 @@ import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator. import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; import {initiateValidatorExit} from "./initiateValidatorExit.js"; -export function processExecutionLayerWithdrawRequest( +export function processExecutionLayerWithdrawalRequest( state: CachedBeaconStateElectra, - executionLayerWithdrawRequest: electra.ExecutionLayerWithdrawRequest + executionLayerWithdrawalRequest: electra.ExecutionLayerWithdrawalRequest ): void { - const amount = Number(executionLayerWithdrawRequest.amount); + const amount = Number(executionLayerWithdrawalRequest.amount); const {pendingPartialWithdrawals, validators, epochCtx} = state; const {pubkey2index, config} = epochCtx; // TODO Electra: Use finalized+unfinalized pubkey cache from 6110 const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT; @@ -28,19 +28,19 @@ export function processExecutionLayerWithdrawRequest( return; } - const validatorIndex = pubkey2index.get(executionLayerWithdrawRequest.validatorPubkey); + const validatorIndex = pubkey2index.get(executionLayerWithdrawalRequest.validatorPubkey); if (validatorIndex === undefined) { throw new Error( - `Can't find validator index from ExecutionLayerWithdrawRequest : pubkey=${toHexString( - executionLayerWithdrawRequest.validatorPubkey + `Can't find validator index from ExecutionLayerWithdrawalRequest : pubkey=${toHexString( + executionLayerWithdrawalRequest.validatorPubkey )}` ); } const validator = validators.getReadonly(validatorIndex); - if (!isValidValidator(validator, executionLayerWithdrawRequest.sourceAddress, state)) { + if (!isValidValidator(validator, executionLayerWithdrawalRequest.sourceAddress, state)) { return; } diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 187aaaad3c3a..54b697441ca9 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -12,7 +12,7 @@ import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWit import {processBlsToExecutionChange} from "./processBlsToExecutionChange.js"; import {processDepositReceipt} from "./processDepositReceipt.js"; import {ProcessBlockOpts} from "./types.js"; -import {processExecutionLayerWithdrawRequest} from "./processExecutionLayerWithdrawRequest.js"; +import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWithdrawalRequest.js"; import {processConsolidation} from "./processConsolidation.js"; export { @@ -75,8 +75,8 @@ export function processOperations( processDepositReceipt(fork, stateElectra, depositReceipt); } - for (const withdrawRequest of bodyElectra.executionPayload.withdrawalRequests) { - processExecutionLayerWithdrawRequest(stateElectra, withdrawRequest); + for (const withdrawalRequest of bodyElectra.executionPayload.withdrawalRequests) { + processExecutionLayerWithdrawalRequest(stateElectra, withdrawalRequest); } for (const consolidation of bodyElectra.consolidations) { diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index bfb0e54db919..407390c44716 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -21,6 +21,7 @@ import { PENDING_BALANCE_DEPOSITS_LIMIT, PENDING_CONSOLIDATIONS_LIMIT, PENDING_PARTIAL_WITHDRAWALS_LIMIT, + MAX_WITHDRAWALS_PER_PAYLOAD, } from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; @@ -136,13 +137,13 @@ export const ExecutionLayerWithdrawalRequests = new ListCompositeType( MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD ); -export const ExecutionLayerWithdrawRequest = new ContainerType( +export const ExecutionLayerWithdrawalRequest = new ContainerType( { sourceAddress: ExecutionAddress, validatorPubkey: BLSPubkey, amount: Gwei, }, - {typeName: "ExecutionLayerWithdrawRequest", jsonCase: "eth2"} + {typeName: "ExecutionLayerWithdrawalRequest", jsonCase: "eth2"} ); export const ExecutionPayload = new ContainerType( diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 7c1d06fa168b..266555f78762 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -50,4 +50,4 @@ export type PendingBalanceDeposit = ValueOf; export type PartialWithdrawal = ValueOf; export type PendingConsolidation = ValueOf; -export type ExecutionLayerWithdrawRequest = ValueOf; +export type ExecutionLayerWithdrawalRequest = ValueOf; From 5aba7f43576e9372f7a1adcd4fa0dfad0badcc3a Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Wed, 1 May 2024 18:04:56 +0800 Subject: [PATCH 33/41] Reflect latest spec changes --- packages/params/src/index.ts | 2 +- packages/params/src/presets/mainnet.ts | 2 +- packages/params/src/presets/minimal.ts | 2 +- packages/params/src/types.ts | 4 +- .../src/block/initiateValidatorExit.ts | 7 +- .../processExecutionLayerWithdrawRequest.ts | 103 -------------- .../processExecutionLayerWithdrawalRequest.ts | 130 +++++++++++------- .../src/block/processOperations.ts | 9 +- .../src/block/processVoluntaryExit.ts | 8 +- .../src/block/processWithdrawals.ts | 4 +- .../src/cache/epochTransitionCache.ts | 7 +- .../src/epoch/processRegistryUpdates.ts | 2 +- .../src/slot/upgradeStateToElectra.ts | 19 ++- packages/state-transition/src/util/electra.ts | 23 +++- packages/state-transition/src/util/epoch.ts | 60 ++++---- .../state-transition/src/util/validator.ts | 3 + packages/validator/src/util/params.ts | 2 +- 17 files changed, 182 insertions(+), 205 deletions(-) delete mode 100644 packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index dd6d2f607b27..c1d3f1bc1981 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -106,8 +106,8 @@ export const { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD, MAX_ATTESTER_SLASHINGS_ELECTRA, MAX_ATTESTATIONS_ELECTRA, + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA, - MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD, } = activePreset; //////////// diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index 3c356bb12127..2495f7ef97a1 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -124,6 +124,7 @@ export const mainnetPreset: BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8, // 2**11 * 10**9 (= 2,048,000,000,000) Gwei MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, // 2**16 (= 65536) @@ -134,5 +135,4 @@ export const mainnetPreset: BeaconPreset = { PENDING_CONSOLIDATIONS_LIMIT: 262144, MAX_CONSOLIDATIONS: 1, WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096, - MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 5984a5bf3089..8e71407965d7 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -125,6 +125,7 @@ export const minimalPreset: BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 1, // 2**11 * 10**9 (= 2,048,000,000,000) Gwei MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, // 2**16 (= 65536) @@ -135,5 +136,4 @@ export const minimalPreset: BeaconPreset = { PENDING_CONSOLIDATIONS_LIMIT: 64, MAX_CONSOLIDATIONS: 1, WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096, - MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8, }; diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index e83679133b26..dffd98518006 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -88,6 +88,7 @@ export type BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: number; MAX_ATTESTER_SLASHINGS_ELECTRA: number; MAX_ATTESTATIONS_ELECTRA: number; + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: number; MAX_EFFECTIVE_BALANCE_ELECTRA: number; MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: number; MIN_ACTIVATION_BALANCE: number; @@ -96,7 +97,6 @@ export type BeaconPreset = { PENDING_CONSOLIDATIONS_LIMIT: number; MAX_CONSOLIDATIONS: number; WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: number; - MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: number; }; /** @@ -188,6 +188,7 @@ export const beaconPresetTypes: BeaconPresetTypes = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: "number", MAX_ATTESTER_SLASHINGS_ELECTRA: "number", MAX_ATTESTATIONS_ELECTRA: "number", + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: "number", MAX_EFFECTIVE_BALANCE_ELECTRA: "number", MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: "number", MIN_ACTIVATION_BALANCE: "number", @@ -196,7 +197,6 @@ export const beaconPresetTypes: BeaconPresetTypes = { PENDING_CONSOLIDATIONS_LIMIT: "number", MAX_CONSOLIDATIONS: "number", WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: "number", - MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: "number", }; type BeaconPresetTypes = { diff --git a/packages/state-transition/src/block/initiateValidatorExit.ts b/packages/state-transition/src/block/initiateValidatorExit.ts index a64323745fd7..d1420daef84c 100644 --- a/packages/state-transition/src/block/initiateValidatorExit.ts +++ b/packages/state-transition/src/block/initiateValidatorExit.ts @@ -2,7 +2,7 @@ import {CompositeViewDU} from "@chainsafe/ssz"; import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params"; import {ssz} from "@lodestar/types"; import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; -import { computeExitEpochAndUpdateChurn } from "../util/epoch.js"; +import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; /** * Initiate the exit of the validator with index ``index``. @@ -53,7 +53,10 @@ export function initiateValidatorExit( } else { // set validator exit epoch // Note we don't use epochCtx.exitQueueChurn and exitQueueEpoch anymore - validator.exitEpoch = computeExitEpochAndUpdateChurn(state as CachedBeaconStateElectra, BigInt(validator.effectiveBalance)); + validator.exitEpoch = computeExitEpochAndUpdateChurn( + state as CachedBeaconStateElectra, + BigInt(validator.effectiveBalance) + ); } validator.withdrawableEpoch = validator.exitEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; } diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts deleted file mode 100644 index d23234dab98a..000000000000 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawRequest.ts +++ /dev/null @@ -1,103 +0,0 @@ -import {toHexString} from "@chainsafe/ssz"; -import {electra, phase0, ssz} from "@lodestar/types"; -import { - FAR_FUTURE_EPOCH, - MIN_ACTIVATION_BALANCE, - PENDING_PARTIAL_WITHDRAWALS_LIMIT, - FULL_EXIT_REQUEST_AMOUNT, - ForkSeq, -} from "@lodestar/params"; - -import {CachedBeaconStateElectra} from "../types.js"; -import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js"; -import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; -import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; -import {initiateValidatorExit} from "./initiateValidatorExit.js"; - -export function processExecutionLayerWithdrawalRequest( - state: CachedBeaconStateElectra, - executionLayerWithdrawalRequest: electra.ExecutionLayerWithdrawalRequest -): void { - const amount = Number(executionLayerWithdrawalRequest.amount); - const {pendingPartialWithdrawals, validators, epochCtx} = state; - const {pubkey2index, config} = epochCtx; // TODO Electra: Use finalized+unfinalized pubkey cache from 6110 - const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT; - - // If partial withdrawal queue is full, only full exits are processed - if (pendingPartialWithdrawals.length >= PENDING_PARTIAL_WITHDRAWALS_LIMIT && !isFullExitRequest) { - return; - } - - const validatorIndex = pubkey2index.get(executionLayerWithdrawalRequest.validatorPubkey); - - if (validatorIndex === undefined) { - throw new Error( - `Can't find validator index from ExecutionLayerWithdrawalRequest : pubkey=${toHexString( - executionLayerWithdrawalRequest.validatorPubkey - )}` - ); - } - - const validator = validators.getReadonly(validatorIndex); - - if (!isValidValidator(validator, executionLayerWithdrawalRequest.sourceAddress, state)) { - return; - } - - // TODO Electra: Consider caching pendingPartialWithdrawals - const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(state, validatorIndex); - const validatorBalance = state.balances.get(validatorIndex); - - if (isFullExitRequest) { - // only exit validator if it has no pending withdrawals in the queue - if (pendingBalanceToWithdraw === 0) { - initiateValidatorExit(ForkSeq.electra, state, validator); - } else { - // Full request can't be fulfilled because still has pending withdrawals. - // TODO Electra: add log here - } - return; - } - - const hasSufficientEffectiveBalance = validator.effectiveBalance >= MIN_ACTIVATION_BALANCE; - const hasExcessBalance = validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw; - - // Only allow partial withdrawals with compounding withdrawal credentials - if ( - hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) && - hasSufficientEffectiveBalance && - hasExcessBalance - ) { - const amountToWithdraw = BigInt( - Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount) - ); - const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, amountToWithdraw); - const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; - - const pendingPartialWithdrawal = ssz.electra.PartialWithdrawal.toViewDU({ - index: validatorIndex, - amount: amountToWithdraw, - withdrawableEpoch, - }); - state.pendingPartialWithdrawals.push(pendingPartialWithdrawal); - } -} - -function isValidValidator( - validator: phase0.Validator, - sourceAddress: Uint8Array, - state: CachedBeaconStateElectra -): boolean { - const {withdrawalCredentials} = validator; - const addressStr = toHexString(withdrawalCredentials.slice(12)); - const sourceAddressStr = toHexString(sourceAddress); - const {epoch: currentEpoch, config} = state.epochCtx; - - return ( - hasExecutionWithdrawalCredential(withdrawalCredentials) && - addressStr === sourceAddressStr && - isActiveValidator(validator, currentEpoch) && - validator.exitEpoch === FAR_FUTURE_EPOCH && - currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD - ); -} diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts index 165da38492cb..dc6975fda827 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts @@ -1,68 +1,104 @@ -import {CompositeViewDU} from "@chainsafe/ssz"; -import {electra, ssz} from "@lodestar/types"; -import {ETH1_ADDRESS_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params"; +import {toHexString} from "@chainsafe/ssz"; +import {electra, phase0, ssz} from "@lodestar/types"; +import { + FAR_FUTURE_EPOCH, + MIN_ACTIVATION_BALANCE, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + FULL_EXIT_REQUEST_AMOUNT, + ForkSeq, +} from "@lodestar/params"; -import {isActiveValidator} from "../util/index.js"; import {CachedBeaconStateElectra} from "../types.js"; -import {initiateValidatorExit} from "./index.js"; +import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js"; +import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; +import {computeExitEpochAndUpdateChurn} from "../util/epoch.js"; +import {initiateValidatorExit} from "./initiateValidatorExit.js"; -const FULL_EXIT_REQUEST_AMOUNT = 0; -/** - * Process execution layer exit messages and initiate exit incase they belong to a valid active validator - * otherwise silent ignore. - */ export function processExecutionLayerWithdrawalRequest( fork: ForkSeq, state: CachedBeaconStateElectra, - withdrawalRequest: electra.ExecutionLayerWithdrawalRequest + executionLayerWithdrawalRequest: electra.ExecutionLayerWithdrawalRequest ): void { - const isFullExitRequest = withdrawalRequest.amount === FULL_EXIT_REQUEST_AMOUNT; + const amount = Number(executionLayerWithdrawalRequest.amount); + const {pendingPartialWithdrawals, validators, epochCtx} = state; + const {pubkey2index, config} = epochCtx; // TODO Electra: Use finalized+unfinalized pubkey cache from 6110 + const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT; - if (isFullExitRequest) { - const validator = isValidExecutionLayerExit(state, withdrawalRequest); - if (validator === null) { - return; - } - - initiateValidatorExit(fork, state, validator); - } else { - // partial withdral request add codeblock + // If partial withdrawal queue is full, only full exits are processed + if (pendingPartialWithdrawals.length >= PENDING_PARTIAL_WITHDRAWALS_LIMIT && !isFullExitRequest) { + return; } -} -// TODO electra : add pending withdrawal check before exit -export function isValidExecutionLayerExit( - state: CachedBeaconStateElectra, - exit: electra.ExecutionLayerWithdrawalRequest -): CompositeViewDU | null { - const {config, epochCtx} = state; - const validatorIndex = epochCtx.getValidatorIndex(exit.validatorPubkey); - const validator = validatorIndex !== undefined ? state.validators.getReadonly(validatorIndex) : undefined; - if (validator === undefined) { - return null; + const validatorIndex = pubkey2index.get(executionLayerWithdrawalRequest.validatorPubkey); + + if (validatorIndex === undefined) { + throw new Error( + `Can't find validator index from ExecutionLayerWithdrawalRequest : pubkey=${toHexString( + executionLayerWithdrawalRequest.validatorPubkey + )}` + ); } - const {withdrawalCredentials} = validator; - if (withdrawalCredentials[0] !== ETH1_ADDRESS_WITHDRAWAL_PREFIX) { - return null; + const validator = validators.getReadonly(validatorIndex); + + if (!isValidValidator(validator, executionLayerWithdrawalRequest.sourceAddress, state)) { + return; } - const executionAddress = withdrawalCredentials.subarray(12, 32); - if (Buffer.compare(executionAddress, exit.sourceAddress) !== 0) { - return null; + // TODO Electra: Consider caching pendingPartialWithdrawals + const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(state, validatorIndex); + const validatorBalance = state.balances.get(validatorIndex); + + if (isFullExitRequest) { + // only exit validator if it has no pending withdrawals in the queue + if (pendingBalanceToWithdraw === 0) { + initiateValidatorExit(fork, state, validator); + } else { + // Full request can't be fulfilled because still has pending withdrawals. + // TODO Electra: add log here + } + return; } - const currentEpoch = epochCtx.epoch; + const hasSufficientEffectiveBalance = validator.effectiveBalance >= MIN_ACTIVATION_BALANCE; + const hasExcessBalance = validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw; + + // Only allow partial withdrawals with compounding withdrawal credentials if ( - // verify the validator is active + hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) && + hasSufficientEffectiveBalance && + hasExcessBalance + ) { + const amountToWithdraw = BigInt( + Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount) + ); + const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, amountToWithdraw); + const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; + + const pendingPartialWithdrawal = ssz.electra.PartialWithdrawal.toViewDU({ + index: validatorIndex, + amount: amountToWithdraw, + withdrawableEpoch, + }); + state.pendingPartialWithdrawals.push(pendingPartialWithdrawal); + } +} + +function isValidValidator( + validator: phase0.Validator, + sourceAddress: Uint8Array, + state: CachedBeaconStateElectra +): boolean { + const {withdrawalCredentials} = validator; + const addressStr = toHexString(withdrawalCredentials.slice(12)); + const sourceAddressStr = toHexString(sourceAddress); + const {epoch: currentEpoch, config} = state.epochCtx; + + return ( + hasExecutionWithdrawalCredential(withdrawalCredentials) && + addressStr === sourceAddressStr && isActiveValidator(validator, currentEpoch) && - // verify exit has not been initiated validator.exitEpoch === FAR_FUTURE_EPOCH && - // verify the validator had been active long enough currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD - ) { - return validator; - } else { - return null; - } + ); } diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 54b697441ca9..20dde8abaf9e 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -56,11 +56,6 @@ export function processOperations( for (const voluntaryExit of body.voluntaryExits) { processVoluntaryExit(fork, state, voluntaryExit, opts.verifySignatures); } - if (fork >= ForkSeq.electra) { - for (const elWithdrawalRequest of (body as electra.BeaconBlockBody).executionPayload.withdrawalRequests) { - processExecutionLayerWithdrawalRequest(fork ,state as CachedBeaconStateElectra, elWithdrawalRequest); - } - } if (fork >= ForkSeq.capella) { for (const blsToExecutionChange of (body as capella.BeaconBlockBody).blsToExecutionChanges) { @@ -75,8 +70,8 @@ export function processOperations( processDepositReceipt(fork, stateElectra, depositReceipt); } - for (const withdrawalRequest of bodyElectra.executionPayload.withdrawalRequests) { - processExecutionLayerWithdrawalRequest(stateElectra, withdrawalRequest); + for (const elWithdrawalRequest of bodyElectra.executionPayload.withdrawalRequests) { + processExecutionLayerWithdrawalRequest(fork ,state as CachedBeaconStateElectra, elWithdrawalRequest); } for (const consolidation of bodyElectra.consolidations) { diff --git a/packages/state-transition/src/block/processVoluntaryExit.ts b/packages/state-transition/src/block/processVoluntaryExit.ts index 810da2f021a6..af23d5147010 100644 --- a/packages/state-transition/src/block/processVoluntaryExit.ts +++ b/packages/state-transition/src/block/processVoluntaryExit.ts @@ -58,8 +58,10 @@ function isValidVoluntaryExitElectra( signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true ): boolean { - const isValidPreElectra = isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature); - // only exit validator if it has no pending withdrawals in the queue (post-Electra only) - return isValidPreElectra && getPendingBalanceToWithdraw(state, signedVoluntaryExit.message.validatorIndex) === 0; + if (getPendingBalanceToWithdraw(state, signedVoluntaryExit.message.validatorIndex) === 0) { + return isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature); + } + + return false; } diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 634b3fc4571e..931805272295 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -4,7 +4,7 @@ import { MAX_WITHDRAWALS_PER_PAYLOAD, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP, ForkSeq, - MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD, + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, } from "@lodestar/params"; @@ -99,7 +99,7 @@ export function getExpectedWithdrawals( const stateElectra = state as CachedBeaconStateElectra; for (const withdrawal of stateElectra.pendingPartialWithdrawals.getAllReadonly()) { - if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD) { + if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP) { break; } diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index 537d84f3cdbb..53746f2de3cb 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -1,6 +1,11 @@ import {Epoch, ValidatorIndex} from "@lodestar/types"; import {intDiv} from "@lodestar/utils"; -import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MAX_EFFECTIVE_BALANCE, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; +import { + EPOCHS_PER_SLASHINGS_VECTOR, + FAR_FUTURE_EPOCH, + ForkSeq, + MIN_ACTIVATION_BALANCE, +} from "@lodestar/params"; import { AttesterStatus, diff --git a/packages/state-transition/src/epoch/processRegistryUpdates.ts b/packages/state-transition/src/epoch/processRegistryUpdates.ts index d9ff7bd63d0b..3804b273cd8b 100644 --- a/packages/state-transition/src/epoch/processRegistryUpdates.ts +++ b/packages/state-transition/src/epoch/processRegistryUpdates.ts @@ -45,7 +45,7 @@ export function processRegistryUpdates( let indicesEligibleForActivation; if (fork < ForkSeq.electra) { // dequeue validators for activation up to churn limit - indicesEligibleForActivation = cache.indicesEligibleForActivation.slice(0, epochCtx.activationChurnLimit) + indicesEligibleForActivation = cache.indicesEligibleForActivation.slice(0, epochCtx.activationChurnLimit); } else { // no churn limit post-electra indicesEligibleForActivation = cache.indicesEligibleForActivation; diff --git a/packages/state-transition/src/slot/upgradeStateToElectra.ts b/packages/state-transition/src/slot/upgradeStateToElectra.ts index 326b9087aa6a..760595c3a86b 100644 --- a/packages/state-transition/src/slot/upgradeStateToElectra.ts +++ b/packages/state-transition/src/slot/upgradeStateToElectra.ts @@ -1,8 +1,12 @@ import {ssz} from "@lodestar/types"; -import {UNSET_DEPOSIT_RECEIPTS_START_INDEX} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, UNSET_DEPOSIT_RECEIPTS_START_INDEX} from "@lodestar/params"; import {CachedBeaconStateDeneb} from "../types.js"; import {CachedBeaconStateElectra, getCachedBeaconState} from "../cache/stateCache.js"; -import {hasCompoundingWithdrawalCredential, queueExcessActiveBalance} from "../util/electra.js"; +import { + hasCompoundingWithdrawalCredential, + queueEntireBalanceAndResetValidator, + queueExcessActiveBalance, +} from "../util/electra.js"; /** * Upgrade a state from Capella to Deneb. @@ -25,11 +29,18 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache // default value of depositReceiptsStartIndex is UNSET_DEPOSIT_RECEIPTS_START_INDEX stateElectra.depositReceiptsStartIndex = UNSET_DEPOSIT_RECEIPTS_START_INDEX; - // [EIP-7251]: Ensure early adopters of compounding credentials go through the activation churn const validatorsArr = stateElectra.validators.getAllReadonly(); for (let i = 0; i < validatorsArr.length; i++) { - const withdrawalCredential = validatorsArr[i].withdrawalCredentials; + const validator = validatorsArr[i]; + + // [EIP-7251]: add validators that are not yet active to pending balance deposits + if (validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH) { + queueEntireBalanceAndResetValidator(stateElectra, i); + } + + // [EIP-7251]: Ensure early adopters of compounding credentials go through the activation churn + const withdrawalCredential = validator.withdrawalCredentials; if (hasCompoundingWithdrawalCredential(withdrawalCredential)) { queueExcessActiveBalance(stateElectra, i); } diff --git a/packages/state-transition/src/util/electra.ts b/packages/state-transition/src/util/electra.ts index 96e22abda22a..002a6d7224f5 100644 --- a/packages/state-transition/src/util/electra.ts +++ b/packages/state-transition/src/util/electra.ts @@ -1,4 +1,10 @@ -import {COMPOUNDING_WITHDRAWAL_PREFIX, ForkSeq, MAX_EFFECTIVE_BALANCE, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; +import { + COMPOUNDING_WITHDRAWAL_PREFIX, + FAR_FUTURE_EPOCH, + ForkSeq, + MAX_EFFECTIVE_BALANCE, + MIN_ACTIVATION_BALANCE, +} from "@lodestar/params"; import {ValidatorIndex, phase0, ssz} from "@lodestar/types"; import {CachedBeaconStateElectra} from "../types.js"; import {getValidatorMaxEffectiveBalance} from "./validator.js"; @@ -77,3 +83,18 @@ export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: state.pendingBalanceDeposits.push(pendingBalanceDeposit); } } + +export function queueEntireBalanceAndResetValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void { + const balance = state.balances.get(index); + state.balances.set(index, 0); + + const validator = state.validators.get(index); + validator.effectiveBalance = 0; + validator.activationEligibilityEpoch = FAR_FUTURE_EPOCH; + + const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ + index, + amount: BigInt(balance), + }); + state.pendingBalanceDeposits.push(pendingBalanceDeposit); +} diff --git a/packages/state-transition/src/util/epoch.ts b/packages/state-transition/src/util/epoch.ts index f719284bc2f5..e399d3276e3e 100644 --- a/packages/state-transition/src/util/epoch.ts +++ b/packages/state-transition/src/util/epoch.ts @@ -42,27 +42,24 @@ export function computeActivationExitEpoch(epoch: Epoch): Epoch { } export function computeExitEpochAndUpdateChurn(state: CachedBeaconStateElectra, exitBalance: Gwei): number { - const earliestExitEpoch = computeActivationExitEpoch(state.epochCtx.epoch); + let earliestExitEpoch = Math.max(state.earliestExitEpoch, computeActivationExitEpoch(state.epochCtx.epoch)); const perEpochChurn = getActivationExitChurnLimit(state); // New epoch for exits. - if (state.earliestExitEpoch < earliestExitEpoch) { - state.earliestExitEpoch = earliestExitEpoch; - state.exitBalanceToConsume = BigInt(perEpochChurn); + let exitBalanceToConsume = + state.earliestExitEpoch < earliestExitEpoch ? perEpochChurn : Number(state.exitBalanceToConsume); + + // Exit doesn't fit in the current earliest epoch. + if (exitBalance > exitBalanceToConsume) { + const balanceToProcess = Number(exitBalance) - exitBalanceToConsume; + const additionalEpochs = Math.floor((balanceToProcess - 1) / (perEpochChurn + 1)); + earliestExitEpoch += additionalEpochs; + exitBalanceToConsume += additionalEpochs * perEpochChurn; } - if (exitBalance <= state.exitBalanceToConsume) { - // Exit fits in the current earliest epoch. - state.exitBalanceToConsume -= exitBalance; - } else { - // Exit doesn't fit in the current earliest epoch. - const balanceToProcess = exitBalance - state.exitBalanceToConsume; - const additionalEpochs = balanceToProcess / BigInt(perEpochChurn); - const remainder = balanceToProcess % BigInt(perEpochChurn); - - state.earliestExitEpoch += Number(additionalEpochs) + 1; - state.exitBalanceToConsume = BigInt(perEpochChurn) - remainder; - } + // Consume the balance and update state variables. + state.exitBalanceToConsume = BigInt(exitBalanceToConsume) - exitBalance; + state.earliestExitEpoch = earliestExitEpoch; return state.earliestExitEpoch; } @@ -71,23 +68,30 @@ export function computeConsolidationEpochAndUpdateChurn( state: CachedBeaconStateElectra, consolidationBalance: Gwei ): number { - const earliestConsolidationEpoch = computeActivationExitEpoch(state.epochCtx.epoch); + let earliestConsolidationEpoch = Math.max( + state.earliestConsolidationEpoch, + computeActivationExitEpoch(state.epochCtx.epoch) + ); const perEpochConsolidationChurn = getConsolidationChurnLimit(state); // New epoch for consolidations - if (state.earliestConsolidationEpoch < earliestConsolidationEpoch) { - state.earliestConsolidationEpoch = earliestConsolidationEpoch; - state.consolidationBalanceToConsume = BigInt(perEpochConsolidationChurn); - } else { - // Consolidation doesn't fit in the current earliest epoch. - const balanceToProcess = consolidationBalance - state.consolidationBalanceToConsume; - const additionalEpochs = balanceToProcess / BigInt(perEpochConsolidationChurn); - const remainder = balanceToProcess % BigInt(perEpochConsolidationChurn); - - state.earliestConsolidationEpoch += Number(additionalEpochs) + 1; - state.exitBalanceToConsume = BigInt(perEpochConsolidationChurn) - remainder; + let consolidationBalanceToConsume = + state.earliestConsolidationEpoch < earliestConsolidationEpoch + ? perEpochConsolidationChurn + : Number(state.consolidationBalanceToConsume); + + // Consolidation doesn't fit in the current earliest epoch. + if (consolidationBalance > consolidationBalanceToConsume) { + const balanceToProcess = Number(consolidationBalance) - consolidationBalanceToConsume; + const additionalEpochs = Math.floor((balanceToProcess - 1) / (perEpochConsolidationChurn + 1)); + earliestConsolidationEpoch += additionalEpochs; + consolidationBalanceToConsume += additionalEpochs * perEpochConsolidationChurn; } + // Consume the balance and update state variables. + state.consolidationBalanceToConsume = BigInt(consolidationBalanceToConsume) - consolidationBalance; + state.earliestConsolidationEpoch = earliestConsolidationEpoch; + return state.earliestConsolidationEpoch; } diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index e95a13bf5d44..1142c6814d4b 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -61,6 +61,9 @@ export function getConsolidationChurnLimit(state: CachedBeaconStateElectra): num return getActivationExitConsolidationChurnLimit(state) - getActivationExitChurnLimit(state); } +/** + * Get combined churn limit of activation-exit and consolidation + */ export function getActivationExitConsolidationChurnLimit(state: CachedBeaconStateElectra): number { const churnLimitByTotalActiveBalance = Math.floor( (state.epochCtx.totalActiveBalanceIncrements / state.config.CHURN_LIMIT_QUOTIENT) * EFFECTIVE_BALANCE_INCREMENT diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 891d6e8f7ec5..3a497555361a 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -227,6 +227,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Mon, 6 May 2024 14:33:54 +0530 Subject: [PATCH 34/41] rebase fixes, fixes, improvements and cleanup lint --- .../chain/produceBlock/produceBlockBody.ts | 12 ++--------- .../src/metrics/metrics/lodestar.ts | 10 ++++----- .../opPools/aggregatedAttestationPool.test.ts | 4 ++-- packages/state-transition/src/block/index.ts | 16 ++------------ .../processExecutionLayerWithdrawalRequest.ts | 21 +++++++------------ .../src/block/processOperations.ts | 12 +++++------ .../src/block/processWithdrawals.ts | 2 +- .../src/cache/epochTransitionCache.ts | 7 +------ .../epoch/processPendingBalanceDeposits.ts | 4 ++-- .../src/epoch/processPendingConsolidations.ts | 6 +++--- packages/state-transition/src/util/capella.ts | 2 +- packages/state-transition/src/util/electra.ts | 14 ++++++------- packages/types/src/electra/sszTypes.ts | 10 --------- packages/types/src/electra/types.ts | 2 -- 14 files changed, 39 insertions(+), 83 deletions(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index e0af4a7281b7..988e3ca135e1 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -11,13 +11,11 @@ import { capella, deneb, Wei, - electra, } from "@lodestar/types"; import { CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix, - CachedBeaconStateElectra, CachedBeaconStateExecutions, computeEpochAtSlot, computeTimeAtSlot, @@ -557,7 +555,8 @@ function preparePayloadAttributes( suggestedFeeRecipient: feeRecipient, }; - if (ForkSeq[fork] === ForkSeq.capella || ForkSeq[fork] === ForkSeq.electra) { + if (ForkSeq[fork] >= ForkSeq.capella) { + // withdrawals logic is now fork aware as it changes on electra fork post capella (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( ForkSeq[fork], prepareState as CachedBeaconStateCapella @@ -568,13 +567,6 @@ function preparePayloadAttributes( (payloadAttributes as deneb.SSEPayloadAttributes["payloadAttributes"]).parentBeaconBlockRoot = parentBlockRoot; } - if (ForkSeq[fork] >= ForkSeq.electra) { - (payloadAttributes as electra.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( - ForkSeq[fork], - prepareState as CachedBeaconStateElectra - ).withdrawals; - } - return payloadAttributes; } diff --git a/packages/beacon-node/src/metrics/metrics/lodestar.ts b/packages/beacon-node/src/metrics/metrics/lodestar.ts index fd3962aba0f7..cfa4145bfdcc 100644 --- a/packages/beacon-node/src/metrics/metrics/lodestar.ts +++ b/packages/beacon-node/src/metrics/metrics/lodestar.ts @@ -1211,16 +1211,16 @@ export function createLodestarMetrics( help: "Histogram of cloned count per state every time state.clone() is called", buckets: [1, 2, 5, 10, 50, 250], }), - numStatesUpdated: register.histogram({ - name: "lodestar_cp_state_cache_state_updated_count", - help: "Histogram of number of state cache items updated every time removing and adding pubkeys to pubkey cache", - buckets: [1, 2, 5, 10, 50, 250], - }), stateSerializeDuration: register.histogram({ name: "lodestar_cp_state_cache_state_serialize_seconds", help: "Histogram of time to serialize state to db", buckets: [0.1, 0.5, 1, 2, 3, 4], }), + numStatesUpdated: register.histogram({ + name: "lodestar_cp_state_cache_state_updated_count", + help: "Histogram of number of state cache items updated every time removing and adding pubkeys to pubkey cache", + buckets: [1, 2, 5, 10, 50, 250], + }), statePruneFromMemoryCount: register.gauge({ name: "lodestar_cp_state_cache_state_prune_from_memory_count", help: "Total number of states pruned from memory", diff --git a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts index 71dfd63830c1..3c248ad4d194 100644 --- a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts @@ -62,9 +62,9 @@ describe("AggregatedAttestationPool", function () { epochParticipation[committee[i]] = 0b000; } } - (originalState as unknown as CachedBeaconStateAltair).previousEpochParticipation = + (originalState as CachedBeaconStateAltair).previousEpochParticipation = ssz.altair.EpochParticipation.toViewDU(epochParticipation); - (originalState as unknown as CachedBeaconStateAltair).currentEpochParticipation = + (originalState as CachedBeaconStateAltair).currentEpochParticipation = ssz.altair.EpochParticipation.toViewDU(epochParticipation); originalState.commit(); let altairState: CachedBeaconStateAllForks; diff --git a/packages/state-transition/src/block/index.ts b/packages/state-transition/src/block/index.ts index 2d5ebe053f47..3c3361ea7bc0 100644 --- a/packages/state-transition/src/block/index.ts +++ b/packages/state-transition/src/block/index.ts @@ -1,12 +1,7 @@ import {ForkSeq} from "@lodestar/params"; import {allForks, altair, capella} from "@lodestar/types"; import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js"; -import { - CachedBeaconStateAllForks, - CachedBeaconStateCapella, - CachedBeaconStateBellatrix, - CachedBeaconStateElectra, -} from "../types.js"; +import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix} from "../types.js"; import {processExecutionPayload} from "./processExecutionPayload.js"; import {processSyncAggregate} from "./processSyncCommittee.js"; import {processBlockHeader} from "./processBlockHeader.js"; @@ -50,20 +45,13 @@ export function processBlock( const fullOrBlindedPayload = getFullOrBlindedPayload(block); // TODO Deneb: Allow to disable withdrawals for interop testing // https://github.com/ethereum/consensus-specs/blob/b62c9e877990242d63aa17a2a59a49bc649a2f2e/specs/eip4844/beacon-chain.md#disabling-withdrawals - if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { + if (fork >= ForkSeq.capella) { processWithdrawals( fork, state as CachedBeaconStateCapella, fullOrBlindedPayload as capella.FullOrBlindedExecutionPayload ); } - if (fork >= ForkSeq.electra) { - processWithdrawals( - fork, - state as CachedBeaconStateElectra, - fullOrBlindedPayload as capella.FullOrBlindedExecutionPayload - ); - } processExecutionPayload(fork, state as CachedBeaconStateBellatrix, block.body, externalData); } diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts index dc6975fda827..d7daeaa10303 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts @@ -21,7 +21,8 @@ export function processExecutionLayerWithdrawalRequest( ): void { const amount = Number(executionLayerWithdrawalRequest.amount); const {pendingPartialWithdrawals, validators, epochCtx} = state; - const {pubkey2index, config} = epochCtx; // TODO Electra: Use finalized+unfinalized pubkey cache from 6110 + // no need to use unfinalized pubkey cache from 6110 as validator won't be active anyway + const {pubkey2index, config} = epochCtx; const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT; // If partial withdrawal queue is full, only full exits are processed @@ -29,19 +30,15 @@ export function processExecutionLayerWithdrawalRequest( return; } + // bail out if validator is not in beacon state + // note that we don't need to check for 6110 unfinalized vals as they won't be eligible for withdraw/exit anyway const validatorIndex = pubkey2index.get(executionLayerWithdrawalRequest.validatorPubkey); - if (validatorIndex === undefined) { - throw new Error( - `Can't find validator index from ExecutionLayerWithdrawalRequest : pubkey=${toHexString( - executionLayerWithdrawalRequest.validatorPubkey - )}` - ); + return; } const validator = validators.getReadonly(validatorIndex); - - if (!isValidValidator(validator, executionLayerWithdrawalRequest.sourceAddress, state)) { + if (!isValidatorEligibleForWithdrawOrExit(validator, executionLayerWithdrawalRequest.sourceAddress, state)) { return; } @@ -53,13 +50,11 @@ export function processExecutionLayerWithdrawalRequest( // only exit validator if it has no pending withdrawals in the queue if (pendingBalanceToWithdraw === 0) { initiateValidatorExit(fork, state, validator); - } else { - // Full request can't be fulfilled because still has pending withdrawals. - // TODO Electra: add log here } return; } + // partial withdrawal request const hasSufficientEffectiveBalance = validator.effectiveBalance >= MIN_ACTIVATION_BALANCE; const hasExcessBalance = validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw; @@ -84,7 +79,7 @@ export function processExecutionLayerWithdrawalRequest( } } -function isValidValidator( +function isValidatorEligibleForWithdrawOrExit( validator: phase0.Validator, sourceAddress: Uint8Array, state: CachedBeaconStateElectra diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 20dde8abaf9e..5c8e6086b3c6 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -8,11 +8,10 @@ import {processProposerSlashing} from "./processProposerSlashing.js"; import {processAttesterSlashing} from "./processAttesterSlashing.js"; import {processDeposit} from "./processDeposit.js"; import {processVoluntaryExit} from "./processVoluntaryExit.js"; -import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWithdrawalRequest.js"; import {processBlsToExecutionChange} from "./processBlsToExecutionChange.js"; +import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWithdrawalRequest.js"; import {processDepositReceipt} from "./processDepositReceipt.js"; import {ProcessBlockOpts} from "./types.js"; -import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWithdrawalRequest.js"; import {processConsolidation} from "./processConsolidation.js"; export { @@ -66,12 +65,13 @@ export function processOperations( if (fork >= ForkSeq.electra) { const stateElectra = state as CachedBeaconStateElectra; const bodyElectra = body as electra.BeaconBlockBody; - for (const depositReceipt of bodyElectra.executionPayload.depositReceipts) { - processDepositReceipt(fork, stateElectra, depositReceipt); - } for (const elWithdrawalRequest of bodyElectra.executionPayload.withdrawalRequests) { - processExecutionLayerWithdrawalRequest(fork ,state as CachedBeaconStateElectra, elWithdrawalRequest); + processExecutionLayerWithdrawalRequest(fork, state as CachedBeaconStateElectra, elWithdrawalRequest); + } + + for (const depositReceipt of bodyElectra.executionPayload.depositReceipts) { + processDepositReceipt(fork, stateElectra, depositReceipt); } for (const consolidation of bodyElectra.consolidations) { diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 931805272295..fdb7201a3750 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -57,7 +57,7 @@ export function processWithdrawals( } if (fork >= ForkSeq.electra) { - const stateElectra = state as CachedBeaconStateElectra; + const _stateElectra = state as CachedBeaconStateElectra; // TODO Electra: stateElectra.pendingPartialWithdrawals need to implement slicing mechanism for ListCompositeTreeViewDU } diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index 53746f2de3cb..42e8ccc3b790 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -1,11 +1,6 @@ import {Epoch, ValidatorIndex} from "@lodestar/types"; import {intDiv} from "@lodestar/utils"; -import { - EPOCHS_PER_SLASHINGS_VECTOR, - FAR_FUTURE_EPOCH, - ForkSeq, - MIN_ACTIVATION_BALANCE, -} from "@lodestar/params"; +import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; import { AttesterStatus, diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts index 7b063d5daaba..549cfe7a283a 100644 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -12,7 +12,7 @@ import {getActivationExitChurnLimit} from "../util/validator.js"; export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): void { const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state)); let processedAmount = 0n; - let nextDepositIndex = 0; + let _nextDepositIndex = 0; for (const deposit of state.pendingBalanceDeposits.getAllReadonly()) { const {amount} = deposit; @@ -21,7 +21,7 @@ export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): } increaseBalance(state, deposit.index, Number(amount)); processedAmount = processedAmount + amount; - nextDepositIndex++; + _nextDepositIndex++; } // TODO Electra: Impl slicing for ssz diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 0d02fdbdedd9..40b951e7ec72 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -17,14 +17,14 @@ import {switchToCompoundingValidator} from "../util/electra.js"; * */ export function processPendingConsolidations(state: CachedBeaconStateElectra): void { - let nextPendingConsolidation = 0; + let _nextPendingConsolidation = 0; for (const pendingConsolidation of state.pendingConsolidations.getAllReadonly()) { const {sourceIndex, targetIndex} = pendingConsolidation; const sourceValidator = state.validators.getReadonly(sourceIndex); if (sourceValidator.slashed) { - nextPendingConsolidation++; + _nextPendingConsolidation++; continue; } @@ -38,7 +38,7 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v decreaseBalance(state, sourceIndex, activeBalance); increaseBalance(state, targetIndex, activeBalance); - nextPendingConsolidation++; + _nextPendingConsolidation++; } // TODO Electra: impl slicing for ssz diff --git a/packages/state-transition/src/util/capella.ts b/packages/state-transition/src/util/capella.ts index 5f65f6d3bfa3..dfedc2778c75 100644 --- a/packages/state-transition/src/util/capella.ts +++ b/packages/state-transition/src/util/capella.ts @@ -1,4 +1,4 @@ -import {ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params/"; +import {ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params"; /** * https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential diff --git a/packages/state-transition/src/util/electra.ts b/packages/state-transition/src/util/electra.ts index 002a6d7224f5..d642ea3ee1ef 100644 --- a/packages/state-transition/src/util/electra.ts +++ b/packages/state-transition/src/util/electra.ts @@ -29,15 +29,13 @@ export function isFullyWithdrawableValidator( epoch: number ): boolean { const {withdrawableEpoch, withdrawalCredentials: withdrawalCredential} = validatorCredential; - if (fork === ForkSeq.capella || fork === ForkSeq.deneb) { - return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; - } - - if (fork === ForkSeq.electra) { + if (fork >= ForkSeq.electra) { return hasExecutionWithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; + } else if (fork >= ForkSeq.capella) { + return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; + } else { + return false; } - - return false; } export function isPartiallyWithdrawableValidator( @@ -48,7 +46,7 @@ export function isPartiallyWithdrawableValidator( const {effectiveBalance, withdrawalCredentials: withdrawalCredential} = validatorCredential; if (fork < ForkSeq.capella) { - throw new Error("Unsupported fork"); + throw new Error(`isPartiallyWithdrawableValidator not supported at forkSeq=${fork} < ForkSeq.capella`); } const validatorMaxEffectiveBalance = diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 407390c44716..26539dd61365 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -21,7 +21,6 @@ import { PENDING_BALANCE_DEPOSITS_LIMIT, PENDING_CONSOLIDATIONS_LIMIT, PENDING_PARTIAL_WITHDRAWALS_LIMIT, - MAX_WITHDRAWALS_PER_PAYLOAD, } from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; @@ -137,15 +136,6 @@ export const ExecutionLayerWithdrawalRequests = new ListCompositeType( MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD ); -export const ExecutionLayerWithdrawalRequest = new ContainerType( - { - sourceAddress: ExecutionAddress, - validatorPubkey: BLSPubkey, - amount: Gwei, - }, - {typeName: "ExecutionLayerWithdrawalRequest", jsonCase: "eth2"} -); - export const ExecutionPayload = new ContainerType( { ...denebSsz.ExecutionPayload.fields, diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 266555f78762..4de209b48960 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -49,5 +49,3 @@ export type SignedConsolidation = ValueOf; export type PendingBalanceDeposit = ValueOf; export type PartialWithdrawal = ValueOf; export type PendingConsolidation = ValueOf; - -export type ExecutionLayerWithdrawalRequest = ValueOf; From 2f6df3dd3c58c58eeee52004dc9922f04508de46 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Tue, 7 May 2024 10:33:32 +0300 Subject: [PATCH 35/41] Upgrade ssz version --- packages/api/package.json | 2 +- packages/beacon-node/package.json | 2 +- packages/cli/package.json | 2 +- packages/config/package.json | 2 +- packages/db/package.json | 2 +- packages/fork-choice/package.json | 2 +- packages/light-client/package.json | 2 +- packages/state-transition/package.json | 2 +- packages/types/package.json | 2 +- packages/validator/package.json | 2 +- yarn.lock | 25 +++++++++++++++++++------ 11 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index c9bf498dc416..fe9c326af14f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.7.1", - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/config": "^1.18.0", "@lodestar/params": "^1.18.0", "@lodestar/types": "^1.18.0", diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 45e634c37a17..d81b1c8c1de0 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -104,7 +104,7 @@ "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/persistent-merkle-tree": "^0.7.1", "@chainsafe/prometheus-gc-stats": "^1.0.0", - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@chainsafe/threads": "^1.11.1", "@ethersproject/abi": "^5.7.0", "@fastify/bearer-auth": "^9.0.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 70f538f489c7..e309045da162 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -58,7 +58,7 @@ "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/persistent-merkle-tree": "^0.7.1", - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@chainsafe/threads": "^1.11.1", "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", diff --git a/packages/config/package.json b/packages/config/package.json index 84705be379ac..84bf7daf1dc0 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -64,7 +64,7 @@ "blockchain" ], "dependencies": { - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/params": "^1.18.0", "@lodestar/types": "^1.18.0" } diff --git a/packages/db/package.json b/packages/db/package.json index bffc7d8f2d64..96c4d6675bd1 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -35,7 +35,7 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/config": "^1.18.0", "@lodestar/utils": "^1.18.0", "it-all": "^3.0.4", diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index 07f887de7350..ec456159f3a4 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -36,7 +36,7 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/config": "^1.18.0", "@lodestar/params": "^1.18.0", "@lodestar/state-transition": "^1.18.0", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index a50ae5a4bd53..2421ab8ec82c 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -74,7 +74,7 @@ "dependencies": { "@chainsafe/bls": "^8.1.0", "@chainsafe/persistent-merkle-tree": "^0.7.1", - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/api": "^1.18.0", "@lodestar/config": "^1.18.0", "@lodestar/params": "^1.18.0", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 27f4bd2c93a4..dcc53cc9c4be 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -63,7 +63,7 @@ "@chainsafe/blst": "^1.0.0", "@chainsafe/persistent-merkle-tree": "^0.7.1", "@chainsafe/persistent-ts": "^0.19.1", - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/config": "^1.18.0", "@lodestar/params": "^1.18.0", "@lodestar/types": "^1.18.0", diff --git a/packages/types/package.json b/packages/types/package.json index 45b761acee1b..04914f46ed23 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -76,7 +76,7 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/params": "^1.18.0", "ethereum-cryptography": "^2.0.0" }, diff --git a/packages/validator/package.json b/packages/validator/package.json index 8e5539a007c7..c88ca56e7610 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -46,7 +46,7 @@ ], "dependencies": { "@chainsafe/bls": "^8.1.0", - "@chainsafe/ssz": "^0.15.1", + "@chainsafe/ssz": "^0.16.0", "@lodestar/api": "^1.18.0", "@lodestar/config": "^1.18.0", "@lodestar/db": "^1.18.0", diff --git a/yarn.lock b/yarn.lock index 4276979f165e..9cc5039e18f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -274,6 +274,11 @@ resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.4.1.tgz#cfc0737e25f8c206767bdb6703e7943e5d44513e" integrity sha512-IqeeGwQihK6Y2EYLFofqs2eY2ep1I2MvQXHzOAI+5iQN51OZlUkrLgyAugu2x86xZewDk5xas7lNczkzFzF62w== +"@chainsafe/as-sha256@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.4.2.tgz#21ad1371e2245e430c1a554a05f10d333c6f42cc" + integrity sha512-HJ8GZBRjLeWtRsAXf3EbNsNzmTGpzTFjfpSf4yHkLYC+E52DhT6hwz+7qpj6I/EmFzSUm5tYYvT9K8GZokLQCQ== + "@chainsafe/bls-hd-key@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@chainsafe/bls-hd-key/-/bls-hd-key-0.3.0.tgz" @@ -459,6 +464,14 @@ "@chainsafe/as-sha256" "^0.4.1" "@noble/hashes" "^1.3.0" +"@chainsafe/persistent-merkle-tree@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.7.2.tgz#f0ef91daf36752f827432333cbc965f4bf6e750e" + integrity sha512-BUAqrmSUmy6bZhXxnhpR+aYoEDdCeS1dQvq/aje0CDEB14ZHF9UVN2mL9MolOD0ANUiP1OaPG3KfVBxvuW8aTg== + dependencies: + "@chainsafe/as-sha256" "^0.4.2" + "@noble/hashes" "^1.3.0" + "@chainsafe/persistent-ts@^0.19.1": version "0.19.1" resolved "https://registry.npmjs.org/@chainsafe/persistent-ts/-/persistent-ts-0.19.1.tgz" @@ -477,13 +490,13 @@ "@chainsafe/as-sha256" "^0.4.1" "@chainsafe/persistent-merkle-tree" "^0.6.1" -"@chainsafe/ssz@^0.15.1": - version "0.15.1" - resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.15.1.tgz#008a711c3bcdc0d207cd4be15108870b0b1c60c0" - integrity sha512-f09UKTyYwWA1nr1BwrwsFpkXMspDDIZtwWXK1pM5mpPMnexmuPVstnN+P0M4YJ2aHcfqJXG7QOqnOwGj5Z7bUw== +"@chainsafe/ssz@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.16.0.tgz#262c491ac037777a16e8d8db479da2ba27539b8d" + integrity sha512-CgTDyrkbAKvrKwHxPT5rerXAHP3NB+uOvpnN9Gn8aJ/4TGOKhOboj4131bSFUZ679uPJ6pu6391cvInuOdrglw== dependencies: - "@chainsafe/as-sha256" "^0.4.1" - "@chainsafe/persistent-merkle-tree" "^0.7.1" + "@chainsafe/as-sha256" "^0.4.2" + "@chainsafe/persistent-merkle-tree" "^0.7.2" "@chainsafe/threads@^1.11.1": version "1.11.1" From a5b5fe0bca397cd8a67ccdd151c91d98d8d8e646 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Tue, 7 May 2024 10:33:42 +0300 Subject: [PATCH 36/41] Use sliceFrom() --- .../state-transition/src/block/processWithdrawals.ts | 7 +++---- .../src/epoch/processPendingBalanceDeposits.ts | 10 ++++------ .../src/epoch/processPendingConsolidations.ts | 10 ++++------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index fdb7201a3750..80ab52074eb5 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -25,8 +25,7 @@ export function processWithdrawals( state: CachedBeaconStateCapella | CachedBeaconStateElectra, payload: capella.FullOrBlindedExecutionPayload ): void { - // const {withdrawals: expectedWithdrawals, partialWithdrawalsCount} = getExpectedWithdrawals(fork, state); - const {withdrawals: expectedWithdrawals} = getExpectedWithdrawals(fork, state); + const {withdrawals: expectedWithdrawals, partialWithdrawalsCount} = getExpectedWithdrawals(fork, state); const numWithdrawals = expectedWithdrawals.length; if (isCapellaPayloadHeader(payload)) { @@ -57,8 +56,8 @@ export function processWithdrawals( } if (fork >= ForkSeq.electra) { - const _stateElectra = state as CachedBeaconStateElectra; - // TODO Electra: stateElectra.pendingPartialWithdrawals need to implement slicing mechanism for ListCompositeTreeViewDU + const stateElectra = state as CachedBeaconStateElectra; + stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom(partialWithdrawalsCount); } // Update the nextWithdrawalIndex diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts index 549cfe7a283a..95928d66df2d 100644 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts @@ -12,7 +12,7 @@ import {getActivationExitChurnLimit} from "../util/validator.js"; export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): void { const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state)); let processedAmount = 0n; - let _nextDepositIndex = 0; + let nextDepositIndex = 0; for (const deposit of state.pendingBalanceDeposits.getAllReadonly()) { const {amount} = deposit; @@ -21,13 +21,11 @@ export function processPendingBalanceDeposits(state: CachedBeaconStateElectra): } increaseBalance(state, deposit.index, Number(amount)); processedAmount = processedAmount + amount; - _nextDepositIndex++; + nextDepositIndex++; } - // TODO Electra: Impl slicing for ssz - const remainingPendingBalanceDeposits = []; - // const remainingPendingBalanceDeposits = state.pendingBalanceDeposits.slice() - // state.pendingBalanceDeposits = remainingPendingBalanceDeposits + const remainingPendingBalanceDeposits = state.pendingBalanceDeposits.sliceFrom(nextDepositIndex); + state.pendingBalanceDeposits = remainingPendingBalanceDeposits; if (remainingPendingBalanceDeposits.length === 0) { state.depositBalanceToConsume = 0n; diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 40b951e7ec72..e025a454f64e 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -17,14 +17,14 @@ import {switchToCompoundingValidator} from "../util/electra.js"; * */ export function processPendingConsolidations(state: CachedBeaconStateElectra): void { - let _nextPendingConsolidation = 0; + let nextPendingConsolidation = 0; for (const pendingConsolidation of state.pendingConsolidations.getAllReadonly()) { const {sourceIndex, targetIndex} = pendingConsolidation; const sourceValidator = state.validators.getReadonly(sourceIndex); if (sourceValidator.slashed) { - _nextPendingConsolidation++; + nextPendingConsolidation++; continue; } @@ -38,10 +38,8 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra): v decreaseBalance(state, sourceIndex, activeBalance); increaseBalance(state, targetIndex, activeBalance); - _nextPendingConsolidation++; + nextPendingConsolidation++; } - // TODO Electra: impl slicing for ssz - // const remainingPendingConsolidations = []; - // state.pendingConsolidations =remainingPendingConsolidations + state.pendingConsolidations = state.pendingConsolidations.sliceFrom(nextPendingConsolidation); } From fa066f4508b2e086a878a185af661ddf97c8317a Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 7 May 2024 18:03:07 +0530 Subject: [PATCH 37/41] cleanup as per specs feedback subarry --- .../src/block/processConsolidation.ts | 4 ++-- .../processExecutionLayerWithdrawalRequest.ts | 2 +- .../state-transition/src/util/validator.ts | 21 ++++++++----------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/state-transition/src/block/processConsolidation.ts b/packages/state-transition/src/block/processConsolidation.ts index cacc6d0e08d6..846b0e2521b4 100644 --- a/packages/state-transition/src/block/processConsolidation.ts +++ b/packages/state-transition/src/block/processConsolidation.ts @@ -95,8 +95,8 @@ function assertValidConsolidation( } // Verify the same withdrawal address - const sourceWithdrawalAddress = toHexString(sourceValidator.withdrawalCredentials.slice(12)); - const targetWithdrawalAddress = toHexString(targetValidator.withdrawalCredentials.slice(12)); + const sourceWithdrawalAddress = toHexString(sourceValidator.withdrawalCredentials.subarray(12)); + const targetWithdrawalAddress = toHexString(targetValidator.withdrawalCredentials.subarray(12)); if (sourceWithdrawalAddress !== targetWithdrawalAddress) { throw new Error( diff --git a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts index d7daeaa10303..e16fad105aaa 100644 --- a/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts +++ b/packages/state-transition/src/block/processExecutionLayerWithdrawalRequest.ts @@ -85,7 +85,7 @@ function isValidatorEligibleForWithdrawOrExit( state: CachedBeaconStateElectra ): boolean { const {withdrawalCredentials} = validator; - const addressStr = toHexString(withdrawalCredentials.slice(12)); + const addressStr = toHexString(withdrawalCredentials.subarray(12)); const sourceAddressStr = toHexString(sourceAddress); const {epoch: currentEpoch, config} = state.epochCtx; diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 1142c6814d4b..0c8c74b2cb03 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -50,21 +50,14 @@ export function getActivationChurnLimit(config: ChainForkConfig, fork: ForkSeq, } } -export function getActivationExitChurnLimit(state: CachedBeaconStateElectra): number { - return Math.min( - state.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, - getActivationExitConsolidationChurnLimit(state) - ); -} - -export function getConsolidationChurnLimit(state: CachedBeaconStateElectra): number { - return getActivationExitConsolidationChurnLimit(state) - getActivationExitChurnLimit(state); +export function getChurnLimit(config: ChainForkConfig, activeValidatorCount: number): number { + return Math.max(config.MIN_PER_EPOCH_CHURN_LIMIT, intDiv(activeValidatorCount, config.CHURN_LIMIT_QUOTIENT)); } /** * Get combined churn limit of activation-exit and consolidation */ -export function getActivationExitConsolidationChurnLimit(state: CachedBeaconStateElectra): number { +export function getBalanceChurnLimit(state: CachedBeaconStateElectra): number { const churnLimitByTotalActiveBalance = Math.floor( (state.epochCtx.totalActiveBalanceIncrements / state.config.CHURN_LIMIT_QUOTIENT) * EFFECTIVE_BALANCE_INCREMENT ); // TODO Electra: verify calculation @@ -74,8 +67,12 @@ export function getActivationExitConsolidationChurnLimit(state: CachedBeaconStat return churn - (churn % EFFECTIVE_BALANCE_INCREMENT); } -export function getChurnLimit(config: ChainForkConfig, activeValidatorCount: number): number { - return Math.max(config.MIN_PER_EPOCH_CHURN_LIMIT, intDiv(activeValidatorCount, config.CHURN_LIMIT_QUOTIENT)); +export function getActivationExitChurnLimit(state: CachedBeaconStateElectra): number { + return Math.min(state.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, getBalanceChurnLimit(state)); +} + +export function getConsolidationChurnLimit(state: CachedBeaconStateElectra): number { + return getBalanceChurnLimit(state) - getActivationExitChurnLimit(state); } export function getValidatorMaxEffectiveBalance(withdrawalCredentials: Uint8Array): number { From 51eaa3ed1a68748c2ce9ea17618155db6148f6d0 Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 7 May 2024 22:54:12 +0530 Subject: [PATCH 38/41] simplify --- .../src/block/processVoluntaryExit.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/state-transition/src/block/processVoluntaryExit.ts b/packages/state-transition/src/block/processVoluntaryExit.ts index af23d5147010..b08aa7800884 100644 --- a/packages/state-transition/src/block/processVoluntaryExit.ts +++ b/packages/state-transition/src/block/processVoluntaryExit.ts @@ -16,13 +16,12 @@ export function processVoluntaryExit( signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true ): void { - const isAfterElectra = fork >= ForkSeq.electra; - if ( - (!isAfterElectra && !isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature)) || - (isAfterElectra && - !isValidVoluntaryExitElectra(state as CachedBeaconStateElectra, signedVoluntaryExit, verifySignature)) - ) { - throw Error("Invalid voluntary exit"); + const isValidExit = + fork >= ForkSeq.electra + ? isValidVoluntaryExitElectra(state as CachedBeaconStateElectra, signedVoluntaryExit, verifySignature) + : isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature); + if (!isValidExit) { + throw Error(`Invalid voluntary exit at forkSeq=${fork}`); } const validator = state.validators.get(signedVoluntaryExit.message.validatorIndex); From 4a7d167ccc44098c742d5aab9a4aa0f0900c0378 Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 7 May 2024 23:32:33 +0530 Subject: [PATCH 39/41] fix withdrawals --- .../src/block/processWithdrawals.ts | 20 +++++---------- packages/state-transition/src/util/electra.ts | 25 +++++++++++-------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 80ab52074eb5..14146f2b87a4 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -13,8 +13,6 @@ import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; import { decreaseBalance, getValidatorMaxEffectiveBalance, - hasEth1WithdrawalCredential, - hasExecutionWithdrawalCredential, isCapellaPayloadHeader, isFullyWithdrawableValidator, isPartiallyWithdrawableValidator, @@ -90,7 +88,6 @@ export function getExpectedWithdrawals( const epoch = state.epochCtx.epoch; let withdrawalIndex = state.nextWithdrawalIndex; const {validators, balances, nextWithdrawalValidatorIndex} = state; - const bound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP); const withdrawals: capella.Withdrawal[] = []; @@ -115,7 +112,7 @@ export function getExpectedWithdrawals( withdrawals.push({ index: withdrawalIndex, validatorIndex: withdrawal.index, - address: validator.withdrawalCredentials.slice(12), + address: validator.withdrawalCredentials.subarray(12), amount: withdrawableBalance, }); withdrawalIndex++; @@ -124,6 +121,7 @@ export function getExpectedWithdrawals( } const partialWithdrawalsCount = withdrawals.length; + const bound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP); let n = 0; // Just run a bounded loop max iterating over all withdrawals // however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD @@ -131,20 +129,14 @@ export function getExpectedWithdrawals( // Get next validator in turn const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length; - // It's most likely for validators to not have set eth1 credentials, than having 0 balance const validator = validators.getReadonly(validatorIndex); - if ( - (fork === ForkSeq.capella || fork === ForkSeq.deneb) && - !hasEth1WithdrawalCredential(validator.withdrawalCredentials) - ) { - continue; - } - if (fork === ForkSeq.electra && !hasExecutionWithdrawalCredential(validator.withdrawalCredentials)) { + const balance = balances.get(validatorIndex); + // early skip for balance = 0 as its now more likely that validator has exited/slahed with + // balance zero than not have withdrawal credentials set + if (balance === 0) { continue; } - const balance = balances.get(validatorIndex); - if (isFullyWithdrawableValidator(fork, validator, balance, epoch)) { withdrawals.push({ index: withdrawalIndex, diff --git a/packages/state-transition/src/util/electra.ts b/packages/state-transition/src/util/electra.ts index d642ea3ee1ef..00c5bb183eb4 100644 --- a/packages/state-transition/src/util/electra.ts +++ b/packages/state-transition/src/util/electra.ts @@ -29,13 +29,16 @@ export function isFullyWithdrawableValidator( epoch: number ): boolean { const {withdrawableEpoch, withdrawalCredentials: withdrawalCredential} = validatorCredential; - if (fork >= ForkSeq.electra) { - return hasExecutionWithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; - } else if (fork >= ForkSeq.capella) { - return hasEth1WithdrawalCredential(withdrawalCredential) && withdrawableEpoch <= epoch && balance > 0; - } else { - return false; + + if (fork < ForkSeq.capella) { + throw new Error(`isFullyWithdrawableValidator not supported at forkSeq=${fork} < ForkSeq.capella`); } + const hasWithdrawableCredentials = + fork >= ForkSeq.electra + ? hasExecutionWithdrawalCredential(withdrawalCredential) + : hasEth1WithdrawalCredential(withdrawalCredential); + + return hasWithdrawableCredentials && withdrawableEpoch <= epoch && balance > 0; } export function isPartiallyWithdrawableValidator( @@ -48,15 +51,17 @@ export function isPartiallyWithdrawableValidator( if (fork < ForkSeq.capella) { throw new Error(`isPartiallyWithdrawableValidator not supported at forkSeq=${fork} < ForkSeq.capella`); } + const hasWithdrawableCredentials = + fork >= ForkSeq.electra + ? hasExecutionWithdrawalCredential(withdrawalCredential) + : hasEth1WithdrawalCredential(withdrawalCredential); const validatorMaxEffectiveBalance = - fork === ForkSeq.capella || fork === ForkSeq.deneb - ? MAX_EFFECTIVE_BALANCE - : getValidatorMaxEffectiveBalance(withdrawalCredential); + fork >= ForkSeq.electra ? getValidatorMaxEffectiveBalance(withdrawalCredential) : MAX_EFFECTIVE_BALANCE; const hasMaxEffectiveBalance = effectiveBalance === validatorMaxEffectiveBalance; const hasExcessBalance = balance > validatorMaxEffectiveBalance; - return hasEth1WithdrawalCredential(withdrawalCredential) && hasMaxEffectiveBalance && hasExcessBalance; + return hasWithdrawableCredentials && hasMaxEffectiveBalance && hasExcessBalance; } export function switchToCompoundingValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void { From 5ca66e2d41ed32fbb14b3e0299c94f70925e547d Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 7 May 2024 23:33:32 +0530 Subject: [PATCH 40/41] remove slice --- packages/state-transition/src/block/processWithdrawals.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 14146f2b87a4..e81c973e4558 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -141,7 +141,7 @@ export function getExpectedWithdrawals( withdrawals.push({ index: withdrawalIndex, validatorIndex, - address: validator.withdrawalCredentials.slice(12), + address: validator.withdrawalCredentials.subarray(12), amount: BigInt(balance), }); withdrawalIndex++; @@ -149,7 +149,7 @@ export function getExpectedWithdrawals( withdrawals.push({ index: withdrawalIndex, validatorIndex, - address: validator.withdrawalCredentials.slice(12), + address: validator.withdrawalCredentials.subarray(12), amount: BigInt(balance - getValidatorMaxEffectiveBalance(validator.withdrawalCredentials)), }); withdrawalIndex++; From d5c4c92e491dd93a263e44072ca264b32379d548 Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 7 May 2024 23:40:39 +0530 Subject: [PATCH 41/41] fix the slashing quotient determination in slashvalidator --- packages/state-transition/src/block/slashValidator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/state-transition/src/block/slashValidator.ts b/packages/state-transition/src/block/slashValidator.ts index cf68920b0977..c4b7d5f848ea 100644 --- a/packages/state-transition/src/block/slashValidator.ts +++ b/packages/state-transition/src/block/slashValidator.ts @@ -54,14 +54,14 @@ export function slashValidator( ? MIN_SLASHING_PENALTY_QUOTIENT : fork === ForkSeq.altair ? MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR - : fork === ForkSeq.bellatrix + : fork < ForkSeq.electra // no change from bellatrix to deneb ? MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX : MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA; decreaseBalance(state, slashedIndex, Math.floor(effectiveBalance / minSlashingPenaltyQuotient)); // apply proposer and whistleblower rewards const whistleblowerReward = - fork <= ForkSeq.deneb + fork < ForkSeq.electra ? Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT) : Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA); const proposerReward =