diff --git a/packages/beacon-state-transition/src/allForks/signatureSets/proposerSlashings.ts b/packages/beacon-state-transition/src/allForks/signatureSets/proposerSlashings.ts index 5df483de4d14..0bc7b858c820 100644 --- a/packages/beacon-state-transition/src/allForks/signatureSets/proposerSlashings.ts +++ b/packages/beacon-state-transition/src/allForks/signatureSets/proposerSlashings.ts @@ -17,11 +17,12 @@ export function getProposerSlashingSignatureSets( (signedHeader): ISignatureSet => { const epoch = computeEpochAtSlot(config, signedHeader.message.slot); const domain = getDomain(config, state, config.params.DOMAIN_BEACON_PROPOSER, epoch); + const beaconBlockHeaderType = config.getTypes(signedHeader.message.slot).BeaconBlockHeader; return { type: SignatureSetType.single, pubkey, - signingRoot: computeSigningRoot(config, config.types.phase0.BeaconBlockHeader, signedHeader.message, domain), + signingRoot: computeSigningRoot(config, beaconBlockHeaderType, signedHeader.message, domain), signature: signedHeader.signature.valueOf() as Uint8Array, }; } diff --git a/packages/beacon-state-transition/src/allForks/slot/index.ts b/packages/beacon-state-transition/src/allForks/slot/index.ts new file mode 100644 index 000000000000..e4b6c40191b3 --- /dev/null +++ b/packages/beacon-state-transition/src/allForks/slot/index.ts @@ -0,0 +1,24 @@ +import {allForks} from "@chainsafe/lodestar-types"; +import {CachedBeaconState} from "../util"; +import {ZERO_HASH} from "../../constants"; + +/** + * Dial state to next slot. Common for all forks + */ +export function processSlot(state: CachedBeaconState): void { + const {config} = state; + const types = config.getTypes(state.slot); + + // Cache state root + const previousStateRoot = types.BeaconState.hashTreeRoot(state); + state.stateRoots[state.slot % config.params.SLOTS_PER_HISTORICAL_ROOT] = previousStateRoot; + + // Cache latest block header state root + if (types.Root.equals(state.latestBlockHeader.stateRoot, ZERO_HASH)) { + state.latestBlockHeader.stateRoot = previousStateRoot; + } + + // Cache block root + const previousBlockRoot = types.BeaconBlockHeader.hashTreeRoot(state.latestBlockHeader); + state.blockRoots[state.slot % config.params.SLOTS_PER_HISTORICAL_ROOT] = previousBlockRoot; +} diff --git a/packages/beacon-state-transition/src/allForks/stateTransition.ts b/packages/beacon-state-transition/src/allForks/stateTransition.ts index 8cf458d44d42..56f2be6e4337 100644 --- a/packages/beacon-state-transition/src/allForks/stateTransition.ts +++ b/packages/beacon-state-transition/src/allForks/stateTransition.ts @@ -5,23 +5,25 @@ import * as phase0 from "../phase0"; import * as altair from "../altair"; import {IBeaconStateTransitionMetrics} from "../metrics"; import {verifyProposerSignature} from "./signatureSets"; -import {CachedBeaconState} from "./util"; - -type StateTransitionFunctions = { - processSlots( - state: CachedBeaconState, - slot: Slot, - metrics?: IBeaconStateTransitionMetrics | null - ): CachedBeaconState; - upgradeState(state: CachedBeaconState): CachedBeaconState; +import {CachedBeaconState, rotateEpochs} from "./util"; +import {processSlot} from "./slot"; +import {computeEpochAtSlot} from "../util"; +import {GENESIS_EPOCH} from "../constants"; + +type StateAllForks = CachedBeaconState; +type StatePhase0 = CachedBeaconState; + +type ProcessBlockFn = (state: StateAllForks, block: allForks.BeaconBlock, verifySignatures: boolean) => void; +type ProcessEpochFn = (state: StateAllForks) => CachedBeaconState; + +const processBlockByFork: Record = { + [ForkName.phase0]: phase0.processBlock as ProcessBlockFn, + [ForkName.altair]: altair.processBlock as ProcessBlockFn, }; -/** - * Record of fork to state transition functions - */ -const implementations: Record = { - [ForkName.phase0]: (phase0 as unknown) as StateTransitionFunctions, - [ForkName.altair]: (altair as unknown) as StateTransitionFunctions, +const processEpochByFork: Record = { + [ForkName.phase0]: phase0.processEpoch as ProcessEpochFn, + [ForkName.altair]: altair.processEpoch as ProcessEpochFn, }; // Multifork capable state transition @@ -43,30 +45,31 @@ export function stateTransition( let postState = state.clone(); + // Turn caches into a data-structure optimized for fast writes postState.setStateCachesAsTransient(); - // process slots (including those with no blocks) since block - // includes state upgrades - postState = _processSlots(postState, blockSlot, metrics); + // Process slots (including those with no blocks) since block. + // Includes state upgrades + postState = processSlotsWithTransientCache(postState, blockSlot, metrics); - // verify signature + // Verify proposer signature only if (verifyProposer) { if (!verifyProposerSignature(postState, signedBlock)) { throw new Error("Invalid block signature"); } } - // process block - + // Process block processBlock(postState, block, options, metrics); - // verify state root + // Verify state root if (verifyStateRoot) { if (!config.types.Root.equals(block.stateRoot, postState.tree.root)) { throw new Error("Invalid state root"); } } + // Turn caches into a data-structure optimized for hashing and structural sharing postState.setStateCachesAsPersistent(); return postState; @@ -84,26 +87,13 @@ export function processBlock( metrics?: IBeaconStateTransitionMetrics | null ): void { const {verifySignatures = true} = options || {}; - const blockFork = postState.config.getForkName(block.slot); - - switch (blockFork) { - case ForkName.phase0: - phase0.processBlock( - postState as CachedBeaconState, - block as phase0.BeaconBlock, - verifySignatures, - metrics - ); - break; - case ForkName.altair: - altair.processBlock( - postState as CachedBeaconState, - block as altair.BeaconBlock, - verifySignatures - ); - break; - default: - throw new Error(`Block processing not implemented for fork ${blockFork}`); + const fork = postState.config.getForkName(block.slot); + + const timer = metrics?.stfnProcessBlock.startTimer(); + try { + processBlockByFork[fork](postState, block, verifySignatures); + } finally { + if (timer) timer(); } } @@ -119,60 +109,57 @@ export function processSlots( ): CachedBeaconState { let postState = state.clone(); + // Turn caches into a data-structure optimized for fast writes postState.setStateCachesAsTransient(); - postState = _processSlots(postState, slot, metrics); + postState = processSlotsWithTransientCache(postState, slot, metrics); + // Turn caches into a data-structure optimized for hashing and structural sharing postState.setStateCachesAsPersistent(); return postState; } -// eslint-disable-next-line @typescript-eslint/naming-convention -function _processSlots( - state: CachedBeaconState, +/** + * All processSlot() logic but separate so stateTransition() can recycle the caches + */ +function processSlotsWithTransientCache( + postState: StateAllForks, slot: Slot, metrics?: IBeaconStateTransitionMetrics | null -): CachedBeaconState { - let postState = state; - const {config} = state; - const preSlot = state.slot; - - // forks sorted in order - const forkInfos = Object.values(config.forks); - // for each fork - for (let i = 0; i < forkInfos.length; i++) { - const currentForkInfo = forkInfos[i]; - const nextForkInfo = forkInfos[i + 1]; - - const impl = implementations[currentForkInfo.name]; - if (!impl) { - throw new Error(`Slot processing not implemented for fork ${currentForkInfo.name}`); - } - // if there's no next fork, process slots without worrying about fork upgrades and exit - if (!nextForkInfo) { - impl.processSlots(postState, slot); - break; - } - const nextForkStartSlot = config.params.SLOTS_PER_EPOCH * nextForkInfo.epoch; +): StateAllForks { + const {config} = postState; + if (postState.slot > slot) { + throw Error(`Too old slot ${slot}, current=${postState.slot}`); + } - // if the starting state slot is after the current fork, skip to the next fork - if (preSlot > nextForkStartSlot) { - continue; + while (postState.slot < slot) { + processSlot(postState); + + // Process epoch on the first slot of the next epoch + if ((postState.slot + 1) % config.params.SLOTS_PER_EPOCH === 0) { + const fork = postState.config.getForkName(postState.slot + 1); + const timer = metrics?.stfnEpochTransition.startTimer(); + try { + processEpochByFork[fork](postState); + postState.slot++; + rotateEpochs(postState.epochCtx, postState, postState.validators); + } finally { + if (timer) timer(); + } + + // Upgrade state if exactly at epoch boundary + switch (computeEpochAtSlot(config, postState.slot)) { + case GENESIS_EPOCH: + break; // Don't do any upgrades at genesis epoch + case config.params.ALTAIR_FORK_EPOCH: + postState = altair.upgradeState(postState as StatePhase0) as StateAllForks; + break; + } + } else { + postState.slot++; } - // if the requested slot is not after the next fork, process slots and exit - if (slot < nextForkStartSlot) { - impl.processSlots(postState, slot, metrics); - break; - } - const nextImpl = implementations[currentForkInfo.name]; - if (!nextImpl) { - throw new Error(`Slot processing not implemented for fork ${nextForkInfo.name}`); - } - // else (the requested slot is equal or after the next fork), process up to the fork - impl.processSlots(postState, nextForkStartSlot); - - postState = nextImpl.upgradeState(postState); } + return postState; } diff --git a/packages/beacon-state-transition/src/altair/block/processAttestation.ts b/packages/beacon-state-transition/src/altair/block/processAttestation.ts index 35f9f29b729b..2c0d8f847162 100644 --- a/packages/beacon-state-transition/src/altair/block/processAttestation.ts +++ b/packages/beacon-state-transition/src/altair/block/processAttestation.ts @@ -78,8 +78,8 @@ export function processAttestation( if (!isMatchingSource) { throw new Error( "Attestation source does not equal justified checkpoint: " + - `source=${config.types.phase0.Checkpoint.toJson(data.source)} ` + - `justifiedCheckpoint=${config.types.phase0.Checkpoint.toJson(justifiedCheckpoint)}` + `source=${JSON.stringify(config.types.phase0.Checkpoint.toJson(data.source))} ` + + `justifiedCheckpoint=${JSON.stringify(config.types.phase0.Checkpoint.toJson(justifiedCheckpoint))}` ); } // this check is done last because its the most expensive (if signature verification is toggled on) diff --git a/packages/beacon-state-transition/src/altair/index.ts b/packages/beacon-state-transition/src/altair/index.ts index 33e33d6fbee2..7cc33520a02a 100644 --- a/packages/beacon-state-transition/src/altair/index.ts +++ b/packages/beacon-state-transition/src/altair/index.ts @@ -1,7 +1,7 @@ // fast export * from "./block"; export * from "./epoch"; -export * from "./slot"; +export * from "./upgradeState"; // naive export * as naive from "../naive/altair"; export * from "./state_accessor"; diff --git a/packages/beacon-state-transition/src/altair/slot.ts b/packages/beacon-state-transition/src/altair/slot.ts deleted file mode 100644 index eaebf3e9f2e9..000000000000 --- a/packages/beacon-state-transition/src/altair/slot.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {allForks, altair, Slot} from "@chainsafe/lodestar-types"; -import {assert} from "@chainsafe/lodestar-utils"; -import {processEpoch} from "./epoch"; -import {CachedBeaconState, rotateEpochs} from "../allForks/util"; -import {ZERO_HASH} from "../constants"; - -export function processSlots(state: CachedBeaconState, slot: Slot): void { - assert.lt(state.slot, slot, `Too old slot ${slot}, current=${state.slot}`); - const {config} = state; - - while (state.slot < slot) { - processSlot(state); - // Process epoch on the first slot of the next epoch - if ((state.slot + 1) % config.params.SLOTS_PER_EPOCH === 0) { - processEpoch(state); - state.slot++; - rotateEpochs(state.epochCtx, state as CachedBeaconState, state.validators); - } else { - state.slot++; - } - } -} - -function processSlot(state: CachedBeaconState): void { - const {config} = state; - // Cache state root - const previousStateRoot = config.types.altair.BeaconState.hashTreeRoot(state); - state.stateRoots[state.slot % config.params.SLOTS_PER_HISTORICAL_ROOT] = previousStateRoot; - - // Cache latest block header state root - if (config.types.Root.equals(state.latestBlockHeader.stateRoot, ZERO_HASH)) { - state.latestBlockHeader.stateRoot = previousStateRoot; - } - - // Cache block root - const previousBlockRoot = config.types.altair.BeaconBlockHeader.hashTreeRoot(state.latestBlockHeader); - state.blockRoots[state.slot % config.params.SLOTS_PER_HISTORICAL_ROOT] = previousBlockRoot; -} diff --git a/packages/beacon-state-transition/src/altair/state_accessor/balance.ts b/packages/beacon-state-transition/src/altair/state_accessor/balance.ts index 1f78fe9a0a6f..e30e28ab4284 100644 --- a/packages/beacon-state-transition/src/altair/state_accessor/balance.ts +++ b/packages/beacon-state-transition/src/altair/state_accessor/balance.ts @@ -37,7 +37,7 @@ export function getFlagIndexDeltas( for (const index of naive.phase0.getEligibleValidatorIndices(config, (state as unknown) as phase0.BeaconState)) { const baseReward = getBaseReward(config, state, index); if (unslashedParticipatingIndices.indexOf(index) !== -1) { - if (isInInactivityLeak(config, (state as unknown) as phase0.BeaconState)) { + if (isInInactivityLeak(config, state)) { // This flag reward cancels the inactivity penalty corresponding to the flag index rewards[index] += (baseReward * weight) / WEIGHT_DENOMINATOR; } else { @@ -60,7 +60,7 @@ export function getInactivityPenaltyDeltas(config: IBeaconConfig, state: altair. const penalties = newZeroedBigIntArray(validatorCount); const previousEpoch = getPreviousEpoch(config, state); - if (isInInactivityLeak(config, (state as unknown) as phase0.BeaconState)) { + if (isInInactivityLeak(config, state)) { const matchingTargetAttestingIndices = getUnslashedParticipatingIndices( config, state, diff --git a/packages/beacon-state-transition/src/altair/upgradeState.ts b/packages/beacon-state-transition/src/altair/upgradeState.ts new file mode 100644 index 000000000000..ad22fc201b01 --- /dev/null +++ b/packages/beacon-state-transition/src/altair/upgradeState.ts @@ -0,0 +1,85 @@ +import {altair, ParticipationFlags, phase0, Uint8} from "@chainsafe/lodestar-types"; +import {CachedBeaconState, createCachedBeaconState} from "../allForks/util"; +import {getBlockRoot, getBlockRootAtSlot, getCurrentEpoch, newZeroedArray} from "../util"; +import {List, TreeBacked} from "@chainsafe/ssz"; +import {getSyncCommittee} from "./state_accessor"; +import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {IParticipationStatus} from "../allForks/util/cachedEpochParticipation"; + +/** + * Upgrade a state from phase0 to altair. + */ +export function upgradeState(state: CachedBeaconState): CachedBeaconState { + const {config} = state; + const pendingAttesations = Array.from(state.previousEpochAttestations); + const postTreeBackedState = upgradeTreeBackedState( + config, + config.types.phase0.BeaconState.createTreeBacked(state.tree) + ); + const postState = createCachedBeaconState(config, postTreeBackedState); + translateParticipation(postState, pendingAttesations); + return postState; +} + +function upgradeTreeBackedState( + config: IBeaconConfig, + state: TreeBacked +): TreeBacked { + const validatorCount = state.validators.length; + const epoch = getCurrentEpoch(config, state); + const postState = config.types.altair.BeaconState.createTreeBacked(state.tree); + postState.fork = { + previousVersion: state.fork.currentVersion, + currentVersion: config.params.ALTAIR_FORK_VERSION, + epoch, + }; + postState.previousEpochParticipation = newZeroedArray(validatorCount) as List; + postState.currentEpochParticipation = newZeroedArray(validatorCount) as List; + postState.inactivityScores = newZeroedArray(validatorCount) as List; + const syncCommittee = getSyncCommittee(config, state, epoch); + postState.currentSyncCommittee = syncCommittee; + postState.nextSyncCommittee = syncCommittee; + return postState; +} + +/** + * Translate_participation in https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/fork.md + */ +function translateParticipation( + state: CachedBeaconState, + pendingAttesations: phase0.PendingAttestation[] +): void { + const {config} = state; + const epochParticipation = state.previousEpochParticipation; + const currentEpoch = state.currentShuffling.epoch; + for (const attestation of pendingAttesations) { + const data = attestation.data; + let justifiedCheckpoint; + if (data.target.epoch === currentEpoch) { + justifiedCheckpoint = state.currentJustifiedCheckpoint; + } else { + justifiedCheckpoint = state.previousJustifiedCheckpoint; + } + const isMatchingSource = config.types.phase0.Checkpoint.equals(data.source, justifiedCheckpoint); + if (!isMatchingSource) { + throw new Error( + "Attestation source does not equal justified checkpoint: " + + `source=${JSON.stringify(config.types.phase0.Checkpoint.toJson(data.source))} ` + + `justifiedCheckpoint=${JSON.stringify(config.types.phase0.Checkpoint.toJson(justifiedCheckpoint))}` + ); + } + const isMatchingTarget = config.types.Root.equals(data.target.root, getBlockRoot(config, state, data.target.epoch)); + const isMatchingHead = + isMatchingTarget && config.types.Root.equals(data.beaconBlockRoot, getBlockRootAtSlot(config, state, data.slot)); + const attestingIndices = state.getAttestingIndices(data, attestation.aggregationBits); + for (const index of attestingIndices) { + const status = epochParticipation.getStatus(index) as IParticipationStatus; + const newStatus = { + timelyHead: status.timelyHead || isMatchingHead, + timelySource: true, + timelyTarget: status.timelyTarget || isMatchingTarget, + }; + epochParticipation.setStatus(index, newStatus); + } + } +} diff --git a/packages/beacon-state-transition/src/naive/phase0/block/operations/deposit.ts b/packages/beacon-state-transition/src/naive/phase0/block/operations/deposit.ts index 04edb376fec4..58b3a59cd892 100644 --- a/packages/beacon-state-transition/src/naive/phase0/block/operations/deposit.ts +++ b/packages/beacon-state-transition/src/naive/phase0/block/operations/deposit.ts @@ -3,7 +3,7 @@ */ import bls from "@chainsafe/bls"; -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks, phase0} from "@chainsafe/lodestar-types"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {DEPOSIT_CONTRACT_TREE_DEPTH, FAR_FUTURE_EPOCH} from "../../../../constants"; import {computeDomain, increaseBalance, computeSigningRoot} from "../../../../util"; @@ -12,7 +12,7 @@ import {assert, bigIntMin, verifyMerkleBranch} from "@chainsafe/lodestar-utils"; /** * Process an Eth1 deposit, registering a validator or increasing its balance. */ -export function processDeposit(config: IBeaconConfig, state: phase0.BeaconState, deposit: phase0.Deposit): void { +export function processDeposit(config: IBeaconConfig, state: allForks.BeaconState, deposit: phase0.Deposit): void { // Verify the Merkle branch assert.true( verifyMerkleBranch( diff --git a/packages/beacon-state-transition/src/phase0/block/index.ts b/packages/beacon-state-transition/src/phase0/block/index.ts index 9fd60c3931f5..04c42e9b9884 100644 --- a/packages/beacon-state-transition/src/phase0/block/index.ts +++ b/packages/beacon-state-transition/src/phase0/block/index.ts @@ -1,7 +1,6 @@ import {allForks, phase0} from "@chainsafe/lodestar-types"; import {CachedBeaconState} from "../../allForks/util"; import {processBlockHeader, processEth1Data, processRandao} from "../../allForks/block"; -import {IBeaconStateTransitionMetrics} from "../../metrics"; import {processOperations} from "./processOperations"; import {processAttestation} from "./processAttestation"; import {processAttesterSlashing} from "./processAttesterSlashing"; @@ -24,13 +23,10 @@ export { export function processBlock( state: CachedBeaconState, block: phase0.BeaconBlock, - verifySignatures = true, - metrics?: IBeaconStateTransitionMetrics | null + verifySignatures = true ): void { - const timer = metrics?.stfnProcessBlock.startTimer(); processBlockHeader(state as CachedBeaconState, block); processRandao(state as CachedBeaconState, block, verifySignatures); processEth1Data(state as CachedBeaconState, block.body); processOperations(state, block.body, verifySignatures); - if (timer) timer(); } diff --git a/packages/beacon-state-transition/src/phase0/index.ts b/packages/beacon-state-transition/src/phase0/index.ts index d8beb7709f62..ef241bbd446a 100644 --- a/packages/beacon-state-transition/src/phase0/index.ts +++ b/packages/beacon-state-transition/src/phase0/index.ts @@ -1,8 +1,6 @@ // fast export * from "./block"; export * from "./epoch"; -export * from "./slot"; -export * from "./upgrade"; // naive export * as naive from "../naive/phase0"; diff --git a/packages/beacon-state-transition/src/phase0/slot/index.ts b/packages/beacon-state-transition/src/phase0/slot/index.ts deleted file mode 100644 index 31d9741d8973..000000000000 --- a/packages/beacon-state-transition/src/phase0/slot/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {allForks, phase0, Slot} from "@chainsafe/lodestar-types"; -import {CachedBeaconState, rotateEpochs} from "../../allForks/util"; -import {assert} from "@chainsafe/lodestar-utils"; -import {processEpoch} from "../epoch"; -import {processSlot} from "./processSlot"; -import {IBeaconStateTransitionMetrics} from "../../metrics"; - -export {processSlot}; - -export function processSlots( - state: CachedBeaconState, - slot: Slot, - metrics?: IBeaconStateTransitionMetrics | null -): void { - assert.lte(state.slot, slot, `State slot ${state.slot} must transition to a future slot ${slot}`); - while (state.slot < slot) { - processSlot(state); - // process epoch on the start slot of the next epoch - if ((state.slot + 1) % state.config.params.SLOTS_PER_EPOCH === 0) { - const timer = metrics?.stfnEpochTransition.startTimer(); - processEpoch(state); - state.slot += 1; - rotateEpochs(state.epochCtx, state as CachedBeaconState, state.validators); - if (timer) timer(); - } else { - state.slot += 1; - } - } -} diff --git a/packages/beacon-state-transition/src/phase0/slot/processSlot.ts b/packages/beacon-state-transition/src/phase0/slot/processSlot.ts deleted file mode 100644 index c7d3994b519a..000000000000 --- a/packages/beacon-state-transition/src/phase0/slot/processSlot.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {phase0} from "@chainsafe/lodestar-types"; -import {CachedBeaconState} from "../../allForks/util"; - -export function processSlot(state: CachedBeaconState): void { - const config = state.config; - const {SLOTS_PER_HISTORICAL_ROOT} = config.params; - // cache state root - const prevStateRoot = state.tree.root; - state.stateRoots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = prevStateRoot; - // cache latest block header state root - if (config.types.Root.equals(state.latestBlockHeader.stateRoot, new Uint8Array(32))) { - state.latestBlockHeader.stateRoot = prevStateRoot; - } - // cache block root - state.blockRoots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = config.types.phase0.BeaconBlockHeader.hashTreeRoot( - state.latestBlockHeader - ); -} diff --git a/packages/beacon-state-transition/src/phase0/upgrade.ts b/packages/beacon-state-transition/src/phase0/upgrade.ts deleted file mode 100644 index 146b9b9e9b18..000000000000 --- a/packages/beacon-state-transition/src/phase0/upgrade.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {phase0} from "@chainsafe/lodestar-types"; -import {CachedBeaconState} from "../allForks/util"; - -export function upgradeState(state: CachedBeaconState): CachedBeaconState { - return state; -} diff --git a/packages/beacon-state-transition/src/util/block.ts b/packages/beacon-state-transition/src/util/block.ts index 9838a6419515..b24569c9970f 100644 --- a/packages/beacon-state-transition/src/util/block.ts +++ b/packages/beacon-state-transition/src/util/block.ts @@ -1,16 +1,17 @@ import bls from "@chainsafe/bls"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {allForks, phase0} from "@chainsafe/lodestar-types"; +import {allForks} from "@chainsafe/lodestar-types"; import {getDomain} from "./domain"; import {computeSigningRoot} from "./signingRoot"; export function verifyBlockSignature( config: IBeaconConfig, state: allForks.BeaconState, - signedBlock: phase0.SignedBeaconBlock + signedBlock: allForks.SignedBeaconBlock ): boolean { - const domain = getDomain(config, state as phase0.BeaconState, config.params.DOMAIN_BEACON_PROPOSER); - const signingRoot = computeSigningRoot(config, config.types.phase0.BeaconBlock, signedBlock.message, domain); + const domain = getDomain(config, state, config.params.DOMAIN_BEACON_PROPOSER); + const blockType = config.getTypes(signedBlock.message.slot).BeaconBlock; + const signingRoot = computeSigningRoot(config, blockType, signedBlock.message, domain); const proposer = state.validators[signedBlock.message.proposerIndex]; return bls.verify( proposer.pubkey.valueOf() as Uint8Array, diff --git a/packages/beacon-state-transition/src/util/blockRoot.ts b/packages/beacon-state-transition/src/util/blockRoot.ts index df226d0b7198..a8ef0f867e53 100644 --- a/packages/beacon-state-transition/src/util/blockRoot.ts +++ b/packages/beacon-state-transition/src/util/blockRoot.ts @@ -2,13 +2,12 @@ * @module chain/stateTransition/util */ -import {Epoch, Slot, Root, phase0, altair, allForks} from "@chainsafe/lodestar-types"; +import {Epoch, Slot, Root, phase0, allForks} from "@chainsafe/lodestar-types"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {assert} from "@chainsafe/lodestar-utils"; import {ZERO_HASH} from "../constants"; import {computeStartSlotAtEpoch} from "./epoch"; -import {ContainerType} from "@chainsafe/ssz"; /** * Return the block root at a recent [[slot]]. @@ -32,32 +31,27 @@ export function getBlockRoot(config: IBeaconConfig, state: allForks.BeaconState, /** * Return the block header corresponding to a block with ``state_root`` set to ``ZERO_HASH``. */ -export function getTemporaryBlockHeader( - config: IBeaconConfig, - block: phase0.BeaconBlock | altair.BeaconBlock -): phase0.BeaconBlockHeader { +export function getTemporaryBlockHeader(config: IBeaconConfig, block: allForks.BeaconBlock): phase0.BeaconBlockHeader { return { slot: block.slot, proposerIndex: block.proposerIndex, parentRoot: block.parentRoot, // `state_root` is zeroed and overwritten in the next `process_slot` call stateRoot: ZERO_HASH, - bodyRoot: (config.getTypes(block.slot).BeaconBlockBody as ContainerType).hashTreeRoot( - block.body - ), + bodyRoot: config.getTypes(block.slot).BeaconBlockBody.hashTreeRoot(block.body), }; } /** * Receives a BeaconBlock, and produces the corresponding BeaconBlockHeader. */ -export function blockToHeader(config: IBeaconConfig, block: phase0.BeaconBlock): phase0.BeaconBlockHeader { +export function blockToHeader(config: IBeaconConfig, block: allForks.BeaconBlock): phase0.BeaconBlockHeader { return { stateRoot: block.stateRoot, proposerIndex: block.proposerIndex, slot: block.slot, parentRoot: block.parentRoot, - bodyRoot: config.types.phase0.BeaconBlockBody.hashTreeRoot(block.body), + bodyRoot: config.getTypes(block.slot).BeaconBlockBody.hashTreeRoot(block.body), }; } @@ -66,7 +60,7 @@ export function blockToHeader(config: IBeaconConfig, block: phase0.BeaconBlock): */ export function signedBlockToSignedHeader( config: IBeaconConfig, - signedBlock: phase0.SignedBeaconBlock + signedBlock: allForks.SignedBeaconBlock ): phase0.SignedBeaconBlockHeader { return { message: blockToHeader(config, signedBlock.message), diff --git a/packages/beacon-state-transition/src/util/domain.ts b/packages/beacon-state-transition/src/util/domain.ts index 4706cb673f44..d178a22ea1a9 100644 --- a/packages/beacon-state-transition/src/util/domain.ts +++ b/packages/beacon-state-transition/src/util/domain.ts @@ -41,7 +41,7 @@ export function getDomain( domainType: DomainType, messageEpoch: Epoch | null = null ): Buffer { - const epoch = messageEpoch || getCurrentEpoch(config, state); + const epoch = messageEpoch ?? getCurrentEpoch(config, state); const forkVersion = getForkVersion(state.fork, epoch); return computeDomain(config, domainType, forkVersion, state.genesisValidatorsRoot); } diff --git a/packages/beacon-state-transition/src/util/genesis.ts b/packages/beacon-state-transition/src/util/genesis.ts index 4ede591c7e7a..dd544eb2c36c 100644 --- a/packages/beacon-state-transition/src/util/genesis.ts +++ b/packages/beacon-state-transition/src/util/genesis.ts @@ -1,10 +1,11 @@ import {List, TreeBacked} from "@chainsafe/ssz"; -import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {ForkName, IBeaconConfig} from "@chainsafe/lodestar-config"; import {GENESIS_SLOT} from "@chainsafe/lodestar-params"; -import {allForks, Bytes32, Number64, phase0, Root} from "@chainsafe/lodestar-types"; +import {allForks, altair, Bytes32, Number64, phase0, Root} from "@chainsafe/lodestar-types"; import {bigIntMin} from "@chainsafe/lodestar-utils"; -import {processDeposit} from "../naive/phase0"; +import {processDeposit as phase0ProcessDeposit} from "../naive/phase0"; +import {processDeposit as altairProcessDeposit} from "../naive/altair"; import {computeEpochAtSlot} from "./epoch"; import {getActiveValidatorIndices} from "./validator"; import {getTemporaryBlockHeader} from "./blockRoot"; @@ -46,9 +47,10 @@ export function getGenesisBeaconState( const state: allForks.BeaconState = config.getTypes(GENESIS_SLOT).BeaconState.defaultTreeBacked(); // MISC state.slot = GENESIS_SLOT; + const version = config.getForkVersion(GENESIS_SLOT); state.fork = { - previousVersion: config.params.GENESIS_FORK_VERSION, - currentVersion: config.params.GENESIS_FORK_VERSION, + previousVersion: version, + currentVersion: version, epoch: computeEpochAtSlot(config, GENESIS_SLOT), } as phase0.Fork; @@ -128,7 +130,13 @@ export function applyDeposits( } state.eth1Data.depositCount += 1; - processDeposit(config, state as phase0.BeaconState, deposit); + + const forkName = config.getForkName(GENESIS_SLOT); + if (forkName == ForkName.phase0) { + phase0ProcessDeposit(config, state, deposit); + } else { + altairProcessDeposit(config, state as altair.BeaconState, deposit); + } } // Process activations @@ -149,7 +157,9 @@ export function applyDeposits( } // Set genesis validators root for domain separation and chain versioning - state.genesisValidatorsRoot = config.types.phase0.BeaconState.fields.validators.hashTreeRoot(state.validators); + state.genesisValidatorsRoot = config + .getTypes(state.slot) + .BeaconState.fields.validators.hashTreeRoot(state.validators); } /** @@ -168,7 +178,7 @@ export function initializeBeaconStateFromEth1( const state = getGenesisBeaconState( config, config.types.phase0.Eth1Data.defaultValue(), - getTemporaryBlockHeader(config, config.types.phase0.BeaconBlock.defaultValue()) + getTemporaryBlockHeader(config, config.getTypes(GENESIS_SLOT).BeaconBlock.defaultValue()) ); applyTimestamp(config, state, eth1Timestamp); diff --git a/packages/beacon-state-transition/test/perf/phase0/slot/slots.test.ts b/packages/beacon-state-transition/test/perf/phase0/slot/slots.test.ts index 647c971b9eab..0f0f5340caea 100644 --- a/packages/beacon-state-transition/test/perf/phase0/slot/slots.test.ts +++ b/packages/beacon-state-transition/test/perf/phase0/slot/slots.test.ts @@ -15,7 +15,7 @@ describe("Process Slots Performance Test", function () { for (let i = 0; i < maxTry; i++) { state = generatePerfTestCachedBeaconState({goBackOneSlot: true}); const start = Date.now(); - phase0.processSlots(state, state.slot + numSlot); + allForks.processSlots(state as allForks.CachedBeaconState, state.slot + numSlot); duration += Date.now() - start; } logger.profile(`Process ${numSlot} slots ${maxTry} times`); diff --git a/packages/cli/src/cmds/beacon/initBeaconState.ts b/packages/cli/src/cmds/beacon/initBeaconState.ts index 9dd197aaf716..eb65a4b7f4ec 100644 --- a/packages/cli/src/cmds/beacon/initBeaconState.ts +++ b/packages/cli/src/cmds/beacon/initBeaconState.ts @@ -12,6 +12,7 @@ import { initStateFromDb, initStateFromEth1, } from "@chainsafe/lodestar"; +import {getStateTypeFromBytes} from "@chainsafe/lodestar/lib/util/multifork"; import {downloadOrLoadFile} from "../../util"; import {IBeaconArgs} from "./options"; import {defaultNetwork, IGlobalArgs} from "../../options/globalOptions"; @@ -34,7 +35,8 @@ export async function initBeaconState( signal: AbortSignal ): Promise> { async function initFromFile(pathOrUrl: string): Promise> { - const anchorState = config.types.phase0.BeaconState.createTreeBackedFromBytes(await downloadOrLoadFile(pathOrUrl)); + const stateBytes = await downloadOrLoadFile(pathOrUrl); + const anchorState = getStateTypeFromBytes(config, stateBytes).createTreeBackedFromBytes(stateBytes); return await initStateFromAnchorState(config, db, logger, anchorState as TreeBacked); } diff --git a/packages/lodestar/src/api/impl/beacon/beacon.ts b/packages/lodestar/src/api/impl/beacon/beacon.ts index c533e0e0b9d2..63117197bdbe 100644 --- a/packages/lodestar/src/api/impl/beacon/beacon.ts +++ b/packages/lodestar/src/api/impl/beacon/beacon.ts @@ -3,7 +3,8 @@ */ import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {phase0} from "@chainsafe/lodestar-types"; +import {GENESIS_SLOT} from "@chainsafe/lodestar-params"; +import {allForks, phase0} from "@chainsafe/lodestar-types"; import {LodestarEventIterator} from "@chainsafe/lodestar-utils"; import {ChainEvent, IBeaconChain} from "../../../chain"; import {IApiOptions} from "../../options"; @@ -33,15 +34,16 @@ export class BeaconApi implements IBeaconApi { } async getGenesis(): Promise { + const genesisForkVersion = this.config.getForkVersion(GENESIS_SLOT); return { - genesisForkVersion: this.config.params.GENESIS_FORK_VERSION, + genesisForkVersion, genesisTime: BigInt(this.chain.genesisTime), genesisValidatorsRoot: this.chain.genesisValidatorsRoot, }; } - getBlockStream(): LodestarEventIterator { - return new LodestarEventIterator(({push}) => { + getBlockStream(): LodestarEventIterator { + return new LodestarEventIterator(({push}) => { this.chain.emitter.on(ChainEvent.block, push); return () => { this.chain.emitter.off(ChainEvent.block, push); diff --git a/packages/lodestar/src/api/impl/beacon/blocks/index.ts b/packages/lodestar/src/api/impl/beacon/blocks/index.ts index c1a004d0ca4a..bb8d0da8c1c8 100644 --- a/packages/lodestar/src/api/impl/beacon/blocks/index.ts +++ b/packages/lodestar/src/api/impl/beacon/blocks/index.ts @@ -9,7 +9,6 @@ import {BlockId, IBeaconBlocksApi} from "./interface"; import {resolveBlockId, toBeaconHeaderResponse} from "./utils"; import {IBeaconSync} from "../../../../sync"; import {INetwork} from "../../../../network/interface"; -import {getBlockType} from "../../../../util/multifork"; export * from "./interface"; @@ -80,7 +79,9 @@ export class BeaconBlockApi implements IBeaconBlocksApi { if (!canonicalBlock) { return []; } - const canonicalRoot = this.config.types.phase0.BeaconBlock.hashTreeRoot(canonicalBlock.message); + const canonicalRoot = this.config + .getTypes(canonicalBlock.message.slot) + .BeaconBlock.hashTreeRoot(canonicalBlock.message); result.push(toBeaconHeaderResponse(this.config, canonicalBlock, true)); // fork blocks @@ -126,10 +127,10 @@ export class BeaconBlockApi implements IBeaconBlocksApi { // Slow path const block = await resolveBlockId(this.chain.forkChoice, this.db, blockId); - return getBlockType(this.config, block.message).hashTreeRoot(block.message); + return this.config.getTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message); } - async publishBlock(signedBlock: phase0.SignedBeaconBlock): Promise { + async publishBlock(signedBlock: allForks.SignedBeaconBlock): Promise { await Promise.all([this.chain.receiveBlock(signedBlock), this.network.gossip.publishBeaconBlock(signedBlock)]); } } diff --git a/packages/lodestar/src/api/impl/beacon/blocks/interface.ts b/packages/lodestar/src/api/impl/beacon/blocks/interface.ts index 356c9a0887a6..3b0f38c8e8d4 100644 --- a/packages/lodestar/src/api/impl/beacon/blocks/interface.ts +++ b/packages/lodestar/src/api/impl/beacon/blocks/interface.ts @@ -5,7 +5,7 @@ export interface IBeaconBlocksApi { getBlockHeaders(filters: Partial<{slot: Slot; parentRoot: Root}>): Promise; getBlockHeader(blockId: BlockId): Promise; getBlockRoot(blockId: BlockId): Promise; - publishBlock(block: phase0.SignedBeaconBlock): Promise; + publishBlock(block: allForks.SignedBeaconBlock): Promise; } export type BlockId = string | "head" | "genesis" | "finalized"; diff --git a/packages/lodestar/src/api/impl/beacon/blocks/utils.ts b/packages/lodestar/src/api/impl/beacon/blocks/utils.ts index d1a5eedb3641..ccdb574c7a77 100644 --- a/packages/lodestar/src/api/impl/beacon/blocks/utils.ts +++ b/packages/lodestar/src/api/impl/beacon/blocks/utils.ts @@ -10,11 +10,11 @@ import {ApiError, ValidationError} from "../../errors"; export function toBeaconHeaderResponse( config: IBeaconConfig, - block: phase0.SignedBeaconBlock, + block: allForks.SignedBeaconBlock, canonical = false ): phase0.SignedBeaconHeaderResponse { return { - root: config.types.phase0.BeaconBlock.hashTreeRoot(block.message), + root: config.getTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message), canonical, header: { message: blockToHeader(config, block.message), diff --git a/packages/lodestar/src/api/impl/beacon/interface.ts b/packages/lodestar/src/api/impl/beacon/interface.ts index ae1bb4b8c0c2..a894cf5e0827 100644 --- a/packages/lodestar/src/api/impl/beacon/interface.ts +++ b/packages/lodestar/src/api/impl/beacon/interface.ts @@ -2,7 +2,7 @@ * @module api/rpc */ -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks, phase0} from "@chainsafe/lodestar-types"; import {IStoppableEventIterable} from "@chainsafe/lodestar-utils"; import {IBeaconBlocksApi} from "./blocks"; import {IBeaconPoolApi} from "./pool"; @@ -13,5 +13,5 @@ export interface IBeaconApi { state: IBeaconStateApi; pool: IBeaconPoolApi; getGenesis(): Promise; - getBlockStream(): IStoppableEventIterable; + getBlockStream(): IStoppableEventIterable; } diff --git a/packages/lodestar/src/api/impl/events/handlers.ts b/packages/lodestar/src/api/impl/events/handlers.ts index e3de6394a5dd..c1a542929526 100644 --- a/packages/lodestar/src/api/impl/events/handlers.ts +++ b/packages/lodestar/src/api/impl/events/handlers.ts @@ -32,7 +32,7 @@ export function handleBeaconBlockEvent( callback({ type: BeaconEventType.BLOCK, message: { - block: config.types.phase0.BeaconBlock.hashTreeRoot(payload.message), + block: config.getTypes(payload.message.slot).BeaconBlock.hashTreeRoot(payload.message), slot: payload.message.slot, }, }); diff --git a/packages/lodestar/src/api/impl/validator/validator.ts b/packages/lodestar/src/api/impl/validator/validator.ts index 56e242eb97d0..4b9aec0ced3a 100644 --- a/packages/lodestar/src/api/impl/validator/validator.ts +++ b/packages/lodestar/src/api/impl/validator/validator.ts @@ -104,13 +104,7 @@ export class ValidatorApi implements IValidatorApi { const headRoot = this.chain.forkChoice.getHeadRoot(); const state = await this.chain.regen.getBlockSlotState(headRoot, slot); - return assembleAttestationData( - state.config, - state as CachedBeaconState, - headRoot, - slot, - committeeIndex - ); + return assembleAttestationData(state.config, state, headRoot, slot, committeeIndex); } /** @@ -392,7 +386,9 @@ export class ValidatorApi implements IValidatorApi { const genesisBlock = await this.chain.getCanonicalBlockAtSlot(GENESIS_SLOT); if (genesisBlock) { - this.genesisBlockRoot = this.config.types.phase0.SignedBeaconBlock.hashTreeRoot(genesisBlock); + this.genesisBlockRoot = this.config + .getTypes(genesisBlock.message.slot) + .SignedBeaconBlock.hashTreeRoot(genesisBlock); } } diff --git a/packages/lodestar/src/api/rest/beacon/blocks/getBlock.ts b/packages/lodestar/src/api/rest/beacon/blocks/getBlock.ts index df2a29a1bb00..464151d13c3e 100644 --- a/packages/lodestar/src/api/rest/beacon/blocks/getBlock.ts +++ b/packages/lodestar/src/api/rest/beacon/blocks/getBlock.ts @@ -1,13 +1,11 @@ import {ApiController} from "../../types"; -import {getSignedBlockType} from "../../../../util/multifork"; // V2 handler is backwards compatible so re-use it for both versions const handler: ApiController["handler"] = async function (req) { const block = await this.api.beacon.blocks.getBlock(req.params.blockId); - const type = getSignedBlockType(this.config, block); return { version: this.config.getForkName(block.message.slot), - data: type.toJson(block, {case: "snake"}), + data: this.config.getTypes(block.message.slot).SignedBeaconBlock.toJson(block, {case: "snake"}), }; }; diff --git a/packages/lodestar/src/api/rest/beacon/blocks/publishBlock.ts b/packages/lodestar/src/api/rest/beacon/blocks/publishBlock.ts index 6b463c032d23..ea434038706f 100644 --- a/packages/lodestar/src/api/rest/beacon/blocks/publishBlock.ts +++ b/packages/lodestar/src/api/rest/beacon/blocks/publishBlock.ts @@ -1,6 +1,5 @@ -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks} from "@chainsafe/lodestar-types"; import {SignedBeaconBlock} from "@chainsafe/lodestar-types/lib/allForks"; -import {getSignedBlockType} from "../../../../util/multifork"; import {ValidationError} from "../../../impl/errors"; import {ApiController} from "../../types"; @@ -12,10 +11,10 @@ export const publishBlock: ApiController = { id: "publishBlock", handler: async function (req) { - let block: phase0.SignedBeaconBlock; + let block: allForks.SignedBeaconBlock; try { - const type = getSignedBlockType(this.config, req.body as SignedBeaconBlock); - block = type.fromJson(req.body, {case: "snake"}); + const slot = (req.body as SignedBeaconBlock).message.slot; + block = this.config.getTypes(slot).SignedBeaconBlock.fromJson(req.body, {case: "snake"}); } catch (e) { throw new ValidationError(`Failed to deserialize block: ${(e as Error).message}`); } diff --git a/packages/lodestar/src/api/rest/debug/getStates.ts b/packages/lodestar/src/api/rest/debug/getStates.ts index c8c7737b5b18..294663a52fd1 100644 --- a/packages/lodestar/src/api/rest/debug/getStates.ts +++ b/packages/lodestar/src/api/rest/debug/getStates.ts @@ -1,4 +1,3 @@ -import {getStateTypeFromState} from "../../../util/multifork"; import {ApiController, HttpHeader} from "../types"; const SSZ_MIME_TYPE = "application/octet-stream"; @@ -6,7 +5,7 @@ const SSZ_MIME_TYPE = "application/octet-stream"; // V2 handler is backwards compatible so re-use it for both versions const handler: ApiController["handler"] = async function (req, resp) { const state = await this.api.debug.beacon.getState(req.params.stateId); - const type = getStateTypeFromState(this.config, state); + const type = this.config.getTypes(state.slot).BeaconState; if (req.headers[HttpHeader.ACCEPT] === SSZ_MIME_TYPE) { const stateSsz = type.serialize(state); return resp.status(200).header(HttpHeader.CONTENT_TYPE, SSZ_MIME_TYPE).send(Buffer.from(stateSsz)); diff --git a/packages/lodestar/src/api/rest/validator/produceBlock.ts b/packages/lodestar/src/api/rest/validator/produceBlock.ts index e668528e37f9..ba2518b416e5 100644 --- a/packages/lodestar/src/api/rest/validator/produceBlock.ts +++ b/packages/lodestar/src/api/rest/validator/produceBlock.ts @@ -1,5 +1,4 @@ import {fromHex} from "@chainsafe/lodestar-utils"; -import {getBlockType} from "../../../util/multifork"; import {ApiController} from "../types"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -19,10 +18,9 @@ const handler: ApiController["handler"] = async function (req) { fromHex(req.query.randao_reveal), req.query.grafitti ); - const type = getBlockType(this.config, block); return { version: this.config.getForkName(block.slot), - data: type.toJson(block, {case: "snake"}), + data: this.config.getTypes(block.slot).BeaconBlock.toJson(block, {case: "snake"}), }; }; diff --git a/packages/lodestar/src/api/rest/validator/produceSyncCommitteeContribution.ts b/packages/lodestar/src/api/rest/validator/produceSyncCommitteeContribution.ts index 692558adeb9f..3e51c798833c 100644 --- a/packages/lodestar/src/api/rest/validator/produceSyncCommitteeContribution.ts +++ b/packages/lodestar/src/api/rest/validator/produceSyncCommitteeContribution.ts @@ -28,7 +28,7 @@ export const produceSyncCommitteeContribution: ApiController = { schema: { querystring: { type: "object", - required: ["committee_index", "slot"], + required: ["slot", "subcommittee_index", "beacon_block_root"], properties: { slot: { type: "number", diff --git a/packages/lodestar/src/chain/blocks/pool.ts b/packages/lodestar/src/chain/blocks/pool.ts index d32da2bdccc1..97dad25491d9 100644 --- a/packages/lodestar/src/chain/blocks/pool.ts +++ b/packages/lodestar/src/chain/blocks/pool.ts @@ -1,6 +1,6 @@ import {fromHexString, toHexString} from "@chainsafe/ssz"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {Root, phase0, Slot} from "@chainsafe/lodestar-types"; +import {Root, Slot, allForks} from "@chainsafe/lodestar-types"; /** * The BlockPool is a cache of blocks that are pending processing. @@ -32,7 +32,7 @@ export class BlockPool { this.blocksBySlot = new Map>(); } - addByParent(signedBlock: phase0.SignedBeaconBlock): void { + addByParent(signedBlock: allForks.SignedBeaconBlock): void { // put block in two indices: // blocks const blockKey = this.getBlockKey(signedBlock); @@ -53,7 +53,7 @@ export class BlockPool { blocksWithParent.add(blockKey); } - addBySlot(signedBlock: phase0.SignedBeaconBlock): void { + addBySlot(signedBlock: allForks.SignedBeaconBlock): void { // put block in two indices: // blocks const blockKey = this.getBlockKey(signedBlock); @@ -74,7 +74,7 @@ export class BlockPool { blocksAtSlot.add(blockKey); } - remove(signedBlock: phase0.SignedBeaconBlock): void { + remove(signedBlock: allForks.SignedBeaconBlock): void { // remove block from three indices: // blocks const blockKey = this.getBlockKey(signedBlock); @@ -139,16 +139,16 @@ export class BlockPool { return Array.from(new Set(blockRoots)).map((root) => fromHexString(root)); } - private getParentKey(block: phase0.SignedBeaconBlock): string { + private getParentKey(block: allForks.SignedBeaconBlock): string { return toHexString(block.message.parentRoot); } - private getSlotKey(block: phase0.SignedBeaconBlock): number { + private getSlotKey(block: allForks.SignedBeaconBlock): number { return block.message.slot; } - private getBlockKey(block: phase0.SignedBeaconBlock): string { - return toHexString(this.config.types.phase0.BeaconBlock.hashTreeRoot(block.message)); + private getBlockKey(block: allForks.SignedBeaconBlock): string { + return toHexString(this.config.getTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message)); } private getBlockKeyByRoot(root: Root): string { diff --git a/packages/lodestar/src/chain/blocks/processor.ts b/packages/lodestar/src/chain/blocks/processor.ts index f203d0a357a2..4b5cd369639e 100644 --- a/packages/lodestar/src/chain/blocks/processor.ts +++ b/packages/lodestar/src/chain/blocks/processor.ts @@ -2,7 +2,7 @@ import {AbortSignal} from "abort-controller"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {IForkChoice} from "@chainsafe/lodestar-fork-choice"; -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks} from "@chainsafe/lodestar-types"; import {IBlockJob, IChainSegmentJob} from "../interface"; import {ChainEvent, ChainEventEmitter} from "../emitter"; @@ -83,10 +83,11 @@ export async function processBlockJob(modules: BlockProcessorModules, job: IBloc * Similar to processBlockJob but this process a chain segment */ export async function processChainSegmentJob(modules: BlockProcessorModules, job: IChainSegmentJob): Promise { + const config = modules.config; const blocks = job.signedBlocks; // Validate and filter out irrelevant blocks - const filteredChainSegment: phase0.SignedBeaconBlock[] = []; + const filteredChainSegment: allForks.SignedBeaconBlock[] = []; for (const [i, block] of blocks.entries()) { const child = blocks[i + 1]; if (child) { @@ -96,8 +97,8 @@ export async function processChainSegmentJob(modules: BlockProcessorModules, job // Without this check it would be possible to have a block verified using the // incorrect shuffling. That would be bad, mmkay. if ( - !modules.config.types.Root.equals( - modules.config.types.phase0.BeaconBlock.hashTreeRoot(block.message), + !config.types.Root.equals( + config.getTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message), child.message.parentRoot ) ) { diff --git a/packages/lodestar/src/chain/blocks/stateTransition.ts b/packages/lodestar/src/chain/blocks/stateTransition.ts index a3b746ba6af3..c5fe7284ce76 100644 --- a/packages/lodestar/src/chain/blocks/stateTransition.ts +++ b/packages/lodestar/src/chain/blocks/stateTransition.ts @@ -134,14 +134,16 @@ function emitCheckpointEvent( const config = checkpointState.config; const slot = checkpointState.slot; assert.true(slot % config.params.SLOTS_PER_EPOCH === 0, "Checkpoint state slot must be first in an epoch"); - const blockHeader = config.types.phase0.BeaconBlockHeader.clone(checkpointState.latestBlockHeader); + const blockHeader = config + .getTypes(checkpointState.latestBlockHeader.slot) + .BeaconBlockHeader.clone(checkpointState.latestBlockHeader); if (config.types.Root.equals(blockHeader.stateRoot, ZERO_HASH)) { blockHeader.stateRoot = config.getTypes(slot).BeaconState.hashTreeRoot(checkpointState); } emitter.emit( ChainEvent.checkpoint, { - root: config.types.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader), + root: config.getTypes(blockHeader.slot).BeaconBlockHeader.hashTreeRoot(blockHeader), epoch: computeEpochAtSlot(config, slot), }, checkpointState diff --git a/packages/lodestar/src/chain/blocks/util.ts b/packages/lodestar/src/chain/blocks/util.ts index 8193077d5bfb..9d5e1a6cedc1 100644 --- a/packages/lodestar/src/chain/blocks/util.ts +++ b/packages/lodestar/src/chain/blocks/util.ts @@ -1,6 +1,6 @@ import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {computeEpochAtSlot} from "@chainsafe/lodestar-beacon-state-transition"; -import {Epoch, phase0} from "@chainsafe/lodestar-types"; +import {allForks, Epoch} from "@chainsafe/lodestar-types"; /** * Groups blocks by ascending epoch @@ -11,9 +11,9 @@ import {Epoch, phase0} from "@chainsafe/lodestar-types"; */ export function groupBlocksByEpoch( config: IBeaconConfig, - blocks: phase0.SignedBeaconBlock[] -): phase0.SignedBeaconBlock[][] { - const blocksByEpoch = new Map(); + blocks: allForks.SignedBeaconBlock[] +): allForks.SignedBeaconBlock[][] { + const blocksByEpoch = new Map(); for (const block of blocks) { const epoch = computeEpochAtSlot(config, block.message.slot); diff --git a/packages/lodestar/src/chain/blocks/validate.ts b/packages/lodestar/src/chain/blocks/validate.ts index d0b1cbe0ee99..733b45b9ca1a 100644 --- a/packages/lodestar/src/chain/blocks/validate.ts +++ b/packages/lodestar/src/chain/blocks/validate.ts @@ -11,7 +11,7 @@ export function validateBlock( job: IBlockJob ): void { try { - const blockHash = config.types.phase0.BeaconBlock.hashTreeRoot(job.signedBlock.message); + const blockHash = config.getTypes(job.signedBlock.message.slot).BeaconBlock.hashTreeRoot(job.signedBlock.message); const blockSlot = job.signedBlock.message.slot; if (blockSlot === 0) { throw new BlockError({ diff --git a/packages/lodestar/src/chain/factory/attestation/data.ts b/packages/lodestar/src/chain/factory/attestation/data.ts index e68ff3a0f29f..261d26014e19 100644 --- a/packages/lodestar/src/chain/factory/attestation/data.ts +++ b/packages/lodestar/src/chain/factory/attestation/data.ts @@ -1,4 +1,4 @@ -import {phase0, CommitteeIndex, Slot, Root} from "@chainsafe/lodestar-types"; +import {phase0, CommitteeIndex, Slot, Root, allForks} from "@chainsafe/lodestar-types"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import { computeStartSlotAtEpoch, @@ -8,7 +8,7 @@ import { export function assembleAttestationData( config: IBeaconConfig, - headState: phase0.BeaconState, + headState: allForks.BeaconState, headBlockRoot: Uint8Array, slot: Slot, index: CommitteeIndex diff --git a/packages/lodestar/src/chain/factory/block/index.ts b/packages/lodestar/src/chain/factory/block/index.ts index 581c2d89af0e..f8a88fdc0275 100644 --- a/packages/lodestar/src/chain/factory/block/index.ts +++ b/packages/lodestar/src/chain/factory/block/index.ts @@ -9,7 +9,6 @@ import {ZERO_HASH} from "../../../constants"; import {IBeaconDb} from "../../../db"; import {IEth1ForBlockProduction} from "../../../eth1"; import {IMetrics} from "../../../metrics"; -import {getStateTypeFromState} from "../../../util/multifork"; import {IBeaconChain} from "../../interface"; import {assembleBody} from "./body"; @@ -59,5 +58,5 @@ function computeNewStateRoot( // verifySignatures = false since the data to assemble the block is trusted allForks.processBlock(postState, block, {verifySignatures: false}, metrics); - return getStateTypeFromState(config, postState).hashTreeRoot(postState); + return config.getTypes(state.slot).BeaconState.hashTreeRoot(postState); } diff --git a/packages/lodestar/src/chain/initState.ts b/packages/lodestar/src/chain/initState.ts index 634b1c745ceb..ab3056632031 100644 --- a/packages/lodestar/src/chain/initState.ts +++ b/packages/lodestar/src/chain/initState.ts @@ -25,7 +25,7 @@ import {CheckpointStateCache, StateContextCache} from "./stateCache"; export async function persistGenesisResult( db: IBeaconDb, genesisResult: IGenesisResult, - genesisBlock: phase0.SignedBeaconBlock + genesisBlock: allForks.SignedBeaconBlock ): Promise { await Promise.all([ db.stateArchive.add(genesisResult.state), diff --git a/packages/lodestar/src/chain/regen/regen.ts b/packages/lodestar/src/chain/regen/regen.ts index 51b8e5980544..04258768286f 100644 --- a/packages/lodestar/src/chain/regen/regen.ts +++ b/packages/lodestar/src/chain/regen/regen.ts @@ -55,7 +55,7 @@ export class StateRegenerator implements IStateRegenerator { this.metrics = metrics; } - async getPreState(block: phase0.BeaconBlock): Promise> { + async getPreState(block: allForks.BeaconBlock): Promise> { const parentBlock = this.forkChoice.getBlock(block.parentRoot); if (!parentBlock) { throw new RegenError({ diff --git a/packages/lodestar/src/chain/validation/block.ts b/packages/lodestar/src/chain/validation/block.ts index f9941350fd16..5315acee056b 100644 --- a/packages/lodestar/src/chain/validation/block.ts +++ b/packages/lodestar/src/chain/validation/block.ts @@ -2,7 +2,7 @@ import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {IBeaconChain, IBlockJob} from ".."; import {IBeaconDb} from "../../db"; import {computeStartSlotAtEpoch} from "@chainsafe/lodestar-beacon-state-transition"; -import {allForks, phase0} from "@chainsafe/lodestar-beacon-state-transition"; +import {allForks} from "@chainsafe/lodestar-beacon-state-transition"; import {BlockError, BlockErrorCode} from "../errors"; export async function validateGossipBlock( @@ -13,7 +13,7 @@ export async function validateGossipBlock( ): Promise { const block = blockJob.signedBlock; const blockSlot = block.message.slot; - const blockRoot = config.types.phase0.BeaconBlock.hashTreeRoot(block.message); + const blockRoot = config.getTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message); const finalizedCheckpoint = chain.getFinalizedCheckpoint(); const finalizedSlot = computeStartSlotAtEpoch(config, finalizedCheckpoint.epoch); // block is too old @@ -90,7 +90,7 @@ export async function validateGossipBlock( } } -export function isExpectedProposer(epochCtx: allForks.EpochContext, block: phase0.BeaconBlock): boolean { +export function isExpectedProposer(epochCtx: allForks.EpochContext, block: allForks.BeaconBlock): boolean { const supposedProposerIndex = epochCtx.getBeaconProposer(block.slot); return supposedProposerIndex === block.proposerIndex; } diff --git a/packages/lodestar/src/db/beacon.ts b/packages/lodestar/src/db/beacon.ts index 7a5259844d0a..30a2c69763f5 100644 --- a/packages/lodestar/src/db/beacon.ts +++ b/packages/lodestar/src/db/beacon.ts @@ -2,7 +2,7 @@ * @module db/api/beacon */ -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks} from "@chainsafe/lodestar-types"; import {DatabaseService, IDatabaseApiOptions} from "@chainsafe/lodestar-db"; import {IBeaconDb} from "./interface"; import { @@ -76,7 +76,7 @@ export class BeaconDb extends DatabaseService implements IBeaconDb { /** * Remove stored operations based on a newly processed block */ - async processBlockOperations(signedBlock: phase0.SignedBeaconBlock): Promise { + async processBlockOperations(signedBlock: allForks.SignedBeaconBlock): Promise { await Promise.all([ this.voluntaryExit.batchRemove(signedBlock.message.body.voluntaryExits), this.depositEvent.deleteOld(signedBlock.message.body.eth1Data.depositCount), diff --git a/packages/lodestar/src/db/interface.ts b/packages/lodestar/src/db/interface.ts index 642e8fddede5..44baf2f3135a 100644 --- a/packages/lodestar/src/db/interface.ts +++ b/packages/lodestar/src/db/interface.ts @@ -2,7 +2,7 @@ * @module db/api/beacon */ -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks} from "@chainsafe/lodestar-types"; import { AggregateAndProofRepository, @@ -68,7 +68,7 @@ export interface IBeaconDb { syncCommittee: SyncCommitteeCache; syncCommitteeContribution: SyncCommitteeContributionCache; - processBlockOperations(signedBlock: phase0.SignedBeaconBlock): Promise; + processBlockOperations(signedBlock: allForks.SignedBeaconBlock): Promise; /** * Start the connection to the db instance and open the db store. diff --git a/packages/lodestar/src/db/repositories/block.ts b/packages/lodestar/src/db/repositories/block.ts index 08c2f9acd81b..ced99b91cc02 100644 --- a/packages/lodestar/src/db/repositories/block.ts +++ b/packages/lodestar/src/db/repositories/block.ts @@ -1,7 +1,7 @@ import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {Bucket, IDatabaseController, Repository} from "@chainsafe/lodestar-db"; import {allForks} from "@chainsafe/lodestar-types"; -import {getSignedBlockType, getSignedBlockTypeFromBytes} from "../../util/multifork"; +import {getSignedBlockTypeFromBytes} from "../../util/multifork"; /** * Blocks by root @@ -18,11 +18,11 @@ export class BlockRepository extends Repository { - const blockRoot = getSignedBlockType(this.config, value).fields["message"].hashTreeRoot(value.message); + const blockRoot = this.config.getTypes(value.message.slot).BeaconBlock.hashTreeRoot(value.message); const slot = value.message.slot; await Promise.all([ super.put(key, value), @@ -63,9 +63,7 @@ export class BlockArchiveRepository extends Repository { const slot = item.value.message.slot; - const blockRoot = getSignedBlockType(this.config, item.value).fields["message"].hashTreeRoot( - item.value.message - ); + const blockRoot = this.config.getTypes(slot).BeaconBlock.hashTreeRoot(item.value.message); return storeRootIndex(this.db, slot, blockRoot); }), Array.from(items).map((item) => { @@ -87,7 +85,7 @@ export class BlockArchiveRepository extends Repository { await Promise.all([ super.remove(value), - deleteRootIndex(this.db, getSignedBlockType(this.config, value), value), + deleteRootIndex(this.db, this.config.getTypes(value.message.slot).SignedBeaconBlock, value), deleteParentRootIndex(this.db, value), ]); } @@ -95,7 +93,9 @@ export class BlockArchiveRepository extends Repository): Promise { await Promise.all([ super.batchRemove(values), - Array.from(values).map((value) => deleteRootIndex(this.db, getSignedBlockType(this.config, value), value)), + Array.from(values).map((value) => + deleteRootIndex(this.db, this.config.getTypes(value.message.slot).SignedBeaconBlock, value) + ), Array.from(values).map((value) => deleteParentRootIndex(this.db, value)), ]); } diff --git a/packages/lodestar/src/db/repositories/pendingBlock.ts b/packages/lodestar/src/db/repositories/pendingBlock.ts index 9dc587fb6c9c..14976482b4eb 100644 --- a/packages/lodestar/src/db/repositories/pendingBlock.ts +++ b/packages/lodestar/src/db/repositories/pendingBlock.ts @@ -1,7 +1,7 @@ import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {Bucket, IDatabaseController, Repository} from "@chainsafe/lodestar-db"; import {allForks} from "@chainsafe/lodestar-types"; -import {getSignedBlockType, getSignedBlockTypeFromBytes} from "../../util/multifork"; +import {getSignedBlockTypeFromBytes} from "../../util/multifork"; /** * Blocks by root @@ -18,11 +18,11 @@ export class PendingBlockRepository extends Repository> { constructor(config: IBeaconConfig, db: IDatabaseController) { - const type = (config.types.phase0.BeaconState as unknown) as ContainerType>; // Pick some type but won't be used + // Pick some type but won't be used + const type = (config.types.phase0.BeaconState as unknown) as ContainerType>; super(config, db, Bucket.allForks_stateArchive, type); } // Overrides for multi-fork encodeValue(value: allForks.BeaconState): Buffer { - return getStateTypeFromState(this.config, value).serialize(value) as Buffer; + return this.config.getTypes(value.slot).BeaconState.serialize(value) as Buffer; } decodeValue(data: Buffer): TreeBacked { diff --git a/packages/lodestar/src/network/forks.ts b/packages/lodestar/src/network/forks.ts index 26ad1f0673e6..460f2fc2ba2a 100644 --- a/packages/lodestar/src/network/forks.ts +++ b/packages/lodestar/src/network/forks.ts @@ -78,6 +78,7 @@ export function getCurrentAndNextFork( config: IBeaconConfig, epoch: Epoch ): {currentFork: IForkInfo; nextFork: IForkInfo | undefined} { + if (epoch < 0) epoch = 0; // NOTE: forks are sorted by ascending epoch, phase0 first const forks = Object.values(config.forks); let currentForkIdx = -1; diff --git a/packages/lodestar/src/network/gossip/topic.ts b/packages/lodestar/src/network/gossip/topic.ts index faf68155cf45..de0e8b85fac4 100644 --- a/packages/lodestar/src/network/gossip/topic.ts +++ b/packages/lodestar/src/network/gossip/topic.ts @@ -105,7 +105,7 @@ export function getGossipSSZType(config: IBeaconConfig, case GossipType.voluntary_exit: return (config.types[topic.fork].SignedVoluntaryExit as unknown) as ContainerType; case GossipType.sync_committee_contribution_and_proof: - return (config.types.altair.SignedAggregateAndProof as unknown) as ContainerType; + return (config.types.altair.SignedContributionAndProof as unknown) as ContainerType; case GossipType.sync_committee: return (config.types.altair.SyncCommitteeSignature as unknown) as ContainerType; default: diff --git a/packages/lodestar/src/network/gossip/validatorFns/block.ts b/packages/lodestar/src/network/gossip/validatorFns/block.ts index b18c77457954..baba7d1ac7f7 100644 --- a/packages/lodestar/src/network/gossip/validatorFns/block.ts +++ b/packages/lodestar/src/network/gossip/validatorFns/block.ts @@ -20,7 +20,7 @@ export async function validateBeaconBlock( validProposerSignature: false, }); logger.debug("gossip - Block - accept", { - root: toHexString(config.types.phase0.BeaconBlock.hashTreeRoot(signedBlock.message)), + root: toHexString(config.getTypes(signedBlock.message.slot).BeaconBlock.hashTreeRoot(signedBlock.message)), slot: signedBlock.message.slot, }); } catch (e) { diff --git a/packages/lodestar/src/network/network.ts b/packages/lodestar/src/network/network.ts index 0d5b8f5d85d8..aaa95577f1dc 100644 --- a/packages/lodestar/src/network/network.ts +++ b/packages/lodestar/src/network/network.ts @@ -121,6 +121,8 @@ export class Network implements INetwork { this.metadata.start(this.getEnr()); this.peerManager.start(); this.gossip.start(); + this.attnetsService.start(); + this.syncnetsService.start(); const multiaddresses = this.libp2p.multiaddrs.map((m) => m.toString()).join(","); this.logger.info(`PeerId ${this.libp2p.peerId.toB58String()}, Multiaddrs ${multiaddresses}`); } @@ -133,6 +135,8 @@ export class Network implements INetwork { this.metadata.stop(); this.gossip.stop(); this.reqResp.stop(); + this.attnetsService.stop(); + this.syncnetsService.stop(); this.gossip.stop(); await this.libp2p.stop(); } diff --git a/packages/lodestar/src/network/reqresp/handlers/beaconBlocksByRange.ts b/packages/lodestar/src/network/reqresp/handlers/beaconBlocksByRange.ts index 1e1764ed671e..d1d50b902b12 100644 --- a/packages/lodestar/src/network/reqresp/handlers/beaconBlocksByRange.ts +++ b/packages/lodestar/src/network/reqresp/handlers/beaconBlocksByRange.ts @@ -37,10 +37,10 @@ export async function* onBeaconBlocksByRange( } export async function* injectRecentBlocks( - archiveStream: AsyncIterable, + archiveStream: AsyncIterable, chain: IBeaconChain, request: phase0.BeaconBlocksByRangeRequest -): AsyncGenerator { +): AsyncGenerator { let slot = -1; for await (const archiveBlock of archiveStream) { yield archiveBlock; diff --git a/packages/lodestar/src/network/subnetsService.ts b/packages/lodestar/src/network/subnetsService.ts index 6bbc605bd155..6c8bb03c1efc 100644 --- a/packages/lodestar/src/network/subnetsService.ts +++ b/packages/lodestar/src/network/subnetsService.ts @@ -25,6 +25,8 @@ export type CommitteeSubscription = { }; export interface ISubnetsService { + start(): void; + stop(): void; addCommitteeSubscriptions(subscriptions: CommitteeSubscription[]): void; shouldProcess(subnet: number, slot: phase0.Slot): boolean; getActiveSubnets(): number[]; diff --git a/packages/lodestar/src/sync/range/batch.ts b/packages/lodestar/src/sync/range/batch.ts index 9fe90f9b6260..4af57c5efb82 100644 --- a/packages/lodestar/src/sync/range/batch.ts +++ b/packages/lodestar/src/sync/range/batch.ts @@ -1,5 +1,5 @@ import PeerId from "peer-id"; -import {Epoch, phase0} from "@chainsafe/lodestar-types"; +import {allForks, Epoch, phase0} from "@chainsafe/lodestar-types"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {LodestarError} from "@chainsafe/lodestar-utils"; import {computeStartSlotAtEpoch} from "@chainsafe/lodestar-beacon-state-transition"; @@ -43,7 +43,7 @@ export type Attempt = { export type BatchState = | {status: BatchStatus.AwaitingDownload} | {status: BatchStatus.Downloading; peer: PeerId} - | {status: BatchStatus.AwaitingProcessing; peer: PeerId; blocks: phase0.SignedBeaconBlock[]} + | {status: BatchStatus.AwaitingProcessing; peer: PeerId; blocks: allForks.SignedBeaconBlock[]} | {status: BatchStatus.Processing; attempt: Attempt} | {status: BatchStatus.AwaitingValidation; attempt: Attempt}; @@ -112,7 +112,7 @@ export class Batch { /** * Downloading -> AwaitingProcessing */ - downloadingSuccess(blocks: phase0.SignedBeaconBlock[]): void { + downloadingSuccess(blocks: allForks.SignedBeaconBlock[]): void { if (this.state.status !== BatchStatus.Downloading) { throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading)); } @@ -139,7 +139,7 @@ export class Batch { /** * AwaitingProcessing -> Processing */ - startProcessing(): phase0.SignedBeaconBlock[] { + startProcessing(): allForks.SignedBeaconBlock[] { if (this.state.status !== BatchStatus.AwaitingProcessing) { throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingProcessing)); } diff --git a/packages/lodestar/src/sync/range/chain.ts b/packages/lodestar/src/sync/range/chain.ts index 03014530c47f..59a4daa48111 100644 --- a/packages/lodestar/src/sync/range/chain.ts +++ b/packages/lodestar/src/sync/range/chain.ts @@ -1,5 +1,5 @@ import PeerId from "peer-id"; -import {Epoch, Root, Slot, phase0} from "@chainsafe/lodestar-types"; +import {Epoch, Root, Slot, phase0, allForks} from "@chainsafe/lodestar-types"; import {computeStartSlotAtEpoch} from "@chainsafe/lodestar-beacon-state-transition"; import {ErrorAborted, ILogger} from "@chainsafe/lodestar-utils"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; @@ -35,12 +35,12 @@ export type SyncChainFns = { * Must return if ALL blocks are processed successfully * If SOME blocks are processed must throw BlockProcessorError() */ - processChainSegment: (blocks: phase0.SignedBeaconBlock[]) => Promise; + processChainSegment: (blocks: allForks.SignedBeaconBlock[]) => Promise; /** Must download blocks, and validate their range */ downloadBeaconBlocksByRange: ( peer: PeerId, request: phase0.BeaconBlocksByRangeRequest - ) => Promise; + ) => Promise; /** Report peer for negative actions. Decouples from the full network instance */ reportPeer: (peer: PeerId, action: PeerAction, actionName: string) => void; /** Hook called when Chain state completes */ diff --git a/packages/lodestar/src/sync/range/utils/hashBlocks.ts b/packages/lodestar/src/sync/range/utils/hashBlocks.ts index 889ef5a79dc1..bde62bcf9803 100644 --- a/packages/lodestar/src/sync/range/utils/hashBlocks.ts +++ b/packages/lodestar/src/sync/range/utils/hashBlocks.ts @@ -1,5 +1,5 @@ import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks} from "@chainsafe/lodestar-types"; import {byteArrayConcat} from "../../../util/bytes"; /** @@ -7,6 +7,8 @@ import {byteArrayConcat} from "../../../util/bytes"; * @param blocks * @param config */ -export function hashBlocks(blocks: phase0.SignedBeaconBlock[], config: IBeaconConfig): Uint8Array { - return byteArrayConcat(blocks.map((block) => config.types.phase0.SignedBeaconBlock.hashTreeRoot(block))); +export function hashBlocks(blocks: allForks.SignedBeaconBlock[], config: IBeaconConfig): Uint8Array { + return byteArrayConcat( + blocks.map((block) => config.getTypes(block.message.slot).SignedBeaconBlock.hashTreeRoot(block)) + ); } diff --git a/packages/lodestar/src/sync/sync.ts b/packages/lodestar/src/sync/sync.ts index 864180a1a981..2dd8c111e557 100644 --- a/packages/lodestar/src/sync/sync.ts +++ b/packages/lodestar/src/sync/sync.ts @@ -197,7 +197,8 @@ export class BeaconSync implements IBeaconSync { private onUnknownBlockRoot = async (err: BlockError): Promise => { if (err.type.code !== BlockErrorCode.PARENT_UNKNOWN) return; - const blockRoot = this.config.types.phase0.BeaconBlock.hashTreeRoot(err.job.signedBlock.message); + const block = err.job.signedBlock.message; + const blockRoot = this.config.getTypes(block.slot).BeaconBlock.hashTreeRoot(block); const parentRoot = this.chain.pendingBlocks.getMissingAncestor(blockRoot); const parentRootHex = toHexString(parentRoot); diff --git a/packages/lodestar/src/sync/utils/unknownRoot.ts b/packages/lodestar/src/sync/utils/unknownRoot.ts index 9826823ca059..f4bed87e1ec2 100644 --- a/packages/lodestar/src/sync/utils/unknownRoot.ts +++ b/packages/lodestar/src/sync/utils/unknownRoot.ts @@ -1,4 +1,4 @@ -import {Root, phase0} from "@chainsafe/lodestar-types"; +import {Root, allForks} from "@chainsafe/lodestar-types"; import {ILogger} from "@chainsafe/lodestar-utils"; import {List, toHexString} from "@chainsafe/ssz"; import {INetwork} from "../../network"; @@ -8,7 +8,7 @@ export async function fetchUnknownBlockRoot( unknownAncestorRoot: Root, network: INetwork, logger: ILogger -): Promise { +): Promise { const connectedPeers = shuffle(network.getConnectedPeers()); const parentRootHex = toHexString(unknownAncestorRoot); diff --git a/packages/lodestar/src/util/multifork.ts b/packages/lodestar/src/util/multifork.ts index 1ed8707db003..fb68fa514d29 100644 --- a/packages/lodestar/src/util/multifork.ts +++ b/packages/lodestar/src/util/multifork.ts @@ -1,5 +1,5 @@ -import {ForkName, IBeaconConfig} from "@chainsafe/lodestar-config"; -import {allForks, Slot} from "@chainsafe/lodestar-types"; +import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {allForks} from "@chainsafe/lodestar-types"; import {bytesToInt} from "@chainsafe/lodestar-utils"; import {ContainerType} from "@chainsafe/ssz"; @@ -35,61 +35,18 @@ const SLOT_BYTES_POSITION_IN_BLOCK = 100; */ const SLOT_BYTES_POSITION_IN_STATE = 40; -type BlockType = ContainerType; -type SignedBlockType = ContainerType; -type StateType = ContainerType; - -// Block - -export function getBlockType(config: IBeaconConfig, block: allForks.BeaconBlock): BlockType { - return getBlockTypeFromSlot(config, block.slot); -} - -function getBlockTypeFromSlot(config: IBeaconConfig, slot: Slot): BlockType { - switch (config.getForkName(slot)) { - case ForkName.phase0: - return (config.types.phase0.BeaconBlock as unknown) as BlockType; - case ForkName.altair: - return (config.types.altair.BeaconBlock as unknown) as BlockType; - } -} - -// SignedBlock - -export function getSignedBlockType(config: IBeaconConfig, block: allForks.SignedBeaconBlock): SignedBlockType { - return getSignedBlockTypeFromSlot(config, block.message.slot); -} - -export function getSignedBlockTypeFromBytes(config: IBeaconConfig, bytes: Buffer): SignedBlockType { +export function getSignedBlockTypeFromBytes( + config: IBeaconConfig, + bytes: Buffer | Uint8Array +): ContainerType { const slot = bytesToInt(bytes.slice(SLOT_BYTES_POSITION_IN_BLOCK, SLOT_BYTES_POSITION_IN_BLOCK + SLOT_BYTE_COUNT)); - return getSignedBlockTypeFromSlot(config, slot); -} - -function getSignedBlockTypeFromSlot(config: IBeaconConfig, slot: Slot): SignedBlockType { - switch (config.getForkName(slot)) { - case ForkName.phase0: - return (config.types.phase0.SignedBeaconBlock as unknown) as SignedBlockType; - case ForkName.altair: - return (config.types.altair.SignedBeaconBlock as unknown) as SignedBlockType; - } -} - -// State - -export function getStateTypeFromState(config: IBeaconConfig, state: allForks.BeaconState): StateType { - return getStateTypeFromSlot(config, state.slot); + return config.getTypes(slot).SignedBeaconBlock; } -export function getStateTypeFromBytes(config: IBeaconConfig, bytes: Buffer): StateType { +export function getStateTypeFromBytes( + config: IBeaconConfig, + bytes: Buffer | Uint8Array +): ContainerType { const slot = bytesToInt(bytes.slice(SLOT_BYTES_POSITION_IN_STATE, SLOT_BYTES_POSITION_IN_STATE + SLOT_BYTE_COUNT)); - return getStateTypeFromSlot(config, slot); -} - -function getStateTypeFromSlot(config: IBeaconConfig, slot: Slot): StateType { - switch (config.getForkName(slot)) { - case ForkName.phase0: - return (config.types.phase0.BeaconState as unknown) as StateType; - case ForkName.altair: - return (config.types.altair.BeaconState as unknown) as StateType; - } + return config.getTypes(slot).BeaconState; } diff --git a/packages/lodestar/test/e2e/db/api/beacon/repositories/blockArchive.test.ts b/packages/lodestar/test/e2e/db/api/beacon/repositories/blockArchive.test.ts index ea39ec34c55c..c3968b81389e 100644 --- a/packages/lodestar/test/e2e/db/api/beacon/repositories/blockArchive.test.ts +++ b/packages/lodestar/test/e2e/db/api/beacon/repositories/blockArchive.test.ts @@ -5,7 +5,7 @@ import {generateSignedBlock} from "../../../../../utils/block"; import {testLogger} from "../../../../../utils/logger"; import {fromHexString} from "@chainsafe/ssz"; import {IBlockSummary} from "@chainsafe/lodestar-fork-choice"; -import {phase0} from "@chainsafe/lodestar-types"; +import {allForks, phase0} from "@chainsafe/lodestar-types"; import {expect} from "chai"; describe("BlockArchiveRepository", function () { @@ -108,9 +108,9 @@ function notNull(arr: (T | null)[]): T[] { return arr as T[]; } -const toBlockSummary = (signedBlock: phase0.SignedBeaconBlock): IBlockSummary => { +const toBlockSummary = (signedBlock: allForks.SignedBeaconBlock): IBlockSummary => { return { - blockRoot: config.types.phase0.BeaconBlock.hashTreeRoot(signedBlock.message), + blockRoot: config.getTypes(signedBlock.message.slot).BeaconBlock.hashTreeRoot(signedBlock.message), finalizedEpoch: 0, justifiedEpoch: 0, parentRoot: signedBlock.message.parentRoot as Uint8Array, diff --git a/packages/lodestar/test/e2e/network/peers/peerManager.test.ts b/packages/lodestar/test/e2e/network/peers/peerManager.test.ts index ace9adb30f06..bf8d9db04e1a 100644 --- a/packages/lodestar/test/e2e/network/peers/peerManager.test.ts +++ b/packages/lodestar/test/e2e/network/peers/peerManager.test.ts @@ -65,6 +65,10 @@ describe("network / peers / PeerManager", function () { shouldProcess: () => true, // eslint-disable-next-line @typescript-eslint/no-empty-function addCommitteeSubscriptions: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + start: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + stop: () => {}, }; const peerManager = new PeerManager( diff --git a/packages/lodestar/test/sim/singleNodeSingleThread.test.ts b/packages/lodestar/test/sim/singleNodeSingleThread.test.ts index c19382394c10..d47dcffb0620 100644 --- a/packages/lodestar/test/sim/singleNodeSingleThread.test.ts +++ b/packages/lodestar/test/sim/singleNodeSingleThread.test.ts @@ -9,6 +9,7 @@ import {testLogger, TestLoggerOpts, LogLevel} from "../utils/logger"; import {logFiles} from "./params"; import {simTestInfoTracker} from "../utils/node/simTest"; import {sleep, TimestampFormatCode} from "@chainsafe/lodestar-utils"; +import {initBLS} from "@chainsafe/lodestar-cli/src/util"; /* eslint-disable no-console, @typescript-eslint/naming-convention */ @@ -24,15 +25,52 @@ describe("Run single node single thread interop validators (no eth1) until check TARGET_AGGREGATORS_PER_COMMITTEE: 4, }; + before(async function () { + await initBLS(); + }); + const testCases: { validatorClientCount: number; validatorsPerClient: number; event: ChainEvent.justified | ChainEvent.finalized; params: Partial; }[] = [ - {validatorClientCount: 1, validatorsPerClient: 32, event: ChainEvent.justified, params: manyValidatorParams}, - {validatorClientCount: 8, validatorsPerClient: 8, event: ChainEvent.justified, params: testParams}, - {validatorClientCount: 8, validatorsPerClient: 8, event: ChainEvent.finalized, params: testParams}, + { + validatorClientCount: 1, + validatorsPerClient: 32, + event: ChainEvent.justified, + params: {...manyValidatorParams, ALTAIR_FORK_EPOCH: 0}, + }, + { + validatorClientCount: 1, + validatorsPerClient: 32, + event: ChainEvent.justified, + params: {...manyValidatorParams, ALTAIR_FORK_EPOCH: 1}, + }, + { + validatorClientCount: 8, + validatorsPerClient: 8, + event: ChainEvent.justified, + params: {...testParams, ALTAIR_FORK_EPOCH: 0}, + }, + { + validatorClientCount: 8, + validatorsPerClient: 8, + event: ChainEvent.justified, + params: {...testParams, ALTAIR_FORK_EPOCH: 1}, + }, + { + validatorClientCount: 8, + validatorsPerClient: 8, + event: ChainEvent.finalized, + params: {...testParams, ALTAIR_FORK_EPOCH: 0}, + }, + { + validatorClientCount: 8, + validatorsPerClient: 8, + event: ChainEvent.finalized, + params: {...testParams, ALTAIR_FORK_EPOCH: 1}, + }, ]; for (const testCase of testCases) { diff --git a/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts b/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts index 731f6bc62795..f98752961274 100644 --- a/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts +++ b/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts @@ -2,7 +2,7 @@ import sinon, {SinonStubbedInstance} from "sinon"; import {expect} from "chai"; import {config} from "@chainsafe/lodestar-config/minimal"; -import * as processBlock from "@chainsafe/lodestar-beacon-state-transition/lib/phase0/block"; +import * as processBlock from "@chainsafe/lodestar-beacon-state-transition/lib/allForks/stateTransition"; import {ForkChoice} from "@chainsafe/lodestar-fork-choice"; import {BeaconChain} from "../../../../../src/chain"; diff --git a/packages/lodestar/test/unit/network/attestationService.test.ts b/packages/lodestar/test/unit/network/attestationService.test.ts index 8be84e90dfd8..9193ea2db07a 100644 --- a/packages/lodestar/test/unit/network/attestationService.test.ts +++ b/packages/lodestar/test/unit/network/attestationService.test.ts @@ -153,7 +153,7 @@ describe("AttestationService", function () { // Advance through the fork transition so it un-subscribes from all phase0 subs - while (chain.clock.currentSlot * SLOTS_PER_EPOCH < altairEpoch + EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION) { + while (chain.clock.currentSlot < (altairEpoch + EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION) * SLOTS_PER_EPOCH) { service.addCommitteeSubscriptions([subscription]); sandbox.clock.tick(SLOTS_PER_EPOCH * SECONDS_PER_SLOT * 1000); } diff --git a/packages/lodestar/test/unit/network/fork.test.ts b/packages/lodestar/test/unit/network/fork.test.ts new file mode 100644 index 000000000000..9be812a519e5 --- /dev/null +++ b/packages/lodestar/test/unit/network/fork.test.ts @@ -0,0 +1,31 @@ +import {expect} from "chai"; +import {ForkName, IBeaconConfig, IForkInfo} from "@chainsafe/lodestar-config"; +import {getCurrentAndNextFork} from "../../../src/network/forks"; + +describe("network / fork", () => { + const forks: Record = { + phase0: { + name: ForkName.phase0, + epoch: 0, + version: Buffer.from([0, 0, 0, 0]), + }, + altair: { + name: ForkName.altair, + epoch: 0, + version: Buffer.from([0, 0, 0, 1]), + }, + }; + const altairEpoch0 = {forks} as IBeaconConfig; + it("should return altair on epoch -1", () => { + expect(getCurrentAndNextFork(altairEpoch0, -1)).to.deep.equal({ + currentFork: forks[ForkName.altair], + nextFork: undefined, + }); + }); + it("should return altair on epoch 0", () => { + expect(getCurrentAndNextFork(altairEpoch0, 0)).to.deep.equal({ + currentFork: forks[ForkName.altair], + nextFork: undefined, + }); + }); +}); diff --git a/packages/spec-test-runner/test/spec/altair/sanity/slots/minimal.test.ts b/packages/spec-test-runner/test/spec/altair/sanity/slots/minimal.test.ts index 35eed8c80180..3b1abcc9e43b 100644 --- a/packages/spec-test-runner/test/spec/altair/sanity/slots/minimal.test.ts +++ b/packages/spec-test-runner/test/spec/altair/sanity/slots/minimal.test.ts @@ -19,8 +19,11 @@ describeDirectorySpecTest( config, (testcase.pre as TreeBacked).clone() ); - altair.processSlots(wrappedState, wrappedState.slot + Number(testcase.slots)); - return wrappedState; + const postState = allForks.processSlots( + wrappedState as allForks.CachedBeaconState, + wrappedState.slot + Number(testcase.slots) + ); + return postState.type.createTreeBacked(postState.tree) as altair.BeaconState; }, { inputTypes: { diff --git a/packages/spec-test-runner/test/spec/phase0/sanity/slots/sanity_slots_mainnet.test.ts b/packages/spec-test-runner/test/spec/phase0/sanity/slots/sanity_slots_mainnet.test.ts index 9eb826c32b47..aca16b2fb9f9 100644 --- a/packages/spec-test-runner/test/spec/phase0/sanity/slots/sanity_slots_mainnet.test.ts +++ b/packages/spec-test-runner/test/spec/phase0/sanity/slots/sanity_slots_mainnet.test.ts @@ -17,8 +17,11 @@ describeDirectorySpecTest( config, testcase.pre as TreeBacked ); - phase0.processSlots(wrappedState, wrappedState.slot + Number(testcase.slots)); - return wrappedState; + const postState = allForks.processSlots( + wrappedState as allForks.CachedBeaconState, + wrappedState.slot + Number(testcase.slots) + ); + return postState.type.createTreeBacked(postState.tree) as phase0.BeaconState; }, { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/validator/src/api/interface.ts b/packages/validator/src/api/interface.ts index a358c15404ff..06b92841fccc 100644 --- a/packages/validator/src/api/interface.ts +++ b/packages/validator/src/api/interface.ts @@ -1,14 +1,15 @@ import {EventEmitter} from "events"; import StrictEventEmitter from "strict-event-emitter-types"; import { + allForks, + phase0, + altair, Epoch, Slot, Root, - phase0, ValidatorIndex, BLSSignature, CommitteeIndex, - altair, } from "@chainsafe/lodestar-types"; import {IStoppableEventIterable} from "@chainsafe/lodestar-utils"; import {IValidatorFilters} from "../util"; @@ -43,7 +44,7 @@ export interface IApiClient { }; blocks: { getBlockRoot(blockId: BlockId): Promise; - publishBlock(block: phase0.SignedBeaconBlock): Promise; + publishBlock(block: allForks.SignedBeaconBlock): Promise; }; pool: { submitAttestations(attestation: phase0.Attestation[]): Promise; @@ -70,7 +71,7 @@ export interface IApiClient { getProposerDuties(epoch: Epoch): Promise; getAttesterDuties(epoch: Epoch, validatorIndices: ValidatorIndex[]): Promise; getSyncCommitteeDuties(epoch: number, validatorIndices: ValidatorIndex[]): Promise; - produceBlock(slot: Slot, randaoReveal: BLSSignature, graffiti: string): Promise; + produceBlock(slot: Slot, randaoReveal: BLSSignature, graffiti: string): Promise; produceAttestationData(index: CommitteeIndex, slot: Slot): Promise; produceSyncCommitteeContribution( slot: Slot, diff --git a/packages/validator/src/api/rest/beacon.ts b/packages/validator/src/api/rest/beacon.ts index 47dbf12651fb..bc1ae15a3523 100644 --- a/packages/validator/src/api/rest/beacon.ts +++ b/packages/validator/src/api/rest/beacon.ts @@ -1,6 +1,6 @@ -import {ForkName, IBeaconConfig} from "@chainsafe/lodestar-config"; -import {allForks, altair, BLSPubkey, phase0, Root, Slot, ValidatorIndex} from "@chainsafe/lodestar-types"; -import {ContainerType, Json, toHexString} from "@chainsafe/ssz"; +import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {allForks, altair, BLSPubkey, phase0, Root, ValidatorIndex} from "@chainsafe/lodestar-types"; +import {Json, toHexString} from "@chainsafe/ssz"; import {HttpClient, IValidatorFilters} from "../../util"; import {BlockId, IApiClient} from "../interface"; @@ -31,12 +31,15 @@ export function BeaconApi(config: IBeaconConfig, client: HttpClient): IApiClient blocks: { async publishBlock(block: allForks.SignedBeaconBlock): Promise { - await client.post("/eth/v1/beacon/blocks", getSignedBlockType(config, block).toJson(block, {case: "snake"})); + await client.post( + "/eth/v1/beacon/blocks", + config.getTypes(block.message.slot).SignedBeaconBlock.toJson(block, {case: "snake"}) + ); }, async getBlockRoot(blockId: BlockId): Promise { - const res = await client.get<{data: Json}>(`/eth/v1/beacon/blocks/${blockId}/root`); - return config.types.phase0.Root.fromJson(res.data, {case: "snake"}); + const res = await client.get<{data: {root: Json}}>(`/eth/v1/beacon/blocks/${blockId}/root`); + return config.types.phase0.Root.fromJson(res.data.root, {case: "snake"}); }, }, @@ -74,20 +77,3 @@ function formatIndex(validatorId: ValidatorIndex | BLSPubkey): string { return toHexString(validatorId); } } - -// TODO: Consider de-duplicating this code that also exists in `packages/lodestar/src/util/multifork.ts` - -type SignedBlockType = ContainerType; - -function getSignedBlockType(config: IBeaconConfig, block: allForks.SignedBeaconBlock): SignedBlockType { - return getSignedBlockTypeFromSlot(config, block.message.slot); -} - -function getSignedBlockTypeFromSlot(config: IBeaconConfig, slot: Slot): SignedBlockType { - switch (config.getForkName(slot)) { - case ForkName.phase0: - return (config.types.phase0.SignedBeaconBlock as unknown) as SignedBlockType; - case ForkName.altair: - return (config.types.altair.SignedBeaconBlock as unknown) as SignedBlockType; - } -} diff --git a/packages/validator/src/api/rest/index.ts b/packages/validator/src/api/rest/index.ts index d259fe636d29..c3bcd7d7ff2c 100644 --- a/packages/validator/src/api/rest/index.ts +++ b/packages/validator/src/api/rest/index.ts @@ -20,7 +20,7 @@ export function ApiClientOverRest(config: IBeaconConfig, baseUrl: string): IApiC config: ConfigApi(config.types, client), node: NodeApi(config.types, client), events: EventsApi(config.types, client), - validator: ValidatorApi(config.types, client), + validator: ValidatorApi(config, client), url: baseUrl, registerAbortSignal(signal: AbortSignal) { diff --git a/packages/validator/src/api/rest/validator.ts b/packages/validator/src/api/rest/validator.ts index 8a575ad7e0f7..2d076a9f5b12 100644 --- a/packages/validator/src/api/rest/validator.ts +++ b/packages/validator/src/api/rest/validator.ts @@ -1,50 +1,42 @@ -import { - CommitteeIndex, - Epoch, - Root, - phase0, - Slot, - ValidatorIndex, - IBeaconSSZTypes, - altair, -} from "@chainsafe/lodestar-types"; +import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {allForks, phase0, altair, CommitteeIndex, Epoch, Root, Slot, ValidatorIndex} from "@chainsafe/lodestar-types"; import {Json, toHexString} from "@chainsafe/ssz"; import {HttpClient} from "../../util"; import {IApiClient} from "../interface"; /* eslint-disable @typescript-eslint/naming-convention */ -export function ValidatorApi(types: IBeaconSSZTypes, client: HttpClient): IApiClient["validator"] { +export function ValidatorApi(config: IBeaconConfig, client: HttpClient): IApiClient["validator"] { return { async getProposerDuties(epoch: Epoch): Promise { const res = await client.get<{data: Json[]; dependentRoot: string}>( `/eth/v1/validator/duties/proposer/${epoch.toString()}` ); - return types.phase0.ProposerDutiesApi.fromJson(res, {case: "snake"}); + return config.types.phase0.ProposerDutiesApi.fromJson(res, {case: "snake"}); }, async getAttesterDuties(epoch: Epoch, indices: ValidatorIndex[]): Promise { const res = await client.post( `/eth/v1/validator/duties/attester/${epoch.toString()}`, - indices.map((index) => types.ValidatorIndex.toJson(index) as string) + indices.map((index) => config.types.ValidatorIndex.toJson(index) as string) ); - return types.phase0.AttesterDutiesApi.fromJson(res, {case: "snake"}); + return config.types.phase0.AttesterDutiesApi.fromJson(res, {case: "snake"}); }, async getSyncCommitteeDuties(epoch: number, indices: ValidatorIndex[]): Promise { const res = await client.post( `/eth/v1/validator/duties/sync/${epoch.toString()}`, - indices.map((index) => types.ValidatorIndex.toJson(index) as string) + indices.map((index) => config.types.ValidatorIndex.toJson(index) as string) ); - return types.altair.SyncDutiesApi.fromJson(res, {case: "snake"}); + return config.types.altair.SyncDutiesApi.fromJson(res, {case: "snake"}); }, - async produceBlock(slot: Slot, randaoReveal: Uint8Array, graffiti: string): Promise { + async produceBlock(slot: Slot, randaoReveal: Uint8Array, graffiti: string): Promise { const res = await client.get<{data: Json}>(`/eth/v1/validator/blocks/${slot}`, { randao_reveal: toHexString(randaoReveal), graffiti, }); - return types.phase0.BeaconBlock.fromJson(res.data, {case: "snake"}); + return config.getTypes(slot).BeaconBlock.fromJson(res.data, {case: "snake"}); }, async produceAttestationData(index: CommitteeIndex, slot: Slot): Promise { @@ -52,7 +44,7 @@ export function ValidatorApi(types: IBeaconSSZTypes, client: HttpClient): IApiCl committee_index: index, slot, }); - return types.phase0.AttestationData.fromJson(res.data, {case: "snake"}); + return config.types.phase0.AttestationData.fromJson(res.data, {case: "snake"}); }, async produceSyncCommitteeContribution( @@ -65,42 +57,44 @@ export function ValidatorApi(types: IBeaconSSZTypes, client: HttpClient): IApiCl slot, beacon_block_root: toHexString(beaconBlockRoot), }); - return types.altair.SyncCommitteeContribution.fromJson(res.data, {case: "snake"}); + return config.types.altair.SyncCommitteeContribution.fromJson(res.data, {case: "snake"}); }, async getAggregatedAttestation(attestationDataRoot: Root, slot: Slot): Promise { const res = await client.get<{data: Json[]}>("/eth/v1/validator/aggregate_attestation", { - attestation_data_root: types.Root.toJson(attestationDataRoot) as string, + attestation_data_root: config.types.Root.toJson(attestationDataRoot) as string, slot, }); - return types.phase0.Attestation.fromJson(res.data, {case: "snake"}); + return config.types.phase0.Attestation.fromJson(res.data, {case: "snake"}); }, async publishAggregateAndProofs(signedAggregateAndProofs: phase0.SignedAggregateAndProof[]): Promise { await client.post( "/eth/v1/validator/aggregate_and_proofs", - signedAggregateAndProofs.map((a) => types.phase0.SignedAggregateAndProof.toJson(a, {case: "snake"})) + signedAggregateAndProofs.map((a) => config.types.phase0.SignedAggregateAndProof.toJson(a, {case: "snake"})) ); }, async publishContributionAndProofs(contributionAndProofs: altair.SignedContributionAndProof[]): Promise { await client.post( "/eth/v1/validator/contribution_and_proofs", - contributionAndProofs.map((item) => types.altair.SignedContributionAndProof.toJson(item, {case: "snake"})) + contributionAndProofs.map((item) => + config.types.altair.SignedContributionAndProof.toJson(item, {case: "snake"}) + ) ); }, async prepareBeaconCommitteeSubnet(subscriptions: phase0.BeaconCommitteeSubscription[]): Promise { await client.post( "/eth/v1/validator/beacon_committee_subscriptions", - subscriptions.map((s) => types.phase0.BeaconCommitteeSubscription.toJson(s, {case: "snake"})) + subscriptions.map((s) => config.types.phase0.BeaconCommitteeSubscription.toJson(s, {case: "snake"})) ); }, async prepareSyncCommitteeSubnets(subscriptions: altair.SyncCommitteeSubscription[]): Promise { await client.post( "/eth/v1/validator/sync_committee_subscriptions", - subscriptions.map((item) => types.altair.SyncCommitteeSubscription.toJson(item, {case: "snake"})) + subscriptions.map((item) => config.types.altair.SyncCommitteeSubscription.toJson(item, {case: "snake"})) ); }, }; diff --git a/packages/validator/src/services/attestationDuties.ts b/packages/validator/src/services/attestationDuties.ts index 056f9f0aaec6..f6e7783b796b 100644 --- a/packages/validator/src/services/attestationDuties.ts +++ b/packages/validator/src/services/attestationDuties.ts @@ -190,7 +190,7 @@ export class AttestationDutiesService { } private async getDutyAndProof(duty: phase0.AttesterDuty): Promise { - const selectionProof = await this.validatorStore.signSelectionProof(duty.pubkey, duty.slot); + const selectionProof = await this.validatorStore.signAttestationSelectionProof(duty.pubkey, duty.slot); const isAggregator = isAttestationAggregator(this.config, duty, selectionProof); return { diff --git a/packages/validator/src/services/indices.ts b/packages/validator/src/services/indices.ts index a4158a7fa858..43c488e978bb 100644 --- a/packages/validator/src/services/indices.ts +++ b/packages/validator/src/services/indices.ts @@ -8,9 +8,9 @@ import {ValidatorStore} from "./validatorStore"; type PubkeyHex = string; export class IndicesService { + readonly index2pubkey = new Map(); /** Indexed by pubkey in hex 0x prefixed */ - private readonly pubkey2index = new Map(); - private readonly indices = new Set(); + readonly pubkey2index = new Map(); // Request indices once private pollValidatorIndicesPromise: Promise | null = null; @@ -22,12 +22,12 @@ export class IndicesService { /** Return all known indices from the validatorStore pubkeys */ getAllLocalIndices(): ValidatorIndex[] { - return Array.from(this.indices.values()); + return Array.from(this.index2pubkey.keys()); } /** Return true if `index` is active part of this validator client */ hasValidatorIndex(index: ValidatorIndex): boolean { - return this.indices.has(index); + return this.index2pubkey.has(index); } pollValidatorIndices(): Promise { @@ -66,7 +66,7 @@ export class IndicesService { if (!this.pubkey2index.has(pubkeyHex)) { this.logger.debug("Discovered validator", {pubkey: pubkeyHex, index: validatorState.index}); this.pubkey2index.set(pubkeyHex, validatorState.index); - this.indices.add(validatorState.index); + this.index2pubkey.set(validatorState.index, pubkeyHex); newIndices.push(validatorState.index); } } diff --git a/packages/validator/src/services/syncCommittee.ts b/packages/validator/src/services/syncCommittee.ts index 108f07434858..ea4dd30e4b7c 100644 --- a/packages/validator/src/services/syncCommittee.ts +++ b/packages/validator/src/services/syncCommittee.ts @@ -45,7 +45,7 @@ export class SyncCommitteeService { // Fetch info first so a potential delay is absorved by the sleep() below const dutiesAtSlot = await this.dutiesService.getDutiesAtSlot(slot); - const dutiesBySubcommitteeIndex = groupSyncDutiesBySubCommitteeIndex(this.config, dutiesAtSlot); + const dutiesBySubcommitteeIndex = groupSyncDutiesBySubCommitteeIndex(dutiesAtSlot); if (dutiesAtSlot.length === 0) { return; } diff --git a/packages/validator/src/services/syncCommitteeDuties.ts b/packages/validator/src/services/syncCommitteeDuties.ts index 9c71185035f0..bf39312d498c 100644 --- a/packages/validator/src/services/syncCommitteeDuties.ts +++ b/packages/validator/src/services/syncCommitteeDuties.ts @@ -8,6 +8,7 @@ import {IApiClient} from "../api"; import {extendError, isSyncCommitteeAggregator, notAborted} from "../util"; import {IClock} from "../util/clock"; import {ValidatorStore} from "./validatorStore"; +import {SYNC_COMMITTEE_SUBNET_COUNT} from "@chainsafe/lodestar-params"; /** Only retain `HISTORICAL_DUTIES_PERIODS` duties prior to the current periods. */ const HISTORICAL_DUTIES_PERIODS = 2; @@ -16,11 +17,19 @@ const ALTAIR_FORK_LOOKAHEAD_EPOCHS = 1; /** How many epochs prior from a subscription starting, ask the node to subscribe */ const SUBSCRIPTIONS_LOOKAHEAD_EPOCHS = 2; +export type SyncDutySubCommittee = { + pubkey: altair.SyncDuty["pubkey"]; + validatorIndex: altair.SyncDuty["validatorIndex"]; + /** A single index of the validator in the sync committee. */ + validatorSyncCommitteeIndex: number; +}; + /** Neatly joins SyncDuty with the locally-generated `selectionProof`. */ export type SyncDutyAndProof = { - duty: altair.SyncDuty; + duty: SyncDutySubCommittee; /** This value is only set to not null if the proof indicates that the validator is an aggregator. */ selectionProof: BLSSignature | null; + subCommitteeIndex: number; }; // To assist with readability @@ -63,8 +72,17 @@ export class SyncCommitteeDutiesService { const dutyAtPeriod = dutiesByPeriod.get(period); // Validator always has a duty during the entire period if (dutyAtPeriod) { - // getDutyAndProof() is async beacuse it may have to fetch the fork but should never happen in practice - duties.push(await this.getDutyAndProof(slot, dutyAtPeriod.duty)); + for (const index of dutyAtPeriod.duty.validatorSyncCommitteeIndices) { + duties.push( + // Compute a different DutyAndProof for each validatorSyncCommitteeIndices. Unwrapping here simplifies downstream code. + // getDutyAndProof() is async beacuse it may have to fetch the fork but should never happen in practice + await this.getDutyAndProof(slot, { + pubkey: dutyAtPeriod.duty.pubkey, + validatorIndex: dutyAtPeriod.duty.validatorIndex, + validatorSyncCommitteeIndex: index, + }) + ); + } } } @@ -201,14 +219,21 @@ export class SyncCommitteeDutiesService { } } - private async getDutyAndProof(slot: Slot, duty: altair.SyncDuty): Promise { - const selectionProof = await this.validatorStore.signSelectionProof(duty.pubkey, slot); - const isAggregator = isSyncCommitteeAggregator(this.config, selectionProof); - + private async getDutyAndProof(slot: Slot, duty: SyncDutySubCommittee): Promise { + // TODO: Cache this value + const SYNC_COMMITTEE_SUBNET_SIZE = Math.floor(this.config.params.SYNC_COMMITTEE_SIZE / SYNC_COMMITTEE_SUBNET_COUNT); + const subCommitteeIndex = Math.floor(duty.validatorSyncCommitteeIndex / SYNC_COMMITTEE_SUBNET_SIZE); + const selectionProof = await this.validatorStore.signSyncCommitteeSelectionProof( + // Fast indexing with precomputed pubkeyHex. Fallback to toHexString(duty.pubkey) + this.indicesService.index2pubkey.get(duty.validatorIndex) ?? duty.pubkey, + slot, + subCommitteeIndex + ); return { duty, // selectionProof === null is used to check if is aggregator - selectionProof: isAggregator ? selectionProof : null, + selectionProof: isSyncCommitteeAggregator(this.config, selectionProof) ? selectionProof : null, + subCommitteeIndex, }; } diff --git a/packages/validator/src/services/utils.ts b/packages/validator/src/services/utils.ts index 930823408f3f..9aab80b60797 100644 --- a/packages/validator/src/services/utils.ts +++ b/packages/validator/src/services/utils.ts @@ -1,6 +1,4 @@ import {SecretKey} from "@chainsafe/bls"; -import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {SYNC_COMMITTEE_SUBNET_COUNT} from "@chainsafe/lodestar-params"; import {CommitteeIndex, SubCommitteeIndex} from "@chainsafe/lodestar-types"; import {toHexString} from "@chainsafe/ssz"; import {PubkeyHex, BLSKeypair} from "../types"; @@ -37,24 +35,18 @@ export function groupAttDutiesByCommitteeIndex(duties: AttDutyAndProof[]): Map { const dutiesBySubCommitteeIndex = new Map(); - // TODO: Cache this value - const SYNC_COMMITTEE_SUBNET_SIZE = Math.floor(config.params.SYNC_COMMITTEE_SIZE / SYNC_COMMITTEE_SUBNET_COUNT); - for (const dutyAndProof of duties) { - for (const committeeIndex of dutyAndProof.duty.validatorSyncCommitteeIndices) { - const subCommitteeIndex = Math.floor(committeeIndex / SYNC_COMMITTEE_SUBNET_SIZE); - let dutyAndProofArr = dutiesBySubCommitteeIndex.get(subCommitteeIndex); - if (!dutyAndProofArr) { - dutyAndProofArr = []; - dutiesBySubCommitteeIndex.set(subCommitteeIndex, dutyAndProofArr); - } - dutyAndProofArr.push(dutyAndProof); + const subCommitteeIndex = dutyAndProof.subCommitteeIndex; + let dutyAndProofArr = dutiesBySubCommitteeIndex.get(subCommitteeIndex); + if (!dutyAndProofArr) { + dutyAndProofArr = []; + dutiesBySubCommitteeIndex.set(subCommitteeIndex, dutyAndProofArr); } + dutyAndProofArr.push(dutyAndProof); } return dutiesBySubCommitteeIndex; diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index 6124edafa003..76c3a3b75c08 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -1,7 +1,22 @@ import {SecretKey} from "@chainsafe/bls"; -import {computeDomain, computeEpochAtSlot, computeSigningRoot} from "@chainsafe/lodestar-beacon-state-transition"; +import { + computeDomain, + computeEpochAtSlot, + computeSigningRoot, + computeStartSlotAtEpoch, +} from "@chainsafe/lodestar-beacon-state-transition"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {altair, BLSPubkey, BLSSignature, DomainType, Epoch, phase0, Root, Slot} from "@chainsafe/lodestar-types"; +import { + allForks, + altair, + BLSPubkey, + BLSSignature, + DomainType, + Epoch, + phase0, + Root, + Slot, +} from "@chainsafe/lodestar-types"; import {Genesis, ValidatorIndex} from "@chainsafe/lodestar-types/phase0"; import {List, toHexString} from "@chainsafe/ssz"; import {ISlashingProtection} from "../slashingProtection"; @@ -41,7 +56,11 @@ export class ValidatorStore { return this.validators.has(pubkeyHex); } - async signBlock(pubkey: BLSPubkey, block: phase0.BeaconBlock, currentSlot: Slot): Promise { + async signBlock( + pubkey: BLSPubkey, + block: allForks.BeaconBlock, + currentSlot: Slot + ): Promise { // Make sure the block slot is not higher than the current slot to avoid potential attacks. if (block.slot > currentSlot) { throw Error(`Not signing block with slot ${block.slot} greater than current slot ${currentSlot}`); @@ -51,7 +70,8 @@ export class ValidatorStore { this.config.params.DOMAIN_BEACON_PROPOSER, computeEpochAtSlot(this.config, block.slot) ); - const signingRoot = computeSigningRoot(this.config, this.config.types.phase0.BeaconBlock, block, proposerDomain); + const blockType = this.config.getTypes(block.slot).BeaconBlock; + const signingRoot = computeSigningRoot(this.config, blockType, block, proposerDomain); const secretKey = this.getSecretKey(pubkey); // Get before writing to slashingProtection await this.slashingProtection.checkAndInsertBlockProposal(pubkey, {slot: block.slot, signingRoot}); @@ -157,7 +177,7 @@ export class ValidatorStore { } async signContributionAndProof( - duty: altair.SyncDuty, + duty: Pick, selectionProof: BLSSignature, contribution: altair.SyncCommitteeContribution ): Promise { @@ -184,7 +204,7 @@ export class ValidatorStore { }; } - async signSelectionProof(pubkey: BLSPubkey, slot: Slot): Promise { + async signAttestationSelectionProof(pubkey: BLSPubkey, slot: Slot): Promise { const domain = await this.getDomain( this.config.params.DOMAIN_SELECTION_PROOF, computeEpochAtSlot(this.config, slot) @@ -193,16 +213,37 @@ export class ValidatorStore { return this.getSecretKey(pubkey).sign(signingRoot).toBytes(); } + async signSyncCommitteeSelectionProof( + pubkey: BLSPubkey | string, + slot: Slot, + subCommitteeIndex: number + ): Promise { + const domain = await this.getDomain( + this.config.params.DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, + computeEpochAtSlot(this.config, slot) + ); + const signingData: altair.SyncCommitteeSigningData = { + slot, + subCommitteeIndex: subCommitteeIndex, + }; + + const signingRoot = computeSigningRoot( + this.config, + this.config.types.altair.SyncCommitteeSigningData, + signingData, + domain + ); + return this.getSecretKey(pubkey).sign(signingRoot).toBytes(); + } + private async getDomain(domainType: DomainType, epoch: Epoch): Promise { - // Get fork from cache or in very rare cases fetch from Beacon node API - const fork = await this.forkService.getFork(); - const forkVersion = epoch < fork.epoch ? fork.previousVersion : fork.currentVersion; + const forkVersion = this.config.getForkVersion(computeStartSlotAtEpoch(this.config, epoch)); return computeDomain(this.config, domainType, forkVersion, this.genesisValidatorsRoot); } - private getSecretKey(pubkey: BLSPubkey): SecretKey { + private getSecretKey(pubkey: BLSPubkey | string): SecretKey { // TODO: Refactor indexing to not have to run toHexString() on the pubkey every time - const pubkeyHex = toHexString(pubkey); + const pubkeyHex = typeof pubkey === "string" ? pubkey : toHexString(pubkey); const validator = this.validators.get(pubkeyHex); if (!validator) { diff --git a/packages/validator/test/unit/services/attestation.test.ts b/packages/validator/test/unit/services/attestation.test.ts index 7485823c6187..f5ee1da96d0d 100644 --- a/packages/validator/test/unit/services/attestation.test.ts +++ b/packages/validator/test/unit/services/attestation.test.ts @@ -36,7 +36,7 @@ describe("AttestationService", function () { validatorStore.votingPubkeys.returns(pubkeys); validatorStore.hasVotingPubkey.returns(true); validatorStore.hasSomeValidators.returns(true); - validatorStore.signSelectionProof.resolves(ZERO_HASH); + validatorStore.signAttestationSelectionProof.resolves(ZERO_HASH); }); let controller: AbortController; // To stop clock diff --git a/packages/validator/test/unit/services/attestationDuties.test.ts b/packages/validator/test/unit/services/attestationDuties.test.ts index 5cbe95f40f6d..48f6a6d8570e 100644 --- a/packages/validator/test/unit/services/attestationDuties.test.ts +++ b/packages/validator/test/unit/services/attestationDuties.test.ts @@ -33,7 +33,7 @@ describe("AttestationDutiesService", function () { pubkeys = secretKeys.map((sk) => sk.toPublicKey().toBytes()); validatorStore.votingPubkeys.returns(pubkeys); validatorStore.hasVotingPubkey.returns(true); - validatorStore.signSelectionProof.resolves(ZERO_HASH); + validatorStore.signAttestationSelectionProof.resolves(ZERO_HASH); }); let controller: AbortController; // To stop clock diff --git a/packages/validator/test/unit/services/syncCommitteDuties.test.ts b/packages/validator/test/unit/services/syncCommitteDuties.test.ts index 47ebb80fa16b..d6e05990a280 100644 --- a/packages/validator/test/unit/services/syncCommitteDuties.test.ts +++ b/packages/validator/test/unit/services/syncCommitteDuties.test.ts @@ -41,7 +41,8 @@ describe("SyncCommitteeDutiesService", function () { pubkeys = secretKeys.map((sk) => sk.toPublicKey().toBytes()); validatorStore.votingPubkeys.returns(pubkeys); validatorStore.hasVotingPubkey.returns(true); - validatorStore.signSelectionProof.resolves(ZERO_HASH); + validatorStore.signAttestationSelectionProof.resolves(ZERO_HASH); + validatorStore.signSyncCommitteeSelectionProof.resolves(ZERO_HASH); }); let controller: AbortController; // To stop clock @@ -100,7 +101,17 @@ describe("SyncCommitteeDutiesService", function () { ); expect(await dutiesService.getDutiesAtSlot(slot)).to.deep.equal( - [{duty, selectionProof: null}], + [ + { + duty: { + pubkey: duty.pubkey, + validatorIndex: duty.validatorIndex, + validatorSyncCommitteeIndex: duty.validatorSyncCommitteeIndices[0], + }, + selectionProof: null, + subCommitteeIndex: 0, + }, + ], "Wrong getAttestersAtSlot()" ); diff --git a/packages/validator/test/unit/services/syncCommittee.test.ts b/packages/validator/test/unit/services/syncCommittee.test.ts index 18488a345ad6..a1c02afb8861 100644 --- a/packages/validator/test/unit/services/syncCommittee.test.ts +++ b/packages/validator/test/unit/services/syncCommittee.test.ts @@ -37,7 +37,7 @@ describe("SyncCommitteeService", function () { validatorStore.votingPubkeys.returns(pubkeys); validatorStore.hasVotingPubkey.returns(true); validatorStore.hasSomeValidators.returns(true); - validatorStore.signSelectionProof.resolves(ZERO_HASH); + validatorStore.signAttestationSelectionProof.resolves(ZERO_HASH); }); let controller: AbortController; // To stop clock @@ -65,9 +65,10 @@ describe("SyncCommitteeService", function () { duty: { pubkey: pubkeys[0], validatorIndex: 0, - validatorSyncCommitteeIndices: [7], + validatorSyncCommitteeIndex: 7, }, selectionProof: ZERO_HASH, + subCommitteeIndex: 0, }, ];