parent
9e3fa3cfba
commit
090935f90a
@ -0,0 +1,106 @@ |
||||
const async = require('async') |
||||
const EthQuery = require('eth-query') |
||||
const ethUtil = require('ethereumjs-util') |
||||
const BN = ethUtil.BN |
||||
const ethBinToOps = require('eth-bin-to-ops') |
||||
|
||||
module.exports = class txProviderUtils { |
||||
constructor (provider) { |
||||
this.provider = provider |
||||
this.query = new EthQuery(provider) |
||||
} |
||||
analyzeGasUsage (txData, cb) { |
||||
var self = this |
||||
this.query.getBlockByNumber('latest', true, (err, block) => { |
||||
if (err) return cb(err) |
||||
async.waterfall([ |
||||
self.estimateTxGas.bind(self, txData, block.gasLimit), |
||||
self.checkForTxGasError.bind(self, txData), |
||||
self.setTxGas.bind(self, txData, block.gasLimit), |
||||
], cb) |
||||
}) |
||||
} |
||||
|
||||
// perform static analyis on the target contract code
|
||||
analyzeForDelegateCall (txParams, cb) { |
||||
if (txParams.to) { |
||||
this.query.getCode(txParams.to, function (err, result) { |
||||
if (err) return cb(err) |
||||
|
||||
var code = ethUtil.toBuffer(result) |
||||
if (code !== '0x') { |
||||
var ops = ethBinToOps(code) |
||||
var containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL') |
||||
cb(containsDelegateCall) |
||||
} else { |
||||
cb() |
||||
} |
||||
}) |
||||
} else { |
||||
cb() |
||||
} |
||||
} |
||||
|
||||
estimateTxGas (txData, blockGasLimitHex, cb) { |
||||
const txParams = txData.txParams |
||||
// check if gasLimit is already specified
|
||||
txData.gasLimitSpecified = Boolean(txParams.gas) |
||||
// if not, fallback to block gasLimit
|
||||
if (!txData.gasLimitSpecified) { |
||||
txParams.gas = blockGasLimitHex |
||||
} |
||||
// run tx, see if it will OOG
|
||||
this.query.estimateGas(txParams, cb) |
||||
} |
||||
|
||||
checkForTxGasError (txData, estimatedGasHex, cb) { |
||||
txData.estimatedGas = estimatedGasHex |
||||
// all gas used - must be an error
|
||||
if (estimatedGasHex === txData.txParams.gas) { |
||||
txData.simulationFails = true |
||||
} |
||||
cb() |
||||
} |
||||
|
||||
handleFork (block) { |
||||
|
||||
} |
||||
|
||||
setTxGas (txData, blockGasLimitHex, cb) { |
||||
const txParams = txData.txParams |
||||
// if OOG, nothing more to do
|
||||
if (txData.simulationFails) { |
||||
cb() |
||||
return |
||||
} |
||||
// if gasLimit was specified and doesnt OOG,
|
||||
// use original specified amount
|
||||
if (txData.gasLimitSpecified) { |
||||
txData.estimatedGas = txParams.gas |
||||
cb() |
||||
return |
||||
} |
||||
// if gasLimit not originally specified,
|
||||
// try adding an additional gas buffer to our estimation for safety
|
||||
const estimatedGasBn = new BN(ethUtil.stripHexPrefix(txData.estimatedGas), 16) |
||||
const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(blockGasLimitHex), 16) |
||||
const estimationWithBuffer = new BN(this.addGasBuffer(estimatedGasBn), 16) |
||||
// added gas buffer is too high
|
||||
if (estimationWithBuffer.gt(blockGasLimitBn)) { |
||||
txParams.gas = txData.estimatedGas |
||||
// added gas buffer is safe
|
||||
} else { |
||||
const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer) |
||||
txParams.gas = gasWithBufferHex |
||||
} |
||||
cb() |
||||
return |
||||
} |
||||
|
||||
addGasBuffer (gas) { |
||||
const gasBuffer = new BN('100000', 10) |
||||
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16) |
||||
const correct = bnGas.add(gasBuffer) |
||||
return ethUtil.addHexPrefix(correct.toString(16)) |
||||
} |
||||
} |
@ -0,0 +1,179 @@ |
||||
const EventEmitter = require('events') |
||||
const extend = require('xtend') |
||||
const TxProviderUtil = require('./lib/provider-utils') |
||||
|
||||
module.exports = class TransactionManager extends EventEmitter { |
||||
constructor (opts) { |
||||
super() |
||||
this.txList = opts.TxListFromStore || [] |
||||
this._persistTxList = opts.setTxList |
||||
this._unconfTxCbs = {} |
||||
this.txLimit = opts.txLimit |
||||
this.provider = opts.provider |
||||
} |
||||
|
||||
// Returns the tx list
|
||||
getTxList () { |
||||
return this.txList |
||||
} |
||||
|
||||
// Saves the new/updated txList.
|
||||
// Function is intended only for internal use
|
||||
_saveTxList (txList) { |
||||
this.txList = txList |
||||
this._persistTxList(txList) |
||||
} |
||||
|
||||
// Adds a tx to the txlist
|
||||
addTx (txData, onTxDoneCb) { |
||||
var txList = this.getTxList() |
||||
var txLimit = this.txLimit |
||||
if (txList.length > txLimit - 1) { |
||||
txList.shift() |
||||
} |
||||
txList.push(txData) |
||||
this._saveTxList(txList) |
||||
this.addOnTxDoneCb(txData.id, onTxDoneCb) |
||||
this.emit('unapproved', txData) |
||||
this.emit('update') |
||||
} |
||||
|
||||
getTx (txId, cb) { |
||||
var txList = this.getTxList() |
||||
var tx = txList.find((tx) => tx.id === txId) |
||||
return cb ? cb(tx) : tx |
||||
} |
||||
|
||||
updateTx (txData) { |
||||
var txId = txData.id |
||||
var txList = this.getTxList() |
||||
|
||||
var updatedTxList = txList.map((tx) => { |
||||
if (tx.id === txId) { |
||||
tx = txData |
||||
} |
||||
return tx |
||||
}) |
||||
this._saveTxList(updatedTxList) |
||||
} |
||||
|
||||
get unConftxCount () { |
||||
return Object.keys(this.getUnapprovedTxList()).length |
||||
} |
||||
|
||||
get pendingTxCount () { |
||||
return this.getTxsByMetaData('status', 'signed').length |
||||
} |
||||
|
||||
getUnapprovedTxList () { |
||||
var txList = this.getTxList() |
||||
return txList.filter((tx) => { |
||||
return tx.status === 'unapproved' |
||||
}).reduce((result, tx) => { |
||||
result[tx.id] = tx |
||||
return result |
||||
}, {}) |
||||
} |
||||
|
||||
getFilterdTxList (opts) { |
||||
var filteredTxList |
||||
Object.keys(opts).forEach((key) => { |
||||
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) |
||||
}) |
||||
return filteredTxList |
||||
} |
||||
|
||||
getTxsByMetaData (key, value, txList = this.getTxList()) { |
||||
return txList.filter((tx) => { |
||||
if (key in tx.txParams) { |
||||
return tx.txParams[key] === value |
||||
} else { |
||||
return tx[key] === value |
||||
} |
||||
}) |
||||
} |
||||
|
||||
addOnTxDoneCb (txId, cb) { |
||||
this._unconfTxCbs[txId] = cb || noop |
||||
} |
||||
|
||||
// should return the tx
|
||||
|
||||
// Should find the tx in the tx list and
|
||||
// update it.
|
||||
// should set the status in txData
|
||||
// // - `'unapproved'` the user has not responded
|
||||
// // - `'rejected'` the user has responded no!
|
||||
// // - `'signed'` the tx is signed
|
||||
// // - `'submitted'` the tx is sent to a server
|
||||
// // - `'confirmed'` the tx has been included in a block.
|
||||
setTxStatus (txId, status) { |
||||
var txData = this.getTx(txId) |
||||
txData.status = status |
||||
this.emit(status, txId) |
||||
this.updateTx(txData, status) |
||||
} |
||||
|
||||
|
||||
// should return the status of the tx.
|
||||
getTxStatus (txId, cb) { |
||||
const txData = this.getTx(txId) |
||||
return cb ? cb(txData.staus) : txData.status |
||||
} |
||||
|
||||
|
||||
// should update the status of the tx to 'signed'.
|
||||
setTxStatusSigned (txId) { |
||||
this.setTxStatus(txId, 'signed') |
||||
this.emit('update') |
||||
} |
||||
|
||||
// should update the status of the tx to 'rejected'.
|
||||
setTxStatusRejected (txId) { |
||||
this.setTxStatus(txId, 'rejected') |
||||
this.emit('update') |
||||
} |
||||
|
||||
setTxStatusConfirmed (txId) { |
||||
this.setTxStatus(txId, 'confirmed') |
||||
// this.removeListener(`check${txId}`, this.checkForTxInBlock)
|
||||
} |
||||
|
||||
// merges txParams obj onto txData.txParams
|
||||
// use extend to ensure that all fields are filled
|
||||
updateTxParams (txId, txParams) { |
||||
var txData = this.getTx(txId) |
||||
txData.txParams = extend(txData, txParams) |
||||
this.updateTx(txData) |
||||
} |
||||
|
||||
setProvider (provider) { |
||||
this.provider = provider |
||||
this.txProviderUtils = new TxProviderUtil(provider) |
||||
this.provider.on('block', this.checkForTxInBlock.bind(this)) |
||||
} |
||||
|
||||
checkForTxInBlock () { |
||||
var signedTxList = this.getFilterdTxList({status: 'signed'}) |
||||
if (!signedTxList.length) return |
||||
var self = this |
||||
signedTxList.forEach((tx) => { |
||||
var txHash = tx.hash |
||||
var txId = tx.id |
||||
if (!txHash) return |
||||
// var d
|
||||
this.txProviderUtils.query.getTransactionByHash(txHash, (err, txData) => { |
||||
if (err) { |
||||
tx |
||||
|
||||
return console.error(err) |
||||
} |
||||
if (txData.blockNumber !== null) { |
||||
self.setTxStatusConfirmed(txId) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
function noop () {} |
Loading…
Reference in new issue