diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/index.ts b/packages/lodestar-beacon-state-transition/src/fast/block/index.ts index d40e3f0ce090..67b2438e05cb 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/index.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/index.ts @@ -1,5 +1,5 @@ -import {EpochContext} from "../util"; -import {BeaconBlock, BeaconState} from "@chainsafe/lodestar-types"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; +import {BeaconBlock} from "@chainsafe/lodestar-types"; import {processBlockHeader} from "./processBlockHeader"; import {processRandao} from "./processRandao"; @@ -25,7 +25,7 @@ export { export function processBlock( epochCtx: EpochContext, - state: BeaconState, + state: CachedValidatorsBeaconState, block: BeaconBlock, verifySignatures = true ): void { diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/initiateValidatorExit.ts b/packages/lodestar-beacon-state-transition/src/fast/block/initiateValidatorExit.ts index 6f94e91373bb..3b8c7b600c74 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/initiateValidatorExit.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/initiateValidatorExit.ts @@ -1,25 +1,27 @@ -import {readOnlyMap} from "@chainsafe/ssz"; -import {BeaconState, ValidatorIndex} from "@chainsafe/lodestar-types"; +import {ValidatorIndex} from "@chainsafe/lodestar-types"; import {FAR_FUTURE_EPOCH} from "../../constants"; import {computeActivationExitEpoch, getChurnLimit} from "../../util"; -import {EpochContext} from "../util"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; /** * Initiate the exit of the validator with index ``index``. */ -export function initiateValidatorExit(epochCtx: EpochContext, state: BeaconState, index: ValidatorIndex): void { +export function initiateValidatorExit( + epochCtx: EpochContext, + state: CachedValidatorsBeaconState, + index: ValidatorIndex +): void { const config = epochCtx.config; // return if validator already initiated exit - const validator = state.validators[index]; - if (validator.exitEpoch !== FAR_FUTURE_EPOCH) { + if (state.validators[index].exitEpoch !== FAR_FUTURE_EPOCH) { return; } const currentEpoch = epochCtx.currentShuffling.epoch; // compute exit queue epoch - const validatorExitEpochs = readOnlyMap(state.validators, (v) => v.exitEpoch); + const validatorExitEpochs = state.flatValidators().map((v) => v.exitEpoch); const exitEpochs = validatorExitEpochs.filter((exitEpoch) => exitEpoch !== FAR_FUTURE_EPOCH); exitEpochs.push(computeActivationExitEpoch(config, currentEpoch)); let exitQueueEpoch = Math.max(...exitEpochs); @@ -29,6 +31,8 @@ export function initiateValidatorExit(epochCtx: EpochContext, state: BeaconState } // set validator exit epoch and withdrawable epoch - validator.exitEpoch = exitQueueEpoch; - validator.withdrawableEpoch = validator.exitEpoch + config.params.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; + state.setValidator(index, { + exitEpoch: exitQueueEpoch, + withdrawableEpoch: exitQueueEpoch + config.params.MIN_VALIDATOR_WITHDRAWABILITY_DELAY, + }); } diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/processAttesterSlashing.ts b/packages/lodestar-beacon-state-transition/src/fast/block/processAttesterSlashing.ts index c4eaf08e7060..2e2ad624aac8 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/processAttesterSlashing.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/processAttesterSlashing.ts @@ -1,13 +1,13 @@ -import {AttesterSlashing, BeaconState} from "@chainsafe/lodestar-types"; +import {AttesterSlashing, ValidatorIndex} from "@chainsafe/lodestar-types"; import {isSlashableValidator, isSlashableAttestationData} from "../../util"; -import {EpochContext} from "../util"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; import {slashValidator} from "./slashValidator"; import {isValidIndexedAttestation} from "./isValidIndexedAttestation"; export function processAttesterSlashing( epochCtx: EpochContext, - state: BeaconState, + state: CachedValidatorsBeaconState, attesterSlashing: AttesterSlashing, verifySignatures = true ): void { @@ -27,7 +27,7 @@ export function processAttesterSlashing( let slashedAny = false; const attSet1 = new Set(attestation1.attestingIndices); const attSet2 = new Set(attestation2.attestingIndices); - const indices = []; + const indices: ValidatorIndex[] = []; for (const i of attSet1.values()) { if (attSet2.has(i)) { indices.push(i); diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/processDeposit.ts b/packages/lodestar-beacon-state-transition/src/fast/block/processDeposit.ts index afff5980aa7c..34c466c91b76 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/processDeposit.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/processDeposit.ts @@ -1,12 +1,12 @@ import bls from "@chainsafe/bls"; -import {BeaconState, Deposit} from "@chainsafe/lodestar-types"; +import {Deposit} from "@chainsafe/lodestar-types"; import {verifyMerkleBranch, bigIntMin} from "@chainsafe/lodestar-utils"; import {DEPOSIT_CONTRACT_TREE_DEPTH, DomainType, FAR_FUTURE_EPOCH} from "../../constants"; import {computeDomain, computeSigningRoot, increaseBalance} from "../../util"; -import {EpochContext} from "../util"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; -export function processDeposit(epochCtx: EpochContext, state: BeaconState, deposit: Deposit): void { +export function processDeposit(epochCtx: EpochContext, state: CachedValidatorsBeaconState, deposit: Deposit): void { const config = epochCtx.config; const {EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE} = config.params; // verify the merkle branch @@ -43,7 +43,7 @@ export function processDeposit(epochCtx: EpochContext, state: BeaconState, depos } // add validator and balance entries - state.validators.push({ + state.addValidator({ pubkey: pubkey, withdrawalCredentials: deposit.data.withdrawalCredentials, activationEligibilityEpoch: FAR_FUTURE_EPOCH, diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/processOperations.ts b/packages/lodestar-beacon-state-transition/src/fast/block/processOperations.ts index 0259ac8b6678..04a73e5fcd9a 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/processOperations.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/processOperations.ts @@ -3,13 +3,12 @@ import { Attestation, AttesterSlashing, BeaconBlockBody, - BeaconState, Deposit, ProposerSlashing, VoluntaryExit, } from "@chainsafe/lodestar-types"; -import {EpochContext} from "../util"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; import {processProposerSlashing} from "./processProposerSlashing"; import {processAttesterSlashing} from "./processAttesterSlashing"; import {processAttestation} from "./processAttestation"; @@ -17,11 +16,16 @@ import {processDeposit} from "./processDeposit"; import {processVoluntaryExit} from "./processVoluntaryExit"; type Operation = ProposerSlashing | AttesterSlashing | Attestation | Deposit | VoluntaryExit; -type OperationFunction = (epochCtx: EpochContext, state: BeaconState, op: Operation, verify: boolean) => void; +type OperationFunction = ( + epochCtx: EpochContext, + state: CachedValidatorsBeaconState, + op: Operation, + verify: boolean +) => void; export function processOperations( epochCtx: EpochContext, - state: BeaconState, + state: CachedValidatorsBeaconState, body: BeaconBlockBody, verifySignatures = true ): void { diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/processProposerSlashing.ts b/packages/lodestar-beacon-state-transition/src/fast/block/processProposerSlashing.ts index 001a11f64539..c3fdc222f918 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/processProposerSlashing.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/processProposerSlashing.ts @@ -1,13 +1,13 @@ import {BeaconState, ProposerSlashing} from "@chainsafe/lodestar-types"; import {DomainType} from "../../constants"; import {computeEpochAtSlot, computeSigningRoot, getDomain, isSlashableValidator} from "../../util"; -import {EpochContext} from "../util"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; import {slashValidator} from "./slashValidator"; import {ISignatureSet, SignatureSetType, verifySignatureSet} from "../signatureSets"; export function processProposerSlashing( epochCtx: EpochContext, - state: BeaconState, + state: CachedValidatorsBeaconState, proposerSlashing: ProposerSlashing, verifySignatures = true ): void { diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/processVoluntaryExit.ts b/packages/lodestar-beacon-state-transition/src/fast/block/processVoluntaryExit.ts index 06b7ae44ffd0..48dd5eefe1a9 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/processVoluntaryExit.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/processVoluntaryExit.ts @@ -2,12 +2,12 @@ import {BeaconState, SignedVoluntaryExit} from "@chainsafe/lodestar-types"; import {DomainType, FAR_FUTURE_EPOCH} from "../../constants"; import {computeSigningRoot, getDomain, isActiveValidator} from "../../util"; import {ISignatureSet, SignatureSetType, verifySignatureSet} from "../signatureSets"; -import {EpochContext} from "../util"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; import {initiateValidatorExit} from "./initiateValidatorExit"; export function processVoluntaryExit( epochCtx: EpochContext, - state: BeaconState, + state: CachedValidatorsBeaconState, signedVoluntaryExit: SignedVoluntaryExit, verifySignature = true ): void { diff --git a/packages/lodestar-beacon-state-transition/src/fast/block/slashValidator.ts b/packages/lodestar-beacon-state-transition/src/fast/block/slashValidator.ts index 42964e7a289a..a9f1238a6279 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/block/slashValidator.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/block/slashValidator.ts @@ -1,12 +1,12 @@ -import {BeaconState, ValidatorIndex} from "@chainsafe/lodestar-types"; +import {ValidatorIndex} from "@chainsafe/lodestar-types"; import {decreaseBalance, increaseBalance} from "../../util"; -import {EpochContext} from "../util"; +import {EpochContext, CachedValidatorsBeaconState} from "../util"; import {initiateValidatorExit} from "./initiateValidatorExit"; export function slashValidator( epochCtx: EpochContext, - state: BeaconState, + state: CachedValidatorsBeaconState, slashedIndex: ValidatorIndex, whistleblowerIndex?: ValidatorIndex ): void { @@ -19,8 +19,10 @@ export function slashValidator( const epoch = epochCtx.currentShuffling.epoch; initiateValidatorExit(epochCtx, state, slashedIndex); const validator = state.validators[slashedIndex]; - validator.slashed = true; - validator.withdrawableEpoch = Math.max(validator.withdrawableEpoch, epoch + EPOCHS_PER_SLASHINGS_VECTOR); + state.setValidator(slashedIndex, { + slashed: true, + withdrawableEpoch: Math.max(validator.withdrawableEpoch, epoch + EPOCHS_PER_SLASHINGS_VECTOR), + }); state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effectiveBalance; decreaseBalance(state, slashedIndex, validator.effectiveBalance / BigInt(MIN_SLASHING_PENALTY_QUOTIENT)); diff --git a/packages/lodestar-beacon-state-transition/src/fast/epoch/index.ts b/packages/lodestar-beacon-state-transition/src/fast/epoch/index.ts index 91eacfcb9533..d9f7b0f21451 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/epoch/index.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/epoch/index.ts @@ -1,6 +1,4 @@ -import {BeaconState} from "@chainsafe/lodestar-types"; - -import {prepareEpochProcessState} from "../util"; +import {CachedValidatorsBeaconState, prepareEpochProcessState} from "../util"; import {StateTransitionEpochContext} from "../util/epochContext"; import {processJustificationAndFinalization} from "./processJustificationAndFinalization"; import {processRewardsAndPenalties} from "./processRewardsAndPenalties"; @@ -20,7 +18,7 @@ export { getAttestationDeltas, }; -export function processEpoch(epochCtx: StateTransitionEpochContext, state: BeaconState): void { +export function processEpoch(epochCtx: StateTransitionEpochContext, state: CachedValidatorsBeaconState): void { const process = prepareEpochProcessState(epochCtx, state); epochCtx.epochProcess = process; processJustificationAndFinalization(epochCtx, process, state); diff --git a/packages/lodestar-beacon-state-transition/src/fast/epoch/processFinalUpdates.ts b/packages/lodestar-beacon-state-transition/src/fast/epoch/processFinalUpdates.ts index c5f8bc600a23..d0b59b2203fa 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/epoch/processFinalUpdates.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/epoch/processFinalUpdates.ts @@ -1,11 +1,15 @@ import {readOnlyMap, List} from "@chainsafe/ssz"; -import {BeaconState, Eth1Data, PendingAttestation} from "@chainsafe/lodestar-types"; +import {Eth1Data, PendingAttestation} from "@chainsafe/lodestar-types"; import {bigIntMin, intDiv} from "@chainsafe/lodestar-utils"; import {getRandaoMix} from "../../util"; -import {EpochContext, IEpochProcess} from "../util"; +import {EpochContext, IEpochProcess, CachedValidatorsBeaconState} from "../util"; -export function processFinalUpdates(epochCtx: EpochContext, process: IEpochProcess, state: BeaconState): void { +export function processFinalUpdates( + epochCtx: EpochContext, + process: IEpochProcess, + state: CachedValidatorsBeaconState +): void { const config = epochCtx.config; const currentEpoch = process.currentEpoch; const nextEpoch = currentEpoch + 1; @@ -37,10 +41,9 @@ export function processFinalUpdates(epochCtx: EpochContext, process: IEpochProce const balance = balances[i]; const effectiveBalance = status.validator.effectiveBalance; if (balance + DOWNWARD_THRESHOLD < effectiveBalance || effectiveBalance + UPWARD_THRESHOLD < balance) { - state.validators[i].effectiveBalance = bigIntMin( - balance - (balance % EFFECTIVE_BALANCE_INCREMENT), - MAX_EFFECTIVE_BALANCE - ); + state.setValidator(i, { + effectiveBalance: bigIntMin(balance - (balance % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE), + }); } } diff --git a/packages/lodestar-beacon-state-transition/src/fast/epoch/processRegistryUpdates.ts b/packages/lodestar-beacon-state-transition/src/fast/epoch/processRegistryUpdates.ts index 300cfb1dd928..14082882c699 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/epoch/processRegistryUpdates.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/epoch/processRegistryUpdates.ts @@ -1,20 +1,22 @@ -import {BeaconState} from "@chainsafe/lodestar-types"; - import {computeActivationExitEpoch} from "../../util"; -import {EpochContext, IEpochProcess} from "../util"; +import {EpochContext, IEpochProcess, CachedValidatorsBeaconState} from "../util"; -export function processRegistryUpdates(epochCtx: EpochContext, process: IEpochProcess, state: BeaconState): void { +export function processRegistryUpdates( + epochCtx: EpochContext, + process: IEpochProcess, + state: CachedValidatorsBeaconState +): void { const config = epochCtx.config; let exitEnd = process.exitQueueEnd; let endChurn = process.exitQueueEndChurn; const {MIN_VALIDATOR_WITHDRAWABILITY_DELAY} = epochCtx.config.params; // process ejections process.indicesToEject.forEach((index) => { - const validator = state.validators[index]; - // set validator exit epoch and withdrawable epoch - validator.exitEpoch = exitEnd; - validator.withdrawableEpoch = exitEnd + MIN_VALIDATOR_WITHDRAWABILITY_DELAY; + state.setValidator(index, { + exitEpoch: exitEnd, + withdrawableEpoch: exitEnd + MIN_VALIDATOR_WITHDRAWABILITY_DELAY, + }); endChurn += 1; if (endChurn >= process.churnLimit) { @@ -25,7 +27,9 @@ export function processRegistryUpdates(epochCtx: EpochContext, process: IEpochPr // set new activation eligibilities process.indicesToSetActivationEligibility.forEach((index) => { - state.validators[index].activationEligibilityEpoch = epochCtx.currentShuffling.epoch + 1; + state.setValidator(index, { + activationEligibilityEpoch: epochCtx.currentShuffling.epoch + 1, + }); }); const finalityEpoch = state.finalizedCheckpoint.epoch; @@ -35,6 +39,8 @@ export function processRegistryUpdates(epochCtx: EpochContext, process: IEpochPr if (process.statuses[index].validator.activationEligibilityEpoch > finalityEpoch) { break; // remaining validators all have an activationEligibilityEpoch that is higher anyway, break early } - state.validators[index].activationEpoch = computeActivationExitEpoch(config, process.currentEpoch); + state.setValidator(index, { + activationEpoch: computeActivationExitEpoch(config, process.currentEpoch), + }); } } diff --git a/packages/lodestar-beacon-state-transition/src/fast/index.ts b/packages/lodestar-beacon-state-transition/src/fast/index.ts index fd9a5d0fef82..649e8904d060 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/index.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/index.ts @@ -1,6 +1,6 @@ -import {BeaconState, SignedBeaconBlock} from "@chainsafe/lodestar-types"; +import {SignedBeaconBlock} from "@chainsafe/lodestar-types"; -import {verifyBlockSignature} from "./util"; +import {CachedValidatorsBeaconState, verifyBlockSignature} from "./util"; import {IStateContext} from "./util"; import {StateTransitionEpochContext} from "./util/epochContext"; import {EpochContext} from "./util/epochContext"; @@ -22,7 +22,7 @@ export function fastStateTransition( const types = epochCtx.config.types; const block = signedBlock.message; - const postState = types.BeaconState.clone(state); + const postState = state.clone(); // process slots (including those with no blocks) since block processSlots(epochCtx, postState, block.slot); @@ -46,7 +46,10 @@ export function fastStateTransition( /** * Trim epochProcess in epochCtx, and insert the standard/exchange interface epochProcess to the final IStateContext */ -export function toIStateContext(epochCtx: StateTransitionEpochContext, state: BeaconState): IStateContext { +export function toIStateContext( + epochCtx: StateTransitionEpochContext, + state: CachedValidatorsBeaconState +): IStateContext { const epochProcess = epochCtx.epochProcess; epochCtx.epochProcess = undefined; return { diff --git a/packages/lodestar-beacon-state-transition/src/fast/slot/index.ts b/packages/lodestar-beacon-state-transition/src/fast/slot/index.ts index 1b66be7fd568..b0886f6c312d 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/slot/index.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/slot/index.ts @@ -1,12 +1,17 @@ -import {BeaconState, Slot} from "@chainsafe/lodestar-types"; +import {Slot} from "@chainsafe/lodestar-types"; import {StateTransitionEpochContext} from "../util/epochContext"; import {processEpoch} from "../epoch"; import {processSlot} from "./processSlot"; +import {CachedValidatorsBeaconState} from "../util/interface"; export {processSlot}; -export function processSlots(epochCtx: StateTransitionEpochContext, state: BeaconState, slot: Slot): void { +export function processSlots( + epochCtx: StateTransitionEpochContext, + state: CachedValidatorsBeaconState, + slot: Slot +): void { if (!(state.slot < slot)) { throw new Error("State slot must transition to a future slot: " + `stateSlot=${state.slot} slot=${slot}`); } diff --git a/packages/lodestar-beacon-state-transition/src/fast/util/epochContext.ts b/packages/lodestar-beacon-state-transition/src/fast/util/epochContext.ts index 28386b850130..0975482d4da5 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/util/epochContext.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/util/epochContext.ts @@ -26,6 +26,7 @@ import { } from "../../util"; import {computeEpochShuffling, IEpochShuffling} from "./epochShuffling"; import {IEpochProcess} from "./epochProcess"; +import {CachedValidatorsBeaconState} from "./interface"; export class PubkeyIndexMap extends Map { get(key: ByteVector): ValidatorIndex | undefined { @@ -138,15 +139,13 @@ export class EpochContext { * Called to re-use information, such as the shuffling of the next epoch, after transitioning into a * new epoch. */ - public rotateEpochs(state: BeaconState): void { + public rotateEpochs(state: CachedValidatorsBeaconState): void { this.previousShuffling = this.currentShuffling; this.currentShuffling = this.nextShuffling; const nextEpoch = this.currentShuffling.epoch + 1; - const indicesBounded: [ValidatorIndex, Epoch, Epoch][] = readOnlyMap(state.validators, (v, i) => [ - i, - v.activationEpoch, - v.exitEpoch, - ]); + const indicesBounded: [ValidatorIndex, Epoch, Epoch][] = state + .flatValidators() + .map((v, i) => [i, v.activationEpoch, v.exitEpoch]); this.nextShuffling = computeEpochShuffling(this.config, state, indicesBounded, nextEpoch); this._resetProposers(state); } diff --git a/packages/lodestar-beacon-state-transition/src/fast/util/epochProcess.ts b/packages/lodestar-beacon-state-transition/src/fast/util/epochProcess.ts index c8fffec0a62c..116d06cb5ce2 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/util/epochProcess.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/util/epochProcess.ts @@ -1,5 +1,5 @@ import {List, readOnlyForEach, readOnlyMap} from "@chainsafe/ssz"; -import {Epoch, ValidatorIndex, Gwei, BeaconState, PendingAttestation} from "@chainsafe/lodestar-types"; +import {Epoch, ValidatorIndex, Gwei, PendingAttestation} from "@chainsafe/lodestar-types"; import {intDiv} from "@chainsafe/lodestar-utils"; import {computeActivationExitEpoch, getBlockRootAtSlot, computeStartSlotAtEpoch, getChurnLimit} from "../../util"; @@ -19,7 +19,8 @@ import { } from "./attesterStatus"; import {IEpochStakeSummary} from "./epochStakeSummary"; import {StateTransitionEpochContext} from "./epochContext"; -import {createIFlatValidator, isActiveIFlatValidator} from "./flatValidator"; +import {isActiveIFlatValidator} from "./flatValidator"; +import {CachedValidatorsBeaconState} from "./interface"; /** * The AttesterStatus (and FlatValidator under status.validator) objects and @@ -67,7 +68,10 @@ export function createIEpochProcess(): IEpochProcess { }; } -export function prepareEpochProcessState(epochCtx: StateTransitionEpochContext, state: BeaconState): IEpochProcess { +export function prepareEpochProcessState( + epochCtx: StateTransitionEpochContext, + state: CachedValidatorsBeaconState +): IEpochProcess { const out = createIEpochProcess(); const config = epochCtx.config; @@ -87,8 +91,7 @@ export function prepareEpochProcessState(epochCtx: StateTransitionEpochContext, let exitQueueEnd = computeActivationExitEpoch(config, currentEpoch); let activeCount = 0; - readOnlyForEach(state.validators, (validator, i) => { - const v = createIFlatValidator(validator); + state.flatValidators().forEach((v, i) => { const status = createIAttesterStatus(v); if (v.slashed) { diff --git a/packages/lodestar-beacon-state-transition/src/fast/util/index.ts b/packages/lodestar-beacon-state-transition/src/fast/util/index.ts index c5b3d4840b0b..ebe7c71885b7 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/util/index.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/util/index.ts @@ -1,6 +1,6 @@ -import {BeaconState} from "@chainsafe/lodestar-types"; import {EpochContext} from "./epochContext"; import {IEpochProcess} from "./epochProcess"; +import {CachedValidatorsBeaconState} from "./interface"; export * from "./block"; export * from "./attesterStatus"; @@ -15,7 +15,7 @@ export * from "./interface"; * Exchange Interface of StateContext */ export interface IStateContext { - state: BeaconState; + state: CachedValidatorsBeaconState; epochCtx: EpochContext; epochProcess?: IEpochProcess; } diff --git a/packages/lodestar-beacon-state-transition/src/fast/util/interface.ts b/packages/lodestar-beacon-state-transition/src/fast/util/interface.ts index 1142e34a0a4c..03f084b03a59 100644 --- a/packages/lodestar-beacon-state-transition/src/fast/util/interface.ts +++ b/packages/lodestar-beacon-state-transition/src/fast/util/interface.ts @@ -1,6 +1,8 @@ import {IReadonlyEpochShuffling} from "."; -import {ValidatorIndex, Slot} from "@chainsafe/lodestar-types"; -import {ByteVector} from "@chainsafe/ssz"; +import {ValidatorIndex, Slot, BeaconState, Validator} from "@chainsafe/lodestar-types"; +import {ByteVector, readOnlyForEach} from "@chainsafe/ssz"; +import {createIFlatValidator, IFlatValidator} from "./flatValidator"; +import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet"; /** * Readonly interface for EpochContext. @@ -12,3 +14,137 @@ export type ReadonlyEpochContext = { readonly previousShuffling?: IReadonlyEpochShuffling; getBeaconProposer: (slot: Slot) => ValidatorIndex; }; + +/** + * Instead of accesing `validators` array directly inside BeaconState, use: + * + flatValidators() for the loop + * + setValidator() for an update + * + addValidator() for a creation + * that'd update both the cached validators array and the one in the original state. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface CachedValidatorsBeaconState extends BeaconState { + flatValidators(): IFlatValidator[]; + setValidator(i: ValidatorIndex, value: Partial): void; + addValidator(validator: Validator): void; + getOriginalState(): BeaconState; + clone(): CachedValidatorsBeaconState; +} + +/** + * Looping through validators inside TreeBacked is so expensive. + * Cache validators from TreeBacked. + * When write, write to both the cache and TreeBacked. + * When read, just return the cache. + */ +export class CachedValidatorsBeaconState { + public _state: BeaconState; + private _cachedValidators: IFlatValidator[]; + private isSynced: boolean; + + constructor(state: BeaconState, cachedValidators?: IFlatValidator[]) { + this._state = state; + if (cachedValidators && cachedValidators.length > 0) { + this._cachedValidators = cachedValidators; + this.isSynced = true; + } else { + this._cachedValidators = []; + this.isSynced = false; + } + } + + public createProxy(): CachedValidatorsBeaconState { + return new Proxy(this, new CachedValidatorsBeaconStateProxyHandler()); + } + + /** + * Write to both the cached validator and BeaconState + */ + public setValidator(i: ValidatorIndex, value: Partial): void { + if (this._cachedValidators) { + const validator = this._cachedValidators[i]; + this._cachedValidators[i] = {...validator, ...value}; + } + const validator = this._state.validators[i]; + if (value.activationEligibilityEpoch !== undefined) + validator.activationEligibilityEpoch = value.activationEligibilityEpoch; + if (value.activationEpoch !== undefined) validator.activationEpoch = value.activationEpoch; + if (value.effectiveBalance !== undefined) validator.effectiveBalance = value.effectiveBalance; + if (value.exitEpoch !== undefined) validator.exitEpoch = value.exitEpoch; + if (value.slashed !== undefined) validator.slashed = value.slashed; + if (value.withdrawableEpoch !== undefined) validator.withdrawableEpoch = value.withdrawableEpoch; + } + + /** + * Add validator to both the cache and BeaconState + */ + public addValidator(validator: Validator): void { + if (this.isSynced) { + this._cachedValidators.push(createIFlatValidator(validator)); + } + this._state.validators.push(validator); + } + + /** + * Loop through the cached validators, not the TreeBacked validators inside BeaconState. + */ + public flatValidators(): IFlatValidator[] { + this.sync(); + return this._cachedValidators; + } + + public clone(): CachedValidatorsBeaconState { + const clonedState = config.types.BeaconState.clone(this._state); + return cloneCachedValidatorsBeaconState(clonedState, [...this._cachedValidators]); + } + + public getOriginalState(): BeaconState { + return this._state; + } + + private sync(): void { + if (this.isSynced) return; + readOnlyForEach(this._state.validators, (validator) => { + this._cachedValidators!.push(createIFlatValidator(validator)); + }); + this.isSynced = true; + } +} + +export function createCachedValidatorsBeaconState(state: BeaconState): CachedValidatorsBeaconState { + return new CachedValidatorsBeaconState(state).createProxy(); +} + +function cloneCachedValidatorsBeaconState( + state: BeaconState, + cachedValidators: IFlatValidator[] +): CachedValidatorsBeaconState { + return new CachedValidatorsBeaconState(state, cachedValidators).createProxy(); +} + +class CachedValidatorsBeaconStateProxyHandler implements ProxyHandler { + /** + * Forward all BeaconState property getters to _state. + * StateTransitionBeaconState should handle validators, setValidator + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public get(target: CachedValidatorsBeaconState, p: keyof BeaconState): any { + if (target[p] !== undefined) { + return target[p]; + } + return target._state[p]; + } + + /** + * Forward all BeaconState property setters to _state. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public set(target: CachedValidatorsBeaconState, p: keyof BeaconState, value: any): boolean { + if (target[p] !== undefined) { + target[p] = value; + } else { + target._state[p] = value; + } + return true; + } +} diff --git a/packages/lodestar-beacon-state-transition/test/perf/epoch_processing.test.ts b/packages/lodestar-beacon-state-transition/test/perf/epoch_processing.test.ts index eccd903715d5..989728c3ad53 100644 --- a/packages/lodestar-beacon-state-transition/test/perf/epoch_processing.test.ts +++ b/packages/lodestar-beacon-state-transition/test/perf/epoch_processing.test.ts @@ -1,7 +1,11 @@ import {config} from "@chainsafe/lodestar-config/mainnet"; -import {BeaconState} from "@chainsafe/lodestar-types"; import {WinstonLogger} from "@chainsafe/lodestar-utils"; -import {IEpochProcess, prepareEpochProcessState} from "../../src/fast/util"; +import { + CachedValidatorsBeaconState, + createCachedValidatorsBeaconState, + IEpochProcess, + prepareEpochProcessState, +} from "../../src/fast/util"; import {EpochContext, StateTransitionEpochContext} from "../../src/fast/util/epochContext"; import { processFinalUpdates, @@ -15,7 +19,7 @@ import {generatePerformanceState, initBLS} from "./util"; import {expect} from "chai"; describe("Epoch Processing Performance Tests", function () { - let state: BeaconState; + let state: CachedValidatorsBeaconState; let epochCtx: StateTransitionEpochContext; let process: IEpochProcess; const logger = new WinstonLogger(); @@ -23,11 +27,12 @@ describe("Epoch Processing Performance Tests", function () { before(async function () { this.timeout(0); await initBLS(); - state = await generatePerformanceState(); + const origState = await generatePerformanceState(); // go back 1 slot to process epoch - state.slot -= 1; + origState.slot -= 1; epochCtx = new EpochContext(config); - epochCtx.loadState(state); + epochCtx.loadState(origState); + state = createCachedValidatorsBeaconState(origState); }); it("prepareEpochProcessState", async () => { diff --git a/packages/lodestar-beacon-state-transition/test/perf/sanity/blocks.test.ts b/packages/lodestar-beacon-state-transition/test/perf/sanity/blocks.test.ts index bc506aea889e..7bea0dbfdd37 100644 --- a/packages/lodestar-beacon-state-transition/test/perf/sanity/blocks.test.ts +++ b/packages/lodestar-beacon-state-transition/test/perf/sanity/blocks.test.ts @@ -4,6 +4,7 @@ import {WinstonLogger} from "@chainsafe/lodestar-utils"; import {List} from "@chainsafe/ssz"; import {expect} from "chai"; import {EpochContext, fastStateTransition, IStateContext} from "../../../src/fast"; +import {createCachedValidatorsBeaconState} from "../../../src/fast/util"; import {generatePerformanceBlock, generatePerformanceState, initBLS} from "../util"; describe("Process Blocks Performance Test", function () { @@ -12,10 +13,10 @@ describe("Process Blocks Performance Test", function () { const logger = new WinstonLogger(); before(async () => { await initBLS(); - const state = await generatePerformanceState(); + const origState = await generatePerformanceState(); const epochCtx = new EpochContext(config); - epochCtx.loadState(state); - stateCtx = {state, epochCtx}; + epochCtx.loadState(origState); + stateCtx = {state: createCachedValidatorsBeaconState(origState), epochCtx}; }); it("should process block", async () => { @@ -50,8 +51,7 @@ describe("Process Blocks Performance Test", function () { verifySignatures: false, verifyStateRoot: false, }); - // could be up to 7000 - expect(Date.now() - start).lt(6000); + expect(Date.now() - start).lt(1400); logger.profile(`Process block ${signedBlock.message.slot} with ${numValidatorExits} validator exits`); }); }); diff --git a/packages/lodestar-beacon-state-transition/test/perf/sanity/slots.test.ts b/packages/lodestar-beacon-state-transition/test/perf/sanity/slots.test.ts index 38dd0ea5772d..7825e5510c66 100644 --- a/packages/lodestar-beacon-state-transition/test/perf/sanity/slots.test.ts +++ b/packages/lodestar-beacon-state-transition/test/perf/sanity/slots.test.ts @@ -1,23 +1,27 @@ import {config} from "@chainsafe/lodestar-config/mainnet"; -import {BeaconState} from "@chainsafe/lodestar-types"; import {WinstonLogger} from "@chainsafe/lodestar-utils"; import {expect} from "chai"; import {EpochContext} from "../../../src/fast"; import {processSlots} from "../../../src/fast/slot"; import {StateTransitionEpochContext} from "../../../src/fast/util/epochContext"; +import {CachedValidatorsBeaconState, createCachedValidatorsBeaconState} from "../../../src/fast/util"; import {generatePerformanceState, initBLS} from "../util"; describe("Process Slots Performance Test", function () { this.timeout(0); const logger = new WinstonLogger(); - let state: BeaconState; + let state: CachedValidatorsBeaconState; let epochCtx: StateTransitionEpochContext; before(async () => { await initBLS(); - state = await generatePerformanceState(); + }); + + beforeEach(async () => { + const origState = await generatePerformanceState(); epochCtx = new EpochContext(config); - epochCtx.loadState(state); + epochCtx.loadState(origState); + state = createCachedValidatorsBeaconState(origState); }); it("process 1 empty epoch", async () => { @@ -26,7 +30,7 @@ describe("Process Slots Performance Test", function () { const start = Date.now(); processSlots(epochCtx, state, state.slot + numSlot); logger.profile(`Process ${numSlot} slots`); - expect(Date.now() - start).lt(2800); + expect(Date.now() - start).lt(2300); }); it("process double empty epochs", async () => { @@ -35,7 +39,7 @@ describe("Process Slots Performance Test", function () { const start = Date.now(); processSlots(epochCtx, state, state.slot + numSlot); logger.profile(`Process ${numSlot} slots`); - expect(Date.now() - start).lt(5300); + expect(Date.now() - start).lt(3200); }); it("process 4 empty epochs", async () => { @@ -44,6 +48,6 @@ describe("Process Slots Performance Test", function () { const start = Date.now(); processSlots(epochCtx, state, state.slot + numSlot); logger.profile(`Process ${numSlot} slots`); - expect(Date.now() - start).lt(11000); + expect(Date.now() - start).lt(5100); }); }); diff --git a/packages/lodestar-beacon-state-transition/test/unit/fast/util/interface.test.ts b/packages/lodestar-beacon-state-transition/test/unit/fast/util/interface.test.ts new file mode 100644 index 000000000000..c30878f78a59 --- /dev/null +++ b/packages/lodestar-beacon-state-transition/test/unit/fast/util/interface.test.ts @@ -0,0 +1,81 @@ +import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet"; +import {BeaconState, Validator} from "@chainsafe/lodestar-types"; +import {WinstonLogger} from "@chainsafe/lodestar-utils"; +import {List, TreeBacked} from "@chainsafe/ssz"; +import {expect} from "chai"; +import {createCachedValidatorsBeaconState, CachedValidatorsBeaconState} from "../../../../src/fast/util"; +import {generateState} from "../../../utils/state"; + +const NUM_VALIDATORS = 100000; + +describe("StateTransitionBeaconState", function () { + let state: TreeBacked; + let wrappedState: CachedValidatorsBeaconState; + const logger = new WinstonLogger(); + + before(function () { + this.timeout(0); + const validators: Validator[] = []; + for (let i = 0; i < NUM_VALIDATORS; i++) { + validators.push({ + pubkey: Buffer.alloc(48), + withdrawalCredentials: Buffer.alloc(32), + effectiveBalance: BigInt(1000000), + slashed: false, + activationEligibilityEpoch: i + 10, + activationEpoch: i, + exitEpoch: i + 20, + withdrawableEpoch: i + 30, + }); + } + const defaultState = generateState({validators: validators as List}); + state = config.types.BeaconState.tree.createValue(defaultState); + }); + + beforeEach(() => { + state = state.clone(); + wrappedState = createCachedValidatorsBeaconState(state); + }); + + it("should read the same value of TreeBacked", () => { + expect(state.validators[1000].activationEpoch).to.be.equal(1000); + expect(wrappedState.validators[1000].activationEpoch).to.be.equal(1000); + expect(wrappedState.flatValidators()[1000].activationEpoch).to.be.equal(1000); + }); + + it("should modify both state and wrappedState", () => { + wrappedState.setValidator(1000, {activationEpoch: 2020}); + expect(wrappedState.flatValidators()[1000].activationEpoch).to.be.equal(2020); + expect(state.validators[1000].activationEpoch).to.be.equal(2020); + }); + + it("should add validator to both state and wrappedState", () => { + wrappedState.addValidator({ + pubkey: Buffer.alloc(48), + withdrawalCredentials: Buffer.alloc(32), + effectiveBalance: BigInt(1000000), + slashed: false, + activationEligibilityEpoch: NUM_VALIDATORS + 10, + activationEpoch: NUM_VALIDATORS, + exitEpoch: NUM_VALIDATORS + 20, + withdrawableEpoch: NUM_VALIDATORS + 30, + }); + + expect(wrappedState.flatValidators().length).to.be.equal(NUM_VALIDATORS + 1); + expect(state.validators.length).to.be.equal(NUM_VALIDATORS + 1); + expect(wrappedState.flatValidators()[NUM_VALIDATORS].activationEpoch).to.be.equal(NUM_VALIDATORS); + expect(state.validators[NUM_VALIDATORS].activationEpoch).to.be.equal(NUM_VALIDATORS); + }); + + it("should not take time from 2nd loop", () => { + logger.profile("First loop"); + wrappedState.flatValidators().forEach((v) => v.activationEpoch); + logger.profile("First loop"); + logger.profile("Second loop"); + wrappedState.flatValidators().forEach((v) => v.activationEpoch); + logger.profile("Second loop"); + logger.profile("Third loop"); + wrappedState.flatValidators().forEach((v) => v.activationEpoch); + logger.profile("Third loop"); + }); +}); diff --git a/packages/lodestar/src/chain/blocks/stateTransition.ts b/packages/lodestar/src/chain/blocks/stateTransition.ts index 7160ccd8b23f..805c25662942 100644 --- a/packages/lodestar/src/chain/blocks/stateTransition.ts +++ b/packages/lodestar/src/chain/blocks/stateTransition.ts @@ -23,6 +23,8 @@ import {sleep} from "@chainsafe/lodestar-utils"; import {IBeaconDb} from "../../db"; import {BlockError, BlockErrorCode} from "../errors"; import {verifySignatureSetsBatch} from "../bls"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; +import {StateTransitionEpochContext} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/epochContext"; /** * Emits a properly formed "checkpoint" event, given a checkpoint state context @@ -65,26 +67,30 @@ export async function processSlotsToNearestCheckpoint( emitter: ChainEventEmitter, stateCtx: ITreeStateContext, slot: Slot -): Promise { +): Promise { const config = stateCtx.epochCtx.config; const {SLOTS_PER_EPOCH} = config.params; const preSlot = stateCtx.state.slot; const postSlot = slot; const preEpoch = computeEpochAtSlot(config, preSlot); - let postCtx = cloneStateCtx(stateCtx); + const postCtx = cloneStateCtx(stateCtx); + const stateTransitionState = createCachedValidatorsBeaconState(postCtx.state); + const stateTranstionEpochContext = new StateTransitionEpochContext(undefined, postCtx.epochCtx); for ( let nextEpochSlot = computeStartSlotAtEpoch(config, preEpoch + 1); nextEpochSlot <= postSlot; nextEpochSlot += SLOTS_PER_EPOCH ) { - processSlots(postCtx.epochCtx, postCtx.state, nextEpochSlot); - postCtx = toTreeStateContext(toIStateContext(postCtx.epochCtx, postCtx.state)); - emitCheckpointEvent(emitter, postCtx); - postCtx = cloneStateCtx(postCtx); + processSlots(stateTranstionEpochContext, stateTransitionState, nextEpochSlot); + const checkpointCtx = toTreeStateContext(toIStateContext(stateTranstionEpochContext, stateTransitionState)); + emitCheckpointEvent(emitter, cloneStateCtx(checkpointCtx)); // this avoids keeping our node busy processing blocks await sleep(0); } - return postCtx; + return { + epochCtx: stateTranstionEpochContext, + state: stateTransitionState, + }; } /** @@ -97,12 +103,11 @@ export async function processSlotsByCheckpoint( stateCtx: ITreeStateContext, slot: Slot ): Promise { - let postCtx = await processSlotsToNearestCheckpoint(emitter, stateCtx, slot); + const postCtx = await processSlotsToNearestCheckpoint(emitter, stateCtx, slot); if (postCtx.state.slot < slot) { processSlots(postCtx.epochCtx, postCtx.state, slot); - postCtx = toTreeStateContext(toIStateContext(postCtx.epochCtx, postCtx.state)); } - return postCtx; + return toTreeStateContext(postCtx); } export function emitForkChoiceHeadEvents( @@ -196,7 +201,7 @@ export async function runStateTransition( */ function toTreeStateContext(stateCtx: IStateContext): ITreeStateContext { const treeStateCtx: ITreeStateContext = { - state: stateCtx.state as TreeBacked, + state: stateCtx.state.getOriginalState() as TreeBacked, epochCtx: new LodestarEpochContext(undefined, stateCtx.epochCtx), }; diff --git a/packages/lodestar/src/chain/factory/block/index.ts b/packages/lodestar/src/chain/factory/block/index.ts index 740ad4c8edb9..b4de6b13a6a8 100644 --- a/packages/lodestar/src/chain/factory/block/index.ts +++ b/packages/lodestar/src/chain/factory/block/index.ts @@ -3,6 +3,7 @@ */ import {processBlock} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/block"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {BeaconBlock, Bytes96, Root, Slot} from "@chainsafe/lodestar-types"; import {ZERO_HASH} from "../../../constants"; @@ -43,7 +44,7 @@ export async function assembleBlock( */ function computeNewStateRoot(config: IBeaconConfig, stateContext: ITreeStateContext, block: BeaconBlock): Root { const postState = { - state: stateContext.state.clone(), + state: createCachedValidatorsBeaconState(stateContext.state.clone()), epochCtx: stateContext.epochCtx.copy(), }; processBlock(postState.epochCtx, postState.state, block, true); diff --git a/packages/lodestar/test/e2e/chain/factory/block/assembleBlock.test.ts b/packages/lodestar/test/e2e/chain/factory/block/assembleBlock.test.ts index 2bb25a7861fc..3d317c56b4f9 100644 --- a/packages/lodestar/test/e2e/chain/factory/block/assembleBlock.test.ts +++ b/packages/lodestar/test/e2e/chain/factory/block/assembleBlock.test.ts @@ -27,6 +27,7 @@ import {ValidatorApi} from "../../../../../src/api/impl/validator"; import {StubbedBeaconDb} from "../../../../utils/stub"; import {silentLogger} from "../../../../utils/logger"; import {StateRegenerator} from "../../../../../src/chain/regen"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; describe("produce block", function () { this.timeout("10 min"); @@ -90,7 +91,8 @@ describe("produce block", function () { return await assembleBlock(config, chainStub, dbStub, eth1, slot, randao); }); const block = await blockProposingService.createAndPublishBlock(0, 1, state.fork, ZERO_HASH); - expect(() => fastStateTransition({state, epochCtx}, block!, {verifyStateRoot: false})).to.not.throw(); + const wrappedState = createCachedValidatorsBeaconState(state); + expect(() => fastStateTransition({state: wrappedState, epochCtx}, block!, {verifyStateRoot: false})).to.not.throw(); }); function getBlockProposingService(secretKey: SecretKey): BlockProposingService { diff --git a/packages/spec-test-runner/test/spec/epoch_processing/finalUpdates/final_updates_fast.test.ts b/packages/spec-test-runner/test/spec/epoch_processing/finalUpdates/final_updates_fast.test.ts index c48642325213..d5c70fb2ef1f 100644 --- a/packages/spec-test-runner/test/spec/epoch_processing/finalUpdates/final_updates_fast.test.ts +++ b/packages/spec-test-runner/test/spec/epoch_processing/finalUpdates/final_updates_fast.test.ts @@ -5,6 +5,7 @@ import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processFinalUpdates} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/epoch"; import {prepareEpochProcessState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {BeaconState} from "@chainsafe/lodestar-types"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IStateTestCase} from "../../../utils/specTestTypes/stateTestCase"; @@ -17,8 +18,9 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - const process = prepareEpochProcessState(epochCtx, state); - processFinalUpdates(epochCtx, process, state); + const wrappedState = createCachedValidatorsBeaconState(state); + const process = prepareEpochProcessState(epochCtx, wrappedState); + processFinalUpdates(epochCtx, process, wrappedState); return state; }, { diff --git a/packages/spec-test-runner/test/spec/epoch_processing/justification/justification_and_finalization_fast.test.ts b/packages/spec-test-runner/test/spec/epoch_processing/justification/justification_and_finalization_fast.test.ts index fc736bf48228..038595b0219e 100644 --- a/packages/spec-test-runner/test/spec/epoch_processing/justification/justification_and_finalization_fast.test.ts +++ b/packages/spec-test-runner/test/spec/epoch_processing/justification/justification_and_finalization_fast.test.ts @@ -5,6 +5,8 @@ import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processJustificationAndFinalization} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/epoch"; import {prepareEpochProcessState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; + import {BeaconState} from "@chainsafe/lodestar-types"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IStateTestCase} from "../../../utils/specTestTypes/stateTestCase"; @@ -17,8 +19,9 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - const process = prepareEpochProcessState(epochCtx, state); - processJustificationAndFinalization(epochCtx, process, state); + const wrappedState = createCachedValidatorsBeaconState(state); + const process = prepareEpochProcessState(epochCtx, wrappedState); + processJustificationAndFinalization(epochCtx, process, wrappedState); return state; }, { diff --git a/packages/spec-test-runner/test/spec/epoch_processing/registryUpdates/registry_updates_fast.test.ts b/packages/spec-test-runner/test/spec/epoch_processing/registryUpdates/registry_updates_fast.test.ts index dab2863754d5..3c8b80f398c3 100644 --- a/packages/spec-test-runner/test/spec/epoch_processing/registryUpdates/registry_updates_fast.test.ts +++ b/packages/spec-test-runner/test/spec/epoch_processing/registryUpdates/registry_updates_fast.test.ts @@ -5,6 +5,7 @@ import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processRegistryUpdates} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/epoch"; import {prepareEpochProcessState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {BeaconState} from "@chainsafe/lodestar-types"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IStateTestCase} from "../../../utils/specTestTypes/stateTestCase"; @@ -17,8 +18,9 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - const process = prepareEpochProcessState(epochCtx, state); - processRegistryUpdates(epochCtx, process, state); + const wrappedState = createCachedValidatorsBeaconState(state); + const process = prepareEpochProcessState(epochCtx, wrappedState); + processRegistryUpdates(epochCtx, process, wrappedState); return state; }, { diff --git a/packages/spec-test-runner/test/spec/epoch_processing/rewardsAndPenalties/rewards_and_penalties_fast.test.ts b/packages/spec-test-runner/test/spec/epoch_processing/rewardsAndPenalties/rewards_and_penalties_fast.test.ts index 79ec9dd004fc..47af8bf8976b 100644 --- a/packages/spec-test-runner/test/spec/epoch_processing/rewardsAndPenalties/rewards_and_penalties_fast.test.ts +++ b/packages/spec-test-runner/test/spec/epoch_processing/rewardsAndPenalties/rewards_and_penalties_fast.test.ts @@ -5,6 +5,7 @@ import {config} from "@chainsafe/lodestar-config/minimal"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processRewardsAndPenalties} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/epoch"; import {prepareEpochProcessState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {BeaconState} from "@chainsafe/lodestar-types"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IStateTestCase} from "../../../utils/specTestTypes/stateTestCase"; @@ -17,8 +18,9 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - const process = prepareEpochProcessState(epochCtx, state); - processRewardsAndPenalties(epochCtx, process, state); + const wrappedState = createCachedValidatorsBeaconState(state); + const process = prepareEpochProcessState(epochCtx, wrappedState); + processRewardsAndPenalties(epochCtx, process, wrappedState); return state; }, { diff --git a/packages/spec-test-runner/test/spec/epoch_processing/slashings/slashings_fast.test.ts b/packages/spec-test-runner/test/spec/epoch_processing/slashings/slashings_fast.test.ts index 5886b102e9f8..12958793541c 100644 --- a/packages/spec-test-runner/test/spec/epoch_processing/slashings/slashings_fast.test.ts +++ b/packages/spec-test-runner/test/spec/epoch_processing/slashings/slashings_fast.test.ts @@ -6,6 +6,7 @@ import {BeaconState} from "@chainsafe/lodestar-types"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processSlashings} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/epoch"; import {prepareEpochProcessState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IStateTestCase} from "../../../utils/specTestTypes/stateTestCase"; import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases"; @@ -17,8 +18,9 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - const process = prepareEpochProcessState(epochCtx, state); - processSlashings(epochCtx, process, state); + const wrappedState = createCachedValidatorsBeaconState(state); + const process = prepareEpochProcessState(epochCtx, wrappedState); + processSlashings(epochCtx, process, wrappedState); return state; }, { diff --git a/packages/spec-test-runner/test/spec/finality/finality_fast.test.ts b/packages/spec-test-runner/test/spec/finality/finality_fast.test.ts index 965118d18536..75064bf7efc0 100644 --- a/packages/spec-test-runner/test/spec/finality/finality_fast.test.ts +++ b/packages/spec-test-runner/test/spec/finality/finality_fast.test.ts @@ -7,16 +7,18 @@ import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-tes import {IFinalityTestCase} from "./type"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {SPEC_TEST_LOCATION} from "../../utils/specTestCases"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; describeDirectorySpecTest( "finality fast", join(SPEC_TEST_LOCATION, "/tests/mainnet/phase0/finality/finality/pyspec_tests"), (testcase) => { const state = config.types.BeaconState.tree.createValue(testcase.pre); + const wrappedState = createCachedValidatorsBeaconState(state); const epochCtx = new EpochContext(config); epochCtx.loadState(state); const verify = !!testcase.meta && !!testcase.meta.blsSetting && testcase.meta.blsSetting === BigInt(1); - let stateContext: IStateContext = {epochCtx, state}; + let stateContext: IStateContext = {epochCtx, state: wrappedState}; for (let i = 0; i < Number(testcase.meta.blocksCount); i++) { stateContext = fastStateTransition(stateContext, testcase[`blocks_${i}`] as SignedBeaconBlock, { verifyStateRoot: verify, @@ -24,7 +26,7 @@ describeDirectorySpecTest( verifySignatures: verify, }); } - return stateContext.state; + return stateContext.state.getOriginalState(); }, { inputTypes: { diff --git a/packages/spec-test-runner/test/spec/operations/attesterSlashing/attester_slashing_fast.test.ts b/packages/spec-test-runner/test/spec/operations/attesterSlashing/attester_slashing_fast.test.ts index 1cfd74a4a1ee..f4c2574ce47c 100644 --- a/packages/spec-test-runner/test/spec/operations/attesterSlashing/attester_slashing_fast.test.ts +++ b/packages/spec-test-runner/test/spec/operations/attesterSlashing/attester_slashing_fast.test.ts @@ -4,6 +4,7 @@ import {BeaconState} from "@chainsafe/lodestar-types"; import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processAttesterSlashing} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/block"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IProcessAttesterSlashingTestCase} from "./type"; import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases"; @@ -16,7 +17,8 @@ describeDirectorySpecTest( const epochCtx = new EpochContext(config); epochCtx.loadState(state); const verify = !!testcase.meta && !!testcase.meta.blsSetting && testcase.meta.blsSetting === BigInt(1); - processAttesterSlashing(epochCtx, state, testcase.attester_slashing, verify); + const wrappedState = createCachedValidatorsBeaconState(state); + processAttesterSlashing(epochCtx, wrappedState, testcase.attester_slashing, verify); return state; }, { diff --git a/packages/spec-test-runner/test/spec/operations/deposit/deposit_fast.test.ts b/packages/spec-test-runner/test/spec/operations/deposit/deposit_fast.test.ts index 670644febcbe..384b83019b39 100644 --- a/packages/spec-test-runner/test/spec/operations/deposit/deposit_fast.test.ts +++ b/packages/spec-test-runner/test/spec/operations/deposit/deposit_fast.test.ts @@ -4,6 +4,7 @@ import {BeaconState} from "@chainsafe/lodestar-types"; import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processDeposit} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/block"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {describeDirectorySpecTest} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IProcessDepositTestCase} from "./type"; import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases"; @@ -15,7 +16,8 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - processDeposit(epochCtx, state, testcase.deposit); + const wrappedState = createCachedValidatorsBeaconState(state); + processDeposit(epochCtx, wrappedState, testcase.deposit); return state; }, { diff --git a/packages/spec-test-runner/test/spec/operations/proposerSlashing/proposer_slashing_fast.test.ts b/packages/spec-test-runner/test/spec/operations/proposerSlashing/proposer_slashing_fast.test.ts index 3ffbb0c8f7b2..ba310f7ba542 100644 --- a/packages/spec-test-runner/test/spec/operations/proposerSlashing/proposer_slashing_fast.test.ts +++ b/packages/spec-test-runner/test/spec/operations/proposerSlashing/proposer_slashing_fast.test.ts @@ -4,6 +4,7 @@ import {BeaconState} from "@chainsafe/lodestar-types"; import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processProposerSlashing} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/block"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {describeDirectorySpecTest} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IProcessProposerSlashingTestCase} from "./type"; import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases"; @@ -15,7 +16,8 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - processProposerSlashing(epochCtx, state, testcase.proposer_slashing); + const wrappedState = createCachedValidatorsBeaconState(state); + processProposerSlashing(epochCtx, wrappedState, testcase.proposer_slashing); return state; }, { diff --git a/packages/spec-test-runner/test/spec/operations/voluntaryExit/voluntary_exit_fast.test.ts b/packages/spec-test-runner/test/spec/operations/voluntaryExit/voluntary_exit_fast.test.ts index 8b24f86a0ec7..1cdc44834637 100644 --- a/packages/spec-test-runner/test/spec/operations/voluntaryExit/voluntary_exit_fast.test.ts +++ b/packages/spec-test-runner/test/spec/operations/voluntaryExit/voluntary_exit_fast.test.ts @@ -4,6 +4,7 @@ import {BeaconState} from "@chainsafe/lodestar-types"; import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processVoluntaryExit} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/block"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {describeDirectorySpecTest} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IProcessVoluntaryExitTestCase} from "./type"; import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases"; @@ -15,7 +16,8 @@ describeDirectorySpecTest( const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - processVoluntaryExit(epochCtx, state, testcase.voluntary_exit); + const wrappedState = createCachedValidatorsBeaconState(state); + processVoluntaryExit(epochCtx, wrappedState, testcase.voluntary_exit); return state; }, { diff --git a/packages/spec-test-runner/test/spec/rewards/rewards_fast.test.ts b/packages/spec-test-runner/test/spec/rewards/rewards_fast.test.ts index 8d24cb4066da..7c2c8c0f8a0d 100644 --- a/packages/spec-test-runner/test/spec/rewards/rewards_fast.test.ts +++ b/packages/spec-test-runner/test/spec/rewards/rewards_fast.test.ts @@ -1,5 +1,8 @@ import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; -import {prepareEpochProcessState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; +import { + createCachedValidatorsBeaconState, + prepareEpochProcessState, +} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; import {getAttestationDeltas} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/epoch"; import {config} from "@chainsafe/lodestar-config/mainnet"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util"; @@ -16,7 +19,8 @@ import {expect} from "chai"; const state = testcase.pre; const epochCtx = new EpochContext(config); epochCtx.loadState(state); - const process = prepareEpochProcessState(epochCtx, state); + const wrappedState = createCachedValidatorsBeaconState(state); + const process = prepareEpochProcessState(epochCtx, wrappedState); const [rewards, penalties] = getAttestationDeltas(epochCtx, process, state); return { rewards, diff --git a/packages/spec-test-runner/test/spec/sanity/blocks/sanity_blocks_fast.test.ts b/packages/spec-test-runner/test/spec/sanity/blocks/sanity_blocks_fast.test.ts index 8cc5d78d5112..6b39a6632a1d 100644 --- a/packages/spec-test-runner/test/spec/sanity/blocks/sanity_blocks_fast.test.ts +++ b/packages/spec-test-runner/test/spec/sanity/blocks/sanity_blocks_fast.test.ts @@ -1,22 +1,24 @@ import {join} from "path"; import {expect} from "chai"; import {BeaconState, SignedBeaconBlock} from "@chainsafe/lodestar-types"; -import {EpochContext, fastStateTransition, IStateContext} from "@chainsafe/lodestar-beacon-state-transition"; +import {EpochContext, fastStateTransition} from "@chainsafe/lodestar-beacon-state-transition"; import {config} from "@chainsafe/lodestar-config/mainnet"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IBlockSanityTestCase} from "./type"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util"; describeDirectorySpecTest( "block sanity mainnet", join(SPEC_TEST_LOCATION, "/tests/mainnet/phase0/sanity/blocks/pyspec_tests"), (testcase) => { const state = config.types.BeaconState.tree.createValue(testcase.pre); + const wrappedState = createCachedValidatorsBeaconState(state); const epochCtx = new EpochContext(config); - epochCtx.loadState(state); + epochCtx.loadState(wrappedState); + let stateContext = {epochCtx, state: wrappedState}; const verify = !!testcase.meta && !!testcase.meta.blsSetting && testcase.meta.blsSetting === BigInt(1); - let stateContext: IStateContext = {epochCtx, state}; for (let i = 0; i < Number(testcase.meta.blocksCount); i++) { stateContext = fastStateTransition(stateContext, testcase[`blocks_${i}`] as SignedBeaconBlock, { verifyStateRoot: verify, @@ -24,7 +26,7 @@ describeDirectorySpecTest( verifySignatures: verify, }); } - return stateContext.state; + return stateContext.state.getOriginalState(); }, { inputTypes: { diff --git a/packages/spec-test-runner/test/spec/sanity/slots/sanity_slots_fast.test.ts b/packages/spec-test-runner/test/spec/sanity/slots/sanity_slots_fast.test.ts index 644e1481b536..1447dff23108 100644 --- a/packages/spec-test-runner/test/spec/sanity/slots/sanity_slots_fast.test.ts +++ b/packages/spec-test-runner/test/spec/sanity/slots/sanity_slots_fast.test.ts @@ -4,6 +4,7 @@ import {config} from "@chainsafe/lodestar-config/mainnet"; import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition"; import {processSlots} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/slot"; import {BeaconState} from "@chainsafe/lodestar-types"; +import {createCachedValidatorsBeaconState} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/util/interface"; import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util/lib/single"; import {IProcessSlotsTestCase} from "./type"; import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases"; @@ -15,7 +16,8 @@ describeDirectorySpecTest( const state = config.types.BeaconState.tree.createValue(testcase.pre); const epochCtx = new EpochContext(config); epochCtx.loadState(state); - processSlots(epochCtx, state, state.slot + Number(testcase.slots)); + const wrappedState = createCachedValidatorsBeaconState(state); + processSlots(epochCtx, wrappedState, state.slot + Number(testcase.slots)); return state; }, {