Skip to content

Commit

Permalink
StateManager: Interface Refactor/Simplification (#3543)
Browse files Browse the repository at this point in the history
* Move originalStorageCache to main interface (implemented by all SMs, alternative: make optional)

* Remove redundand getProof from interface

* Move generateCanonicalGenesis to main SM interface, make optional, remove VM genesisState option (only 1 internal usage, can be easily replaced)

* Move dumpStorage() over to main interface, make optional

* Move dumpStorageRange() over to main interface, make optional, SM method clean up

* Bug fix, fix Kaustinen6 test

* Add simple clearStorage() implementation for simple SM

* Fully remove EVMStateManager interface

* Add clearCaches() to the official interface (called into from runBlock(), implemented by all SMs

* Lint and test fixes

* Edit comment

---------

Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com>
  • Loading branch information
holgerd77 and acolytec3 authored Jul 26, 2024
1 parent 91270f5 commit 8d87e80
Show file tree
Hide file tree
Showing 22 changed files with 103 additions and 140 deletions.
4 changes: 2 additions & 2 deletions packages/client/src/execution/vmexecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,11 @@ export class VMExecution extends Execution {
this.chain['_customGenesisState'] ?? getGenesis(Number(blockchain.common.chainId()))
if (
!genesisState &&
(this.vm instanceof DefaultStateManager || !this.config.statelessVerkle)
(!('generateCanonicalGenesis' in this.vm.stateManager) || !this.config.statelessVerkle)
) {
throw new Error('genesisState not available')
} else {
await this.vm.stateManager.generateCanonicalGenesis(genesisState)
await this.vm.stateManager.generateCanonicalGenesis!(genesisState)
}
}

Expand Down
24 changes: 21 additions & 3 deletions packages/client/src/rpc/modules/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,13 @@ export class Debug {
const memory = []
let storage = {}
if (opts.disableStorage === false) {
storage = await vmCopy.stateManager.dumpStorage(step.address)
if (!('dumpStorage' in vmCopy.stateManager)) {
throw {
message: 'stateManager has no dumpStorage implementation',
code: INTERNAL_ERROR,
}
}
storage = await vmCopy.stateManager.dumpStorage!(step.address)
}
if (opts.enableMemory === true) {
for (let x = 0; x < step.memoryWordCount; x++) {
Expand Down Expand Up @@ -260,7 +266,13 @@ export class Debug {
const memory = []
let storage = {}
if (opts.disableStorage === false) {
storage = await vm.stateManager.dumpStorage(step.address)
if (!('dumpStorage' in vm.stateManager)) {
throw {
message: 'stateManager has no dumpStorage implementation',
code: INTERNAL_ERROR,
}
}
storage = await vm.stateManager.dumpStorage!(step.address)
}
if (opts.enableMemory === true) {
for (let x = 0; x < step.memoryWordCount; x++) {
Expand Down Expand Up @@ -347,13 +359,19 @@ export class Debug {
const parentBlock = await this.chain.getBlock(block.header.parentHash)
// Copy the VM and run transactions including the relevant transaction.
const vmCopy = await this.vm.shallowCopy()
if (!('dumpStorageRange' in vmCopy.stateManager)) {
throw {
code: INTERNAL_ERROR,
message: 'stateManager has no dumpStorageRange implementation',
}
}
await vmCopy.stateManager.setStateRoot(parentBlock.header.stateRoot)
for (let i = 0; i <= txIndex; i++) {
await runTx(vmCopy, { tx: block.transactions[i], block })
}

// await here so that any error can be handled in the catch below for proper response
return vmCopy.stateManager.dumpStorageRange(
return vmCopy.stateManager.dumpStorageRange!(
// Validator already verified that `account` and `startKey` are properly formatted.
Address.fromString(account),
BigInt(startKey),
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/eth/estimateGas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe(
const { execution } = client.services.find((s) => s.name === 'eth') as FullEthereumService
assert.notEqual(execution, undefined, 'should have valid execution')
const { vm } = execution
await vm.stateManager.generateCanonicalGenesis(getGenesis(1))
await vm.stateManager.generateCanonicalGenesis!(getGenesis(1))

// genesis address with balance
const address = Address.fromString('0xccfd725760a68823ff1e062f4cc97e1360e8d997')
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/eth/getBalance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe(

// since synchronizer.run() is not executed in the mock setup,
// manually run stateManager.generateCanonicalGenesis()
await vm.stateManager.generateCanonicalGenesis(getGenesis(1))
await vm.stateManager.generateCanonicalGenesis!(getGenesis(1))

// genesis address with balance
const address = Address.fromString('0xccfd725760a68823ff1e062f4cc97e1360e8d997')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe(method, () => {
assert.notEqual(execution, undefined, 'should have valid execution')
const { vm } = execution

await vm.stateManager.generateCanonicalGenesis(getGenesis(1))
await vm.stateManager.generateCanonicalGenesis!(getGenesis(1))

const address = Address.fromString('0xccfd725760a68823ff1e062f4cc97e1360e8d997')

Expand Down Expand Up @@ -80,7 +80,7 @@ describe(method, () => {
assert.notEqual(execution, undefined, 'should have valid execution')
const { vm } = execution

await vm.stateManager.generateCanonicalGenesis(getGenesis(1))
await vm.stateManager.generateCanonicalGenesis!(getGenesis(1))

const address = Address.fromString('0xccfd725760a68823ff1e062f4cc97e1360e8d997')

Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/eth/getCode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe(method, () => {
const { execution } = client.services.find((s) => s.name === 'eth') as FullEthereumService
assert.notEqual(execution, undefined, 'should have valid execution')
const { vm } = execution
await vm.stateManager.generateCanonicalGenesis(getGenesis(1))
await vm.stateManager.generateCanonicalGenesis!(getGenesis(1))

// genesis address
const address = Address.fromString('0xccfd725760a68823ff1e062f4cc97e1360e8d997')
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/eth/getTransactionCount.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe(method, () => {

// since synchronizer.run() is not executed in the mock setup,
// manually run stateManager.generateCanonicalGenesis()
await vm.stateManager.generateCanonicalGenesis(getGenesis(1))
await vm.stateManager.generateCanonicalGenesis!(getGenesis(1))

// a genesis address
const address = Address.fromString('0xccfd725760a68823ff1e062f4cc97e1360e8d997')
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/txpool/content.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe(method, () => {
const { execution } = client.services.find((s) => s.name === 'eth') as FullEthereumService
assert.notEqual(execution, undefined, 'should have valid execution')
const { vm } = execution
await vm.stateManager.generateCanonicalGenesis(getGenesis(1))
await vm.stateManager.generateCanonicalGenesis!(getGenesis(1))
const gasLimit = 2000000
const parent = await blockchain.getCanonicalHeadHeader()
const block = createBlockFromBlockData(
Expand Down
46 changes: 30 additions & 16 deletions packages/common/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ export interface AccessWitnessInterface {
*
*/
export interface StateManagerInterface {
/*
* Core Access Functionality
*/
// Account methods
getAccount(address: Address): Promise<Account | undefined>
putAccount(address: Address, account?: Account): Promise<void>
Expand All @@ -164,35 +167,46 @@ export interface StateManagerInterface {
putStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise<void>
clearStorage(address: Address): Promise<void>

// Checkpointing methods
/*
* Checkpointing Functionality
*/
checkpoint(): Promise<void>
commit(): Promise<void>
revert(): Promise<void>

// State root methods
/*
* State Root Functionality
*/
getStateRoot(): Promise<Uint8Array>
setStateRoot(stateRoot: Uint8Array, clearCache?: boolean): Promise<void>
hasStateRoot(root: Uint8Array): Promise<boolean> // only used in client

// Other
/*
* Extra Functionality
*
* Optional non-essential methods, these methods should always be guarded
* on usage (check for existance)
*/
// Client RPC
getProof?(address: Address, storageSlots: Uint8Array[]): Promise<Proof>
shallowCopy(downlevelCaches?: boolean): StateManagerInterface
getAppliedKey?(address: Uint8Array): Uint8Array
dumpStorage?(address: Address): Promise<StorageDump>
dumpStorageRange?(address: Address, startKey: bigint, limit: number): Promise<StorageRange>

// Verkle (experimental)
checkChunkWitnessPresent?(contract: Address, programCounter: number): Promise<boolean>
}

export interface EVMStateManagerInterface extends StateManagerInterface {
/*
* EVM/VM Specific Functionality
*/
originalStorageCache: {
get(address: Address, key: Uint8Array): Promise<Uint8Array>
clear(): void
}
generateCanonicalGenesis?(initState: any): Promise<void> // TODO make input more typesafe
// only Verkle/EIP-6800 (experimental)
checkChunkWitnessPresent?(contract: Address, programCounter: number): Promise<boolean>
getAppliedKey?(address: Uint8Array): Uint8Array // only for preimages

dumpStorage(address: Address): Promise<StorageDump> // only used in client
dumpStorageRange(address: Address, startKey: bigint, limit: number): Promise<StorageRange> // only used in client
generateCanonicalGenesis(initState: any): Promise<void> // TODO make input more typesafe
getProof(address: Address, storageSlots?: Uint8Array[]): Promise<Proof>

shallowCopy(downlevelCaches?: boolean): EVMStateManagerInterface
/*
* Utility
*/
clearCaches(): void
shallowCopy(downlevelCaches?: boolean): StateManagerInterface
}
4 changes: 2 additions & 2 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import type {
ExecResult,
bn128,
} from './types.js'
import type { Common, EVMStateManagerInterface } from '@ethereumjs/common'
import type { Common, StateManagerInterface } from '@ethereumjs/common'

const debug = debugDefault('evm:evm')
const debugGas = debugDefault('evm:gas')
Expand Down Expand Up @@ -94,7 +94,7 @@ export class EVM implements EVMInterface {
public readonly common: Common
public readonly events: AsyncEventEmitter<EVMEvents>

public stateManager: EVMStateManagerInterface
public stateManager: StateManagerInterface
public blockchain: Blockchain
public journal: Journal

Expand Down
10 changes: 5 additions & 5 deletions packages/evm/src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import type { EVM } from './evm.js'
import type { Journal } from './journal.js'
import type { AsyncOpHandler, Opcode, OpcodeMapEntry } from './opcodes/index.js'
import type { Block, Blockchain, EOFEnv, EVMProfilerOpts, EVMResult, Log } from './types.js'
import type { AccessWitnessInterface, Common, EVMStateManagerInterface } from '@ethereumjs/common'
import type { AccessWitnessInterface, Common, StateManagerInterface } from '@ethereumjs/common'
import type { Address, PrefixedHexString } from '@ethereumjs/util'

const debugGas = debugDefault('evm:gas')
Expand Down Expand Up @@ -86,7 +86,7 @@ export interface RunState {
shouldDoJumpAnalysis: boolean
validJumps: Uint8Array // array of values where validJumps[index] has value 0 (default), 1 (jumpdest), 2 (beginsub)
cachedPushes: { [pc: number]: bigint }
stateManager: EVMStateManagerInterface
stateManager: StateManagerInterface
blockchain: Blockchain
env: Env
messageGasLimit?: bigint // Cache value from `gas.ts` to save gas limit for a message call
Expand All @@ -105,7 +105,7 @@ export interface InterpreterResult {
export interface InterpreterStep {
gasLeft: bigint
gasRefund: bigint
stateManager: EVMStateManagerInterface
stateManager: StateManagerInterface
stack: bigint[]
pc: number
depth: number
Expand All @@ -128,7 +128,7 @@ export interface InterpreterStep {
export class Interpreter {
protected _vm: any
protected _runState: RunState
protected _stateManager: EVMStateManagerInterface
protected _stateManager: StateManagerInterface
protected common: Common
public _evm: EVM
public journal: Journal
Expand All @@ -147,7 +147,7 @@ export class Interpreter {
// TODO remove gasLeft as constructor argument
constructor(
evm: EVM,
stateManager: EVMStateManagerInterface,
stateManager: StateManagerInterface,
blockchain: Blockchain,
env: Env,
gasLeft: bigint,
Expand Down
6 changes: 3 additions & 3 deletions packages/evm/src/journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import debugDefault from 'debug'
import { hexToBytes } from 'ethereum-cryptography/utils'

import type { Common, EVMStateManagerInterface } from '@ethereumjs/common'
import type { Common, StateManagerInterface } from '@ethereumjs/common'
import type { Account, PrefixedHexString } from '@ethereumjs/util'
import type { Debugger } from 'debug'

Expand All @@ -32,7 +32,7 @@ type JournalDiffItem = [Set<AddressString>, Map<AddressString, Set<SlotString>>,
type JournalHeight = number

export class Journal {
private stateManager: EVMStateManagerInterface
private stateManager: StateManagerInterface
private common: Common
private DEBUG: boolean
private _debug: Debugger
Expand All @@ -47,7 +47,7 @@ export class Journal {
public accessList?: Map<AddressString, Set<SlotString>>
public preimages?: Map<PrefixedHexString, Uint8Array>

constructor(stateManager: EVMStateManagerInterface, common: Common) {
constructor(stateManager: StateManagerInterface, common: Common) {
// Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables
// Additional window check is to prevent vite browser bundling (and potentially other) to break
this.DEBUG =
Expand Down
6 changes: 3 additions & 3 deletions packages/evm/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import type { PrecompileFunc } from './precompiles/types.js'
import type {
AccessWitnessInterface,
Common,
EVMStateManagerInterface,
ParamsDict,
StateManagerInterface,
} from '@ethereumjs/common'
import type { Account, Address, AsyncEventEmitter, PrefixedHexString } from '@ethereumjs/util'

Expand Down Expand Up @@ -162,7 +162,7 @@ export interface EVMInterface {
startReportingAccessList(): void
startReportingPreimages?(): void
}
stateManager: EVMStateManagerInterface
stateManager: StateManagerInterface
precompiles: Map<string, PrecompileFunc>
runCall(opts: EVMRunCallOpts): Promise<EVMResult>
runCode(opts: EVMRunCodeOpts): Promise<ExecResult>
Expand Down Expand Up @@ -312,7 +312,7 @@ export interface EVMOpts {
* implementations for different needs (MPT-tree backed, RPC, experimental verkle)
* which can be used by this option as a replacement.
*/
stateManager?: EVMStateManagerInterface
stateManager?: StateManagerInterface

/**
*
Expand Down
18 changes: 2 additions & 16 deletions packages/statemanager/src/rpcStateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,13 @@ import { keccak256 } from 'ethereum-cryptography/keccak.js'
import { AccountCache, CacheType, OriginalStorageCache, StorageCache } from './cache/index.js'

import type { Proof, RPCStateManagerOpts } from './index.js'
import type {
AccountFields,
EVMStateManagerInterface,
StorageDump,
StorageRange,
} from '@ethereumjs/common'
import type { AccountFields, StateManagerInterface, StorageDump } from '@ethereumjs/common'
import type { Address, PrefixedHexString } from '@ethereumjs/util'
import type { Debugger } from 'debug'

const KECCAK256_RLP_EMPTY_ACCOUNT = RLP.encode(new Account().serialize()).slice(2)

export class RPCStateManager implements EVMStateManagerInterface {
export class RPCStateManager implements StateManagerInterface {
protected _provider: string
protected _contractCache: Map<string, Uint8Array>
protected _storageCache: StorageCache
Expand Down Expand Up @@ -213,11 +208,6 @@ export class RPCStateManager implements EVMStateManagerInterface {
return Promise.resolve(dump)
}

dumpStorageRange(_address: Address, _startKey: bigint, _limit: number): Promise<StorageRange> {
// TODO: Implement.
return Promise.reject()
}

/**
* Checks if an `account` exists at `address`
* @param address - Address of the `account` to check
Expand Down Expand Up @@ -436,10 +426,6 @@ export class RPCStateManager implements EVMStateManagerInterface {
hasStateRoot = () => {
throw new Error('function not implemented')
}

generateCanonicalGenesis(_initState: any): Promise<void> {
return Promise.resolve()
}
}

export class RPCBlockChain {
Expand Down
Loading

0 comments on commit 8d87e80

Please sign in to comment.