test(prover): move tests for prover to vitest (#6074)

* Automatic assertion migration

* Fix some left over assertions

* Remove sinon

* Add config files

* Move browser tests to vitest

* Fix browser tests

* Add a skip lib check for now

* Fix the spellings

* Fix deprecated flags

* Fix types

* Update the script tasks

* Fix the e2e tests

* Fix e2e tests

* Update webdriverio to fix browser installtion issue

* Fix the vitest config

* Add a peer dependency

* Add vite resolution

* Add a manual install for browsers

* Update packages to support additional browser options

* Update the resolutions

* Add resolutions to fix dependencies

* Update the yarn linter script

* Add sigfault handler

* Rebuild native modules with debug flag

* Update depedencies

* Update e2e tests

* Update dependencies

* Add browser capabilities

* Revert a whitespace change

* Remove segfault handler

* Remove native dep for lightclient

* Add debug flag to introspect error

* Rename a file

* Upgrade the vitest package

* Fix the regressions introduced in vitest 1 >

* Revert the regression of vitest 1.0.1

* Clean dependencies

* Update multiple fork fix

* Add dependency fix

* Fix broken singleThread option for e2e
This commit is contained in:
Nazar Hussain
2023-12-08 10:30:08 +01:00
committed by GitHub
parent 7935caf280
commit 53378e1e08
46 changed files with 2139 additions and 637 deletions

View File

@@ -270,7 +270,10 @@ jobs:
packages/*/.git-data.json
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ github.sha }}
fail-on-cache-miss: true
- name: Install Chrome browser
run: npx @puppeteer/browsers install chrome
- name: Install Firefox browser
run: npx @puppeteer/browsers install firefox
- name: Browser tests
run: |
export DISPLAY=':99.0'

View File

@@ -52,6 +52,8 @@
"@types/sinon-chai": "^3.2.9",
"@typescript-eslint/eslint-plugin": "6.7.2",
"@typescript-eslint/parser": "6.7.2",
"@vitest/coverage-v8": "^1.0.1",
"@vitest/browser": "^1.0.1",
"c8": "^8.0.1",
"chai": "^4.3.8",
"chai-as-promised": "^7.1.1",
@@ -59,12 +61,13 @@
"crypto-browserify": "^3.12.0",
"electron": "^26.2.2",
"eslint": "^8.50.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-chai-expect": "^3.0.0",
"eslint-plugin-mocha": "^10.2.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-chai-expect": "^3.0.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-mocha": "^10.2.0",
"eslint-plugin-prettier": "^5.0.0",
"https-browserify": "^1.0.0",
"jsdom": "^23.0.1",
"karma": "^6.4.2",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.2.0",
@@ -93,13 +96,17 @@
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
"typescript-docs-verifier": "^2.5.0",
"webpack": "^5.88.2",
"vite-plugin-node-polyfills": "^0.17.0",
"vite-plugin-top-level-await": "^1.3.1",
"vitest": "^1.0.2",
"vitest-when": "^0.3.0",
"wait-port": "^1.1.0",
"vitest": "^0.34.6",
"vitest-when": "^0.2.0",
"@vitest/coverage-v8": "^0.34.6"
"webdriverio": "^8.24.12",
"webpack": "^5.88.2"
},
"resolutions": {
"dns-over-http-resolver": "^2.1.1"
"dns-over-http-resolver": "^2.1.1",
"chai": "^4.3.10",
"loupe": "^2.3.6"
}
}

View File

@@ -80,7 +80,7 @@
"test:unit:minimal": "vitest --run --segfaultRetry 3 --dir test/unit/ --coverage",
"test:unit:mainnet": "LODESTAR_PRESET=mainnet nyc --cache-dir .nyc_output/.cache -e .ts mocha 'test/unit-mainnet/**/*.test.ts'",
"test:unit": "yarn test:unit:minimal && yarn test:unit:mainnet",
"test:e2e": "LODESTAR_PRESET=minimal vitest --run --segfaultRetry 3 --single-thread --dir test/e2e",
"test:e2e": "LODESTAR_PRESET=minimal vitest --run --segfaultRetry 3 --poolOptions.threads.singleThread --dir test/e2e",
"test:sim": "mocha 'test/sim/**/*.test.ts'",
"test:sim:merge-interop": "mocha 'test/sim/merge-interop.test.ts'",
"test:sim:mergemock": "mocha 'test/sim/mergemock.test.ts'",

View File

@@ -38,7 +38,7 @@ describe("chain / opPools / SyncCommitteeMessagePool", function () {
vi.clearAllMocks();
});
it("should preaggregate SyncCommitteeContribution", () => {
it("should propagate SyncCommitteeContribution", () => {
clockStub.secFromSlot.mockReturnValue(0);
let contribution = cache.getContribution(subcommitteeIndex, syncCommittee.slot, syncCommittee.beaconBlockRoot);
expect(contribution).not.toBeNull();

View File

@@ -1,4 +1,4 @@
import {describe, it, expect, beforeEach, afterEach, vi, SpyInstance, Mock} from "vitest";
import {describe, it, expect, beforeEach, afterEach, vi, Mock, MockInstance} from "vitest";
import {config} from "@lodestar/config/default";
import {ForkName, SLOTS_PER_EPOCH} from "@lodestar/params";
import {routes} from "@lodestar/api";
@@ -20,7 +20,7 @@ describe("PrepareNextSlot scheduler", () => {
let regenStub: MockedBeaconChain["regen"];
let loggerStub: MockedLogger;
let beaconProposerCacheStub: MockedBeaconChain["beaconProposerCache"];
let getForkStub: SpyInstance<[number], ForkName>;
let getForkStub: MockInstance<[number], ForkName>;
let updateBuilderStatus: MockedBeaconChain["updateBuilderStatus"];
let executionEngineStub: MockedBeaconChain["executionEngine"];
const emitPayloadAttributes = true;

View File

@@ -1,4 +1,4 @@
import {describe, it, expect, beforeEach, afterEach, vi, SpyInstance} from "vitest";
import {describe, it, expect, beforeEach, afterEach, vi, MockInstance} from "vitest";
import {config} from "@lodestar/config/default";
import {TimeoutError} from "@lodestar/utils";
import {Eth1DepositDataTracker} from "../../../src/eth1/eth1DepositDataTracker.js";
@@ -17,8 +17,8 @@ describe("Eth1DepositDataTracker", function () {
const eth1Provider = new Eth1Provider(config, opts, signal, null);
let db: BeaconDb;
let eth1DepositDataTracker: Eth1DepositDataTracker;
let getBlocksByNumberStub: SpyInstance;
let getDepositEventsStub: SpyInstance;
let getBlocksByNumberStub: MockInstance;
let getDepositEventsStub: MockInstance;
beforeEach(() => {
db = getMockedBeaconDb();

View File

@@ -1,4 +1,4 @@
import {describe, it, expect, beforeEach, beforeAll, afterAll, vi, afterEach, SpyInstance} from "vitest";
import {describe, it, expect, beforeEach, beforeAll, afterAll, vi, afterEach, MockInstance} from "vitest";
import {ErrorAborted, TimeoutError} from "@lodestar/utils";
import {RegistryMetricCreator} from "../../../src/index.js";
import {HistogramExtra} from "../../../src/metrics/utils/histogram.js";
@@ -115,7 +115,7 @@ describe("monitoring / service", () => {
});
describe("MonitoringService - close", () => {
let clearTimeout: SpyInstance;
let clearTimeout: MockInstance;
beforeAll(() => {
clearTimeout = vi.spyOn(global, "clearTimeout");

View File

@@ -2,6 +2,7 @@
import path from "node:path";
import {sleep, toHex, toHexString} from "@lodestar/utils";
import {ApiError} from "@lodestar/api";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {CLIQUE_SEALING_PERIOD, SIM_TESTS_SECONDS_PER_SLOT} from "../utils/simulation/constants.js";
import {AssertionMatch, BeaconClient, ExecutionClient, ValidatorClient} from "../utils/simulation/interfaces.js";
import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js";
@@ -213,7 +214,8 @@ const unknownBlockSync = await env.createNodePair({
// unknown block sync can work only if the gap is maximum `slotImportTolerance * 2`
// default value for slotImportTolerance is one epoch, so if gap is more than 2 epoch
// unknown block sync will not work. So why we have to increase it for tests.
"sync.slotImportTolerance": headForUnknownBlockSync.response.data.message.slot / 2 + 2,
// Adding SLOTS_PER_EPOCH will cover the case if the node starts on the last slot of epoch
"sync.slotImportTolerance": headForUnknownBlockSync.response.data.message.slot / 2 + SLOTS_PER_EPOCH,
},
},
},

View File

@@ -1,8 +0,0 @@
colors: true
timeout: 2000
exit: true
extension: ["ts"]
require:
- ./test/setup.ts
node-option:
- "loader=ts-node/esm"

View File

@@ -1,3 +0,0 @@
{
"extends": "../../.nycrc.json"
}

View File

@@ -1,9 +0,0 @@
const karmaConfig = require("../../karma.base.config.js");
const webpackConfig = require("./webpack.test.config.cjs");
module.exports = function karmaConfigurator(config) {
config.set({
...karmaConfig,
webpack: webpackConfig,
});
};

View File

@@ -53,9 +53,12 @@
"lint:fix": "yarn run lint --fix",
"pretest": "yarn run check-types",
"test": "yarn test:unit && yarn test:e2e",
"test:unit": "nyc --cache-dir .nyc_output/.cache -e .ts mocha 'test/unit/**/*.test.ts'",
"test:browsers": "yarn karma start karma.config.cjs",
"test:e2e": "LODESTAR_PRESET=minimal mocha 'test/e2e/**/*.test.ts'",
"test:unit": "vitest --run --dir test/unit/ --coverage",
"test:browsers": "yarn test:browsers:chrome && yarn test:browsers:firefox && yarn test:browsers:electron",
"test:browsers:chrome": "vitest --run --browser chrome --config ./vitest.browser.config.ts --dir test/unit",
"test:browsers:firefox": "vitest --run --browser firefox --config ./vitest.browser.config.ts --dir test/unit",
"test:browsers:electron": "echo 'Electron tests will be introduced back in the future as soon vitest supports electron.'",
"test:e2e": "LODESTAR_PRESET=minimal vitest --run --poolOptions.threads.singleThread --dir test/e2e",
"check-readme": "typescript-docs-verifier",
"generate-fixtures": "node --loader ts-node/esm scripts/generate_fixtures.ts"
},

View File

@@ -1,7 +1,7 @@
import childProcess from "node:child_process";
import {writeFile, mkdir} from "node:fs/promises";
import path from "node:path";
import {expect} from "chai";
import {describe, it, expect, beforeAll, afterAll} from "vitest";
import Web3 from "web3";
import {runCliCommand, spawnCliCommand, stopChildProcess} from "@lodestar/test-utils";
import {sleep} from "@lodestar/utils";
@@ -15,11 +15,11 @@ describe("prover/start", () => {
it("should show help", async () => {
const output = await runCliCommand(cli, ["start", "--help"]);
expect(output).contains("Show help");
expect(output).toEqual(expect.stringContaining("Show help"));
});
it("should fail when --executionRpcUrl is missing", async () => {
await expect(runCliCommand(cli, ["start", "--port", "8088"])).eventually.rejectedWith(
await expect(runCliCommand(cli, ["start", "--port", "8088"])).rejects.toThrow(
"Missing required argument: executionRpcUrl"
);
});
@@ -33,13 +33,13 @@ describe("prover/start", () => {
"--beaconBootnodes",
"http://localhost:0000",
])
).eventually.rejectedWith("Arguments beaconBootnodes and beaconUrls are mutually exclusive");
).rejects.toThrow("Arguments beaconBootnodes and beaconUrls are mutually exclusive");
});
it("should fail when both of --beaconUrls and --beaconBootnodes are not provided", async () => {
await expect(
runCliCommand(cli, ["start", "--port", "8088", "--executionRpcUrl", "http://localhost:3000"])
).eventually.rejectedWith("Either --beaconUrls or --beaconBootnodes must be provided");
).rejects.toThrow("Either --beaconUrls or --beaconBootnodes must be provided");
});
describe("when started", () => {
@@ -47,8 +47,7 @@ describe("prover/start", () => {
const paramsFilePath = path.join("/tmp", "e2e-test-env", "params.json");
const web3: Web3 = new Web3(proxyUrl);
before(async function () {
this.timeout(50000);
beforeAll(async function () {
await waitForCapellaFork();
await mkdir(path.dirname(paramsFilePath), {recursive: true});
await writeFile(paramsFilePath, JSON.stringify(chainConfigToJson(config as ChainConfig)));
@@ -72,22 +71,22 @@ describe("prover/start", () => {
);
// Give sometime to the prover to start proxy server
await sleep(3000);
});
}, 50000);
after(async () => {
afterAll(async () => {
await stopChildProcess(proc);
});
it("should respond to verified calls", async () => {
const accounts = await web3.eth.getAccounts();
expect(accounts.length).to.be.gt(0);
await expect(web3.eth.getBalance(accounts[0])).eventually.not.null;
expect(accounts.length).toBeGreaterThan(0);
await expect(web3.eth.getBalance(accounts[0])).resolves.not.toBeNull();
});
it("should respond to unverified calls", async () => {
// Because web3 latest version return numbers as bigint by default
await expect(web3.eth.getChainId()).eventually.eql(BigInt(chainId));
await expect(web3.eth.getChainId()).resolves.toEqual(BigInt(chainId));
});
});
});

View File

@@ -1,18 +1,16 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {expect} from "chai";
import {describe, it, expect, beforeAll} from "vitest";
import Web3 from "web3";
import {LCTransport} from "../../src/interfaces.js";
import {createVerifiedExecutionProvider} from "../../src/web3_provider.js";
import {rpcUrl, beaconUrl, config} from "../utils/e2e_env.js";
import {getVerificationFailedMessage} from "../../src/utils/json_rpc.js";
/* prettier-ignore */
describe("web3_batch_requests", function () {
// Give some margin to sync light client
this.timeout("10s");
let web3: Web3;
before(() => {
beforeAll(() => {
const {provider} = createVerifiedExecutionProvider(new Web3.providers.HttpProvider(rpcUrl), {
transport: LCTransport.Rest,
urls: [beaconUrl],
@@ -45,8 +43,8 @@ describe("web3_batch_requests", function () {
await batch.execute();
expect(results.length).to.be.gt(1);
await expect(Promise.all(results)).to.be.fulfilled;
expect(results.length).toBeGreaterThan(1);
await expect(Promise.all(results)).resolves.toBeDefined();
});
it("should be able to process batch request containing error", async () => {
@@ -66,8 +64,8 @@ describe("web3_batch_requests", function () {
await batch.execute();
await expect(successRequest).to.be.fulfilled;
await expect(errorRequest).to.be.rejectedWith(getVerificationFailedMessage("eth_getBlockByHash"));
await expect(successRequest).resolves.toBeDefined();
await expect(errorRequest).rejects.toThrow(getVerificationFailedMessage("eth_getBlockByHash"));
});
});
});
}, {timeout: 10_000});

View File

@@ -1,15 +1,14 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {expect} from "chai";
import {describe, it, expect, beforeAll} from "vitest";
import Web3 from "web3";
import {ethers} from "ethers";
import {LCTransport} from "../../src/interfaces.js";
import {createVerifiedExecutionProvider} from "../../src/web3_provider.js";
import {waitForCapellaFork, testTimeout, rpcUrl, beaconUrl, config} from "../utils/e2e_env.js";
/* prettier-ignore */
describe("web3_provider", function () {
this.timeout(testTimeout);
before("wait for the capella fork", async () => {
beforeAll(async () => {
await waitForCapellaFork();
});
@@ -26,8 +25,8 @@ describe("web3_provider", function () {
const accounts = await web3.eth.getAccounts();
// `getProof` will always remain the non-verified method
// as we use it to create proof and verify
expect(accounts).not.to.be.empty;
await expect(web3.eth.getProof(accounts[0], [], "latest")).fulfilled;
expect(Object.keys(accounts)).not.toHaveLength(0);
await expect(web3.eth.getProof(accounts[0], [], "latest")).resolves.toBeDefined();
});
});
@@ -40,9 +39,9 @@ describe("web3_provider", function () {
});
const accounts = await provider.listAccounts();
expect(accounts).not.to.be.empty;
await expect(provider.send("eth_getProof", [accounts[0].address, [], "latest"])).fulfilled;
expect(Object.keys(accounts)).not.toHaveLength(0);
await expect(provider.send("eth_getProof", [accounts[0].address, [], "latest"])).resolves.toBeDefined();
});
});
});
});
}, {timeout: testTimeout});

View File

@@ -0,0 +1,2 @@
export async function setup(): Promise<void> {}
export async function teardown(): Promise<void> {}

View File

@@ -1,4 +1,5 @@
import sinon from "sinon";
import {vi, expect} from "vitest";
import {when} from "vitest-when";
import deepmerge from "deepmerge";
import {NetworkName} from "@lodestar/config/networks";
import {ForkConfig} from "@lodestar/config";
@@ -51,28 +52,29 @@ export interface TestFixture<R = unknown, P = unknown[]> {
dependentRequests: {payload: JsonRpcRequestOrBatch; response: Writeable<JsonRpcResponseOrBatch>}[];
}
function matchTransaction(value: ELTransaction, expected: ELTransaction): boolean {
if (
value.to?.toLowerCase() !== expected.to?.toLowerCase() ||
value.from.toLocaleLowerCase() !== expected.from.toLowerCase()
) {
function matchTransaction(received: ELTransaction, expected: ELTransaction): boolean {
if (received.to?.toLowerCase() !== expected.to?.toLowerCase()) {
return false;
}
if ("value" in value && value.value.toLowerCase() !== expected.value.toLowerCase()) {
if ("from" in expected && "from" in received && received.from.toLowerCase() !== expected.from.toLowerCase()) {
return false;
}
if ("data" in value && value.data?.toLowerCase() !== expected.data?.toLowerCase()) {
if ("value" in received && received.value.toLowerCase() !== expected.value.toLowerCase()) {
return false;
}
if ("data" in received && received.data?.toLowerCase() !== expected.data?.toLowerCase()) {
return false;
}
return true;
}
function matchParams(params: unknown[], expected: unknown[]): boolean {
for (let i = 0; i < params.length; i++) {
const item = params[i];
function matchParams(received: unknown[], expected: unknown[]): boolean {
for (let i = 0; i < received.length; i++) {
const item = received[i];
const expectedItem = expected[i];
if (typeof item === "string" && typeof expectedItem === "string") {
@@ -92,20 +94,12 @@ function matchParams(params: unknown[], expected: unknown[]): boolean {
return true;
}
function getPayloadParamsMatcher(expected: unknown[]): sinon.SinonMatcher {
return sinon.match(function (params: unknown[]): boolean {
return matchParams(params, expected);
}, "payload match params");
}
function getBatchPayloadMatcher(expected: JsonRpcBatchRequest): sinon.SinonMatcher {
return sinon.match(function (value: JsonRpcBatchRequest): boolean {
for (const [index, item] of value.entries()) {
if (item.method !== expected[index].method) return false;
if (!matchParams(item.params, expected[index].params)) return false;
}
return true;
}, "batch payload match");
function matchBatchPayload(received: JsonRpcBatchRequest, expected: JsonRpcBatchRequest): boolean {
for (const [index, item] of received.entries()) {
if (item.method !== expected[index].method) return false;
if (!matchParams(item.params, expected[index].params)) return false;
}
return true;
}
export function generateReqHandlerOptionsMock(
@@ -119,7 +113,7 @@ export function generateReqHandlerOptionsMock(
const options = {
logger: getEmptyLogger(),
proofProvider: {
getExecutionPayload: sinon.stub().resolves(executionPayload),
getExecutionPayload: vi.fn().mockResolvedValue(executionPayload),
config: {
...config,
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -129,35 +123,46 @@ export function generateReqHandlerOptionsMock(
} as unknown as ProofProvider,
network: data.network as NetworkName,
rpc: {
request: sinon.stub(),
batchRequest: sinon.stub(),
request: vi.fn(),
batchRequest: vi.fn(),
getRequestId: () => (Math.random() * 10000).toFixed(0),
},
};
options.rpc.request
.withArgs(data.request.method, getPayloadParamsMatcher(data.request.params), sinon.match.any)
.resolves(data.response);
when(options.rpc.request)
.calledWith(
data.request.method,
expect.toSatisfy((received) => matchParams(received as unknown[], data.request.params)),
expect.anything()
)
.thenResolve(data.response);
for (const {payload, response} of data.dependentRequests) {
if (isBatchRequest(payload)) {
options.rpc.batchRequest
.withArgs(getBatchPayloadMatcher(payload), sinon.match.any)
.resolves(mergeBatchReqResp(payload, response as JsonRpcBatchResponse));
when(options.rpc.batchRequest)
.calledWith(
expect.toSatisfy((received) => matchBatchPayload(received as JsonRpcBatchRequest, payload)),
expect.anything()
)
.thenResolve(mergeBatchReqResp(payload, response as JsonRpcBatchResponse));
} else {
options.rpc.request
.withArgs(payload.method, getPayloadParamsMatcher(payload.params), sinon.match.any)
.resolves(response);
when(options.rpc.request)
.calledWith(
payload.method,
expect.toSatisfy((received) => matchParams(received as unknown[], data.request.params)),
expect.anything()
)
.thenResolve(response);
}
}
options.rpc.request
.withArgs("eth_getBlockByNumber", [data.execution.block.number, true], sinon.match.any)
.resolves({id: 1233, jsonrpc: "2.0", result: data.execution.block});
when(options.rpc.request)
.calledWith("eth_getBlockByNumber", [data.execution.block.number, true], expect.anything())
.thenResolve({id: 1233, jsonrpc: "2.0", result: data.execution.block});
options.rpc.request
.withArgs("eth_getBlockByHash", [data.execution.block.hash, true], sinon.match.any)
.resolves({id: 1233, jsonrpc: "2.0", result: data.execution.block});
when(options.rpc.request)
.calledWith("eth_getBlockByHash", [data.execution.block.hash, true], expect.anything())
.thenResolve({id: 1233, jsonrpc: "2.0", result: data.execution.block});
return options as unknown as Omit<ELVerifiedRequestHandlerOpts<any>, "payload">;
}

View File

@@ -1,6 +0,0 @@
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import sinonChai from "sinon-chai";
chai.use(chaiAsPromised);
chai.use(sinonChai);

View File

@@ -0,0 +1,6 @@
{
"extends": "../tsconfig",
"compilerOptions": {
"noEmit": false
}
}

View File

@@ -1,25 +1,25 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {OrderedMap} from "../../../src/proof_provider/ordered_map.js";
describe("proof_provider/ordered_map", () => {
it("should initialize the min with undefined", () => {
const omap = new OrderedMap<string>();
expect(omap.min).to.undefined;
expect(omap.min).toBeUndefined();
});
it("should initialize the max with undefined", () => {
const omap = new OrderedMap<string>();
expect(omap.max).to.undefined;
expect(omap.max).toBeUndefined();
});
it("should set the min and max to the first value ", () => {
const omap = new OrderedMap<string>();
omap.set(11, "value");
expect(omap.min).eql(11);
expect(omap.max).eql(11);
expect(omap.min).toEqual(11);
expect(omap.max).toEqual(11);
});
it("should set the max value", () => {
@@ -27,7 +27,7 @@ describe("proof_provider/ordered_map", () => {
omap.set(10, "value");
omap.set(11, "value");
expect(omap.max).eql(11);
expect(omap.max).toEqual(11);
});
it("should set the min value", () => {
@@ -35,6 +35,6 @@ describe("proof_provider/ordered_map", () => {
omap.set(10, "value");
omap.set(11, "value");
expect(omap.min).eql(10);
expect(omap.min).toEqual(10);
});
});

View File

@@ -1,17 +1,14 @@
import {expect} from "chai";
import chai from "chai";
import sinon from "sinon";
import sinonChai from "sinon-chai";
import {Api} from "@lodestar/api";
import {describe, it, expect, beforeEach, vi, MockedObject} from "vitest";
import {when} from "vitest-when";
import {Api, HttpStatusCode, routes} from "@lodestar/api";
import {hash} from "@lodestar/utils";
import {Logger} from "@lodestar/logger";
import {allForks, capella} from "@lodestar/types";
import {toHexString} from "@lodestar/utils";
import {ForkName} from "@lodestar/params";
import {PayloadStore} from "../../../src/proof_provider/payload_store.js";
import {MAX_PAYLOAD_HISTORY} from "../../../src/constants.js";
chai.use(sinonChai);
const createHash = (input: string): Uint8Array => hash(Buffer.from(input, "utf8"));
const buildPayload = ({blockNumber}: {blockNumber: number}): allForks.ExecutionPayload =>
@@ -47,22 +44,23 @@ const buildBlockResponse = ({
}: {
slot: number;
blockNumber: number;
}): {ok: boolean; response: {version: number; executionOptimistic: boolean; data: allForks.SignedBeaconBlock}} => ({
}): routes.beacon.block.BlockV2Response<"json"> => ({
ok: true,
status: HttpStatusCode.OK,
response: {
version: 12,
version: ForkName.altair,
executionOptimistic: true,
data: buildBlock({slot, blockNumber}),
},
});
describe("proof_provider/payload_store", function () {
let api: Api;
let api: Api & {beacon: MockedObject<Api["beacon"]>};
let logger: Logger;
let store: PayloadStore;
beforeEach(() => {
api = {beacon: {getBlockV2: sinon.stub()}} as unknown as Api;
api = {beacon: {getBlockV2: vi.fn()}} as unknown as Api & {beacon: MockedObject<Api["beacon"]>};
logger = console as unknown as Logger;
store = new PayloadStore({api, logger});
});
@@ -82,7 +80,7 @@ describe("proof_provider/payload_store", function () {
const payload = buildPayload({blockNumber: 10});
store.set(payload, true);
expect(store.finalized).to.eql(payload);
expect(store.finalized).toEqual(payload);
});
it("should return highest finalized payload", () => {
@@ -91,7 +89,7 @@ describe("proof_provider/payload_store", function () {
store.set(payload1, true);
store.set(payload2, true);
expect(store.finalized).to.eql(payload2);
expect(store.finalized).toEqual(payload2);
});
});
@@ -106,7 +104,7 @@ describe("proof_provider/payload_store", function () {
store.set(payload1, true);
store.set(payload2, true);
expect(store.latest).to.eql(payload2);
expect(store.latest).toEqual(payload2);
});
it("should return latest payload if not finalized", () => {
@@ -115,20 +113,20 @@ describe("proof_provider/payload_store", function () {
store.set(payload1, false);
store.set(payload2, false);
expect(store.latest).to.eql(payload2);
expect(store.latest).toEqual(payload2);
});
});
describe("get", () => {
it("should return undefined for an empty store", async () => {
await expect(store.get(10)).to.eventually.undefined;
await expect(store.get(10)).resolves.toBeUndefined();
});
it("should return undefined for non existing block id", async () => {
const payload1 = buildPayload({blockNumber: 10});
store.set(payload1, false);
await expect(store.get(11)).to.eventually.undefined;
await expect(store.get(11)).resolves.toBeUndefined();
});
it("should return undefined for non existing block hash", async () => {
@@ -136,7 +134,7 @@ describe("proof_provider/payload_store", function () {
store.set(payload1, false);
const nonExistingBlockHash = createHash("non-existing-block-hash");
await expect(store.get(toHexString(nonExistingBlockHash))).to.eventually.undefined;
await expect(store.get(toHexString(nonExistingBlockHash))).resolves.toBeUndefined();
});
describe("block hash as blockId", () => {
@@ -144,7 +142,7 @@ describe("proof_provider/payload_store", function () {
const payload1 = buildPayload({blockNumber: 10});
store.set(payload1, false);
await expect(store.get(toHexString(payload1.blockHash))).to.eventually.eql(payload1);
await expect(store.get(toHexString(payload1.blockHash))).resolves.toEqual(payload1);
});
});
@@ -153,7 +151,7 @@ describe("proof_provider/payload_store", function () {
const finalizedPayload = buildPayload({blockNumber: 10});
store.set(finalizedPayload, true);
await expect(store.get(11)).to.rejectedWith(
await expect(store.get(11)).rejects.toThrow(
"Block number 11 is higher than the latest finalized block number. We recommend to use block hash for unfinalized blocks."
);
});
@@ -162,28 +160,28 @@ describe("proof_provider/payload_store", function () {
const payload1 = buildPayload({blockNumber: 10});
store.set(payload1, false);
await expect(store.get(10)).to.eventually.undefined;
await expect(store.get(10)).resolves.toBeUndefined();
});
it("should return payload for a block number in hex", async () => {
const payload1 = buildPayload({blockNumber: 10});
store.set(payload1, true);
await expect(store.get(`0x${payload1.blockNumber.toString(16)}`)).to.eventually.eql(payload1);
await expect(store.get(`0x${payload1.blockNumber.toString(16)}`)).resolves.toEqual(payload1);
});
it("should return payload for a block number as string", async () => {
const payload1 = buildPayload({blockNumber: 10});
store.set(payload1, true);
await expect(store.get(payload1.blockNumber.toString())).to.eventually.eql(payload1);
await expect(store.get(payload1.blockNumber.toString())).resolves.toEqual(payload1);
});
it("should return payload for a block number as integer", async () => {
const payload1 = buildPayload({blockNumber: 10});
store.set(payload1, true);
await expect(store.get(10)).to.eventually.eql(payload1);
await expect(store.get(10)).resolves.toEqual(payload1);
});
it("should fetch the finalized payload from API if payload root not exists", async () => {
@@ -193,22 +191,22 @@ describe("proof_provider/payload_store", function () {
const availablePayload = buildPayload({blockNumber});
const unavailablePayload = buildPayload({blockNumber: unavailableBlockNumber});
(api.beacon.getBlockV2 as sinon.SinonStub)
.withArgs(blockNumber)
.resolves(buildBlockResponse({blockNumber, slot: blockNumber}));
when(api.beacon.getBlockV2)
.calledWith(blockNumber)
.thenResolve(buildBlockResponse({blockNumber, slot: blockNumber}));
(api.beacon.getBlockV2 as sinon.SinonStub)
.withArgs(unavailableBlockNumber)
.resolves(buildBlockResponse({blockNumber: unavailableBlockNumber, slot: unavailableBlockNumber}));
when(api.beacon.getBlockV2)
.calledWith(unavailableBlockNumber)
.thenResolve(buildBlockResponse({blockNumber: unavailableBlockNumber, slot: unavailableBlockNumber}));
store.set(availablePayload, true);
const result = await store.get(unavailablePayload.blockNumber);
expect(api.beacon.getBlockV2 as sinon.SinonStub).calledTwice;
expect(api.beacon.getBlockV2 as sinon.SinonStub).calledWith(blockNumber);
expect(api.beacon.getBlockV2 as sinon.SinonStub).calledWith(unavailableBlockNumber);
expect(result).to.eql(unavailablePayload);
expect(api.beacon.getBlockV2).toHaveBeenCalledTimes(2);
expect(api.beacon.getBlockV2).toHaveBeenCalledWith(blockNumber);
expect(api.beacon.getBlockV2).toHaveBeenCalledWith(unavailableBlockNumber);
expect(result).toEqual(unavailablePayload);
});
});
});
@@ -219,16 +217,16 @@ describe("proof_provider/payload_store", function () {
store.set(payload1, false);
// Unfinalized blocks are not indexed by block hash
await expect(store.get(toHexString(payload1.blockHash))).to.eventually.eql(payload1);
expect(store.finalized).to.eql(undefined);
await expect(store.get(toHexString(payload1.blockHash))).resolves.toEqual(payload1);
expect(store.finalized).toEqual(undefined);
});
it("should set the payload for finalized blocks", async () => {
const payload1 = buildPayload({blockNumber: 10});
store.set(payload1, true);
await expect(store.get(payload1.blockNumber.toString())).to.eventually.eql(payload1);
expect(store.finalized).to.eql(payload1);
await expect(store.get(payload1.blockNumber.toString())).resolves.toEqual(payload1);
expect(store.finalized).toEqual(payload1);
});
});
@@ -243,15 +241,15 @@ describe("proof_provider/payload_store", function () {
const slot = 20;
const header = buildLCHeader({slot, blockNumber});
const blockResponse = buildBlockResponse({blockNumber, slot});
const executionPayload = (blockResponse.response.data as capella.SignedBeaconBlock).message.body
const executionPayload = (blockResponse.response?.data as capella.SignedBeaconBlock).message.body
.executionPayload;
(api.beacon.getBlockV2 as sinon.SinonStub).resolves(blockResponse);
api.beacon.getBlockV2.mockResolvedValue(blockResponse);
await store.processLCHeader(header, true);
expect(api.beacon.getBlockV2).calledOnce;
expect(api.beacon.getBlockV2).calledWith(20);
expect(store.finalized).to.eql(executionPayload);
expect(api.beacon.getBlockV2).toHaveBeenCalledOnce();
expect(api.beacon.getBlockV2).toHaveBeenCalledWith(20);
expect(store.finalized).toEqual(executionPayload);
});
it("should process lightclient header for finalized block which exists as un-finalized in store", async () => {
@@ -259,9 +257,9 @@ describe("proof_provider/payload_store", function () {
const slot = 20;
const header = buildLCHeader({slot, blockNumber});
const blockResponse = buildBlockResponse({blockNumber, slot});
const executionPayload = (blockResponse.response.data as capella.SignedBeaconBlock).message.body
const executionPayload = (blockResponse.response?.data as capella.SignedBeaconBlock).message.body
.executionPayload;
(api.beacon.getBlockV2 as sinon.SinonStub).resolves(blockResponse);
api.beacon.getBlockV2.mockResolvedValue(blockResponse);
expect(store.finalized).to.undefined;
// First process as unfinalized
@@ -271,8 +269,8 @@ describe("proof_provider/payload_store", function () {
await store.processLCHeader(header, true);
// Called only once when we process unfinalized
expect(api.beacon.getBlockV2).to.be.calledOnce;
expect(store.finalized).to.eql(executionPayload);
expect(api.beacon.getBlockV2).to.be.toHaveBeenCalledOnce();
expect(store.finalized).toEqual(executionPayload);
});
});
@@ -280,19 +278,19 @@ describe("proof_provider/payload_store", function () {
const blockNumber = 10;
const slot = 20;
const header = buildLCHeader({slot, blockNumber});
(api.beacon.getBlockV2 as sinon.SinonStub).resolves(buildBlockResponse({blockNumber, slot}));
api.beacon.getBlockV2.mockResolvedValue(buildBlockResponse({blockNumber, slot}));
await store.processLCHeader(header);
expect(api.beacon.getBlockV2).calledOnce;
expect(api.beacon.getBlockV2).calledWith(20);
expect(api.beacon.getBlockV2).toHaveBeenCalledOnce();
expect(api.beacon.getBlockV2).toHaveBeenCalledWith(20);
});
it("should not fetch existing payload for lightclient header", async () => {
const blockNumber = 10;
const slot = 20;
const header = buildLCHeader({slot, blockNumber});
(api.beacon.getBlockV2 as sinon.SinonStub).resolves(buildBlockResponse({blockNumber, slot}));
api.beacon.getBlockV2.mockResolvedValue(buildBlockResponse({blockNumber, slot}));
await store.processLCHeader(header);
@@ -300,21 +298,21 @@ describe("proof_provider/payload_store", function () {
await store.processLCHeader(header);
// The network fetch should be done once
expect(api.beacon.getBlockV2).calledOnce;
expect(api.beacon.getBlockV2).calledWith(20);
expect(api.beacon.getBlockV2).toHaveBeenCalledOnce();
expect(api.beacon.getBlockV2).toHaveBeenCalledWith(20);
});
it("should prune the existing payloads", async () => {
const blockNumber = 10;
const slot = 20;
const header = buildLCHeader({slot, blockNumber});
(api.beacon.getBlockV2 as sinon.SinonStub).resolves(buildBlockResponse({blockNumber, slot}));
api.beacon.getBlockV2.mockResolvedValue(buildBlockResponse({blockNumber, slot}));
sinon.spy(store, "prune");
vi.spyOn(store, "prune");
await store.processLCHeader(header);
expect(store.prune).to.be.calledOnce;
expect(store.prune).toHaveBeenCalledOnce();
});
});
@@ -330,11 +328,11 @@ describe("proof_provider/payload_store", function () {
store.set(buildPayload({blockNumber: i}), true);
}
expect(store["payloads"].size).to.equal(numberOfPayloads);
expect(store["payloads"].size).toEqual(numberOfPayloads);
store.prune();
expect(store["payloads"].size).to.equal(MAX_PAYLOAD_HISTORY);
expect(store["payloads"].size).toEqual(MAX_PAYLOAD_HISTORY);
});
it("should not prune the existing payloads if equal to MAX_PAYLOAD_HISTORY", () => {
@@ -344,11 +342,11 @@ describe("proof_provider/payload_store", function () {
store.set(buildPayload({blockNumber: i}), true);
}
expect(store["payloads"].size).to.equal(MAX_PAYLOAD_HISTORY);
expect(store["payloads"].size).toEqual(MAX_PAYLOAD_HISTORY);
store.prune();
expect(store["payloads"].size).to.equal(MAX_PAYLOAD_HISTORY);
expect(store["payloads"].size).toEqual(MAX_PAYLOAD_HISTORY);
});
it("should not prune the existing payloads if less than MAX_PAYLOAD_HISTORY", () => {
@@ -358,11 +356,11 @@ describe("proof_provider/payload_store", function () {
store.set(buildPayload({blockNumber: i}), true);
}
expect(store["payloads"].size).to.equal(numberOfPayloads);
expect(store["payloads"].size).toEqual(numberOfPayloads);
store.prune();
expect(store["payloads"].size).to.equal(numberOfPayloads);
expect(store["payloads"].size).toEqual(numberOfPayloads);
});
it("should prune finalized roots", () => {
@@ -372,33 +370,33 @@ describe("proof_provider/payload_store", function () {
store.set(buildPayload({blockNumber: i}), true);
}
expect(store["finalizedRoots"].size).to.equal(numberOfPayloads);
expect(store["finalizedRoots"].size).toEqual(numberOfPayloads);
store.prune();
expect(store["finalizedRoots"].size).to.equal(MAX_PAYLOAD_HISTORY);
expect(store["finalizedRoots"].size).toEqual(MAX_PAYLOAD_HISTORY);
});
it("should prune unfinalized roots", async () => {
const numberOfPayloads = MAX_PAYLOAD_HISTORY + 2;
for (let i = 1; i <= numberOfPayloads; i++) {
(api.beacon.getBlockV2 as sinon.SinonStub)
.withArgs(i)
.resolves(buildBlockResponse({blockNumber: 500 + i, slot: i}));
when(api.beacon.getBlockV2)
.calledWith(i)
.thenResolve(buildBlockResponse({blockNumber: 500 + i, slot: i}));
await store.processLCHeader(buildLCHeader({blockNumber: 500 + i, slot: i}), false);
}
// Because all payloads are unfinalized, they are not pruned
expect(store["unfinalizedRoots"].size).to.equal(numberOfPayloads);
expect(store["unfinalizedRoots"].size).toEqual(numberOfPayloads);
// Let make some payloads finalized
await store.processLCHeader(buildLCHeader({blockNumber: 500 + 1, slot: 1}), true);
await store.processLCHeader(buildLCHeader({blockNumber: 500 + 2, slot: 2}), true);
// store.processLCHeader will call the prune method internally and clean the unfinalized roots
expect(store["unfinalizedRoots"].size).to.equal(numberOfPayloads - 2);
expect(store["unfinalizedRoots"].size).toEqual(numberOfPayloads - 2);
});
});
});

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {ethers} from "ethers";
import Web3 from "web3";
import {isSendProvider, isWeb3jsProvider, isEthersProvider} from "../../../src/utils/assertion.js";
@@ -11,41 +11,41 @@ describe("utils/assertion", () => {
// Do nothing;
},
};
expect(isSendProvider(provider)).to.be.true;
expect(isSendProvider(provider)).toBe(true);
});
it("should return false for ethers provider", () => {
const provider = new ethers.JsonRpcProvider("https://lodestar-sepoliarpc.chainsafe.io");
expect(isSendProvider(provider)).to.be.false;
expect(isSendProvider(provider)).toBe(false);
});
it("should return false for web3 provider", () => {
const provider = new Web3.providers.HttpProvider("https://lodestar-sepoliarpc.chainsafe.io");
expect(isSendProvider(provider)).to.be.false;
expect(isSendProvider(provider)).toBe(false);
});
});
describe("isWeb3jsProvider", () => {
it("should return true if provider is web3.js provider", () => {
const provider = new Web3.providers.HttpProvider("https://lodestar-sepoliarpc.chainsafe.io");
expect(isWeb3jsProvider(provider)).to.be.true;
expect(isWeb3jsProvider(provider)).toBe(true);
});
it("should return false if provider is not web3.js provider", () => {
const provider = new ethers.JsonRpcProvider("https://lodestar-sepoliarpc.chainsafe.io");
expect(isWeb3jsProvider(provider)).to.be.false;
expect(isWeb3jsProvider(provider)).toBe(false);
});
});
describe("isEthersProvider", () => {
it("should return false if provider is not ethers provider", () => {
const provider = new Web3.providers.HttpProvider("https://lodestar-sepoliarpc.chainsafe.io");
expect(isEthersProvider(provider)).to.be.false;
expect(isEthersProvider(provider)).toBe(false);
});
it("should return true if provider is ethers provider", () => {
const provider = new ethers.JsonRpcProvider("https://lodestar-sepoliarpc.chainsafe.io");
expect(isEthersProvider(provider)).to.be.true;
expect(isEthersProvider(provider)).toBe(true);
});
});
});

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {chunkIntoN} from "../../../src/utils/conversion.js";
describe("utils/conversion", () => {
@@ -71,12 +71,12 @@ describe("utils/conversion", () => {
for (const {title, input, output} of testCases) {
it(`should chunkify data when ${title}`, async () => {
expect(chunkIntoN(input.data, input.n)).to.be.deep.eq(output);
expect(chunkIntoN(input.data, input.n)).toEqual(output);
});
}
it("should not change the order of elements", () => {
expect(chunkIntoN([6, 5, 4, 3, 2, 1], 2)).to.be.deep.eq([
expect(chunkIntoN([6, 5, 4, 3, 2, 1], 2)).toEqual([
[6, 5],
[4, 3],
[2, 1],

View File

@@ -1,6 +1,4 @@
import {expect} from "chai";
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import {describe, it, expect} from "vitest";
import deepmerge from "deepmerge";
import {getEnvLogger} from "@lodestar/logger/env";
import {ELProof, ELStorageProof} from "../../../src/types.js";
@@ -16,8 +14,6 @@ const validStateRoot = hexToBuffer(eoaProof.beacon.executionPayload.state_root);
const invalidAccountProof = deepmerge(validAccountProof, {});
delete invalidAccountProof.accountProof[0];
chai.use(chaiAsPromised);
describe("uitls/execution", () => {
const logger = getEnvLogger();
@@ -30,7 +26,7 @@ describe("uitls/execution", () => {
stateRoot: validStateRoot,
logger,
})
).eventually.to.be.true;
).resolves.toBe(true);
});
it("should fail with error if proof is valid but address is wrong", async () => {
@@ -48,7 +44,7 @@ describe("uitls/execution", () => {
stateRoot,
logger,
})
).eventually.to.be.false;
).resolves.toBe(false);
});
it("should fail with error if account is not valid", async () => {
@@ -62,7 +58,7 @@ describe("uitls/execution", () => {
stateRoot,
logger,
})
).eventually.to.be.false;
).resolves.toBe(false);
});
});
@@ -76,7 +72,7 @@ describe("uitls/execution", () => {
storageKeys,
logger,
})
).eventually.to.be.true;
).resolves.toBe(true);
});
it("should fail with error for a wrong proof", async () => {
@@ -88,7 +84,7 @@ describe("uitls/execution", () => {
proof: invalidStorageProof,
storageKeys,
})
).eventually.to.be.false;
).resolves.toBe(false);
});
it("should fail with error for a non existance key", async () => {
@@ -110,7 +106,7 @@ describe("uitls/execution", () => {
storageKeys,
logger,
})
).eventually.to.be.false;
).resolves.toBe(false);
});
it("should return true empty keys", async () => {
@@ -126,7 +122,7 @@ describe("uitls/execution", () => {
storageKeys,
logger,
})
).eventually.to.be.true;
).resolves.toBe(true);
});
});
});

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {ELTransaction} from "../../../lib/types.js";
@@ -28,7 +28,7 @@ describe("verified_requests / eth_call", () => {
},
});
expect(response).to.eql(testCase.response);
expect(response).toEqual(testCase.response);
});
it("should return the json-rpc response with error for an invalid call", async () => {
@@ -57,7 +57,7 @@ describe("verified_requests / eth_call", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: VERIFICATION_FAILED_RESPONSE_CODE, message: getVerificationFailedMessage("eth_call")},

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {ELTransaction} from "../../../lib/types.js";
@@ -29,7 +29,7 @@ describe("verified_requests / eth_estimateGas", () => {
},
});
expect(response).to.eql(testCase.response);
expect(response).toEqual(testCase.response);
});
it("should return the json-rpc response with error for an invalid call", async () => {
@@ -59,7 +59,7 @@ describe("verified_requests / eth_estimateGas", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: VERIFICATION_FAILED_RESPONSE_CODE, message: getVerificationFailedMessage("eth_estimateGas")},

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {VERIFICATION_FAILED_RESPONSE_CODE} from "../../../src/constants.js";
@@ -25,7 +25,7 @@ describe("verified_requests / eth_getBalance", () => {
params: [data.request.params[0], data.request.params[1]],
},
});
expect(response).to.eql(data.response);
expect(response).toEqual(data.response);
});
it("should return the json-rpc response with error for an invalid account", async () => {
@@ -43,7 +43,7 @@ describe("verified_requests / eth_getBalance", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: data.request.id,
error: {code: VERIFICATION_FAILED_RESPONSE_CODE, message: getVerificationFailedMessage("eth_getBalance")},

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {VERIFICATION_FAILED_RESPONSE_CODE} from "../../../src/constants.js";
@@ -29,7 +29,7 @@ describe("verified_requests / eth_getBlockByHash", () => {
params: testCase.request.params as [string, boolean],
},
});
expect(response).to.eql(testCase.response);
expect(response).toEqual(testCase.response);
});
it("should return the json-rpc response with error for an invalid block header with valid execution payload", async () => {
@@ -48,7 +48,7 @@ describe("verified_requests / eth_getBlockByHash", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: VERIFICATION_FAILED_RESPONSE_CODE, message: getVerificationFailedMessage("eth_getBlockByHash")},
@@ -71,7 +71,7 @@ describe("verified_requests / eth_getBlockByHash", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: VERIFICATION_FAILED_RESPONSE_CODE, message: getVerificationFailedMessage("eth_getBlockByHash")},
@@ -94,7 +94,7 @@ describe("verified_requests / eth_getBlockByHash", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: VERIFICATION_FAILED_RESPONSE_CODE, message: getVerificationFailedMessage("eth_getBlockByHash")},

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {VERIFICATION_FAILED_RESPONSE_CODE} from "../../../src/constants.js";
@@ -29,7 +29,7 @@ describe("verified_requests / eth_getBlockByNumber", () => {
params: testCase.request.params as [string | number, boolean],
},
});
expect(response).to.eql(testCase.response);
expect(response).toEqual(testCase.response);
});
it("should return the json-rpc response with error for an invalid block header with valid execution payload", async () => {
@@ -48,7 +48,7 @@ describe("verified_requests / eth_getBlockByNumber", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {
@@ -74,7 +74,7 @@ describe("verified_requests / eth_getBlockByNumber", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {
@@ -103,7 +103,7 @@ describe("verified_requests / eth_getBlockByNumber", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {VERIFICATION_FAILED_RESPONSE_CODE} from "../../../src/constants.js";
@@ -25,7 +25,7 @@ describe("verified_requests / eth_getCode", () => {
},
});
expect(response).to.eql(testCase.response);
expect(response).toEqual(testCase.response);
});
it("should return the json-rpc response with error for an invalid account", async () => {
@@ -41,7 +41,7 @@ describe("verified_requests / eth_getCode", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: VERIFICATION_FAILED_RESPONSE_CODE, message: getVerificationFailedMessage("eth_getCode")},

View File

@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect} from "vitest";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {VERIFICATION_FAILED_RESPONSE_CODE} from "../../../src/constants.js";
@@ -25,7 +25,7 @@ describe("verified_requests / eth_getTransactionCount", () => {
},
});
expect(response).to.eql(testCase.response);
expect(response).toEqual(testCase.response);
});
it("should return the json-rpc response with error for an invalid account", async () => {
@@ -43,7 +43,7 @@ describe("verified_requests / eth_getTransactionCount", () => {
},
});
expect(response).to.eql({
expect(response).toEqual({
jsonrpc: "2.0",
id: testCase.request.id,
error: {

View File

@@ -1,22 +1,19 @@
import {expect} from "chai";
import {describe, it, expect, afterEach, vi} from "vitest";
import Web3 from "web3";
import {ethers} from "ethers";
import sinon from "sinon";
import {createVerifiedExecutionProvider, ProofProvider, LCTransport} from "@lodestar/prover/browser";
import {ELRpc} from "../../src/utils/rpc.js";
describe("web3_provider", () => {
const sandbox = sinon.createSandbox();
afterEach(() => {
sandbox.restore();
vi.clearAllMocks();
});
describe("createVerifiedExecutionProvider", () => {
describe("web3", () => {
it("should create a verified execution provider for the web3 provider", () => {
// Don't invoke network in unit tests
sandbox.stub(ELRpc.prototype, "verifyCompatibility").resolves();
vi.spyOn(ELRpc.prototype, "verifyCompatibility").mockResolvedValue();
const {provider, proofProvider} = createVerifiedExecutionProvider(
new Web3.providers.HttpProvider("https://lodestar-sepoliarpc.chainsafe.io"),
@@ -27,15 +24,15 @@ describe("web3_provider", () => {
}
);
expect(provider).be.instanceof(Web3.providers.HttpProvider);
expect(proofProvider).be.instanceOf(ProofProvider);
expect(provider).toBeInstanceOf(Web3.providers.HttpProvider);
expect(proofProvider).toBeInstanceOf(ProofProvider);
});
});
describe("ethers", () => {
it("should create a verified execution provider for the ethers provider", () => {
// Don't invoke network in unit tests
sandbox.stub(ELRpc.prototype, "verifyCompatibility").resolves();
vi.spyOn(ELRpc.prototype, "verifyCompatibility").mockResolvedValue();
const {provider, proofProvider} = createVerifiedExecutionProvider(
new ethers.JsonRpcProvider("https://lodestar-sepoliarpc.chainsafe.io"),
@@ -46,8 +43,8 @@ describe("web3_provider", () => {
}
);
expect(provider).be.instanceof(ethers.JsonRpcProvider);
expect(proofProvider).be.instanceOf(ProofProvider);
expect(provider).toBeInstanceOf(ethers.JsonRpcProvider);
expect(proofProvider).toBeInstanceOf(ProofProvider);
});
});
});

View File

@@ -0,0 +1,14 @@
import {defineConfig, mergeConfig} from "vitest/config";
import vitestConfig from "../../vitest.base.browser.config";
export default mergeConfig(
vitestConfig,
defineConfig({
test: {
globalSetup: ["./test/globalSetup.ts"],
},
optimizeDeps: {
exclude: ["@chainsafe/blst"],
},
})
);

View File

@@ -0,0 +1,11 @@
import {defineConfig, mergeConfig} from "vitest/config";
import vitestConfig from "../../vitest.base.config";
export default mergeConfig(
vitestConfig,
defineConfig({
test: {
globalSetup: ["./test/globalSetup.ts"],
},
})
);

View File

@@ -1,5 +0,0 @@
const webpackConfig = require("../../webpack.test.config.js");
module.exports = {
...webpackConfig,
};

View File

@@ -1,5 +1,5 @@
import bls from "@chainsafe/bls";
import {CoordType} from "@chainsafe/blst";
import {CoordType} from "@chainsafe/bls/types";
import {BeaconConfig} from "@lodestar/config";
import {loadState} from "../util/loadState/loadState.js";
import {EpochCache, EpochCacheImmutableData, EpochCacheOpts} from "./epochCache.js";

View File

@@ -1,5 +1,5 @@
import bls from "@chainsafe/bls";
import {CoordType} from "@chainsafe/blst";
import {CoordType} from "@chainsafe/bls/types";
import {itBench, setBenchOpts} from "@dapplion/benchmark";
import {loadState} from "../../../../src/util/loadState/loadState.js";
import {createCachedBeaconState} from "../../../../src/cache/stateCache.js";

View File

@@ -30,11 +30,19 @@ export async function runCliCommand<T>(
return wrapTimeout(
// eslint-disable-next-line no-async-promise-executor
new Promise(async (resolve, reject) => {
await cli.parseAsync(parseArgs(args), {}, (err, _argv, output) => {
if (err) return reject(err);
try {
await cli
.parseAsync(parseArgs(args), {}, (err, _argv, output) => {
if (err) return reject(err);
resolve(output);
});
resolve(output);
})
.catch(() => {
// We are suppressing error here as we are throwing from inside the callback
});
} catch (err) {
reject(err);
}
}),
opts.timeoutMs
);

View File

@@ -5,8 +5,21 @@ OUTPUT=$(yarn install --check-files 2>&1)
echo $OUTPUT
MATCH=("warning")
# There are few yarn warnings we can't find a fix for. Excluding those.
# TODO: Keep checking occasionally if the warnings are fixed upstream.
EXCLUDE=("Pattern \[\".*\"\] is trying to unpack in the same destination")
ARGS=()
for m in "${MATCH[@]}"; do ARGS+=(-e "$m"); done
for e in "${EXCLUDE[@]}"; do ARGS+=(--exclude "$e"); done
COMMAND="grep -qi ${ARGS[@]}"
echo "Running $COMMAND"
# grep the output for 'warning'
if echo "$OUTPUT" | grep -qi 'warning'; then
if echo "$OUTPUT" | ${COMMAND}; then
echo "There were warnings in yarn install --check-files"
exit 1
else

View File

@@ -52,4 +52,17 @@ expect.extend({
message: () => message,
};
},
toSatisfy: (received: unknown, func: (received: unknown) => boolean) => {
if (func(received)) {
return {
message: () => "Expected value satisfied the condition",
pass: true,
};
}
return {
pass: false,
message: () => "Expected value did not satisfy the condition",
};
},
});

View File

@@ -0,0 +1,2 @@
export default null;
export const performance = {};

View File

@@ -23,6 +23,12 @@
"declaration": true,
"declarationMap": true,
"incremental": true,
"preserveWatchOutput": true
"preserveWatchOutput": true,
// There are two duplicate type definitions included from `chai` and `vitest` packages.
// There is one invalid type declaration introduced from `webdriverio -> got` package.
// TODO: Once we completely remove `chai` and upgrade `webdriverio` we can enable this check again.
"skipLibCheck": true,
}
}

View File

@@ -28,8 +28,15 @@ interface CustomMatchers<R = unknown> {
toBeWithMessage(expected: unknown, message: string): R;
}
interface CustomAsymmetricMatchers<R = unknown> extends CustomMatchers<R> {
/**
* Non-asymmetric matcher already exists, we just need to add asymmetric version
*/
toSatisfy(func: (received: unknown) => boolean): R;
}
declare module "vitest" {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface Assertion<T = any> extends CustomMatchers<T> {}
interface AsymmetricMatchersContaining extends CustomMatchers {}
interface AsymmetricMatchersContaining extends CustomAsymmetricMatchers {}
}

View File

@@ -0,0 +1,49 @@
import path from "node:path";
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";
export default defineConfig({
plugins: [
topLevelAwait(),
nodePolyfills({
include: ["buffer", "process", "util", "string_decoder", "url", "querystring", "events"],
globals: {Buffer: true, process: true},
protocolImports: true,
}),
],
test: {
include: ["**/*.test.ts"],
exclude: [
"**/*.node.test.ts",
"**/node_modules/**",
"**/dist/**",
"**/lib/**",
"**/cypress/**",
"**/.{idea,git,cache,output,temp}/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
],
setupFiles: [path.join(__dirname, "./scripts/vitest/customMatchers.ts")],
reporters: ["default", "hanging-process"],
coverage: {
enabled: false,
},
browser: {
name: "chrome",
headless: true,
provider: "webdriverio",
slowHijackESM: false,
providerOptions: {
capabilities: {
browserVersion: "latest",
},
},
},
},
resolve: {
alias: {
"node:perf_hooks": path.join(__dirname, "scripts/vitest/polyfills/perf_hooks.js"),
},
},
});

View File

@@ -4,6 +4,16 @@ const __dirname = new URL(".", import.meta.url).pathname;
export default defineConfig({
test: {
pool: "threads",
include: ["**/*.test.ts"],
exclude: [
"**/*.browser.test.ts",
"**/node_modules/**",
"**/dist/**",
"**/cypress/**",
"**/.{idea,git,cache,output,temp}/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
],
setupFiles: [path.join(__dirname, "./scripts/vitest/customMatchers.ts")],
reporters: ["default", "hanging-process"],
coverage: {

2115
yarn.lock

File diff suppressed because it is too large Load Diff