diff --git a/assets/events.worker.js b/assets/events.worker.js index 6639c4d..1f886eb 100644 --- a/assets/events.worker.js +++ b/assets/events.worker.js @@ -1,18 +1,16 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ +import { AES, HmacSHA256, enc } from 'crypto-js' +import { isEmpty } from 'lodash' +import { BigNumber, Contract } from 'ethers' +import { poseidon } from '@tornado/circomlib' +import { decrypt } from 'eth-sig-util' -const { AES, HmacSHA256, enc } = require('crypto-js') -const { isEmpty } = require('lodash') -const { BigNumber, Contract } = require('ethers') -const { poseidon } = require('@tornado/circomlib') -const { decrypt } = require('eth-sig-util') - -const { IndexedDB } = require('./services/idb') -const { BatchEventsService } = require('./services/batch') -const { getAllCommitments } = require('./services/graph') -const { ExtendedProvider } = require('./services/provider') -const { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST, workerEvents, numbers } = require('./services/constants') -const { sleep } = require('./services/utilities') -const { poolAbi } = require('./services/pool') +import { IndexedDB } from './services/idb' +import { BatchEventsService } from './services/batch' +import { getAllCommitments } from './services/graph' +import { ExtendedProvider } from './services/provider' +import { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST, workerEvents, numbers } from './services/constants' +import { sleep } from './services/utilities' +import { poolAbi } from './services/pool' const getProviderWithSigner = (chainId) => { return new ExtendedProvider(RPC_LIST[chainId], chainId, FALLBACK_RPC_LIST[chainId]) @@ -103,7 +101,7 @@ const getCommitmentBatch = async ({ blockFrom, blockTo, cachedEvents, withCache }) events.push(...graphEvents) - blockFrom = lastSyncBlock + numbers.ONE + blockFrom = lastSyncBlock } if (!blockTo || blockTo > blockFrom) { diff --git a/assets/nullifier.worker.js b/assets/nullifier.worker.js index e24d100..7bcf302 100644 --- a/assets/nullifier.worker.js +++ b/assets/nullifier.worker.js @@ -1,14 +1,13 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ -const { isEmpty } = require('lodash') -const { BigNumber, Contract } = require('ethers') +import { isEmpty } from 'lodash' +import { BigNumber, Contract } from 'ethers' -const { IndexedDB } = require('./services/idb') -const { BatchEventsService } = require('./services/batch') -const { getAllNullifiers } = require('./services/graph') -const { ExtendedProvider } = require('./services/provider') -const { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST, workerEvents, numbers } = require('./services/constants') -const { sleep } = require('./services/utilities') -const { poolAbi } = require('./services/pool') +import { IndexedDB } from './services/idb' +import { BatchEventsService } from './services/batch' +import { getAllNullifiers } from './services/graph' +import { ExtendedProvider } from './services/provider' +import { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST, workerEvents, numbers } from './services/constants' +import { sleep } from './services/utilities' +import { poolAbi } from './services/pool' const getProviderWithSigner = (chainId) => { return new ExtendedProvider(RPC_LIST[chainId], chainId, FALLBACK_RPC_LIST[chainId]) @@ -138,7 +137,7 @@ const getNullifiers = async (blockFrom) => { }) events.push(...graphEvents) - blockFrom = lastSyncBlock + numbers.ONE + blockFrom = lastSyncBlock } let nodeEvents = await self.BatchEventsService.getBatchEvents({ diff --git a/assets/services/batch.js b/assets/services/batch.js index cf29211..e0030ab 100644 --- a/assets/services/batch.js +++ b/assets/services/batch.js @@ -1,6 +1,6 @@ -const { sleep, getBatches } = require('./utilities') +import { sleep, getBatches } from './utilities' -class BatchEventsService { +export class BatchEventsService { constructor({ provider, contract, @@ -82,6 +82,4 @@ class BatchEventsService { return events; } -} - -module.exports = { BatchEventsService } \ No newline at end of file +} \ No newline at end of file diff --git a/assets/services/bridgeHelper.js b/assets/services/bridgeHelper.js new file mode 100644 index 0000000..ee69d62 --- /dev/null +++ b/assets/services/bridgeHelper.js @@ -0,0 +1,237 @@ +export const bridgeAbi = [ + { + inputs: [ + { + internalType: "contract IOmnibridge", + name: "_bridge", + type: "address", + }, + { + internalType: "contract IWETH", + name: "_weth", + type: "address", + }, + { + internalType: "address", + name: "_owner", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "bytes", + name: "key", + type: "bytes", + }, + ], + name: "PublicKey", + type: "event", + }, + { + inputs: [], + name: "WETH", + outputs: [ + { + internalType: "contract IWETH", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "bridge", + outputs: [ + { + internalType: "contract IOmnibridge", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_token", + type: "address", + }, + { + internalType: "address", + name: "_to", + type: "address", + }, + ], + name: "claimTokens", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_token", + type: "address", + }, + { + internalType: "uint256", + name: "_value", + type: "uint256", + }, + { + internalType: "bytes", + name: "_data", + type: "bytes", + }, + ], + name: "onTokenBridged", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "bytes", + name: "publicKey", + type: "bytes", + }, + ], + internalType: "struct L1Helper.Account", + name: "_account", + type: "tuple", + }, + ], + name: "register", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "wrapAndRelayTokens", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_receiver", + type: "address", + }, + { + internalType: "bytes", + name: "_data", + type: "bytes", + }, + ], + name: "wrapAndRelayTokens", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_receiver", + type: "address", + }, + { + internalType: "bytes", + name: "_data", + type: "bytes", + }, + { + components: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "bytes", + name: "publicKey", + type: "bytes", + }, + ], + internalType: "struct L1Helper.Account", + name: "_account", + type: "tuple", + }, + ], + name: "wrapAndRelayTokens", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_receiver", + type: "address", + }, + ], + name: "wrapAndRelayTokens", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + stateMutability: "payable", + type: "receive", + }, +] \ No newline at end of file diff --git a/assets/services/constants.js b/assets/services/constants.js index 15aacfc..3656b00 100644 --- a/assets/services/constants.js +++ b/assets/services/constants.js @@ -1,40 +1,40 @@ -const BSC_CHAIN_ID = 56 -const XDAI_CHAIN_ID = 100 -const MAINNET_CHAIN_ID = 1 +export const BSC_CHAIN_ID = 56 +export const XDAI_CHAIN_ID = 100 +export const MAINNET_CHAIN_ID = 1 -const ChainId = { +export const ChainId = { BSC: BSC_CHAIN_ID, XDAI: XDAI_CHAIN_ID, MAINNET: MAINNET_CHAIN_ID, } -const OFFCHAIN_ORACLE_CONTRACT = '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb' +export const OFFCHAIN_ORACLE_CONTRACT = '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb' -const POOL_CONTRACT = { +export const POOL_CONTRACT = { [ChainId.XDAI]: '0xD692Fd2D0b2Fbd2e52CFa5B5b9424bC981C30696', // ETH // [ChainId.XDAI]: '0x772F007F13604ac286312C85b9Cd9B2D691B353E', // BNB } -const REDGISTRY_CONTRACT = { +export const REDGISTRY_CONTRACT = { [ChainId.MAINNET]: '0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2', } -const AGGREGATOR_FACTORY = { +export const AGGREGATOR_FACTORY = { [ChainId.MAINNET]: '0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49', } -const WRAPPED_TOKEN = { +export const WRAPPED_TOKEN = { [ChainId.MAINNET]: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH on mainnet [ChainId.XDAI]: '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', // WETH on xdai [ChainId.BSC]: '0xCa8d20f3e0144a72C6B5d576e9Bd3Fd8557E2B04', // WBNB on xdai } -const RPC_LIST = { +export const RPC_LIST = { [ChainId.BSC]: 'https://tornadocash-rpc.com/bsc', [ChainId.MAINNET]: 'https://tornadocash-rpc.com/mainnet', [ChainId.XDAI]: 'https://tornadocash-rpc.com/gnosis', } -const FALLBACK_RPC_LIST = { +export const FALLBACK_RPC_LIST = { [ChainId.BSC]: [ 'https://binance.nodereal.io', // 'https://rpc.ankr.com/bsc/dbe08b852ba176a8aeac783cc1fa8becaf4f107235dfdae79241063fbf52ca4a', @@ -49,52 +49,98 @@ const FALLBACK_RPC_LIST = { ], } -const RPC_WS_LIST = { +export const RPC_WS_LIST = { [ChainId.MAINNET]: 'wss://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607', [ChainId.BSC]: 'wss://bsc-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607', [ChainId.XDAI]: 'wss://gnosis-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607', } -const MULTICALL = { +export const MULTICALL = { [ChainId.BSC]: '0xf072f255A3324198C7F653237B44E1C4e66f8C42', [ChainId.XDAI]: '0x8677b93D543d0217B32B8FDc20F2316E138D619B', [ChainId.MAINNET]: '0x1F98415757620B543A52E61c46B32eB19261F984', } -const BRIDGE_PROXY = { +export const BRIDGE_PROXY = { [ChainId.BSC]: '0x05185872898b6f94AA600177EF41B9334B1FA48B', [ChainId.MAINNET]: '0x4c36d2919e407f0cc2ee3c993ccf8ac26d9ce64e', } -const AMB_BRIDGE = { +export const AMB_BRIDGE = { [ChainId.XDAI]: '0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59', // ETH // [ChainId.XDAI]: '0x162E898bD0aacB578C8D5F8d6ca588c13d2A383F', // BNB [ChainId.MAINNET]: '0x162E898bD0aacB578C8D5F8d6ca588c13d2A383F', } -const BRIDGE_HELPER = { +export const BRIDGE_HELPER = { [ChainId.MAINNET]: '0xCa0840578f57fE71599D29375e16783424023357', [ChainId.BSC]: '0x8845F740F8B01bC7D9A4C82a6fD4A60320c07AF1', } -const BRIDGE_FEE_MANAGER = { +export const BRIDGE_FEE_MANAGER = { [ChainId.XDAI]: '0x5dbC897aEf6B18394D845A922BF107FA98E3AC55', } -const FOREIGN_OMNIBRIDGE = { +export const FOREIGN_OMNIBRIDGE = { [ChainId.MAINNET]: '0x88ad09518695c6c3712AC10a214bE5109a655671', } -const OMNIBRIDGE = { +export const OMNIBRIDGE = { [ChainId.XDAI]: '0xf6A78083ca3e2a662D6dd1703c939c8aCE2e268d', } -const SANCTION_LIST = { +export const SANCTION_LIST = { [ChainId.MAINNET]: '0x40C57923924B5c5c5455c48D93317139ADDaC8fb', } +export const CHAINS = { + [ChainId.XDAI]: { + symbol: 'XDAI', + name: 'xdai', + shortName: 'xdai', + icon: 'ethereum', + network: 'XDAI', + blockDuration: 3000, // ms + deployBlock: 19097755, // ETH + // deployBlock: 20446605, // BNB + blockGasLimit: 144000000, // rpc block gas limit + hexChainId: '0x64', + isEipSupported: false, + ensSubdomainKey: 'gnosis-nova', + blockExplorerUrl: 'https://gnosisscan.io' + }, + [ChainId.MAINNET]: { + symbol: 'ETH', + name: 'ethereum', + shortName: 'eth', + icon: 'ethereum', + network: 'Mainnet', + deployBlock: 13494216, + blockDuration: 15000, + blockGasLimit: 144000000, + hexChainId: '0x1', + isEipSupported: true, + ensSubdomainKey: 'mainnet-tornado', + blockExplorerUrl: 'https://etherscan.io' + }, + [ChainId.BSC]: { + symbol: 'BNB', + name: 'bsc', + shortName: 'bsc', + icon: 'binance', + network: 'BSC', + deployBlock: 14931075, + blockDuration: 3000, + blockGasLimit: 144000000, + hexChainId: '0x38', + isEipSupported: false, + ensSubdomainKey: 'bsc-tornado', + blockExplorerUrl: 'https://bscscan.com' + }, +} -const workerEvents = { + +export const workerEvents = { INIT_WORKER: 'initWorker', GET_COMMITMENT_EVENTS: 'get_commitment_events', // nullifier @@ -112,7 +158,7 @@ const workerEvents = { SAVE_LAST_SYNC_BLOCK: 'save_last_sync_block', } -const numbers = { +export const numbers = { ZERO: 0, TWO: 2, ONE: 1, @@ -128,13 +174,4 @@ const numbers = { DECRYPT_WORKERS_COUNT: 8, MIN_BLOCKS_INTERVAL_LINE: 200000, EPHEM_PUBLIC_KEY_BUF_LENGTH: 56, -} - -module.exports = { - ChainId, - POOL_CONTRACT, - RPC_LIST, - FALLBACK_RPC_LIST, - workerEvents, - numbers -} +} \ No newline at end of file diff --git a/assets/services/graph/index.js b/assets/services/graph/index.js index e1ba4b3..1b52991 100644 --- a/assets/services/graph/index.js +++ b/assets/services/graph/index.js @@ -1,8 +1,11 @@ -const { isEmpty } = require('lodash') -const { ApolloClient, InMemoryCache, gql } = require('@apollo/client/core') +import { isEmpty } from 'lodash' +import { ApolloClient, InMemoryCache, gql } from '@apollo/client/core' +import { utils } from 'ethers' -const { GET_COMMITMENT, GET_NULLIFIER } = require('./queries') -const { ChainId, numbers } = require('../constants') +import { GET_ACCOUNTS, GET_COMMITMENT, GET_NULLIFIER } from './queries' +import { ChainId, numbers } from '../constants' + +const { getAddress } = utils const first = 1000 const breakLength = 900 @@ -23,7 +26,91 @@ const client = new ApolloClient({ cache: new InMemoryCache(), }) -async function getCommitments({ fromBlock, chainId }) { +export async function getAccounts({ fromBlock, chainId }) { + const { data } = await client.query({ + context: { + chainId, + }, + query: gql(GET_ACCOUNTS), + variables: { first, fromBlock }, + }) + + if (!data) { + return { + results: [], + lastSyncBlock: data._meta.block.number + } + } + + return { + results: data.accounts, + lastSyncBlock: data._meta.block.number + } +} + +export async function getAllAccounts({ fromBlock, toBlock, chainId }) { + try { + let accounts = [] + let lastSyncBlock + + while (true) { + let { results, lastSyncBlock: lastBlock } = await getAccounts({ fromBlock, chainId }) + + lastSyncBlock = lastBlock + + if (isEmpty(results)) { + break + } + + if (results.length < breakLength) { + accounts = accounts.concat(results) + break + } + + const [lastEvent] = results.slice(-numbers.ONE) + + results = results.filter((e) => e.blockNumber !== lastEvent.blockNumber) + fromBlock = Number(lastEvent.blockNumber) + + accounts = accounts.concat(results) + + if (toBlock && fromBlock >= Number(toBlock)) { + break + } + } + + if (!accounts) { + return { + lastSyncBlock, + events: [], + } + } + + const data = accounts.map((e) => ({ + key: e.key, + owner: getAddress(e.owner), + blockNumber: Number(e.blockNumber), + })) + + const [lastEvent] = data.slice(-numbers.ONE) + + return { + events: data, + lastSyncBlock: (lastEvent && lastEvent.blockNumber >= lastSyncBlock) + ? lastEvent.blockNumber + numbers.ONE + : lastSyncBlock, + } + } catch (err) { + console.log('Error from getAllAccounts') + console.log(err) + return { + lastSyncBlock: '', + events: [], + } + } +} + +export async function getCommitments({ fromBlock, chainId }) { const { data } = await client.query({ context: { chainId, @@ -45,7 +132,7 @@ async function getCommitments({ fromBlock, chainId }) { } } -async function getAllCommitments({ fromBlock, toBlock, chainId }) { +export async function getAllCommitments({ fromBlock, toBlock, chainId }) { try { let commitments = [] let lastSyncBlock @@ -83,19 +170,21 @@ async function getAllCommitments({ fromBlock, toBlock, chainId }) { } } - const data = commitments.map((e) => ({ - index: Number(e.index), - commitment: e.commitment, - blockNumber: Number(e.blockNumber), - encryptedOutput: e.encryptedOutput, - transactionHash: e.transactionHash - })) + const data = commitments + .map((e) => ({ + blockNumber: Number(e.blockNumber), + transactionHash: e.transactionHash, + index: Number(e.index), + commitment: e.commitment, + encryptedOutput: e.encryptedOutput + })) + .sort((a, b) => a.index - b.index) const [lastEvent] = data.slice(-numbers.ONE) return { events: data, - lastSyncBlock: (lastEvent && lastEvent.blockNumber > lastSyncBlock) + lastSyncBlock: (lastEvent && lastEvent.blockNumber >= lastSyncBlock) ? lastEvent.blockNumber + numbers.ONE : lastSyncBlock, } @@ -109,7 +198,7 @@ async function getAllCommitments({ fromBlock, toBlock, chainId }) { } } -async function getNullifiers({ fromBlock, chainId }) { +export async function getNullifiers({ fromBlock, chainId }) { const { data } = await client.query({ context: { chainId, @@ -131,7 +220,7 @@ async function getNullifiers({ fromBlock, chainId }) { } } -async function getAllNullifiers({ fromBlock, chainId }) { +export async function getAllNullifiers({ fromBlock, chainId }) { try { let nullifiers = [] let lastSyncBlock @@ -175,7 +264,7 @@ async function getAllNullifiers({ fromBlock, chainId }) { return { events: data, - lastSyncBlock: (lastEvent && lastEvent.blockNumber > lastSyncBlock) + lastSyncBlock: (lastEvent && lastEvent.blockNumber >= lastSyncBlock) ? lastEvent.blockNumber + numbers.ONE : lastSyncBlock, } @@ -187,9 +276,4 @@ async function getAllNullifiers({ fromBlock, chainId }) { events: [], } } -} - -module.exports = { - getAllCommitments, - getAllNullifiers } \ No newline at end of file diff --git a/assets/services/graph/queries.js b/assets/services/graph/queries.js index a1964e7..41ec38a 100644 --- a/assets/services/graph/queries.js +++ b/assets/services/graph/queries.js @@ -1,4 +1,23 @@ -const GET_COMMITMENT = ` +export const GET_ACCOUNTS = ` + query getAccounts($first: Int, $fromBlock: Int) { + accounts(first: $first, orderBy: blockNumber, orderDirection: asc, where: { + blockNumber_gte: $fromBlock + }) { + id + key + owner + blockNumber + } + _meta { + block { + number + } + hasIndexingErrors + } + } +` + +export const GET_COMMITMENT = ` query getCommitment($first: Int, $fromBlock: Int) { commitments(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock @@ -18,7 +37,7 @@ const GET_COMMITMENT = ` } ` -const GET_NULLIFIER = ` +export const GET_NULLIFIER = ` query getNullifier($first: Int, $fromBlock: Int) { nullifiers(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock @@ -34,6 +53,4 @@ const GET_NULLIFIER = ` hasIndexingErrors } } -` - -module.exports = { GET_COMMITMENT, GET_NULLIFIER } \ No newline at end of file +` \ No newline at end of file diff --git a/assets/services/idb.js b/assets/services/idb.js index ae52f88..67c49c3 100644 --- a/assets/services/idb.js +++ b/assets/services/idb.js @@ -1,12 +1,12 @@ -const { deleteDB, openDB } = require('idb') +import { deleteDB, openDB } from 'idb' -const VERSION_ERROR = 'less than the existing version' -const INDEX_DB_ERROR = 'A mutation operation was attempted on a database that did not allow mutations.' +export const VERSION_ERROR = 'less than the existing version' +export const INDEX_DB_ERROR = 'A mutation operation was attempted on a database that did not allow mutations.' -const IDB_VERSION = 9 +export const IDB_VERSION = 9 // TODO method for migration, remove indexed -class IndexedDB { +export class IndexedDB { constructor({ stores, dbName }) { this.dbExists = false this.isBlocked = false @@ -220,5 +220,3 @@ class IndexedDB { } } } - -module.exports = { IndexedDB } diff --git a/assets/services/pool.js b/assets/services/pool.js index 8ea7639..dcd750e 100644 --- a/assets/services/pool.js +++ b/assets/services/pool.js @@ -1,4 +1,4 @@ -const poolAbi = [ +export const poolAbi = [ { inputs: [ { @@ -1037,6 +1037,4 @@ const poolAbi = [ stateMutability: "pure", type: "function", }, -] - -module.exports = { poolAbi } \ No newline at end of file +] \ No newline at end of file diff --git a/assets/services/provider.js b/assets/services/provider.js index f8975ab..5d8741c 100644 --- a/assets/services/provider.js +++ b/assets/services/provider.js @@ -1,10 +1,10 @@ -const { ethers } = require('ethers') -const { fetchJson } = require('@ethersproject/web') -const { numbers } = require('./constants') +import { ethers } from 'ethers' +import { fetchJson } from 'ethers/lib/utils' +import { numbers } from './constants' const defaultRetryAttempt = 0 -class ExtendedProvider extends ethers.providers.StaticJsonRpcProvider { +export class ExtendedProvider extends ethers.providers.StaticJsonRpcProvider { constructor(url, network, fallbackRpcs) { super(url, network) this.fallbackRpcs = fallbackRpcs @@ -83,6 +83,4 @@ class ExtendedProvider extends ethers.providers.StaticJsonRpcProvider { // return (data?.includes(ERROR_DATA) || message?.includes(ERROR_MESSAGE)) && code === ERROR_CODE // } -} - -module.exports = { ExtendedProvider } \ No newline at end of file +} \ No newline at end of file diff --git a/assets/services/utilities.js b/assets/services/utilities.js index 8e3203f..ad2e9b6 100644 --- a/assets/services/utilities.js +++ b/assets/services/utilities.js @@ -1,6 +1,6 @@ -const ZERO_ELEMENT = 0 +export const ZERO_ELEMENT = 0 -function getBatches(array, batchSize) { +export function getBatches(array, batchSize) { const batches = [] while (array.length) { batches.push(array.splice(ZERO_ELEMENT, batchSize)) @@ -8,12 +8,6 @@ function getBatches(array, batchSize) { return batches } -async function sleep(ms) { +export async function sleep(ms) { return await new Promise((resolve) => setTimeout(resolve, ms)) -} - -module.exports = { - ZERO_ELEMENT, - getBatches, - sleep } \ No newline at end of file diff --git a/assets/services/zip.js b/assets/services/zip.js new file mode 100644 index 0000000..fb9da92 --- /dev/null +++ b/assets/services/zip.js @@ -0,0 +1,25 @@ +import { zip, unzip } from 'fflate' + +export function zipAsync(file) { + return new Promise((res, rej) => { + zip(file, { mtime: new Date('1/1/1980') }, (err, data) => { + if (err) { + rej(err); + return; + } + res(data); + }); + }); +} + +export function unzipAsync(data) { + return new Promise((res, rej) => { + unzip(data, {}, (err, data) => { + if (err) { + rej(err); + return; + } + res(data); + }); + }); +} \ No newline at end of file diff --git a/assets/syncEvents.js b/assets/syncEvents.js new file mode 100644 index 0000000..28c34db --- /dev/null +++ b/assets/syncEvents.js @@ -0,0 +1,285 @@ +import path from 'path' +import { stat, readFile, writeFile } from 'fs/promises' +import { Contract, providers, utils } from 'ethers' + +import { BatchEventsService } from './services/batch' +import { getAllAccounts, getAllCommitments, getAllNullifiers } from './services/graph' +import { POOL_CONTRACT, BRIDGE_HELPER, RPC_LIST, ChainId, CHAINS, numbers } from './services/constants' +import { zipAsync, unzipAsync } from './services/zip' +import { poolAbi } from './services/pool' +import { bridgeAbi } from './services/bridgeHelper' + +const { getAddress } = utils +const { StaticJsonRpcProvider } = providers + +const EVENT_PATH = './static' + +async function existsAsync(fileOrDir) { + try { + await stat(fileOrDir); + + return true; + } catch { + return false; + } +} + +const getProvider = (chainId) => { + return new StaticJsonRpcProvider({ skipFetchSetup: true, url: RPC_LIST[chainId] }, chainId) +} + +const getTornadoPool = (chainId, provider) => { + const TornadoPool = new Contract(POOL_CONTRACT[chainId], poolAbi, provider) + + return { + TornadoPool, + BatchEventsService: new BatchEventsService({ + provider, + contract: TornadoPool + }) + } +} + +const getBridgeHelper = (chainId, provider) => { + const BridgeHelper = new Contract(BRIDGE_HELPER[chainId], bridgeAbi, provider) + + return { + BridgeHelper, + BridgeEventsService: new BatchEventsService({ + provider, + contract: BridgeHelper + }) + } +} + +const loadEvents = async (fileName, deployedBlock) => { + fileName = fileName.toLowerCase() + + const filePath = path.join(EVENT_PATH, fileName + '.zip') + + if (!(await existsAsync(filePath))) { + return { + events: [], + lastBlock: deployedBlock + } + } + + try { + const data = await readFile(filePath) + const { [fileName]: content } = await unzipAsync(data) + + const events = JSON.parse(new TextDecoder().decode(content)) + + const lastBlock = events && Array.isArray(events) && events[events.length - 1] + ? events[events.length - 1].blockNumber + : deployedBlock + + return { + events, + lastBlock + } + } catch { + return { + events: [], + lastBlock: deployedBlock + } + } +} + +const saveEvents = async (fileName, events) => { + fileName = fileName.toLowerCase() + + const filePath = path.join(EVENT_PATH, fileName + '.zip') + + const payload = await zipAsync({ + [fileName]: new TextEncoder().encode(JSON.stringify(events, null, 2) + '\n') + }) + + await writeFile(filePath, payload) +} + +const syncAccounts = async (chainId, BatchEventsService) => { + const fileName = `accounts_${chainId}.json` + + console.log(`Syncing ${fileName}`) + + const cachedEvents = await loadEvents(fileName, CHAINS[chainId].deployBlock) + + const events = [...cachedEvents.events] + let fromBlock = cachedEvents.lastBlock + numbers.ONE + + console.log({ + cachedEvents: events.length, + cachedBlock: fromBlock + }) + + const { events: graphEvents, lastSyncBlock } = await getAllAccounts({ + fromBlock, + chainId + }) + + console.log({ + graphEvents: graphEvents.length, + graphBlock: lastSyncBlock + }) + + if (lastSyncBlock) { + events.push(...graphEvents) + fromBlock = lastSyncBlock + } + + let nodeEvents = await BatchEventsService.getBatchEvents({ + fromBlock, + type: 'PublicKey' + }) + + console.log({ + nodeEvents: nodeEvents.length, + nodeBlock: nodeEvents && nodeEvents[nodeEvents.length - 1] ? nodeEvents[nodeEvents.length - 1].blockNumber : undefined + }) + + if (nodeEvents && nodeEvents.length) { + nodeEvents = nodeEvents.map(({ blockNumber, args }) => ({ + key: args.key, + owner: getAddress(args.owner), + blockNumber, + })) + + events.push(...nodeEvents) + } + + await saveEvents(fileName, events) +} + +const syncCommitments = async (chainId, BatchEventsService) => { + const fileName = `commitments_${chainId}.json` + + console.log(`Syncing ${fileName}`) + + const cachedEvents = await loadEvents(fileName, CHAINS[chainId].deployBlock) + + const events = [...cachedEvents.events] + let fromBlock = cachedEvents.lastBlock + numbers.ONE + + console.log({ + cachedEvents: events.length, + cachedBlock: fromBlock + }) + + const { events: graphEvents, lastSyncBlock } = await getAllCommitments({ + fromBlock, + chainId + }) + + console.log({ + graphEvents: graphEvents.length, + graphBlock: lastSyncBlock + }) + + if (lastSyncBlock) { + events.push(...graphEvents) + fromBlock = lastSyncBlock + } + + let nodeEvents = await BatchEventsService.getBatchEvents({ + fromBlock, + type: 'NewCommitment' + }) + + console.log({ + nodeEvents: nodeEvents.length, + nodeBlock: nodeEvents && nodeEvents[nodeEvents.length - 1] ? nodeEvents[nodeEvents.length - 1].blockNumber : undefined + }) + + if (nodeEvents && nodeEvents.length) { + nodeEvents = nodeEvents.map(({ blockNumber, transactionHash, args }) => ({ + blockNumber, + transactionHash, + index: Number(args.index), + commitment: args.commitment, + encryptedOutput: args.encryptedOutput, + })) + + events.push(...nodeEvents) + } + + await saveEvents(fileName, events) +} + +const syncNullifiers = async (chainId, BatchEventsService) => { + const fileName = `nullifiers_${chainId}.json` + + console.log(`Syncing ${fileName}`) + + const cachedEvents = await loadEvents(fileName, CHAINS[chainId].deployBlock) + + const events = [...cachedEvents.events] + let fromBlock = cachedEvents.lastBlock + numbers.ONE + + console.log({ + cachedEvents: events.length, + cachedBlock: fromBlock + }) + + const { events: graphEvents, lastSyncBlock } = await getAllNullifiers({ + fromBlock, + chainId + }) + + console.log({ + graphEvents: graphEvents.length, + graphBlock: lastSyncBlock + }) + + if (lastSyncBlock) { + events.push(...graphEvents) + fromBlock = lastSyncBlock + } + + let nodeEvents = await BatchEventsService.getBatchEvents({ + fromBlock, + type: 'NewNullifier' + }) + + console.log({ + nodeEvents: nodeEvents.length, + nodeBlock: nodeEvents && nodeEvents[nodeEvents.length - 1] ? nodeEvents[nodeEvents.length - 1].blockNumber : undefined + }) + + if (nodeEvents && nodeEvents.length) { + nodeEvents = nodeEvents.map(({ blockNumber, transactionHash, args }) => ({ + blockNumber, + transactionHash, + nullifier: args.nullifier, + })) + + events.push(...nodeEvents) + } + + await saveEvents(fileName, events) +} + +const main = async () => { + const chainId = ChainId.XDAI + + const ethChainId = ChainId.MAINNET + + const provider = getProvider(chainId) + + const ethProvider = getProvider(ethChainId) + + const { BatchEventsService } = getTornadoPool(chainId, provider) + + const { BridgeEventsService } = getBridgeHelper(ethChainId, ethProvider) + + console.log(`Connected with ${chainId}: (block: ${await provider.getBlockNumber()})`) + + console.log(`Connected with ${ethChainId}: (block: ${await ethProvider.getBlockNumber()})`) + + await syncAccounts(ethChainId, BridgeEventsService) + + await syncCommitments(chainId, BatchEventsService) + + await syncNullifiers(chainId, BatchEventsService) +} +main() \ No newline at end of file diff --git a/package.json b/package.json index 4790037..35889cb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "generate": "yarn worker:compile && nuxt generate && yarn copyFile dist/404.html dist/ipfs-404.html", "prepare": "husky install", "ipfs:upload": "node --loader ts-node/esm ipfsUpload.ts", - "worker:compile": "webpack" + "worker:compile": "webpack", + "update:events": "webpack && node ./syncEvents.cjs" }, "dependencies": { "@apollo/client": "^3.4.16", @@ -73,6 +74,7 @@ "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-promise": "^5.1.0", "eslint-plugin-vue": "^7.16.0", + "fflate": "^0.8.2", "form-data": "^4.0.0", "husky": "^6.0.0", "lint-staged": "10.2.11", diff --git a/services/graph/index.ts b/services/graph/index.ts index a60b6de..06e7149 100644 --- a/services/graph/index.ts +++ b/services/graph/index.ts @@ -231,13 +231,15 @@ export async function getAllCommitments({ fromBlock, chainId }: Params) { } } - const data = commitments.map((e) => ({ - index: Number(e.index), - commitment: e.commitment, - blockNumber: Number(e.blockNumber), - encryptedOutput: e.encryptedOutput, - transactionHash: e.transactionHash - })) + const data = commitments + .map((e) => ({ + index: Number(e.index), + commitment: e.commitment, + blockNumber: Number(e.blockNumber), + encryptedOutput: e.encryptedOutput, + transactionHash: e.transactionHash + })) + .sort((a, b) => a.index - b.index) const [lastEvent] = data.slice(-numbers.ONE) diff --git a/static/accounts_1.json.zip b/static/accounts_1.json.zip new file mode 100644 index 0000000..d329c41 Binary files /dev/null and b/static/accounts_1.json.zip differ diff --git a/static/commitments_100.json.zip b/static/commitments_100.json.zip new file mode 100644 index 0000000..956d16d Binary files /dev/null and b/static/commitments_100.json.zip differ diff --git a/static/nullifiers_100.json.zip b/static/nullifiers_100.json.zip new file mode 100644 index 0000000..48e9726 Binary files /dev/null and b/static/nullifiers_100.json.zip differ diff --git a/webpack.config.js b/webpack.config.js index d57f5eb..b28ddb4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ import path from 'path' +import webpack from 'webpack' export default [ { @@ -16,5 +17,37 @@ export default [ path: path.resolve('static'), filename: 'nullifier.worker.js', } + }, + { + mode: 'production', + entry: './assets/syncEvents.js', + output: { + path: path.resolve('.'), + filename: 'syncEvents.cjs', + }, + target: 'node', + plugins: [ + new webpack.BannerPlugin({ + banner: '#!/usr/bin/env node\n', + raw: true + }) + ], + module: { + rules: [ + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto' + } + ] + }, + resolve: { + alias: { + 'fflate': 'fflate/esm' + } + }, + optimization: { + minimize: false, + } } ] \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 7da7bba..b1d8291 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6322,6 +6322,11 @@ ffjavascript@^0.2.48: wasmcurves "0.2.2" web-worker "^1.2.0" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + ffwasm@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ffwasm/-/ffwasm-0.0.7.tgz#23bb9a3537ecc87c0f24fcfb3a9ddd0e86855fff"