Socket
Socket
Sign inDemoInstall

@nomicfoundation/ethereumjs-statemanager

Package Overview
Dependencies
Maintainers
4
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nomicfoundation/ethereumjs-statemanager - npm Package Compare versions

Comparing version 2.0.3 to 2.0.4

dist/cjs/cache/account.d.ts

56

package.json
{
"name": "@nomicfoundation/ethereumjs-statemanager",
"version": "2.0.3",
"version": "2.0.4",
"description": "An Ethereum statemanager implementation",

@@ -22,4 +22,5 @@ "keywords": [

],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "commonjs",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"files": [

@@ -32,42 +33,35 @@ "dist",

"clean": "../../config/cli/clean-package.sh",
"coverage": "../../config/cli/coverage.sh",
"docs:build": "typedoc --options typedoc.js",
"coverage": "DEBUG=ethjs npx vitest run --coverage.enabled --coverage.reporter=lcov",
"docs:build": "typedoc --options typedoc.cjs",
"examples": "tsx ../../scripts/examples-runner.ts -- statemanager",
"lint": "../../config/cli/lint.sh",
"lint:diff": "../../config/cli/lint-diff.sh",
"lint:fix": "../../config/cli/lint-fix.sh",
"tape": "tape -r ts-node/register",
"test": "npm run test:node && npm run test:browser",
"test:browser": "karma start karma.conf.js",
"test:node": "npm run tape -- test/*.spec.ts",
"test:browser": "npx vitest run --config=vitest.config.browser.ts --browser.name=webkit --browser.provider=playwright --browser.headless",
"test:node": "npx vitest run",
"tsc": "../../config/cli/ts-compile.sh"
},
"dependencies": {
"@nomicfoundation/ethereumjs-common": "4.0.3",
"@nomicfoundation/ethereumjs-rlp": "5.0.3",
"@nomicfoundation/ethereumjs-common": "4.0.4",
"@nomicfoundation/ethereumjs-rlp": "5.0.4",
"@nomicfoundation/ethereumjs-trie": "6.0.4",
"@nomicfoundation/ethereumjs-util": "9.0.4",
"debug": "^4.3.3",
"ethereum-cryptography": "0.1.3",
"ethers": "^5.7.1",
"js-sdsl": "^4.1.4"
"js-sdsl": "^4.1.4",
"lru-cache": "^10.0.0"
},
"devDependencies": {
"@nomicfoundation/ethereumjs-block": "5.0.3",
"@nomicfoundation/ethereumjs-trie": "6.0.3",
"@nomicfoundation/ethereumjs-util": "9.0.3",
"@types/node": "^16.11.7",
"@types/tape": "^4.13.2",
"debug": "^4.3.3",
"eslint": "^8.0.0",
"ethereum-cryptography": "0.1.3",
"functional-red-black-tree": "^1.0.1",
"karma": "^6.3.2",
"karma-chrome-launcher": "^3.1.0",
"karma-firefox-launcher": "^2.1.0",
"karma-tap": "^4.2.0",
"karma-typescript": "^5.5.3",
"nyc": "^15.1.0",
"standard": "^10.0.0",
"tape": "^5.3.1",
"ts-node": "^10.2.1",
"typescript": "^4.4.2"
"@nomicfoundation/ethereumjs-block": "5.0.4",
"@types/debug": "^4.1.9"
},
"peerDependencies": {
"@nomicfoundation/ethereumjs-verkle": "0.0.2"
},
"peerDependenciesMeta": {
"@nomicfoundation/ethereumjs-verkle": {
"optional": true
}
}
}

@@ -9,4 +9,4 @@ # @ethereumjs/statemanager

| TypeScript implementation of the Ethereum StateManager. |
| ------------------------------------------------------- |
| Library to provide high level access to Ethereum State |
| ------------------------------------------------------ |

@@ -29,12 +29,17 @@ ## Installation

The library includes a TypeScript interface `StateManager` to ensure a unified interface (e.g. when passed to the VM) as well as a concrete Trie-based implementation `DefaultStateManager` as well as an `EthersStateManager` implementation that sources state and history data from an external `ethers` provider.
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.
### `DefaultStateManager` Example
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.
```typescript
### `DefaultStateManager`
#### Usage example
```ts
import { Account, Address } from '@ethereumjs/util'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { hexToBytes } from '@ethereumjs/util'
const stateManager = new DefaultStateManager()
const address = new Address(Buffer.from('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', 'hex'))
const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b'))
const account = new Account(BigInt(0), BigInt(1000))

@@ -47,13 +52,41 @@ await stateManager.checkpoint()

### `EthersStateManager`
#### Account, Storage and Code Caches
Starting with the v2 release and complemented by the v2.1 release the StateManager comes with a significantly more elaborate caching mechanism for account, storage and code caches.
There are now two cache options available: an unbounded cache (`CacheType.ORDERED_MAP`) for short-lived usage scenarios (this one is the default cache) and a fixed-size cache (`CacheType.LRU`) for a long-lived large cache scenario.
Caches now "survive" a flush operation and especially long-lived usage scenarios will benefit from increased performance by a growing and more "knowing" cache leading to less and less trie reads.
Have a loot at the extended `CacheOptions` on how to use and leverage the new cache system.
#### 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). See below example:
```ts
// setup `stateManager` with some existing address
const proof = await stateManager.getProof(address)
const proofWithStorage = await stateManger.getProof(contractAddress, [storageKey1, storageKey2])
const partialStateManager = await DefaultStateManager.fromProof(proof)
// To add more proof data, use `addProofData`
await partialStateManager.addProofData(proofWithStorage)
const accountFromNewSM = await partialStateManager.getAccount(address)
const accountFromOldSM = await stateManager.getAccount(address)
console.log(accountFromNewSM, accountFromOldSM) // should match
const slot1FromNewSM = await stateManager.getContractStorage(contractAddress, storageKey1)
const slot2FromNewSM = await stateManager.getContractStorage(contractAddress, storageKey1) // should also match
```
### `RPCStateManager`
First, a simple example of usage:
```typescript
```ts
import { Account, Address } from '@ethereumjs/util'
import { EthersStateManager } from '@ethereumjs/statemanager'
import { ethers } from 'ethers'
import { RPCStateManager } from '@ethereumjs/statemanager'
const provider = new ethers.providers.JsonRpcProvider('https://path.to.my.provider.com')
const stateManager = new EthersStateManager({ provider, blockTag: 500000n })
const provider = 'https://path.to.my.provider.com'
const stateManager = new RPCStateManager({ provider, blockTag: 500000n })
const vitalikDotEth = Address.fromString('0xd8da6bf26964af9d7eed9e03e53415d37aa96045')

@@ -64,15 +97,30 @@ const account = await stateManager.getAccount(vitalikDotEth)

The `EthersStateManager` can be be used with an `ethers` `JsonRpcProvider` or one of its subclasses. Instantiate the `VM` and pass in an `EthersStateManager` to run transactions against accounts sourced from the provider or to run blocks pulled from the provider at any specified block height.
The `RPCStateManager` can be be used with any JSON-RPC provider that supports the `eth` namespace. Instantiate the `VM` and pass in an `RPCStateManager` to run transactions against accounts sourced from the provider or to run blocks pulled from the provider at any specified block height.
**Note:** Usage of this StateManager can cause a heavy load regarding state request API calls, so be careful (or at least: aware) if used in combination with an Ethers provider connecting to a third-party API service like Infura!
**Note:** Usage of this StateManager can cause a heavy load regarding state request API calls, so be careful (or at least: aware) if used in combination with a JSON-RPC provider connecting to a third-party API service like Infura!
### Points on usage:
#### Points on `RPCStateManager` usage
#### Provider selection
##### Instantiating the EVM
- If you don't have access to a provider, you can use the `CloudFlareProvider` from the `@ethersproject/providers` module to get a quickstart.
In order to have an EVM instance that supports the BLOCKHASH opcode (which requires access to block history), you must instantiate both the `RPCStateManager` and the `RpcBlockChain` and use that when initalizing your EVM instance as below:
```js
import { RPCStateManager, RPCBlockChain } from '../src/rpcStateManager.js'
import { EVM } from '@ethereumjs/evm'
const blockchain = new RPCBlockChain({}, provider)
const blockTag = 1n
const state = new RPCStateManager({ provider, blockTag })
const evm = new EVM({ blockchain, stateManager: state })
```
Note: Failing to provide the `RPCBlockChain` instance when instantiating the EVM means that the `BLOCKHASH` opcode will fail to work correctly during EVM execution.
##### Provider selection
- The provider you select must support the `eth_getProof`, `eth_getCode`, and `eth_getStorageAt` RPC methods.
- Not all providers support retrieving state from all block heights so refer to your provider's documentation. Trying to use a block height not supported by your provider (e.g. any block older than the last 256 for CloudFlare) will result in RPC errors when using the state manager.
#### Block Tag selection
##### Block Tag selection

@@ -84,11 +132,17 @@ - You have to pass a block number or `earliest` in the constructor that specifies the block height you want to pull state from.

#### Potential gotchas
##### Potential gotchas
- The Ethers State Manager cannot compute valid state roots when running blocks as it does not have access to the entire Ethereum state trie so can not compute correct state roots, either for the account trie or for storage tries.
- The RPC State Manager cannot compute valid state roots when running blocks as it does not have access to the entire Ethereum state trie so can not compute correct state roots, either for the account trie or for storage tries.
- If you are replaying mainnet transactions and an account or account storage is touched by multiple transactions in a block, you must replay those transactions in order (with regard to their position in that block) or calculated gas will likely be different than actual gas consumed.
#### Further reference
##### Further reference
Refer to [this test script](./test/ethersStateManager.spec.ts) for complete examples of running transactions and blocks in the `vm` with data sourced from a provider.
Refer to [this test script](./test/rpcStateManager.spec.ts) for complete examples of running transactions and blocks in the `vm` with data sourced from a provider.
## Browser
With the breaking release round in Summer 2023 we have added hybrid ESM/CJS builds for all our libraries (see section below) and have eliminated many of the caveats which had previously prevented a frictionless browser usage.
It is now easily possible to run a browser build of one of the EthereumJS libraries within a modern browser using the provided ESM build. For a setup example see [./examples/browser.html](./examples/browser.html).
## API

@@ -100,2 +154,26 @@

### Hybrid CJS/ESM Builds
With the breaking releases from Summer 2023 we have started to ship our libraries with both CommonJS (`cjs` folder) and ESM builds (`esm` folder), see `package.json` for the detailed setup.
If you use an ES6-style `import` in your code files from the ESM build will be used:
```ts
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]'
```
If you use Node.js specific `require`, the CJS build will be used:
```ts
const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]')
```
Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking which CJS can not provide.
### Buffer -> Uint8Array
With the breaking releases from Summer 2023 we have removed all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`).
We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance.
### BigInt Support

@@ -102,0 +180,0 @@

@@ -1,4 +0,4 @@

export { BaseStateManager } from './baseStateManager'
export { EthersStateManager, EthersStateManagerOpts } from './ethersStateManager'
export { AccountFields, StateAccess, StateManager } from './interface'
export { DefaultStateManager, Proof } from './stateManager'
export * from './cache/index.js'
export * from './rpcStateManager.js'
export * from './statelessVerkleStateManager.js'
export * from './stateManager.js'

@@ -0,1 +1,2 @@

import { Chain, Common } from '@nomicfoundation/ethereumjs-common'
import { RLP } from '@nomicfoundation/ethereumjs-rlp'

@@ -5,20 +6,41 @@ import { Trie } from '@nomicfoundation/ethereumjs-trie'

Account,
Address,
KECCAK256_NULL,
KECCAK256_NULL_S,
KECCAK256_RLP,
KECCAK256_RLP_S,
bigIntToHex,
bufferToHex,
bytesToBigInt,
bytesToHex,
bytesToUnprefixedHex,
concatBytes,
equalsBytes,
hexToBytes,
setLengthLeft,
short,
toBuffer,
unpadBuffer,
toBytes,
unpadBytes,
unprefixedHexToBytes,
utf8ToBytes,
} from '@nomicfoundation/ethereumjs-util'
import { keccak256 } from 'ethereum-cryptography/keccak'
import debugDefault from 'debug'
import { keccak256 as bufferKeccak256 } from 'ethereum-cryptography/keccak.js'
import { BaseStateManager } from './baseStateManager'
import { Cache } from './cache'
import { AccountCache, CacheType, CodeCache, StorageCache } from './cache/index.js'
import { OriginalStorageCache } from './cache/originalStorageCache.js'
import type { getCb, putCb } from './cache'
import type { StateManager, StorageDump } from './interface'
import type { Address, PrefixedHexString } from '@nomicfoundation/ethereumjs-util'
import type {
AccountFields,
EVMStateManagerInterface,
StorageDump,
} from '@nomicfoundation/ethereumjs-common'
import type { StorageRange } from '@nomicfoundation/ethereumjs-common/src'
import type { DB, PrefixedHexString } from '@nomicfoundation/ethereumjs-util'
import type { Debugger } from 'debug'
const { debug: createDebugLogger } = debugDefault
function keccak256(msg: Uint8Array): Uint8Array {
return new Uint8Array(bufferKeccak256(Buffer.from(msg)))
}
export type StorageProof = {

@@ -40,2 +62,45 @@ key: PrefixedHexString

type CacheOptions = {
/**
* Allows for cache deactivation
*
* Depending on the use case and underlying datastore (and eventual concurrent cache
* mechanisms there), usage with or without cache can be faster
*
* Default: false
*/
deactivate?: boolean
/**
* Cache type to use.
*
* Available options:
*
* ORDERED_MAP: Cache with no fixed upper bound and dynamic allocation,
* use for dynamic setups like testing or similar.
*
* LRU: LRU cache with pre-allocation of memory and a fixed size.
* Use for larger and more persistent caches.
*/
type?: CacheType
/**
* Size of the cache (only for LRU cache)
*
* Default: 100000 (account cache) / 20000 (storage cache) / 20000 (code cache)
*
* Note: the cache/trie interplay mechanism is designed in a way that
* the theoretical number of max modified accounts between two flush operations
* should be smaller than the cache size, otherwise the cache will "forget" the
* old modifications resulting in an incomplete set of trie-flushed accounts.
*/
size?: number
}
type CacheSettings = {
deactivate: boolean
type: CacheType
size: number
}
/**

@@ -49,3 +114,3 @@ * Prefix to distinguish between a contract deployed with code `0x80`

*/
const CODEHASH_PREFIX = Buffer.from('c')
export const CODEHASH_PREFIX = utf8ToBytes('c')

@@ -67,2 +132,25 @@ /**

prefixCodeHashes?: boolean
/**
* Option to prefix the keys for the storage tries with the first 7 bytes from the
* associated account address. Activating this option gives a noticeable performance
* boost for storage DB reads when operating on larger tries.
*
* Note: Activating/deactivating this option causes continued state reads to be
* incompatible with existing databases.
*
* Default: false (for backwards compatibility reasons)
*/
prefixStorageTrieKeys?: boolean
accountCacheOpts?: CacheOptions
storageCacheOpts?: CacheOptions
codeCacheOpts?: CacheOptions
/**
* The common to use
*/
common?: Common
}

@@ -78,54 +166,203 @@

* The default state manager implementation uses a
* `@nomicfoundation/ethereumjs-trie` trie as a data backend.
* `@ethereumjs/trie` trie as a data backend.
*/
export class DefaultStateManager extends BaseStateManager implements StateManager {
_trie: Trie
_storageTries: { [key: string]: Trie }
export class DefaultStateManager implements EVMStateManagerInterface {
protected _debug: Debugger
protected _accountCache?: AccountCache
protected _storageCache?: StorageCache
protected _codeCache?: CodeCache
private readonly _prefixCodeHashes: boolean
originalStorageCache: OriginalStorageCache
protected _trie: Trie
protected _storageTries: { [key: string]: Trie }
protected readonly _prefixCodeHashes: boolean
protected readonly _prefixStorageTrieKeys: boolean
protected readonly _accountCacheSettings: CacheSettings
protected readonly _storageCacheSettings: CacheSettings
protected readonly _codeCacheSettings: CacheSettings
public readonly common: Common
protected _checkpointCount: number
protected _proofTrie: Trie
private keccakFunction: Function
/**
* StateManager is run in DEBUG mode (default: false)
* Taken from DEBUG environment variable
*
* Safeguards on debug() calls are added for
* performance reasons to avoid string literal evaluation
* @hidden
*/
protected readonly DEBUG: boolean = false
/**
* Instantiate the StateManager interface.
*/
constructor(opts: DefaultStateManagerOpts = {}) {
super(opts)
// 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 =
typeof window === 'undefined' ? process?.env?.DEBUG?.includes('ethjs') ?? false : false
this._trie = opts.trie ?? new Trie({ useKeyHashing: true })
this._debug = createDebugLogger('statemanager:statemanager')
this.common = opts.common ?? new Common({ chain: Chain.Mainnet })
this._proofTrie = new Trie({ useKeyHashing: true, common: this.common })
this._checkpointCount = 0
this._trie = opts.trie ?? new Trie({ useKeyHashing: true, common: this.common })
this._storageTries = {}
this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak256
this.originalStorageCache = new OriginalStorageCache(this.getContractStorage.bind(this))
this._prefixCodeHashes = opts.prefixCodeHashes ?? true
this._prefixStorageTrieKeys = opts.prefixStorageTrieKeys ?? false
this._accountCacheSettings = {
deactivate:
(opts.accountCacheOpts?.deactivate === true || opts.accountCacheOpts?.size === 0) ?? false,
type: opts.accountCacheOpts?.type ?? CacheType.ORDERED_MAP,
size: opts.accountCacheOpts?.size ?? 100000,
}
/*
* For a custom StateManager implementation adopt these
* callbacks passed to the `Cache` instantiated to perform
* the `get`, `put` and `delete` operations with the
* desired backend.
*/
const getCb: getCb = async (address) => {
const rlp = await this._trie.get(address.buf)
return rlp ? Account.fromRlpSerializedAccount(rlp) : undefined
if (!this._accountCacheSettings.deactivate) {
this._accountCache = new AccountCache({
size: this._accountCacheSettings.size,
type: this._accountCacheSettings.type,
})
}
const putCb: putCb = async (keyBuf, accountRlp) => {
const trie = this._trie
await trie.put(keyBuf, accountRlp)
this._storageCacheSettings = {
deactivate:
(opts.storageCacheOpts?.deactivate === true || opts.storageCacheOpts?.size === 0) ?? false,
type: opts.storageCacheOpts?.type ?? CacheType.ORDERED_MAP,
size: opts.storageCacheOpts?.size ?? 20000,
}
const deleteCb = async (keyBuf: Buffer) => {
if (!this._storageCacheSettings.deactivate) {
this._storageCache = new StorageCache({
size: this._storageCacheSettings.size,
type: this._storageCacheSettings.type,
})
}
this._codeCacheSettings = {
deactivate:
(opts.codeCacheOpts?.deactivate === true || opts.codeCacheOpts?.size === 0) ?? false,
type: opts.codeCacheOpts?.type ?? CacheType.ORDERED_MAP,
size: opts.codeCacheOpts?.size ?? 20000,
}
if (!this._codeCacheSettings.deactivate) {
this._codeCache = new CodeCache({
size: this._codeCacheSettings.size,
type: this._codeCacheSettings.type,
})
}
}
/**
* Gets the account associated with `address` or `undefined` if account does not exist
* @param address - Address of the `account` to get
*/
async getAccount(address: Address): Promise<Account | undefined> {
if (!this._accountCacheSettings.deactivate) {
const elem = this._accountCache!.get(address)
if (elem !== undefined) {
return elem.accountRLP !== undefined
? Account.fromRlpSerializedAccount(elem.accountRLP)
: undefined
}
}
const rlp = await this._trie.get(address.bytes)
const account = rlp !== null ? Account.fromRlpSerializedAccount(rlp) : undefined
if (this.DEBUG) {
this._debug(`Get account ${address} from DB (${account ? 'exists' : 'non-existent'})`)
}
this._accountCache?.put(address, account)
return account
}
/**
* Saves an account into state under the provided `address`.
* @param address - Address under which to store `account`
* @param account - The account to store or undefined if to be deleted
*/
async putAccount(address: Address, account: Account | undefined): Promise<void> {
if (this.DEBUG) {
this._debug(
`Save account address=${address} nonce=${account?.nonce} balance=${
account?.balance
} contract=${account && account.isContract() ? 'yes' : 'no'} empty=${
account && account.isEmpty() ? 'yes' : 'no'
}`
)
}
if (this._accountCacheSettings.deactivate) {
const trie = this._trie
await trie.del(keyBuf)
if (account !== undefined) {
await trie.put(address.bytes, account.serialize())
} else {
await trie.del(address.bytes)
}
} else {
if (account !== undefined) {
this._accountCache!.put(address, account)
} else {
this._accountCache!.del(address)
}
}
this._cache = new Cache({ getCb, putCb, deleteCb })
}
/**
* Copies the current instance of the `StateManager`
* at the last fully committed point, i.e. as if all current
* checkpoints were reverted.
* Gets the account associated with `address`, modifies the given account
* fields, then saves the account into state. Account fields can include
* `nonce`, `balance`, `storageRoot`, and `codeHash`.
* @param address - Address of the account to modify
* @param accountFields - Object containing account fields and values to modify
*/
copy(): StateManager {
return new DefaultStateManager({
trie: this._trie.copy(false),
})
async modifyAccountFields(address: Address, accountFields: AccountFields): Promise<void> {
let account = await this.getAccount(address)
if (!account) {
account = new Account()
}
account.nonce = accountFields.nonce ?? account.nonce
account.balance = accountFields.balance ?? account.balance
account.storageRoot = accountFields.storageRoot ?? account.storageRoot
account.codeHash = accountFields.codeHash ?? account.codeHash
await this.putAccount(address, account)
}
/**
* Deletes an account from state under the provided `address`.
* @param address - Address of the account which should be deleted
*/
async deleteAccount(address: Address) {
if (this.DEBUG) {
this._debug(`Delete account ${address}`)
}
this._codeCache?.del(address)
if (this._accountCacheSettings.deactivate) {
await this._trie.del(address.bytes)
} else {
this._accountCache!.del(address)
}
if (!this._storageCacheSettings.deactivate) {
this._storageCache?.clearContractStorage(address)
}
}
/**
* Adds `value` to the state trie as code, and sets `codeHash` on the account

@@ -136,16 +373,16 @@ * corresponding to `address` to reference this.

*/
async putContractCode(address: Address, value: Buffer): Promise<void> {
const codeHash = Buffer.from(keccak256(value))
if (codeHash.equals(KECCAK256_NULL)) {
async putContractCode(address: Address, value: Uint8Array): Promise<void> {
this._codeCache?.put(address, value)
const codeHash = this.keccakFunction(value)
if (equalsBytes(codeHash, KECCAK256_NULL)) {
return
}
const key = this._prefixCodeHashes ? Buffer.concat([CODEHASH_PREFIX, codeHash]) : codeHash
// @ts-expect-error
await this._trie._db.put(key, value)
if (this.DEBUG) {
this._debug(`Update codeHash (-> ${short(codeHash)}) for account ${address}`)
}
if ((await this.getAccount(address)) === undefined) {
await this.putAccount(address, new Account())
}
await this.modifyAccountFields(address, { codeHash })

@@ -157,29 +394,58 @@ }

* @param address - Address to get the `code` for
* @returns {Promise<Buffer>} - Resolves with the code corresponding to the provided address.
* Returns an empty `Buffer` if the account has no associated code.
* @returns {Promise<Uint8Array>} - Resolves with the code corresponding to the provided address.
* Returns an empty `Uint8Array` if the account has no associated code.
*/
async getContractCode(address: Address): Promise<Buffer> {
async getContractCode(address: Address): Promise<Uint8Array> {
if (!this._codeCacheSettings.deactivate) {
const elem = this._codeCache?.get(address)
if (elem !== undefined) {
return elem.code ?? new Uint8Array(0)
}
}
const account = await this.getAccount(address)
if (!account) {
return new Uint8Array(0)
}
if (!account.isContract()) {
return Buffer.alloc(0)
return new Uint8Array(0)
}
const key = this._prefixCodeHashes
? Buffer.concat([CODEHASH_PREFIX, account.codeHash])
? concatBytes(CODEHASH_PREFIX, account.codeHash)
: account.codeHash
// @ts-expect-error
const code = await this._trie._db.get(key)
return code ?? Buffer.alloc(0)
const code = (await this._trie.database().get(key)) ?? new Uint8Array(0)
if (!this._codeCacheSettings.deactivate) {
this._codeCache!.put(address, code)
}
return code
}
/**
* Creates a storage trie from the primary storage trie
* for an account and saves this in the storage cache.
* Gets the storage trie for an account from the storage
* cache or does a lookup.
* @private
*/
async _lookupStorageTrie(address: Address): Promise<Trie> {
// from state trie
const account = await this.getAccount(address)
const storageTrie = this._trie.copy(false)
storageTrie.root(account.storageRoot)
storageTrie.flushCheckpoints()
// TODO PR: have a better interface for hashed address pull?
protected _getStorageTrie(addressOrHash: Address | Uint8Array, account?: Account): Trie {
// use hashed key for lookup from storage cache
const addressHex = bytesToUnprefixedHex(
addressOrHash instanceof Address ? this.keccakFunction(addressOrHash.bytes) : addressOrHash
)
let storageTrie = this._storageTries[addressHex]
if (storageTrie === undefined) {
const keyPrefix = this._prefixStorageTrieKeys
? (addressOrHash instanceof Address
? this.keccakFunction(addressOrHash.bytes)
: addressOrHash
).slice(0, 7)
: undefined
storageTrie = this._trie.shallowCopy(false, { keyPrefix })
if (account !== undefined) {
storageTrie.root(account.storageRoot)
} else {
storageTrie.root(storageTrie.EMPTY_TRIE_ROOT)
}
storageTrie.flushCheckpoints()
this._storageTries[addressHex] = storageTrie
}
return storageTrie

@@ -193,14 +459,16 @@ }

*/
async _getStorageTrie(address: Address): Promise<Trie> {
// from storage cache
const addressHex = address.buf.toString('hex')
let storageTrie = this._storageTries[addressHex]
if (storageTrie === undefined || storageTrie === null) {
// lookup from state
storageTrie = await this._lookupStorageTrie(address)
}
return storageTrie
protected _getAccountTrie(): Trie {
return this._trie
}
/**
* Gets the storage trie for an account from the storage
* cache or does a lookup.
* @private
*/
protected _getCodeDB(): DB {
return this._trie.database()
}
/**
* Gets the storage value associated with the provided `address` and `key`. This method returns

@@ -210,14 +478,28 @@ * the shortest representation of the stored value.

* @param key - Key in the account's storage to get the value for. Must be 32 bytes long.
* @returns {Promise<Buffer>} - The storage value for the account
* @returns - The storage value for the account
* corresponding to the provided address at the provided key.
* If this does not exist an empty `Buffer` is returned.
* If this does not exist an empty `Uint8Array` is returned.
*/
async getContractStorage(address: Address, key: Buffer): Promise<Buffer> {
async getContractStorage(address: Address, key: Uint8Array): Promise<Uint8Array> {
if (key.length !== 32) {
throw new Error('Storage key must be 32 bytes long')
}
if (!this._storageCacheSettings.deactivate) {
const value = this._storageCache!.get(address, key)
if (value !== undefined) {
const decoded = RLP.decode(value ?? new Uint8Array(0)) as Uint8Array
return decoded
}
}
const trie = await this._getStorageTrie(address)
const account = await this.getAccount(address)
if (!account) {
throw new Error('getContractStorage() called on non-existing account')
}
const trie = this._getStorageTrie(address, account)
const value = await trie.get(key)
const decoded = Buffer.from(RLP.decode(Uint8Array.from(value ?? [])) as Uint8Array)
if (!this._storageCacheSettings.deactivate) {
this._storageCache?.put(address, key, value ?? hexToBytes('0x80'))
}
const decoded = RLP.decode(value ?? new Uint8Array(0)) as Uint8Array
return decoded

@@ -232,4 +514,5 @@ }

*/
async _modifyContractStorage(
protected async _modifyContractStorage(
address: Address,
account: Account,
modifyTrie: (storageTrie: Trie, done: Function) => void

@@ -239,14 +522,12 @@ ): Promise<void> {

return new Promise(async (resolve) => {
const storageTrie = await this._getStorageTrie(address)
const storageTrie = this._getStorageTrie(address, account)
modifyTrie(storageTrie, async () => {
// update storage cache
const addressHex = address.buf.toString('hex')
const addressHex = bytesToUnprefixedHex(address.bytes)
this._storageTries[addressHex] = storageTrie
// update contract storageRoot
const contract = this._cache.get(address)
contract.storageRoot = storageTrie.root()
await this.putAccount(address, contract)
account.storageRoot = storageTrie.root()
await this.putAccount(address, account)
resolve()

@@ -257,2 +538,27 @@ })

protected async _writeContractStorage(
address: Address,
account: Account,
key: Uint8Array,
value: Uint8Array
) {
await this._modifyContractStorage(address, account, async (storageTrie, done) => {
if (value instanceof Uint8Array && value.length) {
// format input
const encodedValue = RLP.encode(value)
if (this.DEBUG) {
this._debug(`Update contract storage for account ${address} to ${short(value)}`)
}
await storageTrie.put(key, encodedValue)
} else {
// deleting a value
if (this.DEBUG) {
this._debug(`Delete contract storage for account`)
}
await storageTrie.del(key)
}
done()
})
}
/**

@@ -263,5 +569,7 @@ * Adds value to the state trie for the `account`

* @param key - Key to set the value at. Must be 32 bytes long.
* @param value - Value to set at `key` for account corresponding to `address`. Cannot be more than 32 bytes. Leading zeros are stripped. If it is a empty or filled with zeros, deletes the value.
* @param value - Value to set at `key` for account corresponding to `address`.
* Cannot be more than 32 bytes. Leading zeros are stripped.
* If it is a empty or filled with zeros, deletes the value.
*/
async putContractStorage(address: Address, key: Buffer, value: Buffer): Promise<void> {
async putContractStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise<void> {
if (key.length !== 32) {

@@ -275,21 +583,14 @@ throw new Error('Storage key must be 32 bytes long')

value = unpadBuffer(value)
const account = await this.getAccount(address)
if (!account) {
throw new Error('putContractStorage() called on non-existing account')
}
await this._modifyContractStorage(address, async (storageTrie, done) => {
if (Buffer.isBuffer(value) && value.length) {
// format input
const encodedValue = Buffer.from(RLP.encode(Uint8Array.from(value)))
if (this.DEBUG) {
this._debug(`Update contract storage for account ${address} to ${short(value)}`)
}
await storageTrie.put(key, encodedValue)
} else {
// deleting a value
if (this.DEBUG) {
this._debug(`Delete contract storage for account`)
}
await storageTrie.del(key)
}
done()
})
value = unpadBytes(value)
if (!this._storageCacheSettings.deactivate) {
const encodedValue = RLP.encode(value)
this._storageCache!.put(address, key, encodedValue)
} else {
await this._writeContractStorage(address, account, key, value)
}
}

@@ -299,6 +600,11 @@

* Clears all storage entries for the account corresponding to `address`.
* @param address - Address to clear the storage of
* @param address - Address to clear the storage of
*/
async clearContractStorage(address: Address): Promise<void> {
await this._modifyContractStorage(address, (storageTrie, done) => {
let account = await this.getAccount(address)
if (!account) {
account = new Account()
}
this._storageCache?.clearContractStorage(address)
await this._modifyContractStorage(address, account, (storageTrie, done) => {
storageTrie.root(storageTrie.EMPTY_TRIE_ROOT)

@@ -316,3 +622,6 @@ done()

this._trie.checkpoint()
await super.checkpoint()
this._storageCache?.checkpoint()
this._accountCache?.checkpoint()
this._codeCache?.checkpoint()
this._checkpointCount++
}

@@ -327,3 +636,15 @@

await this._trie.commit()
await super.commit()
this._storageCache?.commit()
this._accountCache?.commit()
this._codeCache?.commit()
this._checkpointCount--
if (this._checkpointCount === 0) {
await this.flush()
this.originalStorageCache.clear()
}
if (this.DEBUG) {
this._debug(`state checkpoint committed`)
}
}

@@ -338,7 +659,75 @@

await this._trie.revert()
this._storageCache?.revert()
this._accountCache?.revert()
this._codeCache?.revert()
this._storageTries = {}
await super.revert()
this._checkpointCount--
if (this._checkpointCount === 0) {
await this.flush()
this.originalStorageCache.clear()
}
}
/**
* Writes all cache items to the trie
*/
async flush(): Promise<void> {
if (!this._codeCacheSettings.deactivate) {
const items = this._codeCache!.flush()
for (const item of items) {
const addr = Address.fromString(`0x${item[0]}`)
const code = item[1].code
if (code === undefined) {
continue
}
// update code in database
const codeHash = this.keccakFunction(code)
const key = this._prefixCodeHashes ? concatBytes(CODEHASH_PREFIX, codeHash) : codeHash
await this._getCodeDB().put(key, code)
// update code root of associated account
if ((await this.getAccount(addr)) === undefined) {
await this.putAccount(addr, new Account())
}
await this.modifyAccountFields(addr, { codeHash })
}
}
if (!this._storageCacheSettings.deactivate) {
const items = this._storageCache!.flush()
for (const item of items) {
const address = Address.fromString(`0x${item[0]}`)
const keyHex = item[1]
const keyBytes = unprefixedHexToBytes(keyHex)
const value = item[2]
const decoded = RLP.decode(value ?? new Uint8Array(0)) as Uint8Array
const account = await this.getAccount(address)
if (account) {
await this._writeContractStorage(address, account, keyBytes, decoded)
}
}
}
if (!this._accountCacheSettings.deactivate) {
const items = this._accountCache!.flush()
for (const item of items) {
const addressHex = item[0]
const addressBytes = unprefixedHexToBytes(addressHex)
const elem = item[1]
if (elem.accountRLP === undefined) {
const trie = this._trie
await trie.del(addressBytes)
} else {
const trie = this._trie
await trie.put(addressBytes, elem.accountRLP)
}
}
}
}
/**
* Get an EIP-1186 proof

@@ -348,19 +737,30 @@ * @param address address to get proof of

*/
async getProof(address: Address, storageSlots: Buffer[] = []): Promise<Proof> {
async getProof(address: Address, storageSlots: Uint8Array[] = []): Promise<Proof> {
await this.flush()
const account = await this.getAccount(address)
const accountProof: PrefixedHexString[] = (await this._trie.createProof(address.buf)).map((p) =>
bufferToHex(p)
if (!account) {
// throw new Error(`getProof() can only be called for an existing account`)
const returnValue: Proof = {
address: address.toString(),
balance: '0x0',
codeHash: KECCAK256_NULL_S,
nonce: '0x0',
storageHash: KECCAK256_RLP_S,
accountProof: (await this._trie.createProof(address.bytes)).map((p) => bytesToHex(p)),
storageProof: [],
}
return returnValue
}
const accountProof: PrefixedHexString[] = (await this._trie.createProof(address.bytes)).map(
(p) => bytesToHex(p)
)
const storageProof: StorageProof[] = []
const storageTrie = await this._getStorageTrie(address)
const storageTrie = this._getStorageTrie(address, account)
for (const storageKey of storageSlots) {
const proof = (await storageTrie.createProof(storageKey)).map((p) => bufferToHex(p))
let value = bufferToHex(await this.getContractStorage(address, storageKey))
if (value === '0x') {
value = '0x0'
}
const proof = (await storageTrie.createProof(storageKey)).map((p) => bytesToHex(p))
const value = bytesToHex(await this.getContractStorage(address, storageKey))
const proofItem: StorageProof = {
key: bufferToHex(storageKey),
value,
key: bytesToHex(storageKey),
value: value === '0x' ? '0x0' : value, // Return '0x' values as '0x0' since this is a JSON RPC response
proof,

@@ -374,5 +774,5 @@ }

balance: bigIntToHex(account.balance),
codeHash: bufferToHex(account.codeHash),
codeHash: bytesToHex(account.codeHash),
nonce: bigIntToHex(account.nonce),
storageHash: bufferToHex(account.storageRoot),
storageHash: bytesToHex(account.storageRoot),
accountProof,

@@ -385,2 +785,89 @@ storageProof,

/**
* Create a StateManager and initialize this with proof(s) gotten previously from getProof
* This generates a (partial) StateManager where one can retrieve all items from the proof
* @param proof Either a proof retrieved from `getProof`, or an array of those proofs
* @param safe Wether or not to verify that the roots of the proof items match the reported roots
* @param verifyRoot verify that all proof root nodes match statemanager's stateroot - should be
* set to `false` when constructing a state manager where the underlying trie has proof nodes from different state roots
* @returns A new DefaultStateManager with elements from the given proof included in its backing state trie
*/
static async fromProof(
proof: Proof | Proof[],
safe: boolean = false,
opts: DefaultStateManagerOpts = {}
): Promise<DefaultStateManager> {
if (Array.isArray(proof)) {
if (proof.length === 0) {
return new DefaultStateManager(opts)
} else {
const trie =
opts.trie ??
(await Trie.createTrieFromProof(
proof[0].accountProof.map((e) => hexToBytes(e)),
{ useKeyHashing: true }
))
const sm = new DefaultStateManager({ ...opts, trie })
const address = Address.fromString(proof[0].address)
await sm.addStorageProof(proof[0].storageProof, proof[0].storageHash, address, safe)
for (let i = 1; i < proof.length; i++) {
const proofItem = proof[i]
await sm.addProofData(proofItem, true)
}
await sm.flush() // TODO verify if this is necessary
return sm
}
} else {
return DefaultStateManager.fromProof([proof])
}
}
/**
* Adds a storage proof to the state manager
* @param storageProof The storage proof
* @param storageHash The root hash of the storage trie
* @param address The address
* @param safe Whether or not to verify if the reported roots match the current storage root
*/
private async addStorageProof(
storageProof: StorageProof[],
storageHash: string,
address: Address,
safe: boolean = false
) {
const trie = this._getStorageTrie(address)
trie.root(hexToBytes(storageHash))
for (let i = 0; i < storageProof.length; i++) {
await trie.updateTrieFromProof(
storageProof[i].proof.map((e) => hexToBytes(e)),
safe
)
}
}
/**
* Add proof(s) into an already existing trie
* @param proof The proof(s) retrieved from `getProof`
* @param verifyRoot verify that all proof root nodes match statemanager's stateroot - should be
* set to `false` when constructing a state manager where the underlying trie has proof nodes from different state roots
*/
async addProofData(proof: Proof | Proof[], safe: boolean = false) {
if (Array.isArray(proof)) {
for (let i = 0; i < proof.length; i++) {
await this._trie.updateTrieFromProof(
proof[i].accountProof.map((e) => hexToBytes(e)),
safe
)
await this.addStorageProof(
proof[i].storageProof,
proof[i].storageHash,
Address.fromString(proof[i].address),
safe
)
}
} else {
await this.addProofData([proof], safe)
}
}
/**
* Verify an EIP-1186 proof. Throws if proof is invalid, otherwise returns true.

@@ -390,6 +877,6 @@ * @param proof the proof to prove

async verifyProof(proof: Proof): Promise<boolean> {
const rootHash = Buffer.from(keccak256(toBuffer(proof.accountProof[0])))
const key = toBuffer(proof.address)
const rootHash = this.keccakFunction(hexToBytes(proof.accountProof[0]))
const key = hexToBytes(proof.address)
const accountProof = proof.accountProof.map((rlpString: PrefixedHexString) =>
toBuffer(rlpString)
hexToBytes(rlpString)
)

@@ -399,22 +886,22 @@

// Verify that it matches the reported account.
const value = await new Trie({ useKeyHashing: true }).verifyProof(rootHash, key, accountProof)
const value = await this._proofTrie.verifyProof(rootHash, key, accountProof)
if (value === null) {
// Verify that the account is empty in the proof.
const emptyBuffer = Buffer.from('')
const emptyBytes = new Uint8Array(0)
const notEmptyErrorMsg = 'Invalid proof provided: account is not empty'
const nonce = unpadBuffer(toBuffer(proof.nonce))
if (!nonce.equals(emptyBuffer)) {
const nonce = unpadBytes(hexToBytes(proof.nonce))
if (!equalsBytes(nonce, emptyBytes)) {
throw new Error(`${notEmptyErrorMsg} (nonce is not zero)`)
}
const balance = unpadBuffer(toBuffer(proof.balance))
if (!balance.equals(emptyBuffer)) {
const balance = unpadBytes(hexToBytes(proof.balance))
if (!equalsBytes(balance, emptyBytes)) {
throw new Error(`${notEmptyErrorMsg} (balance is not zero)`)
}
const storageHash = toBuffer(proof.storageHash)
if (!storageHash.equals(KECCAK256_RLP)) {
const storageHash = hexToBytes(proof.storageHash)
if (!equalsBytes(storageHash, KECCAK256_RLP)) {
throw new Error(`${notEmptyErrorMsg} (storageHash does not equal KECCAK256_RLP)`)
}
const codeHash = toBuffer(proof.codeHash)
if (!codeHash.equals(KECCAK256_NULL)) {
const codeHash = hexToBytes(proof.codeHash)
if (!equalsBytes(codeHash, KECCAK256_NULL)) {
throw new Error(`${notEmptyErrorMsg} (codeHash does not equal KECCAK256_NULL)`)

@@ -432,6 +919,6 @@ }

}
if (!storageRoot.equals(toBuffer(proof.storageHash))) {
if (!equalsBytes(storageRoot, hexToBytes(proof.storageHash))) {
throw new Error(`${invalidErrorMsg} storageHash does not match`)
}
if (!codeHash.equals(toBuffer(proof.codeHash))) {
if (!equalsBytes(codeHash, hexToBytes(proof.codeHash))) {
throw new Error(`${invalidErrorMsg} codeHash does not match`)

@@ -441,18 +928,14 @@ }

const storageRoot = toBuffer(proof.storageHash)
const storageRoot = hexToBytes(proof.storageHash)
for (const stProof of proof.storageProof) {
const storageProof = stProof.proof.map((value: PrefixedHexString) => toBuffer(value))
const storageValue = setLengthLeft(toBuffer(stProof.value), 32)
const storageKey = toBuffer(stProof.key)
const proofValue = await new Trie({ useKeyHashing: true }).verifyProof(
storageRoot,
storageKey,
storageProof
)
const storageProof = stProof.proof.map((value: PrefixedHexString) => hexToBytes(value))
const storageValue = setLengthLeft(hexToBytes(stProof.value), 32)
const storageKey = hexToBytes(stProof.key)
const proofValue = await this._proofTrie.verifyProof(storageRoot, storageKey, storageProof)
const reportedValue = setLengthLeft(
Buffer.from(RLP.decode(Uint8Array.from((proofValue as Buffer) ?? [])) as Uint8Array),
RLP.decode(proofValue ?? new Uint8Array(0)) as Uint8Array,
32
)
if (!reportedValue.equals(storageValue)) {
if (!equalsBytes(reportedValue, storageValue)) {
throw new Error('Reported trie value does not match storage')

@@ -468,6 +951,6 @@ }

* checkpoints on the instance.
* @returns {Promise<Buffer>} - Returns the state-root of the `StateManager`
* @returns {Promise<Uint8Array>} - Returns the state-root of the `StateManager`
*/
async getStateRoot(): Promise<Buffer> {
await this._cache.flush()
async getStateRoot(): Promise<Uint8Array> {
await this.flush()
return this._trie.root()

@@ -483,6 +966,6 @@ }

*/
async setStateRoot(stateRoot: Buffer): Promise<void> {
await this._cache.flush()
async setStateRoot(stateRoot: Uint8Array, clearCache: boolean = true): Promise<void> {
await this.flush()
if (!stateRoot.equals(this._trie.EMPTY_TRIE_ROOT)) {
if (!equalsBytes(stateRoot, this._trie.EMPTY_TRIE_ROOT)) {
const hasRoot = await this._trie.checkRoot(stateRoot)

@@ -495,3 +978,11 @@ if (!hasRoot) {

this._trie.root(stateRoot)
this._cache.clear()
if (this._accountCache !== undefined && clearCache) {
this._accountCache.clear()
}
if (this._storageCache !== undefined && clearCache) {
this._storageCache.clear()
}
if (this._codeCache !== undefined && clearCache) {
this._codeCache!.clear()
}
this._storageTries = {}

@@ -508,25 +999,111 @@ }

async dumpStorage(address: Address): Promise<StorageDump> {
return new Promise((resolve, reject) => {
this._getStorageTrie(address)
.then((trie) => {
const storage: StorageDump = {}
const stream = trie.createReadStream()
await this.flush()
const account = await this.getAccount(address)
if (!account) {
throw new Error(`dumpStorage f() can only be called for an existing account`)
}
const trie = this._getStorageTrie(address, account)
const storage: StorageDump = {}
const stream = trie.createAsyncReadStream()
stream.on('data', (val: any) => {
storage[val.key.toString('hex')] = val.value.toString('hex')
})
stream.on('end', () => {
resolve(storage)
})
})
.catch((e) => {
reject(e)
})
})
for await (const chunk of stream) {
storage[bytesToHex(chunk.key)] = bytesToHex(chunk.value)
}
return storage
}
/**
Dumps a limited number of RLP-encoded storage values for an account specified by `address`,
starting from `startKey` or greater.
@param address - The address of the `account` to return storage for.
@param startKey - The bigint representation of the smallest storage key that will be returned.
@param limit - The maximum number of storage values that will be returned.
@returns {Promise<StorageRange>} - A {@link StorageRange} object that will contain at most `limit` entries in its `storage` field.
The object will also contain `nextKey`, the next (hashed) storage key after the range included in `storage`.
*/
async dumpStorageRange(address: Address, startKey: bigint, limit: number): Promise<StorageRange> {
if (!Number.isSafeInteger(limit) || limit < 0) {
throw new Error(`Limit is not a proper uint.`)
}
await this.flush()
const account = await this.getAccount(address)
if (!account) {
throw new Error(`Account does not exist.`)
}
const trie = this._getStorageTrie(address, account)
let inRange = false
let i = 0
/** Object conforming to {@link StorageRange.storage}. */
const storageMap: StorageRange['storage'] = {}
const stream = trie.createAsyncReadStream()
for await (const chunk of stream) {
if (!inRange) {
// Check if the key is already in the correct range.
if (bytesToBigInt(chunk.key) >= startKey) {
inRange = true
} else {
continue
}
}
if (i < limit) {
storageMap[bytesToHex(chunk.key)] = { key: null, value: bytesToHex(chunk.value) }
i++
} else if (i === limit) {
return {
storage: storageMap,
nextKey: bytesToHex(chunk.key),
}
}
}
return {
storage: storageMap,
nextKey: null,
}
}
/**
* Initializes the provided genesis state into the state trie.
* Will error if there are uncommitted checkpoints on the instance.
* @param initState address -> balance | [balance, code, storage]
*/
async generateCanonicalGenesis(initState: any): Promise<void> {
if (this._checkpointCount !== 0) {
throw new Error('Cannot create genesis state with uncommitted checkpoints')
}
if (this.DEBUG) {
this._debug(`Save genesis state into the state trie`)
}
const addresses = Object.keys(initState)
for (const address of addresses) {
const addr = Address.fromString(address)
const state = initState[address]
if (!Array.isArray(state)) {
// Prior format: address -> balance
const account = Account.fromAccountData({ balance: state })
await this.putAccount(addr, account)
} else {
// New format: address -> [balance, code, storage]
const [balance, code, storage, nonce] = state
const account = Account.fromAccountData({ balance, nonce })
await this.putAccount(addr, account)
if (code !== undefined) {
await this.putContractCode(addr, toBytes(code))
}
if (storage !== undefined) {
for (const [key, value] of storage) {
await this.putContractStorage(addr, toBytes(key), toBytes(value))
}
}
}
}
await this.flush()
}
/**
* Checks whether there is a state corresponding to a stateRoot
*/
async hasStateRoot(root: Buffer): Promise<boolean> {
async hasStateRoot(root: Uint8Array): Promise<boolean> {
return this._trie.checkRoot(root)

@@ -536,20 +1113,63 @@ }

/**
* Checks if the `account` corresponding to `address`
* exists
* @param address - Address of the `account` to check
* Copies the current instance of the `StateManager`
* at the last fully committed point, i.e. as if all current
* checkpoints were reverted.
*
* Caches are downleveled (so: adopted for short-term usage)
* by default.
*
* This means in particular:
* 1. For caches instantiated as an LRU cache type
* the copy() method will instantiate with an ORDERED_MAP cache
* instead, since copied instantances are mostly used in
* short-term usage contexts and LRU cache instantation would create
* a large overhead here.
* 2. The underlying trie object is initialized with 0 cache size
*
* Both adoptions can be deactivated by setting `downlevelCaches` to
* `false`.
*
* Cache values are generally not copied along regardless of the
* `downlevelCaches` setting.
*/
async accountExists(address: Address): Promise<boolean> {
const account = this._cache.lookup(address)
if (
account &&
((account as any).virtual === undefined || (account as any).virtual === false) &&
!this._cache.keyIsDeleted(address)
) {
return true
shallowCopy(downlevelCaches = true): DefaultStateManager {
const common = this.common.copy()
common.setHardfork(this.common.hardfork())
const cacheSize = !downlevelCaches ? this._trie['_opts']['cacheSize'] : 0
const trie = this._trie.shallowCopy(false, { cacheSize })
const prefixCodeHashes = this._prefixCodeHashes
const prefixStorageTrieKeys = this._prefixStorageTrieKeys
let accountCacheOpts = { ...this._accountCacheSettings }
if (downlevelCaches && !this._accountCacheSettings.deactivate) {
accountCacheOpts = { ...accountCacheOpts, type: CacheType.ORDERED_MAP }
}
if (await this._trie.get(address.buf)) {
return true
let storageCacheOpts = { ...this._storageCacheSettings }
if (downlevelCaches && !this._storageCacheSettings.deactivate) {
storageCacheOpts = { ...storageCacheOpts, type: CacheType.ORDERED_MAP }
}
return false
let codeCacheOpts = { ...this._codeCacheSettings }
if (!this._codeCacheSettings.deactivate) {
codeCacheOpts = { ...codeCacheOpts, type: CacheType.ORDERED_MAP }
}
return new DefaultStateManager({
common,
trie,
prefixStorageTrieKeys,
prefixCodeHashes,
accountCacheOpts,
storageCacheOpts,
codeCacheOpts,
})
}
/**
* Clears all underlying caches
*/
clearCaches() {
this._accountCache?.clear()
this._storageCache?.clear()
this._codeCache?.clear()
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc