Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: migrate validator tests to vitest #6301

Merged
merged 2 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions packages/validator/.mocharc.yaml

This file was deleted.

3 changes: 0 additions & 3 deletions packages/validator/.nycrc.json

This file was deleted.

4 changes: 2 additions & 2 deletions packages/validator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
"lint": "eslint --color --ext .ts src/ test/",
"lint:fix": "yarn run lint --fix",
"pretest": "yarn run check-types",
"test:unit": "nyc --cache-dir .nyc_output/.cache -e .ts mocha 'test/unit/**/*.test.ts'",
"test:unit": "vitest --run --dir test/unit --coverage",
"test": "yarn test:unit",
"test:spec": "vitest --run --config vitest.config.spec.ts --dir test/spec/",
"test:e2e": "mocha 'test/e2e/**/*.test.ts'",
"test:e2e": "LODESTAR_PRESET=mainnet vitest --run --poolOptions.threads.singleThread true --dir test/e2e",
"download-spec-tests": "node --loader=ts-node/esm test/spec/downloadTests.ts",
"coverage": "codecov -F lodestar-validator",
"check-readme": "typescript-docs-verifier"
Expand Down
13 changes: 7 additions & 6 deletions packages/validator/test/e2e/web3signer.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {expect} from "chai";
import {expect, describe, it, vi, beforeAll, afterAll} from "vitest";
import {fromHex, toHex} from "@lodestar/utils";
import {config} from "@lodestar/config/default";
import {computeStartSlotAtEpoch, interopSecretKey, interopSecretKeys} from "@lodestar/state-transition";
Expand All @@ -13,7 +13,7 @@ import {IndicesService} from "../../src/services/indices.js";
import {testLogger} from "../utils/logger.js";

describe("web3signer signature test", function () {
this.timeout("60s");
vi.setConfig({testTimeout: 60_000, hookTimeout: 60_000});

const altairSlot = 2375711;
const epoch = 0;
Expand All @@ -39,7 +39,7 @@ describe("web3signer signature test", function () {
pubkey: pubkeyBytes,
};

before("set up validator stores", async () => {
beforeAll(async () => {
validatorStoreLocal = await getValidatorStore({type: SignerType.Local, secretKey: secretKey});

const password = "password";
Expand All @@ -57,15 +57,16 @@ describe("web3signer signature test", function () {
});
});

after("stop external signer container", async () => {
afterAll(async () => {
await externalSigner.container.stop();
});

for (const fork of config.forksAscendingEpochOrder) {
it(`signBlock ${fork.name}`, async function () {
it(`signBlock ${fork.name}`, async ({skip}) => {
// Only test till the fork the signer version supports
if (ForkSeq[fork.name] > externalSigner.supportedForkSeq) {
this.skip();
skip();
return;
}

const block = ssz[fork.name].BeaconBlock.defaultValue();
Expand Down
97 changes: 40 additions & 57 deletions packages/validator/test/unit/services/attestation.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {expect} from "chai";
import {describe, it, expect, beforeAll, beforeEach, afterEach, vi} from "vitest";
import sinon from "sinon";
import bls from "@chainsafe/bls";
import {toHexString} from "@chainsafe/ssz";
Expand All @@ -14,32 +14,36 @@ import {ChainHeaderTracker} from "../../../src/services/chainHeaderTracker.js";
import {ValidatorEventEmitter} from "../../../src/services/emitter.js";
import {ZERO_HASH, ZERO_HASH_HEX} from "../../utils/types.js";

vi.mock("../../../src/services/validatorStore.js");
vi.mock("../../../src/services/emitter.js");
vi.mock("../../../src/services/chainHeaderTracker.js");

describe("AttestationService", function () {
const sandbox = sinon.createSandbox();

const api = getApiClientStub(sandbox);
const validatorStore = sinon.createStubInstance(ValidatorStore) as ValidatorStore &
sinon.SinonStubbedInstance<ValidatorStore>;
const emitter = sinon.createStubInstance(ValidatorEventEmitter) as ValidatorEventEmitter &
sinon.SinonStubbedInstance<ValidatorEventEmitter>;
const chainHeadTracker = sinon.createStubInstance(ChainHeaderTracker) as ChainHeaderTracker &
sinon.SinonStubbedInstance<ChainHeaderTracker>;
const api = getApiClientStub();
// @ts-expect-error - Mocked class don't need parameters
const validatorStore = vi.mocked(new ValidatorStore());
const emitter = vi.mocked(new ValidatorEventEmitter());
// @ts-expect-error - Mocked class don't need parameters
const chainHeadTracker = vi.mocked(new ChainHeaderTracker());

let pubkeys: Uint8Array[]; // Initialize pubkeys in before() so bls is already initialized

before(() => {
beforeAll(() => {
const secretKeys = Array.from({length: 1}, (_, i) => bls.SecretKey.fromBytes(Buffer.alloc(32, i + 1)));
pubkeys = secretKeys.map((sk) => sk.toPublicKey().toBytes());
validatorStore.votingPubkeys.returns(pubkeys.map(toHexString));
validatorStore.hasVotingPubkey.returns(true);
validatorStore.hasSomeValidators.returns(true);
validatorStore.signAttestationSelectionProof.resolves(ZERO_HASH);
validatorStore.votingPubkeys.mockReturnValue(pubkeys.map(toHexString));
validatorStore.hasVotingPubkey.mockReturnValue(true);
validatorStore.hasSomeValidators.mockReturnValue(true);
validatorStore.signAttestationSelectionProof.mockResolvedValue(ZERO_HASH);
});

let controller: AbortController; // To stop clock
beforeEach(() => (controller = new AbortController()));
beforeEach(() => {
controller = new AbortController();
});
afterEach(() => {
controller.abort();
sandbox.resetHistory();
vi.resetAllMocks();
});

const testContexts: [string, AttestationServiceOpts][] = [
Expand All @@ -49,7 +53,7 @@ describe("AttestationService", function () {
];

for (const [title, opts] of testContexts) {
context(title, () => {
describe(title, () => {
it("Should produce, sign, and publish an attestation + aggregate", async () => {
const clock = new ClockMock();
const attestationService = new AttestationService(
Expand Down Expand Up @@ -82,12 +86,12 @@ describe("AttestationService", function () {
];

// Return empty replies to duties service
api.beacon.getStateValidators.resolves({
api.beacon.getStateValidators.mockResolvedValue({
response: {executionOptimistic: false, data: []},
ok: true,
status: HttpStatusCode.OK,
});
api.validator.getAttesterDuties.resolves({
api.validator.getAttesterDuties.mockResolvedValue({
response: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false, data: []},
ok: true,
status: HttpStatusCode.OK,
Expand All @@ -98,22 +102,22 @@ describe("AttestationService", function () {

// Mock beacon's attestation and aggregates endpoints

api.validator.produceAttestationData.resolves({
api.validator.produceAttestationData.mockResolvedValue({
response: {data: attestation.data},
ok: true,
status: HttpStatusCode.OK,
});
api.validator.getAggregatedAttestation.resolves({
api.validator.getAggregatedAttestation.mockResolvedValue({
response: {data: attestation},
ok: true,
status: HttpStatusCode.OK,
});
api.beacon.submitPoolAttestations.resolves({
api.beacon.submitPoolAttestations.mockResolvedValue({
response: undefined,
ok: true,
status: HttpStatusCode.OK,
});
api.validator.publishAggregateAndProofs.resolves({
api.validator.publishAggregateAndProofs.mockResolvedValue({
response: undefined,
ok: true,
status: HttpStatusCode.OK,
Expand All @@ -122,22 +126,22 @@ describe("AttestationService", function () {
if (opts.distributedAggregationSelection) {
// Mock distributed validator middleware client selections endpoint
// and return a selection proof that passes `is_aggregator` test
api.validator.submitBeaconCommitteeSelections.resolves({
api.validator.submitBeaconCommitteeSelections.mockResolvedValue({
response: {data: [{validatorIndex: 0, slot: 0, selectionProof: Buffer.alloc(1, 0x10)}]},
ok: true,
status: HttpStatusCode.OK,
});
// Accept all subscriptions
api.validator.prepareBeaconCommitteeSubnet.resolves({
api.validator.prepareBeaconCommitteeSubnet.mockResolvedValue({
response: undefined,
ok: true,
status: HttpStatusCode.OK,
});
}

// Mock signing service
validatorStore.signAttestation.resolves(attestation);
validatorStore.signAggregateAndProof.resolves(aggregate);
validatorStore.signAttestation.mockResolvedValue(attestation);
validatorStore.signAggregateAndProof.mockResolvedValue(aggregate);

// Trigger clock onSlot for slot 0
await clock.tickSlotFns(0, controller.signal);
Expand All @@ -149,14 +153,8 @@ describe("AttestationService", function () {
slot: 0,
selectionProof: ZERO_HASH,
};
expect(api.validator.submitBeaconCommitteeSelections.callCount).to.equal(
1,
"submitBeaconCommitteeSelections() must be called once"
);
expect(api.validator.submitBeaconCommitteeSelections.getCall(0).args).to.deep.equal(
[[selection]], // 1 arg, = selection[]
"wrong submitBeaconCommitteeSelections() args"
);
expect(api.validator.submitBeaconCommitteeSelections).toHaveBeenCalledOnce();
expect(api.validator.submitBeaconCommitteeSelections).toHaveBeenCalledWith([selection]);

// Must resubscribe validator as aggregator on beacon committee subnet
const subscription: routes.validator.BeaconCommitteeSubscription = {
Expand All @@ -166,32 +164,17 @@ describe("AttestationService", function () {
slot: 0,
isAggregator: true,
};
expect(api.validator.prepareBeaconCommitteeSubnet.callCount).to.equal(
1,
"prepareBeaconCommitteeSubnet() must be called once"
);
expect(api.validator.prepareBeaconCommitteeSubnet.getCall(0).args).to.deep.equal(
[[subscription]], // 1 arg, = subscription[]
"wrong prepareBeaconCommitteeSubnet() args"
);
expect(api.validator.prepareBeaconCommitteeSubnet).toHaveBeenCalledOnce();
expect(api.validator.prepareBeaconCommitteeSubnet).toHaveBeenCalledWith([subscription]);
}

// Must submit the attestation received through produceAttestationData()
expect(api.beacon.submitPoolAttestations.callCount).to.equal(1, "submitAttestations() must be called once");
expect(api.beacon.submitPoolAttestations.getCall(0).args).to.deep.equal(
[[attestation]], // 1 arg, = attestation[]
"wrong submitAttestations() args"
);
expect(api.beacon.submitPoolAttestations).toHaveBeenCalledOnce();
expect(api.beacon.submitPoolAttestations).toHaveBeenCalledWith([attestation]);

// Must submit the aggregate received through getAggregatedAttestation() then createAndSignAggregateAndProof()
expect(api.validator.publishAggregateAndProofs.callCount).to.equal(
1,
"publishAggregateAndProofs() must be called once"
);
expect(api.validator.publishAggregateAndProofs.getCall(0).args).to.deep.equal(
[[aggregate]], // 1 arg, = aggregate[]
"wrong publishAggregateAndProofs() args"
);
expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledOnce();
expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledWith([aggregate]);
});
});
}
Expand Down
Loading
Loading