53 Commits

Author SHA1 Message Date
1705dd1ce2 Set new software version for anti-contract withdrawal fix 2023-10-17 06:29:29 -07:00
1e99a136cc Add protection from abusing relayers (force spending fee for reverted tx): fail if address is invalid or smart-contract (not EOA) 2023-10-16 16:49:56 -07:00
03c71f28bb Upgrade dependecies to self-hosted versions & bump node to 16 2023-09-18 22:50:24 -07:00
d04480a62f Update tornado-oracles to fetch gas price and limit only once 2023-09-07 10:48:28 -07:00
5191207cc2 Update tornado-oracles lib to fix gas price issues 2023-08-30 08:16:35 -07:00
bb76455d6a Calculate fees and gas via @tornado/tornado-oracles lib & bump relayer version to 5.2.0 2023-08-27 09:47:11 -07:00
c33a7f9ef2 Expose redis port for local deployment 2023-07-30 03:03:40 -07:00
0a7e97e62e Change fee parameter naming for more clearance 2023-07-30 02:15:55 -07:00
fb2fbb89f3 Add multiple chains deployment with docker & bump version to stable 5.1.0 and change README 2023-07-13 22:10:44 -07:00
bc6778bda2 Bump node in docker-container & add all .env files, except example, to gitignore 2023-07-03 12:49:35 -07:00
64ccb5d4d0 Correctly estimate withdrawal gas fee 2023-07-02 17:59:48 -07:00
b2ae3d3399 Change default oracle RPC from censored and change minimum required relayer balance for sidechains: 10 MATIC/AVAX or 0.1 ETH/BNB 2023-07-02 17:58:44 -07:00
9b14e3ed89 Bump gas-price-oracle to correct gas estimation & add 'concurrently' and change 'start' command, because if run server without concurrently, worker and healthWatcher wont be started 2023-07-02 17:52:39 -07:00
4c1500bd96 Set default fee for sidechains as 0.1% - calculation formula here: https://docs.tornado.ws/general/guides/relayer.html 2023-07-02 17:44:53 -07:00
Danil Kovtonyuk
a4916d1cb4 fix: calculate optimism l1 fee 2022-05-31 00:13:07 +10:00
Danil Kovtonyuk
7350591ebd bump tx-manager 2022-05-25 19:25:58 +10:00
smart_ex
a453444318 log healthWatcher errors 2022-05-25 19:25:58 +10:00
smart_ex
bca85764bc fix logging error messages 2022-05-25 19:25:58 +10:00
smart_ex
38fd1e6a4c fix queue collision 2022-05-25 19:25:58 +10:00
smart_ex
ecdcb545fc fix isKnownContract validation 2022-05-25 19:25:58 +10:00
smart_ex
ed9f003d50 block iframe 2022-05-25 19:25:58 +10:00
smart_ex
526f1889f9 clear errors on start 2022-05-25 19:25:58 +10:00
smart_ex
e5dd4b465f log errors to redis 2022-05-25 19:25:58 +10:00
smart_ex
92bc3bb0a8 log errors to redis 2022-05-25 19:25:58 +10:00
smart_ex
7d8a6dc30f log errors to redis 2022-05-25 19:25:58 +10:00
Danil Kovtonyuk
5b2815e6a0 bump gas price oracle 2022-04-13 23:57:02 +10:00
Danil Kovtonyuk
220a71fe09 fix: workflow 2022-04-13 21:52:53 +10:00
Danil Kovtonyuk
692ab64d49 Update README.md 2022-04-13 21:11:45 +10:00
_den
6eb3d702a4 added link to instructions for install rpc nodes 2022-03-10 21:11:13 +10:00
Alexey Pertsev
06848352f1 update docker tag 2022-03-01 11:38:21 +01:00
Alexey Pertsev
87d3528346 Update Readme 2022-03-01 11:36:40 +01:00
_den
1d6c2575f9 added discord notification (#87)
* added discord notification

* updated message

* updated discord notification

* updated discord notification message

Co-authored-by: _den <_den@outlook.com>
2022-02-16 10:37:44 +01:00
Danil Kovtonyuk
96b6e722d8 fix: add optimism gas 2022-01-28 11:01:25 +01:00
Danil Kovtonyuk
4a28d4646e feat: add optimism 2022-01-13 00:30:13 +10:00
Danil Kovtonyuk
3d952f1906 feat: add Arbitrum 2021-11-29 16:06:04 +03:00
Danil Kovtonyuk
07b091ff46 bump gas price oracle 2021-11-16 03:52:47 +10:00
Danil Kovtonyuk
10b2fed839 fix: update gas price oracle 2021-11-16 01:59:41 +10:00
Danil Kovtonyuk
c0320285b9 fix: update gas price oracle 2021-11-15 17:58:18 +10:00
Danil Kovtonyuk
7ea0b9abd6 feat: add Avalanche 2021-09-17 19:18:58 +10:00
Danil Kovtonyuk
aa7d389766 fix: update gas price oracle 2021-08-26 01:57:00 +10:00
Danil Kovtonyuk
e538236904 fix: update contracts 2021-08-26 01:57:00 +10:00
Danil Kovtonyuk
787c9663fe feat: add xDai 2021-08-26 01:57:00 +10:00
Danil Kovtonyuk
71b638e640 fix: variables 2021-06-29 00:50:44 +03:00
Danil Kovtonyuk
3d7a9b5f93 fix: get gas prices 2021-06-29 00:50:44 +03:00
Danil Kovtonyuk
96d937ee0d fix: update contracts 2021-06-29 00:50:44 +03:00
Danil Kovtonyuk
9b49cab775 fix: tx gas price 2021-06-29 00:50:44 +03:00
Danil Kovtonyuk
9747c1a397 feat: add polygon 2021-06-29 00:50:44 +03:00
Danil Kovtonyuk
3cf642fe68 fix: match tag 2021-06-11 12:10:43 +03:00
Danil Kovtonyuk
5cfe527be8 fix: bump gas price oracle 2021-06-10 23:41:43 +03:00
Danil Kovtonyuk
e24af4239e fix: match tag 2021-06-10 21:18:00 +03:00
Danil Kovtonyuk
a875bc77fa update build 2021-06-10 20:06:48 +03:00
Danil Kovtonyuk
7286aeaff0 fix: update constants 2021-06-10 13:02:53 +03:00
Danil Kovtonyuk
803a104a93 light relayer 2021-06-03 17:23:30 +03:00
47 changed files with 2283 additions and 745922 deletions

View File

@@ -1,8 +1,5 @@
NET_ID=1
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://api.securerpc.com/v1
NET_ID=56
HTTP_RPC_URL=https://bsc-dataseed1.ninicoin.io/
REDIS_URL=redis://127.0.0.1:6379
# DNS settings
@@ -12,13 +9,10 @@ APP_PORT=8000
# without 0x prefix
PRIVATE_KEY=
# 0.05 means 0.05%
RELAYER_FEE=0.4
MINING_SERVICE_FEE=0.05
# 0.1 means 0.1%
RELAYER_FEE=0.1
REWARD_ACCOUNT=
CONFIRMATIONS=4
# in GWEI
MAX_GAS_PRICE=1000
BASE_FEE_RESERVE_PERCENTAGE=25
AGGREGATOR=0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49

View File

@@ -3,7 +3,7 @@ name: build
on:
push:
branches: ['*']
tags: ['v[0-9]+.[0-9]+.[0-9]+']
tags: ['v[0-9]+.[0-9]+.[0-9]+*']
pull_request:
jobs:
@@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 14
- run: yarn install
- run: yarn test
- run: yarn lint
@@ -22,7 +22,7 @@ jobs:
uses: appleboy/telegram-action@master
if: failure()
with:
message: ❗ Build failed for [${{ github.repository }}](https://github.com/${{ github.repository }}/actions) because of ${{ github.actor }}
message: ❗ Build failed for [${{ github.repository }}](https://github.com/${{ github.repository }}/actions) for sidechains because of ${{ github.actor }}
format: markdown
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
@@ -43,7 +43,7 @@ jobs:
- name: Check package.json version vs tag
run: |
[ ${{ steps.vars.outputs.version }} = $(grep '"version":' package.json | grep -o "[0-9.]*") ] || (echo "Git tag doesn't match version in package.json" && false)
[ ${{ steps.vars.outputs.version }} = $(sed -nE 's/^\s*"version": "([0-9]+.[0-9]+.[0-9]+(-beta.[0-9]+)?)",$/\1/p' package.json) ] || (echo "Git tag doesn't match version in package.json" && false)
- name: Build and push Docker image
uses: docker/build-push-action@v1.1.0
@@ -51,7 +51,7 @@ jobs:
dockerfile: Dockerfile
repository: tornadocash/relayer
tag_with_ref: true
tags: mining,candidate
tags: light
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
@@ -60,7 +60,7 @@ jobs:
with:
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
message: 🚀 Published a [${{ steps.vars.outputs.repo_name }}](https://github.com/${{ github.repository }}) version ${{ steps.vars.outputs.version }} to docker hub
message: 🚀 Published a [${{ steps.vars.outputs.repo_name }}](https://github.com/${{ github.repository }}) for sidechains version ${{ steps.vars.outputs.version }} to docker hub
debug: true
format: markdown
@@ -70,11 +70,10 @@ jobs:
to: ${{ secrets.TELEGRAM_RELAYER_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
message: |
🚀 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.
🚀 Published a new version of the sidechains relayer node service to docker hub: `tornadocash/relayer:v${{ steps.vars.outputs.version }}` and `tornadocash/relayer:light`.
Please update your sidechains nodes ❗️
DO NOT TOUCH MAINNET AND NOVA RELAYERS.
debug: true
format: markdown
@@ -84,16 +83,16 @@ jobs:
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`.
🚀 Published a new version of the sidechains relayer node service to docker hub: `tornadocash/relayer:v${{ steps.vars.outputs.version }}` and `tornadocash/relayer:light`.
Please update your mainnet nodes ❗️
DO NOT TOUCH SIDECHAINS AND NOVA RELAYERS.
Please update your sidechains nodes ❗️
DO NOT TOUCH MAINNET AND NOVA RELAYERS.
- name: Telegram Failure Notification
uses: appleboy/telegram-action@master
if: failure()
with:
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 }}
message: ❗ Failed to publish [${{ steps.vars.outputs.repo_name }}](https://github.com/${{ github.repository }}/actions) for sidechains because of ${{ github.actor }}
format: markdown
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}

5
.gitignore vendored
View File

@@ -1,8 +1,9 @@
.vscode
node_modules/
.env
.env.mainnet
.env.kovan
.env.*
!.env.*.example
!.env.example
kovan.*
dump.rdb
.idea

137
README.md
View File

@@ -1,25 +1,35 @@
# Relayer for Tornado Cash [![Build Status](https://github.com/tornadocash/relayer/workflows/build/badge.svg)](https://github.com/tornadocash/relayer/actions) ![Static Badge](https://img.shields.io/badge/version-5.1.0-blue?logo=docker)
__*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.__
***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.**
__*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).__
***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).**
## Deploy with docker-compose (recommended)
*The following instructions are for Ubuntu 22.10, other operating systems may vary. These instructions include automated SSL configuration with LetsEncrypt.*
## Deploy with docker-compose
__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`
__FIREWALL CONFIGURATION__
@@ -31,70 +41,101 @@ _* Warning: Failure to configure SSH as the first UFW rule, will lock you out of
3. Allow HTTP, and HTTPS by running `ufw allow https/tcp/http`
4. Finalize changes and enable firewall `ufw enable`
__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
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:
- `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
__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
__DEPLOYMENT__
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
1. Clone the repository and enter the directory
- `git clone https://git.tornado.ws/tornadocash/classic-relayer -b sidechain-v5 && cd classic-relayer`
2. Check environment files:
By default each network is preconfigured the naming of `.env.<NETWORK>`
- `.env.bsc` for Binance Smart Chain
- `.env.arb` for Arbitrum
- `.env.op` for Optimism
- `.env.gnosis` for Gnosis (xdai)
- `.env.polygon` for Polygon (matic)
- `.env.avax` for Avalanche C-Chain
3. Configure (fill) environment files for those networks on which the relayer will be deployed:
- Set `PRIVATE_KEY` to your relayer address (remove the 0x from your private key) to each environment file (*It is recommended not to reuse the same private keys for each network as a security measure*)
- Set `VIRTUAL_HOST` and `LETSENCRYPT_HOST` a unique subndomain for every network to each environment file, eg: `mainnet.example.com` for Ethereum, `binance.example.com` for Binance etc
- Add a A wildcard record DNS record with the value assigned to your instance IP address to configure subdomains
- Set `RELAYER_FEE` to what you would like to charge as your fee
- Set `RPC_URL` to a non-censoring RPC (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
4. Build docker image for sidechain with simple `npm run build` command
5. Uncomment the `env_file` lines (remove `# `) for the associated network services in `docker-compose.yml` for chosen chains (networks)
6. Run docker-compose for the configured networks specified via `--profile <NETWORK_SYMBOL>`, for example (if you run relayer only Binance Smart Chain and Arbitrum):
- `docker-compose --profile bsc --profile arb up -d`
7. Visit your domain addresses and check each `/status` endpoint to ensure there is no errors in the `status` fields
## Run locally for one chain
1. `yarn`
2. `cp .env.example .env`
3. Modify `.env` as needed (described above)
4. `yarn start`
5. Go to `http://127.0.0.1:8000`
6. 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
_Note._ If you want to change contracts' addresses go to [config.js](./config.js) file.
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
## Input data example
## Run own RPC nodes
```json
{
"proof": "0x0f8cb4c2ca9cbb23a5f21475773e19e39d3470436d7296f25c8730d19d88fcef2986ec694ad094f4c5fff79a4e5043bd553df20b23108bc023ec3670718143c20cc49c6d9798e1ae831fd32a878b96ff8897728f9b7963f0d5a4b5574426ac6203b2456d360b8e825d8f5731970bf1fc1b95b9713e3b24203667ecdd5939c2e40dec48f9e51d9cc8dc2f7f3916f0e9e31519c7df2bea8c51a195eb0f57beea4924cb846deaa78cdcbe361a6c310638af6f6157317bc27d74746bfaa2e1f8d2e9088fd10fa62100740874cdffdd6feb15c95c5a303f6bc226d5e51619c5b825471a17ddfeb05b250c0802261f7d05cf29a39a72c13e200e5bc721b0e4c50d55e6",
"args": [
"0x1579d41e5290ab5bcec9a7df16705e49b5c0b869095299196c19c5e14462c9e3",
"0x0cf7f49c5b35c48b9e1d43713e0b46a75977e3d10521e9ac1e4c3cd5e3da1c5d",
"0x03ebd0748aa4d1457cf479cce56309641e0a98f5",
"0xbd4369dc854c5d5b79fe25492e3a3cfcb5d02da5",
"0x000000000000000000000000000000000000000000000000058d15e176280000",
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
"contract": "0xA27E34Ad97F171846bAf21399c370c9CE6129e0D"
}
```
It is strongly recommended that you use your own RPC nodes. Instruction on how to run full nodes can be found [here](https://github.com/feshchenkod/rpc-nodes).
## Architecture
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
Disclaimer:

View File

@@ -1,319 +0,0 @@
[
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "domains",
"type": "bytes32[]"
}
],
"name": "bulkResolve",
"outputs": [
{
"internalType": "address[]",
"name": "result",
"type": "address[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract Governance",
"name": "governance",
"type": "address"
}
],
"name": "getAllProposals",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "proposer",
"type": "address"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "uint256",
"name": "startTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "endTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "forVotes",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "againstVotes",
"type": "uint256"
},
{
"internalType": "bool",
"name": "executed",
"type": "bool"
},
{
"internalType": "bool",
"name": "extended",
"type": "bool"
},
{
"internalType": "enum Governance.ProposalState",
"name": "state",
"type": "uint8"
}
],
"internalType": "struct GovernanceAggregator.Proposal[]",
"name": "proposals",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract Governance",
"name": "governance",
"type": "address"
},
{
"internalType": "address[]",
"name": "accs",
"type": "address[]"
}
],
"name": "getGovernanceBalances",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "fromTokens",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "oneUnitAmounts",
"type": "uint256[]"
}
],
"name": "getPricesInETH",
"outputs": [
{
"internalType": "uint256[]",
"name": "prices",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract Governance",
"name": "governance",
"type": "address"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "getUserData",
"outputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "latestProposalId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "latestProposalIdState",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "timelock",
"type": "uint256"
},
{
"internalType": "address",
"name": "delegatee",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract Miner",
"name": "miner",
"type": "address"
},
{
"internalType": "address[]",
"name": "instances",
"type": "address[]"
}
],
"name": "minerRates",
"outputs": [
{
"internalType": "uint256[]",
"name": "_rates",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "resolve",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract RewardSwap",
"name": "swap",
"type": "address"
}
],
"name": "swapState",
"outputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "poolWeight",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract Miner",
"name": "miner",
"type": "address"
},
{
"internalType": "address[]",
"name": "instances",
"type": "address[]"
},
{
"internalType": "contract RewardSwap",
"name": "swap",
"type": "address"
}
],
"name": "miningData",
"outputs": [
{
"internalType": "uint256[]",
"name": "_rates",
"type": "uint256[]"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "poolWeight",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "fromTokens",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "oneUnitAmounts",
"type": "uint256[]"
},
{
"internalType": "contract RewardSwap",
"name": "swap",
"type": "address"
}
],
"name": "marketData",
"outputs": [
{
"internalType": "uint256[]",
"name": "prices",
"type": "uint256[]"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,151 @@
[
{
"inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "DecimalsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "GasPriceUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "L1BaseFeeUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "OverheadUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" },
{ "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" }
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "ScalarUpdated",
"type": "event"
},
{
"inputs": [],
"name": "decimals",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPrice",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "bytes", "name": "_data", "type": "bytes" }],
"name": "getL1Fee",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "bytes", "name": "_data", "type": "bytes" }],
"name": "getL1GasUsed",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "l1BaseFee",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "overhead",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "scalar",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_decimals", "type": "uint256" }],
"name": "setDecimals",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_gasPrice", "type": "uint256" }],
"name": "setGasPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_baseFee", "type": "uint256" }],
"name": "setL1BaseFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_overhead", "type": "uint256" }],
"name": "setOverhead",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_scalar", "type": "uint256" }],
"name": "setScalar",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

105
abis/proxyLightABI.json Normal file
View File

@@ -0,0 +1,105 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes",
"name": "encryptedNote",
"type": "bytes"
}
],
"name": "EncryptedNote",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes[]",
"name": "_encryptedNotes",
"type": "bytes[]"
}
],
"name": "backupNotes",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ITornadoInstance",
"name": "_tornado",
"type": "address"
},
{
"internalType": "bytes32",
"name": "_commitment",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_encryptedNote",
"type": "bytes"
}
],
"name": "deposit",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ITornadoInstance",
"name": "_tornado",
"type": "address"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "_root",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_nullifierHash",
"type": "bytes32"
},
{
"internalType": "address payable",
"name": "_recipient",
"type": "address"
},
{
"internalType": "address payable",
"name": "_relayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "_fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_refund",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]

View File

@@ -1,252 +0,0 @@
[
{
"inputs": [
{
"internalType": "bytes32",
"name": "_torn",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_miner",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_miningCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_initialLiquidity",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "newWeight",
"type": "uint256"
}
],
"name": "PoolWeightUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "pTORN",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "TORN",
"type": "uint256"
}
],
"name": "Swap",
"type": "event"
},
{
"inputs": [],
"name": "DURATION",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "initialLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "liquidity",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "miner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolWeight",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "resolve",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokensSold",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "torn",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "swap",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "getExpectedReturn",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tornVirtualBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newWeight",
"type": "uint256"
}
],
"name": "setPoolWeight",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -1,498 +0,0 @@
[
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "_newOperator",
"type": "address"
}
],
"name": "changeOperator",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "nullifierHashes",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "_root",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_nullifierHash",
"type": "bytes32"
},
{
"internalType": "address payable",
"name": "_recipient",
"type": "address"
},
{
"internalType": "address payable",
"name": "_relayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "_fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_refund",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "verifier",
"outputs": [
{
"internalType": "contract IVerifier",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "bytes32",
"name": "_left",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_right",
"type": "bytes32"
}
],
"name": "hashLeftRight",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "FIELD_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "levels",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "operator",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "bytes32",
"name": "_root",
"type": "bytes32"
}
],
"name": "isKnownRoot",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "commitments",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "denomination",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "currentRootIndex",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "_newVerifier",
"type": "address"
}
],
"name": "updateVerifier",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "bytes32",
"name": "_commitment",
"type": "bytes32"
}
],
"name": "deposit",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getLastRoot",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "roots",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "ROOT_HISTORY_SIZE",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "bytes32",
"name": "_nullifierHash",
"type": "bytes32"
}
],
"name": "isSpent",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "zeros",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "ZERO_VALUE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "filledSubtrees",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "nextIndex",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IVerifier",
"name": "_verifier",
"type": "address"
},
{
"internalType": "uint256",
"name": "_denomination",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "_merkleTreeHeight",
"type": "uint32"
},
{
"internalType": "address",
"name": "_operator",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "commitment",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint32",
"name": "leafIndex",
"type": "uint32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "timestamp",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "nullifierHash",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "Withdrawal",
"type": "event"
}
]

View File

@@ -1,171 +0,0 @@
[
{
"inputs": [
{
"internalType": "bytes32",
"name": "_tornadoTrees",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_governance",
"type": "bytes32"
},
{
"internalType": "contract ITornado[]",
"name": "_instances",
"type": "address[]"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "governance",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ITornado",
"name": "",
"type": "address"
}
],
"name": "instances",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "resolve",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tornadoTrees",
"outputs": [
{
"internalType": "contract ITornadoTrees",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ITornado",
"name": "tornado",
"type": "address"
},
{
"internalType": "bytes32",
"name": "commitment",
"type": "bytes32"
}
],
"name": "deposit",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ITornado",
"name": "instance",
"type": "address"
},
{
"internalType": "bool",
"name": "update",
"type": "bool"
}
],
"name": "updateInstances",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ITornado",
"name": "tornado",
"type": "address"
},
{
"internalType": "bytes",
"name": "proof",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "nullifierHash",
"type": "bytes32"
},
{
"internalType": "address payable",
"name": "recipient",
"type": "address"
},
{
"internalType": "address payable",
"name": "relayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "refund",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]

2
app.js
View File

@@ -1 +1 @@
module.exports = require('./src/server')
module.exports = require('./src/index')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -45,130 +45,10 @@ services:
- nginx
- dockergen
# ---------------------- ETH Mainnet ----------------------- #
eth-server:
build: .
image: tornadorelayer:mainnet
profiles: ['eth']
restart: always
command: server
env_file: .env.eth
environment:
NET_ID: 1
REDIS_URL: redis://redis/0
nginx_proxy_read_timeout: 600
depends_on: [redis]
eth-treeWatcher:
image: tornadorelayer:mainnet
profiles: ['eth']
restart: always
command: treeWatcher
env_file: .env.eth
environment:
NET_ID: 1
REDIS_URL: redis://redis/0
depends_on: [redis, eth-server]
eth-priceWatcher:
image: tornadorelayer:mainnet
profiles: ['eth']
restart: always
command: priceWatcher
env_file: .env.eth
environment:
NET_ID: 1
REDIS_URL: redis://redis/0
depends_on: [redis, eth-server]
eth-healthWatcher:
image: tornadorelayer:mainnet
profiles: ['eth']
restart: always
command: healthWatcher
env_file: .env.eth
environment:
NET_ID: 1
REDIS_URL: redis://redis/0
depends_on: [redis, eth-server]
eth-worker1:
image: tornadorelayer:mainnet
profiles: ['eth']
restart: always
command: worker
env_file: .env.eth
environment:
NET_ID: 1
REDIS_URL: redis://redis/0
depends_on: [redis, eth-server]
# # 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: .env2.eth
# environment:
# REDIS_URL: redis://redis/0
# # this container will proxy *.onion domain to the server container
# # if you want to run *only* as .onion service, you don't need `nginx`, `letsencrypt`, `dockergen` containers
# tor:
# image: strm/tor
# restart: always
# depends_on: [server]
# environment:
# LISTEN_PORT: 80
# REDIRECT: server:8000
# # Generate a new key with
# # docker run --rm --entrypoint shallot strm/tor-hiddenservice-nginx ^foo
# PRIVATE_KEY: |
# -----BEGIN RSA PRIVATE KEY-----
# ...
# -----END RSA PRIVATE KEY-----
# # auto update docker containers when new image is pushed to docker hub (be careful with that)
# watchtower:
# image: v2tec/watchtower
# restart: always
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock
# # this container will send Telegram notifications when other containers are stopped/restarted
# # it's best to run this container on some other instance, otherwise it can't notify if the whole instance goes down
# notifier:
# image: poma/docker-telegram-notifier
# restart: always
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
# environment:
# # How to create bot: https://core.telegram.org/bots#3-how-do-i-create-a-bot
# # How to get chat id: https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id/32572159#32572159
# TELEGRAM_NOTIFIER_BOT_TOKEN: ...
# TELEGRAM_NOTIFIER_CHAT_ID: ...
# # this container will send Telegram notifications if specified address doesn't have enough funds
# monitor_mainnet:
# image: peppersec/monitor_eth
# restart: always
# environment:
# TELEGRAM_NOTIFIER_BOT_TOKEN: ...
# TELEGRAM_NOTIFIER_CHAT_ID: ...
# ADDRESS: '0x0000000000000000000000000000000000000000'
# THRESHOLD: 0.5 # ETH
# RPC_URL: https://mainnet.infura.io
# BLOCK_EXPLORER: etherscan.io
# -------------------------------------------------- #
# ---------------------- BSC (Binance Smart Chain) ----------------------- #
bsc-server:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['bsc']
restart: always
command: server
@@ -180,7 +60,7 @@ services:
depends_on: [redis]
bsc-healthWatcher:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['bsc']
restart: always
command: healthWatcher
@@ -191,7 +71,7 @@ services:
depends_on: [redis, bsc-server]
bsc-worker1:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['bsc']
restart: always
command: worker
@@ -206,7 +86,7 @@ services:
# ---------------------- Polygon (MATIC) --------------------- #
polygon-server:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['polygon']
restart: always
command: server
@@ -218,7 +98,7 @@ services:
depends_on: [redis]
polygon-healthWatcher:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['polygon']
restart: always
command: healthWatcher
@@ -229,7 +109,7 @@ services:
depends_on: [redis, polygon-server]
polygon-worker1:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['polygon']
restart: always
command: worker
@@ -244,7 +124,7 @@ services:
# ---------------------- Gnosis (XDAI) ---------------------- #
gnosis-server:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['gnosis']
restart: always
command: server
@@ -256,7 +136,7 @@ services:
depends_on: [redis]
gnosis-healthWatcher:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['gnosis']
restart: always
command: healthWatcher
@@ -267,7 +147,7 @@ services:
depends_on: [redis, gnosis-server]
gnosis-worker1:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['gnosis']
restart: always
command: worker
@@ -282,7 +162,7 @@ services:
# ---------------------- AVAX ---------------------- #
avax-server:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['avax']
restart: always
command: server
@@ -294,7 +174,7 @@ services:
depends_on: [redis]
avax-healthWatcher:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['avax']
restart: always
command: healthWatcher
@@ -305,7 +185,7 @@ services:
depends_on: [redis, avax-server]
avax-worker1:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['avax']
restart: always
command: worker
@@ -320,7 +200,7 @@ services:
# ---------------------- OP ------------------------ #
op-server:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['op']
restart: always
command: server
@@ -332,7 +212,7 @@ services:
depends_on: [redis]
op-healthWatcher:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['op']
restart: always
command: healthWatcher
@@ -343,7 +223,7 @@ services:
depends_on: [redis, op-server]
op-worker1:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['op']
restart: always
command: worker
@@ -358,7 +238,7 @@ services:
# ---------------------- Arbitrum ----------------------- #
arb-server:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['arb']
restart: always
command: server
@@ -370,7 +250,7 @@ services:
depends_on: [redis]
arb-healthWatcher:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['arb']
restart: always
command: healthWatcher
@@ -381,7 +261,7 @@ services:
depends_on: [redis, arb-server]
arb-worker1:
image: tornadorelayer:sidechain
image: tornadocash/relayer:sidechain-v5
profiles: ['arb']
restart: always
command: worker
@@ -393,65 +273,6 @@ services:
# -------------------------------------------------- #
# ---------------------- 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:
vhost:

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1 +0,0 @@
zabbix

View File

@@ -1 +0,0 @@
zabbix

View File

@@ -1,37 +0,0 @@
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=

View File

@@ -1,9 +0,0 @@
# 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

View File

@@ -1,60 +0,0 @@
# 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=

View File

@@ -1,26 +0,0 @@
# 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.

View File

@@ -1,67 +0,0 @@
# 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)
```

View File

@@ -1,186 +0,0 @@
# 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:

View File

@@ -1,393 +0,0 @@
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

View File

@@ -1,70 +0,0 @@
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

Binary file not shown.

View File

@@ -1,42 +1,33 @@
{
"name": "relay",
"version": "5.2.0",
"description": "Relayer for Tornado.cash privacy solution.",
"version": "5.2.2",
"description": "Relayer for Tornado.cash privacy solution. https://tornado.cash",
"scripts": {
"server": "node src/server.js",
"worker": "node src/worker",
"treeWatcher": "node src/treeWatcher",
"priceWatcher": "node src/priceWatcher",
"healthWatcher": "node src/healthWatcher",
"eslint": "eslint --ext .js --ignore-path .gitignore .",
"prettier:check": "npx prettier --check . --config .prettierrc",
"prettier:fix": "npx prettier --write . --config .prettierrc",
"lint": "yarn eslint && yarn prettier:check",
"test": "mocha",
"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\""
"build": "docker build -t tornadocash/relayer:sidechain-v5 .",
"start": "docker-compose up -d redis && concurrently \"yarn server\" \"yarn worker\" \"yarn healthWatcher\""
},
"author": "Tornado Cash team",
"author": "tornado.cash",
"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-config": "^1.0.8",
"@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",
"concurrently": "^8.2.0",
"dotenv": "^8.2.0",
"eth-ens-namehash": "^2.0.8",
"express": "^4.17.1",
"ioredis": "^4.14.1",
"node-fetch": "^2.6.7",
"uuid": "^8.3.0",
"web3": "^1.3.0",
"web3-core-promievent": "^1.3.0",
"web3-utils": "^1.2.2"
},
"devDependencies": {

View File

@@ -1,27 +1,27 @@
require('dotenv').config()
const { jobType } = require('./constants')
const tornConfig = require('@tornado/tornado-config')
const { networkConfig } = require('./constants')
const netId = Number(process.env.NET_ID) || 56
const instances = tornConfig.instances[netId]
const proxyLight = tornConfig.tornadoProxyLight.address
const { gasPrices, nativeCurrency } = networkConfig[`netId${netId}`]
module.exports = {
netId: Number(process.env.NET_ID) || 1,
netId,
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://api.securerpc.com/v1',
aggregatorAddress: process.env.AGGREGATOR,
oracleRpcUrl: process.env.ORACLE_RPC_URL || 'https://rpc.payload.de',
minerMerkleTreeHeight: 20,
privateKey: process.env.PRIVATE_KEY,
instances: tornConfig.instances,
torn: tornConfig,
instances,
port: process.env.APP_PORT || 8000,
tornadoServiceFee: Number(process.env.RELAYER_FEE),
rewardAccount: process.env.REWARD_ACCOUNT,
governanceAddress: '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
tornadoGoerliProxy: '0x454d870a72e29d5E5697f635128D18077BD04C60',
gasLimits: {
[jobType.MINING_REWARD]: 455000,
[jobType.MINING_WITHDRAW]: 400000,
},
minimumBalance: '500000000000000000',
baseFeeReserve: Number(process.env.BASE_FEE_RESERVE_PERCENTAGE),
gasPrices,
proxyLight,
nativeCurrency,
minimumBalance: netId === 137 || netId === 43114 ? '10000000000000000000' : '100000000000000000', // 10 or 0.1
}

View File

@@ -1,7 +1,7 @@
const jobType = Object.freeze({
TORNADO_WITHDRAW: 'TORNADO_WITHDRAW',
MINING_REWARD: 'MINING_REWARD',
MINING_WITHDRAW: 'MINING_WITHDRAW',
OP_TORNADO_WITHDRAW: 'OP_TORNADO_WITHDRAW',
ARB_TORNADO_WITHDRAW: 'ARB_TORNADO_WITHDRAW',
})
const status = Object.freeze({
@@ -14,7 +14,65 @@ const status = Object.freeze({
FAILED: 'FAILED',
})
const networkConfig = {
netId56: {
gasPrices: {
instant: 5,
fast: 5,
standard: 5,
low: 5,
},
nativeCurrency: 'bnb',
},
netId10: {
gasPrices: {
instant: 0.001,
fast: 0.001,
standard: 0.001,
low: 0.001,
},
nativeCurrency: 'eth',
},
netId100: {
gasPrices: {
instant: 6,
fast: 5,
standard: 4,
low: 1,
},
nativeCurrency: 'xdai',
},
netId137: {
gasPrices: {
instant: 100,
fast: 75,
standard: 50,
low: 30,
},
nativeCurrency: 'matic',
},
netId42161: {
gasPrices: {
instant: 4,
fast: 3,
standard: 2.52,
low: 2.29,
},
nativeCurrency: 'eth',
},
netId43114: {
gasPrices: {
instant: 225,
fast: 35,
standard: 25,
low: 25,
},
nativeCurrency: 'avax',
},
}
module.exports = {
jobType,
status,
networkConfig,
}

View File

@@ -1,55 +0,0 @@
const {
getTornadoWithdrawInputError,
getMiningRewardInputError,
getMiningWithdrawInputError,
} = require('../modules/validator')
const { postJob } = require('../queue')
const { jobType } = require('../constants')
async function tornadoWithdraw(req, res) {
const inputError = getTornadoWithdrawInputError(req.body)
if (inputError) {
console.log('Invalid input:', inputError)
return res.status(400).json({ error: inputError })
}
const id = await postJob({
type: jobType.TORNADO_WITHDRAW,
request: req.body,
})
return res.json({ id })
}
async function miningReward(req, res) {
const inputError = getMiningRewardInputError(req.body)
if (inputError) {
console.log('Invalid input:', inputError)
return res.status(400).json({ error: inputError })
}
const id = await postJob({
type: jobType.MINING_REWARD,
request: req.body,
})
return res.json({ id })
}
async function miningWithdraw(req, res) {
const inputError = getMiningWithdrawInputError(req.body)
if (inputError) {
console.log('Invalid input:', inputError)
return res.status(400).json({ error: inputError })
}
const id = await postJob({
type: jobType.MINING_WITHDRAW,
request: req.body,
})
return res.json({ id })
}
module.exports = {
tornadoWithdraw,
miningReward,
miningWithdraw,
}

View File

@@ -1,4 +0,0 @@
module.exports = {
controller: require('./controller'),
status: require('./status'),
}

21
src/controller.js Normal file
View File

@@ -0,0 +1,21 @@
const { getTornadoWithdrawInputError } = require('./modules/validator')
const { postJob } = require('./queue')
const { jobType } = require('./constants')
async function tornadoWithdraw(req, res) {
const inputError = getTornadoWithdrawInputError(req.body)
if (inputError) {
console.log('Invalid input:', inputError)
return res.status(400).json({ error: inputError })
}
const id = await postJob({
type: jobType.TORNADO_WITHDRAW,
request: req.body,
})
return res.json({ id })
}
module.exports = {
tornadoWithdraw,
}

View File

@@ -1,19 +1,25 @@
const { setSafeInterval, toBN, fromWei, RelayerError } = require('./utils')
const { privateKey, minimumBalance } = require('./config')
const Web3 = require('web3')
const { toBN, fromWei } = require('web3-utils')
const { setSafeInterval, logRelayerError } = require('./utils')
const { httpRpcUrl, privateKey, minimumBalance, nativeCurrency, instances } = require('./config')
const web3 = new Web3(httpRpcUrl)
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 RelayerError(`Not enough balance, less than ${fromWei(minimumBalance)} ETH`, 1)
const currency = instances[nativeCurrency].symbol
throw new Error(`Not enough balance, less than ${fromWei(minimumBalance)} ${currency}`)
}
await redis.hset('health', { status: true, error: '' })
} catch (e) {
console.error('healthWatcher', e.message)
await logRelayerError(redis, e)
await redis.hset('health', { status: false, error: e.message })
}
}

View File

@@ -2,10 +2,8 @@ const { createClient } = require('ioredis')
const { redisUrl } = require('../config')
const redis = createClient(redisUrl)
const redisSubscribe = createClient(redisUrl)
module.exports = {
redis,
redisSubscribe,
redisUrl,
}

View File

@@ -1,29 +0,0 @@
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 {
constructor() {
this.addresses = {}
}
async resolve(domains) {
if (!Array.isArray(domains)) {
domains = [domains]
}
const unresolved = domains.filter(d => !this.addresses[d])
if (unresolved.length) {
const resolved = await aggregator.methods.bulkResolve(unresolved.map(ens.hash)).call()
for (let i = 0; i < resolved.length; i++) {
this.addresses[domains[i]] = resolved[i]
}
}
const addresses = domains.map(domain => this.addresses[domain])
return addresses.length === 1 ? addresses[0] : addresses
}
}
module.exports = new ENSResolver()

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,23 +0,0 @@
const { setSafeInterval, RelayerError, logRelayerError } = require('./utils')
const { redis } = require('./modules/redis')
const { TokenPriceOracle } = require('@tornado/tornado-oracles')
const { oracleRpcUrl } = require('./config')
const priceOracle = new TokenPriceOracle(oracleRpcUrl)
async function main() {
try {
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)
}
}
setSafeInterval(main, 30 * 1000)

View File

@@ -1,4 +1,5 @@
const { controller, status } = require('./contollers')
const controller = require('./controller')
const status = require('./status')
const router = require('express').Router()
// Add CORS headers
@@ -24,7 +25,5 @@ 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

View File

@@ -1,14 +1,16 @@
const express = require('express')
const router = require('./router')
const { port, rewardAccount } = require('./config')
const { version } = require('../package.json')
const { isAddress } = require('./utils')
const router = require('./router')
const { isAddress } = require('web3-utils')
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}`)

View File

@@ -1,22 +1,19 @@
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')
const queue = require('./queue')
const { netId, tornadoServiceFee, instances, rewardAccount } = require('./config')
const { version } = require('../package.json')
const { readRelayerErrors } = require('./utils')
const { redis } = require('./modules/redis')
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],
instances,
netId,
ethPrices,
tornadoServiceFee,
miningServiceFee,
version,
health,
currentQueue,
@@ -25,7 +22,7 @@ async function status(req, res) {
function index(req, res) {
res.send(
'This is <a href=https://tornado.ws>Tornado Cash</a> Relayer service. Check the <a href=/v1/status>/status</a> for settings',
'This is <a href=https://tornado.cash>tornado.cash</a> Relayer service. Check the <a href=/v1/status>/status</a> for settings',
)
}

View File

@@ -1,136 +0,0 @@
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
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) {
if (err) {
throw new Error(`Event handler error: ${err}`)
// console.error(err)
// return
}
console.log(
`New account event
Index: ${event.returnValues.index}
Commitment: ${event.returnValues.commitment}
Nullifier: ${event.returnValues.nullifier}
EncAcc: ${event.returnValues.encryptedAccount}`,
)
const { commitment, index } = event.returnValues
if (tree.elements().length === Number(index)) {
tree.insert(toBN(commitment))
await updateRedis()
} else if (tree.elements().length === Number(index) + 1) {
console.log('Replacing element', index)
tree.update(index, toBN(commitment))
await updateRedis()
} else {
console.log(`Invalid element index ${index}, rebuilding tree`)
rebuild()
}
}
async function processNewBlock(err) {
if (err) {
throw new Error(`Event handler error: ${err}`)
// console.error(err)
// return
}
// what if updateRedis takes more than 15 sec?
await updateRedis()
}
async function updateRedis() {
const rootOnContract = await contract.methods.getLastAccountRoot().call()
if (!tree.root().eq(toBN(rootOnContract))) {
console.log(`Invalid tree root: ${tree.root()} != ${toBN(rootOnContract)}, rebuilding tree`)
rebuild()
return
}
const rootInRedis = await redis.get('tree:root')
if (!rootInRedis || !tree.root().eq(toBN(rootInRedis))) {
const serializedTree = JSON.stringify(tree.serialize())
await redis.set('tree:elements', serializedTree)
await redis.set('tree:root', tree.root().toString())
await redis.publish('treeUpdate', tree.root().toString())
console.log('Updated tree in redis, new root:', tree.root().toString())
} else {
console.log('Tree in redis is up to date, skipping update')
}
}
function rebuild() {
process.exit(1)
// await eventSubscription.unsubscribe()
// await blockSubscription.unsubscribe()
// setTimeout(init, 3000)
}
async function init() {
try {
console.log('Initializing')
const miner = await resolver.resolve(torn.miningV2.address)
contract = new web3.eth.Contract(MinerABI, miner)
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 ${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)
}
}
init()
process.on('unhandledRejection', error => {
console.error('Unhandled promise rejection', error)
process.exit(1)
})

View File

@@ -1,13 +1,10 @@
const { instances, netId } = require('./config')
const { poseidon } = require('@tornado/circomlib')
const { toBN, toChecksumAddress, BN, fromWei, isAddress, toWei, toHex } = require('web3-utils')
const { instances } = require('./config')
const { toChecksumAddress, BN } = require('web3-utils')
const addressMap = new Map()
const instance = instances[netId]
for (const [currency, { instanceAddress, symbol, decimals }] of Object.entries(instance)) {
for (const [currency, { instanceAddress, symbol, decimals }] of Object.entries(instances)) {
Object.entries(instanceAddress).forEach(([amount, address]) =>
addressMap.set(`${netId}_${address}`, {
addressMap.set(address, {
currency,
amount,
symbol,
@@ -16,10 +13,9 @@ for (const [currency, { instanceAddress, symbol, decimals }] of Object.entries(i
)
}
const sleep = ms => new Promise(res => setTimeout(res, ms))
function getInstance(address) {
const key = `${netId}_${toChecksumAddress(address)}`
address = toChecksumAddress(address)
const key = toChecksumAddress(address)
if (addressMap.has(key)) {
return addressMap.get(key)
} else {
@@ -27,9 +23,6 @@ function getInstance(address) {
}
}
const poseidonHash = items => toBN(poseidon(items).toString())
const poseidonHash2 = (a, b) => poseidonHash([a, b])
function setSafeInterval(func, interval) {
func()
.catch(console.error)
@@ -38,21 +31,6 @@ function setSafeInterval(func, interval) {
})
}
/**
* A promise that resolves when the source emits specified event
*/
function when(source, event) {
return new Promise((resolve, reject) => {
source
.once(event, payload => {
resolve(payload)
})
.on('error', error => {
reject(error)
})
})
}
function fromDecimals(value, decimals) {
value = value.toString()
let ether = value.toString()
@@ -102,15 +80,8 @@ 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)
await redis.zadd('errors', 'INCR', 1, e.message)
}
const readRelayerErrors = async redis => {
@@ -123,21 +94,14 @@ const readRelayerErrors = async redis => {
return errors
}
const clearRelayerErrors = redis => {
redis.del('errors')
}
module.exports = {
getInstance,
setSafeInterval,
poseidonHash2,
sleep,
when,
fromDecimals,
toBN,
toChecksumAddress,
fromWei,
toWei,
toHex,
BN,
isAddress,
RelayerError,
logRelayerError,
readRelayerErrors,
clearRelayerErrors,
}

View File

@@ -1,211 +1,93 @@
const fs = require('fs')
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 Web3 = require('web3')
const { toBN, fromWei, isAddress, toChecksumAddress } = require('web3-utils')
const { redis } = require('./modules/redis')
const proxyLightABI = require('../abis/proxyLightABI.json')
const { queue } = require('./queue')
const {
poseidonHash2,
getInstance,
sleep,
toBN,
fromWei,
toChecksumAddress,
isAddress,
RelayerError,
logRelayerError,
} = require('./utils')
const { getInstance, logRelayerError, clearRelayerErrors } = require('./utils')
const { jobType, status } = require('./constants')
const {
torn,
netId,
gasLimits,
privateKey,
httpRpcUrl,
oracleRpcUrl,
baseFeeReserve,
miningServiceFee,
tornadoServiceFee,
tornadoGoerliProxy,
} = require('./config')
const resolver = require('./modules/resolver')
const { netId, gasPrices, privateKey, proxyLight, httpRpcUrl, tornadoServiceFee } = require('./config')
const { TxManager } = require('@tornado/tx-manager')
const { redis, redisSubscribe } = require('./modules/redis')
const getWeb3 = require('./modules/web3')
const { TornadoFeeOracleV5 } = require('@tornado/tornado-oracles')
let web3
let currentTx
let currentJob
let tree
let txManager
let controller
let swap
let minerContract
const feeOracle = new TornadoFeeOracleV5(netId, oracleRpcUrl)
let tornadoProxyInstance
const feeOracle = new TornadoFeeOracleV5(netId, httpRpcUrl, gasPrices)
async function fetchTree() {
const elements = await redis.get('tree:elements')
const convert = (_, val) => (typeof val === 'string' ? toBN(val) : val)
tree = MerkleTree.deserialize(JSON.parse(elements, convert), poseidonHash2)
if (currentTx && currentJob && ['MINING_REWARD', 'MINING_WITHDRAW'].includes(currentJob.data.type)) {
const { proof, args } = currentJob.data
if (toBN(args.account.inputRoot).eq(toBN(tree.root()))) {
console.log('Account root is up to date. Skipping Root Update operation...')
return
} else {
console.log('Account root is outdated. Starting Root Update operation...')
}
const update = await controller.treeUpdate(args.account.outputCommitment, tree)
const minerAddress = await resolver.resolve(torn.miningV2.address)
const instance = new web3.eth.Contract(miningABI, minerAddress)
const data =
currentJob.data.type === 'MINING_REWARD'
? instance.methods.reward(proof, args, update.proof, update.args).encodeABI()
: instance.methods.withdraw(proof, args, update.proof, update.args).encodeABI()
await currentTx.replace({
to: minerAddress,
data,
})
console.log('replaced pending tx')
}
}
async function start() {
function start() {
try {
await clearErrors()
web3 = getWeb3()
web3 = new Web3(httpRpcUrl)
tornadoProxyInstance = new web3.eth.Contract(proxyLightABI, proxyLight)
clearRelayerErrors(redis)
const { CONFIRMATIONS, MAX_GAS_PRICE } = process.env
const gasPriceOracleConfig = {
chainId: netId,
defaultRpc: httpRpcUrl,
defaultFallbackGasPrices: gasPrices,
minPriority: 0.05,
percentile: 5,
blocksCount: 20,
}
txManager = new TxManager({
privateKey,
rpcUrl: httpRpcUrl,
config: {
CONFIRMATIONS,
MAX_GAS_PRICE,
THROW_ON_REVERT: false,
BASE_FEE_RESERVE_PERCENTAGE: baseFeeReserve,
},
config: { CONFIRMATIONS, MAX_GAS_PRICE, THROW_ON_REVERT: false },
gasPriceOracleConfig,
})
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)
await fetchTree()
const provingKeys = {
treeUpdateCircuit: require('../keys/TreeUpdate.json'),
treeUpdateProvingKey: fs.readFileSync('./keys/TreeUpdate_proving_key.bin').buffer,
}
controller = new Controller({ provingKeys })
await controller.init()
queue.process(processJob)
console.log('Worker started')
} catch (e) {
await logRelayerError(redis, e)
logRelayerError(redis, e)
console.error('error on start worker', e.message)
}
}
function checkFee({ data }, gasInfo) {
if (data.type === jobType.TORNADO_WITHDRAW) {
return checkTornadoFee(data, gasInfo)
}
return checkMiningFee(data)
}
async function checkTornadoFee({ data }, tx) {
const userProvidedFee = toBN(data.args[4])
const { amount, decimals, currency } = getInstance(data.contract)
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 totalWithdrawalFee = await feeOracle.calculateWithdrawalFeeViaRelayer({
const relayerDesiredFee = await feeOracle.calculateWithdrawalFeeViaRelayer({
tx,
txType: 'relayer_withdrawal',
amount,
currency,
decimals,
refundInEth: refund.toString(),
predefinedGasLimit: gasLimit,
predefinedGasPrice: gasPrice,
tokenPriceInEth: ethPrice,
relayerFeePercent: tornadoServiceFee,
currency,
amount,
decimals,
gasLimit: tx.gasLimit,
gasPrice: tx.gasPrice,
})
console.log(
'user-provided fee, desired fee',
fromWei(userProvidedFee.toString()),
fromWei(toBN(totalWithdrawalFee).toString()),
fromWei(toBN(relayerDesiredFee).toString()),
)
if (userProvidedFee.lt(toBN(totalWithdrawalFee))) {
throw new RelayerError(
'Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.',
0,
)
if (userProvidedFee.lt(toBN(relayerDesiredFee))) {
throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.')
}
}
async function checkMiningFee({ args }) {
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)
async function getTxObject({ data }) {
const calldata = tornadoProxyInstance.methods.withdraw(data.contract, data.proof, ...data.args).encodeABI()
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()
const poolWeight = await swap.methods.poolWeight().call()
const expenseInPoints = Utils.reverseTornadoFormula({ balance, tokens: expenseInTorn, poolWeight })
/* eslint-disable */
const serviceFeePercent = isMiningReward
? toBN(0)
: toBN(args.amount)
.sub(providedFee) // args.amount includes fee
.mul(toBN(parseInt(miningServiceFee * 1e10)))
.div(toBN(1e10 * 100))
/* eslint-enable */
const desiredFee = expenseInPoints.add(serviceFeePercent) // in points
console.log(
'user provided fee, desired fee, serviceFeePercent',
providedFee.toString(),
desiredFee.toString(),
serviceFeePercent.toString(),
)
if (toBN(providedFee).lt(desiredFee)) {
throw new RelayerError('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.')
const incompleteTx = {
value: data.args[5],
to: tornadoProxyInstance._address,
data: calldata,
}
}
const { gasLimit, gasPrice } = await feeOracle.getGasParams({
tx: incompleteTx,
txType: 'relayer_withdrawal',
})
async function getProxyContract() {
let proxyAddress
if (netId === 5) {
proxyAddress = tornadoGoerliProxy
} else {
proxyAddress = await resolver.resolve(torn.tornadoRouter.address)
}
const contract = new web3.eth.Contract(tornadoProxyABI, proxyAddress)
return {
contract,
isOldProxy: checkOldProxy(proxyAddress),
}
}
function checkOldProxy(address) {
const OLD_PROXY = '0x905b63Fff465B9fFBF41DeA908CEb12478ec7601'
return toChecksumAddress(address) === toChecksumAddress(OLD_PROXY)
return { ...incompleteTx, gasLimit, gasPrice }
}
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')
@@ -213,58 +95,10 @@ async function checkRecipient({ data }) {
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()
let calldata = contract.methods.withdraw(data.contract, data.proof, ...data.args).encodeABI()
if (isOldProxy && getInstance(data.contract).currency !== 'eth') {
contract = new web3.eth.Contract(tornadoABI, data.contract)
calldata = contract.methods.withdraw(data.proof, ...data.args).encodeABI()
}
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,
}
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()
return {
to: minerContract._address,
data: calldata,
gasLimit: gasLimits[data.type],
}
}
}
async function isOutdatedTreeRevert(receipt, currentTx) {
try {
await web3.eth.call(currentTx.tx, receipt.blockNumber)
console.log('Simulated call successful')
return false
} catch (e) {
console.log('Decoded revert reason:', e.message)
return (
e.message.indexOf('Outdated account merkle root') !== -1 ||
e.message.indexOf('Outdated tree update merkle root') !== -1
)
}
}
async function processJob(job) {
try {
if (!jobType[job.data.type]) {
throw new RelayerError(`Unknown job type: ${job.data.type}`)
throw new Error(`Unknown job type: ${job.data.type}`)
}
currentJob = job
await updateStatus(status.ACCEPTED)
@@ -273,19 +107,15 @@ async function processJob(job) {
} catch (e) {
console.error('processJob', e.message)
await updateStatus(status.FAILED)
throw new RelayerError(e.message)
throw e
}
}
async function submitTx(job, retry = 0) {
async function submitTx(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()
}
const tx = await getTxObject(job)
await checkTornadoFee(job, tx)
currentTx = await txManager.createTx(tx)
try {
const receipt = await currentTx
@@ -303,35 +133,12 @@ async function submitTx(job, retry = 0) {
if (receipt.status === 1) {
await updateStatus(status.CONFIRMED)
} else {
if (job.data.type !== jobType.TORNADO_WITHDRAW && (await isOutdatedTreeRevert(receipt, currentTx))) {
if (retry < 3) {
await updateStatus(status.RESUBMITTED)
await submitTx(job, retry + 1)
} else {
throw new RelayerError('Tree update retry limit exceeded')
}
} else {
throw new RelayerError('Submitted transaction failed')
}
throw new Error('Submitted transaction failed')
}
} catch (e) {
// todo this could result in duplicated error logs
// todo handle a case where account tree is still not up to date (wait and retry)?
if (
job.data.type !== jobType.TORNADO_WITHDRAW &&
(e.message.indexOf('Outdated account merkle root') !== -1 ||
e.message.indexOf('Outdated tree update merkle root') !== -1)
) {
if (retry < 5) {
await sleep(3000)
console.log('Tree is still not up to date, resubmitting')
await submitTx(job, retry + 1)
} else {
throw new RelayerError('Tree update retry limit exceeded')
}
} else {
throw new RelayerError(`Revert by smart contract ${e.message}`)
}
throw new Error(`Revert by smart contract ${e.message}`)
}
}
@@ -353,9 +160,4 @@ async function updateStatus(status) {
await currentJob.update(currentJob.data)
}
async function clearErrors() {
console.log('Errors list cleared')
await redis.del('errors')
}
start()

View File

@@ -1,10 +1,6 @@
require('chai').should()
const {
getTornadoWithdrawInputError,
getMiningRewardInputError,
getMiningWithdrawInputError,
} = require('../src/modules/validator')
const { getTornadoWithdrawInputError } = require('../src/modules/validator')
describe('Validator', () => {
describe('#getTornadoWithdrawInputError', () => {
@@ -41,56 +37,6 @@ describe('Validator', () => {
malformedData.contract = withdrawData.contract
})
})
describe('#getMiningRewardInputError', () => {
it('should work', () => {
getMiningRewardInputError(rewardData)
})
it('should throw for incorrect proof', () => {
const malformedData = { ...rewardData }
malformedData.proof = '0xbeef'
getMiningRewardInputError(malformedData).should.be.equal(
'.proof should match pattern "^0x[a-fA-F0-9]{512}$"',
)
})
it('should throw something is missing', () => {
const malformedData = { ...rewardData }
delete malformedData.proof
getMiningRewardInputError(malformedData).should.be.equal(" should have required property 'proof'")
malformedData.proof = rewardData.proof
delete malformedData.args
getMiningRewardInputError(malformedData).should.be.equal(" should have required property 'args'")
malformedData.args = rewardData.args
})
})
describe('#getMiningWithdrawInputError', () => {
it('should work', () => {
getMiningWithdrawInputError(miningWithdrawData)
})
it('should throw for incorrect proof', () => {
const malformedData = { ...miningWithdrawData }
malformedData.proof = '0xbeef'
getMiningWithdrawInputError(malformedData).should.be.equal(
'.proof should match pattern "^0x[a-fA-F0-9]{512}$"',
)
})
it('should throw something is missing', () => {
const malformedData = { ...miningWithdrawData }
delete malformedData.proof
getMiningWithdrawInputError(malformedData).should.be.equal(" should have required property 'proof'")
malformedData.proof = miningWithdrawData.proof
delete malformedData.args
getMiningWithdrawInputError(malformedData).should.be.equal(" should have required property 'args'")
malformedData.args = miningWithdrawData.args
})
})
})
const withdrawData = {
@@ -104,54 +50,5 @@ const withdrawData = {
'0x000000000000000000000000000000000000000000000000058d15e176280000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
],
contract: '0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936',
}
const rewardData = {
proof:
'0x2e0f4c76b35ce3275bf57492cbe12ddc76fae4eabdbeaacdcc7cd5255d0abb2325bd80b2a867f9c1bab854de5d7c443a18eb9ad796943dd53c30c04e8f0a37ae164916c932776b3c28dd49808a5d5e1648d8bc9006b2386096b88757644ce8f102f7e2f1505bb66385a1d53a101922a17d8ab653694dedd7d150ec71d543202e0f0a67e5d59904d75af1c52bef4dfac0a302c2beb2ca3bb29b6bbbe1038368702e5ba8d6d829d74968a94e321cc91cccbc0654f5df6460a0a6ad73b06c42b7d1289ff36655fc7106b5538bd2c6617dd0c313919331e63bcb4de9c9b45dc2207b098a5729efbecf79a4cab39ade3c99e5772bfbe5ae75d932facbf9e0910a34ae',
args: {
rate: '0x000000000000000000000000000000000000000000000000000000000000000a',
fee: '0x0000000000000000000000000000000000000000000000000000000000000000',
instance: '0x8b3f5393bA08c24cc7ff5A66a832562aAB7bC95f',
rewardNullifier: '0x08fdc416b85c76d246925994ae0c0df539789fd1669c45b57104907c7ef8b0b5',
extDataHash: '0x006c5f12c20933beab10cfffab31ea0c9d736cf9aa868ee29eed3047d4ea4c2e',
depositRoot: '0x0405962838a47fb25ffd75d80d53b268654a06bc1bdde7e5ad94c675c2f2f0ff',
withdrawalRoot: '0x1cd83f5df5dbc826fecbf6be87f05db9c9dc617a3f1b1f3a421b1335c1ff7dbf',
extData: {
relayer: '0x0000000000000000000000000000000000000000',
encryptedAccount:
'0x6a8494fca4c433ef323d03f0db3fede90c3d2c6f216d73345ffc77ceec79622f327a83c4254063a3027620c262835e335fa32c33600a70547a53b2aa311d3ff35cf943e8f9e8f321f60d4266f680e0606a5837d78deb4d74c8b4fa3e9b67414513c71b73e38995cd8d57fd08aa9e135b342cecaf4128d4cfbb26148022e7a87da8b2423440b62034be202a6a48b45baa9736def6455771b442baaf2358fc52aa6c1d14a9a452b064d280fafd69f2a3ba416c10c1d8276f1c3810c664b24e0f1eefc75d63',
},
account: {
inputRoot: '0x22e875e5e54d8569fb40d0c568984e87b4c97da6383d8d8a334a79e22b48fd54',
inputNullifierHash: '0x24be972a00e3938a58f44ea6f8ead271ecdd6ab2cab42d1910fb7190b5816188',
outputRoot: '0x04a3cd1e37487dcee5da51cbce4245742903262a5824aef77fb7aff84a3cb053',
outputPathIndices: '0x0000000000000000000000000000000000000000000000000000000000000000',
outputCommitment: '0x0ae58c1605312bd42fffdfc41d5e0f9a364ad458717c522bf9338068ab258601',
},
},
}
const miningWithdrawData = {
proof:
'0x087c02cdc5946b44f295e1adb8b65341708fe43854e44f05f205da6e46e2e4c4248b2dd5ee30236e7be2ea657265765b4e43dae263d67ff43190bb806faaafc10dd0a771f9d589b5061ddf0a713f27fc0b496d1b136dc4e98838b88f60efb072087c3018fa5c25b1f78b4bb968291b9afa3966d976e961d0a86719a8e07d771209dad29620f3bc2fc21c00510749a19e7ff369ade6b9fd1a7f05b74e70faee771fd839c710bd983927c9d3d5f39bb5e839a2ece19e899c4d50a91b29d5ac3f1a0e8faf7eeb2f6f672561bfba39bcb1d851f6c97d5c14b7fce6661cf315af3468119855a426fc4df511e848011bcdb704369deba20541a7651ab4d5813a60c056',
args: {
amount: '0x000000000000000000000000000000000000000000000000000000000000000f',
fee: '0x0000000000000000000000000000000000000000000000000000000000000000',
extDataHash: '0x00d95a201b89061613b5bc539bcf8fdee63a400ea80f1f5e813d6aacfee3ec67',
extData: {
recipient: '0xf17f52151ebef6c7334fad080c5704d77216b732',
relayer: '0x0000000000000000000000000000000000000000',
encryptedAccount:
'0x4bd7f84edab796b390181d8b1dd850c418c8b3fe41d63b9677b7b99a2fadc505dcc70df336a42847dc00fa39175d16ddfec0d80dc166282e024b5371f561467651ed94e71524fa2e365a8330b053d5cff7c3bcc3564b335fb9e74fb805a3a6e760b811db60e5d6b4e154376196c3cb61457bac6d5ea804f63208a389555cde72f40ab1b94705e728f692e699fc441504b9df34390b3992a1a1eac160dcf0df0b5c5a9ec9cd6c0c8f5f8aa11627fdf2b3bedece5836e9ca38b09d70ff7ba06702971d245d',
},
account: {
inputRoot: '0x1a756aeee7f7d05f276b20c8ca83150e110e1a436c2d959e501ab306420ab536',
inputNullifierHash: '0x0dc8ea0330171a1f868ef5f3f9f92e919d7be754846f6145c5e7819e87738e65',
outputRoot: '0x0d9d85371bd8c941400ae54815491799e98d1f335a9d263e41f0b81f22b55aa8',
outputPathIndices: '0x0000000000000000000000000000000000000000000000000000000000000001',
outputCommitment: '0x1ebd38a8bc53f47386687386397c8b5cefd33d55341b62a2a576b39d9bcec57c',
},
},
contract: '0xd47438C816c9E7f2E2888E060936a499Af9582b3',
}

4538
yarn.lock

File diff suppressed because it is too large Load Diff