diff --git a/lib/config.js b/lib/config.js index 0909da4..59c6f14 100644 --- a/lib/config.js +++ b/lib/config.js @@ -70,6 +70,6 @@ function getRelayerConfig() { cacheDir: path_1.default.join(STATIC_DIR, './events'), userEventsDir: path_1.default.join(USER_DIR, './events'), userTreeDir: path_1.default.join(USER_DIR, './trees'), - syncInterval: Number(process_1.default.env.SYNC_INTERVAL || 120), + syncInterval: Number(process_1.default.env.SYNC_INTERVAL || 180), }; } diff --git a/lib/services/events.d.ts b/lib/services/events.d.ts index 85fecd3..f4c4642 100644 --- a/lib/services/events.d.ts +++ b/lib/services/events.d.ts @@ -1,4 +1,4 @@ -import { BaseTornadoService, BaseEncryptedNotesService, BaseGovernanceService, BaseRegistryService, BaseTornadoServiceConstructor, BaseEncryptedNotesServiceConstructor, BaseGovernanceServiceConstructor, BaseRegistryServiceConstructor, BaseEchoServiceConstructor, BaseEchoService, CachedRelayers, BatchEventsService, BaseEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, EchoEvents, BatchEventServiceConstructor, BatchEventOnProgress, NetIdType, AllRelayerRegistryEvents, BaseRevenueService, BaseRevenueServiceConstructor, StakeBurnedEvents } from '@tornado/core'; +import { BaseTornadoService, BaseMultiTornadoService, BaseEncryptedNotesService, BaseGovernanceService, BaseRegistryService, BaseTornadoServiceConstructor, BaseMultiTornadoServiceConstructor, BaseEncryptedNotesServiceConstructor, BaseGovernanceServiceConstructor, BaseRegistryServiceConstructor, BaseEchoServiceConstructor, BaseEchoService, CachedRelayers, BatchEventsService, BaseEvents, DepositsEvents, WithdrawalsEvents, MultiDepositsEvents, MultiWithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, EchoEvents, BatchEventServiceConstructor, BatchEventOnProgress, NetIdType, AllRelayerRegistryEvents, BaseRevenueService, BaseRevenueServiceConstructor, StakeBurnedEvents, BatchBlockOnProgress, BatchBlockServiceConstructor, BatchBlockService, BatchTransactionService } from '@tornado/core'; import type { Logger } from 'winston'; import { TreeCache } from './treeCache'; export interface NodeEventsConstructor extends BatchEventServiceConstructor { @@ -12,6 +12,23 @@ export declare class NodeEventsService extends BatchEventsService { getInstanceName: () => string; constructor(serviceConstructor: NodeEventsConstructor); } +export interface NodeBlocksConstructor extends BatchBlockServiceConstructor { + netId: NetIdType; + logger: Logger; + getInstanceName: () => string; +} +export declare class NodeBlocksService extends BatchBlockService { + netId: NetIdType; + logger: Logger; + getInstanceName: () => string; + constructor(serviceConstructor: NodeBlocksConstructor); +} +export declare class NodeTransactionsService extends BatchTransactionService { + netId: NetIdType; + logger: Logger; + getInstanceName: () => string; + constructor(serviceConstructor: NodeBlocksConstructor); +} export interface NodeTornadoServiceConstructor extends BaseTornadoServiceConstructor { cacheDirectory: string; userDirectory: string; @@ -27,10 +44,12 @@ export declare class NodeTornadoService extends BaseTornadoService { treeCache?: TreeCache; constructor(serviceConstructor: NodeTornadoServiceConstructor); updateEventProgress({ fromBlock, toBlock, count }: Parameters[0]): void; + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]): void; + updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]): void; getEventsFromDB(): Promise>; getEventsFromCache(): Promise>; - validateEvents({ events, lastBlock, hasNewEvents, }: BaseEvents & { - hasNewEvents?: boolean; + validateEvents({ events, newEvents, lastBlock, }: BaseEvents & { + newEvents: (DepositsEvents | WithdrawalsEvents)[]; }): Promise; saveEvents({ events, lastBlock }: BaseEvents): Promise; updateEvents(): Promise<{ @@ -39,6 +58,37 @@ export declare class NodeTornadoService extends BaseTornadoService { validateResult: Awaited; }>; } +export interface NodeMultiTornadoServiceConstructor extends BaseMultiTornadoServiceConstructor { + cacheDirectory: string; + userDirectory: string; + nativeCurrency: string; + logger: Logger; + treeCache?: TreeCache; +} +export declare class NodeMultiTornadoService extends BaseMultiTornadoService { + cacheDirectory: string; + userDirectory: string; + nativeCurrency: string; + logger: Logger; + treeCache?: TreeCache; + constructor(serviceConstructor: NodeMultiTornadoServiceConstructor); + updateEventProgress({ fromBlock, toBlock, count }: Parameters[0]): void; + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]): void; + updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]): void; + getEventsFromDB(): Promise>; + getEventsFromCache(): Promise>; + validateEvents({ events, newEvents, lastBlock, }: BaseEvents & { + newEvents: (MultiDepositsEvents | MultiWithdrawalsEvents)[]; + }): Promise; + saveEvents({ events, newEvents, lastBlock, }: BaseEvents & { + newEvents: (MultiDepositsEvents | MultiWithdrawalsEvents)[]; + }): Promise; + updateEvents(): Promise<{ + events: (MultiDepositsEvents | MultiWithdrawalsEvents)[]; + lastBlock: number; + validateResult: Awaited; + }>; +} export interface NodeEchoServiceConstructor extends BaseEchoServiceConstructor { cacheDirectory: string; userDirectory: string; @@ -90,6 +140,7 @@ export declare class NodeGovernanceService extends BaseGovernanceService { logger: Logger; constructor(serviceConstructor: NodeGovernanceServiceConstructor); updateEventProgress({ fromBlock, toBlock, count }: Parameters[0]): void; + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]): void; getEventsFromDB(): Promise>; getEventsFromCache(): Promise>; saveEvents({ events, lastBlock }: BaseEvents): Promise; @@ -133,6 +184,8 @@ export declare class NodeRevenueService extends BaseRevenueService { logger: Logger; constructor(serviceConstructor: NodeRevenueServiceConstructor); updateEventProgress({ fromBlock, toBlock, count }: Parameters[0]): void; + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]): void; + updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]): void; getEventsFromDB(): Promise>; getEventsFromCache(): Promise>; saveEvents({ events, lastBlock }: BaseEvents): Promise; diff --git a/lib/services/events.js b/lib/services/events.js index 52032e0..2fde0e0 100644 --- a/lib/services/events.js +++ b/lib/services/events.js @@ -3,10 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.NodeRevenueService = exports.NodeRegistryService = exports.NodeGovernanceService = exports.NodeEncryptedNotesService = exports.NodeEchoService = exports.NodeTornadoService = exports.NodeEventsService = void 0; +exports.NodeRevenueService = exports.NodeRegistryService = exports.NodeGovernanceService = exports.NodeEncryptedNotesService = exports.NodeEchoService = exports.NodeMultiTornadoService = exports.NodeTornadoService = exports.NodeTransactionsService = exports.NodeBlocksService = exports.NodeEventsService = void 0; const path_1 = __importDefault(require("path")); const promises_1 = require("fs/promises"); const core_1 = require("@tornado/core"); +const contracts_1 = require("@tornado/contracts"); const data_1 = require("./data"); class NodeEventsService extends core_1.BatchEventsService { netId; @@ -20,6 +21,30 @@ class NodeEventsService extends core_1.BatchEventsService { } } exports.NodeEventsService = NodeEventsService; +class NodeBlocksService extends core_1.BatchBlockService { + netId; + logger; + getInstanceName; + constructor(serviceConstructor) { + super(serviceConstructor); + this.netId = serviceConstructor.netId; + this.logger = serviceConstructor.logger; + this.getInstanceName = serviceConstructor.getInstanceName; + } +} +exports.NodeBlocksService = NodeBlocksService; +class NodeTransactionsService extends core_1.BatchTransactionService { + netId; + logger; + getInstanceName; + constructor(serviceConstructor) { + super(serviceConstructor); + this.netId = serviceConstructor.netId; + this.logger = serviceConstructor.logger; + this.getInstanceName = serviceConstructor.getInstanceName; + } +} +exports.NodeTransactionsService = NodeTransactionsService; class NodeTornadoService extends core_1.BaseTornadoService { cacheDirectory; userDirectory; @@ -41,6 +66,20 @@ class NodeTornadoService extends core_1.BaseTornadoService { logger, getInstanceName: () => `${type.toLowerCase()}s_${netId}_${currency}_${amount}`, }); + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: () => `${type.toLowerCase()}s_${netId}_${currency}_${amount}`, + }); + this.batchBlockService = new NodeBlocksService({ + netId, + provider, + onProgress: this.updateBlockProgress, + logger, + getInstanceName: () => `${type.toLowerCase()}s_${netId}_${currency}_${amount}`, + }); this.treeCache = treeCache; } updateEventProgress({ fromBlock, toBlock, count }) { @@ -48,6 +87,16 @@ class NodeTornadoService extends core_1.BaseTornadoService { this.logger.debug(`${this.getInstanceName()}: Fetched ${count} events from ${fromBlock} to ${toBlock}`); } } + updateTransactionProgress({ currentIndex, totalIndex }) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} deposit txs of ${totalIndex}`); + } + } + updateBlockProgress({ currentIndex, totalIndex }) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} withdrawal blocks of ${totalIndex}`); + } + } async getEventsFromDB() { return await (0, data_1.loadSavedEvents)({ name: this.getInstanceName(), @@ -61,15 +110,21 @@ class NodeTornadoService extends core_1.BaseTornadoService { deployedBlock: this.deployedBlock, }); } - async validateEvents({ events, lastBlock, hasNewEvents, }) { + async validateEvents({ events, newEvents, lastBlock, }) { const tree = await super.validateEvents({ events, + newEvents, lastBlock, - hasNewEvents, }); if (tree && this.currency === this.nativeCurrency && this.treeCache) { const merkleTree = tree; - await this.treeCache.createTree(events, merkleTree); + await this.treeCache.createTree({ + netId: this.netId, + amount: this.amount, + currency: this.currency, + events: events, + tree: tree, + }); console.log(`${this.getInstanceName()}: Updated tree cache with root ${(0, core_1.toFixedHex)(BigInt(merkleTree.root))}\n`); } return tree; @@ -97,6 +152,186 @@ class NodeTornadoService extends core_1.BaseTornadoService { } } exports.NodeTornadoService = NodeTornadoService; +class NodeMultiTornadoService extends core_1.BaseMultiTornadoService { + cacheDirectory; + userDirectory; + nativeCurrency; + logger; + treeCache; + constructor(serviceConstructor) { + super(serviceConstructor); + const { netId, provider, cacheDirectory, userDirectory, nativeCurrency, logger, treeCache } = serviceConstructor; + this.cacheDirectory = cacheDirectory; + this.userDirectory = userDirectory; + this.nativeCurrency = nativeCurrency; + this.logger = logger; + this.batchEventsService = new NodeEventsService({ + netId, + provider, + contract: this.contract, + address: Object.keys(this.instances), + onProgress: this.updateEventProgress, + logger, + getInstanceName: this.getInstanceName, + }); + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: this.getInstanceName, + }); + this.batchBlockService = new NodeBlocksService({ + netId, + provider, + onProgress: this.updateBlockProgress, + logger, + getInstanceName: this.getInstanceName, + }); + this.treeCache = treeCache; + } + updateEventProgress({ fromBlock, toBlock, count }) { + if (toBlock) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${count} events from ${fromBlock} to ${toBlock}`); + } + } + updateTransactionProgress({ currentIndex, totalIndex }) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} deposit txs of ${totalIndex}`); + } + } + updateBlockProgress({ currentIndex, totalIndex }) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} withdrawal blocks of ${totalIndex}`); + } + } + async getEventsFromDB() { + return await (0, data_1.loadSavedEvents)({ + name: this.getInstanceName(), + userDirectory: this.userDirectory, + }); + } + async getEventsFromCache() { + return await (0, data_1.loadCachedEvents)({ + name: this.getInstanceName(), + cacheDirectory: this.cacheDirectory, + deployedBlock: this.deployedBlock, + }); + } + async validateEvents({ events, newEvents, lastBlock, }) { + const tree = await super.validateEvents({ + events, + newEvents, + lastBlock, + }); + if (this.merkleTreeService && this.treeCache) { + const instancesWithNewEvents = [ + ...new Set(newEvents.filter(({ event }) => event === 'Deposit').map(({ instanceAddress }) => instanceAddress)), + ]; + for (const instance of instancesWithNewEvents) { + const { amount, currency } = this.instances[instance]; + // Only create cached tree for native currencies + if (currency !== this.nativeCurrency) { + continue; + } + const depositEvents = events.filter(({ instanceAddress, event }) => instanceAddress === instance && event === 'Deposit'); + // Create tree cache with existing tree + if (tree && this.merkleTreeService.Tornado.target === instance) { + await this.treeCache.createTree({ + netId: this.netId, + amount, + currency, + events: depositEvents, + tree: tree, + }); + // Create new tree cache + } + else { + const merkleTreeService = new core_1.MerkleTreeService({ + netId: this.netId, + amount, + currency, + Tornado: contracts_1.Tornado__factory.connect(instance, this.provider), + merkleWorkerPath: this.merkleTreeService.merkleWorkerPath, + }); + const tree = await merkleTreeService.verifyTree(depositEvents); + await this.treeCache.createTree({ + netId: this.netId, + amount, + currency, + events: depositEvents, + tree, + }); + } + } + } + return tree; + } + async saveEvents({ events, newEvents, lastBlock, }) { + const instancesWithNewEvents = [...new Set(newEvents.map(({ instanceAddress }) => instanceAddress))]; + for (const instance of instancesWithNewEvents) { + const { currency, amount } = this.instances[instance]; + const { depositEvents, withdrawalEvents } = events.reduce((acc, curr) => { + if (curr.instanceAddress === instance) { + if (curr.event === 'Deposit') { + acc.depositEvents.push({ + ...curr, + event: undefined, + instanceAddress: undefined, + }); + } + else if (curr.event === 'Withdrawal') { + acc.withdrawalEvents.push({ + ...curr, + event: undefined, + instanceAddress: undefined, + relayerAddress: undefined, + }); + } + } + return acc; + }, { + depositEvents: [], + withdrawalEvents: [], + }); + await (0, promises_1.writeFile)(path_1.default.join(this.userDirectory, `recent_deposits_${this.netId}_${currency}_${amount}.json`), JSON.stringify(depositEvents.slice(depositEvents.length - 10).reverse(), null, 2) + '\n'); + await (0, data_1.saveUserFile)({ + fileName: `deposits_${this.netId}_${currency}_${amount}.json`, + userDirectory: this.userDirectory, + dataString: JSON.stringify(depositEvents, null, 2) + '\n', + lastBlock, + }); + await (0, data_1.saveUserFile)({ + fileName: `withdrawals_${this.netId}_${currency}_${amount}.json`, + userDirectory: this.userDirectory, + dataString: JSON.stringify(withdrawalEvents, null, 2) + '\n', + lastBlock, + }); + console.log(`\nSaved ${this.netId}_${currency}_${amount}.json event: ` + + `${depositEvents.length} deposits ${withdrawalEvents.length} withdrawals\n`); + } + await (0, data_1.saveUserFile)({ + fileName: this.getInstanceName() + '.json', + userDirectory: this.userDirectory, + dataString: JSON.stringify(events, null, 2) + '\n', + lastBlock, + }); + } + async updateEvents() { + const { events, lastBlock, validateResult } = await super.updateEvents(); + await (0, data_1.saveLastBlock)({ + fileName: this.getInstanceName(), + userDirectory: this.userDirectory, + lastBlock, + }); + return { + events, + lastBlock, + validateResult, + }; + } +} +exports.NodeMultiTornadoService = NodeMultiTornadoService; class NodeEchoService extends core_1.BaseEchoService { cacheDirectory; userDirectory; @@ -237,12 +472,24 @@ class NodeGovernanceService extends core_1.BaseGovernanceService { logger, getInstanceName: this.getInstanceName, }); + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: this.getInstanceName, + }); } updateEventProgress({ fromBlock, toBlock, count }) { if (toBlock) { this.logger.debug(`${this.getInstanceName()}: Fetched ${count} events from ${fromBlock} to ${toBlock}`); } } + updateTransactionProgress({ currentIndex, totalIndex }) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} governance txs of ${totalIndex}`); + } + } async getEventsFromDB() { return await (0, data_1.loadSavedEvents)({ name: this.getInstanceName(), @@ -423,12 +670,36 @@ class NodeRevenueService extends core_1.BaseRevenueService { logger, getInstanceName: this.getInstanceName, }); + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: this.getInstanceName, + }); + this.batchBlockService = new NodeBlocksService({ + netId, + provider, + onProgress: this.updateBlockProgress, + logger, + getInstanceName: this.getInstanceName, + }); } updateEventProgress({ fromBlock, toBlock, count }) { if (toBlock) { this.logger.debug(`${this.getInstanceName()}: Fetched ${count} events from ${fromBlock} to ${toBlock}`); } } + updateTransactionProgress({ currentIndex, totalIndex }) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} txs of ${totalIndex}`); + } + } + updateBlockProgress({ currentIndex, totalIndex }) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} blocks of ${totalIndex}`); + } + } async getEventsFromDB() { return await (0, data_1.loadSavedEvents)({ name: this.getInstanceName(), diff --git a/lib/services/router.js b/lib/services/router.js index 9ff7385..b148dc9 100644 --- a/lib/services/router.js +++ b/lib/services/router.js @@ -116,28 +116,39 @@ async function handleGetJob(router, req, reply) { async function handleEvents(router, netId, req, reply) { const { relayerConfig: { userEventsDir: userDirectory }, } = router; const { type, currency, amount, fromBlock, recent } = req.body; - const name = [core_1.DEPOSIT, core_1.WITHDRAWAL].includes(type) ? `${type}s_${netId}_${currency}_${amount}` : `${type}_${netId}`; - // Can return 0 events but we just return error codes here + const name = ['deposit', 'withdrawal'].includes(type) + ? `${type}s_${netId}_${currency}_${amount}` + : `${type}_${netId}`; + // Return 0 length events if not exist (likely 0 events, can be checked by lastSyncBlock === fromBlock) if (!(await (0, data_1.existsAsync)(path_1.default.join(userDirectory, `${name}.json`)))) { - reply.code(404).send(`Events ${name} not found!`); + reply.send({ + events: [], + lastSyncBlock: fromBlock, + }); return; } const { syncManagerStatus } = await (0, routerMsg_1.sendMessage)(router, { type: 'status' }); - const lastSyncBlock = Number([core_1.DEPOSIT, core_1.WITHDRAWAL].includes(type) - ? syncManagerStatus.cachedEvents[netId]?.instances?.[String(currency)]?.[String(amount)]?.[`${type}s`]?.lastBlock + const lastSyncBlock = Number(['deposit', 'withdrawal'].includes(type) + ? syncManagerStatus.cachedEvents[netId]?.tornado?.lastBlock : syncManagerStatus.cachedEvents[netId]?.[String(type)]?.lastBlock); + if (type === 'deposit' && recent) { + const { events } = await (0, data_1.loadSavedEvents)({ + name: 'recent_' + name, + userDirectory, + }); + reply.send({ + events, + lastSyncBlock, + }); + return; + } const { events } = await (0, data_1.loadSavedEvents)({ name, userDirectory, }); if (recent) { reply.send({ - events: events.slice(-10).sort((a, b) => { - if (a.blockNumber === b.blockNumber) { - return b.logIndex - a.logIndex; - } - return b.blockNumber - a.blockNumber; - }), + events: events.slice(events.length - 10).reverse(), lastSyncBlock, }); return; diff --git a/lib/services/schema.js b/lib/services/schema.js index 3555d0f..7d8686a 100644 --- a/lib/services/schema.js +++ b/lib/services/schema.js @@ -139,7 +139,7 @@ function getEventsKeyword(netId) { validate: (schema, data) => { try { const { type, currency, amount } = data; - if ([core_1.DEPOSIT, core_1.WITHDRAWAL].includes(type)) { + if (['deposit', 'withdrawal'].includes(type)) { const instanceAddress = config.tokens[String(currency)]?.instanceAddress?.[String(amount)]; if (!instanceAddress) { return false; @@ -159,7 +159,7 @@ function getEventsKeyword(netId) { } return true; } - return ['echo', 'encrypted_notes'].includes(type); + return ['tornado', 'echo', 'encrypted_notes'].includes(type); } catch { return false; diff --git a/lib/services/sync.d.ts b/lib/services/sync.d.ts index 02673b1..ff41596 100644 --- a/lib/services/sync.d.ts +++ b/lib/services/sync.d.ts @@ -2,18 +2,8 @@ import type { Provider } from 'ethers'; import type { Logger } from 'winston'; import { NetIdType, TokenPriceOracle, TornadoFeeOracle, TovarishEventsStatus, TovarishSyncStatus } from '@tornado/core'; import { RelayerConfig } from '../config'; -import { NodeEchoService, NodeEncryptedNotesService, NodeGovernanceService, NodeRegistryService, NodeRevenueService, NodeTornadoService } from './events'; +import { NodeEchoService, NodeEncryptedNotesService, NodeGovernanceService, NodeRegistryService, NodeRevenueService, NodeMultiTornadoService } from './events'; import { ErrorTypes, ErrorMessages } from './error'; -export interface AmountsServices { - depositsService: NodeTornadoService; - withdrawalsService: NodeTornadoService; -} -export interface CurrencyServices { - [index: string]: AmountsServices; -} -export interface TornadoServices { - [index: string]: CurrencyServices; -} export interface Services { provider: Provider; tokenPriceOracle: TokenPriceOracle; @@ -23,7 +13,7 @@ export interface Services { revenueService?: NodeRevenueService; echoService: NodeEchoService; encryptedNotesService: NodeEncryptedNotesService; - tornadoServices: TornadoServices; + tornadoServices: NodeMultiTornadoService; } export interface CachedServices { [index: NetIdType]: Services; diff --git a/lib/services/sync.js b/lib/services/sync.js index f8c11a0..ec1ca11 100644 --- a/lib/services/sync.js +++ b/lib/services/sync.js @@ -18,7 +18,7 @@ function setupServices(syncManager) { const config = (0, core_1.getConfig)(netId); const rpcUrl = relayerConfig.rpcUrls[netId]; const provider = (0, core_1.getProviderWithNetId)(netId, rpcUrl, config); - const { tokens, nativeCurrency, routerContract, echoContract, registryContract, aggregatorContract, reverseRecordsContract, governanceContract, multicallContract, offchainOracleContract, ovmGasPriceOracleContract, deployedBlock, constants: { GOVERNANCE_BLOCK, REGISTRY_BLOCK, NOTE_ACCOUNT_BLOCK, ENCRYPTED_NOTES_BLOCK }, } = config; + const { nativeCurrency, routerContract, echoContract, registryContract, aggregatorContract, reverseRecordsContract, governanceContract, multicallContract, offchainOracleContract, ovmGasPriceOracleContract, deployedBlock, constants: { GOVERNANCE_BLOCK, REGISTRY_BLOCK, NOTE_ACCOUNT_BLOCK, ENCRYPTED_NOTES_BLOCK }, } = config; if (!syncStatus[netId]) { syncStatus[netId] = { events: false, @@ -85,49 +85,29 @@ function setupServices(syncManager) { userDirectory, logger, }); - services.tornadoServices = {}; - for (const currency of (0, core_1.getActiveTokens)(config)) { - const currencyConfig = tokens[currency]; - const currencyService = (services.tornadoServices[currency] = {}); - for (const [amount, instanceAddress] of Object.entries(currencyConfig.instanceAddress)) { - const Tornado = contracts_1.Tornado__factory.connect(instanceAddress, provider); - const amountService = (currencyService[amount] = {}); - const TornadoServiceConstructor = { - netId, - provider, - Tornado, - amount, - currency, - deployedBlock, - cacheDirectory, - userDirectory, - nativeCurrency, - logger, - }; - amountService.depositsService = new events_1.NodeTornadoService({ - ...TornadoServiceConstructor, - merkleTreeService: new core_1.MerkleTreeService({ - netId, - amount, - currency, - Tornado, - merkleWorkerPath, - }), - treeCache: new treeCache_1.TreeCache({ - netId, - amount, - currency, - userDirectory: userTreeDir, - }), - optionalTree: true, - type: 'Deposit', - }); - amountService.withdrawalsService = new events_1.NodeTornadoService({ - ...TornadoServiceConstructor, - type: 'Withdrawal', - }); - } - } + const instances = (0, core_1.getMultiInstances)(netId, config); + const [firstInstance, { amount, currency }] = Object.entries(instances)[0]; + services.tornadoServices = new events_1.NodeMultiTornadoService({ + netId, + provider, + instances, + deployedBlock, + merkleTreeService: new core_1.MerkleTreeService({ + netId, + amount, + currency, + Tornado: contracts_1.Tornado__factory.connect(firstInstance, provider), + merkleWorkerPath, + }), + treeCache: new treeCache_1.TreeCache({ + userDirectory: userTreeDir, + }), + optionalTree: true, + nativeCurrency, + cacheDirectory, + userDirectory, + logger, + }); } syncManager.cachedServices = cachedServices; } @@ -215,12 +195,11 @@ async function syncNetworkEvents(syncManager, netId) { logger.info(`${netId}: Syncing events from block ${await provider.getBlockNumber()}`); const eventsStatus = { governance: governanceService ? {} : undefined, - registered: registryService ? {} : undefined, registry: registryService ? {} : undefined, revenue: revenueService ? {} : undefined, echo: {}, encrypted_notes: {}, - instances: {}, + tornado: {}, }; if (governanceService) { const { events, lastBlock } = await governanceService.updateEvents(); @@ -241,11 +220,6 @@ async function syncNetworkEvents(syncManager, netId) { } { const { lastBlock, timestamp, relayers } = await registryService.updateRelayers(); - eventsStatus.registered = { - lastBlock, - timestamp, - relayers: relayers.length, - }; logger.info(`${netId}: Updated registry relayers (total: ${relayers.length}, block: ${lastBlock}, timestamp: ${timestamp})`); } } @@ -269,30 +243,12 @@ async function syncNetworkEvents(syncManager, netId) { lastBlock: encryptedNotesEvents.lastBlock, }; logger.info(`${netId}: Updated encrypted notes events (total: ${encryptedNotesEvents.events.length}, block: ${encryptedNotesEvents.lastBlock})`); - const currencies = Object.keys(tornadoServices); - for (const currency of currencies) { - const currencyStatus = (eventsStatus.instances[currency] = {}); - const amounts = Object.keys(tornadoServices[currency]); - for (const amount of amounts) { - const instanceStatus = (currencyStatus[amount] = { - deposits: {}, - withdrawals: {}, - }); - const { depositsService, withdrawalsService } = tornadoServices[currency][amount]; - const depositEvents = await depositsService.updateEvents(); - instanceStatus.deposits = { - events: depositEvents.events.length, - lastBlock: depositEvents.lastBlock, - }; - logger.info(`${netId}: Updated ${currency} ${amount} Tornado deposit events (total: ${depositEvents.events.length}, block: ${depositEvents.lastBlock})`); - const withdrawalEvents = await withdrawalsService.updateEvents(); - instanceStatus.withdrawals = { - events: withdrawalEvents.events.length, - lastBlock: withdrawalEvents.lastBlock, - }; - logger.info(`${netId}: Updated ${currency} ${amount} Tornado withdrawal events (total: ${withdrawalEvents.events.length}, block: ${withdrawalEvents.lastBlock})`); - } - } + const tornadoEvents = await tornadoServices.updateEvents(); + eventsStatus.tornado = { + events: tornadoEvents.events.length, + lastBlock: tornadoEvents.lastBlock, + }; + logger.info(`${netId}: Updated tornado events (total: ${tornadoEvents.events.length}, block: ${tornadoEvents.lastBlock})`); cachedEvents[netId] = eventsStatus; syncStatus[netId].events = true; logger.info(`${netId}: Synced all events`); diff --git a/lib/services/treeCache.d.ts b/lib/services/treeCache.d.ts index 7ccfff8..02e80f3 100644 --- a/lib/services/treeCache.d.ts +++ b/lib/services/treeCache.d.ts @@ -7,9 +7,6 @@ import { MerkleTree } from '@tornado/fixed-merkle-tree'; import { DepositsEvents } from '@tornado/core'; import type { NetIdType } from '@tornado/core'; export interface TreeCacheConstructor { - netId: NetIdType; - amount: string; - currency: string; userDirectory: string; PARTS_COUNT?: number; LEAVES?: number; @@ -24,12 +21,19 @@ export interface treeMetadata { leafIndex: number; } export declare class TreeCache { - netId: NetIdType; - amount: string; - currency: string; userDirectory: string; PARTS_COUNT: number; - constructor({ netId, amount, currency, userDirectory, PARTS_COUNT }: TreeCacheConstructor); - getInstanceName(): string; - createTree(events: DepositsEvents[], tree: MerkleTree): Promise; + constructor({ userDirectory, PARTS_COUNT }: TreeCacheConstructor); + static getInstanceName({ netId, amount, currency, }: { + netId: NetIdType; + amount: string; + currency: string; + }): string; + createTree({ netId, amount, currency, events, tree, }: { + netId: NetIdType; + amount: string; + currency: string; + events: DepositsEvents[]; + tree: MerkleTree; + }): Promise; } diff --git a/lib/services/treeCache.js b/lib/services/treeCache.js index 009f4ac..7a6f19a 100644 --- a/lib/services/treeCache.js +++ b/lib/services/treeCache.js @@ -7,24 +7,19 @@ exports.TreeCache = void 0; const bloomfilter_js_1 = __importDefault(require("bloomfilter.js")); const data_1 = require("./data"); class TreeCache { - netId; - amount; - currency; userDirectory; PARTS_COUNT; - constructor({ netId, amount, currency, userDirectory, PARTS_COUNT = 4 }) { - this.netId = netId; - this.amount = amount; - this.currency = currency; + constructor({ userDirectory, PARTS_COUNT = 4 }) { this.userDirectory = userDirectory; this.PARTS_COUNT = PARTS_COUNT; } - getInstanceName() { - return `deposits_${this.netId}_${this.currency}_${this.amount}`; + static getInstanceName({ netId, amount, currency, }) { + return `deposits_${netId}_${currency}_${amount}`; } - async createTree(events, tree) { + async createTree({ netId, amount, currency, events, tree, }) { const bloom = new bloomfilter_js_1.default(events.length); - console.log(`Creating cached tree for ${this.getInstanceName()}\n`); + const instance = TreeCache.getInstanceName({ netId, amount, currency }); + console.log(`Creating cached tree for ${instance}\n`); // events indexed by commitment const eventsData = events.reduce((acc, { leafIndex, commitment, ...rest }, i) => { if (leafIndex !== i) { @@ -46,7 +41,7 @@ class TreeCache { ...slice, metadata, }, null, 2) + '\n'; - const fileName = `${this.getInstanceName()}_slice${index + 1}.json`; + const fileName = `${instance}_slice${index + 1}.json`; await (0, data_1.saveUserFile)({ fileName, userDirectory: this.userDirectory, @@ -54,7 +49,7 @@ class TreeCache { }); })); const dataString = bloom.serialize() + '\n'; - const fileName = `${this.getInstanceName()}_bloom.json`; + const fileName = `${instance}_bloom.json`; await (0, data_1.saveUserFile)({ fileName, userDirectory: this.userDirectory, diff --git a/package.json b/package.json index 88ef637..386d165 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "license": "MIT", "dependencies": { "@fastify/cors": "^10.0.1", - "@tornado/core": "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#94a62e6193c99457a8dfae0d8684bee299cb1097", + "@tornado/core": "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#68fcf07c2a344a1f7f74895301f362aa059b53e2", "bloomfilter.js": "^1.0.2", "dotenv": "^16.4.5", "fastify": "^5.0.0", diff --git a/src/config.ts b/src/config.ts index f2efae5..0d2266d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -98,6 +98,6 @@ export function getRelayerConfig(): RelayerConfig { cacheDir: path.join(STATIC_DIR, './events'), userEventsDir: path.join(USER_DIR, './events'), userTreeDir: path.join(USER_DIR, './trees'), - syncInterval: Number(process.env.SYNC_INTERVAL || 120), + syncInterval: Number(process.env.SYNC_INTERVAL || 180), }; } diff --git a/src/services/events.ts b/src/services/events.ts index c9f5275..80882e3 100644 --- a/src/services/events.ts +++ b/src/services/events.ts @@ -1,11 +1,13 @@ import path from 'path'; -import { readFile } from 'fs/promises'; +import { readFile, writeFile } from 'fs/promises'; import { BaseTornadoService, + BaseMultiTornadoService, BaseEncryptedNotesService, BaseGovernanceService, BaseRegistryService, BaseTornadoServiceConstructor, + BaseMultiTornadoServiceConstructor, BaseEncryptedNotesServiceConstructor, BaseGovernanceServiceConstructor, BaseRegistryServiceConstructor, @@ -17,6 +19,9 @@ import { BaseEvents, DepositsEvents, WithdrawalsEvents, + MultiDepositsEvents, + MultiWithdrawalsEvents, + MerkleTreeService, EncryptedNotesEvents, AllGovernanceEvents, EchoEvents, @@ -27,7 +32,12 @@ import { BaseRevenueService, BaseRevenueServiceConstructor, StakeBurnedEvents, + BatchBlockOnProgress, + BatchBlockServiceConstructor, + BatchBlockService, + BatchTransactionService, } from '@tornado/core'; +import { Tornado__factory } from '@tornado/contracts'; import type { MerkleTree } from '@tornado/fixed-merkle-tree'; import type { Logger } from 'winston'; import { saveUserFile, loadSavedEvents, loadCachedEvents, existsAsync, saveLastBlock } from './data'; @@ -53,6 +63,40 @@ export class NodeEventsService extends BatchEventsService { } } +export interface NodeBlocksConstructor extends BatchBlockServiceConstructor { + netId: NetIdType; + logger: Logger; + getInstanceName: () => string; +} + +export class NodeBlocksService extends BatchBlockService { + netId: NetIdType; + logger: Logger; + getInstanceName: () => string; + + constructor(serviceConstructor: NodeBlocksConstructor) { + super(serviceConstructor); + + this.netId = serviceConstructor.netId; + this.logger = serviceConstructor.logger; + this.getInstanceName = serviceConstructor.getInstanceName; + } +} + +export class NodeTransactionsService extends BatchTransactionService { + netId: NetIdType; + logger: Logger; + getInstanceName: () => string; + + constructor(serviceConstructor: NodeBlocksConstructor) { + super(serviceConstructor); + + this.netId = serviceConstructor.netId; + this.logger = serviceConstructor.logger; + this.getInstanceName = serviceConstructor.getInstanceName; + } +} + export interface NodeTornadoServiceConstructor extends BaseTornadoServiceConstructor { cacheDirectory: string; userDirectory: string; @@ -102,6 +146,22 @@ export class NodeTornadoService extends BaseTornadoService { getInstanceName: () => `${type.toLowerCase()}s_${netId}_${currency}_${amount}`, }); + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: () => `${type.toLowerCase()}s_${netId}_${currency}_${amount}`, + }); + + this.batchBlockService = new NodeBlocksService({ + netId, + provider, + onProgress: this.updateBlockProgress, + logger, + getInstanceName: () => `${type.toLowerCase()}s_${netId}_${currency}_${amount}`, + }); + this.treeCache = treeCache; } @@ -111,6 +171,18 @@ export class NodeTornadoService extends BaseTornadoService { } } + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} deposit txs of ${totalIndex}`); + } + } + + updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} withdrawal blocks of ${totalIndex}`); + } + } + async getEventsFromDB() { return await loadSavedEvents({ name: this.getInstanceName(), @@ -128,21 +200,27 @@ export class NodeTornadoService extends BaseTornadoService { async validateEvents({ events, + newEvents, lastBlock, - hasNewEvents, }: BaseEvents & { - hasNewEvents?: boolean; + newEvents: (DepositsEvents | WithdrawalsEvents)[]; }): Promise { const tree = await super.validateEvents({ events, + newEvents, lastBlock, - hasNewEvents, }); if (tree && this.currency === this.nativeCurrency && this.treeCache) { const merkleTree = tree as unknown as MerkleTree; - await this.treeCache.createTree(events as DepositsEvents[], merkleTree); + await this.treeCache.createTree({ + netId: this.netId, + amount: this.amount, + currency: this.currency, + events: events as DepositsEvents[], + tree: tree as unknown as MerkleTree, + }); console.log( `${this.getInstanceName()}: Updated tree cache with root ${toFixedHex(BigInt(merkleTree.root))}\n`, @@ -178,6 +256,253 @@ export class NodeTornadoService extends BaseTornadoService { } } +export interface NodeMultiTornadoServiceConstructor extends BaseMultiTornadoServiceConstructor { + cacheDirectory: string; + userDirectory: string; + nativeCurrency: string; + logger: Logger; + treeCache?: TreeCache; +} + +export class NodeMultiTornadoService extends BaseMultiTornadoService { + cacheDirectory: string; + userDirectory: string; + + nativeCurrency: string; + logger: Logger; + + treeCache?: TreeCache; + + constructor(serviceConstructor: NodeMultiTornadoServiceConstructor) { + super(serviceConstructor); + + const { netId, provider, cacheDirectory, userDirectory, nativeCurrency, logger, treeCache } = + serviceConstructor; + + this.cacheDirectory = cacheDirectory; + this.userDirectory = userDirectory; + this.nativeCurrency = nativeCurrency; + + this.logger = logger; + + this.batchEventsService = new NodeEventsService({ + netId, + provider, + contract: this.contract, + address: Object.keys(this.instances), + onProgress: this.updateEventProgress, + logger, + getInstanceName: this.getInstanceName, + }); + + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: this.getInstanceName, + }); + + this.batchBlockService = new NodeBlocksService({ + netId, + provider, + onProgress: this.updateBlockProgress, + logger, + getInstanceName: this.getInstanceName, + }); + + this.treeCache = treeCache; + } + + updateEventProgress({ fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${count} events from ${fromBlock} to ${toBlock}`); + } + } + + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} deposit txs of ${totalIndex}`); + } + } + + updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} withdrawal blocks of ${totalIndex}`); + } + } + + async getEventsFromDB() { + return await loadSavedEvents({ + name: this.getInstanceName(), + userDirectory: this.userDirectory, + }); + } + + async getEventsFromCache() { + return await loadCachedEvents({ + name: this.getInstanceName(), + cacheDirectory: this.cacheDirectory, + deployedBlock: this.deployedBlock, + }); + } + + async validateEvents({ + events, + newEvents, + lastBlock, + }: BaseEvents & { + newEvents: (MultiDepositsEvents | MultiWithdrawalsEvents)[]; + }): Promise { + const tree = await super.validateEvents({ + events, + newEvents, + lastBlock, + }); + + if (this.merkleTreeService && this.treeCache) { + const instancesWithNewEvents = [ + ...new Set( + newEvents.filter(({ event }) => event === 'Deposit').map(({ instanceAddress }) => instanceAddress), + ), + ]; + + for (const instance of instancesWithNewEvents) { + const { amount, currency } = this.instances[instance]; + + // Only create cached tree for native currencies + if (currency !== this.nativeCurrency) { + continue; + } + + const depositEvents = events.filter( + ({ instanceAddress, event }) => instanceAddress === instance && event === 'Deposit', + ) as MultiDepositsEvents[]; + + // Create tree cache with existing tree + if (tree && this.merkleTreeService.Tornado.target === instance) { + await this.treeCache.createTree({ + netId: this.netId, + amount, + currency, + events: depositEvents, + tree: tree as unknown as MerkleTree, + }); + + // Create new tree cache + } else { + const merkleTreeService = new MerkleTreeService({ + netId: this.netId, + amount, + currency, + Tornado: Tornado__factory.connect(instance, this.provider), + merkleWorkerPath: this.merkleTreeService.merkleWorkerPath, + }); + + const tree = await merkleTreeService.verifyTree(depositEvents); + + await this.treeCache.createTree({ + netId: this.netId, + amount, + currency, + events: depositEvents, + tree, + }); + } + } + } + + return tree; + } + + async saveEvents({ + events, + newEvents, + lastBlock, + }: BaseEvents & { + newEvents: (MultiDepositsEvents | MultiWithdrawalsEvents)[]; + }) { + const instancesWithNewEvents = [...new Set(newEvents.map(({ instanceAddress }) => instanceAddress))]; + + for (const instance of instancesWithNewEvents) { + const { currency, amount } = this.instances[instance]; + + const { depositEvents, withdrawalEvents } = events.reduce( + (acc, curr) => { + if (curr.instanceAddress === instance) { + if (curr.event === 'Deposit') { + acc.depositEvents.push({ + ...(curr as MultiDepositsEvents), + event: undefined, + instanceAddress: undefined, + } as DepositsEvents); + } else if (curr.event === 'Withdrawal') { + acc.withdrawalEvents.push({ + ...(curr as MultiWithdrawalsEvents), + event: undefined, + instanceAddress: undefined, + relayerAddress: undefined, + } as WithdrawalsEvents); + } + } + return acc; + }, + { + depositEvents: [] as DepositsEvents[], + withdrawalEvents: [] as WithdrawalsEvents[], + }, + ); + + await writeFile( + path.join(this.userDirectory, `recent_deposits_${this.netId}_${currency}_${amount}.json`), + JSON.stringify(depositEvents.slice(depositEvents.length - 10).reverse(), null, 2) + '\n', + ); + + await saveUserFile({ + fileName: `deposits_${this.netId}_${currency}_${amount}.json`, + userDirectory: this.userDirectory, + dataString: JSON.stringify(depositEvents, null, 2) + '\n', + lastBlock, + }); + + await saveUserFile({ + fileName: `withdrawals_${this.netId}_${currency}_${amount}.json`, + userDirectory: this.userDirectory, + dataString: JSON.stringify(withdrawalEvents, null, 2) + '\n', + lastBlock, + }); + + console.log( + `\nSaved ${this.netId}_${currency}_${amount}.json event: ` + + `${depositEvents.length} deposits ${withdrawalEvents.length} withdrawals\n`, + ); + } + + await saveUserFile({ + fileName: this.getInstanceName() + '.json', + userDirectory: this.userDirectory, + dataString: JSON.stringify(events, null, 2) + '\n', + lastBlock, + }); + } + + async updateEvents() { + const { events, lastBlock, validateResult } = await super.updateEvents(); + + await saveLastBlock({ + fileName: this.getInstanceName(), + userDirectory: this.userDirectory, + lastBlock, + }); + + return { + events, + lastBlock, + validateResult, + }; + } +} + export interface NodeEchoServiceConstructor extends BaseEchoServiceConstructor { cacheDirectory: string; userDirectory: string; @@ -368,6 +693,14 @@ export class NodeGovernanceService extends BaseGovernanceService { logger, getInstanceName: this.getInstanceName, }); + + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: this.getInstanceName, + }); } updateEventProgress({ fromBlock, toBlock, count }: Parameters[0]) { @@ -376,6 +709,12 @@ export class NodeGovernanceService extends BaseGovernanceService { } } + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} governance txs of ${totalIndex}`); + } + } + async getEventsFromDB() { return await loadSavedEvents({ name: this.getInstanceName(), @@ -599,6 +938,22 @@ export class NodeRevenueService extends BaseRevenueService { logger, getInstanceName: this.getInstanceName, }); + + this.batchTransactionService = new NodeTransactionsService({ + netId, + provider, + onProgress: this.updateTransactionProgress, + logger, + getInstanceName: this.getInstanceName, + }); + + this.batchBlockService = new NodeBlocksService({ + netId, + provider, + onProgress: this.updateBlockProgress, + logger, + getInstanceName: this.getInstanceName, + }); } updateEventProgress({ fromBlock, toBlock, count }: Parameters[0]) { @@ -607,6 +962,18 @@ export class NodeRevenueService extends BaseRevenueService { } } + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} txs of ${totalIndex}`); + } + } + + updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + this.logger.debug(`${this.getInstanceName()}: Fetched ${currentIndex} blocks of ${totalIndex}`); + } + } + async getEventsFromDB() { return await loadSavedEvents({ name: this.getInstanceName(), diff --git a/src/services/router.ts b/src/services/router.ts index 7da11ae..ef3ba19 100644 --- a/src/services/router.ts +++ b/src/services/router.ts @@ -7,8 +7,6 @@ import { fastifyCors } from '@fastify/cors'; import { NetIdType, getConfig, - DEPOSIT, - WITHDRAWAL, DepositsEvents, WithdrawalsEvents, EchoEvents, @@ -195,11 +193,16 @@ export async function handleEvents(router: Router, netId: NetIdType, req: Fastif } = router; const { type, currency, amount, fromBlock, recent } = req.body as unknown as TovarishEventsQuery; - const name = [DEPOSIT, WITHDRAWAL].includes(type) ? `${type}s_${netId}_${currency}_${amount}` : `${type}_${netId}`; + const name = ['deposit', 'withdrawal'].includes(type) + ? `${type}s_${netId}_${currency}_${amount}` + : `${type}_${netId}`; - // Can return 0 events but we just return error codes here + // Return 0 length events if not exist (likely 0 events, can be checked by lastSyncBlock === fromBlock) if (!(await existsAsync(path.join(userDirectory, `${name}.json`)))) { - reply.code(404).send(`Events ${name} not found!`); + reply.send({ + events: [], + lastSyncBlock: fromBlock, + } as BaseTovarishEvents); return; } @@ -208,13 +211,24 @@ export async function handleEvents(router: Router, netId: NetIdType, req: Fastif }>(router, { type: 'status' }); const lastSyncBlock = Number( - [DEPOSIT, WITHDRAWAL].includes(type) - ? syncManagerStatus.cachedEvents[netId]?.instances?.[String(currency)]?.[String(amount)]?.[ - `${type}s` as 'deposits' | 'withdrawals' - ]?.lastBlock + ['deposit', 'withdrawal'].includes(type) + ? syncManagerStatus.cachedEvents[netId]?.tornado?.lastBlock : syncManagerStatus.cachedEvents[netId]?.[String(type) as keyof TovarishEventsStatus]?.lastBlock, ); + if (type === 'deposit' && recent) { + const { events } = await loadSavedEvents({ + name: 'recent_' + name, + userDirectory, + }); + + reply.send({ + events, + lastSyncBlock, + } as BaseTovarishEvents); + return; + } + const { events } = await loadSavedEvents({ name, userDirectory, @@ -222,12 +236,7 @@ export async function handleEvents(router: Router, netId: NetIdType, req: Fastif if (recent) { reply.send({ - events: events.slice(-10).sort((a, b) => { - if (a.blockNumber === b.blockNumber) { - return b.logIndex - a.logIndex; - } - return b.blockNumber - a.blockNumber; - }), + events: events.slice(events.length - 10).reverse(), lastSyncBlock, } as BaseTovarishEvents); return; diff --git a/src/services/schema.ts b/src/services/schema.ts index 997d715..1c98ff1 100644 --- a/src/services/schema.ts +++ b/src/services/schema.ts @@ -7,8 +7,6 @@ import { getInstanceByAddress, enabledChains, TovarishEventsQuery, - WITHDRAWAL, - DEPOSIT, addressSchemaType, proofSchemaType, bytes32SchemaType, @@ -182,7 +180,7 @@ export function getEventsKeyword(netId: NetIdType) { try { const { type, currency, amount } = data; - if ([DEPOSIT, WITHDRAWAL].includes(type)) { + if (['deposit', 'withdrawal'].includes(type)) { const instanceAddress = config.tokens[String(currency)]?.instanceAddress?.[String(amount)]; if (!instanceAddress) { @@ -207,7 +205,7 @@ export function getEventsKeyword(netId: NetIdType) { return true; } - return ['echo', 'encrypted_notes'].includes(type); + return ['tornado', 'echo', 'encrypted_notes'].includes(type); } catch { return false; } diff --git a/src/services/sync.ts b/src/services/sync.ts index 18d2cf1..5953723 100644 --- a/src/services/sync.ts +++ b/src/services/sync.ts @@ -21,12 +21,10 @@ import { OffchainOracle__factory, TornadoFeeOracle, OvmGasPriceOracle__factory, - getActiveTokens, TovarishEventsStatus, - InstanceEventsStatus, TovarishSyncStatus, - EventsStatus, ReverseRecords__factory, + getMultiInstances, } from '@tornado/core'; import { RelayerConfig } from '../config'; @@ -38,23 +36,10 @@ import { NodeGovernanceService, NodeRegistryService, NodeRevenueService, - NodeTornadoService, + NodeMultiTornadoService, } from './events'; import { ErrorTypes, ErrorMessages, newError } from './error'; -export interface AmountsServices { - depositsService: NodeTornadoService; - withdrawalsService: NodeTornadoService; -} - -export interface CurrencyServices { - [index: string]: AmountsServices; -} - -export interface TornadoServices { - [index: string]: CurrencyServices; -} - export interface Services { provider: Provider; tokenPriceOracle: TokenPriceOracle; @@ -64,7 +49,7 @@ export interface Services { revenueService?: NodeRevenueService; echoService: NodeEchoService; encryptedNotesService: NodeEncryptedNotesService; - tornadoServices: TornadoServices; + tornadoServices: NodeMultiTornadoService; } export interface CachedServices { @@ -131,7 +116,6 @@ function setupServices(syncManager: SyncManager) { const provider = getProviderWithNetId(netId, rpcUrl, config); const { - tokens, nativeCurrency, routerContract, echoContract, @@ -229,56 +213,31 @@ function setupServices(syncManager: SyncManager) { logger, }); - services.tornadoServices = {} as TornadoServices; + const instances = getMultiInstances(netId, config); - for (const currency of getActiveTokens(config)) { - const currencyConfig = tokens[currency]; + const [firstInstance, { amount, currency }] = Object.entries(instances)[0]; - const currencyService = (services.tornadoServices[currency] = {} as CurrencyServices); - - for (const [amount, instanceAddress] of Object.entries(currencyConfig.instanceAddress)) { - const Tornado = Tornado__factory.connect(instanceAddress, provider); - - const amountService = (currencyService[amount] = {} as AmountsServices); - - const TornadoServiceConstructor = { - netId, - provider, - Tornado, - amount, - currency, - deployedBlock, - cacheDirectory, - userDirectory, - nativeCurrency, - logger, - }; - - amountService.depositsService = new NodeTornadoService({ - ...TornadoServiceConstructor, - merkleTreeService: new MerkleTreeService({ - netId, - amount, - currency, - Tornado, - merkleWorkerPath, - }), - treeCache: new TreeCache({ - netId, - amount, - currency, - userDirectory: userTreeDir, - }), - optionalTree: true, - type: 'Deposit', - }); - - amountService.withdrawalsService = new NodeTornadoService({ - ...TornadoServiceConstructor, - type: 'Withdrawal', - }); - } - } + services.tornadoServices = new NodeMultiTornadoService({ + netId, + provider, + instances, + deployedBlock, + merkleTreeService: new MerkleTreeService({ + netId, + amount, + currency, + Tornado: Tornado__factory.connect(firstInstance, provider), + merkleWorkerPath, + }), + treeCache: new TreeCache({ + userDirectory: userTreeDir, + }), + optionalTree: true, + nativeCurrency, + cacheDirectory, + userDirectory, + logger, + }); } syncManager.cachedServices = cachedServices; @@ -413,12 +372,11 @@ export async function syncNetworkEvents(syncManager: SyncManager, netId: NetIdTy const eventsStatus = { governance: governanceService ? {} : undefined, - registered: registryService ? {} : undefined, registry: registryService ? {} : undefined, revenue: revenueService ? {} : undefined, echo: {}, encrypted_notes: {}, - instances: {}, + tornado: {}, } as TovarishEventsStatus; if (governanceService) { @@ -447,12 +405,6 @@ export async function syncNetworkEvents(syncManager: SyncManager, netId: NetIdTy { const { lastBlock, timestamp, relayers } = await registryService.updateRelayers(); - eventsStatus.registered = { - lastBlock, - timestamp, - relayers: relayers.length, - }; - logger.info( `${netId}: Updated registry relayers (total: ${relayers.length}, block: ${lastBlock}, timestamp: ${timestamp})`, ); @@ -492,44 +444,16 @@ export async function syncNetworkEvents(syncManager: SyncManager, netId: NetIdTy `${netId}: Updated encrypted notes events (total: ${encryptedNotesEvents.events.length}, block: ${encryptedNotesEvents.lastBlock})`, ); - const currencies = Object.keys(tornadoServices); + const tornadoEvents = await tornadoServices.updateEvents(); - for (const currency of currencies) { - const currencyStatus = (eventsStatus.instances[currency] = {} as InstanceEventsStatus); + eventsStatus.tornado = { + events: tornadoEvents.events.length, + lastBlock: tornadoEvents.lastBlock, + }; - const amounts = Object.keys(tornadoServices[currency]); - - for (const amount of amounts) { - const instanceStatus = (currencyStatus[amount] = { - deposits: {} as EventsStatus, - withdrawals: {} as EventsStatus, - }); - - const { depositsService, withdrawalsService } = tornadoServices[currency][amount]; - - const depositEvents = await depositsService.updateEvents(); - - instanceStatus.deposits = { - events: depositEvents.events.length, - lastBlock: depositEvents.lastBlock, - }; - - logger.info( - `${netId}: Updated ${currency} ${amount} Tornado deposit events (total: ${depositEvents.events.length}, block: ${depositEvents.lastBlock})`, - ); - - const withdrawalEvents = await withdrawalsService.updateEvents(); - - instanceStatus.withdrawals = { - events: withdrawalEvents.events.length, - lastBlock: withdrawalEvents.lastBlock, - }; - - logger.info( - `${netId}: Updated ${currency} ${amount} Tornado withdrawal events (total: ${withdrawalEvents.events.length}, block: ${withdrawalEvents.lastBlock})`, - ); - } - } + logger.info( + `${netId}: Updated tornado events (total: ${tornadoEvents.events.length}, block: ${tornadoEvents.lastBlock})`, + ); cachedEvents[netId] = eventsStatus; diff --git a/src/services/treeCache.ts b/src/services/treeCache.ts index 08ffce5..5ffc3d8 100644 --- a/src/services/treeCache.ts +++ b/src/services/treeCache.ts @@ -10,9 +10,6 @@ import type { NetIdType } from '@tornado/core'; import { saveUserFile } from './data'; export interface TreeCacheConstructor { - netId: NetIdType; - amount: string; - currency: string; userDirectory: string; PARTS_COUNT?: number; LEAVES?: number; @@ -29,30 +26,46 @@ export interface treeMetadata { } export class TreeCache { - netId: NetIdType; - amount: string; - currency: string; userDirectory: string; PARTS_COUNT: number; - constructor({ netId, amount, currency, userDirectory, PARTS_COUNT = 4 }: TreeCacheConstructor) { - this.netId = netId; - this.amount = amount; - this.currency = currency; + constructor({ userDirectory, PARTS_COUNT = 4 }: TreeCacheConstructor) { this.userDirectory = userDirectory; this.PARTS_COUNT = PARTS_COUNT; } - getInstanceName(): string { - return `deposits_${this.netId}_${this.currency}_${this.amount}`; + static getInstanceName({ + netId, + amount, + currency, + }: { + netId: NetIdType; + amount: string; + currency: string; + }): string { + return `deposits_${netId}_${currency}_${amount}`; } - async createTree(events: DepositsEvents[], tree: MerkleTree) { + async createTree({ + netId, + amount, + currency, + events, + tree, + }: { + netId: NetIdType; + amount: string; + currency: string; + events: DepositsEvents[]; + tree: MerkleTree; + }) { const bloom = new BloomFilter(events.length); - console.log(`Creating cached tree for ${this.getInstanceName()}\n`); + const instance = TreeCache.getInstanceName({ netId, amount, currency }); + + console.log(`Creating cached tree for ${instance}\n`); // events indexed by commitment const eventsData = events.reduce( @@ -90,7 +103,7 @@ export class TreeCache { 2, ) + '\n'; - const fileName = `${this.getInstanceName()}_slice${index + 1}.json`; + const fileName = `${instance}_slice${index + 1}.json`; await saveUserFile({ fileName, @@ -102,7 +115,7 @@ export class TreeCache { const dataString = bloom.serialize() + '\n'; - const fileName = `${this.getInstanceName()}_bloom.json`; + const fileName = `${instance}_bloom.json`; await saveUserFile({ fileName, diff --git a/static/events/tornado_1.json.zip b/static/events/tornado_1.json.zip new file mode 100644 index 0000000..1f6587a Binary files /dev/null and b/static/events/tornado_1.json.zip differ diff --git a/static/events/tornado_10.json.zip b/static/events/tornado_10.json.zip new file mode 100644 index 0000000..b066f21 Binary files /dev/null and b/static/events/tornado_10.json.zip differ diff --git a/static/events/tornado_100.json.zip b/static/events/tornado_100.json.zip new file mode 100644 index 0000000..da18f09 Binary files /dev/null and b/static/events/tornado_100.json.zip differ diff --git a/static/events/tornado_11155111.json.zip b/static/events/tornado_11155111.json.zip new file mode 100644 index 0000000..7094fb4 Binary files /dev/null and b/static/events/tornado_11155111.json.zip differ diff --git a/static/events/tornado_137.json.zip b/static/events/tornado_137.json.zip new file mode 100644 index 0000000..4ce9000 Binary files /dev/null and b/static/events/tornado_137.json.zip differ diff --git a/static/events/tornado_42161.json.zip b/static/events/tornado_42161.json.zip new file mode 100644 index 0000000..c9b0a62 Binary files /dev/null and b/static/events/tornado_42161.json.zip differ diff --git a/static/events/tornado_43114.json.zip b/static/events/tornado_43114.json.zip new file mode 100644 index 0000000..efaac8d Binary files /dev/null and b/static/events/tornado_43114.json.zip differ diff --git a/static/events/tornado_56.json.zip b/static/events/tornado_56.json.zip new file mode 100644 index 0000000..866a83b Binary files /dev/null and b/static/events/tornado_56.json.zip differ diff --git a/yarn.lock b/yarn.lock index d6c1bea..838e33c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -690,9 +690,9 @@ "@openzeppelin/contracts-v3" "npm:@openzeppelin/contracts@3.2.0-rc.0" ethers "^6.13.4" -"@tornado/core@git+https://git.tornado.ws/tornadocontrib/tornado-core.git#94a62e6193c99457a8dfae0d8684bee299cb1097": +"@tornado/core@git+https://git.tornado.ws/tornadocontrib/tornado-core.git#68fcf07c2a344a1f7f74895301f362aa059b53e2": version "1.0.19" - resolved "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#94a62e6193c99457a8dfae0d8684bee299cb1097" + resolved "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#68fcf07c2a344a1f7f74895301f362aa059b53e2" dependencies: "@ensdomains/content-hash" "2.5.7" "@metamask/eth-sig-util" "^8.0.0"