fix vmContext & txRunnerVM

pull/5370/head
yann300 4 years ago
parent dd6cc654ad
commit 43f164eadd
  1. 112
      apps/remix-ide/src/blockchain/execution-context.js
  2. 96
      libs/remix-lib/src/execution/txRunnerVM.ts
  3. 2
      libs/remix-simulator/src/methods/transactions.ts
  4. 126
      libs/remix-simulator/src/vm-context.ts

@ -2,11 +2,6 @@
'use strict' 'use strict'
import Web3 from 'web3' import Web3 from 'web3'
import EventManager from '../lib/events' import EventManager from '../lib/events'
import { rlp, keccak, bufferToHex } from 'ethereumjs-util'
import { Web3VmProvider } from '../web3Provider/web3VmProvider'
import VM from '@ethereumjs/vm'
import Common from '@ethereumjs/common'
import StateManager from '@ethereumjs/vm/dist/state/stateManager'
let web3 let web3
@ -17,82 +12,6 @@ if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
} }
/*
extend vm state manager and instanciate VM
*/
class StateManagerCommonStorageDump extends StateManager {
constructor () {
super()
/*
* dictionary containing keccak(b) as key and b as value. used to get the initial value from its hash.
* type: { [key: string]: string }
*/
this.keyHashes = {}
}
putContractStorage (address, key, value) {
this.keyHashes[keccak(key).toString('hex')] = bufferToHex(key)
return super.putContractStorage(address, key, value)
}
async dumpStorage (address) {
let trie
try {
trie = await this._getStorageTrie(address)
} catch (e) {
console.log(e)
throw e
}
return new Promise((resolve, reject) => {
try {
const storage = {}
const stream = trie.createReadStream()
stream.on('data', (val) => {
const value = rlp.decode(val.value)
storage['0x' + val.key.toString('hex')] = {
key: this.keyHashes[val.key.toString('hex')],
value: '0x' + value.toString('hex')
}
})
stream.on('end', function () {
resolve(storage)
})
} catch (e) {
reject(e)
}
})
}
async getStateRoot (force = false) {
await this._cache.flush()
const stateRoot = this._trie.root
return stateRoot
}
async setStateRoot (stateRoot) {
await this._cache.flush()
if (stateRoot === this._trie.EMPTY_TRIE_ROOT) {
this._trie.root = stateRoot
this._cache.clear()
this._storageTries = {}
return
}
const hasRoot = await this._trie.checkRoot(stateRoot)
if (!hasRoot) {
throw new Error('State trie does not contain state root')
}
this._trie.root = stateRoot
this._cache.clear()
this._storageTries = {}
}
}
/* /*
trigger contextChanged, web3EndpointChanged trigger contextChanged, web3EndpointChanged
*/ */
@ -103,15 +22,6 @@ export class ExecutionContext {
this.blockGasLimitDefault = 4300000 this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = 'berlin' this.currentFork = 'berlin'
this.vms = {
/*
byzantium: createVm('byzantium'),
constantinople: createVm('constantinople'),
petersburg: createVm('petersburg'),
istanbul: createVm('istanbul'),
*/
berlin: this.createVm('berlin')
}
this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3' this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {} this.customNetWorks = {}
this.blocks = {} this.blocks = {}
@ -129,20 +39,6 @@ export class ExecutionContext {
} }
} }
createVm (hardfork) {
const stateManager = new StateManagerCommonStorageDump()
const common = new Common({ chain: 'mainnet', hardfork })
const vm = new VM({
common,
activatePrecompiles: true,
stateManager: stateManager
})
const web3vm = new Web3VmProvider()
web3vm.setVM(vm)
return { vm, web3vm, stateManager, common }
}
askPermission () { askPermission () {
// metamask // metamask
if (ethereum && typeof ethereum.enable === 'function') ethereum.enable() if (ethereum && typeof ethereum.enable === 'function') ethereum.enable()
@ -217,14 +113,6 @@ export class ExecutionContext {
return new Web3() return new Web3()
} }
vm () {
return this.vms[this.currentFork].vm
}
vmObject () {
return this.vms[this.currentFork]
}
setContext (context, endPointUrl, confirmCb, infoCb) { setContext (context, endPointUrl, confirmCb, infoCb) {
this.executionContext = context this.executionContext = context
this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null) this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null)

@ -1,13 +1,12 @@
'use strict' 'use strict'
import { Transaction } from 'ethereumjs-tx' import { Transaction } from '@ethereumjs/tx'
import Block from 'ethereumjs-block' import { Block } from '@ethereumjs/block'
import { BN, bufferToHex } from 'ethereumjs-util' import { BN, bufferToHex, Address } from 'ethereumjs-util'
import { EventManager } from '../eventManager' import { EventManager } from '../eventManager'
import { LogsManager } from './logsManager' import { LogsManager } from './logsManager'
export class TxRunnerVM { export class TxRunnerVM {
event event
_api
blockNumber blockNumber
runAsync runAsync
pendingTxs pendingTxs
@ -16,14 +15,15 @@ export class TxRunnerVM {
blocks blocks
txs txs
logsManager logsManager
getVM: () => any commonContext
getVMObject: () => any
constructor (vmaccounts, api, getVM) { constructor (vmaccounts, api, getVMObject) {
this.event = new EventManager() this.event = new EventManager()
this.logsManager = new LogsManager() this.logsManager = new LogsManager()
// has a default for now for backwards compatability // has a default for now for backwards compatability
this.getVM = getVM this.getVMObject = getVMObject
this._api = api this.commonContext = this.getVMObject().common
this.blockNumber = 0 this.blockNumber = 0
this.runAsync = true this.runAsync = true
this.blockNumber = 0 // 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.
@ -53,54 +53,56 @@ export class TxRunnerVM {
if (!account) { if (!account) {
return callback('Invalid account selected') return callback('Invalid account selected')
} }
if (Number.isInteger(gasLimit)) {
gasLimit = '0x' + gasLimit.toString(16)
}
this.getVMObject().stateManager.getAccount(Address.fromString(from)).then((res) => {
// See https://github.com/ethereumjs/ethereumjs-tx/blob/master/docs/classes/transaction.md#constructor
// for initialization fields and their types
value = value ? parseInt(value) : 0
const tx = Transaction.fromTxData({
nonce: new BN(res.nonce),
gasPrice: '0x1',
gasLimit: gasLimit,
to: to,
value: value,
data: Buffer.from(data.slice(2), 'hex')
}, { common: this.commonContext }).sign(account.privateKey)
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
var block = Block.fromBlockData({
header: {
timestamp: timestamp || (new Date().getTime() / 1000 | 0),
number: self.blockNumber,
coinbase: coinbases[self.blockNumber % coinbases.length],
difficulty: difficulties[self.blockNumber % difficulties.length],
gasLimit: new BN(gasLimit.replace('0x', ''), 16).imuln(2)
},
transactions: [tx]
}, { common: this.commonContext })
this.getVM().stateManager.getAccount(Buffer.from(from.replace('0x', ''), 'hex'), (err, res) => { if (!useCall) {
if (err) { ++self.blockNumber
callback('Account not found') this.runBlockInVm(tx, block, callback)
} else { } else {
// See https://github.com/ethereumjs/ethereumjs-tx/blob/master/docs/classes/transaction.md#constructor this.getVMObject().stateManager.checkpoint().then(() => {
// for initialization fields and their types this.runBlockInVm(tx, block, (err, result) => {
value = value ? parseInt(value) : 0 this.getVMObject().stateManager.revert().then(() => {
const tx = new Transaction({ callback(err, result)
nonce: new BN(res.nonce),
gasPrice: '0x1',
gasLimit: gasLimit,
to: to,
value: value,
data: Buffer.from(data.slice(2), 'hex')
})
tx.sign(account.privateKey)
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
const block = new Block({
header: {
timestamp: timestamp || (new Date().getTime() / 1000 | 0),
number: self.blockNumber,
coinbase: coinbases[self.blockNumber % coinbases.length],
difficulty: difficulties[self.blockNumber % difficulties.length],
gasLimit: new BN(gasLimit, 10).imuln(2)
},
transactions: [tx],
uncleHeaders: []
})
if (!useCall) {
++self.blockNumber
this.runBlockInVm(tx, block, callback)
} else {
this.getVM().stateManager.checkpoint(() => {
this.runBlockInVm(tx, block, (err, result) => {
this.getVM().stateManager.revert(() => {
callback(err, result)
})
}) })
}) })
} })
} }
}).catch((e) => {
callback(e)
}) })
} }
runBlockInVm (tx, block, callback) { runBlockInVm (tx, block, callback) {
this.getVM().runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then((results) => { this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then((results) => {
const result = results.results[0] const result = results.results[0]
if (result) { if (result) {
const status = result.execResult.exceptionError ? 0 : 1 const status = result.execResult.exceptionError ? 0 : 1

@ -40,7 +40,7 @@ export class Transactions {
} }
} }
this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vm()) this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vmObject())
this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, { runAsync: false }) this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, { runAsync: false })
this.txRunnerInstance.vmaccounts = accounts this.txRunnerInstance.vmaccounts = accounts
} }

@ -2,61 +2,82 @@
'use strict' 'use strict'
import Web3 from 'web3' import Web3 from 'web3'
import { rlp, keccak, bufferToHex } from 'ethereumjs-util' import { rlp, keccak, bufferToHex } from 'ethereumjs-util'
import { vm, execution } from '@remix-project/remix-lib' import { vm as remixLibVm, execution } from '@remix-project/remix-lib'
const EthJSVM = require('ethereumjs-vm').default import VM from '@ethereumjs/vm'
const StateManager = require('ethereumjs-vm/dist/state/stateManager').default import Common from '@ethereumjs/common'
import StateManager from '@ethereumjs/vm/dist/state/stateManager'
import { StorageDump } from '@ethereumjs/vm/dist/state/interface'
/* /*
extend vm state manager and instanciate VM extend vm state manager and instanciate VM
*/ */
class StateManagerCommonStorageDump extends StateManager { class StateManagerCommonStorageDump extends StateManager {
constructor (arg) {
super(arg) keyHashes: { [key: string]: string }
constructor () {
super()
this.keyHashes = {} this.keyHashes = {}
} }
putContractStorage (address, key, value, cb) { putContractStorage (address, key, value) {
this.keyHashes[keccak(key).toString('hex')] = bufferToHex(key) this.keyHashes[keccak(key).toString('hex')] = bufferToHex(key)
super.putContractStorage(address, key, value, cb) return super.putContractStorage(address, key, value)
} }
dumpStorage (address, cb) { async dumpStorage (address) {
this._getStorageTrie(address, (err, trie) => { let trie
if (err) { try {
return cb(err) trie = await this._getStorageTrie(address)
} catch (e) {
console.log(e)
throw e
}
return new Promise<StorageDump>((resolve, reject) => {
try {
const storage = {}
const stream = trie.createReadStream()
stream.on('data', (val) => {
const value = rlp.decode(val.value)
storage['0x' + val.key.toString('hex')] = {
key: this.keyHashes[val.key.toString('hex')],
value: '0x' + value.toString('hex')
}
})
stream.on('end', function () {
resolve(storage)
})
} catch (e) {
reject(e)
} }
const storage = {}
const stream = trie.createReadStream()
stream.on('data', (val) => {
const value = rlp.decode(val.value)
storage['0x' + val.key.toString('hex')] = {
key: this.keyHashes[val.key.toString('hex')],
value: '0x' + value.toString('hex')
}
})
stream.on('end', function () {
cb(storage)
})
}) })
} }
getStateRoot (cb) { async getStateRoot (force = false) {
const checkpoint = this._checkpointCount await this._cache.flush()
this._checkpointCount = 0
super.getStateRoot((err, stateRoot) => { const stateRoot = this._trie.root
this._checkpointCount = checkpoint return stateRoot
cb(err, stateRoot)
})
} }
setStateRoot (stateRoot, cb) { async setStateRoot (stateRoot) {
const checkpoint = this._checkpointCount await this._cache.flush()
this._checkpointCount = 0
super.setStateRoot(stateRoot, (err) => { if (stateRoot === this._trie.EMPTY_TRIE_ROOT) {
this._checkpointCount = checkpoint this._trie.root = stateRoot
cb(err) this._cache.clear()
}) this._storageTries = {}
return
}
const hasRoot = await this._trie.checkRoot(stateRoot)
if (!hasRoot) {
throw new Error('State trie does not contain state root')
}
this._trie.root = stateRoot
this._cache.clear()
this._storageTries = {}
} }
} }
@ -79,7 +100,7 @@ export class VMContext {
constructor () { constructor () {
this.blockGasLimitDefault = 4300000 this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = 'muirGlacier' this.currentFork = 'berlin'
this.vms = { this.vms = {
/* /*
byzantium: createVm('byzantium'), byzantium: createVm('byzantium'),
@ -87,7 +108,7 @@ export class VMContext {
petersburg: createVm('petersburg'), petersburg: createVm('petersburg'),
istanbul: createVm('istanbul'), istanbul: createVm('istanbul'),
*/ */
muirGlacier: this.createVm('muirGlacier') berlin: this.createVm('berlin')
} }
this.blocks = {} this.blocks = {}
this.latestBlockNumber = 0 this.latestBlockNumber = 0
@ -97,22 +118,21 @@ export class VMContext {
} }
createVm (hardfork) { createVm (hardfork) {
const stateManager = new StateManagerCommonStorageDump({}) const stateManager = new StateManagerCommonStorageDump()
stateManager.checkpoint(() => {}) const common = new Common({ chain: 'mainnet', hardfork })
const ethvm = new EthJSVM({ const vm = new VM({
common,
activatePrecompiles: true, activatePrecompiles: true,
blockchain: stateManager.blockchain, stateManager: stateManager
stateManager: stateManager,
hardfork: hardfork
}) })
ethvm.blockchain.validate = false
this.web3vm = new vm.Web3VMProvider() const web3vm = new remixLibVm.Web3VMProvider()
this.web3vm.setVM(ethvm) web3vm.setVM(vm)
return { vm: ethvm, web3vm: this.web3vm, stateManager } return { vm, web3vm, stateManager, common }
} }
web3 () { web3 () {
return this.web3vm return this.vms[this.currentFork].web3vm
} }
blankWeb3 () { blankWeb3 () {
@ -123,6 +143,10 @@ export class VMContext {
return this.vms[this.currentFork].vm return this.vms[this.currentFork].vm
} }
vmObject () {
return this.vms[this.currentFork]
}
addBlock (block) { addBlock (block) {
let blockNumber = '0x' + block.header.number.toString('hex') let blockNumber = '0x' + block.header.number.toString('hex')
if (blockNumber === '0x') { if (blockNumber === '0x') {
@ -133,7 +157,7 @@ export class VMContext {
this.blocks[blockNumber] = block this.blocks[blockNumber] = block
this.latestBlockNumber = blockNumber this.latestBlockNumber = blockNumber
this.logsManager.checkBlock(blockNumber, block, this.web3vm) this.logsManager.checkBlock(blockNumber, block, this.web3())
} }
trackTx (tx, block) { trackTx (tx, block) {

Loading…
Cancel
Save