mirror of
https://github.com/ChainSafe/lodestar.git
synced 2026-01-10 08:08:16 -05:00
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:
16
package.json
16
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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)[] = [];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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}},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5,4 +5,3 @@ export * from "./keystores.js";
|
||||
export * from "./path.js";
|
||||
export * from "./timeout.js";
|
||||
export * from "./http.js";
|
||||
export * from "./interfaces.js";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user