/* Ethereum Store * * This module is responsible for tracking any number of accounts * and caching their current balances & transaction counts. * * It also tracks transaction hashes, and checks their inclusion status * on each new block. */ const EventEmitter = require('events').EventEmitter const inherits = require('util').inherits const async = require('async') const clone = require('clone') const EthQuery = require('eth-query') module.exports = EthereumStore inherits(EthereumStore, EventEmitter) function EthereumStore(engine) { const self = this EventEmitter.call(self) self._currentState = { accounts: {}, transactions: {}, } self._query = new EthQuery(engine) engine.on('block', self._updateForBlock.bind(self)) } // // public // EthereumStore.prototype.getState = function () { const self = this return clone(self._currentState) } EthereumStore.prototype.addAccount = function (address) { const self = this self._currentState.accounts[address] = {} self._didUpdate() if (!self.currentBlockNumber) return self._updateAccount(self.currentBlockNumber, address, noop) } EthereumStore.prototype.removeAccount = function (address) { const self = this delete self._currentState.accounts[address] self._didUpdate() } EthereumStore.prototype.addTransaction = function (txHash) { const self = this self._currentState.transactions[txHash] = {} self._didUpdate() if (!self.currentBlockNumber) return self._updateTransaction(self.currentBlockNumber, txHash, noop) } EthereumStore.prototype.removeTransaction = function (address) { const self = this delete self._currentState.transactions[address] self._didUpdate() } // // private // EthereumStore.prototype._didUpdate = function () { const self = this var state = self.getState() self.emit('update', state) } EthereumStore.prototype._updateForBlock = function (block) { const self = this var blockNumber = '0x'+block.number.toString('hex') self.currentBlockNumber = blockNumber async.parallel([ self._updateAccounts.bind(self), self._updateTransactions.bind(self, blockNumber), ], function (err) { if (err) return console.error(err) self.emit('block', self.getState()) }) } EthereumStore.prototype._updateAccounts = function (cb) { const self = this var accountsState = self._currentState.accounts var addresses = Object.keys(accountsState) async.each(addresses, self._updateAccount.bind(self), cb) } EthereumStore.prototype._updateAccount = function (address, cb) { const self = this var accountsState = self._currentState.accounts self._query.getAccount(address, function (err, result) { if (err) return cb(err) result.address = address // only populate if the entry is still present if (accountsState[address]) { accountsState[address] = result self._didUpdate() } cb(null, result) }) } EthereumStore.prototype.getAccount = function (address, cb) { const block = 'latest' async.parallel({ balance: this._query.getBalance.bind(this, address, block), nonce: this._query.getNonce.bind(this, address, block), code: this._query.getCode.bind(this, address, block), }, cb) } EthereumStore.prototype._updateTransactions = function (block, cb) { const self = this var transactionsState = self._currentState.transactions var txHashes = Object.keys(transactionsState) async.each(txHashes, self._updateTransaction.bind(self, block), cb) } EthereumStore.prototype._updateTransaction = function (block, txHash, cb) { const self = this // would use the block here to determine how many confirmations the tx has var transactionsState = self._currentState.transactions self._query.getTransaction(txHash, function (err, result) { if (err) return cb(err) // only populate if the entry is still present if (transactionsState[txHash]) { transactionsState[txHash] = result self._didUpdate() } cb(null, result) }) } function noop() {}