@lodestar/state-transition 1.35.0-dev.e9dd48f165 → 1.35.0-dev.fcf8d024ea
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/README.md +1 -1
- package/lib/block/externalData.d.ts.map +1 -0
- package/lib/block/index.d.ts +2 -2
- package/lib/block/index.d.ts.map +1 -0
- package/lib/block/index.js +2 -2
- package/lib/block/index.js.map +1 -1
- 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/processAttestationPhase0.js +1 -2
- package/lib/block/processAttestationPhase0.js.map +1 -1
- package/lib/block/processAttestations.d.ts.map +1 -0
- package/lib/block/processAttestationsAltair.d.ts +1 -1
- package/lib/block/processAttestationsAltair.d.ts.map +1 -0
- package/lib/block/processAttestationsAltair.js +1 -1
- package/lib/block/processAttestationsAltair.js.map +1 -1
- package/lib/block/processAttesterSlashing.d.ts.map +1 -0
- package/lib/block/processAttesterSlashing.js.map +1 -1
- 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/processBlsToExecutionChange.js.map +1 -1
- package/lib/block/processConsolidationRequest.d.ts.map +1 -0
- package/lib/block/processConsolidationRequest.js.map +1 -1
- package/lib/block/processDeposit.d.ts +2 -2
- package/lib/block/processDeposit.d.ts.map +1 -0
- package/lib/block/processDeposit.js +1 -1
- package/lib/block/processDeposit.js.map +1 -1
- package/lib/block/processDepositRequest.d.ts.map +1 -0
- package/lib/block/processDepositRequest.js.map +1 -1
- package/lib/block/processEth1Data.d.ts.map +1 -0
- package/lib/block/processExecutionPayload.d.ts.map +1 -0
- package/lib/block/processOperations.d.ts.map +1 -0
- package/lib/block/processOperations.js.map +1 -1
- 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/processSyncCommittee.js +1 -2
- package/lib/block/processSyncCommittee.js.map +1 -1
- package/lib/block/processVoluntaryExit.d.ts.map +1 -0
- package/lib/block/processWithdrawalRequest.d.ts.map +1 -0
- package/lib/block/processWithdrawalRequest.js.map +1 -1
- package/lib/block/processWithdrawals.d.ts.map +1 -0
- package/lib/block/processWithdrawals.js.map +1 -1
- package/lib/block/slashValidator.d.ts.map +1 -0
- package/lib/block/slashValidator.js.map +1 -1
- 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/epochCache.js +130 -0
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/cache/epochTransitionCache.d.ts.map +1 -0
- package/lib/cache/epochTransitionCache.js.map +1 -1
- 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/index.js.map +1 -1
- 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/processSlashings.js.map +1 -1
- package/lib/epoch/processSlashingsReset.d.ts.map +1 -0
- package/lib/epoch/processSyncCommitteeUpdates.d.ts.map +1 -0
- package/lib/index.d.ts +17 -17
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +16 -16
- package/lib/index.js.map +1 -1
- 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/blsToExecutionChange.js.map +1 -1
- package/lib/signatureSets/index.d.ts +1 -1
- package/lib/signatureSets/index.d.ts.map +1 -0
- package/lib/signatureSets/index.js +1 -1
- package/lib/signatureSets/index.js.map +1 -1
- 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 +1 -2
- package/lib/slot/upgradeStateToDeneb.d.ts.map +1 -0
- package/lib/slot/upgradeStateToDeneb.js.map +1 -1
- 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/slot/upgradeStateToGloas.js +2 -2
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/stateTransition.d.ts.map +1 -0
- package/lib/types.d.ts +2 -2
- 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.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.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 +3 -6
- package/lib/util/genesis.js.map +1 -1
- package/lib/util/index.d.ts +5 -5
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/index.js +5 -5
- package/lib/util/index.js.map +1 -1
- package/lib/util/interop.d.ts.map +1 -0
- package/lib/util/interop.js +1 -1
- package/lib/util/interop.js.map +1 -1
- 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/rootCache.js +5 -2
- package/lib/util/rootCache.js.map +1 -1
- package/lib/util/seed.d.ts.map +1 -0
- package/lib/util/seed.js +1 -2
- package/lib/util/seed.js.map +1 -1
- 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 +1 -5
- 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/lib/util/weakSubjectivity.js.map +1 -1
- 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 +144 -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 +177 -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,1213 @@
|
|
|
1
|
+
import {PublicKey} from "@chainsafe/blst";
|
|
2
|
+
import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
|
|
3
|
+
import {BeaconConfig, ChainConfig, createBeaconConfig} from "@lodestar/config";
|
|
4
|
+
import {
|
|
5
|
+
ATTESTATION_SUBNET_COUNT,
|
|
6
|
+
DOMAIN_BEACON_PROPOSER,
|
|
7
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
8
|
+
FAR_FUTURE_EPOCH,
|
|
9
|
+
ForkSeq,
|
|
10
|
+
GENESIS_EPOCH,
|
|
11
|
+
PROPOSER_WEIGHT,
|
|
12
|
+
SLOTS_PER_EPOCH,
|
|
13
|
+
WEIGHT_DENOMINATOR,
|
|
14
|
+
} from "@lodestar/params";
|
|
15
|
+
import {
|
|
16
|
+
Attestation,
|
|
17
|
+
BLSSignature,
|
|
18
|
+
CommitteeIndex,
|
|
19
|
+
Epoch,
|
|
20
|
+
IndexedAttestation,
|
|
21
|
+
RootHex,
|
|
22
|
+
Slot,
|
|
23
|
+
SubnetID,
|
|
24
|
+
SyncPeriod,
|
|
25
|
+
ValidatorIndex,
|
|
26
|
+
electra,
|
|
27
|
+
phase0,
|
|
28
|
+
} from "@lodestar/types";
|
|
29
|
+
import {LodestarError} from "@lodestar/utils";
|
|
30
|
+
import {getTotalSlashingsByIncrement} from "../epoch/processSlashings.js";
|
|
31
|
+
import {AttesterDuty, calculateCommitteeAssignments} from "../util/calculateCommitteeAssignments.js";
|
|
32
|
+
import {
|
|
33
|
+
EpochShuffling,
|
|
34
|
+
IShufflingCache,
|
|
35
|
+
calculateShufflingDecisionRoot,
|
|
36
|
+
computeEpochShuffling,
|
|
37
|
+
} from "../util/epochShuffling.js";
|
|
38
|
+
import {
|
|
39
|
+
computeActivationExitEpoch,
|
|
40
|
+
computeEpochAtSlot,
|
|
41
|
+
computeProposers,
|
|
42
|
+
computeStartSlotAtEpoch,
|
|
43
|
+
computeSyncPeriodAtEpoch,
|
|
44
|
+
getActivationChurnLimit,
|
|
45
|
+
getChurnLimit,
|
|
46
|
+
getSeed,
|
|
47
|
+
isActiveValidator,
|
|
48
|
+
isAggregatorFromCommitteeLength,
|
|
49
|
+
} from "../util/index.js";
|
|
50
|
+
import {computeBaseRewardPerIncrement, computeSyncParticipantReward} from "../util/syncCommittee.js";
|
|
51
|
+
import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js";
|
|
52
|
+
import {EffectiveBalanceIncrements, getEffectiveBalanceIncrementsWithLen} from "./effectiveBalanceIncrements.js";
|
|
53
|
+
import {EpochTransitionCache} from "./epochTransitionCache.js";
|
|
54
|
+
import {Index2PubkeyCache, syncPubkeys} from "./pubkeyCache.js";
|
|
55
|
+
import {CachedBeaconStateAllForks, CachedBeaconStateFulu} from "./stateCache.js";
|
|
56
|
+
import {
|
|
57
|
+
SyncCommitteeCache,
|
|
58
|
+
SyncCommitteeCacheEmpty,
|
|
59
|
+
computeSyncCommitteeCache,
|
|
60
|
+
getSyncCommitteeCache,
|
|
61
|
+
} from "./syncCommitteeCache.js";
|
|
62
|
+
import {BeaconStateAllForks, BeaconStateAltair} from "./types.js";
|
|
63
|
+
|
|
64
|
+
/** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */
|
|
65
|
+
export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT);
|
|
66
|
+
|
|
67
|
+
export type EpochCacheImmutableData = {
|
|
68
|
+
config: BeaconConfig;
|
|
69
|
+
pubkey2index: PubkeyIndexMap;
|
|
70
|
+
index2pubkey: Index2PubkeyCache;
|
|
71
|
+
shufflingCache?: IShufflingCache;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type EpochCacheOpts = {
|
|
75
|
+
skipSyncCommitteeCache?: boolean;
|
|
76
|
+
skipSyncPubkeys?: boolean;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Defers computing proposers by persisting only the seed, and dropping it once indexes are computed */
|
|
80
|
+
type ProposersDeferred = {computed: false; seed: Uint8Array} | {computed: true; indexes: ValidatorIndex[]};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* EpochCache is the parent object of:
|
|
84
|
+
* - Any data-structures not part of the spec'ed BeaconState
|
|
85
|
+
* - Necessary to only compute data once
|
|
86
|
+
* - Must be kept at all times through an epoch
|
|
87
|
+
*
|
|
88
|
+
* The performance gains with EpochCache are fundamental for the BeaconNode to be able to participate in a
|
|
89
|
+
* production network with 100_000s of validators. In summary, it contains:
|
|
90
|
+
*
|
|
91
|
+
* Expensive data constant through the epoch:
|
|
92
|
+
* - pubkey cache
|
|
93
|
+
* - proposer indexes
|
|
94
|
+
* - shufflings
|
|
95
|
+
* - sync committee indexed
|
|
96
|
+
* Counters (maybe) mutated through the epoch:
|
|
97
|
+
* - churnLimit
|
|
98
|
+
* - exitQueueEpoch
|
|
99
|
+
* - exitQueueChurn
|
|
100
|
+
* Time data faster than recomputing from the state:
|
|
101
|
+
* - epoch
|
|
102
|
+
* - syncPeriod
|
|
103
|
+
**/
|
|
104
|
+
export class EpochCache {
|
|
105
|
+
config: BeaconConfig;
|
|
106
|
+
/**
|
|
107
|
+
* Unique globally shared pubkey registry. There should only exist one for the entire application.
|
|
108
|
+
*
|
|
109
|
+
* TODO: this is a hack, we need a safety mechanism in case a bad eth1 majority vote is in,
|
|
110
|
+
* or handle non finalized data differently, or use an immutable.js structure for cheap copies
|
|
111
|
+
*
|
|
112
|
+
* $VALIDATOR_COUNT x 192 char String -> Number Map
|
|
113
|
+
*/
|
|
114
|
+
pubkey2index: PubkeyIndexMap;
|
|
115
|
+
/**
|
|
116
|
+
* Unique globally shared pubkey registry. There should only exist one for the entire application.
|
|
117
|
+
*
|
|
118
|
+
* $VALIDATOR_COUNT x BLST deserialized pubkey (Jacobian coordinates)
|
|
119
|
+
*/
|
|
120
|
+
index2pubkey: Index2PubkeyCache;
|
|
121
|
+
/**
|
|
122
|
+
* ShufflingCache is passed in from `beacon-node` so should be available at runtime but may not be
|
|
123
|
+
* present during testing.
|
|
124
|
+
*/
|
|
125
|
+
shufflingCache?: IShufflingCache;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Indexes of the block proposers for the current epoch.
|
|
129
|
+
* For pre-fulu, this is computed and cached from the current shuffling.
|
|
130
|
+
* For post-fulu, this is copied from the state.proposerLookahead.
|
|
131
|
+
*
|
|
132
|
+
* 32 x Number
|
|
133
|
+
*/
|
|
134
|
+
proposers: ValidatorIndex[];
|
|
135
|
+
|
|
136
|
+
/** Proposers for previous epoch, initialized to null in first epoch */
|
|
137
|
+
proposersPrevEpoch: ValidatorIndex[] | null;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The next proposer seed is only used in the getBeaconProposersNextEpoch call. It cannot be moved into
|
|
141
|
+
* getBeaconProposersNextEpoch because it needs state as input and all data needed by getBeaconProposersNextEpoch
|
|
142
|
+
* should be in the epoch context.
|
|
143
|
+
*
|
|
144
|
+
* For pre-fulu, this is lazily computed from the next epoch's shuffling.
|
|
145
|
+
* For post-fulu, this is copied from the state.proposerLookahead.
|
|
146
|
+
*/
|
|
147
|
+
proposersNextEpoch: ProposersDeferred;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Epoch decision roots to look up correct shuffling from the Shuffling Cache
|
|
151
|
+
*/
|
|
152
|
+
previousDecisionRoot: RootHex;
|
|
153
|
+
currentDecisionRoot: RootHex;
|
|
154
|
+
nextDecisionRoot: RootHex;
|
|
155
|
+
/**
|
|
156
|
+
* Shuffling of validator indexes. Immutable through the epoch, then it's replaced entirely.
|
|
157
|
+
* Note: Per spec definition, shuffling will always be defined. They are never called before loadState()
|
|
158
|
+
*
|
|
159
|
+
* $VALIDATOR_COUNT x Number
|
|
160
|
+
*/
|
|
161
|
+
previousShuffling: EpochShuffling;
|
|
162
|
+
/** Same as previousShuffling */
|
|
163
|
+
currentShuffling: EpochShuffling;
|
|
164
|
+
/** Same as previousShuffling */
|
|
165
|
+
nextShuffling: EpochShuffling | null;
|
|
166
|
+
/**
|
|
167
|
+
* Cache nextActiveIndices so that in afterProcessEpoch the next shuffling can be build synchronously
|
|
168
|
+
* in case it is not built or the ShufflingCache is not available
|
|
169
|
+
*/
|
|
170
|
+
nextActiveIndices: Uint32Array;
|
|
171
|
+
/**
|
|
172
|
+
* Effective balances, for altair processAttestations()
|
|
173
|
+
*/
|
|
174
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements;
|
|
175
|
+
/**
|
|
176
|
+
* Total state.slashings by increment, for processSlashing()
|
|
177
|
+
*/
|
|
178
|
+
totalSlashingsByIncrement: number;
|
|
179
|
+
syncParticipantReward: number;
|
|
180
|
+
syncProposerReward: number;
|
|
181
|
+
/**
|
|
182
|
+
* Update freq: once per epoch after `process_effective_balance_updates()`
|
|
183
|
+
*/
|
|
184
|
+
baseRewardPerIncrement: number;
|
|
185
|
+
/**
|
|
186
|
+
* Total active balance for current epoch, to be used instead of getTotalBalance()
|
|
187
|
+
*/
|
|
188
|
+
totalActiveBalanceIncrements: number;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Rate at which validators can enter or leave the set per epoch. Depends only on activeIndexes, so it does not
|
|
192
|
+
* change through the epoch. It's used in initiateValidatorExit(). Must be update after changing active indexes.
|
|
193
|
+
*/
|
|
194
|
+
churnLimit: number;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Fork limited actual activationChurnLimit
|
|
198
|
+
*/
|
|
199
|
+
activationChurnLimit: number;
|
|
200
|
+
/**
|
|
201
|
+
* Closest epoch with available churn for validators to exit at. May be updated every block as validators are
|
|
202
|
+
* initiateValidatorExit(). This value may vary on each fork of the state.
|
|
203
|
+
*
|
|
204
|
+
* NOTE: Changes block to block
|
|
205
|
+
* NOTE: No longer used by initiateValidatorExit post-electra
|
|
206
|
+
*/
|
|
207
|
+
exitQueueEpoch: Epoch;
|
|
208
|
+
/**
|
|
209
|
+
* Number of validators initiating an exit at exitQueueEpoch. May be updated every block as validators are
|
|
210
|
+
* initiateValidatorExit(). This value may vary on each fork of the state.
|
|
211
|
+
*
|
|
212
|
+
* NOTE: Changes block to block
|
|
213
|
+
* NOTE: No longer used by initiateValidatorExit post-electra
|
|
214
|
+
*/
|
|
215
|
+
exitQueueChurn: number;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Total cumulative balance increments through epoch for current target.
|
|
219
|
+
* Required for unrealized checkpoints issue pull-up tips N+1. Otherwise must run epoch transition every block
|
|
220
|
+
* This value is equivalent to:
|
|
221
|
+
* - Forward current state to end-of-epoch
|
|
222
|
+
* - Run beforeProcessEpoch
|
|
223
|
+
* - epochTransitionCache.currEpochUnslashedTargetStakeByIncrement
|
|
224
|
+
*/
|
|
225
|
+
currentTargetUnslashedBalanceIncrements: number;
|
|
226
|
+
/**
|
|
227
|
+
* Total cumulative balance increments through epoch for previous target.
|
|
228
|
+
* Required for unrealized checkpoints issue pull-up tips N+1. Otherwise must run epoch transition every block
|
|
229
|
+
* This value is equivalent to:
|
|
230
|
+
* - Forward current state to end-of-epoch
|
|
231
|
+
* - Run beforeProcessEpoch
|
|
232
|
+
* - epochTransitionCache.prevEpochUnslashedStake.targetStakeByIncrement
|
|
233
|
+
*/
|
|
234
|
+
previousTargetUnslashedBalanceIncrements: number;
|
|
235
|
+
|
|
236
|
+
/** TODO: Indexed SyncCommitteeCache */
|
|
237
|
+
currentSyncCommitteeIndexed: SyncCommitteeCache;
|
|
238
|
+
/** TODO: Indexed SyncCommitteeCache */
|
|
239
|
+
nextSyncCommitteeIndexed: SyncCommitteeCache;
|
|
240
|
+
|
|
241
|
+
// TODO: Helper stats
|
|
242
|
+
syncPeriod: SyncPeriod;
|
|
243
|
+
|
|
244
|
+
epoch: Epoch;
|
|
245
|
+
|
|
246
|
+
get nextEpoch(): Epoch {
|
|
247
|
+
return this.epoch + 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
constructor(data: {
|
|
251
|
+
config: BeaconConfig;
|
|
252
|
+
pubkey2index: PubkeyIndexMap;
|
|
253
|
+
index2pubkey: Index2PubkeyCache;
|
|
254
|
+
shufflingCache?: IShufflingCache;
|
|
255
|
+
proposers: number[];
|
|
256
|
+
proposersPrevEpoch: number[] | null;
|
|
257
|
+
proposersNextEpoch: ProposersDeferred;
|
|
258
|
+
previousDecisionRoot: RootHex;
|
|
259
|
+
currentDecisionRoot: RootHex;
|
|
260
|
+
nextDecisionRoot: RootHex;
|
|
261
|
+
previousShuffling: EpochShuffling;
|
|
262
|
+
currentShuffling: EpochShuffling;
|
|
263
|
+
nextShuffling: EpochShuffling | null;
|
|
264
|
+
nextActiveIndices: Uint32Array;
|
|
265
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements;
|
|
266
|
+
totalSlashingsByIncrement: number;
|
|
267
|
+
syncParticipantReward: number;
|
|
268
|
+
syncProposerReward: number;
|
|
269
|
+
baseRewardPerIncrement: number;
|
|
270
|
+
totalActiveBalanceIncrements: number;
|
|
271
|
+
churnLimit: number;
|
|
272
|
+
activationChurnLimit: number;
|
|
273
|
+
exitQueueEpoch: Epoch;
|
|
274
|
+
exitQueueChurn: number;
|
|
275
|
+
currentTargetUnslashedBalanceIncrements: number;
|
|
276
|
+
previousTargetUnslashedBalanceIncrements: number;
|
|
277
|
+
currentSyncCommitteeIndexed: SyncCommitteeCache;
|
|
278
|
+
nextSyncCommitteeIndexed: SyncCommitteeCache;
|
|
279
|
+
epoch: Epoch;
|
|
280
|
+
syncPeriod: SyncPeriod;
|
|
281
|
+
}) {
|
|
282
|
+
this.config = data.config;
|
|
283
|
+
this.pubkey2index = data.pubkey2index;
|
|
284
|
+
this.index2pubkey = data.index2pubkey;
|
|
285
|
+
this.shufflingCache = data.shufflingCache;
|
|
286
|
+
this.proposers = data.proposers;
|
|
287
|
+
this.proposersPrevEpoch = data.proposersPrevEpoch;
|
|
288
|
+
this.proposersNextEpoch = data.proposersNextEpoch;
|
|
289
|
+
this.previousDecisionRoot = data.previousDecisionRoot;
|
|
290
|
+
this.currentDecisionRoot = data.currentDecisionRoot;
|
|
291
|
+
this.nextDecisionRoot = data.nextDecisionRoot;
|
|
292
|
+
this.previousShuffling = data.previousShuffling;
|
|
293
|
+
this.currentShuffling = data.currentShuffling;
|
|
294
|
+
this.nextShuffling = data.nextShuffling;
|
|
295
|
+
this.nextActiveIndices = data.nextActiveIndices;
|
|
296
|
+
this.effectiveBalanceIncrements = data.effectiveBalanceIncrements;
|
|
297
|
+
this.totalSlashingsByIncrement = data.totalSlashingsByIncrement;
|
|
298
|
+
this.syncParticipantReward = data.syncParticipantReward;
|
|
299
|
+
this.syncProposerReward = data.syncProposerReward;
|
|
300
|
+
this.baseRewardPerIncrement = data.baseRewardPerIncrement;
|
|
301
|
+
this.totalActiveBalanceIncrements = data.totalActiveBalanceIncrements;
|
|
302
|
+
this.churnLimit = data.churnLimit;
|
|
303
|
+
this.activationChurnLimit = data.activationChurnLimit;
|
|
304
|
+
this.exitQueueEpoch = data.exitQueueEpoch;
|
|
305
|
+
this.exitQueueChurn = data.exitQueueChurn;
|
|
306
|
+
this.currentTargetUnslashedBalanceIncrements = data.currentTargetUnslashedBalanceIncrements;
|
|
307
|
+
this.previousTargetUnslashedBalanceIncrements = data.previousTargetUnslashedBalanceIncrements;
|
|
308
|
+
this.currentSyncCommitteeIndexed = data.currentSyncCommitteeIndexed;
|
|
309
|
+
this.nextSyncCommitteeIndexed = data.nextSyncCommitteeIndexed;
|
|
310
|
+
this.epoch = data.epoch;
|
|
311
|
+
this.syncPeriod = data.syncPeriod;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Create an epoch cache
|
|
316
|
+
* @param state a finalized beacon state. Passing in unfinalized state may cause unexpected behaviour
|
|
317
|
+
*
|
|
318
|
+
* SLOW CODE - 🐢
|
|
319
|
+
*/
|
|
320
|
+
static createFromState(
|
|
321
|
+
state: BeaconStateAllForks,
|
|
322
|
+
{config, pubkey2index, index2pubkey, shufflingCache}: EpochCacheImmutableData,
|
|
323
|
+
opts?: EpochCacheOpts
|
|
324
|
+
): EpochCache {
|
|
325
|
+
const currentEpoch = computeEpochAtSlot(state.slot);
|
|
326
|
+
const isGenesis = currentEpoch === GENESIS_EPOCH;
|
|
327
|
+
const previousEpoch = isGenesis ? GENESIS_EPOCH : currentEpoch - 1;
|
|
328
|
+
const nextEpoch = currentEpoch + 1;
|
|
329
|
+
|
|
330
|
+
let totalActiveBalanceIncrements = 0;
|
|
331
|
+
let exitQueueEpoch = computeActivationExitEpoch(currentEpoch);
|
|
332
|
+
let exitQueueChurn = 0;
|
|
333
|
+
|
|
334
|
+
const validators = state.validators.getAllReadonlyValues();
|
|
335
|
+
const validatorCount = validators.length;
|
|
336
|
+
|
|
337
|
+
// syncPubkeys here to ensure EpochCacheImmutableData is popualted before computing the rest of caches
|
|
338
|
+
// - computeSyncCommitteeCache() needs a fully populated pubkey2index cache
|
|
339
|
+
if (!opts?.skipSyncPubkeys) {
|
|
340
|
+
syncPubkeys(validators, pubkey2index, index2pubkey);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const effectiveBalanceIncrements = getEffectiveBalanceIncrementsWithLen(validatorCount);
|
|
344
|
+
const totalSlashingsByIncrement = getTotalSlashingsByIncrement(state);
|
|
345
|
+
const previousActiveIndicesAsNumberArray: ValidatorIndex[] = [];
|
|
346
|
+
const currentActiveIndicesAsNumberArray: ValidatorIndex[] = [];
|
|
347
|
+
const nextActiveIndicesAsNumberArray: ValidatorIndex[] = [];
|
|
348
|
+
|
|
349
|
+
// BeaconChain could provide a shuffling cache to avoid re-computing shuffling every epoch
|
|
350
|
+
// in that case, we don't need to compute shufflings again
|
|
351
|
+
const previousDecisionRoot = calculateShufflingDecisionRoot(config, state, previousEpoch);
|
|
352
|
+
const cachedPreviousShuffling = shufflingCache?.getSync(previousEpoch, previousDecisionRoot);
|
|
353
|
+
const currentDecisionRoot = calculateShufflingDecisionRoot(config, state, currentEpoch);
|
|
354
|
+
const cachedCurrentShuffling = shufflingCache?.getSync(currentEpoch, currentDecisionRoot);
|
|
355
|
+
const nextDecisionRoot = calculateShufflingDecisionRoot(config, state, nextEpoch);
|
|
356
|
+
const cachedNextShuffling = shufflingCache?.getSync(nextEpoch, nextDecisionRoot);
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < validatorCount; i++) {
|
|
359
|
+
const validator = validators[i];
|
|
360
|
+
|
|
361
|
+
// Note: Not usable for fork-choice balances since in-active validators are not zero'ed
|
|
362
|
+
effectiveBalanceIncrements[i] = Math.floor(validator.effectiveBalance / EFFECTIVE_BALANCE_INCREMENT);
|
|
363
|
+
|
|
364
|
+
// we only need to track active indices for previous, current and next epoch if we have to compute shufflings
|
|
365
|
+
// skip doing that if we already have cached shufflings
|
|
366
|
+
if (cachedPreviousShuffling == null && isActiveValidator(validator, previousEpoch)) {
|
|
367
|
+
previousActiveIndicesAsNumberArray.push(i);
|
|
368
|
+
}
|
|
369
|
+
if (isActiveValidator(validator, currentEpoch)) {
|
|
370
|
+
if (cachedCurrentShuffling == null) {
|
|
371
|
+
currentActiveIndicesAsNumberArray.push(i);
|
|
372
|
+
}
|
|
373
|
+
// We track totalActiveBalanceIncrements as ETH to fit total network balance in a JS number (53 bits)
|
|
374
|
+
totalActiveBalanceIncrements += effectiveBalanceIncrements[i];
|
|
375
|
+
}
|
|
376
|
+
if (cachedNextShuffling == null && isActiveValidator(validator, nextEpoch)) {
|
|
377
|
+
nextActiveIndicesAsNumberArray.push(i);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const {exitEpoch} = validator;
|
|
381
|
+
if (exitEpoch !== FAR_FUTURE_EPOCH) {
|
|
382
|
+
if (exitEpoch > exitQueueEpoch) {
|
|
383
|
+
exitQueueEpoch = exitEpoch;
|
|
384
|
+
exitQueueChurn = 1;
|
|
385
|
+
} else if (exitEpoch === exitQueueEpoch) {
|
|
386
|
+
exitQueueChurn += 1;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Spec: `EFFECTIVE_BALANCE_INCREMENT` Gwei minimum to avoid divisions by zero
|
|
392
|
+
// 1 = 1 unit of EFFECTIVE_BALANCE_INCREMENT
|
|
393
|
+
if (totalActiveBalanceIncrements < 1) {
|
|
394
|
+
totalActiveBalanceIncrements = 1;
|
|
395
|
+
} else if (totalActiveBalanceIncrements >= Number.MAX_SAFE_INTEGER) {
|
|
396
|
+
throw Error("totalActiveBalanceIncrements >= Number.MAX_SAFE_INTEGER. MAX_EFFECTIVE_BALANCE is too low.");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const nextActiveIndices = new Uint32Array(nextActiveIndicesAsNumberArray);
|
|
400
|
+
let previousShuffling: EpochShuffling;
|
|
401
|
+
let currentShuffling: EpochShuffling;
|
|
402
|
+
let nextShuffling: EpochShuffling;
|
|
403
|
+
|
|
404
|
+
if (!shufflingCache) {
|
|
405
|
+
// Only for testing. shufflingCache should always be available in prod
|
|
406
|
+
previousShuffling = computeEpochShuffling(
|
|
407
|
+
state,
|
|
408
|
+
new Uint32Array(previousActiveIndicesAsNumberArray),
|
|
409
|
+
previousEpoch
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
currentShuffling = isGenesis
|
|
413
|
+
? previousShuffling
|
|
414
|
+
: computeEpochShuffling(state, new Uint32Array(currentActiveIndicesAsNumberArray), currentEpoch);
|
|
415
|
+
|
|
416
|
+
nextShuffling = computeEpochShuffling(state, nextActiveIndices, nextEpoch);
|
|
417
|
+
} else {
|
|
418
|
+
currentShuffling = cachedCurrentShuffling
|
|
419
|
+
? cachedCurrentShuffling
|
|
420
|
+
: shufflingCache.getSync(currentEpoch, currentDecisionRoot, {
|
|
421
|
+
state,
|
|
422
|
+
activeIndices: new Uint32Array(currentActiveIndicesAsNumberArray),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
previousShuffling = cachedPreviousShuffling
|
|
426
|
+
? cachedPreviousShuffling
|
|
427
|
+
: isGenesis
|
|
428
|
+
? currentShuffling
|
|
429
|
+
: shufflingCache.getSync(previousEpoch, previousDecisionRoot, {
|
|
430
|
+
state,
|
|
431
|
+
activeIndices: new Uint32Array(previousActiveIndicesAsNumberArray),
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
nextShuffling = cachedNextShuffling
|
|
435
|
+
? cachedNextShuffling
|
|
436
|
+
: shufflingCache.getSync(nextEpoch, nextDecisionRoot, {
|
|
437
|
+
state,
|
|
438
|
+
activeIndices: nextActiveIndices,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const currentProposerSeed = getSeed(state, currentEpoch, DOMAIN_BEACON_PROPOSER);
|
|
443
|
+
|
|
444
|
+
let proposers: number[];
|
|
445
|
+
if (currentEpoch >= config.FULU_FORK_EPOCH) {
|
|
446
|
+
// Overwrite proposers with state.proposerLookahead
|
|
447
|
+
proposers = (state as CachedBeaconStateFulu).proposerLookahead.getAll().slice(0, SLOTS_PER_EPOCH);
|
|
448
|
+
} else {
|
|
449
|
+
// We need to calculate Pre-fulu
|
|
450
|
+
// Allow to create CachedBeaconState for empty states, or no active validators
|
|
451
|
+
proposers =
|
|
452
|
+
currentShuffling.activeIndices.length > 0
|
|
453
|
+
? computeProposers(
|
|
454
|
+
config.getForkSeqAtEpoch(currentEpoch),
|
|
455
|
+
currentProposerSeed,
|
|
456
|
+
currentShuffling,
|
|
457
|
+
effectiveBalanceIncrements
|
|
458
|
+
)
|
|
459
|
+
: [];
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const proposersNextEpoch: ProposersDeferred = {
|
|
463
|
+
computed: false,
|
|
464
|
+
seed: getSeed(state, nextEpoch, DOMAIN_BEACON_PROPOSER),
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// Only after altair, compute the indices of the current sync committee
|
|
468
|
+
const afterAltairFork = currentEpoch >= config.ALTAIR_FORK_EPOCH;
|
|
469
|
+
|
|
470
|
+
// Values syncParticipantReward, syncProposerReward, baseRewardPerIncrement are only used after altair.
|
|
471
|
+
// However, since they are very cheap to compute they are computed always to simplify upgradeState function.
|
|
472
|
+
const syncParticipantReward = computeSyncParticipantReward(totalActiveBalanceIncrements);
|
|
473
|
+
const syncProposerReward = Math.floor(syncParticipantReward * PROPOSER_WEIGHT_FACTOR);
|
|
474
|
+
const baseRewardPerIncrement = computeBaseRewardPerIncrement(totalActiveBalanceIncrements);
|
|
475
|
+
|
|
476
|
+
let currentSyncCommitteeIndexed: SyncCommitteeCache;
|
|
477
|
+
let nextSyncCommitteeIndexed: SyncCommitteeCache;
|
|
478
|
+
// Allow to skip populating sync committee for initializeBeaconStateFromEth1()
|
|
479
|
+
if (afterAltairFork && !opts?.skipSyncCommitteeCache) {
|
|
480
|
+
const altairState = state as BeaconStateAltair;
|
|
481
|
+
currentSyncCommitteeIndexed = computeSyncCommitteeCache(altairState.currentSyncCommittee, pubkey2index);
|
|
482
|
+
nextSyncCommitteeIndexed = computeSyncCommitteeCache(altairState.nextSyncCommittee, pubkey2index);
|
|
483
|
+
} else {
|
|
484
|
+
currentSyncCommitteeIndexed = new SyncCommitteeCacheEmpty();
|
|
485
|
+
nextSyncCommitteeIndexed = new SyncCommitteeCacheEmpty();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute everytime the
|
|
489
|
+
// active validator indices set changes in size. Validators change active status only when:
|
|
490
|
+
// - validator.activation_epoch is set. Only changes in process_registry_updates() if validator can be activated. If
|
|
491
|
+
// the value changes it will be set to `epoch + 1 + MAX_SEED_LOOKAHEAD`.
|
|
492
|
+
// - validator.exit_epoch is set. Only changes in initiate_validator_exit() if validator exits. If the value changes,
|
|
493
|
+
// it will be set to at least `epoch + 1 + MAX_SEED_LOOKAHEAD`.
|
|
494
|
+
// ```
|
|
495
|
+
// is_active_validator = validator.activation_epoch <= epoch < validator.exit_epoch
|
|
496
|
+
// ```
|
|
497
|
+
// So the returned value of is_active_validator(epoch) is guaranteed to not change during `MAX_SEED_LOOKAHEAD` epochs.
|
|
498
|
+
//
|
|
499
|
+
// activeIndices size is dependent on the state epoch. The epoch is advanced after running the epoch transition, and
|
|
500
|
+
// the first block of the epoch process_block() call. So churnLimit must be computed at the end of the before epoch
|
|
501
|
+
// transition and the result is valid until the end of the next epoch transition
|
|
502
|
+
const churnLimit = getChurnLimit(config, currentShuffling.activeIndices.length);
|
|
503
|
+
const activationChurnLimit = getActivationChurnLimit(
|
|
504
|
+
config,
|
|
505
|
+
config.getForkSeq(state.slot),
|
|
506
|
+
currentShuffling.activeIndices.length
|
|
507
|
+
);
|
|
508
|
+
if (exitQueueChurn >= churnLimit) {
|
|
509
|
+
exitQueueEpoch += 1;
|
|
510
|
+
exitQueueChurn = 0;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// TODO: describe issue. Compute progressive target balances
|
|
514
|
+
// Compute balances from zero, note this state could be mid-epoch so target balances != 0
|
|
515
|
+
let previousTargetUnslashedBalanceIncrements = 0;
|
|
516
|
+
let currentTargetUnslashedBalanceIncrements = 0;
|
|
517
|
+
|
|
518
|
+
if (config.getForkSeq(state.slot) >= ForkSeq.altair) {
|
|
519
|
+
const {previousEpochParticipation, currentEpochParticipation} = state as BeaconStateAltair;
|
|
520
|
+
previousTargetUnslashedBalanceIncrements = sumTargetUnslashedBalanceIncrements(
|
|
521
|
+
previousEpochParticipation.getAll(),
|
|
522
|
+
previousEpoch,
|
|
523
|
+
validators
|
|
524
|
+
);
|
|
525
|
+
currentTargetUnslashedBalanceIncrements = sumTargetUnslashedBalanceIncrements(
|
|
526
|
+
currentEpochParticipation.getAll(),
|
|
527
|
+
currentEpoch,
|
|
528
|
+
validators
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return new EpochCache({
|
|
533
|
+
config,
|
|
534
|
+
pubkey2index,
|
|
535
|
+
index2pubkey,
|
|
536
|
+
shufflingCache,
|
|
537
|
+
proposers,
|
|
538
|
+
// On first epoch, set to null to prevent unnecessary work since this is only used for metrics
|
|
539
|
+
proposersPrevEpoch: null,
|
|
540
|
+
proposersNextEpoch,
|
|
541
|
+
previousDecisionRoot,
|
|
542
|
+
currentDecisionRoot,
|
|
543
|
+
nextDecisionRoot,
|
|
544
|
+
previousShuffling,
|
|
545
|
+
currentShuffling,
|
|
546
|
+
nextShuffling,
|
|
547
|
+
nextActiveIndices,
|
|
548
|
+
effectiveBalanceIncrements,
|
|
549
|
+
totalSlashingsByIncrement,
|
|
550
|
+
syncParticipantReward,
|
|
551
|
+
syncProposerReward,
|
|
552
|
+
baseRewardPerIncrement,
|
|
553
|
+
totalActiveBalanceIncrements,
|
|
554
|
+
churnLimit,
|
|
555
|
+
activationChurnLimit,
|
|
556
|
+
exitQueueEpoch,
|
|
557
|
+
exitQueueChurn,
|
|
558
|
+
previousTargetUnslashedBalanceIncrements,
|
|
559
|
+
currentTargetUnslashedBalanceIncrements,
|
|
560
|
+
currentSyncCommitteeIndexed,
|
|
561
|
+
nextSyncCommitteeIndexed,
|
|
562
|
+
epoch: currentEpoch,
|
|
563
|
+
syncPeriod: computeSyncPeriodAtEpoch(currentEpoch),
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Copies a given EpochCache while avoiding copying its immutable parts.
|
|
569
|
+
*/
|
|
570
|
+
clone(): EpochCache {
|
|
571
|
+
// warning: pubkey cache is not copied, it is shared, as eth1 is not expected to reorder validators.
|
|
572
|
+
// Shallow copy all data from current epoch context to the next
|
|
573
|
+
// All data is completely replaced, or only-appended
|
|
574
|
+
return new EpochCache({
|
|
575
|
+
config: this.config,
|
|
576
|
+
// Common append-only structures shared with all states, no need to clone
|
|
577
|
+
pubkey2index: this.pubkey2index,
|
|
578
|
+
index2pubkey: this.index2pubkey,
|
|
579
|
+
shufflingCache: this.shufflingCache,
|
|
580
|
+
// Immutable data
|
|
581
|
+
proposers: this.proposers,
|
|
582
|
+
proposersPrevEpoch: this.proposersPrevEpoch,
|
|
583
|
+
proposersNextEpoch: this.proposersNextEpoch,
|
|
584
|
+
previousDecisionRoot: this.previousDecisionRoot,
|
|
585
|
+
currentDecisionRoot: this.currentDecisionRoot,
|
|
586
|
+
nextDecisionRoot: this.nextDecisionRoot,
|
|
587
|
+
previousShuffling: this.previousShuffling,
|
|
588
|
+
currentShuffling: this.currentShuffling,
|
|
589
|
+
nextShuffling: this.nextShuffling,
|
|
590
|
+
nextActiveIndices: this.nextActiveIndices,
|
|
591
|
+
// Uint8Array, requires cloning, but it is cloned only when necessary before an epoch transition
|
|
592
|
+
// See EpochCache.beforeEpochTransition()
|
|
593
|
+
effectiveBalanceIncrements: this.effectiveBalanceIncrements,
|
|
594
|
+
totalSlashingsByIncrement: this.totalSlashingsByIncrement,
|
|
595
|
+
// Basic types (numbers) cloned implicitly
|
|
596
|
+
syncParticipantReward: this.syncParticipantReward,
|
|
597
|
+
syncProposerReward: this.syncProposerReward,
|
|
598
|
+
baseRewardPerIncrement: this.baseRewardPerIncrement,
|
|
599
|
+
totalActiveBalanceIncrements: this.totalActiveBalanceIncrements,
|
|
600
|
+
churnLimit: this.churnLimit,
|
|
601
|
+
activationChurnLimit: this.activationChurnLimit,
|
|
602
|
+
exitQueueEpoch: this.exitQueueEpoch,
|
|
603
|
+
exitQueueChurn: this.exitQueueChurn,
|
|
604
|
+
previousTargetUnslashedBalanceIncrements: this.previousTargetUnslashedBalanceIncrements,
|
|
605
|
+
currentTargetUnslashedBalanceIncrements: this.currentTargetUnslashedBalanceIncrements,
|
|
606
|
+
currentSyncCommitteeIndexed: this.currentSyncCommitteeIndexed,
|
|
607
|
+
nextSyncCommitteeIndexed: this.nextSyncCommitteeIndexed,
|
|
608
|
+
epoch: this.epoch,
|
|
609
|
+
syncPeriod: this.syncPeriod,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Called to re-use information, such as the shuffling of the next epoch, after transitioning into a
|
|
615
|
+
* new epoch. Also handles pre-computation of values that may change during the upcoming epoch and
|
|
616
|
+
* that get used in the following epoch transition. Often those pre-computations are not used by the
|
|
617
|
+
* chain but are courtesy values that are served via the API for epoch look ahead of duties.
|
|
618
|
+
*
|
|
619
|
+
* Steps for afterProcessEpoch
|
|
620
|
+
* 1) update previous/current/next values of cached items
|
|
621
|
+
*
|
|
622
|
+
* At fork boundary, this runs pre-fork logic and it happens before `upgradeState*` is called.
|
|
623
|
+
*/
|
|
624
|
+
afterProcessEpoch(state: CachedBeaconStateAllForks, epochTransitionCache: EpochTransitionCache): void {
|
|
625
|
+
// Because the slot was incremented before entering this function the "next epoch" is actually the "current epoch"
|
|
626
|
+
// in this context but that is not actually true because the state transition happens in the last 4 seconds of the
|
|
627
|
+
// epoch. For the context of this function "upcoming epoch" is used to denote the epoch that will begin after this
|
|
628
|
+
// function returns. The epoch that is "next" once the state transition is complete is referred to as the
|
|
629
|
+
// epochAfterUpcoming for the same reason to help minimize confusion.
|
|
630
|
+
const upcomingEpoch = this.nextEpoch;
|
|
631
|
+
const epochAfterUpcoming = upcomingEpoch + 1;
|
|
632
|
+
|
|
633
|
+
// move current to previous
|
|
634
|
+
this.previousShuffling = this.currentShuffling;
|
|
635
|
+
this.previousDecisionRoot = this.currentDecisionRoot;
|
|
636
|
+
|
|
637
|
+
// move next to current or calculate upcoming
|
|
638
|
+
this.currentDecisionRoot = this.nextDecisionRoot;
|
|
639
|
+
if (this.nextShuffling) {
|
|
640
|
+
// was already pulled from the ShufflingCache to the EpochCache (should be in most cases)
|
|
641
|
+
this.currentShuffling = this.nextShuffling;
|
|
642
|
+
} else {
|
|
643
|
+
this.shufflingCache?.metrics?.shufflingCache.nextShufflingNotOnEpochCache.inc();
|
|
644
|
+
this.currentShuffling =
|
|
645
|
+
this.shufflingCache?.getSync(upcomingEpoch, this.currentDecisionRoot, {
|
|
646
|
+
state,
|
|
647
|
+
// have to use the "nextActiveIndices" that were saved in the last transition here to calculate
|
|
648
|
+
// the upcoming shuffling if it is not already built (similar condition to the below computation)
|
|
649
|
+
activeIndices: this.nextActiveIndices,
|
|
650
|
+
}) ??
|
|
651
|
+
// allow for this case during testing where the ShufflingCache is not present, may affect perf testing
|
|
652
|
+
// so should be taken into account when structuring tests. Should not affect unit or other tests though
|
|
653
|
+
computeEpochShuffling(state, this.nextActiveIndices, upcomingEpoch);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// handle next values
|
|
657
|
+
this.nextDecisionRoot = epochTransitionCache.nextShufflingDecisionRoot;
|
|
658
|
+
this.nextActiveIndices = epochTransitionCache.nextShufflingActiveIndices;
|
|
659
|
+
if (this.shufflingCache) {
|
|
660
|
+
if (!epochTransitionCache.asyncShufflingCalculation) {
|
|
661
|
+
this.nextShuffling = this.shufflingCache.getSync(epochAfterUpcoming, this.nextDecisionRoot, {
|
|
662
|
+
state,
|
|
663
|
+
activeIndices: this.nextActiveIndices,
|
|
664
|
+
});
|
|
665
|
+
} else {
|
|
666
|
+
this.nextShuffling = null;
|
|
667
|
+
// This promise will resolve immediately after the synchronous code of the state-transition runs. Until
|
|
668
|
+
// the build is done on a worker thread it will be calculated immediately after the epoch transition
|
|
669
|
+
// completes. Once the work is done concurrently it should be ready by time this get runs so the promise
|
|
670
|
+
// will resolve directly on the next spin of the event loop because the epoch transition and shuffling take
|
|
671
|
+
// about the same time to calculate so theoretically its ready now. Do not await here though in case it
|
|
672
|
+
// is not ready yet as the transition must not be asynchronous.
|
|
673
|
+
this.shufflingCache
|
|
674
|
+
.get(epochAfterUpcoming, this.nextDecisionRoot)
|
|
675
|
+
.then((shuffling) => {
|
|
676
|
+
if (!shuffling) {
|
|
677
|
+
throw new Error("EpochShuffling not returned from get in afterProcessEpoch");
|
|
678
|
+
}
|
|
679
|
+
this.nextShuffling = shuffling;
|
|
680
|
+
})
|
|
681
|
+
.catch((err) => {
|
|
682
|
+
this.shufflingCache?.logger?.error(
|
|
683
|
+
"EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR",
|
|
684
|
+
{epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot},
|
|
685
|
+
err
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
// Only for testing. shufflingCache should always be available in prod
|
|
691
|
+
this.nextShuffling = computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// TODO: DEDUPLICATE from createEpochCache
|
|
695
|
+
//
|
|
696
|
+
// Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute every time the
|
|
697
|
+
// active validator indices set changes in size. Validators change active status only when:
|
|
698
|
+
// - validator.activation_epoch is set. Only changes in process_registry_updates() if validator can be activated. If
|
|
699
|
+
// the value changes it will be set to `epoch + 1 + MAX_SEED_LOOKAHEAD`.
|
|
700
|
+
// - validator.exit_epoch is set. Only changes in initiate_validator_exit() if validator exits. If the value changes,
|
|
701
|
+
// it will be set to at least `epoch + 1 + MAX_SEED_LOOKAHEAD`.
|
|
702
|
+
// ```
|
|
703
|
+
// is_active_validator = validator.activation_epoch <= epoch < validator.exit_epoch
|
|
704
|
+
// ```
|
|
705
|
+
// So the returned value of is_active_validator(epoch) is guaranteed to not change during `MAX_SEED_LOOKAHEAD` epochs.
|
|
706
|
+
//
|
|
707
|
+
// activeIndices size is dependent on the state epoch. The epoch is advanced after running the epoch transition, and
|
|
708
|
+
// the first block of the epoch process_block() call. So churnLimit must be computed at the end of the before epoch
|
|
709
|
+
// transition and the result is valid until the end of the next epoch transition
|
|
710
|
+
this.churnLimit = getChurnLimit(this.config, this.currentShuffling.activeIndices.length);
|
|
711
|
+
this.activationChurnLimit = getActivationChurnLimit(
|
|
712
|
+
this.config,
|
|
713
|
+
this.config.getForkSeq(state.slot),
|
|
714
|
+
this.currentShuffling.activeIndices.length
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
// Maybe advance exitQueueEpoch at the end of the epoch if there haven't been any exists for a while
|
|
718
|
+
const exitQueueEpoch = computeActivationExitEpoch(upcomingEpoch);
|
|
719
|
+
if (exitQueueEpoch > this.exitQueueEpoch) {
|
|
720
|
+
this.exitQueueEpoch = exitQueueEpoch;
|
|
721
|
+
this.exitQueueChurn = 0;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
this.totalActiveBalanceIncrements = epochTransitionCache.nextEpochTotalActiveBalanceByIncrement;
|
|
725
|
+
if (upcomingEpoch >= this.config.ALTAIR_FORK_EPOCH) {
|
|
726
|
+
this.syncParticipantReward = computeSyncParticipantReward(this.totalActiveBalanceIncrements);
|
|
727
|
+
this.syncProposerReward = Math.floor(this.syncParticipantReward * PROPOSER_WEIGHT_FACTOR);
|
|
728
|
+
this.baseRewardPerIncrement = computeBaseRewardPerIncrement(this.totalActiveBalanceIncrements);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
this.previousTargetUnslashedBalanceIncrements = this.currentTargetUnslashedBalanceIncrements;
|
|
732
|
+
this.currentTargetUnslashedBalanceIncrements = 0;
|
|
733
|
+
|
|
734
|
+
// Advance time units
|
|
735
|
+
// state.slot is advanced right before calling this function
|
|
736
|
+
// ```
|
|
737
|
+
// postState.slot++;
|
|
738
|
+
// afterProcessEpoch(postState, epochTransitionCache);
|
|
739
|
+
// ```
|
|
740
|
+
this.epoch = computeEpochAtSlot(state.slot);
|
|
741
|
+
this.syncPeriod = computeSyncPeriodAtEpoch(this.epoch);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* At fork boundary, this runs post-fork logic and it happens after `upgradeState*` is called.
|
|
746
|
+
*/
|
|
747
|
+
finalProcessEpoch(state: CachedBeaconStateAllForks): void {
|
|
748
|
+
// this.epoch was updated at the end of afterProcessEpoch()
|
|
749
|
+
const upcomingEpoch = this.epoch;
|
|
750
|
+
const epochAfterUpcoming = upcomingEpoch + 1;
|
|
751
|
+
|
|
752
|
+
this.proposersPrevEpoch = this.proposers;
|
|
753
|
+
if (upcomingEpoch >= this.config.FULU_FORK_EPOCH) {
|
|
754
|
+
// Populate proposer cache with lookahead from state
|
|
755
|
+
const proposerLookahead = (state as CachedBeaconStateFulu).proposerLookahead.getAll();
|
|
756
|
+
this.proposers = proposerLookahead.slice(0, SLOTS_PER_EPOCH);
|
|
757
|
+
|
|
758
|
+
if (proposerLookahead.length >= SLOTS_PER_EPOCH * 2) {
|
|
759
|
+
this.proposersNextEpoch = {
|
|
760
|
+
computed: true,
|
|
761
|
+
indexes: proposerLookahead.slice(SLOTS_PER_EPOCH, SLOTS_PER_EPOCH * 2),
|
|
762
|
+
};
|
|
763
|
+
} else {
|
|
764
|
+
// This should not happen unless MIN_SEED_LOOKAHEAD is set to 0
|
|
765
|
+
// this ensures things don't break if the proposer lookahead is not long enough
|
|
766
|
+
this.proposersNextEpoch = {computed: false, seed: getSeed(state, epochAfterUpcoming, DOMAIN_BEACON_PROPOSER)};
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
// Need to calculate proposers pre-fulu
|
|
770
|
+
const upcomingProposerSeed = getSeed(state, upcomingEpoch, DOMAIN_BEACON_PROPOSER);
|
|
771
|
+
// next epoch was moved to current epoch so use current here
|
|
772
|
+
this.proposers = computeProposers(
|
|
773
|
+
this.config.getForkSeqAtEpoch(upcomingEpoch),
|
|
774
|
+
upcomingProposerSeed,
|
|
775
|
+
this.currentShuffling,
|
|
776
|
+
this.effectiveBalanceIncrements
|
|
777
|
+
);
|
|
778
|
+
// Only pre-compute the seed since it's very cheap. Do the expensive computeProposers() call only on demand.
|
|
779
|
+
this.proposersNextEpoch = {computed: false, seed: getSeed(state, epochAfterUpcoming, DOMAIN_BEACON_PROPOSER)};
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
beforeEpochTransition(): void {
|
|
784
|
+
// Clone (copy) before being mutated in processEffectiveBalanceUpdates
|
|
785
|
+
// NOTE: Force to use Uint16Array.slice (copy) instead of Buffer.call (not copy)
|
|
786
|
+
this.effectiveBalanceIncrements = Uint16Array.prototype.slice.call(this.effectiveBalanceIncrements, 0);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Return the beacon committee at slot for index.
|
|
791
|
+
*/
|
|
792
|
+
getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array {
|
|
793
|
+
return this.getBeaconCommittees(slot, [index])[0];
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Return a Uint32Array[] representing committees of indices
|
|
798
|
+
*/
|
|
799
|
+
getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array[] {
|
|
800
|
+
if (indices.length === 0) {
|
|
801
|
+
throw new Error("Attempt to get committees without providing CommitteeIndex");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const slotCommittees = this.getShufflingAtSlot(slot).committees[slot % SLOTS_PER_EPOCH];
|
|
805
|
+
const committees = [];
|
|
806
|
+
|
|
807
|
+
for (const index of indices) {
|
|
808
|
+
if (index >= slotCommittees.length) {
|
|
809
|
+
throw new EpochCacheError({
|
|
810
|
+
code: EpochCacheErrorCode.COMMITTEE_INDEX_OUT_OF_RANGE,
|
|
811
|
+
index,
|
|
812
|
+
maxIndex: slotCommittees.length,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
committees.push(slotCommittees[index]);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return committees;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
getCommitteeCountPerSlot(epoch: Epoch): number {
|
|
822
|
+
return this.getShufflingAtEpoch(epoch).committeesPerSlot;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Compute the correct subnet for a slot/committee index
|
|
827
|
+
*/
|
|
828
|
+
computeSubnetForSlot(slot: number, committeeIndex: number): SubnetID {
|
|
829
|
+
const slotsSinceEpochStart = slot % SLOTS_PER_EPOCH;
|
|
830
|
+
const committeesPerSlot = this.getCommitteeCountPerSlot(computeEpochAtSlot(slot));
|
|
831
|
+
const committeesSinceEpochStart = committeesPerSlot * slotsSinceEpochStart;
|
|
832
|
+
return (committeesSinceEpochStart + committeeIndex) % ATTESTATION_SUBNET_COUNT;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Read from proposers instead of state.proposer_lookahead because we set it in `finalProcessEpoch()`
|
|
837
|
+
* See https://github.com/ethereum/consensus-specs/blob/e9266b2145c09b63ba0039a9f477cfe8a629c78b/specs/fulu/beacon-chain.md#modified-get_beacon_proposer_index
|
|
838
|
+
*/
|
|
839
|
+
getBeaconProposer(slot: Slot): ValidatorIndex {
|
|
840
|
+
const epoch = computeEpochAtSlot(slot);
|
|
841
|
+
if (epoch !== this.currentShuffling.epoch) {
|
|
842
|
+
throw new EpochCacheError({
|
|
843
|
+
code: EpochCacheErrorCode.PROPOSER_EPOCH_MISMATCH,
|
|
844
|
+
currentEpoch: this.currentShuffling.epoch,
|
|
845
|
+
requestedEpoch: epoch,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
return this.proposers[slot % SLOTS_PER_EPOCH];
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
getBeaconProposers(): ValidatorIndex[] {
|
|
852
|
+
return this.proposers;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
getBeaconProposersPrevEpoch(): ValidatorIndex[] | null {
|
|
856
|
+
return this.proposersPrevEpoch;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* We allow requesting proposal duties 1 epoch in the future as in normal network conditions it's possible to predict
|
|
861
|
+
* the correct shuffling with high probability. While knowing the proposers in advance is not useful for consensus,
|
|
862
|
+
* users want to know it to plan manteinance and avoid missing block proposals.
|
|
863
|
+
*
|
|
864
|
+
* **How to predict future proposers**
|
|
865
|
+
*
|
|
866
|
+
* Proposer duties for epoch N are guaranteed to be known at epoch N. Proposer duties depend exclusively on:
|
|
867
|
+
* 1. seed (from randao_mix): known 2 epochs ahead
|
|
868
|
+
* 2. active validator set: known 4 epochs ahead
|
|
869
|
+
* 3. effective balance: not known ahead
|
|
870
|
+
*
|
|
871
|
+
* ```python
|
|
872
|
+
* def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
|
|
873
|
+
* epoch = get_current_epoch(state)
|
|
874
|
+
* seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot))
|
|
875
|
+
* indices = get_active_validator_indices(state, epoch)
|
|
876
|
+
* return compute_proposer_index(state, indices, seed)
|
|
877
|
+
* ```
|
|
878
|
+
*
|
|
879
|
+
* **1**: If `MIN_SEED_LOOKAHEAD = 1` the randao_mix used for the seed is from 2 epochs ago. So at epoch N, the seed
|
|
880
|
+
* is known and unchangable for duties at epoch N+1 and N+2 for proposer duties.
|
|
881
|
+
*
|
|
882
|
+
* ```python
|
|
883
|
+
* def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32:
|
|
884
|
+
* mix = get_randao_mix(state, Epoch(epoch - MIN_SEED_LOOKAHEAD - 1))
|
|
885
|
+
* return hash(domain_type + uint_to_bytes(epoch) + mix)
|
|
886
|
+
* ```
|
|
887
|
+
*
|
|
888
|
+
* **2**: The active validator set can be predicted `MAX_SEED_LOOKAHEAD` in advance due to how activations are
|
|
889
|
+
* processed. We already compute the active validator set for the next epoch to optimize epoch processing, so it's
|
|
890
|
+
* reused here.
|
|
891
|
+
*
|
|
892
|
+
* **3**: Effective balance is not known ahead of time, but it rarely changes. Even if it changes, only a few
|
|
893
|
+
* balances are sampled to adjust the probability of the next selection (32 per epoch on average). So to invalidate
|
|
894
|
+
* the prediction the effective of one of those 32 samples should change and change the random_byte inequality.
|
|
895
|
+
*/
|
|
896
|
+
getBeaconProposersNextEpoch(): ValidatorIndex[] {
|
|
897
|
+
if (!this.proposersNextEpoch.computed) {
|
|
898
|
+
// this is lazily computed pre-fulu
|
|
899
|
+
const indexes = computeProposers(
|
|
900
|
+
this.config.getForkSeqAtEpoch(this.nextEpoch),
|
|
901
|
+
this.proposersNextEpoch.seed,
|
|
902
|
+
this.getShufflingAtEpoch(this.nextEpoch),
|
|
903
|
+
this.effectiveBalanceIncrements
|
|
904
|
+
);
|
|
905
|
+
this.proposersNextEpoch = {computed: true, indexes};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// this is eagerly computed post-fulu
|
|
909
|
+
return this.proposersNextEpoch.indexes;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Return the indexed attestation corresponding to ``attestation``.
|
|
914
|
+
*/
|
|
915
|
+
getIndexedAttestation(fork: ForkSeq, attestation: Attestation): IndexedAttestation {
|
|
916
|
+
const {data} = attestation;
|
|
917
|
+
const attestingIndices = this.getAttestingIndices(fork, attestation);
|
|
918
|
+
|
|
919
|
+
// sort in-place
|
|
920
|
+
attestingIndices.sort((a, b) => a - b);
|
|
921
|
+
return {
|
|
922
|
+
attestingIndices: attestingIndices,
|
|
923
|
+
data: data,
|
|
924
|
+
signature: attestation.signature,
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Return indices of validators who attestested in `attestation`
|
|
930
|
+
*/
|
|
931
|
+
getAttestingIndices(fork: ForkSeq, attestation: Attestation): number[] {
|
|
932
|
+
if (fork < ForkSeq.electra) {
|
|
933
|
+
const {aggregationBits, data} = attestation;
|
|
934
|
+
const validatorIndices = this.getBeaconCommittee(data.slot, data.index);
|
|
935
|
+
|
|
936
|
+
return aggregationBits.intersectValues(validatorIndices);
|
|
937
|
+
}
|
|
938
|
+
const {aggregationBits, committeeBits, data} = attestation as electra.Attestation;
|
|
939
|
+
|
|
940
|
+
// There is a naming conflict on the term `committeeIndices`
|
|
941
|
+
// In Lodestar it usually means a list of validator indices of participants in a committee
|
|
942
|
+
// In the spec it means a list of committee indices according to committeeBits
|
|
943
|
+
// This `committeeIndices` refers to the latter
|
|
944
|
+
// TODO Electra: resolve the naming conflicts
|
|
945
|
+
const committeeIndices = committeeBits.getTrueBitIndexes();
|
|
946
|
+
|
|
947
|
+
const validatorsByCommittee = this.getBeaconCommittees(data.slot, committeeIndices);
|
|
948
|
+
|
|
949
|
+
// Create a new Uint32Array to flatten `validatorsByCommittee`
|
|
950
|
+
const totalLength = validatorsByCommittee.reduce((acc, curr) => acc + curr.length, 0);
|
|
951
|
+
const committeeValidators = new Uint32Array(totalLength);
|
|
952
|
+
|
|
953
|
+
let offset = 0;
|
|
954
|
+
for (const committee of validatorsByCommittee) {
|
|
955
|
+
committeeValidators.set(committee, offset);
|
|
956
|
+
offset += committee.length;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return aggregationBits.intersectValues(committeeValidators);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
getCommitteeAssignments(
|
|
963
|
+
epoch: Epoch,
|
|
964
|
+
requestedValidatorIndices: ValidatorIndex[]
|
|
965
|
+
): Map<ValidatorIndex, AttesterDuty> {
|
|
966
|
+
const shuffling = this.getShufflingAtEpoch(epoch);
|
|
967
|
+
return calculateCommitteeAssignments(shuffling, requestedValidatorIndices);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Return the committee assignment in the ``epoch`` for ``validator_index``.
|
|
972
|
+
* ``assignment`` returned is a tuple of the following form:
|
|
973
|
+
* ``assignment[0]`` is the list of validators in the committee
|
|
974
|
+
* ``assignment[1]`` is the index to which the committee is assigned
|
|
975
|
+
* ``assignment[2]`` is the slot at which the committee is assigned
|
|
976
|
+
* Return null if no assignment..
|
|
977
|
+
*/
|
|
978
|
+
getCommitteeAssignment(epoch: Epoch, validatorIndex: ValidatorIndex): phase0.CommitteeAssignment | null {
|
|
979
|
+
if (epoch > this.currentShuffling.epoch + 1) {
|
|
980
|
+
throw Error(
|
|
981
|
+
`Requesting committee assignment for more than 1 epoch ahead: ${epoch} > ${this.currentShuffling.epoch} + 1`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const epochStartSlot = computeStartSlotAtEpoch(epoch);
|
|
986
|
+
const committeeCountPerSlot = this.getCommitteeCountPerSlot(epoch);
|
|
987
|
+
for (let slot = epochStartSlot; slot < epochStartSlot + SLOTS_PER_EPOCH; slot++) {
|
|
988
|
+
for (let i = 0; i < committeeCountPerSlot; i++) {
|
|
989
|
+
const committee = this.getBeaconCommittee(slot, i);
|
|
990
|
+
if (committee.includes(validatorIndex)) {
|
|
991
|
+
return {
|
|
992
|
+
validators: Array.from(committee),
|
|
993
|
+
committeeIndex: i,
|
|
994
|
+
slot,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
isAggregator(slot: Slot, index: CommitteeIndex, slotSignature: BLSSignature): boolean {
|
|
1003
|
+
const committee = this.getBeaconCommittee(slot, index);
|
|
1004
|
+
return isAggregatorFromCommitteeLength(committee.length, slotSignature);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Return pubkey given the validator index.
|
|
1009
|
+
*/
|
|
1010
|
+
getPubkey(index: ValidatorIndex): PublicKey | undefined {
|
|
1011
|
+
return this.index2pubkey[index];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
getValidatorIndex(pubkey: Uint8Array): ValidatorIndex | null {
|
|
1015
|
+
return this.pubkey2index.get(pubkey);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
addPubkey(index: ValidatorIndex, pubkey: Uint8Array): void {
|
|
1019
|
+
this.pubkey2index.set(pubkey, index);
|
|
1020
|
+
this.index2pubkey[index] = PublicKey.fromBytes(pubkey); // Optimize for aggregation
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
getShufflingAtSlot(slot: Slot): EpochShuffling {
|
|
1024
|
+
const epoch = computeEpochAtSlot(slot);
|
|
1025
|
+
return this.getShufflingAtEpoch(epoch);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
getShufflingAtSlotOrNull(slot: Slot): EpochShuffling | null {
|
|
1029
|
+
const epoch = computeEpochAtSlot(slot);
|
|
1030
|
+
return this.getShufflingAtEpochOrNull(epoch);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
getShufflingAtEpoch(epoch: Epoch): EpochShuffling {
|
|
1034
|
+
const shuffling = this.getShufflingAtEpochOrNull(epoch);
|
|
1035
|
+
if (shuffling === null) {
|
|
1036
|
+
if (epoch === this.nextEpoch) {
|
|
1037
|
+
throw new EpochCacheError({
|
|
1038
|
+
code: EpochCacheErrorCode.NEXT_SHUFFLING_NOT_AVAILABLE,
|
|
1039
|
+
epoch: epoch,
|
|
1040
|
+
decisionRoot: this.getShufflingDecisionRoot(this.nextEpoch),
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
throw new EpochCacheError({
|
|
1044
|
+
code: EpochCacheErrorCode.COMMITTEE_EPOCH_OUT_OF_RANGE,
|
|
1045
|
+
currentEpoch: this.currentShuffling.epoch,
|
|
1046
|
+
requestedEpoch: epoch,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
return shuffling;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
getShufflingDecisionRoot(epoch: Epoch): RootHex {
|
|
1054
|
+
switch (epoch) {
|
|
1055
|
+
case this.epoch - 1:
|
|
1056
|
+
return this.previousDecisionRoot;
|
|
1057
|
+
case this.epoch:
|
|
1058
|
+
return this.currentDecisionRoot;
|
|
1059
|
+
case this.nextEpoch:
|
|
1060
|
+
return this.nextDecisionRoot;
|
|
1061
|
+
default:
|
|
1062
|
+
throw new EpochCacheError({
|
|
1063
|
+
code: EpochCacheErrorCode.DECISION_ROOT_EPOCH_OUT_OF_RANGE,
|
|
1064
|
+
currentEpoch: this.epoch,
|
|
1065
|
+
requestedEpoch: epoch,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
getShufflingAtEpochOrNull(epoch: Epoch): EpochShuffling | null {
|
|
1071
|
+
switch (epoch) {
|
|
1072
|
+
case this.epoch - 1:
|
|
1073
|
+
return this.previousShuffling;
|
|
1074
|
+
case this.epoch:
|
|
1075
|
+
return this.currentShuffling;
|
|
1076
|
+
case this.nextEpoch:
|
|
1077
|
+
if (!this.nextShuffling) {
|
|
1078
|
+
this.nextShuffling =
|
|
1079
|
+
this.shufflingCache?.getSync(this.nextEpoch, this.getShufflingDecisionRoot(this.nextEpoch)) ?? null;
|
|
1080
|
+
}
|
|
1081
|
+
return this.nextShuffling;
|
|
1082
|
+
default:
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Note: The range of slots a validator has to perform duties is off by one.
|
|
1089
|
+
* The previous slot wording means that if your validator is in a sync committee for a period that runs from slot
|
|
1090
|
+
* 100 to 200,then you would actually produce signatures in slot 99 - 199.
|
|
1091
|
+
*/
|
|
1092
|
+
getIndexedSyncCommittee(slot: Slot): SyncCommitteeCache {
|
|
1093
|
+
// See note above for the +1 offset
|
|
1094
|
+
return this.getIndexedSyncCommitteeAtEpoch(computeEpochAtSlot(slot + 1));
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* **DO NOT USE FOR GOSSIP VALIDATION**: Sync committee duties are offset by one slot. @see {@link EpochCache.getIndexedSyncCommittee}
|
|
1099
|
+
*
|
|
1100
|
+
* Get indexed sync committee at epoch without offsets
|
|
1101
|
+
*/
|
|
1102
|
+
getIndexedSyncCommitteeAtEpoch(epoch: Epoch): SyncCommitteeCache {
|
|
1103
|
+
switch (computeSyncPeriodAtEpoch(epoch)) {
|
|
1104
|
+
case this.syncPeriod:
|
|
1105
|
+
return this.currentSyncCommitteeIndexed;
|
|
1106
|
+
case this.syncPeriod + 1:
|
|
1107
|
+
return this.nextSyncCommitteeIndexed;
|
|
1108
|
+
default:
|
|
1109
|
+
throw new EpochCacheError({code: EpochCacheErrorCode.NO_SYNC_COMMITTEE, epoch});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/** On processSyncCommitteeUpdates rotate next to current and set nextSyncCommitteeIndexed */
|
|
1114
|
+
rotateSyncCommitteeIndexed(nextSyncCommitteeIndices: Uint32Array): void {
|
|
1115
|
+
this.currentSyncCommitteeIndexed = this.nextSyncCommitteeIndexed;
|
|
1116
|
+
this.nextSyncCommitteeIndexed = getSyncCommitteeCache(nextSyncCommitteeIndices);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/** On phase0 -> altair fork, set both current and nextSyncCommitteeIndexed */
|
|
1120
|
+
setSyncCommitteesIndexed(nextSyncCommitteeIndices: Uint32Array): void {
|
|
1121
|
+
this.nextSyncCommitteeIndexed = getSyncCommitteeCache(nextSyncCommitteeIndices);
|
|
1122
|
+
this.currentSyncCommitteeIndexed = this.nextSyncCommitteeIndexed;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
effectiveBalanceIncrementsSet(index: number, effectiveBalance: number): void {
|
|
1126
|
+
if (this.isPostElectra()) {
|
|
1127
|
+
// TODO: electra
|
|
1128
|
+
// getting length and setting getEffectiveBalanceIncrementsByteLen is not fork safe
|
|
1129
|
+
// so each time we add an index, we should new the Uint8Array to keep it forksafe
|
|
1130
|
+
// one simple optimization could be to increment the length once per block rather
|
|
1131
|
+
// on each add/set
|
|
1132
|
+
//
|
|
1133
|
+
// there could still be some unused length remaining from the prev ELECTRA padding
|
|
1134
|
+
const newLength =
|
|
1135
|
+
index >= this.effectiveBalanceIncrements.length ? index + 1 : this.effectiveBalanceIncrements.length;
|
|
1136
|
+
const effectiveBalanceIncrements = this.effectiveBalanceIncrements;
|
|
1137
|
+
this.effectiveBalanceIncrements = new Uint16Array(newLength);
|
|
1138
|
+
this.effectiveBalanceIncrements.set(effectiveBalanceIncrements, 0);
|
|
1139
|
+
} else {
|
|
1140
|
+
if (index >= this.effectiveBalanceIncrements.length) {
|
|
1141
|
+
// Clone and extend effectiveBalanceIncrements
|
|
1142
|
+
const effectiveBalanceIncrements = this.effectiveBalanceIncrements;
|
|
1143
|
+
this.effectiveBalanceIncrements = new Uint16Array(getEffectiveBalanceIncrementsByteLen(index + 1));
|
|
1144
|
+
this.effectiveBalanceIncrements.set(effectiveBalanceIncrements, 0);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
this.effectiveBalanceIncrements[index] = Math.floor(effectiveBalance / EFFECTIVE_BALANCE_INCREMENT);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
isPostElectra(): boolean {
|
|
1152
|
+
return this.epoch >= this.config.ELECTRA_FORK_EPOCH;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function getEffectiveBalanceIncrementsByteLen(validatorCount: number): number {
|
|
1157
|
+
// TODO: Research what's the best number to minimize both memory cost and copy costs
|
|
1158
|
+
return 1024 * Math.ceil(validatorCount / 1024);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export enum EpochCacheErrorCode {
|
|
1162
|
+
COMMITTEE_INDEX_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_COMMITTEE_INDEX_OUT_OF_RANGE",
|
|
1163
|
+
COMMITTEE_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_COMMITTEE_EPOCH_OUT_OF_RANGE",
|
|
1164
|
+
DECISION_ROOT_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_DECISION_ROOT_EPOCH_OUT_OF_RANGE",
|
|
1165
|
+
NEXT_SHUFFLING_NOT_AVAILABLE = "EPOCH_CONTEXT_ERROR_NEXT_SHUFFLING_NOT_AVAILABLE",
|
|
1166
|
+
NO_SYNC_COMMITTEE = "EPOCH_CONTEXT_ERROR_NO_SYNC_COMMITTEE",
|
|
1167
|
+
PROPOSER_EPOCH_MISMATCH = "EPOCH_CONTEXT_ERROR_PROPOSER_EPOCH_MISMATCH",
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
type EpochCacheErrorType =
|
|
1171
|
+
| {
|
|
1172
|
+
code: EpochCacheErrorCode.COMMITTEE_INDEX_OUT_OF_RANGE;
|
|
1173
|
+
index: number;
|
|
1174
|
+
maxIndex: number;
|
|
1175
|
+
}
|
|
1176
|
+
| {
|
|
1177
|
+
code: EpochCacheErrorCode.COMMITTEE_EPOCH_OUT_OF_RANGE;
|
|
1178
|
+
requestedEpoch: Epoch;
|
|
1179
|
+
currentEpoch: Epoch;
|
|
1180
|
+
}
|
|
1181
|
+
| {
|
|
1182
|
+
code: EpochCacheErrorCode.DECISION_ROOT_EPOCH_OUT_OF_RANGE;
|
|
1183
|
+
requestedEpoch: Epoch;
|
|
1184
|
+
currentEpoch: Epoch;
|
|
1185
|
+
}
|
|
1186
|
+
| {
|
|
1187
|
+
code: EpochCacheErrorCode.NEXT_SHUFFLING_NOT_AVAILABLE;
|
|
1188
|
+
epoch: Epoch;
|
|
1189
|
+
decisionRoot: RootHex;
|
|
1190
|
+
}
|
|
1191
|
+
| {
|
|
1192
|
+
code: EpochCacheErrorCode.NO_SYNC_COMMITTEE;
|
|
1193
|
+
epoch: Epoch;
|
|
1194
|
+
}
|
|
1195
|
+
| {
|
|
1196
|
+
code: EpochCacheErrorCode.PROPOSER_EPOCH_MISMATCH;
|
|
1197
|
+
requestedEpoch: Epoch;
|
|
1198
|
+
currentEpoch: Epoch;
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
export class EpochCacheError extends LodestarError<EpochCacheErrorType> {}
|
|
1202
|
+
|
|
1203
|
+
export function createEmptyEpochCacheImmutableData(
|
|
1204
|
+
chainConfig: ChainConfig,
|
|
1205
|
+
state: Pick<BeaconStateAllForks, "genesisValidatorsRoot">
|
|
1206
|
+
): EpochCacheImmutableData {
|
|
1207
|
+
return {
|
|
1208
|
+
config: createBeaconConfig(chainConfig, state.genesisValidatorsRoot),
|
|
1209
|
+
// This is a test state, there's no need to have a global shared cache of keys
|
|
1210
|
+
pubkey2index: new PubkeyIndexMap(),
|
|
1211
|
+
index2pubkey: [],
|
|
1212
|
+
};
|
|
1213
|
+
}
|