diff --git a/packages/lodestar-beacon-state-transition/src/epoch/fork.ts b/packages/lodestar-beacon-state-transition/src/epoch/fork.ts new file mode 100644 index 000000000000..c84d812fbe5f --- /dev/null +++ b/packages/lodestar-beacon-state-transition/src/epoch/fork.ts @@ -0,0 +1,19 @@ +import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {BeaconState} from "@chainsafe/lodestar-types"; +import {getCurrentEpoch} from ".."; +import {intToBytes} from "@chainsafe/lodestar-utils"; + +export function processForkChanged(config: IBeaconConfig, state: BeaconState): void { + const currentEpoch = getCurrentEpoch(config, state); + const nextEpoch = currentEpoch + 1; + const currentForkVersion = state.fork.currentVersion; + const nextFork = config.params.ALL_FORKS && config.params.ALL_FORKS.find( + (fork) => config.types.Version.equals(currentForkVersion, intToBytes(fork.previousVersion, 4))); + if (nextFork && nextFork.epoch === nextEpoch) { + state.fork = { + previousVersion: state.fork.currentVersion, + currentVersion: intToBytes(nextFork.currentVersion, 4), + epoch: nextFork.epoch, + }; + } +} \ No newline at end of file diff --git a/packages/lodestar-beacon-state-transition/src/epoch/index.ts b/packages/lodestar-beacon-state-transition/src/epoch/index.ts index fcd52bb2c340..20cde3c3184f 100644 --- a/packages/lodestar-beacon-state-transition/src/epoch/index.ts +++ b/packages/lodestar-beacon-state-transition/src/epoch/index.ts @@ -10,6 +10,7 @@ import {processFinalUpdates} from "./finalUpdates"; import {processJustificationAndFinalization} from "./justification"; import {processRegistryUpdates} from "./registryUpdates"; import {processSlashings} from "./slashings"; +import {processForkChanged} from "./fork"; export * from "./balanceUpdates"; export * from "./finalUpdates"; @@ -41,6 +42,9 @@ export function processEpoch(config: IBeaconConfig, state: BeaconState): BeaconS // Final Updates processFinalUpdates(config, state); + // check and process planned hard fork + processForkChanged(config, state); + // TODO Later Phase // afterProcessFinalUpdates diff --git a/packages/lodestar-beacon-state-transition/test/unit/stateTransition/epoch/fork.test.ts b/packages/lodestar-beacon-state-transition/test/unit/stateTransition/epoch/fork.test.ts new file mode 100644 index 000000000000..30322fc1bdc2 --- /dev/null +++ b/packages/lodestar-beacon-state-transition/test/unit/stateTransition/epoch/fork.test.ts @@ -0,0 +1,59 @@ +import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet"; +import {BeaconState} from "@chainsafe/lodestar-types"; +import {generateState} from "../../../utils/state"; +import {processForkChanged} from "../../../../src/epoch/fork"; +import {expect} from "chai"; +import {bytesToInt} from "@chainsafe/lodestar-utils"; + +describe("processForkChanged", () => { + let state: BeaconState; + + beforeEach(() => { + state = generateState(); + state.fork = { + currentVersion: Buffer.from([1, 0, 0, 0]), + epoch: 100, + previousVersion: Buffer.alloc(4), + }; + }); + + afterEach(() => { + config.params.ALL_FORKS = undefined; + }); + + it("should not update fork if no matched next fork", () => { + config.params.ALL_FORKS = undefined; + const preFork = state.fork; + processForkChanged(config, state); + expect(config.types.Fork.equals(preFork, state.fork)).to.be.true; + }); + + it("should not update fork if found matched next fork but epoch not matched", () => { + config.params.ALL_FORKS = [ + { + previousVersion: bytesToInt(Buffer.from([1, 0, 0, 0])), + currentVersion: bytesToInt(Buffer.from([2, 0, 0, 0])), + epoch: 200, + }, + ]; + const preFork = state.fork; + processForkChanged(config, state); + expect(config.types.Fork.equals(preFork, state.fork)).to.be.true; + }); + + it("should update fork if found matched next fork and matched epoch", () => { + config.params.ALL_FORKS = [ + { + previousVersion: bytesToInt(Buffer.from([1, 0, 0, 0])), + currentVersion: bytesToInt(Buffer.from([2, 0, 0, 0])), + epoch: 200, + }, + ]; + const preFork = state.fork; + state.slot = 200 * config.params.SLOTS_PER_EPOCH - 1; + processForkChanged(config, state); + expect(config.types.Fork.equals(preFork, state.fork)).to.be.false; + expect(config.types.Version.equals(preFork.currentVersion, state.fork.previousVersion)); + expect(state.fork.epoch).to.be.equal(200); + }); +}); \ No newline at end of file diff --git a/packages/lodestar-cli/src/commands/beacon.ts b/packages/lodestar-cli/src/commands/beacon.ts index 3f94b5b0e3fe..118d8a36d318 100644 --- a/packages/lodestar-cli/src/commands/beacon.ts +++ b/packages/lodestar-cli/src/commands/beacon.ts @@ -16,10 +16,13 @@ import {getTomlConfig} from "../lodestar/util/file"; import {createNodeJsLibp2p, loadPeerIdFromJsonFile} from "@chainsafe/lodestar/lib/network/nodejs"; import {ENR} from "@chainsafe/discv5"; import {initBLS} from "@chainsafe/bls"; +import fs from "fs"; +import {load} from "js-yaml"; interface IBeaconCommandOptions { [key: string]: string; peerId: string; + forkFile?: string; configFile?: string; preset?: string; loggingLevel?: string; @@ -52,7 +55,7 @@ export class BeaconNodeCommand implements ICliCommand { generateCommanderOptions(command, BeaconNodeOptions); } - public async action(cmdOptions: IBeaconCommandOptions, logger: ILogger): Promise { + public async action(cmdOptions: IBeaconCommandOptions, logger: ILogger): Promise { let nodeOptions: Partial = {}; //find better place for this once this cli is refactored await initBLS(); @@ -81,7 +84,12 @@ export class BeaconNodeCommand implements ICliCommand { const libp2p = await createNodeJsLibp2p(peerId, libp2pOpt); const config = cmdOptions.preset === "minimal" ? minimalConfig : mainnetConfig; // nodejs will create EthersEth1Notifier by default + if (cmdOptions.forkFile) { + // @ts-ignore + config.params.ALL_FORKS = load(fs.readFileSync(cmdOptions.forkFile)); + } this.node = new BeaconNode(nodeOptions, {config, logger, libp2p}); await this.node.start(); + return this.node; } } diff --git a/packages/lodestar-cli/test/e2e/commands/beacon.test.ts b/packages/lodestar-cli/test/e2e/commands/beacon.test.ts index 2a1d4f98726b..df5c8c6947bc 100644 --- a/packages/lodestar-cli/test/e2e/commands/beacon.test.ts +++ b/packages/lodestar-cli/test/e2e/commands/beacon.test.ts @@ -1,12 +1,15 @@ import {config} from "@chainsafe/lodestar-config/lib/presets/minimal"; import rimraf from "rimraf"; import fs from "fs"; +import {assert} from "chai"; import {BeaconNodeCommand, DepositCommand} from "../../../src/commands"; import {ILogger, WinstonLogger} from "@chainsafe/lodestar-utils/lib/logger"; import {PrivateEth1Network} from "@chainsafe/lodestar/lib/eth1/dev"; import {JsonRpcProvider} from "ethers/providers"; import {createPeerId} from "@chainsafe/lodestar/lib/network"; import {savePeerId} from "@chainsafe/lodestar/lib/network/nodejs"; +import {bytesToInt, intToBytes} from "@chainsafe/lodestar-utils"; +import {dump} from "js-yaml"; describe("beacon cli", function() { this.timeout(0); @@ -16,6 +19,7 @@ describe("beacon cli", function() { //same folder of default db const tmpDir = ".tmp"; const peerIdPath = `${tmpDir}/peer-id.json`; + const forkFile = `${tmpDir}/forks.yml`; before(async () => { if (fs.existsSync(tmpDir)) { @@ -24,6 +28,16 @@ describe("beacon cli", function() { fs.mkdirSync(`${tmpDir}/lodestar-db`, {recursive: true}); const peerId = await createPeerId(); await savePeerId(peerIdPath, peerId); + const ALL_FORKS = [ + { + currentVersion: 2, + epoch: 100, + // GENESIS_FORK_VERSION is but previousVersion = 16777216 not 1 due to bytesToInt + previousVersion: bytesToInt(config.params.GENESIS_FORK_VERSION) + }, + ]; + const yml = dump(ALL_FORKS); + fs.writeFileSync(forkFile, yml); }); after(() => { @@ -66,11 +80,15 @@ describe("beacon cli", function() { eth1RpcUrl: eth1Network.rpcUrl(), networkId: "999", depositContractBlockNum: "0", // not really but it's ok - depositContract: contractAddress + depositContract: contractAddress, + forkFile: forkFile, }; const cmd = new BeaconNodeCommand(); - await cmd.action(cmdOptions, logger); + const node = await cmd.action(cmdOptions, logger); logger.verbose("cmd.action started node successfully"); + const enrForkID = await node.chain.getENRForkID(); + assert(config.types.Version.equals(enrForkID.nextForkVersion, intToBytes(2, 4))); + assert(enrForkID.nextForkEpoch === 100); await new Promise((resolve) => setTimeout(resolve, 10 * config.params.SECONDS_PER_SLOT * 1000)); await cmd.node.stop(); logger.verbose("cmd.stop stopped node successfully"); diff --git a/packages/lodestar-params/src/interface.ts b/packages/lodestar-params/src/interface.ts index e4e30316d94e..400fccd9b5cc 100644 --- a/packages/lodestar-params/src/interface.ts +++ b/packages/lodestar-params/src/interface.ts @@ -70,4 +70,17 @@ export interface IBeaconParams { MAX_ATTESTATIONS: number; MAX_DEPOSITS: number; MAX_VOLUNTARY_EXITS: number; + + // Old and future forks + ALL_FORKS: IFork[]; +} + +interface IFork { + // 4 bytes + previousVersion: number; + // 4 bytes + currentVersion: number; + // Fork epoch number + epoch: number; } + diff --git a/packages/lodestar-types/src/ssz/generators/misc.ts b/packages/lodestar-types/src/ssz/generators/misc.ts index ab81ea8cd742..51823777d7cf 100644 --- a/packages/lodestar-types/src/ssz/generators/misc.ts +++ b/packages/lodestar-types/src/ssz/generators/misc.ts @@ -23,6 +23,14 @@ export const ForkData = (ssz: IBeaconSSZTypes): ContainerType => new ContainerTy }, }); +export const ENRForkID = (ssz: IBeaconSSZTypes): ContainerType => new ContainerType({ + fields: { + forkDigest: ssz.ForkDigest, + nextForkVersion: ssz.Version, + nextForkEpoch: ssz.Epoch, + }, +}); + export const Checkpoint = (ssz: IBeaconSSZTypes): ContainerType => new ContainerType({ fields: { epoch: ssz.Epoch, diff --git a/packages/lodestar-types/src/ssz/interface.ts b/packages/lodestar-types/src/ssz/interface.ts index ecd59e6483db..93dc78a7ea4c 100644 --- a/packages/lodestar-types/src/ssz/interface.ts +++ b/packages/lodestar-types/src/ssz/interface.ts @@ -34,6 +34,7 @@ export interface IBeaconSSZTypes { // misc Fork: ContainerType; ForkData: ContainerType; + ENRForkID: ContainerType; Checkpoint: ContainerType; Validator: ContainerType; AttestationData: ContainerType; @@ -110,6 +111,7 @@ export const typeNames: (keyof IBeaconSSZTypes)[] = [ // misc "Fork", "ForkData", + "ENRForkID", "Checkpoint", "Validator", "AttestationData", diff --git a/packages/lodestar-types/src/types/misc.ts b/packages/lodestar-types/src/types/misc.ts index 08dad9791270..85f40b8fbb55 100644 --- a/packages/lodestar-types/src/types/misc.ts +++ b/packages/lodestar-types/src/types/misc.ts @@ -18,6 +18,7 @@ import { CommitteeIndex, Bytes32, Domain, + ForkDigest, } from "./primitive"; export interface Fork { @@ -36,6 +37,15 @@ export interface ForkData { genesisValidatorsRoot: Root; } +export interface ENRForkID { + // Current fork digest + forkDigest: ForkDigest; + // next planned fork versin + nextForkVersion: Version; + // next fork epoch + nextForkEpoch: Epoch; +} + export interface Checkpoint { epoch: Epoch; root: Root; diff --git a/packages/lodestar-utils/test/unit/bytes.test.ts b/packages/lodestar-utils/test/unit/bytes.test.ts index 862c8dad1a36..24b40c130db8 100644 --- a/packages/lodestar-utils/test/unit/bytes.test.ts +++ b/packages/lodestar-utils/test/unit/bytes.test.ts @@ -2,10 +2,11 @@ import {assert, expect} from "chai"; import {describe, it} from "mocha"; import {intToBytes, bytesToInt} from "../../src"; -describe("intToBytes", () => { +describe("intToBytes", () => { const zeroedArray = (length: number): number[] => Array.from({length}, () => 0); const testCases: { input: [bigint | number, number]; output: Buffer }[] = [ - {input: [255, 1], output: Buffer.from([255])}, + {input: [255, 1], output: Buffer.from([255])}, + {input: [1, 4], output: Buffer.from([1, 0, 0, 0])}, {input: [255n, 1], output: Buffer.from([255])}, {input: [65535, 2], output: Buffer.from([255, 255])}, {input: [65535n, 2], output: Buffer.from([255, 255])}, @@ -31,7 +32,7 @@ describe("intToBytes", () => { } }); -describe.only("bytesToInt", () => { +describe("bytesToInt", () => { const testCases: { input: Buffer; output: number}[] = [ {input: Buffer.from([3]), output: 3}, {input: Buffer.from([20, 0]), output: 20}, diff --git a/packages/lodestar/src/chain/blocks/process.ts b/packages/lodestar/src/chain/blocks/process.ts index ef93e2daae70..ab8410abb0ed 100644 --- a/packages/lodestar/src/chain/blocks/process.ts +++ b/packages/lodestar/src/chain/blocks/process.ts @@ -1,15 +1,21 @@ import {IBlockProcessJob} from "../chain"; import {BeaconState, Root, SignedBeaconBlock} from "@chainsafe/lodestar-types"; -import {stateTransition} from "@chainsafe/lodestar-beacon-state-transition"; +import {stateTransition, computeEpochAtSlot} from "@chainsafe/lodestar-beacon-state-transition"; import {toHexString} from "@chainsafe/ssz"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {IBeaconDb} from "../../db/api"; import {ILogger} from "@chainsafe/lodestar-utils/lib/logger"; import {ILMDGHOST} from "../forkChoice"; import {BlockPool} from "./pool"; +import {ChainEventEmitter} from ".."; export function processBlock( - config: IBeaconConfig, db: IBeaconDb, logger: ILogger, forkChoice: ILMDGHOST, pool: BlockPool + config: IBeaconConfig, + db: IBeaconDb, + logger: ILogger, + forkChoice: ILMDGHOST, + pool: BlockPool, + eventBus: ChainEventEmitter, ): (source: AsyncIterable) => AsyncGenerator<{block: SignedBeaconBlock; postState: BeaconState}> { return (source) => { return (async function*() { @@ -32,6 +38,12 @@ export function processBlock( const newChainHeadRoot = await updateForkChoice(config, db, forkChoice, job.signedBlock, newState); if(newChainHeadRoot) { logger.important(`Fork choice changed head to 0x${toHexString(newChainHeadRoot)}`); + if(!config.types.Fork.equals(preState.fork, newState.fork)) { + const epoch = computeEpochAtSlot(config, newState.slot); + const currentVersion = newState.fork.currentVersion; + logger.important(`Fork version changed to ${currentVersion} at slot ${newState.slot} and epoch ${epoch}`); + eventBus.emit("forkDigestChanged"); + } await updateDepositMerkleTree(config, db, newState); } pool.onProcessedBlock(job.signedBlock); diff --git a/packages/lodestar/src/chain/blocks/processor.ts b/packages/lodestar/src/chain/blocks/processor.ts index d6afec810851..3a49bb9dc7fa 100644 --- a/packages/lodestar/src/chain/blocks/processor.ts +++ b/packages/lodestar/src/chain/blocks/processor.ts @@ -64,7 +64,7 @@ export class BlockProcessor implements IService { return abortable(source, abortSignal, {returnOnAbort: true}); }, validateBlock(this.config, this.logger, this.db, this.forkChoice), - processBlock(this.config, this.db, this.logger, this.forkChoice, this.pendingBlocks), + processBlock(this.config, this.db, this.logger, this.forkChoice, this.pendingBlocks, this.eventBus), postProcess( this.config, this.db, diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index d6e87a44f417..2afd6a076df7 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -5,7 +5,8 @@ import {EventEmitter} from "events"; import {fromHexString, List, toHexString, TreeBacked} from "@chainsafe/ssz"; import {Attestation, BeaconState, Root, SignedBeaconBlock, Uint16, Uint64, - ForkDigest} from "@chainsafe/lodestar-types"; + ForkDigest, + ENRForkID} from "@chainsafe/lodestar-types"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {EMPTY_SIGNATURE, GENESIS_SLOT} from "../constants"; import {IBeaconDb} from "../db"; @@ -24,6 +25,7 @@ import {IBeaconClock} from "./clock/interface"; import {LocalClock} from "./clock/local/LocalClock"; import {BlockProcessor} from "./blocks"; import {Block} from "ethers/providers"; +import {intToBytes} from "@chainsafe/lodestar-utils"; export interface IBeaconChainModules { config: IBeaconConfig; @@ -32,6 +34,7 @@ export interface IBeaconChainModules { eth1: IEth1Notifier; logger: ILogger; metrics: IBeaconMetrics; + forkChoice?: ILMDGHOST; } export interface IBlockProcessJob { @@ -39,6 +42,7 @@ export interface IBlockProcessJob { trusted: boolean; } +const MAX_VERSION = Buffer.from([255, 255, 255, 255]); export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) implements IBeaconChain { public readonly chain: string; @@ -58,7 +62,8 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) private attestationProcessor: IAttestationProcessor; private eth1Listener: (eth1Block: Block) => void; - public constructor(opts: IChainOptions, {config, db, eth1, opPool, logger, metrics}: IBeaconChainModules) { + public constructor( + opts: IChainOptions, {config, db, eth1, opPool, logger, metrics, forkChoice}: IBeaconChainModules) { super(); this.opts = opts; this.chain = opts.name; @@ -68,7 +73,7 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) this.opPool = opPool; this.logger = logger; this.metrics = metrics; - this.forkChoice = new StatefulDagLMDGHOST(config); + this.forkChoice = forkChoice || new StatefulDagLMDGHOST(config); this.chainId = 0; // TODO make this real this.networkId = 0n; // TODO make this real this.attestationProcessor = new AttestationProcessor(this, this.forkChoice, {config, db, logger}); @@ -100,12 +105,14 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) this.forkChoice.start(state.genesisTime, this.clock); await this.blockProcessor.start(); this._currentForkDigest = await this.getCurrentForkDigest(); + this.on("forkDigestChanged", this.handleForkDigestChanged); } public async stop(): Promise { await this.forkChoice.stop(); await this.clock.stop(); await this.blockProcessor.stop(); + this.removeListener("forkDigestChanged", this.handleForkDigestChanged); } public get currentForkDigest(): ForkDigest { @@ -161,6 +168,23 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) this.logger.info("Beacon chain initialized"); } + public async getENRForkID(): Promise { + const state = await this.getHeadState(); + const currentVersion = state.fork.currentVersion; + const nextVersion = this.config.params.ALL_FORKS && this.config.params.ALL_FORKS.find( + fork => this.config.types.Version.equals(currentVersion, intToBytes(fork.previousVersion, 4))); + return { + forkDigest: this.currentForkDigest, + nextForkVersion: nextVersion? intToBytes(nextVersion.currentVersion, 4) : MAX_VERSION, + nextForkEpoch: nextVersion? nextVersion.epoch : Number.MAX_SAFE_INTEGER, + }; + } + + private async handleForkDigestChanged(): Promise { + this._currentForkDigest = await this.getCurrentForkDigest(); + this.emit("forkDigest", this._currentForkDigest); + } + private async getCurrentForkDigest(): Promise { const state = await this.getHeadState(); return computeForkDigest(this.config, state.fork.currentVersion, state.genesisValidatorsRoot); diff --git a/packages/lodestar/src/chain/interface.ts b/packages/lodestar/src/chain/interface.ts index c58f0151880d..cd38c1d113db 100644 --- a/packages/lodestar/src/chain/interface.ts +++ b/packages/lodestar/src/chain/interface.ts @@ -10,6 +10,7 @@ import { Uint16, Uint64, ForkDigest, + ENRForkID, } from "@chainsafe/lodestar-types"; import {ILMDGHOST} from "./forkChoice"; @@ -23,6 +24,8 @@ export interface IChainEvents { processedAttestation: (attestation: Attestation) => void; justifiedCheckpoint: (checkpoint: Checkpoint) => void; finalizedCheckpoint: (checkpoint: Checkpoint) => void; + forkDigestChanged: () => void; + forkDigest: (forkDigest: ForkDigest) => void; } export type ChainEventEmitter = StrictEventEmitter; @@ -47,6 +50,11 @@ export interface IBeaconChain extends ChainEventEmitter { */ stop(): Promise; + /** + * Return ENRForkID. + */ + getENRForkID(): Promise; + getHeadState(): Promise; getHeadBlock(): Promise; diff --git a/packages/lodestar/src/network/metadata/metadata.ts b/packages/lodestar/src/network/metadata/metadata.ts index e80583539f9a..39e9d66a9cc0 100644 --- a/packages/lodestar/src/network/metadata/metadata.ts +++ b/packages/lodestar/src/network/metadata/metadata.ts @@ -1,7 +1,8 @@ import {BitVector} from "@chainsafe/ssz"; import {ENR} from "@chainsafe/discv5"; -import {Metadata} from "@chainsafe/lodestar-types"; +import {Metadata, ForkDigest} from "@chainsafe/lodestar-types"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {IBeaconChain} from "../../chain"; export interface IMetadataOpts { enr?: ENR; @@ -10,21 +11,33 @@ export interface IMetadataOpts { export interface IMetadataModules { config: IBeaconConfig; + chain: IBeaconChain; } export class MetadataController { public enr?: ENR; private config: IBeaconConfig; + private chain: IBeaconChain; private _metadata: Metadata; constructor(opts: IMetadataOpts, modules: IMetadataModules) { this.enr = opts.enr; this.config = modules.config; + this.chain = modules.chain; this._metadata = opts.metadata || this.config.types.Metadata.defaultValue(); + } + + public async start(): Promise { if (this.enr) { this.enr.set("attnets", Buffer.from(this.config.types.AttestationSubnets.serialize(this._metadata.attnets))); + this.enr.set("eth2", Buffer.from(this.config.types.ENRForkID.serialize(await this.chain.getENRForkID()))); } + this.chain.on("forkDigest", this.handleForkDigest); + } + + public async stop(): Promise { + this.chain.removeListener("forkDigest", this.handleForkDigest); } get seqNumber(): bigint { @@ -46,4 +59,11 @@ export class MetadataController { get metadata(): Metadata { return this._metadata; } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private async handleForkDigest(forkDigest: ForkDigest): Promise { + if (this.enr) { + this.enr.set("eth2", Buffer.from(this.config.types.ENRForkID.serialize(await this.chain.getENRForkID()))); + } + } } diff --git a/packages/lodestar/src/network/network.ts b/packages/lodestar/src/network/network.ts index b8cccf125457..beb42777f8b5 100644 --- a/packages/lodestar/src/network/network.ts +++ b/packages/lodestar/src/network/network.ts @@ -60,7 +60,7 @@ export class Libp2pNetwork extends (EventEmitter as { new(): NetworkEventEmitter this.libp2p = libp2p; this.reqResp = new ReqResp(opts, {config, libp2p, logger}); const enr = opts.discv5 && opts.discv5.enr || undefined; - this.metadata = new MetadataController({enr}, {config}); + this.metadata = new MetadataController({enr}, {config, chain}); this.gossip = (new Gossip(opts, this.metadata, {config, libp2p, logger, validator, chain})) as unknown as IGossip; resolve(); @@ -73,6 +73,7 @@ export class Libp2pNetwork extends (EventEmitter as { new(): NetworkEventEmitter await this.libp2p.start(); await this.reqResp.start(); await this.gossip.start(); + await this.metadata.start(); this.libp2p.on("peer:connect", this.emitPeerConnect); this.libp2p.on("peer:disconnect", this.emitPeerDisconnect); const multiaddresses = this.libp2p.peerInfo.multiaddrs.toArray().map((m) => m.toString()).join(","); @@ -80,6 +81,7 @@ export class Libp2pNetwork extends (EventEmitter as { new(): NetworkEventEmitter } public async stop(): Promise { + await this.metadata.stop(); await this.gossip.stop(); await this.reqResp.stop(); await this.libp2p.stop(); diff --git a/packages/lodestar/test/unit/chain/blocks/process.test.ts b/packages/lodestar/test/unit/chain/blocks/process.test.ts index 4a089d5c631f..059b5087ae78 100644 --- a/packages/lodestar/test/unit/chain/blocks/process.test.ts +++ b/packages/lodestar/test/unit/chain/blocks/process.test.ts @@ -13,7 +13,7 @@ import {WinstonLogger} from "@chainsafe/lodestar-utils/lib/logger"; import {ILMDGHOST, StatefulDagLMDGHOST} from "../../../../src/chain/forkChoice"; import {collect} from "./utils"; import {expect} from "chai"; -import {IBlockProcessJob} from "../../../../src/chain"; +import {IBlockProcessJob, ChainEventEmitter, BeaconChain} from "../../../../src/chain"; import {BlockPool} from "../../../../src/chain/blocks/pool"; import {processBlock} from "../../../../src/chain/blocks/process"; import * as stateTransitionUtils from "@chainsafe/lodestar-beacon-state-transition"; @@ -32,6 +32,7 @@ describe("block process stream", function () { let forkChoiceStub: SinonStubbedInstance; let blockPoolStub: SinonStubbedInstance; let stateTransitionStub: SinonStub; + let eventBusStub: SinonStubbedInstance; const sandbox = sinon.createSandbox(); @@ -49,7 +50,8 @@ describe("block process stream", function () { dbStub.depositDataRootList = depositDataRootListDbStub as unknown as DepositDataRootListRepository; blockPoolStub = sinon.createStubInstance(BlockPool); forkChoiceStub = sinon.createStubInstance(StatefulDagLMDGHOST); - stateTransitionStub = sandbox.stub(stateTransitionUtils, "stateTransition") + stateTransitionStub = sandbox.stub(stateTransitionUtils, "stateTransition"); + eventBusStub = sinon.createStubInstance(BeaconChain); }); afterEach(function () { @@ -64,7 +66,7 @@ describe("block process stream", function () { blockDbStub.get.withArgs(receivedJob.signedBlock.message.parentRoot.valueOf() as Uint8Array).resolves(null); const result = await pipe( [receivedJob], - processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool), + processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool, eventBusStub), collect ); expect(result).to.have.length(0); @@ -81,7 +83,7 @@ describe("block process stream", function () { stateDbStub.get.resolves(null); const result = await pipe( [receivedJob], - processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool), + processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool, eventBusStub), collect ); expect(result).to.have.length(0); @@ -98,7 +100,7 @@ describe("block process stream", function () { stateTransitionStub.throws(); const result = await pipe( [receivedJob], - processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool), + processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool, eventBusStub), collect ); expect(result).to.have.length(0); @@ -118,7 +120,7 @@ describe("block process stream", function () { forkChoiceStub.head.returns(Buffer.alloc(32, 1)); const result = await pipe( [receivedJob], - processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool), + processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool, eventBusStub), collect ); expect(result).to.have.length(1); @@ -145,7 +147,7 @@ describe("block process stream", function () { blockDbStub.get.resolves(receivedJob.signedBlock); const result = await pipe( [receivedJob], - processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool), + processBlock(config, dbStub, sinon.createStubInstance(WinstonLogger), forkChoiceStub, blockPoolStub as unknown as BlockPool, eventBusStub), collect ); expect(result).to.have.length(1); diff --git a/packages/lodestar/test/unit/chain/chain.test.ts b/packages/lodestar/test/unit/chain/chain.test.ts new file mode 100644 index 000000000000..a07ef829efa2 --- /dev/null +++ b/packages/lodestar/test/unit/chain/chain.test.ts @@ -0,0 +1,85 @@ +import chainOpts from "../../../src/chain/options"; +import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet"; +import sinon from "sinon"; +import {expect} from "chai"; +import {StateRepository, BlockRepository} from "../../../src/db/api/beacon/repositories"; +import {IEth1Notifier} from "../../../src/eth1"; +import {InteropEth1Notifier} from "../../../src/eth1/impl/interop"; +import {OpPool} from "../../../src/opPool"; +import {WinstonLogger, bytesToInt} from "@chainsafe/lodestar-utils"; +import {BeaconMetrics} from "../../../src/metrics"; +import {IBeaconChain, BeaconChain, StatefulDagLMDGHOST} from "../../../src/chain"; +import {generateState} from "../../utils/state"; + +describe("BeaconChain", function() { + const sandbox = sinon.createSandbox(); + let dbStub: any, eth1: IEth1Notifier, opPool: any, metrics: any, forkChoice: any; + const logger = new WinstonLogger(); + let chain: IBeaconChain; + + beforeEach(async () => { + dbStub = { + state: sandbox.createStubInstance(StateRepository), + block: sandbox.createStubInstance(BlockRepository), + }; + eth1 = new InteropEth1Notifier(); + opPool = sandbox.createStubInstance(OpPool); + metrics = sandbox.createStubInstance(BeaconMetrics); + forkChoice = sandbox.createStubInstance(StatefulDagLMDGHOST); + const state = generateState(); + dbStub.state.get.resolves(state); + dbStub.state.getLatest.resolves(state); + chain = new BeaconChain(chainOpts, {config, db: dbStub, eth1, opPool, logger, metrics, forkChoice}); + await chain.start(); + }); + + afterEach(async () => { + await chain.stop(); + sandbox.restore(); + }); + + describe("getENRForkID", () => { + it("should get enr fork id if not found next fork", async () => { + forkChoice.headStateRoot.returns(Buffer.alloc(0)); + const enrForkID = await chain.getENRForkID(); + expect(config.types.Version.equals(enrForkID.nextForkVersion, Buffer.from([255, 255, 255, 255]))); + expect(enrForkID.nextForkEpoch === Number.MAX_SAFE_INTEGER); + // it's possible to serialize enr fork id + config.types.ENRForkID.hashTreeRoot(enrForkID); + }); + + it("should get enr fork id if found next fork", async () => { + config.params.ALL_FORKS = [ + { + currentVersion: 2, + epoch: 100, + previousVersion: bytesToInt(config.params.GENESIS_FORK_VERSION) + } + ]; + forkChoice.headStateRoot.returns(Buffer.alloc(0)); + const enrForkID = await chain.getENRForkID(); + expect(config.types.Version.equals(enrForkID.nextForkVersion, Buffer.from([2, 0, 0, 0]))); + expect(enrForkID.nextForkEpoch === 100); + // it's possible to serialize enr fork id + config.types.ENRForkID.hashTreeRoot(enrForkID); + config.params.ALL_FORKS = undefined; + }); + }); + + describe("forkDigestChanged event", () => { + it("should should receive forkDigest event", async () => { + const spy = sinon.spy(); + const received = new Promise((resolve) => { + chain.on("forkDigest", () => { + spy(); + resolve(); + }); + }); + chain.emit("forkDigestChanged"); + await received; + expect(spy.callCount).to.be.equal(1); + }); + }); + + +}); \ No newline at end of file diff --git a/packages/lodestar/test/utils/mocks/chain/chain.ts b/packages/lodestar/test/utils/mocks/chain/chain.ts index 49faf80fc026..849a62e7b266 100644 --- a/packages/lodestar/test/utils/mocks/chain/chain.ts +++ b/packages/lodestar/test/utils/mocks/chain/chain.ts @@ -1,6 +1,6 @@ import {EventEmitter} from "events"; -import {Number64, Uint16, Uint64, ForkDigest} from "@chainsafe/lodestar-types"; +import {Number64, Uint16, Uint64, ForkDigest, ENRForkID} from "@chainsafe/lodestar-types"; import {IBeaconChain, ILMDGHOST} from "../../../../src/chain"; import {IBeaconClock} from "../../../../src/chain/clock/interface"; import {BeaconState} from "@chainsafe/lodestar-types"; @@ -50,6 +50,10 @@ export class MockBeaconChain extends EventEmitter implements IBeaconChain { return undefined; } + public async getENRForkID(): Promise { + return undefined; + } + isInitialized(): boolean { return !!this.initialized; }