fix: update relayer

-add unique job id
-handle contract errors
-fix queue management
This commit is contained in:
Danil Kovtonyuk 2021-09-30 23:45:11 +10:00 committed by 0xZick
parent bb9d6de05f
commit bd8f40881c
5 changed files with 118 additions and 57 deletions

View File

@ -33,3 +33,14 @@ const FIELD_SIZE = BigNumber.from('218882428718392752222464057452572750885483644
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
export { numbers, NETWORKS_INFO, FIELD_SIZE, BG_ZERO, ZERO_ADDRESS }; export { numbers, NETWORKS_INFO, FIELD_SIZE, BG_ZERO, ZERO_ADDRESS };
export const CONTRACT_ERRORS = [
'Invalid merkle root',
'Input is already spent',
'Incorrect external data hash',
'Invalid fee',
'Invalid ext amount',
'Invalid public amount',
'Invalid transaction proof',
"Can't withdraw to zero address",
];

View File

@ -1,10 +1,12 @@
import { Queue, Job } from 'bull'; import { Queue } from 'bull';
import { v4 as uuid } from 'uuid';
import { InjectQueue } from '@nestjs/bull'; import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ProviderService } from '@/services'; import { ProviderService } from '@/services';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { Transaction } from '@/types';
@Injectable() @Injectable()
class ApiService { class ApiService {
constructor( constructor(
@ -32,13 +34,24 @@ class ApiService {
} }
async transaction(data: any): Promise<string> { async transaction(data: any): Promise<string> {
const job = await this.transactionQueue.add(data); const jobId = uuid();
return String(job.id); await this.transactionQueue.add({ ...data, status: 'QUEUED' }, { jobId });
return jobId;
} }
async getJob(id: string): Promise<Job | null> { async getJob(id: string): Promise<Transaction | null> {
return await this.transactionQueue.getJob(id); const job = await this.transactionQueue.getJob(id);
if (!job) {
return null;
}
return {
...job.data,
failedReason: job.failedReason,
};
} }
private async healthCheck(): Promise<Health> { private async healthCheck(): Promise<Health> {

View File

@ -1,48 +1,18 @@
import { Job, Queue } from 'bull'; import { BigNumber } from 'ethers';
import { BigNumber, BigNumberish } from 'ethers';
import { BytesLike } from '@ethersproject/bytes';
import { TxManager } from 'tx-manager'; import { TxManager } from 'tx-manager';
import { Job, Queue, DoneCallback } from 'bull';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { InjectQueue, Process, Processor } from '@nestjs/bull'; import { InjectQueue, Process, Processor, OnQueueActive, OnQueueCompleted, OnQueueFailed } from '@nestjs/bull';
import { numbers } from '@/constants'; import { numbers, CONTRACT_ERRORS } from '@/constants';
import { toWei, getToIntegerMultiplier } from '@/utilities'; import { toWei, getToIntegerMultiplier } from '@/utilities';
import { GasPriceService, ProviderService } from '@/services'; import { GasPriceService, ProviderService } from '@/services';
import txMangerConfig from '@/config/txManager.config'; import txMangerConfig from '@/config/txManager.config';
import { BaseProcessor } from './base.processor'; import { BaseProcessor } from './base.processor';
import { ChainId } from '@/types'; import { ChainId, Transaction } from '@/types';
export type ExtData = {
recipient: string;
relayer: string;
fee: BigNumberish;
extAmount: BigNumberish;
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
};
export type ArgsProof = {
proof: BytesLike;
root: BytesLike;
newRoot: BytesLike;
inputNullifiers: string[];
outputCommitments: BytesLike[];
outPathIndices: string;
publicAmount: string;
extDataHash: string;
};
export interface Transaction {
extData: ExtData;
args: ArgsProof;
txHash: string;
status: string;
confirmations: number;
}
@Injectable() @Injectable()
@Processor('transaction') @Processor('transaction')
export class TransactionProcessor extends BaseProcessor<Transaction> { export class TransactionProcessor extends BaseProcessor<Transaction> {
@ -58,19 +28,40 @@ export class TransactionProcessor extends BaseProcessor<Transaction> {
} }
@Process() @Process()
async processTransactions(job: Job<Transaction>) { async processTransactions(job: Job<Transaction>, cb: DoneCallback) {
try { try {
await job.isActive();
const { extData } = job.data; const { extData } = job.data;
await this.checkFee({ fee: extData.fee, externalAmount: extData.extAmount }); await this.checkFee({ fee: extData.fee, externalAmount: extData.extAmount });
await this.submitTx(job); await this.submitTx(job);
cb(null);
} catch (err) { } catch (err) {
await job.moveToFailed(err, true); cb(err);
} }
} }
@OnQueueActive()
async onActive(job: Job) {
job.data.status = 'ACCEPTED';
await job.update(job.data);
}
@OnQueueCompleted()
async onCompleted(job: Job) {
job.data.status = 'CONFIRMED';
await job.update(job.data);
}
@OnQueueFailed()
async onFailed(job: Job) {
job.data.status = 'FAILED';
await job.update(job.data);
}
async submitTx(job: Job<Transaction>) { async submitTx(job: Job<Transaction>) {
try { try {
const txManager = new TxManager(txMangerConfig()); const txManager = new TxManager(txMangerConfig());
@ -97,17 +88,11 @@ export class TransactionProcessor extends BaseProcessor<Transaction> {
await job.update(job.data); await job.update(job.data);
}); });
if (receipt.status === 1) { if (receipt.status !== 1) {
await job.isCompleted();
job.data.status = 'SENT';
await job.update(job.data);
} else {
throw new Error('Submitted transaction failed'); throw new Error('Submitted transaction failed');
} }
} catch (e) { } catch (err) {
throw new Error(`Revert by smart contract ${e.message}`); return this.handleError(err);
} }
} }
@ -170,4 +155,21 @@ export class TransactionProcessor extends BaseProcessor<Transaction> {
throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.'); throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.');
} }
} }
handleError(e) {
// Sometimes ethers wraps known errors, unwrap it in this case
if (e?.error?.error) {
e = e.error;
}
const message = e?.error ? e.error.message : e.message;
const error = CONTRACT_ERRORS.find((e) => (typeof e === 'string' ? e === message : message.match(e)));
if (error) {
throw new Error(`Revert by smart contract: ${error}`);
}
throw new Error('Relayer did not send your transaction. Please choose a different relayer.');
}
} }

View File

@ -1,9 +1,39 @@
const MAINNET_CHAIN_ID = 1 import { BigNumberish } from 'ethers';
const GOERLI_CHAIN_ID = 5 import { BytesLike } from '@ethersproject/bytes';
const OPTIMISM_CHAIN_ID = 69
const MAINNET_CHAIN_ID = 1;
const GOERLI_CHAIN_ID = 5;
const OPTIMISM_CHAIN_ID = 69;
export enum ChainId { export enum ChainId {
MAINNET = MAINNET_CHAIN_ID, MAINNET = MAINNET_CHAIN_ID,
GOERLI = GOERLI_CHAIN_ID, GOERLI = GOERLI_CHAIN_ID,
OPTIMISM = OPTIMISM_CHAIN_ID, OPTIMISM = OPTIMISM_CHAIN_ID,
} }
export type ExtData = {
recipient: string;
relayer: string;
fee: BigNumberish;
extAmount: BigNumberish;
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
};
export type ArgsProof = {
proof: BytesLike;
root: BytesLike;
inputNullifiers: string[];
outputCommitments: BytesLike[];
publicAmount: string;
extDataHash: string;
};
export interface Transaction {
extData: ExtData;
args: ArgsProof;
status: string;
txHash?: string;
confirmations?: number;
failedReason?: string;
}

View File

@ -5871,11 +5871,16 @@ utils-merge@1.0.1:
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2: uuid@8.3.2, uuid@^8.3.0:
version "8.3.2" version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3: v8-compile-cache@^2.0.3:
version "2.3.0" version "2.3.0"
resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"