13 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
14 changed files with 1041 additions and 1227 deletions

View File

@@ -10,7 +10,7 @@ APP_PORT=8000
# without 0x prefix
PRIVATE_KEY=
# 0.1 means 0.1%
REGULAR_TORNADO_WITHDRAW_FEE=0.1
RELAYER_FEE=0.1
REWARD_ACCOUNT=
CONFIRMATIONS=4

View File

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

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

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
@tornado:registry=https://git.tornado.ws/api/packages/tornado-packages/npm/

View File

@@ -1,4 +1,4 @@
FROM node:12
FROM node:16
WORKDIR /app
COPY package.json yarn.lock ./

119
README.md
View File

@@ -1,36 +1,115 @@
# Relayer for Tornado Cash [![Build Status](https://github.com/tornadocash/relayer/workflows/build/badge.svg)](https://github.com/tornadocash/relayer/actions) [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/tornadocash/relayer/light?logo=docker&logoColor=%23FFFFFF&sort=semver)](https://hub.docker.com/repository/docker/tornadocash/relayer)
# 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.**
***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
docker-compose.yml contains a stack that will automatically provision SSL certificates for your domain name and will add a https redirect to port 80.
__PREREQUISITES__
1. Download [docker-compose.yml](/docker-compose.yml) and [.env.example](/.env.example)
1. Update core dependencies
```
wget https://raw.githubusercontent.com/tornadocash/tornado-relayer/light/docker-compose.yml
wget https://raw.githubusercontent.com/tornadocash/tornado-relayer/light/.env.example -O .env
```
- `sudo apt-get update`
2. Setup environment variables
2. Install docker-compose
- set `NET_ID` (1 for mainnet, 5 for Goerli)
- set `HTTP_RPC_URL` rpc url for your ethereum node
- set `PRIVATE_KEY` for your relayer address (without 0x prefix)
- set `VIRTUAL_HOST` and `LETSENCRYPT_HOST` to your domain and add DNS record pointing to your relayer ip address
- set `REGULAR_TORNADO_WITHDRAW_FEE` - fee in % that is used for tornado pool withdrawals
- set `REWARD_ACCOUNT` - eth address that is used to collect fees
- update `CONFIRMATIONS` if needed - how many block confirmations to wait before processing an event. Not recommended to set less than 3
- update `MAX_GAS_PRICE` if needed - maximum value of gwei value for relayer's transaction
- `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`
If you want to use more than 1 eth address for relaying transactions, please add as many `workers` as you want. For example, you can comment out `worker2` in docker-compose.yml file, but please use a different `PRIVATE_KEY` for each worker.
3. Install Docker
3. Run `docker-compose up -d`
- `curl -fsSL https://get.docker.com -o get-docker.sh && chmod +x get-docker.sh && ./get-docker.sh`
## Run locally
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__
_* Warning: Failure to configure SSH as the first UFW rule, will lock you out of the instance_
1. Make sure UFW is installed by running `apt update` and `apt install ufw`
2. Allow SSH in the first position in UFW by running `ufw insert 1 allow ssh`*
3. Allow HTTP, and HTTPS by running `ufw allow https/tcp/http`
4. Finalize changes and enable firewall `ufw enable`
__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`
__DEPLOYMENT__
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
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

View File

@@ -1,53 +0,0 @@
version: '2'
# ssh-agent && ssh-add -K ~/.ssh/id_rsa
# DOCKER_BUILDKIT=1 docker build --ssh default -t tornadocash/relayer .
services:
server:
image: tornadocash/relayer
restart: always
command: server
env_file: .env
ports:
- 8000:8000
environment:
REDIS_URL: redis://redis/0
nginx_proxy_read_timeout: 600
depends_on: [redis]
healthWatcher:
image: tornadocash/relayer
restart: always
command: healthWatcher
env_file: .env
environment:
REDIS_URL: redis://redis/0
depends_on: [redis]
worker1:
image: tornadocash/relayer
restart: always
command: worker
env_file: .env
environment:
REDIS_URL: redis://redis/0
depends_on: [redis]
# worker2:
# image: tornadocash/relayer
# restart: always
# command: worker
# env_file: .env
# environment:
# PRIVATE_KEY: qwe
# REDIS_URL: redis://redis/0
redis:
image: redis
restart: always
command: [redis-server, --appendonly, 'yes']
volumes:
- redis:/data
volumes:
redis:

View File

@@ -1,97 +1,14 @@
version: '2'
services:
server:
image: tornadocash/relayer:light
restart: always
command: server
env_file: .env
environment:
REDIS_URL: redis://redis/0
nginx_proxy_read_timeout: 600
depends_on: [redis]
healthWatcher:
image: tornadocash/relayer:light
restart: always
command: healthWatcher
env_file: .env
environment:
REDIS_URL: redis://redis/0
depends_on: [redis]
worker1:
image: tornadocash/relayer:light
restart: always
command: worker
env_file: .env
environment:
REDIS_URL: redis://redis/0
depends_on: [redis]
# worker2:
# image: tornadocash/relayer:light
# restart: always
# command: worker
# env_file: .env
# environment:
# PRIVATE_KEY: qwe
# 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
redis:
image: redis
restart: always
command: [redis-server, --appendonly, 'yes']
volumes:
- redis:/data
ports:
- '127.0.0.1:6379:6379'
nginx:
image: nginx:alpine
@@ -128,6 +45,234 @@ services:
- nginx
- dockergen
# ---------------------- BSC (Binance Smart Chain) ----------------------- #
bsc-server:
image: tornadocash/relayer:sidechain-v5
profiles: ['bsc']
restart: always
command: server
env_file: .env.bsc
environment:
NET_ID: 56
REDIS_URL: redis://redis/1
nginx_proxy_read_timeout: 600
depends_on: [redis]
bsc-healthWatcher:
image: tornadocash/relayer:sidechain-v5
profiles: ['bsc']
restart: always
command: healthWatcher
env_file: .env.bsc
environment:
NET_ID: 56
REDIS_URL: redis://redis/1
depends_on: [redis, bsc-server]
bsc-worker1:
image: tornadocash/relayer:sidechain-v5
profiles: ['bsc']
restart: always
command: worker
env_file: .env.bsc
environment:
NET_ID: 56
REDIS_URL: redis://redis/1
depends_on: [redis, bsc-server]
# -------------------------------------------------- #
# ---------------------- Polygon (MATIC) --------------------- #
polygon-server:
image: tornadocash/relayer:sidechain-v5
profiles: ['polygon']
restart: always
command: server
env_file: .env.polygon
environment:
NET_ID: 137
REDIS_URL: redis://redis/2
nginx_proxy_read_timeout: 600
depends_on: [redis]
polygon-healthWatcher:
image: tornadocash/relayer:sidechain-v5
profiles: ['polygon']
restart: always
command: healthWatcher
env_file: .env.polygon
environment:
NET_ID: 137
REDIS_URL: redis://redis/2
depends_on: [redis, polygon-server]
polygon-worker1:
image: tornadocash/relayer:sidechain-v5
profiles: ['polygon']
restart: always
command: worker
env_file: .env.polygon
environment:
NET_ID: 137
REDIS_URL: redis://redis/2
depends_on: [redis, polygon-server]
# -------------------------------------------------- #
# ---------------------- Gnosis (XDAI) ---------------------- #
gnosis-server:
image: tornadocash/relayer:sidechain-v5
profiles: ['gnosis']
restart: always
command: server
env_file: .env.gnosis
environment:
NET_ID: 100
REDIS_URL: redis://redis/3
nginx_proxy_read_timeout: 600
depends_on: [redis]
gnosis-healthWatcher:
image: tornadocash/relayer:sidechain-v5
profiles: ['gnosis']
restart: always
command: healthWatcher
env_file: .env.gnosis
environment:
NET_ID: 100
REDIS_URL: redis://redis/3
depends_on: [redis, gnosis-server]
gnosis-worker1:
image: tornadocash/relayer:sidechain-v5
profiles: ['gnosis']
restart: always
command: worker
env_file: .env.gnosis
environment:
NET_ID: 100
REDIS_URL: redis://redis/3
depends_on: [redis, gnosis-server]
# -------------------------------------------------- #
# ---------------------- AVAX ---------------------- #
avax-server:
image: tornadocash/relayer:sidechain-v5
profiles: ['avax']
restart: always
command: server
env_file: .env.avax
environment:
NET_ID: 43114
REDIS_URL: redis://redis/4
nginx_proxy_read_timeout: 600
depends_on: [redis]
avax-healthWatcher:
image: tornadocash/relayer:sidechain-v5
profiles: ['avax']
restart: always
command: healthWatcher
env_file: .env.avax
environment:
NET_ID: 43114
REDIS_URL: redis://redis/4
depends_on: [redis, avax-server]
avax-worker1:
image: tornadocash/relayer:sidechain-v5
profiles: ['avax']
restart: always
command: worker
env_file: .env.avax
environment:
NET_ID: 43114
REDIS_URL: redis://redis/4
depends_on: [redis, avax-server]
# -------------------------------------------------- #
# ---------------------- OP ------------------------ #
op-server:
image: tornadocash/relayer:sidechain-v5
profiles: ['op']
restart: always
command: server
env_file: .env.op
environment:
NET_ID: 10
REDIS_URL: redis://redis/5
nginx_proxy_read_timeout: 600
depends_on: [redis]
op-healthWatcher:
image: tornadocash/relayer:sidechain-v5
profiles: ['op']
restart: always
command: healthWatcher
env_file: .env.op
environment:
NET_ID: 10
REDIS_URL: redis://redis/5
depends_on: [redis, op-server]
op-worker1:
image: tornadocash/relayer:sidechain-v5
profiles: ['op']
restart: always
command: worker
env_file: .env.op
environment:
NET_ID: 10
REDIS_URL: redis://redis/5
depends_on: [redis, op-server]
# -------------------------------------------------- #
# ---------------------- Arbitrum ----------------------- #
arb-server:
image: tornadocash/relayer:sidechain-v5
profiles: ['arb']
restart: always
command: server
env_file: .env.arb
environment:
NET_ID: 42161
REDIS_URL: redis://redis/6
nginx_proxy_read_timeout: 600
depends_on: [redis]
arb-healthWatcher:
image: tornadocash/relayer:sidechain-v5
profiles: ['arb']
restart: always
command: healthWatcher
env_file: .env.arb
environment:
NET_ID: 42161
REDIS_URL: redis://redis/6
depends_on: [redis, arb-server]
arb-worker1:
image: tornadocash/relayer:sidechain-v5
profiles: ['arb']
restart: always
command: worker
env_file: .env.arb
environment:
NET_ID: 42161
REDIS_URL: redis://redis/6
depends_on: [redis, arb-server]
# -------------------------------------------------- #
volumes:
conf:
vhost:

View File

@@ -1,6 +1,6 @@
{
"name": "relay",
"version": "5.0.0-beta.12",
"version": "5.2.2",
"description": "Relayer for Tornado.cash privacy solution. https://tornado.cash",
"scripts": {
"server": "node src/server.js",
@@ -11,19 +11,21 @@
"prettier:fix": "npx prettier --write . --config .prettierrc",
"lint": "yarn eslint && yarn prettier:check",
"test": "mocha",
"start": "yarn server & 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",
"license": "MIT",
"dependencies": {
"@tornado/tornado-config": "^1.0.8",
"@tornado/tornado-oracles": "^3.3.0",
"@tornado/tx-manager": "^0.4.9",
"ajv": "^6.12.5",
"bull": "^3.12.1",
"concurrently": "^8.2.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"gas-price-oracle": "^0.4.7",
"ioredis": "^4.14.1",
"torn-token": "^1.0.5",
"tx-manager": "^0.4.8",
"uuid": "^8.3.0",
"web3": "^1.3.0",
"web3-utils": "^1.2.2"

View File

@@ -1,11 +1,11 @@
require('dotenv').config()
const tornConfig = require('torn-token')
const tornConfig = require('@tornado/tornado-config')
const { jobType, networkConfig } = require('./constants')
const { networkConfig } = require('./constants')
const netId = Number(process.env.NET_ID) || 56
const instances = tornConfig.instances[`netId${netId}`]
const instances = tornConfig.instances[netId]
const proxyLight = tornConfig.tornadoProxyLight.address
const { gasPrices, nativeCurrency } = networkConfig[`netId${netId}`]
@@ -13,20 +13,15 @@ module.exports = {
netId,
redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
httpRpcUrl: process.env.HTTP_RPC_URL,
oracleRpcUrl: process.env.ORACLE_RPC_URL || 'https://mainnet.infura.io/',
oracleRpcUrl: process.env.ORACLE_RPC_URL || 'https://rpc.payload.de',
minerMerkleTreeHeight: 20,
privateKey: process.env.PRIVATE_KEY,
instances,
port: process.env.APP_PORT || 8000,
tornadoServiceFee: Number(process.env.REGULAR_TORNADO_WITHDRAW_FEE),
tornadoServiceFee: Number(process.env.RELAYER_FEE),
rewardAccount: process.env.REWARD_ACCOUNT,
gasPrices,
gasLimits: {
[jobType.TORNADO_WITHDRAW]: 390000,
[jobType.OP_TORNADO_WITHDRAW]: 440000,
[jobType.ARB_TORNADO_WITHDRAW]: 1900000,
},
proxyLight,
nativeCurrency,
minimumBalance: '500000000000000000', // 0.5
minimumBalance: netId === 137 || netId === 43114 ? '10000000000000000000' : '100000000000000000', // 10 or 0.1
}

View File

@@ -1,30 +1,20 @@
const Web3 = require('web3')
const { GasPriceOracle } = require('gas-price-oracle')
const { serialize } = require('@ethersproject/transactions')
const { toBN, toWei, fromWei, toHex } = require('web3-utils')
const { toBN, fromWei, isAddress, toChecksumAddress } = require('web3-utils')
const { redis } = require('./modules/redis')
const proxyLightABI = require('../abis/proxyLightABI.json')
const ovmGasPriceOracleABI = require('../abis/ovmGasPriceOracleABI.json')
const { queue } = require('./queue')
const { getInstance, fromDecimals, logRelayerError, clearRelayerErrors } = require('./utils')
const { getInstance, logRelayerError, clearRelayerErrors } = require('./utils')
const { jobType, status } = require('./constants')
const {
netId,
gasPrices,
gasLimits,
privateKey,
proxyLight,
httpRpcUrl,
tornadoServiceFee,
} = require('./config')
const { TxManager } = require('tx-manager')
const { netId, gasPrices, privateKey, proxyLight, httpRpcUrl, tornadoServiceFee } = require('./config')
const { TxManager } = require('@tornado/tx-manager')
const { TornadoFeeOracleV5 } = require('@tornado/tornado-oracles')
let web3
let currentTx
let currentJob
let txManager
let gasPriceOracle
let tornadoProxyInstance
const feeOracle = new TornadoFeeOracleV5(netId, httpRpcUrl, gasPrices)
function start() {
try {
@@ -32,14 +22,15 @@ function start() {
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,
}
gasPriceOracle = new GasPriceOracle(gasPriceOracleConfig)
txManager = new TxManager({
privateKey,
rpcUrl: httpRpcUrl,
@@ -55,77 +46,27 @@ function start() {
}
}
function getGasPrices() {
return gasPriceOracle.gasPrices()
}
async function checkTornadoFee({ data }, tx) {
const userProvidedFee = toBN(data.args[4])
const { amount, decimals, currency } = getInstance(data.contract)
function getGasLimit() {
let action
switch (Number(netId)) {
case 10:
action = jobType.OP_TORNADO_WITHDRAW
break
case 42161:
action = jobType.ARB_TORNADO_WITHDRAW
break
default:
action = jobType.TORNADO_WITHDRAW
}
return gasLimits[action]
}
async function getL1Fee({ data, gasPrice }) {
const { address } = web3.eth.accounts.privateKeyToAccount(privateKey)
const nonce = await web3.eth.getTransactionCount(address)
const ovmGasPriceOracleContract = '0x420000000000000000000000000000000000000F'
const oracleInstance = new web3.eth.Contract(ovmGasPriceOracleABI, ovmGasPriceOracleContract)
const calldata = tornadoProxyInstance.methods.withdraw(data.contract, data.proof, ...data.args).encodeABI()
const tx = serialize({
nonce,
type: 0,
data: calldata,
chainId: netId,
value: data.args[5],
to: tornadoProxyInstance._address,
gasLimit: getGasLimit(),
gasPrice: toHex(gasPrice),
const relayerDesiredFee = await feeOracle.calculateWithdrawalFeeViaRelayer({
tx,
txType: 'relayer_withdrawal',
relayerFeePercent: tornadoServiceFee,
currency,
amount,
decimals,
gasLimit: tx.gasLimit,
gasPrice: tx.gasPrice,
})
const l1Fee = await oracleInstance.methods.getL1Fee(tx).call()
return l1Fee
}
async function checkTornadoFee({ data }) {
const fee = toBN(data.args[4])
const { amount, decimals } = getInstance(data.contract)
const { fast } = await getGasPrices()
const gasPrice = toWei(fast.toString(), 'gwei')
let expense = toBN(gasPrice).mul(toBN(getGasLimit()))
if (netId === 10) {
const l1Fee = await getL1Fee({ data, gasPrice })
expense = expense.add(toBN(l1Fee))
}
const feePercent = toBN(fromDecimals(amount, decimals))
.mul(toBN(parseInt(tornadoServiceFee * 1e10)))
.div(toBN(1e10 * 100))
const desiredFee = expense.add(feePercent)
console.log(
'sent fee, desired fee, feePercent',
fromWei(fee.toString()),
fromWei(desiredFee.toString()),
fromWei(feePercent.toString()),
'user-provided fee, desired fee',
fromWei(userProvidedFee.toString()),
fromWei(toBN(relayerDesiredFee).toString()),
)
if (fee.lt(desiredFee)) {
if (userProvidedFee.lt(toBN(relayerDesiredFee))) {
throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.')
}
}
@@ -133,15 +74,25 @@ async function checkTornadoFee({ data }) {
async function getTxObject({ data }) {
const calldata = tornadoProxyInstance.methods.withdraw(data.contract, data.proof, ...data.args).encodeABI()
const { fast } = await getGasPrices()
return {
const incompleteTx = {
value: data.args[5],
to: tornadoProxyInstance._address,
data: calldata,
gasLimit: getGasLimit(),
gasPrice: toHex(toWei(fast.toString(), 'gwei')),
}
const { gasLimit, gasPrice } = await feeOracle.getGasParams({
tx: incompleteTx,
txType: 'relayer_withdrawal',
})
return { ...incompleteTx, gasLimit, gasPrice }
}
async function checkRecipient({ data }) {
const recipient = data.args[2]
if (!isAddress(recipient)) throw new Error('Recipient address is invalid')
const addressCode = await web3.eth.getCode(toChecksumAddress(recipient))
if (addressCode !== '0x') throw new Error('Recipient cannot be a smart-contract, only EOA')
}
async function processJob(job) {
@@ -161,8 +112,10 @@ async function processJob(job) {
}
async function submitTx(job) {
await checkTornadoFee(job)
currentTx = await txManager.createTx(await getTxObject(job))
await checkRecipient(job)
const tx = await getTxObject(job)
await checkTornadoFee(job, tx)
currentTx = await txManager.createTx(tx)
try {
const receipt = await currentTx

15
tornado-stream.conf Normal file
View File

@@ -0,0 +1,15 @@
map $ssl_preread_server_name $name {
yourdomain.com tornado_mainnet;
default tornado_mainnet;
}
upstream tornado_mainnet {
server 127.0.0.1:4380;
}
server {
listen 0.0.0.0:443;
proxy_pass $name;
ssl_preread on;
}

87
tornado.conf Normal file
View File

@@ -0,0 +1,87 @@
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $http_x_forwarded_proto;
'' $scheme;
}
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port {
default $http_x_forwarded_port;
'' $server_port;
}
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
default upgrade;
'' close;
}
# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparam
# Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto
map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl {
default off;
https on;
}
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$upstream_addr"';
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Original-URI $request_uri;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
# Request rate limiting per second, 2Mb zone @ 5 requests per second
limit_req_zone $binary_remote_addr zone=one:2m rate=5r/s;
# Connections per IP limited to 2
limit_conn_zone $binary_remote_addr zone=two:2m;
server {
server_name _; # This is just an invalid value which will never trigger on a real hostname.
server_tokens off;
listen 80;
access_log /var/log/nginx/access.log vhost;
return 503;
}
server {
server_name yourdomain.com;
# Connection timeouts
client_body_timeout 10s;
client_header_timeout 10s;
listen 80;
access_log /var/log/nginx/access.log vhost;
# Do not HTTPS redirect LetsEncrypt ACME challenge
location ^~ /.well-known/acme-challenge/ {
limit_req zone=one;
limit_conn two 1;
proxy_pass http://127.0.0.1:8000;
break;
}
location / {
limit_req zone=one;
limit_conn two 1;
return 301 https://$host$request_uri;
}
}

1501
yarn.lock

File diff suppressed because it is too large Load Diff