import { PublicKey } from "@chainsafe/blst"; import { PubkeyIndexMap } from "@chainsafe/pubkey-index-map"; import { BeaconConfig, ChainConfig } from "@lodestar/config"; import { ForkSeq } from "@lodestar/params"; import { Attestation, BLSSignature, CommitteeIndex, Epoch, IndexedAttestation, RootHex, Slot, SubnetID, SyncPeriod, ValidatorIndex, gloas } from "@lodestar/types"; import { LodestarError } from "@lodestar/utils"; import { EpochShuffling } from "../util/epochShuffling.js"; import { AttesterDuty } from "../util/shuffling.js"; import { EffectiveBalanceIncrements } from "./effectiveBalanceIncrements.js"; import { EpochTransitionCache } from "./epochTransitionCache.js"; import { Index2PubkeyCache } from "./pubkeyCache.js"; import { CachedBeaconStateAllForks } from "./stateCache.js"; import { SyncCommitteeCache } from "./syncCommitteeCache.js"; import { BeaconStateAllForks, ShufflingGetter } from "./types.js"; /** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */ export declare const PROPOSER_WEIGHT_FACTOR: number; export type EpochCacheImmutableData = { config: BeaconConfig; pubkey2index: PubkeyIndexMap; index2pubkey: Index2PubkeyCache; }; export type EpochCacheOpts = { skipSyncCommitteeCache?: boolean; skipSyncPubkeys?: boolean; shufflingGetter?: ShufflingGetter; }; /** Defers computing proposers by persisting only the seed, and dropping it once indexes are computed */ type ProposersDeferred = { computed: false; seed: Uint8Array; } | { computed: true; indexes: ValidatorIndex[]; }; /** * EpochCache is the parent object of: * - Any data-structures not part of the spec'ed BeaconState * - Necessary to only compute data once * - Must be kept at all times through an epoch * * The performance gains with EpochCache are fundamental for the BeaconNode to be able to participate in a * production network with 100_000s of validators. In summary, it contains: * * Expensive data constant through the epoch: * - pubkey cache * - proposer indexes * - shufflings * - sync committee indexed * Counters (maybe) mutated through the epoch: * - churnLimit * - exitQueueEpoch * - exitQueueChurn * Time data faster than recomputing from the state: * - epoch * - syncPeriod **/ export declare class EpochCache { config: BeaconConfig; /** * Unique globally shared pubkey registry. There should only exist one for the entire application. * * $VALIDATOR_COUNT x 192 char String -> Number Map */ pubkey2index: PubkeyIndexMap; /** * Unique globally shared pubkey registry. There should only exist one for the entire application. * * $VALIDATOR_COUNT x BLST deserialized pubkey (Jacobian coordinates) */ index2pubkey: Index2PubkeyCache; /** * Indexes of the block proposers for the current epoch. * For pre-fulu, this is computed and cached from the current shuffling. * For post-fulu, this is copied from the state.proposerLookahead. * * 32 x Number */ proposers: ValidatorIndex[]; /** Proposers for previous epoch, initialized to null in first epoch */ proposersPrevEpoch: ValidatorIndex[] | null; /** * The next proposer seed is only used in the getBeaconProposersNextEpoch call. It cannot be moved into * getBeaconProposersNextEpoch because it needs state as input and all data needed by getBeaconProposersNextEpoch * should be in the epoch context. * * For pre-fulu, this is lazily computed from the next epoch's shuffling. * For post-fulu, this is copied from the state.proposerLookahead. */ proposersNextEpoch: ProposersDeferred; /** * Epoch decision roots to look up correct shuffling from the Shuffling Cache */ previousDecisionRoot: RootHex; currentDecisionRoot: RootHex; nextDecisionRoot: RootHex; /** * Shuffling of validator indexes. Immutable through the epoch, then it's replaced entirely. * Note: Per spec definition, shuffling will always be defined. They are never called before loadState() * * $VALIDATOR_COUNT x Number */ previousShuffling: EpochShuffling; /** Same as previousShuffling */ currentShuffling: EpochShuffling; /** Same as previousShuffling */ nextShuffling: EpochShuffling; /** * Cache nextActiveIndices so that in afterProcessEpoch the next shuffling can be build synchronously * in case it is not built or the ShufflingCache is not available */ nextActiveIndices: Uint32Array; /** * Effective balances, for altair processAttestations() */ effectiveBalanceIncrements: EffectiveBalanceIncrements; /** * Total state.slashings by increment, for processSlashing() */ totalSlashingsByIncrement: number; syncParticipantReward: number; syncProposerReward: number; /** * Update freq: once per epoch after `process_effective_balance_updates()` */ baseRewardPerIncrement: number; /** * Total active balance for current epoch, to be used instead of getTotalBalance() */ totalActiveBalanceIncrements: number; /** * Rate at which validators can enter or leave the set per epoch. Depends only on activeIndexes, so it does not * change through the epoch. It's used in initiateValidatorExit(). Must be update after changing active indexes. */ churnLimit: number; /** * Fork limited actual activationChurnLimit */ activationChurnLimit: number; /** * Closest epoch with available churn for validators to exit at. May be updated every block as validators are * initiateValidatorExit(). This value may vary on each fork of the state. * * NOTE: Changes block to block * NOTE: No longer used by initiateValidatorExit post-electra */ exitQueueEpoch: Epoch; /** * Number of validators initiating an exit at exitQueueEpoch. May be updated every block as validators are * initiateValidatorExit(). This value may vary on each fork of the state. * * NOTE: Changes block to block * NOTE: No longer used by initiateValidatorExit post-electra */ exitQueueChurn: number; /** * Total cumulative balance increments through epoch for current target. * Required for unrealized checkpoints issue pull-up tips N+1. Otherwise must run epoch transition every block * This value is equivalent to: * - Forward current state to end-of-epoch * - Run beforeProcessEpoch * - epochTransitionCache.currEpochUnslashedTargetStakeByIncrement */ currentTargetUnslashedBalanceIncrements: number; /** * Total cumulative balance increments through epoch for previous target. * Required for unrealized checkpoints issue pull-up tips N+1. Otherwise must run epoch transition every block * This value is equivalent to: * - Forward current state to end-of-epoch * - Run beforeProcessEpoch * - epochTransitionCache.prevEpochUnslashedStake.targetStakeByIncrement */ previousTargetUnslashedBalanceIncrements: number; /** TODO: Indexed SyncCommitteeCache */ currentSyncCommitteeIndexed: SyncCommitteeCache; /** TODO: Indexed SyncCommitteeCache */ nextSyncCommitteeIndexed: SyncCommitteeCache; payloadTimelinessCommittee: ValidatorIndex[][]; syncPeriod: SyncPeriod; epoch: Epoch; get nextEpoch(): Epoch; constructor(data: { config: BeaconConfig; pubkey2index: PubkeyIndexMap; index2pubkey: Index2PubkeyCache; proposers: number[]; proposersPrevEpoch: number[] | null; proposersNextEpoch: ProposersDeferred; previousDecisionRoot: RootHex; currentDecisionRoot: RootHex; nextDecisionRoot: RootHex; previousShuffling: EpochShuffling; currentShuffling: EpochShuffling; nextShuffling: EpochShuffling; nextActiveIndices: Uint32Array; effectiveBalanceIncrements: EffectiveBalanceIncrements; totalSlashingsByIncrement: number; syncParticipantReward: number; syncProposerReward: number; baseRewardPerIncrement: number; totalActiveBalanceIncrements: number; churnLimit: number; activationChurnLimit: number; exitQueueEpoch: Epoch; exitQueueChurn: number; currentTargetUnslashedBalanceIncrements: number; previousTargetUnslashedBalanceIncrements: number; currentSyncCommitteeIndexed: SyncCommitteeCache; nextSyncCommitteeIndexed: SyncCommitteeCache; payloadTimelinessCommittee: ValidatorIndex[][]; epoch: Epoch; syncPeriod: SyncPeriod; }); /** * Create an epoch cache * @param state a finalized beacon state. Passing in unfinalized state may cause unexpected behaviour * * SLOW CODE - 🐢 */ static createFromState(state: BeaconStateAllForks, { config, pubkey2index, index2pubkey }: EpochCacheImmutableData, opts?: EpochCacheOpts): EpochCache; /** * Copies a given EpochCache while avoiding copying its immutable parts. */ clone(): EpochCache; /** * Called to re-use information, such as the shuffling of the next epoch, after transitioning into a * new epoch. Also handles pre-computation of values that may change during the upcoming epoch and * that get used in the following epoch transition. Often those pre-computations are not used by the * chain but are courtesy values that are served via the API for epoch look ahead of duties. * * Steps for afterProcessEpoch * 1) update previous/current/next values of cached items * * At fork boundary, this runs pre-fork logic and it happens before `upgradeState*` is called. */ afterProcessEpoch(state: CachedBeaconStateAllForks, epochTransitionCache: EpochTransitionCache): void; /** * At fork boundary, this runs post-fork logic and it happens after `upgradeState*` is called. */ finalProcessEpoch(state: CachedBeaconStateAllForks): void; beforeEpochTransition(): void; /** * Return the beacon committee at slot for index. */ getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array; /** * Return a Uint32Array[] representing committees of indices */ getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array[]; getCommitteeCountPerSlot(epoch: Epoch): number; /** * Compute the correct subnet for a slot/committee index */ computeSubnetForSlot(slot: number, committeeIndex: number): SubnetID; /** * Read from proposers instead of state.proposer_lookahead because we set it in `finalProcessEpoch()` * See https://github.com/ethereum/consensus-specs/blob/e9266b2145c09b63ba0039a9f477cfe8a629c78b/specs/fulu/beacon-chain.md#modified-get_beacon_proposer_index */ getBeaconProposer(slot: Slot): ValidatorIndex; getBeaconProposers(): ValidatorIndex[]; getBeaconProposersPrevEpoch(): ValidatorIndex[] | null; /** * We allow requesting proposal duties 1 epoch in the future as in normal network conditions it's possible to predict * the correct shuffling with high probability. While knowing the proposers in advance is not useful for consensus, * users want to know it to plan manteinance and avoid missing block proposals. * * **How to predict future proposers** * * Proposer duties for epoch N are guaranteed to be known at epoch N. Proposer duties depend exclusively on: * 1. seed (from randao_mix): known 2 epochs ahead * 2. active validator set: known 4 epochs ahead * 3. effective balance: not known ahead * * ```python * def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: * epoch = get_current_epoch(state) * seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) * indices = get_active_validator_indices(state, epoch) * return compute_proposer_index(state, indices, seed) * ``` * * **1**: If `MIN_SEED_LOOKAHEAD = 1` the randao_mix used for the seed is from 2 epochs ago. So at epoch N, the seed * is known and unchangable for duties at epoch N+1 and N+2 for proposer duties. * * ```python * def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: * mix = get_randao_mix(state, Epoch(epoch - MIN_SEED_LOOKAHEAD - 1)) * return hash(domain_type + uint_to_bytes(epoch) + mix) * ``` * * **2**: The active validator set can be predicted `MAX_SEED_LOOKAHEAD` in advance due to how activations are * processed. We already compute the active validator set for the next epoch to optimize epoch processing, so it's * reused here. * * **3**: Effective balance is not known ahead of time, but it rarely changes. Even if it changes, only a few * balances are sampled to adjust the probability of the next selection (32 per epoch on average). So to invalidate * the prediction the effective of one of those 32 samples should change and change the random_byte inequality. */ getBeaconProposersNextEpoch(): ValidatorIndex[]; /** * Return the indexed attestation corresponding to ``attestation``. */ getIndexedAttestation(fork: ForkSeq, attestation: Attestation): IndexedAttestation; /** * Return indices of validators who attestested in `attestation` */ getAttestingIndices(fork: ForkSeq, attestation: Attestation): number[]; getCommitteeAssignments(epoch: Epoch, requestedValidatorIndices: ValidatorIndex[]): Map; isAggregator(slot: Slot, index: CommitteeIndex, slotSignature: BLSSignature): boolean; /** * Return pubkey given the validator index. */ getPubkey(index: ValidatorIndex): PublicKey | undefined; getValidatorIndex(pubkey: Uint8Array): ValidatorIndex | null; addPubkey(index: ValidatorIndex, pubkey: Uint8Array): void; getShufflingAtSlot(slot: Slot): EpochShuffling; getShufflingAtSlotOrNull(slot: Slot): EpochShuffling | null; getShufflingAtEpoch(epoch: Epoch): EpochShuffling; getShufflingDecisionRoot(epoch: Epoch): RootHex; getShufflingAtEpochOrNull(epoch: Epoch): EpochShuffling | null; /** * Note: The range of slots a validator has to perform duties is off by one. * The previous slot wording means that if your validator is in a sync committee for a period that runs from slot * 100 to 200,then you would actually produce signatures in slot 99 - 199. */ getIndexedSyncCommittee(slot: Slot): SyncCommitteeCache; /** * **DO NOT USE FOR GOSSIP VALIDATION**: Sync committee duties are offset by one slot. @see {@link EpochCache.getIndexedSyncCommittee} * * Get indexed sync committee at epoch without offsets */ getIndexedSyncCommitteeAtEpoch(epoch: Epoch): SyncCommitteeCache; /** On processSyncCommitteeUpdates rotate next to current and set nextSyncCommitteeIndexed */ rotateSyncCommitteeIndexed(nextSyncCommitteeIndices: Uint32Array): void; /** On phase0 -> altair fork, set both current and nextSyncCommitteeIndexed */ setSyncCommitteesIndexed(nextSyncCommitteeIndices: Uint32Array): void; effectiveBalanceIncrementsSet(index: number, effectiveBalance: number): void; isPostElectra(): boolean; getPayloadTimelinessCommittee(slot: Slot): ValidatorIndex[]; getIndexedPayloadAttestation(slot: Slot, payloadAttestation: gloas.PayloadAttestation): gloas.IndexedPayloadAttestation; } export declare enum EpochCacheErrorCode { COMMITTEE_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_COMMITTEE_EPOCH_OUT_OF_RANGE", DECISION_ROOT_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_DECISION_ROOT_EPOCH_OUT_OF_RANGE", NEXT_SHUFFLING_NOT_AVAILABLE = "EPOCH_CONTEXT_ERROR_NEXT_SHUFFLING_NOT_AVAILABLE", NO_SYNC_COMMITTEE = "EPOCH_CONTEXT_ERROR_NO_SYNC_COMMITTEE", PROPOSER_EPOCH_MISMATCH = "EPOCH_CONTEXT_ERROR_PROPOSER_EPOCH_MISMATCH" } type EpochCacheErrorType = { code: EpochCacheErrorCode.COMMITTEE_EPOCH_OUT_OF_RANGE; requestedEpoch: Epoch; currentEpoch: Epoch; } | { code: EpochCacheErrorCode.DECISION_ROOT_EPOCH_OUT_OF_RANGE; requestedEpoch: Epoch; currentEpoch: Epoch; } | { code: EpochCacheErrorCode.NEXT_SHUFFLING_NOT_AVAILABLE; epoch: Epoch; decisionRoot: RootHex; } | { code: EpochCacheErrorCode.NO_SYNC_COMMITTEE; epoch: Epoch; } | { code: EpochCacheErrorCode.PROPOSER_EPOCH_MISMATCH; requestedEpoch: Epoch; currentEpoch: Epoch; }; export declare class EpochCacheError extends LodestarError { } export declare function createEmptyEpochCacheImmutableData(chainConfig: ChainConfig, state: Pick): EpochCacheImmutableData; export {}; //# sourceMappingURL=epochCache.d.ts.map