ES6 class && async/await && wait for tx available

pull/5370/head
yann300 6 years ago
parent 6cf813b3f6
commit 0820f05e18
  1. 331
      remix-lib/src/execution/txRunner.js

@ -6,193 +6,224 @@ var BN = ethJSUtil.BN
var executionContext = require('./execution-context') var executionContext = require('./execution-context')
var EventManager = require('../eventManager') var EventManager = require('../eventManager')
function TxRunner (vmaccounts, api) { class TxRunner {
this.event = new EventManager() constructor (vmaccounts, api) {
this._api = api this.event = new EventManager()
this.blockNumber = 0 this._api = api
this.runAsync = true this.blockNumber = 0
if (executionContext.isVM()) { this.runAsync = true
this.blockNumber = 1150000 // The VM is running in Homestead mode, which started at this block. if (executionContext.isVM()) {
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.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) { rawRun (args, confirmationCb, gasEstimationForceSend, promptCb, cb) {
if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice) run(this, args, Date.now(), confirmationCb, gasEstimationForceSend, promptCb, cb)
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)
} }
}
TxRunner.prototype._sendTransaction = function (sendTx, tx, pass, callback) { _executeTx (tx, gasPrice, api, promptCb, callback) {
var self = this if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice)
var cb = function (err, resp) { if (api.personalMode()) {
if (err) { promptCb(
return callback(err, resp) (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) { _sendTransaction (sendTx, tx, pass, callback) {
var self = this var self = this
var cb = function (err, resp) {
var data = args.data if (err) {
if (data.slice(0, 2) !== '0x') { return callback(err, resp)
data = '0x' + data }
} 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) var args = pass !== null ? [tx, pass, cb] : [tx, cb]
} else {
try { try {
self.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback) sendTx.apply({}, args)
} catch (e) { } 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) { execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
const self = this var 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()
}
executionContext.vm().runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) { var data = args.data
if (useCall) { if (data.slice(0, 2) !== '0x') {
executionContext.vm().stateManager.revert(function () {}) data = '0x' + data
} }
err = err ? err.message : err
if (result) { if (!executionContext.isVM()) {
result.status = '0x' + result.vm.exception.toString(16) 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) { runInVm (from, to, data, value, gasLimit, useCall, callback) {
const self = this const self = this
var tx = { from: from, to: to, data: data, value: value } 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) { executionContext.vm().runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) {
tx.gas = gasLimit if (useCall) {
return executionContext.web3().eth.call(tx, function (error, result) { executionContext.vm().stateManager.revert(function () {})
callback(error, { }
err = err ? err.message : err
if (result) {
result.status = '0x' + result.vm.exception.toString(16)
}
callback(err, {
result: result, 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')) { runInNode (from, to, data, value, gasLimit, useCall, confirmCb, gasEstimationForceSend, promptCb, callback) {
return self._executeTx(tx, null, self._api, 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 (self._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) {
if (err) { return self._executeTx(tx, null, self._api, promptCb, callback)
console.log(err)
return
} }
confirmCb(network, tx, tx.gas, (gasPrice) => { self._api.detectNetwork((err, network) => {
return self._executeTx(tx, gasPrice, self._api, promptCb, callback) if (err) {
}, (error) => { console.log(err)
callback(error) return
}
confirmCb(network, tx, tx.gas, (gasPrice) => {
return self._executeTx(tx, gasPrice, self._api, promptCb, callback)
}, (error) => {
callback(error)
})
}) })
}) }, () => {
}, () => { var blockGasLimit = executionContext.currentblockGasLimit()
var blockGasLimit = executionContext.currentblockGasLimit() // NOTE: estimateGas very likely will return a large limit if execution of the code failed
// 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
// 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)
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). ' 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 warnEstimation += ' ' + err
if (gasEstimation > gasLimit) { if (gasEstimation > gasLimit) {
return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation) return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation)
} }
if (gasEstimation > blockGasLimit) { if (gasEstimation > blockGasLimit) {
return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation) 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) { async function tryTillTxAvailable (txhash, done) {
executionContext.web3().eth.getTransactionReceipt(txhash, function (err, result) { return new Promise((resolve, reject) => {
if (err || !result) { executionContext.web3().eth.getTransaction(txhash, async (err, tx) => {
// Try again with a bit of delay if error or if result still null if (err || !tx) {
setTimeout(function () { tryTillResponse(txhash, done) }, 500) // Try again with a bit of delay if error or if result still null
} else { await pause()
done(err, { return resolve(await tryTillTxAvailable(txhash))
result: result, } else {
transactionHash: result ? result.transactionHash : null return resolve(tx)
}) }
} })
}) })
} }
async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) }
function run (self, tx, stamp, confirmationCb, gasEstimationForceSend, promptCb, callback) { function run (self, tx, stamp, confirmationCb, gasEstimationForceSend, promptCb, callback) {
if (!self.runAsync && Object.keys(self.pendingTxs).length) { if (!self.runAsync && Object.keys(self.pendingTxs).length) {
self.queusTxs.push({ tx, stamp, callback }) self.queusTxs.push({ tx, stamp, callback })

Loading…
Cancel
Save