-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1993 from privacy-scaling-explorations/feature/re…
…layer-api feat(relayer): add publish message api method
- Loading branch information
Showing
14 changed files
with
438 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { HttpStatus, ValidationPipe, type INestApplication } from "@nestjs/common"; | ||
import { Test } from "@nestjs/testing"; | ||
import { ZeroAddress } from "ethers"; | ||
import { Keypair } from "maci-domainobjs"; | ||
import request from "supertest"; | ||
|
||
import type { App } from "supertest/types"; | ||
|
||
import { AppModule } from "../ts/app.module"; | ||
|
||
describe("e2e messages", () => { | ||
let app: INestApplication; | ||
|
||
beforeAll(async () => { | ||
const moduleFixture = await Test.createTestingModule({ | ||
imports: [AppModule], | ||
}).compile(); | ||
|
||
app = moduleFixture.createNestApplication(); | ||
app.useGlobalPipes(new ValidationPipe({ transform: true })); | ||
await app.listen(3001); | ||
}); | ||
|
||
afterAll(async () => { | ||
await app.close(); | ||
}); | ||
|
||
describe("/v1/messages/publish", () => { | ||
const keypair = new Keypair(); | ||
|
||
const defaultSaveMessagesArgs = { | ||
maciContractAddress: ZeroAddress, | ||
poll: 0, | ||
messages: [ | ||
{ | ||
data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], | ||
publicKey: keypair.pubKey.serialize(), | ||
}, | ||
], | ||
}; | ||
|
||
test("should throw an error if dto is invalid", async () => { | ||
const result = await request(app.getHttpServer() as App) | ||
.post("/v1/messages/publish") | ||
.send({ | ||
maciContractAddress: "invalid", | ||
poll: "-1", | ||
messages: [], | ||
}) | ||
.expect(HttpStatus.BAD_REQUEST); | ||
|
||
expect(result.body).toStrictEqual({ | ||
error: "Bad Request", | ||
statusCode: HttpStatus.BAD_REQUEST, | ||
message: [ | ||
"poll must not be less than 0", | ||
"poll must be an integer number", | ||
"maciContractAddress must be an Ethereum address", | ||
"messages must contain at least 1 elements", | ||
], | ||
}); | ||
}); | ||
|
||
test("should throw an error if messages dto is invalid", async () => { | ||
const result = await request(app.getHttpServer() as App) | ||
.post("/v1/messages/publish") | ||
.send({ | ||
...defaultSaveMessagesArgs, | ||
messages: [{ data: [], publicKey: "invalid" }], | ||
}) | ||
.expect(HttpStatus.BAD_REQUEST); | ||
|
||
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"], | ||
}); | ||
}); | ||
|
||
test("should publish user messages properly", async () => { | ||
const result = await request(app.getHttpServer() as App) | ||
.post("/v1/messages/publish") | ||
.send(defaultSaveMessagesArgs) | ||
.expect(HttpStatus.CREATED); | ||
|
||
expect(result.status).toBe(HttpStatus.CREATED); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
apps/relayer/ts/message/__tests__/message.controller.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { HttpException, HttpStatus } from "@nestjs/common"; | ||
import { Test } from "@nestjs/testing"; | ||
|
||
import { MessageController } from "../message.controller"; | ||
import { MessageService } from "../message.service"; | ||
|
||
import { defaultSaveMessagesArgs } from "./utils"; | ||
|
||
describe("MessageController", () => { | ||
let controller: MessageController; | ||
|
||
const mockMessageService = { | ||
saveMessages: jest.fn(), | ||
merge: jest.fn(), | ||
}; | ||
|
||
beforeEach(async () => { | ||
const app = await Test.createTestingModule({ | ||
controllers: [MessageController], | ||
}) | ||
.useMocker((token) => { | ||
if (token === MessageService) { | ||
mockMessageService.saveMessages.mockResolvedValue(true); | ||
|
||
return mockMessageService; | ||
} | ||
|
||
return jest.fn(); | ||
}) | ||
.compile(); | ||
|
||
controller = app.get<MessageController>(MessageController); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe("v1/messages/publish", () => { | ||
test("should publish user messages properly", async () => { | ||
const data = await controller.publish(defaultSaveMessagesArgs); | ||
|
||
expect(data).toBe(true); | ||
}); | ||
|
||
test("should throw an error if messages saving is failed", async () => { | ||
const error = new Error("error"); | ||
mockMessageService.saveMessages.mockRejectedValue(error); | ||
|
||
await expect(controller.publish(defaultSaveMessagesArgs)).rejects.toThrow( | ||
new HttpException(error.message, HttpStatus.BAD_REQUEST), | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { MessageService } from "../message.service"; | ||
|
||
import { defaultSaveMessagesArgs } from "./utils"; | ||
|
||
describe("MessageService", () => { | ||
test("should save messages properly", async () => { | ||
const service = new MessageService(); | ||
|
||
const result = await service.saveMessages(defaultSaveMessagesArgs); | ||
|
||
expect(result).toBe(true); | ||
}); | ||
|
||
test("should publish messages properly", async () => { | ||
const service = new MessageService(); | ||
|
||
const result = await service.publishMessages(defaultSaveMessagesArgs); | ||
|
||
expect(result).toStrictEqual({ hash: "", ipfsHash: "" }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ZeroAddress } from "ethers"; | ||
import { Keypair } from "maci-domainobjs"; | ||
|
||
const keypair = new Keypair(); | ||
|
||
export const defaultSaveMessagesArgs = { | ||
maciContractAddress: ZeroAddress, | ||
poll: 0, | ||
messages: [ | ||
{ | ||
data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], | ||
publicKey: keypair.pubKey.serialize(), | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Keypair } from "maci-domainobjs"; | ||
|
||
import { PublicKeyValidator } from "../validation"; | ||
|
||
describe("PublicKeyValidator", () => { | ||
test("should validate valid public key", () => { | ||
const keypair = new Keypair(); | ||
const validator = new PublicKeyValidator(); | ||
|
||
const result = validator.validate(keypair.pubKey.serialize()); | ||
|
||
expect(result).toBe(true); | ||
}); | ||
|
||
test("should validate invalid public key", () => { | ||
const validator = new PublicKeyValidator(); | ||
|
||
const result = validator.validate("invalid"); | ||
|
||
expect(result).toBe(false); | ||
}); | ||
|
||
test("should return default message properly", () => { | ||
const validator = new PublicKeyValidator(); | ||
|
||
const result = validator.defaultMessage(); | ||
|
||
expect(result).toBe("Public key ($value) is invalid"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { ApiProperty } from "@nestjs/swagger"; | ||
import { Type } from "class-transformer"; | ||
import { | ||
IsEthereumAddress, | ||
IsInt, | ||
Min, | ||
Validate, | ||
IsArray, | ||
ArrayMinSize, | ||
ArrayMaxSize, | ||
ValidateNested, | ||
} from "class-validator"; | ||
import { Message } from "maci-domainobjs"; | ||
|
||
import { PublicKeyValidator } from "./validation"; | ||
|
||
/** | ||
* Max messages per batch | ||
*/ | ||
const MAX_MESSAGES = 20; | ||
|
||
/** | ||
* Data transfer object for user message | ||
*/ | ||
export class MessageContractParamsDto { | ||
/** | ||
* Message data | ||
*/ | ||
@ApiProperty({ | ||
description: "Message data", | ||
type: [String], | ||
}) | ||
@IsArray() | ||
@ArrayMinSize(Message.DATA_LENGTH) | ||
@ArrayMaxSize(Message.DATA_LENGTH) | ||
data!: string[]; | ||
|
||
/** | ||
* Public key | ||
*/ | ||
@ApiProperty({ | ||
description: "Public key", | ||
type: String, | ||
}) | ||
@Validate(PublicKeyValidator) | ||
publicKey!: string; | ||
} | ||
|
||
/** | ||
* Data transfer object for publish messages | ||
*/ | ||
export class PublishMessagesDto { | ||
/** | ||
* Poll id | ||
*/ | ||
@ApiProperty({ | ||
description: "Poll id", | ||
minimum: 0, | ||
type: Number, | ||
}) | ||
@IsInt() | ||
@Min(0) | ||
poll!: number; | ||
|
||
/** | ||
* Maci contract address | ||
*/ | ||
@ApiProperty({ | ||
description: "MACI contract address", | ||
type: String, | ||
}) | ||
@IsEthereumAddress() | ||
maciContractAddress!: string; | ||
|
||
/** | ||
* Messages | ||
*/ | ||
@ApiProperty({ | ||
description: "User messages with public key", | ||
type: [MessageContractParamsDto], | ||
}) | ||
@IsArray() | ||
@ArrayMinSize(1) | ||
@ArrayMaxSize(MAX_MESSAGES) | ||
@ValidateNested({ each: true }) | ||
@Type(() => MessageContractParamsDto) | ||
messages!: MessageContractParamsDto[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* eslint-disable @typescript-eslint/no-shadow */ | ||
import { Body, Controller, HttpException, HttpStatus, Logger, Post } from "@nestjs/common"; | ||
import { ApiBearerAuth, ApiBody, ApiResponse, ApiTags } from "@nestjs/swagger"; | ||
|
||
import { PublishMessagesDto } from "./dto"; | ||
import { MessageService } from "./message.service"; | ||
|
||
@ApiTags("v1/messages") | ||
@ApiBearerAuth() | ||
@Controller("v1/messages") | ||
export class MessageController { | ||
/** | ||
* Logger | ||
*/ | ||
private readonly logger = new Logger(MessageController.name); | ||
|
||
/** | ||
* Initialize MessageController | ||
* | ||
*/ | ||
constructor(private readonly messageService: MessageService) {} | ||
|
||
/** | ||
* Publish user messages api method. | ||
* Saves messages batch and then send them onchain by calling `publishMessages` method via cron job. | ||
* | ||
* @param args - publish messages dto | ||
* @returns success or not | ||
*/ | ||
@ApiBody({ type: PublishMessagesDto }) | ||
@ApiResponse({ status: HttpStatus.CREATED, description: "The messages have been successfully accepted" }) | ||
@ApiResponse({ status: HttpStatus.FORBIDDEN, description: "Forbidden" }) | ||
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: "BadRequest" }) | ||
@Post("publish") | ||
async publish(@Body() args: PublishMessagesDto): Promise<boolean> { | ||
return this.messageService.saveMessages(args).catch((error: Error) => { | ||
this.logger.error(`Error:`, error); | ||
throw new HttpException(error.message, HttpStatus.BAD_REQUEST); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Module } from "@nestjs/common"; | ||
|
||
import { MessageController } from "./message.controller"; | ||
import { MessageService } from "./message.service"; | ||
|
||
@Module({ | ||
controllers: [MessageController], | ||
providers: [MessageService], | ||
}) | ||
export class MessageModule {} |
Oops, something went wrong.