diff options
Diffstat (limited to 'test/unit/pending-tx-test.js')
-rw-r--r-- | test/unit/pending-tx-test.js | 271 |
1 files changed, 271 insertions, 0 deletions
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 |