@lodestar/state-transition 1.35.0-dev.6e3c7ca8d0 → 1.35.0-dev.792fcea33a
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,70 @@
|
|
|
1
|
+
import {ForkSeq, MAX_COMMITTEES_PER_SLOT, MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params";
|
|
2
|
+
import {IndexedAttestation, IndexedAttestationBigint} from "@lodestar/types";
|
|
3
|
+
import {getIndexedAttestationBigintSignatureSet, getIndexedAttestationSignatureSet} from "../signatureSets/index.js";
|
|
4
|
+
import {CachedBeaconStateAllForks} from "../types.js";
|
|
5
|
+
import {verifySignatureSet} from "../util/index.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if `indexedAttestation` has sorted and unique indices and a valid aggregate signature.
|
|
9
|
+
*/
|
|
10
|
+
export function isValidIndexedAttestation(
|
|
11
|
+
state: CachedBeaconStateAllForks,
|
|
12
|
+
indexedAttestation: IndexedAttestation,
|
|
13
|
+
verifySignature: boolean
|
|
14
|
+
): boolean {
|
|
15
|
+
if (!isValidIndexedAttestationIndices(state, indexedAttestation.attestingIndices)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (verifySignature) {
|
|
20
|
+
return verifySignatureSet(getIndexedAttestationSignatureSet(state, indexedAttestation));
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isValidIndexedAttestationBigint(
|
|
26
|
+
state: CachedBeaconStateAllForks,
|
|
27
|
+
indexedAttestation: IndexedAttestationBigint,
|
|
28
|
+
verifySignature: boolean
|
|
29
|
+
): boolean {
|
|
30
|
+
if (!isValidIndexedAttestationIndices(state, indexedAttestation.attestingIndices)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (verifySignature) {
|
|
35
|
+
return verifySignatureSet(getIndexedAttestationBigintSignatureSet(state, indexedAttestation));
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if `indexedAttestation` has sorted and unique indices and a valid aggregate signature.
|
|
42
|
+
*/
|
|
43
|
+
export function isValidIndexedAttestationIndices(state: CachedBeaconStateAllForks, indices: number[]): boolean {
|
|
44
|
+
// verify max number of indices
|
|
45
|
+
const maxIndices =
|
|
46
|
+
state.config.getForkSeq(state.slot) >= ForkSeq.electra
|
|
47
|
+
? MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT
|
|
48
|
+
: MAX_VALIDATORS_PER_COMMITTEE;
|
|
49
|
+
if (!(indices.length > 0 && indices.length <= maxIndices)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// verify indices are sorted and unique.
|
|
54
|
+
// Just check if they are monotonically increasing,
|
|
55
|
+
// instead of creating a set and sorting it. Should be (O(n)) instead of O(n log(n))
|
|
56
|
+
let prev = -1;
|
|
57
|
+
for (const index of indices) {
|
|
58
|
+
if (index <= prev) return false;
|
|
59
|
+
prev = index;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// check if indices are out of bounds, by checking the highest index (since it is sorted)
|
|
63
|
+
// TODO - SLOW CODE - Does this .length check the tree and is expensive?
|
|
64
|
+
const lastIndex = indices.at(-1);
|
|
65
|
+
if (lastIndex && lastIndex >= state.validators.length) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {ForkSeq, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
+
import {Attestation, Slot, electra, phase0, ssz} from "@lodestar/types";
|
|
3
|
+
import {assert, toRootHex} from "@lodestar/utils";
|
|
4
|
+
import {CachedBeaconStateAllForks, CachedBeaconStatePhase0} from "../types.js";
|
|
5
|
+
import {computeEndSlotAtEpoch, computeEpochAtSlot} from "../util/index.js";
|
|
6
|
+
import {isValidIndexedAttestation} from "./index.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Process an Attestation operation. Validates an attestation and appends it to state.currentEpochAttestations or
|
|
10
|
+
* state.previousEpochAttestations to be processed in bulk at the epoch transition.
|
|
11
|
+
*
|
|
12
|
+
* PERF: Work depends on number of Attestation per block. On mainnet the average is 89.7 / block, with 87.8 participant
|
|
13
|
+
* true bits on average. See `packages/state-transition/test/perf/analyzeBlocks.ts`
|
|
14
|
+
*/
|
|
15
|
+
export function processAttestationPhase0(
|
|
16
|
+
state: CachedBeaconStatePhase0,
|
|
17
|
+
attestation: phase0.Attestation,
|
|
18
|
+
verifySignature = true
|
|
19
|
+
): void {
|
|
20
|
+
const {epochCtx} = state;
|
|
21
|
+
const slot = state.slot;
|
|
22
|
+
const data = attestation.data;
|
|
23
|
+
|
|
24
|
+
validateAttestation(ForkSeq.phase0, state, attestation);
|
|
25
|
+
|
|
26
|
+
const pendingAttestation = ssz.phase0.PendingAttestation.toViewDU({
|
|
27
|
+
data: data,
|
|
28
|
+
aggregationBits: attestation.aggregationBits,
|
|
29
|
+
inclusionDelay: slot - data.slot,
|
|
30
|
+
proposerIndex: epochCtx.getBeaconProposer(slot),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (data.target.epoch === epochCtx.epoch) {
|
|
34
|
+
if (!ssz.phase0.Checkpoint.equals(data.source, state.currentJustifiedCheckpoint)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Attestation source does not equal current justified checkpoint: source=${checkpointToStr(
|
|
37
|
+
data.source
|
|
38
|
+
)} currentJustifiedCheckpoint=${checkpointToStr(state.currentJustifiedCheckpoint)}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
state.currentEpochAttestations.push(pendingAttestation);
|
|
42
|
+
} else {
|
|
43
|
+
if (!ssz.phase0.Checkpoint.equals(data.source, state.previousJustifiedCheckpoint)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Attestation source does not equal previous justified checkpoint: source=${checkpointToStr(
|
|
46
|
+
data.source
|
|
47
|
+
)} previousJustifiedCheckpoint=${checkpointToStr(state.previousJustifiedCheckpoint)}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
state.previousEpochAttestations.push(pendingAttestation);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!isValidIndexedAttestation(state, epochCtx.getIndexedAttestation(ForkSeq.phase0, attestation), verifySignature)) {
|
|
54
|
+
throw new Error("Attestation is not valid");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllForks, attestation: Attestation): void {
|
|
59
|
+
const {epochCtx} = state;
|
|
60
|
+
const slot = state.slot;
|
|
61
|
+
const data = attestation.data;
|
|
62
|
+
const computedEpoch = computeEpochAtSlot(data.slot);
|
|
63
|
+
const committeeCount = epochCtx.getCommitteeCountPerSlot(computedEpoch);
|
|
64
|
+
|
|
65
|
+
if (!(data.target.epoch === epochCtx.previousShuffling.epoch || data.target.epoch === epochCtx.epoch)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"Attestation target epoch not in previous or current epoch: " +
|
|
68
|
+
`targetEpoch=${data.target.epoch} currentEpoch=${epochCtx.epoch}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (!(data.target.epoch === computedEpoch)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
"Attestation target epoch does not match epoch computed from slot: " +
|
|
74
|
+
`targetEpoch=${data.target.epoch} computedEpoch=${computedEpoch}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// post deneb, the attestations are valid till end of next epoch
|
|
79
|
+
if (!(data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= slot && isTimelyTarget(fork, slot - data.slot))) {
|
|
80
|
+
const windowStart = data.slot + MIN_ATTESTATION_INCLUSION_DELAY;
|
|
81
|
+
const windowEnd = fork >= ForkSeq.deneb ? computeEndSlotAtEpoch(computedEpoch + 1) : data.slot + SLOTS_PER_EPOCH;
|
|
82
|
+
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Attestation slot not within inclusion window: slot=${data.slot} window=${windowStart}..${windowEnd}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (fork >= ForkSeq.electra) {
|
|
89
|
+
assert.equal(data.index, 0, `AttestationData.index must be zero: index=${data.index}`);
|
|
90
|
+
const attestationElectra = attestation as electra.Attestation;
|
|
91
|
+
const committeeIndices = attestationElectra.committeeBits.getTrueBitIndexes();
|
|
92
|
+
|
|
93
|
+
const lastCommitteeIndex = committeeIndices.at(-1);
|
|
94
|
+
if (lastCommitteeIndex === undefined) {
|
|
95
|
+
throw Error("Attestation should have at least one committee bit set");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (lastCommitteeIndex >= committeeCount) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Attestation committee index exceeds committee count: lastCommitteeIndex=${lastCommitteeIndex} numCommittees=${committeeCount}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const validatorsByCommittee = epochCtx.getBeaconCommittees(data.slot, committeeIndices);
|
|
105
|
+
const aggregationBitsArray = attestationElectra.aggregationBits.toBoolArray();
|
|
106
|
+
|
|
107
|
+
// Total number of attestation participants of every committee specified
|
|
108
|
+
let committeeOffset = 0;
|
|
109
|
+
for (const committeeValidators of validatorsByCommittee) {
|
|
110
|
+
const committeeAggregationBits = aggregationBitsArray.slice(
|
|
111
|
+
committeeOffset,
|
|
112
|
+
committeeOffset + committeeValidators.length
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Assert aggregation bits in this committee have at least one true bit
|
|
116
|
+
if (committeeAggregationBits.every((bit) => !bit)) {
|
|
117
|
+
throw new Error("Every committee in aggregation bits must have at least one attester");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
committeeOffset += committeeValidators.length;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Bitfield length matches total number of participants
|
|
124
|
+
assert.equal(
|
|
125
|
+
attestationElectra.aggregationBits.bitLen,
|
|
126
|
+
committeeOffset,
|
|
127
|
+
`Attestation aggregation bits length does not match total number of committee participants aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${committeeOffset}`
|
|
128
|
+
);
|
|
129
|
+
} else {
|
|
130
|
+
if (!(data.index < committeeCount)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"Attestation committee index not within current committee count: " +
|
|
133
|
+
`committeeIndex=${data.index} committeeCount=${committeeCount}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const committee = epochCtx.getBeaconCommittee(data.slot, data.index);
|
|
138
|
+
if (attestation.aggregationBits.bitLen !== committee.length) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"Attestation aggregation bits length does not match committee length: " +
|
|
141
|
+
`aggregationBitsLength=${attestation.aggregationBits.bitLen} committeeLength=${committee.length}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Modified https://github.com/ethereum/consensus-specs/pull/3360
|
|
148
|
+
export function isTimelyTarget(fork: ForkSeq, inclusionDistance: Slot): boolean {
|
|
149
|
+
// post deneb attestation is valid till end of next epoch for target
|
|
150
|
+
if (fork >= ForkSeq.deneb) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return inclusionDistance <= SLOTS_PER_EPOCH;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function checkpointToStr(checkpoint: phase0.Checkpoint): string {
|
|
157
|
+
return `${toRootHex(checkpoint.root)}:${checkpoint.epoch}`;
|
|
158
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {ForkSeq} from "@lodestar/params";
|
|
2
|
+
import {Attestation} from "@lodestar/types";
|
|
3
|
+
import {BeaconStateTransitionMetrics} from "../metrics.js";
|
|
4
|
+
import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStatePhase0} from "../types.js";
|
|
5
|
+
import {processAttestationPhase0} from "./processAttestationPhase0.js";
|
|
6
|
+
import {processAttestationsAltair} from "./processAttestationsAltair.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TODO
|
|
10
|
+
*/
|
|
11
|
+
export function processAttestations(
|
|
12
|
+
fork: ForkSeq,
|
|
13
|
+
state: CachedBeaconStateAllForks,
|
|
14
|
+
attestations: Attestation[],
|
|
15
|
+
verifySignatures = true,
|
|
16
|
+
metrics?: BeaconStateTransitionMetrics | null
|
|
17
|
+
): void {
|
|
18
|
+
if (fork === ForkSeq.phase0) {
|
|
19
|
+
for (const attestation of attestations) {
|
|
20
|
+
processAttestationPhase0(state as CachedBeaconStatePhase0, attestation, verifySignatures);
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
processAttestationsAltair(fork, state as CachedBeaconStateAltair, attestations, verifySignatures, metrics);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
2
|
+
import {
|
|
3
|
+
ForkSeq,
|
|
4
|
+
MIN_ATTESTATION_INCLUSION_DELAY,
|
|
5
|
+
PROPOSER_WEIGHT,
|
|
6
|
+
SLOTS_PER_EPOCH,
|
|
7
|
+
TIMELY_HEAD_FLAG_INDEX,
|
|
8
|
+
TIMELY_HEAD_WEIGHT,
|
|
9
|
+
TIMELY_SOURCE_FLAG_INDEX,
|
|
10
|
+
TIMELY_SOURCE_WEIGHT,
|
|
11
|
+
TIMELY_TARGET_FLAG_INDEX,
|
|
12
|
+
TIMELY_TARGET_WEIGHT,
|
|
13
|
+
WEIGHT_DENOMINATOR,
|
|
14
|
+
} from "@lodestar/params";
|
|
15
|
+
import {Attestation, Epoch, phase0} from "@lodestar/types";
|
|
16
|
+
import {intSqrt} from "@lodestar/utils";
|
|
17
|
+
import {BeaconStateTransitionMetrics} from "../metrics.js";
|
|
18
|
+
import {getAttestationWithIndicesSignatureSet} from "../signatureSets/indexedAttestation.js";
|
|
19
|
+
import {CachedBeaconStateAltair} from "../types.js";
|
|
20
|
+
import {increaseBalance, verifySignatureSet} from "../util/index.js";
|
|
21
|
+
import {RootCache} from "../util/rootCache.js";
|
|
22
|
+
import {checkpointToStr, isTimelyTarget, validateAttestation} from "./processAttestationPhase0.js";
|
|
23
|
+
|
|
24
|
+
const PROPOSER_REWARD_DOMINATOR = ((WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR) / PROPOSER_WEIGHT;
|
|
25
|
+
|
|
26
|
+
/** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */
|
|
27
|
+
const TIMELY_SOURCE = 1 << TIMELY_SOURCE_FLAG_INDEX;
|
|
28
|
+
const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX;
|
|
29
|
+
const TIMELY_HEAD = 1 << TIMELY_HEAD_FLAG_INDEX;
|
|
30
|
+
const SLOTS_PER_EPOCH_SQRT = intSqrt(SLOTS_PER_EPOCH);
|
|
31
|
+
|
|
32
|
+
export function processAttestationsAltair(
|
|
33
|
+
fork: ForkSeq,
|
|
34
|
+
state: CachedBeaconStateAltair,
|
|
35
|
+
attestations: Attestation[],
|
|
36
|
+
verifySignature = true,
|
|
37
|
+
metrics?: BeaconStateTransitionMetrics | null
|
|
38
|
+
): void {
|
|
39
|
+
const {epochCtx} = state;
|
|
40
|
+
const {effectiveBalanceIncrements} = epochCtx;
|
|
41
|
+
const stateSlot = state.slot;
|
|
42
|
+
const rootCache = new RootCache(state);
|
|
43
|
+
const currentEpoch = epochCtx.epoch;
|
|
44
|
+
|
|
45
|
+
// Process all attestations first and then increase the balance of the proposer once
|
|
46
|
+
let proposerReward = 0;
|
|
47
|
+
let newSeenAttesters = 0;
|
|
48
|
+
let newSeenAttestersEffectiveBalance = 0;
|
|
49
|
+
for (const attestation of attestations) {
|
|
50
|
+
const data = attestation.data;
|
|
51
|
+
|
|
52
|
+
validateAttestation(fork, state, attestation);
|
|
53
|
+
|
|
54
|
+
// Retrieve the validator indices from the attestation participation bitfield
|
|
55
|
+
const attestingIndices = epochCtx.getAttestingIndices(fork, attestation);
|
|
56
|
+
|
|
57
|
+
// this check is done last because its the most expensive (if signature verification is toggled on)
|
|
58
|
+
// TODO: Why should we verify an indexed attestation that we just created? If it's just for the signature
|
|
59
|
+
// we can verify only that and nothing else.
|
|
60
|
+
if (verifySignature) {
|
|
61
|
+
const sigSet = getAttestationWithIndicesSignatureSet(state, attestation, attestingIndices);
|
|
62
|
+
if (!verifySignatureSet(sigSet)) {
|
|
63
|
+
throw new Error("Attestation signature is not valid");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const inCurrentEpoch = data.target.epoch === currentEpoch;
|
|
68
|
+
const epochParticipation = inCurrentEpoch ? state.currentEpochParticipation : state.previousEpochParticipation;
|
|
69
|
+
|
|
70
|
+
const flagsAttestation = getAttestationParticipationStatus(
|
|
71
|
+
fork,
|
|
72
|
+
data,
|
|
73
|
+
stateSlot - data.slot,
|
|
74
|
+
epochCtx.epoch,
|
|
75
|
+
rootCache
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// For each participant, update their participation
|
|
79
|
+
// In epoch processing, this participation info is used to calculate balance updates
|
|
80
|
+
let totalBalanceIncrementsWithWeight = 0;
|
|
81
|
+
const validators = state.validators;
|
|
82
|
+
for (const validatorIndex of attestingIndices) {
|
|
83
|
+
const flags = epochParticipation.get(validatorIndex);
|
|
84
|
+
|
|
85
|
+
// For normal block, > 90% of attestations belong to current epoch
|
|
86
|
+
// At epoch boundary, 100% of attestations belong to previous epoch
|
|
87
|
+
// so we want to update the participation flag tree in batch
|
|
88
|
+
|
|
89
|
+
// Note ParticipationFlags type uses option {setBitwiseOR: true}, .set() does a |= operation
|
|
90
|
+
epochParticipation.set(validatorIndex, flagsAttestation);
|
|
91
|
+
// epochParticipation.setStatus(index, newStatus);
|
|
92
|
+
|
|
93
|
+
// Returns flags that are NOT set before (~ bitwise NOT) AND are set after
|
|
94
|
+
const flagsNewSet = ~flags & flagsAttestation;
|
|
95
|
+
if (flagsNewSet !== 0) {
|
|
96
|
+
newSeenAttesters++;
|
|
97
|
+
newSeenAttestersEffectiveBalance += effectiveBalanceIncrements[validatorIndex];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Spec:
|
|
101
|
+
// baseReward = state.validators[index].effectiveBalance / EFFECTIVE_BALANCE_INCREMENT * baseRewardPerIncrement;
|
|
102
|
+
// proposerRewardNumerator += baseReward * totalWeight
|
|
103
|
+
let totalWeight = 0;
|
|
104
|
+
if ((flagsNewSet & TIMELY_SOURCE) === TIMELY_SOURCE) totalWeight += TIMELY_SOURCE_WEIGHT;
|
|
105
|
+
if ((flagsNewSet & TIMELY_TARGET) === TIMELY_TARGET) totalWeight += TIMELY_TARGET_WEIGHT;
|
|
106
|
+
if ((flagsNewSet & TIMELY_HEAD) === TIMELY_HEAD) totalWeight += TIMELY_HEAD_WEIGHT;
|
|
107
|
+
|
|
108
|
+
if (totalWeight > 0) {
|
|
109
|
+
totalBalanceIncrementsWithWeight += effectiveBalanceIncrements[validatorIndex] * totalWeight;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// TODO: describe issue. Compute progressive target balances
|
|
113
|
+
// When processing each attestation, increase the cummulative target balance. Only applies post-altair
|
|
114
|
+
if ((flagsNewSet & TIMELY_TARGET) === TIMELY_TARGET) {
|
|
115
|
+
const validator = validators.getReadonly(validatorIndex);
|
|
116
|
+
if (!validator.slashed) {
|
|
117
|
+
if (inCurrentEpoch) {
|
|
118
|
+
epochCtx.currentTargetUnslashedBalanceIncrements += effectiveBalanceIncrements[validatorIndex];
|
|
119
|
+
} else {
|
|
120
|
+
epochCtx.previousTargetUnslashedBalanceIncrements += effectiveBalanceIncrements[validatorIndex];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Do the discrete math inside the loop to ensure a deterministic result
|
|
127
|
+
const totalIncrements = totalBalanceIncrementsWithWeight;
|
|
128
|
+
const proposerRewardNumerator = totalIncrements * state.epochCtx.baseRewardPerIncrement;
|
|
129
|
+
proposerReward += Math.floor(proposerRewardNumerator / PROPOSER_REWARD_DOMINATOR);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
metrics?.newSeenAttestersPerBlock.set(newSeenAttesters);
|
|
133
|
+
metrics?.newSeenAttestersEffectiveBalancePerBlock.set(newSeenAttestersEffectiveBalance);
|
|
134
|
+
metrics?.attestationsPerBlock.set(attestations.length);
|
|
135
|
+
|
|
136
|
+
increaseBalance(state, epochCtx.getBeaconProposer(state.slot), proposerReward);
|
|
137
|
+
state.proposerRewards.attestations = proposerReward;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/beacon-chain.md#get_attestation_participation_flag_indices
|
|
142
|
+
*/
|
|
143
|
+
export function getAttestationParticipationStatus(
|
|
144
|
+
fork: ForkSeq,
|
|
145
|
+
data: phase0.AttestationData,
|
|
146
|
+
inclusionDelay: number,
|
|
147
|
+
currentEpoch: Epoch,
|
|
148
|
+
rootCache: RootCache
|
|
149
|
+
): number {
|
|
150
|
+
const justifiedCheckpoint =
|
|
151
|
+
data.target.epoch === currentEpoch ? rootCache.currentJustifiedCheckpoint : rootCache.previousJustifiedCheckpoint;
|
|
152
|
+
|
|
153
|
+
// The source and target votes are part of the FFG vote, the head vote is part of the fork choice vote
|
|
154
|
+
// Both are tracked to properly incentivise validators
|
|
155
|
+
//
|
|
156
|
+
// The source vote always matches the justified checkpoint (else its invalid)
|
|
157
|
+
// The target vote should match the most recent checkpoint (eg: the first root of the epoch)
|
|
158
|
+
// The head vote should match the root at the attestation slot (eg: the root at data.slot)
|
|
159
|
+
const isMatchingSource = checkpointValueEquals(data.source, justifiedCheckpoint);
|
|
160
|
+
if (!isMatchingSource) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Attestation source does not equal justified checkpoint: source=${checkpointToStr(
|
|
163
|
+
data.source
|
|
164
|
+
)} justifiedCheckpoint=${checkpointToStr(justifiedCheckpoint)}`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const isMatchingTarget = byteArrayEquals(data.target.root, rootCache.getBlockRoot(data.target.epoch));
|
|
169
|
+
|
|
170
|
+
// a timely head is only be set if the target is _also_ matching
|
|
171
|
+
const isMatchingHead =
|
|
172
|
+
isMatchingTarget && byteArrayEquals(data.beaconBlockRoot, rootCache.getBlockRootAtSlot(data.slot));
|
|
173
|
+
|
|
174
|
+
let flags = 0;
|
|
175
|
+
if (isMatchingSource && inclusionDelay <= SLOTS_PER_EPOCH_SQRT) flags |= TIMELY_SOURCE;
|
|
176
|
+
if (isMatchingTarget && isTimelyTarget(fork, inclusionDelay)) flags |= TIMELY_TARGET;
|
|
177
|
+
if (isMatchingHead && inclusionDelay === MIN_ATTESTATION_INCLUSION_DELAY) flags |= TIMELY_HEAD;
|
|
178
|
+
|
|
179
|
+
return flags;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function checkpointValueEquals(cp1: phase0.Checkpoint, cp2: phase0.Checkpoint): boolean {
|
|
183
|
+
return cp1.epoch === cp2.epoch && byteArrayEquals(cp1.root, cp2.root);
|
|
184
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {ForkSeq} from "@lodestar/params";
|
|
2
|
+
import {AttesterSlashing} from "@lodestar/types";
|
|
3
|
+
import {CachedBeaconStateAllForks} from "../types.js";
|
|
4
|
+
import {getAttesterSlashableIndices, isSlashableAttestationData, isSlashableValidator} from "../util/index.js";
|
|
5
|
+
import {isValidIndexedAttestationBigint} from "./isValidIndexedAttestation.js";
|
|
6
|
+
import {slashValidator} from "./slashValidator.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Process an AttesterSlashing operation. Initiates the exit of a validator, decreases the balance of the slashed
|
|
10
|
+
* validators and increases the block proposer balance.
|
|
11
|
+
*
|
|
12
|
+
* PERF: Work depends on number of AttesterSlashing per block. On regular networks the average is 0 / block.
|
|
13
|
+
*/
|
|
14
|
+
export function processAttesterSlashing(
|
|
15
|
+
fork: ForkSeq,
|
|
16
|
+
state: CachedBeaconStateAllForks,
|
|
17
|
+
attesterSlashing: AttesterSlashing,
|
|
18
|
+
verifySignatures = true
|
|
19
|
+
): void {
|
|
20
|
+
assertValidAttesterSlashing(state, attesterSlashing, verifySignatures);
|
|
21
|
+
|
|
22
|
+
const intersectingIndices = getAttesterSlashableIndices(attesterSlashing);
|
|
23
|
+
|
|
24
|
+
let slashedAny = false;
|
|
25
|
+
const validators = state.validators; // Get the validators sub tree once for all indices
|
|
26
|
+
// Spec requires to sort indexes beforehand
|
|
27
|
+
for (const index of intersectingIndices.sort((a, b) => a - b)) {
|
|
28
|
+
if (isSlashableValidator(validators.getReadonly(index), state.epochCtx.epoch)) {
|
|
29
|
+
slashValidator(fork, state, index);
|
|
30
|
+
slashedAny = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!slashedAny) {
|
|
35
|
+
throw new Error("AttesterSlashing did not result in any slashings");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function assertValidAttesterSlashing(
|
|
40
|
+
state: CachedBeaconStateAllForks,
|
|
41
|
+
attesterSlashing: AttesterSlashing,
|
|
42
|
+
verifySignatures = true
|
|
43
|
+
): void {
|
|
44
|
+
const attestation1 = attesterSlashing.attestation1;
|
|
45
|
+
const attestation2 = attesterSlashing.attestation2;
|
|
46
|
+
|
|
47
|
+
if (!isSlashableAttestationData(attestation1.data, attestation2.data)) {
|
|
48
|
+
throw new Error("AttesterSlashing is not slashable");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// In state transition, AttesterSlashing attestations are only partially validated. Their slot and epoch could
|
|
52
|
+
// be higher than the clock and the slashing would still be valid. Same applies to attestation data index, which
|
|
53
|
+
// can be any arbitrary value. Must use bigint variants to hash correctly to all possible values
|
|
54
|
+
for (const [i, attestation] of [attestation1, attestation2].entries()) {
|
|
55
|
+
if (!isValidIndexedAttestationBigint(state, attestation, verifySignatures)) {
|
|
56
|
+
throw new Error(`AttesterSlashing attestation${i} is invalid`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {BlockExternalData, ExecutionPayloadStatus} from "./externalData.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* https://github.com/ethereum/consensus-specs/blob/11a037fd9227e29ee809c9397b09f8cc3383a8c0/specs/eip4844/beacon-chain.md#blob-kzg-commitments
|
|
5
|
+
*
|
|
6
|
+
* def process_blob_kzg_commitments(state: BeaconState, body: BeaconBlockBody):
|
|
7
|
+
* assert verify_kzg_commitments_against_transactions(
|
|
8
|
+
* body.execution_payload.transactions,
|
|
9
|
+
* body.blob_kzg_commitments
|
|
10
|
+
* )
|
|
11
|
+
*/
|
|
12
|
+
export function processBlobKzgCommitments(externalData: BlockExternalData): void {
|
|
13
|
+
switch (externalData.executionPayloadStatus) {
|
|
14
|
+
case ExecutionPayloadStatus.preMerge:
|
|
15
|
+
throw Error("executionPayloadStatus preMerge");
|
|
16
|
+
case ExecutionPayloadStatus.invalid:
|
|
17
|
+
throw Error("Invalid execution payload");
|
|
18
|
+
case ExecutionPayloadStatus.valid:
|
|
19
|
+
break; // ok
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
2
|
+
import {BeaconBlock, BlindedBeaconBlock, ssz} from "@lodestar/types";
|
|
3
|
+
import {toRootHex} from "@lodestar/utils";
|
|
4
|
+
import {ZERO_HASH} from "../constants/index.js";
|
|
5
|
+
import {CachedBeaconStateAllForks} from "../types.js";
|
|
6
|
+
import {blindedOrFullBlockToHeader} from "../util/index.js";
|
|
7
|
+
/**
|
|
8
|
+
* Converts a Deposit record (created by the eth-execution deposit contract) into a Validator object that goes into the eth-consensus state.
|
|
9
|
+
*
|
|
10
|
+
* PERF: Fixed work independent of block contents.
|
|
11
|
+
* NOTE: `block` body root MUST be pre-cached.
|
|
12
|
+
*/
|
|
13
|
+
export function processBlockHeader(state: CachedBeaconStateAllForks, block: BeaconBlock | BlindedBeaconBlock): void {
|
|
14
|
+
const slot = state.slot;
|
|
15
|
+
// verify that the slots match
|
|
16
|
+
if (block.slot !== slot) {
|
|
17
|
+
throw new Error(`Block slot does not match state slot blockSlot=${block.slot} stateSlot=${slot}`);
|
|
18
|
+
}
|
|
19
|
+
// Verify that the block is newer than latest block header
|
|
20
|
+
if (!(block.slot > state.latestBlockHeader.slot)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Block is not newer than latest block header blockSlot=${block.slot} latestBlockHeader.slot=${state.latestBlockHeader.slot}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
// verify that proposer index is the correct index
|
|
26
|
+
const proposerIndex = state.epochCtx.getBeaconProposer(slot);
|
|
27
|
+
if (block.proposerIndex !== proposerIndex) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Block proposer index does not match state proposer index blockProposerIndex=${block.proposerIndex} stateProposerIndex=${proposerIndex}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// verify that the parent matches
|
|
34
|
+
if (!byteArrayEquals(block.parentRoot, ssz.phase0.BeaconBlockHeader.hashTreeRoot(state.latestBlockHeader))) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Block parent root ${toRootHex(block.parentRoot)} does not match state latest block, block slot=${slot}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const blockHeader = blindedOrFullBlockToHeader(state.config, block);
|
|
41
|
+
// cache current block as the new latest block
|
|
42
|
+
state.latestBlockHeader = ssz.phase0.BeaconBlockHeader.toViewDU({
|
|
43
|
+
slot,
|
|
44
|
+
proposerIndex,
|
|
45
|
+
parentRoot: block.parentRoot,
|
|
46
|
+
stateRoot: ZERO_HASH,
|
|
47
|
+
bodyRoot: blockHeader.bodyRoot,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// verify proposer is not slashed. Only once per block, may use the slower read from tree
|
|
51
|
+
if (state.validators.getReadonly(proposerIndex).slashed) {
|
|
52
|
+
throw new Error("Block proposer is slashed");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {digest} from "@chainsafe/as-sha256";
|
|
2
|
+
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
3
|
+
import {BLS_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params";
|
|
4
|
+
import {capella} from "@lodestar/types";
|
|
5
|
+
import {toHex} from "@lodestar/utils";
|
|
6
|
+
import {verifyBlsToExecutionChangeSignature} from "../signatureSets/index.js";
|
|
7
|
+
import {CachedBeaconStateCapella} from "../types.js";
|
|
8
|
+
|
|
9
|
+
export function processBlsToExecutionChange(
|
|
10
|
+
state: CachedBeaconStateCapella,
|
|
11
|
+
signedBlsToExecutionChange: capella.SignedBLSToExecutionChange
|
|
12
|
+
): void {
|
|
13
|
+
const addressChange = signedBlsToExecutionChange.message;
|
|
14
|
+
|
|
15
|
+
const validation = isValidBlsToExecutionChange(state, signedBlsToExecutionChange, true);
|
|
16
|
+
if (!validation.valid) {
|
|
17
|
+
throw validation.error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const validator = state.validators.get(addressChange.validatorIndex);
|
|
21
|
+
const newWithdrawalCredentials = new Uint8Array(32);
|
|
22
|
+
newWithdrawalCredentials[0] = ETH1_ADDRESS_WITHDRAWAL_PREFIX;
|
|
23
|
+
newWithdrawalCredentials.set(addressChange.toExecutionAddress, 12);
|
|
24
|
+
|
|
25
|
+
// Set the new credentials back
|
|
26
|
+
validator.withdrawalCredentials = newWithdrawalCredentials;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isValidBlsToExecutionChange(
|
|
30
|
+
state: CachedBeaconStateCapella,
|
|
31
|
+
signedBLSToExecutionChange: capella.SignedBLSToExecutionChange,
|
|
32
|
+
verifySignature = true
|
|
33
|
+
): {valid: true} | {valid: false; error: Error} {
|
|
34
|
+
const addressChange = signedBLSToExecutionChange.message;
|
|
35
|
+
|
|
36
|
+
if (addressChange.validatorIndex >= state.validators.length) {
|
|
37
|
+
return {
|
|
38
|
+
valid: false,
|
|
39
|
+
error: Error(
|
|
40
|
+
`withdrawalValidatorIndex ${addressChange.validatorIndex} > state.validators len ${state.validators.length}`
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const validator = state.validators.getReadonly(addressChange.validatorIndex);
|
|
46
|
+
const {withdrawalCredentials} = validator;
|
|
47
|
+
if (withdrawalCredentials[0] !== BLS_WITHDRAWAL_PREFIX) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
error: Error(
|
|
51
|
+
`Invalid withdrawalCredentials prefix expected=${BLS_WITHDRAWAL_PREFIX} actual=${withdrawalCredentials[0]}`
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const digestCredentials = digest(addressChange.fromBlsPubkey);
|
|
57
|
+
// Set the BLS_WITHDRAWAL_PREFIX on the digestCredentials for direct match
|
|
58
|
+
digestCredentials[0] = BLS_WITHDRAWAL_PREFIX;
|
|
59
|
+
if (!byteArrayEquals(withdrawalCredentials, digestCredentials)) {
|
|
60
|
+
return {
|
|
61
|
+
valid: false,
|
|
62
|
+
error: Error(
|
|
63
|
+
`Invalid withdrawalCredentials expected=${toHex(withdrawalCredentials)} actual=${toHex(digestCredentials)}`
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (verifySignature && !verifyBlsToExecutionChangeSignature(state, signedBLSToExecutionChange)) {
|
|
69
|
+
return {
|
|
70
|
+
valid: false,
|
|
71
|
+
error: Error(
|
|
72
|
+
`Signature could not be verified for BLS to Execution Change for validatorIndex${addressChange.validatorIndex}`
|
|
73
|
+
),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {valid: true};
|
|
78
|
+
}
|