Skip to content
This repository has been archived by the owner on Mar 3, 2021. It is now read-only.

Commit

Permalink
Merge pull request #1267 from ethereum/improve_remix_sim3
Browse files Browse the repository at this point in the history
working websocket support; implement eth_subscribe; eth_unsubscribe; eth_getLogs
  • Loading branch information
yann300 authored Sep 6, 2019
2 parents 0dcd095 + b4d42a3 commit a57aa65
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 28 deletions.
9 changes: 9 additions & 0 deletions remix-lib/src/execution/execution-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ var ethUtil = require('ethereumjs-util')
var StateManager = require('ethereumjs-vm/dist/stateManager')
var Web3VMProvider = require('../web3Provider/web3VmProvider')

var LogsManager = require('./logsManager.js')

var rlp = ethUtil.rlp

var injectedProvider
Expand Down Expand Up @@ -105,6 +107,8 @@ function ExecutionContext () {
var self = this
this.event = new EventManager()

this.logsManager = new LogsManager()

var executionContext = null

this.blockGasLimitDefault = 4300000
Expand Down Expand Up @@ -298,10 +302,15 @@ function ExecutionContext () {

this.addBlock = function (block) {
let blockNumber = '0x' + block.header.number.toString('hex')
if (blockNumber === '0x') {
blockNumber = '0x0'
}
blockNumber = web3.toHex(web3.toBigNumber(blockNumber))

self.blocks['0x' + block.hash().toString('hex')] = block
self.blocks[blockNumber] = block

this.logsManager.checkBlock(blockNumber, block, this.web3())
}

this.trackTx = function (tx, block) {
Expand Down
123 changes: 123 additions & 0 deletions remix-lib/src/execution/logsManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const async = require('async')
const crypto = require('crypto')

class LogsManager {

constructor () {
this.notificationCallbacks = []
this.subscriptions = {}
this.oldLogs = []
}

checkBlock (blockNumber, block, web3) {
async.eachOf(block.transactions, (tx, i, next) => {
let txHash = '0x' + tx.hash().toString('hex')

web3.eth.getTransactionReceipt(txHash, (_error, receipt) => {
for (let log of receipt.logs) {
this.oldLogs.push({ type: 'block', blockNumber, block, tx, log, txNumber: i })
let subscriptions = this.getSubscriptionsFor({ type: 'block', blockNumber, block, tx, log })

for (let subscriptionId of subscriptions) {
let result = {
'logIndex': '0x1', // 1
'blockNumber': blockNumber,
'blockHash': ('0x' + block.hash().toString('hex')),
'transactionHash': ('0x' + tx.hash().toString('hex')),
'transactionIndex': '0x' + i.toString(16),
// TODO: if it's a contract deploy, it should be that address instead
'address': log.address,
'data': log.data,
'topics': log.topics
}

if (result.address === '0x') {
delete result.address
}

let response = { 'jsonrpc': '2.0', 'method': 'eth_subscription', params: { 'result': result, 'subscription': subscriptionId } }
this.transmit(response)
}
}
})
}, (_err) => {
})
}

eventMatchesFilter (changeEvent, queryType, queryFilter) {
if (queryFilter.topics.filter((logTopic) => changeEvent.log.topics.indexOf(logTopic) >= 0).length === 0) return false

if (queryType === 'logs') {
if ((queryFilter.address === ('0x' + changeEvent.tx.to.toString('hex'))) && (queryFilter.address === ('0x' + changeEvent.tx.from.toString('hex')))) {
if (!queryFilter.toBlock) {
return true
} else if (parseInt(queryFilter.toBlock) > parseInt(changeEvent.blockNumber)) {
return true
}
}
}

return false
}

getSubscriptionsFor (changeEvent) {
let matchedSubscriptions = []
for (let subscriptionId of Object.keys(this.subscriptions)) {
const subscriptionParams = this.subscriptions[subscriptionId]
const [queryType, queryFilter] = subscriptionParams

if (this.eventMatchesFilter(changeEvent, queryType, queryFilter)) {
matchedSubscriptions.push(subscriptionId)
}
}
return matchedSubscriptions
}

transmit (result) {
this.notificationCallbacks.forEach((callback) => {
if (result.params.result.raw) {
result.params.result.data = result.params.result.raw.data
result.params.result.topics = result.params.result.raw.topics
}
callback(result)
})
}

addListener (_type, cb) {
this.notificationCallbacks.push(cb)
}

subscribe (params) {
let subscriptionId = '0x' + crypto.randomBytes(16).toString('hex')
this.subscriptions[subscriptionId] = params
return subscriptionId
}

unsubscribe (subscriptionId) {
delete this.subscriptions[subscriptionId]
}

getLogsFor (params) {
let results = []
for (let log of this.oldLogs) {
if (this.eventMatchesFilter(log, 'logs', params)) {
results.push({
'logIndex': '0x1', // 1
'blockNumber': log.blockNumber,
'blockHash': ('0x' + log.block.hash().toString('hex')),
'transactionHash': ('0x' + log.tx.hash().toString('hex')),
'transactionIndex': '0x' + log.txNumber.toString(16),
// TODO: if it's a contract deploy, it should be that address instead
'address': log.log.address,
'data': log.log.data,
'topics': log.log.topics
})
}
}

return results
}

}

module.exports = LogsManager
2 changes: 1 addition & 1 deletion remix-lib/src/execution/txRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class TxRunner {
this.runAsync = true
if (executionContext.isVM()) {
// this.blockNumber = 1150000 // The VM is running in Homestead mode, which started at this block.
this.blockNumber = 2 // The VM is running in Homestead mode, which started at this block.
this.blockNumber = 0 // 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 = {}
Expand Down
6 changes: 3 additions & 3 deletions remix-simulator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Implemented:
* [_] eth_uninstallFilter
* [_] eth_getFilterChanges
* [_] eth_getFilterLogs
* [_] eth_getLogs
* [X] eth_getLogs
* [_] eth_getWork
* [_] eth_submitWork
* [_] eth_submitHashrate
Expand All @@ -68,8 +68,8 @@ Implemented:
* [_] bzz_hive (stub)
* [_] bzz_info (stub)
* [_] debug_traceTransaction
* [_] eth_subscribe
* [_] eth_unsubscribe
* [X] eth_subscribe
* [X] eth_unsubscribe
* [_] miner_start
* [_] miner_stop
* [_] personal_listAccounts
Expand Down
4 changes: 3 additions & 1 deletion remix-simulator/bin/ethsim
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ program
.option('-p, --port [port]', 'specify port')
.option('-b, --ip [host]', 'specify host')
.option('-c, --coinbase [coinbase]', 'specify host')
.option('--rpc', 'run rpc server only')
.parse(process.argv)

const Server = require('../src/server')
const server = new Server({
coinbase: program.coinbase || "0x0000000000000000000000000000000000000000"
coinbase: program.coinbase || "0x0000000000000000000000000000000000000000",
rpc: program.rpc
})
server.start(program.host || '127.0.0.1', program.port || 8545)

3 changes: 2 additions & 1 deletion remix-simulator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"main": "./index.js",
"dependencies": {
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
"color-support": "^1.1.3",
"commander": "^2.19.0",
Expand All @@ -28,7 +29,7 @@
"remix-lib": "0.4.12",
"standard": "^10.0.3",
"time-stamp": "^2.0.0",
"web3": "1.0.0-beta.27"
"web3": "^1.0.0-beta.37"
},
"devDependencies": {
"@babel/core": "^7.4.5",
Expand Down
2 changes: 1 addition & 1 deletion remix-simulator/src/genesis.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function generateBlock () {
var block = new EthJSBlock({
header: {
timestamp: (new Date().getTime() / 1000 | 0),
number: 1,
number: 0,
coinbase: '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a',
difficulty: (new BN('69762765929000', 10)),
gasLimit: new BN('8000000').imuln(1)
Expand Down
32 changes: 32 additions & 0 deletions remix-simulator/src/methods/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var RemixLib = require('remix-lib')
var executionContext = RemixLib.execution.executionContext

var Filters = function (_options) {
// const options = _options || {}
}

Filters.prototype.methods = function () {
return {
eth_getLogs: this.eth_getLogs.bind(this),
eth_subscribe: this.eth_subscribe.bind(this),
eth_unsubscribe: this.eth_unsubscribe.bind(this)
}
}

// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
Filters.prototype.eth_getLogs = function (payload, cb) {
let results = executionContext.logsManager.getLogsFor(payload.params[0])
cb(null, results)
}

Filters.prototype.eth_subscribe = function (payload, cb) {
let subscriptionId = executionContext.logsManager.subscribe(payload.params)
cb(null, subscriptionId)
}

Filters.prototype.eth_unsubscribe = function (payload, cb) {
executionContext.logsManager.unsubscribe(payload.params[0])
cb(null, true)
}

module.exports = Filters
14 changes: 10 additions & 4 deletions remix-simulator/src/methods/txProcess.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ function createContract (payload, from, data, value, gasLimit, txRunner, callbac
TxExecution.createContract(from, data, value, gasLimit, txRunner, callbacks, finalCallback)
}

let txRunnerInstance

function processTx (accounts, payload, isCall, callback) {
let api = {
logMessage: (msg) => {
Expand All @@ -65,7 +67,11 @@ function processTx (accounts, payload, isCall, callback) {

executionContext.init(api.config)

let txRunner = new TxRunner(accounts, api)
// let txRunner = new TxRunner(accounts, api)
if (!txRunnerInstance) {
txRunnerInstance = new TxRunner(accounts, api)
}
txRunnerInstance.vmaccounts = accounts
let { from, to, data, value, gas } = payload.params[0]
gas = gas || 3000000

Expand All @@ -85,11 +91,11 @@ function processTx (accounts, payload, isCall, callback) {
}

if (isCall) {
runCall(payload, from, to, data, value, gas, txRunner, callbacks, callback)
runCall(payload, from, to, data, value, gas, txRunnerInstance, callbacks, callback)
} else if (to) {
runTx(payload, from, to, data, value, gas, txRunner, callbacks, callback)
runTx(payload, from, to, data, value, gas, txRunnerInstance, callbacks, callback)
} else {
createContract(payload, from, data, value, gas, txRunner, callbacks, callback)
createContract(payload, from, data, value, gas, txRunnerInstance, callbacks, callback)
}
}

Expand Down
9 changes: 9 additions & 0 deletions remix-simulator/src/provider.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
var RemixLib = require('remix-lib')
var executionContext = RemixLib.execution.executionContext

const log = require('./utils/logs.js')
const merge = require('merge')

const Accounts = require('./methods/accounts.js')
const Blocks = require('./methods/blocks.js')
const Filters = require('./methods/filters.js')
const Misc = require('./methods/misc.js')
const Net = require('./methods/net.js')
const Transactions = require('./methods/transactions.js')
Expand All @@ -18,6 +22,7 @@ var Provider = function (options) {
this.methods = merge(this.methods, this.Accounts.methods())
this.methods = merge(this.methods, (new Blocks(options)).methods())
this.methods = merge(this.methods, (new Misc()).methods())
this.methods = merge(this.methods, (new Filters()).methods())
this.methods = merge(this.methods, (new Net()).methods())
this.methods = merge(this.methods, (this.Transactions.methods()))
this.methods = merge(this.methods, (new Whisper()).methods())
Expand Down Expand Up @@ -54,4 +59,8 @@ Provider.prototype.isConnected = function () {
return true
}

Provider.prototype.on = function (type, cb) {
executionContext.logsManager.addListener(type, cb)
}

module.exports = Provider
37 changes: 22 additions & 15 deletions remix-simulator/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Server {
}).catch((error) => {
log(error)
})
this.rpcOnly = options.rpc
}

start (host, port) {
Expand All @@ -27,25 +28,31 @@ class Server {
res.send('Welcome to remix-simulator')
})

app.use((req, res) => {
this.provider.sendAsync(req.body, (err, jsonResponse) => {
if (err) {
return res.send(JSON.stringify({error: err}))
}
res.send(jsonResponse)
})
})

app.ws('/', (ws, req) => {
ws.on('message', function (msg) {
this.provider.sendAsync(JSON.parse(msg), (err, jsonResponse) => {
if (this.rpcOnly) {
app.use((req, res) => {
this.provider.sendAsync(req.body, (err, jsonResponse) => {
if (err) {
return ws.send(JSON.stringify({error: err}))
return res.send(JSON.stringify({ error: err }))
}
ws.send(JSON.stringify(jsonResponse))
res.send(jsonResponse)
})
})
})
} else {
app.ws('/', (ws, req) => {
ws.on('message', (msg) => {
this.provider.sendAsync(JSON.parse(msg), (err, jsonResponse) => {
if (err) {
return ws.send(JSON.stringify({ error: err }))
}
ws.send(JSON.stringify(jsonResponse))
})
})

this.provider.on('data', (result) => {
ws.send(JSON.stringify(result))
})
})
}

app.listen(port, host, () => log('Remix Simulator listening on port ' + host + ':' + port))
}
Expand Down
Loading

0 comments on commit a57aa65

Please sign in to comment.