@lodestar/state-transition 1.43.0-dev.bc569affb9 → 1.43.0-dev.c98da75ec7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/block/processConsolidationRequest.d.ts.map +1 -1
- package/lib/block/processConsolidationRequest.js +2 -1
- package/lib/block/processConsolidationRequest.js.map +1 -1
- package/lib/block/processDepositRequest.d.ts +3 -11
- package/lib/block/processDepositRequest.d.ts.map +1 -1
- package/lib/block/processDepositRequest.js +27 -35
- package/lib/block/processDepositRequest.js.map +1 -1
- package/lib/block/processParentExecutionPayload.d.ts +2 -2
- package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
- package/lib/block/processParentExecutionPayload.js +7 -6
- package/lib/block/processParentExecutionPayload.js.map +1 -1
- package/lib/block/processWithdrawals.d.ts.map +1 -1
- package/lib/block/processWithdrawals.js +4 -6
- package/lib/block/processWithdrawals.js.map +1 -1
- package/lib/cache/epochCache.js +3 -3
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/epoch/processPendingDeposits.d.ts.map +1 -1
- package/lib/epoch/processPendingDeposits.js +4 -2
- package/lib/epoch/processPendingDeposits.js.map +1 -1
- package/lib/lightClient/spec/index.d.ts +22 -0
- package/lib/lightClient/spec/index.d.ts.map +1 -0
- package/lib/lightClient/spec/index.js +58 -0
- package/lib/lightClient/spec/index.js.map +1 -0
- package/lib/lightClient/spec/isBetterUpdate.d.ts +23 -0
- package/lib/lightClient/spec/isBetterUpdate.d.ts.map +1 -0
- package/lib/lightClient/spec/isBetterUpdate.js +66 -0
- package/lib/lightClient/spec/isBetterUpdate.js.map +1 -0
- package/lib/lightClient/spec/processLightClientUpdate.d.ts +12 -0
- package/lib/lightClient/spec/processLightClientUpdate.d.ts.map +1 -0
- package/lib/lightClient/spec/processLightClientUpdate.js +80 -0
- package/lib/lightClient/spec/processLightClientUpdate.js.map +1 -0
- package/lib/lightClient/spec/store.d.ts +45 -0
- package/lib/lightClient/spec/store.d.ts.map +1 -0
- package/lib/lightClient/spec/store.js +56 -0
- package/lib/lightClient/spec/store.js.map +1 -0
- package/lib/lightClient/spec/utils.d.ts +47 -0
- package/lib/lightClient/spec/utils.d.ts.map +1 -0
- package/lib/lightClient/spec/utils.js +197 -0
- package/lib/lightClient/spec/utils.js.map +1 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.d.ts +4 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.js +22 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -0
- package/lib/lightClient/spec/validateLightClientUpdate.d.ts +5 -0
- package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -0
- package/lib/lightClient/spec/validateLightClientUpdate.js +88 -0
- package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -0
- package/lib/signatureSets/index.d.ts +1 -0
- package/lib/signatureSets/index.d.ts.map +1 -1
- package/lib/signatureSets/index.js +1 -0
- package/lib/signatureSets/index.js.map +1 -1
- package/lib/signatureSets/proposerPreferences.d.ts +4 -0
- package/lib/signatureSets/proposerPreferences.d.ts.map +1 -0
- package/lib/signatureSets/proposerPreferences.js +8 -0
- package/lib/signatureSets/proposerPreferences.js.map +1 -0
- package/lib/slot/upgradeStateToElectra.d.ts.map +1 -1
- package/lib/slot/upgradeStateToElectra.js +2 -2
- package/lib/slot/upgradeStateToElectra.js.map +1 -1
- package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
- package/lib/slot/upgradeStateToGloas.js +33 -28
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/stateView/beaconStateView.d.ts +14 -5
- package/lib/stateView/beaconStateView.d.ts.map +1 -1
- package/lib/stateView/beaconStateView.js +40 -11
- package/lib/stateView/beaconStateView.js.map +1 -1
- package/lib/stateView/interface.d.ts +7 -5
- package/lib/stateView/interface.d.ts.map +1 -1
- package/lib/stateView/interface.js.map +1 -1
- package/lib/util/epoch.d.ts.map +1 -1
- package/lib/util/epoch.js +6 -4
- package/lib/util/epoch.js.map +1 -1
- package/lib/util/gloas.d.ts +0 -1
- package/lib/util/gloas.d.ts.map +1 -1
- package/lib/util/gloas.js +0 -4
- package/lib/util/gloas.js.map +1 -1
- package/lib/util/index.d.ts +1 -0
- package/lib/util/index.d.ts.map +1 -1
- package/lib/util/index.js +1 -0
- package/lib/util/index.js.map +1 -1
- package/lib/util/loadState/loadState.js +4 -4
- package/lib/util/loadState/loadState.js.map +1 -1
- package/lib/util/pendingDepositsLookup.d.ts +40 -0
- package/lib/util/pendingDepositsLookup.d.ts.map +1 -0
- package/lib/util/pendingDepositsLookup.js +84 -0
- package/lib/util/pendingDepositsLookup.js.map +1 -0
- package/lib/util/validator.d.ts +14 -2
- package/lib/util/validator.d.ts.map +1 -1
- package/lib/util/validator.js +24 -2
- package/lib/util/validator.js.map +1 -1
- package/package.json +13 -8
- package/src/block/processConsolidationRequest.ts +2 -1
- package/src/block/processDepositRequest.ts +29 -47
- package/src/block/processParentExecutionPayload.ts +7 -6
- package/src/block/processWithdrawals.ts +6 -6
- package/src/cache/epochCache.ts +3 -3
- package/src/epoch/processPendingDeposits.ts +5 -2
- package/src/lightClient/spec/index.ts +101 -0
- package/src/lightClient/spec/isBetterUpdate.ts +94 -0
- package/src/lightClient/spec/processLightClientUpdate.ts +119 -0
- package/src/lightClient/spec/store.ts +106 -0
- package/src/lightClient/spec/utils.ts +317 -0
- package/src/lightClient/spec/validateLightClientBootstrap.ts +39 -0
- package/src/lightClient/spec/validateLightClientUpdate.ts +145 -0
- package/src/signatureSets/index.ts +1 -0
- package/src/signatureSets/proposerPreferences.ts +12 -0
- package/src/slot/upgradeStateToElectra.ts +4 -2
- package/src/slot/upgradeStateToGloas.ts +41 -44
- package/src/stateView/beaconStateView.ts +43 -12
- package/src/stateView/interface.ts +7 -5
- package/src/util/epoch.ts +13 -4
- package/src/util/gloas.ts +0 -5
- package/src/util/index.ts +1 -0
- package/src/util/loadState/loadState.ts +4 -4
- package/src/util/pendingDepositsLookup.ts +105 -0
- package/src/util/validator.ts +42 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {PublicKey, Signature, fastAggregateVerify} from "@chainsafe/blst";
|
|
2
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
+
import {
|
|
4
|
+
DOMAIN_SYNC_COMMITTEE,
|
|
5
|
+
FINALIZED_ROOT_DEPTH,
|
|
6
|
+
FINALIZED_ROOT_DEPTH_ELECTRA,
|
|
7
|
+
FINALIZED_ROOT_INDEX,
|
|
8
|
+
FINALIZED_ROOT_INDEX_ELECTRA,
|
|
9
|
+
GENESIS_SLOT,
|
|
10
|
+
MIN_SYNC_COMMITTEE_PARTICIPANTS,
|
|
11
|
+
NEXT_SYNC_COMMITTEE_DEPTH,
|
|
12
|
+
NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA,
|
|
13
|
+
NEXT_SYNC_COMMITTEE_INDEX,
|
|
14
|
+
NEXT_SYNC_COMMITTEE_INDEX_ELECTRA,
|
|
15
|
+
} from "@lodestar/params";
|
|
16
|
+
import {LightClientUpdate, Root, isElectraLightClientUpdate, ssz} from "@lodestar/types";
|
|
17
|
+
import type {ILightClientStore, SyncCommitteeFast} from "./store.js";
|
|
18
|
+
import {
|
|
19
|
+
ZERO_HASH,
|
|
20
|
+
getParticipantPubkeys,
|
|
21
|
+
isFinalityUpdate,
|
|
22
|
+
isSyncCommitteeUpdate,
|
|
23
|
+
isValidLightClientHeader,
|
|
24
|
+
isValidMerkleBranch,
|
|
25
|
+
isZeroedHeader,
|
|
26
|
+
isZeroedSyncCommittee,
|
|
27
|
+
sumBits,
|
|
28
|
+
} from "./utils.js";
|
|
29
|
+
|
|
30
|
+
export function validateLightClientUpdate(
|
|
31
|
+
config: ChainForkConfig,
|
|
32
|
+
store: ILightClientStore,
|
|
33
|
+
update: LightClientUpdate,
|
|
34
|
+
syncCommittee: SyncCommitteeFast
|
|
35
|
+
): void {
|
|
36
|
+
// Verify sync committee has sufficient participants
|
|
37
|
+
if (sumBits(update.syncAggregate.syncCommitteeBits) < MIN_SYNC_COMMITTEE_PARTICIPANTS) {
|
|
38
|
+
throw Error("Sync committee has not sufficient participants");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!isValidLightClientHeader(config, update.attestedHeader)) {
|
|
42
|
+
throw Error("Attested Header is not Valid Light Client Header");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Sanity check that slots are in correct order
|
|
46
|
+
if (update.signatureSlot <= update.attestedHeader.beacon.slot) {
|
|
47
|
+
throw Error(
|
|
48
|
+
`signature slot ${update.signatureSlot} must be after attested header slot ${update.attestedHeader.beacon.slot}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (update.attestedHeader.beacon.slot < update.finalizedHeader.beacon.slot) {
|
|
52
|
+
throw Error(
|
|
53
|
+
`attested header slot ${update.signatureSlot} must be after finalized header slot ${update.finalizedHeader.beacon.slot}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Verify that the `finality_branch`, if present, confirms `finalized_header`
|
|
58
|
+
// to match the finalized checkpoint root saved in the state of `attested_header`.
|
|
59
|
+
// Note that the genesis finalized checkpoint root is represented as a zero hash.
|
|
60
|
+
if (!isFinalityUpdate(update)) {
|
|
61
|
+
if (!isZeroedHeader(update.finalizedHeader.beacon)) {
|
|
62
|
+
throw Error("finalizedHeader must be zero for non-finality update");
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
let finalizedRoot: Root;
|
|
66
|
+
|
|
67
|
+
if (update.finalizedHeader.beacon.slot === GENESIS_SLOT) {
|
|
68
|
+
if (!isZeroedHeader(update.finalizedHeader.beacon)) {
|
|
69
|
+
throw Error("finalizedHeader must be zero for not finality update");
|
|
70
|
+
}
|
|
71
|
+
finalizedRoot = ZERO_HASH;
|
|
72
|
+
} else {
|
|
73
|
+
if (!isValidLightClientHeader(config, update.finalizedHeader)) {
|
|
74
|
+
throw Error("Finalized Header is not valid Light Client Header");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
finalizedRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
!isValidMerkleBranch(
|
|
82
|
+
finalizedRoot,
|
|
83
|
+
update.finalityBranch,
|
|
84
|
+
isElectraLightClientUpdate(update) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH,
|
|
85
|
+
isElectraLightClientUpdate(update) ? FINALIZED_ROOT_INDEX_ELECTRA : FINALIZED_ROOT_INDEX,
|
|
86
|
+
update.attestedHeader.beacon.stateRoot
|
|
87
|
+
)
|
|
88
|
+
) {
|
|
89
|
+
throw Error("Invalid finality header merkle branch");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Verify that the `next_sync_committee`, if present, actually is the next sync committee saved in the
|
|
94
|
+
// state of the `attested_header`
|
|
95
|
+
if (!isSyncCommitteeUpdate(update)) {
|
|
96
|
+
if (!isZeroedSyncCommittee(update.nextSyncCommittee)) {
|
|
97
|
+
throw Error("nextSyncCommittee must be zero for non sync committee update");
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (
|
|
101
|
+
!isValidMerkleBranch(
|
|
102
|
+
ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee),
|
|
103
|
+
update.nextSyncCommitteeBranch,
|
|
104
|
+
isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH,
|
|
105
|
+
isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_INDEX_ELECTRA : NEXT_SYNC_COMMITTEE_INDEX,
|
|
106
|
+
update.attestedHeader.beacon.stateRoot
|
|
107
|
+
)
|
|
108
|
+
) {
|
|
109
|
+
throw Error("Invalid next sync committee merkle branch");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Verify sync committee aggregate signature
|
|
114
|
+
|
|
115
|
+
const participantPubkeys = getParticipantPubkeys(syncCommittee.pubkeys, update.syncAggregate.syncCommitteeBits);
|
|
116
|
+
|
|
117
|
+
const signingRoot = ssz.phase0.SigningData.hashTreeRoot({
|
|
118
|
+
objectRoot: ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.attestedHeader.beacon),
|
|
119
|
+
domain: store.config.getDomain(update.signatureSlot - 1, DOMAIN_SYNC_COMMITTEE),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!isValidBlsAggregate(participantPubkeys, signingRoot, update.syncAggregate.syncCommitteeSignature)) {
|
|
123
|
+
throw Error("Invalid aggregate signature");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Same as BLS.verifyAggregate but with detailed error messages
|
|
129
|
+
*/
|
|
130
|
+
function isValidBlsAggregate(publicKeys: PublicKey[], message: Uint8Array, signature: Uint8Array): boolean {
|
|
131
|
+
let sig: Signature;
|
|
132
|
+
try {
|
|
133
|
+
sig = Signature.fromBytes(signature, true);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
(e as Error).message = `Error deserializing signature: ${(e as Error).message}`;
|
|
136
|
+
throw e;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
return fastAggregateVerify(message, publicKeys, sig);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
(e as Error).message = `Error verifying signature: ${(e as Error).message}`;
|
|
143
|
+
throw e;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -20,6 +20,7 @@ export * from "./executionPayloadEnvelope.js";
|
|
|
20
20
|
export * from "./indexedAttestation.js";
|
|
21
21
|
export * from "./indexedPayloadAttestation.js";
|
|
22
22
|
export * from "./proposer.js";
|
|
23
|
+
export * from "./proposerPreferences.js";
|
|
23
24
|
export * from "./proposerSlashings.js";
|
|
24
25
|
export * from "./randao.js";
|
|
25
26
|
export * from "./voluntaryExits.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
2
|
+
import {DOMAIN_PROPOSER_PREFERENCES} from "@lodestar/params";
|
|
3
|
+
import {gloas, ssz} from "@lodestar/types";
|
|
4
|
+
import {computeSigningRoot} from "../util/index.js";
|
|
5
|
+
|
|
6
|
+
export function getProposerPreferencesSigningRoot(
|
|
7
|
+
config: BeaconConfig,
|
|
8
|
+
preferences: gloas.ProposerPreferences
|
|
9
|
+
): Uint8Array {
|
|
10
|
+
const domain = config.getDomain(preferences.proposalSlot, DOMAIN_PROPOSER_PREFERENCES);
|
|
11
|
+
return computeSigningRoot(ssz.gloas.ProposerPreferences, preferences, domain);
|
|
12
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {FAR_FUTURE_EPOCH, GENESIS_SLOT, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
|
|
1
|
+
import {FAR_FUTURE_EPOCH, ForkSeq, GENESIS_SLOT, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
|
|
2
2
|
import {ValidatorIndex, ssz} from "@lodestar/types";
|
|
3
3
|
import {CachedBeaconStateElectra, getCachedBeaconState} from "../cache/stateCache.js";
|
|
4
4
|
import {G2_POINT_AT_INFINITY} from "../constants/constants.js";
|
|
@@ -78,7 +78,9 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache
|
|
|
78
78
|
stateElectraView.commit();
|
|
79
79
|
const tmpElectraState = getCachedBeaconState(stateElectraView, stateDeneb);
|
|
80
80
|
stateElectraView.exitBalanceToConsume = BigInt(getActivationExitChurnLimit(tmpElectraState.epochCtx));
|
|
81
|
-
stateElectraView.consolidationBalanceToConsume = BigInt(
|
|
81
|
+
stateElectraView.consolidationBalanceToConsume = BigInt(
|
|
82
|
+
getConsolidationChurnLimit(ForkSeq.electra, tmpElectraState.epochCtx)
|
|
83
|
+
);
|
|
82
84
|
|
|
83
85
|
preActivation.sort((i0, i1) => {
|
|
84
86
|
const res = validatorsArr[i0].activationEligibilityEpoch - validatorsArr[i1].activationEligibilityEpoch;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
2
2
|
import {ssz} from "@lodestar/types";
|
|
3
|
-
import {
|
|
4
|
-
import {isValidDepositSignature} from "../block/processDeposit.js";
|
|
3
|
+
import {toPubkeyHex} from "@lodestar/utils";
|
|
5
4
|
import {applyDepositForBuilder} from "../block/processDepositRequest.js";
|
|
6
5
|
import {getCachedBeaconState} from "../cache/stateCache.js";
|
|
7
6
|
import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
|
|
8
7
|
import {initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
|
|
9
8
|
import {isValidatorKnown} from "../util/index.js";
|
|
9
|
+
import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Upgrade a state from Fulu to Gloas.
|
|
@@ -89,63 +89,60 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
|
|
|
89
89
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.2/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits
|
|
90
90
|
*/
|
|
91
91
|
function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void {
|
|
92
|
-
// Track pubkeys of new validators to keep their deposits pending
|
|
93
|
-
const validatorPubkeys = new Set<string>();
|
|
94
|
-
|
|
95
92
|
// Track pubkeys of new builders added when applying deposits
|
|
96
93
|
const builderPubkeys = new Set<string>();
|
|
97
94
|
|
|
98
|
-
const
|
|
95
|
+
const pendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
|
|
96
|
+
const pendingDepositsLookup = PendingDepositsLookup.buildEmpty();
|
|
97
|
+
|
|
99
98
|
for (let i = 0; i < state.pendingDeposits.length; i++) {
|
|
100
99
|
const deposit = state.pendingDeposits.getReadonly(i);
|
|
101
100
|
|
|
102
101
|
const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey);
|
|
103
|
-
const pubkeyHex =
|
|
102
|
+
const pubkeyHex = toPubkeyHex(deposit.pubkey);
|
|
104
103
|
|
|
105
|
-
// Deposits for existing validators stay in pending queue
|
|
106
|
-
if (isValidatorKnown(state, validatorIndex)
|
|
107
|
-
|
|
104
|
+
// Deposits for existing validators stay in the pending queue
|
|
105
|
+
if (isValidatorKnown(state, validatorIndex)) {
|
|
106
|
+
pendingDeposits.push(deposit);
|
|
107
|
+
pendingDepositsLookup.add(deposit, pubkeyHex);
|
|
108
108
|
continue;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
deposit
|
|
121
|
-
deposit
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
111
|
+
// `applyDepositForBuilder` can mutate the state and add a builder to the registry, so
|
|
112
|
+
// the set of builder pubkeys must be recomputed each iteration. `builderPubkeys` stands
|
|
113
|
+
// in for the spec's `[b.pubkey for b in state.builders]`: `state.builders` starts empty
|
|
114
|
+
// at the fork, so every builder is one added in a previous iteration of this loop.
|
|
115
|
+
if (!builderPubkeys.has(pubkeyHex)) {
|
|
116
|
+
// Deposits for non-builders stay in the pending queue. If there is a valid pending
|
|
117
|
+
// deposit for a new validator with this pubkey, keep this deposit in the pending
|
|
118
|
+
// queue to be applied to that validator later.
|
|
119
|
+
if (!isBuilderWithdrawalCredential(deposit.withdrawalCredentials)) {
|
|
120
|
+
pendingDeposits.push(deposit);
|
|
121
|
+
pendingDepositsLookup.add(deposit, pubkeyHex);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (pendingDepositsLookup.hasPendingValidator(state.config, pubkeyHex)) {
|
|
125
|
+
pendingDeposits.push(deposit);
|
|
126
|
+
pendingDepositsLookup.add(deposit, pubkeyHex);
|
|
127
|
+
continue;
|
|
128
128
|
}
|
|
129
|
-
continue;
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
validatorPubkeys.add(pubkeyHex);
|
|
146
|
-
remainingPendingDeposits.push(deposit);
|
|
131
|
+
const buildersLenBefore = state.builders.length;
|
|
132
|
+
// TODO GLOAS: handle 20k 1ETH deposits on time
|
|
133
|
+
// there is a note in the spec https://github.com/ethereum/consensus-specs/pull/5227
|
|
134
|
+
applyDepositForBuilder(
|
|
135
|
+
state,
|
|
136
|
+
deposit.pubkey,
|
|
137
|
+
deposit.withdrawalCredentials,
|
|
138
|
+
deposit.amount,
|
|
139
|
+
deposit.signature,
|
|
140
|
+
deposit.slot
|
|
141
|
+
);
|
|
142
|
+
if (state.builders.length > buildersLenBefore) {
|
|
143
|
+
builderPubkeys.add(pubkeyHex);
|
|
147
144
|
}
|
|
148
145
|
}
|
|
149
146
|
|
|
150
|
-
state.pendingDeposits =
|
|
147
|
+
state.pendingDeposits = pendingDeposits;
|
|
151
148
|
}
|
|
@@ -68,7 +68,7 @@ import {canBuilderCoverBid} from "../util/gloas.js";
|
|
|
68
68
|
import {loadState} from "../util/loadState/loadState.js";
|
|
69
69
|
import {getRandaoMix} from "../util/seed.js";
|
|
70
70
|
import {getLatestWeakSubjectivityCheckpointEpoch} from "../util/weakSubjectivity.js";
|
|
71
|
-
import {IBeaconStateView, IBeaconStateViewLatestFork} from "./interface.js";
|
|
71
|
+
import {IBeaconStateView, IBeaconStateViewGloas, IBeaconStateViewLatestFork, isStatePostGloas} from "./interface.js";
|
|
72
72
|
|
|
73
73
|
export class BeaconStateView implements IBeaconStateViewLatestFork {
|
|
74
74
|
private readonly config: BeaconConfig;
|
|
@@ -406,16 +406,44 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
/**
|
|
409
|
-
* Return the
|
|
410
|
-
* return -1 if validator is not in the PTC committee for the given slot.
|
|
409
|
+
* Return the PTCs for an epoch
|
|
411
410
|
*/
|
|
412
|
-
|
|
411
|
+
getEpochPTCs(epoch: Epoch): Uint32Array[] {
|
|
412
|
+
if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
|
|
413
|
+
throw new Error("PTC committees are not supported before Gloas");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const epochCtx = (this.cachedState as CachedBeaconStateGloas).epochCtx;
|
|
417
|
+
if (epoch === epochCtx.epoch) {
|
|
418
|
+
return epochCtx.payloadTimelinessCommittees;
|
|
419
|
+
}
|
|
420
|
+
if (epoch === epochCtx.nextEpoch) {
|
|
421
|
+
return epochCtx.nextPayloadTimelinessCommittees;
|
|
422
|
+
}
|
|
423
|
+
throw new Error(`PTC committees are not available for epoch=${epoch}`);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Return all positions of the validator in the PTC committee for the given slot.
|
|
427
|
+
*
|
|
428
|
+
* `compute_ptc` samples by effective balance and may place the same validator at multiple
|
|
429
|
+
* positions, so a validator can have more than one index. Returns an empty array if the
|
|
430
|
+
* validator is not in the PTC for the given slot.
|
|
431
|
+
*
|
|
432
|
+
* Spec: gloas/fork-choice.md#new-on_payload_attestation_message
|
|
433
|
+
*/
|
|
434
|
+
getIndicesInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number[] {
|
|
413
435
|
if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
|
|
414
436
|
throw new Error("PTC committees are not supported before Gloas");
|
|
415
437
|
}
|
|
416
438
|
|
|
417
439
|
const ptcCommittee = (this.cachedState as CachedBeaconStateGloas).epochCtx.getPayloadTimelinessCommittee(slot);
|
|
418
|
-
|
|
440
|
+
const indices: number[] = [];
|
|
441
|
+
for (let i = 0; i < ptcCommittee.length; i++) {
|
|
442
|
+
if (ptcCommittee[i] === validatorIndex) {
|
|
443
|
+
indices.push(i);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return indices;
|
|
419
447
|
}
|
|
420
448
|
|
|
421
449
|
// Shuffling and committees
|
|
@@ -786,16 +814,19 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
786
814
|
/**
|
|
787
815
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/validator.md#executionpayload
|
|
788
816
|
*/
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
throw new Error("getExpectedWithdrawalsForFullParent is not available before Gloas");
|
|
817
|
+
withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas {
|
|
818
|
+
if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
|
|
819
|
+
throw new Error("withParentPayloadApplied is not available before Gloas");
|
|
793
820
|
}
|
|
794
|
-
// Make a copy of the state to avoid mutability issues
|
|
795
821
|
const stateCopy = this.cachedState.clone(true) as CachedBeaconStateGloas;
|
|
796
|
-
|
|
822
|
+
|
|
797
823
|
applyParentExecutionPayload(stateCopy, executionRequests);
|
|
798
824
|
|
|
799
|
-
|
|
825
|
+
const stateView = new BeaconStateView(stateCopy);
|
|
826
|
+
if (!isStatePostGloas(stateView)) {
|
|
827
|
+
throw new Error("Expected gloas state after clone");
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return stateView;
|
|
800
831
|
}
|
|
801
832
|
}
|
|
@@ -252,13 +252,15 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
|
|
|
252
252
|
payloadExpectedWithdrawals: capella.Withdrawal[];
|
|
253
253
|
getBuilder(index: BuilderIndex): gloas.Builder;
|
|
254
254
|
canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
|
|
255
|
-
|
|
255
|
+
getEpochPTCs(epoch: Epoch): Uint32Array[];
|
|
256
|
+
getIndicesInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number[];
|
|
256
257
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
258
|
+
* Clone the state and apply parent execution payload effects.
|
|
259
|
+
* Used during block production and prepareNextSlot so that withdrawals and
|
|
260
|
+
* operation selection (e.g. voluntary exits) see the same post-apply state that the block
|
|
261
|
+
* processor will see at import.
|
|
260
262
|
*/
|
|
261
|
-
|
|
263
|
+
withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas;
|
|
262
264
|
}
|
|
263
265
|
|
|
264
266
|
/**
|
package/src/util/epoch.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EPOCHS_PER_SYNC_COMMITTEE_PERIOD,
|
|
3
|
+
ForkSeq,
|
|
4
|
+
GENESIS_EPOCH,
|
|
5
|
+
MAX_SEED_LOOKAHEAD,
|
|
6
|
+
SLOTS_PER_EPOCH,
|
|
7
|
+
} from "@lodestar/params";
|
|
2
8
|
import {BeaconState, Epoch, Gwei, Slot, SyncPeriod} from "@lodestar/types";
|
|
3
9
|
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
|
|
4
|
-
import {getActivationExitChurnLimit, getConsolidationChurnLimit} from "./validator.js";
|
|
10
|
+
import {getActivationExitChurnLimit, getConsolidationChurnLimit, getExitChurnLimit} from "./validator.js";
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Return the epoch number at the given slot.
|
|
@@ -45,8 +51,10 @@ export function computeExitEpochAndUpdateChurn(
|
|
|
45
51
|
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
|
|
46
52
|
exitBalance: Gwei
|
|
47
53
|
): number {
|
|
54
|
+
const fork = state.config.getForkSeq(state.slot);
|
|
48
55
|
let earliestExitEpoch = Math.max(state.earliestExitEpoch, computeActivationExitEpoch(state.epochCtx.epoch));
|
|
49
|
-
const perEpochChurn =
|
|
56
|
+
const perEpochChurn =
|
|
57
|
+
fork >= ForkSeq.gloas ? getExitChurnLimit(state.epochCtx) : getActivationExitChurnLimit(state.epochCtx);
|
|
50
58
|
|
|
51
59
|
// New epoch for exits.
|
|
52
60
|
let exitBalanceToConsume =
|
|
@@ -71,11 +79,12 @@ export function computeConsolidationEpochAndUpdateChurn(
|
|
|
71
79
|
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
|
|
72
80
|
consolidationBalance: Gwei
|
|
73
81
|
): number {
|
|
82
|
+
const fork = state.config.getForkSeq(state.slot);
|
|
74
83
|
let earliestConsolidationEpoch = Math.max(
|
|
75
84
|
state.earliestConsolidationEpoch,
|
|
76
85
|
computeActivationExitEpoch(state.epochCtx.epoch)
|
|
77
86
|
);
|
|
78
|
-
const perEpochConsolidationChurn = getConsolidationChurnLimit(state.epochCtx);
|
|
87
|
+
const perEpochConsolidationChurn = getConsolidationChurnLimit(fork, state.epochCtx);
|
|
79
88
|
|
|
80
89
|
// New epoch for consolidations
|
|
81
90
|
let consolidationBalanceToConsume =
|
package/src/util/gloas.ts
CHANGED
|
@@ -172,11 +172,6 @@ export function isAttestationSameSlotRootCache(rootCache: RootCache, data: Attes
|
|
|
172
172
|
return isMatchingBlockRoot && isCurrentBlockRoot;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
// TODO GLOAS: This function no longer exists in v1.7.0-alpha.5 specs. Remove it when appropriate to do so
|
|
176
|
-
export function isParentBlockFull(state: CachedBeaconStateGloas): boolean {
|
|
177
|
-
return byteArrayEquals(state.latestExecutionPayloadBid.blockHash, state.latestBlockHash);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
175
|
export function initializePtcWindow(state: CachedBeaconStateFulu): Uint32Array[] {
|
|
181
176
|
const ptcWindow: Uint32Array[] = Array.from({length: SLOTS_PER_EPOCH}, () => new Uint32Array(PTC_SIZE));
|
|
182
177
|
const currentEpoch = state.epochCtx.epoch;
|
package/src/util/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./genesis.js";
|
|
|
18
18
|
export * from "./gloas.js";
|
|
19
19
|
export * from "./interop.js";
|
|
20
20
|
export * from "./loadState/index.js";
|
|
21
|
+
export * from "./pendingDepositsLookup.js";
|
|
21
22
|
export * from "./rootCache.js";
|
|
22
23
|
export * from "./seed.js";
|
|
23
24
|
export * from "./shuffling.js";
|
|
@@ -110,8 +110,8 @@ function loadInactivityScores(
|
|
|
110
110
|
seedState: BeaconStateAltair,
|
|
111
111
|
inactivityScoresBytes: Uint8Array
|
|
112
112
|
): void {
|
|
113
|
-
//
|
|
114
|
-
migratedState.inactivityScores = seedState.inactivityScores.clone();
|
|
113
|
+
// true = do not transfer cache
|
|
114
|
+
migratedState.inactivityScores = seedState.inactivityScores.clone(true);
|
|
115
115
|
const oldValidator = migratedState.inactivityScores.length;
|
|
116
116
|
// UintNum64 = 8 bytes
|
|
117
117
|
const newValidator = inactivityScoresBytes.length / 8;
|
|
@@ -187,8 +187,8 @@ function loadValidators(
|
|
|
187
187
|
const newValidatorCount = Math.floor(newValidatorsBytes.length / VALIDATOR_BYTES_SIZE);
|
|
188
188
|
const isMoreValidator = newValidatorCount >= seedValidatorCount;
|
|
189
189
|
const minValidatorCount = Math.min(seedValidatorCount, newValidatorCount);
|
|
190
|
-
//
|
|
191
|
-
migratedState.validators = seedState.validators.clone();
|
|
190
|
+
// true = do not transfer cache
|
|
191
|
+
migratedState.validators = seedState.validators.clone(true);
|
|
192
192
|
// 80% of validators serialization time comes from memory allocation
|
|
193
193
|
// seedStateValidatorsBytes is an optimization at beacon-node side to avoid memory allocation here
|
|
194
194
|
const seedValidatorsBytes = seedStateValidatorsBytes ?? seedState.validators.serialize();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
2
|
+
import {PubkeyHex, electra} from "@lodestar/types";
|
|
3
|
+
import {toPubkeyHex} from "@lodestar/utils";
|
|
4
|
+
import {isValidDepositSignature} from "../block/processDeposit.js";
|
|
5
|
+
import {CachedBeaconStateGloas} from "../types.js";
|
|
6
|
+
|
|
7
|
+
type PendingDepositsValidation = {
|
|
8
|
+
hasValidSignature: boolean;
|
|
9
|
+
validatedCount: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Mutable lookup for the pending-deposit sequence used by builder-routing logic.
|
|
14
|
+
*
|
|
15
|
+
* This is to implement the spec's `is_pending_validator(pending_deposits, pubkey)` lazily:
|
|
16
|
+
* deposits are grouped by pubkey without verifying signatures, and BLS verification is
|
|
17
|
+
* deferred until a builder deposit needs to know whether the same pubkey already has a
|
|
18
|
+
* valid pending validator deposit.
|
|
19
|
+
*
|
|
20
|
+
* Call `add()` whenever a deposit is appended to the represented sequence. A cached `true`
|
|
21
|
+
* result short-circuits all subsequent checks for that pubkey; a cached `false` records
|
|
22
|
+
* how many deposits were already verified, so appending a new deposit only verifies the
|
|
23
|
+
* newly-appended tail rather than re-running BLS on previously-invalid entries.
|
|
24
|
+
*/
|
|
25
|
+
export class PendingDepositsLookup {
|
|
26
|
+
private constructor(
|
|
27
|
+
private readonly depositsByPubkey: Map<PubkeyHex, electra.PendingDeposit[]>,
|
|
28
|
+
private readonly validationCache: Map<PubkeyHex, PendingDepositsValidation>
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
/** Build an empty lookup for a sequence that will be populated incrementally. */
|
|
32
|
+
static buildEmpty(): PendingDepositsLookup {
|
|
33
|
+
return new PendingDepositsLookup(new Map(), new Map());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build a pubkey -> pending-deposits lookup from `state.pendingDeposits`.
|
|
38
|
+
* No BLS work is done here; signature verification happens lazily in `hasPendingValidator`.
|
|
39
|
+
*/
|
|
40
|
+
static build(state: CachedBeaconStateGloas): PendingDepositsLookup {
|
|
41
|
+
const lookup = PendingDepositsLookup.buildEmpty();
|
|
42
|
+
for (const pendingDeposit of state.pendingDeposits.getAllReadonly()) {
|
|
43
|
+
lookup.add(pendingDeposit);
|
|
44
|
+
}
|
|
45
|
+
return lookup;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Append a pending deposit to the represented sequence.
|
|
50
|
+
* Pass `pubkeyHex` if the caller has already computed it.
|
|
51
|
+
*/
|
|
52
|
+
add(pendingDeposit: electra.PendingDeposit, pubkeyHex?: PubkeyHex): void {
|
|
53
|
+
const key = pubkeyHex ?? toPubkeyHex(pendingDeposit.pubkey);
|
|
54
|
+
const existing = this.depositsByPubkey.get(key);
|
|
55
|
+
if (existing) {
|
|
56
|
+
existing.push(pendingDeposit);
|
|
57
|
+
} else {
|
|
58
|
+
this.depositsByPubkey.set(key, [pendingDeposit]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns true if any pending deposit for `pubkeyHex` has a valid BLS deposit signature.
|
|
64
|
+
* Memoizes the result in `validationCache` so repeated checks for the same pubkey
|
|
65
|
+
* within a block only verify deposits that have not already been checked.
|
|
66
|
+
*/
|
|
67
|
+
hasPendingValidator(config: BeaconConfig, pubkeyHex: PubkeyHex): boolean {
|
|
68
|
+
const validation = this.validationCache.get(pubkeyHex);
|
|
69
|
+
if (validation?.hasValidSignature === true) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const deposits = this.depositsByPubkey.get(pubkeyHex);
|
|
74
|
+
if (deposits === undefined) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// hasValidSignature is false or undefined; resume from the last validatedCount so
|
|
79
|
+
// previously-checked invalid deposits are not re-verified.
|
|
80
|
+
const startIndex = validation?.validatedCount ?? 0;
|
|
81
|
+
if (startIndex === deposits.length) {
|
|
82
|
+
// Nothing new to check; the cached false result still holds.
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (let i = startIndex; i < deposits.length; i++) {
|
|
87
|
+
const deposit = deposits[i];
|
|
88
|
+
if (
|
|
89
|
+
isValidDepositSignature(
|
|
90
|
+
config,
|
|
91
|
+
deposit.pubkey,
|
|
92
|
+
deposit.withdrawalCredentials,
|
|
93
|
+
deposit.amount,
|
|
94
|
+
deposit.signature
|
|
95
|
+
)
|
|
96
|
+
) {
|
|
97
|
+
this.validationCache.set(pubkeyHex, {hasValidSignature: true, validatedCount: i + 1});
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.validationCache.set(pubkeyHex, {hasValidSignature: false, validatedCount: deposits.length});
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/util/validator.ts
CHANGED
|
@@ -44,7 +44,12 @@ export function getActiveValidatorIndices(state: BeaconStateAllForks, epoch: Epo
|
|
|
44
44
|
return new Uint32Array(indices);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
// Deneb fork upgrade only
|
|
48
|
+
export function getValidatorActivationChurnLimit(
|
|
49
|
+
config: ChainForkConfig,
|
|
50
|
+
fork: ForkSeq,
|
|
51
|
+
activeValidatorCount: number
|
|
52
|
+
): number {
|
|
48
53
|
if (fork >= ForkSeq.deneb) {
|
|
49
54
|
return Math.min(config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT, getChurnLimit(config, activeValidatorCount));
|
|
50
55
|
}
|
|
@@ -84,7 +89,42 @@ export function getActivationExitChurnLimit(epochCtx: EpochCache): number {
|
|
|
84
89
|
return Math.min(epochCtx.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, getBalanceChurnLimitFromCache(epochCtx));
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
|
|
92
|
+
/**
|
|
93
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-get_activation_churn_limit
|
|
94
|
+
*/
|
|
95
|
+
export function getActivationChurnLimit(epochCtx: EpochCache): number {
|
|
96
|
+
const churn = getBalanceChurnLimit(
|
|
97
|
+
epochCtx.totalActiveBalanceIncrements,
|
|
98
|
+
epochCtx.config.CHURN_LIMIT_QUOTIENT_GLOAS,
|
|
99
|
+
epochCtx.config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA
|
|
100
|
+
);
|
|
101
|
+
return Math.min(epochCtx.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS, churn);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-get_exit_churn_limit
|
|
106
|
+
*/
|
|
107
|
+
export function getExitChurnLimit(epochCtx: EpochCache): number {
|
|
108
|
+
return getBalanceChurnLimit(
|
|
109
|
+
epochCtx.totalActiveBalanceIncrements,
|
|
110
|
+
epochCtx.config.CHURN_LIMIT_QUOTIENT_GLOAS,
|
|
111
|
+
epochCtx.config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Spec (electra): get_consolidation_churn_limit (uses combined balance churn minus activation+exit churn)
|
|
117
|
+
* Spec (gloas): get_consolidation_churn_limit (independent quotient, no MIN floor)
|
|
118
|
+
*/
|
|
119
|
+
export function getConsolidationChurnLimit(fork: ForkSeq, epochCtx: EpochCache): number {
|
|
120
|
+
if (fork >= ForkSeq.gloas) {
|
|
121
|
+
// No MIN floor — pass 0 so getBalanceChurnLimit's max(churn, min) is a no-op.
|
|
122
|
+
return getBalanceChurnLimit(
|
|
123
|
+
epochCtx.totalActiveBalanceIncrements,
|
|
124
|
+
epochCtx.config.CONSOLIDATION_CHURN_LIMIT_QUOTIENT,
|
|
125
|
+
0
|
|
126
|
+
);
|
|
127
|
+
}
|
|
88
128
|
return getBalanceChurnLimitFromCache(epochCtx) - getActivationExitChurnLimit(epochCtx);
|
|
89
129
|
}
|
|
90
130
|
|