"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SyncManager = void 0; exports.syncGasPrice = syncGasPrice; exports.syncPrices = syncPrices; exports.syncNetworkEvents = syncNetworkEvents; const contracts_1 = require("@tornado/contracts"); const core_1 = require("@tornado/core"); const logger_1 = require("./logger"); const treeCache_1 = require("./treeCache"); const events_1 = require("./events"); const error_1 = require("./error"); function setupServices(syncManager) { const { relayerConfig, logger, syncStatus } = syncManager; const { cacheDir: cacheDirectory, userEventsDir: userDirectory, userTreeDir, merkleWorkerPath, enabledNetworks, enableTrees, } = relayerConfig; const cachedServices = {}; for (const netId of enabledNetworks) { const config = (0, core_1.getConfig)(netId); const rpcUrl = relayerConfig.rpcUrls[netId]; const provider = (0, core_1.getProviderWithNetId)(netId, rpcUrl, 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, tokenPrice: false, gasPrice: false, }; } const services = (cachedServices[netId] = {}); services.provider = provider; services.tokenPriceOracle = new core_1.TokenPriceOracle(provider, core_1.Multicall__factory.connect(multicallContract, provider), offchainOracleContract ? core_1.OffchainOracle__factory.connect(offchainOracleContract, provider) : undefined); services.tornadoFeeOracle = new core_1.TornadoFeeOracle(provider, ovmGasPriceOracleContract ? core_1.OvmGasPriceOracle__factory.connect(ovmGasPriceOracleContract, provider) : undefined); if (governanceContract && aggregatorContract && reverseRecordsContract) { services.governanceService = new events_1.NodeGovernanceService({ netId, provider, Governance: contracts_1.Governance__factory.connect(governanceContract, provider), Aggregator: contracts_1.Aggregator__factory.connect(aggregatorContract, provider), ReverseRecords: core_1.ReverseRecords__factory.connect(reverseRecordsContract, provider), deployedBlock: GOVERNANCE_BLOCK, cacheDirectory, userDirectory, logger, }); } if (registryContract && aggregatorContract) { services.registryService = new events_1.NodeRegistryService({ netId, provider, RelayerRegistry: contracts_1.RelayerRegistry__factory.connect(registryContract, provider), Aggregator: contracts_1.Aggregator__factory.connect(aggregatorContract, provider), relayerEnsSubdomains: (0, core_1.getRelayerEnsSubdomains)(), deployedBlock: REGISTRY_BLOCK, cacheDirectory, userDirectory, logger, }); services.revenueService = new events_1.NodeRevenueService({ netId, provider, RelayerRegistry: contracts_1.RelayerRegistry__factory.connect(registryContract, provider), deployedBlock: REGISTRY_BLOCK, cacheDirectory, userDirectory, logger, }); } services.echoService = new events_1.NodeEchoService({ netId, provider, Echoer: contracts_1.Echoer__factory.connect(echoContract, provider), deployedBlock: NOTE_ACCOUNT_BLOCK, cacheDirectory, userDirectory, logger, }); services.encryptedNotesService = new events_1.NodeEncryptedNotesService({ netId, provider, Router: contracts_1.TornadoRouter__factory.connect(routerContract, provider), deployedBlock: ENCRYPTED_NOTES_BLOCK, cacheDirectory, userDirectory, logger, }); const instances = (0, core_1.getMultiInstances)(netId, config); const [firstInstance, { amount, currency }] = Object.entries(instances)[0]; const tornadoServiceConstructor = { netId, provider, instances, deployedBlock, nativeCurrency, cacheDirectory, userDirectory, logger, }; if (enableTrees) { tornadoServiceConstructor.merkleTreeService = new core_1.MerkleTreeService({ netId, amount, currency, Tornado: contracts_1.Tornado__factory.connect(firstInstance, provider), merkleWorkerPath, }); tornadoServiceConstructor.treeCache = new treeCache_1.TreeCache({ userDirectory: userTreeDir, }); tornadoServiceConstructor.optionalTree = true; } services.tornadoServices = new events_1.NodeMultiTornadoService(tornadoServiceConstructor); } syncManager.cachedServices = cachedServices; } async function syncGasPrice(syncManager, netId) { const { cachedServices, logger, errors, cachedGasPrices, latestBlocks, latestBalances, syncStatus, relayerConfig: { rewardAccount }, } = syncManager; try { const services = cachedServices[netId]; const { provider, tornadoFeeOracle } = services; const [blockNumber, balance, gasPrice, l1Fee] = await Promise.all([ provider.getBlockNumber(), provider.getBalance(rewardAccount), tornadoFeeOracle.gasPrice(20), tornadoFeeOracle.fetchL1OptimismFee(), ]); cachedGasPrices[netId] = { gasPrice: gasPrice.toString(), l1Fee: l1Fee ? l1Fee.toString() : undefined, }; latestBlocks[netId] = blockNumber; latestBalances[netId] = balance.toString(); syncStatus[netId].gasPrice = true; } catch (err) { logger.error(`${netId}: Failed to sync gas prices`); console.log(err); syncStatus[netId].gasPrice = false; errors.push((0, error_1.newError)('SyncManager (gas)', netId, err)); } } async function syncPrices(syncManager, netId) { const { cachedServices, logger, errors, cachedPrices, syncStatus } = syncManager; try { const config = (0, core_1.getConfig)(netId); const { nativeCurrency, tornContract } = config; const services = cachedServices[netId]; const { tokenPriceOracle } = services; // Classic UI ajv validator requires all token prices to present const allTokens = Object.keys(config.tokens); if (tornContract && !allTokens.includes('torn')) { allTokens.push('torn'); } const tokens = allTokens .map((currency) => { if (currency === nativeCurrency) { return; } if (currency === 'torn') { return { currency, tokenAddress: tornContract, decimals: 18, }; } const { tokenAddress, decimals } = config.tokens[currency]; return { currency, tokenAddress, decimals, }; }) .filter((t) => t); if (!tokens.length) { syncStatus[netId].tokenPrice = true; return; } cachedPrices[netId] = (await tokenPriceOracle.fetchPrices(tokens)).reduce((acc, price, index) => { acc[tokens[index].currency] = price; return acc; }, {}); syncStatus[netId].tokenPrice = true; logger.info(`${netId}: Synced ${tokens.length} tokens price`); } catch (err) { logger.error(`${netId}: Failed to sync prices`); console.log(err); syncStatus[netId].tokenPrice = false; errors.push((0, error_1.newError)('SyncManager (price)', netId, err)); } } async function syncNetworkEvents(syncManager, netId) { const { cachedEvents, cachedServices, logger, errors, syncStatus } = syncManager; try { const services = cachedServices[netId]; const { provider, governanceService, registryService, revenueService, echoService, encryptedNotesService, tornadoServices, } = services; logger.info(`${netId}: Syncing events from block ${await provider.getBlockNumber()}`); const eventsStatus = { governance: governanceService ? {} : undefined, registry: registryService ? {} : undefined, revenue: revenueService ? {} : undefined, echo: {}, encrypted_notes: {}, tornado: {}, }; if (governanceService) { const { events, lastBlock } = await governanceService.updateEvents(); eventsStatus.governance = { events: events.length, lastBlock, }; logger.info(`${netId}: Updated governance events (total: ${events.length}, block: ${lastBlock})`); } if (registryService) { { const { events, lastBlock } = await registryService.updateEvents(); eventsStatus.registry = { events: events.length, lastBlock, }; logger.info(`${netId}: Updated registry events (total: ${events.length}, block: ${lastBlock})`); } { const { lastBlock, timestamp, relayers } = await registryService.updateRelayers(); logger.info(`${netId}: Updated registry relayers (total: ${relayers.length}, block: ${lastBlock}, timestamp: ${timestamp})`); } } if (revenueService) { const { events, lastBlock } = await revenueService.updateEvents(); eventsStatus.revenue = { events: events.length, lastBlock, }; logger.info(`${netId}: Updated revenue events (total: ${events.length}, block: ${lastBlock})`); } const echoEvents = await echoService.updateEvents(); eventsStatus.echo = { events: echoEvents.events.length, lastBlock: echoEvents.lastBlock, }; logger.info(`${netId}: Updated echo events (total: ${echoEvents.events.length}, block: ${echoEvents.lastBlock})`); const encryptedNotesEvents = await encryptedNotesService.updateEvents(); eventsStatus.encrypted_notes = { events: encryptedNotesEvents.events.length, lastBlock: encryptedNotesEvents.lastBlock, }; logger.info(`${netId}: Updated encrypted notes events (total: ${encryptedNotesEvents.events.length}, block: ${encryptedNotesEvents.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`); await Promise.all([syncPrices(syncManager, netId), syncGasPrice(syncManager, netId)]); } catch (err) { logger.error(`${netId}: Failed to sync events`); console.log(err); syncStatus[netId].events = false; errors.push((0, error_1.newError)('SyncManager (events)', netId, err)); } } class SyncManager { relayerConfig; logger; cachedServices; cachedEvents; cachedPrices; cachedGasPrices; syncStatus; latestBlocks; latestBalances; errors; onSyncEvents; constructor(relayerConfig) { this.relayerConfig = relayerConfig; this.logger = (0, logger_1.getLogger)('[SyncManager]', relayerConfig.logLevel); this.cachedServices = {}; this.cachedEvents = {}; this.cachedPrices = {}; this.cachedGasPrices = {}; this.syncStatus = {}; this.latestBlocks = {}; this.latestBalances = {}; this.errors = []; this.onSyncEvents = false; setupServices(this); } getStatus() { return { cachedEvents: this.cachedEvents, cachedPrices: JSON.parse(JSON.stringify(this.cachedPrices)), cachedGasPrices: JSON.parse(JSON.stringify(this.cachedGasPrices)), syncStatus: this.syncStatus, latestBlocks: this.latestBlocks, latestBalances: this.latestBalances, errors: this.errors.map(({ type, netId, timestamp }) => ({ type, netId, timestamp, })), onSyncEvents: this.onSyncEvents, }; } getPrice(netId, token) { return this.cachedPrices[netId]?.[token] || BigInt(0); } getGasPrice(netId) { return this.cachedGasPrices[netId]; } async syncEvents() { if (this.onSyncEvents) { return; } this.onSyncEvents = true; await Promise.all(this.relayerConfig.enabledNetworks.map((netId) => syncNetworkEvents(this, Number(netId)))); this.onSyncEvents = false; } } exports.SyncManager = SyncManager;