@lodestar/state-transition 1.35.0-dev.e9dd48f165 → 1.35.0-dev.f45a2be721
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/block/externalData.d.ts.map +1 -0
- package/lib/block/index.d.ts +2 -2
- package/lib/block/index.d.ts.map +1 -0
- package/lib/block/index.js +2 -2
- package/lib/block/index.js.map +1 -1
- package/lib/block/initiateValidatorExit.d.ts.map +1 -0
- package/lib/block/isValidIndexedAttestation.d.ts.map +1 -0
- package/lib/block/processAttestationPhase0.d.ts.map +1 -0
- package/lib/block/processAttestationPhase0.js +1 -2
- package/lib/block/processAttestationPhase0.js.map +1 -1
- package/lib/block/processAttestations.d.ts.map +1 -0
- package/lib/block/processAttestationsAltair.d.ts +1 -1
- package/lib/block/processAttestationsAltair.d.ts.map +1 -0
- package/lib/block/processAttestationsAltair.js +1 -1
- package/lib/block/processAttestationsAltair.js.map +1 -1
- package/lib/block/processAttesterSlashing.d.ts.map +1 -0
- package/lib/block/processAttesterSlashing.js.map +1 -1
- package/lib/block/processBlobKzgCommitments.d.ts.map +1 -0
- package/lib/block/processBlockHeader.d.ts.map +1 -0
- package/lib/block/processBlsToExecutionChange.d.ts.map +1 -0
- package/lib/block/processBlsToExecutionChange.js.map +1 -1
- package/lib/block/processConsolidationRequest.d.ts.map +1 -0
- package/lib/block/processConsolidationRequest.js.map +1 -1
- package/lib/block/processDeposit.d.ts +2 -2
- package/lib/block/processDeposit.d.ts.map +1 -0
- package/lib/block/processDeposit.js +1 -1
- package/lib/block/processDeposit.js.map +1 -1
- package/lib/block/processDepositRequest.d.ts.map +1 -0
- package/lib/block/processDepositRequest.js.map +1 -1
- package/lib/block/processEth1Data.d.ts.map +1 -0
- package/lib/block/processExecutionPayload.d.ts.map +1 -0
- package/lib/block/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/epochCache.js +130 -0
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/cache/epochTransitionCache.d.ts.map +1 -0
- package/lib/cache/epochTransitionCache.js.map +1 -1
- package/lib/cache/pubkeyCache.d.ts.map +1 -0
- package/lib/cache/rewardCache.d.ts.map +1 -0
- package/lib/cache/stateCache.d.ts.map +1 -0
- package/lib/cache/syncCommitteeCache.d.ts.map +1 -0
- package/lib/cache/types.d.ts +2 -2
- package/lib/cache/types.d.ts.map +1 -0
- package/lib/constants/constants.d.ts.map +1 -0
- package/lib/constants/index.d.ts.map +1 -0
- package/lib/epoch/computeUnrealizedCheckpoints.d.ts.map +1 -0
- package/lib/epoch/getAttestationDeltas.d.ts.map +1 -0
- package/lib/epoch/getRewardsAndPenalties.d.ts.map +1 -0
- package/lib/epoch/index.d.ts.map +1 -0
- package/lib/epoch/index.js.map +1 -1
- package/lib/epoch/processEffectiveBalanceUpdates.d.ts.map +1 -0
- package/lib/epoch/processEth1DataReset.d.ts.map +1 -0
- package/lib/epoch/processHistoricalRootsUpdate.d.ts.map +1 -0
- package/lib/epoch/processHistoricalSummariesUpdate.d.ts.map +1 -0
- package/lib/epoch/processInactivityUpdates.d.ts.map +1 -0
- package/lib/epoch/processJustificationAndFinalization.d.ts.map +1 -0
- package/lib/epoch/processParticipationFlagUpdates.d.ts.map +1 -0
- package/lib/epoch/processParticipationRecordUpdates.d.ts.map +1 -0
- package/lib/epoch/processPendingAttestations.d.ts.map +1 -0
- package/lib/epoch/processPendingConsolidations.d.ts.map +1 -0
- package/lib/epoch/processPendingDeposits.d.ts.map +1 -0
- package/lib/epoch/processProposerLookahead.d.ts.map +1 -0
- package/lib/epoch/processRandaoMixesReset.d.ts.map +1 -0
- package/lib/epoch/processRegistryUpdates.d.ts.map +1 -0
- package/lib/epoch/processRewardsAndPenalties.d.ts.map +1 -0
- package/lib/epoch/processSlashings.d.ts.map +1 -0
- package/lib/epoch/processSlashings.js.map +1 -1
- package/lib/epoch/processSlashingsReset.d.ts.map +1 -0
- package/lib/epoch/processSyncCommitteeUpdates.d.ts.map +1 -0
- package/lib/index.d.ts +17 -17
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +16 -16
- package/lib/index.js.map +1 -1
- package/lib/metrics.d.ts.map +1 -0
- package/lib/signatureSets/attesterSlashings.d.ts.map +1 -0
- package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -0
- package/lib/signatureSets/blsToExecutionChange.js.map +1 -1
- package/lib/signatureSets/index.d.ts +1 -1
- package/lib/signatureSets/index.d.ts.map +1 -0
- package/lib/signatureSets/index.js +1 -1
- package/lib/signatureSets/index.js.map +1 -1
- package/lib/signatureSets/indexedAttestation.d.ts.map +1 -0
- package/lib/signatureSets/proposer.d.ts.map +1 -0
- package/lib/signatureSets/proposerSlashings.d.ts.map +1 -0
- package/lib/signatureSets/randao.d.ts.map +1 -0
- package/lib/signatureSets/voluntaryExits.d.ts.map +1 -0
- package/lib/slot/index.d.ts.map +1 -0
- package/lib/slot/upgradeStateToAltair.d.ts.map +1 -0
- package/lib/slot/upgradeStateToBellatrix.d.ts.map +1 -0
- package/lib/slot/upgradeStateToCapella.d.ts.map +1 -0
- package/lib/slot/upgradeStateToDeneb.d.ts +1 -2
- package/lib/slot/upgradeStateToDeneb.d.ts.map +1 -0
- package/lib/slot/upgradeStateToDeneb.js.map +1 -1
- package/lib/slot/upgradeStateToElectra.d.ts.map +1 -0
- package/lib/slot/upgradeStateToFulu.d.ts.map +1 -0
- package/lib/slot/upgradeStateToGloas.d.ts.map +1 -0
- package/lib/slot/upgradeStateToGloas.js +2 -2
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/stateTransition.d.ts.map +1 -0
- package/lib/types.d.ts +2 -2
- package/lib/types.d.ts.map +1 -0
- package/lib/util/aggregator.d.ts.map +1 -0
- package/lib/util/altair.d.ts.map +1 -0
- package/lib/util/array.d.ts.map +1 -0
- package/lib/util/attestation.d.ts.map +1 -0
- package/lib/util/attesterStatus.d.ts.map +1 -0
- package/lib/util/balance.d.ts.map +1 -0
- package/lib/util/blindedBlock.d.ts +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 +3 -6
- package/lib/util/genesis.js.map +1 -1
- package/lib/util/index.d.ts +5 -5
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/index.js +5 -5
- package/lib/util/index.js.map +1 -1
- package/lib/util/interop.d.ts.map +1 -0
- package/lib/util/interop.js +1 -1
- package/lib/util/interop.js.map +1 -1
- package/lib/util/loadState/findModifiedInactivityScores.d.ts.map +1 -0
- package/lib/util/loadState/findModifiedValidators.d.ts.map +1 -0
- package/lib/util/loadState/index.d.ts.map +1 -0
- package/lib/util/loadState/loadState.d.ts.map +1 -0
- package/lib/util/loadState/loadValidator.d.ts.map +1 -0
- package/lib/util/rootCache.d.ts.map +1 -0
- package/lib/util/rootCache.js +5 -2
- package/lib/util/rootCache.js.map +1 -1
- package/lib/util/seed.d.ts.map +1 -0
- package/lib/util/seed.js +1 -2
- package/lib/util/seed.js.map +1 -1
- package/lib/util/shufflingDecisionRoot.d.ts.map +1 -0
- package/lib/util/signatureSets.d.ts.map +1 -0
- package/lib/util/signingRoot.d.ts.map +1 -0
- package/lib/util/slot.d.ts +0 -1
- package/lib/util/slot.d.ts.map +1 -0
- package/lib/util/slot.js +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
package/src/util/seed.ts
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import {digest} from "@chainsafe/as-sha256";
|
|
2
|
+
import {
|
|
3
|
+
computeProposerIndex as nativeComputeProposerIndex,
|
|
4
|
+
computeSyncCommitteeIndices as nativeComputeSyncCommitteeIndices,
|
|
5
|
+
} from "@chainsafe/swap-or-not-shuffle";
|
|
6
|
+
import {
|
|
7
|
+
DOMAIN_BEACON_PROPOSER,
|
|
8
|
+
DOMAIN_SYNC_COMMITTEE,
|
|
9
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
10
|
+
EPOCHS_PER_HISTORICAL_VECTOR,
|
|
11
|
+
ForkSeq,
|
|
12
|
+
MAX_EFFECTIVE_BALANCE,
|
|
13
|
+
MAX_EFFECTIVE_BALANCE_ELECTRA,
|
|
14
|
+
MIN_SEED_LOOKAHEAD,
|
|
15
|
+
SHUFFLE_ROUND_COUNT,
|
|
16
|
+
SLOTS_PER_EPOCH,
|
|
17
|
+
SYNC_COMMITTEE_SIZE,
|
|
18
|
+
} from "@lodestar/params";
|
|
19
|
+
import {Bytes32, DomainType, Epoch, ValidatorIndex} from "@lodestar/types";
|
|
20
|
+
import {assert, bytesToBigInt, bytesToInt, intToBytes} from "@lodestar/utils";
|
|
21
|
+
import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js";
|
|
22
|
+
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
|
|
23
|
+
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "./epoch.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute proposer indices for an epoch
|
|
27
|
+
*/
|
|
28
|
+
export function computeProposers(
|
|
29
|
+
fork: ForkSeq,
|
|
30
|
+
epochSeed: Uint8Array,
|
|
31
|
+
shuffling: {epoch: Epoch; activeIndices: Uint32Array},
|
|
32
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements
|
|
33
|
+
): number[] {
|
|
34
|
+
const startSlot = computeStartSlotAtEpoch(shuffling.epoch);
|
|
35
|
+
const proposers = [];
|
|
36
|
+
for (let slot = startSlot; slot < startSlot + SLOTS_PER_EPOCH; slot++) {
|
|
37
|
+
proposers.push(
|
|
38
|
+
computeProposerIndex(
|
|
39
|
+
fork,
|
|
40
|
+
effectiveBalanceIncrements,
|
|
41
|
+
shuffling.activeIndices,
|
|
42
|
+
// TODO: if we use hashTree, we can precompute the roots for the next n loops
|
|
43
|
+
digest(Buffer.concat([epochSeed, intToBytes(slot, 8)]))
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return proposers;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Return from ``indices`` a random index sampled by effective balance.
|
|
52
|
+
* This is just to make sure lodestar follows the spec, this is not for production.
|
|
53
|
+
*
|
|
54
|
+
* SLOW CODE - 🐢
|
|
55
|
+
*/
|
|
56
|
+
export function naiveComputeProposerIndex(
|
|
57
|
+
fork: ForkSeq,
|
|
58
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements,
|
|
59
|
+
indices: ArrayLike<ValidatorIndex>,
|
|
60
|
+
seed: Uint8Array
|
|
61
|
+
): ValidatorIndex {
|
|
62
|
+
if (indices.length === 0) {
|
|
63
|
+
throw Error("Validator indices must not be empty");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (fork >= ForkSeq.electra) {
|
|
67
|
+
const MAX_RANDOM_VALUE = 2 ** 16 - 1;
|
|
68
|
+
const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT;
|
|
69
|
+
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (true) {
|
|
72
|
+
const candidateIndex = indices[computeShuffledIndex(i % indices.length, indices.length, seed)];
|
|
73
|
+
const randomBytes = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 16), 8, "le")]));
|
|
74
|
+
const offset = (i % 16) * 2;
|
|
75
|
+
const randomValue = bytesToInt(randomBytes.subarray(offset, offset + 2));
|
|
76
|
+
|
|
77
|
+
const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex];
|
|
78
|
+
if (effectiveBalanceIncrement * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomValue) {
|
|
79
|
+
return candidateIndex;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
i += 1;
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
const MAX_RANDOM_BYTE = 2 ** 8 - 1;
|
|
86
|
+
const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT;
|
|
87
|
+
|
|
88
|
+
let i = 0;
|
|
89
|
+
while (true) {
|
|
90
|
+
const candidateIndex = indices[computeShuffledIndex(i % indices.length, indices.length, seed)];
|
|
91
|
+
const randomByte = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 32), 8, "le")]))[i % 32];
|
|
92
|
+
|
|
93
|
+
const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex];
|
|
94
|
+
if (effectiveBalanceIncrement * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomByte) {
|
|
95
|
+
return candidateIndex;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
i += 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Optimized version of `naiveComputeProposerIndex`.
|
|
105
|
+
* It shows > 3x speedup according to the perf test.
|
|
106
|
+
*/
|
|
107
|
+
export function computeProposerIndex(
|
|
108
|
+
fork: ForkSeq,
|
|
109
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements,
|
|
110
|
+
indices: Uint32Array,
|
|
111
|
+
seed: Uint8Array
|
|
112
|
+
): ValidatorIndex {
|
|
113
|
+
if (indices.length === 0) {
|
|
114
|
+
throw Error("Validator indices must not be empty");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let maxEffectiveBalance: number;
|
|
118
|
+
let randByteCount: number;
|
|
119
|
+
if (fork >= ForkSeq.electra) {
|
|
120
|
+
maxEffectiveBalance = MAX_EFFECTIVE_BALANCE_ELECTRA;
|
|
121
|
+
randByteCount = 2;
|
|
122
|
+
} else {
|
|
123
|
+
maxEffectiveBalance = MAX_EFFECTIVE_BALANCE;
|
|
124
|
+
randByteCount = 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return nativeComputeProposerIndex(
|
|
128
|
+
seed,
|
|
129
|
+
indices,
|
|
130
|
+
effectiveBalanceIncrements,
|
|
131
|
+
randByteCount,
|
|
132
|
+
maxEffectiveBalance,
|
|
133
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
134
|
+
SHUFFLE_ROUND_COUNT
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Return the proposer indices for the given `epoch`.
|
|
140
|
+
* A more generic version of `computeProposers`
|
|
141
|
+
*/
|
|
142
|
+
export function computeProposerIndices(
|
|
143
|
+
fork: ForkSeq,
|
|
144
|
+
state: CachedBeaconStateAllForks,
|
|
145
|
+
shuffling: {activeIndices: Uint32Array},
|
|
146
|
+
epoch: Epoch
|
|
147
|
+
): ValidatorIndex[] {
|
|
148
|
+
const startSlot = computeStartSlotAtEpoch(epoch);
|
|
149
|
+
const proposers = [];
|
|
150
|
+
const epochSeed = getSeed(state, epoch, DOMAIN_BEACON_PROPOSER);
|
|
151
|
+
|
|
152
|
+
for (let slot = startSlot; slot < startSlot + SLOTS_PER_EPOCH; slot++) {
|
|
153
|
+
proposers.push(
|
|
154
|
+
computeProposerIndex(
|
|
155
|
+
fork,
|
|
156
|
+
state.epochCtx.effectiveBalanceIncrements,
|
|
157
|
+
shuffling.activeIndices,
|
|
158
|
+
digest(Buffer.concat([epochSeed, intToBytes(slot, 8)]))
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return proposers;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Naive version, this is not supposed to be used in production.
|
|
167
|
+
* See `computeProposerIndex` for the optimized version.
|
|
168
|
+
*
|
|
169
|
+
* Return the sync committee indices for a given state and epoch.
|
|
170
|
+
* Aligns `epoch` to `baseEpoch` so the result is the same with any `epoch` within a sync period.
|
|
171
|
+
* Note: This function should only be called at sync committee period boundaries, as
|
|
172
|
+
* ``get_sync_committee_indices`` is not stable within a given period.
|
|
173
|
+
*
|
|
174
|
+
* SLOW CODE - 🐢
|
|
175
|
+
*/
|
|
176
|
+
export function naiveGetNextSyncCommitteeIndices(
|
|
177
|
+
fork: ForkSeq,
|
|
178
|
+
state: BeaconStateAllForks,
|
|
179
|
+
activeValidatorIndices: ArrayLike<ValidatorIndex>,
|
|
180
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements
|
|
181
|
+
): ValidatorIndex[] {
|
|
182
|
+
const syncCommitteeValidatorIndices = [];
|
|
183
|
+
|
|
184
|
+
if (fork >= ForkSeq.electra) {
|
|
185
|
+
const MAX_RANDOM_VALUE = 2 ** 16 - 1;
|
|
186
|
+
const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT;
|
|
187
|
+
|
|
188
|
+
const epoch = computeEpochAtSlot(state.slot) + 1;
|
|
189
|
+
const activeValidatorCount = activeValidatorIndices.length;
|
|
190
|
+
const seed = getSeed(state, epoch, DOMAIN_SYNC_COMMITTEE);
|
|
191
|
+
|
|
192
|
+
let i = 0;
|
|
193
|
+
while (syncCommitteeValidatorIndices.length < SYNC_COMMITTEE_SIZE) {
|
|
194
|
+
const shuffledIndex = computeShuffledIndex(i % activeValidatorCount, activeValidatorCount, seed);
|
|
195
|
+
const candidateIndex = activeValidatorIndices[shuffledIndex];
|
|
196
|
+
const randomBytes = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 16), 8, "le")]));
|
|
197
|
+
const offset = (i % 16) * 2;
|
|
198
|
+
const randomValue = bytesToInt(randomBytes.subarray(offset, offset + 2));
|
|
199
|
+
|
|
200
|
+
const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex];
|
|
201
|
+
if (effectiveBalanceIncrement * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomValue) {
|
|
202
|
+
syncCommitteeValidatorIndices.push(candidateIndex);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
i += 1;
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
const MAX_RANDOM_BYTE = 2 ** 8 - 1;
|
|
209
|
+
const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT;
|
|
210
|
+
|
|
211
|
+
const epoch = computeEpochAtSlot(state.slot) + 1;
|
|
212
|
+
const activeValidatorCount = activeValidatorIndices.length;
|
|
213
|
+
const seed = getSeed(state, epoch, DOMAIN_SYNC_COMMITTEE);
|
|
214
|
+
|
|
215
|
+
let i = 0;
|
|
216
|
+
while (syncCommitteeValidatorIndices.length < SYNC_COMMITTEE_SIZE) {
|
|
217
|
+
const shuffledIndex = computeShuffledIndex(i % activeValidatorCount, activeValidatorCount, seed);
|
|
218
|
+
const candidateIndex = activeValidatorIndices[shuffledIndex];
|
|
219
|
+
const randomByte = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 32), 8, "le")]))[i % 32];
|
|
220
|
+
|
|
221
|
+
const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex];
|
|
222
|
+
if (effectiveBalanceIncrement * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomByte) {
|
|
223
|
+
syncCommitteeValidatorIndices.push(candidateIndex);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
i += 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return syncCommitteeValidatorIndices;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Optmized version of `naiveGetNextSyncCommitteeIndices`.
|
|
235
|
+
*
|
|
236
|
+
* In the worse case scenario, this could be >1000x speedup according to the perf test.
|
|
237
|
+
*/
|
|
238
|
+
export function getNextSyncCommitteeIndices(
|
|
239
|
+
fork: ForkSeq,
|
|
240
|
+
state: BeaconStateAllForks,
|
|
241
|
+
activeValidatorIndices: Uint32Array,
|
|
242
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements
|
|
243
|
+
): Uint32Array {
|
|
244
|
+
let maxEffectiveBalance: number;
|
|
245
|
+
let randByteCount: number;
|
|
246
|
+
|
|
247
|
+
if (fork >= ForkSeq.electra) {
|
|
248
|
+
maxEffectiveBalance = MAX_EFFECTIVE_BALANCE_ELECTRA;
|
|
249
|
+
randByteCount = 2;
|
|
250
|
+
} else {
|
|
251
|
+
maxEffectiveBalance = MAX_EFFECTIVE_BALANCE;
|
|
252
|
+
randByteCount = 1;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const epoch = computeEpochAtSlot(state.slot) + 1;
|
|
256
|
+
const seed = getSeed(state, epoch, DOMAIN_SYNC_COMMITTEE);
|
|
257
|
+
return nativeComputeSyncCommitteeIndices(
|
|
258
|
+
seed,
|
|
259
|
+
activeValidatorIndices,
|
|
260
|
+
effectiveBalanceIncrements,
|
|
261
|
+
randByteCount,
|
|
262
|
+
SYNC_COMMITTEE_SIZE,
|
|
263
|
+
maxEffectiveBalance,
|
|
264
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
265
|
+
SHUFFLE_ROUND_COUNT
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Return the shuffled validator index corresponding to ``seed`` (and ``index_count``).
|
|
271
|
+
*
|
|
272
|
+
* Swap or not
|
|
273
|
+
* https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf
|
|
274
|
+
*
|
|
275
|
+
* See the 'generalized domain' algorithm on page 3.
|
|
276
|
+
* This is the naive implementation just to make sure lodestar follows the spec, this is not for production.
|
|
277
|
+
* The optimized version is in `getComputeShuffledIndexFn`.
|
|
278
|
+
*/
|
|
279
|
+
export function computeShuffledIndex(index: number, indexCount: number, seed: Bytes32): number {
|
|
280
|
+
let permuted = index;
|
|
281
|
+
assert.lt(index, indexCount, "indexCount must be less than index");
|
|
282
|
+
assert.lte(indexCount, 2 ** 40, "indexCount too big");
|
|
283
|
+
const _seed = seed;
|
|
284
|
+
for (let i = 0; i < SHUFFLE_ROUND_COUNT; i++) {
|
|
285
|
+
const pivot = Number(
|
|
286
|
+
bytesToBigInt(digest(Buffer.concat([_seed, intToBytes(i, 1)])).slice(0, 8)) % BigInt(indexCount)
|
|
287
|
+
);
|
|
288
|
+
const flip = (pivot + indexCount - permuted) % indexCount;
|
|
289
|
+
const position = Math.max(permuted, flip);
|
|
290
|
+
const source = digest(Buffer.concat([_seed, intToBytes(i, 1), intToBytes(Math.floor(position / 256), 4)]));
|
|
291
|
+
const byte = source[Math.floor((position % 256) / 8)];
|
|
292
|
+
const bit = (byte >> (position % 8)) % 2;
|
|
293
|
+
permuted = bit ? flip : permuted;
|
|
294
|
+
}
|
|
295
|
+
return permuted;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
type ComputeShuffledIndexFn = (index: number) => number;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* An optimized version of `computeShuffledIndex`, this is for production.
|
|
302
|
+
*/
|
|
303
|
+
export function getComputeShuffledIndexFn(indexCount: number, seed: Bytes32): ComputeShuffledIndexFn {
|
|
304
|
+
// there are possibly SHUFFLE_ROUND_COUNT (90 for mainnet) values for this cache
|
|
305
|
+
// this cache will always hit after the 1st call
|
|
306
|
+
const pivotByIndex: Map<number, number> = new Map();
|
|
307
|
+
// given 2M active validators, there are 2 M / 256 = 8k possible positionDiv
|
|
308
|
+
// it means there are at most 8k different sources for each round
|
|
309
|
+
const sourceByPositionDivByIndex: Map<number, Map<number, Uint8Array>> = new Map();
|
|
310
|
+
// 32 bytes seed + 1 byte i
|
|
311
|
+
const pivotBuffer = Buffer.alloc(32 + 1);
|
|
312
|
+
pivotBuffer.set(seed, 0);
|
|
313
|
+
// 32 bytes seed + 1 byte i + 4 bytes positionDiv
|
|
314
|
+
const sourceBuffer = Buffer.alloc(32 + 1 + 4);
|
|
315
|
+
sourceBuffer.set(seed, 0);
|
|
316
|
+
|
|
317
|
+
return (index): number => {
|
|
318
|
+
assert.lt(index, indexCount, "indexCount must be less than index");
|
|
319
|
+
assert.lte(indexCount, 2 ** 40, "indexCount too big");
|
|
320
|
+
let permuted = index;
|
|
321
|
+
// const _seed = seed;
|
|
322
|
+
for (let i = 0; i < SHUFFLE_ROUND_COUNT; i++) {
|
|
323
|
+
// optimized version of the below naive code
|
|
324
|
+
// const pivot = Number(
|
|
325
|
+
// bytesToBigInt(digest(Buffer.concat([_seed, intToBytes(i, 1)])).slice(0, 8)) % BigInt(indexCount)
|
|
326
|
+
// );
|
|
327
|
+
|
|
328
|
+
let pivot = pivotByIndex.get(i);
|
|
329
|
+
if (pivot == null) {
|
|
330
|
+
// naive version always creates a new buffer, we can reuse the buffer
|
|
331
|
+
// pivot = Number(
|
|
332
|
+
// bytesToBigInt(digest(Buffer.concat([_seed, intToBytes(i, 1)])).slice(0, 8)) % BigInt(indexCount)
|
|
333
|
+
// );
|
|
334
|
+
pivotBuffer[32] = i % 256;
|
|
335
|
+
pivot = Number(bytesToBigInt(digest(pivotBuffer).subarray(0, 8)) % BigInt(indexCount));
|
|
336
|
+
pivotByIndex.set(i, pivot);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const flip = (pivot + indexCount - permuted) % indexCount;
|
|
340
|
+
const position = Math.max(permuted, flip);
|
|
341
|
+
|
|
342
|
+
// optimized version of the below naive code
|
|
343
|
+
// const source = digest(Buffer.concat([_seed, intToBytes(i, 1), intToBytes(Math.floor(position / 256), 4)]));
|
|
344
|
+
let sourceByPositionDiv = sourceByPositionDivByIndex.get(i);
|
|
345
|
+
if (sourceByPositionDiv == null) {
|
|
346
|
+
sourceByPositionDiv = new Map<number, Uint8Array>();
|
|
347
|
+
sourceByPositionDivByIndex.set(i, sourceByPositionDiv);
|
|
348
|
+
}
|
|
349
|
+
const positionDiv256 = Math.floor(position / 256);
|
|
350
|
+
let source = sourceByPositionDiv.get(positionDiv256);
|
|
351
|
+
if (source == null) {
|
|
352
|
+
// naive version always creates a new buffer, we can reuse the buffer
|
|
353
|
+
// don't want to go through intToBytes() to avoid BigInt
|
|
354
|
+
sourceBuffer[32] = i % 256;
|
|
355
|
+
sourceBuffer.writeUint32LE(positionDiv256, 33);
|
|
356
|
+
source = digest(sourceBuffer);
|
|
357
|
+
sourceByPositionDiv.set(positionDiv256, source);
|
|
358
|
+
}
|
|
359
|
+
const byte = source[Math.floor((position % 256) / 8)];
|
|
360
|
+
const bit = (byte >> (position % 8)) % 2;
|
|
361
|
+
permuted = bit ? flip : permuted;
|
|
362
|
+
}
|
|
363
|
+
return permuted;
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Return the randao mix at a recent [[epoch]].
|
|
369
|
+
*/
|
|
370
|
+
export function getRandaoMix(state: BeaconStateAllForks, epoch: Epoch): Bytes32 {
|
|
371
|
+
return state.randaoMixes.get(epoch % EPOCHS_PER_HISTORICAL_VECTOR);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Return the seed at [[epoch]].
|
|
376
|
+
*/
|
|
377
|
+
export function getSeed(state: BeaconStateAllForks, epoch: Epoch, domainType: DomainType): Uint8Array {
|
|
378
|
+
const mix = getRandaoMix(state, epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1);
|
|
379
|
+
|
|
380
|
+
return digest(Buffer.concat([domainType as Buffer, intToBytes(epoch, 8), mix]));
|
|
381
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {Epoch, Root, Slot} from "@lodestar/types";
|
|
2
|
+
import {CachedBeaconStateAllForks} from "../types.js";
|
|
3
|
+
import {getBlockRootAtSlot} from "./blockRoot.js";
|
|
4
|
+
import {computeStartSlotAtEpoch} from "./epoch.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns the block root which decided the proposer shuffling for the current epoch. This root
|
|
8
|
+
* can be used to key this proposer shuffling.
|
|
9
|
+
*
|
|
10
|
+
* Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
|
|
11
|
+
* It should be set to the latest block applied to this `state` or the genesis block root.
|
|
12
|
+
*/
|
|
13
|
+
export function proposerShufflingDecisionRoot(state: CachedBeaconStateAllForks): Root | null {
|
|
14
|
+
const decisionSlot = proposerShufflingDecisionSlot(state);
|
|
15
|
+
if (state.slot === decisionSlot) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return getBlockRootAtSlot(state, decisionSlot);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns the slot at which the proposer shuffling was decided. The block root at this slot
|
|
23
|
+
* can be used to key the proposer shuffling for the current epoch.
|
|
24
|
+
*/
|
|
25
|
+
function proposerShufflingDecisionSlot(state: CachedBeaconStateAllForks): Slot {
|
|
26
|
+
const startSlot = computeStartSlotAtEpoch(state.epochCtx.epoch);
|
|
27
|
+
return Math.max(startSlot - 1, 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the block root which decided the attester shuffling for the given `requestedEpoch`.
|
|
32
|
+
* This root can be used to key that attester shuffling.
|
|
33
|
+
*
|
|
34
|
+
* Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
|
|
35
|
+
* It should be set to the latest block applied to this `state` or the genesis block root.
|
|
36
|
+
*/
|
|
37
|
+
export function attesterShufflingDecisionRoot(state: CachedBeaconStateAllForks, requestedEpoch: Epoch): Root | null {
|
|
38
|
+
const decisionSlot = attesterShufflingDecisionSlot(state, requestedEpoch);
|
|
39
|
+
if (state.slot === decisionSlot) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return getBlockRootAtSlot(state, decisionSlot);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the slot at which the proposer shuffling was decided. The block root at this slot
|
|
47
|
+
* can be used to key the proposer shuffling for the current epoch.
|
|
48
|
+
*/
|
|
49
|
+
function attesterShufflingDecisionSlot(state: CachedBeaconStateAllForks, requestedEpoch: Epoch): Slot {
|
|
50
|
+
const epoch = attesterShufflingDecisionEpoch(state, requestedEpoch);
|
|
51
|
+
const slot = computeStartSlotAtEpoch(epoch);
|
|
52
|
+
return Math.max(slot - 1, 0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the epoch at which the attester shuffling was decided.
|
|
57
|
+
*
|
|
58
|
+
* Spec ref: https://github.com/ethereum/beacon-APIs/blob/v2.1.0/apis/validator/duties/attester.yaml#L15
|
|
59
|
+
*
|
|
60
|
+
* Throws an error when:
|
|
61
|
+
* - `EpochTooLow` when `requestedEpoch` is more than 1 prior to `currentEpoch`.
|
|
62
|
+
* - `EpochTooHigh` when `requestedEpoch` is more than 1 after `currentEpoch`.
|
|
63
|
+
*/
|
|
64
|
+
function attesterShufflingDecisionEpoch(state: CachedBeaconStateAllForks, requestedEpoch: Epoch): Epoch {
|
|
65
|
+
const currentEpoch = state.epochCtx.epoch;
|
|
66
|
+
|
|
67
|
+
// Next
|
|
68
|
+
if (requestedEpoch === currentEpoch + 1) return currentEpoch;
|
|
69
|
+
// Current
|
|
70
|
+
if (requestedEpoch === currentEpoch) return Math.max(currentEpoch - 1, 0);
|
|
71
|
+
// Previous
|
|
72
|
+
if (requestedEpoch === currentEpoch - 1) return Math.max(currentEpoch - 2, 0);
|
|
73
|
+
|
|
74
|
+
if (requestedEpoch < currentEpoch) {
|
|
75
|
+
throw Error(`EpochTooLow: current ${currentEpoch} requested ${requestedEpoch}`);
|
|
76
|
+
}
|
|
77
|
+
throw Error(`EpochTooHigh: current ${currentEpoch} requested ${requestedEpoch}`);
|
|
78
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {PublicKey, Signature, fastAggregateVerify, verify} from "@chainsafe/blst";
|
|
2
|
+
import {Root} from "@lodestar/types";
|
|
3
|
+
|
|
4
|
+
export enum SignatureSetType {
|
|
5
|
+
single = "single",
|
|
6
|
+
aggregate = "aggregate",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type SingleSignatureSet = {
|
|
10
|
+
type: SignatureSetType.single;
|
|
11
|
+
pubkey: PublicKey;
|
|
12
|
+
signingRoot: Root;
|
|
13
|
+
signature: Uint8Array;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type AggregatedSignatureSet = {
|
|
17
|
+
type: SignatureSetType.aggregate;
|
|
18
|
+
pubkeys: PublicKey[];
|
|
19
|
+
signingRoot: Root;
|
|
20
|
+
signature: Uint8Array;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ISignatureSet = SingleSignatureSet | AggregatedSignatureSet;
|
|
24
|
+
|
|
25
|
+
export function verifySignatureSet(signatureSet: ISignatureSet): boolean {
|
|
26
|
+
// All signatures are not trusted and must be group checked (p2.subgroup_check)
|
|
27
|
+
const signature = Signature.fromBytes(signatureSet.signature, true);
|
|
28
|
+
|
|
29
|
+
switch (signatureSet.type) {
|
|
30
|
+
case SignatureSetType.single:
|
|
31
|
+
return verify(signatureSet.signingRoot, signatureSet.pubkey, signature);
|
|
32
|
+
|
|
33
|
+
case SignatureSetType.aggregate:
|
|
34
|
+
return fastAggregateVerify(signatureSet.signingRoot, signatureSet.pubkeys, signature);
|
|
35
|
+
|
|
36
|
+
default:
|
|
37
|
+
throw Error("Unknown signature set type");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createSingleSignatureSetFromComponents(
|
|
42
|
+
pubkey: PublicKey,
|
|
43
|
+
signingRoot: Root,
|
|
44
|
+
signature: Uint8Array
|
|
45
|
+
): SingleSignatureSet {
|
|
46
|
+
return {
|
|
47
|
+
type: SignatureSetType.single,
|
|
48
|
+
pubkey,
|
|
49
|
+
signingRoot,
|
|
50
|
+
signature,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createAggregateSignatureSetFromComponents(
|
|
55
|
+
pubkeys: PublicKey[],
|
|
56
|
+
signingRoot: Root,
|
|
57
|
+
signature: Uint8Array
|
|
58
|
+
): AggregatedSignatureSet {
|
|
59
|
+
return {
|
|
60
|
+
type: SignatureSetType.aggregate,
|
|
61
|
+
pubkeys,
|
|
62
|
+
signingRoot,
|
|
63
|
+
signature,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {Type} from "@chainsafe/ssz";
|
|
2
|
+
import {Domain, phase0, ssz} from "@lodestar/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return the signing root of an object by calculating the root of the object-domain tree.
|
|
6
|
+
*/
|
|
7
|
+
export function computeSigningRoot<T>(type: Type<T>, sszObject: T, domain: Domain): Uint8Array {
|
|
8
|
+
const domainWrappedObject: phase0.SigningData = {
|
|
9
|
+
objectRoot: type.hashTreeRoot(sszObject),
|
|
10
|
+
domain,
|
|
11
|
+
};
|
|
12
|
+
return ssz.phase0.SigningData.hashTreeRoot(domainWrappedObject);
|
|
13
|
+
}
|
package/src/util/slot.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {ChainConfig} from "@lodestar/config";
|
|
2
|
+
import {GENESIS_SLOT} from "@lodestar/params";
|
|
3
|
+
import {Epoch, Slot, TimeSeconds} from "@lodestar/types";
|
|
4
|
+
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "./epoch.js";
|
|
5
|
+
|
|
6
|
+
export function getSlotsSinceGenesis(config: ChainConfig, genesisTime: TimeSeconds): Slot {
|
|
7
|
+
const diffInSeconds = Date.now() / 1000 - genesisTime;
|
|
8
|
+
return Math.floor(diffInSeconds / (config.SLOT_DURATION_MS / 1000));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCurrentSlot(config: ChainConfig, genesisTime: TimeSeconds): Slot {
|
|
12
|
+
return GENESIS_SLOT + getSlotsSinceGenesis(config, genesisTime);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function computeSlotsSinceEpochStart(slot: Slot, epoch?: Epoch): Slot {
|
|
16
|
+
const computeEpoch = epoch ?? computeEpochAtSlot(slot);
|
|
17
|
+
return slot - computeStartSlotAtEpoch(computeEpoch);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function computeTimeAtSlot(config: ChainConfig, slot: Slot, genesisTime: TimeSeconds): TimeSeconds {
|
|
21
|
+
return genesisTime + slot * (config.SLOT_DURATION_MS / 1000);
|
|
22
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
+
import {ForkAll, ForkSeq} from "@lodestar/params";
|
|
3
|
+
import {SSZTypesFor, Slot} from "@lodestar/types";
|
|
4
|
+
import {bytesToInt} from "@lodestar/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Slot uint64
|
|
8
|
+
*/
|
|
9
|
+
const SLOT_BYTE_COUNT = 8;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 48 + 32 + 8 + 1 + 8 + 8 + 8 + 8 = 121
|
|
13
|
+
* ```
|
|
14
|
+
* class Validator(Container):
|
|
15
|
+
pubkey: BLSPubkey [fixed - 48 bytes]
|
|
16
|
+
withdrawal_credentials: Bytes32 [fixed - 32 bytes]
|
|
17
|
+
effective_balance: Gwei [fixed - 8 bytes]
|
|
18
|
+
slashed: boolean [fixed - 1 byte]
|
|
19
|
+
# Status epochs
|
|
20
|
+
activation_eligibility_epoch: Epoch [fixed - 8 bytes]
|
|
21
|
+
activation_epoch: Epoch [fixed - 8 bytes]
|
|
22
|
+
exit_epoch: Epoch [fixed - 8 bytes]
|
|
23
|
+
withdrawable_epoch: Epoch [fixed - 8 bytes]
|
|
24
|
+
```
|
|
25
|
+
*/
|
|
26
|
+
export const VALIDATOR_BYTES_SIZE = 121;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 8 + 32 = 40
|
|
30
|
+
* ```
|
|
31
|
+
* class BeaconState(Container):
|
|
32
|
+
* genesis_time: uint64 [fixed - 8 bytes]
|
|
33
|
+
* genesis_validators_root: Root [fixed - 32 bytes]
|
|
34
|
+
* slot: Slot [fixed - 8 bytes]
|
|
35
|
+
* ...
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
const SLOT_BYTES_POSITION_IN_STATE = 40;
|
|
39
|
+
|
|
40
|
+
export function getForkFromStateBytes(config: ChainForkConfig, bytes: Uint8Array): ForkSeq {
|
|
41
|
+
const slot = bytesToInt(bytes.subarray(SLOT_BYTES_POSITION_IN_STATE, SLOT_BYTES_POSITION_IN_STATE + SLOT_BYTE_COUNT));
|
|
42
|
+
return config.getForkSeq(slot);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getStateTypeFromBytes(config: ChainForkConfig, bytes: Uint8Array): SSZTypesFor<ForkAll, "BeaconState"> {
|
|
46
|
+
const slot = getStateSlotFromBytes(bytes);
|
|
47
|
+
return config.getForkTypes(slot).BeaconState;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getStateSlotFromBytes(bytes: Uint8Array): Slot {
|
|
51
|
+
return bytesToInt(bytes.subarray(SLOT_BYTES_POSITION_IN_STATE, SLOT_BYTES_POSITION_IN_STATE + SLOT_BYTE_COUNT));
|
|
52
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {aggregateSerializedPublicKeys} from "@chainsafe/blst";
|
|
2
|
+
import {
|
|
3
|
+
BASE_REWARD_FACTOR,
|
|
4
|
+
EFFECTIVE_BALANCE_INCREMENT,
|
|
5
|
+
ForkSeq,
|
|
6
|
+
SLOTS_PER_EPOCH,
|
|
7
|
+
SYNC_COMMITTEE_SIZE,
|
|
8
|
+
SYNC_REWARD_WEIGHT,
|
|
9
|
+
WEIGHT_DENOMINATOR,
|
|
10
|
+
} from "@lodestar/params";
|
|
11
|
+
import {altair} from "@lodestar/types";
|
|
12
|
+
import {bigIntSqrt} from "@lodestar/utils";
|
|
13
|
+
import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js";
|
|
14
|
+
import {BeaconStateAllForks} from "../types.js";
|
|
15
|
+
import {getNextSyncCommitteeIndices} from "./seed.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Return the sync committee for a given state and epoch.
|
|
19
|
+
*
|
|
20
|
+
* SLOW CODE - 🐢
|
|
21
|
+
*/
|
|
22
|
+
export function getNextSyncCommittee(
|
|
23
|
+
fork: ForkSeq,
|
|
24
|
+
state: BeaconStateAllForks,
|
|
25
|
+
activeValidatorIndices: Uint32Array,
|
|
26
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements
|
|
27
|
+
): {indices: Uint32Array; syncCommittee: altair.SyncCommittee} {
|
|
28
|
+
const indices = getNextSyncCommitteeIndices(fork, state, activeValidatorIndices, effectiveBalanceIncrements);
|
|
29
|
+
|
|
30
|
+
// Using the index2pubkey cache is slower because it needs the serialized pubkey.
|
|
31
|
+
const pubkeys = [];
|
|
32
|
+
for (const index of indices) {
|
|
33
|
+
pubkeys.push(state.validators.getReadonly(index).pubkey);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
indices,
|
|
38
|
+
syncCommittee: {
|
|
39
|
+
pubkeys,
|
|
40
|
+
aggregatePubkey: aggregateSerializedPublicKeys(pubkeys).toBytes(),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Same logic in https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#sync-committee-processing
|
|
47
|
+
*/
|
|
48
|
+
export function computeSyncParticipantReward(totalActiveBalanceIncrements: number): number {
|
|
49
|
+
const totalActiveBalance = BigInt(totalActiveBalanceIncrements) * BigInt(EFFECTIVE_BALANCE_INCREMENT);
|
|
50
|
+
const baseRewardPerIncrement = Math.floor(
|
|
51
|
+
(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR) / Number(bigIntSqrt(totalActiveBalance))
|
|
52
|
+
);
|
|
53
|
+
const totalBaseRewards = baseRewardPerIncrement * totalActiveBalanceIncrements;
|
|
54
|
+
const maxParticipantRewards = Math.floor(
|
|
55
|
+
Math.floor((totalBaseRewards * SYNC_REWARD_WEIGHT) / WEIGHT_DENOMINATOR) / SLOTS_PER_EPOCH
|
|
56
|
+
);
|
|
57
|
+
return Math.floor(maxParticipantRewards / SYNC_COMMITTEE_SIZE);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Before we manage bigIntSqrt(totalActiveStake) as BigInt and return BigInt.
|
|
62
|
+
* bigIntSqrt(totalActiveStake) should fit a number (2 ** 53 -1 max)
|
|
63
|
+
**/
|
|
64
|
+
export function computeBaseRewardPerIncrement(totalActiveStakeByIncrement: number): number {
|
|
65
|
+
return Math.floor(
|
|
66
|
+
(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR) /
|
|
67
|
+
Number(bigIntSqrt(BigInt(totalActiveStakeByIncrement) * BigInt(EFFECTIVE_BALANCE_INCREMENT)))
|
|
68
|
+
);
|
|
69
|
+
}
|