Skip to content

Commit

Permalink
EVM/Common: SimpleStateManager (#3482)
Browse files Browse the repository at this point in the history
* Add SimpleStateManager implementation

* Common SimpleStateManager fixes, add explicit ethereum-cryptography dependency (already in through Util dependency)

* Rebuild package-lock.json

* Integrate SimpleStateManager into existing checkpointing tests

* Integrate into EVM, remove stateManager dependency

* Rebuild packack-lock.json

* Move OriginalStorageCache class to Common, deprecate old location

* Use full OriginalStorageCache implementation in SimpleStateManager

* Add some docs

* Rename topA, topC, topS to something more expressive, make protected

* Rename add() -> checkpointSync(), make protected

* More elegant commit() implementation

* Add flexible keccak256

* Minor

* Update packages/common/src/state/simple.ts

Co-authored-by: Gabriel Rocheleau <contact@rockwaterweb.com>

* move simple to stateManager

* add simple example

* lint

* Revert bundler config changes

* update docs

* address feedback

* address feedback

---------

Co-authored-by: Scorbajio <indigophi@protonmail.com>
Co-authored-by: Gabriel Rocheleau <contact@rockwaterweb.com>
Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 12, 2024
1 parent 94d02e6 commit ef20930
Show file tree
Hide file tree
Showing 15 changed files with 1,082 additions and 795 deletions.
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"tsc": "../../config/cli/ts-compile.sh"
},
"dependencies": {
"@ethereumjs/util": "^9.0.3"
"@ethereumjs/util": "^9.0.3",
"ethereum-cryptography": "^2.2.1"
},
"devDependencies": {
"@polkadot/util": "^12.6.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Chain, Common, Hardfork } from '@ethereumjs/common'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { SimpleStateManager } from '@ethereumjs/statemanager'
import {
Account,
Address,
Expand Down Expand Up @@ -168,7 +168,7 @@ export class EVM implements EVMInterface {
}

if (opts.stateManager === undefined) {
opts.stateManager = new DefaultStateManager()
opts.stateManager = new SimpleStateManager()
}

return new EVM(opts, bn128)
Expand Down
8 changes: 7 additions & 1 deletion packages/evm/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,13 @@ export interface EVMOpts {
bls?: EVMBLSInterface

/*
* The StateManager which is used to update the trie
* The EVM comes with a basic dependency-minimized `SimpleStateManager` implementation
* which serves most code execution use cases and which is included in the
* `@ethereumjs/statemanager` package.
*
* The `@ethereumjs/statemanager` package also provides a variety of state manager
* implementations for different needs (MPT-tree backed, RPC, experimental verkle)
* which can be used by this option as a replacement.
*/
stateManager?: EVMStateManagerInterface

Expand Down
11 changes: 0 additions & 11 deletions packages/evm/test/eips/eip-3860.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Chain, Common, Hardfork } from '@ethereumjs/common'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { Address, concatBytes, equalsBytes, hexToBytes, privateToAddress } from '@ethereumjs/util'
import { assert, describe, it } from 'vitest'

Expand All @@ -17,7 +16,6 @@ describe('EIP 3860 tests', () => {
})
const evm = await EVM.create({
common,
stateManager: new DefaultStateManager(),
})

const buffer = new Uint8Array(1000000).fill(0x60)
Expand Down Expand Up @@ -58,11 +56,9 @@ describe('EIP 3860 tests', () => {
const caller = Address.fromString('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')
const evm = await EVM.create({
common: commonWith3860,
stateManager: new DefaultStateManager(),
})
const evmWithout3860 = await EVM.create({
common: commonWithout3860,
stateManager: new DefaultStateManager(),
})
const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b')
const contractAccount = await evm.stateManager.getAccount(contractFactory)
Expand Down Expand Up @@ -104,11 +100,9 @@ describe('EIP 3860 tests', () => {
const caller = Address.fromString('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')
const evm = await EVM.create({
common: commonWith3860,
stateManager: new DefaultStateManager(),
})
const evmWithout3860 = await EVM.create({
common: commonWithout3860,
stateManager: new DefaultStateManager(),
})
const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b')
const contractAccount = await evm.stateManager.getAccount(contractFactory)
Expand Down Expand Up @@ -143,8 +137,6 @@ describe('EIP 3860 tests', () => {
})
const evm = await EVM.create({
common,
stateManager: new DefaultStateManager(),

allowUnlimitedInitCodeSize: true,
})

Expand Down Expand Up @@ -179,14 +171,11 @@ describe('EIP 3860 tests', () => {
for (const code of ['F0', 'F5']) {
const evm = await EVM.create({
common: commonWith3860,
stateManager: new DefaultStateManager(),

allowUnlimitedInitCodeSize: true,
})
const evmDisabled = await EVM.create({
common: commonWith3860,
stateManager: new DefaultStateManager(),

allowUnlimitedInitCodeSize: false,
})
const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b')
Expand Down
38 changes: 33 additions & 5 deletions packages/statemanager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ To obtain the latest version, simply require the project using `npm`:
npm install @ethereumjs/statemanager
```

Note: this library was part of the [@ethereumjs/vm](../vm/) package up till VM `v5`.

## Usage

### Introduction

The `StateManager` provides high-level access and manipulation methods to and for the Ethereum state, thinking in terms of accounts or contract code rather then the storage operations of the underlying data structure (e.g. a [Trie](../trie/)).

The library includes a TypeScript interface `StateManager` to ensure a unified interface (e.g. when passed to the VM), a concrete Trie-based `DefaultStateManager` implementation, as well as an `RPCStateManager` implementation that sources state and history data from an external JSON-RPC provider.
This library includes several different implementations that all implement the `StateManager` interface which is accepted by the `vm` library. These include:

- [`SimpleStateManager`](./src/simpleStateManager.ts) -a minimally functional (and dependency minimized) version of the state manager suitable for most basic EVM bytecode operations
- [`DefaultStateManager`](./src//stateManager.ts) - a Merkle-Patricia Trie-based `DefaultStateManager` implementation that is used by the `@ethereumjs/client` and `@ethereumjs/vm`
- [`RPCStateManager`](./src/rpcStateManager.ts) - a light-weight implementation that sources state and history data from an external JSON-RPC provider
- [`StatelessVerkleStateManager`](./src/statelessVerkleStateManager.ts) - an experimental implementation of a "stateless" state manager that uses Verkle proofs to provide necessary state access for processing verkle-trie based blocks

It also includes a checkpoint/revert/commit mechanism to either persist or revert state changes and provides a sophisticated caching mechanism under the hood to reduce the need for direct state accesses.
It also includes a checkpoint/revert/commit mechanism to either persist or revert state changes and provides a sophisticated caching mechanism under the hood to reduce the need reading state accesses from disk.

### `DefaultStateManager`

Expand Down Expand Up @@ -69,6 +72,31 @@ Caches now "survive" a flush operation and especially long-lived usage scenarios

Have a loot at the extended `CacheOptions` on how to use and leverage the new cache system.

### `SimpleStateManager`

The `SimpleStateManager` is a dependency-minimized simple state manager implementation. While this state manager implementation lacks the implementations of some non-core functionality as well as proof related logic (e.g. `setStateRoot()`) it is suitable for a lot use cases where things like sophisticated caching or state root handling is not needed.

This state manager can be instantiated and used as follows:

```ts
// ./examples/simple.ts

import { SimpleStateManager } from '../src/index.js'
import { Account, Address, randomBytes } from '@ethereumjs/util'

const main = async () => {
const sm = new SimpleStateManager()
const address = Address.fromPrivateKey(randomBytes(32))
const account = new Account(0n, 0xfffffn)
await sm.putAccount(address, account)
console.log(await sm.getAccount(address))
}

main()
```

### `DefaultStateManager` -> Proofs

#### Instantiating from a Proof

The `DefaultStateManager` has a static constructor `fromProof` that accepts one or more [EIP-1186](https://eips.ethereum.org/EIPS/eip-1186) [proofs](./src/stateManager.ts) and will instantiate a `DefaultStateManager` with a partial trie containing the state provided by the proof(s). Be aware that this constructor accepts the `StateManagerOpts` dictionary as a third parameter (i.e. `stateManager.fromProof(proof, safe, opts)`). Therefore, if you need to use a customized trie (e.g. one that does not use key hashing) or specify caching options, you can pass them in here. If you do instantiate a trie and pass it into the `fromProof` constructor, you also need to instantiate the trie using the corresponding `fromProof` constructor to ensure the state root matches when the proof data is added to the trie. See [this test](./test/stateManager.spec.ts#L287-L288) for more details.
Expand Down Expand Up @@ -174,7 +202,7 @@ const main = async () => {
const blockchain = new RPCBlockChain(provider)
const blockTag = 1n
const state = new RPCStateManager({ provider, blockTag })
const evm = new EVM({ blockchain, stateManager: state }) // note that evm is ready to run BLOCKHASH opcodes (over RPC)
const evm = await EVM.create({ blockchain, stateManager: state }) // note that evm is ready to run BLOCKHASH opcodes (over RPC)
} catch (e) {
console.log(e.message) // fetch would fail because provider url is not real. please replace provider with a valid rpc url string.
}
Expand Down
12 changes: 12 additions & 0 deletions packages/statemanager/examples/simple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SimpleStateManager } from '../src/index.js'
import { Account, Address, randomBytes } from '@ethereumjs/util'

const main = async () => {
const sm = new SimpleStateManager()
const address = Address.fromPrivateKey(randomBytes(32))
const account = new Account(0n, 0xfffffn)
await sm.putAccount(address, account)
console.log(await sm.getAccount(address))
}

main()
9 changes: 9 additions & 0 deletions packages/statemanager/src/cache/originalStorageCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import type { Address } from '@ethereumjs/util'

type getContractStorage = (address: Address, key: Uint8Array) => Promise<Uint8Array>

/**
* Helper class to cache original storage values (so values already being present in
* the pre-state of a call), mainly for correct gas cost calculation in EVM/VM.
*
* TODO: Usage of this class is very implicit through the injected `getContractStorage()`
* method bound to the calling state manager. It should be examined if there are alternative
* designs being more transparent and direct along the next breaking release round.
*
*/
export class OriginalStorageCache {
private map: Map<string, Map<string, Uint8Array>>
private getContractStorage: getContractStorage
Expand Down
1 change: 1 addition & 0 deletions packages/statemanager/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './accessWitness.js'
export * from './cache/index.js'
export * from './rpcStateManager.js'
export * from './simpleStateManager.js'
export * from './statelessVerkleStateManager.js'
export * from './stateManager.js'
Loading

0 comments on commit ef20930

Please sign in to comment.