From b471afcdb34cd121b9d3e3cccb3883943ba8aef5 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 2 Aug 2017 19:24:34 -0400 Subject: [PATCH 01/21] use error for #approveTransaction when setting failed --- app/scripts/controllers/transactions.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 720323e41..d4f32e049 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -229,11 +229,8 @@ module.exports = class TransactionController extends EventEmitter { // must set transaction to submitted/failed before releasing lock nonceLock.releaseLock() } catch (err) { - this.setTxStatusFailed(txId, { - stack: err.stack || err.message, - errCode: err.errCode || err, - message: err.message || 'Transaction failed during approval', - }) + if(!err.message) err.message = 'Transaction failed during approval' + this.setTxStatusFailed(txId, err) // must set transaction to submitted/failed before releasing lock if (nonceLock) nonceLock.releaseLock() // continue with error chain From 3dcc199845f4026cc4d02f9f760332ac9752ff69 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 13:27:08 -0400 Subject: [PATCH 02/21] rename the test description Transaction Manger -> Message Manger --- test/unit/message-manager-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js index 30cb4f067..9b76241ed 100644 --- a/test/unit/message-manager-test.js +++ b/test/unit/message-manager-test.js @@ -1,7 +1,7 @@ const assert = require('assert') const MessageManger = require('../../app/scripts/lib/message-manager') -describe('Transaction Manager', function () { +describe('Message Manager', function () { let messageManager beforeEach(function () { From caee2a9e35c0e80efed9da0798cb75044db6c920 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 13:55:00 -0400 Subject: [PATCH 03/21] move util functions to util.js --- app/scripts/lib/tx-utils.js | 41 ++++++++----------------------------- app/scripts/lib/util.js | 31 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 3687a9652..a2db4abd8 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -1,7 +1,11 @@ -const ethUtil = require('ethereumjs-util') +const EthQuery = require('ethjs-query') const Transaction = require('ethereumjs-tx') const normalize = require('eth-sig-util').normalize -const BN = ethUtil.BN +const { + hexToBn, + BnMultiplyByFraction, + bnToHex, +} = require('./util') /* tx-utils are utility methods for Transaction manager @@ -10,8 +14,8 @@ and used to do things like calculate gas of a tx. */ module.exports = class txProvideUtils { - constructor (ethQuery) { - this.query = ethQuery + constructor (provider) { + this.query = new EthQuery(provider) } async analyzeGasUsage (txMeta) { @@ -91,31 +95,4 @@ module.exports = class txProvideUtils { throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) } } - - sufficientBalance (txParams, hexBalance) { - const balance = hexToBn(hexBalance) - const value = hexToBn(txParams.value) - const gasLimit = hexToBn(txParams.gas) - const gasPrice = hexToBn(txParams.gasPrice) - - const maxCost = value.add(gasLimit.mul(gasPrice)) - return balance.gte(maxCost) - } - -} - -// util - -function bnToHex (inputBn) { - return ethUtil.addHexPrefix(inputBn.toString(16)) -} - -function hexToBn (inputHex) { - return new BN(ethUtil.stripHexPrefix(inputHex), 16) -} - -function BnMultiplyByFraction (targetBN, numerator, denominator) { - const numBN = new BN(numerator) - const denomBN = new BN(denominator) - return targetBN.mul(numBN).div(denomBN) -} +} \ No newline at end of file diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index bddd60ee8..70390e95c 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -1,8 +1,39 @@ +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN + module.exports = { getStack, + sufficientBalance, + hexToBn, + bnToHex, + BnMultiplyByFraction, } function getStack () { const stack = new Error('Stack trace generator - not an error').stack return stack } + +function sufficientBalance (txParams, hexBalance) { + const balance = hexToBn(hexBalance) + const value = hexToBn(txParams.value) + const gasLimit = hexToBn(txParams.gas) + const gasPrice = hexToBn(txParams.gasPrice) + + const maxCost = value.add(gasLimit.mul(gasPrice)) + return balance.gte(maxCost) +} + +function bnToHex (inputBn) { + return ethUtil.addHexPrefix(inputBn.toString(16)) +} + +function hexToBn (inputHex) { + return new BN(ethUtil.stripHexPrefix(inputHex), 16) +} + +function BnMultiplyByFraction (targetBN, numerator, denominator) { + const numBN = new BN(numerator) + const denomBN = new BN(denominator) + return targetBN.mul(numBN).div(denomBN) +} From 4ac0b972025ddd55a9a42b4a15db0a5f207f4078 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 14:36:27 -0400 Subject: [PATCH 04/21] test for SufficientBalance --- test/unit/util-test.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/unit/util-test.js diff --git a/test/unit/util-test.js b/test/unit/util-test.js new file mode 100644 index 000000000..7716e4872 --- /dev/null +++ b/test/unit/util-test.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const { sufficientBalance } = require('../../app/scripts/lib/util') + + +describe.only('SufficientBalance', function () { + it('returns true if max tx cost is equal to balance.', function () { + const tx = { + 'value': '0x1', + 'gas': '0x2', + 'gasPrice': '0x3', + } + const balance = '0x8' + + const result = sufficientBalance(tx, balance) + assert.ok(result, 'sufficient balance found.') + }) + + it('returns true if max tx cost is less than balance.', function () { + const tx = { + 'value': '0x1', + 'gas': '0x2', + 'gasPrice': '0x3', + } + const balance = '0x9' + + const result = sufficientBalance(tx, balance) + assert.ok(result, 'sufficient balance found.') + }) + + it('returns false if max tx cost is more than balance.', function () { + const tx = { + 'value': '0x1', + 'gas': '0x2', + 'gasPrice': '0x3', + } + const balance = '0x6' + + const result = sufficientBalance(tx, balance) + assert.ok(!result, 'insufficient balance found.') + }) +}) \ No newline at end of file From 54739cb798b70e14d7774d9e92745188afca5461 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 14:40:22 -0400 Subject: [PATCH 05/21] test for pending tx watcher --- test/unit/pending-tx-test.js | 271 ++++++++++++++++++++++++++++++++ test/unit/tx-controller-test.js | 49 +----- test/unit/tx-utils-test.js | 58 +------ test/unit/util-test.js | 2 +- 4 files changed, 280 insertions(+), 100 deletions(-) create mode 100644 test/unit/pending-tx-test.js diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js new file mode 100644 index 000000000..5103a9b62 --- /dev/null +++ b/test/unit/pending-tx-test.js @@ -0,0 +1,271 @@ +const assert = require('assert') +const ethUtil = require('ethereumjs-util') +const EthTx = require('ethereumjs-tx') +const ObservableStore = require('obs-store') +const clone = require('clone') +const PendingTransactionWatcher = require('../../app/scripts/lib/pending-tx-watchers') +const noop = () => true +const currentNetworkId = 42 +const otherNetworkId = 36 +const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') + +describe('PendingTransactionWatcher', function () { + let pendingTxWatcher, txMeta, txMetaNoHash, txMetaNoRawTx + this.timeout(10000) + beforeEach(function () { + txMeta = { + id: 1, + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eebBlock', + status: 'signed', + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x1', + value: '0xfffff', + }, + rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + } + txMetaNoHash = { + id: 2, + status: 'signed', + txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, + } + txMetaNoRawTx = { + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eebBlock', + status: 'signed', + txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, + } + + pendingTxWatcher = new PendingTransactionWatcher({ + provider: { sendAsync: noop }, + getBalance: () => {}, + nonceTracker: { + getGlobalLock: async () => { + return { releaseLock: () => {} } + } + }, + getPendingTransactions: () => {return []}, + sufficientBalance: () => {}, + publishTransaction: () => {}, + }) + + pendingTxWatcher.query = new Proxy({}, { + get: (queryStubResult, key) => { + if (key === 'stubResult') { + return function (method, ...args) { + queryStubResult[method] = args + } + } else { + const returnValues = queryStubResult[key] + return () => Promise.resolve(...returnValues) + } + }, + }) + + }) + + describe('#checkForTxInBlock', function () { + it('should return if no pending transactions', function () { + // throw a type error if it trys to do anything on the block + // thus failing the test + const block = Proxy.revocable({}, {}).revoke() + pendingTxWatcher.checkForTxInBlock(block) + }) + it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { + const block = Proxy.revocable({}, {}).revoke() + pendingTxWatcher.getPendingTransactions = () => [txMetaNoHash] + pendingTxWatcher.once('txFailed', (txId, err) => { + assert(txId, txMetaNoHash.id, 'should pass txId') + done() + }) + pendingTxWatcher.checkForTxInBlock(block) + }) + it('should emit \'txConfirmed\' if the tx is in the block', function (done) { + const block = { transactions: [txMeta]} + pendingTxWatcher.getPendingTransactions = () => [txMeta] + pendingTxWatcher.once('txConfirmed', (txId) => { + assert(txId, txMeta.id, 'should pass txId') + done() + }) + pendingTxWatcher.once('txFailed', (_, err) => { done(err) }) + pendingTxWatcher.checkForTxInBlock(block) + }) + }) + describe('#queryPendingTxs', function () { + it('should call #_checkPendingTxs if their is no oldBlock', function (done) { + let newBlock, oldBlock + newBlock = { number: '0x01' } + pendingTxWatcher._checkPendingTxs = done + pendingTxWatcher.queryPendingTxs({oldBlock, newBlock}) + }) + it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { + let newBlock, oldBlock + oldBlock = { number: '0x01' } + newBlock = { number: '0x03' } + pendingTxWatcher._checkPendingTxs = done + pendingTxWatcher.queryPendingTxs({oldBlock, newBlock}) + }) + it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { + let newBlock, oldBlock + oldBlock = { number: '0x1' } + newBlock = { number: '0x2' } + pendingTxWatcher._checkPendingTxs = () => { + const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') + done(err) + } + pendingTxWatcher.queryPendingTxs({oldBlock, newBlock}) + done() + }) + }) + + describe('#_checkPendingTx', function () { + it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { + pendingTxWatcher.once('txFailed', (txId, err) => { + assert(txId, txMetaNoHash.id, 'should pass txId') + done() + }) + pendingTxWatcher._checkPendingTx(txMetaNoHash) + }) + + it('should should return if query does not return txParams', function () { + pendingTxWatcher.query.stubResult('getTransactionByHash', null) + pendingTxWatcher._checkPendingTx(txMeta) + }) + + it('should emit \'txConfirmed\'', function (done) { + pendingTxWatcher.query.stubResult('getTransactionByHash', {blockNumber: '0x01'}) + pendingTxWatcher.once('txConfirmed', (txId) => { + assert(txId, txMeta.id, 'should pass txId') + done() + }) + pendingTxWatcher.once('txFailed', (_, err) => { done(err) }) + pendingTxWatcher._checkPendingTx(txMeta) + }) + }) + + describe('#_checkPendingTxs', function () { + beforeEach(function () { + const txMeta2 = txMeta3 = txMeta + txMeta2.id = 2 + txMeta3.id = 3 + txList = [txMeta, txMeta2, txMeta3].map((tx) => { + tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) + return tx + }) + }) + + it('should warp all txMeta\'s in #_checkPendingTx', function (done) { + pendingTxWatcher.getPendingTransactions = () => txList + pendingTxWatcher._checkPendingTx = (tx) => { tx.resolve(tx) } + const list = txList.map + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + + pendingTxWatcher._checkPendingTxs() + }) + }) + + describe('#resubmitPendingTxs', function () { + beforeEach(function () { + const txMeta2 = txMeta3 = txMeta + txList = [txMeta, txMeta2, txMeta3].map((tx) => { + tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) + return tx + }) + }) + + it('should return if no pending transactions', function () { + pendingTxWatcher.resubmitPendingTxs() + }) + it('should call #_resubmitTx for all pending tx\'s', function (done) { + pendingTxWatcher.getPendingTransactions = () => txList + pendingTxWatcher._resubmitTx = async (tx) => { tx.resolve(tx) } + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + pendingTxWatcher.resubmitPendingTxs() + }) + it('should not emit \'txFailed\' if the txMeta throws a known txError', function (done) { + knownErrors =[ + // geth + ' Replacement transaction Underpriced ', + ' known transaction', + // parity + 'Gas price too low to replace ', + ' transaction with the sAme hash was already imported', + // other + ' gateway timeout', + ' noncE too low ', + ] + const enoughForAllErrors = txList.concat(txList) + + pendingTxWatcher.on('txFailed', (_, err) => done(err)) + + pendingTxWatcher.getPendingTransactions = () => enoughForAllErrors + pendingTxWatcher._resubmitTx = async (tx) => { + tx.resolve() + throw new Error(knownErrors.pop()) + } + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + + pendingTxWatcher.resubmitPendingTxs() + }) + it('should emit \'txFailed\' if it encountered a real error', function (done) { + pendingTxWatcher.once('txFailed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err)) + + pendingTxWatcher.getPendingTransactions = () => txList + pendingTxWatcher._resubmitTx = async (tx) => { throw new TypeError('im some real error') } + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + + pendingTxWatcher.resubmitPendingTxs() + }) + }) + describe('#_resubmitTx with a too-low balance', function () { + it('should return before publishing the transaction because to low of balance', function (done) { + const lowBalance = '0x0' + pendingTxWatcher.getBalance = (address) => { + assert.equal(address, txMeta.txParams.from, 'Should pass the address') + return lowBalance + } + pendingTxWatcher.publishTransaction = async (rawTx) => { + done(new Error('tried to publish transaction')) + } + + // Stubbing out current account state: + // Adding the fake tx: + pendingTxWatcher.once('txWarning', (txMeta) => { + assert(txMeta.warning.message, 'Should have a warning message') + done() + }) + pendingTxWatcher._resubmitTx(txMeta) + .catch((err) => { + assert.ifError(err, 'should not throw an error') + done(err) + }) + }) + + it('should publishing the transaction', function (done) { + const enoughBalance = '0x100000' + pendingTxWatcher.getBalance = (address) => { + assert.equal(address, txMeta.txParams.from, 'Should pass the address') + return enoughBalance + } + pendingTxWatcher.publishTransaction = async (rawTx) => { + assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx') + } + + // Stubbing out current account state: + // Adding the fake tx: + pendingTxWatcher._resubmitTx(txMeta) + .then(() => done()) + .catch((err) => { + assert.ifError(err, 'should not throw an error') + done(err) + }) + }) + }) +}) \ No newline at end of file diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index f290088a1..e54dc9719 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -27,7 +27,8 @@ describe('Transaction Controller', function () { }), }) txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) - txController.query = new Proxy({}, { + txController.txProviderUtils = new TxProvideUtils(txController.provider) + txController.query = txController.txProviderUtils.query = new Proxy({}, { get: (queryStubResult, key) => { if (key === 'stubResult') { return function (method, ...args) { @@ -39,7 +40,7 @@ describe('Transaction Controller', function () { } }, }) - txController.txProviderUtils = new TxProvideUtils(txController.query) + }) describe('#newUnapprovedTransaction', function () { @@ -76,7 +77,6 @@ describe('Transaction Controller', function () { it('should resolve when finished and status is submitted and resolve with the hash', function (done) { txController.once('newUnaprovedTx', (txMetaFromEmit) => { setTimeout(() => { - console.log('HELLLO') txController.setTxHash(txMetaFromEmit.id, '0x0') txController.setTxStatusSubmitted(txMetaFromEmit.id) }, 10) @@ -93,7 +93,6 @@ describe('Transaction Controller', function () { it('should reject when finished and status is rejected', function (done) { txController.once('newUnaprovedTx', (txMetaFromEmit) => { setTimeout(() => { - console.log('HELLLO') txController.setTxStatusRejected(txMetaFromEmit.id) }, 10) }) @@ -429,46 +428,4 @@ describe('Transaction Controller', function () { }).catch(done) }) }) - - describe('#_resubmitTx with a too-low balance', function () { - it('should fail the transaction', function (done) { - const from = '0xda0da0' - const txMeta = { - id: 1, - status: 'submitted', - metamaskNetworkId: currentNetworkId, - txParams: { - from, - nonce: '0x1', - value: '0xfffff', - }, - } - - const lowBalance = '0x0' - const fakeStoreState = { accounts: {} } - fakeStoreState.accounts[from] = { - balance: lowBalance, - nonce: '0x0', - } - - // Stubbing out current account state: - const getStateStub = sinon.stub(txController.ethStore, 'getState') - .returns(fakeStoreState) - - // Adding the fake tx: - txController.addTx(clone(txMeta)) - - txController._resubmitTx(txMeta) - .then(() => { - const updatedMeta = txController.getTx(txMeta.id) - assert.notEqual(updatedMeta.status, txMeta.status, 'status changed.') - assert.equal(updatedMeta.status, 'failed', 'tx set to failed.') - done() - }) - .catch((err) => { - assert.ifError(err, 'should not throw an error') - done(err) - }) - }) - }) }) \ No newline at end of file diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index a43bcfb35..43128b977 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -1,7 +1,7 @@ const assert = require('assert') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN +const BN = require('bn.js') +const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') const TxUtils = require('../../app/scripts/lib/tx-utils') @@ -16,44 +16,6 @@ describe('txUtils', function () { })) }) - describe('#sufficientBalance', function () { - it('returns true if max tx cost is equal to balance.', function () { - const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', - } - const balance = '0x8' - - const result = txUtils.sufficientBalance(tx, balance) - assert.ok(result, 'sufficient balance found.') - }) - - it('returns true if max tx cost is less than balance.', function () { - const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', - } - const balance = '0x9' - - const result = txUtils.sufficientBalance(tx, balance) - assert.ok(result, 'sufficient balance found.') - }) - - it('returns false if max tx cost is more than balance.', function () { - const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', - } - const balance = '0x6' - - const result = txUtils.sufficientBalance(tx, balance) - assert.ok(!result, 'insufficient balance found.') - }) - }) - describe('chain Id', function () { it('prepares a transaction with the provided chainId', function () { const txParams = { @@ -96,7 +58,7 @@ describe('txUtils', function () { assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') }) - it('buffers up to reccomend gas limit reccomended ceiling', function () { + it('buffers up to recommend gas limit recommended ceiling', function () { // naive estimatedGas: 0x16e360 (1.5 mil) const inputHex = '0x16e360' // dummy gas limit: 0x1e8480 (2 mil) @@ -107,17 +69,7 @@ describe('txUtils', function () { // const inputBn = hexToBn(inputHex) // const outputBn = hexToBn(output) const expectedHex = bnToHex(ceilGasLimitBn) - assert.equal(output, expectedHex, 'returns the gas limit reccomended ceiling value') + assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') }) }) -}) - -// util - -function hexToBn (inputHex) { - return new BN(ethUtil.stripHexPrefix(inputHex), 16) -} - -function bnToHex (inputBn) { - return ethUtil.addHexPrefix(inputBn.toString(16)) -} +}) \ No newline at end of file diff --git a/test/unit/util-test.js b/test/unit/util-test.js index 7716e4872..6da185b2c 100644 --- a/test/unit/util-test.js +++ b/test/unit/util-test.js @@ -2,7 +2,7 @@ const assert = require('assert') const { sufficientBalance } = require('../../app/scripts/lib/util') -describe.only('SufficientBalance', function () { +describe('SufficientBalance', function () { it('returns true if max tx cost is equal to balance.', function () { const tx = { 'value': '0x1', From 087cd9fb1a42cb59579b8e24804583d6d127e901 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 14:41:35 -0400 Subject: [PATCH 06/21] break out tx status pendding watchers --- app/scripts/controllers/transactions.js | 172 ++++-------------------- app/scripts/lib/pending-tx-watchers.js | 165 +++++++++++++++++++++++ 2 files changed, 192 insertions(+), 145 deletions(-) create mode 100644 app/scripts/lib/pending-tx-watchers.js diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index d4f32e049..a652c3278 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -5,6 +5,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const EthQuery = require('ethjs-query') const TxProviderUtil = require('../lib/tx-utils') +const PendingTransactionUtils = require('../lib/pending-tx-watchers') const getStack = require('../lib/util').getStack const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') @@ -21,6 +22,9 @@ module.exports = class TransactionController extends EventEmitter { this.txHistoryLimit = opts.txHistoryLimit this.provider = opts.provider this.blockTracker = opts.blockTracker + this.signEthTx = opts.signTransaction + this.ethStore = opts.ethStore + this.nonceTracker = new NonceTracker({ provider: this.provider, getPendingTransactions: (address) => { @@ -32,15 +36,31 @@ module.exports = class TransactionController extends EventEmitter { }, }) this.query = new EthQuery(this.provider) - this.txProviderUtils = new TxProviderUtil(this.query) - this.blockTracker.on('rawBlock', this.checkForTxInBlock.bind(this)) + this.txProviderUtils = new TxProviderUtil(this.provider) + + this.pendingTxUtils = new PendingTransactionUtils({ + provider: this.provider, + nonceTracker: this.nonceTracker, + getBalance: (address) => this.ethStore.getState().accounts[address].balance, + publishTransaction: this.txProviderUtils.publishTransaction.bind(this.txProviderUtils), + getPendingTransactions: (address) => { + return this.getFilteredTxList({ + from: address, + status: 'submitted', + }) + }, + }) + + this.pendingTxUtils.on('txWarning', this.updateTx.bind(this)) + this.pendingTxUtils.on('txFailed', this.setTxStatusFailed.bind(this)) + this.pendingTxUtils.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) + + this.blockTracker.on('rawBlock', this.pendingTxUtils.checkForTxInBlock.bind(this)) // this is a little messy but until ethstore has been either // removed or redone this is to guard against the race condition // where ethStore hasent been populated by the results yet - this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.resubmitPendingTxs.bind(this))) - this.blockTracker.on('sync', this.queryPendingTxs.bind(this)) - this.signEthTx = opts.signTransaction - this.ethStore = opts.ethStore + this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxUtils.resubmitPendingTxs.bind(this))) + this.blockTracker.on('sync', this.pendingTxUtils.queryPendingTxs.bind(this)) // memstore is computed from a few different stores this._updateMemstore() this.store.subscribe(() => this._updateMemstore()) @@ -229,7 +249,7 @@ module.exports = class TransactionController extends EventEmitter { // must set transaction to submitted/failed before releasing lock nonceLock.releaseLock() } catch (err) { - if(!err.message) err.message = 'Transaction failed during approval' + if (!err.message) err.message = 'Transaction failed during approval' this.setTxStatusFailed(txId, err) // must set transaction to submitted/failed before releasing lock if (nonceLock) nonceLock.releaseLock() @@ -374,73 +394,6 @@ module.exports = class TransactionController extends EventEmitter { this.updateTx(txMeta) } - // checks if a signed tx is in a block and - // if included sets the tx status as 'confirmed' - checkForTxInBlock (block) { - const signedTxList = this.getFilteredTxList({status: 'submitted'}) - if (!signedTxList.length) return - signedTxList.forEach((txMeta) => { - const txHash = txMeta.hash - const txId = txMeta.id - - if (!txHash) { - const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') - noTxHashErr.name = 'NoTxHashError' - this.setTxStatusFailed(txId, noTxHashErr) - } - - - block.transactions.forEach((tx) => { - if (tx.hash === txHash) this.setTxStatusConfirmed(txId) - }) - }) - } - - queryPendingTxs ({oldBlock, newBlock}) { - // check pending transactions on start - if (!oldBlock) { - this._checkPendingTxs() - return - } - // if we synced by more than one block, check for missed pending transactions - const diff = Number.parseInt(newBlock.number) - Number.parseInt(oldBlock.number) - if (diff > 1) this._checkPendingTxs() - } - - resubmitPendingTxs () { - const pending = this.getTxsByMetaData('status', 'submitted') - // only try resubmitting if their are transactions to resubmit - if (!pending.length) return - pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => { - /* - Dont marked as failed if the error is a "known" transaction warning - "there is already a transaction with the same sender-nonce - but higher/same gas price" - */ - const errorMessage = err.message.toLowerCase() - const isKnownTx = ( - // geth - errorMessage.includes('replacement transaction underpriced') - || errorMessage.includes('known transaction') - // parity - || errorMessage.includes('gas price too low to replace') - || errorMessage.includes('transaction with the same hash was already imported') - // other - || errorMessage.includes('gateway timeout') - || errorMessage.includes('nonce too low') - ) - // ignore resubmit warnings, return early - if (isKnownTx) return - // encountered real error - transition to error state - this.setTxStatusFailed(txMeta.id, { - stack: err.stack || err.message, - errCode: err.errCode || err, - message: err.message, - }) - })) - } - - /* _____________________________________ | | | PRIVATE METHODS | @@ -482,75 +435,4 @@ module.exports = class TransactionController extends EventEmitter { }) this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) } - - async _resubmitTx (txMeta) { - const address = txMeta.txParams.from - const balance = this.ethStore.getState().accounts[address].balance - if (!('retryCount' in txMeta)) txMeta.retryCount = 0 - - // if the value of the transaction is greater then the balance, fail. - if (!this.txProviderUtils.sufficientBalance(txMeta.txParams, balance)) { - const message = 'Insufficient balance.' - this.setTxStatusFailed(txMeta.id, { - stack: '_resubmitTx: custom tx-controller error', - message, - }) - log.error(message) - return - } - - // Only auto-submit already-signed txs: - if (!('rawTx' in txMeta)) return - - // Increment a try counter. - txMeta.retryCount++ - const rawTx = txMeta.rawTx - return await this.txProviderUtils.publishTransaction(rawTx) - } - - // checks the network for signed txs and - // if confirmed sets the tx status as 'confirmed' - async _checkPendingTxs () { - const signedTxList = this.getFilteredTxList({status: 'submitted'}) - // in order to keep the nonceTracker accurate we block it while updating pending transactions - const nonceGlobalLock = await this.nonceTracker.getGlobalLock() - try { - await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta))) - } catch (err) { - console.error('TransactionController - Error updating pending transactions') - console.error(err) - } - nonceGlobalLock.releaseLock() - } - - async _checkPendingTx (txMeta) { - const txHash = txMeta.hash - const txId = txMeta.id - // extra check in case there was an uncaught error during the - // signature and submission process - if (!txHash) { - const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') - noTxHashErr.name = 'NoTxHashError' - this.setTxStatusFailed(txId, noTxHashErr) - } - // get latest transaction status - let txParams - try { - txParams = await this.query.getTransactionByHash(txHash) - if (!txParams) return - if (txParams.blockNumber) { - this.setTxStatusConfirmed(txId) - } - } catch (err) { - if (err || !txParams) { - txMeta.err = { - isWarning: true, - errorCode: err, - message: 'There was a problem loading this transaction.', - } - this.updateTx(txMeta) - throw err - } - } - } } \ No newline at end of file diff --git a/app/scripts/lib/pending-tx-watchers.js b/app/scripts/lib/pending-tx-watchers.js new file mode 100644 index 000000000..4158e8bb5 --- /dev/null +++ b/app/scripts/lib/pending-tx-watchers.js @@ -0,0 +1,165 @@ +const EventEmitter = require('events') +const EthQuery = require('ethjs-query') +const sufficientBalance = require('./util').sufficientBalance +/* + + Utility class for tracking the transactions as they + go from a pending state to a confirmed (mined in a block) state + + As well as continues broadcast while in the pending state + + ~config is not optional~ + requires a: { + provider: //, + nonceTracker: //see nonce tracker, + getBalnce: //(address) a function for getting balances, + getPendingTransactions: //() a function for getting an array of transactions, + publishTransaction: //(rawTx) a async function for publishing raw transactions, + } + +*/ + +module.exports = class PendingTransactionWatcher extends EventEmitter { + constructor (config) { + super() + this.query = new EthQuery(config.provider) + this.nonceTracker = config.nonceTracker + + this.getBalance = config.getBalance + this.getPendingTransactions = config.getPendingTransactions + this.publishTransaction = config.publishTransaction + } + + // checks if a signed tx is in a block and + // if included sets the tx status as 'confirmed' + checkForTxInBlock (block) { + const signedTxList = this.getPendingTransactions() + if (!signedTxList.length) return + signedTxList.forEach((txMeta) => { + const txHash = txMeta.hash + const txId = txMeta.id + + if (!txHash) { + const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') + noTxHashErr.name = 'NoTxHashError' + this.emit('txFailed', txId, noTxHashErr) + return + } + + + block.transactions.forEach((tx) => { + if (tx.hash === txHash) this.emit('txConfirmed', txId) + }) + }) + } + + queryPendingTxs ({oldBlock, newBlock}) { + // check pending transactions on start + if (!oldBlock) { + this._checkPendingTxs() + return + } + // if we synced by more than one block, check for missed pending transactions + const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16) + if (diff > 1) this._checkPendingTxs() + } + + + resubmitPendingTxs () { + const pending = this.getPendingTransactions('status', 'submitted') + // only try resubmitting if their are transactions to resubmit + if (!pending.length) return + pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => { + /* + Dont marked as failed if the error is a "known" transaction warning + "there is already a transaction with the same sender-nonce + but higher/same gas price" + */ + const errorMessage = err.message.toLowerCase() + const isKnownTx = ( + // geth + errorMessage.includes('replacement transaction underpriced') + || errorMessage.includes('known transaction') + // parity + || errorMessage.includes('gas price too low to replace') + || errorMessage.includes('transaction with the same hash was already imported') + // other + || errorMessage.includes('gateway timeout') + || errorMessage.includes('nonce too low') + ) + // ignore resubmit warnings, return early + if (isKnownTx) return + // encountered real error - transition to error state + this.emit('txFailed', txMeta.id, err) + })) + } + + async _resubmitTx (txMeta) { + const address = txMeta.txParams.from + const balance = this.getBalance(address) + if (!('retryCount' in txMeta)) txMeta.retryCount = 0 + + // if the value of the transaction is greater then the balance, fail. + if (!sufficientBalance(txMeta.txParams, balance)) { + const message = 'Insufficient balance during rebroadcast.' + txMeta.warning = { + message, + } + this.emit('txWarning', txMeta) + log.error(message) + return + } + + // Only auto-submit already-signed txs: + if (!('rawTx' in txMeta)) return + + // Increment a try counter. + txMeta.retryCount++ + const rawTx = txMeta.rawTx + return await this.publishTransaction(rawTx) + } + + async _checkPendingTx (txMeta) { + const txHash = txMeta.hash + const txId = txMeta.id + // extra check in case there was an uncaught error during the + // signature and submission process + if (!txHash) { + const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') + noTxHashErr.name = 'NoTxHashError' + this.emit('txFailed', txId, noTxHashErr) + return + } + // get latest transaction status + let txParams + try { + txParams = await this.query.getTransactionByHash(txHash) + if (!txParams) return + if (txParams.blockNumber) { + this.emit('txConfirmed', txId) + } + } catch (err) { + txMeta.warning = { + error: err, + message: 'There was a problem loading this transaction.', + } + this.emit('txWarning', txMeta) + throw err + } + } + + // checks the network for signed txs and + // if confirmed sets the tx status as 'confirmed' + async _checkPendingTxs () { + const signedTxList = this.getPendingTransactions() + // in order to keep the nonceTracker accurate we block it while updating pending transactions + const nonceGlobalLock = await this.nonceTracker.getGlobalLock() + try { + await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta))) + } catch (err) { + console.error('PendingTransactionWatcher - Error updating pending transactions') + console.error(err) + } + nonceGlobalLock.releaseLock() + } +} \ No newline at end of file From cddff73703e9f6611e5ad793d6d05ba91ee96e06 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 14:42:13 -0400 Subject: [PATCH 07/21] bring your own BN --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9171bc206..6a2404337 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "babel-runtime": "^6.23.0", "bip39": "^2.2.0", "bluebird": "^3.5.0", + "bn.js": "^4.11.7", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", "client-sw-ready-event": "^3.3.0", From fa3df576bcf879904b303c4ed1015d27db64642d Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 14:43:08 -0400 Subject: [PATCH 08/21] fixed: showing tx-s as errors vs. warning --- ui/app/components/transaction-list-item.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index dbda66a31..01355abad 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -154,12 +154,21 @@ function failIfFailed (transaction) { if (transaction.status === 'rejected') { return h('span.error', ' (Rejected)') } - if (transaction.err) { + if (transaction.err || transaction.warning) { + const { err, warning = {} } = transaction + const errFirst = !!(( err && warning ) || err) + const message = errFirst ? err.message : warning.message + + errFirst ? err.message : warning.message + + return h(Tooltip, { - title: transaction.err.message, + title: message, position: 'bottom', }, [ - h('span.error', ' (Failed)'), + h(`span.${errFirst ? 'error' : 'warning'}`, + ` (${errFirst ? 'Failed' : 'Warning'})` + ), ]) } } From 08f49ab35f5a78fba6921a2957a92e0a2e5b065a Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 4 Aug 2017 14:50:34 -0400 Subject: [PATCH 09/21] rename PendingTransactionUtils -> PendingTransactionWatchers --- app/scripts/controllers/transactions.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 498cac9af..c1a0ef0f3 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -5,7 +5,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const EthQuery = require('ethjs-query') const TxProviderUtil = require('../lib/tx-utils') -const PendingTransactionUtils = require('../lib/pending-tx-watchers') +const PendingTransactionWatchers = require('../lib/pending-tx-watchers') const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') @@ -37,7 +37,7 @@ module.exports = class TransactionController extends EventEmitter { this.query = new EthQuery(this.provider) this.txProviderUtils = new TxProviderUtil(this.provider) - this.pendingTxUtils = new PendingTransactionUtils({ + this.pendingTxWatcher = new PendingTransactionWatchers({ provider: this.provider, nonceTracker: this.nonceTracker, getBalance: (address) => this.ethStore.getState().accounts[address].balance, @@ -50,16 +50,16 @@ module.exports = class TransactionController extends EventEmitter { }, }) - this.pendingTxUtils.on('txWarning', this.updateTx.bind(this)) - this.pendingTxUtils.on('txFailed', this.setTxStatusFailed.bind(this)) - this.pendingTxUtils.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) + this.pendingTxWatcher.on('txWarning', this.updateTx.bind(this)) + this.pendingTxWatcher.on('txFailed', this.setTxStatusFailed.bind(this)) + this.pendingTxWatcher.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) - this.blockTracker.on('rawBlock', this.pendingTxUtils.checkForTxInBlock.bind(this)) + this.blockTracker.on('rawBlock', this.pendingTxWatcher.checkForTxInBlock.bind(this)) // this is a little messy but until ethstore has been either // removed or redone this is to guard against the race condition // where ethStore hasent been populated by the results yet - this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxUtils.resubmitPendingTxs.bind(this))) - this.blockTracker.on('sync', this.pendingTxUtils.queryPendingTxs.bind(this)) + this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxWatcher.resubmitPendingTxs.bind(this))) + this.blockTracker.on('sync', this.pendingTxWatcher.queryPendingTxs.bind(this)) // memstore is computed from a few different stores this._updateMemstore() this.store.subscribe(() => this._updateMemstore()) From fb9866b4e10c823e987d4cee9fc499673d664a8a Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 7 Aug 2017 14:37:20 -0400 Subject: [PATCH 10/21] fix spelling --- app/scripts/controllers/transactions.js | 18 +++++++++--------- app/scripts/lib/pending-tx-watchers.js | 9 +++------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index c1a0ef0f3..bc8e5b729 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -4,7 +4,7 @@ const clone = require('clone') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const EthQuery = require('ethjs-query') -const TxProviderUtil = require('../lib/tx-utils') +const TxProviderUtils = require('../lib/tx-utils') const PendingTransactionWatchers = require('../lib/pending-tx-watchers') const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') @@ -35,9 +35,9 @@ module.exports = class TransactionController extends EventEmitter { }, }) this.query = new EthQuery(this.provider) - this.txProviderUtils = new TxProviderUtil(this.provider) + this.txProviderUtils = new TxProviderUtils(this.provider) - this.pendingTxWatcher = new PendingTransactionWatchers({ + this.pendingTxWatchers = new PendingTransactionWatchers({ provider: this.provider, nonceTracker: this.nonceTracker, getBalance: (address) => this.ethStore.getState().accounts[address].balance, @@ -50,16 +50,16 @@ module.exports = class TransactionController extends EventEmitter { }, }) - this.pendingTxWatcher.on('txWarning', this.updateTx.bind(this)) - this.pendingTxWatcher.on('txFailed', this.setTxStatusFailed.bind(this)) - this.pendingTxWatcher.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) + this.pendingTxWatchers.on('txWarning', this.updateTx.bind(this)) + this.pendingTxWatchers.on('txFailed', this.setTxStatusFailed.bind(this)) + this.pendingTxWatchers.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) - this.blockTracker.on('rawBlock', this.pendingTxWatcher.checkForTxInBlock.bind(this)) + this.blockTracker.on('rawBlock', this.pendingTxWatchers.checkForTxInBlock.bind(this)) // this is a little messy but until ethstore has been either // removed or redone this is to guard against the race condition // where ethStore hasent been populated by the results yet - this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxWatcher.resubmitPendingTxs.bind(this))) - this.blockTracker.on('sync', this.pendingTxWatcher.queryPendingTxs.bind(this)) + this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxWatchers.resubmitPendingTxs.bind(this))) + this.blockTracker.on('sync', this.pendingTxWatchers.queryPendingTxs.bind(this)) // memstore is computed from a few different stores this._updateMemstore() this.store.subscribe(() => this._updateMemstore()) diff --git a/app/scripts/lib/pending-tx-watchers.js b/app/scripts/lib/pending-tx-watchers.js index 4158e8bb5..5b23cc67c 100644 --- a/app/scripts/lib/pending-tx-watchers.js +++ b/app/scripts/lib/pending-tx-watchers.js @@ -19,7 +19,7 @@ const sufficientBalance = require('./util').sufficientBalance */ -module.exports = class PendingTransactionWatcher extends EventEmitter { +module.exports = class PendingTransactionWatchers extends EventEmitter { constructor (config) { super() this.query = new EthQuery(config.provider) @@ -101,11 +101,8 @@ module.exports = class PendingTransactionWatcher extends EventEmitter { // if the value of the transaction is greater then the balance, fail. if (!sufficientBalance(txMeta.txParams, balance)) { - const message = 'Insufficient balance during rebroadcast.' - txMeta.warning = { - message, - } - this.emit('txWarning', txMeta) + const insufficientFundsError = new Error('Insufficient balance during rebroadcast.') + this.emit('txFailed', txMeta.id, insufficientFundsError) log.error(message) return } From a54c26382e4e5d4e4fec543ac4e0ca90d0d91191 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 7 Aug 2017 14:40:07 -0400 Subject: [PATCH 11/21] remove unnecessary if statment for error message --- app/scripts/controllers/transactions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index bc8e5b729..efc8d7117 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -246,7 +246,6 @@ module.exports = class TransactionController extends EventEmitter { // must set transaction to submitted/failed before releasing lock nonceLock.releaseLock() } catch (err) { - if (!err.message) err.message = 'Transaction failed during approval' this.setTxStatusFailed(txId, err) // must set transaction to submitted/failed before releasing lock if (nonceLock) nonceLock.releaseLock() From 59124eb6fd749cde1c021c69717d6f2c797428c7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 7 Aug 2017 14:45:43 -0400 Subject: [PATCH 12/21] remove logging of the message and log the error --- app/scripts/lib/pending-tx-watchers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/pending-tx-watchers.js b/app/scripts/lib/pending-tx-watchers.js index 5b23cc67c..60d0a09ad 100644 --- a/app/scripts/lib/pending-tx-watchers.js +++ b/app/scripts/lib/pending-tx-watchers.js @@ -103,7 +103,7 @@ module.exports = class PendingTransactionWatchers extends EventEmitter { if (!sufficientBalance(txMeta.txParams, balance)) { const insufficientFundsError = new Error('Insufficient balance during rebroadcast.') this.emit('txFailed', txMeta.id, insufficientFundsError) - log.error(message) + log.error(insufficientFundsError) return } From e7f838e626ff1ce29490a081d7ee3f917745dd62 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 7 Aug 2017 14:56:17 -0400 Subject: [PATCH 13/21] fix test --- test/unit/pending-tx-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js index 5103a9b62..d0606bb17 100644 --- a/test/unit/pending-tx-test.js +++ b/test/unit/pending-tx-test.js @@ -224,7 +224,7 @@ describe('PendingTransactionWatcher', function () { pendingTxWatcher.resubmitPendingTxs() }) }) - describe('#_resubmitTx with a too-low balance', function () { + describe.only('#_resubmitTx with a too-low balance', function () { it('should return before publishing the transaction because to low of balance', function (done) { const lowBalance = '0x0' pendingTxWatcher.getBalance = (address) => { @@ -237,8 +237,8 @@ describe('PendingTransactionWatcher', function () { // Stubbing out current account state: // Adding the fake tx: - pendingTxWatcher.once('txWarning', (txMeta) => { - assert(txMeta.warning.message, 'Should have a warning message') + pendingTxWatcher.once('txFailed', (txId, err) => { + assert(err, 'Should have a error') done() }) pendingTxWatcher._resubmitTx(txMeta) From 28fbdca83006fa32c9411081f2152560005167ad Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 7 Aug 2017 16:41:54 -0400 Subject: [PATCH 14/21] remove .only --- test/unit/pending-tx-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js index d0606bb17..6e50dae6a 100644 --- a/test/unit/pending-tx-test.js +++ b/test/unit/pending-tx-test.js @@ -224,7 +224,7 @@ describe('PendingTransactionWatcher', function () { pendingTxWatcher.resubmitPendingTxs() }) }) - describe.only('#_resubmitTx with a too-low balance', function () { + describe('#_resubmitTx with a too-low balance', function () { it('should return before publishing the transaction because to low of balance', function (done) { const lowBalance = '0x0' pendingTxWatcher.getBalance = (address) => { From 3a2190ec3cac0211d37dab6434d29f7bb484d4c4 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 7 Aug 2017 16:57:17 -0400 Subject: [PATCH 15/21] fix the bind on pending tx watchers --- app/scripts/controllers/transactions.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index efc8d7117..cf987db91 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -54,12 +54,12 @@ module.exports = class TransactionController extends EventEmitter { this.pendingTxWatchers.on('txFailed', this.setTxStatusFailed.bind(this)) this.pendingTxWatchers.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) - this.blockTracker.on('rawBlock', this.pendingTxWatchers.checkForTxInBlock.bind(this)) + this.blockTracker.on('rawBlock', this.pendingTxWatchers.checkForTxInBlock.bind(this.pendingTxWatchers)) // this is a little messy but until ethstore has been either // removed or redone this is to guard against the race condition // where ethStore hasent been populated by the results yet - this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxWatchers.resubmitPendingTxs.bind(this))) - this.blockTracker.on('sync', this.pendingTxWatchers.queryPendingTxs.bind(this)) + this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxWatchers.resubmitPendingTxs.bind(this.pendingTxWatchers))) + this.blockTracker.on('sync', this.pendingTxWatchers.queryPendingTxs.bind(this.pendingTxWatchers)) // memstore is computed from a few different stores this._updateMemstore() this.store.subscribe(() => this._updateMemstore()) From b6a7281737a320ab8a4d0819d5a8fce108f701dc Mon Sep 17 00:00:00 2001 From: sdtsui Date: Tue, 8 Aug 2017 15:17:24 -0700 Subject: [PATCH 16/21] Make buy ui more central to the screen --- ui/app/components/buy-button-subview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 87084f92d..759eb15bd 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -143,7 +143,7 @@ BuyButtonSubview.prototype.formVersionSubview = function () { return h('div.flex-column', { style: { alignItems: 'center', - margin: '50px', + margin: '20px 50px', }, }, [ h('h3.text-transform-uppercase', { From 9d3b5348aad0715f62c5bc2c0f4b5d0982ee2634 Mon Sep 17 00:00:00 2001 From: sdtsui Date: Tue, 8 Aug 2017 15:17:52 -0700 Subject: [PATCH 17/21] Fix layout issues flagged by @tmashuang --- ui/app/components/shapeshift-form.js | 148 ++++++++++++++------------- 1 file changed, 79 insertions(+), 69 deletions(-) diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 76a265d63..901a4a956 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -33,10 +33,10 @@ ShapeshiftForm.prototype.renderMain = function () { return h('.flex-column', { style: { - // marginTop: '10px', + position: 'relative', padding: '25px', paddingTop: '5px', - width: '100%', + width: '90%', minHeight: '215px', alignItems: 'center', overflowY: 'auto', @@ -58,7 +58,9 @@ ShapeshiftForm.prototype.renderMain = function () { }, }), - h('.input-container', [ + h('.input-container', { + position: 'relative', + }, [ h('input#fromCoin.buy-inputs.ex-coins', { type: 'text', list: 'coinList', @@ -79,27 +81,31 @@ ShapeshiftForm.prototype.renderMain = function () { style: { fontSize: '12px', color: '#F7861C', - position: 'relative', - bottom: '48px', - left: '106px', + position: 'absolute', }, }), ]), - h('.icon-control', [ - h('i.fa.fa-refresh.fa-4.orange', { - style: { - bottom: '5px', - left: '5px', - color: '#F7861C', - }, - onClick: this.updateCoin.bind(this), - }), + h('.icon-control', { + style: { + position: 'relative', + }, + }, [ + // Not visible on the screen, can't see it on master. + + // h('i.fa.fa-refresh.fa-4.orange', { + // style: { + // bottom: '5px', + // left: '5px', + // color: '#F7861C', + // }, + // onClick: this.updateCoin.bind(this), + // }), h('i.fa.fa-chevron-right.fa-4.orange', { style: { - position: 'relative', - bottom: '26px', - left: '10px', + position: 'absolute', + bottom: '35%', + left: '0%', color: '#F7861C', }, onClick: this.updateCoin.bind(this), @@ -117,69 +123,73 @@ ShapeshiftForm.prototype.renderMain = function () { }, }), ]), + h('.flex-column', { style: { + marginTop: '1%', alignItems: 'flex-start', }, }, [ - this.props.warning ? this.props.warning && h('span.error.flex-center', { - style: { - textAlign: 'center', - width: '229px', - height: '82px', - }, - }, - this.props.warning) : this.renderInfo(), + this.props.warning + ? this.props.warning + && h('span.error.flex-center', { + style: { + textAlign: 'center', + width: '229px', + height: '82px', + }, + }, this.props.warning) + : this.renderInfo(), + + this.renderRefundAddressForCoin(coin), ]), - h(this.activeToggle('.input-container'), { + ]) +} + +ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) { + return h(this.activeToggle('.input-container'), { + style: { + marginTop: '1%', + }, + }, [ + + h('div', `${coin} Address:`), + + h('input#fromCoinAddress.buy-inputs', { + type: 'text', + placeholder: `Your ${coin} Refund Address`, + dataset: { + persistentFormId: 'refund-address', + + }, style: { - padding: '10px', - paddingTop: '0px', - width: '100%', + boxSizing: 'border-box', + width: '227px', + height: '30px', + padding: ' 5px ', + }, + }), + + h('i.fa.fa-pencil-square-o.edit-text', { + style: { + fontSize: '12px', + color: '#F7861C', + position: 'absolute', + }, + }), + h('div.flex-row', { + style: { + justifyContent: 'flex-start', }, }, [ - - h('div', `${coin} Address:`), - - h('input#fromCoinAddress.buy-inputs', { - type: 'text', - placeholder: `Your ${coin} Refund Address`, - dataset: { - persistentFormId: 'refund-address', - }, + h('button', { + onClick: this.shift.bind(this), style: { - boxSizing: 'border-box', - width: '227px', - height: '30px', - padding: ' 5px ', + marginTop: '1%', }, - }), - - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'relative', - bottom: '10px', - right: '11px', - }, - }), - h('.flex-row', { - style: { - justifyContent: 'flex-end', - }, - }, [ - h('button', { - onClick: this.shift.bind(this), - style: { - marginTop: '10px', - position: 'relative', - bottom: '40px', - }, - }, - 'Submit'), - ]), + }, + 'Submit'), ]), ]) } From be4011c310eab76d3010da2e6354a953396f85f5 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 8 Aug 2017 18:30:03 -0400 Subject: [PATCH 18/21] create a provider stub --- test/stub/provider.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/stub/provider.js diff --git a/test/stub/provider.js b/test/stub/provider.js new file mode 100644 index 000000000..8a306f6d9 --- /dev/null +++ b/test/stub/provider.js @@ -0,0 +1,25 @@ +const JsonRpcEngine = require('json-rpc-engine') +const scaffoldMiddleware = require('eth-json-rpc-middleware/scaffold') + +module.exports = { + createEngineForTestData, + providerFromEngine, + scaffoldMiddleware, + createStubedProvider +} + + +function createEngineForTestData () { + return new JsonRpcEngine() +} + +function providerFromEngine (engine) { + const provider = { sendAsync: engine.handle.bind(engine) } + return provider +} + +function createStubedProvider (resultStub) { + const engine = createEngineForTestData() + engine.push(scaffoldMiddleware(resultStub)) + return providerFromEngine(engine) +} \ No newline at end of file From e761fb0ef7d7d658bd6e558fe2fcbe69d8eb4999 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 8 Aug 2017 18:30:22 -0400 Subject: [PATCH 19/21] use provider stub --- test/unit/pending-tx-test.js | 119 +++++++++++++++----------------- test/unit/tx-controller-test.js | 34 +++------ 2 files changed, 65 insertions(+), 88 deletions(-) diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js index 6e50dae6a..8c6d287f8 100644 --- a/test/unit/pending-tx-test.js +++ b/test/unit/pending-tx-test.js @@ -3,19 +3,20 @@ const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') const clone = require('clone') -const PendingTransactionWatcher = require('../../app/scripts/lib/pending-tx-watchers') +const { createStubedProvider } = require('../stub/provider') +const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker') const noop = () => true const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') -describe('PendingTransactionWatcher', function () { - let pendingTxWatcher, txMeta, txMetaNoHash, txMetaNoRawTx +describe('PendingTransactionTracker', function () { + let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, provider this.timeout(10000) beforeEach(function () { txMeta = { id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eebBlock', + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', status: 'signed', txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', @@ -30,13 +31,15 @@ describe('PendingTransactionWatcher', function () { txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, } txMetaNoRawTx = { - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eebBlock', + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', status: 'signed', txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, } + providerResultStub = {} + provider = createStubedProvider(providerResultStub) - pendingTxWatcher = new PendingTransactionWatcher({ - provider: { sendAsync: noop }, + pendingTxTracker = new PendingTransactionTracker({ + provider, getBalance: () => {}, nonceTracker: { getGlobalLock: async () => { @@ -47,20 +50,6 @@ describe('PendingTransactionWatcher', function () { sufficientBalance: () => {}, publishTransaction: () => {}, }) - - pendingTxWatcher.query = new Proxy({}, { - get: (queryStubResult, key) => { - if (key === 'stubResult') { - return function (method, ...args) { - queryStubResult[method] = args - } - } else { - const returnValues = queryStubResult[key] - return () => Promise.resolve(...returnValues) - } - }, - }) - }) describe('#checkForTxInBlock', function () { @@ -68,77 +57,77 @@ describe('PendingTransactionWatcher', function () { // throw a type error if it trys to do anything on the block // thus failing the test const block = Proxy.revocable({}, {}).revoke() - pendingTxWatcher.checkForTxInBlock(block) + pendingTxTracker.checkForTxInBlock(block) }) it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { const block = Proxy.revocable({}, {}).revoke() - pendingTxWatcher.getPendingTransactions = () => [txMetaNoHash] - pendingTxWatcher.once('txFailed', (txId, err) => { + pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] + pendingTxTracker.once('txFailed', (txId, err) => { assert(txId, txMetaNoHash.id, 'should pass txId') done() }) - pendingTxWatcher.checkForTxInBlock(block) + pendingTxTracker.checkForTxInBlock(block) }) it('should emit \'txConfirmed\' if the tx is in the block', function (done) { const block = { transactions: [txMeta]} - pendingTxWatcher.getPendingTransactions = () => [txMeta] - pendingTxWatcher.once('txConfirmed', (txId) => { + pendingTxTracker.getPendingTransactions = () => [txMeta] + pendingTxTracker.once('txConfirmed', (txId) => { assert(txId, txMeta.id, 'should pass txId') done() }) - pendingTxWatcher.once('txFailed', (_, err) => { done(err) }) - pendingTxWatcher.checkForTxInBlock(block) + pendingTxTracker.once('txFailed', (_, err) => { done(err) }) + pendingTxTracker.checkForTxInBlock(block) }) }) describe('#queryPendingTxs', function () { it('should call #_checkPendingTxs if their is no oldBlock', function (done) { let newBlock, oldBlock newBlock = { number: '0x01' } - pendingTxWatcher._checkPendingTxs = done - pendingTxWatcher.queryPendingTxs({oldBlock, newBlock}) + pendingTxTracker._checkPendingTxs = done + pendingTxTracker.queryPendingTxs({oldBlock, newBlock}) }) it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { let newBlock, oldBlock oldBlock = { number: '0x01' } newBlock = { number: '0x03' } - pendingTxWatcher._checkPendingTxs = done - pendingTxWatcher.queryPendingTxs({oldBlock, newBlock}) + pendingTxTracker._checkPendingTxs = done + pendingTxTracker.queryPendingTxs({oldBlock, newBlock}) }) it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { let newBlock, oldBlock oldBlock = { number: '0x1' } newBlock = { number: '0x2' } - pendingTxWatcher._checkPendingTxs = () => { + pendingTxTracker._checkPendingTxs = () => { const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') done(err) } - pendingTxWatcher.queryPendingTxs({oldBlock, newBlock}) + pendingTxTracker.queryPendingTxs({oldBlock, newBlock}) done() }) }) describe('#_checkPendingTx', function () { it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { - pendingTxWatcher.once('txFailed', (txId, err) => { + pendingTxTracker.once('txFailed', (txId, err) => { assert(txId, txMetaNoHash.id, 'should pass txId') done() }) - pendingTxWatcher._checkPendingTx(txMetaNoHash) + pendingTxTracker._checkPendingTx(txMetaNoHash) }) it('should should return if query does not return txParams', function () { - pendingTxWatcher.query.stubResult('getTransactionByHash', null) - pendingTxWatcher._checkPendingTx(txMeta) + providerResultStub.eth_getTransactionByHash = null + pendingTxTracker._checkPendingTx(txMeta) }) it('should emit \'txConfirmed\'', function (done) { - pendingTxWatcher.query.stubResult('getTransactionByHash', {blockNumber: '0x01'}) - pendingTxWatcher.once('txConfirmed', (txId) => { + providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'} + pendingTxTracker.once('txConfirmed', (txId) => { assert(txId, txMeta.id, 'should pass txId') done() }) - pendingTxWatcher.once('txFailed', (_, err) => { done(err) }) - pendingTxWatcher._checkPendingTx(txMeta) + pendingTxTracker.once('txFailed', (_, err) => { done(err) }) + pendingTxTracker._checkPendingTx(txMeta) }) }) @@ -154,14 +143,14 @@ describe('PendingTransactionWatcher', function () { }) it('should warp all txMeta\'s in #_checkPendingTx', function (done) { - pendingTxWatcher.getPendingTransactions = () => txList - pendingTxWatcher._checkPendingTx = (tx) => { tx.resolve(tx) } + pendingTxTracker.getPendingTransactions = () => txList + pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } const list = txList.map Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxWatcher._checkPendingTxs() + pendingTxTracker._checkPendingTxs() }) }) @@ -175,15 +164,15 @@ describe('PendingTransactionWatcher', function () { }) it('should return if no pending transactions', function () { - pendingTxWatcher.resubmitPendingTxs() + pendingTxTracker.resubmitPendingTxs() }) it('should call #_resubmitTx for all pending tx\'s', function (done) { - pendingTxWatcher.getPendingTransactions = () => txList - pendingTxWatcher._resubmitTx = async (tx) => { tx.resolve(tx) } + pendingTxTracker.getPendingTransactions = () => txList + pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) } Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxWatcher.resubmitPendingTxs() + pendingTxTracker.resubmitPendingTxs() }) it('should not emit \'txFailed\' if the txMeta throws a known txError', function (done) { knownErrors =[ @@ -199,10 +188,10 @@ describe('PendingTransactionWatcher', function () { ] const enoughForAllErrors = txList.concat(txList) - pendingTxWatcher.on('txFailed', (_, err) => done(err)) + pendingTxTracker.on('txFailed', (_, err) => done(err)) - pendingTxWatcher.getPendingTransactions = () => enoughForAllErrors - pendingTxWatcher._resubmitTx = async (tx) => { + pendingTxTracker.getPendingTransactions = () => enoughForAllErrors + pendingTxTracker._resubmitTx = async (tx) => { tx.resolve() throw new Error(knownErrors.pop()) } @@ -210,38 +199,38 @@ describe('PendingTransactionWatcher', function () { .then((txCompletedList) => done()) .catch(done) - pendingTxWatcher.resubmitPendingTxs() + pendingTxTracker.resubmitPendingTxs() }) it('should emit \'txFailed\' if it encountered a real error', function (done) { - pendingTxWatcher.once('txFailed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err)) + pendingTxTracker.once('txFailed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err)) - pendingTxWatcher.getPendingTransactions = () => txList - pendingTxWatcher._resubmitTx = async (tx) => { throw new TypeError('im some real error') } + pendingTxTracker.getPendingTransactions = () => txList + pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') } Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxWatcher.resubmitPendingTxs() + pendingTxTracker.resubmitPendingTxs() }) }) describe('#_resubmitTx with a too-low balance', function () { it('should return before publishing the transaction because to low of balance', function (done) { const lowBalance = '0x0' - pendingTxWatcher.getBalance = (address) => { + pendingTxTracker.getBalance = (address) => { assert.equal(address, txMeta.txParams.from, 'Should pass the address') return lowBalance } - pendingTxWatcher.publishTransaction = async (rawTx) => { + pendingTxTracker.publishTransaction = async (rawTx) => { done(new Error('tried to publish transaction')) } // Stubbing out current account state: // Adding the fake tx: - pendingTxWatcher.once('txFailed', (txId, err) => { + pendingTxTracker.once('txFailed', (txId, err) => { assert(err, 'Should have a error') done() }) - pendingTxWatcher._resubmitTx(txMeta) + pendingTxTracker._resubmitTx(txMeta) .catch((err) => { assert.ifError(err, 'should not throw an error') done(err) @@ -250,17 +239,17 @@ describe('PendingTransactionWatcher', function () { it('should publishing the transaction', function (done) { const enoughBalance = '0x100000' - pendingTxWatcher.getBalance = (address) => { + pendingTxTracker.getBalance = (address) => { assert.equal(address, txMeta.txParams.from, 'Should pass the address') return enoughBalance } - pendingTxWatcher.publishTransaction = async (rawTx) => { + pendingTxTracker.publishTransaction = async (rawTx) => { assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx') } // Stubbing out current account state: // Adding the fake tx: - pendingTxWatcher._resubmitTx(txMeta) + pendingTxTracker._resubmitTx(txMeta) .then(() => done()) .catch((err) => { assert.ifError(err, 'should not throw an error') diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index e54dc9719..57d7deccd 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -10,16 +10,20 @@ const noop = () => true const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') +const { createStubedProvider } = require('../stub/provider') describe('Transaction Controller', function () { - let txController + let txController, engine, provider, providerResultStub beforeEach(function () { + providerResultStub = {} + provider = createStubedProvider(providerResultStub) + txController = new TransactionController({ + provider, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, - provider: { sendAsync: noop }, ethStore: { getState: noop }, signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(privKey) @@ -28,19 +32,6 @@ describe('Transaction Controller', function () { }) txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) txController.txProviderUtils = new TxProvideUtils(txController.provider) - txController.query = txController.txProviderUtils.query = new Proxy({}, { - get: (queryStubResult, key) => { - if (key === 'stubResult') { - return function (method, ...args) { - queryStubResult[method] = args - } - } else { - const returnValues = queryStubResult[key] - return () => Promise.resolve(...returnValues) - } - }, - }) - }) describe('#newUnapprovedTransaction', function () { @@ -132,11 +123,9 @@ describe('Transaction Controller', function () { 'to':'0xc684832530fcbddae4b4230a47e991ddcec2831d', }, } - - txController.query.stubResult('gasPrice', '0x4a817c800') - txController.query.stubResult('getBlockByNumber', { gasLimit: '0x47b784' }) - txController.query.stubResult('estimateGas', '0x5209') - + providerResultStub.eth_gasPrice = '4a817c800' + providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } + providerResultStub.eth_estimateGas = '5209' txController.addTxDefaults(txMeta) .then((txMetaWithDefaults) => { assert(txMetaWithDefaults.txParams.value, '0x0','should have added 0x0 as the value') @@ -393,9 +382,8 @@ describe('Transaction Controller', function () { const wrongValue = '0x05' txController.addTx(txMeta) - - txController.query.stubResult('estimateGas', wrongValue) - txController.query.stubResult('gasPrice', wrongValue) + providerResultStub.eth_gasPrice = wrongValue + providerResultStub.eth_estimateGas = '0x5209' const signStub = sinon.stub(txController, 'signTransaction').callsFake(() => Promise.resolve()) From a13643bdb545af60bd4514c9026e9657ce8aa5ea Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 8 Aug 2017 18:30:49 -0400 Subject: [PATCH 20/21] fix class names --- app/scripts/controllers/transactions.js | 38 +++++++++++-------- ...g-tx-watchers.js => pending-tx-tracker.js} | 3 +- app/scripts/lib/tx-utils.js | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) rename app/scripts/lib/{pending-tx-watchers.js => pending-tx-tracker.js} (98%) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index cf987db91..664dbff6b 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -4,8 +4,8 @@ const clone = require('clone') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const EthQuery = require('ethjs-query') -const TxProviderUtils = require('../lib/tx-utils') -const PendingTransactionWatchers = require('../lib/pending-tx-watchers') +const TxProviderUtil = require('../lib/tx-utils') +const PendingTransactionTracker = require('../lib/pending-tx-tracker') const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') @@ -35,13 +35,17 @@ module.exports = class TransactionController extends EventEmitter { }, }) this.query = new EthQuery(this.provider) - this.txProviderUtils = new TxProviderUtils(this.provider) + this.txProviderUtil = new TxProviderUtil(this.provider) - this.pendingTxWatchers = new PendingTransactionWatchers({ + this.pendingTxTracker = new PendingTransactionTracker({ provider: this.provider, nonceTracker: this.nonceTracker, - getBalance: (address) => this.ethStore.getState().accounts[address].balance, - publishTransaction: this.txProviderUtils.publishTransaction.bind(this.txProviderUtils), + getBalance: async (address) => { + const account = this.ethStore.getState().accounts[address] + if (!account) return + return account.balance + }, + publishTransaction: this.txProviderUtil.publishTransaction.bind(this.txProviderUtil), getPendingTransactions: (address) => { return this.getFilteredTxList({ from: address, @@ -50,16 +54,18 @@ module.exports = class TransactionController extends EventEmitter { }, }) - this.pendingTxWatchers.on('txWarning', this.updateTx.bind(this)) - this.pendingTxWatchers.on('txFailed', this.setTxStatusFailed.bind(this)) - this.pendingTxWatchers.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) + this.pendingTxTracker.on('txWarning', this.updateTx.bind(this)) + this.pendingTxTracker.on('txFailed', this.setTxStatusFailed.bind(this)) + this.pendingTxTracker.on('txConfirmed', this.setTxStatusConfirmed.bind(this)) - this.blockTracker.on('rawBlock', this.pendingTxWatchers.checkForTxInBlock.bind(this.pendingTxWatchers)) + this.blockTracker.on('rawBlock', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker)) // this is a little messy but until ethstore has been either // removed or redone this is to guard against the race condition // where ethStore hasent been populated by the results yet - this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.pendingTxWatchers.resubmitPendingTxs.bind(this.pendingTxWatchers))) - this.blockTracker.on('sync', this.pendingTxWatchers.queryPendingTxs.bind(this.pendingTxWatchers)) + this.blockTracker.once('latest', () => { + this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker)) + }) + this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker)) // memstore is computed from a few different stores this._updateMemstore() this.store.subscribe(() => this._updateMemstore()) @@ -191,7 +197,7 @@ module.exports = class TransactionController extends EventEmitter { async addUnapprovedTransaction (txParams) { // validate - await this.txProviderUtils.validateTxParams(txParams) + await this.txProviderUtil.validateTxParams(txParams) // construct txMeta const txMeta = { id: createId(), @@ -217,7 +223,7 @@ module.exports = class TransactionController extends EventEmitter { txParams.gasPrice = gasPrice } // set gasLimit - return await this.txProviderUtils.analyzeGasUsage(txMeta) + return await this.txProviderUtil.analyzeGasUsage(txMeta) } async updateAndApproveTransaction (txMeta) { @@ -260,7 +266,7 @@ module.exports = class TransactionController extends EventEmitter { const fromAddress = txParams.from // add network/chain id txParams.chainId = this.getChainId() - const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) + const ethTx = this.txProviderUtil.buildEthTxFromParams(txParams) await this.signEthTx(ethTx, fromAddress) this.setTxStatusSigned(txMeta.id) const rawTx = ethUtil.bufferToHex(ethTx.serialize()) @@ -271,7 +277,7 @@ module.exports = class TransactionController extends EventEmitter { const txMeta = this.getTx(txId) txMeta.rawTx = rawTx this.updateTx(txMeta) - const txHash = await this.txProviderUtils.publishTransaction(rawTx) + const txHash = await this.txProviderUtil.publishTransaction(rawTx) this.setTxHash(txId, txHash) this.setTxStatusSubmitted(txId) } diff --git a/app/scripts/lib/pending-tx-watchers.js b/app/scripts/lib/pending-tx-tracker.js similarity index 98% rename from app/scripts/lib/pending-tx-watchers.js rename to app/scripts/lib/pending-tx-tracker.js index 60d0a09ad..6921997b2 100644 --- a/app/scripts/lib/pending-tx-watchers.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -19,7 +19,7 @@ const sufficientBalance = require('./util').sufficientBalance */ -module.exports = class PendingTransactionWatchers extends EventEmitter { +module.exports = class PendingTransactionTracker extends EventEmitter { constructor (config) { super() this.query = new EthQuery(config.provider) @@ -97,6 +97,7 @@ module.exports = class PendingTransactionWatchers extends EventEmitter { async _resubmitTx (txMeta) { const address = txMeta.txParams.from const balance = this.getBalance(address) + if (balance === undefined) return if (!('retryCount' in txMeta)) txMeta.retryCount = 0 // if the value of the transaction is greater then the balance, fail. diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index a2db4abd8..b64ea6712 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -13,7 +13,7 @@ its passed ethquery and used to do things like calculate gas of a tx. */ -module.exports = class txProvideUtils { +module.exports = class txProvideUtil { constructor (provider) { this.query = new EthQuery(provider) } From 88b84e389513c3486e99c09b9a589c2a6f636248 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 8 Aug 2017 18:34:59 -0400 Subject: [PATCH 21/21] add json-rpc-engine && eth-json-rpc-middleware to devDependencies --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index a275247ba..f35ac4963 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "enzyme": "^2.8.2", "eslint-plugin-chai": "0.0.1", "eslint-plugin-mocha": "^4.9.0", + "eth-json-rpc-middleware": "^1.2.7", "fs-promise": "^1.0.0", "gulp": "github:gulpjs/gulp#4.0", "gulp-if": "^2.0.1", @@ -169,6 +170,7 @@ "jsdom": "^8.1.0", "jsdom-global": "^1.7.0", "jshint-stylish": "~0.1.5", + "json-rpc-engine": "^3.0.1", "lodash.assign": "^4.0.6", "mocha": "^2.4.5", "mocha-eslint": "^2.1.1",