Skip to content

Commit

Permalink
test: migrate validator tests to vitest (ChainSafe#6301)
Browse files Browse the repository at this point in the history
* Migrate validator tests to vitest

* Increase hook timeout for e2e test
  • Loading branch information
nazarhussain authored and ensi321 committed Jan 22, 2024
1 parent c41f3a9 commit e5d9810
Show file tree
Hide file tree
Showing 26 changed files with 383 additions and 499 deletions.
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

0 comments on commit e5d9810

Please sign in to comment.