Fixed Subgraph Query Implementation

+ Change orderby to BlockNumber since using timestamp only gives timestamp of block
+ Added filter to exclude duplicated results
This commit is contained in:
Ayanami 2022-02-27 19:11:33 +09:00
parent f86c74b9fc
commit 0abc883b78
No known key found for this signature in database
GPG Key ID: 0CABDF03077D92E4
2 changed files with 68 additions and 39 deletions

View File

@ -121,9 +121,13 @@ Backed up invoice as ./backup-tornadoInvoice-eth-0.1-5-0x1b680c7d.txt
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. 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 ```bash
$ node cli.js depositInvoice <invoice> $ node cli.js depositInvoice <invoice> --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: ##### Example:
```bash ```bash
@ -144,9 +148,17 @@ Sender account balance is x.xxxxxxx ETH
#### To withdraw, you will need deposit note that matches with your deposit transaction. #### To withdraw, you will need deposit note that matches with your deposit transaction.
```bash ```bash
$ node cli.js withdraw <note> <recipient> $ 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: ##### Example:
```bash ```bash

91
cli.js
View 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));
@ -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]);
@ -308,7 +308,7 @@ async function generateMerkleProof(deposit, currency, amount) {
isValidRoot = await tornadoContract.methods.isKnownRoot(toHex(root)).call(); isValidRoot = await tornadoContract.methods.isKnownRoot(toHex(root)).call();
isSpent = await tornadoContract.methods.isSpent(toHex(deposit.nullifierHash)).call(); isSpent = await tornadoContract.methods.isSpent(toHex(deposit.nullifierHash)).call();
} }
assert(isValidRoot === true, 'Merkle tree is corrupted'); assert(isValidRoot === true, 'Merkle tree is corrupted. Some events could be missing, please try again.');
assert(isSpent === false, 'The note is already spent'); assert(isSpent === false, 'The note is already spent');
assert(leafIndex >= 0, 'The deposit is not found in the tree'); assert(leafIndex >= 0, 'The deposit is not found in the tree');
@ -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]);
@ -905,7 +905,7 @@ async function fetchEvents({ type, currency, amount }) {
options = { httpsAgent: new SocksProxyAgent('socks5h://127.0.0.1:' + torPort), headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' } }; options = { httpsAgent: new SocksProxyAgent('socks5h://127.0.0.1:' + torPort), headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' } };
} }
async function queryLatestTimestamp() { async function queryLatestBlockNumber() {
try { try {
const variables = { const variables = {
currency: currency.toString(), currency: currency.toString(),
@ -915,8 +915,8 @@ async function fetchEvents({ type, currency, amount }) {
const query = { const query = {
query: ` query: `
query($currency: String, $amount: String){ query($currency: String, $amount: String){
deposits(first: 1, orderBy: timestamp, orderDirection: desc, where: {currency: $currency, amount: $amount}) { deposits(first: 1, orderBy: blockNumber, orderDirection: desc, where: {currency: $currency, amount: $amount}) {
timestamp blockNumber
} }
} }
`, `,
@ -924,14 +924,14 @@ async function fetchEvents({ type, currency, amount }) {
} }
const querySubgraph = await axios.post(subgraph, query, options); const querySubgraph = await axios.post(subgraph, query, options);
const queryResult = querySubgraph.data.data.deposits; const queryResult = querySubgraph.data.data.deposits;
const result = queryResult[0].timestamp; const result = queryResult[0].blockNumber;
return Number(result); return Number(result);
} else { } else {
const query = { const query = {
query: ` query: `
query($currency: String, $amount: String){ query($currency: String, $amount: String){
withdrawals(first: 1, orderBy: timestamp, orderDirection: desc, where: {currency: $currency, amount: $amount}) { withdrawals(first: 1, orderBy: blockNumber, orderDirection: desc, where: {currency: $currency, amount: $amount}) {
timestamp blockNumber
} }
} }
`, `,
@ -939,7 +939,7 @@ async function fetchEvents({ type, currency, amount }) {
} }
const querySubgraph = await axios.post(subgraph, query, options); const querySubgraph = await axios.post(subgraph, query, options);
const queryResult = querySubgraph.data.data.withdrawals; const queryResult = querySubgraph.data.data.withdrawals;
const result = queryResult[0].timestamp; const result = queryResult[0].blockNumber;
return Number(result); return Number(result);
} }
} catch (error) { } catch (error) {
@ -947,18 +947,18 @@ async function fetchEvents({ type, currency, amount }) {
} }
} }
async function queryFromGraph(timestamp) { async function queryFromGraph(blockNumber) {
try { try {
const variables = { const variables = {
currency: currency.toString(), currency: currency.toString(),
amount: amount.toString(), amount: amount.toString(),
timestamp: timestamp blockNumber: blockNumber
} }
if (type === "deposit") { if (type === "deposit") {
const query = { const query = {
query: ` query: `
query($currency: String, $amount: String, $timestamp: Int){ query($currency: String, $amount: String, $blockNumber: Int){
deposits(orderBy: timestamp, first: 1000, where: {currency: $currency, amount: $amount, timestamp_gt: $timestamp}) { deposits(orderBy: blockNumber, first: 1000, where: {currency: $currency, amount: $amount, blockNumber_gte: $blockNumber}) {
blockNumber blockNumber
transactionHash transactionHash
commitment commitment
@ -984,8 +984,8 @@ async function fetchEvents({ type, currency, amount }) {
} else { } else {
const query = { const query = {
query: ` query: `
query($currency: String, $amount: String, $timestamp: Int){ query($currency: String, $amount: String, $blockNumber: Int){
withdrawals(orderBy: timestamp, first: 1000, where: {currency: $currency, amount: $amount, timestamp_gt: $timestamp}) { withdrawals(orderBy: blockNumber, first: 1000, where: {currency: $currency, amount: $amount, blockNumber_gte: $blockNumber}) {
blockNumber blockNumber
transactionHash transactionHash
nullifier nullifier
@ -1016,9 +1016,25 @@ async function fetchEvents({ type, currency, amount }) {
async function updateCache(fetchedEvents) { async function updateCache(fetchedEvents) {
try { try {
let events = [];
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`; const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`;
const localEvents = await initJson(fileName); const localEvents = await initJson(fileName);
const events = localEvents.concat(fetchedEvents); const totalEvents = localEvents.concat(fetchedEvents);
if (type === "deposit") {
const commit = new Set();
events = totalEvents.filter((r) => {
const notSameCommit = commit.has(r.commitment);
commit.add(r.commitment);
return !notSameCommit;
});
} else {
const nullifi = new Set();
events = totalEvents.filter((r) => {
const notSameNull = nullifi.has(r.nullifierHash);
nullifi.add(r.nullifierHash);
return !notSameNull;
});
}
await fs.writeFileSync(fileName, JSON.stringify(events, null, 2), 'utf8'); await fs.writeFileSync(fileName, JSON.stringify(events, null, 2), 'utf8');
} catch (error) { } catch (error) {
throw new Error('Writing cache file failed:',error); throw new Error('Writing cache file failed:',error);
@ -1027,29 +1043,30 @@ async function fetchEvents({ type, currency, amount }) {
async function fetchGraphEvents() { async function fetchGraphEvents() {
console.log("Querying latest events from TheGraph"); console.log("Querying latest events from TheGraph");
const latestTimestamp = await queryLatestTimestamp(); const latestBlockNumber = await queryLatestBlockNumber();
if (latestTimestamp) { if (latestBlockNumber) {
const getCachedBlock = await web3.eth.getBlock(startBlock); for (let i = startBlock; i < latestBlockNumber;) {
const cachedTimestamp = getCachedBlock.timestamp;
for (let i = cachedTimestamp; i < latestTimestamp;) {
const result = await queryFromGraph(i); const result = await queryFromGraph(i);
if (Object.keys(result).length === 0) { if (Object.keys(result).length < 20) {
i = latestTimestamp; const block = new Set();
} else { const filteredResult = result.filter((r) => {
if (type === "deposit") { const notSameBlock = block.has(r.blockNumber);
const resultBlock = result[result.length - 1].blockNumber; block.add(r.blockNumber);
const resultTimestamp = result[result.length - 1].timestamp; return !notSameBlock;
await updateCache(result); });
i = resultTimestamp; if (Object.keys(filteredResult).length === 1) {
console.log("Fetched", amount, currency.toUpperCase(), type, "events to block:", Number(resultBlock)); i = latestBlockNumber;
} else { } else {
const resultBlock = result[result.length - 1].blockNumber; const resultBlock = result[result.length - 1].blockNumber;
const getResultBlock = await web3.eth.getBlock(resultBlock);
const resultTimestamp = getResultBlock.timestamp;
await updateCache(result); await updateCache(result);
i = resultTimestamp; i = resultBlock;
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 {
const resultBlock = result[result.length - 1].blockNumber;
await updateCache(result);
i = resultBlock;
console.log("Fetched", amount, currency.toUpperCase(), type, "events to block:", Number(resultBlock));
} }
} }
} else { } else {
@ -1059,7 +1076,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();
@ -1069,7 +1086,7 @@ async function fetchEvents({ type, currency, amount }) {
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`; const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`;
const updatedEvents = await initJson(fileName); const updatedEvents = await initJson(fileName);
const updatedBlock = updatedEvents[updatedEvents.length - 1].blockNumber; const updatedBlock = updatedEvents[updatedEvents.length - 1].blockNumber;
console.log("Cache updated for Tornado",type,amount,currency,"instance to block",updatedBlock,"successfully"); console.log("Cache updated for Tornado", type, amount, currency.toUpperCase(), "instance to block", updatedBlock);
console.log(`Total ${type}s:`, updatedEvents.length); console.log(`Total ${type}s:`, updatedEvents.length);
return updatedEvents; return updatedEvents;
} }