From fe6f67e1e04bbf6c83c3cc134afd747be99d4d5e Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 17 Sep 2018 12:53:55 +0200 Subject: [PATCH] ES6 class && async/await && wait for tx available --- remix-lib/src/execution/txRunner.js | 331 +++++++++++++++------------- 1 file changed, 181 insertions(+), 150 deletions(-) diff --git a/remix-lib/src/execution/txRunner.js b/remix-lib/src/execution/txRunner.js index 9a38016c91..47d37e4427 100644 --- a/remix-lib/src/execution/txRunner.js +++ b/remix-lib/src/execution/txRunner.js @@ -6,193 +6,224 @@ var BN = ethJSUtil.BN var executionContext = require('./execution-context') var EventManager = require('../eventManager') -function TxRunner (vmaccounts, api) { - this.event = new EventManager() - this._api = api - this.blockNumber = 0 - this.runAsync = true - if (executionContext.isVM()) { - this.blockNumber = 1150000 // The VM is running in Homestead mode, which started at this block. - this.runAsync = false // We have to run like this cause the VM Event Manager does not support running multiple txs at the same time. +class TxRunner { + constructor (vmaccounts, api) { + this.event = new EventManager() + this._api = api + this.blockNumber = 0 + this.runAsync = true + if (executionContext.isVM()) { + this.blockNumber = 1150000 // The VM is running in Homestead mode, which started at this block. + this.runAsync = false // We have to run like this cause the VM Event Manager does not support running multiple txs at the same time. + } + this.pendingTxs = {} + this.vmaccounts = vmaccounts + this.queusTxs = [] } - this.pendingTxs = {} - this.vmaccounts = vmaccounts - this.queusTxs = [] -} - -TxRunner.prototype.rawRun = function (args, confirmationCb, gasEstimationForceSend, promptCb, cb) { - run(this, args, Date.now(), confirmationCb, gasEstimationForceSend, promptCb, cb) -} -TxRunner.prototype._executeTx = function (tx, gasPrice, api, promptCb, callback) { - if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice) - if (api.personalMode()) { - promptCb( - (value) => { - this._sendTransaction(executionContext.web3().personal.sendTransaction, tx, value, callback) - }, - () => { - return callback('Canceled by user.') - } - ) - } else { - this._sendTransaction(executionContext.web3().eth.sendTransaction, tx, null, callback) + rawRun (args, confirmationCb, gasEstimationForceSend, promptCb, cb) { + run(this, args, Date.now(), confirmationCb, gasEstimationForceSend, promptCb, cb) } -} -TxRunner.prototype._sendTransaction = function (sendTx, tx, pass, callback) { - var self = this - var cb = function (err, resp) { - if (err) { - return callback(err, resp) + _executeTx (tx, gasPrice, api, promptCb, callback) { + if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice) + if (api.personalMode()) { + promptCb( + (value) => { + this._sendTransaction(executionContext.web3().personal.sendTransaction, tx, value, callback) + }, + () => { + return callback('Canceled by user.') + } + ) + } else { + this._sendTransaction(executionContext.web3().eth.sendTransaction, tx, null, callback) } - self.event.trigger('transactionBroadcasted', [resp]) - tryTillResponse(resp, callback) } - var args = pass !== null ? [tx, pass, cb] : [tx, cb] - try { - sendTx.apply({}, args) - } catch (e) { - return callback(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `) - } -} -TxRunner.prototype.execute = function (args, confirmationCb, gasEstimationForceSend, promptCb, callback) { - var self = this - - var data = args.data - if (data.slice(0, 2) !== '0x') { - data = '0x' + data - } + _sendTransaction (sendTx, tx, pass, callback) { + var self = this + var cb = function (err, resp) { + if (err) { + return callback(err, resp) + } + self.event.trigger('transactionBroadcasted', [resp]) + + var listenOnResponse = () => { + return new Promise(async (resolve, reject) => { + var result = await tryTillReceiptAvailable(resp) + tx = await tryTillTxAvailable(resp) + resolve({ + result, + tx, + transactionHash: result ? result.transactionHash : null, + }) + }) + } + listenOnResponse().then((txData) => { callback(null, txData) }).catch((error) => { callback(error) }) - if (!executionContext.isVM()) { - self.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, confirmationCb, gasEstimationForceSend, promptCb, callback) - } else { + } + var args = pass !== null ? [tx, pass, cb] : [tx, cb] try { - self.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback) + sendTx.apply({}, args) } catch (e) { - callback(e, null) + return callback(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `) } } -} -TxRunner.prototype.runInVm = function (from, to, data, value, gasLimit, useCall, callback) { - const self = this - var account = self.vmaccounts[from] - if (!account) { - return callback('Invalid account selected') - } - var tx = new EthJSTX({ - nonce: new BN(account.nonce++), - gasPrice: new BN(1), - gasLimit: new BN(gasLimit, 10), - to: to, - value: new BN(value, 10), - data: new Buffer(data.slice(2), 'hex') - }) - tx.sign(account.privateKey) - - const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ] - const difficulties = [ new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10) ] - var block = new EthJSBlock({ - header: { - timestamp: new Date().getTime() / 1000 | 0, - number: self.blockNumber, - coinbase: coinbases[self.blockNumber % coinbases.length], - difficulty: difficulties[self.blockNumber % difficulties.length], - gasLimit: new BN(gasLimit, 10).imuln(2) - }, - transactions: [], - uncleHeaders: [] - }) - if (!useCall) { - ++self.blockNumber - } else { - executionContext.vm().stateManager.checkpoint() - } + execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) { + var self = this - executionContext.vm().runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) { - if (useCall) { - executionContext.vm().stateManager.revert(function () {}) + var data = args.data + if (data.slice(0, 2) !== '0x') { + data = '0x' + data } - err = err ? err.message : err - if (result) { - result.status = '0x' + result.vm.exception.toString(16) + + if (!executionContext.isVM()) { + self.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, confirmationCb, gasEstimationForceSend, promptCb, callback) + } else { + try { + self.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback) + } catch (e) { + callback(e, null) + } } - callback(err, { - result: result, - transactionHash: ethJSUtil.bufferToHex(new Buffer(tx.hash())) - }) - }) -} + } -TxRunner.prototype.runInNode = function (from, to, data, value, gasLimit, useCall, confirmCb, gasEstimationForceSend, promptCb, callback) { - const self = this - var tx = { from: from, to: to, data: data, value: value } + runInVm (from, to, data, value, gasLimit, useCall, callback) { + const self = this + var account = self.vmaccounts[from] + if (!account) { + return callback('Invalid account selected') + } + var tx = new EthJSTX({ + nonce: new BN(account.nonce++), + gasPrice: new BN(1), + gasLimit: new BN(gasLimit, 10), + to: to, + value: new BN(value, 10), + data: new Buffer(data.slice(2), 'hex') + }) + tx.sign(account.privateKey) + + const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ] + const difficulties = [ new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10) ] + var block = new EthJSBlock({ + header: { + timestamp: new Date().getTime() / 1000 | 0, + number: self.blockNumber, + coinbase: coinbases[self.blockNumber % coinbases.length], + difficulty: difficulties[self.blockNumber % difficulties.length], + gasLimit: new BN(gasLimit, 10).imuln(2) + }, + transactions: [], + uncleHeaders: [] + }) + if (!useCall) { + ++self.blockNumber + } else { + executionContext.vm().stateManager.checkpoint() + } - if (useCall) { - tx.gas = gasLimit - return executionContext.web3().eth.call(tx, function (error, result) { - callback(error, { + executionContext.vm().runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) { + if (useCall) { + executionContext.vm().stateManager.revert(function () {}) + } + err = err ? err.message : err + if (result) { + result.status = '0x' + result.vm.exception.toString(16) + } + callback(err, { result: result, - transactionHash: result ? result.transactionHash : null + transactionHash: ethJSUtil.bufferToHex(new Buffer(tx.hash())) }) }) } - executionContext.web3().eth.estimateGas(tx, function (err, gasEstimation) { - gasEstimationForceSend(err, () => { - // callback is called whenever no error - tx.gas = !gasEstimation ? gasLimit : gasEstimation - if (self._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) { - return self._executeTx(tx, null, self._api, promptCb, callback) - } + runInNode (from, to, data, value, gasLimit, useCall, confirmCb, gasEstimationForceSend, promptCb, callback) { + const self = this + var tx = { from: from, to: to, data: data, value: value } + + if (useCall) { + tx.gas = gasLimit + return executionContext.web3().eth.call(tx, function (error, result) { + callback(error, { + result: result, + transactionHash: result ? result.transactionHash : null + }) + }) + } + executionContext.web3().eth.estimateGas(tx, function (err, gasEstimation) { + gasEstimationForceSend(err, () => { + // callback is called whenever no error + tx.gas = !gasEstimation ? gasLimit : gasEstimation - self._api.detectNetwork((err, network) => { - if (err) { - console.log(err) - return + if (self._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) { + return self._executeTx(tx, null, self._api, promptCb, callback) } - confirmCb(network, tx, tx.gas, (gasPrice) => { - return self._executeTx(tx, gasPrice, self._api, promptCb, callback) - }, (error) => { - callback(error) + self._api.detectNetwork((err, network) => { + if (err) { + console.log(err) + return + } + + confirmCb(network, tx, tx.gas, (gasPrice) => { + return self._executeTx(tx, gasPrice, self._api, promptCb, callback) + }, (error) => { + callback(error) + }) }) - }) - }, () => { - var blockGasLimit = executionContext.currentblockGasLimit() - // NOTE: estimateGas very likely will return a large limit if execution of the code failed - // we want to be able to run the code in order to debug and find the cause for the failure - if (err) return callback(err) + }, () => { + var blockGasLimit = executionContext.currentblockGasLimit() + // NOTE: estimateGas very likely will return a large limit if execution of the code failed + // we want to be able to run the code in order to debug and find the cause for the failure + if (err) return callback(err) - var warnEstimation = ' An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that\'s also the reason of strong gas estimation). ' - warnEstimation += ' ' + err + var warnEstimation = ' An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that\'s also the reason of strong gas estimation). ' + warnEstimation += ' ' + err - if (gasEstimation > gasLimit) { - return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation) - } - if (gasEstimation > blockGasLimit) { - return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation) + if (gasEstimation > gasLimit) { + return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation) + } + if (gasEstimation > blockGasLimit) { + return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation) + } + }) + }) + } +} + +async function tryTillReceiptAvailable (txhash, done) { + return new Promise((resolve, reject) => { + executionContext.web3().eth.getTransactionReceipt(txhash, async (err, receipt) => { + if (err || !receipt) { + // Try again with a bit of delay if error or if result still null + await pause() + return resolve(await tryTillReceiptAvailable(txhash)) + } else { + return resolve(receipt) } }) }) } -function tryTillResponse (txhash, done) { - executionContext.web3().eth.getTransactionReceipt(txhash, function (err, result) { - if (err || !result) { - // Try again with a bit of delay if error or if result still null - setTimeout(function () { tryTillResponse(txhash, done) }, 500) - } else { - done(err, { - result: result, - transactionHash: result ? result.transactionHash : null - }) - } +async function tryTillTxAvailable (txhash, done) { + return new Promise((resolve, reject) => { + executionContext.web3().eth.getTransaction(txhash, async (err, tx) => { + if (err || !tx) { + // Try again with a bit of delay if error or if result still null + await pause() + return resolve(await tryTillTxAvailable(txhash)) + } else { + return resolve(tx) + } + }) }) } +async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) } + function run (self, tx, stamp, confirmationCb, gasEstimationForceSend, promptCb, callback) { if (!self.runAsync && Object.keys(self.pendingTxs).length) { self.queusTxs.push({ tx, stamp, callback })