diff --git a/packages/client/src/execution/vmexecution.ts b/packages/client/src/execution/vmexecution.ts index 2d7a0090b1..460b049a83 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -4,10 +4,15 @@ import { DBSetHashToNumber, DBSetTD, } from '@ethereumjs/blockchain' -import { CacheType, ConsensusType, Hardfork } from '@ethereumjs/common' +import { ConsensusType, Hardfork } from '@ethereumjs/common' import { MCLBLS, RustBN254 } from '@ethereumjs/evm' import { getGenesis } from '@ethereumjs/genesis' -import { DefaultStateManager, StatelessVerkleStateManager } from '@ethereumjs/statemanager' +import { + CacheType, + Caches, + DefaultStateManager, + StatelessVerkleStateManager, +} from '@ethereumjs/statemanager' import { createTrie } from '@ethereumjs/trie' import { BIGINT_0, @@ -35,7 +40,6 @@ import { ReceiptsManager } from './receipt.js' import type { ExecutionOptions } from './execution.js' import type { Block } from '@ethereumjs/block' -import type { Trie } from '@ethereumjs/trie' import type { PrefixedHexString } from '@ethereumjs/util' import type { RunBlockOpts, TxReceipt } from '@ethereumjs/vm' @@ -160,21 +164,20 @@ export class VMExecution extends Execution { const stateManager = new DefaultStateManager({ trie, prefixStorageTrieKeys: this.config.prefixStorageTrieKeys, - accountCacheOpts: { - deactivate: false, - type: CacheType.LRU, - size: this.config.accountCache, - }, - storageCacheOpts: { - deactivate: false, - type: CacheType.LRU, - size: this.config.storageCache, - }, - codeCacheOpts: { - deactivate: false, - type: CacheType.LRU, - size: this.config.codeCache, - }, + caches: new Caches({ + account: { + type: CacheType.LRU, + size: this.config.accountCache, + }, + storage: { + type: CacheType.LRU, + size: this.config.storageCache, + }, + code: { + type: CacheType.LRU, + size: this.config.codeCache, + }, + }), common: this.config.chainCommon, }) @@ -1059,25 +1062,22 @@ export class VMExecution extends Execution { stats() { if (this._statsVM instanceof DefaultStateManager) { - const sm = this._statsVM.stateManager as any - const disactivatedStats = { size: 0, reads: 0, hits: 0, writes: 0 } + const sm = this._statsVM.stateManager as DefaultStateManager + const deactivatedStats = { size: 0, reads: 0, hits: 0, writes: 0 } let stats - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - stats = !sm._accountCacheSettings.deactivate ? sm._accountCache.stats() : disactivatedStats + stats = sm['_caches']?.account?.stats() ?? deactivatedStats this.config.logger.info( `Account cache stats size=${stats.size} reads=${stats.reads} hits=${stats.hits} writes=${stats.writes}`, ) - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - stats = !sm._storageCacheSettings.deactivate ? sm._storageCache.stats() : disactivatedStats + stats = sm['_caches']?.storage?.stats() ?? deactivatedStats this.config.logger.info( `Storage cache stats size=${stats.size} reads=${stats.reads} hits=${stats.hits} writes=${stats.writes}`, ) - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - stats = !sm._codeCacheSettings.deactivate ? sm._codeCache.stats() : disactivatedStats + stats = sm['_caches']?.code?.stats() ?? deactivatedStats this.config.logger.info( `Code cache stats size=${stats.size} reads=${stats.reads} hits=${stats.hits} writes=${stats.writes}`, ) - const tStats = (sm._trie as Trie).database().stats() + const tStats = sm['_trie'].database().stats() this.config.logger.info( `Trie cache stats size=${tStats.size} reads=${tStats.cache.reads} hits=${tStats.cache.hits} ` + `writes=${tStats.cache.writes} readsDB=${tStats.db.reads} hitsDB=${tStats.db.hits} writesDB=${tStats.db.writes}`, diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 6cdb852b77..36da556bd4 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -45,8 +45,8 @@ import type { EthProtocol } from '../../net/protocol/index.js' import type { FullEthereumService, Service } from '../../service/index.js' import type { RpcTx } from '../types.js' import type { Block, JsonRpcBlock } from '@ethereumjs/block' -import type { Proof } from '@ethereumjs/common' import type { Log } from '@ethereumjs/evm' +import type { Proof } from '@ethereumjs/statemanager' import type { FeeMarketEIP1559Transaction, LegacyTransaction, diff --git a/packages/common/src/interfaces.ts b/packages/common/src/interfaces.ts index a6b5967131..f8c224e38f 100644 --- a/packages/common/src/interfaces.ts +++ b/packages/common/src/interfaces.ts @@ -169,35 +169,4 @@ export interface StateManagerInterface { */ clearCaches(): void shallowCopy(downlevelCaches?: boolean): StateManagerInterface - - /* - * Cache properties - */ - _accountCache?: Cache - _storageCache?: Cache - _codeCache?: Cache - - _accountCacheSettings?: CacheSettings - _storageCacheSettings?: CacheSettings - _codeCacheSettings?: CacheSettings -} - -/** - * Cache related - */ -export enum CacheType { - LRU = 'lru', - ORDERED_MAP = 'ordered_map', -} - -export type CacheSettings = { - deactivate: boolean - type: CacheType - size: number -} - -interface Cache { - checkpoint(): void - commit(): void - revert(): void } diff --git a/packages/statemanager/src/cache/account.ts b/packages/statemanager/src/cache/account.ts index eedb7ef5c5..9837178873 100644 --- a/packages/statemanager/src/cache/account.ts +++ b/packages/statemanager/src/cache/account.ts @@ -1,10 +1,10 @@ -import { CacheType } from '@ethereumjs/common' import { bytesToUnprefixedHex } from '@ethereumjs/util' import { OrderedMap } from '@js-sdsl/ordered-map' import debugDefault from 'debug' import { LRUCache } from 'lru-cache' import { Cache } from './cache.js' +import { CacheType } from './types.js' import type { CacheOpts } from './types.js' import type { Account, Address } from '@ethereumjs/util' @@ -68,14 +68,14 @@ export class AccountCache extends Cache { put( address: Address, account: Account | undefined, - couldBeParitalAccount: boolean = false, + couldBePartialAccount: boolean = false, ): void { const addressHex = bytesToUnprefixedHex(address.bytes) this._saveCachePreState(addressHex) const elem = { accountRLP: account !== undefined - ? couldBeParitalAccount + ? couldBePartialAccount ? account.serializeWithPartialInfo() : account.serialize() : undefined, diff --git a/packages/statemanager/src/cache/caches.ts b/packages/statemanager/src/cache/caches.ts new file mode 100644 index 0000000000..1373d02f16 --- /dev/null +++ b/packages/statemanager/src/cache/caches.ts @@ -0,0 +1,89 @@ +import { AccountCache } from './account.js' +import { CodeCache } from './code.js' +import { StorageCache } from './storage.js' +import { CacheType, type CachesStateManagerOpts } from './types.js' + +import type { CacheOpts } from './types.js' +import type { Address } from '@ethereumjs/util' + +export class Caches { + account?: AccountCache + code?: CodeCache + storage?: StorageCache + + settings: Record<'account' | 'code' | 'storage', CacheOpts> + + constructor(opts: CachesStateManagerOpts = {}) { + const accountSettings = { + type: opts.account?.type ?? CacheType.ORDERED_MAP, + size: opts.account?.size ?? 100000, + } + + const codeSettings = { + type: opts.code?.type ?? CacheType.ORDERED_MAP, + size: opts.code?.size ?? 20000, + } + + const storageSettings = { + type: opts.storage?.type ?? CacheType.ORDERED_MAP, + size: opts.storage?.size ?? 20000, + } + + this.settings = { + account: accountSettings, + code: codeSettings, + storage: storageSettings, + } + + if (this.settings.account.size !== 0) { + this.account = new AccountCache({ + size: this.settings.account.size, + type: this.settings.account.type, + }) + } + + if (this.settings.code.size !== 0) { + this.code = new CodeCache({ + size: this.settings.code.size, + type: this.settings.code.type, + }) + } + + if (this.settings.storage.size !== 0) { + this.storage = new StorageCache({ + size: this.settings.storage.size, + type: this.settings.storage.type, + }) + } + } + + checkpoint() { + this.account?.checkpoint() + this.storage?.checkpoint() + this.code?.checkpoint() + } + + clear() { + this.account?.clear() + this.storage?.clear() + this.code?.clear() + } + + commit() { + this.account?.commit() + this.storage?.commit() + this.code?.commit() + } + + deleteAccount(address: Address) { + this.code?.del(address) + this.account?.del(address) + this.storage?.clearStorage(address) + } + + revert() { + this.account?.revert() + this.storage?.revert() + this.code?.revert() + } +} diff --git a/packages/statemanager/src/cache/code.ts b/packages/statemanager/src/cache/code.ts index 61a957de62..3aaeb25db8 100644 --- a/packages/statemanager/src/cache/code.ts +++ b/packages/statemanager/src/cache/code.ts @@ -1,10 +1,10 @@ -import { CacheType } from '@ethereumjs/common' import { bytesToUnprefixedHex } from '@ethereumjs/util' import { OrderedMap } from '@js-sdsl/ordered-map' import debugDefault from 'debug' import { LRUCache } from 'lru-cache' import { Cache } from './cache.js' +import { CacheType } from './types.js' import type { CacheOpts } from './types.js' import type { Address } from '@ethereumjs/util' diff --git a/packages/statemanager/src/cache/index.ts b/packages/statemanager/src/cache/index.ts index e19c3405e7..799b350c1b 100644 --- a/packages/statemanager/src/cache/index.ts +++ b/packages/statemanager/src/cache/index.ts @@ -1,4 +1,5 @@ export * from './account.js' +export * from './caches.js' export * from './code.js' export * from './originalStorageCache.js' export * from './storage.js' diff --git a/packages/statemanager/src/cache/storage.ts b/packages/statemanager/src/cache/storage.ts index 6c9c153e14..345e74ad3b 100644 --- a/packages/statemanager/src/cache/storage.ts +++ b/packages/statemanager/src/cache/storage.ts @@ -1,10 +1,10 @@ -import { CacheType } from '@ethereumjs/common' import { bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util' import { OrderedMap } from '@js-sdsl/ordered-map' import debugDefault from 'debug' import { LRUCache } from 'lru-cache' import { Cache } from './cache.js' +import { CacheType } from './types.js' import type { CacheOpts } from './types.js' import type { Address } from '@ethereumjs/util' diff --git a/packages/statemanager/src/cache/types.ts b/packages/statemanager/src/cache/types.ts index 7f283d7c65..e0819913e0 100644 --- a/packages/statemanager/src/cache/types.ts +++ b/packages/statemanager/src/cache/types.ts @@ -1,6 +1,36 @@ -import type { CacheType } from '@ethereumjs/common' +export enum CacheType { + LRU = 'lru', + ORDERED_MAP = 'ordered_map', +} export interface CacheOpts { + /** + * 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 + /** + * 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 } + +export interface CachesStateManagerOpts { + account?: Partial + code?: Partial + storage?: Partial +} diff --git a/packages/statemanager/src/capabilities.ts b/packages/statemanager/src/capabilities.ts deleted file mode 100644 index 9b48cd0468..0000000000 --- a/packages/statemanager/src/capabilities.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { type AccountFields, CacheType, type StateManagerInterface } from '@ethereumjs/common' -import { Account } from '@ethereumjs/util' - -import { AccountCache, CodeCache, OriginalStorageCache, StorageCache } from './cache/index.js' - -import type { CacheStateManagerOpts } from './types.js' -import type { Address } from '@ethereumjs/util' - -export function checkpointCaches(stateManager: StateManagerInterface): void { - stateManager._accountCache?.checkpoint() - stateManager._storageCache?.checkpoint() - stateManager._codeCache?.checkpoint() -} - -export function commitCaches(stateManager: StateManagerInterface): void { - stateManager._accountCache?.commit() - stateManager._storageCache?.commit() - stateManager._codeCache?.commit() -} - -export function initializeCaches( - stateManager: StateManagerInterface, - options: CacheStateManagerOpts, -): void { - stateManager.originalStorageCache = new OriginalStorageCache( - stateManager.getStorage.bind(stateManager), - ) - - stateManager._accountCacheSettings = { - deactivate: options.accountCacheOpts?.deactivate ?? false, - type: options.accountCacheOpts?.type ?? CacheType.ORDERED_MAP, - size: options.accountCacheOpts?.size ?? 100000, - } - - if (stateManager._accountCacheSettings.deactivate === false) { - stateManager._accountCache = new AccountCache({ - size: stateManager._accountCacheSettings.size, - type: stateManager._accountCacheSettings.type, - }) - } - - stateManager._storageCacheSettings = { - deactivate: options.storageCacheOpts?.deactivate ?? false, - type: options.storageCacheOpts?.type ?? CacheType.ORDERED_MAP, - size: options.storageCacheOpts?.size ?? 20000, - } - - if (stateManager._storageCacheSettings.deactivate === false) { - stateManager._storageCache = new StorageCache({ - size: stateManager._storageCacheSettings.size, - type: stateManager._storageCacheSettings.type, - }) - } - - stateManager._codeCacheSettings = { - deactivate: - (options.codeCacheOpts?.deactivate === true || options.codeCacheOpts?.size === 0) ?? false, - type: options.codeCacheOpts?.type ?? CacheType.ORDERED_MAP, - size: options.codeCacheOpts?.size ?? 20000, - } - - if (stateManager._codeCacheSettings.deactivate === false) { - stateManager._codeCache = new CodeCache({ - size: stateManager._codeCacheSettings.size, - type: stateManager._codeCacheSettings.type, - }) - } -} - -export async function modifyAccountFields( - stateManager: StateManagerInterface, - address: Address, - accountFields: AccountFields, -): Promise { - const account = (await stateManager.getAccount(address)) ?? 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 stateManager.putAccount(address, account) -} - -export function revertCaches(stateManager: StateManagerInterface): void { - stateManager._accountCache?.revert() - stateManager._storageCache?.revert() - stateManager._codeCache?.revert() -} diff --git a/packages/statemanager/src/rpcStateManager.ts b/packages/statemanager/src/rpcStateManager.ts index 2c6016aac4..f00f2f5b28 100644 --- a/packages/statemanager/src/rpcStateManager.ts +++ b/packages/statemanager/src/rpcStateManager.ts @@ -1,4 +1,4 @@ -import { CacheType, Common, Mainnet } from '@ethereumjs/common' +import { Common, Mainnet } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { verifyTrieProof } from '@ethereumjs/trie' import { @@ -16,11 +16,11 @@ import { import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { AccountCache, OriginalStorageCache, StorageCache } from './cache/index.js' -import * as Capabilities from './capabilities.js' +import { Caches, OriginalStorageCache } from './cache/index.js' +import { modifyAccountFields } from './util.js' -import type { RPCStateManagerOpts } from './index.js' -import type { AccountFields, Proof, StateManagerInterface, StorageDump } from '@ethereumjs/common' +import type { Proof, RPCStateManagerOpts } from './index.js' +import type { AccountFields, StateManagerInterface, StorageDump } from '@ethereumjs/common' import type { Address, PrefixedHexString } from '@ethereumjs/util' import type { Debugger } from 'debug' @@ -28,14 +28,10 @@ const KECCAK256_RLP_EMPTY_ACCOUNT = RLP.encode(new Account().serialize()).slice( export class RPCStateManager implements StateManagerInterface { protected _provider: string + protected _caches: Caches protected _blockTag: string - - _accountCache: AccountCache - _storageCache: StorageCache - _contractCache: Map - originalStorageCache: OriginalStorageCache - _debug: Debugger + protected _debug: Debugger protected DEBUG: boolean private keccakFunction: Function public readonly common: Common @@ -55,9 +51,11 @@ export class RPCStateManager implements StateManagerInterface { this._blockTag = opts.blockTag === 'earliest' ? opts.blockTag : bigIntToHex(opts.blockTag) - this._contractCache = new Map() - this._storageCache = new StorageCache({ size: 100000, type: CacheType.ORDERED_MAP }) - this._accountCache = new AccountCache({ size: 100000, type: CacheType.ORDERED_MAP }) + this._caches = new Caches({ storage: { size: 100000 }, code: { size: 100000 } }) + + // this._contractCache = new Map() + // this._storageCache = new StorageCache({ size: 100000, type: CacheType.ORDERED_MAP }) + // this._accountCache = new AccountCache({ size: 100000, type: CacheType.ORDERED_MAP }) this.originalStorageCache = new OriginalStorageCache(this.getStorage.bind(this)) this.common = opts.common ?? new Common({ chain: Mainnet }) @@ -74,15 +72,8 @@ export class RPCStateManager implements StateManagerInterface { provider: this._provider, blockTag: BigInt(this._blockTag), }) - newState._contractCache = new Map(this._contractCache) - newState._storageCache = new StorageCache({ - size: 100000, - type: CacheType.ORDERED_MAP, - }) - newState._accountCache = new AccountCache({ - size: 100000, - type: CacheType.ORDERED_MAP, - }) + newState._caches = new Caches({ storage: { size: 100000 } }) + return newState } @@ -102,9 +93,7 @@ export class RPCStateManager implements StateManagerInterface { * initially be retrieved from the provider */ clearCaches(): void { - this._contractCache.clear() - this._storageCache.clear() - this._accountCache.clear() + this._caches.clear() } /** @@ -114,14 +103,14 @@ export class RPCStateManager implements StateManagerInterface { * Returns an empty `Uint8Array` if the account has no associated code. */ async getCode(address: Address): Promise { - let codeBytes = this._contractCache.get(address.toString()) + let codeBytes = this._caches.code?.get(address)?.code if (codeBytes !== undefined) return codeBytes const code = await fetchFromProvider(this._provider, { method: 'eth_getCode', params: [address.toString(), this._blockTag], }) codeBytes = toBytes(code) - this._contractCache.set(address.toString(), codeBytes) + this._caches.code?.put(address, codeBytes) return codeBytes } @@ -138,7 +127,7 @@ export class RPCStateManager implements StateManagerInterface { */ async putCode(address: Address, value: Uint8Array): Promise { // Store contract code in the cache - this._contractCache.set(address.toString(), value) + this._caches.code?.put(address, value) } /** @@ -156,7 +145,7 @@ export class RPCStateManager implements StateManagerInterface { throw new Error('Storage key must be 32 bytes long') } - let value = this._storageCache!.get(address, key) + let value = this._caches.storage?.get(address, key) if (value !== undefined) { return value } @@ -182,7 +171,7 @@ export class RPCStateManager implements StateManagerInterface { * If it is empty or filled with zeros, deletes the value. */ async putStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise { - this._storageCache.put(address, key, value) + this._caches.storage?.put(address, key, value) } /** @@ -190,7 +179,7 @@ export class RPCStateManager implements StateManagerInterface { * @param address - Address to clear the storage of */ async clearStorage(address: Address): Promise { - this._storageCache.clearStorage(address) + this._caches.storage?.clearStorage(address) } /** @@ -201,7 +190,7 @@ export class RPCStateManager implements StateManagerInterface { * Both are represented as `0x` prefixed hex strings. */ dumpStorage(address: Address): Promise { - const storageMap = this._storageCache.dump(address) + const storageMap = this._caches.storage?.dump(address) const dump: StorageDump = {} if (storageMap !== undefined) { for (const slot of storageMap) { @@ -218,7 +207,7 @@ export class RPCStateManager implements StateManagerInterface { async accountExists(address: Address): Promise { if (this.DEBUG) this._debug?.(`verify if ${address.toString()} exists`) - const localAccount = this._accountCache.get(address) + const localAccount = this._caches.account?.get(address) if (localAccount !== undefined) return true // Get merkle proof for `address` from provider const proof = await fetchFromProvider(this._provider, { @@ -240,7 +229,7 @@ export class RPCStateManager implements StateManagerInterface { * @param address - Address of the `account` to get */ async getAccount(address: Address): Promise { - const elem = this._accountCache?.get(address) + const elem = this._caches.account?.get(address) if (elem !== undefined) { return elem.accountRLP !== undefined ? createAccountFromRLP(elem.accountRLP) : undefined } @@ -252,7 +241,7 @@ export class RPCStateManager implements StateManagerInterface { ? undefined : createAccountFromRLP(accountFromProvider.serialize()) - this._accountCache?.put(address, account) + this._caches.account?.put(address, account) return account } @@ -293,9 +282,9 @@ export class RPCStateManager implements StateManagerInterface { ) } if (account !== undefined) { - this._accountCache!.put(address, account) + this._caches.account!.put(address, account) } else { - this._accountCache!.del(address) + this._caches.account!.del(address) } } @@ -320,7 +309,7 @@ export class RPCStateManager implements StateManagerInterface { ), ) } - await Capabilities.modifyAccountFields(this, address, accountFields) + await modifyAccountFields(this, address, accountFields) } /** @@ -331,7 +320,7 @@ export class RPCStateManager implements StateManagerInterface { if (this.DEBUG) { this._debug(`deleting account corresponding to ${address.toString()}`) } - this._accountCache.del(address) + this._caches.account?.del(address) } /** @@ -344,11 +333,7 @@ export class RPCStateManager implements StateManagerInterface { if (this.DEBUG) this._debug(`retrieving proof from provider for ${address.toString()}`) const proof = await fetchFromProvider(this._provider, { method: 'eth_getProof', - params: [ - address.toString(), - [storageSlots.map((slot) => bytesToHex(slot))], - this._blockTag, - ] as any, + params: [address.toString(), storageSlots.map(bytesToHex).join(','), this._blockTag], }) return proof @@ -368,12 +353,9 @@ export class RPCStateManager implements StateManagerInterface { * Checkpoints the current state of the StateManager instance. * State changes that follow can then be committed by calling * `commit` or `reverted` by calling rollback. - * - * Partial implementation, called from the subclass. */ async checkpoint(): Promise { - this._accountCache.checkpoint() - this._storageCache.checkpoint() + this._caches.checkpoint() } /** @@ -384,7 +366,7 @@ export class RPCStateManager implements StateManagerInterface { */ async commit(): Promise { // setup cache checkpointing - this._accountCache.commit() + this._caches.account?.commit() } /** @@ -394,13 +376,11 @@ export class RPCStateManager implements StateManagerInterface { * Partial implementation , called from the subclass. */ async revert(): Promise { - this._accountCache.revert() - this._storageCache.revert() - this._contractCache.clear() + this._caches.revert() } async flush(): Promise { - this._accountCache.flush() + this._caches.account?.flush() } /** diff --git a/packages/statemanager/src/simpleStateManager.ts b/packages/statemanager/src/simpleStateManager.ts index 58d6977a96..30bf009641 100644 --- a/packages/statemanager/src/simpleStateManager.ts +++ b/packages/statemanager/src/simpleStateManager.ts @@ -2,7 +2,7 @@ import { Account, bytesToHex } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { OriginalStorageCache } from './cache/originalStorageCache.js' -import * as Capabilities from './capabilities.js' +import { modifyAccountFields } from './util.js' import type { SimpleStateManagerOpts } from './index.js' import type { AccountFields, Common, StateManagerInterface } from '@ethereumjs/common' @@ -79,7 +79,7 @@ export class SimpleStateManager implements StateManagerInterface { } async modifyAccountFields(address: Address, accountFields: AccountFields): Promise { - await Capabilities.modifyAccountFields(this, address, accountFields) + await modifyAccountFields(this, address, accountFields) } async getCode(address: Address): Promise { diff --git a/packages/statemanager/src/stateManager.ts b/packages/statemanager/src/stateManager.ts index 7daf7e1917..e973137b95 100644 --- a/packages/statemanager/src/stateManager.ts +++ b/packages/statemanager/src/stateManager.ts @@ -1,4 +1,4 @@ -import { CacheType, Common, Mainnet } from '@ethereumjs/common' +import { Common, Mainnet } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { Trie, @@ -31,17 +31,17 @@ import { import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import * as Capabilities from './capabilities.js' +import { CacheType, OriginalStorageCache } from './cache/index.js' +import { modifyAccountFields } from './util.js' -import { CODEHASH_PREFIX, type CacheSettings, type DefaultStateManagerOpts } from './index.js' +import { CODEHASH_PREFIX, Caches, type DefaultStateManagerOpts } from './index.js' -import type { AccountCache, CodeCache, OriginalStorageCache, StorageCache } from './cache/index.js' +import type { StorageProof } from './index.js' import type { AccountFields, Proof, StateManagerInterface, StorageDump, - StorageProof, StorageRange, } from '@ethereumjs/common' import type { Address, DB, PrefixedHexString } from '@ethereumjs/util' @@ -64,9 +64,9 @@ import type { Debugger } from 'debug' */ export class DefaultStateManager implements StateManagerInterface { protected _debug: Debugger - _accountCache?: AccountCache - _storageCache?: StorageCache - _codeCache?: CodeCache + protected _caches?: Caches + + originalStorageCache: OriginalStorageCache protected _trie: Trie protected _storageTries: { [key: string]: Trie } @@ -74,12 +74,6 @@ export class DefaultStateManager implements StateManagerInterface { protected readonly _prefixCodeHashes: boolean protected readonly _prefixStorageTrieKeys: boolean - // Non-null assertion necessary to inform TypeScript that these properties are set in the constructor through a helper function - originalStorageCache!: OriginalStorageCache - readonly _accountCacheSettings!: CacheSettings - readonly _storageCacheSettings!: CacheSettings - readonly _codeCacheSettings!: CacheSettings - public readonly common: Common protected _checkpointCount: number @@ -116,10 +110,12 @@ export class DefaultStateManager implements StateManagerInterface { this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak256 + this.originalStorageCache = new OriginalStorageCache(this.getStorage.bind(this)) + this._prefixCodeHashes = opts.prefixCodeHashes ?? true this._prefixStorageTrieKeys = opts.prefixStorageTrieKeys ?? false - Capabilities.initializeCaches(this, opts) + this._caches = opts.caches } /** @@ -127,11 +123,9 @@ export class DefaultStateManager implements StateManagerInterface { * @param address - Address of the `account` to get */ async getAccount(address: Address): Promise { - if (!this._accountCacheSettings.deactivate) { - const elem = this._accountCache!.get(address) - if (elem !== undefined) { - return elem.accountRLP !== undefined ? createAccountFromRLP(elem.accountRLP) : undefined - } + const elem = this._caches?.account?.get(address) + if (elem !== undefined) { + return elem.accountRLP !== undefined ? createAccountFromRLP(elem.accountRLP) : undefined } const rlp = await this._trie.get(address.bytes) @@ -139,7 +133,7 @@ export class DefaultStateManager implements StateManagerInterface { if (this.DEBUG) { this._debug(`Get account ${address} from DB (${account ? 'exists' : 'non-existent'})`) } - this._accountCache?.put(address, account) + this._caches?.account?.put(address, account) return account } @@ -158,7 +152,7 @@ export class DefaultStateManager implements StateManagerInterface { }`, ) } - if (this._accountCacheSettings.deactivate) { + if (this._caches?.account === undefined) { const trie = this._trie if (account !== undefined) { await trie.put(address.bytes, account.serialize()) @@ -167,9 +161,9 @@ export class DefaultStateManager implements StateManagerInterface { } } else { if (account !== undefined) { - this._accountCache!.put(address, account) + this._caches.account?.put(address, account) } else { - this._accountCache!.del(address) + this._caches.account?.del(address) } } } @@ -182,7 +176,7 @@ export class DefaultStateManager implements StateManagerInterface { * @param accountFields - Object containing account fields and values to modify */ async modifyAccountFields(address: Address, accountFields: AccountFields): Promise { - await Capabilities.modifyAccountFields(this, address, accountFields) + await modifyAccountFields(this, address, accountFields) } /** @@ -194,15 +188,10 @@ export class DefaultStateManager implements StateManagerInterface { this._debug(`Delete account ${address}`) } - this._codeCache?.del(address) + this._caches?.deleteAccount(address) - if (this._accountCacheSettings.deactivate) { + if (this._caches?.account === undefined) { await this._trie.del(address.bytes) - } else { - this._accountCache!.del(address) - } - if (!this._storageCacheSettings.deactivate) { - this._storageCache?.clearStorage(address) } } @@ -213,7 +202,7 @@ export class DefaultStateManager implements StateManagerInterface { * @param value - The value of the `code` */ async putCode(address: Address, value: Uint8Array): Promise { - this._codeCache?.put(address, value) + this._caches?.code?.put(address, value) const codeHash = this.keccakFunction(value) if (this.DEBUG) { @@ -233,11 +222,9 @@ export class DefaultStateManager implements StateManagerInterface { * Returns an empty `Uint8Array` if the account has no associated code. */ async getCode(address: Address): Promise { - if (!this._codeCacheSettings.deactivate) { - const elem = this._codeCache?.get(address) - if (elem !== undefined) { - return elem.code ?? new Uint8Array(0) - } + const elem = this._caches?.code?.get(address) + if (elem !== undefined) { + return elem.code ?? new Uint8Array(0) } const account = await this.getAccount(address) if (!account) { @@ -251,9 +238,7 @@ export class DefaultStateManager implements StateManagerInterface { : account.codeHash const code = (await this._trie.database().get(key)) ?? new Uint8Array(0) - if (!this._codeCacheSettings.deactivate) { - this._codeCache!.put(address, code) - } + this._caches?.code?.put(address, code) return code } @@ -333,12 +318,10 @@ export class DefaultStateManager implements StateManagerInterface { 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 cachedValue = this._caches?.storage?.get(address, key) + if (cachedValue !== undefined) { + const decoded = RLP.decode(cachedValue ?? new Uint8Array(0)) as Uint8Array + return decoded } const account = await this.getAccount(address) @@ -347,9 +330,7 @@ export class DefaultStateManager implements StateManagerInterface { } const trie = this._getStorageTrie(address, account) const value = await trie.get(key) - if (!this._storageCacheSettings.deactivate) { - this._storageCache?.put(address, key, value ?? hexToBytes('0x80')) - } + this._caches?.storage?.put(address, key, value ?? hexToBytes('0x80')) const decoded = RLP.decode(value ?? new Uint8Array(0)) as Uint8Array return decoded } @@ -431,12 +412,8 @@ export class DefaultStateManager implements StateManagerInterface { } 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) - } + this._caches?.storage?.put(address, key, RLP.encode(value)) ?? + (await this._writeContractStorage(address, account, key, value)) } /** @@ -448,7 +425,7 @@ export class DefaultStateManager implements StateManagerInterface { if (!account) { account = new Account() } - this._storageCache?.clearStorage(address) + this._caches?.storage?.clearStorage(address) await this._modifyContractStorage(address, account, (storageTrie, done) => { storageTrie.root(storageTrie.EMPTY_TRIE_ROOT) done() @@ -462,7 +439,7 @@ export class DefaultStateManager implements StateManagerInterface { */ async checkpoint(): Promise { this._trie.checkpoint() - Capabilities.checkpointCaches(this) + this._caches?.checkpoint() this._checkpointCount++ } @@ -473,7 +450,7 @@ export class DefaultStateManager implements StateManagerInterface { async commit(): Promise { // setup trie checkpointing await this._trie.commit() - Capabilities.commitCaches(this) + this._caches?.commit() this._checkpointCount-- if (this._checkpointCount === 0) { @@ -493,7 +470,7 @@ export class DefaultStateManager implements StateManagerInterface { async revert(): Promise { // setup trie checkpointing await this._trie.revert() - Capabilities.revertCaches(this) + this._caches?.revert() this._storageTries = {} @@ -509,56 +486,51 @@ export class DefaultStateManager implements StateManagerInterface { * Writes all cache items to the trie */ async flush(): Promise { - if (!this._codeCacheSettings.deactivate) { - const items = this._codeCache!.flush() - for (const item of items) { - const addr = createAddressFromString(`0x${item[0]}`) - - const code = item[1].code - if (code === undefined) { - continue - } + const codeItems = this._caches?.code?.flush() ?? [] + for (const item of codeItems) { + const addr = createAddressFromString(`0x${item[0]}`) - // update code in database - const codeHash = this.keccakFunction(code) - const key = this._prefixCodeHashes ? concatBytes(CODEHASH_PREFIX, codeHash) : codeHash - await this._getCodeDB().put(key, code) + const code = item[1].code + if (code === undefined) { + continue + } - // update code root of associated account - if ((await this.getAccount(addr)) === undefined) { - await this.putAccount(addr, new Account()) - } - await this.modifyAccountFields(addr, { codeHash }) + // 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()) } - } - if (!this._storageCacheSettings.deactivate) { - const items = this._storageCache!.flush() - for (const item of items) { - const address = createAddressFromString(`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) - } + await this.modifyAccountFields(addr, { codeHash }) + } + const storageItems = this._caches?.storage?.flush() ?? [] + for (const item of storageItems) { + const address = createAddressFromString(`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) - } + + const accountItems = this._caches?.account?.flush() ?? [] + for (const item of accountItems) { + 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) } } } @@ -808,14 +780,8 @@ export class DefaultStateManager implements StateManagerInterface { } this._trie.root(stateRoot) - if (this._accountCache !== undefined && clearCache) { - this._accountCache.clear() - } - if (this._storageCache !== undefined && clearCache) { - this._storageCache.clear() - } - if (this._codeCache !== undefined && clearCache) { - this._codeCache!.clear() + if (clearCache) { + this._caches?.clear() } this._storageTries = {} } @@ -955,16 +921,16 @@ export class DefaultStateManager implements StateManagerInterface { const trie = this._trie.shallowCopy(false, { cacheSize }) const prefixCodeHashes = this._prefixCodeHashes const prefixStorageTrieKeys = this._prefixStorageTrieKeys - let accountCacheOpts = { ...this._accountCacheSettings } - if (downlevelCaches && !this._accountCacheSettings.deactivate) { + let accountCacheOpts = { ...this._caches?.settings.account } + if (downlevelCaches && this._caches?.settings.account.size !== 0) { accountCacheOpts = { ...accountCacheOpts, type: CacheType.ORDERED_MAP } } - let storageCacheOpts = { ...this._storageCacheSettings } - if (downlevelCaches && !this._storageCacheSettings.deactivate) { + let storageCacheOpts = { ...this._caches?.settings.storage } + if (downlevelCaches && this._caches?.settings.storage.size !== 0) { storageCacheOpts = { ...storageCacheOpts, type: CacheType.ORDERED_MAP } } - let codeCacheOpts = { ...this._codeCacheSettings } - if (!this._codeCacheSettings.deactivate) { + let codeCacheOpts = { ...this._caches?.settings.code } + if (this._caches?.settings.code.size !== 0) { codeCacheOpts = { ...codeCacheOpts, type: CacheType.ORDERED_MAP } } @@ -973,9 +939,11 @@ export class DefaultStateManager implements StateManagerInterface { trie, prefixStorageTrieKeys, prefixCodeHashes, - accountCacheOpts, - storageCacheOpts, - codeCacheOpts, + caches: new Caches({ + account: accountCacheOpts, + code: codeCacheOpts, + storage: storageCacheOpts, + }), }) } @@ -983,9 +951,7 @@ export class DefaultStateManager implements StateManagerInterface { * Clears all underlying caches */ clearCaches() { - this._accountCache?.clear() - this._storageCache?.clear() - this._codeCache?.clear() + this._caches?.clear() } /** diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index cc9b93d15f..0b19a3bad6 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -25,16 +25,11 @@ import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { AccessWitness, AccessedStateType, decodeValue } from './accessWitness.js' -import * as Capabilities from './capabilities.js' - -import { - type CacheSettings, - type StatelessVerkleStateManagerOpts, - type VerkleState, -} from './index.js' +import { Caches, OriginalStorageCache } from './cache/index.js' +import { modifyAccountFields } from './util.js' import type { AccessedStateWithAddress } from './accessWitness.js' -import type { AccountCache, CodeCache, OriginalStorageCache, StorageCache } from './cache/index.js' +import type { StatelessVerkleStateManagerOpts, VerkleState } from './index.js' import type { DefaultStateManager } from './stateManager.js' import type { AccountFields, Proof, StateManagerInterface } from '@ethereumjs/common' import type { @@ -67,18 +62,13 @@ const ZEROVALUE = '0x00000000000000000000000000000000000000000000000000000000000 * */ export class StatelessVerkleStateManager implements StateManagerInterface { - _accountCache?: AccountCache - _storageCache?: StorageCache - _codeCache?: CodeCache _cachedStateRoot?: Uint8Array + originalStorageCache: OriginalStorageCache + verkleCrypto: VerkleCrypto - // Non-null assertion necessary to inform TypeScript that these properties are set in the constructor through a helper function - originalStorageCache!: OriginalStorageCache - readonly _accountCacheSettings!: CacheSettings - readonly _storageCacheSettings!: CacheSettings - readonly _codeCacheSettings!: CacheSettings + protected _caches?: Caches /** * StateManager is run in DEBUG mode (default: false) @@ -113,7 +103,9 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * Instantiate the StateManager interface. */ constructor(opts: StatelessVerkleStateManagerOpts) { - Capabilities.initializeCaches(this, opts) + this.originalStorageCache = new OriginalStorageCache(this.getStorage.bind(this)) + + this._caches = opts.caches this._cachedStateRoot = opts.initialStateRoot @@ -214,8 +206,11 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * at the last fully committed point, i.e. as if all current * checkpoints were reverted. */ - shallowCopy(): StateManagerInterface { - const stateManager = new StatelessVerkleStateManager({ verkleCrypto: this.verkleCrypto }) + shallowCopy(): StatelessVerkleStateManager { + const stateManager = new StatelessVerkleStateManager({ + caches: this._caches !== undefined ? new Caches() : undefined, + verkleCrypto: this.verkleCrypto, + }) return stateManager } @@ -230,7 +225,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { debug(`putCode address=${address.toString()} value=${short(value)}`) } - this._codeCache?.put(address, value) + this._caches?.code?.put(address, value) const codeHash = keccak256(value) if (KECCAK256_NULL === codeHash) { // If the code hash is the null hash, no code has to be stored @@ -254,11 +249,9 @@ export class StatelessVerkleStateManager implements StateManagerInterface { debug(`getCode address=${address.toString()}`) } - if (!this._codeCacheSettings.deactivate) { - const elem = this._codeCache?.get(address) - if (elem !== undefined) { - return elem.code ?? new Uint8Array(0) - } + const elem = this._caches?.code?.get(address) + if (elem !== undefined) { + return elem.code ?? new Uint8Array(0) } const account = await this.getAccount(address) @@ -301,26 +294,22 @@ export class StatelessVerkleStateManager implements StateManagerInterface { // Return accessedCode where only accessed code has been copied const contactCode = accessedCode.slice(0, codeSize) - if (!this._codeCacheSettings.deactivate) { - this._codeCache?.put(address, contactCode) - } + this._caches?.code?.put(address, contactCode) return contactCode } async getCodeSize(address: Address): Promise { - if (!this._accountCacheSettings.deactivate) { - const elem = this._accountCache!.get(address) - if (elem !== undefined) { - const account = - elem.accountRLP !== undefined ? createPartialAccountFromRLP(elem.accountRLP) : undefined - if (account === undefined) { - const errorMsg = `account=${account} in cache` - debug(errorMsg) - throw Error(errorMsg) - } - return account.codeSize + const elem = this._caches?.account?.get(address) + if (elem !== undefined) { + const account = + elem.accountRLP !== undefined ? createPartialAccountFromRLP(elem.accountRLP) : undefined + if (account === undefined) { + const errorMsg = `account=${account} in cache` + debug(errorMsg) + throw Error(errorMsg) } + return account.codeSize } // load the account basic fields and codeSize should be in it @@ -341,11 +330,9 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * If this does not exist an empty `Uint8Array` is returned. */ async getStorage(address: Address, key: Uint8Array): Promise { - if (!this._storageCacheSettings.deactivate) { - const value = this._storageCache!.get(address, key) - if (value !== undefined) { - return value - } + const value = this._caches?.storage?.get(address, key) + if (value !== undefined) { + return value } const storageKey = await getVerkleTreeKeyForStorageSlot( @@ -355,9 +342,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { ) const storageValue = toBytes(this._state[bytesToHex(storageKey)]) - if (!this._storageCacheSettings.deactivate) { - this._storageCache?.put(address, key, storageValue ?? hexToBytes('0x80')) - } + this._caches?.storage?.put(address, key, storageValue ?? hexToBytes('0x80')) return storageValue } @@ -370,8 +355,8 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * @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 putStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise { - if (!this._storageCacheSettings.deactivate) { - this._storageCache!.put(address, key, value) + if (this._caches?.storage !== undefined) { + this._caches.storage.put(address, key, value) } else { // TODO: Consider refactoring this in a writeContractStorage function? Like in stateManager.ts const storageKey = await getVerkleTreeKeyForStorageSlot( @@ -392,19 +377,17 @@ export class StatelessVerkleStateManager implements StateManagerInterface { async clearStorage(address: Address): Promise { const stem = getVerkleStem(this.verkleCrypto, address, 0) const codeHashKey = getVerkleKey(stem, VerkleLeafType.CodeHash) - this._storageCache?.clearStorage(address) + this._caches?.storage?.clearStorage(address) // Update codeHash to `c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` this._state[bytesToHex(codeHashKey)] = KECCAK256_NULL_S } async getAccount(address: Address): Promise { - if (!this._accountCacheSettings.deactivate) { - const elem = this._accountCache!.get(address) - if (elem !== undefined) { - return elem.accountRLP !== undefined - ? createPartialAccountFromRLP(elem.accountRLP) - : undefined - } + const elem = this._caches?.account?.get(address) + if (elem !== undefined) { + return elem.accountRLP !== undefined + ? createPartialAccountFromRLP(elem.accountRLP) + : undefined } const stem = getVerkleStem(this.verkleCrypto, address, 0) @@ -481,9 +464,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { debug(`getAccount address=${address.toString()} stem=${short(stem)}`) } - if (!this._accountCacheSettings.deactivate) { - this._accountCache?.put(address, account, true) - } + this._caches?.account?.put(address, account, true) return account } @@ -493,7 +474,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { debug(`putAccount address=${address.toString()}`) } - if (this._accountCacheSettings.deactivate) { + if (this._caches?.account === undefined) { const stem = getVerkleStem(this.verkleCrypto, address, 0) const balanceKey = getVerkleKey(stem, VerkleLeafType.Balance) const nonceKey = getVerkleKey(stem, VerkleLeafType.Nonce) @@ -507,9 +488,9 @@ export class StatelessVerkleStateManager implements StateManagerInterface { this._state[bytesToHex(codeHashKey)] = bytesToHex(account.codeHash) } else { if (account !== undefined) { - this._accountCache!.put(address, account, true) + this._caches?.account?.put(address, account, true) } else { - this._accountCache!.del(address) + this._caches?.account?.del(address) } } } @@ -523,16 +504,11 @@ export class StatelessVerkleStateManager implements StateManagerInterface { debug(`Delete account ${address}`) } - this._codeCache?.del(address) - this._accountCache!.del(address) - - if (!this._storageCacheSettings.deactivate) { - this._storageCache?.clearStorage(address) - } + this._caches?.deleteAccount(address) } async modifyAccountFields(address: Address, accountFields: AccountFields): Promise { - await Capabilities.modifyAccountFields(this, address, accountFields) + await modifyAccountFields(this, address, accountFields) } getProof(_: Address, __: Uint8Array[] = []): Promise { @@ -555,12 +531,12 @@ export class StatelessVerkleStateManager implements StateManagerInterface { // Verifies that the witness post-state matches the computed post-state verifyPostState(): boolean { // track what all chunks were accessed so as to compare in the end if any chunks were missed - // in access while comparising against the provided poststate in the execution witness + // in access while comparing against the provided poststate in the execution witness const accessedChunks = new Map() // switch to false if postVerify fails let postFailures = 0 - for (const accessedState of this.accessWitness!.accesses()) { + for (const accessedState of this.accessWitness?.accesses() ?? []) { const { address, type } = accessedState let extraMeta = '' if (accessedState.type === AccessedStateType.Code) { @@ -642,7 +618,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { const { address, type } = accessedState switch (type) { case AccessedStateType.Version: { - const encodedAccount = this._accountCache?.get(address)?.accountRLP + const encodedAccount = this._caches?.account?.get(address)?.accountRLP if (encodedAccount === undefined) { return null } @@ -651,7 +627,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { return ZEROVALUE } case AccessedStateType.Balance: { - const encodedAccount = this._accountCache?.get(address)?.accountRLP + const encodedAccount = this._caches?.account?.get(address)?.accountRLP if (encodedAccount === undefined) { return null } @@ -661,7 +637,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } case AccessedStateType.Nonce: { - const encodedAccount = this._accountCache?.get(address)?.accountRLP + const encodedAccount = this._caches?.account?.get(address)?.accountRLP if (encodedAccount === undefined) { return null } @@ -670,7 +646,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } case AccessedStateType.CodeHash: { - const encodedAccount = this._accountCache?.get(address)?.accountRLP + const encodedAccount = this._caches?.account?.get(address)?.accountRLP if (encodedAccount === undefined) { return null } @@ -678,10 +654,10 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } case AccessedStateType.CodeSize: { - const codeSize = this._codeCache?.get(address)?.code?.length + const codeSize = this._caches?.code?.get(address)?.code?.length if (codeSize === undefined) { // it could be an EOA lets check for that - const encodedAccount = this._accountCache?.get(address)?.accountRLP + const encodedAccount = this._caches?.account?.get(address)?.accountRLP if (encodedAccount === undefined) { return null } @@ -701,7 +677,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { case AccessedStateType.Code: { const { codeOffset } = accessedState - const code = this._codeCache?.get(address)?.code + const code = this._caches?.code?.get(address)?.code if (code === undefined) { return null } @@ -717,7 +693,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { const { slot } = accessedState const key = setLengthLeft(bigIntToBytes(slot), 32) - const storage = this._storageCache?.get(address, key) + const storage = this._caches?.storage?.get(address, key) if (storage === undefined) { return null } @@ -733,7 +709,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { */ async checkpoint(): Promise { this._checkpoints.push(this._state) - Capabilities.checkpointCaches(this) + this._caches?.checkpoint() } /** @@ -742,7 +718,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { */ async commit(): Promise { this._checkpoints.pop() - Capabilities.commitCaches(this) + this._caches?.commit() } // TODO @@ -757,7 +733,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { async revert(): Promise { // setup trie checkpointing this._checkpoints.pop() - Capabilities.revertCaches(this) + this._caches?.revert() } /** @@ -791,9 +767,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * Clears all underlying caches */ clearCaches() { - this._accountCache?.clear() - this._codeCache?.clear() - this._storageCache?.clear() + this._caches?.clear() } // TODO: Removing this causes a Kaustinen6 test in client to fail diff --git a/packages/statemanager/src/types.ts b/packages/statemanager/src/types.ts index 340a21eb3c..a9051bc2e0 100644 --- a/packages/statemanager/src/types.ts +++ b/packages/statemanager/src/types.ts @@ -1,53 +1,10 @@ import { type PrefixedHexString, utf8ToBytes } from '@ethereumjs/util' -import type { AccessWitness } from './index.js' -import type { CacheType, Common } from '@ethereumjs/common' +import type { AccessWitness, Caches } from './index.js' +import type { Common } from '@ethereumjs/common' import type { Trie } from '@ethereumjs/trie' import type { VerkleCrypto } from '@ethereumjs/util' -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 -} - -export type CacheSettings = { - deactivate: boolean - type: CacheType - size: number -} - /** * Basic state manager options (not to be used directly) */ @@ -58,15 +15,6 @@ interface BaseStateManagerOpts { common?: Common } -/** - * Cache state manager options (not to be used directly) - */ -export interface CacheStateManagerOpts { - accountCacheOpts?: CacheOptions - storageCacheOpts?: CacheOptions - codeCacheOpts?: CacheOptions -} - /** * Options for constructing a {@link SimpleStateManager}. */ @@ -82,7 +30,7 @@ export interface RPCStateManagerOpts extends BaseStateManagerOpts { /** * Options for constructing a {@link StateManager}. */ -export interface DefaultStateManagerOpts extends BaseStateManagerOpts, CacheStateManagerOpts { +export interface DefaultStateManagerOpts extends BaseStateManagerOpts { /** * A {@link Trie} instance */ @@ -106,17 +54,25 @@ export interface DefaultStateManagerOpts extends BaseStateManagerOpts, CacheStat * Default: false (for backwards compatibility reasons) */ prefixStorageTrieKeys?: boolean + + /** + * Options to enable and configure the use of a cache account, code and storage + * This can be useful for speeding up reads, especially when the trie is large. + * The cache is only used for reading from the trie and is not used for writing to the trie. + * + * Default: false + */ + caches?: Caches } /** * Options dictionary. */ -export interface StatelessVerkleStateManagerOpts - extends BaseStateManagerOpts, - CacheStateManagerOpts { +export interface StatelessVerkleStateManagerOpts extends BaseStateManagerOpts { accesses?: AccessWitness verkleCrypto: VerkleCrypto initialStateRoot?: Uint8Array + caches?: Caches } export interface VerkleState { @@ -136,3 +92,19 @@ export interface EncodedVerkleProof { * misbehaviour in the underlying trie library. */ export const CODEHASH_PREFIX = utf8ToBytes('c') + +export type StorageProof = { + key: PrefixedHexString + proof: PrefixedHexString[] + value: PrefixedHexString +} + +export type Proof = { + address: PrefixedHexString + balance: PrefixedHexString + codeHash: PrefixedHexString + nonce: PrefixedHexString + storageHash: PrefixedHexString + accountProof: PrefixedHexString[] + storageProof: StorageProof[] +} diff --git a/packages/statemanager/src/util.ts b/packages/statemanager/src/util.ts new file mode 100644 index 0000000000..3628ab842d --- /dev/null +++ b/packages/statemanager/src/util.ts @@ -0,0 +1,18 @@ +import { Account } from '@ethereumjs/util' + +import type { AccountFields, StateManagerInterface } from '@ethereumjs/common' +import type { Address } from '@ethereumjs/util' + +export async function modifyAccountFields( + stateManager: StateManagerInterface, + address: Address, + accountFields: AccountFields, +): Promise { + const account = (await stateManager.getAccount(address)) ?? 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 stateManager.putAccount(address, account) +} diff --git a/packages/statemanager/test/cache/account.spec.ts b/packages/statemanager/test/cache/account.spec.ts index 4c69073eb8..533f62d095 100644 --- a/packages/statemanager/test/cache/account.spec.ts +++ b/packages/statemanager/test/cache/account.spec.ts @@ -1,8 +1,7 @@ -import { CacheType } from '@ethereumjs/common' import { Account, Address, equalsBytes, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { AccountCache } from '../../src/cache/index.js' +import { AccountCache, CacheType } from '../../src/cache/index.js' import { createAccountWithDefaults } from '../util.js' describe('Account Cache: initialization', () => { diff --git a/packages/statemanager/test/cache/code.spec.ts b/packages/statemanager/test/cache/code.spec.ts index cc1aac0b40..2bbe3eaea7 100644 --- a/packages/statemanager/test/cache/code.spec.ts +++ b/packages/statemanager/test/cache/code.spec.ts @@ -1,8 +1,7 @@ -import { CacheType } from '@ethereumjs/common' import { Address, equalsBytes, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { CodeCache } from '../../src/cache/index.js' +import { CacheType, CodeCache } from '../../src/cache/index.js' describe('Code Cache: initialization', () => { for (const type of [CacheType.LRU, CacheType.ORDERED_MAP]) { diff --git a/packages/statemanager/test/cache/storage.spec.ts b/packages/statemanager/test/cache/storage.spec.ts index dbd9914767..3fcb30d5f6 100644 --- a/packages/statemanager/test/cache/storage.spec.ts +++ b/packages/statemanager/test/cache/storage.spec.ts @@ -1,8 +1,7 @@ -import { CacheType } from '@ethereumjs/common' import { Address, equalsBytes, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { StorageCache } from '../../src/cache/index.js' +import { CacheType, StorageCache } from '../../src/cache/index.js' describe('Storage Cache: initialization', () => { for (const type of [CacheType.LRU, CacheType.ORDERED_MAP]) { diff --git a/packages/statemanager/test/checkpointing.code.spec.ts b/packages/statemanager/test/checkpointing.code.spec.ts index 12e8815dd4..e53b39c109 100644 --- a/packages/statemanager/test/checkpointing.code.spec.ts +++ b/packages/statemanager/test/checkpointing.code.spec.ts @@ -2,7 +2,7 @@ import { type StateManagerInterface } from '@ethereumjs/common' import { Account, Address, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { DefaultStateManager, SimpleStateManager } from '../src/index.js' +import { Caches, DefaultStateManager, SimpleStateManager } from '../src/index.js' const codeEval = async ( sm: StateManagerInterface, @@ -93,7 +93,19 @@ describe('StateManager -> Code Checkpointing', () => { for (const SM of stateManagers) { for (const c of codeSets) { it(`No CP -> C1 -> Flush() (-> C1)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -107,7 +119,12 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`CP -> C1.1 -> Commit -> Flush() (-> C1.1)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } await sm.putAccount(address, new Account()) await sm.checkpoint() @@ -121,7 +138,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`CP -> C1.1 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.checkpoint() @@ -137,7 +160,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`C1.1 -> CP -> Commit -> Flush() (-> C1.1)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -151,7 +180,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`C1.1 -> CP -> Revert -> Flush() (-> C1.1)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -165,7 +200,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`C1.1 -> CP -> C1.2 -> Commit -> Flush() (-> C1.2)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -180,7 +221,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`C1.1 -> CP -> C1.2 -> Commit -> C1.3 -> Flush() (-> C1.3)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -196,7 +243,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`C1.1 -> CP -> C1.2 -> C1.3 -> Commit -> Flush() (-> C1.3)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -212,7 +265,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`CP -> C1.1 -> C1.2 -> Commit -> Flush() (-> C1.2)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.checkpoint() @@ -227,7 +286,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`CP -> C1.1 -> C1.2 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.checkpoint() @@ -243,7 +308,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it(`C1.1 -> CP -> C1.2 -> Revert -> Flush() (-> C1.1)`, async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -258,7 +329,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Commit -> Commit -> Flush() (-> C1.3)', async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -276,7 +353,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Commit -> Revert -> Flush() (-> C1.1)', async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -294,7 +377,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> Commit -> Flush() (-> C1.2)', async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -312,7 +401,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> C1.4 -> Commit -> Flush() (-> C1.4)', async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) @@ -331,7 +426,13 @@ describe('StateManager -> Code Checkpointing', () => { }) it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> C1.4 -> CP -> C1.5 -> Commit -> Commit -> Flush() (-> C1.5)', async () => { - const sm = new SM() + let sm: DefaultStateManager | SimpleStateManager + if (SM === DefaultStateManager) { + sm = new SM({ caches: new Caches() }) + } else { + sm = new SM() + } + await sm.putAccount(address, new Account()) await sm.putCode(address, c.c1.value) diff --git a/packages/statemanager/test/rpcStateManager.spec.ts b/packages/statemanager/test/rpcStateManager.spec.ts index 5dc0ea890f..1213ed2ec6 100644 --- a/packages/statemanager/test/rpcStateManager.spec.ts +++ b/packages/statemanager/test/rpcStateManager.spec.ts @@ -47,18 +47,10 @@ describe('RPC State Manager initialization tests', async () => { it('should work', () => { let state = new RPCStateManager({ provider, blockTag: 1n }) assert.ok(state instanceof RPCStateManager, 'was able to instantiate state manager') - assert.equal( - (state as any)._blockTag, - '0x1', - 'State manager starts with default block tag of 1', - ) + assert.equal(state['_blockTag'], '0x1', 'State manager starts with default block tag of 1') state = new RPCStateManager({ provider, blockTag: 1n }) - assert.equal( - (state as any)._blockTag, - '0x1', - 'State Manager instantiated with predefined blocktag', - ) + assert.equal(state['_blockTag'], '0x1', 'State Manager instantiated with predefined blocktag') state = new RPCStateManager({ provider: 'https://google.com', blockTag: 1n }) assert.ok( @@ -87,7 +79,7 @@ describe('RPC State Manager API tests', () => { await state.putAccount(vitalikDotEth, account!) const retrievedVitalikAccount = createAccountFromRLP( - (state as any)._accountCache.get(vitalikDotEth)!.accountRLP, + state['_caches'].account?.get(vitalikDotEth)?.accountRLP!, ) assert.ok(retrievedVitalikAccount.nonce > 0n, 'Vitalik.eth is stored in cache') @@ -106,7 +98,7 @@ describe('RPC State Manager API tests', () => { await state.putCode(UNIerc20ContractAddress, UNIContractCode) assert.ok( - typeof (state as any)._contractCache.get(UNIerc20ContractAddress.toString()) !== 'undefined', + state['_caches'].code?.get(UNIerc20ContractAddress) !== undefined, 'UNI ERC20 contract code was found in cache', ) @@ -219,16 +211,16 @@ describe('RPC State Manager API tests', () => { } assert.equal( - (state as any)._contractCache.get(UNIerc20ContractAddress), + state['_caches'].account?.get(UNIerc20ContractAddress), undefined, 'should not have any code for contract after cache is reverted', ) - assert.equal((state as any)._blockTag, '0x1', 'blockTag defaults to 1') + assert.equal(state['_blockTag'], '0x1', 'blockTag defaults to 1') state.setBlockTag(5n) - assert.equal((state as any)._blockTag, '0x5', 'blockTag set to 0x5') + assert.equal(state['_blockTag'], '0x5', 'blockTag set to 0x5') state.setBlockTag('earliest') - assert.equal((state as any)._blockTag, 'earliest', 'blockTag set to earliest') + assert.equal(state['_blockTag'], 'earliest', 'blockTag set to earliest') await state.checkpoint() }) diff --git a/packages/statemanager/test/stateManager.account.spec.ts b/packages/statemanager/test/stateManager.account.spec.ts index ef97c11ce7..6d0e010253 100644 --- a/packages/statemanager/test/stateManager.account.spec.ts +++ b/packages/statemanager/test/stateManager.account.spec.ts @@ -1,18 +1,16 @@ import { Address, KECCAK256_RLP, bytesToHex, equalsBytes, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { DefaultStateManager } from '../src/index.js' +import { Caches, DefaultStateManager } from '../src/index.js' import { createAccountWithDefaults } from './util.js' describe('StateManager -> General/Account', () => { - for (const accountCacheOpts of [ - { deactivate: false }, - { deactivate: true }, - { deactivate: false, size: 0 }, - ]) { + for (const accountCacheOpts of [{ size: 1000 }, { size: 0 }]) { it(`should set the state root to empty`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) assert.ok(equalsBytes(stateManager['_trie'].root(), KECCAK256_RLP), 'it has default root') // commit some data to the trie @@ -32,7 +30,9 @@ describe('StateManager -> General/Account', () => { }) it(`should clear the cache when the state root is set`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const account = createAccountWithDefaults() @@ -75,7 +75,9 @@ describe('StateManager -> General/Account', () => { }) it('should put and get account, and add to the underlying cache if the account is not found', async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const account = createAccountWithDefaults() const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) @@ -86,7 +88,7 @@ describe('StateManager -> General/Account', () => { assert.equal(res1!.balance, BigInt(0xfff384)) await stateManager.flush() - stateManager['_accountCache']?.clear() + stateManager['_caches']?.account?.clear() const res2 = await stateManager.getAccount(address) @@ -94,7 +96,9 @@ describe('StateManager -> General/Account', () => { }) it(`should return undefined for a non-existent account`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const res = (await stateManager.getAccount(address)) === undefined @@ -103,7 +107,9 @@ describe('StateManager -> General/Account', () => { }) it(`should return undefined for an existent account`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const account = createAccountWithDefaults(BigInt(0x1), BigInt(0x1)) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) @@ -115,7 +121,9 @@ describe('StateManager -> General/Account', () => { }) it(`should modify account fields correctly`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const account = createAccountWithDefaults() const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) await stateManager.putAccount(address, account) diff --git a/packages/statemanager/test/stateManager.code.spec.ts b/packages/statemanager/test/stateManager.code.spec.ts index ee7dc802ac..a31026d662 100644 --- a/packages/statemanager/test/stateManager.code.spec.ts +++ b/packages/statemanager/test/stateManager.code.spec.ts @@ -7,18 +7,14 @@ import { } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { DefaultStateManager } from '../src/index.js' +import { Caches, DefaultStateManager } from '../src/index.js' import { createAccountWithDefaults } from './util.js' import type { AccountData } from '@ethereumjs/util' describe('StateManager -> Code', () => { - for (const accountCacheOpts of [ - { deactivate: false }, - { deactivate: true }, - { deactivate: false, size: 0 }, - ]) { + for (const accountCacheOpts of [{ size: 1000 }, { size: 0 }]) { it(`should store codehashes using a prefix`, async () => { /* This test is mostly an example of why a code prefix is necessary @@ -31,8 +27,12 @@ describe('StateManager -> Code', () => { */ // Setup - const stateManager = new DefaultStateManager({ accountCacheOpts }) - const codeStateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) + const codeStateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const address1 = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const account = createAccountWithDefaults() const key1 = hexToBytes(`0x${'00'.repeat(32)}`) @@ -87,7 +87,9 @@ describe('StateManager -> Code', () => { }) it(`should set and get code`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const code = hexToBytes( '0x73095e7baea6a6c7c4c2dfeb977efac326af552d873173095e7baea6a6c7c4c2dfeb977efac326af552d873157', @@ -105,7 +107,9 @@ describe('StateManager -> Code', () => { }) it(`should not get code if is not contract`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const raw: AccountData = { nonce: '0x0', @@ -118,7 +122,9 @@ describe('StateManager -> Code', () => { }) it(`should set empty code`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const raw: AccountData = { nonce: '0x0', @@ -133,7 +139,9 @@ describe('StateManager -> Code', () => { }) it(`should prefix codehashes by default`, async () => { - const stateManager = new DefaultStateManager({ accountCacheOpts }) + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const code = hexToBytes('0x80') await stateManager.putCode(address, code) diff --git a/packages/statemanager/test/stateManager.spec.ts b/packages/statemanager/test/stateManager.spec.ts index 6aca9047bb..2ec8bbb856 100644 --- a/packages/statemanager/test/stateManager.spec.ts +++ b/packages/statemanager/test/stateManager.spec.ts @@ -1,4 +1,3 @@ -import { CacheType } from '@ethereumjs/common' import { Trie, createTrie, createTrieFromProof } from '@ethereumjs/trie' import { Account, @@ -15,7 +14,7 @@ import { } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { DefaultStateManager } from '../src/index.js' +import { CacheType, Caches, DefaultStateManager } from '../src/index.js' import type { PrefixedHexString } from '@ethereumjs/util' @@ -83,22 +82,24 @@ describe('StateManager -> General', () => { sm = new DefaultStateManager({ trie, - accountCacheOpts: { - type: CacheType.LRU, - }, - storageCacheOpts: { - type: CacheType.LRU, - }, + caches: new Caches({ + account: { + type: CacheType.LRU, + }, + storage: { + type: CacheType.LRU, + }, + }), }) smCopy = sm.shallowCopy() assert.equal( - smCopy['_accountCacheSettings'].type, + smCopy['_caches']?.settings.account.type, CacheType.ORDERED_MAP, 'should switch to ORDERED_MAP account cache on copy()', ) assert.equal( - smCopy['_storageCacheSettings'].type, + smCopy['_caches']?.settings.storage.type, CacheType.ORDERED_MAP, 'should switch to ORDERED_MAP storage cache on copy()', ) @@ -106,12 +107,12 @@ describe('StateManager -> General', () => { smCopy = sm.shallowCopy(false) assert.equal( - smCopy['_accountCacheSettings'].type, + smCopy['_caches']?.settings.account.type, CacheType.LRU, 'should retain account cache type when deactivate cache downleveling', ) assert.equal( - smCopy['_storageCacheSettings'].type, + smCopy['_caches']?.settings.storage.type, CacheType.LRU, 'should retain storage cache type when deactivate cache downleveling', ) diff --git a/packages/statemanager/test/stateManager.storage.spec.ts b/packages/statemanager/test/stateManager.storage.spec.ts index ed65a30f8f..f8a1fb48cc 100644 --- a/packages/statemanager/test/stateManager.storage.spec.ts +++ b/packages/statemanager/test/stateManager.storage.spec.ts @@ -11,20 +11,19 @@ import { import { keccak256 } from 'ethereum-cryptography/keccak.js' import { assert, describe, it } from 'vitest' -import { DefaultStateManager } from '../src/index.js' +import { Caches, DefaultStateManager } from '../src/index.js' import { createAccountWithDefaults } from './util.js' const isBrowser = new Function('try {return this===window;}catch(e){ return false;}') describe('StateManager -> Storage', () => { - for (const storageCacheOpts of [ - { deactivate: false }, - { deactivate: true }, - { deactivate: false, size: 0 }, - ]) { + for (const storageCacheOpts of [{ size: 1000 }, { size: 0 }]) { for (const prefixStorageTrieKeys of [false, true]) { it.skipIf(isBrowser() === true)(`should dump storage`, async () => { - const stateManager = new DefaultStateManager({ prefixStorageTrieKeys, storageCacheOpts }) + const stateManager = new DefaultStateManager({ + prefixStorageTrieKeys, + caches: new Caches({ storage: storageCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const account = createAccountWithDefaults() @@ -40,7 +39,10 @@ describe('StateManager -> Storage', () => { }) it("should validate the key's length when modifying a contract's storage", async () => { - const stateManager = new DefaultStateManager({ prefixStorageTrieKeys, storageCacheOpts }) + const stateManager = new DefaultStateManager({ + prefixStorageTrieKeys, + caches: new Caches({ storage: storageCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const account = createAccountWithDefaults() await stateManager.putAccount(address, account) @@ -56,7 +58,10 @@ describe('StateManager -> Storage', () => { }) it("should validate the key's length when reading a contract's storage", async () => { - const stateManager = new DefaultStateManager({ prefixStorageTrieKeys, storageCacheOpts }) + const stateManager = new DefaultStateManager({ + prefixStorageTrieKeys, + caches: new Caches({ storage: storageCacheOpts }), + }) const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) const account = createAccountWithDefaults() await stateManager.putAccount(address, account) @@ -72,7 +77,10 @@ describe('StateManager -> Storage', () => { }) it(`should throw on storage values larger than 32 bytes`, async () => { - const stateManager = new DefaultStateManager({ prefixStorageTrieKeys, storageCacheOpts }) + const stateManager = new DefaultStateManager({ + prefixStorageTrieKeys, + caches: new Caches({ storage: storageCacheOpts }), + }) const address = createZeroAddress() const account = createAccountWithDefaults() await stateManager.putAccount(address, account) @@ -88,7 +96,10 @@ describe('StateManager -> Storage', () => { }) it(`should strip zeros of storage values`, async () => { - const stateManager = new DefaultStateManager({ prefixStorageTrieKeys, storageCacheOpts }) + const stateManager = new DefaultStateManager({ + prefixStorageTrieKeys, + caches: new Caches({ storage: storageCacheOpts }), + }) const address = createZeroAddress() const account = createAccountWithDefaults() await stateManager.putAccount(address, account) @@ -118,7 +129,10 @@ describe('StateManager -> Storage', () => { const zeroLengths = [0, 1, 31, 32] // checks for arbitrary-length zeros for (const length of zeroLengths) { - const stateManager = new DefaultStateManager({ prefixStorageTrieKeys, storageCacheOpts }) + const stateManager = new DefaultStateManager({ + prefixStorageTrieKeys, + caches: new Caches({ storage: storageCacheOpts }), + }) const account = createAccountWithDefaults() await stateManager.putAccount(address, account) @@ -138,7 +152,10 @@ describe('StateManager -> Storage', () => { }) it(`should not strip trailing zeros`, async () => { - const stateManager = new DefaultStateManager({ prefixStorageTrieKeys, storageCacheOpts }) + const stateManager = new DefaultStateManager({ + prefixStorageTrieKeys, + caches: new Caches({ storage: storageCacheOpts }), + }) const address = createZeroAddress() const account = createAccountWithDefaults() await stateManager.putAccount(address, account) diff --git a/packages/statemanager/test/statelessVerkleStateManager.spec.ts b/packages/statemanager/test/statelessVerkleStateManager.spec.ts index 310fcebd7b..6fc153efae 100644 --- a/packages/statemanager/test/statelessVerkleStateManager.spec.ts +++ b/packages/statemanager/test/statelessVerkleStateManager.spec.ts @@ -1,5 +1,5 @@ import { createBlock } from '@ethereumjs/block' -import { CacheType, createCommonFromGethGenesis } from '@ethereumjs/common' +import { createCommonFromGethGenesis } from '@ethereumjs/common' import { createTxFromSerializedData } from '@ethereumjs/tx' import { Address, @@ -16,7 +16,7 @@ import { import { loadVerkleCrypto } from 'verkle-cryptography-wasm' import { assert, beforeAll, describe, it, test } from 'vitest' -import { StatelessVerkleStateManager } from '../src/index.js' +import { CacheType, Caches, StatelessVerkleStateManager } from '../src/index.js' import * as testnetVerkleKaustinen from './testdata/testnetVerkleKaustinen.json' import * as verkleBlockJSON from './testdata/verkleKaustinen6Block72.json' @@ -67,7 +67,11 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { }) it('put/delete/modify account', async () => { - const stateManager = new StatelessVerkleStateManager({ common, verkleCrypto }) + const stateManager = new StatelessVerkleStateManager({ + common, + caches: new Caches(), + verkleCrypto, + }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) const address = new Address(randomBytes(20)) @@ -144,26 +148,28 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { it(`copy()`, async () => { const stateManager = new StatelessVerkleStateManager({ - accountCacheOpts: { - type: CacheType.ORDERED_MAP, - }, - storageCacheOpts: { - type: CacheType.ORDERED_MAP, - }, + caches: new Caches({ + account: { + type: CacheType.ORDERED_MAP, + }, + storage: { + type: CacheType.ORDERED_MAP, + }, + }), common, verkleCrypto, }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) - const stateManagerCopy = stateManager.shallowCopy() as StatelessVerkleStateManager + const stateManagerCopy = stateManager.shallowCopy() assert.equal( - (stateManagerCopy as any)['_accountCacheSettings'].type, + stateManagerCopy['_caches']?.settings.account.type, CacheType.ORDERED_MAP, 'should switch to ORDERED_MAP account cache on copy()', ) assert.equal( - (stateManagerCopy as any)['_storageCacheSettings'].type, + stateManagerCopy['_caches']?.settings.storage.type, CacheType.ORDERED_MAP, 'should switch to ORDERED_MAP storage cache on copy()', ) diff --git a/packages/vm/src/vm.ts b/packages/vm/src/vm.ts index a0af8bb891..d523f1ac28 100644 --- a/packages/vm/src/vm.ts +++ b/packages/vm/src/vm.ts @@ -1,7 +1,7 @@ import { createBlockchain } from '@ethereumjs/blockchain' import { Common, Mainnet } from '@ethereumjs/common' import { createEVM, getActivePrecompiles } from '@ethereumjs/evm' -import { DefaultStateManager } from '@ethereumjs/statemanager' +import { Caches, DefaultStateManager } from '@ethereumjs/statemanager' import { Account, Address, @@ -82,7 +82,10 @@ export class VM { } if (opts.stateManager === undefined) { - opts.stateManager = new DefaultStateManager({ common: opts.common }) + opts.stateManager = new DefaultStateManager({ + caches: new Caches(), + common: opts.common, + }) } if (opts.blockchain === undefined) { diff --git a/packages/vm/test/api/EIPs/eip-6800-verkle.spec.ts b/packages/vm/test/api/EIPs/eip-6800-verkle.spec.ts index 2c3cbe5aa4..1a60eef6db 100644 --- a/packages/vm/test/api/EIPs/eip-6800-verkle.spec.ts +++ b/packages/vm/test/api/EIPs/eip-6800-verkle.spec.ts @@ -1,7 +1,7 @@ import { createBlock } from '@ethereumjs/block' import { Hardfork, Mainnet, createCustomCommon } from '@ethereumjs/common' import { createEVM } from '@ethereumjs/evm' -import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' +import { Caches, StatelessVerkleStateManager } from '@ethereumjs/statemanager' import { createTxFromSerializedData } from '@ethereumjs/tx' import { hexToBytes } from '@ethereumjs/util' import { loadVerkleCrypto } from 'verkle-cryptography-wasm' @@ -33,7 +33,11 @@ const block = createBlock({ ...verkleBlockJSON, transactions: decodedTxs } as Bl describe('EIP 6800 tests', () => { it('successfully run transactions statelessly using the block witness', async () => { const verkleCrypto = await loadVerkleCrypto() - const verkleStateManager = new StatelessVerkleStateManager({ common, verkleCrypto }) + const verkleStateManager = new StatelessVerkleStateManager({ + caches: new Caches(), + common, + verkleCrypto, + }) const evm = await createEVM({ common, stateManager: verkleStateManager }) const vm = await VM.create({ common, diff --git a/packages/vm/test/tester/runners/BlockchainTestsRunner.ts b/packages/vm/test/tester/runners/BlockchainTestsRunner.ts index 92e20239fd..a33b167960 100644 --- a/packages/vm/test/tester/runners/BlockchainTestsRunner.ts +++ b/packages/vm/test/tester/runners/BlockchainTestsRunner.ts @@ -3,7 +3,7 @@ import { EthashConsensus, createBlockchain } from '@ethereumjs/blockchain' import { ConsensusAlgorithm } from '@ethereumjs/common' import { Ethash } from '@ethereumjs/ethash' import { RLP } from '@ethereumjs/rlp' -import { DefaultStateManager } from '@ethereumjs/statemanager' +import { Caches, DefaultStateManager } from '@ethereumjs/statemanager' import { Trie } from '@ethereumjs/trie' import { createTxFromSerializedData } from '@ethereumjs/tx' import { @@ -49,6 +49,7 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes let cacheDB = new MapDB() let state = new Trie({ useKeyHashing: true, common }) let stateManager = new DefaultStateManager({ + caches: new Caches(), trie: state, common, }) diff --git a/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts b/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts index 992cc6c11c..fe715a1cbd 100644 --- a/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts +++ b/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts @@ -1,7 +1,7 @@ import { Block } from '@ethereumjs/block' import { createBlockchain } from '@ethereumjs/blockchain' import { type InterpreterStep } from '@ethereumjs/evm' -import { DefaultStateManager } from '@ethereumjs/statemanager' +import { Caches, DefaultStateManager } from '@ethereumjs/statemanager' import { Trie } from '@ethereumjs/trie' import { Account, @@ -81,6 +81,7 @@ async function runTestCase(options: any, testData: any, t: tape.Test) { const blockchain = await createBlockchain({ genesisBlock, common }) const state = new Trie({ useKeyHashing: true, common }) const stateManager = new DefaultStateManager({ + caches: new Caches(), trie: state, common, })