@lodestar/state-transition 1.35.0-dev.5e2a80008e → 1.35.0-dev.643707ecd3
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 +3 -3
- package/lib/util/blindedBlock.d.ts.map +1 -0
- package/lib/util/blindedBlock.js.map +1 -1
- package/lib/util/blockRoot.d.ts.map +1 -0
- package/lib/util/calculateCommitteeAssignments.d.ts.map +1 -0
- package/lib/util/capella.d.ts.map +1 -0
- package/lib/util/computeAnchorCheckpoint.d.ts.map +1 -0
- package/lib/util/deposit.d.ts.map +1 -0
- package/lib/util/domain.d.ts.map +1 -0
- package/lib/util/electra.d.ts.map +1 -0
- package/lib/util/epoch.d.ts.map +1 -0
- package/lib/util/epochShuffling.d.ts.map +1 -0
- package/lib/util/execution.d.ts +2 -2
- package/lib/util/execution.d.ts.map +1 -0
- package/lib/util/execution.js.map +1 -1
- package/lib/util/finality.d.ts.map +1 -0
- package/lib/util/fulu.d.ts.map +1 -0
- package/lib/util/genesis.d.ts.map +1 -0
- package/lib/util/genesis.js +0 -3
- package/lib/util/genesis.js.map +1 -1
- package/lib/util/index.d.ts +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 +145 -0
- package/src/util/blockRoot.ts +72 -0
- package/src/util/calculateCommitteeAssignments.ts +43 -0
- package/src/util/capella.ts +8 -0
- package/src/util/computeAnchorCheckpoint.ts +38 -0
- package/src/util/deposit.ts +22 -0
- package/src/util/domain.ts +31 -0
- package/src/util/electra.ts +68 -0
- package/src/util/epoch.ts +135 -0
- package/src/util/epochShuffling.ts +185 -0
- package/src/util/execution.ts +179 -0
- package/src/util/finality.ts +17 -0
- package/src/util/fulu.ts +43 -0
- package/src/util/genesis.ts +340 -0
- package/src/util/index.ts +29 -0
- package/src/util/interop.ts +22 -0
- package/src/util/loadState/findModifiedInactivityScores.ts +47 -0
- package/src/util/loadState/findModifiedValidators.ts +46 -0
- package/src/util/loadState/index.ts +2 -0
- package/src/util/loadState/loadState.ts +225 -0
- package/src/util/loadState/loadValidator.ts +77 -0
- package/src/util/rootCache.ts +37 -0
- package/src/util/seed.ts +381 -0
- package/src/util/shufflingDecisionRoot.ts +78 -0
- package/src/util/signatureSets.ts +65 -0
- package/src/util/signingRoot.ts +13 -0
- package/src/util/slot.ts +22 -0
- package/src/util/sszBytes.ts +52 -0
- package/src/util/syncCommittee.ts +69 -0
- package/src/util/targetUnslashedBalance.ts +30 -0
- package/src/util/validator.ts +105 -0
- package/src/util/weakSubjectivity.ts +186 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {ForkSeq} from "@lodestar/params";
|
|
2
|
+
import {phase0, ssz} from "@lodestar/types";
|
|
3
|
+
import {getProposerSlashingSignatureSets} from "../signatureSets/index.js";
|
|
4
|
+
import {CachedBeaconStateAllForks} from "../types.js";
|
|
5
|
+
import {isSlashableValidator} from "../util/index.js";
|
|
6
|
+
import {verifySignatureSet} from "../util/signatureSets.js";
|
|
7
|
+
import {slashValidator} from "./slashValidator.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Process a ProposerSlashing operation. Initiates the exit of a validator, decreases the balance of the slashed
|
|
11
|
+
* validator and increases the block proposer balance.
|
|
12
|
+
*
|
|
13
|
+
* PERF: Work depends on number of ProposerSlashing per block. On regular networks the average is 0 / block.
|
|
14
|
+
*/
|
|
15
|
+
export function processProposerSlashing(
|
|
16
|
+
fork: ForkSeq,
|
|
17
|
+
state: CachedBeaconStateAllForks,
|
|
18
|
+
proposerSlashing: phase0.ProposerSlashing,
|
|
19
|
+
verifySignatures = true
|
|
20
|
+
): void {
|
|
21
|
+
assertValidProposerSlashing(state, proposerSlashing, verifySignatures);
|
|
22
|
+
|
|
23
|
+
slashValidator(fork, state, proposerSlashing.signedHeader1.message.proposerIndex);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function assertValidProposerSlashing(
|
|
27
|
+
state: CachedBeaconStateAllForks,
|
|
28
|
+
proposerSlashing: phase0.ProposerSlashing,
|
|
29
|
+
verifySignatures = true
|
|
30
|
+
): void {
|
|
31
|
+
const header1 = proposerSlashing.signedHeader1.message;
|
|
32
|
+
const header2 = proposerSlashing.signedHeader2.message;
|
|
33
|
+
|
|
34
|
+
// verify header slots match
|
|
35
|
+
if (header1.slot !== header2.slot) {
|
|
36
|
+
throw new Error(`ProposerSlashing slots do not match: slot1=${header1.slot} slot2=${header2.slot}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// verify header proposer indices match
|
|
40
|
+
if (header1.proposerIndex !== header2.proposerIndex) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`ProposerSlashing proposer indices do not match: proposerIndex1=${header1.proposerIndex} proposerIndex2=${header2.proposerIndex}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// verify headers are different
|
|
47
|
+
if (ssz.phase0.BeaconBlockHeaderBigint.equals(header1, header2)) {
|
|
48
|
+
throw new Error("ProposerSlashing headers are equal");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// verify the proposer is slashable
|
|
52
|
+
const proposer = state.validators.getReadonly(header1.proposerIndex);
|
|
53
|
+
if (!isSlashableValidator(proposer, state.epochCtx.epoch)) {
|
|
54
|
+
throw new Error("ProposerSlashing proposer is not slashable");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// verify signatures
|
|
58
|
+
if (verifySignatures) {
|
|
59
|
+
const signatureSets = getProposerSlashingSignatureSets(state, proposerSlashing);
|
|
60
|
+
for (let i = 0; i < signatureSets.length; i++) {
|
|
61
|
+
if (!verifySignatureSet(signatureSets[i])) {
|
|
62
|
+
throw new Error(`ProposerSlashing header${i + 1} signature invalid`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {digest} from "@chainsafe/as-sha256";
|
|
2
|
+
import {EPOCHS_PER_HISTORICAL_VECTOR} from "@lodestar/params";
|
|
3
|
+
import {BeaconBlock} from "@lodestar/types";
|
|
4
|
+
import {xor} from "@lodestar/utils";
|
|
5
|
+
import {verifyRandaoSignature} from "../signatureSets/index.js";
|
|
6
|
+
import {CachedBeaconStateAllForks} from "../types.js";
|
|
7
|
+
import {getRandaoMix} from "../util/index.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Commit a randao reveal to generate pseudorandomness seeds
|
|
11
|
+
*
|
|
12
|
+
* PERF: Fixed work independent of block contents.
|
|
13
|
+
*/
|
|
14
|
+
export function processRandao(state: CachedBeaconStateAllForks, block: BeaconBlock, verifySignature = true): void {
|
|
15
|
+
const {epochCtx} = state;
|
|
16
|
+
const epoch = epochCtx.epoch;
|
|
17
|
+
const randaoReveal = block.body.randaoReveal;
|
|
18
|
+
|
|
19
|
+
// verify RANDAO reveal
|
|
20
|
+
if (verifySignature && !verifyRandaoSignature(state, block)) {
|
|
21
|
+
throw new Error("RANDAO reveal is an invalid signature");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// mix in RANDAO reveal
|
|
25
|
+
const randaoMix = xor(getRandaoMix(state, epoch), digest(randaoReveal));
|
|
26
|
+
state.randaoMixes.set(epoch % EPOCHS_PER_HISTORICAL_VECTOR, randaoMix);
|
|
27
|
+
}
|
|
@@ -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
|
+
}
|