From 08351f801abd1bfd2a8c7071c6a3b0bd40570a4e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 13:56:21 -0800 Subject: [PATCH 1/9] Move eth-store@1.1.0 into local repo. --- app/scripts/lib/eth-store.js | 132 +++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 app/scripts/lib/eth-store.js diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js new file mode 100644 index 000000000..aeb5667e5 --- /dev/null +++ b/app/scripts/lib/eth-store.js @@ -0,0 +1,132 @@ +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._updateAccountForBlock(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._updateAccountsForBlock.bind(self, blockNumber), + self._updateTransactions.bind(self, blockNumber), + ], function(err){ + if (err) return console.error(err) + self.emit('block', self.getState()) + }) +} + +EthereumStore.prototype._updateAccountsForBlock = function(block, cb) { + const self = this + var accountsState = self._currentState.accounts + var addresses = Object.keys(accountsState) + async.each(addresses, self._updateAccountForBlock.bind(self, block), cb) +} + +EthereumStore.prototype._updateAccountForBlock = function(block, address, cb) { + const self = this + var accountsState = self._currentState.accounts + self._query.getAccount(address, block, 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._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 valuesFor(obj){ + return Object.keys(obj).map(function(key){ return obj[key] }) +} + +function noop(){} From 3588aabdf2639f65a5896f2f249c1277f3c95b84 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:01:32 -0800 Subject: [PATCH 2/9] Removed reliance on eth-store internal custom eth-query --- app/scripts/lib/eth-store.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index aeb5667e5..e2451408c 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -34,7 +34,7 @@ EthereumStore.prototype.addAccount = function(address){ self._currentState.accounts[address] = {} self._didUpdate() if (!self.currentBlockNumber) return - self._updateAccountForBlock(self.currentBlockNumber, address, noop) + self._updateAccount(self.currentBlockNumber, address, noop) } EthereumStore.prototype.removeAccount = function(address){ @@ -73,7 +73,7 @@ EthereumStore.prototype._updateForBlock = function(block) { var blockNumber = '0x'+block.number.toString('hex') self.currentBlockNumber = blockNumber async.parallel([ - self._updateAccountsForBlock.bind(self, blockNumber), + self._updateAccounts.bind(self), self._updateTransactions.bind(self, blockNumber), ], function(err){ if (err) return console.error(err) @@ -81,17 +81,17 @@ EthereumStore.prototype._updateForBlock = function(block) { }) } -EthereumStore.prototype._updateAccountsForBlock = function(block, cb) { +EthereumStore.prototype._updateAccounts = function(cb) { const self = this var accountsState = self._currentState.accounts var addresses = Object.keys(accountsState) - async.each(addresses, self._updateAccountForBlock.bind(self, block), cb) + async.each(addresses, self._updateAccount.bind(self), cb) } -EthereumStore.prototype._updateAccountForBlock = function(block, address, cb) { +EthereumStore.prototype._updateAccount = function(address, cb) { const self = this var accountsState = self._currentState.accounts - self._query.getAccount(address, block, function(err, result){ + self._query.getAccount(address, function(err, result){ if (err) return cb(err) result.address = address // only populate if the entry is still present @@ -103,6 +103,15 @@ EthereumStore.prototype._updateAccountForBlock = function(block, address, cb) { }) } +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 From d4958b7ffd11e73c01ea637f1acf61f4f297a02d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:01:43 -0800 Subject: [PATCH 3/9] Remove old eth-store dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 4c33ad9ab..6f0fe00c7 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "eth-bin-to-ops": "^1.0.1", "eth-lightwallet": "^2.3.3", "eth-query": "^1.0.3", - "eth-store": "^1.1.0", "ethereumjs-tx": "^1.0.0", "ethereumjs-util": "^4.4.0", "ethereumjs-wallet": "^0.6.0", From 0fab22082a4583f2ca8b39c8a73ee338302d6561 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:02:13 -0800 Subject: [PATCH 4/9] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b10ff010f..a3b97d477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Remove certain non-essential permissions from certain builds. - Add a check for when a tx is included in a block. +- Fix bug where sometimes loading account data would fail by querying a future block. ## 2.14.1 2016-12-20 From 381a60695d218d2ae965df9a9a2566029302d133 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:05:00 -0800 Subject: [PATCH 5/9] Linted --- app/scripts/lib/eth-store.js | 39 ++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index e2451408c..0ed7ef545 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -24,12 +24,12 @@ function EthereumStore(engine) { // public // -EthereumStore.prototype.getState = function(){ +EthereumStore.prototype.getState = function () { const self = this return clone(self._currentState) } -EthereumStore.prototype.addAccount = function(address){ +EthereumStore.prototype.addAccount = function (address) { const self = this self._currentState.accounts[address] = {} self._didUpdate() @@ -37,13 +37,13 @@ EthereumStore.prototype.addAccount = function(address){ self._updateAccount(self.currentBlockNumber, address, noop) } -EthereumStore.prototype.removeAccount = function(address){ +EthereumStore.prototype.removeAccount = function (address) { const self = this delete self._currentState.accounts[address] self._didUpdate() } -EthereumStore.prototype.addTransaction = function(txHash){ +EthereumStore.prototype.addTransaction = function (txHash) { const self = this self._currentState.transactions[txHash] = {} self._didUpdate() @@ -51,7 +51,7 @@ EthereumStore.prototype.addTransaction = function(txHash){ self._updateTransaction(self.currentBlockNumber, txHash, noop) } -EthereumStore.prototype.removeTransaction = function(address){ +EthereumStore.prototype.removeTransaction = function (address) { const self = this delete self._currentState.transactions[address] self._didUpdate() @@ -62,36 +62,36 @@ EthereumStore.prototype.removeTransaction = function(address){ // private // -EthereumStore.prototype._didUpdate = function() { +EthereumStore.prototype._didUpdate = function () { const self = this var state = self.getState() self.emit('update', state) } -EthereumStore.prototype._updateForBlock = function(block) { +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){ + ], function (err) { if (err) return console.error(err) self.emit('block', self.getState()) }) } -EthereumStore.prototype._updateAccounts = function(cb) { +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) { +EthereumStore.prototype._updateAccount = function (address, cb) { const self = this var accountsState = self._currentState.accounts - self._query.getAccount(address, function(err, result){ + self._query.getAccount(address, function (err, result) { if (err) return cb(err) result.address = address // only populate if the entry is still present @@ -103,7 +103,7 @@ EthereumStore.prototype._updateAccount = function(address, cb) { }) } -EthereumStore.prototype.getAccount = function(address, cb){ +EthereumStore.prototype.getAccount = function (address, cb) { const block = 'latest' async.parallel({ balance: this._query.getBalance.bind(this, address, block), @@ -112,21 +112,20 @@ EthereumStore.prototype.getAccount = function(address, cb){ }, cb) } -EthereumStore.prototype._updateTransactions = function(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) { +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){ + self._query.getTransaction(txHash, function (err, result) { if (err) return cb(err) - // only populate if the entry is still present - if (transactionsState[txHash]) { + // only populate if the entry is still present if (transactionsState[txHash]) { transactionsState[txHash] = result self._didUpdate() } @@ -134,8 +133,4 @@ EthereumStore.prototype._updateTransaction = function(block, txHash, cb) { }) } -function valuesFor(obj){ - return Object.keys(obj).map(function(key){ return obj[key] }) -} - -function noop(){} +function noop() {} From 18e6d266c1ac31bb17a5f2816167ea2d4b01ad64 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:07:08 -0800 Subject: [PATCH 6/9] Add descriptive comment --- app/scripts/lib/eth-store.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 0ed7ef545..cca6f7d5d 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -1,3 +1,12 @@ +/* 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') From a107b66854a736a2809fba2a490039b9920b92d5 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:14:45 -0800 Subject: [PATCH 7/9] Caught typo --- app/scripts/lib/eth-store.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index cca6f7d5d..3b5501095 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -79,7 +79,7 @@ EthereumStore.prototype._didUpdate = function () { EthereumStore.prototype._updateForBlock = function (block) { const self = this - var blockNumber = '0x'+block.number.toString('hex') + var blockNumber = '0x' + block.number.toString('hex') self.currentBlockNumber = blockNumber async.parallel([ self._updateAccounts.bind(self), @@ -134,7 +134,8 @@ EthereumStore.prototype._updateTransaction = function (block, txHash, cb) { 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]) { + // only populate if the entry is still present + if (transactionsState[txHash]) { transactionsState[txHash] = result self._didUpdate() } From ead8a05034e22da36d2a0d02109f3de35f1785f1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:21:36 -0800 Subject: [PATCH 8/9] Fix dependency reference --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c0d2f3b4c..8bfb85370 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1,5 +1,5 @@ const extend = require('xtend') -const EthStore = require('eth-store') +const EthStore = require('./lib/eth-store') const MetaMaskProvider = require('web3-provider-engine/zero.js') const KeyringController = require('./keyring-controller') const NoticeController = require('./notice-controller') From 6c99d09404d4b0a43d49c7a98351018f61229542 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Jan 2017 14:54:40 -0800 Subject: [PATCH 9/9] Fixed bugs with sanity-checking - Was incorrectly calling some eth-query methods (left over from old local eth-query) - Was still passing block to getAccount in addAccount - Now emitting update only after all account balances are loaded, reducing UI update traffic. --- app/scripts/lib/eth-store.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 3b5501095..a42b2417f 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -43,7 +43,7 @@ EthereumStore.prototype.addAccount = function (address) { self._currentState.accounts[address] = {} self._didUpdate() if (!self.currentBlockNumber) return - self._updateAccount(self.currentBlockNumber, address, noop) + self._updateAccount(address, noop) } EthereumStore.prototype.removeAccount = function (address) { @@ -87,37 +87,35 @@ EthereumStore.prototype._updateForBlock = function (block) { ], function (err) { if (err) return console.error(err) self.emit('block', self.getState()) + self._didUpdate() }) } EthereumStore.prototype._updateAccounts = function (cb) { - const self = this - var accountsState = self._currentState.accounts + var accountsState = this._currentState.accounts var addresses = Object.keys(accountsState) - async.each(addresses, self._updateAccount.bind(self), cb) + async.each(addresses, this._updateAccount.bind(this), cb) } EthereumStore.prototype._updateAccount = function (address, cb) { - const self = this - var accountsState = self._currentState.accounts - self._query.getAccount(address, function (err, result) { + var accountsState = this._currentState.accounts + this.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' + const query = this._query 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), + balance: query.getBalance.bind(query, address), + nonce: query.getTransactionCount.bind(query, address), + code: query.getCode.bind(query, address), }, cb) }