Skip to content

Commit

Permalink
Merge pull request #2102 from privacy-scaling-explorations/feature/sd…
Browse files Browse the repository at this point in the history
…k-relayer

feat(sdk): add relayer functions
  • Loading branch information
0xmad authored Feb 4, 2025
2 parents 109b590 + 6273ef8 commit 6dc818d
Show file tree
Hide file tree
Showing 41 changed files with 1,394 additions and 871 deletions.
9 changes: 6 additions & 3 deletions apps/relayer/tests/messageBatches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ describe("Integration message batches", () => {
.get("/v1/messageBatches/get")
.send({
limit: 0,
skip: -1,
poll: -1,
maciContractAddress: "invalid",
publicKey: "invalid",
publicKeys: ["invalid"],
ipfsHashes: ["invalid1", "invalid2"],
})
.expect(HttpStatus.BAD_REQUEST);
Expand All @@ -93,10 +94,11 @@ describe("Integration message batches", () => {
statusCode: HttpStatus.BAD_REQUEST,
message: [
"limit must be a positive number",
"skip must not be less than 0",
"poll must not be less than 0",
"maciContractAddress must be an Ethereum address",
"IPFS hash is invalid",
"Public key (invalid) is invalid",
"Public key is invalid",
],
});
});
Expand All @@ -106,9 +108,10 @@ describe("Integration message batches", () => {
.get("/v1/messageBatches/get")
.send({
limit: 10,
skip: 0,
poll: 0,
maciContractAddress,
publicKey: user!.pubKey.serialize(),
publicKeys: [user!.pubKey.serialize()],
})
.expect(HttpStatus.OK);

Expand Down
4 changes: 2 additions & 2 deletions apps/relayer/tests/messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("Integration messages", () => {

beforeAll(async () => {
const { TestDeploy } = await import("./deploy");
await TestDeploy.sleep(5_000);
await TestDeploy.sleep(10_000);
const testDeploy = await TestDeploy.getInstance();
const poll = testDeploy.contractsData.maciState!.polls.get(0n);

Expand Down Expand Up @@ -136,7 +136,7 @@ describe("Integration messages", () => {
expect(result.body).toStrictEqual({
error: "Bad Request",
statusCode: HttpStatus.BAD_REQUEST,
message: ["messages.0.data must contain at least 10 elements", "messages.0.Public key (invalid) is invalid"],
message: ["messages.0.data must contain at least 10 elements", "messages.0.Public key is invalid"],
});
});

Expand Down
6 changes: 3 additions & 3 deletions apps/relayer/ts/message/__tests__/message.controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Test } from "@nestjs/testing";
import { MessageController } from "../message.controller";
import { MessageService } from "../message.service";

import { defaultSaveMessagesArgs } from "./utils";
import { defaultSaveMessagesDto } from "./utils";

describe("MessageController", () => {
let controller: MessageController;
Expand Down Expand Up @@ -39,7 +39,7 @@ describe("MessageController", () => {

describe("v1/messages/publish", () => {
test("should publish user messages properly", async () => {
const data = await controller.publish(defaultSaveMessagesArgs);
const data = await controller.publish(defaultSaveMessagesDto);

expect(data).toBe(true);
});
Expand All @@ -48,7 +48,7 @@ describe("MessageController", () => {
const error = new Error("error");
mockMessageService.saveMessages.mockImplementation(() => Promise.reject(error));

await expect(controller.publish(defaultSaveMessagesArgs)).rejects.toThrow(
await expect(controller.publish(defaultSaveMessagesDto)).rejects.toThrow(
new HttpException(error.message, HttpStatus.BAD_REQUEST),
);
});
Expand Down
11 changes: 8 additions & 3 deletions apps/relayer/ts/message/__tests__/message.repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,26 @@ describe("MessageRepository", () => {
{
publicKey: new Keypair().pubKey.serialize(),
data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
hash: "hash",
maciContractAddress: ZeroAddress,
poll: 0,
},
];

const mockMessageModel = {
find: jest.fn().mockReturnValue({
limit: jest.fn().mockReturnValue({ exec: jest.fn().mockImplementation(() => Promise.resolve(defaultMessages)) }),
limit: jest.fn().mockReturnValue({
skip: jest.fn().mockReturnValue({ exec: jest.fn().mockImplementation(() => Promise.resolve(defaultMessages)) }),
}),
}),
insertMany: jest.fn().mockImplementation(() => Promise.resolve(defaultMessages)),
};

beforeEach(() => {
mockMessageModel.find = jest.fn().mockReturnValue({
limit: jest.fn().mockReturnValue({ exec: jest.fn().mockImplementation(() => Promise.resolve(defaultMessages)) }),
limit: jest.fn().mockReturnValue({
skip: jest.fn().mockReturnValue({ exec: jest.fn().mockImplementation(() => Promise.resolve(defaultMessages)) }),
}),
});
mockMessageModel.insertMany = jest.fn().mockImplementation(() => Promise.resolve(defaultMessages));
});
Expand Down Expand Up @@ -67,7 +72,7 @@ describe("MessageRepository", () => {

(mockMessageModel.find as jest.Mock).mockReturnValue({
limit: jest.fn().mockReturnValue({
exec: jest.fn().mockImplementation(() => Promise.reject(error)),
skip: jest.fn().mockReturnValue({ exec: jest.fn().mockImplementation(() => Promise.reject(error)) }),
}),
});

Expand Down
31 changes: 28 additions & 3 deletions apps/relayer/ts/message/__tests__/message.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { jest } from "@jest/globals";
import { ZeroAddress } from "ethers";
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-sdk";

import type { MessageBatchService } from "../../messageBatch/messageBatch.service";
import type { MessageRepository } from "../message.repository";

import { MessageService } from "../message.service";

import { defaultMessages, defaultSaveMessagesArgs } from "./utils";
import { defaultMessages, defaultSaveMessagesDto } from "./utils";

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

describe("MessageService", () => {
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()) })),
};

const mockMessageBatchService = {
saveMessageBatches: jest.fn().mockImplementation((args) => Promise.resolve(args)),
};
Expand All @@ -18,6 +40,9 @@ describe("MessageService", () => {
};

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

mockMessageBatchService.saveMessageBatches = jest.fn().mockImplementation((args) => Promise.resolve(args));

mockRepository.create = jest.fn().mockImplementation(() => Promise.resolve(defaultMessages));
Expand All @@ -34,7 +59,7 @@ describe("MessageService", () => {
mockRepository as unknown as MessageRepository,
);

const result = await service.saveMessages(defaultSaveMessagesArgs);
const result = await service.saveMessages(defaultSaveMessagesDto);

expect(result).toStrictEqual(defaultMessages);
});
Expand All @@ -49,7 +74,7 @@ describe("MessageService", () => {
mockRepository as unknown as MessageRepository,
);

await expect(service.saveMessages(defaultSaveMessagesArgs)).rejects.toThrow(error);
await expect(service.saveMessages(defaultSaveMessagesDto)).rejects.toThrow(error);
});

test("should publish messages properly", async () => {
Expand Down
21 changes: 17 additions & 4 deletions apps/relayer/ts/message/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,30 @@ import { Keypair } from "maci-domainobjs";

import { defaultMessageBatches } from "../../messageBatch/__tests__/utils";
import { PublishMessagesDto } from "../message.dto";
import { ICreateMessages } from "../types";

const keypair = new Keypair();

export const defaultMessages = defaultMessageBatches[0].messages;

export const defaultSaveMessagesArgs = new PublishMessagesDto();
defaultSaveMessagesArgs.maciContractAddress = ZeroAddress;
defaultSaveMessagesArgs.poll = 0;
defaultSaveMessagesArgs.messages = [
export const defaultSaveMessagesDto = new PublishMessagesDto();
defaultSaveMessagesDto.maciContractAddress = ZeroAddress;
defaultSaveMessagesDto.poll = 0;
defaultSaveMessagesDto.messages = [
{
data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
publicKey: keypair.pubKey.serialize(),
},
];

export const defaultSaveMessagesArgs: ICreateMessages = {
maciContractAddress: defaultSaveMessagesDto.maciContractAddress,
poll: defaultSaveMessagesDto.poll,
messages: [
{
data: defaultSaveMessagesDto.messages[0].data,
hash: "0",
publicKey: defaultSaveMessagesDto.messages[0].publicKey,
},
],
};
2 changes: 1 addition & 1 deletion apps/relayer/ts/message/__tests__/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ describe("PublicKeyValidator", () => {

const result = validator.defaultMessage();

expect(result).toBe("Public key ($value) is invalid");
expect(result).toBe("Public key is invalid");
});
});
7 changes: 7 additions & 0 deletions apps/relayer/ts/message/message.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
ValidateNested,
ArrayNotEmpty,
IsString,
MaxLength,
MinLength,
} from "class-validator";
import { Message } from "maci-domainobjs";

Expand All @@ -33,6 +35,9 @@ export class MessageContractParamsDto {
type: [String],
})
@IsArray()
@IsString({ each: true })
@MinLength(1, { each: true })
@MaxLength(256, { each: true })
@ArrayMinSize(Message.DATA_LENGTH)
@ArrayMaxSize(Message.DATA_LENGTH)
data!: string[];
Expand Down Expand Up @@ -76,6 +81,8 @@ export class PublishMessagesDto {
@ArrayMinSize(8)
@ArrayMaxSize(8)
@IsString({ each: true })
@MinLength(1, { each: true })
@MaxLength(256, { each: true })
proof!: string[];

/**
Expand Down
14 changes: 10 additions & 4 deletions apps/relayer/ts/message/message.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Injectable, Logger } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model, RootFilterQuery } from "mongoose";

import { PublishMessagesDto } from "./message.dto";
import type { ICreateMessages } from "./types";

import { Message, MESSAGES_LIMIT } from "./message.schema";

/**
Expand All @@ -28,11 +29,12 @@ export class MessageRepository {
* @param dto publish messages dto
* @returns inserted messages
*/
async create(dto: PublishMessagesDto): Promise<Message[]> {
const messages = dto.messages.map(({ data, publicKey }) => ({
async create(dto: ICreateMessages): Promise<Message[]> {
const messages = dto.messages.map(({ data, publicKey, hash }) => ({
data,
publicKey,
maciContractAddress: dto.maciContractAddress,
hash,
poll: dto.poll,
}));

Expand All @@ -48,9 +50,13 @@ export class MessageRepository {
* @param filter filter query
* @returns messages
*/
async find(filter: RootFilterQuery<Message>, limit = MESSAGES_LIMIT): Promise<Message[]> {
async find(
filter: RootFilterQuery<Message>,
{ limit = MESSAGES_LIMIT, skip = 0 }: Partial<{ limit: number; skip: number }> = {},
): Promise<Message[]> {
return this.MessageModel.find(filter)
.limit(limit)
.skip(skip)
.exec()
.catch((error) => {
this.logger.error(`Find messages error:`, error);
Expand Down
6 changes: 6 additions & 0 deletions apps/relayer/ts/message/message.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export class Message {
@Prop({ required: true })
data!: string[];

/**
* Message hash
*/
@Prop({ required: true })
hash!: string;

/**
* MACI contract address
*/
Expand Down
18 changes: 17 additions & 1 deletion apps/relayer/ts/message/message.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Injectable, Logger } from "@nestjs/common";
import { Cron, CronExpression } from "@nestjs/schedule";
import { PubKey } from "maci-domainobjs";
import { getDefaultSigner, MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-sdk";

import type { PublishMessagesDto } from "./message.dto";

Expand Down Expand Up @@ -36,7 +38,21 @@ export class MessageService {
* @returns success or not
*/
async saveMessages(args: PublishMessagesDto): Promise<Message[]> {
return this.messageRepository.create(args).catch((error) => {
const signer = await getDefaultSigner();

const maciContract = MACIFactory.connect(args.maciContractAddress, signer);
const pollAddresses = await maciContract.polls(args.poll);
const pollContract = PollFactory.connect(pollAddresses.poll, signer);

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

const messages = args.messages.map((message, index) => ({ ...message, hash: hashes[index].toString() }));

return this.messageRepository.create({ ...args, messages }).catch((error) => {
this.logger.error(`Save messages error:`, error);
throw error;
});
Expand Down
39 changes: 39 additions & 0 deletions apps/relayer/ts/message/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Interface for saving messages
*/
export interface ICreateMessages {
/**
* Messages
*/
messages: ICreateMessageWithHash[];

/**
* MACI contract address
*/
maciContractAddress: string;

/**
* Poll id
*/
poll: number;
}

/**
* Interface for saved messages
*/
export interface ICreateMessageWithHash {
/**
* Message data
*/
data: string[];

/**
* Public key
*/
publicKey: string;

/**
* Message hash
*/
hash: string;
}
2 changes: 1 addition & 1 deletion apps/relayer/ts/message/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ export class PublicKeyValidator implements ValidatorConstraintInterface {
* @returns default validation message
*/
defaultMessage(): string {
return "Public key ($value) is invalid";
return "Public key is invalid";
}
}
Loading

0 comments on commit 6dc818d

Please sign in to comment.