deps: update vitest to latest version (#6967)

* Upgrade vitest

* --segfault-retry removed from vitest

* Fix the vitest config

* Fix deprecated syntax

* Fix types

* Add a patch for browser tests

* Update build config

* Revert changes from bundle

* Fix the e2e tests context

* Fix the e2e tests

* Fix the e2e callback hooks

* Fix callback hooks

---------

Co-authored-by: Cayman <caymannava@gmail.com>
This commit is contained in:
Nazar Hussain
2024-08-01 22:26:10 +05:00
committed by GitHub
parent 12e187148d
commit 5b0608aaf9
29 changed files with 1487 additions and 1314 deletions

View File

@@ -54,8 +54,8 @@
"@types/node": "^20.12.8",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitest/browser": "^1.6.0",
"@vitest/coverage-v8": "^1.6.0",
"@vitest/browser": "^2.0.4",
"@vitest/coverage-v8": "^2.0.4",
"crypto-browserify": "^3.12.0",
"dotenv": "^16.4.5",
"electron": "^26.2.2",
@@ -81,12 +81,12 @@
"ts-node": "^10.9.2",
"typescript": "^5.4.2",
"typescript-docs-verifier": "^2.5.0",
"vite": "^5.2.11",
"vite": "^5.3.4",
"vite-plugin-dts": "^3.9.1",
"vite-plugin-node-polyfills": "^0.21.0",
"vite-plugin-top-level-await": "^1.4.1",
"vitest": "^1.6.0",
"vitest-when": "^0.3.1",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-top-level-await": "^1.4.2",
"vitest": "^2.0.4",
"vitest-when": "^0.4.1",
"wait-port": "^1.1.0",
"webdriverio": "^8.36.1"
},
@@ -94,7 +94,7 @@
"@puppeteer/browsers": "^2.1.0",
"dns-over-http-resolver": "^2.1.1",
"loupe": "^2.3.6",
"vite": "^5.2.11",
"vite": "^5.3.4",
"testcontainers/**/nan": "^2.19.0"
}
}

View File

@@ -17,7 +17,7 @@ describe("httpClient fallback", () => {
// Using fetchSub instead of actually setting up servers because there are some strange
// race conditions, where the server stub doesn't count the call in time before the test is over.
const fetchStub = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>();
const fetchStub = vi.fn<(...args: Parameters<typeof fetch>) => ReturnType<typeof fetch>>();
let httpClient: HttpClient;

View File

@@ -78,10 +78,10 @@
"lint": "eslint --color --ext .ts src/ test/",
"lint:fix": "yarn run lint --fix",
"test": "yarn test:unit && yarn test:e2e",
"test:unit:minimal": "LODESTAR_PRESET=minimal vitest --run --segfaultRetry 3 --dir test/unit/",
"test:unit:mainnet": "LODESTAR_PRESET=mainnet vitest --run --segfaultRetry 3 --dir test/unit-mainnet",
"test:unit:minimal": "LODESTAR_PRESET=minimal vitest --run --dir test/unit/",
"test:unit:mainnet": "LODESTAR_PRESET=mainnet vitest --run --dir test/unit-mainnet",
"test:unit": "wrapper() { yarn test:unit:minimal $@ && yarn test:unit:mainnet $@; }; wrapper",
"test:e2e": "LODESTAR_PRESET=minimal vitest --run --segfaultRetry 3 --config vitest.e2e.config.ts --dir test/e2e",
"test:e2e": "LODESTAR_PRESET=minimal vitest --run --config vitest.e2e.config.ts --dir test/e2e",
"test:sim": "vitest --run test/sim/**/*.test.ts",
"test:sim:mergemock": "vitest --run test/sim/mergemock.test.ts",
"test:sim:blobs": "vitest --run test/sim/4844-interop.test.ts",

View File

@@ -1,4 +1,4 @@
import {describe, it, afterEach, expect} from "vitest";
import {describe, it, afterEach, expect, vi} from "vitest";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {TimestampFormatCode} from "@lodestar/logger";
import {ChainConfig} from "@lodestar/config";
@@ -12,125 +12,122 @@ import {getAndInitDevValidators} from "../../utils/node/validator.js";
import {waitForEvent} from "../../utils/events/resolver.js";
import {ReorgEventData} from "../../../src/chain/emitter.js";
describe(
"proposer boost reorg",
function () {
const validatorCount = 8;
const testParams: Pick<ChainConfig, "SECONDS_PER_SLOT" | "REORG_PARENT_WEIGHT_THRESHOLD" | "PROPOSER_SCORE_BOOST"> =
{
// eslint-disable-next-line @typescript-eslint/naming-convention
SECONDS_PER_SLOT: 2,
// need this to make block `reorgSlot - 1` strong enough
// eslint-disable-next-line @typescript-eslint/naming-convention
REORG_PARENT_WEIGHT_THRESHOLD: 80,
// need this to make block `reorgSlot + 1` to become the head
// eslint-disable-next-line @typescript-eslint/naming-convention
PROPOSER_SCORE_BOOST: 120,
};
describe("proposer boost reorg", function () {
vi.setConfig({testTimeout: 60000});
const afterEachCallbacks: (() => Promise<unknown> | void)[] = [];
afterEach(async () => {
while (afterEachCallbacks.length > 0) {
const callback = afterEachCallbacks.pop();
if (callback) await callback();
}
});
const validatorCount = 8;
const testParams: Pick<ChainConfig, "SECONDS_PER_SLOT" | "REORG_PARENT_WEIGHT_THRESHOLD" | "PROPOSER_SCORE_BOOST"> = {
// eslint-disable-next-line @typescript-eslint/naming-convention
SECONDS_PER_SLOT: 2,
// need this to make block `reorgSlot - 1` strong enough
// eslint-disable-next-line @typescript-eslint/naming-convention
REORG_PARENT_WEIGHT_THRESHOLD: 80,
// need this to make block `reorgSlot + 1` to become the head
// eslint-disable-next-line @typescript-eslint/naming-convention
PROPOSER_SCORE_BOOST: 120,
};
const reorgSlot = 10;
const proposerBoostReorg = true;
/**
* reorgSlot
* /
* reorgSlot - 1 ------------ reorgSlot + 1
*
* Note that in addition of being not timely, there are other criterion that
* the block needs to satisfy before being re-orged out. This test assumes
* other criterion are already satisfied
*/
it(`should reorg a late block at slot ${reorgSlot}`, async () => {
// the node needs time to transpile/initialize bls worker threads
const genesisSlotsDelay = 7;
const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT;
const testLoggerOpts: TestLoggerOpts = {
level: LogLevel.debug,
timestampFormat: {
format: TimestampFormatCode.EpochSlot,
genesisTime,
slotsPerEpoch: SLOTS_PER_EPOCH,
secondsPerSlot: testParams.SECONDS_PER_SLOT,
},
};
const logger = testLogger("BeaconNode", testLoggerOpts);
const bn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false},
chain: {
blsVerifyAllMainThread: true,
forkchoiceConstructor: TimelinessForkChoice,
proposerBoost: true,
proposerBoostReorg,
},
},
validatorCount,
const afterEachCallbacks: (() => Promise<unknown> | void)[] = [];
afterEach(async () => {
while (afterEachCallbacks.length > 0) {
const callback = afterEachCallbacks.pop();
if (callback) await callback();
}
});
const reorgSlot = 10;
const proposerBoostReorg = true;
/**
* reorgSlot
* /
* reorgSlot - 1 ------------ reorgSlot + 1
*
* Note that in addition of being not timely, there are other criterion that
* the block needs to satisfy before being re-orged out. This test assumes
* other criterion are already satisfied
*/
it(`should reorg a late block at slot ${reorgSlot}`, async () => {
// the node needs time to transpile/initialize bls worker threads
const genesisSlotsDelay = 7;
const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT;
const testLoggerOpts: TestLoggerOpts = {
level: LogLevel.debug,
timestampFormat: {
format: TimestampFormatCode.EpochSlot,
genesisTime,
logger,
});
(bn.chain.forkChoice as TimelinessForkChoice).lateSlot = reorgSlot;
afterEachCallbacks.push(async () => bn.close());
const {validators} = await getAndInitDevValidators({
node: bn,
logPrefix: "vc-0",
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
});
afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close())));
const commonAncestor = await waitForEvent<{slot: Slot; block: RootHex}>(
bn.chain.emitter,
routes.events.EventType.head,
240000,
({slot}) => slot === reorgSlot - 1
);
// reorgSlot
// /
// commonAncestor ------------ newBlock
const commonAncestorRoot = commonAncestor.block;
const reorgBlockEventData = await waitForEvent<{slot: Slot; block: RootHex}>(
bn.chain.emitter,
routes.events.EventType.head,
240000,
({slot}) => slot === reorgSlot
);
const reorgBlockRoot = reorgBlockEventData.block;
const [newBlockEventData, reorgEventData] = await Promise.all([
waitForEvent<{slot: Slot; block: RootHex}>(
bn.chain.emitter,
routes.events.EventType.block,
240000,
({slot}) => slot === reorgSlot + 1
),
waitForEvent<ReorgEventData>(bn.chain.emitter, routes.events.EventType.chainReorg, 240000),
]);
expect(reorgEventData.slot).toEqual(reorgSlot + 1);
const newBlock = await bn.chain.getBlockByRoot(newBlockEventData.block);
if (newBlock == null) {
throw Error(`Block ${reorgSlot + 1} not found`);
}
expect(reorgEventData.oldHeadBlock).toEqual(reorgBlockRoot);
expect(reorgEventData.newHeadBlock).toEqual(newBlockEventData.block);
expect(reorgEventData.depth).toEqual(2);
expect(toHexString(newBlock?.block.message.parentRoot)).toEqual(commonAncestorRoot);
logger.info("New block", {
slot: newBlock.block.message.slot,
parentRoot: toHexString(newBlock.block.message.parentRoot),
});
slotsPerEpoch: SLOTS_PER_EPOCH,
secondsPerSlot: testParams.SECONDS_PER_SLOT,
},
};
const logger = testLogger("BeaconNode", testLoggerOpts);
const bn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false},
chain: {
blsVerifyAllMainThread: true,
forkchoiceConstructor: TimelinessForkChoice,
proposerBoost: true,
proposerBoostReorg,
},
},
validatorCount,
genesisTime,
logger,
});
},
{timeout: 60000}
);
(bn.chain.forkChoice as TimelinessForkChoice).lateSlot = reorgSlot;
afterEachCallbacks.push(async () => bn.close());
const {validators} = await getAndInitDevValidators({
node: bn,
logPrefix: "vc-0",
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
});
afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close())));
const commonAncestor = await waitForEvent<{slot: Slot; block: RootHex}>(
bn.chain.emitter,
routes.events.EventType.head,
240000,
({slot}) => slot === reorgSlot - 1
);
// reorgSlot
// /
// commonAncestor ------------ newBlock
const commonAncestorRoot = commonAncestor.block;
const reorgBlockEventData = await waitForEvent<{slot: Slot; block: RootHex}>(
bn.chain.emitter,
routes.events.EventType.head,
240000,
({slot}) => slot === reorgSlot
);
const reorgBlockRoot = reorgBlockEventData.block;
const [newBlockEventData, reorgEventData] = await Promise.all([
waitForEvent<{slot: Slot; block: RootHex}>(
bn.chain.emitter,
routes.events.EventType.block,
240000,
({slot}) => slot === reorgSlot + 1
),
waitForEvent<ReorgEventData>(bn.chain.emitter, routes.events.EventType.chainReorg, 240000),
]);
expect(reorgEventData.slot).toEqual(reorgSlot + 1);
const newBlock = await bn.chain.getBlockByRoot(newBlockEventData.block);
if (newBlock == null) {
throw Error(`Block ${reorgSlot + 1} not found`);
}
expect(reorgEventData.oldHeadBlock).toEqual(reorgBlockRoot);
expect(reorgEventData.newHeadBlock).toEqual(newBlockEventData.block);
expect(reorgEventData.depth).toEqual(2);
expect(toHexString(newBlock?.block.message.parentRoot)).toEqual(commonAncestorRoot);
logger.info("New block", {
slot: newBlock.block.message.slot,
parentRoot: toHexString(newBlock.block.message.parentRoot),
});
});
});

View File

@@ -1,4 +1,4 @@
import {describe, it, afterEach, expect} from "vitest";
import {describe, it, afterEach, expect, vi} from "vitest";
import {Gauge, Histogram} from "prom-client";
import {ChainConfig} from "@lodestar/config";
import {Slot, phase0} from "@lodestar/types";
@@ -19,429 +19,427 @@ import {ReorgedForkChoice} from "../../../mocks/fork-choice/reorg.js";
* This includes several tests which make >6 min to pass in CI, so let's only run 1 of them and leave remaining ones
* for local investigation.
*/
describe(
"regen/reload states with n-historical states configuration",
function () {
const validatorCount = 8;
const testParams: Pick<ChainConfig, "SECONDS_PER_SLOT"> = {
// eslint-disable-next-line @typescript-eslint/naming-convention
SECONDS_PER_SLOT: 2,
};
describe("regen/reload states with n-historical states configuration", function () {
vi.setConfig({testTimeout: 96_000});
const afterEachCallbacks: (() => Promise<unknown> | void)[] = [];
afterEach(async () => {
while (afterEachCallbacks.length > 0) {
const callback = afterEachCallbacks.pop();
if (callback) await callback();
}
});
const validatorCount = 8;
const testParams: Pick<ChainConfig, "SECONDS_PER_SLOT"> = {
// eslint-disable-next-line @typescript-eslint/naming-convention
SECONDS_PER_SLOT: 2,
};
// all tests run until this slot
const LAST_SLOT = 33;
/**
* (n+1)
* -----------------|
* /
* |---------|---------|
* ^ ^
* (n+1-x) reorgedSlot n
* ^
* commonAncestor
* |<--reorgDistance-->|
*/
const testCases: {
name: string;
reorgedSlot: number;
reorgDistance: number;
maxBlockStates: number;
maxCPStateEpochsInMemory: number;
reloadCount: number;
// total persist count, to compare to metrics
persistCount: number;
numStatesInMemory: number;
// number of states persisted at the end of test
numStatesPersisted: number;
numEpochsInMemory: number;
numEpochsPersisted: number;
skip?: boolean;
}[] = [
/**
* Block slot 28 has parent slot 25, block slot 26 and 27 are reorged
* --------------------|---
* / ^ ^ ^ ^
* / 28 29 32 33
* |----------------|----------
* ^ ^ ^ ^
* 24 25 26 27
* */
{
name: "0 historical state, reorg in same epoch",
reorgedSlot: 27,
reorgDistance: 3,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 0,
// reload at cp epoch 1 once to regen state 9 (12 - 3)
reloadCount: 1,
// persist for epoch 0 to 4, no need to persist cp epoch 3 again
persistCount: 5,
// run through slot 33, no state in memory
numStatesInMemory: 0,
// epoch 0 1 2 3 4 but finalized at epoch 2 so store checkpoint states for epoch 2 3 4
numStatesPersisted: 3,
numEpochsInMemory: 0,
// epoch 0 1 2 3 4 but finalized at eopch 2 so store checkpoint states for epoch 2 3 4
numEpochsPersisted: 3,
// chain is finalized at epoch 2 end of test
skip: true,
},
/**
* Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged
* --------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------
* 16 ^ ^ ^ ^ ^
* ^ 23 24 25 26 27
* reload ^
* 2 checkpoint states at epoch 3 are persisted
*/
{
name: "0 historical state, reorg 1 epoch",
reorgedSlot: 27,
reorgDistance: 5,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 0,
// reload at cp epoch 2 once to regen state 23 (28 - 5)
reloadCount: 1,
// 1 cp state for epoch 0 1 2 4, and 2 cp states for epoch 3 (different roots)
persistCount: 6,
numStatesInMemory: 0,
// epoch 0 1 2 4 has 1 cp state, epoch 3 has 2 checkpoint states
numStatesPersisted: 6,
numEpochsInMemory: 0,
// epoch 0 1 2 3 4
numEpochsPersisted: 5,
// chain is not finalized end of test
skip: true,
},
/**
* Block slot 28 has parent slot 25, block slot 26 and 27 are reorged
* --------------------|---
* / ^ ^ ^ ^
* / 28 29 32 33
* |----------------|----------
* ^ ^ ^ ^
* 24 25 26 27
* */
{
name: "maxCPStateEpochsInMemory=1, reorg in same epoch",
reorgedSlot: 27,
reorgDistance: 3,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// no need to reload as cp state epoch 3 is available in memory
reloadCount: 0,
// 1 time for epoch 0 1 2 3, cp state epoch 4 is in memory
persistCount: 4,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// epoch 2 3, epoch 4 is in-memory
numStatesPersisted: 2,
// epoch 3
numEpochsInMemory: 1,
// epoch 2 3, epoch 4 is in-memory
numEpochsPersisted: 2,
// chain is finalized at epoch 2 end of test
skip: true,
},
/**
* Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged
* --------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------
* 16 ^ ^ ^ ^ ^
* 23 24 25 26 27
* ^
* both PRCS and CRCS are persisted
*/
{
name: "maxCPStateEpochsInMemory=1, reorg last slot of previous epoch",
reorgedSlot: 27,
reorgDistance: 5,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// PRCS at epoch 3 is available in memory so no need to reload
reloadCount: 0,
// {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3}
persistCount: 5,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// chain is not finalized, same to persistCount
numStatesPersisted: 5,
// epoch 4
numEpochsInMemory: 1,
// chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted
numEpochsPersisted: 4,
// chain is NOT finalized end of test
skip: true,
},
/**
* Block slot 28 has parent slot 19, block slot 24 25 26 and 27 are reorged
* --------------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------
* 16 ^ ^ ^ ^ ^ ^
* 19 23 24 25 26 27
* ^
* both PRCS and CRCS are persisted since their roots are unknown to block state 33
*/
{
name: "maxCPStateEpochsInMemory=1, reorg middle slot of previous epoch",
reorgedSlot: 27,
reorgDistance: 9,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// reload CP state epoch 2 (slot = 16)
reloadCount: 1,
// {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root19, epoch: 3}
persistCount: 6,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// chain is not finalized, same to persist count
numStatesPersisted: 6,
// epoch 4
numEpochsInMemory: 1,
// chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted
numEpochsPersisted: 4,
// chain is NOT finalized end of test
skip: true,
},
/**
* Block slot 28 has parent slot 15, block slot 24 25 26 and 27 are reorged
* --------------------------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------------|---------- ^
* ^ ^ 16 ^ ^ ^ ^ ^ ^ test end
* 8 15 19 23 24 25 26 27
*reload ^
* both PRCS and CRCS are persisted because roots are unknown to block 28
*/
{
name: "maxCPStateEpochsInMemory=1, reorg 2 epochs",
reorgedSlot: 27,
reorgDistance: 13,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// reload CP state epoch 2 (slot = 16)
reloadCount: 1,
// {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root15, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root15, epoch: 3}
persistCount: 7,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// chain is not finalized, so same number to persistCount
numStatesPersisted: 7,
// epoch 4
numEpochsInMemory: 1,
// chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted
numEpochsPersisted: 4,
// chain is NOT finalized end of test
},
];
for (const {
name,
reorgedSlot,
reorgDistance,
maxBlockStates,
maxCPStateEpochsInMemory,
reloadCount,
persistCount,
numStatesInMemory,
numStatesPersisted,
numEpochsInMemory,
numEpochsPersisted,
skip,
} of testCases) {
const wrappedIt = skip ? it.skip : it;
wrappedIt(`${name} reorgedSlot=${reorgedSlot} reorgDistance=${reorgDistance}`, async function () {
// the node needs time to transpile/initialize bls worker threads
const genesisSlotsDelay = 7;
const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT;
const testLoggerOpts: TestLoggerOpts = {
level: LogLevel.debug,
timestampFormat: {
format: TimestampFormatCode.EpochSlot,
genesisTime,
slotsPerEpoch: SLOTS_PER_EPOCH,
secondsPerSlot: testParams.SECONDS_PER_SLOT,
},
};
const loggerNodeA = testLogger("Reorg-Node-A", testLoggerOpts);
const loggerNodeB = testLogger("FollowUp-Node-B", {...testLoggerOpts, level: LogLevel.debug});
const reorgedBn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false},
// run the first bn with ReorgedForkChoice, no nHistoricalStates flag so it does not have to reload
chain: {
blsVerifyAllMainThread: true,
forkchoiceConstructor: ReorgedForkChoice,
// this node does not need to reload state
nHistoricalStates: false,
proposerBoost: true,
},
},
validatorCount,
genesisTime,
logger: loggerNodeA,
});
// stop bn after validators
afterEachCallbacks.push(() => reorgedBn.close());
const followupBn = await getDevBeaconNode({
params: testParams,
options: {
api: {rest: {enabled: false}},
network: {mdns: true, useWorker: false},
// run the 2nd bn with nHistoricalStates flag and the configured maxBlockStates, maxCPStateEpochsInMemory
chain: {
blsVerifyAllMainThread: true,
forkchoiceConstructor: ReorgedForkChoice,
// this node can follow with nHistoricalStates flag and it has to reload state
nHistoricalStates: true,
maxBlockStates,
maxCPStateEpochsInMemory,
proposerBoost: true,
},
metrics: {enabled: true},
},
validatorCount,
genesisTime,
logger: loggerNodeB,
});
afterEachCallbacks.push(() => followupBn.close());
const connected = Promise.all([onPeerConnect(followupBn.network), onPeerConnect(reorgedBn.network)]);
await connect(followupBn.network, reorgedBn.network);
await connected;
loggerNodeB.info("Node B connected to Node A");
const {validators} = await getAndInitDevValidators({
node: reorgedBn,
logPrefix: "Val-Node-A",
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
});
afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close())));
// wait for checkpoint 3 at slot 24, both nodes should reach same checkpoint
const cpEpoch = 3;
const cpSlot = 3 * SLOTS_PER_EPOCH;
const checkpoints = await Promise.all(
[reorgedBn, followupBn].map((bn) =>
waitForEvent<phase0.Checkpoint>(
bn.chain.emitter,
ChainEvent.checkpoint,
(cpSlot + genesisSlotsDelay + 1) * testParams.SECONDS_PER_SLOT * 1000,
(cp) => cp.epoch === cpEpoch
)
)
);
expect(checkpoints[0]).toEqual(checkpoints[1]);
expect(checkpoints[0].epoch).toEqual(3);
const head = reorgedBn.chain.forkChoice.getHead();
loggerNodeA.info("Node A emitted checkpoint event, head slot: " + head.slot);
// setup reorg data for both bns
for (const bn of [reorgedBn, followupBn]) {
(bn.chain.forkChoice as ReorgedForkChoice).reorgedSlot = reorgedSlot;
(bn.chain.forkChoice as ReorgedForkChoice).reorgDistance = reorgDistance;
}
// both nodes see the reorg event
const reorgDatas = await Promise.all(
[reorgedBn, followupBn].map((bn) =>
waitForEvent<ReorgEventData>(
bn.chain.emitter,
routes.events.EventType.chainReorg,
// reorged event happens at reorgedSlot + 1
(reorgedSlot + 1 - cpSlot + 1) * testParams.SECONDS_PER_SLOT * 1000,
(reorgData) => reorgData.slot === reorgedSlot + 1
)
)
);
for (const reorgData of reorgDatas) {
expect(reorgData.slot).toEqual(reorgedSlot + 1);
expect(reorgData.depth).toEqual(reorgDistance);
}
// make sure both nodes can reach another checkpoint
const checkpoints2 = await Promise.all(
[reorgedBn, followupBn].map((bn) =>
waitForEvent<phase0.Checkpoint>(bn.chain.emitter, ChainEvent.checkpoint, 240000, (cp) => cp.epoch === 4)
)
);
expect(checkpoints2[0]).toEqual(checkpoints2[1]);
expect(checkpoints2[0].epoch).toEqual(4);
// wait for 1 more slot to persist states
await waitForEvent<{slot: Slot}>(
reorgedBn.chain.emitter,
routes.events.EventType.block,
240000,
({slot}) => slot === LAST_SLOT
);
const reloadMetricValues = await (followupBn.metrics?.cpStateCache.stateReloadDuration as Histogram).get();
expect(
reloadMetricValues?.values.find(
(value) => value.metricName === "lodestar_cp_state_cache_state_reload_seconds_count"
)?.value
).toEqual(reloadCount);
const stateSszMetricValues = await (followupBn.metrics?.cpStateCache.stateSerializeDuration as Histogram).get();
expect(
stateSszMetricValues?.values.find(
(value) => value.metricName === "lodestar_cp_state_cache_state_serialize_seconds_count"
)?.value
).toEqual(persistCount);
// assert number of persisted/in-memory states
const stateSizeMetricValues = await (followupBn.metrics?.cpStateCache.size as unknown as Gauge).get();
const numStateInMemoryItem = stateSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.inMemory
);
const numStatePersistedItem = stateSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.persisted
);
expect(numStateInMemoryItem?.value).toEqual(numStatesInMemory);
expect(numStatePersistedItem?.value).toEqual(numStatesPersisted);
// assert number of epochs persisted/in-memory
const epochSizeMetricValues = await (followupBn.metrics?.cpStateCache.epochSize as unknown as Gauge).get();
const numEpochsInMemoryItem = epochSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.inMemory
);
const numEpochsPersistedItem = epochSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.persisted
);
expect(numEpochsInMemoryItem?.value).toEqual(numEpochsInMemory);
expect(numEpochsPersistedItem?.value).toEqual(numEpochsPersisted);
});
const afterEachCallbacks: (() => Promise<unknown> | void)[] = [];
afterEach(async () => {
while (afterEachCallbacks.length > 0) {
const callback = afterEachCallbacks.pop();
if (callback) await callback();
}
},
{timeout: 96_000}
);
});
// all tests run until this slot
const LAST_SLOT = 33;
/**
* (n+1)
* -----------------|
* /
* |---------|---------|
* ^ ^
* (n+1-x) reorgedSlot n
* ^
* commonAncestor
* |<--reorgDistance-->|
*/
const testCases: {
name: string;
reorgedSlot: number;
reorgDistance: number;
maxBlockStates: number;
maxCPStateEpochsInMemory: number;
reloadCount: number;
// total persist count, to compare to metrics
persistCount: number;
numStatesInMemory: number;
// number of states persisted at the end of test
numStatesPersisted: number;
numEpochsInMemory: number;
numEpochsPersisted: number;
skip?: boolean;
}[] = [
/**
* Block slot 28 has parent slot 25, block slot 26 and 27 are reorged
* --------------------|---
* / ^ ^ ^ ^
* / 28 29 32 33
* |----------------|----------
* ^ ^ ^ ^
* 24 25 26 27
* */
{
name: "0 historical state, reorg in same epoch",
reorgedSlot: 27,
reorgDistance: 3,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 0,
// reload at cp epoch 1 once to regen state 9 (12 - 3)
reloadCount: 1,
// persist for epoch 0 to 4, no need to persist cp epoch 3 again
persistCount: 5,
// run through slot 33, no state in memory
numStatesInMemory: 0,
// epoch 0 1 2 3 4 but finalized at epoch 2 so store checkpoint states for epoch 2 3 4
numStatesPersisted: 3,
numEpochsInMemory: 0,
// epoch 0 1 2 3 4 but finalized at eopch 2 so store checkpoint states for epoch 2 3 4
numEpochsPersisted: 3,
// chain is finalized at epoch 2 end of test
skip: true,
},
/**
* Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged
* --------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------
* 16 ^ ^ ^ ^ ^
* ^ 23 24 25 26 27
* reload ^
* 2 checkpoint states at epoch 3 are persisted
*/
{
name: "0 historical state, reorg 1 epoch",
reorgedSlot: 27,
reorgDistance: 5,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 0,
// reload at cp epoch 2 once to regen state 23 (28 - 5)
reloadCount: 1,
// 1 cp state for epoch 0 1 2 4, and 2 cp states for epoch 3 (different roots)
persistCount: 6,
numStatesInMemory: 0,
// epoch 0 1 2 4 has 1 cp state, epoch 3 has 2 checkpoint states
numStatesPersisted: 6,
numEpochsInMemory: 0,
// epoch 0 1 2 3 4
numEpochsPersisted: 5,
// chain is not finalized end of test
skip: true,
},
/**
* Block slot 28 has parent slot 25, block slot 26 and 27 are reorged
* --------------------|---
* / ^ ^ ^ ^
* / 28 29 32 33
* |----------------|----------
* ^ ^ ^ ^
* 24 25 26 27
* */
{
name: "maxCPStateEpochsInMemory=1, reorg in same epoch",
reorgedSlot: 27,
reorgDistance: 3,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// no need to reload as cp state epoch 3 is available in memory
reloadCount: 0,
// 1 time for epoch 0 1 2 3, cp state epoch 4 is in memory
persistCount: 4,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// epoch 2 3, epoch 4 is in-memory
numStatesPersisted: 2,
// epoch 3
numEpochsInMemory: 1,
// epoch 2 3, epoch 4 is in-memory
numEpochsPersisted: 2,
// chain is finalized at epoch 2 end of test
skip: true,
},
/**
* Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged
* --------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------
* 16 ^ ^ ^ ^ ^
* 23 24 25 26 27
* ^
* both PRCS and CRCS are persisted
*/
{
name: "maxCPStateEpochsInMemory=1, reorg last slot of previous epoch",
reorgedSlot: 27,
reorgDistance: 5,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// PRCS at epoch 3 is available in memory so no need to reload
reloadCount: 0,
// {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3}
persistCount: 5,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// chain is not finalized, same to persistCount
numStatesPersisted: 5,
// epoch 4
numEpochsInMemory: 1,
// chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted
numEpochsPersisted: 4,
// chain is NOT finalized end of test
skip: true,
},
/**
* Block slot 28 has parent slot 19, block slot 24 25 26 and 27 are reorged
* --------------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------
* 16 ^ ^ ^ ^ ^ ^
* 19 23 24 25 26 27
* ^
* both PRCS and CRCS are persisted since their roots are unknown to block state 33
*/
{
name: "maxCPStateEpochsInMemory=1, reorg middle slot of previous epoch",
reorgedSlot: 27,
reorgDistance: 9,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// reload CP state epoch 2 (slot = 16)
reloadCount: 1,
// {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root19, epoch: 3}
persistCount: 6,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// chain is not finalized, same to persist count
numStatesPersisted: 6,
// epoch 4
numEpochsInMemory: 1,
// chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted
numEpochsPersisted: 4,
// chain is NOT finalized end of test
skip: true,
},
/**
* Block slot 28 has parent slot 15, block slot 24 25 26 and 27 are reorged
* --------------------------------------------|---
* / | ^ ^ ^ ^
* / | 28 29 32 33
* |----------------|----------------|---------- ^
* ^ ^ 16 ^ ^ ^ ^ ^ ^ test end
* 8 15 19 23 24 25 26 27
*reload ^
* both PRCS and CRCS are persisted because roots are unknown to block 28
*/
{
name: "maxCPStateEpochsInMemory=1, reorg 2 epochs",
reorgedSlot: 27,
reorgDistance: 13,
maxBlockStates: 1,
maxCPStateEpochsInMemory: 1,
// reload CP state epoch 2 (slot = 16)
reloadCount: 1,
// {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root15, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root15, epoch: 3}
persistCount: 7,
// epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State
numStatesInMemory: 2,
// chain is not finalized, so same number to persistCount
numStatesPersisted: 7,
// epoch 4
numEpochsInMemory: 1,
// chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted
numEpochsPersisted: 4,
// chain is NOT finalized end of test
},
];
for (const {
name,
reorgedSlot,
reorgDistance,
maxBlockStates,
maxCPStateEpochsInMemory,
reloadCount,
persistCount,
numStatesInMemory,
numStatesPersisted,
numEpochsInMemory,
numEpochsPersisted,
skip,
} of testCases) {
const wrappedIt = skip ? it.skip : it;
wrappedIt(`${name} reorgedSlot=${reorgedSlot} reorgDistance=${reorgDistance}`, async function () {
// the node needs time to transpile/initialize bls worker threads
const genesisSlotsDelay = 7;
const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT;
const testLoggerOpts: TestLoggerOpts = {
level: LogLevel.debug,
timestampFormat: {
format: TimestampFormatCode.EpochSlot,
genesisTime,
slotsPerEpoch: SLOTS_PER_EPOCH,
secondsPerSlot: testParams.SECONDS_PER_SLOT,
},
};
const loggerNodeA = testLogger("Reorg-Node-A", testLoggerOpts);
const loggerNodeB = testLogger("FollowUp-Node-B", {...testLoggerOpts, level: LogLevel.debug});
const reorgedBn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false},
// run the first bn with ReorgedForkChoice, no nHistoricalStates flag so it does not have to reload
chain: {
blsVerifyAllMainThread: true,
forkchoiceConstructor: ReorgedForkChoice,
// this node does not need to reload state
nHistoricalStates: false,
proposerBoost: true,
},
},
validatorCount,
genesisTime,
logger: loggerNodeA,
});
// stop bn after validators
afterEachCallbacks.push(() => reorgedBn.close());
const followupBn = await getDevBeaconNode({
params: testParams,
options: {
api: {rest: {enabled: false}},
network: {mdns: true, useWorker: false},
// run the 2nd bn with nHistoricalStates flag and the configured maxBlockStates, maxCPStateEpochsInMemory
chain: {
blsVerifyAllMainThread: true,
forkchoiceConstructor: ReorgedForkChoice,
// this node can follow with nHistoricalStates flag and it has to reload state
nHistoricalStates: true,
maxBlockStates,
maxCPStateEpochsInMemory,
proposerBoost: true,
},
metrics: {enabled: true},
},
validatorCount,
genesisTime,
logger: loggerNodeB,
});
afterEachCallbacks.push(() => followupBn.close());
const connected = Promise.all([onPeerConnect(followupBn.network), onPeerConnect(reorgedBn.network)]);
await connect(followupBn.network, reorgedBn.network);
await connected;
loggerNodeB.info("Node B connected to Node A");
const {validators} = await getAndInitDevValidators({
node: reorgedBn,
logPrefix: "Val-Node-A",
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
});
afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close())));
// wait for checkpoint 3 at slot 24, both nodes should reach same checkpoint
const cpEpoch = 3;
const cpSlot = 3 * SLOTS_PER_EPOCH;
const checkpoints = await Promise.all(
[reorgedBn, followupBn].map((bn) =>
waitForEvent<phase0.Checkpoint>(
bn.chain.emitter,
ChainEvent.checkpoint,
(cpSlot + genesisSlotsDelay + 1) * testParams.SECONDS_PER_SLOT * 1000,
(cp) => cp.epoch === cpEpoch
)
)
);
expect(checkpoints[0]).toEqual(checkpoints[1]);
expect(checkpoints[0].epoch).toEqual(3);
const head = reorgedBn.chain.forkChoice.getHead();
loggerNodeA.info("Node A emitted checkpoint event, head slot: " + head.slot);
// setup reorg data for both bns
for (const bn of [reorgedBn, followupBn]) {
(bn.chain.forkChoice as ReorgedForkChoice).reorgedSlot = reorgedSlot;
(bn.chain.forkChoice as ReorgedForkChoice).reorgDistance = reorgDistance;
}
// both nodes see the reorg event
const reorgDatas = await Promise.all(
[reorgedBn, followupBn].map((bn) =>
waitForEvent<ReorgEventData>(
bn.chain.emitter,
routes.events.EventType.chainReorg,
// reorged event happens at reorgedSlot + 1
(reorgedSlot + 1 - cpSlot + 1) * testParams.SECONDS_PER_SLOT * 1000,
(reorgData) => reorgData.slot === reorgedSlot + 1
)
)
);
for (const reorgData of reorgDatas) {
expect(reorgData.slot).toEqual(reorgedSlot + 1);
expect(reorgData.depth).toEqual(reorgDistance);
}
// make sure both nodes can reach another checkpoint
const checkpoints2 = await Promise.all(
[reorgedBn, followupBn].map((bn) =>
waitForEvent<phase0.Checkpoint>(bn.chain.emitter, ChainEvent.checkpoint, 240000, (cp) => cp.epoch === 4)
)
);
expect(checkpoints2[0]).toEqual(checkpoints2[1]);
expect(checkpoints2[0].epoch).toEqual(4);
// wait for 1 more slot to persist states
await waitForEvent<{slot: Slot}>(
reorgedBn.chain.emitter,
routes.events.EventType.block,
240000,
({slot}) => slot === LAST_SLOT
);
const reloadMetricValues = await (followupBn.metrics?.cpStateCache.stateReloadDuration as Histogram).get();
expect(
reloadMetricValues?.values.find(
(value) => value.metricName === "lodestar_cp_state_cache_state_reload_seconds_count"
)?.value
).toEqual(reloadCount);
const stateSszMetricValues = await (followupBn.metrics?.cpStateCache.stateSerializeDuration as Histogram).get();
expect(
stateSszMetricValues?.values.find(
(value) => value.metricName === "lodestar_cp_state_cache_state_serialize_seconds_count"
)?.value
).toEqual(persistCount);
// assert number of persisted/in-memory states
const stateSizeMetricValues = await (followupBn.metrics?.cpStateCache.size as unknown as Gauge).get();
const numStateInMemoryItem = stateSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.inMemory
);
const numStatePersistedItem = stateSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.persisted
);
expect(numStateInMemoryItem?.value).toEqual(numStatesInMemory);
expect(numStatePersistedItem?.value).toEqual(numStatesPersisted);
// assert number of epochs persisted/in-memory
const epochSizeMetricValues = await (followupBn.metrics?.cpStateCache.epochSize as unknown as Gauge).get();
const numEpochsInMemoryItem = epochSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.inMemory
);
const numEpochsPersistedItem = epochSizeMetricValues?.values.find(
(value) => value.labels.type === CacheItemType.persisted
);
expect(numEpochsInMemoryItem?.value).toEqual(numEpochsInMemory);
expect(numEpochsPersistedItem?.value).toEqual(numEpochsPersisted);
});
}
});

View File

@@ -1,4 +1,4 @@
import {describe, it, expect, afterEach} from "vitest";
import {describe, it, expect, afterEach, vi} from "vitest";
import {createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {sleep} from "@lodestar/utils";
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
@@ -8,26 +8,22 @@ import {GossipType, GossipHandlers, GossipHandlerParamGeneric} from "../../../sr
import {getNetworkForTest} from "../../utils/networkWithMockDb.js";
import {connect, onPeerConnect} from "../../utils/network.js";
describe(
"gossipsub / main thread",
function () {
runTests({useWorker: false});
},
{timeout: 3000}
);
describe("gossipsub / main thread", function () {
vi.setConfig({testTimeout: 3000});
runTests({useWorker: false});
});
/**
* This is nice to have to investigate networking issue in local environment.
* Since we use vitest to run tests in parallel, including this causes the test to be unstable.
* See https://github.com/ChainSafe/lodestar/issues/6358
*/
describe.skip(
"gossipsub / worker",
function () {
runTests({useWorker: true});
},
{timeout: 10_000}
);
describe.skip("gossipsub / worker", function () {
vi.setConfig({testTimeout: 3000});
runTests({useWorker: true});
});
function runTests({useWorker}: {useWorker: boolean}): void {
const afterEachCallbacks: (() => Promise<void> | void)[] = [];

View File

@@ -9,21 +9,17 @@ import {connect, disconnect, onPeerConnect, onPeerDisconnect} from "../../utils/
import {getNetworkForTest} from "../../utils/networkWithMockDb.js";
import {getValidPeerId} from "../../utils/peer.js";
describe(
"network / main thread",
function () {
runTests({useWorker: false});
},
{timeout: 3000}
);
describe("network / main thread", function () {
vi.setConfig({testTimeout: 3000});
describe(
"network / worker",
function () {
runTests({useWorker: true});
},
{timeout: 10_000}
);
runTests({useWorker: false});
});
describe("network / worker", function () {
vi.setConfig({testTimeout: 10_000});
runTests({useWorker: true});
});
function runTests({useWorker}: {useWorker: boolean}): void {
const afterEachCallbacks: (() => Promise<void> | void)[] = [];
@@ -111,11 +107,11 @@ function runTests({useWorker}: {useWorker: boolean}): void {
// NetworkEvent.reqRespRequest does not work on worker thread
// so we only test the peerDisconnected event
const onGoodbyeNetB = useWorker ? null : vi.fn<[phase0.Goodbye, PeerId]>();
const onGoodbyeNetB = useWorker ? null : vi.fn<(message: phase0.Goodbye, peerId: PeerId) => void>();
netB.events.on(NetworkEvent.reqRespRequest, ({request, peer}) => {
if (request.method === ReqRespMethod.Goodbye && onGoodbyeNetB) onGoodbyeNetB(request.body, peer);
});
const onDisconnectNetB = vi.fn<[string]>();
const onDisconnectNetB = vi.fn<(_: string) => void>();
netB.events.on(NetworkEvent.peerDisconnected, ({peer}) => {
onDisconnectNetB(peer);
});

View File

@@ -1,4 +1,4 @@
import {describe, it, expect, afterEach, beforeEach} from "vitest";
import {describe, it, expect, afterEach, beforeEach, vi} from "vitest";
import {createChainForkConfig, ChainForkConfig} from "@lodestar/config";
import {chainConfig} from "@lodestar/config/default";
import {ForkName} from "@lodestar/params";
@@ -15,21 +15,17 @@ import {PeerIdStr} from "../../../src/util/peerId.js";
/* eslint-disable require-yield, @typescript-eslint/naming-convention */
describe(
"network / reqresp / main thread",
function () {
runTests({useWorker: false});
},
{timeout: 3000}
);
describe("network / reqresp / main thread", function () {
vi.setConfig({testTimeout: 3000});
describe(
"network / reqresp / worker",
function () {
runTests({useWorker: true});
},
{timeout: 30_000}
);
runTests({useWorker: false});
});
describe("network / reqresp / worker", function () {
vi.setConfig({testTimeout: 30_000});
runTests({useWorker: true});
});
function runTests({useWorker}: {useWorker: boolean}): void {
// Schedule ALTAIR_FORK_EPOCH to trigger registering lightclient ReqResp protocols immediately

View File

@@ -20,7 +20,7 @@ import {getMockedClock} from "./clock.js";
export type MockedBeaconChain = Mocked<BeaconChain> & {
logger: Mocked<Logger>;
getHeadState: Mock<[]>;
getHeadState: Mock;
forkChoice: MockedForkChoice;
executionEngine: Mocked<ExecutionEngineHttp>;
executionBuilder: Mocked<ExecutionBuilderHttp>;
@@ -31,10 +31,10 @@ export type MockedBeaconChain = Mocked<BeaconChain> & {
shufflingCache: Mocked<ShufflingCache>;
regen: Mocked<QueuedStateRegenerator>;
bls: {
verifySignatureSets: Mock<[boolean]>;
verifySignatureSetsSameMessage: Mock<[boolean]>;
verifySignatureSets: Mock<() => boolean>;
verifySignatureSetsSameMessage: Mock<() => boolean>;
close: Mock;
canAcceptWork: Mock<[boolean]>;
canAcceptWork: Mock<() => boolean>;
};
lightClientServer: Mocked<LightClientServer>;
};

View File

@@ -19,7 +19,7 @@ describe("PrepareNextSlot scheduler", () => {
let regenStub: MockedBeaconChain["regen"];
let loggerStub: MockedLogger;
let beaconProposerCacheStub: MockedBeaconChain["beaconProposerCache"];
let getForkStub: MockInstance<[number], ForkName>;
let getForkStub: MockInstance<(_: number) => ForkName>;
let updateBuilderStatus: MockedBeaconChain["updateBuilderStatus"];
let executionEngineStub: MockedBeaconChain["executionEngine"];
const emitPayloadAttributes = true;

View File

@@ -16,7 +16,7 @@ describe("gossip block validation", function () {
let chain: MockedBeaconChain;
let forkChoice: MockedBeaconChain["forkChoice"];
let regen: Mocked<QueuedStateRegenerator>;
let verifySignature: Mock<[boolean]>;
let verifySignature: Mock<() => boolean>;
let job: SignedBeaconBlock;
const proposerIndex = 0;
const clockSlot = 32;

View File

@@ -1,5 +1,5 @@
import path from "node:path";
import {afterAll, describe, it, vi, beforeEach, afterEach} from "vitest";
import {describe, it, vi, onTestFinished} from "vitest";
import {toHexString} from "@chainsafe/ssz";
import {sleep, retry} from "@lodestar/utils";
import {getClient} from "@lodestar/api";
@@ -26,8 +26,11 @@ describe("bLSToExecutionChange cmd", function () {
// Speed up test to make genesis happen faster
"--params.SECONDS_PER_SLOT=2",
],
{pipeStdioToParent: true, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}}
{pipeStdioToParent: true, logPrefix: "dev"}
);
onTestFinished(async () => {
await stopChildProcess(devBnProc);
});
// Exit early if process exits
devBnProc.on("exit", (code) => {

View File

@@ -37,21 +37,23 @@ describe("import from fs same cmd as validate", function () {
// Check that there are not keys loaded without adding extra args `--importKeystores`
it("run 'validator' there are no keys loaded", async () => {
const {keymanagerClient} = await startValidatorWithKeyManager([], {
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {
dataDir,
logPrefix: "case-1",
});
await expectKeys(keymanagerClient, [], "Wrong listKeys response data");
await stopValidator();
});
// Run validator with extra arguments to load keystores in same step
it("run 'validator' check keys are loaded", async () => {
const {keymanagerClient} = await startValidatorWithKeyManager(
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager(
[`--importKeystores=${importFromDir}`, `--importKeystoresPassword=${passphraseFilepath}`],
{dataDir, logPrefix: "case-2"}
);
await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys response data");
await stopValidator();
});
});

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import {describe, it, expect, beforeAll, vi} from "vitest";
import {describe, it, expect, beforeAll, vi, onTestFinished} from "vitest";
import {rimraf} from "rimraf";
import {execCliCommand} from "@lodestar/test-utils";
import {getKeystoresStr} from "@lodestar/test-utils";
@@ -59,7 +59,10 @@ describe("import from fs then validate", function () {
});
it("run 'validator' check keys are loaded", async function () {
const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir});
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys response data");
});

View File

@@ -1,5 +1,5 @@
import path from "node:path";
import {describe, it, expect, beforeAll, vi, afterAll, beforeEach, afterEach} from "vitest";
import {describe, it, expect, beforeAll, vi, onTestFinished} from "vitest";
import {rimraf} from "rimraf";
import {DeletionStatus, getClient, ImportStatus} from "@lodestar/api/keymanager";
import {config} from "@lodestar/config/default";
@@ -55,7 +55,11 @@ describe("import keystores from api", function () {
const slashingProtectionStr = JSON.stringify(slashingProtection);
it("run 'validator' and import remote keys from API", async () => {
const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir});
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
// Produce and encrypt keystores
const keystoresStr = await getKeystoresStr(passphrase, secretKeys);
@@ -92,7 +96,6 @@ describe("import keystores from api", function () {
// Attempt to run a second process and expect the keystore lock to throw
const validator = await spawnCliCommand("packages/cli/bin/lodestar.js", ["validator", "--dataDir", dataDir], {
logPrefix: "vc-2",
testContext: {beforeEach, afterEach, afterAll},
});
await new Promise<void>((resolve, reject) => {
@@ -117,8 +120,10 @@ describe("import keystores from api", function () {
});
it("run 'validator' check keys are loaded + delete", async function () {
const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir});
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
// Check that keys imported in previous it() are still there
await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys before deleting");
@@ -135,13 +140,20 @@ describe("import keystores from api", function () {
});
it("different process check no keys are loaded", async function () {
const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir});
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
// After deleting there should be no keys
await expectKeys(keymanagerClient, [], "Wrong listKeys");
});
it("reject calls without bearerToken", async function () {
await startValidatorWithKeyManager([], {dataDir});
const {stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
const keymanagerClientNoAuth = getClient(
{baseUrl: "http://localhost:38011", globalInit: {bearerToken: undefined}},

View File

@@ -1,5 +1,5 @@
import path from "node:path";
import {describe, it, expect, beforeAll, vi} from "vitest";
import {describe, it, expect, beforeAll, vi, onTestFinished} from "vitest";
import {rimraf} from "rimraf";
import {ApiClient, DeleteRemoteKeyStatus, getClient, ImportRemoteKeyStatus} from "@lodestar/api/keymanager";
import {config} from "@lodestar/config/default";
@@ -33,7 +33,10 @@ describe("import remoteKeys from api", function () {
const pubkeysToAdd = [cachedPubkeysHex[0], cachedPubkeysHex[1]];
it("run 'validator' and import remote keys from API", async () => {
const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir});
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
// Wrap in retry since the API may not be listening yet
await expectKeys(keymanagerClient, [], "Wrong listRemoteKeys before importing");
@@ -63,7 +66,11 @@ describe("import remoteKeys from api", function () {
});
it("run 'validator' check keys are loaded + delete", async function () {
const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir});
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
// Check that keys imported in previous it() are still there
await expectKeys(keymanagerClient, pubkeysToAdd, "Wrong listRemoteKeys before deleting");
@@ -80,7 +87,11 @@ describe("import remoteKeys from api", function () {
});
it("reject calls without bearerToken", async function () {
await startValidatorWithKeyManager([], {dataDir});
const {stopValidator} = await startValidatorWithKeyManager([], {dataDir});
onTestFinished(async () => {
await stopValidator();
});
const keymanagerUrl = "http://localhost:38011";
const keymanagerClientNoAuth = getClient({baseUrl: keymanagerUrl, globalInit: {bearerToken: undefined}}, {config});
const res = await keymanagerClientNoAuth.listRemoteKeys();

View File

@@ -1,5 +1,5 @@
import path from "node:path";
import {describe, it, beforeAll, vi} from "vitest";
import {describe, it, beforeAll, vi, onTestFinished} from "vitest";
import {rimraf} from "rimraf";
import {ImportStatus} from "@lodestar/api/keymanager";
import {Interchange} from "@lodestar/validator";
@@ -49,9 +49,16 @@ describe("import keystores from api, test DefaultProposerConfig", function () {
const slashingProtectionStr = JSON.stringify(slashingProtection);
it("1 . run 'validator' import keys from API, getdefaultfeeRecipient", async () => {
const {keymanagerClient} = await startValidatorWithKeyManager([`--graffiti ${defaultOptions.graffiti}`], {
dataDir,
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager(
[`--graffiti ${defaultOptions.graffiti}`],
{
dataDir,
}
);
onTestFinished(async () => {
await stopValidator();
});
// Produce and encrypt keystores
// Import test keys
const keystoresStr = await getKeystoresStr(passphrase, secretKeys);
@@ -108,8 +115,14 @@ describe("import keystores from api, test DefaultProposerConfig", function () {
});
it("2 . run 'validator' Check last feeRecipient and gasLimit persists", async () => {
const {keymanagerClient} = await startValidatorWithKeyManager([`--graffiti ${defaultOptions.graffiti}`], {
dataDir,
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager(
[`--graffiti ${defaultOptions.graffiti}`],
{
dataDir,
}
);
onTestFinished(async () => {
await stopValidator();
});
// next time check edited feeRecipient persists
@@ -164,8 +177,14 @@ describe("import keystores from api, test DefaultProposerConfig", function () {
});
it("3 . run 'validator' FeeRecipient and GasLimit should be default after delete", async () => {
const {keymanagerClient} = await startValidatorWithKeyManager([`--graffiti ${defaultOptions.graffiti}`], {
dataDir,
const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager(
[`--graffiti ${defaultOptions.graffiti}`],
{
dataDir,
}
);
onTestFinished(async () => {
await stopValidator();
});
const feeRecipient0 = (await keymanagerClient.listFeeRecipient({pubkey: pubkeys[0]})).value();

View File

@@ -1,8 +1,8 @@
import {describe, it, vi, beforeEach, afterEach, afterAll} from "vitest";
import {describe, it, vi, onTestFinished} from "vitest";
import {getClient} from "@lodestar/api";
import {config} from "@lodestar/config/default";
import {retry} from "@lodestar/utils";
import {spawnCliCommand} from "@lodestar/test-utils";
import {spawnCliCommand, stopChildProcess} from "@lodestar/test-utils";
describe("Run dev command", function () {
vi.setConfig({testTimeout: 30_000});
@@ -13,8 +13,11 @@ describe("Run dev command", function () {
const devProc = await spawnCliCommand(
"packages/cli/bin/lodestar.js",
["dev", "--reset", "--startValidators=0..7", `--rest.port=${beaconPort}`],
{pipeStdioToParent: true, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}}
{pipeStdioToParent: true, logPrefix: "dev"}
);
onTestFinished(async () => {
await stopChildProcess(devProc);
});
// Exit early if process exits
devProc.on("exit", (code) => {

View File

@@ -1,10 +1,10 @@
import path from "node:path";
import {afterAll, describe, it, vi, beforeEach, afterEach} from "vitest";
import {describe, it, vi, onTestFinished} from "vitest";
import {retry} from "@lodestar/utils";
import {getClient} from "@lodestar/api";
import {config} from "@lodestar/config/default";
import {interopSecretKey} from "@lodestar/state-transition";
import {spawnCliCommand, execCliCommand} from "@lodestar/test-utils";
import {spawnCliCommand, execCliCommand, stopChildProcess} from "@lodestar/test-utils";
import {testFilesDir} from "../utils.js";
describe("voluntaryExit cmd", function () {
@@ -28,14 +28,11 @@ describe("voluntaryExit cmd", function () {
// Allow voluntary exists to be valid immediately
"--params.SHARD_COMMITTEE_PERIOD=0",
],
{pipeStdioToParent: true, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}}
{pipeStdioToParent: true, logPrefix: "dev"}
);
// Exit early if process exits
devBnProc.on("exit", (code) => {
if (code !== null && code > 0) {
throw new Error(`devBnProc process exited with code ${code}`);
}
onTestFinished(async () => {
await stopChildProcess(devBnProc, "SIGINT");
});
const baseUrl = `http://127.0.0.1:${restPort}`;

View File

@@ -1,10 +1,10 @@
import path from "node:path";
import {describe, it, vi, expect, afterAll, beforeEach, afterEach} from "vitest";
import {describe, it, vi, expect, onTestFinished} from "vitest";
import {getClient} from "@lodestar/api";
import {getClient as getKeymanagerClient} from "@lodestar/api/keymanager";
import {config} from "@lodestar/config/default";
import {interopSecretKey} from "@lodestar/state-transition";
import {spawnCliCommand} from "@lodestar/test-utils";
import {spawnCliCommand, stopChildProcess} from "@lodestar/test-utils";
import {retry} from "@lodestar/utils";
import {testFilesDir} from "../utils.js";
@@ -37,8 +37,11 @@ describe("voluntary exit from api", function () {
// Disable bearer token auth to simplify testing
"--keymanager.auth=false",
],
{pipeStdioToParent: false, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}}
{pipeStdioToParent: false, logPrefix: "dev"}
);
onTestFinished(async () => {
await stopChildProcess(devProc);
});
// Exit early if process exits
devProc.on("exit", (code) => {

View File

@@ -1,5 +1,5 @@
import path from "node:path";
import {describe, it, beforeAll, afterAll, beforeEach, afterEach, vi} from "vitest";
import {describe, it, beforeAll, afterAll, vi, onTestFinished} from "vitest";
import {retry} from "@lodestar/utils";
import {getClient} from "@lodestar/api";
import {config} from "@lodestar/config/default";
@@ -10,6 +10,7 @@ import {
startExternalSigner,
StartedExternalSigner,
getKeystoresStr,
stopChildProcess,
} from "@lodestar/test-utils";
import {testFilesDir} from "../utils.js";
@@ -50,8 +51,11 @@ describe("voluntaryExit using remote signer", function () {
// Allow voluntary exists to be valid immediately
"--params.SHARD_COMMITTEE_PERIOD=0",
],
{pipeStdioToParent: false, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}}
{pipeStdioToParent: false, logPrefix: "dev"}
);
onTestFinished(async () => {
await stopChildProcess(devBnProc);
});
// Exit early if process exits
devBnProc.on("exit", (code) => {

View File

@@ -1,5 +1,4 @@
import childProcess from "node:child_process";
import {afterEach} from "vitest";
import {retry} from "@lodestar/utils";
import {ApiClient, getClient} from "@lodestar/api/keymanager";
import {config} from "@lodestar/config/default";
@@ -73,8 +72,6 @@ export async function startValidatorWithKeyManager(
await gracefullyStopChildProcess(validatorProc, 3000);
};
afterEach(stopValidator);
return {
validator: validatorProc,
stopValidator,

View File

@@ -67,12 +67,12 @@
"rimraf": "^4.4.1",
"snappyjs": "^0.7.0",
"tar": "^6.1.13",
"vitest": "^1.2.1"
"vitest": "^2.0.4"
},
"devDependencies": {
"@types/tar": "^6.1.4"
},
"peerDependencies": {
"vitest": "^1.2.1"
"vitest": "^2.0.4"
}
}

View File

@@ -64,12 +64,12 @@
"axios": "^1.3.4",
"testcontainers": "^10.2.1",
"tmp": "^0.2.1",
"vitest": "^1.2.1"
"vitest": "^2.0.4"
},
"devDependencies": {
"@types/yargs": "^17.0.24"
},
"peerDependencies": {
"vitest": "^1.2.1"
"vitest": "^2.0.4"
}
}

View File

@@ -4,7 +4,6 @@ import stream from "node:stream";
import fs from "node:fs";
import path from "node:path";
import {prettyMsToTime, retry, sleep, Logger} from "@lodestar/utils";
import {TestContext} from "./interfaces.js";
export type ChildProcessLogOptions = {
/**
@@ -190,10 +189,6 @@ export type SpawnChildProcessOptions = Partial<HealthCheckOptions> &
* Child process resolve behavior
*/
resolveOn?: ChildProcessResolve;
/**
* Test context to pass to child process. Useful for testing to close the process after test case
*/
testContext?: TestContext;
/**
* Abort signal to stop child process
*/
@@ -258,7 +253,7 @@ export async function spawnChildProcess(
): Promise<childProcess.ChildProcessWithoutNullStreams> {
const options = {...defaultStartOpts, ...opts} as SpawnChildProcessOptions;
const {env, signal, health, resolveOn, healthCheckIntervalMs, logHealthChecksAfterMs, healthTimeoutMs} = options;
const {logPrefix, testContext} = options;
const {logPrefix} = options;
return new Promise<childProcess.ChildProcessWithoutNullStreams>((resolve, reject) => {
void (async () => {
@@ -268,14 +263,6 @@ export async function spawnChildProcess(
handleLoggingForChildProcess(proc, options);
if (testContext) {
testContext.afterEach(async () => {
proc.kill("SIGINT");
await sleep(1000, signal);
await stopChildProcess(proc);
});
}
if (signal) {
signal.addEventListener(
"abort",

View File

@@ -5,4 +5,3 @@ export * from "./keystores.js";
export * from "./path.js";
export * from "./timeout.js";
export * from "./http.js";
export * from "./interfaces.js";

View File

@@ -1,5 +0,0 @@
export interface TestContext {
afterEach: (cb: () => Promise<void> | void) => void;
beforeEach: (cb: () => Promise<void> | void) => void;
afterAll: (cb: () => Promise<void> | void) => void;
}

View File

@@ -3,7 +3,7 @@ import {defineConfig} from "vitest/config";
const __dirname = new URL(".", import.meta.url).pathname;
import {nodePolyfills} from "vite-plugin-node-polyfills";
import topLevelAwait from "vite-plugin-top-level-await";
import { blsBrowserPlugin } from "./scripts/vite/plugins/blsBrowserPlugin";
import {blsBrowserPlugin} from "./scripts/vite/plugins/blsBrowserPlugin.js";
export default defineConfig({
plugins: [
@@ -14,6 +14,19 @@ export default defineConfig({
globals: {Buffer: true, process: true},
protocolImports: true,
}),
// TODO: Should be removed when the vite issue is fixed
// https://github.com/vitest-dev/vitest/issues/6203#issuecomment-2245836028
{
name: "defineArgv",
config() {
return {
define: {
"process.argv": "[]",
"process.nextTick": "function noop(){}",
},
};
},
},
],
test: {
include: ["**/*.test.ts"],
@@ -35,7 +48,7 @@ export default defineConfig({
name: "chrome",
headless: true,
provider: "webdriverio",
slowHijackESM: false,
screenshotFailures: false,
providerOptions: {
capabilities: {
browserVersion: "latest",

1418
yarn.lock

File diff suppressed because it is too large Load Diff