Seperate load context from reset environment

pull/5370/head
ioedeveloper 10 months ago
parent a87c9f24d2
commit 5e91dbc7e7
  1. 44
      apps/remix-ide/src/blockchain/blockchain.tsx
  2. 86
      apps/remix-ide/src/blockchain/execution-context.js
  3. 4
      apps/remix-ide/src/blockchain/providers/injected.ts
  4. 4
      apps/remix-ide/src/blockchain/providers/node.ts
  5. 136
      apps/remix-ide/src/blockchain/providers/vm.ts
  6. 14
      libs/remix-simulator/src/provider.ts
  7. 4
      libs/remix-simulator/src/vm-context.ts
  8. 1
      libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts
  9. 1
      libs/remix-ui/run-tab/src/lib/types/injected.d.ts
  10. 1
      libs/remix-ui/run-tab/src/lib/types/node.d.ts
  11. 1
      libs/remix-ui/run-tab/src/lib/types/vm.d.ts

@ -1,7 +1,7 @@
import React from 'react' // eslint-disable-line
import {fromWei, toBigInt, toWei} from 'web3-utils'
import {Plugin} from '@remixproject/engine'
import {toBuffer, addHexPrefix, bufferToHex} from '@ethereumjs/util'
import {toBuffer, addHexPrefix} from '@ethereumjs/util'
import {EventEmitter} from 'events'
import {format} from 'util'
import {ExecutionContext} from './execution-context'
@ -135,7 +135,9 @@ export class Blockchain extends Plugin {
setupEvents() {
this.executionContext.event.register('contextChanged', async (context) => {
await this.resetEnvironment()
console.log('context changed', context)
// reset environment to last known state of the context
await this.loadContext(context)
this._triggerEvent('contextChanged', [context])
this.detectNetwork((error, network) => {
this.networkStatus = {network, error}
@ -164,7 +166,7 @@ export class Blockchain extends Plugin {
}
setupProviders() {
const vmProvider = new VMProvider(this.executionContext, this)
const vmProvider = new VMProvider(this.executionContext)
this.providers = {}
this.providers['vm'] = vmProvider
this.providers.injected = new InjectedProvider(this.executionContext)
@ -643,8 +645,16 @@ export class Blockchain extends Plugin {
})
}
async resetEnvironment() {
await this.getCurrentProvider().resetEnvironment()
async loadContext(context: string) {
const contextExists = await this.call('fileManager', 'exists', '.context')
if (contextExists) {
const stateDb = await this.call('fileManager', 'readFile', `.states/${context}/state.json`)
await this.getCurrentProvider().loadContext(stateDb)
} else {
await this.getCurrentProvider().resetEnvironment()
}
// TODO: most params here can be refactored away in txRunner
const web3Runner = new TxRunnerWeb3(
{
@ -890,27 +900,9 @@ export class Blockchain extends Plugin {
let returnValue = null
if (isVM) {
if (!tx.useCall) {
// TODO: this won't save the state for transactions executed outside of the UI (dor instande from a script execution).
setTimeout(async() => {
const root = await this.web3().remix.getStateTrieRoot()
const db = await this.web3().remix.getStateDb()
const state = {
root,
db: Object.fromEntries(db._database)
}
console.log('saving', state)
const stringifyed = JSON.stringify(state, (key, value) => {
if (key === 'root') {
return bufferToHex(value)
} else if (key === 'db') {
return value
} else if (key === '') {
return value
}
return bufferToHex(value)
}, '\t')
this.call('fileManager', 'writeFile', '.states/state.json', stringifyed)
}, 500)
await this.executionContext.getStateDetails().then((state) => {
this.call('fileManager', 'writeFile', `.states/${this.executionContext.getProvider()}/state.json`, state)
})
}
const hhlogs = await this.web3().remix.getHHLogsForTx(txResult.transactionHash)

@ -3,6 +3,7 @@
import Web3 from 'web3'
import { execution } from '@remix-project/remix-lib'
import EventManager from '../lib/events'
import {bufferToHex} from '@ethereumjs/util'
const _paq = window._paq = window._paq || []
let web3
@ -71,35 +72,44 @@ export class ExecutionContext {
}
detectNetwork (callback) {
if (this.isVM()) {
callback(null, { id: '-', name: 'VM' })
} else {
if (!web3.currentProvider) {
return callback('No provider set')
}
const cb = (err, id) => {
let name = null
if (err) name = 'Unknown'
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
else if (id === 1) name = 'Main'
else if (id === 3) name = 'Ropsten'
else if (id === 4) name = 'Rinkeby'
else if (id === 5) name = 'Goerli'
else if (id === 42) name = 'Kovan'
else if (id === 11155111) name = 'Sepolia'
else name = 'Custom'
if (id === 1) {
web3.eth.getBlock(0).then((block) => {
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}).catch((error) => callback(error))
} else {
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
return new Promise((resolve, reject) => {
if (this.isVM()) {
callback && callback(null, { id: '-', name: 'VM' })
return resolve({ id: '-', name: 'VM' })
} else {
if (!web3.currentProvider) {
callback && callback('No provider set')
return reject('No provider set')
}
const cb = (err, id) => {
let name = null
if (err) name = 'Unknown'
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
else if (id === 1) name = 'Main'
else if (id === 3) name = 'Ropsten'
else if (id === 4) name = 'Rinkeby'
else if (id === 5) name = 'Goerli'
else if (id === 42) name = 'Kovan'
else if (id === 11155111) name = 'Sepolia'
else name = 'Custom'
if (id === 1) {
web3.eth.getBlock(0).then((block) => {
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback && callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
return resolve({ id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}).catch((error) => {
callback && callback(error)
return reject(error)
})
} else {
callback && callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
return resolve({ id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}
}
web3.eth.net.getId().then(id=>cb(null,parseInt(id))).catch(err=>cb(err))
}
web3.eth.net.getId().then(id=>cb(null,parseInt(id))).catch(err=>cb(err))
}
})
}
removeProvider (name) {
@ -195,4 +205,26 @@ export class ExecutionContext {
return transactionDetailsLinks[network] + hash
}
}
async getStateDetails() {
// TODO: this won't save the state for transactions executed outside of the UI (for instance from a script execution).
const root = await this.web3().remix.getStateTrieRoot()
const db = await this.web3().remix.getStateDb()
const state = {
root,
db: Object.fromEntries(db._database)
}
const stringifyed = JSON.stringify(state, (key, value) => {
if (key === 'root') {
return bufferToHex(value)
} else if (key === 'db') {
return value
} else if (key === '') {
return value
}
return bufferToHex(value)
}, '\t')
return stringifyed
}
}

@ -27,6 +27,10 @@ export class InjectedProvider {
/* Do nothing. */
}
async loadContext (context) {
/* Do nothing. */
}
async getBalanceInEther (address) {
const balance = await this.executionContext.web3().eth.getBalance(address)
const balInString = balance.toString(10)

@ -33,6 +33,10 @@ export class NodeProvider {
/* Do nothing. */
}
async loadContext (context) {
/* Do nothing. */
}
async getBalanceInEther (address) {
const balance = await this.executionContext.web3().eth.getBalance(address)
const balInString = balance.toString(10)

@ -14,9 +14,7 @@ export class VMProvider {
sendAsync: (query: JSONRPCRequestPayload, callback: JSONRPCResponseCallback) => void
}
newAccountCallback: {[stamp: number]: (error: Error, address: string) => void}
plugin: Plugin
constructor (executionContext: ExecutionContext, plugin: Plugin) {
this.plugin = plugin
constructor (executionContext: ExecutionContext) {
this.executionContext = executionContext
this.worker = null
this.provider = null
@ -32,66 +30,41 @@ export class VMProvider {
}
async resetEnvironment () {
if (this.worker) {
const provider = this.executionContext.getProviderObject()
this.worker.postMessage({
cmd: 'init',
fork: this.executionContext.getCurrentFork(),
nodeUrl: provider?.options['nodeUrl'],
blockNumber: provider?.options['blockNumber']
})
} else {
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
this.setWorkerEventListeners(this.worker)
const provider = this.executionContext.getProviderObject()
this.worker.postMessage({
cmd: 'init',
fork: this.executionContext.getCurrentFork(),
nodeUrl: provider?.options['nodeUrl'],
blockNumber: provider?.options['blockNumber']
})
}
}
async loadContext (stringifiedStateDb: string) {
if (this.worker) this.worker.terminate()
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
this.setWorkerEventListeners(this.worker)
const provider = this.executionContext.getProviderObject()
let incr = 0
const stamps = {}
return new Promise((resolve, reject) => {
this.worker.addEventListener('message', (msg) => {
if (msg.data.cmd === 'sendAsyncResult' && stamps[msg.data.stamp]) {
if (stamps[msg.data.stamp].callback) {
stamps[msg.data.stamp].callback(msg.data.error, msg.data.result)
return
}
if (msg.data.error) {
stamps[msg.data.stamp].reject(msg.data.error)
} else {
stamps[msg.data.stamp].resolve(msg.data.result)
}
} else if (msg.data.cmd === 'initiateResult') {
if (!msg.data.error) {
this.provider = {
sendAsync: (query, callback) => {
return new Promise((resolve, reject) => {
const stamp = Date.now() + incr
incr++
stamps[stamp] = { callback, resolve, reject }
this.worker.postMessage({ cmd: 'sendAsync', query, stamp })
})
}
}
this.web3 = new Web3(this.provider as LegacySendAsyncProvider)
this.web3.setConfig({ defaultTransactionType: '0x0' })
extend(this.web3)
this.executionContext.setWeb3(this.executionContext.getProvider(), this.web3)
resolve({})
} else {
reject(new Error(msg.data.error))
}
} else if (msg.data.cmd === 'newAccountResult') {
if (this.newAccountCallback[msg.data.stamp]) {
this.newAccountCallback[msg.data.stamp](msg.data.error, msg.data.result)
delete this.newAccountCallback[msg.data.stamp]
}
}
})
const init = async () => {
let stateDb
if (await this.plugin.call('fileManager', 'exists', '.states/state.json')) {
stateDb = await this.plugin.call('fileManager', 'readFile', '.states/state.json')
}
this.worker.postMessage({
cmd: 'init',
fork: this.executionContext.getCurrentFork(),
nodeUrl: provider?.options['nodeUrl'],
blockNumber: provider?.options['blockNumber'],
stateDb
})
}
init()
this.worker.postMessage({
cmd: 'init',
fork: this.executionContext.getCurrentFork(),
nodeUrl: provider?.options['nodeUrl'],
blockNumber: provider?.options['blockNumber'],
stateDb: stringifiedStateDb
})
}
@ -131,4 +104,49 @@ export class VMProvider {
getProvider () {
return this.executionContext.getProvider()
}
private setWorkerEventListeners (worker: Worker) {
if (!worker) throw new Error('Worker not initialized')
let incr = 0
const stamps = {}
worker.addEventListener('message', (msg) => {
if (msg.data.cmd === 'sendAsyncResult' && stamps[msg.data.stamp]) {
if (stamps[msg.data.stamp].callback) {
stamps[msg.data.stamp].callback(msg.data.error, msg.data.result)
return
}
if (msg.data.error) {
stamps[msg.data.stamp].reject(msg.data.error)
} else {
stamps[msg.data.stamp].resolve(msg.data.result)
}
} else if (msg.data.cmd === 'initiateResult') {
if (!msg.data.error) {
this.provider = {
sendAsync: (query, callback) => {
return new Promise((resolve, reject) => {
const stamp = Date.now() + incr
incr++
stamps[stamp] = { callback, resolve, reject }
worker.postMessage({ cmd: 'sendAsync', query, stamp })
})
}
}
this.web3 = new Web3(this.provider as LegacySendAsyncProvider)
this.web3.setConfig({ defaultTransactionType: '0x0' })
extend(this.web3)
this.executionContext.setWeb3(this.executionContext.getProvider(), this.web3)
} else {
console.error(msg.data.error)
throw new Error(msg.data.error)
}
} else if (msg.data.cmd === 'newAccountResult') {
if (this.newAccountCallback[msg.data.stamp]) {
this.newAccountCallback[msg.data.stamp](msg.data.error, msg.data.result)
delete this.newAccountCallback[msg.data.stamp]
}
}
})
}
}

@ -32,8 +32,16 @@ export type State = {
root: Buffer
}
export type ProviderOptions = {
fork: string,
nodeUrl: string,
blockNumber: number | 'latest',
stateDb: State,
logDetails?: boolean
}
export class Provider {
options: Record<string, string | number | State>
options: ProviderOptions
vmContext
Accounts
Transactions
@ -42,10 +50,10 @@ export class Provider {
initialized: boolean
pendingRequests: Array<any>
constructor (options: Record<string, string | number | State> = {}) {
constructor (options: ProviderOptions = {} as ProviderOptions) {
this.options = options
this.connected = true
this.vmContext = new VMContext(options['fork'] as string, options['nodeUrl'] as string, options['blockNumber'] as (number | 'latest'), options['stateDb'] as State)
this.vmContext = new VMContext(options['fork'], options['nodeUrl'], options['blockNumber'], options['stateDb'])
this.Accounts = new Web3Accounts(this.vmContext)
this.Transactions = new Transactions(this.vmContext)

@ -331,9 +331,9 @@ export class VMContext {
exeResults: Record<string, Transaction>
nodeUrl: string
blockNumber: number | 'latest'
stateDb: any
stateDb: State
constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest', stateDb?: any) {
constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest', stateDb?: State) {
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = fork || 'merge'

@ -61,6 +61,7 @@ export class Blockchain extends Plugin<any, any> {
/** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */
startListening(txlistener: any): void;
resetEnvironment(): Promise<void>;
loadContext(): Promise<void>;
/**
* Create a VM Account
* @param {{privateKey: string, balance: string}} newAccount The new account to create

@ -5,6 +5,7 @@ declare class InjectedProvider {
getAccounts(cb: any): any;
newAccount(passwordPromptCb: any, cb: any): void;
resetEnvironment(): Promise<void>;
loadContext(context: any): Promise<void>;
getBalanceInEther(address: any): Promise<string>;
getGasPrice(cb: any): void;
signMessage(message: any, account: any, _passphrase: any, cb: any): void;

@ -6,6 +6,7 @@ declare class NodeProvider {
getAccounts(cb: any): any;
newAccount(passwordPromptCb: any, cb: any): any;
resetEnvironment(): Promise<void>;
loadContext(context: any): Promise<void>;
getBalanceInEther(address: any): Promise<string>;
getGasPrice(cb: any): void;
signMessage(message: any, account: any, passphrase: any, cb: any): void;

@ -4,6 +4,7 @@ declare class VMProvider {
executionContext: any;
getAccounts(cb: any): void;
resetEnvironment(): Promise<void>;
loadEnvironment(stringifiedStateDb: string): Promise<void>;
accounts: any;
RemixSimulatorProvider: any;
web3: any;

Loading…
Cancel
Save