Skip to content

Commit

Permalink
feat(relayer): relay messages onchain
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmad committed Jan 20, 2025
1 parent 1ac6444 commit 1aa4ea3
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/relayer-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ env:
RELAYER_RPC_URL: "http://localhost:8545"
TTL: ${{ vars.RELAYER_TTL }}
LIMIT: ${{ vars.RELAYER_LIMIT }}
ALLOWED_ORIGINS: ${{ vars.ALLOWED_ORIGINS }}
MONGO_DB_URI: ${{ secrets.RELAYER_MONGO_DB_URI }}
MONGODB_USER: ${{ secrets.MONGODB_USER }}
MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD }}
MONGODB_DATABASE: ${{ secrets.MONGODB_DATABASE }}
MNEMONIC: ${{ secrets.RELAYER_MNEMONIC }}

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
Expand Down
2 changes: 2 additions & 0 deletions apps/relayer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"hardhat": "^2.22.18",
"helia": "^5.2.0",
"helmet": "^8.0.0",
"lodash": "^4.17.21",
"maci-contracts": "workspace:^2.5.0",
"maci-domainobjs": "workspace:^2.5.0",
"mongoose": "^8.9.5",
Expand All @@ -60,6 +61,7 @@
"@nestjs/testing": "^11.0.3",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/lodash": "^4.17.14",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"fast-check": "^3.23.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { jest } from "@jest/globals";
import { ZeroAddress } from "ethers";
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts";

import { IpfsService } from "../../ipfs/ipfs.service";
import { MessageBatchDto } from "../dto";
Expand All @@ -7,6 +9,15 @@ import { MessageBatchService } from "../messageBatch.service";

import { defaultIpfsHash, defaultMessageBatches } from "./utils";

jest.mock("maci-contracts", (): unknown => ({
MACI__factory: {
connect: jest.fn(),
},
Poll__factory: {
connect: jest.fn(),
},
}));

describe("MessageBatchService", () => {
const mockIpfsService = {
add: jest.fn().mockImplementation(() => Promise.resolve(defaultIpfsHash)),
Expand All @@ -16,9 +27,23 @@ describe("MessageBatchService", () => {
create: jest.fn().mockImplementation(() => Promise.resolve(defaultMessageBatches)),
};

const mockMaciContract = {
polls: jest.fn().mockImplementation(() => Promise.resolve({ poll: ZeroAddress })),
};

const mockPollContract = {
hashMessageAndEncPubKey: jest.fn().mockImplementation(() => Promise.resolve("hash")),
relayMessagesBatch: jest
.fn()
.mockImplementation(() => Promise.resolve({ wait: jest.fn().mockImplementation(() => Promise.resolve()) })),
};

beforeEach(() => {
mockRepository.create = jest.fn().mockImplementation(() => Promise.resolve(defaultMessageBatches));
mockIpfsService.add = jest.fn().mockImplementation(() => Promise.resolve(defaultIpfsHash));

MACIFactory.connect = jest.fn().mockImplementation(() => mockMaciContract) as typeof MACIFactory.connect;
PollFactory.connect = jest.fn().mockImplementation(() => mockPollContract) as typeof PollFactory.connect;
});

afterEach(() => {
Expand All @@ -34,6 +59,7 @@ describe("MessageBatchService", () => {
const result = await service.saveMessageBatches(defaultMessageBatches);

expect(result).toStrictEqual(defaultMessageBatches);
expect(mockPollContract.relayMessagesBatch).toHaveBeenCalledTimes(1);
});

test("should throw an error if can't save message batches", async () => {
Expand Down
40 changes: 35 additions & 5 deletions apps/relayer/ts/messageBatch/messageBatch.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Injectable, Logger } from "@nestjs/common";
import { validate } from "class-validator";
import flatten from "lodash/flatten";
import uniqBy from "lodash/uniqBy";
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts";
import { PubKey } from "maci-domainobjs";

import type { MessageBatchDto } from "./dto";

Expand Down Expand Up @@ -49,14 +53,40 @@ export class MessageBatchService {
throw new Error("Validation error");
}

const ipfsHash = await this.ipfsService.add(args.map(({ messages }) => messages)).catch((error) => {
const allMessages = flatten(args.map((item) => item.messages));

const ipfsHash = await this.ipfsService.add(allMessages).catch((error) => {
this.logger.error(`Upload message batches to ipfs error:`, error);
throw error;
});

return this.messageBatchRepository.create(args.map(({ messages }) => ({ messages, ipfsHash }))).catch((error) => {
this.logger.error(`Save message batch error:`, error);
throw error;
});
const messageBatches = await this.messageBatchRepository
.create(args.map(({ messages }) => ({ messages, ipfsHash })))
.catch((error) => {
this.logger.error(`Save message batch error:`, error);
throw error;
});

const [{ maciAddress, pollId }] = uniqBy(
allMessages.map(({ maciContractAddress, poll }) => ({
maciAddress: maciContractAddress,
pollId: poll,
})),
"maciContractAddress",
);

const maciContract = MACIFactory.connect(maciAddress);
const pollAddresses = await maciContract.polls(pollId);
const pollContract = PollFactory.connect(pollAddresses.poll);

const messageHashes = await Promise.all(
allMessages.map(({ data, publicKey }) =>
pollContract.hashMessageAndEncPubKey({ data }, PubKey.deserialize(publicKey).asContractParam()),
),
);

await pollContract.relayMessagesBatch(messageHashes, ipfsHash).then((tx) => tx.wait());

return messageBatches;
}
}
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1aa4ea3

Please sign in to comment.