From 04a0b949a2ce5b0335625236d9cac1dc7153dbf1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 7 Jul 2017 11:24:33 -0700 Subject: [PATCH 1/9] Version 3.8.4 --- CHANGELOG.md | 4 ++++ app/manifest.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3966ea1bb..696d68345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Master +## 3.8.4 2017-7-7 + +- Improve transaction resubmit logic to fail more eagerly when a user would expect it to. + ## 3.8.3 2017-7-6 - Re-enable default token list. diff --git a/app/manifest.json b/app/manifest.json index aafc33e66..d386e43aa 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.8.3", + "version": "3.8.4", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 4fa999e4deae5451e73c126a80e541db6e3d0dc3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 19:02:34 -0700 Subject: [PATCH 2/9] tx controller - resubmit - recognize parity known hash message --- app/scripts/controllers/transactions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 41d70194e..bcce1bf8f 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -431,6 +431,7 @@ module.exports = class TransactionController extends EventEmitter { || errorMessage.startsWith('known transaction') // parity || errorMessage === 'gas price too low to replace' + || errorMessage === 'transaction with the same hash was already imported.' ) // ignore resubmit warnings, return early if (!isKnownTx) this.setTxStatusFailed(txMeta.id, reason.message) From c53aac398a29ff7cfe0efa6c844653693d78157b Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 19:09:32 -0700 Subject: [PATCH 3/9] tx controller - correctly set error message on resubmit error --- app/scripts/controllers/transactions.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index bcce1bf8f..e1eaba232 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -417,14 +417,13 @@ module.exports = class TransactionController extends EventEmitter { // only try resubmitting if their are transactions to resubmit if (!pending.length) return const resubmit = denodeify(this._resubmitTx.bind(this)) - pending.forEach((txMeta) => resubmit(txMeta) - .catch((reason) => { + pending.forEach((txMeta) => resubmit(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 = reason.message.toLowerCase() + const errorMessage = err.message.toLowerCase() const isKnownTx = ( // geth errorMessage === 'replacement transaction underpriced' @@ -434,7 +433,12 @@ module.exports = class TransactionController extends EventEmitter { || errorMessage === 'transaction with the same hash was already imported.' ) // ignore resubmit warnings, return early - if (!isKnownTx) this.setTxStatusFailed(txMeta.id, reason.message) + if (isKnownTx) return + // encountered real error - transition to error state + this.setTxStatusFailed(txMeta.id, { + errCode: err.errCode || err, + message: err.message, + }) })) } From c425ad4ec71c6a5d5cc7af3d2a4d42c56c3ca125 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 19:13:06 -0700 Subject: [PATCH 4/9] tx controller - resubmit - correctly set error on bad nonce/balance --- app/scripts/controllers/transactions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index e1eaba232..18bb245de 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -453,7 +453,7 @@ module.exports = class TransactionController extends EventEmitter { // if the value of the transaction is greater then the balance, fail. if (gtBalance) { const message = 'Insufficient balance.' - this.setTxStatusFailed(txMeta.id, message) + this.setTxStatusFailed(txMeta.id, { message }) cb() return log.error(message) } @@ -461,7 +461,7 @@ module.exports = class TransactionController extends EventEmitter { // if the nonce of the transaction is lower then the accounts nonce, fail. if (txNonce < nonce) { const message = 'Invalid nonce.' - this.setTxStatusFailed(txMeta.id, message) + this.setTxStatusFailed(txMeta.id, { message }) cb() return log.error(message) } From 512b6cae81ee0e71b567a72418ac224804dfc3e6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 19:31:27 -0700 Subject: [PATCH 5/9] migration 16 - move resubmit warning back to submitted state --- app/scripts/migrations/016.js | 41 +++++++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + 2 files changed, 42 insertions(+) create mode 100644 app/scripts/migrations/016.js diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js new file mode 100644 index 000000000..4fc534f1c --- /dev/null +++ b/app/scripts/migrations/016.js @@ -0,0 +1,41 @@ +const version = 16 + +/* + +This migration sets transactions with the 'Gave up submitting tx.' err message +to a 'failed' stated + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.err) return txMeta + if (txMeta.err === 'transaction with the same hash was already imported.') { + txMeta.status = 'submitted' + delete txMeta.err + } + return txMeta + }) + return newState +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 651ee6a9c..a4f9c7c4d 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -26,4 +26,5 @@ module.exports = [ require('./013'), require('./014'), require('./015'), + require('./016'), ] From 11d57adc5c6fa124cbb83a907a55a0aa733bb1cc Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 11 Jul 2017 11:57:42 -0700 Subject: [PATCH 6/9] add "Gateway timeout" to ignored errors when resubmiting and use .includes over .startsWith --- app/scripts/controllers/transactions.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 18bb245de..933c079d2 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -427,10 +427,12 @@ module.exports = class TransactionController extends EventEmitter { const isKnownTx = ( // geth errorMessage === 'replacement transaction underpriced' - || errorMessage.startsWith('known transaction') + || errorMessage.includes('known transaction') // parity || errorMessage === 'gas price too low to replace' || errorMessage === 'transaction with the same hash was already imported.' + // other + || errorMessage.includes('gateway timeout') ) // ignore resubmit warnings, return early if (isKnownTx) return From d97c6533b87b0a9dd6937c1ca57ec05129ac619b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 11 Jul 2017 11:59:56 -0700 Subject: [PATCH 7/9] Remove local nonce error setting. --- CHANGELOG.md | 2 ++ app/scripts/controllers/transactions.js | 8 -------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 696d68345..f53bdead5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- No longer validate nonce client-side in retry loop. + ## 3.8.4 2017-7-7 - Improve transaction resubmit logic to fail more eagerly when a user would expect it to. diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 18bb245de..02487c385 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -458,14 +458,6 @@ module.exports = class TransactionController extends EventEmitter { return log.error(message) } - // if the nonce of the transaction is lower then the accounts nonce, fail. - if (txNonce < nonce) { - const message = 'Invalid nonce.' - this.setTxStatusFailed(txMeta.id, { message }) - cb() - return log.error(message) - } - // Only auto-submit already-signed txs: if (!('rawTx' in txMeta)) return cb() From 611338c4e0b6de670f1a3ac6a0f1ddd3a2c063f7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 11 Jul 2017 12:01:59 -0700 Subject: [PATCH 8/9] use .includes --- 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 933c079d2..c0d4841a9 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -426,11 +426,11 @@ module.exports = class TransactionController extends EventEmitter { const errorMessage = err.message.toLowerCase() const isKnownTx = ( // geth - errorMessage === 'replacement transaction underpriced' + errorMessage.includes('replacement transaction underpriced') || errorMessage.includes('known transaction') // parity - || errorMessage === 'gas price too low to replace' - || errorMessage === 'transaction with the same hash was already imported.' + || errorMessage.includes('gas price too low to replace') + || errorMessage.includes('transaction with the same hash was already imported') // other || errorMessage.includes('gateway timeout') ) From c7b9e3fb1878cebbab26d5343cc18084a601c6bb Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 11 Jul 2017 12:18:07 -0700 Subject: [PATCH 9/9] Improve insufficient balance checking in retry loop --- CHANGELOG.md | 1 + app/scripts/controllers/transactions.js | 5 +--- app/scripts/lib/tx-utils.js | 9 ++++++ test/unit/tx-utils-test.js | 38 +++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f53bdead5..395454b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - No longer validate nonce client-side in retry loop. +- Fix bug where insufficient balance error was sometimes shown on successful transactions. ## 3.8.4 2017-7-7 diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 02487c385..ca379a7ff 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -445,13 +445,10 @@ module.exports = class TransactionController extends EventEmitter { _resubmitTx (txMeta, cb) { const address = txMeta.txParams.from const balance = this.ethStore.getState().accounts[address].balance - const nonce = Number.parseInt(this.ethStore.getState().accounts[address].nonce) - const txNonce = Number.parseInt(txMeta.txParams.nonce) - const gtBalance = Number.parseInt(txMeta.txParams.value) > Number.parseInt(balance) if (!('retryCount' in txMeta)) txMeta.retryCount = 0 // if the value of the transaction is greater then the balance, fail. - if (gtBalance) { + if (!this.txProviderUtils.sufficientBalance(txMeta.txParams, balance)) { const message = 'Insufficient balance.' this.setTxStatusFailed(txMeta.id, { message }) cb() diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 149d93102..4e780fcc0 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -118,6 +118,15 @@ module.exports = class txProviderUtils { } } + sufficientBalance (tx, hexBalance) { + const balance = hexToBn(hexBalance) + const value = hexToBn(tx.value) + const gasLimit = hexToBn(tx.gas) + const gasPrice = hexToBn(tx.gasPrice) + + const maxCost = value.add(gasLimit.mul(gasPrice)) + return balance.gte(maxCost) + } } diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index 7ace1f587..a43bcfb35 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -16,6 +16,44 @@ 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 = {