parent
b924c30f9d
commit
4bf2d990bf
@ -0,0 +1,226 @@ |
||||
'use strict' |
||||
var async = require('async') |
||||
var EventManager = require('ethereum-remix').lib.EventManager |
||||
var remix = require('ethereum-remix') |
||||
var codeUtil = remix.util.code |
||||
var Web3VMProvider = remix.web3.web3VMProvider |
||||
|
||||
/** |
||||
* poll web3 each 2s if web3 |
||||
* listen on transaction executed event if VM |
||||
* attention: blocks returned by the event `newBlock` have slightly different json properties whether web3 or the VM is used |
||||
* trigger 'newBlock' |
||||
* |
||||
*/ |
||||
class TxListener { |
||||
constructor (opt) { |
||||
this.event = new EventManager() |
||||
this._appAPI = opt.appAPI |
||||
this._web3VMProvider = new Web3VMProvider() // TODO this should maybe be put in app.js
|
||||
this._web3VMProvider.setVM(opt.appAPI.vm()) |
||||
this._resolvedTransactions = {} |
||||
this._resolvedContracts = {} |
||||
this.init() |
||||
opt.appEvent.executionContext.register('contextChanged', (context) => { |
||||
if (this.loopId) { |
||||
this.startListening(context) |
||||
} |
||||
}) |
||||
opt.appEvent.udapp.register('transactionExecuted', (to, data, lookupOnly, txResult) => { |
||||
if (this.loopId && this._appAPI.isVM()) { |
||||
this._web3VMProvider.getTransaction(txResult.transactionHash, (error, tx) => { |
||||
if (error) return console.log(error) |
||||
this._newBlock({ |
||||
type: 'VM', |
||||
number: -1, |
||||
transactions: [tx] |
||||
}) |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* reset recorded transactions |
||||
*/ |
||||
init () { |
||||
this.blocks = [] |
||||
this.lastBlock = null |
||||
} |
||||
|
||||
/** |
||||
* start listening for incoming transactions |
||||
* |
||||
* @param {String} type - type/name of the provider to add |
||||
* @param {Object} obj - provider |
||||
*/ |
||||
startListening () { |
||||
this.stopListening() |
||||
this.init() |
||||
if (this._appAPI.context() === 'vm') { |
||||
this.loopId = 'vm-listener' |
||||
} else { |
||||
this.loopId = setInterval(() => { |
||||
this._appAPI.web3().eth.getBlockNumber((error, blockNumber) => { |
||||
if (error) return console.log(error) |
||||
if (!this.lastBlock || blockNumber > this.lastBlock) { |
||||
this.lastBlock = blockNumber |
||||
this._appAPI.web3().eth.getBlock(this.lastBlock, true, (error, result) => { |
||||
if (!error) { |
||||
this._newBlock(Object.assign({type: 'web3'}, result)) |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
}, 2) |
||||
} |
||||
} |
||||
|
||||
currentWeb3 () { // TODO this should maybe be put in app.js
|
||||
return this._appAPI.isVM() ? this._web3VMProvider : this._appAPI.web3() |
||||
} |
||||
|
||||
/** |
||||
* stop listening for incoming transactions. do not reset the recorded pool. |
||||
* |
||||
* @param {String} type - type/name of the provider to add |
||||
* @param {Object} obj - provider |
||||
*/ |
||||
stopListening () { |
||||
if (this.loopId) { |
||||
clearInterval(this.loopId) |
||||
} |
||||
this.loopId = null |
||||
} |
||||
|
||||
/** |
||||
* try to resolve the contract name from the given @arg address |
||||
* |
||||
* @param {String} address - contract address to resolve |
||||
* @return {String} - contract name |
||||
*/ |
||||
resolvedContract (address) { |
||||
return this._resolvedContracts[address] |
||||
} |
||||
|
||||
/** |
||||
* try to resolve the transaction from the given @arg txHash |
||||
* |
||||
* @param {String} txHash - contract address to resolve |
||||
* @return {String} - contract name |
||||
*/ |
||||
resolvedTransaction (txHash) { |
||||
return this._resolvedTransactions[txHash] |
||||
} |
||||
|
||||
_newBlock (block) { |
||||
this.blocks.push(block) |
||||
this._resolve(block, () => { |
||||
this.event.trigger('newBlock', [block]) |
||||
}) |
||||
} |
||||
|
||||
_resolve (block, callback) { |
||||
async.each(block.transactions, (tx, cb) => { |
||||
this._resolveTx(tx, cb) |
||||
}, () => { |
||||
callback() |
||||
}) |
||||
} |
||||
|
||||
_resolveTx (tx, cb) { |
||||
var contracts = this._appAPI.contracts() |
||||
if (!contracts) return cb() |
||||
var contractName |
||||
if (!tx.to) { |
||||
// contract creation / resolve using the creation bytes code
|
||||
// if web3: we have to call getTransactionReceipt to get the created address
|
||||
// if VM: created address already included
|
||||
var code = tx.input |
||||
contractName = this._tryResolveContract(code, contracts, 'bytecode') |
||||
if (contractName) { |
||||
this._resolveCreationAddress(tx, (error, address) => { |
||||
if (error) return cb(error) |
||||
this._resolvedContracts[address] = contractName |
||||
this._resolveFunction(contractName, contracts, tx, true) |
||||
if (this._resolvedTransactions[tx.hash]) { |
||||
this._resolvedTransactions[tx.hash].contractAddress = address |
||||
} |
||||
return cb() |
||||
}) |
||||
return |
||||
} |
||||
return cb() |
||||
} else { |
||||
// first check known contract, resolve against the `runtimeBytecode` if not known
|
||||
contractName = this._resolvedContracts[tx.to] |
||||
if (!contractName) { |
||||
this.currentWeb3().eth.getCode(tx.to, (error, code) => { |
||||
if (error) return cb(error) |
||||
if (code) { |
||||
var contractName = this._tryResolveContract(code, contracts, 'runtimeBytecode') |
||||
if (contractName) { |
||||
this._resolvedContracts[tx.to] = contractName |
||||
this._resolveFunction(contractName, contracts, tx, false) |
||||
return cb() |
||||
} |
||||
return cb() |
||||
} |
||||
return cb() |
||||
}) |
||||
return |
||||
} |
||||
if (contractName) { |
||||
this._resolveFunction(contractName, contracts, tx, false) |
||||
return cb() |
||||
} |
||||
return cb() |
||||
} |
||||
} |
||||
|
||||
_resolveCreationAddress (tx, cb) { |
||||
this.currentWeb3().eth.getTransactionReceipt(tx.hash, (error, receipt) => { |
||||
if (!error) { |
||||
cb(null, receipt.contractAddress) |
||||
} else { |
||||
cb(error) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
_resolveFunction (contractName, compiledContracts, tx, isCtor) { |
||||
if (!isCtor) { |
||||
for (var fn in compiledContracts[contractName].functionHashes) { |
||||
if (compiledContracts[contractName].functionHashes[fn] === tx.input.replace('0x', '').substring(0, 8)) { |
||||
this._resolvedTransactions[tx.hash] = { |
||||
to: tx.to, |
||||
fn: fn, |
||||
params: null |
||||
} |
||||
} |
||||
// fallback function
|
||||
this._resolvedTransactions[tx.hash] = { |
||||
to: tx.to, |
||||
fn: '(fallback)', |
||||
params: null |
||||
} |
||||
} else { |
||||
this._resolvedTransactions[tx.hash] = { |
||||
to: null, |
||||
fn: '(constructor)', |
||||
params: null |
||||
} |
||||
} |
||||
} |
||||
|
||||
_tryResolveContract (codeToResolve, compiledContracts, type) { |
||||
for (var k in compiledContracts) { |
||||
if (codeUtil.compareByteCode(codeToResolve, '0x' + compiledContracts[k][type])) { |
||||
return k |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
} |
||||
|
||||
module.exports = TxListener |
Loading…
Reference in new issue