UNPKG

run-db

Version:

A local database that indexes jig states from RUN transactions

202 lines (162 loc) 7.27 kB
/** * indexer.js * * Main object that discovers, downloads, executes and stores RUN transactions */ const Database = require('./database') const Downloader = require('./downloader') const Executor = require('./executor') const Crawler = require('./crawler') // ------------------------------------------------------------------------------------------------ // Indexer // ------------------------------------------------------------------------------------------------ class Indexer { constructor (database, api, network, numParallelDownloads, numParallelExecutes, logger, startHeight, mempoolExpiration, defaultTrustlist) { this.onDownload = null this.onFailToDownload = null this.onIndex = null this.onFailToIndex = null this.onBlock = null this.onReorg = null this.logger = logger this.database = database this.api = api this.network = network this.startHeight = startHeight this.mempoolExpiration = mempoolExpiration this.defaultTrustlist = defaultTrustlist const fetchFunction = this.api.fetch ? this.api.fetch.bind(this.api) : null this.downloader = new Downloader(fetchFunction, numParallelDownloads) this.executor = new Executor(network, numParallelExecutes, this.database, this.logger) this.crawler = new Crawler(api, this.logger) this.database.onReadyToExecute = this._onReadyToExecute.bind(this) this.database.onAddTransaction = this._onAddTransaction.bind(this) this.database.onDeleteTransaction = this._onDeleteTransaction.bind(this) this.database.onTrustTransaction = this._onTrustTransaction.bind(this) this.database.onUntrustTransaction = this._onUntrustTransaction.bind(this) this.database.onBanTransaction = this._onBanTransaction.bind(this) this.database.onUnbanTransaction = this._onUnbanTransaction.bind(this) this.database.onUnindexTransaction = this._onUnindexTransaction.bind(this) this.database.onRequestDownload = this._onRequestDownload.bind(this) this.downloader.onDownloadTransaction = this._onDownloadTransaction.bind(this) this.downloader.onFailedToDownloadTransaction = this._onFailedToDownloadTransaction.bind(this) this.downloader.onRetryingDownload = this._onRetryingDownload.bind(this) this.executor.onIndexed = this._onIndexed.bind(this) this.executor.onExecuteFailed = this._onExecuteFailed.bind(this) this.executor.onMissingDeps = this._onMissingDeps.bind(this) this.crawler.onCrawlError = this._onCrawlError.bind(this) this.crawler.onCrawlBlockTransactions = this._onCrawlBlockTransactions.bind(this) this.crawler.onRewindBlocks = this._onRewindBlocks.bind(this) this.crawler.onMempoolTransaction = this._onMempoolTransaction.bind(this) this.crawler.onExpireMempoolTransactions = this._onExpireMempoolTransactions.bind(this) } async start () { this.logger.debug('Starting indexer') this.executor.start() this.defaultTrustlist.forEach(txid => this.database.trust(txid)) this.database.loadTransactionsToExecute() const height = this.database.getHeight() || this.startHeight const hash = this.database.getHash() if (this.api.connect) await this.api.connect(height, this.network) this.logger.debug('Loading transactions to download') this.database.getTransactionsToDownload().forEach(txid => this.downloader.add(txid)) this.crawler.start(height, hash) } async stop () { this.crawler.stop() if (this.api.disconnect) await this.api.disconnect() this.downloader.stop() await this.executor.stop() } _onDownloadTransaction (txid, hex, height, time) { this.logger.info(`Downloaded ${txid} (${this.downloader.remaining()} remaining)`) if (!this.database.hasTransaction(txid)) return if (height) this.database.setTransactionHeight(txid, height) if (time) this.database.setTransactionTime(txid, time) this.database.parseAndStoreTransaction(txid, hex) if (this.onDownload) this.onDownload(txid) } _onFailedToDownloadTransaction (txid, e) { this.logger.error('Failed to download', txid, e.toString()) if (this.onFailToDownload) this.onFailToDownload(txid) } _onRetryingDownload (txid, secondsToRetry) { this.logger.info('Retrying download', txid, 'after', secondsToRetry, 'seconds') } _onIndexed (txid, result) { if (!this.database.hasTransaction(txid)) return // Check not re-orged this.logger.info(`Executed ${txid}`) this.database.storeExecutedTransaction(txid, result) if (this.onIndex) this.onIndex(txid) } _onExecuteFailed (txid, e) { this.logger.error(`Failed to execute ${txid}: ${e.toString()}`) this.database.setTransactionExecutionFailed(txid) if (this.onFailToIndex) this.onFailToIndex(txid, e) } _onReadyToExecute (txid) { this.executor.execute(txid) } _onAddTransaction (txid) { this.logger.info('Added', txid) } _onDeleteTransaction (txid) { this.logger.info('Removed', txid) this.downloader.remove(txid) } _onTrustTransaction (txid) { this.logger.info('Trusted', txid) } _onUntrustTransaction (txid) { this.logger.info('Untrusted', txid) } _onBanTransaction (txid) { this.logger.info('Banned', txid) } _onUnbanTransaction (txid) { this.logger.info('Unbanned', txid) } _onUnindexTransaction (txid) { this.logger.info('Unindexed', txid) } _onRequestDownload (txid) { this.downloader.add(txid) } _onMissingDeps (txid, deptxids) { this.logger.debug(`Discovered ${deptxids.length} dep(s) for ${txid}`) this.database.addMissingDeps(txid, deptxids) deptxids.forEach(deptxid => this.downloader.add(deptxid)) } _onCrawlError (e) { this.logger.error(`Crawl error: ${e.toString()}`) } _onCrawlBlockTransactions (height, hash, time, txids, txhexs) { this.logger.info(`Crawled block ${height} for ${txids.length} transactions`) this.database.addBlock(txids, txhexs, height, hash, time) if (this.onBlock) this.onBlock(height) } _onRewindBlocks (newHeight) { this.logger.info(`Rewinding to block ${newHeight}`) const txids = this.database.getTransactionsAboveHeight(newHeight) this.database.transaction(() => { // Put all transactions back into the mempool. This is better than deleting them, because // when we assume they will just go into a different block, we don't need to re-execute. // If they don't make it into a block, then they will be expired in time. txids.forEach(txid => this.database.unconfirmTransaction(txid)) this.database.setHeight(newHeight) this.database.setHash(null) }) if (this.onReorg) this.onReorg(newHeight) } _onMempoolTransaction (txid, hex) { this.database.addTransaction(txid, hex, Database.HEIGHT_MEMPOOL, null) } _onExpireMempoolTransactions () { const expirationTime = Math.round(Date.now() / 1000) - this.mempoolExpiration const expired = this.database.getMempoolTransactionsBeforeTime(expirationTime) const deleted = new Set() this.database.transaction(() => expired.forEach(txid => this.database.deleteTransaction(txid, deleted))) } } // ------------------------------------------------------------------------------------------------ module.exports = Indexer