UNPKG

origintrail-node

Version:

OriginTrail Node - Decentralized Knowledge Graph Node Library

243 lines (210 loc) 9.48 kB
/* eslint-disable no-await-in-loop */ import { ethers } from 'ethers'; import BlockchainEventsService from '../blockchain-events-service.js'; import { MAXIMUM_NUMBERS_OF_BLOCKS_TO_FETCH, MAX_BLOCKCHAIN_EVENT_SYNC_OF_HISTORICAL_BLOCKS_IN_MILLS, NODE_ENVIRONMENTS, ABIs, MONITORED_CONTRACTS, } from '../../../../constants/constants.js'; class OtEthers extends BlockchainEventsService { async initialize(config, logger) { await super.initialize(config, logger); this.contractCallCache = {}; await this._initializeRpcProviders(); await this._initializeContracts(); } async _initializeRpcProviders() { this.providers = {}; for (const blockchain of this.config.blockchains) { const validProviders = []; for (const rpcEndpoint of this.config.rpcEndpoints[blockchain]) { try { const provider = new ethers.providers.JsonRpcProvider(rpcEndpoint); // eslint-disable-next-line no-await-in-loop await provider.getNetwork(); validProviders.push(provider); } catch (error) { this.logger.error( `Failed to initialize provider: ${rpcEndpoint}. Error: ${error.message}`, ); } } if (validProviders.length === 0) { throw new Error(`No valid providers found for blockchain: ${blockchain}`); } this.providers[blockchain] = validProviders; this.logger.info( `Initialized ${validProviders.length} valid providers for blockchain: ${blockchain}`, ); } } _getRandomProvider(blockchain) { const blockchainProviders = this.providers[blockchain]; if (!blockchainProviders || blockchainProviders.length === 0) { throw new Error(`No providers available for blockchain: ${blockchain}`); } const randomIndex = Math.floor(Math.random() * blockchainProviders.length); return blockchainProviders[randomIndex]; } async _initializeContracts() { this.contracts = {}; for (const blockchain of this.config.blockchains) { this.contracts[blockchain] = {}; this.logger.info( `Initializing contracts with hub contract address: ${this.config.hubContractAddress[blockchain]}`, ); this.contracts[blockchain].Hub = this.config.hubContractAddress[blockchain]; const provider = this._getRandomProvider(blockchain); const hubContract = new ethers.Contract( this.config.hubContractAddress[blockchain], ABIs.Hub, provider, ); const contractsAray = await hubContract.getAllContracts(); const assetStoragesArray = await hubContract.getAllAssetStorages(); const allContracts = [...contractsAray, ...assetStoragesArray]; for (const [contractName, contractAddress] of allContracts) { if (MONITORED_CONTRACTS.includes(contractName) && ABIs[contractName] != null) { this.contracts[blockchain][contractName] = contractAddress; } } } } getContractAddress(blockchain, contractName) { return this.contracts[blockchain][contractName]; } updateContractAddress(blockchain, contractName, contractAddress) { this.contracts[blockchain][contractName] = contractAddress; } async getBlock(blockchain, tag) { const provider = this._getRandomProvider(blockchain); return provider.getBlock(tag); } async getPastEvents(blockchain, contractNames, eventsToFilter, lastCheckedBlock, currentBlock) { const maxBlocksToSync = await this._getMaxNumberOfHistoricalBlocksForSync(blockchain); let fromBlock = currentBlock - lastCheckedBlock > maxBlocksToSync ? currentBlock : lastCheckedBlock + 1; const eventsMissed = currentBlock - lastCheckedBlock > maxBlocksToSync; if (eventsMissed) { return { events: [], lastCheckedBlock: currentBlock, eventsMissed, }; } const contractAddresses = []; const topics = []; const addressToContractNameMap = {}; for (const contractName of contractNames) { const contractAddress = this.contracts[blockchain][contractName]; if (!contractAddress) { continue; } const provider = this._getRandomProvider(blockchain); const contract = new ethers.Contract(contractAddress, ABIs[contractName], provider); const contractTopics = []; for (const filterName in contract.filters) { if (!eventsToFilter.includes(filterName)) { continue; } const filter = contract.filters[filterName]().topics[0]; contractTopics.push(filter); } if (contractTopics.length > 0) { contractAddresses.push(contract.address); topics.push(...contractTopics); addressToContractNameMap[contract.address.toLowerCase()] = contractName; } } const events = []; let toBlock = currentBlock; try { while (fromBlock <= currentBlock) { toBlock = Math.min( fromBlock + MAXIMUM_NUMBERS_OF_BLOCKS_TO_FETCH - 1, currentBlock, ); const fromBlockParam = ethers.BigNumber.from(fromBlock) .toHexString() .replace(/^0x0+/, '0x'); const toBlockParam = ethers.BigNumber.from(toBlock) .toHexString() .replace(/^0x0+/, '0x'); const provider = this._getRandomProvider(blockchain); const newLogs = await provider.send('eth_getLogs', [ { address: contractAddresses, fromBlock: fromBlockParam, toBlock: toBlockParam, topics: [topics], }, ]); for (const log of newLogs) { const contractName = addressToContractNameMap[log.address]; const contractInterface = new ethers.utils.Interface(ABIs[contractName]); try { const parsedLog = contractInterface.parseLog(log); events.push({ blockchain, contract: contractName, contractAddress: log.address, event: parsedLog.name, data: JSON.stringify( Object.fromEntries( Object.entries(parsedLog.args).map(([k, v]) => [ k, ethers.BigNumber.isBigNumber(v) ? v.toString() : v, ]), ), ), blockNumber: parseInt(log.blockNumber, 16), transactionIndex: parseInt(log.transactionIndex, 16), logIndex: parseInt(log.logIndex, 16), txHash: log.transactionHash, }); } catch (error) { this.logger.warn( `Failed to parse log for contract: ${contractName}. Error: ${error.message}`, ); } } fromBlock = toBlock + 1; } } catch (error) { this.logger.warn( `Unable to process block range from: ${fromBlock} to: ${toBlock} on blockchain: ${blockchain}. Error: ${error.message}`, ); } return { events, eventsMissed, }; } async _getMaxNumberOfHistoricalBlocksForSync(blockchain) { if (!this.maxNumberOfHistoricalBlocksForSync) { if ( [NODE_ENVIRONMENTS.DEVELOPMENT, NODE_ENVIRONMENTS.TEST].includes( process.env.NODE_ENV, ) ) { this.maxNumberOfHistoricalBlocksForSync = Infinity; } else { const blockTimeMillis = await this._getBlockTimeMillis(blockchain); this.maxNumberOfHistoricalBlocksForSync = Math.round( // 60 * 60 * 1000 = 1 hour MAX_BLOCKCHAIN_EVENT_SYNC_OF_HISTORICAL_BLOCKS_IN_MILLS / blockTimeMillis, ); } } return this.maxNumberOfHistoricalBlocksForSync; } async _getBlockTimeMillis(blockchain, blockRange = 1000) { const latestBlock = await this.getBlock(blockchain); const olderBlock = await this.getBlock(blockchain, latestBlock.number - blockRange); const timeDiffMillis = (latestBlock.timestamp - olderBlock.timestamp) * 1000; return timeDiffMillis / blockRange; } } export default OtEthers;