send alert for tx errors, update docker

This commit is contained in:
smart_ex 2022-06-14 20:06:42 +10:00
parent 978c70de1e
commit 2c20febcab
12 changed files with 174 additions and 688 deletions

View File

@ -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 }}

View File

@ -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"]

View File

@ -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

View File

@ -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",

View File

@ -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';

View File

@ -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);

View File

@ -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);
});
};

View File

@ -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) {

View File

@ -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);

View File

@ -9,7 +9,7 @@ export enum AlertLevel {
'WARN' = '⚠️',
'CRITICAL' = '‼️',
'ERROR' = '💩',
'RECOVERED' = '✅'
'OK' = '✅'
}
export enum AlertType {

View File

@ -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');
}
}

727
yarn.lock

File diff suppressed because it is too large Load Diff