Compare commits
64 Commits
sidechain-
...
mainnet-v5
| Author | SHA1 | Date | |
|---|---|---|---|
| 52473197ea | |||
| cb1212d793 | |||
| 531567d8b2 | |||
| 67c0782794 | |||
| 40c55b3e7c | |||
| fd72f09e11 | |||
| fdbbb05733 | |||
| eb908ff1f5 | |||
| 44f70bd41d | |||
| 16a17079eb | |||
| 6adeb27b83 | |||
| 58dae5b030 | |||
| 3b13d3f508 | |||
| 6b1a41585b | |||
| e90d1c4086 | |||
| 61f464441b | |||
|
|
ed5d99cf44 | ||
|
|
7d10fe2ab9 | ||
|
|
7d3cb5be49 | ||
|
|
ff05e30bd2 | ||
|
|
1e8ddffdf1 | ||
|
|
3dc9314e29 | ||
|
|
5f3da2578a | ||
|
|
cd6bd25d2c | ||
|
|
2247730603 | ||
|
|
c915c97b85 | ||
|
|
582af773e6 | ||
|
|
9488090892 | ||
|
|
632dce129d | ||
|
|
76cda01ee1 | ||
|
|
7f657c1d7d | ||
|
|
e386a1d23c | ||
|
|
16d8e0fc28 | ||
|
|
a7fc9c4b24 | ||
|
|
ee9e27ecad | ||
|
|
95c6dc23c6 | ||
|
|
cfcf1c8677 | ||
|
|
8868040882 | ||
|
|
50054e0516 | ||
|
|
76209e11c0 | ||
|
|
3c5eaa2c4b | ||
|
|
fd36dd5c5e | ||
|
|
350d1f1d11 | ||
|
|
49a90872a2 | ||
|
|
2f79125dd1 | ||
|
|
1f900843de | ||
|
|
cc5ec13d97 | ||
|
|
9e8a6c79ac | ||
|
|
38b1169eae | ||
|
|
d95d495853 | ||
|
|
54e1c03f9e | ||
|
|
429723f370 | ||
|
|
b457820cf5 | ||
|
|
31d697701e | ||
|
|
b103033103 | ||
|
|
1bb2d5f044 | ||
|
|
44f1e1ec7a | ||
|
|
043356f5fe | ||
|
|
8f5b673f3b | ||
|
|
82e5f4ee70 | ||
|
|
cc0a252040 | ||
|
|
a46afed752 | ||
|
|
0cd9e515ae | ||
|
|
fa77997896 |
@@ -1,8 +1,8 @@
|
||||
NET_ID=1
|
||||
HTTP_RPC_URL=https://mainnet.infura.io
|
||||
HTTP_RPC_URL=https://api.securerpc.com/v1
|
||||
WS_RPC_URL=wss://mainnet.infura.io/ws/v3/
|
||||
# ORACLE_RPC_URL should always point to the mainnet
|
||||
ORACLE_RPC_URL=https://mainnet.infura.io
|
||||
ORACLE_RPC_URL=https://api.securerpc.com/v1
|
||||
REDIS_URL=redis://127.0.0.1:6379
|
||||
|
||||
# DNS settings
|
||||
@@ -13,11 +13,12 @@ APP_PORT=8000
|
||||
# without 0x prefix
|
||||
PRIVATE_KEY=
|
||||
# 0.05 means 0.05%
|
||||
REGULAR_TORNADO_WITHDRAW_FEE=0.05
|
||||
RELAYER_FEE=0.4
|
||||
MINING_SERVICE_FEE=0.05
|
||||
REWARD_ACCOUNT=
|
||||
CONFIRMATIONS=4
|
||||
|
||||
# in GWEI
|
||||
MAX_GAS_PRICE=1000
|
||||
AGGREGATOR=0x8cb1436F64a3c33aD17bb42F94e255c4c0E871b2
|
||||
BASE_FEE_RESERVE_PERCENTAGE=25
|
||||
AGGREGATOR=0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49
|
||||
|
||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- run: yarn test
|
||||
- run: yarn lint
|
||||
- name: Telegram Failure Notification
|
||||
uses: appleboy/telegram-action@0.0.7
|
||||
uses: appleboy/telegram-action@master
|
||||
if: failure()
|
||||
with:
|
||||
message: ❗ Build failed for [${{ github.repository }}](https://github.com/${{ github.repository }}/actions) because of ${{ github.actor }}
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Telegram Message Notify
|
||||
uses: appleboy/telegram-action@0.0.7
|
||||
uses: appleboy/telegram-action@master
|
||||
with:
|
||||
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
@@ -65,22 +65,35 @@ jobs:
|
||||
format: markdown
|
||||
|
||||
- name: Telegram Relayer Channel Notification
|
||||
uses: appleboy/telegram-action@0.0.7
|
||||
uses: appleboy/telegram-action@master
|
||||
with:
|
||||
to: ${{ secrets.TELEGRAM_RELAYER_CHAT_ID }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
message: |
|
||||
🚀 Published a new version of the relayer node service to docker hub: `tornadocash/relayer:v${{ steps.vars.outputs.version }}` and `tornadocash/relayer:mining`.
|
||||
🚀 Published a new version of the relayer node service for mainnet to docker hub: `tornadocash/relayer:v${{ steps.vars.outputs.version }}` and `tornadocash/relayer:mining`.
|
||||
|
||||
❗️Please update your mainnet nodes ❗️
|
||||
DO NOT TOUCH SIDECHAINS AND NOVA RELAYERS.
|
||||
|
||||
❗️Please update your nodes ❗️
|
||||
debug: true
|
||||
format: markdown
|
||||
|
||||
- name: Discord Relayer Channel Notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELAYER_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: |
|
||||
🚀 Published a new version of the relayer node service for mainnet to docker hub: `tornadocash/relayer:v${{ steps.vars.outputs.version }}` and `tornadocash/relayer:mining`.
|
||||
|
||||
❗️Please update your mainnet nodes ❗️
|
||||
DO NOT TOUCH SIDECHAINS AND NOVA RELAYERS.
|
||||
|
||||
- name: Telegram Failure Notification
|
||||
uses: appleboy/telegram-action@0.0.7
|
||||
uses: appleboy/telegram-action@master
|
||||
if: failure()
|
||||
with:
|
||||
message: ❗ Failed to publish [${{ steps.vars.outputs.repo_name }}](https://github.com/${{ github.repository }}/actions) because of ${{ github.actor }}
|
||||
message: ❗ Failed to publish [${{ steps.vars.outputs.repo_name }}](https://github.com/${{ github.repository }}/actions):v${{ steps.vars.outputs.version }} for mainnet because of ${{ github.actor }}
|
||||
format: markdown
|
||||
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
|
||||
1
.npmrc
Normal file
1
.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
@tornado:registry=https://git.tornado.ws/api/packages/tornado-packages/npm/
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:12
|
||||
FROM node:16
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
131
README.md
131
README.md
@@ -1,83 +1,100 @@
|
||||
# Relayer for Tornado Cash [](https://github.com/tornadocash/relayer/actions) [](https://hub.docker.com/repository/docker/tornadocash/relayer)
|
||||
# Relayer for Tornado Cash [](https://github.com/tornadocash/relayer/actions) 
|
||||
|
||||
## Getting listed on app.tornado.cash
|
||||
__*Tornado Cash was sanctioned by the US Treasury on 08/08/2022, this makes it illegal for US citizens to interact with Tornado Cash and all of it's associated deployed smart contracts. Please understand the laws where you live and take all necessary steps to protect and anonymize yourself.__
|
||||
|
||||
If you would like to be listed in tornado.cash UI relayer's dropdown option, please do the following:
|
||||
__*It is recommended to run your Relayer on a VPS instance ([Virtual Private Server](https://njal.la/)). Ensure SSH configuration is enabled for security, you can find information about SSH keygen and management [here](https://www.ssh.com/academy/ssh/keygen).__
|
||||
|
||||
1. Setup tornado.cash relayer node(see below for docker-compose.yml example)
|
||||
2. Setup ENS subdomain(`goerli-v2.xxx.eth`, `mainnet-v2.xxx.eth`) with TEXT record and URL key that points to your DNS or IP address.
|
||||
3. Test your relayer setup on Goerli testnet at https://app.tornado.cash by choosing custom relayer's option on withdraw tab. Enter your ens name and initiate a withdrawal.
|
||||
4. Open new Github issue in https://github.com/tornadocash/tornado-relayer/issues and specify the following:
|
||||
## Deploy with docker-compose (recommended)
|
||||
|
||||
- your goerli ens url
|
||||
- your mainnet ens url
|
||||
- your telegram handle
|
||||
- withdrawal tx on goerli
|
||||
- withdrawal tx on mainnet
|
||||
*The following instructions are for Ubuntu 22.10, other operating systems may vary. These instructions include automated SSL configuration with LetsEncrypt.*
|
||||
|
||||
Please choose your testnet relayer's fee wisely.
|
||||
__PREREQUISITES__
|
||||
1. Update core dependencies
|
||||
- `sudo apt-get update`
|
||||
2. Install docker-compose
|
||||
- `curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose && sudo chmod +x /usr/local/bin/docker-compose`
|
||||
3. Install Docker
|
||||
- `curl -fsSL https://get.docker.com -o get-docker.sh && chmod +x get-docker.sh && ./get-docker.sh`
|
||||
4. Install git
|
||||
- `sudo apt-get install git-all`
|
||||
5. Install nginx
|
||||
- `sudo apt install nginx`
|
||||
6. Stop apache2 instance (enabled by default)
|
||||
- `sudo systemctl stop apache2`
|
||||
|
||||
Disclaimer: Please consult with legal and tax advisors regarding the compliance of running a relayer service in your jurisdiction. The authors of this project bear no responsibility.
|
||||
__FIREWALL CONFIGURATION__
|
||||
|
||||
USE AT YOUR OWN RISK.
|
||||
_* Warning: Failure to configure SSH as the first UFW rule, will lock you out of the instance_
|
||||
|
||||
## Deploy with docker-compose
|
||||
1. Make sure UFW is installed by running `apt update` and `apt install ufw`
|
||||
2. Allow SSH in the first position in UFW by running `ufw insert 1 allow ssh`*
|
||||
3. Allow HTTP, and HTTPS by running `ufw allow https/tcp/http`
|
||||
4. Finalize changes and enable firewall `ufw enable`
|
||||
|
||||
docker-compose.yml contains a stack that will automatically provision SSL certificates for your domain name and will add a https redirect to port 80.
|
||||
__DEPLOYMENT__
|
||||
1. Clone the repository and enter the directory
|
||||
- `git clone https://git.tornado.ws/tornadocash/classic-relayer -b mainnet-v5 && cd classic-relayer`
|
||||
2. Clone the example environment file `.env.example` to configure for the preferred network - `cp .env.example .env` , then fill `.env` file.
|
||||
- Set `PRIVATE_KEY` for your relayer address (remove the 0x from your private key)
|
||||
- Set `VIRTUAL_HOST` and `LETSENCRYPT_HOST` to your domain address
|
||||
- add a A record DNS record with the value assigned to your instance IP address to configure the domain
|
||||
- Set `RELAYER_FEE` to what you would like to charge as your fee (remember 0.3% is deducted from your staked relayer balance)
|
||||
- Set `RPC_URL` to a non-censoring RPC endpoint (You can [run your own](https://github.com/feshchenkod/rpc-nodes), or use a [free option](https://chainnodes.org/))
|
||||
- Set `ORACLE_RPC_URL` to an Ethereum native RPC endpoint
|
||||
|
||||
1. Download [docker-compose.yml](/docker-compose.yml) and [.env.example](/.env.example)
|
||||
4. Uncomment the `env_file` lines (remove `# `) for the associated network services in `docker-compose.yml`
|
||||
5. Build and deploy the docker source by specifying the network through:
|
||||
|
||||
```
|
||||
wget https://raw.githubusercontent.com/tornadocash/tornado-relayer/master/docker-compose.yml
|
||||
wget https://raw.githubusercontent.com/tornadocash/tornado-relayer/master/.env.example -O .env
|
||||
```
|
||||
- `npm run build`
|
||||
- `docker-compose up -d`
|
||||
5. Visit your domain address and check the `/status` endpoint and ensure there is no errors in the `status` field
|
||||
|
||||
2. Setup environment variables
|
||||
|
||||
- set `NET_ID` (1 for mainnet, 5 for Goerli)
|
||||
- set `HTTP_RPC_URL` rpc url for your ethereum node
|
||||
- set `WS_RPC_URL` websocket url
|
||||
- set `ORACLE_RPC_URL` - rpc url for mainnet node for fetching prices(always have to be on mainnet)
|
||||
- set `PRIVATE_KEY` for your relayer address (without 0x prefix)
|
||||
- set `VIRTUAL_HOST` and `LETSENCRYPT_HOST` to your domain and add DNS record pointing to your relayer ip address
|
||||
- set `REGULAR_TORNADO_WITHDRAW_FEE` - fee in % that is used for tornado pool withdrawals
|
||||
- set `MINING_SERVICE_FEE` - fee in % that is used for mining AP withdrawals
|
||||
- set `REWARD_ACCOUNT` - eth address that is used to collect fees
|
||||
- update `AGGREGATOR` if needed - Contract address of aggregator instance.
|
||||
- update `CONFIRMATIONS` if needed - how many block confirmations to wait before processing an event. Not recommended to set less than 3
|
||||
- update `MAX_GAS_PRICE` if needed - maximum value of gwei value for relayer's transaction
|
||||
|
||||
If you want to use more than 1 eth address for relaying transactions, please add as many `workers` as you want. For example, you can comment out `worker2` in docker-compose.yml file, but please use a different `PRIVATE_KEY` for each worker.
|
||||
|
||||
3. Run `docker-compose up -d`
|
||||
__NGINX REVERSE PROXY__
|
||||
1. Copy the pre-modified nginx policy as your default policy
|
||||
- `cp tornado.conf /etc/nginx/sites-available/default`
|
||||
2. Append the default nginx configuration to include streams
|
||||
- `echo "stream { map_hash_bucket_size 128; map_hash_max_size 128; include /etc/nginx/conf.d/streams/*.conf; }" >> /etc/nginx/nginx.conf`
|
||||
3. Create the stream configuration
|
||||
- `mkdir /etc/nginx/conf.d/streams && cp tornado-stream.conf /etc/nginx/conf.d/streams/tornado-stream.conf`
|
||||
4. Start nginx to make sure the configuration is correct
|
||||
- `sudo systemctl restart nginx`
|
||||
5. Stop nginx
|
||||
- `sudo systemctl stop nginx`
|
||||
|
||||
## Run locally
|
||||
|
||||
1. `npm i`
|
||||
2. `cp .env.example .env`
|
||||
3. Modify `.env` as needed
|
||||
4. `npm run start`
|
||||
5. Go to `http://127.0.0.1:8000`
|
||||
6. In order to execute withdraw request, you can run following command
|
||||
1. `npm i -g yarn` (if yarn not installed in your system)
|
||||
2. `yarn`
|
||||
3. `cp .env.mainnet.example .env`
|
||||
4. Modify `.env` as needed
|
||||
5. `yarn start`
|
||||
6. Go to `http://127.0.0.1:8000`
|
||||
7. In order to execute withdraw request, you can run following command
|
||||
|
||||
```bash
|
||||
curl -X POST -H 'content-type:application/json' --data '<input data>' http://127.0.0.1:8000/relay
|
||||
```
|
||||
|
||||
Relayer should return a transaction hash
|
||||
Relayer should return a transaction hash.
|
||||
|
||||
In that case you will need to add https termination yourself because browsers with default settings will prevent https
|
||||
tornado.cash UI from submitting your request over http connection
|
||||
_Note._ If you want to change contracts' addresses go to [config.js](./config.js) file.
|
||||
|
||||
## Architecture
|
||||
## Input data example
|
||||
|
||||
1. TreeWatcher module keeps track of Account Tree changes and automatically caches the actual state in Redis and emits `treeUpdate` event to redis pub/sub channel
|
||||
2. Server module is Express.js instance that accepts http requests
|
||||
3. Controller contains handlers for the Server endpoints. It validates input data and adds a Job to Queue.
|
||||
4. Queue module is used by Controller to put and get Job from queue (bull wrapper)
|
||||
5. Status module contains handler to get a Job status. It's used by UI for pull updates
|
||||
6. Validate contains validation logic for all endpoints
|
||||
7. Worker is the main module that gets a Job from queue and processes it
|
||||
```json
|
||||
{
|
||||
"proof": "0x0f8cb4c2ca9cbb23a5f21475773e19e39d3470436d7296f25c8730d19d88fcef2986ec694ad094f4c5fff79a4e5043bd553df20b23108bc023ec3670718143c20cc49c6d9798e1ae831fd32a878b96ff8897728f9b7963f0d5a4b5574426ac6203b2456d360b8e825d8f5731970bf1fc1b95b9713e3b24203667ecdd5939c2e40dec48f9e51d9cc8dc2f7f3916f0e9e31519c7df2bea8c51a195eb0f57beea4924cb846deaa78cdcbe361a6c310638af6f6157317bc27d74746bfaa2e1f8d2e9088fd10fa62100740874cdffdd6feb15c95c5a303f6bc226d5e51619c5b825471a17ddfeb05b250c0802261f7d05cf29a39a72c13e200e5bc721b0e4c50d55e6",
|
||||
"args": [
|
||||
"0x1579d41e5290ab5bcec9a7df16705e49b5c0b869095299196c19c5e14462c9e3",
|
||||
"0x0cf7f49c5b35c48b9e1d43713e0b46a75977e3d10521e9ac1e4c3cd5e3da1c5d",
|
||||
"0x03ebd0748aa4d1457cf479cce56309641e0a98f5",
|
||||
"0xbd4369dc854c5d5b79fe25492e3a3cfcb5d02da5",
|
||||
"0x000000000000000000000000000000000000000000000000058d15e176280000",
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
],
|
||||
"contract": "0xA27E34Ad97F171846bAf21399c370c9CE6129e0D"
|
||||
}
|
||||
```
|
||||
|
||||
Disclaimer:
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "contract IERC20", "name": "srcToken", "type": "address" },
|
||||
{ "internalType": "contract IERC20", "name": "dstToken", "type": "address" }
|
||||
],
|
||||
"name": "getRate",
|
||||
"outputs": [{ "internalType": "uint256", "name": "weightedRate", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
2
app.js
2
app.js
@@ -1 +1 @@
|
||||
module.exports = require('./src/index')
|
||||
module.exports = require('./src/server')
|
||||
|
||||
85902
cache/accounts_farmer_1.json
vendored
Normal file
85902
cache/accounts_farmer_1.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5878
cache/accounts_farmer_5.json
vendored
Normal file
5878
cache/accounts_farmer_5.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,62 +0,0 @@
|
||||
version: '2'
|
||||
|
||||
# ssh-agent && ssh-add -K ~/.ssh/id_rsa
|
||||
# DOCKER_BUILDKIT=1 docker build --ssh default -t tornadocash/relayer .
|
||||
services:
|
||||
server:
|
||||
image: tornadocash/relayer
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env
|
||||
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]
|
||||
|
||||
worker1:
|
||||
image: tornadocash/relayer
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env
|
||||
environment:
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
|
||||
# worker2:
|
||||
# image: tornadocash/relayer
|
||||
# restart: always
|
||||
# command: worker
|
||||
# env_file: .env
|
||||
# environment:
|
||||
# PRIVATE_KEY: qwe
|
||||
# REDIS_URL: redis://redis/0
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
command: [redis-server, --appendonly, 'yes']
|
||||
volumes:
|
||||
- redis:/data
|
||||
|
||||
volumes:
|
||||
redis:
|
||||
@@ -1,59 +1,118 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
server:
|
||||
image: tornadocash/relayer:mining
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
command: [redis-server, --appendonly, 'yes']
|
||||
volumes:
|
||||
- redis:/data
|
||||
ports:
|
||||
- '127.0.0.1:6379:6379'
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: nginx
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- certs:/etc/nginx/certs
|
||||
logging:
|
||||
driver: none
|
||||
|
||||
dockergen:
|
||||
image: poma/docker-gen
|
||||
container_name: dockergen
|
||||
restart: always
|
||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||
volumes_from:
|
||||
- nginx
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
letsencrypt:
|
||||
image: jrcs/letsencrypt-nginx-proxy-companion
|
||||
container_name: letsencrypt
|
||||
restart: always
|
||||
environment:
|
||||
NGINX_DOCKER_GEN_CONTAINER: dockergen
|
||||
volumes_from:
|
||||
- nginx
|
||||
- dockergen
|
||||
|
||||
# ---------------------- ETH Mainnet ----------------------- #
|
||||
|
||||
eth-server:
|
||||
build: .
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['eth']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
treeWatcher:
|
||||
image: tornadocash/relayer:mining
|
||||
eth-treeWatcher:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['eth']
|
||||
restart: always
|
||||
command: treeWatcher
|
||||
env_file: .env
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
priceWatcher:
|
||||
image: tornadocash/relayer:mining
|
||||
eth-priceWatcher:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['eth']
|
||||
restart: always
|
||||
command: priceWatcher
|
||||
env_file: .env
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
healthWatcher:
|
||||
image: tornadocash/relayer:mining
|
||||
eth-healthWatcher:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['eth']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
worker1:
|
||||
image: tornadocash/relayer:mining
|
||||
eth-worker1:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['eth']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis]
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
# worker2:
|
||||
# image: tornadocash/relayer:mining
|
||||
# # This is additional worker for ethereum mainnet
|
||||
# # So you can process transactions from multiple addresses, but before it you need to set up those addresses as workers
|
||||
# eth-worker2:
|
||||
# image: tornadorelayer:mainnet
|
||||
# profiles: [ 'eth' ]
|
||||
# restart: always
|
||||
# command: worker
|
||||
# env_file: .env
|
||||
# env_file: .env2.eth
|
||||
# environment:
|
||||
# PRIVATE_KEY: qwe
|
||||
# REDIS_URL: redis://redis/0
|
||||
|
||||
# # this container will proxy *.onion domain to the server container
|
||||
@@ -104,47 +163,294 @@ services:
|
||||
# RPC_URL: https://mainnet.infura.io
|
||||
# BLOCK_EXPLORER: etherscan.io
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
command: [redis-server, --appendonly, 'yes']
|
||||
volumes:
|
||||
- redis:/data
|
||||
# -------------------------------------------------- #
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: nginx
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- certs:/etc/nginx/certs
|
||||
logging:
|
||||
driver: none
|
||||
# ---------------------- BSC (Binance Smart Chain) ----------------------- #
|
||||
|
||||
dockergen:
|
||||
image: poma/docker-gen
|
||||
container_name: dockergen
|
||||
restart: always
|
||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||
volumes_from:
|
||||
- nginx
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
letsencrypt:
|
||||
image: jrcs/letsencrypt-nginx-proxy-companion
|
||||
container_name: letsencrypt
|
||||
bsc-server:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['bsc']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.bsc
|
||||
environment:
|
||||
NGINX_DOCKER_GEN_CONTAINER: dockergen
|
||||
volumes_from:
|
||||
- nginx
|
||||
- dockergen
|
||||
NET_ID: 56
|
||||
REDIS_URL: redis://redis/1
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
bsc-healthWatcher:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['bsc']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.bsc
|
||||
environment:
|
||||
NET_ID: 56
|
||||
REDIS_URL: redis://redis/1
|
||||
depends_on: [redis, bsc-server]
|
||||
|
||||
bsc-worker1:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['bsc']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.bsc
|
||||
environment:
|
||||
NET_ID: 56
|
||||
REDIS_URL: redis://redis/1
|
||||
depends_on: [redis, bsc-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Polygon (MATIC) --------------------- #
|
||||
|
||||
polygon-server:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['polygon']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.polygon
|
||||
environment:
|
||||
NET_ID: 137
|
||||
REDIS_URL: redis://redis/2
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
polygon-healthWatcher:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['polygon']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.polygon
|
||||
environment:
|
||||
NET_ID: 137
|
||||
REDIS_URL: redis://redis/2
|
||||
depends_on: [redis, polygon-server]
|
||||
|
||||
polygon-worker1:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['polygon']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.polygon
|
||||
environment:
|
||||
NET_ID: 137
|
||||
REDIS_URL: redis://redis/2
|
||||
depends_on: [redis, polygon-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Gnosis (XDAI) ---------------------- #
|
||||
|
||||
gnosis-server:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['gnosis']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.gnosis
|
||||
environment:
|
||||
NET_ID: 100
|
||||
REDIS_URL: redis://redis/3
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
gnosis-healthWatcher:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['gnosis']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.gnosis
|
||||
environment:
|
||||
NET_ID: 100
|
||||
REDIS_URL: redis://redis/3
|
||||
depends_on: [redis, gnosis-server]
|
||||
|
||||
gnosis-worker1:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['gnosis']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.gnosis
|
||||
environment:
|
||||
NET_ID: 100
|
||||
REDIS_URL: redis://redis/3
|
||||
depends_on: [redis, gnosis-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- AVAX ---------------------- #
|
||||
|
||||
avax-server:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['avax']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.avax
|
||||
environment:
|
||||
NET_ID: 43114
|
||||
REDIS_URL: redis://redis/4
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
avax-healthWatcher:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['avax']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.avax
|
||||
environment:
|
||||
NET_ID: 43114
|
||||
REDIS_URL: redis://redis/4
|
||||
depends_on: [redis, avax-server]
|
||||
|
||||
avax-worker1:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['avax']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.avax
|
||||
environment:
|
||||
NET_ID: 43114
|
||||
REDIS_URL: redis://redis/4
|
||||
depends_on: [redis, avax-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- OP ------------------------ #
|
||||
|
||||
op-server:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['op']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.op
|
||||
environment:
|
||||
NET_ID: 10
|
||||
REDIS_URL: redis://redis/5
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
op-healthWatcher:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['op']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.op
|
||||
environment:
|
||||
NET_ID: 10
|
||||
REDIS_URL: redis://redis/5
|
||||
depends_on: [redis, op-server]
|
||||
|
||||
op-worker1:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['op']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.op
|
||||
environment:
|
||||
NET_ID: 10
|
||||
REDIS_URL: redis://redis/5
|
||||
depends_on: [redis, op-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Arbitrum ----------------------- #
|
||||
|
||||
arb-server:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['arb']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.arb
|
||||
environment:
|
||||
NET_ID: 42161
|
||||
REDIS_URL: redis://redis/6
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
arb-healthWatcher:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['arb']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.arb
|
||||
environment:
|
||||
NET_ID: 42161
|
||||
REDIS_URL: redis://redis/6
|
||||
depends_on: [redis, arb-server]
|
||||
|
||||
arb-worker1:
|
||||
image: tornadorelayer:sidechain
|
||||
profiles: ['arb']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.arb
|
||||
environment:
|
||||
NET_ID: 42161
|
||||
REDIS_URL: redis://redis/6
|
||||
depends_on: [redis, arb-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Goerli (Ethereum Testnet) ---------------------- #
|
||||
|
||||
goerli-server:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['geth']
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
goerli-treeWatcher:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['goerli']
|
||||
restart: always
|
||||
command: treeWatcher
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
|
||||
goerli-priceWatcher:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['goerli']
|
||||
restart: always
|
||||
command: priceWatcher
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
|
||||
goerli-healthWatcher:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['goerli']
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
|
||||
goerli-worker1:
|
||||
image: tornadorelayer:mainnet
|
||||
profiles: ['goerli']
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
# -------------------------------------------------- #
|
||||
|
||||
volumes:
|
||||
conf:
|
||||
|
||||
1
monitoring/.POSTGRES_PASSWORD
Normal file
1
monitoring/.POSTGRES_PASSWORD
Normal file
@@ -0,0 +1 @@
|
||||
zabbix
|
||||
1
monitoring/.POSTGRES_USER
Normal file
1
monitoring/.POSTGRES_USER
Normal file
@@ -0,0 +1 @@
|
||||
zabbix
|
||||
37
monitoring/.env_agent
Normal file
37
monitoring/.env_agent
Normal file
@@ -0,0 +1,37 @@
|
||||
ZBX_HOSTNAME=Zabbix
|
||||
# ZBX_SOURCEIP=
|
||||
# ZBX_DEBUGLEVEL=3
|
||||
# ZBX_ENABLEREMOTECOMMANDS=0 # Deprecated since 5.0.0
|
||||
# ZBX_LOGREMOTECOMMANDS=0
|
||||
# ZBX_HOSTINTERFACE= # Available since 4.4.0
|
||||
# ZBX_HOSTINTERFACEITEM= # Available since 4.4.0
|
||||
# ZBX_SERVER_HOST=10.110.0.5
|
||||
# ZBX_PASSIVE_ALLOW=true
|
||||
# ZBX_PASSIVESERVERS=
|
||||
# ZBX_ACTIVE_ALLOW=true
|
||||
# ZBX_ACTIVESERVERS=
|
||||
# ZBX_LISTENIP=
|
||||
# ZBX_STARTAGENTS=3
|
||||
# ZBX_HOSTNAMEITEM=system.hostname
|
||||
# ZBX_METADATA=
|
||||
# ZBX_METADATAITEM=
|
||||
# ZBX_REFRESHACTIVECHECKS=120
|
||||
# ZBX_BUFFERSEND=5
|
||||
# ZBX_BUFFERSIZE=100
|
||||
# ZBX_MAXLINESPERSECOND=20
|
||||
# ZBX_ALIAS=""
|
||||
# ZBX_TIMEOUT=3
|
||||
# ZBX_UNSAFEUSERPARAMETERS=0
|
||||
# ZBX_LOADMODULE="dummy1.so,dummy2.so,dummy10.so"
|
||||
# ZBX_TLSCONNECT=unencrypted
|
||||
# ZBX_TLSACCEPT=unencrypted
|
||||
# ZBX_TLSCAFILE=
|
||||
# ZBX_TLSCRLFILE=
|
||||
# ZBX_TLSSERVERCERTISSUER=
|
||||
# ZBX_TLSSERVERCERTSUBJECT=
|
||||
# ZBX_TLSCERTFILE=
|
||||
# ZBX_TLSKEYFILE=
|
||||
# ZBX_TLSPSKIDENTITY=
|
||||
# ZBX_TLSPSKFILE=
|
||||
# ZBX_DENYKEY=system.run[*]
|
||||
# ZBX_ALLOWKEY=
|
||||
9
monitoring/.env_db_pgsql
Normal file
9
monitoring/.env_db_pgsql
Normal file
@@ -0,0 +1,9 @@
|
||||
# DB_SERVER_HOST=postgres-server
|
||||
# DB_SERVER_PORT=5432
|
||||
# POSTGRES_USER=zabbix
|
||||
POSTGRES_USER_FILE=/run/secrets/POSTGRES_USER
|
||||
# POSTGRES_PASSWORD=zabbix
|
||||
POSTGRES_PASSWORD_FILE=/run/secrets/POSTGRES_PASSWORD
|
||||
POSTGRES_DB=zabbix
|
||||
# DB_SERVER_SCHEMA=public
|
||||
# ENABLE_TIMESCALEDB=tru
|
||||
60
monitoring/.env_srv
Normal file
60
monitoring/.env_srv
Normal file
@@ -0,0 +1,60 @@
|
||||
# ZBX_LISTENIP=
|
||||
# ZBX_HISTORYSTORAGEURL=http://elasticsearch:9200/ # Available since 3.4.5
|
||||
# ZBX_HISTORYSTORAGETYPES=uint,dbl,str,log,text # Available since 3.4.5
|
||||
# ZBX_DBTLSCONNECT=required # Available since 5.0.0
|
||||
# ZBX_DBTLSCAFILE=/run/secrets/root-ca.pem # Available since 5.0.0
|
||||
# ZBX_DBTLSCERTFILE=/run/secrets/client-cert.pem # Available since 5.0.0
|
||||
# ZBX_DBTLSKEYFILE=/run/secrets/client-key.pem # Available since 5.0.0
|
||||
# ZBX_DBTLSCIPHER= # Available since 5.0.0
|
||||
# ZBX_DBTLSCIPHER13= # Available since 5.0.0
|
||||
# ZBX_DEBUGLEVEL=3
|
||||
# ZBX_STARTPOLLERS=5
|
||||
# ZBX_IPMIPOLLERS=0
|
||||
# ZBX_STARTPREPROCESSORS=3 # Available since 3.4.0
|
||||
# ZBX_STARTPOLLERSUNREACHABLE=1
|
||||
# ZBX_STARTTRAPPERS=5
|
||||
# ZBX_STARTPINGERS=1
|
||||
# ZBX_STARTDISCOVERERS=1
|
||||
# ZBX_STARTHTTPPOLLERS=1
|
||||
# ZBX_STARTTIMERS=1
|
||||
# ZBX_STARTESCALATORS=1
|
||||
# ZBX_STARTALERTERS=3 # Available since 3.4.0
|
||||
# ZBX_JAVAGATEWAY_ENABLE=true
|
||||
# ZBX_JAVAGATEWAY=zabbix-java-gateway
|
||||
# ZBX_JAVAGATEWAYPORT=10052
|
||||
# ZBX_STARTJAVAPOLLERS=5
|
||||
# ZBX_STARTVMWARECOLLECTORS=0
|
||||
# ZBX_VMWAREFREQUENCY=60
|
||||
# ZBX_VMWAREPERFFREQUENCY=60
|
||||
# ZBX_VMWARECACHESIZE=8M
|
||||
# ZBX_VMWARETIMEOUT=10
|
||||
# ZBX_ENABLE_SNMP_TRAPS=true
|
||||
# ZBX_SOURCEIP=
|
||||
# ZBX_HOUSEKEEPINGFREQUENCY=1
|
||||
# ZBX_MAXHOUSEKEEPERDELETE=5000
|
||||
# ZBX_SENDERFREQUENCY=30
|
||||
# ZBX_CACHESIZE=8M
|
||||
# ZBX_CACHEUPDATEFREQUENCY=60
|
||||
# ZBX_STARTDBSYNCERS=4
|
||||
# ZBX_HISTORYCACHESIZE=16M
|
||||
# ZBX_HISTORYINDEXCACHESIZE=4M
|
||||
# ZBX_TRENDCACHESIZE=4M
|
||||
# ZBX_VALUECACHESIZE=8M
|
||||
# ZBX_TIMEOUT=4
|
||||
# ZBX_TRAPPERIMEOUT=300
|
||||
# ZBX_UNREACHABLEPERIOD=45
|
||||
# ZBX_UNAVAILABLEDELAY=60
|
||||
# ZBX_UNREACHABLEDELAY=15
|
||||
# ZBX_LOGSLOWQUERIES=3000
|
||||
# ZBX_EXPORTFILESIZE=
|
||||
# ZBX_STARTPROXYPOLLERS=1
|
||||
# ZBX_PROXYCONFIGFREQUENCY=3600
|
||||
# ZBX_PROXYDATAFREQUENCY=1
|
||||
# ZBX_LOADMODULE="dummy1.so,dummy2.so,dummy10.so"
|
||||
# ZBX_TLSCAFILE=
|
||||
# ZBX_TLSCRLFILE=
|
||||
# ZBX_TLSCERTFILE=
|
||||
# ZBX_TLSKEYFILE=
|
||||
# ZBX_VAULTDBPATH=
|
||||
# ZBX_VAULTURL=https://127.0.0.1:8200
|
||||
# VAULT_TOKEN=
|
||||
26
monitoring/.env_web
Normal file
26
monitoring/.env_web
Normal file
@@ -0,0 +1,26 @@
|
||||
# ZBX_SERVER_HOST=zabbix-server
|
||||
# ZBX_SERVER_PORT=10051
|
||||
# ZBX_SERVER_NAME=Monitoring
|
||||
# ZBX_DB_ENCRYPTION=true # Available since 5.0.0
|
||||
# ZBX_DB_KEY_FILE=/run/secrets/client-key.pem # Available since 5.0.0
|
||||
# ZBX_DB_CERT_FILE=/run/secrets/client-cert.pem # Available since 5.0.0
|
||||
# ZBX_DB_CA_FILE=/run/secrets/root-ca.pem # Available since 5.0.0
|
||||
# ZBX_DB_VERIFY_HOST=false # Available since 5.0.0
|
||||
# ZBX_DB_CIPHER_LIST= # Available since 5.0.0
|
||||
# ZBX_VAULTDBPATH=
|
||||
# ZBX_VAULTURL=https://127.0.0.1:8200
|
||||
# VAULT_TOKEN=
|
||||
# ZBX_HISTORYSTORAGEURL=http://elasticsearch:9200/ # Available since 3.4.5
|
||||
# ZBX_HISTORYSTORAGETYPES=['uint', 'dbl', 'str', 'text', 'log'] # Available since 3.4.5
|
||||
# ENABLE_WEB_ACCESS_LOG=true
|
||||
# ZBX_MAXEXECUTIONTIME=600
|
||||
# ZBX_MEMORYLIMIT=128M
|
||||
# ZBX_POSTMAXSIZE=16M
|
||||
# ZBX_UPLOADMAXFILESIZE=2M
|
||||
# ZBX_MAXINPUTTIME=300
|
||||
# ZBX_SESSION_NAME=zbx_sessionid
|
||||
# Timezone one of: http://php.net/manual/en/timezones.php
|
||||
# PHP_TZ=Europe/Riga
|
||||
# ZBX_DENY_GUI_ACCESS=false
|
||||
# ZBX_GUI_ACCESS_IP_RANGE=['127.0.0.1']
|
||||
# ZBX_GUI_WARNING_MSG=Zabbix is under maintenance.
|
||||
67
monitoring/README.md
Normal file
67
monitoring/README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Installing the Zabbix server
|
||||
|
||||
Change default passwords, ports and set listen IP (ports `8080/tcp` and `10051/tcp` will be open on all interfaces, use a firewall or specify the address of the required interface), then run:
|
||||
|
||||
```bash
|
||||
wget https://github.com/tornadocash/tornado-relayer/raw/master/monitoring/zabbix.tar.gz
|
||||
mkdir $HOME/monitoring/
|
||||
tar -xzf zabbix.tar.gz -C $HOME/monitoring/
|
||||
cd $HOME/monitoring/
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
# Installing the Zabbix agent
|
||||
|
||||
Download package from repository [https://repo.zabbix.com/zabbix/5.2/ubuntu/pool/main/z/zabbix/](https://repo.zabbix.com/zabbix/5.2/ubuntu/pool/main/z/zabbix/) and run:
|
||||
|
||||
```bash
|
||||
sudo dpkg -i zabbix-agent_5.2.*.deb
|
||||
sudo usermod -aG docker zabbix
|
||||
```
|
||||
|
||||
Change default values in `/etc/zabbix/zabbix_agent2.conf`:
|
||||
|
||||
- `Hostname` the same as in the zabbix-server web interface;
|
||||
- `Server` and `ServerActive` set zabbix server IP or DNS name;
|
||||
- `ListenIP` to local network IP available from zabbix server or set firewall rules to restrict access to port `10050`;
|
||||
- uncomment `Plugins.Docker.Endpoint=unix:///var/run/docker.sock`.
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable zabbix-agent2.service
|
||||
sudo systemctl restart zabbix-agent2.service
|
||||
```
|
||||
|
||||
# Adding the host
|
||||
|
||||
Log into your Zabbix server (defaul login and passord: `Admin` - `zabbix`) and click on the Configuration tab and then the Hosts tab. Click the Create host button near the top right corner. In the resulting page, change the Host name and IP ADDRESS sections to match the information for your remote server. Set `{$URL}` macros to relayer host, example `http://localhost/v1/status` or `https://domain.name/v1/status`.
|
||||
|
||||
# Import templates
|
||||
|
||||
Import templates using the WebUI:
|
||||
|
||||
- [Docker-template.yaml](/monitoring/templates/Docker-template.yaml);
|
||||
- [Tornado-relayer-template.yaml](/monitoring/templates/Tornado-relayer-template.yaml).
|
||||
|
||||
Link templates with added host. It is also recommended to link `Linux CPU by Zabbix agent`, `Linux filesystems by Zabbix agent` and `Linux memory by Zabbix agent` templates to the host.
|
||||
|
||||
# Alerts
|
||||
|
||||
In WebUI - Administration -> Media types -> Telegram:
|
||||
|
||||
```
|
||||
https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/telegram
|
||||
|
||||
1. Register bot: send "/newbot" to @BotFather and follow instructions
|
||||
2. Copy and paste the obtained token into the "Token" field above
|
||||
3. If you want to send personal notifications, you need to get chat id of the user you want to send messages to:
|
||||
3.1. Send "/getid" to "@myidbot" in Telegram messenger
|
||||
3.2. Copy returned chat id and save it in the "Telegram Webhook" media for the user
|
||||
3.3. Ask the user to send "/start" to your bot (Telegram bot won't send anything to the user without it)
|
||||
4. If you want to send group notifications, you need to get group id of the group you want to send messages to:
|
||||
4.1. Add "@myidbot" to your group
|
||||
4.2. Send "/getgroupid@myidbot" in your group
|
||||
4.3. Copy returned group id save it in the "Telegram Webhook" media for the user you created for group notifications
|
||||
4.4. Send "/start@your_bot_name_here" in your group (Telegram bot won't send anything to the group without it)
|
||||
```
|
||||
186
monitoring/docker-compose.yml
Normal file
186
monitoring/docker-compose.yml
Normal file
@@ -0,0 +1,186 @@
|
||||
# Restrict access to 10051/tcp on public ip
|
||||
|
||||
version: '3.5'
|
||||
services:
|
||||
zabbix-server:
|
||||
image: zabbix/zabbix-server-pgsql:alpine-5.2-latest
|
||||
restart: always
|
||||
ports:
|
||||
- '10051:10051'
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- ./zbx_env/usr/lib/zabbix/alertscripts:/usr/lib/zabbix/alertscripts:ro
|
||||
- ./zbx_env/usr/lib/zabbix/externalscripts:/usr/lib/zabbix/externalscripts:ro
|
||||
- ./zbx_env/var/lib/zabbix/export:/var/lib/zabbix/export:rw
|
||||
- ./zbx_env/var/lib/zabbix/modules:/var/lib/zabbix/modules:ro
|
||||
- ./zbx_env/var/lib/zabbix/enc:/var/lib/zabbix/enc:ro
|
||||
- ./zbx_env/var/lib/zabbix/ssh_keys:/var/lib/zabbix/ssh_keys:ro
|
||||
- ./zbx_env/var/lib/zabbix/mibs:/var/lib/zabbix/mibs:ro
|
||||
- ./zbx_env/var/lib/zabbix/snmptraps:/var/lib/zabbix/snmptraps:ro
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 20000
|
||||
hard: 40000
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.70'
|
||||
memory: 1G
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
env_file:
|
||||
- .env_db_pgsql
|
||||
- .env_srv
|
||||
secrets:
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_PASSWORD
|
||||
depends_on:
|
||||
- postgres-server
|
||||
networks:
|
||||
zbx_net_backend:
|
||||
aliases:
|
||||
- zabbix-server
|
||||
- zabbix-server-pgsql
|
||||
- zabbix-server-alpine-pgsql
|
||||
- zabbix-server-pgsql-alpine
|
||||
zbx_net_frontend:
|
||||
stop_grace_period: 30s
|
||||
sysctls:
|
||||
- net.ipv4.ip_local_port_range=1024 65000
|
||||
- net.ipv4.conf.all.accept_redirects=0
|
||||
- net.ipv4.conf.all.secure_redirects=0
|
||||
- net.ipv4.conf.all.send_redirects=0
|
||||
labels:
|
||||
com.zabbix.description: 'Zabbix server with PostgreSQL database support'
|
||||
com.zabbix.company: 'Zabbix LLC'
|
||||
com.zabbix.component: 'zabbix-server'
|
||||
com.zabbix.dbtype: 'pgsql'
|
||||
com.zabbix.os: 'alpine'
|
||||
|
||||
zabbix-web:
|
||||
image: zabbix/zabbix-web-nginx-pgsql:alpine-5.2-latest
|
||||
restart: always
|
||||
ports:
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- ./zbx_env/etc/ssl/nginx:/etc/ssl/nginx:ro
|
||||
- ./zbx_env/usr/share/zabbix/modules/:/usr/share/zabbix/modules/:ro
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.70'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
env_file:
|
||||
- .env_db_pgsql
|
||||
- .env_web
|
||||
secrets:
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_PASSWORD
|
||||
depends_on:
|
||||
- postgres-server
|
||||
- zabbix-server
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:8080/']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
zbx_net_backend:
|
||||
aliases:
|
||||
- zabbix-web-nginx-pgsql
|
||||
- zabbix-web-nginx-alpine-pgsql
|
||||
- zabbix-web-nginx-pgsql-alpine
|
||||
zbx_net_frontend:
|
||||
stop_grace_period: 10s
|
||||
sysctls:
|
||||
- net.core.somaxconn=65535
|
||||
labels:
|
||||
com.zabbix.description: 'Zabbix frontend on Nginx web-server with PostgreSQL database support'
|
||||
com.zabbix.company: 'Zabbix LLC'
|
||||
com.zabbix.component: 'zabbix-frontend'
|
||||
com.zabbix.webserver: 'nginx'
|
||||
com.zabbix.dbtype: 'pgsql'
|
||||
com.zabbix.os: 'alpine'
|
||||
|
||||
zabbix-agent:
|
||||
image: zabbix/zabbix-agent2:alpine-5.2-latest
|
||||
restart: always
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env_file:
|
||||
- .env_agent
|
||||
privileged: true
|
||||
user: root
|
||||
pid: 'host'
|
||||
networks:
|
||||
zbx_net_backend:
|
||||
aliases:
|
||||
- zabbix-agent
|
||||
- zabbix-agent-passive
|
||||
- zabbix-agent-alpine
|
||||
stop_grace_period: 5s
|
||||
|
||||
postgres-server:
|
||||
image: postgres:alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- ./zbx_env/var/lib/postgresql/data:/var/lib/postgresql/data:rw
|
||||
env_file:
|
||||
- .env_db_pgsql
|
||||
secrets:
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_PASSWORD
|
||||
stop_grace_period: 1m
|
||||
networks:
|
||||
zbx_net_backend:
|
||||
aliases:
|
||||
- postgres-server
|
||||
- pgsql-server
|
||||
- pgsql-database
|
||||
|
||||
portainer:
|
||||
image: portainer/portainer:latest
|
||||
restart: always
|
||||
ports:
|
||||
- '9000:9000'
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- portainer-data:/data
|
||||
|
||||
networks:
|
||||
zbx_net_frontend:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.enable_ipv6: 'false'
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.16.238.0/24
|
||||
zbx_net_backend:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.enable_ipv6: 'false'
|
||||
internal: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.16.239.0/24
|
||||
|
||||
secrets:
|
||||
POSTGRES_USER:
|
||||
file: ./.POSTGRES_USER
|
||||
POSTGRES_PASSWORD:
|
||||
file: ./.POSTGRES_PASSWORD
|
||||
|
||||
volumes:
|
||||
portainer-data:
|
||||
393
monitoring/templates/Docker-template.yaml
Normal file
393
monitoring/templates/Docker-template.yaml
Normal file
@@ -0,0 +1,393 @@
|
||||
zabbix_export:
|
||||
version: '5.2'
|
||||
date: '2021-11-29T12:29:17Z'
|
||||
groups:
|
||||
- name: Docker
|
||||
templates:
|
||||
- template: Docker
|
||||
name: Docker
|
||||
description: |
|
||||
Get Docker engine metrics from plugin for the New Zabbix Agent (zabbix-agent2).
|
||||
|
||||
You can discuss this template or leave feedback on our forum
|
||||
|
||||
Template tooling version used: 0.38
|
||||
groups:
|
||||
- name: Docker
|
||||
applications:
|
||||
- name: Docker
|
||||
- name: 'Zabbix raw items'
|
||||
items:
|
||||
- name: 'Docker: Get containers'
|
||||
key: docker.containers
|
||||
history: '0'
|
||||
trends: '0'
|
||||
value_type: TEXT
|
||||
applications:
|
||||
- name: 'Zabbix raw items'
|
||||
- name: 'Docker: Containers paused'
|
||||
type: DEPENDENT
|
||||
key: docker.containers.paused
|
||||
delay: '0'
|
||||
history: 7d
|
||||
description: 'Total number of containers paused on this host'
|
||||
applications:
|
||||
- name: Docker
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.ContainersPaused
|
||||
master_item:
|
||||
key: docker.info
|
||||
- name: 'Docker: Containers running'
|
||||
type: DEPENDENT
|
||||
key: docker.containers.running
|
||||
delay: '0'
|
||||
history: 7d
|
||||
description: 'Total number of containers running on this host'
|
||||
applications:
|
||||
- name: Docker
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.ContainersRunning
|
||||
master_item:
|
||||
key: docker.info
|
||||
- name: 'Docker: Containers stopped'
|
||||
type: DEPENDENT
|
||||
key: docker.containers.stopped
|
||||
delay: '0'
|
||||
history: 7d
|
||||
description: 'Total number of containers stopped on this host'
|
||||
applications:
|
||||
- name: Docker
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.ContainersStopped
|
||||
master_item:
|
||||
key: docker.info
|
||||
triggers:
|
||||
- expression: '{avg(5m)}>=1'
|
||||
name: 'Docker: containers is stopped'
|
||||
priority: HIGH
|
||||
- name: 'Docker: Containers total'
|
||||
type: DEPENDENT
|
||||
key: docker.containers.total
|
||||
delay: '0'
|
||||
history: 7d
|
||||
description: 'Total number of containers on this host'
|
||||
applications:
|
||||
- name: Docker
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.Containers
|
||||
master_item:
|
||||
key: docker.info
|
||||
- name: 'Docker: Get images'
|
||||
key: docker.images
|
||||
history: '0'
|
||||
trends: '0'
|
||||
status: DISABLED
|
||||
value_type: TEXT
|
||||
applications:
|
||||
- name: 'Zabbix raw items'
|
||||
- name: 'Docker: Get info'
|
||||
key: docker.info
|
||||
history: '0'
|
||||
trends: '0'
|
||||
value_type: TEXT
|
||||
applications:
|
||||
- name: 'Zabbix raw items'
|
||||
- name: 'Docker: Memory total'
|
||||
type: DEPENDENT
|
||||
key: docker.mem.total
|
||||
delay: '0'
|
||||
history: 7d
|
||||
status: DISABLED
|
||||
units: B
|
||||
applications:
|
||||
- name: Docker
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.MemTotal
|
||||
master_item:
|
||||
key: docker.info
|
||||
- name: 'Docker: Ping'
|
||||
key: docker.ping
|
||||
history: 7h
|
||||
applications:
|
||||
- name: Docker
|
||||
valuemap:
|
||||
name: 'Service state'
|
||||
preprocessing:
|
||||
- type: DISCARD_UNCHANGED_HEARTBEAT
|
||||
parameters:
|
||||
- 10m
|
||||
triggers:
|
||||
- expression: '{last()}=0'
|
||||
name: 'Docker: Service is down'
|
||||
priority: AVERAGE
|
||||
manual_close: 'YES'
|
||||
discovery_rules:
|
||||
- name: 'Containers discovery'
|
||||
key: 'docker.containers.discovery[true]'
|
||||
delay: 15m
|
||||
filter:
|
||||
evaltype: AND
|
||||
conditions:
|
||||
- macro: '{#NAME}'
|
||||
value: '{$DOCKER.LLD.FILTER.CONTAINER.MATCHES}'
|
||||
formulaid: A
|
||||
- macro: '{#NAME}'
|
||||
value: '{$DOCKER.LLD.FILTER.CONTAINER.NOT_MATCHES}'
|
||||
operator: NOT_MATCHES_REGEX
|
||||
formulaid: B
|
||||
description: |
|
||||
Discovery for containers metrics
|
||||
|
||||
Parameter:
|
||||
true - Returns all containers
|
||||
false - Returns only running containers
|
||||
item_prototypes:
|
||||
- name: 'Container {#NAME}: Finished at'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.finished["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
value_type: FLOAT
|
||||
units: unixtime
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.FinishedAt
|
||||
- type: DISCARD_UNCHANGED_HEARTBEAT
|
||||
parameters:
|
||||
- 1d
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
- name: 'Container {#NAME}: Restart count'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.restart_count["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.RestartCount
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
trigger_prototypes:
|
||||
- expression: '{last()}>5'
|
||||
name: 'Container {#NAME}: restarting constantly'
|
||||
opdata: '{ITEM.VALUE}'
|
||||
priority: HIGH
|
||||
- name: 'Container {#NAME}: Started at'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.started["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
value_type: FLOAT
|
||||
units: unixtime
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.StartedAt
|
||||
- type: DISCARD_UNCHANGED_HEARTBEAT
|
||||
parameters:
|
||||
- 1d
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
- name: 'Container {#NAME}: Error'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.state.error["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
trends: '0'
|
||||
value_type: CHAR
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.Error
|
||||
- type: DISCARD_UNCHANGED_HEARTBEAT
|
||||
parameters:
|
||||
- 1d
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
trigger_prototypes:
|
||||
- expression: '{diff()}=1 and {strlen()}>0'
|
||||
name: 'Container {#NAME}: An error has occurred in the container'
|
||||
priority: WARNING
|
||||
description: 'Container {#NAME} has an error. Ack to close.'
|
||||
manual_close: 'YES'
|
||||
- name: 'Container {#NAME}: Exit code'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.state.exitcode["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.ExitCode
|
||||
- type: DISCARD_UNCHANGED_HEARTBEAT
|
||||
parameters:
|
||||
- 1d
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
- name: 'Container {#NAME}: Paused'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.state.paused["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
valuemap:
|
||||
name: 'Docker flag'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.Paused
|
||||
- type: BOOL_TO_DECIMAL
|
||||
parameters:
|
||||
- ''
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
- name: 'Container {#NAME}: Restarting'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.state.restarting["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
valuemap:
|
||||
name: 'Docker flag'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.Restarting
|
||||
- type: BOOL_TO_DECIMAL
|
||||
parameters:
|
||||
- ''
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
- name: 'Container {#NAME}: Running'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.state.running["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
valuemap:
|
||||
name: 'Docker flag'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.Running
|
||||
- type: BOOL_TO_DECIMAL
|
||||
parameters:
|
||||
- ''
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
- name: 'Container {#NAME}: Status'
|
||||
type: DEPENDENT
|
||||
key: 'docker.container_info.state.status["{#NAME}"]'
|
||||
delay: '0'
|
||||
history: 7d
|
||||
trends: '0'
|
||||
value_type: CHAR
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.State.Status
|
||||
- type: DISCARD_UNCHANGED_HEARTBEAT
|
||||
parameters:
|
||||
- 1h
|
||||
master_item:
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
- name: 'Container {#NAME}: Get info'
|
||||
key: 'docker.container_info["{#NAME}"]'
|
||||
history: '0'
|
||||
trends: '0'
|
||||
value_type: CHAR
|
||||
description: 'Return low-level information about a container'
|
||||
application_prototypes:
|
||||
- name: 'Docker: Container {#NAME}'
|
||||
trigger_prototypes:
|
||||
- expression: '{Docker:docker.container_info.state.exitcode["{#NAME}"].last()}>0 and {Docker:docker.container_info.state.running["{#NAME}"].last()}=0'
|
||||
name: 'Container {#NAME}: Container has been stopped with error code'
|
||||
opdata: 'Exit code: {ITEM.LASTVALUE1}'
|
||||
priority: AVERAGE
|
||||
manual_close: 'YES'
|
||||
macros:
|
||||
- macro: '{$DOCKER.LLD.FILTER.CONTAINER.MATCHES}'
|
||||
value: '.*'
|
||||
description: 'Filter of discoverable containers'
|
||||
- macro: '{$DOCKER.LLD.FILTER.CONTAINER.NOT_MATCHES}'
|
||||
value: CHANGE_IF_NEEDED
|
||||
description: 'Filter to exclude discovered containers'
|
||||
- macro: '{$DOCKER.LLD.FILTER.IMAGE.MATCHES}'
|
||||
value: '.*'
|
||||
description: 'Filter of discoverable images'
|
||||
- macro: '{$DOCKER.LLD.FILTER.IMAGE.NOT_MATCHES}'
|
||||
value: CHANGE_IF_NEEDED
|
||||
description: 'Filter to exclude discovered images'
|
||||
graphs:
|
||||
- name: 'Docker: Containers'
|
||||
graph_items:
|
||||
- drawtype: GRADIENT_LINE
|
||||
color: 1A7C11
|
||||
item:
|
||||
host: Docker
|
||||
key: docker.containers.running
|
||||
- sortorder: '1'
|
||||
drawtype: BOLD_LINE
|
||||
color: 2774A4
|
||||
item:
|
||||
host: Docker
|
||||
key: docker.containers.paused
|
||||
- sortorder: '2'
|
||||
drawtype: BOLD_LINE
|
||||
color: F63100
|
||||
item:
|
||||
host: Docker
|
||||
key: docker.containers.stopped
|
||||
- sortorder: '3'
|
||||
drawtype: BOLD_LINE
|
||||
color: A54F10
|
||||
item:
|
||||
host: Docker
|
||||
key: docker.containers.total
|
||||
- name: 'Docker: Memory total'
|
||||
graph_items:
|
||||
- drawtype: BOLD_LINE
|
||||
color: 1A7C11
|
||||
item:
|
||||
host: Docker
|
||||
key: docker.mem.total
|
||||
value_maps:
|
||||
- name: 'Docker flag'
|
||||
mappings:
|
||||
- value: '0'
|
||||
newvalue: 'False'
|
||||
- value: '1'
|
||||
newvalue: 'True'
|
||||
- name: 'Service state'
|
||||
mappings:
|
||||
- value: '0'
|
||||
newvalue: Down
|
||||
- value: '1'
|
||||
newvalue: Up
|
||||
70
monitoring/templates/Tornado-relayer-template.yaml
Normal file
70
monitoring/templates/Tornado-relayer-template.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
zabbix_export:
|
||||
version: '5.2'
|
||||
date: '2021-12-01T13:26:59Z'
|
||||
groups:
|
||||
- name: Templates/Applications
|
||||
templates:
|
||||
- template: Tornado-relayer
|
||||
name: Tornado-relayer
|
||||
groups:
|
||||
- name: Templates/Applications
|
||||
items:
|
||||
- name: 'tornado-relayer: health.error'
|
||||
type: DEPENDENT
|
||||
key: tornado-relayer.health.error
|
||||
delay: '0'
|
||||
trends: '0'
|
||||
value_type: TEXT
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.health.error
|
||||
master_item:
|
||||
key: 'web.page.get[{$URL}]'
|
||||
triggers:
|
||||
- expression: '{last()}<>""'
|
||||
name: 'tornado-relayer: health error'
|
||||
priority: AVERAGE
|
||||
- name: 'tornado-relayer: health.status'
|
||||
type: DEPENDENT
|
||||
key: tornado-relayer.health.status
|
||||
delay: '0'
|
||||
trends: '0'
|
||||
value_type: TEXT
|
||||
preprocessing:
|
||||
- type: JSONPATH
|
||||
parameters:
|
||||
- $.health.status
|
||||
master_item:
|
||||
key: 'web.page.get[{$URL}]'
|
||||
triggers:
|
||||
- expression: '{last(#3)}<>"true"'
|
||||
name: 'tornado-relayer: health status <> true'
|
||||
priority: HIGH
|
||||
- name: 'tornado-relayer: data'
|
||||
type: ZABBIX_ACTIVE
|
||||
key: 'web.page.get[{$URL}]'
|
||||
history: '0'
|
||||
trends: '0'
|
||||
value_type: TEXT
|
||||
preprocessing:
|
||||
- type: REGEX
|
||||
parameters:
|
||||
- '\n\s?\n([\s\S]*)'
|
||||
- \1
|
||||
httptests:
|
||||
- name: 'tornado-relayer: status page'
|
||||
agent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/80.0.3987.87 Chrome/80.0.3987.87 Safari/537.36'
|
||||
steps:
|
||||
- name: 'status page'
|
||||
url: '{$URL}'
|
||||
follow_redirects: 'NO'
|
||||
required: status
|
||||
status_codes: '200'
|
||||
triggers:
|
||||
- expression: '{Tornado-relayer:web.test.fail[tornado-relayer: status page].last()}>0'
|
||||
name: 'tornado-relayer: status page failed'
|
||||
priority: AVERAGE
|
||||
- expression: '{Tornado-relayer:web.test.rspcode[tornado-relayer: status page,status page].last(#3)}<>200'
|
||||
name: 'tornado-relayer: status page rspcode <>200'
|
||||
priority: HIGH
|
||||
BIN
monitoring/zabbix.tar.gz
Normal file
BIN
monitoring/zabbix.tar.gz
Normal file
Binary file not shown.
24
package.json
24
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "relay",
|
||||
"version": "4.0.15",
|
||||
"description": "Relayer for Tornado.cash privacy solution. https://tornado.cash",
|
||||
"version": "5.2.0",
|
||||
"description": "Relayer for Tornado.cash privacy solution.",
|
||||
"scripts": {
|
||||
"server": "node src/server.js",
|
||||
"worker": "node src/worker",
|
||||
@@ -13,25 +13,27 @@
|
||||
"prettier:fix": "npx prettier --write . --config .prettierrc",
|
||||
"lint": "yarn eslint && yarn prettier:check",
|
||||
"test": "mocha",
|
||||
"start": "yarn server & yarn priceWatcher & yarn treeWatcher & yarn worker & yarn healthWatcher"
|
||||
"build": "docker build -t tornadocash/relayer:mainnet-v5 .",
|
||||
"start": "docker-compose up -d redis && concurrently \"yarn server\" \"yarn priceWatcher\" \"yarn treeWatcher\" \"yarn worker\" \"yarn healthWatcher\""
|
||||
},
|
||||
"author": "tornado.cash",
|
||||
"author": "Tornado Cash team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tornado/anonymity-mining": "^2.1.5",
|
||||
"@tornado/circomlib": "^0.0.21",
|
||||
"@tornado/fixed-merkle-tree": "0.4.0",
|
||||
"@tornado/tornado-config": "^1",
|
||||
"@tornado/tornado-oracles": "^3.3.0",
|
||||
"@tornado/tx-manager": "^0.4.9",
|
||||
"ajv": "^6.12.5",
|
||||
"async-mutex": "^0.2.4",
|
||||
"bull": "^3.12.1",
|
||||
"circomlib": "git+https://github.com/tornadocash/circomlib.git#3b492f9801573eebcfe1b6c584afe8a3beecf2b4",
|
||||
"concurrently": "^8.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"eth-ens-namehash": "^2.0.8",
|
||||
"express": "^4.17.1",
|
||||
"fixed-merkle-tree": "^0.4.0",
|
||||
"gas-price-oracle": "^0.2.2",
|
||||
"ioredis": "^4.14.1",
|
||||
"node-fetch": "^2.6.0",
|
||||
"torn-token": "1.0.4",
|
||||
"tornado-anonymity-mining": "^2.1.2",
|
||||
"tx-manager": "^0.2.9",
|
||||
"node-fetch": "^2.6.7",
|
||||
"uuid": "^8.3.0",
|
||||
"web3": "^1.3.0",
|
||||
"web3-core-promievent": "^1.3.0",
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
require('dotenv').config()
|
||||
|
||||
const { jobType } = require('./constants')
|
||||
const tornConfig = require('torn-token')
|
||||
const tornConfig = require('@tornado/tornado-config')
|
||||
module.exports = {
|
||||
netId: Number(process.env.NET_ID) || 1,
|
||||
redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
|
||||
httpRpcUrl: process.env.HTTP_RPC_URL,
|
||||
wsRpcUrl: process.env.WS_RPC_URL,
|
||||
oracleRpcUrl: process.env.ORACLE_RPC_URL || 'https://mainnet.infura.io/',
|
||||
offchainOracleAddress: '0x080AB73787A8B13EC7F40bd7d00d6CC07F9b24d0',
|
||||
oracleRpcUrl: process.env.ORACLE_RPC_URL || 'https://api.securerpc.com/v1',
|
||||
aggregatorAddress: process.env.AGGREGATOR,
|
||||
minerMerkleTreeHeight: 20,
|
||||
privateKey: process.env.PRIVATE_KEY,
|
||||
instances: tornConfig.instances,
|
||||
torn: tornConfig,
|
||||
port: process.env.APP_PORT || 8000,
|
||||
tornadoServiceFee: Number(process.env.REGULAR_TORNADO_WITHDRAW_FEE),
|
||||
miningServiceFee: Number(process.env.MINING_SERVICE_FEE),
|
||||
tornadoServiceFee: Number(process.env.RELAYER_FEE),
|
||||
rewardAccount: process.env.REWARD_ACCOUNT,
|
||||
governanceAddress: '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
tornadoGoerliProxy: '0x454d870a72e29d5E5697f635128D18077BD04C60',
|
||||
gasLimits: {
|
||||
[jobType.TORNADO_WITHDRAW]: 390000,
|
||||
WITHDRAW_WITH_EXTRA: 480000,
|
||||
[jobType.MINING_REWARD]: 455000,
|
||||
[jobType.MINING_WITHDRAW]: 400000,
|
||||
},
|
||||
minimumBalance: '1000000000000000000',
|
||||
minimumBalance: '500000000000000000',
|
||||
baseFeeReserve: Number(process.env.BASE_FEE_RESERVE_PERCENTAGE),
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ const {
|
||||
getTornadoWithdrawInputError,
|
||||
getMiningRewardInputError,
|
||||
getMiningWithdrawInputError,
|
||||
} = require('./validator')
|
||||
const { postJob } = require('./queue')
|
||||
const { jobType } = require('./constants')
|
||||
} = require('../modules/validator')
|
||||
const { postJob } = require('../queue')
|
||||
const { jobType } = require('../constants')
|
||||
|
||||
async function tornadoWithdraw(req, res) {
|
||||
const inputError = getTornadoWithdrawInputError(req.body)
|
||||
4
src/contollers/index.js
Normal file
4
src/contollers/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
controller: require('./controller'),
|
||||
status: require('./status'),
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
const queue = require('./queue')
|
||||
const { netId, tornadoServiceFee, miningServiceFee, instances, redisUrl, rewardAccount } = require('./config')
|
||||
const { version } = require('../package.json')
|
||||
const Redis = require('ioredis')
|
||||
const redis = new Redis(redisUrl)
|
||||
const queue = require('../queue')
|
||||
const { netId, tornadoServiceFee, miningServiceFee, instances, rewardAccount } = require('../config')
|
||||
const { version } = require('../../package.json')
|
||||
const { redis } = require('../modules/redis')
|
||||
const { readRelayerErrors } = require('../utils')
|
||||
|
||||
async function status(req, res) {
|
||||
const ethPrices = await redis.hgetall('prices')
|
||||
const health = await redis.hgetall('health')
|
||||
|
||||
health.errorsLog = await readRelayerErrors(redis)
|
||||
const { waiting: currentQueue } = await queue.queue.getJobCounts()
|
||||
|
||||
res.json({
|
||||
rewardAccount,
|
||||
instances: instances[`netId${netId}`],
|
||||
instances: instances[netId],
|
||||
netId,
|
||||
ethPrices,
|
||||
tornadoServiceFee,
|
||||
@@ -25,7 +25,7 @@ async function status(req, res) {
|
||||
|
||||
function index(req, res) {
|
||||
res.send(
|
||||
'This is <a href=https://tornado.cash>tornado.cash</a> Relayer service. Check the <a href=/v1/status>/status</a> for settings',
|
||||
'This is <a href=https://tornado.ws>Tornado Cash</a> Relayer service. Check the <a href=/v1/status>/status</a> for settings',
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
const Web3 = require('web3')
|
||||
const Redis = require('ioredis')
|
||||
const { toBN, fromWei } = require('web3-utils')
|
||||
|
||||
const { setSafeInterval } = require('./utils')
|
||||
const { redisUrl, httpRpcUrl, privateKey, minimumBalance } = require('./config')
|
||||
|
||||
const web3 = new Web3(httpRpcUrl)
|
||||
const redis = new Redis(redisUrl)
|
||||
const { setSafeInterval, toBN, fromWei, RelayerError } = require('./utils')
|
||||
const { privateKey, minimumBalance } = require('./config')
|
||||
const { redis } = require('./modules/redis')
|
||||
const web3 = require('./modules/web3')()
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const { address } = web3.eth.accounts.privateKeyToAccount(privateKey)
|
||||
const balance = await web3.eth.getBalance(address)
|
||||
|
||||
if (toBN(balance).lt(toBN(minimumBalance))) {
|
||||
throw new Error(`Not enough balance, less than ${fromWei(minimumBalance)} ETH`)
|
||||
throw new RelayerError(`Not enough balance, less than ${fromWei(minimumBalance)} ETH`, 1)
|
||||
}
|
||||
|
||||
await redis.hset('health', { status: true, error: '' })
|
||||
|
||||
11
src/modules/redis.js
Normal file
11
src/modules/redis.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { createClient } = require('ioredis')
|
||||
const { redisUrl } = require('../config')
|
||||
|
||||
const redis = createClient(redisUrl)
|
||||
const redisSubscribe = createClient(redisUrl)
|
||||
|
||||
module.exports = {
|
||||
redis,
|
||||
redisSubscribe,
|
||||
redisUrl,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
const { httpRpcUrl, aggregatorAddress } = require('./config')
|
||||
const Web3 = require('web3')
|
||||
const web3 = new Web3(httpRpcUrl)
|
||||
const aggregator = new web3.eth.Contract(require('../abis/Aggregator.abi.json'), aggregatorAddress)
|
||||
const { aggregatorAddress } = require('../config')
|
||||
const web3 = require('./web3')()
|
||||
|
||||
const aggregator = new web3.eth.Contract(require('../../abis/Aggregator.abi.json'), aggregatorAddress)
|
||||
const ens = require('eth-ens-namehash')
|
||||
|
||||
class ENSResolver {
|
||||
@@ -26,5 +26,4 @@ class ENSResolver {
|
||||
return addresses.length === 1 ? addresses[0] : addresses
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ENSResolver
|
||||
module.exports = new ENSResolver()
|
||||
@@ -1,6 +1,6 @@
|
||||
const { isAddress, toChecksumAddress } = require('web3-utils')
|
||||
const { getInstance } = require('./utils')
|
||||
const { rewardAccount } = require('./config')
|
||||
const { getInstance } = require('../utils')
|
||||
const { rewardAccount } = require('../config')
|
||||
|
||||
const Ajv = require('ajv')
|
||||
const ajv = new Ajv({ format: 'fast' })
|
||||
@@ -19,7 +19,7 @@ ajv.addKeyword('isAddress', {
|
||||
ajv.addKeyword('isKnownContract', {
|
||||
validate: (schema, data) => {
|
||||
try {
|
||||
return getInstance(data) !== null
|
||||
return !!getInstance(data)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
30
src/modules/web3.js
Normal file
30
src/modules/web3.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const Web3 = require('web3')
|
||||
const { oracleRpcUrl, httpRpcUrl, wsRpcUrl } = require('../config')
|
||||
const getWeb3 = (type = 'http') => {
|
||||
let url
|
||||
switch (type) {
|
||||
case 'oracle':
|
||||
url = oracleRpcUrl
|
||||
break
|
||||
case 'ws':
|
||||
url = wsRpcUrl
|
||||
return new Web3(
|
||||
new Web3.providers.WebsocketProvider(wsRpcUrl, {
|
||||
clientConfig: {
|
||||
maxReceivedFrameSize: 100000000,
|
||||
maxReceivedMessageSize: 100000000,
|
||||
},
|
||||
}),
|
||||
)
|
||||
case 'http':
|
||||
default:
|
||||
url = httpRpcUrl
|
||||
break
|
||||
}
|
||||
return new Web3(
|
||||
new Web3.providers.HttpProvider(url, {
|
||||
timeout: 200000, // ms
|
||||
}),
|
||||
)
|
||||
}
|
||||
module.exports = getWeb3
|
||||
@@ -1,42 +1,21 @@
|
||||
const Redis = require('ioredis')
|
||||
const { redisUrl, offchainOracleAddress, oracleRpcUrl } = require('./config')
|
||||
const { getArgsForOracle, setSafeInterval } = require('./utils')
|
||||
const redis = new Redis(redisUrl)
|
||||
const Web3 = require('web3')
|
||||
const web3 = new Web3(
|
||||
new Web3.providers.HttpProvider(oracleRpcUrl, {
|
||||
timeout: 200000, // ms
|
||||
}),
|
||||
)
|
||||
const { setSafeInterval, RelayerError, logRelayerError } = require('./utils')
|
||||
const { redis } = require('./modules/redis')
|
||||
const { TokenPriceOracle } = require('@tornado/tornado-oracles')
|
||||
const { oracleRpcUrl } = require('./config')
|
||||
|
||||
const offchainOracleABI = require('../abis/OffchainOracle.abi.json')
|
||||
|
||||
const offchainOracle = new web3.eth.Contract(offchainOracleABI, offchainOracleAddress)
|
||||
const { tokenAddresses, oneUintAmount, currencyLookup } = getArgsForOracle()
|
||||
|
||||
const { toBN } = require('web3-utils')
|
||||
const priceOracle = new TokenPriceOracle(oracleRpcUrl)
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const ethPrices = {}
|
||||
for (let i = 0; i < tokenAddresses.length; i++) {
|
||||
try {
|
||||
const price = await offchainOracle.methods
|
||||
.getRate(tokenAddresses[i], '0x0000000000000000000000000000000000000000')
|
||||
.call()
|
||||
const numerator = toBN(oneUintAmount[i])
|
||||
const denominator = toBN(10).pow(toBN(18)) // eth decimals
|
||||
const priceFormatted = toBN(price).mul(numerator).div(denominator)
|
||||
|
||||
ethPrices[currencyLookup[tokenAddresses[i]]] = priceFormatted.toString()
|
||||
} catch (e) {
|
||||
console.error('cant get price of ', tokenAddresses[i])
|
||||
}
|
||||
const ethPrices = await priceOracle.fetchPrices()
|
||||
if (!Object.values(ethPrices).length) {
|
||||
throw new RelayerError('Can`t update prices', 1)
|
||||
}
|
||||
|
||||
await redis.hmset('prices', ethPrices)
|
||||
console.log('Wrote following prices to redis', ethPrices)
|
||||
} catch (e) {
|
||||
await logRelayerError(redis, e)
|
||||
console.error('priceWatcher error', e)
|
||||
}
|
||||
}
|
||||
|
||||
10
src/queue.js
10
src/queue.js
@@ -1,11 +1,11 @@
|
||||
const { v4: uuid } = require('uuid')
|
||||
const Queue = require('bull')
|
||||
const Redis = require('ioredis')
|
||||
const { redisUrl } = require('./config')
|
||||
const { status } = require('./constants')
|
||||
const redis = new Redis(redisUrl)
|
||||
|
||||
const queue = new Queue('proofs', redisUrl, {
|
||||
const { netId } = require('./config')
|
||||
const { status } = require('./constants')
|
||||
const { redis, redisUrl } = require('./modules/redis')
|
||||
|
||||
const queue = new Queue(`proofs_${netId}`, redisUrl, {
|
||||
lockDuration: 300000, // Key expiration time for job locks.
|
||||
lockRenewTime: 30000, // Interval on which to acquire the job lock
|
||||
stalledInterval: 30000, // How often check for stalled jobs (use 0 for never checking).
|
||||
|
||||
30
src/router.js
Normal file
30
src/router.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const { controller, status } = require('./contollers')
|
||||
const router = require('express').Router()
|
||||
|
||||
// Add CORS headers
|
||||
router.use((req, res, next) => {
|
||||
res.header('X-Frame-Options', 'DENY')
|
||||
res.header('Access-Control-Allow-Origin', '*')
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
|
||||
next()
|
||||
})
|
||||
|
||||
// Log error to console but don't send it to the client to avoid leaking data
|
||||
router.use((err, req, res, next) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
router.get('/', status.index)
|
||||
router.get('/v1/status', status.status)
|
||||
router.get('/v1/jobs/:id', status.getJob)
|
||||
router.post('/v1/tornadoWithdraw', controller.tornadoWithdraw)
|
||||
router.get('/status', status.status)
|
||||
router.post('/relay', controller.tornadoWithdraw)
|
||||
router.post('/v1/miningReward', controller.miningReward)
|
||||
router.post('/v1/miningWithdraw', controller.miningWithdraw)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,41 +1,14 @@
|
||||
const express = require('express')
|
||||
const status = require('./status')
|
||||
const controller = require('./controller')
|
||||
const { port, rewardAccount } = require('./config')
|
||||
const { version } = require('../package.json')
|
||||
const { isAddress } = require('web3-utils')
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
|
||||
// Add CORS headers
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', '*')
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
|
||||
next()
|
||||
})
|
||||
|
||||
// Log error to console but don't send it to the client to avoid leaking data
|
||||
app.use((err, req, res, next) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
app.get('/', status.index)
|
||||
app.get('/v1/status', status.status)
|
||||
app.get('/v1/jobs/:id', status.getJob)
|
||||
app.post('/v1/tornadoWithdraw', controller.tornadoWithdraw)
|
||||
app.get('/status', status.status)
|
||||
app.post('/relay', controller.tornadoWithdraw)
|
||||
app.post('/v1/miningReward', controller.miningReward)
|
||||
app.post('/v1/miningWithdraw', controller.miningWithdraw)
|
||||
const { isAddress } = require('./utils')
|
||||
const router = require('./router')
|
||||
|
||||
if (!isAddress(rewardAccount)) {
|
||||
throw new Error('No REWARD_ACCOUNT specified')
|
||||
}
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
app.use(router)
|
||||
app.listen(port)
|
||||
console.log(`Relayer ${version} started on port ${port}`)
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
const { redisUrl, wsRpcUrl, minerMerkleTreeHeight, torn } = require('./config')
|
||||
const { poseidonHash2 } = require('./utils')
|
||||
const { toBN } = require('web3-utils')
|
||||
const Redis = require('ioredis')
|
||||
const redis = new Redis(redisUrl)
|
||||
const ENSResolver = require('./resolver')
|
||||
const resolver = new ENSResolver()
|
||||
const Web3 = require('web3')
|
||||
const web3 = new Web3(
|
||||
new Web3.providers.WebsocketProvider(wsRpcUrl, {
|
||||
clientConfig: {
|
||||
maxReceivedFrameSize: 100000000,
|
||||
maxReceivedMessageSize: 100000000,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const MerkleTree = require('@tornado/fixed-merkle-tree')
|
||||
const { minerMerkleTreeHeight, torn, netId } = require('./config')
|
||||
const { poseidonHash2, toBN, logRelayerError } = require('./utils')
|
||||
const resolver = require('./modules/resolver')
|
||||
const web3 = require('./modules/web3')('ws')
|
||||
const MinerABI = require('../abis/mining.abi.json')
|
||||
const { redis } = require('./modules/redis')
|
||||
let contract
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let tree, eventSubscription, blockSubscription
|
||||
|
||||
// todo handle the situation when we have two rewards in one block
|
||||
async function fetchEvents(from = 0, to = 'latest') {
|
||||
try {
|
||||
const events = await contract.getPastEvents('NewAccount', {
|
||||
fromBlock: from,
|
||||
toBlock: to,
|
||||
})
|
||||
return events
|
||||
.sort((a, b) => a.returnValues.index - b.returnValues.index)
|
||||
.map(e => toBN(e.returnValues.commitment))
|
||||
} catch (e) {
|
||||
console.error('error fetching events', e)
|
||||
async function fetchEvents(fromBlock, toBlock) {
|
||||
if (fromBlock <= toBlock) {
|
||||
try {
|
||||
return await contract.getPastEvents('NewAccount', {
|
||||
fromBlock,
|
||||
toBlock,
|
||||
})
|
||||
} catch (error) {
|
||||
const midBlock = (fromBlock + toBlock) >> 1
|
||||
|
||||
if (midBlock - fromBlock < 2) {
|
||||
throw new Error(`error fetching events: ${error.message}`)
|
||||
}
|
||||
|
||||
const arr1 = await fetchEvents(fromBlock, midBlock)
|
||||
const arr2 = await fetchEvents(midBlock + 1, toBlock)
|
||||
return [...arr1, ...arr2]
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
async function processNewEvent(err, event) {
|
||||
@@ -105,14 +101,29 @@ async function init() {
|
||||
console.log('Initializing')
|
||||
const miner = await resolver.resolve(torn.miningV2.address)
|
||||
contract = new web3.eth.Contract(MinerABI, miner)
|
||||
const block = await web3.eth.getBlockNumber()
|
||||
const events = await fetchEvents(0, block)
|
||||
tree = new MerkleTree(minerMerkleTreeHeight, events, { hashFunction: poseidonHash2 })
|
||||
|
||||
const cachedEvents = require(`../cache/accounts_farmer_${netId}.json`)
|
||||
const cachedCommitments = cachedEvents.map(e => toBN(e.commitment))
|
||||
|
||||
const toBlock = await web3.eth.getBlockNumber()
|
||||
const [{ blockNumber: fromBlock }] = cachedEvents.slice(-1)
|
||||
|
||||
const newEvents = await fetchEvents(fromBlock + 1, toBlock)
|
||||
const newCommitments = newEvents
|
||||
.sort((a, b) => a.returnValues.index - b.returnValues.index)
|
||||
.map(e => toBN(e.returnValues.commitment))
|
||||
.filter((item, index, arr) => !index || item !== arr[index - 1])
|
||||
|
||||
const commitments = cachedCommitments.concat(newCommitments)
|
||||
|
||||
tree = new MerkleTree(minerMerkleTreeHeight, commitments, { hashFunction: poseidonHash2 })
|
||||
await updateRedis()
|
||||
console.log(`Rebuilt tree with ${events.length} elements, root: ${tree.root()}`)
|
||||
eventSubscription = contract.events.NewAccount({ fromBlock: block + 1 }, processNewEvent)
|
||||
console.log(`Rebuilt tree with ${commitments.length} elements, root: ${tree.root()}`)
|
||||
|
||||
eventSubscription = contract.events.NewAccount({ fromBlock: toBlock + 1 }, processNewEvent)
|
||||
blockSubscription = web3.eth.subscribe('newBlockHeaders', processNewBlock)
|
||||
} catch (e) {
|
||||
await logRelayerError(redis, e)
|
||||
console.error('error on init treeWatcher', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
86
src/utils.js
86
src/utils.js
@@ -1,28 +1,30 @@
|
||||
const { instances, netId } = require('./config')
|
||||
const { poseidon } = require('circomlib')
|
||||
const { toBN, toChecksumAddress, BN } = require('web3-utils')
|
||||
const { poseidon } = require('@tornado/circomlib')
|
||||
const { toBN, toChecksumAddress, BN, fromWei, isAddress, toWei, toHex } = require('web3-utils')
|
||||
|
||||
const TOKENS = {
|
||||
torn: {
|
||||
tokenAddress: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
||||
symbol: 'TORN',
|
||||
decimals: 18,
|
||||
},
|
||||
const addressMap = new Map()
|
||||
const instance = instances[netId]
|
||||
|
||||
for (const [currency, { instanceAddress, symbol, decimals }] of Object.entries(instance)) {
|
||||
Object.entries(instanceAddress).forEach(([amount, address]) =>
|
||||
addressMap.set(`${netId}_${address}`, {
|
||||
currency,
|
||||
amount,
|
||||
symbol,
|
||||
decimals,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const sleep = ms => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
function getInstance(address) {
|
||||
address = toChecksumAddress(address)
|
||||
const inst = instances[`netId${netId}`]
|
||||
for (const currency of Object.keys(inst)) {
|
||||
for (const amount of Object.keys(inst[currency].instanceAddress)) {
|
||||
if (inst[currency].instanceAddress[amount] === address) {
|
||||
return { currency, amount }
|
||||
}
|
||||
}
|
||||
const key = `${netId}_${toChecksumAddress(address)}`
|
||||
if (addressMap.has(key)) {
|
||||
return addressMap.get(key)
|
||||
} else {
|
||||
throw new Error('Unknown contact address')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const poseidonHash = items => toBN(poseidon(items).toString())
|
||||
@@ -51,24 +53,6 @@ function when(source, event) {
|
||||
})
|
||||
}
|
||||
|
||||
function getArgsForOracle() {
|
||||
const tokens = {
|
||||
...instances.netId1,
|
||||
...TOKENS,
|
||||
}
|
||||
const tokenAddresses = []
|
||||
const oneUintAmount = []
|
||||
const currencyLookup = {}
|
||||
Object.entries(tokens).map(([currency, data]) => {
|
||||
if (currency !== 'eth') {
|
||||
tokenAddresses.push(data.tokenAddress)
|
||||
oneUintAmount.push(toBN('10').pow(toBN(data.decimals.toString())).toString())
|
||||
currencyLookup[data.tokenAddress] = currency
|
||||
}
|
||||
})
|
||||
return { tokenAddresses, oneUintAmount, currencyLookup }
|
||||
}
|
||||
|
||||
function fromDecimals(value, decimals) {
|
||||
value = value.toString()
|
||||
let ether = value.toString()
|
||||
@@ -118,12 +102,42 @@ function fromDecimals(value, decimals) {
|
||||
return new BN(wei.toString(10), 10)
|
||||
}
|
||||
|
||||
class RelayerError extends Error {
|
||||
constructor(message, score = 0) {
|
||||
super(message)
|
||||
this.score = score
|
||||
}
|
||||
}
|
||||
|
||||
const logRelayerError = async (redis, e) => {
|
||||
await redis.zadd('errors', 'INCR', e.score || 1, e.message)
|
||||
}
|
||||
|
||||
const readRelayerErrors = async redis => {
|
||||
const set = await redis.zrevrange('errors', 0, -1, 'WITHSCORES')
|
||||
const errors = []
|
||||
while (set.length) {
|
||||
const [message, score] = set.splice(0, 2)
|
||||
errors.push({ message, score })
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getInstance,
|
||||
setSafeInterval,
|
||||
poseidonHash2,
|
||||
sleep,
|
||||
when,
|
||||
getArgsForOracle,
|
||||
fromDecimals,
|
||||
toBN,
|
||||
toChecksumAddress,
|
||||
fromWei,
|
||||
toWei,
|
||||
toHex,
|
||||
BN,
|
||||
isAddress,
|
||||
RelayerError,
|
||||
logRelayerError,
|
||||
readRelayerErrors,
|
||||
}
|
||||
|
||||
162
src/worker.js
162
src/worker.js
@@ -1,34 +1,40 @@
|
||||
const fs = require('fs')
|
||||
const Web3 = require('web3')
|
||||
const { toBN, toWei, fromWei, toChecksumAddress } = require('web3-utils')
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
const Redis = require('ioredis')
|
||||
const { GasPriceOracle } = require('gas-price-oracle')
|
||||
const { Utils, Controller } = require('tornado-anonymity-mining')
|
||||
|
||||
const MerkleTree = require('@tornado/fixed-merkle-tree')
|
||||
const { Utils, Controller } = require('@tornado/anonymity-mining')
|
||||
const { TornadoFeeOracleV5 } = require('@tornado/tornado-oracles')
|
||||
const swapABI = require('../abis/swap.abi.json')
|
||||
const miningABI = require('../abis/mining.abi.json')
|
||||
const tornadoABI = require('../abis/tornadoABI.json')
|
||||
const tornadoProxyABI = require('../abis/tornadoProxyABI.json')
|
||||
const { queue } = require('./queue')
|
||||
const { poseidonHash2, getInstance, fromDecimals, sleep } = require('./utils')
|
||||
const {
|
||||
poseidonHash2,
|
||||
getInstance,
|
||||
sleep,
|
||||
toBN,
|
||||
fromWei,
|
||||
toChecksumAddress,
|
||||
isAddress,
|
||||
RelayerError,
|
||||
logRelayerError,
|
||||
} = require('./utils')
|
||||
const { jobType, status } = require('./constants')
|
||||
const {
|
||||
torn,
|
||||
netId,
|
||||
redisUrl,
|
||||
gasLimits,
|
||||
instances,
|
||||
privateKey,
|
||||
httpRpcUrl,
|
||||
oracleRpcUrl,
|
||||
baseFeeReserve,
|
||||
miningServiceFee,
|
||||
tornadoServiceFee,
|
||||
tornadoGoerliProxy,
|
||||
} = require('./config')
|
||||
const ENSResolver = require('./resolver')
|
||||
const resolver = new ENSResolver()
|
||||
const { TxManager } = require('tx-manager')
|
||||
const resolver = require('./modules/resolver')
|
||||
const { TxManager } = require('@tornado/tx-manager')
|
||||
const { redis, redisSubscribe } = require('./modules/redis')
|
||||
const getWeb3 = require('./modules/web3')
|
||||
|
||||
let web3
|
||||
let currentTx
|
||||
@@ -38,9 +44,7 @@ let txManager
|
||||
let controller
|
||||
let swap
|
||||
let minerContract
|
||||
const redis = new Redis(redisUrl)
|
||||
const redisSubscribe = new Redis(redisUrl)
|
||||
const gasPriceOracle = new GasPriceOracle({ defaultRpc: oracleRpcUrl })
|
||||
const feeOracle = new TornadoFeeOracleV5(netId, oracleRpcUrl)
|
||||
|
||||
async function fetchTree() {
|
||||
const elements = await redis.get('tree:elements')
|
||||
@@ -74,13 +78,20 @@ async function fetchTree() {
|
||||
|
||||
async function start() {
|
||||
try {
|
||||
web3 = new Web3(httpRpcUrl)
|
||||
await clearErrors()
|
||||
web3 = getWeb3()
|
||||
const { CONFIRMATIONS, MAX_GAS_PRICE } = process.env
|
||||
txManager = new TxManager({
|
||||
privateKey,
|
||||
rpcUrl: httpRpcUrl,
|
||||
config: { CONFIRMATIONS, MAX_GAS_PRICE, THROW_ON_REVERT: false },
|
||||
config: {
|
||||
CONFIRMATIONS,
|
||||
MAX_GAS_PRICE,
|
||||
THROW_ON_REVERT: false,
|
||||
BASE_FEE_RESERVE_PERCENTAGE: baseFeeReserve,
|
||||
},
|
||||
})
|
||||
|
||||
swap = new web3.eth.Contract(swapABI, await resolver.resolve(torn.rewardSwap.address))
|
||||
minerContract = new web3.eth.Contract(miningABI, await resolver.resolve(torn.miningV2.address))
|
||||
redisSubscribe.subscribe('treeUpdate', fetchTree)
|
||||
@@ -94,61 +105,57 @@ async function start() {
|
||||
queue.process(processJob)
|
||||
console.log('Worker started')
|
||||
} catch (e) {
|
||||
await logRelayerError(redis, e)
|
||||
console.error('error on start worker', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
function checkFee({ data }) {
|
||||
function checkFee({ data }, gasInfo) {
|
||||
if (data.type === jobType.TORNADO_WITHDRAW) {
|
||||
return checkTornadoFee(data)
|
||||
return checkTornadoFee(data, gasInfo)
|
||||
}
|
||||
return checkMiningFee(data)
|
||||
}
|
||||
|
||||
async function checkTornadoFee({ args, contract }) {
|
||||
const { currency, amount } = getInstance(contract)
|
||||
const { decimals } = instances[`netId${netId}`][currency]
|
||||
const [fee, refund] = [args[4], args[5]].map(toBN)
|
||||
const { fast } = await gasPriceOracle.gasPrices()
|
||||
async function checkTornadoFee({ args, contract }, tx) {
|
||||
const { currency, amount, decimals } = getInstance(contract)
|
||||
const [userProvidedFee, refund] = [args[4], args[5]].map(toBN)
|
||||
const { gasLimit, gasPrice } = tx
|
||||
|
||||
const ethPrice = await redis.hget('prices', currency)
|
||||
const expense = toBN(toWei(fast.toString(), 'gwei')).mul(toBN(gasLimits[jobType.TORNADO_WITHDRAW]))
|
||||
const feePercent = toBN(fromDecimals(amount, decimals))
|
||||
.mul(toBN(parseInt(tornadoServiceFee * 1e10)))
|
||||
.div(toBN(1e10 * 100))
|
||||
let desiredFee
|
||||
switch (currency) {
|
||||
case 'eth': {
|
||||
desiredFee = expense.add(feePercent)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
desiredFee = expense
|
||||
.add(refund)
|
||||
.mul(toBN(10 ** decimals))
|
||||
.div(toBN(ethPrice))
|
||||
desiredFee = desiredFee.add(feePercent)
|
||||
break
|
||||
}
|
||||
}
|
||||
const totalWithdrawalFee = await feeOracle.calculateWithdrawalFeeViaRelayer({
|
||||
tx,
|
||||
txType: 'relayer_withdrawal',
|
||||
amount,
|
||||
currency,
|
||||
decimals,
|
||||
refundInEth: refund.toString(),
|
||||
predefinedGasLimit: gasLimit,
|
||||
predefinedGasPrice: gasPrice,
|
||||
tokenPriceInEth: ethPrice,
|
||||
relayerFeePercent: tornadoServiceFee,
|
||||
})
|
||||
|
||||
console.log(
|
||||
'sent fee, desired fee, feePercent',
|
||||
fromWei(fee.toString()),
|
||||
fromWei(desiredFee.toString()),
|
||||
fromWei(feePercent.toString()),
|
||||
'user-provided fee, desired fee',
|
||||
fromWei(userProvidedFee.toString()),
|
||||
fromWei(toBN(totalWithdrawalFee).toString()),
|
||||
)
|
||||
if (fee.lt(desiredFee)) {
|
||||
throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.')
|
||||
if (userProvidedFee.lt(toBN(totalWithdrawalFee))) {
|
||||
throw new RelayerError(
|
||||
'Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.',
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkMiningFee({ args }) {
|
||||
const { fast } = await gasPriceOracle.gasPrices()
|
||||
const gasPrice = await feeOracle.getGasPrice()
|
||||
const ethPrice = await redis.hget('prices', 'torn')
|
||||
const isMiningReward = currentJob.data.type === jobType.MINING_REWARD
|
||||
const providedFee = isMiningReward ? toBN(args.fee) : toBN(args.extData.fee)
|
||||
|
||||
const expense = toBN(toWei(fast.toString(), 'gwei')).mul(toBN(gasLimits[currentJob.data.type]))
|
||||
const expense = toBN(gasPrice).mul(toBN(gasLimits[currentJob.data.type]))
|
||||
const expenseInTorn = expense.mul(toBN(1e18)).div(toBN(ethPrice))
|
||||
// todo make aggregator for ethPrices and rewardSwap data
|
||||
const balance = await swap.methods.tornVirtualBalance().call()
|
||||
@@ -170,19 +177,17 @@ async function checkMiningFee({ args }) {
|
||||
serviceFeePercent.toString(),
|
||||
)
|
||||
if (toBN(providedFee).lt(desiredFee)) {
|
||||
throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.')
|
||||
throw new RelayerError('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.')
|
||||
}
|
||||
}
|
||||
|
||||
async function getProxyContract() {
|
||||
let proxyAddress
|
||||
|
||||
if (netId === 5) {
|
||||
proxyAddress = tornadoGoerliProxy
|
||||
} else {
|
||||
proxyAddress = await resolver.resolve(torn.tornadoProxy.address)
|
||||
proxyAddress = await resolver.resolve(torn.tornadoRouter.address)
|
||||
}
|
||||
|
||||
const contract = new web3.eth.Contract(tornadoProxyABI, proxyAddress)
|
||||
|
||||
return {
|
||||
@@ -196,6 +201,18 @@ function checkOldProxy(address) {
|
||||
return toChecksumAddress(address) === toChecksumAddress(OLD_PROXY)
|
||||
}
|
||||
|
||||
async function checkRecipient({ data }) {
|
||||
// Checks only for default withdrawals
|
||||
if (data.type !== jobType.TORNADO_WITHDRAW) return
|
||||
|
||||
console.log(data.args)
|
||||
const recipient = data.args[2]
|
||||
if (!isAddress(recipient)) throw new Error('Recipient address is invalid')
|
||||
|
||||
const addressCode = await web3.eth.getCode(toChecksumAddress(recipient))
|
||||
if (addressCode !== '0x') throw new Error('Recipient cannot be a smart-contract, only EOA')
|
||||
}
|
||||
|
||||
async function getTxObject({ data }) {
|
||||
if (data.type === jobType.TORNADO_WITHDRAW) {
|
||||
let { contract, isOldProxy } = await getProxyContract()
|
||||
@@ -207,12 +224,18 @@ async function getTxObject({ data }) {
|
||||
calldata = contract.methods.withdraw(data.proof, ...data.args).encodeABI()
|
||||
}
|
||||
|
||||
return {
|
||||
const incompleteTx = {
|
||||
value: data.args[5],
|
||||
from: txManager.address, // Required, because without it relayerRegistry.burn will fail, because msg.sender is not relayer
|
||||
to: contract._address,
|
||||
data: calldata,
|
||||
gasLimit: gasLimits['WITHDRAW_WITH_EXTRA'],
|
||||
}
|
||||
const { gasLimit, gasPrice } = await feeOracle.getGasParams({
|
||||
tx: incompleteTx,
|
||||
txType: 'relayer_withdrawal',
|
||||
})
|
||||
|
||||
return { ...incompleteTx, gasLimit, gasPrice }
|
||||
} else {
|
||||
const method = data.type === jobType.MINING_REWARD ? 'reward' : 'withdraw'
|
||||
const calldata = minerContract.methods[method](data.proof, data.args).encodeABI()
|
||||
@@ -241,7 +264,7 @@ async function isOutdatedTreeRevert(receipt, currentTx) {
|
||||
async function processJob(job) {
|
||||
try {
|
||||
if (!jobType[job.data.type]) {
|
||||
throw new Error(`Unknown job type: ${job.data.type}`)
|
||||
throw new RelayerError(`Unknown job type: ${job.data.type}`)
|
||||
}
|
||||
currentJob = job
|
||||
await updateStatus(status.ACCEPTED)
|
||||
@@ -250,13 +273,15 @@ async function processJob(job) {
|
||||
} catch (e) {
|
||||
console.error('processJob', e.message)
|
||||
await updateStatus(status.FAILED)
|
||||
throw e
|
||||
throw new RelayerError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function submitTx(job, retry = 0) {
|
||||
await checkFee(job)
|
||||
currentTx = await txManager.createTx(await getTxObject(job))
|
||||
await checkRecipient(job)
|
||||
const rawTx = await getTxObject(job)
|
||||
await checkFee(job, rawTx)
|
||||
currentTx = await txManager.createTx(rawTx)
|
||||
|
||||
if (job.data.type !== jobType.TORNADO_WITHDRAW) {
|
||||
await fetchTree()
|
||||
@@ -283,10 +308,10 @@ async function submitTx(job, retry = 0) {
|
||||
await updateStatus(status.RESUBMITTED)
|
||||
await submitTx(job, retry + 1)
|
||||
} else {
|
||||
throw new Error('Tree update retry limit exceeded')
|
||||
throw new RelayerError('Tree update retry limit exceeded')
|
||||
}
|
||||
} else {
|
||||
throw new Error('Submitted transaction failed')
|
||||
throw new RelayerError('Submitted transaction failed')
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -302,10 +327,10 @@ async function submitTx(job, retry = 0) {
|
||||
console.log('Tree is still not up to date, resubmitting')
|
||||
await submitTx(job, retry + 1)
|
||||
} else {
|
||||
throw new Error('Tree update retry limit exceeded')
|
||||
throw new RelayerError('Tree update retry limit exceeded')
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Revert by smart contract ${e.message}`)
|
||||
throw new RelayerError(`Revert by smart contract ${e.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,4 +353,9 @@ async function updateStatus(status) {
|
||||
await currentJob.update(currentJob.data)
|
||||
}
|
||||
|
||||
async function clearErrors() {
|
||||
console.log('Errors list cleared')
|
||||
await redis.del('errors')
|
||||
}
|
||||
|
||||
start()
|
||||
|
||||
@@ -4,7 +4,7 @@ const {
|
||||
getTornadoWithdrawInputError,
|
||||
getMiningRewardInputError,
|
||||
getMiningWithdrawInputError,
|
||||
} = require('../src/validator')
|
||||
} = require('../src/modules/validator')
|
||||
|
||||
describe('Validator', () => {
|
||||
describe('#getTornadoWithdrawInputError', () => {
|
||||
@@ -19,7 +19,13 @@ describe('Validator', () => {
|
||||
'.proof should match pattern "^0x[a-fA-F0-9]{512}$"',
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw if unknown contract', () => {
|
||||
const malformedData = { ...withdrawData }
|
||||
malformedData.contract = '0xf17f52151ebef6c7334fad080c5704d77216b732'
|
||||
getTornadoWithdrawInputError(malformedData).should.be.equal(
|
||||
'.contract should pass "isKnownContract" keyword validation',
|
||||
)
|
||||
})
|
||||
it('should throw something is missing', () => {
|
||||
const malformedData = { ...withdrawData }
|
||||
delete malformedData.proof
|
||||
|
||||
15
tornado-stream.conf
Normal file
15
tornado-stream.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
map $ssl_preread_server_name $name {
|
||||
yourdomain.com tornado_mainnet;
|
||||
|
||||
default tornado_mainnet;
|
||||
}
|
||||
|
||||
upstream tornado_mainnet {
|
||||
server 127.0.0.1:4380;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 0.0.0.0:443;
|
||||
proxy_pass $name;
|
||||
ssl_preread on;
|
||||
}
|
||||
87
tornado.conf
Normal file
87
tornado.conf
Normal file
@@ -0,0 +1,87 @@
|
||||
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
|
||||
# scheme used to connect to this server
|
||||
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
||||
default $http_x_forwarded_proto;
|
||||
'' $scheme;
|
||||
}
|
||||
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
|
||||
# server port the client connected to
|
||||
map $http_x_forwarded_port $proxy_x_forwarded_port {
|
||||
default $http_x_forwarded_port;
|
||||
'' $server_port;
|
||||
}
|
||||
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
|
||||
# Connection header that may have been passed to this server
|
||||
map $http_upgrade $proxy_connection {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
# Apply fix for very long server names
|
||||
server_names_hash_bucket_size 128;
|
||||
# Default dhparam
|
||||
# Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto
|
||||
map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl {
|
||||
default off;
|
||||
https on;
|
||||
}
|
||||
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent" '
|
||||
'"$upstream_addr"';
|
||||
# HTTP 1.1 support
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $proxy_connection;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
||||
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
# Mitigate httpoxy attack (see README for details)
|
||||
proxy_set_header Proxy "";
|
||||
|
||||
# Request rate limiting per second, 2Mb zone @ 5 requests per second
|
||||
limit_req_zone $binary_remote_addr zone=one:2m rate=5r/s;
|
||||
# Connections per IP limited to 2
|
||||
limit_conn_zone $binary_remote_addr zone=two:2m;
|
||||
|
||||
server {
|
||||
server_name _; # This is just an invalid value which will never trigger on a real hostname.
|
||||
server_tokens off;
|
||||
listen 80;
|
||||
access_log /var/log/nginx/access.log vhost;
|
||||
return 503;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name yourdomain.com;
|
||||
|
||||
# Connection timeouts
|
||||
client_body_timeout 10s;
|
||||
client_header_timeout 10s;
|
||||
|
||||
listen 80;
|
||||
access_log /var/log/nginx/access.log vhost;
|
||||
|
||||
# Do not HTTPS redirect LetsEncrypt ACME challenge
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
limit_req zone=one;
|
||||
limit_conn two 1;
|
||||
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
location / {
|
||||
limit_req zone=one;
|
||||
limit_conn two 1;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user