@lodestar/state-transition 1.35.0-dev.e18102ed8c → 1.35.0-dev.f45a2be721
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/externalData.d.ts.map +1 -0
- package/lib/block/index.d.ts.map +1 -0
- package/lib/block/initiateValidatorExit.d.ts.map +1 -0
- package/lib/block/isValidIndexedAttestation.d.ts.map +1 -0
- package/lib/block/processAttestationPhase0.d.ts.map +1 -0
- package/lib/block/processAttestations.d.ts.map +1 -0
- package/lib/block/processAttestationsAltair.d.ts.map +1 -0
- package/lib/block/processAttesterSlashing.d.ts.map +1 -0
- package/lib/block/processBlobKzgCommitments.d.ts.map +1 -0
- package/lib/block/processBlockHeader.d.ts.map +1 -0
- package/lib/block/processBlsToExecutionChange.d.ts.map +1 -0
- package/lib/block/processConsolidationRequest.d.ts.map +1 -0
- package/lib/block/processDeposit.d.ts.map +1 -0
- package/lib/block/processDepositRequest.d.ts.map +1 -0
- package/lib/block/processEth1Data.d.ts.map +1 -0
- package/lib/block/processExecutionPayload.d.ts.map +1 -0
- package/lib/block/processExecutionPayload.js +3 -3
- package/lib/block/processExecutionPayload.js.map +1 -1
- package/lib/block/processOperations.d.ts.map +1 -0
- package/lib/block/processProposerSlashing.d.ts.map +1 -0
- package/lib/block/processRandao.d.ts.map +1 -0
- package/lib/block/processSyncCommittee.d.ts.map +1 -0
- package/lib/block/processVoluntaryExit.d.ts.map +1 -0
- package/lib/block/processWithdrawalRequest.d.ts.map +1 -0
- package/lib/block/processWithdrawals.d.ts.map +1 -0
- package/lib/block/slashValidator.d.ts.map +1 -0
- package/lib/block/types.d.ts.map +1 -0
- package/lib/cache/effectiveBalanceIncrements.d.ts.map +1 -0
- package/lib/cache/epochCache.d.ts.map +1 -0
- package/lib/cache/epochTransitionCache.d.ts.map +1 -0
- package/lib/cache/pubkeyCache.d.ts.map +1 -0
- package/lib/cache/rewardCache.d.ts.map +1 -0
- package/lib/cache/stateCache.d.ts.map +1 -0
- package/lib/cache/syncCommitteeCache.d.ts.map +1 -0
- package/lib/cache/types.d.ts +2 -2
- package/lib/cache/types.d.ts.map +1 -0
- package/lib/constants/constants.d.ts.map +1 -0
- package/lib/constants/index.d.ts.map +1 -0
- package/lib/epoch/computeUnrealizedCheckpoints.d.ts.map +1 -0
- package/lib/epoch/getAttestationDeltas.d.ts.map +1 -0
- package/lib/epoch/getRewardsAndPenalties.d.ts.map +1 -0
- package/lib/epoch/index.d.ts.map +1 -0
- package/lib/epoch/processEffectiveBalanceUpdates.d.ts.map +1 -0
- package/lib/epoch/processEth1DataReset.d.ts.map +1 -0
- package/lib/epoch/processHistoricalRootsUpdate.d.ts.map +1 -0
- package/lib/epoch/processHistoricalSummariesUpdate.d.ts.map +1 -0
- package/lib/epoch/processInactivityUpdates.d.ts.map +1 -0
- package/lib/epoch/processJustificationAndFinalization.d.ts.map +1 -0
- package/lib/epoch/processParticipationFlagUpdates.d.ts.map +1 -0
- package/lib/epoch/processParticipationRecordUpdates.d.ts.map +1 -0
- package/lib/epoch/processPendingAttestations.d.ts.map +1 -0
- package/lib/epoch/processPendingConsolidations.d.ts.map +1 -0
- package/lib/epoch/processPendingDeposits.d.ts.map +1 -0
- package/lib/epoch/processProposerLookahead.d.ts.map +1 -0
- package/lib/epoch/processRandaoMixesReset.d.ts.map +1 -0
- package/lib/epoch/processRegistryUpdates.d.ts.map +1 -0
- package/lib/epoch/processRewardsAndPenalties.d.ts.map +1 -0
- package/lib/epoch/processSlashings.d.ts.map +1 -0
- package/lib/epoch/processSlashingsReset.d.ts.map +1 -0
- package/lib/epoch/processSyncCommitteeUpdates.d.ts.map +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/metrics.d.ts.map +1 -0
- package/lib/signatureSets/attesterSlashings.d.ts.map +1 -0
- package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -0
- package/lib/signatureSets/index.d.ts.map +1 -0
- package/lib/signatureSets/indexedAttestation.d.ts.map +1 -0
- package/lib/signatureSets/proposer.d.ts.map +1 -0
- package/lib/signatureSets/proposerSlashings.d.ts.map +1 -0
- package/lib/signatureSets/randao.d.ts.map +1 -0
- package/lib/signatureSets/voluntaryExits.d.ts.map +1 -0
- package/lib/slot/index.d.ts.map +1 -0
- package/lib/slot/upgradeStateToAltair.d.ts.map +1 -0
- package/lib/slot/upgradeStateToBellatrix.d.ts.map +1 -0
- package/lib/slot/upgradeStateToCapella.d.ts.map +1 -0
- package/lib/slot/upgradeStateToDeneb.d.ts.map +1 -0
- package/lib/slot/upgradeStateToElectra.d.ts.map +1 -0
- package/lib/slot/upgradeStateToFulu.d.ts.map +1 -0
- package/lib/slot/upgradeStateToGloas.d.ts.map +1 -0
- package/lib/stateTransition.d.ts.map +1 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/util/aggregator.d.ts.map +1 -0
- package/lib/util/altair.d.ts.map +1 -0
- package/lib/util/array.d.ts.map +1 -0
- package/lib/util/attestation.d.ts.map +1 -0
- package/lib/util/attesterStatus.d.ts.map +1 -0
- package/lib/util/balance.d.ts.map +1 -0
- package/lib/util/blindedBlock.d.ts +3 -3
- package/lib/util/blindedBlock.d.ts.map +1 -0
- package/lib/util/blindedBlock.js.map +1 -1
- package/lib/util/blockRoot.d.ts.map +1 -0
- package/lib/util/calculateCommitteeAssignments.d.ts.map +1 -0
- package/lib/util/capella.d.ts.map +1 -0
- package/lib/util/computeAnchorCheckpoint.d.ts.map +1 -0
- package/lib/util/deposit.d.ts.map +1 -0
- package/lib/util/domain.d.ts.map +1 -0
- package/lib/util/electra.d.ts.map +1 -0
- package/lib/util/epoch.d.ts.map +1 -0
- package/lib/util/epochShuffling.d.ts.map +1 -0
- package/lib/util/execution.d.ts +2 -2
- package/lib/util/execution.d.ts.map +1 -0
- package/lib/util/execution.js.map +1 -1
- package/lib/util/finality.d.ts.map +1 -0
- package/lib/util/fulu.d.ts.map +1 -0
- package/lib/util/genesis.d.ts.map +1 -0
- package/lib/util/genesis.js +0 -3
- package/lib/util/genesis.js.map +1 -1
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/interop.d.ts.map +1 -0
- package/lib/util/loadState/findModifiedInactivityScores.d.ts.map +1 -0
- package/lib/util/loadState/findModifiedValidators.d.ts.map +1 -0
- package/lib/util/loadState/index.d.ts.map +1 -0
- package/lib/util/loadState/loadState.d.ts.map +1 -0
- package/lib/util/loadState/loadValidator.d.ts.map +1 -0
- package/lib/util/rootCache.d.ts.map +1 -0
- package/lib/util/seed.d.ts.map +1 -0
- package/lib/util/shufflingDecisionRoot.d.ts.map +1 -0
- package/lib/util/signatureSets.d.ts.map +1 -0
- package/lib/util/signingRoot.d.ts.map +1 -0
- package/lib/util/slot.d.ts +0 -1
- package/lib/util/slot.d.ts.map +1 -0
- package/lib/util/slot.js +3 -7
- package/lib/util/slot.js.map +1 -1
- package/lib/util/sszBytes.d.ts.map +1 -0
- package/lib/util/syncCommittee.d.ts.map +1 -0
- package/lib/util/targetUnslashedBalance.d.ts.map +1 -0
- package/lib/util/validator.d.ts.map +1 -0
- package/lib/util/weakSubjectivity.d.ts.map +1 -0
- package/package.json +13 -11
- package/src/block/externalData.ts +26 -0
- package/src/block/index.ts +81 -0
- package/src/block/initiateValidatorExit.ts +62 -0
- package/src/block/isValidIndexedAttestation.ts +70 -0
- package/src/block/processAttestationPhase0.ts +158 -0
- package/src/block/processAttestations.ts +25 -0
- package/src/block/processAttestationsAltair.ts +184 -0
- package/src/block/processAttesterSlashing.ts +59 -0
- package/src/block/processBlobKzgCommitments.ts +21 -0
- package/src/block/processBlockHeader.ts +54 -0
- package/src/block/processBlsToExecutionChange.ts +78 -0
- package/src/block/processConsolidationRequest.ts +147 -0
- package/src/block/processDeposit.ts +166 -0
- package/src/block/processDepositRequest.ts +19 -0
- package/src/block/processEth1Data.ts +86 -0
- package/src/block/processExecutionPayload.ts +84 -0
- package/src/block/processOperations.ts +83 -0
- package/src/block/processProposerSlashing.ts +66 -0
- package/src/block/processRandao.ts +27 -0
- package/src/block/processSyncCommittee.ts +117 -0
- package/src/block/processVoluntaryExit.ts +55 -0
- package/src/block/processWithdrawalRequest.ts +98 -0
- package/src/block/processWithdrawals.ts +207 -0
- package/src/block/slashValidator.ts +98 -0
- package/src/block/types.ts +9 -0
- package/src/cache/effectiveBalanceIncrements.ts +39 -0
- package/src/cache/epochCache.ts +1213 -0
- package/src/cache/epochTransitionCache.ts +542 -0
- package/src/cache/pubkeyCache.ts +33 -0
- package/src/cache/rewardCache.ts +19 -0
- package/src/cache/stateCache.ts +268 -0
- package/src/cache/syncCommitteeCache.ts +96 -0
- package/src/cache/types.ts +18 -0
- package/src/constants/constants.ts +12 -0
- package/src/constants/index.ts +1 -0
- package/src/epoch/computeUnrealizedCheckpoints.ts +55 -0
- package/src/epoch/getAttestationDeltas.ts +169 -0
- package/src/epoch/getRewardsAndPenalties.ts +137 -0
- package/src/epoch/index.ts +202 -0
- package/src/epoch/processEffectiveBalanceUpdates.ts +111 -0
- package/src/epoch/processEth1DataReset.ts +17 -0
- package/src/epoch/processHistoricalRootsUpdate.ts +25 -0
- package/src/epoch/processHistoricalSummariesUpdate.ts +23 -0
- package/src/epoch/processInactivityUpdates.ts +60 -0
- package/src/epoch/processJustificationAndFinalization.ts +90 -0
- package/src/epoch/processParticipationFlagUpdates.ts +27 -0
- package/src/epoch/processParticipationRecordUpdates.ts +14 -0
- package/src/epoch/processPendingAttestations.ts +75 -0
- package/src/epoch/processPendingConsolidations.ts +59 -0
- package/src/epoch/processPendingDeposits.ts +136 -0
- package/src/epoch/processProposerLookahead.ts +39 -0
- package/src/epoch/processRandaoMixesReset.ts +18 -0
- package/src/epoch/processRegistryUpdates.ts +65 -0
- package/src/epoch/processRewardsAndPenalties.ts +58 -0
- package/src/epoch/processSlashings.ts +97 -0
- package/src/epoch/processSlashingsReset.ts +20 -0
- package/src/epoch/processSyncCommitteeUpdates.ts +44 -0
- package/src/index.ts +67 -0
- package/src/metrics.ts +169 -0
- package/src/signatureSets/attesterSlashings.ts +39 -0
- package/src/signatureSets/blsToExecutionChange.ts +43 -0
- package/src/signatureSets/index.ts +73 -0
- package/src/signatureSets/indexedAttestation.ts +51 -0
- package/src/signatureSets/proposer.ts +47 -0
- package/src/signatureSets/proposerSlashings.ts +41 -0
- package/src/signatureSets/randao.ts +31 -0
- package/src/signatureSets/voluntaryExits.ts +44 -0
- package/src/slot/index.ts +32 -0
- package/src/slot/upgradeStateToAltair.ts +149 -0
- package/src/slot/upgradeStateToBellatrix.ts +63 -0
- package/src/slot/upgradeStateToCapella.ts +71 -0
- package/src/slot/upgradeStateToDeneb.ts +40 -0
- package/src/slot/upgradeStateToElectra.ts +126 -0
- package/src/slot/upgradeStateToFulu.ts +31 -0
- package/src/slot/upgradeStateToGloas.ts +29 -0
- package/src/stateTransition.ts +305 -0
- package/src/types.ts +26 -0
- package/src/util/aggregator.ts +33 -0
- package/src/util/altair.ts +13 -0
- package/src/util/array.ts +53 -0
- package/src/util/attestation.ts +36 -0
- package/src/util/attesterStatus.ts +83 -0
- package/src/util/balance.ts +83 -0
- package/src/util/blindedBlock.ts +145 -0
- package/src/util/blockRoot.ts +72 -0
- package/src/util/calculateCommitteeAssignments.ts +43 -0
- package/src/util/capella.ts +8 -0
- package/src/util/computeAnchorCheckpoint.ts +38 -0
- package/src/util/deposit.ts +22 -0
- package/src/util/domain.ts +31 -0
- package/src/util/electra.ts +68 -0
- package/src/util/epoch.ts +135 -0
- package/src/util/epochShuffling.ts +185 -0
- package/src/util/execution.ts +179 -0
- package/src/util/finality.ts +17 -0
- package/src/util/fulu.ts +43 -0
- package/src/util/genesis.ts +340 -0
- package/src/util/index.ts +29 -0
- package/src/util/interop.ts +22 -0
- package/src/util/loadState/findModifiedInactivityScores.ts +47 -0
- package/src/util/loadState/findModifiedValidators.ts +46 -0
- package/src/util/loadState/index.ts +2 -0
- package/src/util/loadState/loadState.ts +225 -0
- package/src/util/loadState/loadValidator.ts +77 -0
- package/src/util/rootCache.ts +37 -0
- package/src/util/seed.ts +381 -0
- package/src/util/shufflingDecisionRoot.ts +78 -0
- package/src/util/signatureSets.ts +65 -0
- package/src/util/signingRoot.ts +13 -0
- package/src/util/slot.ts +22 -0
- package/src/util/sszBytes.ts +52 -0
- package/src/util/syncCommittee.ts +69 -0
- package/src/util/targetUnslashedBalance.ts +30 -0
- package/src/util/validator.ts +105 -0
- package/src/util/weakSubjectivity.ts +186 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {zeroNode} from "@chainsafe/persistent-merkle-tree";
|
|
2
|
+
import {ssz} from "@lodestar/types";
|
|
3
|
+
import {CachedBeaconStateAltair} from "../types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Updates `state.previousEpochParticipation` with precalculated epoch participation. Creates a new empty tree for
|
|
7
|
+
* `state.currentEpochParticipation`.
|
|
8
|
+
*
|
|
9
|
+
* PERF: Cost = 'proportional' $VALIDATOR_COUNT. Since it updates all of them at once, it will always recreate both
|
|
10
|
+
* trees completely.
|
|
11
|
+
*/
|
|
12
|
+
export function processParticipationFlagUpdates(state: CachedBeaconStateAltair): void {
|
|
13
|
+
// Set view and tree from currentEpochParticipation to previousEpochParticipation
|
|
14
|
+
state.previousEpochParticipation = state.currentEpochParticipation;
|
|
15
|
+
|
|
16
|
+
// We need to replace the node of currentEpochParticipation with a node that represents and empty list of some length.
|
|
17
|
+
// SSZ represents a list as = new BranchNode(chunksNode, lengthNode).
|
|
18
|
+
// Since the chunks represent all zero'ed data we can re-use the pre-compouted zeroNode at chunkDepth to skip any
|
|
19
|
+
// data transformation and create the required tree almost for free.
|
|
20
|
+
const currentEpochParticipationNode = ssz.altair.EpochParticipation.tree_setChunksNode(
|
|
21
|
+
state.currentEpochParticipation.node,
|
|
22
|
+
zeroNode(ssz.altair.EpochParticipation.chunkDepth),
|
|
23
|
+
state.currentEpochParticipation.length
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
state.currentEpochParticipation = ssz.altair.EpochParticipation.getViewDU(currentEpochParticipationNode);
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {ssz} from "@lodestar/types";
|
|
2
|
+
import {CachedBeaconStatePhase0} from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PERF: Should have zero cost. It just moves a rootNode from one key to another. Then it creates an empty tree on the
|
|
6
|
+
* previous key
|
|
7
|
+
*/
|
|
8
|
+
export function processParticipationRecordUpdates(state: CachedBeaconStatePhase0): void {
|
|
9
|
+
// rotate current/previous epoch attestations
|
|
10
|
+
state.previousEpochAttestations = state.currentEpochAttestations;
|
|
11
|
+
|
|
12
|
+
// Reset list to empty
|
|
13
|
+
state.currentEpochAttestations = ssz.phase0.EpochAttestations.defaultViewDU();
|
|
14
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
2
|
+
import {Epoch, phase0} from "@lodestar/types";
|
|
3
|
+
import {CachedBeaconStatePhase0} from "../types.js";
|
|
4
|
+
import {computeStartSlotAtEpoch, getBlockRootAtSlot} from "../util/index.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mutates `proposerIndices`, `inclusionDelays` and `flags` from all pending attestations.
|
|
8
|
+
*
|
|
9
|
+
* PERF: Cost 'proportional' to attestation count + how many bits per attestation + how many flags the attestation triggers
|
|
10
|
+
*
|
|
11
|
+
* - On normal mainnet conditions:
|
|
12
|
+
* - previousEpochAttestations: 3403
|
|
13
|
+
* - currentEpochAttestations: 3129
|
|
14
|
+
* - previousEpochAttestationsBits: 83
|
|
15
|
+
* - currentEpochAttestationsBits: 85
|
|
16
|
+
*/
|
|
17
|
+
export function processPendingAttestations(
|
|
18
|
+
state: CachedBeaconStatePhase0,
|
|
19
|
+
proposerIndices: number[],
|
|
20
|
+
inclusionDelays: number[],
|
|
21
|
+
flags: number[],
|
|
22
|
+
attestations: phase0.PendingAttestation[],
|
|
23
|
+
epoch: Epoch,
|
|
24
|
+
sourceFlag: number,
|
|
25
|
+
targetFlag: number,
|
|
26
|
+
headFlag: number
|
|
27
|
+
): void {
|
|
28
|
+
const {epochCtx, slot: stateSlot} = state;
|
|
29
|
+
const prevEpoch = epochCtx.previousShuffling.epoch;
|
|
30
|
+
if (attestations.length === 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Prevent frequent object get of external CommonJS dependencies
|
|
35
|
+
const byteArrayEqualsFn = byteArrayEquals;
|
|
36
|
+
|
|
37
|
+
const actualTargetBlockRoot = getBlockRootAtSlot(state, computeStartSlotAtEpoch(epoch));
|
|
38
|
+
|
|
39
|
+
for (const att of attestations) {
|
|
40
|
+
// Ignore empty BitArray, from spec test minimal/phase0/epoch_processing/participation_record_updates updated_participation_record
|
|
41
|
+
// See https://github.com/ethereum/consensus-specs/issues/2825
|
|
42
|
+
if (att.aggregationBits.bitLen === 0) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const attData = att.data;
|
|
47
|
+
const inclusionDelay = att.inclusionDelay;
|
|
48
|
+
const proposerIndex = att.proposerIndex;
|
|
49
|
+
const attSlot = attData.slot;
|
|
50
|
+
const attVotedTargetRoot = byteArrayEqualsFn(attData.target.root, actualTargetBlockRoot);
|
|
51
|
+
const attVotedHeadRoot =
|
|
52
|
+
attSlot < stateSlot && byteArrayEqualsFn(attData.beaconBlockRoot, getBlockRootAtSlot(state, attSlot));
|
|
53
|
+
const committee = epochCtx.getBeaconCommittee(attSlot, attData.index);
|
|
54
|
+
const participants = att.aggregationBits.intersectValues(committee);
|
|
55
|
+
|
|
56
|
+
if (epoch === prevEpoch) {
|
|
57
|
+
for (const p of participants) {
|
|
58
|
+
if (proposerIndices[p] === -1 || inclusionDelays[p] > inclusionDelay) {
|
|
59
|
+
proposerIndices[p] = proposerIndex;
|
|
60
|
+
inclusionDelays[p] = inclusionDelay;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const p of participants) {
|
|
66
|
+
flags[p] |= sourceFlag;
|
|
67
|
+
if (attVotedTargetRoot) {
|
|
68
|
+
flags[p] |= targetFlag;
|
|
69
|
+
if (attVotedHeadRoot) {
|
|
70
|
+
flags[p] |= headFlag;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js";
|
|
2
|
+
import {decreaseBalance, increaseBalance} from "../util/balance.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Starting from Electra:
|
|
6
|
+
* Process every `pendingConsolidation` in `state.pendingConsolidations`.
|
|
7
|
+
* Churn limit was applied when enqueueing so we don't care about the limit here
|
|
8
|
+
* However we only process consolidations up to current epoch
|
|
9
|
+
*
|
|
10
|
+
* For each valid `pendingConsolidation`, update withdrawal credential of target
|
|
11
|
+
* validator to compounding, decrease balance of source validator and increase balance
|
|
12
|
+
* of target validator.
|
|
13
|
+
*
|
|
14
|
+
* Dequeue all processed consolidations from `state.pendingConsolidation`
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
export function processPendingConsolidations(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void {
|
|
18
|
+
const nextEpoch = state.epochCtx.epoch + 1;
|
|
19
|
+
let nextPendingConsolidation = 0;
|
|
20
|
+
const validators = state.validators;
|
|
21
|
+
const cachedBalances = cache.balances;
|
|
22
|
+
|
|
23
|
+
let chunkStartIndex = 0;
|
|
24
|
+
const chunkSize = 100;
|
|
25
|
+
const pendingConsolidationsLength = state.pendingConsolidations.length;
|
|
26
|
+
outer: while (chunkStartIndex < pendingConsolidationsLength) {
|
|
27
|
+
const consolidationChunk = state.pendingConsolidations.getReadonlyByRange(chunkStartIndex, chunkSize);
|
|
28
|
+
|
|
29
|
+
for (const pendingConsolidation of consolidationChunk) {
|
|
30
|
+
const {sourceIndex, targetIndex} = pendingConsolidation;
|
|
31
|
+
const sourceValidator = validators.getReadonly(sourceIndex);
|
|
32
|
+
|
|
33
|
+
if (sourceValidator.slashed) {
|
|
34
|
+
nextPendingConsolidation++;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (sourceValidator.withdrawableEpoch > nextEpoch) {
|
|
39
|
+
break outer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Calculate the consolidated balance
|
|
43
|
+
const sourceEffectiveBalance = Math.min(state.balances.get(sourceIndex), sourceValidator.effectiveBalance);
|
|
44
|
+
|
|
45
|
+
// Move active balance to target. Excess balance is withdrawable.
|
|
46
|
+
decreaseBalance(state, sourceIndex, sourceEffectiveBalance);
|
|
47
|
+
increaseBalance(state, targetIndex, sourceEffectiveBalance);
|
|
48
|
+
if (cachedBalances) {
|
|
49
|
+
cachedBalances[sourceIndex] -= sourceEffectiveBalance;
|
|
50
|
+
cachedBalances[targetIndex] += sourceEffectiveBalance;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
nextPendingConsolidation++;
|
|
54
|
+
}
|
|
55
|
+
chunkStartIndex += chunkSize;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
state.pendingConsolidations = state.pendingConsolidations.sliceFrom(nextPendingConsolidation);
|
|
59
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {FAR_FUTURE_EPOCH, ForkSeq, GENESIS_SLOT, MAX_PENDING_DEPOSITS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
+
import {electra} from "@lodestar/types";
|
|
3
|
+
import {addValidatorToRegistry, isValidDepositSignature} from "../block/processDeposit.js";
|
|
4
|
+
import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js";
|
|
5
|
+
import {increaseBalance} from "../util/balance.js";
|
|
6
|
+
import {hasCompoundingWithdrawalCredential, isValidatorKnown} from "../util/electra.js";
|
|
7
|
+
import {computeStartSlotAtEpoch} from "../util/epoch.js";
|
|
8
|
+
import {getActivationExitChurnLimit} from "../util/validator.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Starting from Electra:
|
|
12
|
+
* Process pending balance deposits from state subject to churn limit and depositBalanceToConsume.
|
|
13
|
+
* For each eligible `deposit`, call `increaseBalance()`.
|
|
14
|
+
* Remove the processed deposits from `state.pendingDeposits`.
|
|
15
|
+
* Update `state.depositBalanceToConsume` for the next epoch
|
|
16
|
+
*
|
|
17
|
+
* TODO Electra: Update ssz library to support batch push to `pendingDeposits`
|
|
18
|
+
*/
|
|
19
|
+
export function processPendingDeposits(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void {
|
|
20
|
+
const nextEpoch = state.epochCtx.epoch + 1;
|
|
21
|
+
const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state.epochCtx));
|
|
22
|
+
let processedAmount = 0;
|
|
23
|
+
let nextDepositIndex = 0;
|
|
24
|
+
const depositsToPostpone = [];
|
|
25
|
+
let isChurnLimitReached = false;
|
|
26
|
+
const finalizedSlot = computeStartSlotAtEpoch(state.finalizedCheckpoint.epoch);
|
|
27
|
+
|
|
28
|
+
let startIndex = 0;
|
|
29
|
+
// TODO: is this a good number?
|
|
30
|
+
const chunk = 100;
|
|
31
|
+
const pendingDepositsLength = state.pendingDeposits.length;
|
|
32
|
+
outer: while (startIndex < pendingDepositsLength) {
|
|
33
|
+
const deposits = state.pendingDeposits.getReadonlyByRange(startIndex, chunk);
|
|
34
|
+
|
|
35
|
+
for (const deposit of deposits) {
|
|
36
|
+
// Do not process deposit requests if Eth1 bridge deposits are not yet applied.
|
|
37
|
+
if (
|
|
38
|
+
// Is deposit request
|
|
39
|
+
deposit.slot > GENESIS_SLOT &&
|
|
40
|
+
// There are pending Eth1 bridge deposits
|
|
41
|
+
state.eth1DepositIndex < state.depositRequestsStartIndex
|
|
42
|
+
) {
|
|
43
|
+
break outer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check if deposit has been finalized, otherwise, stop processing.
|
|
47
|
+
if (deposit.slot > finalizedSlot) {
|
|
48
|
+
break outer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if number of processed deposits has not reached the limit, otherwise, stop processing.
|
|
52
|
+
if (nextDepositIndex >= MAX_PENDING_DEPOSITS_PER_EPOCH) {
|
|
53
|
+
break outer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Read validator state
|
|
57
|
+
let isValidatorExited = false;
|
|
58
|
+
let isValidatorWithdrawn = false;
|
|
59
|
+
|
|
60
|
+
const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey);
|
|
61
|
+
if (isValidatorKnown(state, validatorIndex)) {
|
|
62
|
+
const validator = state.validators.getReadonly(validatorIndex);
|
|
63
|
+
isValidatorExited = validator.exitEpoch < FAR_FUTURE_EPOCH;
|
|
64
|
+
isValidatorWithdrawn = validator.withdrawableEpoch < nextEpoch;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isValidatorWithdrawn) {
|
|
68
|
+
// Deposited balance will never become active. Increase balance but do not consume churn
|
|
69
|
+
applyPendingDeposit(state, deposit, cache);
|
|
70
|
+
} else if (isValidatorExited) {
|
|
71
|
+
// Validator is exiting, postpone the deposit until after withdrawable epoch
|
|
72
|
+
depositsToPostpone.push(deposit);
|
|
73
|
+
} else {
|
|
74
|
+
// Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
|
|
75
|
+
isChurnLimitReached = processedAmount + deposit.amount > availableForProcessing;
|
|
76
|
+
if (isChurnLimitReached) {
|
|
77
|
+
break outer;
|
|
78
|
+
}
|
|
79
|
+
// Consume churn and apply deposit.
|
|
80
|
+
processedAmount += deposit.amount;
|
|
81
|
+
applyPendingDeposit(state, deposit, cache);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Regardless of how the deposit was handled, we move on in the queue.
|
|
85
|
+
nextDepositIndex++;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
startIndex += chunk;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const remainingPendingDeposits = state.pendingDeposits.sliceFrom(nextDepositIndex);
|
|
92
|
+
state.pendingDeposits = remainingPendingDeposits;
|
|
93
|
+
|
|
94
|
+
// TODO Electra: add a function in ListCompositeTreeView to support batch push operation
|
|
95
|
+
for (const deposit of depositsToPostpone) {
|
|
96
|
+
state.pendingDeposits.push(deposit);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Accumulate churn only if the churn limit has been hit.
|
|
100
|
+
if (isChurnLimitReached) {
|
|
101
|
+
state.depositBalanceToConsume = availableForProcessing - BigInt(processedAmount);
|
|
102
|
+
} else {
|
|
103
|
+
state.depositBalanceToConsume = 0n;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function applyPendingDeposit(
|
|
108
|
+
state: CachedBeaconStateElectra,
|
|
109
|
+
deposit: electra.PendingDeposit,
|
|
110
|
+
cache: EpochTransitionCache
|
|
111
|
+
): void {
|
|
112
|
+
const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey);
|
|
113
|
+
const {pubkey, withdrawalCredentials, amount, signature} = deposit;
|
|
114
|
+
const cachedBalances = cache.balances;
|
|
115
|
+
|
|
116
|
+
if (!isValidatorKnown(state, validatorIndex)) {
|
|
117
|
+
// Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
|
118
|
+
if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) {
|
|
119
|
+
addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount);
|
|
120
|
+
const newValidatorIndex = state.validators.length - 1;
|
|
121
|
+
cache.isCompoundingValidatorArr[newValidatorIndex] = hasCompoundingWithdrawalCredential(withdrawalCredentials);
|
|
122
|
+
// set balance, so that the next deposit of same pubkey will increase the balance correctly
|
|
123
|
+
// this is to fix the double deposit issue found in mekong
|
|
124
|
+
// see https://github.com/ChainSafe/lodestar/pull/7255
|
|
125
|
+
if (cachedBalances) {
|
|
126
|
+
cachedBalances[newValidatorIndex] = amount;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
// Increase balance
|
|
131
|
+
increaseBalance(state, validatorIndex, amount);
|
|
132
|
+
if (cachedBalances) {
|
|
133
|
+
cachedBalances[validatorIndex] += amount;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {ForkSeq, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
+
import {ssz} from "@lodestar/types";
|
|
3
|
+
import {CachedBeaconStateFulu, EpochTransitionCache} from "../types.js";
|
|
4
|
+
import {computeEpochShuffling} from "../util/epochShuffling.js";
|
|
5
|
+
import {computeProposerIndices} from "../util/seed.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This function updates the `proposer_lookahead` field in the beacon state
|
|
9
|
+
* by shifting out proposer indices from the earliest epoch and appending new
|
|
10
|
+
* proposer indices for the latest epoch. With `MIN_SEED_LOOKAHEAD` set to `1`,
|
|
11
|
+
* this means that at the start of epoch `N`, the proposer lookahead for epoch
|
|
12
|
+
* `N+1` will be computed and included in the beacon state's lookahead.
|
|
13
|
+
*/
|
|
14
|
+
export function processProposerLookahead(
|
|
15
|
+
fork: ForkSeq,
|
|
16
|
+
state: CachedBeaconStateFulu,
|
|
17
|
+
cache: EpochTransitionCache
|
|
18
|
+
): void {
|
|
19
|
+
// Shift out proposers in the first epoch
|
|
20
|
+
const remainingProposerLookahead = state.proposerLookahead.getAll().slice(SLOTS_PER_EPOCH);
|
|
21
|
+
|
|
22
|
+
// Fill in the last epoch with new proposer indices
|
|
23
|
+
const epoch = state.epochCtx.epoch + MIN_SEED_LOOKAHEAD + 1;
|
|
24
|
+
|
|
25
|
+
const shuffling =
|
|
26
|
+
state.epochCtx.shufflingCache?.getSync(epoch, cache.nextShufflingDecisionRoot, {
|
|
27
|
+
state,
|
|
28
|
+
activeIndices: cache.nextShufflingActiveIndices,
|
|
29
|
+
}) ??
|
|
30
|
+
// Only for testing. shufflingCache should always be available in prod
|
|
31
|
+
computeEpochShuffling(state, cache.nextShufflingActiveIndices, epoch);
|
|
32
|
+
|
|
33
|
+
const lastEpochProposerLookahead = computeProposerIndices(fork, state, shuffling, epoch);
|
|
34
|
+
|
|
35
|
+
state.proposerLookahead = ssz.fulu.ProposerLookahead.toViewDU([
|
|
36
|
+
...remainingProposerLookahead,
|
|
37
|
+
...lastEpochProposerLookahead,
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {EPOCHS_PER_HISTORICAL_VECTOR} from "@lodestar/params";
|
|
2
|
+
import {CachedBeaconStateAllForks, EpochTransitionCache} from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Write next randaoMix
|
|
6
|
+
*
|
|
7
|
+
* PERF: Almost no (constant) cost
|
|
8
|
+
*/
|
|
9
|
+
export function processRandaoMixesReset(state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void {
|
|
10
|
+
const currentEpoch = cache.currentEpoch;
|
|
11
|
+
const nextEpoch = currentEpoch + 1;
|
|
12
|
+
|
|
13
|
+
// set randao mix
|
|
14
|
+
state.randaoMixes.set(
|
|
15
|
+
nextEpoch % EPOCHS_PER_HISTORICAL_VECTOR,
|
|
16
|
+
state.randaoMixes.get(currentEpoch % EPOCHS_PER_HISTORICAL_VECTOR)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {ForkSeq} from "@lodestar/params";
|
|
2
|
+
import {initiateValidatorExit} from "../block/index.js";
|
|
3
|
+
import {CachedBeaconStateAllForks, EpochTransitionCache} from "../types.js";
|
|
4
|
+
import {computeActivationExitEpoch} from "../util/index.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Update validator registry for validators that activate + exit
|
|
8
|
+
*
|
|
9
|
+
* PERF: Cost 'proportional' to only validators that active + exit. For mainnet conditions:
|
|
10
|
+
* - indicesEligibleForActivationQueue: Maxing deposits triggers 512 validator mutations
|
|
11
|
+
* - indicesEligibleForActivation: 4 per epoch
|
|
12
|
+
* - indicesToEject: Potentially the entire validator set. On a massive offline event this could trigger many mutations
|
|
13
|
+
* per epoch. Note that once mutated that validator can't be added to indicesToEject.
|
|
14
|
+
*
|
|
15
|
+
* - On normal mainnet conditions only 4 validators will be updated
|
|
16
|
+
* - indicesEligibleForActivation: ~4000
|
|
17
|
+
* - indicesEligibleForActivationQueue: 0
|
|
18
|
+
* - indicesToEject: 0
|
|
19
|
+
*/
|
|
20
|
+
export function processRegistryUpdates(
|
|
21
|
+
fork: ForkSeq,
|
|
22
|
+
state: CachedBeaconStateAllForks,
|
|
23
|
+
cache: EpochTransitionCache
|
|
24
|
+
): void {
|
|
25
|
+
const {epochCtx} = state;
|
|
26
|
+
|
|
27
|
+
// Get the validators sub tree once for all the loop
|
|
28
|
+
const validators = state.validators;
|
|
29
|
+
|
|
30
|
+
// TODO: Batch set this properties in the tree at once with setMany() or setNodes()
|
|
31
|
+
|
|
32
|
+
// process ejections
|
|
33
|
+
for (const index of cache.indicesToEject) {
|
|
34
|
+
// set validator exit epoch and withdrawable epoch
|
|
35
|
+
// TODO: Figure out a way to quickly set properties on the validators tree
|
|
36
|
+
initiateValidatorExit(fork, state, validators.get(index));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// set new activation eligibilities
|
|
40
|
+
for (const index of cache.indicesEligibleForActivationQueue) {
|
|
41
|
+
validators.get(index).activationEligibilityEpoch = epochCtx.epoch + 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const finalityEpoch = state.finalizedCheckpoint.epoch;
|
|
45
|
+
// this avoids an array allocation compared to `slice(0, epochCtx.activationChurnLimit)`
|
|
46
|
+
const len =
|
|
47
|
+
fork < ForkSeq.electra
|
|
48
|
+
? Math.min(cache.indicesEligibleForActivation.length, epochCtx.activationChurnLimit)
|
|
49
|
+
: cache.indicesEligibleForActivation.length;
|
|
50
|
+
const activationEpoch = computeActivationExitEpoch(cache.currentEpoch);
|
|
51
|
+
// dequeue validators for activation up to churn limit
|
|
52
|
+
for (let i = 0; i < len; i++) {
|
|
53
|
+
const validatorIndex = cache.indicesEligibleForActivation[i];
|
|
54
|
+
const validator = validators.get(validatorIndex);
|
|
55
|
+
// placement in queue is finalized
|
|
56
|
+
if (validator.activationEligibilityEpoch > finalityEpoch) {
|
|
57
|
+
// remaining validators all have an activationEligibilityEpoch that is higher anyway, break early
|
|
58
|
+
// activationEligibilityEpoch has been sorted in epoch process in ascending order.
|
|
59
|
+
// At that point the finalityEpoch was not known because processJustificationAndFinalization() wasn't called yet.
|
|
60
|
+
// So we need to filter by finalityEpoch here to comply with the spec.
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
validator.activationEpoch = activationEpoch;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {ForkSeq, GENESIS_EPOCH} from "@lodestar/params";
|
|
2
|
+
import {ssz} from "@lodestar/types";
|
|
3
|
+
import {
|
|
4
|
+
CachedBeaconStateAllForks,
|
|
5
|
+
CachedBeaconStateAltair,
|
|
6
|
+
CachedBeaconStatePhase0,
|
|
7
|
+
EpochTransitionCache,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
import {getAttestationDeltas} from "./getAttestationDeltas.js";
|
|
10
|
+
import {getRewardsAndPenaltiesAltair} from "./getRewardsAndPenalties.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* This data is reused and never gc.
|
|
14
|
+
*/
|
|
15
|
+
const balances = new Array<number>();
|
|
16
|
+
/**
|
|
17
|
+
* Iterate over all validator and compute rewards and penalties to apply to balances.
|
|
18
|
+
*
|
|
19
|
+
* PERF: Cost = 'proportional' to $VALIDATOR_COUNT. Extra work is done per validator the more status flags are set
|
|
20
|
+
*/
|
|
21
|
+
export function processRewardsAndPenalties(
|
|
22
|
+
state: CachedBeaconStateAllForks,
|
|
23
|
+
cache: EpochTransitionCache,
|
|
24
|
+
slashingPenalties: number[] = []
|
|
25
|
+
): void {
|
|
26
|
+
// No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch
|
|
27
|
+
if (cache.currentEpoch === GENESIS_EPOCH) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [rewards, penalties] = getRewardsAndPenalties(state, cache);
|
|
32
|
+
balances.length = state.balances.length;
|
|
33
|
+
state.balances.getAll(balances);
|
|
34
|
+
|
|
35
|
+
for (let i = 0, len = rewards.length; i < len; i++) {
|
|
36
|
+
const result = balances[i] + rewards[i] - penalties[i] - (slashingPenalties[i] ?? 0);
|
|
37
|
+
balances[i] = Math.max(result, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// important: do not change state one balance at a time. Set them all at once, constructing the tree in one go
|
|
41
|
+
// cache the balances array, too
|
|
42
|
+
state.balances = ssz.phase0.Balances.toViewDU(balances);
|
|
43
|
+
|
|
44
|
+
// For processEffectiveBalanceUpdates() to prevent having to re-compute the balances array.
|
|
45
|
+
// For validator metrics
|
|
46
|
+
cache.balances = balances;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Note: abstracted in separate function for easier spec tests
|
|
50
|
+
export function getRewardsAndPenalties(
|
|
51
|
+
state: CachedBeaconStateAllForks,
|
|
52
|
+
epochTransitionCache: EpochTransitionCache
|
|
53
|
+
): [number[], number[]] {
|
|
54
|
+
const fork = state.config.getForkSeq(state.slot);
|
|
55
|
+
return fork === ForkSeq.phase0
|
|
56
|
+
? getAttestationDeltas(state as CachedBeaconStatePhase0, epochTransitionCache)
|
|
57
|
+
: getRewardsAndPenaltiesAltair(state as CachedBeaconStateAltair, epochTransitionCache);
|
|
58
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
3
|
+
ForkSeq,
|
|
4
|
+
PROPORTIONAL_SLASHING_MULTIPLIER,
|
|
5
|
+
PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR,
|
|
6
|
+
PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX,
|
|
7
|
+
} from "@lodestar/params";
|
|
8
|
+
import {BeaconStateAllForks, CachedBeaconStateAllForks, EpochTransitionCache} from "../types.js";
|
|
9
|
+
import {decreaseBalance} from "../util/index.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Update validator registry for validators that activate + exit
|
|
13
|
+
* updateBalance is an optimization:
|
|
14
|
+
* - For spec test, it's true
|
|
15
|
+
* - For processEpoch flow, it's false, i.e to only update balances once in processRewardsAndPenalties()
|
|
16
|
+
*
|
|
17
|
+
* PERF: almost no (constant) cost.
|
|
18
|
+
* - Total slashings by increment is computed once and stored in state.epochCtx.totalSlashingsByIncrement so no need to compute here
|
|
19
|
+
* - Penalties for validators with the same effective balance are the same and computed once
|
|
20
|
+
* - No need to apply penalties to validators here, do it once in processRewardsAndPenalties()
|
|
21
|
+
* - indicesToSlash: max len is 8704. But it's very unlikely since it would require all validators on the same
|
|
22
|
+
* committees to sign slashable attestations.
|
|
23
|
+
* - On normal mainnet conditions indicesToSlash = 0
|
|
24
|
+
*
|
|
25
|
+
* @returns slashing penalties to be applied in processRewardsAndPenalties()
|
|
26
|
+
*/
|
|
27
|
+
export function processSlashings(
|
|
28
|
+
state: CachedBeaconStateAllForks,
|
|
29
|
+
cache: EpochTransitionCache,
|
|
30
|
+
updateBalance = true
|
|
31
|
+
): number[] {
|
|
32
|
+
// Return early if there no index to slash
|
|
33
|
+
if (cache.indicesToSlash.length === 0) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const totalBalanceByIncrement = cache.totalActiveStakeByIncrement;
|
|
38
|
+
const fork = state.config.getForkSeq(state.slot);
|
|
39
|
+
const proportionalSlashingMultiplier =
|
|
40
|
+
fork === ForkSeq.phase0
|
|
41
|
+
? PROPORTIONAL_SLASHING_MULTIPLIER
|
|
42
|
+
: fork === ForkSeq.altair
|
|
43
|
+
? PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR
|
|
44
|
+
: PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX;
|
|
45
|
+
|
|
46
|
+
const {effectiveBalanceIncrements} = state.epochCtx;
|
|
47
|
+
const adjustedTotalSlashingBalanceByIncrement = Math.min(
|
|
48
|
+
state.epochCtx.totalSlashingsByIncrement * proportionalSlashingMultiplier,
|
|
49
|
+
totalBalanceByIncrement
|
|
50
|
+
);
|
|
51
|
+
const increment = EFFECTIVE_BALANCE_INCREMENT;
|
|
52
|
+
|
|
53
|
+
const penaltyPerEffectiveBalanceIncrement = Math.floor(
|
|
54
|
+
(adjustedTotalSlashingBalanceByIncrement * increment) / totalBalanceByIncrement
|
|
55
|
+
);
|
|
56
|
+
const penalties: number[] = [];
|
|
57
|
+
|
|
58
|
+
const penaltiesByEffectiveBalanceIncrement = new Map<number, number>();
|
|
59
|
+
for (const index of cache.indicesToSlash) {
|
|
60
|
+
const effectiveBalanceIncrement = effectiveBalanceIncrements[index];
|
|
61
|
+
let penalty = penaltiesByEffectiveBalanceIncrement.get(effectiveBalanceIncrement);
|
|
62
|
+
if (penalty === undefined) {
|
|
63
|
+
if (fork < ForkSeq.electra) {
|
|
64
|
+
const penaltyNumeratorByIncrement = effectiveBalanceIncrement * adjustedTotalSlashingBalanceByIncrement;
|
|
65
|
+
penalty = Math.floor(penaltyNumeratorByIncrement / totalBalanceByIncrement) * increment;
|
|
66
|
+
} else {
|
|
67
|
+
penalty = penaltyPerEffectiveBalanceIncrement * effectiveBalanceIncrement;
|
|
68
|
+
}
|
|
69
|
+
penaltiesByEffectiveBalanceIncrement.set(effectiveBalanceIncrement, penalty);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (updateBalance) {
|
|
73
|
+
// for spec test only
|
|
74
|
+
decreaseBalance(state, index, penalty);
|
|
75
|
+
} else {
|
|
76
|
+
// do it later in processRewardsAndPenalties()
|
|
77
|
+
penalties[index] = penalty;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return penalties;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get total slashings by increment.
|
|
86
|
+
* By default, total slashings are computed every time we run processSlashings() function above.
|
|
87
|
+
* We improve it by computing it once and store it in state.epochCtx.totalSlashingsByIncrement
|
|
88
|
+
* Every change to state.slashings should update totalSlashingsByIncrement.
|
|
89
|
+
*/
|
|
90
|
+
export function getTotalSlashingsByIncrement(state: BeaconStateAllForks): number {
|
|
91
|
+
let totalSlashingsByIncrement = 0;
|
|
92
|
+
const slashings = state.slashings.getAll();
|
|
93
|
+
for (let i = 0; i < slashings.length; i++) {
|
|
94
|
+
totalSlashingsByIncrement += Math.floor(slashings[i] / EFFECTIVE_BALANCE_INCREMENT);
|
|
95
|
+
}
|
|
96
|
+
return totalSlashingsByIncrement;
|
|
97
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {EFFECTIVE_BALANCE_INCREMENT, EPOCHS_PER_SLASHINGS_VECTOR} from "@lodestar/params";
|
|
2
|
+
import {CachedBeaconStateAllForks, EpochTransitionCache} from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reset the next slashings balance accumulator
|
|
6
|
+
*
|
|
7
|
+
* PERF: Almost no (constant) cost
|
|
8
|
+
*/
|
|
9
|
+
export function processSlashingsReset(state: CachedBeaconStateAllForks, cache: EpochTransitionCache): void {
|
|
10
|
+
const nextEpoch = cache.currentEpoch + 1;
|
|
11
|
+
|
|
12
|
+
// reset slashings
|
|
13
|
+
const slashIndex = nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR;
|
|
14
|
+
const oldSlashingValueByIncrement = Math.floor(state.slashings.get(slashIndex) / EFFECTIVE_BALANCE_INCREMENT);
|
|
15
|
+
state.slashings.set(slashIndex, 0);
|
|
16
|
+
state.epochCtx.totalSlashingsByIncrement = Math.max(
|
|
17
|
+
0,
|
|
18
|
+
state.epochCtx.totalSlashingsByIncrement - oldSlashingValueByIncrement
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {aggregateSerializedPublicKeys} from "@chainsafe/blst";
|
|
2
|
+
import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, ForkSeq} from "@lodestar/params";
|
|
3
|
+
import {ssz} from "@lodestar/types";
|
|
4
|
+
import {CachedBeaconStateAltair} from "../types.js";
|
|
5
|
+
import {getNextSyncCommitteeIndices} from "../util/seed.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Rotate nextSyncCommittee to currentSyncCommittee if sync committee period is over.
|
|
9
|
+
*
|
|
10
|
+
* PERF: Once every `EPOCHS_PER_SYNC_COMMITTEE_PERIOD`, do an expensive operation to compute the next committee.
|
|
11
|
+
* Calculating the next sync committee has a proportional cost to $VALIDATOR_COUNT
|
|
12
|
+
*/
|
|
13
|
+
export function processSyncCommitteeUpdates(fork: ForkSeq, state: CachedBeaconStateAltair): void {
|
|
14
|
+
const nextEpoch = state.epochCtx.epoch + 1;
|
|
15
|
+
|
|
16
|
+
if (nextEpoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD === 0) {
|
|
17
|
+
const activeValidatorIndices = state.epochCtx.nextActiveIndices;
|
|
18
|
+
const {effectiveBalanceIncrements} = state.epochCtx;
|
|
19
|
+
|
|
20
|
+
const nextSyncCommitteeIndices = getNextSyncCommitteeIndices(
|
|
21
|
+
fork,
|
|
22
|
+
state,
|
|
23
|
+
activeValidatorIndices,
|
|
24
|
+
effectiveBalanceIncrements
|
|
25
|
+
);
|
|
26
|
+
const validators = state.validators;
|
|
27
|
+
|
|
28
|
+
// Using the index2pubkey cache is slower because it needs the serialized pubkey.
|
|
29
|
+
const nextSyncCommitteePubkeys = [];
|
|
30
|
+
for (const index of nextSyncCommitteeIndices) {
|
|
31
|
+
nextSyncCommitteePubkeys.push(validators.getReadonly(index).pubkey);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Rotate syncCommittee in state
|
|
35
|
+
state.currentSyncCommittee = state.nextSyncCommittee;
|
|
36
|
+
state.nextSyncCommittee = ssz.altair.SyncCommittee.toViewDU({
|
|
37
|
+
pubkeys: nextSyncCommitteePubkeys,
|
|
38
|
+
aggregatePubkey: aggregateSerializedPublicKeys(nextSyncCommitteePubkeys).toBytes(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Rotate syncCommittee cache
|
|
42
|
+
state.epochCtx.rotateSyncCommitteeIndexed(nextSyncCommitteeIndices);
|
|
43
|
+
}
|
|
44
|
+
}
|