send alert for tx errors, update docker
This commit is contained in:
parent
978c70de1e
commit
2c20febcab
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -2,8 +2,8 @@ name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
tags: ['v[0-9]+.[0-9]+.[0-9]+']
|
||||
branches: [ '*' ]
|
||||
tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@ -14,7 +14,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 16
|
||||
- run: yarn install
|
||||
- run: yarn test
|
||||
- run: yarn lint
|
||||
@ -51,7 +51,7 @@ jobs:
|
||||
dockerfile: Dockerfile
|
||||
repository: tornadocash/relayer
|
||||
tag_with_ref: true
|
||||
tags: mining,candidate
|
||||
tags: candidate
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
|
33
Dockerfile
33
Dockerfile
@ -1,9 +1,30 @@
|
||||
FROM node:12
|
||||
WORKDIR /app
|
||||
FROM node:16-alpine as dev
|
||||
|
||||
ENV NODE_ENV=development
|
||||
|
||||
WORKDIR /usr/app
|
||||
|
||||
COPY yarn.lock .
|
||||
COPY package.json .
|
||||
|
||||
RUN yarn install && yarn cache clean
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn && yarn cache clean --force
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT ["yarn"]
|
||||
RUN yarn build
|
||||
|
||||
FROM node:16-alpine as production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=dev /usr/app/build /app
|
||||
COPY --from=dev /usr/app/package.json /app/
|
||||
COPY --from=dev /usr/app/yarn.lock /app/
|
||||
|
||||
RUN chown -R node: .
|
||||
|
||||
USER node
|
||||
RUN yarn install --non-interactive --frozen-lockfile && yarn cache clean
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
|
@ -8,30 +8,15 @@ services:
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 8000:8000
|
||||
environment:
|
||||
REDIS_URL: redis://redis/0
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
treeWatcher:
|
||||
image: tornadocash/relayer
|
||||
restart: always
|
||||
command: treeWatcher
|
||||
env_file: .env
|
||||
environment:
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
|
||||
priceWatcher:
|
||||
image: tornadocash/relayer
|
||||
restart: always
|
||||
command: priceWatcher
|
||||
env_file: .env
|
||||
environment:
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
depends_on: [ redis ]
|
||||
|
||||
worker1:
|
||||
image: tornadocash/relayer
|
||||
@ -40,7 +25,7 @@ services:
|
||||
env_file: .env
|
||||
environment:
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
depends_on: [ redis ]
|
||||
|
||||
# worker2:
|
||||
# image: tornadocash/relayer
|
||||
@ -54,7 +39,7 @@ services:
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
command: [redis-server, --appendonly, 'yes']
|
||||
command: [ redis-server, --appendonly, 'yes' ]
|
||||
volumes:
|
||||
- redis:/data
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
"scripts": {
|
||||
"dev:app": "nodemon --watch './src/**/*.ts' --exec ts-node src/app/index.ts",
|
||||
"dev:worker": "nodemon --watch './src/**/*.ts' --exec ts-node src/worker.ts",
|
||||
"build": "tsc",
|
||||
"eslint": "eslint --ext .js --ignore-path .gitignore .",
|
||||
"prettier:check": "npx prettier --check . --config .prettierrc",
|
||||
"prettier:fix": "npx prettier --write . --config .prettierrc",
|
||||
@ -31,14 +32,12 @@
|
||||
"torn-token": "link:../torn-token",
|
||||
"tsyringe": "^4.6.0",
|
||||
"tx-manager": "link:../tx-manager",
|
||||
"uuid": "^8.3.0",
|
||||
"web3": "^1.3.0",
|
||||
"web3-core-promievent": "^1.3.0",
|
||||
"web3-utils": "^1.2.2"
|
||||
"uuid": "^8.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typechain/ethers-v5": "^10.0.0",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/node": "^17.0.42",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
||||
"@typescript-eslint/parser": "^5.20.0",
|
||||
|
@ -14,6 +14,7 @@ export const privateKey = process.env.PRIVATE_KEY;
|
||||
export const instances = tornConfig.instances;
|
||||
export const torn = tornConfig;
|
||||
export const port = process.env.APP_PORT || 8000;
|
||||
export const host = process.env.VIRTUAL_HOST || `localhost:${port}`;
|
||||
export const tornadoServiceFee = Number(process.env.REGULAR_TORNADO_WITHDRAW_FEE);
|
||||
export const rewardAccount = process.env.REWARD_ACCOUNT;
|
||||
export const governanceAddress = '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce';
|
||||
|
@ -6,6 +6,7 @@ export const healthProcessor: Processor = async () => {
|
||||
|
||||
try {
|
||||
await healthService.check();
|
||||
await healthService.clearErrorCodes();
|
||||
await healthService.setStatus({ status: true, error: '' });
|
||||
} catch (e) {
|
||||
await healthService.saveError(e);
|
||||
|
@ -26,8 +26,8 @@ export const relayerWorker = async () => {
|
||||
console.log(`Job ${job.id} completed with result: `, result);
|
||||
});
|
||||
relayer.worker.on('failed', (job, error) => {
|
||||
healthService.saveError(error);
|
||||
// console.log(error);
|
||||
healthService.saveError(error, job.id);
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
host,
|
||||
instances,
|
||||
mainnetRpcUrl,
|
||||
minimumBalance,
|
||||
@ -51,11 +52,13 @@ export class ConfigService {
|
||||
private _tokenAddress: string;
|
||||
private _tokenContract: ERC20Abi;
|
||||
balances: { MAIN: { warn: string; critical: string; }; TORN: { warn: string; critical: string; }; };
|
||||
host: string;
|
||||
|
||||
constructor(private store: RedisStore) {
|
||||
this.netIdKey = `netId${this.netId}`;
|
||||
this.queueName = `relayer_${this.netId}`;
|
||||
this.isLightMode = ![1, 5].includes(netId);
|
||||
this.host = host;
|
||||
this.instances = instances[this.netIdKey];
|
||||
this.provider = getProvider(false);
|
||||
this.mainnentProvider = getProvider(false, mainnetRpcUrl, 1);
|
||||
@ -140,7 +143,8 @@ export class ConfigService {
|
||||
const queueKeys = (await this.store.client.keys('bull:*')).filter(s => s.indexOf('relayer') === -1);
|
||||
const errorKeys = await this.store.client.keys('errors:*');
|
||||
// const alertKeys = await this.store.client.keys('alerts:*');
|
||||
await this.store.client.del([...queueKeys, ...errorKeys]);
|
||||
const keys = [...queueKeys, ...errorKeys];
|
||||
if (keys.length) await this.store.client.del([...queueKeys, ...errorKeys]);
|
||||
}
|
||||
|
||||
getInstance(address: string) {
|
||||
|
@ -2,6 +2,7 @@ import { autoInjectable, container } from 'tsyringe';
|
||||
import { ConfigService } from './config.service';
|
||||
import { RedisStore } from '../modules/redis';
|
||||
import { formatEther } from 'ethers/lib/utils';
|
||||
import { Levels } from './notifier.service';
|
||||
|
||||
class RelayerError extends Error {
|
||||
constructor(message: string, code: string) {
|
||||
@ -18,8 +19,8 @@ export class HealthService {
|
||||
constructor(private config: ConfigService, private store: RedisStore) {
|
||||
}
|
||||
|
||||
async clearErrors() {
|
||||
await this.store.client.del('errors:log', 'errors:code');
|
||||
async clearErrorCodes() {
|
||||
await this.store.client.del('errors:code');
|
||||
}
|
||||
|
||||
private async _getErrors(): Promise<{ errorsLog: { message: string, score: number }[], errorsCode: Record<string, number> }> {
|
||||
@ -57,8 +58,11 @@ export class HealthService {
|
||||
}
|
||||
|
||||
async getStatus() {
|
||||
const heathStatus = await this._getStatus();
|
||||
const { errorsLog, errorsCode } = await this._getErrors();
|
||||
if (errorsCode['NETWORK_ERROR'] > 5) {
|
||||
await this.setStatus({ status: false, error: 'Network error' });
|
||||
}
|
||||
const heathStatus = await this._getStatus();
|
||||
|
||||
return {
|
||||
...heathStatus,
|
||||
@ -67,15 +71,28 @@ export class HealthService {
|
||||
};
|
||||
}
|
||||
|
||||
async saveError(e) {
|
||||
async saveError(e, jobId?: string) {
|
||||
await this.store.client.zadd('errors:code', 'INCR', 1, e?.code || 'RUNTIME_ERROR');
|
||||
await this.store.client.zadd('errors:log', 'INCR', 1, e.message);
|
||||
|
||||
if (e?.code === 'REVERTED') {
|
||||
const jobUrl = `https://${this.config.host}/v1/jobs/${jobId}`;
|
||||
await this.pushAlert({
|
||||
message: `${e.message} \n ${jobUrl}`,
|
||||
type: 'REVERTED',
|
||||
level: 'WARN',
|
||||
time: new Date().getTime(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async pushAlert(alert: Alert) {
|
||||
await this.store.client.rpush('alerts:list', JSON.stringify(alert));
|
||||
}
|
||||
|
||||
private async _checkBalance(value, currency: 'MAIN' | 'TORN') {
|
||||
let level = 'OK';
|
||||
let level: Levels = 'OK';
|
||||
const type = 'BALANCE';
|
||||
const key = 'alerts:list';
|
||||
const time = new Date().getTime();
|
||||
if (value.lt(this.config.balances[currency].critical)) {
|
||||
level = 'CRITICAL';
|
||||
@ -89,7 +106,7 @@ export class HealthService {
|
||||
level,
|
||||
time,
|
||||
};
|
||||
await this.store.client.rpush(key, JSON.stringify(alert));
|
||||
await this.pushAlert(alert);
|
||||
|
||||
return alert;
|
||||
}
|
||||
@ -99,7 +116,7 @@ export class HealthService {
|
||||
const mainBalance = await this.config.wallet.getBalance();
|
||||
const tornBalance = await this.config.tokenContract.balanceOf(this.config.wallet.address);
|
||||
// const mainBalance = BigNumber.from(`${1e18}`).add(1);
|
||||
// const tornBalance = BigNumber.from(`${45e18}`);
|
||||
// const tornBalance = BigNumber.from(`${60e18}`);
|
||||
const mainStatus = await this._checkBalance(mainBalance, 'MAIN');
|
||||
const tornStatus = await this._checkBalance(tornBalance, 'TORN');
|
||||
if (mainStatus.level === 'CRITICAL') {
|
||||
@ -117,5 +134,10 @@ type HealthData = {
|
||||
error: string,
|
||||
errorsLog: { message: string, score: number }[]
|
||||
}
|
||||
|
||||
type Alert = {
|
||||
type: string,
|
||||
message: string,
|
||||
level: Levels,
|
||||
time?: number,
|
||||
}
|
||||
export default () => container.resolve(HealthService);
|
||||
|
@ -9,7 +9,7 @@ export enum AlertLevel {
|
||||
'WARN' = '⚠️',
|
||||
'CRITICAL' = '‼️',
|
||||
'ERROR' = '💩',
|
||||
'RECOVERED' = '✅'
|
||||
'OK' = '✅'
|
||||
}
|
||||
|
||||
export enum AlertType {
|
||||
|
@ -84,14 +84,14 @@ export class TxService {
|
||||
});
|
||||
if (receipt.status === 1) {
|
||||
await this.updateJobData({ status: JobStatus.CONFIRMED });
|
||||
} else throw new Error('Submitted transaction failed');
|
||||
} else throw new ExecutionError('Submitted transaction failed', 'REVERTED');
|
||||
return receipt;
|
||||
} catch (e) {
|
||||
const regex = /body=("\{.*}}")/;
|
||||
if (regex.test(e.message)) {
|
||||
const { error } = parseJSON(regex.exec(e.message)[1]);
|
||||
throw new ExecutionError(error.message, 'REVERTED');
|
||||
} else throw e.message;
|
||||
throw new ExecutionError(error.message, 'REVERTED');
|
||||
} else throw new ExecutionError(e.message, 'SEND_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user