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