@lodestar/state-transition 1.35.0-dev.8ea34e52ba → 1.35.0-dev.901d719660
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 +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/processExecutionPayload.js +3 -3
- package/lib/block/processExecutionPayload.js.map +1 -1
- 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/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/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 +0 -3
- 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/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 +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/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,117 @@
|
|
|
1
|
+
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
2
|
+
import {DOMAIN_SYNC_COMMITTEE, SYNC_COMMITTEE_SIZE} from "@lodestar/params";
|
|
3
|
+
import {altair, ssz} from "@lodestar/types";
|
|
4
|
+
import {G2_POINT_AT_INFINITY} from "../constants/index.js";
|
|
5
|
+
import {CachedBeaconStateAllForks} from "../types.js";
|
|
6
|
+
import {
|
|
7
|
+
ISignatureSet,
|
|
8
|
+
SignatureSetType,
|
|
9
|
+
computeSigningRoot,
|
|
10
|
+
decreaseBalance,
|
|
11
|
+
increaseBalance,
|
|
12
|
+
verifySignatureSet,
|
|
13
|
+
} from "../util/index.js";
|
|
14
|
+
|
|
15
|
+
export function processSyncAggregate(
|
|
16
|
+
state: CachedBeaconStateAllForks,
|
|
17
|
+
block: altair.BeaconBlock,
|
|
18
|
+
verifySignatures = true
|
|
19
|
+
): void {
|
|
20
|
+
const committeeIndices = state.epochCtx.currentSyncCommitteeIndexed.validatorIndices;
|
|
21
|
+
|
|
22
|
+
// different from the spec but not sure how to get through signature verification for default/empty SyncAggregate in the spec test
|
|
23
|
+
if (verifySignatures) {
|
|
24
|
+
// This is to conform to the spec - we want the signature to be verified
|
|
25
|
+
const participantIndices = block.body.syncAggregate.syncCommitteeBits.intersectValues(committeeIndices);
|
|
26
|
+
const signatureSet = getSyncCommitteeSignatureSet(state, block, participantIndices);
|
|
27
|
+
// When there's no participation we consider the signature valid and just ignore i
|
|
28
|
+
if (signatureSet !== null && !verifySignatureSet(signatureSet)) {
|
|
29
|
+
throw Error("Sync committee signature invalid");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const {syncParticipantReward, syncProposerReward} = state.epochCtx;
|
|
34
|
+
const {syncCommitteeBits} = block.body.syncAggregate;
|
|
35
|
+
const proposerIndex = state.epochCtx.getBeaconProposer(state.slot);
|
|
36
|
+
let proposerBalance = state.balances.get(proposerIndex);
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < SYNC_COMMITTEE_SIZE; i++) {
|
|
39
|
+
const index = committeeIndices[i];
|
|
40
|
+
|
|
41
|
+
if (syncCommitteeBits.get(i)) {
|
|
42
|
+
// Positive rewards for participants
|
|
43
|
+
if (index === proposerIndex) {
|
|
44
|
+
proposerBalance += syncParticipantReward;
|
|
45
|
+
} else {
|
|
46
|
+
increaseBalance(state, index, syncParticipantReward);
|
|
47
|
+
}
|
|
48
|
+
// Proposer reward
|
|
49
|
+
proposerBalance += syncProposerReward;
|
|
50
|
+
state.proposerRewards.syncAggregate += syncProposerReward;
|
|
51
|
+
} else {
|
|
52
|
+
// Negative rewards for non participants
|
|
53
|
+
if (index === proposerIndex) {
|
|
54
|
+
proposerBalance = Math.max(0, proposerBalance - syncParticipantReward);
|
|
55
|
+
} else {
|
|
56
|
+
decreaseBalance(state, index, syncParticipantReward);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Apply proposer balance
|
|
62
|
+
state.balances.set(proposerIndex, proposerBalance);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getSyncCommitteeSignatureSet(
|
|
66
|
+
state: CachedBeaconStateAllForks,
|
|
67
|
+
block: altair.BeaconBlock,
|
|
68
|
+
/** Optional parameter to prevent computing it twice */
|
|
69
|
+
participantIndices?: number[]
|
|
70
|
+
): ISignatureSet | null {
|
|
71
|
+
const {epochCtx} = state;
|
|
72
|
+
const {syncAggregate} = block.body;
|
|
73
|
+
const signature = syncAggregate.syncCommitteeSignature;
|
|
74
|
+
|
|
75
|
+
// The spec uses the state to get the previous slot
|
|
76
|
+
// ```python
|
|
77
|
+
// previous_slot = max(state.slot, Slot(1)) - Slot(1)
|
|
78
|
+
// ```
|
|
79
|
+
// However we need to run the function getSyncCommitteeSignatureSet() for all the blocks in a epoch
|
|
80
|
+
// with the same state when verifying blocks in batch on RangeSync. Therefore we use the block.slot.
|
|
81
|
+
const previousSlot = Math.max(block.slot, 1) - 1;
|
|
82
|
+
|
|
83
|
+
// The spec uses the state to get the root at previousSlot
|
|
84
|
+
// ```python
|
|
85
|
+
// get_block_root_at_slot(state, previous_slot)
|
|
86
|
+
// ```
|
|
87
|
+
// However we need to run the function getSyncCommitteeSignatureSet() for all the blocks in a epoch
|
|
88
|
+
// with the same state when verifying blocks in batch on RangeSync.
|
|
89
|
+
//
|
|
90
|
+
// On skipped slots state block roots just copy the latest block, so using the parentRoot here is equivalent.
|
|
91
|
+
// So getSyncCommitteeSignatureSet() can be called with a state in any slot (with the correct shuffling)
|
|
92
|
+
const rootSigned = block.parentRoot;
|
|
93
|
+
|
|
94
|
+
if (!participantIndices) {
|
|
95
|
+
const committeeIndices = state.epochCtx.currentSyncCommitteeIndexed.validatorIndices;
|
|
96
|
+
participantIndices = syncAggregate.syncCommitteeBits.intersectValues(committeeIndices);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// When there's no participation we consider the signature valid and just ignore it
|
|
100
|
+
if (participantIndices.length === 0) {
|
|
101
|
+
// Must set signature as G2_POINT_AT_INFINITY when participating bits are empty
|
|
102
|
+
// https://github.com/ethereum/eth2.0-specs/blob/30f2a076377264677e27324a8c3c78c590ae5e20/specs/altair/bls.md#eth2_fast_aggregate_verify
|
|
103
|
+
if (byteArrayEquals(signature, G2_POINT_AT_INFINITY)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
throw Error("Empty sync committee signature is not infinity");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const domain = state.config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, previousSlot);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
type: SignatureSetType.aggregate,
|
|
113
|
+
pubkeys: participantIndices.map((i) => epochCtx.index2pubkey[i]),
|
|
114
|
+
signingRoot: computeSigningRoot(ssz.Root, rootSigned, domain),
|
|
115
|
+
signature,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params";
|
|
2
|
+
import {phase0} from "@lodestar/types";
|
|
3
|
+
import {verifyVoluntaryExitSignature} from "../signatureSets/index.js";
|
|
4
|
+
import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js";
|
|
5
|
+
import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/index.js";
|
|
6
|
+
import {initiateValidatorExit} from "./index.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Process a VoluntaryExit operation. Initiates the exit of a validator.
|
|
10
|
+
*
|
|
11
|
+
* PERF: Work depends on number of VoluntaryExit per block. On regular networks the average is 0 / block.
|
|
12
|
+
*/
|
|
13
|
+
export function processVoluntaryExit(
|
|
14
|
+
fork: ForkSeq,
|
|
15
|
+
state: CachedBeaconStateAllForks,
|
|
16
|
+
signedVoluntaryExit: phase0.SignedVoluntaryExit,
|
|
17
|
+
verifySignature = true
|
|
18
|
+
): void {
|
|
19
|
+
if (!isValidVoluntaryExit(fork, state, signedVoluntaryExit, verifySignature)) {
|
|
20
|
+
throw Error(`Invalid voluntary exit at forkSeq=${fork}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validator = state.validators.get(signedVoluntaryExit.message.validatorIndex);
|
|
24
|
+
initiateValidatorExit(fork, state, validator);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isValidVoluntaryExit(
|
|
28
|
+
fork: ForkSeq,
|
|
29
|
+
state: CachedBeaconStateAllForks,
|
|
30
|
+
signedVoluntaryExit: phase0.SignedVoluntaryExit,
|
|
31
|
+
verifySignature = true
|
|
32
|
+
): boolean {
|
|
33
|
+
const {config, epochCtx} = state;
|
|
34
|
+
const voluntaryExit = signedVoluntaryExit.message;
|
|
35
|
+
const validator = state.validators.get(voluntaryExit.validatorIndex);
|
|
36
|
+
const currentEpoch = epochCtx.epoch;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
// verify the validator is active
|
|
40
|
+
isActiveValidator(validator, currentEpoch) &&
|
|
41
|
+
// verify exit has not been initiated
|
|
42
|
+
validator.exitEpoch === FAR_FUTURE_EPOCH &&
|
|
43
|
+
// exits must specify an epoch when they become valid; they are not valid before then
|
|
44
|
+
currentEpoch >= voluntaryExit.epoch &&
|
|
45
|
+
// verify the validator had been active long enough
|
|
46
|
+
currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD &&
|
|
47
|
+
(fork >= ForkSeq.electra
|
|
48
|
+
? // only exit validator if it has no pending withdrawals in the queue
|
|
49
|
+
getPendingBalanceToWithdraw(state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) === 0
|
|
50
|
+
: // there are no pending withdrawals in previous forks
|
|
51
|
+
true) &&
|
|
52
|
+
// verify signature
|
|
53
|
+
(!verifySignature || verifyVoluntaryExitSignature(state, signedVoluntaryExit))
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FAR_FUTURE_EPOCH,
|
|
3
|
+
FULL_EXIT_REQUEST_AMOUNT,
|
|
4
|
+
ForkSeq,
|
|
5
|
+
MIN_ACTIVATION_BALANCE,
|
|
6
|
+
PENDING_PARTIAL_WITHDRAWALS_LIMIT,
|
|
7
|
+
} from "@lodestar/params";
|
|
8
|
+
import {electra, phase0, ssz} from "@lodestar/types";
|
|
9
|
+
import {toHex} from "@lodestar/utils";
|
|
10
|
+
import {CachedBeaconStateElectra} from "../types.js";
|
|
11
|
+
import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js";
|
|
12
|
+
import {computeExitEpochAndUpdateChurn} from "../util/epoch.js";
|
|
13
|
+
import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js";
|
|
14
|
+
import {initiateValidatorExit} from "./initiateValidatorExit.js";
|
|
15
|
+
|
|
16
|
+
export function processWithdrawalRequest(
|
|
17
|
+
fork: ForkSeq,
|
|
18
|
+
state: CachedBeaconStateElectra,
|
|
19
|
+
withdrawalRequest: electra.WithdrawalRequest
|
|
20
|
+
): void {
|
|
21
|
+
const amount = Number(withdrawalRequest.amount);
|
|
22
|
+
const {pendingPartialWithdrawals, validators, epochCtx} = state;
|
|
23
|
+
// no need to use unfinalized pubkey cache from 6110 as validator won't be active anyway
|
|
24
|
+
const {pubkey2index, config} = epochCtx;
|
|
25
|
+
const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT;
|
|
26
|
+
|
|
27
|
+
// If partial withdrawal queue is full, only full exits are processed
|
|
28
|
+
if (pendingPartialWithdrawals.length >= PENDING_PARTIAL_WITHDRAWALS_LIMIT && !isFullExitRequest) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// bail out if validator is not in beacon state
|
|
33
|
+
// note that we don't need to check for 6110 unfinalized vals as they won't be eligible for withdraw/exit anyway
|
|
34
|
+
const validatorIndex = pubkey2index.get(withdrawalRequest.validatorPubkey);
|
|
35
|
+
if (validatorIndex === null) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const validator = validators.get(validatorIndex);
|
|
40
|
+
if (!isValidatorEligibleForWithdrawOrExit(validator, withdrawalRequest.sourceAddress, state)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TODO Electra: Consider caching pendingPartialWithdrawals
|
|
45
|
+
const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(state, validatorIndex);
|
|
46
|
+
const validatorBalance = state.balances.get(validatorIndex);
|
|
47
|
+
|
|
48
|
+
if (isFullExitRequest) {
|
|
49
|
+
// only exit validator if it has no pending withdrawals in the queue
|
|
50
|
+
if (pendingBalanceToWithdraw === 0) {
|
|
51
|
+
initiateValidatorExit(fork, state, validator);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// partial withdrawal request
|
|
57
|
+
const hasSufficientEffectiveBalance = validator.effectiveBalance >= MIN_ACTIVATION_BALANCE;
|
|
58
|
+
const hasExcessBalance = validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw;
|
|
59
|
+
|
|
60
|
+
// Only allow partial withdrawals with compounding withdrawal credentials
|
|
61
|
+
if (
|
|
62
|
+
hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) &&
|
|
63
|
+
hasSufficientEffectiveBalance &&
|
|
64
|
+
hasExcessBalance
|
|
65
|
+
) {
|
|
66
|
+
const amountToWithdraw = BigInt(
|
|
67
|
+
Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount)
|
|
68
|
+
);
|
|
69
|
+
const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, amountToWithdraw);
|
|
70
|
+
const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
|
|
71
|
+
|
|
72
|
+
const pendingPartialWithdrawal = ssz.electra.PendingPartialWithdrawal.toViewDU({
|
|
73
|
+
validatorIndex,
|
|
74
|
+
amount: amountToWithdraw,
|
|
75
|
+
withdrawableEpoch,
|
|
76
|
+
});
|
|
77
|
+
state.pendingPartialWithdrawals.push(pendingPartialWithdrawal);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isValidatorEligibleForWithdrawOrExit(
|
|
82
|
+
validator: phase0.Validator,
|
|
83
|
+
sourceAddress: Uint8Array,
|
|
84
|
+
state: CachedBeaconStateElectra
|
|
85
|
+
): boolean {
|
|
86
|
+
const {withdrawalCredentials} = validator;
|
|
87
|
+
const addressStr = toHex(withdrawalCredentials.subarray(12));
|
|
88
|
+
const sourceAddressStr = toHex(sourceAddress);
|
|
89
|
+
const {epoch: currentEpoch, config} = state.epochCtx;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
hasExecutionWithdrawalCredential(withdrawalCredentials) &&
|
|
93
|
+
addressStr === sourceAddressStr &&
|
|
94
|
+
isActiveValidator(validator, currentEpoch) &&
|
|
95
|
+
validator.exitEpoch === FAR_FUTURE_EPOCH &&
|
|
96
|
+
currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
2
|
+
import {
|
|
3
|
+
FAR_FUTURE_EPOCH,
|
|
4
|
+
ForkSeq,
|
|
5
|
+
MAX_EFFECTIVE_BALANCE,
|
|
6
|
+
MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
|
|
7
|
+
MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP,
|
|
8
|
+
MAX_WITHDRAWALS_PER_PAYLOAD,
|
|
9
|
+
MIN_ACTIVATION_BALANCE,
|
|
10
|
+
} from "@lodestar/params";
|
|
11
|
+
import {ValidatorIndex, capella, ssz} from "@lodestar/types";
|
|
12
|
+
import {MapDef, toRootHex} from "@lodestar/utils";
|
|
13
|
+
import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js";
|
|
14
|
+
import {
|
|
15
|
+
decreaseBalance,
|
|
16
|
+
getMaxEffectiveBalance,
|
|
17
|
+
hasEth1WithdrawalCredential,
|
|
18
|
+
hasExecutionWithdrawalCredential,
|
|
19
|
+
isCapellaPayloadHeader,
|
|
20
|
+
} from "../util/index.js";
|
|
21
|
+
|
|
22
|
+
export function processWithdrawals(
|
|
23
|
+
fork: ForkSeq,
|
|
24
|
+
state: CachedBeaconStateCapella | CachedBeaconStateElectra,
|
|
25
|
+
payload: capella.FullOrBlindedExecutionPayload
|
|
26
|
+
): void {
|
|
27
|
+
// processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
|
|
28
|
+
const {withdrawals: expectedWithdrawals, processedPartialWithdrawalsCount} = getExpectedWithdrawals(fork, state);
|
|
29
|
+
const numWithdrawals = expectedWithdrawals.length;
|
|
30
|
+
|
|
31
|
+
if (isCapellaPayloadHeader(payload)) {
|
|
32
|
+
const expectedWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(expectedWithdrawals);
|
|
33
|
+
const actualWithdrawalsRoot = payload.withdrawalsRoot;
|
|
34
|
+
if (!byteArrayEquals(expectedWithdrawalsRoot, actualWithdrawalsRoot)) {
|
|
35
|
+
throw Error(
|
|
36
|
+
`Invalid withdrawalsRoot of executionPayloadHeader, expected=${toRootHex(
|
|
37
|
+
expectedWithdrawalsRoot
|
|
38
|
+
)}, actual=${toRootHex(actualWithdrawalsRoot)}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
if (expectedWithdrawals.length !== payload.withdrawals.length) {
|
|
43
|
+
throw Error(`Invalid withdrawals length expected=${numWithdrawals} actual=${payload.withdrawals.length}`);
|
|
44
|
+
}
|
|
45
|
+
for (let i = 0; i < numWithdrawals; i++) {
|
|
46
|
+
const withdrawal = expectedWithdrawals[i];
|
|
47
|
+
if (!ssz.capella.Withdrawal.equals(withdrawal, payload.withdrawals[i])) {
|
|
48
|
+
throw Error(`Withdrawal mismatch at index=${i}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < numWithdrawals; i++) {
|
|
54
|
+
const withdrawal = expectedWithdrawals[i];
|
|
55
|
+
decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (fork >= ForkSeq.electra) {
|
|
59
|
+
const stateElectra = state as CachedBeaconStateElectra;
|
|
60
|
+
stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom(
|
|
61
|
+
processedPartialWithdrawalsCount
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Update the nextWithdrawalIndex
|
|
66
|
+
const latestWithdrawal = expectedWithdrawals.at(-1);
|
|
67
|
+
if (latestWithdrawal) {
|
|
68
|
+
state.nextWithdrawalIndex = latestWithdrawal.index + 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Update the nextWithdrawalValidatorIndex
|
|
72
|
+
if (latestWithdrawal && expectedWithdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) {
|
|
73
|
+
// All slots filled, nextWithdrawalValidatorIndex should be validatorIndex having next turn
|
|
74
|
+
state.nextWithdrawalValidatorIndex = (latestWithdrawal.validatorIndex + 1) % state.validators.length;
|
|
75
|
+
} else {
|
|
76
|
+
// expected withdrawals came up short in the bound, so we move nextWithdrawalValidatorIndex to
|
|
77
|
+
// the next post the bound
|
|
78
|
+
state.nextWithdrawalValidatorIndex =
|
|
79
|
+
(state.nextWithdrawalValidatorIndex + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) % state.validators.length;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getExpectedWithdrawals(
|
|
84
|
+
fork: ForkSeq,
|
|
85
|
+
state: CachedBeaconStateCapella | CachedBeaconStateElectra
|
|
86
|
+
): {
|
|
87
|
+
withdrawals: capella.Withdrawal[];
|
|
88
|
+
sampledValidators: number;
|
|
89
|
+
processedPartialWithdrawalsCount: number;
|
|
90
|
+
} {
|
|
91
|
+
if (fork < ForkSeq.capella) {
|
|
92
|
+
throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const epoch = state.epochCtx.epoch;
|
|
96
|
+
let withdrawalIndex = state.nextWithdrawalIndex;
|
|
97
|
+
const {validators, balances, nextWithdrawalValidatorIndex} = state;
|
|
98
|
+
|
|
99
|
+
const withdrawals: capella.Withdrawal[] = [];
|
|
100
|
+
const withdrawnBalances = new MapDef<ValidatorIndex, number>(() => 0);
|
|
101
|
+
const isPostElectra = fork >= ForkSeq.electra;
|
|
102
|
+
// partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
|
|
103
|
+
let processedPartialWithdrawalsCount = 0;
|
|
104
|
+
|
|
105
|
+
if (isPostElectra) {
|
|
106
|
+
const stateElectra = state as CachedBeaconStateElectra;
|
|
107
|
+
|
|
108
|
+
// MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should only call getAllReadonly() if it makes sense
|
|
109
|
+
// pendingPartialWithdrawals comes from EIP-7002 smart contract where it takes fee so it's more likely than not validator is in correct condition to withdraw
|
|
110
|
+
// also we may break early if withdrawableEpoch > epoch
|
|
111
|
+
const allPendingPartialWithdrawals =
|
|
112
|
+
stateElectra.pendingPartialWithdrawals.length <= MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP
|
|
113
|
+
? stateElectra.pendingPartialWithdrawals.getAllReadonly()
|
|
114
|
+
: null;
|
|
115
|
+
|
|
116
|
+
// EIP-7002: Execution layer triggerable withdrawals
|
|
117
|
+
for (let i = 0; i < stateElectra.pendingPartialWithdrawals.length; i++) {
|
|
118
|
+
const withdrawal = allPendingPartialWithdrawals
|
|
119
|
+
? allPendingPartialWithdrawals[i]
|
|
120
|
+
: stateElectra.pendingPartialWithdrawals.getReadonly(i);
|
|
121
|
+
if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const validator = validators.getReadonly(withdrawal.validatorIndex);
|
|
126
|
+
const totalWithdrawn = withdrawnBalances.getOrDefault(withdrawal.validatorIndex);
|
|
127
|
+
const balance = state.balances.get(withdrawal.validatorIndex) - totalWithdrawn;
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
validator.exitEpoch === FAR_FUTURE_EPOCH &&
|
|
131
|
+
validator.effectiveBalance >= MIN_ACTIVATION_BALANCE &&
|
|
132
|
+
balance > MIN_ACTIVATION_BALANCE
|
|
133
|
+
) {
|
|
134
|
+
const balanceOverMinActivationBalance = BigInt(balance - MIN_ACTIVATION_BALANCE);
|
|
135
|
+
const withdrawableBalance =
|
|
136
|
+
balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount;
|
|
137
|
+
withdrawals.push({
|
|
138
|
+
index: withdrawalIndex,
|
|
139
|
+
validatorIndex: withdrawal.validatorIndex,
|
|
140
|
+
address: validator.withdrawalCredentials.subarray(12),
|
|
141
|
+
amount: withdrawableBalance,
|
|
142
|
+
});
|
|
143
|
+
withdrawalIndex++;
|
|
144
|
+
withdrawnBalances.set(withdrawal.validatorIndex, totalWithdrawn + Number(withdrawableBalance));
|
|
145
|
+
}
|
|
146
|
+
processedPartialWithdrawalsCount++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const bound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP);
|
|
151
|
+
let n = 0;
|
|
152
|
+
// Just run a bounded loop max iterating over all withdrawals
|
|
153
|
+
// however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD
|
|
154
|
+
for (n = 0; n < bound; n++) {
|
|
155
|
+
// Get next validator in turn
|
|
156
|
+
const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length;
|
|
157
|
+
|
|
158
|
+
const validator = validators.getReadonly(validatorIndex);
|
|
159
|
+
const withdrawnBalance = withdrawnBalances.getOrDefault(validatorIndex);
|
|
160
|
+
const balance = isPostElectra
|
|
161
|
+
? // Deduct partially withdrawn balance already queued above
|
|
162
|
+
balances.get(validatorIndex) - withdrawnBalance
|
|
163
|
+
: balances.get(validatorIndex);
|
|
164
|
+
const {withdrawableEpoch, withdrawalCredentials, effectiveBalance} = validator;
|
|
165
|
+
const hasWithdrawableCredentials = isPostElectra
|
|
166
|
+
? hasExecutionWithdrawalCredential(withdrawalCredentials)
|
|
167
|
+
: hasEth1WithdrawalCredential(withdrawalCredentials);
|
|
168
|
+
// early skip for balance = 0 as its now more likely that validator has exited/slashed with
|
|
169
|
+
// balance zero than not have withdrawal credentials set
|
|
170
|
+
if (balance === 0 || !hasWithdrawableCredentials) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// capella full withdrawal
|
|
175
|
+
if (withdrawableEpoch <= epoch) {
|
|
176
|
+
withdrawals.push({
|
|
177
|
+
index: withdrawalIndex,
|
|
178
|
+
validatorIndex,
|
|
179
|
+
address: validator.withdrawalCredentials.subarray(12),
|
|
180
|
+
amount: BigInt(balance),
|
|
181
|
+
});
|
|
182
|
+
withdrawalIndex++;
|
|
183
|
+
withdrawnBalances.set(validatorIndex, withdrawnBalance + balance);
|
|
184
|
+
} else if (
|
|
185
|
+
effectiveBalance === (isPostElectra ? getMaxEffectiveBalance(withdrawalCredentials) : MAX_EFFECTIVE_BALANCE) &&
|
|
186
|
+
balance > effectiveBalance
|
|
187
|
+
) {
|
|
188
|
+
// capella partial withdrawal
|
|
189
|
+
const partialAmount = balance - effectiveBalance;
|
|
190
|
+
withdrawals.push({
|
|
191
|
+
index: withdrawalIndex,
|
|
192
|
+
validatorIndex,
|
|
193
|
+
address: validator.withdrawalCredentials.subarray(12),
|
|
194
|
+
amount: BigInt(partialAmount),
|
|
195
|
+
});
|
|
196
|
+
withdrawalIndex++;
|
|
197
|
+
withdrawnBalances.set(validatorIndex, withdrawnBalance + partialAmount);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Break if we have enough to pack the block
|
|
201
|
+
if (withdrawals.length >= MAX_WITHDRAWALS_PER_PAYLOAD) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount};
|
|
207
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
3
|
+
EPOCHS_PER_SLASHINGS_VECTOR,
|
|
4
|
+
ForkSeq,
|
|
5
|
+
MIN_SLASHING_PENALTY_QUOTIENT,
|
|
6
|
+
MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR,
|
|
7
|
+
MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX,
|
|
8
|
+
MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA,
|
|
9
|
+
PROPOSER_REWARD_QUOTIENT,
|
|
10
|
+
PROPOSER_WEIGHT,
|
|
11
|
+
TIMELY_TARGET_FLAG_INDEX,
|
|
12
|
+
WEIGHT_DENOMINATOR,
|
|
13
|
+
WHISTLEBLOWER_REWARD_QUOTIENT,
|
|
14
|
+
WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA,
|
|
15
|
+
} from "@lodestar/params";
|
|
16
|
+
import {ValidatorIndex} from "@lodestar/types";
|
|
17
|
+
import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "../types.js";
|
|
18
|
+
import {decreaseBalance, increaseBalance} from "../util/index.js";
|
|
19
|
+
import {initiateValidatorExit} from "./initiateValidatorExit.js";
|
|
20
|
+
|
|
21
|
+
/** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */
|
|
22
|
+
const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX;
|
|
23
|
+
|
|
24
|
+
export function slashValidator(
|
|
25
|
+
fork: ForkSeq,
|
|
26
|
+
state: CachedBeaconStateAllForks,
|
|
27
|
+
slashedIndex: ValidatorIndex,
|
|
28
|
+
whistleblowerIndex?: ValidatorIndex
|
|
29
|
+
): void {
|
|
30
|
+
const {epochCtx} = state;
|
|
31
|
+
const {epoch, effectiveBalanceIncrements} = epochCtx;
|
|
32
|
+
const validator = state.validators.get(slashedIndex);
|
|
33
|
+
|
|
34
|
+
// TODO: Bellatrix initiateValidatorExit validators.update() with the one below
|
|
35
|
+
initiateValidatorExit(fork, state, validator);
|
|
36
|
+
|
|
37
|
+
validator.slashed = true;
|
|
38
|
+
validator.withdrawableEpoch = Math.max(validator.withdrawableEpoch, epoch + EPOCHS_PER_SLASHINGS_VECTOR);
|
|
39
|
+
|
|
40
|
+
const {effectiveBalance} = validator;
|
|
41
|
+
|
|
42
|
+
// state.slashings is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because:
|
|
43
|
+
// - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset()
|
|
44
|
+
// - max slashed validators per epoch is SLOTS_PER_EPOCH * MAX_ATTESTER_SLASHINGS * MAX_VALIDATORS_PER_COMMITTEE which is 32 * 2 * 2048 = 131072 on mainnet
|
|
45
|
+
// - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE or 2048_000_000_000 MAX_EFFECTIVE_BALANCE_ELECTRA, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474
|
|
46
|
+
// - we don't need to compute the total slashings from state.slashings, it's handled by totalSlashingsByIncrement in EpochCache
|
|
47
|
+
const slashingIndex = epoch % EPOCHS_PER_SLASHINGS_VECTOR;
|
|
48
|
+
state.slashings.set(slashingIndex, (state.slashings.get(slashingIndex) ?? 0) + effectiveBalance);
|
|
49
|
+
epochCtx.totalSlashingsByIncrement += effectiveBalanceIncrements[slashedIndex];
|
|
50
|
+
|
|
51
|
+
const minSlashingPenaltyQuotient =
|
|
52
|
+
fork === ForkSeq.phase0
|
|
53
|
+
? MIN_SLASHING_PENALTY_QUOTIENT
|
|
54
|
+
: fork === ForkSeq.altair
|
|
55
|
+
? MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR
|
|
56
|
+
: fork < ForkSeq.electra // no change from bellatrix to deneb
|
|
57
|
+
? MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX
|
|
58
|
+
: MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA;
|
|
59
|
+
decreaseBalance(state, slashedIndex, Math.floor(effectiveBalance / minSlashingPenaltyQuotient));
|
|
60
|
+
|
|
61
|
+
// apply proposer and whistleblower rewards
|
|
62
|
+
const whistleblowerReward =
|
|
63
|
+
fork < ForkSeq.electra
|
|
64
|
+
? Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT)
|
|
65
|
+
: Math.floor(effectiveBalance / WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA);
|
|
66
|
+
const proposerReward =
|
|
67
|
+
fork === ForkSeq.phase0
|
|
68
|
+
? Math.floor(whistleblowerReward / PROPOSER_REWARD_QUOTIENT)
|
|
69
|
+
: Math.floor((whistleblowerReward * PROPOSER_WEIGHT) / WEIGHT_DENOMINATOR);
|
|
70
|
+
|
|
71
|
+
const proposerIndex = epochCtx.getBeaconProposer(state.slot);
|
|
72
|
+
if (whistleblowerIndex === undefined || !Number.isSafeInteger(whistleblowerIndex)) {
|
|
73
|
+
// Call increaseBalance() once with `(whistleblowerReward - proposerReward) + proposerReward`
|
|
74
|
+
increaseBalance(state, proposerIndex, whistleblowerReward);
|
|
75
|
+
state.proposerRewards.slashing += whistleblowerReward;
|
|
76
|
+
} else {
|
|
77
|
+
increaseBalance(state, proposerIndex, proposerReward);
|
|
78
|
+
increaseBalance(state, whistleblowerIndex, whistleblowerReward - proposerReward);
|
|
79
|
+
state.proposerRewards.slashing += proposerReward;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// TODO: describe issue. Compute progressive target balances
|
|
83
|
+
// if a validator is slashed, lookup their participation and remove from the cumulative values
|
|
84
|
+
if (fork >= ForkSeq.altair) {
|
|
85
|
+
const {previousEpochParticipation, currentEpochParticipation} = state as CachedBeaconStateAltair;
|
|
86
|
+
|
|
87
|
+
if ((previousEpochParticipation.get(slashedIndex) & TIMELY_TARGET) === TIMELY_TARGET) {
|
|
88
|
+
state.epochCtx.previousTargetUnslashedBalanceIncrements -= Math.floor(
|
|
89
|
+
effectiveBalance / EFFECTIVE_BALANCE_INCREMENT
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if ((currentEpochParticipation.get(slashedIndex) & TIMELY_TARGET) === TIMELY_TARGET) {
|
|
93
|
+
state.epochCtx.currentTargetUnslashedBalanceIncrements -= Math.floor(
|
|
94
|
+
effectiveBalance / EFFECTIVE_BALANCE_INCREMENT
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {EFFECTIVE_BALANCE_INCREMENT} from "@lodestar/params";
|
|
2
|
+
import {BeaconStateAllForks} from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Alias to allow easier refactoring.
|
|
6
|
+
*/
|
|
7
|
+
export type EffectiveBalanceIncrements = Uint16Array;
|
|
8
|
+
|
|
9
|
+
/** Helper to prevent re-writting tests downstream if we change Uint16Array to number[] */
|
|
10
|
+
export function getEffectiveBalanceIncrementsZeroed(len: number): EffectiveBalanceIncrements {
|
|
11
|
+
return new Uint16Array(len);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* effectiveBalanceIncrements length will always be equal or greater than validatorCount. The
|
|
16
|
+
* getEffectiveBalanceIncrementsByteLen() modulo is used to reduce the frequency at which its Uint16Array is recreated.
|
|
17
|
+
* if effectiveBalanceIncrements has length greater than validatorCount it's not a problem since those values would
|
|
18
|
+
* never be accessed.
|
|
19
|
+
*/
|
|
20
|
+
export function getEffectiveBalanceIncrementsWithLen(validatorCount: number): EffectiveBalanceIncrements {
|
|
21
|
+
// TODO: Research what's the best number to minimize both memory cost and copy costs
|
|
22
|
+
const byteLen = 1024 * Math.ceil(validatorCount / 1024);
|
|
23
|
+
|
|
24
|
+
return new Uint16Array(byteLen);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Shows how EffectiveBalanceIncrements is meant to be populated.
|
|
29
|
+
* In practice this function should not be used, since it more efficient to loop the validators array once and do
|
|
30
|
+
* more tasks than only populating effectiveBalanceIncrements
|
|
31
|
+
*/
|
|
32
|
+
export function getEffectiveBalanceIncrements(state: BeaconStateAllForks): EffectiveBalanceIncrements {
|
|
33
|
+
const validatorsArr = state.validators.getAllReadonlyValues();
|
|
34
|
+
const effectiveBalanceIncrements = new Uint16Array(validatorsArr.length);
|
|
35
|
+
for (let i = 0; i < validatorsArr.length; i++) {
|
|
36
|
+
effectiveBalanceIncrements[i] = Math.floor(validatorsArr[i].effectiveBalance / EFFECTIVE_BALANCE_INCREMENT);
|
|
37
|
+
}
|
|
38
|
+
return effectiveBalanceIncrements;
|
|
39
|
+
}
|