simulator in a web worker

pull/3262/head^2
yann300 2 years ago committed by Aniket
parent 4405ac39ef
commit ae2bb4a41d
  1. 1
      apps/debugger/webpack.config.js
  2. 2
      apps/remix-ide/.babelrc
  3. 27
      apps/remix-ide/src/blockchain/providers/vm.js
  4. 37
      apps/remix-ide/src/blockchain/providers/worker-vm.ts
  5. 1
      babel.config.js
  6. 8
      libs/remix-debug/src/code/codeUtils.ts
  7. 53
      libs/remix-lib/src/execution/txRunnerVM.ts
  8. 8
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  9. 5
      libs/remix-simulator/src/genesis.ts
  10. 10
      libs/remix-simulator/src/methods/blocks.ts
  11. 29
      libs/remix-simulator/src/vm-context.ts
  12. 17
      package.json
  13. 821
      yarn.lock

@ -2,6 +2,7 @@ const nxWebpack = require('@nrwl/react/plugins/webpack')
module.exports = config => {
const nxWebpackConfig = nxWebpack(config)
const webpackConfig = {
...nxWebpackConfig,
resolve : {

@ -2,7 +2,7 @@
"presets": ["@babel/preset-env", ["@babel/preset-react",
{"runtime": "automatic"}
]],
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime"],
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", "@babel/plugin-proposal-nullish-coalescing-operator"],
"ignore": [
"**/node_modules/**"
]

@ -1,10 +1,10 @@
const Web3 = require('web3')
const { BN, privateToAddress, hashPersonalMessage } = require('ethereumjs-util')
const { Provider, extend } = require('@remix-project/remix-simulator')
const { extend } = require('@remix-project/remix-simulator')
class VMProvider {
constructor (executionContext) {
this.executionContext = executionContext
this.worker = null
}
getAccounts (cb) {
@ -17,10 +17,27 @@ class VMProvider {
}
resetEnvironment () {
if (this.worker) this.worker.terminate()
this.accounts = {}
this.RemixSimulatorProvider = new Provider({ fork: this.executionContext.getCurrentFork() })
this.RemixSimulatorProvider.init()
this.web3 = new Web3(this.RemixSimulatorProvider)
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork() })
let incr = 0
const stamps = {}
this.worker.addEventListener('message', (msg) => {
if (stamps[msg.data.stamp]) {
stamps[msg.data.stamp](msg.data.error, msg.data.result)
}
})
const provider = {
sendAsync: (query, callback) => {
const stamp = Date.now() + incr
incr++
stamps[stamp] = callback
this.worker.postMessage({ cmd: 'sendAsync', query, stamp })
}
}
this.web3 = new Web3(provider)
extend(this.web3)
this.accounts = {}
this.executionContext.setWeb3('vm', this.web3)

@ -0,0 +1,37 @@
import { Provider } from '@remix-project/remix-simulator'
let provider: Provider = null
self.onmessage = (e: MessageEvent) => {
const data = e.data
switch (data.cmd) {
case 'init':
{
provider = new Provider({ fork: data.fork })
if (provider) provider.init()
break
}
case 'sendAsync':
{
if (provider) {
provider.sendAsync(data.query, (error, result) => {
result = JSON.parse(JSON.stringify(result))
self.postMessage({
cmd: 'sendAsyncResult',
error,
result,
stamp: data.stamp
})
})
} else {
self.postMessage({
cmd: 'sendAsyncResult',
error: 'Provider not instanciated',
result: null,
stamp: data.stamp
})
}
break
}
}
}

@ -4,6 +4,7 @@ module.exports = {
"babel-plugin-replace-ts-export-assignment",
"@babel/plugin-transform-modules-commonjs",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-private-property-in-object", { "loose": false }],
["@babel/plugin-proposal-private-methods", { "loose": false }]]
}

@ -1,11 +1,11 @@
'use strict'
import Common from '@ethereumjs/common'
import { getOpcodesForHF } from '@ethereumjs/vm/dist/evm/opcodes'
import { Common } from '@ethereumjs/common'
import { getOpcodesForHF, OpcodeList } from '@ethereumjs/evm/dist/opcodes/codes'
import getOpcodes from './opcodes'
export function nameOpCodes (raw, hardfork) {
const common = new Common({ chain: 'mainnet', hardfork })
const opcodes = getOpcodesForHF(common)
const opcodes = getOpcodesForHF(common).opcodes
let pushData = ''
const codeMap = {}
@ -47,7 +47,7 @@ type Opcode = {
*/
export function parseCode (raw) {
const common = new Common({ chain: 'mainnet', hardfork: 'london' })
const opcodes = getOpcodesForHF(common)
const opcodes = getOpcodesForHF(common).opcodes
const code = []
for (let i = 0; i < raw.length; i++) {

@ -1,10 +1,21 @@
'use strict'
import { RunBlockResult, RunTxResult } from '@ethereumjs/vm'
import { Transaction, FeeMarketEIP1559Transaction } from '@ethereumjs/tx'
import { Block } from '@ethereumjs/block'
import { BN, bufferToHex, Address } from 'ethereumjs-util'
import type { Account } from '@ethereumjs/util'
import { EventManager } from '../eventManager'
import { LogsManager } from './logsManager'
export type VMxecutionResult = {
result: RunTxResult,
transactionHash: string
block: Block,
tx: Transaction
}
export type VMExecutionCallBack = (error: string | Error, result?: VMxecutionResult) => void
export class TxRunnerVM {
event
blockNumber
@ -48,13 +59,13 @@ export class TxRunnerVM {
}
try {
this.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, args.timestamp, callback)
this.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback)
} catch (e) {
callback(e, null)
}
}
runInVm (from, to, data, value, gasLimit, useCall, timestamp, callback) {
runInVm (from: string, to: string, data: string, value: string, gasLimit: number, useCall: boolean, callback: VMExecutionCallBack) {
const self = this
let account
if (!from && useCall && Object.keys(self.vmaccounts).length) {
@ -65,30 +76,13 @@ export class TxRunnerVM {
if (!account) {
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
if (!value) value = 0
if (typeof value === 'string') {
if (value.startsWith('0x')) value = new BN(value.replace('0x', ''), 'hex')
else {
try {
value = new BN(value, 10)
} catch (e) {
return callback('Unable to parse the value ' + e.message)
}
}
}
this.getVMObject().stateManager.getAccount(Address.fromString(from)).then((res: Account) => {
const EIP1559 = this.commonContext.hardfork() !== 'berlin' // berlin is the only pre eip1559 fork that we handle.
let tx
if (!EIP1559) {
tx = Transaction.fromTxData({
nonce: useCall ? this.nextNonceForCall : new BN(res.nonce),
nonce: useCall ? this.nextNonceForCall : res.nonce,
gasPrice: '0x1',
gasLimit: gasLimit,
to: to,
@ -97,7 +91,7 @@ export class TxRunnerVM {
}, { common: this.commonContext }).sign(account.privateKey)
} else {
tx = FeeMarketEIP1559Transaction.fromTxData({
nonce: useCall ? this.nextNonceForCall : new BN(res.nonce),
nonce: useCall ? this.nextNonceForCall : res.nonce,
maxPriorityFeePerGas: '0x01',
maxFeePerGas: '0x1',
gasLimit: gasLimit,
@ -109,15 +103,15 @@ export class TxRunnerVM {
if (useCall) this.nextNonceForCall++
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
const difficulties = [69762765929000, 70762765929000, 71762765929000]
const block = Block.fromBlockData({
header: {
timestamp: timestamp || (new Date().getTime() / 1000 | 0),
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),
gasLimit,
baseFeePerGas: EIP1559 ? '0x1' : undefined
},
transactions: [tx]
@ -141,14 +135,15 @@ export class TxRunnerVM {
}
runBlockInVm (tx, block, callback) {
this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false, skipNonce: true }).then((results) => {
this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false, skipNonce: true }).then((results: RunBlockResult) => {
const result = results.results[0]
if (result) {
/*if (result) {
const status = result.execResult.exceptionError ? 0 : 1
result.receipt.status
result.status = `0x${status}`
}
}*/
callback(null, {
result: result,
result,
transactionHash: bufferToHex(Buffer.from(tx.hash())),
block,
tx

@ -47,6 +47,10 @@ export class TxRunnerWeb3 {
}
_sendTransaction (sendTx, tx, pass, callback) {
var currentDateTime = new Date();
const start = currentDateTime.getTime() / 1000
console.log('start', start)
const cb = (err, resp) => {
if (err) {
return callback(err, resp)
@ -57,6 +61,10 @@ export class TxRunnerWeb3 {
return new Promise(async (resolve, reject) => {
const receipt = await tryTillReceiptAvailable(resp, this.getWeb3())
tx = await tryTillTxAvailable(resp, this.getWeb3())
currentDateTime = new Date();
const end = currentDateTime.getTime() / 1000
console.log('end', end)
console.log('stopwatch', end - start)
resolve({
receipt,
tx,

@ -1,5 +1,4 @@
import { Block } from '@ethereumjs/block'
import { BN } from 'ethereumjs-util'
export function generateBlock (vmContext) {
return new Promise((resolve, reject) => {
@ -8,8 +7,8 @@ export function generateBlock (vmContext) {
timestamp: (new Date().getTime() / 1000 | 0),
number: 0,
coinbase: '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a',
difficulty: new BN('69762765929000', 10),
gasLimit: new BN('8000000').imuln(1)
difficulty: 69762765929000,
gasLimit: 8000000
}
}, { common: vmContext.vmObject().common })

@ -1,5 +1,7 @@
import Web3 from 'web3'
import { VMContext } from '../vm-context'
import { bigIntToHex } from '@ethereumjs/util'
export class Blocks {
vmContext: VMContext
coinbase: string
@ -47,14 +49,14 @@ export class Blocks {
if (receipt) {
return {
blockHash: '0x' + block.hash().toString('hex'),
blockNumber: '0x' + block.header.number.toString('hex'),
blockNumber: bigIntToHex(block.header.number),
from: receipt.from,
gas: Web3.utils.toHex(receipt.gas),
chainId: '0xd05',
gasPrice: '0x4a817c800', // 20000000000
hash: receipt.transactionHash,
input: receipt.input,
nonce: '0x' + tx.nonce.toString('hex'),
nonce: bigIntToHex(tx.nonce),
transactionIndex: this.TX_INDEX,
value: receipt.value === '0x' ? '0x0' : receipt.value,
to: receipt.to ? receipt.to : null
@ -101,14 +103,14 @@ export class Blocks {
if (receipt) {
return {
blockHash: '0x' + block.hash().toString('hex'),
blockNumber: '0x' + block.header.number.toString('hex'),
blockNumber: bigIntToHex(block.header.number),
from: receipt.from,
gas: Web3.utils.toHex(receipt.gas),
chainId: '0xd05',
gasPrice: '0x4a817c800', // 20000000000
hash: receipt.transactionHash,
input: receipt.input,
nonce: '0x' + tx.nonce.toString('hex'),
nonce: bigIntToHex(tx.nonce),
transactionIndex: this.TX_INDEX,
value: receipt.value === '0x' ? '0x0' : receipt.value,
to: receipt.to ? receipt.to : null

@ -5,18 +5,19 @@ import { rlp, keccak, bufferToHex } from 'ethereumjs-util'
import { execution } from '@remix-project/remix-lib'
const { LogsManager } = execution
import { VmProxy } from './VmProxy'
import VM from '@ethereumjs/vm'
import Common from '@ethereumjs/common'
import StateManager from '@ethereumjs/vm/dist/state/stateManager'
import { StorageDump } from '@ethereumjs/vm/dist/state/interface'
import { VM } from '@ethereumjs/vm'
import { Common } from '@ethereumjs/common'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { StorageDump } from '@ethereumjs/statemanager/dist/interface'
import { Block } from '@ethereumjs/block'
import { Transaction } from '@ethereumjs/tx'
import { bigIntToHex } from '@ethereumjs/util'
/*
extend vm state manager and instanciate VM
*/
class StateManagerCommonStorageDump extends StateManager {
class StateManagerCommonStorageDump extends DefaultStateManager {
keyHashes: { [key: string]: string }
constructor () {
super()
@ -52,17 +53,19 @@ class StateManagerCommonStorageDump extends StateManager {
})
}
/*
async getStateRoot (force = false) {
await this._cache.flush()
const stateRoot = this._trie.root
return stateRoot
}
*/
async setStateRoot (stateRoot) {
if (this._checkpointCount !== 0) {
/*if (this._checkpointCount !== 0) {
throw new Error('Cannot set state root with uncommitted checkpoints')
}
}*/
await this._cache.flush()
@ -106,7 +109,7 @@ export class VMContext {
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = fork || 'london'
this.currentVm = this.createVm(this.currentFork)
this.createVm(this.currentFork).then((vm) => this.currentVm = vm )
this.blocks = {}
this.latestBlockNumber = "0x0"
this.blockByTxHash = {}
@ -115,14 +118,14 @@ export class VMContext {
this.logsManager = new LogsManager()
}
createVm (hardfork) {
async createVm (hardfork) {
const stateManager = new StateManagerCommonStorageDump()
const common = new Common({ chain: 'mainnet', hardfork })
const vm = new VM({
const vm = await VM.create({
common,
activatePrecompiles: true,
stateManager,
allowUnlimitedContractSize: true
stateManager
// allowUnlimitedContractSize: true
})
// VmProxy and VMContext are very intricated.
@ -153,7 +156,7 @@ export class VMContext {
}
addBlock (block: Block) {
let blockNumber = '0x' + block.header.number.toString('hex')
let blockNumber = bigIntToHex(block.header.number)
if (blockNumber === '0x') {
blockNumber = '0x0'
}

@ -114,12 +114,16 @@
"build-contracts": "find ./node_modules/@openzeppelin/contracts | grep -i '.sol' > libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt && find ./node_modules/@uniswap/v3-core/contracts | grep -i '.sol' >> libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@erebos/bzz-node": "^0.13.0",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@ethereumjs/block": "^4.0.0",
"@ethereumjs/common": "^3.0.0",
"@ethereumjs/evm": "^1.0.0",
"@ethereumjs/statemanager": "^1.0.0",
"@ethereumjs/tx": "^4.0.0",
"@ethereumjs/util": "^8.0.0",
"@ethereumjs/vm": "^6.0.0",
"@ethersphere/bee-js": "^3.2.0",
"@isomorphic-git/lightning-fs": "^4.4.1",
"@monaco-editor/react": "4.4.5",
@ -146,7 +150,6 @@
"deep-equal": "^1.0.1",
"document-register-element": "1.13.1",
"eslint-config-prettier": "^8.5.0",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2",
"ethjs-util": "^0.1.6",
"express": "^4.18.2",
@ -331,7 +334,7 @@
"style-loader": "^3.3.1",
"tap-spec": "^5.0.0",
"tape": "^4.13.3",
"terser-webpack-plugin": "^4.2.3",
"terser-webpack-plugin": "^5.3.6",
"timers-browserify": "^2.0.12",
"ts-jest": "^29.0.3",
"ts-node": "10.9.1",

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save