Skip to content

Commit d92238f

Browse files
authored
feat: update weak subjectivity calculation for electra (#7735)
Related spec ~~ethereum/consensus-specs#4243~~ ethereum/consensus-specs#4179 Closes #7645
1 parent 7ea98fd commit d92238f

File tree

3 files changed

+115
-27
lines changed

3 files changed

+115
-27
lines changed

packages/state-transition/src/util/validator.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,34 @@ export function getChurnLimit(config: ChainForkConfig, activeValidatorCount: num
5656
/**
5757
* Get combined churn limit of activation-exit and consolidation
5858
*/
59-
export function getBalanceChurnLimit(epochCtx: EpochCache): number {
59+
export function getBalanceChurnLimit(
60+
totalActiveBalanceIncrements: number,
61+
churnLimitQuotient: number,
62+
minPerEpochChurnLimit: number
63+
): number {
6064
const churnLimitByTotalActiveBalance = Math.floor(
61-
(epochCtx.totalActiveBalanceIncrements / epochCtx.config.CHURN_LIMIT_QUOTIENT) * EFFECTIVE_BALANCE_INCREMENT
62-
); // TODO Electra: verify calculation
65+
(totalActiveBalanceIncrements / churnLimitQuotient) * EFFECTIVE_BALANCE_INCREMENT
66+
);
6367

64-
const churn = Math.max(churnLimitByTotalActiveBalance, epochCtx.config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA);
68+
const churn = Math.max(churnLimitByTotalActiveBalance, minPerEpochChurnLimit);
6569

6670
return churn - (churn % EFFECTIVE_BALANCE_INCREMENT);
6771
}
6872

73+
export function getBalanceChurnLimitFromCache(epochCtx: EpochCache): number {
74+
return getBalanceChurnLimit(
75+
epochCtx.totalActiveBalanceIncrements,
76+
epochCtx.config.CHURN_LIMIT_QUOTIENT,
77+
epochCtx.config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA
78+
);
79+
}
80+
6981
export function getActivationExitChurnLimit(epochCtx: EpochCache): number {
70-
return Math.min(epochCtx.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, getBalanceChurnLimit(epochCtx));
82+
return Math.min(epochCtx.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, getBalanceChurnLimitFromCache(epochCtx));
7183
}
7284

7385
export function getConsolidationChurnLimit(epochCtx: EpochCache): number {
74-
return getBalanceChurnLimit(epochCtx) - getActivationExitChurnLimit(epochCtx);
86+
return getBalanceChurnLimitFromCache(epochCtx) - getActivationExitChurnLimit(epochCtx);
7587
}
7688

7789
export function getMaxEffectiveBalance(withdrawalCredentials: Uint8Array): number {

packages/state-transition/src/util/weakSubjectivity.ts

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
2-
import {EFFECTIVE_BALANCE_INCREMENT, MAX_DEPOSITS, MAX_EFFECTIVE_BALANCE, SLOTS_PER_EPOCH} from "@lodestar/params";
2+
import {
3+
EFFECTIVE_BALANCE_INCREMENT,
4+
MAX_DEPOSITS,
5+
MAX_EFFECTIVE_BALANCE,
6+
SLOTS_PER_EPOCH,
7+
isForkPostElectra,
8+
} from "@lodestar/params";
39
import {Epoch, Root} from "@lodestar/types";
410
import {ssz} from "@lodestar/types";
511
import {Checkpoint} from "@lodestar/types/phase0";
@@ -8,7 +14,12 @@ import {ZERO_HASH} from "../constants/constants.js";
814
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
915
import {computeCheckpointEpochAtStateSlot, computeEpochAtSlot, getCurrentEpoch} from "./epoch.js";
1016
import {getCurrentSlot} from "./slot.js";
11-
import {getActiveValidatorIndices, getChurnLimit} from "./validator.js";
17+
import {
18+
getActiveValidatorIndices,
19+
getBalanceChurnLimit,
20+
getBalanceChurnLimitFromCache,
21+
getChurnLimit,
22+
} from "./validator.js";
1223

1324
export const ETH_TO_GWEI = 10 ** 9;
1425
const SAFETY_DECAY = 10;
@@ -37,12 +48,20 @@ export function computeWeakSubjectivityPeriodCachedState(
3748
state: CachedBeaconStateAllForks
3849
): number {
3950
const activeValidatorCount = state.epochCtx.currentShuffling.activeIndices.length;
40-
return computeWeakSubjectivityPeriodFromConstituents(
41-
activeValidatorCount,
42-
state.epochCtx.totalActiveBalanceIncrements,
43-
getChurnLimit(config, activeValidatorCount),
44-
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
45-
);
51+
const fork = state.config.getForkName(state.slot);
52+
53+
return isForkPostElectra(fork)
54+
? computeWeakSubjectivityPeriodFromConstituentsElectra(
55+
state.epochCtx.totalActiveBalanceIncrements,
56+
getBalanceChurnLimitFromCache(state.epochCtx),
57+
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
58+
)
59+
: computeWeakSubjectivityPeriodFromConstituentsPhase0(
60+
activeValidatorCount,
61+
state.epochCtx.totalActiveBalanceIncrements,
62+
getChurnLimit(config, activeValidatorCount),
63+
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
64+
);
4665
}
4766

4867
/**
@@ -52,22 +71,35 @@ export function computeWeakSubjectivityPeriodCachedState(
5271
export function computeWeakSubjectivityPeriod(config: ChainForkConfig, state: BeaconStateAllForks): number {
5372
const activeIndices = getActiveValidatorIndices(state, getCurrentEpoch(state));
5473
const validators = state.validators.getAllReadonlyValues();
74+
const fork = config.getForkName(state.slot);
75+
5576
let totalActiveBalanceIncrements = 0;
5677
for (const index of activeIndices) {
5778
totalActiveBalanceIncrements += Math.floor(validators[index].effectiveBalance / EFFECTIVE_BALANCE_INCREMENT);
5879
}
5980
if (totalActiveBalanceIncrements <= 1) {
6081
totalActiveBalanceIncrements = 1;
6182
}
62-
return computeWeakSubjectivityPeriodFromConstituents(
63-
activeIndices.length,
64-
totalActiveBalanceIncrements,
65-
getChurnLimit(config, activeIndices.length),
66-
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
67-
);
83+
84+
return isForkPostElectra(fork)
85+
? computeWeakSubjectivityPeriodFromConstituentsElectra(
86+
totalActiveBalanceIncrements,
87+
getBalanceChurnLimit(
88+
totalActiveBalanceIncrements,
89+
config.CHURN_LIMIT_QUOTIENT,
90+
config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA
91+
),
92+
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
93+
)
94+
: computeWeakSubjectivityPeriodFromConstituentsPhase0(
95+
activeIndices.length,
96+
totalActiveBalanceIncrements,
97+
getChurnLimit(config, activeIndices.length),
98+
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
99+
);
68100
}
69101

70-
export function computeWeakSubjectivityPeriodFromConstituents(
102+
export function computeWeakSubjectivityPeriodFromConstituentsPhase0(
71103
activeValidatorCount: number,
72104
totalBalanceByIncrement: number,
73105
churnLimit: number,
@@ -97,6 +129,20 @@ export function computeWeakSubjectivityPeriodFromConstituents(
97129
return wsPeriod;
98130
}
99131

132+
export function computeWeakSubjectivityPeriodFromConstituentsElectra(
133+
totalBalanceByIncrement: number,
134+
// Note this is not the same as churnLimit in `computeWeakSubjectivityPeriodFromConstituentsPhase0`
135+
balanceChurnLimit: number,
136+
minWithdrawabilityDelay: number
137+
): number {
138+
// Keep t as increment for now. Multiply final result by EFFECTIVE_BALANCE_INCREMENT
139+
const t = totalBalanceByIncrement;
140+
const delta = balanceChurnLimit;
141+
const epochsForValidatorSetChurn = Math.floor(((SAFETY_DECAY * t) / (2 * delta * 100)) * EFFECTIVE_BALANCE_INCREMENT);
142+
143+
return minWithdrawabilityDelay + epochsForValidatorSetChurn;
144+
}
145+
100146
export function getLatestBlockRoot(state: BeaconStateAllForks): Root {
101147
const header = ssz.phase0.BeaconBlockHeader.clone(state.latestBlockHeader);
102148
if (ssz.Root.equals(header.stateRoot, ZERO_HASH)) {

packages/state-transition/test/unit/util/weakSubjectivity.test.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import {config} from "@lodestar/config/default";
22
import {describe, expect, it} from "vitest";
3-
import {getChurnLimit} from "../../../src/util/validator.js";
4-
import {computeWeakSubjectivityPeriodFromConstituents} from "../../../src/util/weakSubjectivity.js";
3+
import {getBalanceChurnLimit, getChurnLimit} from "../../../src/util/validator.js";
4+
import {
5+
computeWeakSubjectivityPeriodFromConstituentsElectra,
6+
computeWeakSubjectivityPeriodFromConstituentsPhase0,
7+
} from "../../../src/util/weakSubjectivity.js";
58

69
describe("weak subjectivity tests", () => {
7-
describe("computeWeakSubjectivityPeriodFromConstituents", () => {
10+
describe("computeWeakSubjectivityPeriodFromConstituentsPhase0", () => {
811
const balance28 = 28;
912
const balance32 = 32;
1013

@@ -25,14 +28,41 @@ describe("weak subjectivity tests", () => {
2528

2629
it.each(testValues)(
2730
"should have wsPeriod: $wsPeriod with avgValBalance: $avgValBalance and valCount: $valCount",
28-
({valCount, avgValBalance}) => {
29-
const wsPeriod = computeWeakSubjectivityPeriodFromConstituents(
31+
({valCount, avgValBalance, wsPeriod: expectedWsPeriod}) => {
32+
const wsPeriod = computeWeakSubjectivityPeriodFromConstituentsPhase0(
3033
valCount,
3134
avgValBalance * valCount,
3235
getChurnLimit(config, valCount),
3336
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
3437
);
35-
expect(wsPeriod).toBe(wsPeriod);
38+
expect(wsPeriod).toBe(expectedWsPeriod);
39+
}
40+
);
41+
});
42+
describe("computeWeakSubjectivityPeriodFromConstituentsElectra", () => {
43+
// Values from https://github.com/ethereum/consensus-specs/blob/8ebb5e80862641287d7e8db2bbf69fa31612640b/specs/electra/weak-subjectivity.md#weak-subjectivity-period
44+
const testValues = [
45+
{totalBalanceIncrement: 1_048_576, wsPeriod: 665},
46+
{totalBalanceIncrement: 2_097_152, wsPeriod: 1075},
47+
{totalBalanceIncrement: 4_194_304, wsPeriod: 1894},
48+
{totalBalanceIncrement: 8_388_608, wsPeriod: 3532},
49+
{totalBalanceIncrement: 16_777_216, wsPeriod: 3532},
50+
{totalBalanceIncrement: 33_554_432, wsPeriod: 3532},
51+
];
52+
53+
it.each(testValues)(
54+
"should have wsPeriod: $wsPeriod with totalActiveBalance: $totalBalanceIncrement",
55+
({totalBalanceIncrement, wsPeriod: expectedWsPeriod}) => {
56+
const wsPeriod = computeWeakSubjectivityPeriodFromConstituentsElectra(
57+
totalBalanceIncrement,
58+
getBalanceChurnLimit(
59+
totalBalanceIncrement,
60+
config.CHURN_LIMIT_QUOTIENT,
61+
config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA
62+
),
63+
config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
64+
);
65+
expect(wsPeriod).toBe(expectedWsPeriod);
3666
}
3767
);
3868
});

0 commit comments

Comments
 (0)