@lodestar/state-transition 1.35.0-dev.83de5b8dea → 1.35.0-dev.8689cc3545
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/block/externalData.d.ts.map +1 -0
- package/lib/block/index.d.ts.map +1 -0
- package/lib/block/initiateValidatorExit.d.ts.map +1 -0
- package/lib/block/isValidIndexedAttestation.d.ts.map +1 -0
- package/lib/block/processAttestationPhase0.d.ts.map +1 -0
- package/lib/block/processAttestations.d.ts.map +1 -0
- package/lib/block/processAttestationsAltair.d.ts.map +1 -0
- package/lib/block/processAttesterSlashing.d.ts.map +1 -0
- package/lib/block/processBlobKzgCommitments.d.ts.map +1 -0
- package/lib/block/processBlockHeader.d.ts.map +1 -0
- package/lib/block/processBlsToExecutionChange.d.ts.map +1 -0
- package/lib/block/processConsolidationRequest.d.ts.map +1 -0
- package/lib/block/processDeposit.d.ts.map +1 -0
- package/lib/block/processDepositRequest.d.ts.map +1 -0
- package/lib/block/processEth1Data.d.ts.map +1 -0
- package/lib/block/processExecutionPayload.d.ts.map +1 -0
- package/lib/block/processOperations.d.ts.map +1 -0
- package/lib/block/processProposerSlashing.d.ts.map +1 -0
- package/lib/block/processRandao.d.ts.map +1 -0
- package/lib/block/processSyncCommittee.d.ts.map +1 -0
- package/lib/block/processVoluntaryExit.d.ts.map +1 -0
- package/lib/block/processWithdrawalRequest.d.ts.map +1 -0
- package/lib/block/processWithdrawals.d.ts.map +1 -0
- package/lib/block/slashValidator.d.ts.map +1 -0
- package/lib/block/types.d.ts.map +1 -0
- package/lib/cache/effectiveBalanceIncrements.d.ts.map +1 -0
- package/lib/cache/epochCache.d.ts.map +1 -0
- package/lib/cache/epochTransitionCache.d.ts.map +1 -0
- package/lib/cache/pubkeyCache.d.ts.map +1 -0
- package/lib/cache/rewardCache.d.ts.map +1 -0
- package/lib/cache/stateCache.d.ts.map +1 -0
- package/lib/cache/syncCommitteeCache.d.ts.map +1 -0
- package/lib/cache/types.d.ts.map +1 -0
- package/lib/constants/constants.d.ts.map +1 -0
- package/lib/constants/index.d.ts.map +1 -0
- package/lib/epoch/computeUnrealizedCheckpoints.d.ts.map +1 -0
- package/lib/epoch/getAttestationDeltas.d.ts.map +1 -0
- package/lib/epoch/getRewardsAndPenalties.d.ts.map +1 -0
- package/lib/epoch/index.d.ts.map +1 -0
- package/lib/epoch/processEffectiveBalanceUpdates.d.ts.map +1 -0
- package/lib/epoch/processEth1DataReset.d.ts.map +1 -0
- package/lib/epoch/processHistoricalRootsUpdate.d.ts.map +1 -0
- package/lib/epoch/processHistoricalSummariesUpdate.d.ts.map +1 -0
- package/lib/epoch/processInactivityUpdates.d.ts.map +1 -0
- package/lib/epoch/processJustificationAndFinalization.d.ts.map +1 -0
- package/lib/epoch/processParticipationFlagUpdates.d.ts.map +1 -0
- package/lib/epoch/processParticipationRecordUpdates.d.ts.map +1 -0
- package/lib/epoch/processPendingAttestations.d.ts.map +1 -0
- package/lib/epoch/processPendingConsolidations.d.ts.map +1 -0
- package/lib/epoch/processPendingDeposits.d.ts.map +1 -0
- package/lib/epoch/processProposerLookahead.d.ts.map +1 -0
- package/lib/epoch/processRandaoMixesReset.d.ts.map +1 -0
- package/lib/epoch/processRegistryUpdates.d.ts.map +1 -0
- package/lib/epoch/processRewardsAndPenalties.d.ts.map +1 -0
- package/lib/epoch/processSlashings.d.ts.map +1 -0
- package/lib/epoch/processSlashingsReset.d.ts.map +1 -0
- package/lib/epoch/processSyncCommitteeUpdates.d.ts.map +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/metrics.d.ts.map +1 -0
- package/lib/signatureSets/attesterSlashings.d.ts.map +1 -0
- package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -0
- package/lib/signatureSets/index.d.ts.map +1 -0
- package/lib/signatureSets/indexedAttestation.d.ts.map +1 -0
- package/lib/signatureSets/proposer.d.ts.map +1 -0
- package/lib/signatureSets/proposerSlashings.d.ts.map +1 -0
- package/lib/signatureSets/randao.d.ts.map +1 -0
- package/lib/signatureSets/voluntaryExits.d.ts.map +1 -0
- package/lib/slot/index.d.ts.map +1 -0
- package/lib/slot/upgradeStateToAltair.d.ts.map +1 -0
- package/lib/slot/upgradeStateToBellatrix.d.ts.map +1 -0
- package/lib/slot/upgradeStateToCapella.d.ts.map +1 -0
- package/lib/slot/upgradeStateToDeneb.d.ts.map +1 -0
- package/lib/slot/upgradeStateToElectra.d.ts.map +1 -0
- package/lib/slot/upgradeStateToFulu.d.ts.map +1 -0
- package/lib/slot/upgradeStateToGloas.d.ts.map +1 -0
- package/lib/stateTransition.d.ts.map +1 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/util/aggregator.d.ts.map +1 -0
- package/lib/util/altair.d.ts.map +1 -0
- package/lib/util/array.d.ts.map +1 -0
- package/lib/util/attestation.d.ts.map +1 -0
- package/lib/util/attesterStatus.d.ts.map +1 -0
- package/lib/util/balance.d.ts.map +1 -0
- package/lib/util/blindedBlock.d.ts.map +1 -0
- 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/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/index.d.ts.map +1 -0
- package/lib/util/interop.d.ts.map +1 -0
- package/lib/util/loadState/findModifiedInactivityScores.d.ts.map +1 -0
- package/lib/util/loadState/findModifiedValidators.d.ts.map +1 -0
- package/lib/util/loadState/index.d.ts.map +1 -0
- package/lib/util/loadState/loadState.d.ts.map +1 -0
- package/lib/util/loadState/loadValidator.d.ts.map +1 -0
- package/lib/util/rootCache.d.ts.map +1 -0
- package/lib/util/seed.d.ts.map +1 -0
- package/lib/util/shufflingDecisionRoot.d.ts.map +1 -0
- package/lib/util/signatureSets.d.ts.map +1 -0
- package/lib/util/signingRoot.d.ts.map +1 -0
- package/lib/util/slot.d.ts.map +1 -0
- package/lib/util/sszBytes.d.ts.map +1 -0
- package/lib/util/syncCommittee.d.ts.map +1 -0
- package/lib/util/targetUnslashedBalance.d.ts.map +1 -0
- package/lib/util/validator.d.ts.map +1 -0
- package/lib/util/weakSubjectivity.d.ts.map +1 -0
- package/package.json +9 -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 +343 -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 +27 -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,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
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params";
|
|
2
|
+
import {electra, ssz} from "@lodestar/types";
|
|
3
|
+
import {CachedBeaconStateElectra} from "../types.js";
|
|
4
|
+
import {hasEth1WithdrawalCredential} from "../util/capella.js";
|
|
5
|
+
import {
|
|
6
|
+
hasCompoundingWithdrawalCredential,
|
|
7
|
+
hasExecutionWithdrawalCredential,
|
|
8
|
+
isPubkeyKnown,
|
|
9
|
+
switchToCompoundingValidator,
|
|
10
|
+
} from "../util/electra.js";
|
|
11
|
+
import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js";
|
|
12
|
+
import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js";
|
|
13
|
+
|
|
14
|
+
// TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest
|
|
15
|
+
export function processConsolidationRequest(
|
|
16
|
+
state: CachedBeaconStateElectra,
|
|
17
|
+
consolidationRequest: electra.ConsolidationRequest
|
|
18
|
+
): void {
|
|
19
|
+
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
|
|
20
|
+
if (!isPubkeyKnown(state, sourcePubkey) || !isPubkeyKnown(state, targetPubkey)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
|
|
25
|
+
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);
|
|
26
|
+
|
|
27
|
+
if (sourceIndex === null || targetIndex === null) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isValidSwitchToCompoundRequest(state, consolidationRequest)) {
|
|
32
|
+
switchToCompoundingValidator(state, sourceIndex);
|
|
33
|
+
// Early return since we have already switched validator to compounding
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Verify that source != target, so a consolidation cannot be used as an exit.
|
|
38
|
+
if (sourceIndex === targetIndex) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If the pending consolidations queue is full, consolidation requests are ignored
|
|
43
|
+
if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If there is too little available consolidation churn limit, consolidation requests are ignored
|
|
48
|
+
if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const sourceValidator = state.validators.get(sourceIndex);
|
|
53
|
+
const targetValidator = state.validators.getReadonly(targetIndex);
|
|
54
|
+
const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12);
|
|
55
|
+
const currentEpoch = state.epochCtx.epoch;
|
|
56
|
+
|
|
57
|
+
// Verify source withdrawal credentials
|
|
58
|
+
const hasCorrectCredential = hasExecutionWithdrawalCredential(sourceValidator.withdrawalCredentials);
|
|
59
|
+
const isCorrectSourceAddress = Buffer.compare(sourceWithdrawalAddress, sourceAddress) === 0;
|
|
60
|
+
if (!(hasCorrectCredential && isCorrectSourceAddress)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Verify that target has compounding withdrawal credentials
|
|
65
|
+
if (!hasCompoundingWithdrawalCredential(targetValidator.withdrawalCredentials)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Verify the source and the target are active
|
|
70
|
+
if (!isActiveValidator(sourceValidator, currentEpoch) || !isActiveValidator(targetValidator, currentEpoch)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Verify exits for source and target have not been initiated
|
|
75
|
+
if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH || targetValidator.exitEpoch !== FAR_FUTURE_EPOCH) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Verify the source has been active long enough
|
|
80
|
+
if (currentEpoch < sourceValidator.activationEpoch + state.config.SHARD_COMMITTEE_PERIOD) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Verify the source has no pending withdrawals in the queue
|
|
85
|
+
if (getPendingBalanceToWithdraw(state, sourceIndex) > 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Initiate source validator exit and append pending consolidation
|
|
90
|
+
// TODO Electra: See if we can get rid of big int
|
|
91
|
+
const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(sourceValidator.effectiveBalance));
|
|
92
|
+
sourceValidator.exitEpoch = exitEpoch;
|
|
93
|
+
sourceValidator.withdrawableEpoch = exitEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
|
|
94
|
+
|
|
95
|
+
const pendingConsolidation = ssz.electra.PendingConsolidation.toViewDU({
|
|
96
|
+
sourceIndex,
|
|
97
|
+
targetIndex,
|
|
98
|
+
});
|
|
99
|
+
state.pendingConsolidations.push(pendingConsolidation);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Determine if we should set consolidation target validator to compounding credential
|
|
104
|
+
*/
|
|
105
|
+
function isValidSwitchToCompoundRequest(
|
|
106
|
+
state: CachedBeaconStateElectra,
|
|
107
|
+
consolidationRequest: electra.ConsolidationRequest
|
|
108
|
+
): boolean {
|
|
109
|
+
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
|
|
110
|
+
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
|
|
111
|
+
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);
|
|
112
|
+
|
|
113
|
+
// Verify pubkey exists
|
|
114
|
+
if (sourceIndex === null) {
|
|
115
|
+
// this check is mainly to make the compiler happy, pubkey is checked by the consumer already
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Switch to compounding requires source and target be equal
|
|
120
|
+
if (sourceIndex !== targetIndex) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const sourceValidator = state.validators.getReadonly(sourceIndex);
|
|
125
|
+
const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12);
|
|
126
|
+
// Verify request has been authorized
|
|
127
|
+
if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Verify source withdrawal credentials
|
|
132
|
+
if (!hasEth1WithdrawalCredential(sourceValidator.withdrawalCredentials)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Verify the source is active
|
|
137
|
+
if (!isActiveValidator(sourceValidator, state.epochCtx.epoch)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Verify exit for source has not been initiated
|
|
142
|
+
if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {PublicKey, Signature, verify} from "@chainsafe/blst";
|
|
2
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
+
import {
|
|
4
|
+
DEPOSIT_CONTRACT_TREE_DEPTH,
|
|
5
|
+
DOMAIN_DEPOSIT,
|
|
6
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
7
|
+
FAR_FUTURE_EPOCH,
|
|
8
|
+
ForkSeq,
|
|
9
|
+
GENESIS_SLOT,
|
|
10
|
+
MAX_EFFECTIVE_BALANCE,
|
|
11
|
+
} from "@lodestar/params";
|
|
12
|
+
import {BLSPubkey, Bytes32, UintNum64, electra, phase0, ssz} from "@lodestar/types";
|
|
13
|
+
import {verifyMerkleBranch} from "@lodestar/utils";
|
|
14
|
+
import {ZERO_HASH} from "../constants/index.js";
|
|
15
|
+
import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js";
|
|
16
|
+
import {computeDomain, computeSigningRoot, getMaxEffectiveBalance, increaseBalance} from "../util/index.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Process a Deposit operation. Potentially adds a new validator to the registry. Mutates the validators and balances
|
|
20
|
+
* trees, pushing contigious values at the end.
|
|
21
|
+
*
|
|
22
|
+
* PERF: Work depends on number of Deposit per block. On regular networks the average is 0 / block.
|
|
23
|
+
*/
|
|
24
|
+
export function processDeposit(fork: ForkSeq, state: CachedBeaconStateAllForks, deposit: phase0.Deposit): void {
|
|
25
|
+
// verify the merkle branch
|
|
26
|
+
if (
|
|
27
|
+
!verifyMerkleBranch(
|
|
28
|
+
ssz.phase0.DepositData.hashTreeRoot(deposit.data),
|
|
29
|
+
deposit.proof,
|
|
30
|
+
DEPOSIT_CONTRACT_TREE_DEPTH + 1,
|
|
31
|
+
state.eth1DepositIndex,
|
|
32
|
+
state.eth1Data.depositRoot
|
|
33
|
+
)
|
|
34
|
+
) {
|
|
35
|
+
throw new Error("Deposit has invalid merkle proof");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// deposits must be processed in order
|
|
39
|
+
state.eth1DepositIndex += 1;
|
|
40
|
+
|
|
41
|
+
applyDeposit(fork, state, deposit.data);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds a new validator into the registry. Or increase balance if already exist.
|
|
46
|
+
* Follows applyDeposit() in consensus spec. Will be used by processDeposit() and processDepositRequest()
|
|
47
|
+
*
|
|
48
|
+
*/
|
|
49
|
+
export function applyDeposit(
|
|
50
|
+
fork: ForkSeq,
|
|
51
|
+
state: CachedBeaconStateAllForks,
|
|
52
|
+
deposit: phase0.DepositData | electra.DepositRequest
|
|
53
|
+
): void {
|
|
54
|
+
const {config, epochCtx, validators} = state;
|
|
55
|
+
const {pubkey, withdrawalCredentials, amount, signature} = deposit;
|
|
56
|
+
|
|
57
|
+
const cachedIndex = epochCtx.getValidatorIndex(pubkey);
|
|
58
|
+
const isNewValidator = cachedIndex === null || !Number.isSafeInteger(cachedIndex) || cachedIndex >= validators.length;
|
|
59
|
+
|
|
60
|
+
if (fork < ForkSeq.electra) {
|
|
61
|
+
if (isNewValidator) {
|
|
62
|
+
if (isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, signature)) {
|
|
63
|
+
addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, amount);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// increase balance by deposit amount right away pre-electra
|
|
67
|
+
increaseBalance(state, cachedIndex, amount);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const stateElectra = state as CachedBeaconStateElectra;
|
|
71
|
+
const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({
|
|
72
|
+
pubkey,
|
|
73
|
+
withdrawalCredentials,
|
|
74
|
+
amount,
|
|
75
|
+
signature,
|
|
76
|
+
slot: GENESIS_SLOT, // Use GENESIS_SLOT to distinguish from a pending deposit request
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (isNewValidator) {
|
|
80
|
+
if (isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature)) {
|
|
81
|
+
addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, 0);
|
|
82
|
+
stateElectra.pendingDeposits.push(pendingDeposit);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
stateElectra.pendingDeposits.push(pendingDeposit);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function addValidatorToRegistry(
|
|
91
|
+
fork: ForkSeq,
|
|
92
|
+
state: CachedBeaconStateAllForks,
|
|
93
|
+
pubkey: BLSPubkey,
|
|
94
|
+
withdrawalCredentials: Bytes32,
|
|
95
|
+
amount: UintNum64
|
|
96
|
+
): void {
|
|
97
|
+
const {validators, epochCtx} = state;
|
|
98
|
+
// add validator and balance entries
|
|
99
|
+
const effectiveBalance = Math.min(
|
|
100
|
+
amount - (amount % EFFECTIVE_BALANCE_INCREMENT),
|
|
101
|
+
fork < ForkSeq.electra ? MAX_EFFECTIVE_BALANCE : getMaxEffectiveBalance(withdrawalCredentials)
|
|
102
|
+
);
|
|
103
|
+
validators.push(
|
|
104
|
+
ssz.phase0.Validator.toViewDU({
|
|
105
|
+
pubkey,
|
|
106
|
+
withdrawalCredentials,
|
|
107
|
+
activationEligibilityEpoch: FAR_FUTURE_EPOCH,
|
|
108
|
+
activationEpoch: FAR_FUTURE_EPOCH,
|
|
109
|
+
exitEpoch: FAR_FUTURE_EPOCH,
|
|
110
|
+
withdrawableEpoch: FAR_FUTURE_EPOCH,
|
|
111
|
+
effectiveBalance,
|
|
112
|
+
slashed: false,
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const validatorIndex = validators.length - 1;
|
|
117
|
+
// TODO Electra: Review this
|
|
118
|
+
// Updating here is better than updating at once on epoch transition
|
|
119
|
+
// - Simplify genesis fn applyDeposits(): effectiveBalanceIncrements is populated immediately
|
|
120
|
+
// - Keep related code together to reduce risk of breaking this cache
|
|
121
|
+
// - Should have equal performance since it sets a value in a flat array
|
|
122
|
+
epochCtx.effectiveBalanceIncrementsSet(validatorIndex, effectiveBalance);
|
|
123
|
+
|
|
124
|
+
// now that there is a new validator, update the epoch context with the new pubkey
|
|
125
|
+
epochCtx.addPubkey(validatorIndex, pubkey);
|
|
126
|
+
|
|
127
|
+
// Only after altair:
|
|
128
|
+
if (fork >= ForkSeq.altair) {
|
|
129
|
+
const stateAltair = state as CachedBeaconStateAltair;
|
|
130
|
+
|
|
131
|
+
stateAltair.inactivityScores.push(0);
|
|
132
|
+
|
|
133
|
+
// add participation caches
|
|
134
|
+
stateAltair.previousEpochParticipation.push(0);
|
|
135
|
+
stateAltair.currentEpochParticipation.push(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
state.balances.push(amount);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isValidDepositSignature(
|
|
142
|
+
config: BeaconConfig,
|
|
143
|
+
pubkey: Uint8Array,
|
|
144
|
+
withdrawalCredentials: Uint8Array,
|
|
145
|
+
amount: number,
|
|
146
|
+
depositSignature: Uint8Array
|
|
147
|
+
): boolean {
|
|
148
|
+
// verify the deposit signature (proof of posession) which is not checked by the deposit contract
|
|
149
|
+
const depositMessage = {
|
|
150
|
+
pubkey,
|
|
151
|
+
withdrawalCredentials,
|
|
152
|
+
amount,
|
|
153
|
+
};
|
|
154
|
+
// fork-agnostic domain since deposits are valid across forks
|
|
155
|
+
const domain = computeDomain(DOMAIN_DEPOSIT, config.GENESIS_FORK_VERSION, ZERO_HASH);
|
|
156
|
+
const signingRoot = computeSigningRoot(ssz.phase0.DepositMessage, depositMessage, domain);
|
|
157
|
+
try {
|
|
158
|
+
// Pubkeys must be checked for group + inf. This must be done only once when the validator deposit is processed
|
|
159
|
+
const publicKey = PublicKey.fromBytes(pubkey, true);
|
|
160
|
+
const signature = Signature.fromBytes(depositSignature, true);
|
|
161
|
+
|
|
162
|
+
return verify(signingRoot, publicKey, signature);
|
|
163
|
+
} catch (_e) {
|
|
164
|
+
return false; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
|
|
2
|
+
import {electra, ssz} from "@lodestar/types";
|
|
3
|
+
import {CachedBeaconStateElectra} from "../types.js";
|
|
4
|
+
|
|
5
|
+
export function processDepositRequest(state: CachedBeaconStateElectra, depositRequest: electra.DepositRequest): void {
|
|
6
|
+
if (state.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) {
|
|
7
|
+
state.depositRequestsStartIndex = depositRequest.index;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Create pending deposit
|
|
11
|
+
const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({
|
|
12
|
+
pubkey: depositRequest.pubkey,
|
|
13
|
+
withdrawalCredentials: depositRequest.withdrawalCredentials,
|
|
14
|
+
amount: depositRequest.amount,
|
|
15
|
+
signature: depositRequest.signature,
|
|
16
|
+
slot: state.slot,
|
|
17
|
+
});
|
|
18
|
+
state.pendingDeposits.push(pendingDeposit);
|
|
19
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {Node} from "@chainsafe/persistent-merkle-tree";
|
|
2
|
+
import {CompositeViewDU} from "@chainsafe/ssz";
|
|
3
|
+
import {EPOCHS_PER_ETH1_VOTING_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
4
|
+
import {phase0, ssz} from "@lodestar/types";
|
|
5
|
+
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Store vote counts for every eth-execution block that has votes; if any eth-execution block wins majority support within a 1024-slot
|
|
9
|
+
* voting period, formally accept that eth-execution block and set it as the official "latest known eth-execution block" in the eth-consensus state.
|
|
10
|
+
*
|
|
11
|
+
* PERF: Processing cost depends on the current amount of votes.
|
|
12
|
+
* - Best case: Vote is already decided, zero work. See becomesNewEth1Data conditions
|
|
13
|
+
* - Worst case: 1023 votes and no majority vote yet.
|
|
14
|
+
*/
|
|
15
|
+
export function processEth1Data(state: CachedBeaconStateAllForks, eth1Data: phase0.Eth1Data): void {
|
|
16
|
+
// Convert to view first to hash once and compare hashes
|
|
17
|
+
const eth1DataView = ssz.phase0.Eth1Data.toViewDU(eth1Data);
|
|
18
|
+
|
|
19
|
+
if (becomesNewEth1Data(state, eth1DataView)) {
|
|
20
|
+
state.eth1Data = eth1DataView;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
state.eth1DataVotes.push(eth1DataView);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns true if adding the given `eth1Data` to `state.eth1DataVotes` would
|
|
28
|
+
* result in a change to `state.eth1Data`.
|
|
29
|
+
*/
|
|
30
|
+
export function becomesNewEth1Data(
|
|
31
|
+
state: BeaconStateAllForks,
|
|
32
|
+
newEth1Data: CompositeViewDU<typeof ssz.phase0.Eth1Data>
|
|
33
|
+
): boolean {
|
|
34
|
+
const SLOTS_PER_ETH1_VOTING_PERIOD = EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH;
|
|
35
|
+
|
|
36
|
+
// If there are not more than 50% votes, then we do not have to count to find a winner.
|
|
37
|
+
if ((state.eth1DataVotes.length + 1) * 2 <= SLOTS_PER_ETH1_VOTING_PERIOD) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Nothing to do if the state already has this as eth1data (happens a lot after majority vote is in)
|
|
42
|
+
if (isEqualEth1DataView(state.eth1Data, newEth1Data)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Close to half the EPOCHS_PER_ETH1_VOTING_PERIOD it can be expensive to do so many comparisions.
|
|
47
|
+
// `eth1DataVotes.getAllReadonly()` navigates the tree once to fetch all the LeafNodes efficiently.
|
|
48
|
+
// Then isEqualEth1DataView compares cached roots (HashObject as of Jan 2022) which is much cheaper
|
|
49
|
+
// than doing structural equality, which requires tree -> value conversions
|
|
50
|
+
let sameVotesCount = 0;
|
|
51
|
+
// biome-ignore lint/complexity/noForEach: ssz api
|
|
52
|
+
state.eth1DataVotes.forEach((eth1DataVote) => {
|
|
53
|
+
if (isEqualEth1DataView(eth1DataVote, newEth1Data)) {
|
|
54
|
+
sameVotesCount++;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// The +1 is to account for the `eth1Data` supplied to the function.
|
|
59
|
+
if ((sameVotesCount + 1) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isEqualEth1DataView(
|
|
66
|
+
eth1DataA: CompositeViewDU<typeof ssz.phase0.Eth1Data>,
|
|
67
|
+
eth1DataB: CompositeViewDU<typeof ssz.phase0.Eth1Data>
|
|
68
|
+
): boolean {
|
|
69
|
+
return isEqualNode(eth1DataA.node, eth1DataB.node);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// TODO: Upstream to persistent-merkle-tree
|
|
73
|
+
function isEqualNode(nA: Node, nB: Node): boolean {
|
|
74
|
+
const hA = nA.rootHashObject;
|
|
75
|
+
const hB = nB.rootHashObject;
|
|
76
|
+
return (
|
|
77
|
+
hA.h0 === hB.h0 &&
|
|
78
|
+
hA.h1 === hB.h1 &&
|
|
79
|
+
hA.h2 === hB.h2 &&
|
|
80
|
+
hA.h3 === hB.h3 &&
|
|
81
|
+
hA.h4 === hB.h4 &&
|
|
82
|
+
hA.h5 === hB.h5 &&
|
|
83
|
+
hA.h6 === hB.h6 &&
|
|
84
|
+
hA.h7 === hB.h7
|
|
85
|
+
);
|
|
86
|
+
}
|