import {fromHexString} from "@chainsafe/ssz"; import {routes} from "@lodestar/api"; import {ChainConfig} from "@lodestar/config"; import {TimestampFormatCode} from "@lodestar/logger"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {phase0} from "@lodestar/types"; import {afterEach, describe, expect, it, vi} from "vitest"; import {ChainEvent} from "../../../src/chain/index.js"; import {waitForEvent} from "../../utils/events/resolver.js"; import {LogLevel, TestLoggerOpts, testLogger} from "../../utils/logger.js"; import {connect, onPeerConnect} from "../../utils/network.js"; import {getDevBeaconNode} from "../../utils/node/beacon.js"; import {getAndInitDevValidators} from "../../utils/node/validator.js"; describe("sync / finalized sync for fulu", () => { // chain is finalized at slot 32, plus 4 slots for genesis delay => ~72s it should sync pretty fast vi.setConfig({testTimeout: 90_000}); const validatorCount = 8; const ELECTRA_FORK_EPOCH = 0; const FULU_FORK_EPOCH = 1; const SECONDS_PER_SLOT = 2; const testParams: Partial = { SECONDS_PER_SLOT, ALTAIR_FORK_EPOCH: ELECTRA_FORK_EPOCH, BELLATRIX_FORK_EPOCH: ELECTRA_FORK_EPOCH, CAPELLA_FORK_EPOCH: ELECTRA_FORK_EPOCH, DENEB_FORK_EPOCH: ELECTRA_FORK_EPOCH, ELECTRA_FORK_EPOCH: ELECTRA_FORK_EPOCH, FULU_FORK_EPOCH: FULU_FORK_EPOCH, BLOB_SCHEDULE: [ { EPOCH: 1, MAX_BLOBS_PER_BLOCK: 3, }, ], }; const afterEachCallbacks: (() => Promise | void)[] = []; afterEach(async () => { while (afterEachCallbacks.length > 0) { const callback = afterEachCallbacks.pop(); if (callback) await callback(); } }); it("should do a finalized sync from another BN", async () => { // single node at beginning, use main thread to verify bls const genesisSlotsDelay = 4; const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * SECONDS_PER_SLOT; const testLoggerOpts: TestLoggerOpts = { level: LogLevel.info, timestampFormat: { format: TimestampFormatCode.EpochSlot, genesisTime, slotsPerEpoch: SLOTS_PER_EPOCH, secondsPerSlot: SECONDS_PER_SLOT, }, }; const loggerNodeA = testLogger("FinalizedSync-Node-A", testLoggerOpts); const loggerNodeB = testLogger("FinalizedSync-Node-B", testLoggerOpts); const bn = await getDevBeaconNode({ params: testParams, options: { sync: {isSingleNode: true}, network: {allowPublishToZeroPeers: true, useWorker: false}, chain: {blsVerifyAllMainThread: true}, }, validatorCount, genesisTime, logger: loggerNodeA, }); afterEachCallbacks.push(() => bn.close()); const {validators} = await getAndInitDevValidators({ node: bn, logPrefix: "FinalizedSyncVc", validatorsPerClient: validatorCount, validatorClientCount: 1, startIndex: 0, useRestApi: false, testLoggerOpts, }); afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.close()))); // stop beacon node after validators afterEachCallbacks.push(() => bn.close()); await Promise.all([ waitForEvent( bn.chain.emitter, ChainEvent.forkChoiceFinalized, 240000, (finalized) => finalized.epoch >= FULU_FORK_EPOCH ), waitForEvent( bn.chain.emitter, routes.events.EventType.head, 100000, // at block slot 32 imported, finalized checkpoint epoch 2 is processed ({slot}) => slot === 32 ), ]); loggerNodeA.info("Node A emitted finalized checkpoint event for fulu"); const bn2 = await getDevBeaconNode({ params: testParams, options: { api: {rest: {enabled: false}}, network: {useWorker: false}, chain: {blsVerifyAllMainThread: true}, }, validatorCount, genesisTime, logger: loggerNodeB, }); loggerNodeA.info("Node B created"); afterEachCallbacks.push(() => bn2.close()); afterEachCallbacks.push(() => bn2.close()); const headSummary = bn.chain.forkChoice.getHead(); const head = await bn.db.block.get(fromHexString(headSummary.blockRoot)); if (!head) throw Error("First beacon node has no head block"); const waitForSynced = waitForEvent( bn2.chain.emitter, routes.events.EventType.head, 100000, ({block}) => block === headSummary.blockRoot ); await Promise.all([connect(bn2.network, bn.network), onPeerConnect(bn2.network), onPeerConnect(bn.network)]); loggerNodeA.info("Node A connected to Node B"); try { await waitForSynced; loggerNodeB.info("Node B synced to Node A, received fulu head block", {slot: head.message.slot}); } catch (_e) { expect.fail("Failed to sync to other node in time"); } }); });