Merge pull request #1267 from ethereum/improve_remix_sim3

working websocket support; implement eth_subscribe; eth_unsubscribe; eth_getLogs
pull/7/head
yann300 6 years ago committed by GitHub
commit a57aa65ab5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      remix-lib/src/execution/execution-context.js
  2. 123
      remix-lib/src/execution/logsManager.js
  3. 2
      remix-lib/src/execution/txRunner.js
  4. 6
      remix-simulator/README.md
  5. 4
      remix-simulator/bin/ethsim
  6. 3
      remix-simulator/package.json
  7. 2
      remix-simulator/src/genesis.js
  8. 32
      remix-simulator/src/methods/filters.js
  9. 14
      remix-simulator/src/methods/txProcess.js
  10. 9
      remix-simulator/src/provider.js
  11. 37
      remix-simulator/src/server.js
  12. 4
      remix-simulator/test/blocks.js

@ -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
@ -105,6 +107,8 @@ function ExecutionContext () {
var self = this
this.event = new EventManager()
this.logsManager = new LogsManager()
var executionContext = null
this.blockGasLimitDefault = 4300000
@ -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) {

@ -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

@ -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 = {}

@ -46,7 +46,7 @@ Implemented:
* [_] eth_uninstallFilter
* [_] eth_getFilterChanges
* [_] eth_getFilterLogs
* [_] eth_getLogs
* [X] eth_getLogs
* [_] eth_getWork
* [_] eth_submitWork
* [_] eth_submitHashrate
@ -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

@ -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)

@ -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",
@ -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",

@ -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)

@ -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

@ -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) => {
@ -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
@ -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)
}
}

@ -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')
@ -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())
@ -54,4 +59,8 @@ Provider.prototype.isConnected = function () {
return true
}
Provider.prototype.on = function (type, cb) {
executionContext.logsManager.addListener(type, cb)
}
module.exports = Provider

@ -14,6 +14,7 @@ class Server {
}).catch((error) => {
log(error)
})
this.rpcOnly = options.rpc
}
start (host, port) {
@ -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))
}

@ -13,7 +13,7 @@ describe('blocks', function () {
})
it('should get block given its number', async function () {
let block = await web3.eth.getBlock(1)
let block = await web3.eth.getBlock(0)
let expectedBlock = {
difficulty: '69762765929000',
@ -24,7 +24,7 @@ describe('blocks', function () {
logsBloom: '0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331',
miner: '0x0000000000000000000000000000000000000001',
nonce: '0x0000000000000000',
number: 1,
number: 0,
parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
size: 163591,

Loading…
Cancel
Save