Skip to content

Commit

Permalink
feat(relayer): fetch message batches
Browse files Browse the repository at this point in the history
- [x] Add message batches fetch api method
- [x] Refactoring for integration tests
  • Loading branch information
0xmad committed Jan 29, 2025
1 parent 66dfab7 commit 1ebe11e
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 87 deletions.
2 changes: 2 additions & 0 deletions apps/relayer/jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
}
]
},
"setupFilesAfterEnv": ["<rootDir>/ts/jest/setup.ts"],
"preset": "ts-jest/presets/default-esm",
"moduleNameMapper": {
"^(\\.{1,2}/.*)\\.[jt]s$": "$1"
},
"extensionsToTreatAsEsm": [".ts"],
"collectCoverageFrom": [
"**/*.(t|j)s",
"!<rootDir>/tests/*.ts",
"!<rootDir>/ts/main.ts",
"!<rootDir>/ts/jest/*.js",
"!<rootDir>/hardhat.config.js"
Expand Down
2 changes: 1 addition & 1 deletion apps/relayer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"start": "pnpm run run:node ./ts/main.ts",
"start:prod": "pnpm run run:node build/ts/main.js",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --forceExit",
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage --forceExit",
"test:coverage": "pnpm run test --coverage",
"types": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
Expand Down
126 changes: 126 additions & 0 deletions apps/relayer/tests/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import hardhat from "hardhat";
import { deploy, deployPoll, deployVkRegistryContract, joinPoll, setVerifyingKeys, signup } from "maci-cli";
import { genMaciStateFromContract } from "maci-contracts";
import { Keypair } from "maci-domainobjs";

import {
INT_STATE_TREE_DEPTH,
MESSAGE_BATCH_SIZE,
STATE_TREE_DEPTH,
VOTE_OPTION_TREE_DEPTH,
pollJoinedZkey,
pollJoiningZkey,
processMessagesZkeyPathNonQv,
tallyVotesZkeyPathNonQv,
pollWasm,
pollWitgen,
rapidsnark,
} from "./constants";

interface IContractsData {
initialized: boolean;
user?: Keypair;
voiceCredits?: number;
timestamp?: number;
stateLeafIndex?: number;
maciContractAddress?: string;
maciState?: Awaited<ReturnType<typeof genMaciStateFromContract>>;
}

export class TestDeploy {
private static INSTANCE?: TestDeploy;

readonly contractsData: IContractsData = {
initialized: false,
};

static async getInstance(): Promise<TestDeploy> {
if (!TestDeploy.INSTANCE) {
TestDeploy.INSTANCE = new TestDeploy();
await TestDeploy.INSTANCE.contractsInit();
}

return TestDeploy.INSTANCE;
}

private async contractsInit(): Promise<void> {
const [signer] = await hardhat.ethers.getSigners();
const coordinatorKeypair = new Keypair();
const user = new Keypair();

const vkRegistry = await deployVkRegistryContract({ signer });
await setVerifyingKeys({
quiet: true,
vkRegistry,
stateTreeDepth: STATE_TREE_DEPTH,
intStateTreeDepth: INT_STATE_TREE_DEPTH,
voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH,
messageBatchSize: MESSAGE_BATCH_SIZE,
processMessagesZkeyPathNonQv,
tallyVotesZkeyPathNonQv,
pollJoiningZkeyPath: pollJoiningZkey,
pollJoinedZkeyPath: pollJoinedZkey,
useQuadraticVoting: false,
signer,
});

const maciAddresses = await deploy({ stateTreeDepth: 10, signer });

await deployPoll({
pollDuration: 30,
intStateTreeDepth: INT_STATE_TREE_DEPTH,
messageBatchSize: MESSAGE_BATCH_SIZE,
voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH,
coordinatorPubkey: coordinatorKeypair.pubKey.serialize(),
useQuadraticVoting: false,
signer,
});

await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer });

const { pollStateIndex, timestamp, voiceCredits } = await joinPoll({
maciAddress: maciAddresses.maciAddress,
pollId: 0n,
privateKey: user.privKey.serialize(),
stateIndex: 1n,
pollJoiningZkey,
pollWasm,
pollWitgen,
rapidsnark,
signer,
useWasm: true,
quiet: true,
});

const maciState = await genMaciStateFromContract(
signer.provider,
maciAddresses.maciAddress,
coordinatorKeypair,
0n,
);

this.contractsData.maciState = maciState;
this.contractsData.maciContractAddress = maciAddresses.maciAddress;
this.contractsData.stateLeafIndex = Number(pollStateIndex);
this.contractsData.timestamp = Number(timestamp);
this.contractsData.voiceCredits = Number(voiceCredits);
this.contractsData.user = user;
this.contractsData.initialized = true;
}
}

const testDeploy = await TestDeploy.getInstance();

export async function waitForInitialization(): Promise<void> {
return new Promise((resolve) => {
const checkInitialization = () => {
if (testDeploy.contractsData.initialized) {
resolve();
} else {
setTimeout(checkInitialization, 1000);
}
};

setTimeout(checkInitialization, 2000);
});
}
118 changes: 118 additions & 0 deletions apps/relayer/tests/messageBatches.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { jest } from "@jest/globals";
import { HttpStatus, ValidationPipe, type INestApplication } from "@nestjs/common";
import { Test } from "@nestjs/testing";
import { formatProofForVerifierContract, genProofSnarkjs } from "maci-contracts";
import { Keypair } from "maci-domainobjs";
import request from "supertest";

import type { App } from "supertest/types";

import { AppModule } from "../ts/app.module";

import { pollJoinedWasm, pollJoinedZkey } from "./constants";
import { TestDeploy, waitForInitialization } from "./deploy";

jest.unmock("maci-contracts/typechain-types");

describe("Integration message batches", () => {
let app: INestApplication;
let stateLeafIndex: number;
let maciContractAddress: string;
let user: Keypair;

beforeAll(async () => {
await waitForInitialization();
const testDeploy = await TestDeploy.getInstance();
const poll = testDeploy.contractsData.maciState!.polls.get(0n);

poll!.updatePoll(BigInt(testDeploy.contractsData.maciState!.pubKeys.length));

stateLeafIndex = Number(testDeploy.contractsData.stateLeafIndex);
maciContractAddress = testDeploy.contractsData.maciContractAddress!;
user = testDeploy.contractsData.user!;

const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();

app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ transform: true }));
await app.listen(3001);

const circuitInputs = poll!.joinedCircuitInputs({
maciPrivKey: testDeploy.contractsData.user!.privKey,
stateLeafIndex: BigInt(testDeploy.contractsData.stateLeafIndex!),
voiceCreditsBalance: BigInt(testDeploy.contractsData.voiceCredits!),
joinTimestamp: BigInt(testDeploy.contractsData.timestamp!),
});

const { proof } = await genProofSnarkjs({
inputs: circuitInputs as unknown as Record<string, bigint>,
zkeyPath: pollJoinedZkey,
wasmPath: pollJoinedWasm,
});

const keypair = new Keypair();

await request(app.getHttpServer() as App)
.post("/v1/messages/publish")
.send({
messages: [
{
data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
publicKey: keypair.pubKey.serialize(),
},
],
poll: 0,
maciContractAddress,
stateLeafIndex,
proof: formatProofForVerifierContract(proof),
})
.expect(HttpStatus.CREATED);
});

afterAll(async () => {
await app.close();
});

describe("/v1/messageBatches/get", () => {
test("should throw an error if dto is invalid", async () => {
const result = await request(app.getHttpServer() as App)
.get("/v1/messageBatches/get")
.send({
limit: 0,
poll: -1,
maciContractAddress: "invalid",
publicKey: "invalid",
ipfsHashes: ["invalid1", "invalid2"],
})
.expect(HttpStatus.BAD_REQUEST);

expect(result.body).toStrictEqual({
error: "Bad Request",
statusCode: HttpStatus.BAD_REQUEST,
message: [
"limit must be a positive number",
"poll must not be less than 0",
"maciContractAddress must be an Ethereum address",
"IPFS hash is invalid",
"Public key (invalid) is invalid",
],
});
});

test("should get message batches properly", async () => {
const result = await request(app.getHttpServer() as App)
.get("/v1/messageBatches/get")
.send({
limit: 10,
poll: 0,
maciContractAddress,
publicKey: user!.pubKey.serialize(),
})
.expect(HttpStatus.OK);

expect(result.status).toBe(HttpStatus.OK);
});
});
});
92 changes: 14 additions & 78 deletions apps/relayer/tests/messages.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { jest } from "@jest/globals";
import { HttpStatus, ValidationPipe, type INestApplication } from "@nestjs/common";
import { Test } from "@nestjs/testing";
import hardhat from "hardhat";
import { deploy, deployPoll, deployVkRegistryContract, joinPoll, setVerifyingKeys, signup } from "maci-cli";
import { formatProofForVerifierContract, genMaciStateFromContract } from "maci-contracts";
import { formatProofForVerifierContract } from "maci-contracts";
import { Keypair } from "maci-domainobjs";
import { genProofSnarkjs } from "maci-sdk";
import request from "supertest";
Expand All @@ -12,18 +10,8 @@ import type { App } from "supertest/types";

import { AppModule } from "../ts/app.module";

import {
INT_STATE_TREE_DEPTH,
MESSAGE_BATCH_SIZE,
STATE_TREE_DEPTH,
VOTE_OPTION_TREE_DEPTH,
pollJoinedZkey,
pollJoiningZkey,
processMessagesZkeyPathNonQv,
tallyVotesZkeyPathNonQv,
pollWasm,
pollJoinedWasm,
} from "./constants";
import { pollJoinedWasm, pollJoinedZkey } from "./constants";
import { TestDeploy, waitForInitialization } from "./deploy";

jest.unmock("maci-contracts/typechain-types");

Expand All @@ -33,74 +21,22 @@ describe("Integration messages", () => {
let stateLeafIndex: number;
let maciContractAddress: string;

const coordinatorKeypair = new Keypair();
const user = new Keypair();

beforeAll(async () => {
const [signer] = await hardhat.ethers.getSigners();

const vkRegistry = await deployVkRegistryContract({ signer });
await setVerifyingKeys({
quiet: true,
vkRegistry,
stateTreeDepth: STATE_TREE_DEPTH,
intStateTreeDepth: INT_STATE_TREE_DEPTH,
voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH,
messageBatchSize: MESSAGE_BATCH_SIZE,
processMessagesZkeyPathNonQv,
tallyVotesZkeyPathNonQv,
pollJoiningZkeyPath: pollJoiningZkey,
pollJoinedZkeyPath: pollJoinedZkey,
useQuadraticVoting: false,
signer,
});

const maciAddresses = await deploy({ stateTreeDepth: 10, signer });

maciContractAddress = maciAddresses.maciAddress;

await deployPoll({
pollDuration: 30,
intStateTreeDepth: INT_STATE_TREE_DEPTH,
messageBatchSize: MESSAGE_BATCH_SIZE,
voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH,
coordinatorPubkey: coordinatorKeypair.pubKey.serialize(),
useQuadraticVoting: false,
signer,
});

await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer });

const { pollStateIndex, timestamp, voiceCredits } = await joinPoll({
maciAddress: maciAddresses.maciAddress,
pollId: 0n,
privateKey: user.privKey.serialize(),
stateIndex: 1n,
pollJoiningZkey,
pollWasm,
signer,
useWasm: true,
quiet: true,
});

const maciState = await genMaciStateFromContract(
signer.provider,
maciAddresses.maciAddress,
coordinatorKeypair,
0n,
);
await waitForInitialization();
const testDeploy = await TestDeploy.getInstance();
const poll = testDeploy.contractsData.maciState!.polls.get(0n);

const poll = maciState.polls.get(0n);
poll!.updatePoll(BigInt(testDeploy.contractsData.maciState!.pubKeys.length));

poll!.updatePoll(BigInt(maciState.pubKeys.length));
stateLeafIndex = Number(testDeploy.contractsData.stateLeafIndex);

stateLeafIndex = Number(pollStateIndex);
maciContractAddress = testDeploy.contractsData.maciContractAddress!;

circuitInputs = poll!.joinedCircuitInputs({
maciPrivKey: user.privKey,
stateLeafIndex: BigInt(pollStateIndex),
voiceCreditsBalance: BigInt(voiceCredits),
joinTimestamp: BigInt(timestamp),
maciPrivKey: testDeploy.contractsData.user!.privKey,
stateLeafIndex: BigInt(testDeploy.contractsData.stateLeafIndex!),
voiceCreditsBalance: BigInt(testDeploy.contractsData.voiceCredits!),
joinTimestamp: BigInt(testDeploy.contractsData.timestamp!),
}) as unknown as typeof circuitInputs;

const moduleFixture = await Test.createTestingModule({
Expand All @@ -109,7 +45,7 @@ describe("Integration messages", () => {

app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ transform: true }));
await app.listen(3001);
await app.listen(3002);
});

afterAll(async () => {
Expand Down
Loading

0 comments on commit 1ebe11e

Please sign in to comment.