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