Commit d6166ff9 authored by yann300's avatar yann300 Committed by GitHub

Merge pull request #612 from ethereum/txListenner

Transaction Listener
parents b924c30f 29b763e1
......@@ -29,6 +29,7 @@ var FilePanel = require('./app/file-panel')
var EditorPanel = require('./app/editor-panel')
var RighthandPanel = require('./app/righthand-panel')
var examples = require('./app/example-contracts')
// var Txlistener = require('./app/txListener')
var css = csjs`
html { box-sizing: border-box; }
......@@ -847,6 +848,46 @@ function run () {
var node = document.getElementById('staticanalysisView')
node.insertBefore(staticanalysis.render(), node.childNodes[0])
// ----------------- Tx listener -----------------
// Commented for now. will be used later.
/*
var txlistener = new Txlistener({
api: {
web3: function () { return executionContext.web3() },
isVM: function () { return executionContext.isVM() },
vm: function () { return executionContext.vm() },
contracts: function () {
if (compiler.lastCompilationResult && compiler.lastCompilationResult.data) {
return compiler.lastCompilationResult.data.contracts
}
return null
},
context: function () {
return executionContext.getProvider()
}
},
event: {
executionContext: executionContext.event,
udapp: udapp.event
}
})
txlistener.startListening()
txlistener.event.register('newTransaction', (tx) => {
var resolvedTransaction = txlistener.resolvedTransaction(tx.hash)
var address = null
if (resolvedTransaction) {
address = resolvedTransaction.contractAddress ? resolvedTransaction.contractAddress : tx.to
}
console.log({
tx: tx,
resolvedContract: txlistener.resolvedContract(address),
resolvedTransaction: resolvedTransaction
})
})
*/
// ----------------- autoCompile -----------------
var autoCompile = document.querySelector('#autoCompile').checked
if (config.exists('autoCompile')) {
......
'use strict'
var async = require('async')
var ethJSABI = require('ethereumjs-abi')
var ethJSUtil = require('ethereumjs-util')
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._api = opt.api
this._web3VMProvider = new Web3VMProvider() // TODO this should maybe be put in app.js
this._web3VMProvider.setVM(opt.api.vm())
this._resolvedTransactions = {}
this._resolvedContracts = {}
this.init()
opt.event.executionContext.register('contextChanged', (context) => {
if (this.loopId) {
this.startListening(context)
}
})
opt.event.udapp.register('transactionExecuted', (to, data, lookupOnly, txResult) => {
if (this.loopId && this._api.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._api.context() === 'vm') {
this.loopId = 'vm-listener'
} else {
this.loopId = setInterval(() => {
this._api.web3().eth.getBlockNumber((error, blockNumber) => {
if (error) return console.log(error)
if (!this.lastBlock || blockNumber > this.lastBlock) {
this.lastBlock = blockNumber
this._api.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._api.isVM() ? this._web3VMProvider : this._api.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, () => {
this.event.trigger('newTransaction', [tx])
cb()
})
}, () => {
callback()
})
}
_resolveTx (tx, cb) {
var contracts = this._api.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
}
if (contractName) {
this._resolveFunction(contractName, contracts, tx, false)
}
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) {
var abi = JSON.parse(compiledContracts[contractName].interface)
var inputData = tx.input.replace('0x', '')
if (!isCtor) {
for (var fn in compiledContracts[contractName].functionHashes) {
if (compiledContracts[contractName].functionHashes[fn] === inputData.substring(0, 8)) {
this._resolvedTransactions[tx.hash] = {
to: tx.to,
fn: fn,
params: this._decodeInputParams(inputData.substring(8), getFunction(abi, fn))
}
return
}
}
// fallback function
this._resolvedTransactions[tx.hash] = {
to: tx.to,
fn: '(fallback)',
params: null
}
} else {
var bytecode = compiledContracts[contractName].bytecode
var params = null
if (bytecode && bytecode.length) {
params = this._decodeInputParams(inputData.substring(bytecode.length), getConstructorInterface(abi))
}
this._resolvedTransactions[tx.hash] = {
to: null,
fn: '(constructor)',
params: params
}
}
}
_tryResolveContract (codeToResolve, compiledContracts, type) {
for (var k in compiledContracts) {
if (codeUtil.compareByteCode(codeToResolve, '0x' + compiledContracts[k][type])) {
return k
}
}
return null
}
_decodeInputParams (data, abi) {
data = ethJSUtil.toBuffer('0x' + data)
var inputTypes = []
for (var i = 0; i < abi.inputs.length; i++) {
inputTypes.push(abi.inputs[i].type)
}
return ethJSABI.rawDecode(inputTypes, data)
}
}
// those function will be duplicate after the merged of the compile and run tabs split
function getConstructorInterface (abi) {
var funABI = { 'name': '', 'inputs': [], 'type': 'constructor', 'outputs': [] }
for (var i = 0; i < abi.length; i++) {
if (abi[i].type === 'constructor') {
funABI.inputs = abi[i].inputs || []
break
}
}
return funABI
}
function getFunction (abi, fnName) {
fnName = fnName.split('(')[0]
for (var i = 0; i < abi.length; i++) {
if (abi[i].name === fnName) {
return abi[i]
}
}
return null
}
module.exports = TxListener
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment