Add --onlyrpc mode

+ Add mode to disable remote ip connection and to disable thegraph api

+ Faster event sync for deposit

+ Update README guide for users
This commit is contained in:
Ayanami 2022-02-27 07:32:39 +09:00
parent 2854b7a12f
commit c185961a62
No known key found for this signature in database
GPG Key ID: 0CABDF03077D92E4
2 changed files with 120 additions and 21 deletions

@ -10,11 +10,11 @@ Download and install [node.js](https://nodejs.org/en/download/).
You also need to install C++ build tools in order to do 'npm install', for more information please checkout https://github.com/nodejs/node-gyp#on-unix. You also need to install C++ build tools in order to do 'npm install', for more information please checkout https://github.com/nodejs/node-gyp#on-unix.
For Windows: https://stackoverflow.com/a/64224475 - For Windows: https://stackoverflow.com/a/64224475
For MacOS: Install XCode Command Line Tools - For MacOS: Install XCode Command Line Tools
For Linux: Install make & gcc, for ubuntu `$ sudo apt-get install -y build-essentials` - For Linux: Install make & gcc, for ubuntu `$ sudo apt-get install -y build-essentials`
If you have git installed on your system, clone the master branch. If you have git installed on your system, clone the master branch.
@ -33,14 +33,26 @@ $ cd tornado-cli
$ npm install $ npm install
``` ```
If you want to use Tor connection to conceal ip address, install [Tor Browser](https://www.torproject.org/download/) and add `--tor 9150` for `cli.js` if you connect tor with browser. If you want to use Tor connection to conceal ip address, install [Tor Browser](https://www.torproject.org/download/) and add `--tor 9150` for `cli.js` if you connect tor with browser. (For non tor-browser tor service you can use the default 9050 port).
Note that you should reset your tor connection by restarting the browser every time when you deposit & withdraw otherwise you will have the same exit node used for connection. Note that you should reset your tor connection by restarting the browser every time when you deposit & withdraw otherwise you will have the same exit node used for connection.
### Goerli, Mainnet, Binance Smart Chain, Gnosis Chain, Polygon Network, Arbitrum, Avalanche ### Goerli, Mainnet, Binance Smart Chain, Gnosis Chain, Polygon Network, Arbitrum, Avalanche
1. Add `PRIVATE_KEY` to `.env` file 1. Add `PRIVATE_KEY` to `.env` file
2. `node cli.js --help` 2. `node cli.js --help`
3. If you want to use secure, anonymous tor connection add `--tor <torPort>` behind the command.
Example: #### To deposit:
```bash
$ node cli.js deposit <currency> <amount> --rpc <rpc url> --tor <torPort>
```
Note that `--tor <torPort>` is optional.
For RPC nodes please refer to the list of public RPC nodes below.
##### Example:
```bash ```bash
$ node cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150 $ node cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150
@ -52,6 +64,22 @@ Tornado ETH balance is 9
Sender account ETH balance is 1004873.361652048361352542 Sender account ETH balance is 1004873.361652048361352542
``` ```
#### To withdraw:
```bash
$ node cli.js withdraw <note> <recipient> --rpc <rpc url> --relayer <relayer url> --tor <torPort>
```
Note that `--relayer <relayer url>`, `--tor <torPort>` is optional.
If you want to use Tornado Cash relayer for your first withdrawal to your new ethereum account, please refer to the list of relayers below.
If you don't need relayer while doing withdrawals, you must apply your withdrawal account's private key to `.env` file.
Copy the `PRIVATE_KEY=` line of `.env.example` to `.env`, and add your private key behind the `=`.
##### Example:
```bash ```bash
$ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9150 $ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9150
@ -70,7 +98,15 @@ One of the main features of tornado-cli is that it supports creating deposit not
After the private-key like notes are backed up somewhere safe, you can copy the created deposit invoices and use them to create new deposit transaction on online environment. After the private-key like notes are backed up somewhere safe, you can copy the created deposit invoices and use them to create new deposit transaction on online environment.
To create deposit notes with `createNote` command. #### To create deposit notes with `createNote` command.
```bash
$ node cli.js createNote <currency> <amount> <chainId>
```
To find out chainId value for your network, refer to https://chainlist.org/.
##### Example:
```bash ```bash
$ node cli.js createNote ETH 0.1 5 $ node cli.js createNote ETH 0.1 5
@ -80,7 +116,15 @@ Backed up deposit note as ./backup-tornado-eth-0.1-5-0x1d9771a7.txt
Backed up invoice as ./backup-tornadoInvoice-eth-0.1-5-0x1b680c7d.txt Backed up invoice as ./backup-tornadoInvoice-eth-0.1-5-0x1b680c7d.txt
``` ```
To create corresponding deposit transaction you only need invoice value, so that the deposit note could be stored without exposed anywhere. #### To create corresponding deposit transaction with `depositInvoice` command.
Creating deposit transaction with `depositInvoice` only requires valid deposit note created by `createNote` command, so that the deposit note could be stored without exposed anywhere.
```bash
$ node cli.js depositInvoice <invoice>
```
##### Example:
```bash ```bash
node cli.js depositInvoice tornadoInvoice-eth-0.1-5-0x1b680c7dda0c2dd1b85f0fe126d49b16ed594b3cd6d5114db5f4593877a6b84f --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150 node cli.js depositInvoice tornadoInvoice-eth-0.1-5-0x1b680c7dda0c2dd1b85f0fe126d49b16ed594b3cd6d5114db5f4593877a6b84f --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150
@ -97,6 +141,27 @@ Tornado contract balance is xxx.x ETH
Sender account balance is x.xxxxxxx ETH Sender account balance is x.xxxxxxx ETH
``` ```
#### To withdraw, you will need deposit note that matches with your deposit transaction.
```bash
$ node cli.js withdraw <note> <recipient>
```
##### Example:
```bash
$ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9150
Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
Getting current state from tornado contract
Generating SNARK proof
Proof time: 9117.051ms
Sending withdraw transaction through relay
Transaction submitted through the relay. View transaction on etherscan https://goerli.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b
Transaction mined in block 17036120
Done
```
### List of public rpc & relayers for withdrawal ### List of public rpc & relayers for withdrawal
Infura API key fetched from https://rpc.info (Same one with Metamask) Infura API key fetched from https://rpc.info (Same one with Metamask)

50
cli.js Normal file → Executable file

@ -65,7 +65,7 @@ async function printERC20Balance({ address, name, tokenAddress }) {
let tokenDecimals, tokenBalance, tokenName, tokenSymbol; let tokenDecimals, tokenBalance, tokenName, tokenSymbol;
const erc20ContractJson = require('./build/contracts/ERC20Mock.json'); const erc20ContractJson = require('./build/contracts/ERC20Mock.json');
erc20 = tokenAddress ? new web3.eth.Contract(erc20ContractJson.abi, tokenAddress) : erc20; erc20 = tokenAddress ? new web3.eth.Contract(erc20ContractJson.abi, tokenAddress) : erc20;
if (!isTestRPC || !multiCall) { if (!isTestRPC && !multiCall) {
const tokenCall = await useMultiCall([[tokenAddress, erc20.methods.balanceOf(address).encodeABI()], [tokenAddress, erc20.methods.decimals().encodeABI()], [tokenAddress, erc20.methods.name().encodeABI()], [tokenAddress, erc20.methods.symbol().encodeABI()]]); const tokenCall = await useMultiCall([[tokenAddress, erc20.methods.balanceOf(address).encodeABI()], [tokenAddress, erc20.methods.decimals().encodeABI()], [tokenAddress, erc20.methods.name().encodeABI()], [tokenAddress, erc20.methods.symbol().encodeABI()]]);
tokenDecimals = parseInt(tokenCall[1]); tokenDecimals = parseInt(tokenCall[1]);
tokenBalance = new BigNumber(tokenCall[0]).div(BigNumber(10).pow(tokenDecimals)); tokenBalance = new BigNumber(tokenCall[0]).div(BigNumber(10).pow(tokenDecimals));
@ -220,7 +220,7 @@ async function createInvoice({ currency, amount, chainId }) {
*/ */
async function deposit({ currency, amount, commitmentNote }) { async function deposit({ currency, amount, commitmentNote }) {
assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit'); assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit');
let commitment; let commitment, noteString;
if (!commitmentNote) { if (!commitmentNote) {
console.log("Creating new random deposit note"); console.log("Creating new random deposit note");
const deposit = createDeposit({ const deposit = createDeposit({
@ -228,7 +228,7 @@ async function deposit({ currency, amount, commitmentNote }) {
secret: rbigint(31) secret: rbigint(31)
}); });
const note = toHex(deposit.preimage, 62); const note = toHex(deposit.preimage, 62);
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`; noteString = `tornado-${currency}-${amount}-${netId}-${note}`;
console.log(`Your note: ${noteString}`); console.log(`Your note: ${noteString}`);
await backupNote({ currency, amount, netId, note, noteString }); await backupNote({ currency, amount, netId, note, noteString });
commitment = toHex(deposit.commitment); commitment = toHex(deposit.commitment);
@ -300,7 +300,7 @@ async function generateMerkleProof(deposit, currency, amount) {
// Validate that our data is correct // Validate that our data is correct
const root = tree.root(); const root = tree.root();
let isValidRoot, isSpent; let isValidRoot, isSpent;
if (!isTestRPC || !multiCall) { if (!isTestRPC && !multiCall) {
const callContract = await useMultiCall([[tornadoContract._address, tornadoContract.methods.isKnownRoot(toHex(root)).encodeABI()], [tornadoContract._address, tornadoContract.methods.isSpent(toHex(deposit.nullifierHash)).encodeABI()]]) const callContract = await useMultiCall([[tornadoContract._address, tornadoContract.methods.isKnownRoot(toHex(root)).encodeABI()], [tornadoContract._address, tornadoContract.methods.isSpent(toHex(deposit.nullifierHash)).encodeABI()]])
isValidRoot = web3.eth.abi.decodeParameter('bool', callContract[0]); isValidRoot = web3.eth.abi.decodeParameter('bool', callContract[0]);
isSpent = web3.eth.abi.decodeParameter('bool', callContract[1]); isSpent = web3.eth.abi.decodeParameter('bool', callContract[1]);
@ -459,7 +459,7 @@ async function send({ address, amount, tokenAddress }) {
const erc20ContractJson = require('./build/contracts/ERC20Mock.json'); const erc20ContractJson = require('./build/contracts/ERC20Mock.json');
erc20 = new web3.eth.Contract(erc20ContractJson.abi, tokenAddress); erc20 = new web3.eth.Contract(erc20ContractJson.abi, tokenAddress);
let tokenBalance, tokenDecimals, tokenSymbol; let tokenBalance, tokenDecimals, tokenSymbol;
if (!isTestRPC || !multiCall) { if (!isTestRPC && !multiCall) {
const callToken = await useMultiCall([[tokenAddress, erc20.methods.balanceOf(senderAccount).encodeABI()], [tokenAddress, erc20.methods.decimals().encodeABI()], [tokenAddress, erc20.methods.symbol().encodeABI()]]); const callToken = await useMultiCall([[tokenAddress, erc20.methods.balanceOf(senderAccount).encodeABI()], [tokenAddress, erc20.methods.decimals().encodeABI()], [tokenAddress, erc20.methods.symbol().encodeABI()]]);
tokenBalance = new BigNumber(callToken[0]); tokenBalance = new BigNumber(callToken[0]);
tokenDecimals = parseInt(callToken[1]); tokenDecimals = parseInt(callToken[1]);
@ -1035,6 +1035,13 @@ async function fetchEvents({ type, currency, amount }) {
const result = await queryFromGraph(i); const result = await queryFromGraph(i);
if (Object.keys(result).length === 0) { if (Object.keys(result).length === 0) {
i = latestTimestamp; i = latestTimestamp;
} else {
if (type === "deposit") {
const resultBlock = result[result.length - 1].blockNumber;
const resultTimestamp = result[result.length - 1].timestamp;
await updateCache(result);
i = resultTimestamp;
console.log("Fetched", amount, currency.toUpperCase(), type, "events to block:", Number(resultBlock));
} else { } else {
const resultBlock = result[result.length - 1].blockNumber; const resultBlock = result[result.length - 1].blockNumber;
const getResultBlock = await web3.eth.getBlock(resultBlock); const getResultBlock = await web3.eth.getBlock(resultBlock);
@ -1044,6 +1051,7 @@ async function fetchEvents({ type, currency, amount }) {
console.log("Fetched", amount, currency.toUpperCase(), type, "events to block:", Number(resultBlock)); console.log("Fetched", amount, currency.toUpperCase(), type, "events to block:", Number(resultBlock));
} }
} }
}
} else { } else {
console.log("Fallback to web3 events"); console.log("Fallback to web3 events");
await syncEvents(); await syncEvents();
@ -1051,7 +1059,7 @@ async function fetchEvents({ type, currency, amount }) {
} }
await fetchGraphEvents(); await fetchGraphEvents();
} }
if (!privateRpc || !subgraph || !isTestRPC) { if (!privateRpc && !subgraph && !isTestRPC) {
await syncGraphEvents(); await syncGraphEvents();
} else { } else {
await syncEvents(); await syncEvents();
@ -1217,7 +1225,7 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', balanceC
} }
const rpcHost = new URL(rpc).hostname; const rpcHost = new URL(rpc).hostname;
const isIpPrivate = is_ip_private(rpcHost); const isIpPrivate = is_ip_private(rpcHost);
if (!isIpPrivate && !rpc.includes("localhost")) { if (!isIpPrivate && !rpc.includes("localhost") && !privateRpc) {
try { try {
const fetchRemoteIP = await axios.get('https://ip.tornado.cash', ipOptions); const fetchRemoteIP = await axios.get('https://ip.tornado.cash', ipOptions);
const { country, ip } = fetchRemoteIP.data; const { country, ip } = fetchRemoteIP.data;
@ -1322,7 +1330,8 @@ async function main() {
.option('-r, --rpc <URL>', 'The RPC that CLI should interact with', 'http://localhost:8545') .option('-r, --rpc <URL>', 'The RPC that CLI should interact with', 'http://localhost:8545')
.option('-R, --relayer <URL>', 'Withdraw via relayer') .option('-R, --relayer <URL>', 'Withdraw via relayer')
.option('-T, --tor <PORT>', 'Optional tor port') .option('-T, --tor <PORT>', 'Optional tor port')
.option('-L, --local', 'Local Node - Does not submit signed transaction to the node'); .option('-L, --local', 'Local Node - Does not submit signed transaction to the node')
.option('-o, --onlyrpc', 'Only rpc mode - Does not enable thegraph api nor remote ip detection');
program program
.command('createNote <currency> <amount> <chainId>') .command('createNote <currency> <amount> <chainId>')
.description( .description(
@ -1338,6 +1347,9 @@ async function main() {
'Submit a deposit of invoice from default eth account and return the resulting note.' 'Submit a deposit of invoice from default eth account and return the resulting note.'
) )
.action(async (invoice) => { .action(async (invoice) => {
if (program.onlyrpc) {
privateRpc = true;
}
torPort = program.tor; torPort = program.tor;
const { currency, amount, netId, commitmentNote } = parseInvoice(invoice); const { currency, amount, netId, commitmentNote } = parseInvoice(invoice);
await init({ rpc: program.rpc, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, currency, amount, localMode: program.local });
@ -1350,6 +1362,9 @@ async function main() {
'Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.' 'Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.'
) )
.action(async (currency, amount) => { .action(async (currency, amount) => {
if (program.onlyrpc) {
privateRpc = true;
}
currency = currency.toLowerCase(); currency = currency.toLowerCase();
torPort = program.tor; torPort = program.tor;
await init({ rpc: program.rpc, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, currency, amount, localMode: program.local });
@ -1361,6 +1376,9 @@ async function main() {
'Withdraw a note to a recipient account using relayer or specified private key. You can exchange some of your deposit`s tokens to ETH during the withdrawal by specifing ETH_purchase (e.g. 0.01) to pay for gas in future transactions. Also see the --relayer option.' 'Withdraw a note to a recipient account using relayer or specified private key. You can exchange some of your deposit`s tokens to ETH during the withdrawal by specifing ETH_purchase (e.g. 0.01) to pay for gas in future transactions. Also see the --relayer option.'
) )
.action(async (noteString, recipient, refund) => { .action(async (noteString, recipient, refund) => {
if (program.onlyrpc) {
privateRpc = true;
}
const { currency, amount, netId, deposit } = parseNote(noteString); const { currency, amount, netId, deposit } = parseNote(noteString);
torPort = program.tor; torPort = program.tor;
await init({ rpc: program.rpc, noteNetId: netId, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, noteNetId: netId, currency, amount, localMode: program.local });
@ -1377,6 +1395,9 @@ async function main() {
.command('balance [address] [token_address]') .command('balance [address] [token_address]')
.description('Check ETH and ERC20 balance') .description('Check ETH and ERC20 balance')
.action(async (address, tokenAddress) => { .action(async (address, tokenAddress) => {
if (program.onlyrpc) {
privateRpc = true;
}
torPort = program.tor; torPort = program.tor;
await init({ rpc: program.rpc, balanceCheck: true }); await init({ rpc: program.rpc, balanceCheck: true });
if (!address && senderAccount) { if (!address && senderAccount) {
@ -1392,6 +1413,9 @@ async function main() {
.command('send <address> [amount] [token_address]') .command('send <address> [amount] [token_address]')
.description('Send ETH or ERC to address') .description('Send ETH or ERC to address')
.action(async (address, amount, tokenAddress) => { .action(async (address, amount, tokenAddress) => {
if (program.onlyrpc) {
privateRpc = true;
}
torPort = program.tor; torPort = program.tor;
await init({ rpc: program.rpc, balanceCheck: true, localMode: program.local }); await init({ rpc: program.rpc, balanceCheck: true, localMode: program.local });
await send({ address, amount, tokenAddress }); await send({ address, amount, tokenAddress });
@ -1400,6 +1424,9 @@ async function main() {
.command('broadcast <signedTX>') .command('broadcast <signedTX>')
.description('Submit signed TX to the remote node') .description('Submit signed TX to the remote node')
.action(async (signedTX) => { .action(async (signedTX) => {
if (program.onlyrpc) {
privateRpc = true;
}
torPort = program.tor; torPort = program.tor;
await init({ rpc: program.rpc, balanceCheck: true }); await init({ rpc: program.rpc, balanceCheck: true });
await submitTransaction(signedTX); await submitTransaction(signedTX);
@ -1410,6 +1437,9 @@ async function main() {
'Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.' 'Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.'
) )
.action(async (noteString) => { .action(async (noteString) => {
if (program.onlyrpc) {
privateRpc = true;
}
const { currency, amount, netId, deposit } = parseNote(noteString); const { currency, amount, netId, deposit } = parseNote(noteString);
torPort = program.tor; torPort = program.tor;
await init({ rpc: program.rpc, noteNetId: netId, currency, amount }); await init({ rpc: program.rpc, noteNetId: netId, currency, amount });
@ -1445,6 +1475,9 @@ async function main() {
'Sync the local cache file of deposit / withdrawal events for specific currency.' 'Sync the local cache file of deposit / withdrawal events for specific currency.'
) )
.action(async (type, currency, amount) => { .action(async (type, currency, amount) => {
if (program.onlyrpc) {
privateRpc = true;
}
console.log("Starting event sync command"); console.log("Starting event sync command");
currency = currency.toLowerCase(); currency = currency.toLowerCase();
torPort = program.tor; torPort = program.tor;
@ -1456,6 +1489,7 @@ async function main() {
.command('test') .command('test')
.description('Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.') .description('Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.')
.action(async () => { .action(async () => {
privateRpc = true;
console.log('Start performing ETH deposit-withdraw test'); console.log('Start performing ETH deposit-withdraw test');
let currency = 'eth'; let currency = 'eth';
let amount = '0.1'; let amount = '0.1';