@ethereumjs/statemanager
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -37,4 +37,4 @@ "use strict"; | ||
_saveCachePreState(cacheKeyHex) { | ||
const it = this._diffCache[this._checkpoints].get(cacheKeyHex); | ||
if (it === undefined) { | ||
const diffMap = this._diffCache[this._checkpoints]; | ||
if (!diffMap.has(cacheKeyHex)) { | ||
let oldElem; | ||
@@ -47,3 +47,3 @@ if (this._lruCache) { | ||
} | ||
this._diffCache[this._checkpoints].set(cacheKeyHex, oldElem); | ||
diffMap.set(cacheKeyHex, oldElem); | ||
} | ||
@@ -50,0 +50,0 @@ } |
export * from './account.js'; | ||
export * from './code.js'; | ||
export * from './storage.js'; | ||
export * from './types.js'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -18,4 +18,5 @@ "use strict"; | ||
__exportStar(require("./account.js"), exports); | ||
__exportStar(require("./code.js"), exports); | ||
__exportStar(require("./storage.js"), exports); | ||
__exportStar(require("./types.js"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -93,4 +93,10 @@ import { OrderedMap } from 'js-sdsl'; | ||
clear(): void; | ||
/** | ||
* Dumps the RLP-encoded storage values for an `account` specified by `address`. | ||
* @param address - The address of the `account` to return storage for | ||
* @returns {StorageCacheMap | undefined} - The storage values for the `account` or undefined if the `account` is not in the cache | ||
*/ | ||
dump(address: Address): StorageCacheMap | undefined; | ||
} | ||
export {}; | ||
//# sourceMappingURL=storage.d.ts.map |
@@ -40,9 +40,3 @@ "use strict"; | ||
const addressStoragePreState = this._diffCache[this._checkpoints].get(addressHex); | ||
let diffStorageMap; | ||
if (addressStoragePreState === undefined) { | ||
diffStorageMap = new Map(); | ||
} | ||
else { | ||
diffStorageMap = addressStoragePreState; | ||
} | ||
const diffStorageMap = addressStoragePreState ?? new Map(); | ||
if (!diffStorageMap.has(keyHex)) { | ||
@@ -327,4 +321,19 @@ let oldStorageMap; | ||
} | ||
/** | ||
* Dumps the RLP-encoded storage values for an `account` specified by `address`. | ||
* @param address - The address of the `account` to return storage for | ||
* @returns {StorageCacheMap | undefined} - The storage values for the `account` or undefined if the `account` is not in the cache | ||
*/ | ||
dump(address) { | ||
let storageMap; | ||
if (this._lruCache) { | ||
storageMap = this._lruCache.get((0, util_1.bytesToUnprefixedHex)(address.bytes)); | ||
} | ||
else { | ||
storageMap = this._orderedMapCache?.getElementByKey((0, util_1.bytesToUnprefixedHex)(address.bytes)); | ||
} | ||
return storageMap; | ||
} | ||
} | ||
exports.StorageCache = StorageCache; | ||
//# sourceMappingURL=storage.js.map |
@@ -167,3 +167,3 @@ "use strict"; | ||
dumpStorage(address) { | ||
const storageMap = this._storageCache._lruCache?.get(address.toString()); | ||
const storageMap = this._storageCache.dump(address); | ||
const dump = {}; | ||
@@ -170,0 +170,0 @@ if (storageMap !== undefined) { |
import { Common } from '@ethereumjs/common'; | ||
import { Trie } from '@ethereumjs/trie'; | ||
import { Account, Address } from '@ethereumjs/util'; | ||
import { AccountCache, CacheType, StorageCache } from './cache/index.js'; | ||
import { AccountCache, CacheType, CodeCache, StorageCache } from './cache/index.js'; | ||
import { OriginalStorageCache } from './cache/originalStorageCache.js'; | ||
import type { AccountFields, EVMStateManagerInterface, StorageDump } from '@ethereumjs/common'; | ||
import type { StorageRange } from '@ethereumjs/common/src'; | ||
import type { PrefixedHexString } from '@ethereumjs/util'; | ||
import type { DB, PrefixedHexString } from '@ethereumjs/util'; | ||
import type { Debugger } from 'debug'; | ||
@@ -49,3 +49,3 @@ export declare type StorageProof = { | ||
* | ||
* Default: 100000 (account cache) / 20000 (storage cache) | ||
* Default: 100000 (account cache) / 20000 (storage cache) / 20000 (code cache) | ||
* | ||
@@ -88,4 +88,16 @@ * Note: the cache/trie interplay mechanism is designed in a way that | ||
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; | ||
/** | ||
@@ -110,2 +122,3 @@ * The common to use | ||
protected _storageCache?: StorageCache; | ||
protected _codeCache?: CodeCache; | ||
originalStorageCache: OriginalStorageCache; | ||
@@ -116,8 +129,7 @@ protected _trie: Trie; | ||
}; | ||
protected _codeCache: { | ||
[key: string]: Uint8Array; | ||
}; | ||
protected readonly _prefixCodeHashes: boolean; | ||
protected readonly _prefixStorageTrieKeys: boolean; | ||
protected readonly _accountCacheSettings: CacheSettings; | ||
protected readonly _storageCacheSettings: CacheSettings; | ||
protected readonly _codeCacheSettings: CacheSettings; | ||
readonly common: Common; | ||
@@ -182,4 +194,16 @@ protected _checkpointCount: number; | ||
*/ | ||
protected _getStorageTrie(address: Address, account: Account): Promise<Trie>; | ||
protected _getStorageTrie(addressOrHash: Address | Uint8Array, account?: Account): Trie; | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
protected _getAccountTrie(): Trie; | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
protected _getCodeDB(): DB; | ||
/** | ||
* Gets the storage value associated with the provided `address` and `key`. This method returns | ||
@@ -296,3 +320,6 @@ * the shortest representation of the stored value. | ||
* | ||
* Note on caches: | ||
* Caches are downleveled (so: adopted for short-term usage) | ||
* by default. | ||
* | ||
* This means in particular: | ||
* 1. For caches instantiated as an LRU cache type | ||
@@ -303,5 +330,11 @@ * the copy() method will instantiate with an ORDERED_MAP cache | ||
* a large overhead here. | ||
* 2. Cache values are generally not copied along | ||
* 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. | ||
*/ | ||
shallowCopy(): DefaultStateManager; | ||
shallowCopy(downlevelCaches?: boolean): DefaultStateManager; | ||
/** | ||
@@ -308,0 +341,0 @@ * Clears all underlying caches |
@@ -56,7 +56,7 @@ "use strict"; | ||
this._storageTries = {}; | ||
this._codeCache = {}; | ||
this.originalStorageCache = new originalStorageCache_js_1.OriginalStorageCache(this.getContractStorage.bind(this)); | ||
this._prefixCodeHashes = opts.prefixCodeHashes ?? true; | ||
this._prefixStorageTrieKeys = opts.prefixStorageTrieKeys ?? false; | ||
this._accountCacheSettings = { | ||
deactivate: opts.accountCacheOpts?.deactivate ?? false, | ||
deactivate: (opts.accountCacheOpts?.deactivate === true || opts.accountCacheOpts?.size === 0) ?? false, | ||
type: opts.accountCacheOpts?.type ?? index_js_1.CacheType.ORDERED_MAP, | ||
@@ -72,3 +72,3 @@ size: opts.accountCacheOpts?.size ?? 100000, | ||
this._storageCacheSettings = { | ||
deactivate: opts.storageCacheOpts?.deactivate ?? false, | ||
deactivate: (opts.storageCacheOpts?.deactivate === true || opts.storageCacheOpts?.size === 0) ?? false, | ||
type: opts.storageCacheOpts?.type ?? index_js_1.CacheType.ORDERED_MAP, | ||
@@ -83,2 +83,13 @@ size: opts.storageCacheOpts?.size ?? 20000, | ||
} | ||
this._codeCacheSettings = { | ||
deactivate: (opts.codeCacheOpts?.deactivate === true || opts.codeCacheOpts?.size === 0) ?? false, | ||
type: opts.codeCacheOpts?.type ?? index_js_1.CacheType.ORDERED_MAP, | ||
size: opts.codeCacheOpts?.size ?? 20000, | ||
}; | ||
if (!this._codeCacheSettings.deactivate) { | ||
this._codeCache = new index_js_1.CodeCache({ | ||
size: this._codeCacheSettings.size, | ||
type: this._codeCacheSettings.type, | ||
}); | ||
} | ||
} | ||
@@ -159,2 +170,3 @@ /** | ||
} | ||
this._codeCache?.del(address); | ||
if (this._accountCacheSettings.deactivate) { | ||
@@ -177,2 +189,3 @@ await this._trie.del(address.bytes); | ||
async putContractCode(address, value) { | ||
this._codeCache?.put(address, value); | ||
const codeHash = (0, keccak_js_1.keccak256)(value); | ||
@@ -182,6 +195,2 @@ if ((0, util_1.equalsBytes)(codeHash, util_1.KECCAK256_NULL)) { | ||
} | ||
const key = this._prefixCodeHashes ? (0, util_1.concatBytes)(exports.CODEHASH_PREFIX, codeHash) : codeHash; | ||
await this._trie.database().put(key, value); | ||
const keyHex = (0, util_1.bytesToUnprefixedHex)(key); | ||
this._codeCache[keyHex] = value; | ||
if (this.DEBUG) { | ||
@@ -202,2 +211,8 @@ this._debug(`Update codeHash (-> ${(0, util_1.short)(codeHash)}) for account ${address}`); | ||
async getContractCode(address) { | ||
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); | ||
@@ -213,11 +228,7 @@ if (!account) { | ||
: account.codeHash; | ||
const keyHex = (0, util_1.bytesToUnprefixedHex)(key); | ||
if (keyHex in this._codeCache) { | ||
return this._codeCache[keyHex]; | ||
const code = (await this._trie.database().get(key)) ?? new Uint8Array(0); | ||
if (!this._codeCacheSettings.deactivate) { | ||
this._codeCache.put(address, code); | ||
} | ||
else { | ||
const code = (await this._trie.database().get(key)) ?? new Uint8Array(0); | ||
this._codeCache[keyHex] = code; | ||
return code; | ||
} | ||
return code; | ||
} | ||
@@ -229,12 +240,20 @@ /** | ||
*/ | ||
async _getStorageTrie(address, account) { | ||
// from storage cache | ||
const addressHex = (0, util_1.bytesToUnprefixedHex)(address.bytes); | ||
const storageTrie = this._storageTries[addressHex]; | ||
// TODO PR: have a better interface for hashed address pull? | ||
_getStorageTrie(addressOrHash, account) { | ||
// use hashed key for lookup from storage cache | ||
const addressHex = (0, util_1.bytesToUnprefixedHex)(addressOrHash instanceof util_1.Address ? (0, keccak_js_1.keccak256)(addressOrHash.bytes) : addressOrHash); | ||
let storageTrie = this._storageTries[addressHex]; | ||
if (storageTrie === undefined) { | ||
const storageTrie = this._trie.shallowCopy(false); | ||
storageTrie.root(account.storageRoot); | ||
const keyPrefix = this._prefixStorageTrieKeys | ||
? (addressOrHash instanceof util_1.Address ? (0, keccak_js_1.keccak256)(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; | ||
} | ||
@@ -244,2 +263,18 @@ return storageTrie; | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
_getAccountTrie() { | ||
return this._trie; | ||
} | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
_getCodeDB() { | ||
return this._trie.database(); | ||
} | ||
/** | ||
* Gets the storage value associated with the provided `address` and `key`. This method returns | ||
@@ -268,3 +303,3 @@ * the shortest representation of the stored value. | ||
} | ||
const trie = await this._getStorageTrie(address, account); | ||
const trie = this._getStorageTrie(address, account); | ||
const value = await trie.get(key); | ||
@@ -286,3 +321,3 @@ if (!this._storageCacheSettings.deactivate) { | ||
return new Promise(async (resolve) => { | ||
const storageTrie = await this._getStorageTrie(address, account); | ||
const storageTrie = this._getStorageTrie(address, account); | ||
modifyTrie(storageTrie, async () => { | ||
@@ -372,2 +407,3 @@ // update storage cache | ||
this._accountCache?.checkpoint(); | ||
this._codeCache?.checkpoint(); | ||
this._checkpointCount++; | ||
@@ -384,2 +420,3 @@ } | ||
this._accountCache?.commit(); | ||
this._codeCache?.commit(); | ||
this._checkpointCount--; | ||
@@ -403,4 +440,4 @@ if (this._checkpointCount === 0) { | ||
this._accountCache?.revert(); | ||
this._codeCache?.revert(); | ||
this._storageTries = {}; | ||
this._codeCache = {}; | ||
this._checkpointCount--; | ||
@@ -416,2 +453,21 @@ if (this._checkpointCount === 0) { | ||
async flush() { | ||
if (!this._codeCacheSettings.deactivate) { | ||
const items = this._codeCache.flush(); | ||
for (const item of items) { | ||
const addr = util_1.Address.fromString(`0x${item[0]}`); | ||
const code = item[1].code; | ||
if (code === undefined) { | ||
continue; | ||
} | ||
// update code in database | ||
const codeHash = (0, keccak_js_1.keccak256)(code); | ||
const key = this._prefixCodeHashes ? (0, util_1.concatBytes)(exports.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 util_1.Account()); | ||
} | ||
await this.modifyAccountFields(addr, { codeHash }); | ||
} | ||
} | ||
if (!this._storageCacheSettings.deactivate) { | ||
@@ -459,5 +515,5 @@ const items = this._storageCache.flush(); | ||
address: address.toString(), | ||
balance: '0x', | ||
balance: '0x0', | ||
codeHash: util_1.KECCAK256_NULL_S, | ||
nonce: '0x', | ||
nonce: '0x0', | ||
storageHash: util_1.KECCAK256_RLP_S, | ||
@@ -471,3 +527,3 @@ accountProof: (await this._trie.createProof(address.bytes)).map((p) => (0, util_1.bytesToHex)(p)), | ||
const storageProof = []; | ||
const storageTrie = await this._getStorageTrie(address, account); | ||
const storageTrie = this._getStorageTrie(address, account); | ||
for (const storageKey of storageSlots) { | ||
@@ -588,4 +644,6 @@ const proof = (await storageTrie.createProof(storageKey)).map((p) => (0, util_1.bytesToHex)(p)); | ||
} | ||
if (this._codeCache !== undefined && clearCache) { | ||
this._codeCache.clear(); | ||
} | ||
this._storageTries = {}; | ||
this._codeCache = {}; | ||
} | ||
@@ -605,15 +663,13 @@ /** | ||
} | ||
const trie = this._getStorageTrie(address, account); | ||
return new Promise((resolve, reject) => { | ||
this._getStorageTrie(address, account) | ||
.then((trie) => { | ||
const storage = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
storage[(0, util_1.bytesToHex)(val.key)] = (0, util_1.bytesToHex)(val.value); | ||
}); | ||
stream.on('end', () => { | ||
resolve(storage); | ||
}); | ||
}) | ||
.catch((e) => { | ||
const storage = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
storage[(0, util_1.bytesToHex)(val.key)] = (0, util_1.bytesToHex)(val.value); | ||
}); | ||
stream.on('end', () => { | ||
resolve(storage); | ||
}); | ||
stream.on('error', (e) => { | ||
reject(e); | ||
@@ -641,41 +697,37 @@ }); | ||
} | ||
const trie = this._getStorageTrie(address, account); | ||
return new Promise((resolve, reject) => { | ||
this._getStorageTrie(address, account) | ||
.then((trie) => { | ||
let inRange = false; | ||
let i = 0; | ||
/** Object conforming to {@link StorageRange.storage}. */ | ||
const storageMap = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
if (!inRange) { | ||
// Check if the key is already in the correct range. | ||
if ((0, util_1.bytesToBigInt)(val.key) >= startKey) { | ||
inRange = true; | ||
} | ||
else { | ||
return; | ||
} | ||
let inRange = false; | ||
let i = 0; | ||
/** Object conforming to {@link StorageRange.storage}. */ | ||
const storageMap = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
if (!inRange) { | ||
// Check if the key is already in the correct range. | ||
if ((0, util_1.bytesToBigInt)(val.key) >= startKey) { | ||
inRange = true; | ||
} | ||
if (i < limit) { | ||
storageMap[(0, util_1.bytesToHex)(val.key)] = { key: null, value: (0, util_1.bytesToHex)(val.value) }; | ||
i++; | ||
else { | ||
return; | ||
} | ||
else if (i === limit) { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: (0, util_1.bytesToHex)(val.key), | ||
}); | ||
} | ||
}); | ||
stream.on('end', () => { | ||
} | ||
if (i < limit) { | ||
storageMap[(0, util_1.bytesToHex)(val.key)] = { key: null, value: (0, util_1.bytesToHex)(val.value) }; | ||
i++; | ||
} | ||
else if (i === limit) { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: null, | ||
nextKey: (0, util_1.bytesToHex)(val.key), | ||
}); | ||
} | ||
}); | ||
stream.on('end', () => { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: null, | ||
}); | ||
}) | ||
.catch((e) => { | ||
reject(e); | ||
}); | ||
stream.on('error', (e) => reject(e)); | ||
}); | ||
@@ -732,3 +784,6 @@ } | ||
* | ||
* Note on caches: | ||
* Caches are downleveled (so: adopted for short-term usage) | ||
* by default. | ||
* | ||
* This means in particular: | ||
* 1. For caches instantiated as an LRU cache type | ||
@@ -739,20 +794,37 @@ * the copy() method will instantiate with an ORDERED_MAP cache | ||
* a large overhead here. | ||
* 2. Cache values are generally not copied along | ||
* 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. | ||
*/ | ||
shallowCopy() { | ||
const trie = this._trie.shallowCopy(false); | ||
shallowCopy(downlevelCaches = true) { | ||
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 (!this._accountCacheSettings.deactivate) { | ||
if (downlevelCaches && !this._accountCacheSettings.deactivate) { | ||
accountCacheOpts = { ...accountCacheOpts, type: index_js_1.CacheType.ORDERED_MAP }; | ||
} | ||
let storageCacheOpts = { ...this._storageCacheSettings }; | ||
if (!this._storageCacheSettings.deactivate) { | ||
if (downlevelCaches && !this._storageCacheSettings.deactivate) { | ||
storageCacheOpts = { ...storageCacheOpts, type: index_js_1.CacheType.ORDERED_MAP }; | ||
} | ||
let codeCacheOpts = { ...this._codeCacheSettings }; | ||
if (!this._codeCacheSettings.deactivate) { | ||
codeCacheOpts = { ...codeCacheOpts, type: index_js_1.CacheType.ORDERED_MAP }; | ||
} | ||
return new DefaultStateManager({ | ||
common, | ||
trie, | ||
prefixStorageTrieKeys, | ||
prefixCodeHashes, | ||
accountCacheOpts, | ||
storageCacheOpts, | ||
codeCacheOpts, | ||
}); | ||
@@ -766,2 +838,3 @@ } | ||
this._storageCache?.clear(); | ||
this._codeCache?.clear(); | ||
} | ||
@@ -768,0 +841,0 @@ } |
@@ -34,4 +34,4 @@ import { bytesToUnprefixedHex } from '@ethereumjs/util'; | ||
_saveCachePreState(cacheKeyHex) { | ||
const it = this._diffCache[this._checkpoints].get(cacheKeyHex); | ||
if (it === undefined) { | ||
const diffMap = this._diffCache[this._checkpoints]; | ||
if (!diffMap.has(cacheKeyHex)) { | ||
let oldElem; | ||
@@ -44,3 +44,3 @@ if (this._lruCache) { | ||
} | ||
this._diffCache[this._checkpoints].set(cacheKeyHex, oldElem); | ||
diffMap.set(cacheKeyHex, oldElem); | ||
} | ||
@@ -47,0 +47,0 @@ } |
export * from './account.js'; | ||
export * from './code.js'; | ||
export * from './storage.js'; | ||
export * from './types.js'; | ||
//# sourceMappingURL=index.d.ts.map |
export * from './account.js'; | ||
export * from './code.js'; | ||
export * from './storage.js'; | ||
export * from './types.js'; | ||
//# sourceMappingURL=index.js.map |
@@ -93,4 +93,10 @@ import { OrderedMap } from 'js-sdsl'; | ||
clear(): void; | ||
/** | ||
* Dumps the RLP-encoded storage values for an `account` specified by `address`. | ||
* @param address - The address of the `account` to return storage for | ||
* @returns {StorageCacheMap | undefined} - The storage values for the `account` or undefined if the `account` is not in the cache | ||
*/ | ||
dump(address: Address): StorageCacheMap | undefined; | ||
} | ||
export {}; | ||
//# sourceMappingURL=storage.d.ts.map |
@@ -37,9 +37,3 @@ import { bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util'; | ||
const addressStoragePreState = this._diffCache[this._checkpoints].get(addressHex); | ||
let diffStorageMap; | ||
if (addressStoragePreState === undefined) { | ||
diffStorageMap = new Map(); | ||
} | ||
else { | ||
diffStorageMap = addressStoragePreState; | ||
} | ||
const diffStorageMap = addressStoragePreState ?? new Map(); | ||
if (!diffStorageMap.has(keyHex)) { | ||
@@ -324,3 +318,18 @@ let oldStorageMap; | ||
} | ||
/** | ||
* Dumps the RLP-encoded storage values for an `account` specified by `address`. | ||
* @param address - The address of the `account` to return storage for | ||
* @returns {StorageCacheMap | undefined} - The storage values for the `account` or undefined if the `account` is not in the cache | ||
*/ | ||
dump(address) { | ||
let storageMap; | ||
if (this._lruCache) { | ||
storageMap = this._lruCache.get(bytesToUnprefixedHex(address.bytes)); | ||
} | ||
else { | ||
storageMap = this._orderedMapCache?.getElementByKey(bytesToUnprefixedHex(address.bytes)); | ||
} | ||
return storageMap; | ||
} | ||
} | ||
//# sourceMappingURL=storage.js.map |
@@ -164,3 +164,3 @@ import { Trie } from '@ethereumjs/trie'; | ||
dumpStorage(address) { | ||
const storageMap = this._storageCache._lruCache?.get(address.toString()); | ||
const storageMap = this._storageCache.dump(address); | ||
const dump = {}; | ||
@@ -167,0 +167,0 @@ if (storageMap !== undefined) { |
import { Common } from '@ethereumjs/common'; | ||
import { Trie } from '@ethereumjs/trie'; | ||
import { Account, Address } from '@ethereumjs/util'; | ||
import { AccountCache, CacheType, StorageCache } from './cache/index.js'; | ||
import { AccountCache, CacheType, CodeCache, StorageCache } from './cache/index.js'; | ||
import { OriginalStorageCache } from './cache/originalStorageCache.js'; | ||
import type { AccountFields, EVMStateManagerInterface, StorageDump } from '@ethereumjs/common'; | ||
import type { StorageRange } from '@ethereumjs/common/src'; | ||
import type { PrefixedHexString } from '@ethereumjs/util'; | ||
import type { DB, PrefixedHexString } from '@ethereumjs/util'; | ||
import type { Debugger } from 'debug'; | ||
@@ -49,3 +49,3 @@ export declare type StorageProof = { | ||
* | ||
* Default: 100000 (account cache) / 20000 (storage cache) | ||
* Default: 100000 (account cache) / 20000 (storage cache) / 20000 (code cache) | ||
* | ||
@@ -88,4 +88,16 @@ * Note: the cache/trie interplay mechanism is designed in a way that | ||
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; | ||
/** | ||
@@ -110,2 +122,3 @@ * The common to use | ||
protected _storageCache?: StorageCache; | ||
protected _codeCache?: CodeCache; | ||
originalStorageCache: OriginalStorageCache; | ||
@@ -116,8 +129,7 @@ protected _trie: Trie; | ||
}; | ||
protected _codeCache: { | ||
[key: string]: Uint8Array; | ||
}; | ||
protected readonly _prefixCodeHashes: boolean; | ||
protected readonly _prefixStorageTrieKeys: boolean; | ||
protected readonly _accountCacheSettings: CacheSettings; | ||
protected readonly _storageCacheSettings: CacheSettings; | ||
protected readonly _codeCacheSettings: CacheSettings; | ||
readonly common: Common; | ||
@@ -182,4 +194,16 @@ protected _checkpointCount: number; | ||
*/ | ||
protected _getStorageTrie(address: Address, account: Account): Promise<Trie>; | ||
protected _getStorageTrie(addressOrHash: Address | Uint8Array, account?: Account): Trie; | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
protected _getAccountTrie(): Trie; | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
protected _getCodeDB(): DB; | ||
/** | ||
* Gets the storage value associated with the provided `address` and `key`. This method returns | ||
@@ -296,3 +320,6 @@ * the shortest representation of the stored value. | ||
* | ||
* Note on caches: | ||
* Caches are downleveled (so: adopted for short-term usage) | ||
* by default. | ||
* | ||
* This means in particular: | ||
* 1. For caches instantiated as an LRU cache type | ||
@@ -303,5 +330,11 @@ * the copy() method will instantiate with an ORDERED_MAP cache | ||
* a large overhead here. | ||
* 2. Cache values are generally not copied along | ||
* 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. | ||
*/ | ||
shallowCopy(): DefaultStateManager; | ||
shallowCopy(downlevelCaches?: boolean): DefaultStateManager; | ||
/** | ||
@@ -308,0 +341,0 @@ * Clears all underlying caches |
@@ -7,3 +7,3 @@ import { Chain, Common } from '@ethereumjs/common'; | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js'; | ||
import { AccountCache, CacheType, StorageCache } from './cache/index.js'; | ||
import { AccountCache, CacheType, CodeCache, StorageCache } from './cache/index.js'; | ||
import { OriginalStorageCache } from './cache/originalStorageCache.js'; | ||
@@ -54,7 +54,7 @@ const { debug: createDebugLogger } = debugDefault; | ||
this._storageTries = {}; | ||
this._codeCache = {}; | ||
this.originalStorageCache = new OriginalStorageCache(this.getContractStorage.bind(this)); | ||
this._prefixCodeHashes = opts.prefixCodeHashes ?? true; | ||
this._prefixStorageTrieKeys = opts.prefixStorageTrieKeys ?? false; | ||
this._accountCacheSettings = { | ||
deactivate: opts.accountCacheOpts?.deactivate ?? false, | ||
deactivate: (opts.accountCacheOpts?.deactivate === true || opts.accountCacheOpts?.size === 0) ?? false, | ||
type: opts.accountCacheOpts?.type ?? CacheType.ORDERED_MAP, | ||
@@ -70,3 +70,3 @@ size: opts.accountCacheOpts?.size ?? 100000, | ||
this._storageCacheSettings = { | ||
deactivate: opts.storageCacheOpts?.deactivate ?? false, | ||
deactivate: (opts.storageCacheOpts?.deactivate === true || opts.storageCacheOpts?.size === 0) ?? false, | ||
type: opts.storageCacheOpts?.type ?? CacheType.ORDERED_MAP, | ||
@@ -81,2 +81,13 @@ size: opts.storageCacheOpts?.size ?? 20000, | ||
} | ||
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, | ||
}); | ||
} | ||
} | ||
@@ -157,2 +168,3 @@ /** | ||
} | ||
this._codeCache?.del(address); | ||
if (this._accountCacheSettings.deactivate) { | ||
@@ -175,2 +187,3 @@ await this._trie.del(address.bytes); | ||
async putContractCode(address, value) { | ||
this._codeCache?.put(address, value); | ||
const codeHash = keccak256(value); | ||
@@ -180,6 +193,2 @@ if (equalsBytes(codeHash, KECCAK256_NULL)) { | ||
} | ||
const key = this._prefixCodeHashes ? concatBytes(CODEHASH_PREFIX, codeHash) : codeHash; | ||
await this._trie.database().put(key, value); | ||
const keyHex = bytesToUnprefixedHex(key); | ||
this._codeCache[keyHex] = value; | ||
if (this.DEBUG) { | ||
@@ -200,2 +209,8 @@ this._debug(`Update codeHash (-> ${short(codeHash)}) for account ${address}`); | ||
async getContractCode(address) { | ||
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); | ||
@@ -211,11 +226,7 @@ if (!account) { | ||
: account.codeHash; | ||
const keyHex = bytesToUnprefixedHex(key); | ||
if (keyHex in this._codeCache) { | ||
return this._codeCache[keyHex]; | ||
const code = (await this._trie.database().get(key)) ?? new Uint8Array(0); | ||
if (!this._codeCacheSettings.deactivate) { | ||
this._codeCache.put(address, code); | ||
} | ||
else { | ||
const code = (await this._trie.database().get(key)) ?? new Uint8Array(0); | ||
this._codeCache[keyHex] = code; | ||
return code; | ||
} | ||
return code; | ||
} | ||
@@ -227,12 +238,20 @@ /** | ||
*/ | ||
async _getStorageTrie(address, account) { | ||
// from storage cache | ||
const addressHex = bytesToUnprefixedHex(address.bytes); | ||
const storageTrie = this._storageTries[addressHex]; | ||
// TODO PR: have a better interface for hashed address pull? | ||
_getStorageTrie(addressOrHash, account) { | ||
// use hashed key for lookup from storage cache | ||
const addressHex = bytesToUnprefixedHex(addressOrHash instanceof Address ? keccak256(addressOrHash.bytes) : addressOrHash); | ||
let storageTrie = this._storageTries[addressHex]; | ||
if (storageTrie === undefined) { | ||
const storageTrie = this._trie.shallowCopy(false); | ||
storageTrie.root(account.storageRoot); | ||
const keyPrefix = this._prefixStorageTrieKeys | ||
? (addressOrHash instanceof Address ? keccak256(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; | ||
} | ||
@@ -242,2 +261,18 @@ return storageTrie; | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
_getAccountTrie() { | ||
return this._trie; | ||
} | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
_getCodeDB() { | ||
return this._trie.database(); | ||
} | ||
/** | ||
* Gets the storage value associated with the provided `address` and `key`. This method returns | ||
@@ -266,3 +301,3 @@ * the shortest representation of the stored value. | ||
} | ||
const trie = await this._getStorageTrie(address, account); | ||
const trie = this._getStorageTrie(address, account); | ||
const value = await trie.get(key); | ||
@@ -284,3 +319,3 @@ if (!this._storageCacheSettings.deactivate) { | ||
return new Promise(async (resolve) => { | ||
const storageTrie = await this._getStorageTrie(address, account); | ||
const storageTrie = this._getStorageTrie(address, account); | ||
modifyTrie(storageTrie, async () => { | ||
@@ -370,2 +405,3 @@ // update storage cache | ||
this._accountCache?.checkpoint(); | ||
this._codeCache?.checkpoint(); | ||
this._checkpointCount++; | ||
@@ -382,2 +418,3 @@ } | ||
this._accountCache?.commit(); | ||
this._codeCache?.commit(); | ||
this._checkpointCount--; | ||
@@ -401,4 +438,4 @@ if (this._checkpointCount === 0) { | ||
this._accountCache?.revert(); | ||
this._codeCache?.revert(); | ||
this._storageTries = {}; | ||
this._codeCache = {}; | ||
this._checkpointCount--; | ||
@@ -414,2 +451,21 @@ if (this._checkpointCount === 0) { | ||
async flush() { | ||
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 = keccak256(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) { | ||
@@ -457,5 +513,5 @@ const items = this._storageCache.flush(); | ||
address: address.toString(), | ||
balance: '0x', | ||
balance: '0x0', | ||
codeHash: KECCAK256_NULL_S, | ||
nonce: '0x', | ||
nonce: '0x0', | ||
storageHash: KECCAK256_RLP_S, | ||
@@ -469,3 +525,3 @@ accountProof: (await this._trie.createProof(address.bytes)).map((p) => bytesToHex(p)), | ||
const storageProof = []; | ||
const storageTrie = await this._getStorageTrie(address, account); | ||
const storageTrie = this._getStorageTrie(address, account); | ||
for (const storageKey of storageSlots) { | ||
@@ -586,4 +642,6 @@ const proof = (await storageTrie.createProof(storageKey)).map((p) => bytesToHex(p)); | ||
} | ||
if (this._codeCache !== undefined && clearCache) { | ||
this._codeCache.clear(); | ||
} | ||
this._storageTries = {}; | ||
this._codeCache = {}; | ||
} | ||
@@ -603,15 +661,13 @@ /** | ||
} | ||
const trie = this._getStorageTrie(address, account); | ||
return new Promise((resolve, reject) => { | ||
this._getStorageTrie(address, account) | ||
.then((trie) => { | ||
const storage = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
storage[bytesToHex(val.key)] = bytesToHex(val.value); | ||
}); | ||
stream.on('end', () => { | ||
resolve(storage); | ||
}); | ||
}) | ||
.catch((e) => { | ||
const storage = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
storage[bytesToHex(val.key)] = bytesToHex(val.value); | ||
}); | ||
stream.on('end', () => { | ||
resolve(storage); | ||
}); | ||
stream.on('error', (e) => { | ||
reject(e); | ||
@@ -639,41 +695,37 @@ }); | ||
} | ||
const trie = this._getStorageTrie(address, account); | ||
return new Promise((resolve, reject) => { | ||
this._getStorageTrie(address, account) | ||
.then((trie) => { | ||
let inRange = false; | ||
let i = 0; | ||
/** Object conforming to {@link StorageRange.storage}. */ | ||
const storageMap = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
if (!inRange) { | ||
// Check if the key is already in the correct range. | ||
if (bytesToBigInt(val.key) >= startKey) { | ||
inRange = true; | ||
} | ||
else { | ||
return; | ||
} | ||
let inRange = false; | ||
let i = 0; | ||
/** Object conforming to {@link StorageRange.storage}. */ | ||
const storageMap = {}; | ||
const stream = trie.createReadStream(); | ||
stream.on('data', (val) => { | ||
if (!inRange) { | ||
// Check if the key is already in the correct range. | ||
if (bytesToBigInt(val.key) >= startKey) { | ||
inRange = true; | ||
} | ||
if (i < limit) { | ||
storageMap[bytesToHex(val.key)] = { key: null, value: bytesToHex(val.value) }; | ||
i++; | ||
else { | ||
return; | ||
} | ||
else if (i === limit) { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: bytesToHex(val.key), | ||
}); | ||
} | ||
}); | ||
stream.on('end', () => { | ||
} | ||
if (i < limit) { | ||
storageMap[bytesToHex(val.key)] = { key: null, value: bytesToHex(val.value) }; | ||
i++; | ||
} | ||
else if (i === limit) { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: null, | ||
nextKey: bytesToHex(val.key), | ||
}); | ||
} | ||
}); | ||
stream.on('end', () => { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: null, | ||
}); | ||
}) | ||
.catch((e) => { | ||
reject(e); | ||
}); | ||
stream.on('error', (e) => reject(e)); | ||
}); | ||
@@ -730,3 +782,6 @@ } | ||
* | ||
* Note on caches: | ||
* Caches are downleveled (so: adopted for short-term usage) | ||
* by default. | ||
* | ||
* This means in particular: | ||
* 1. For caches instantiated as an LRU cache type | ||
@@ -737,20 +792,37 @@ * the copy() method will instantiate with an ORDERED_MAP cache | ||
* a large overhead here. | ||
* 2. Cache values are generally not copied along | ||
* 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. | ||
*/ | ||
shallowCopy() { | ||
const trie = this._trie.shallowCopy(false); | ||
shallowCopy(downlevelCaches = true) { | ||
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 (!this._accountCacheSettings.deactivate) { | ||
if (downlevelCaches && !this._accountCacheSettings.deactivate) { | ||
accountCacheOpts = { ...accountCacheOpts, type: CacheType.ORDERED_MAP }; | ||
} | ||
let storageCacheOpts = { ...this._storageCacheSettings }; | ||
if (!this._storageCacheSettings.deactivate) { | ||
if (downlevelCaches && !this._storageCacheSettings.deactivate) { | ||
storageCacheOpts = { ...storageCacheOpts, type: CacheType.ORDERED_MAP }; | ||
} | ||
let codeCacheOpts = { ...this._codeCacheSettings }; | ||
if (!this._codeCacheSettings.deactivate) { | ||
codeCacheOpts = { ...codeCacheOpts, type: CacheType.ORDERED_MAP }; | ||
} | ||
return new DefaultStateManager({ | ||
common, | ||
trie, | ||
prefixStorageTrieKeys, | ||
prefixCodeHashes, | ||
accountCacheOpts, | ||
storageCacheOpts, | ||
codeCacheOpts, | ||
}); | ||
@@ -764,4 +836,5 @@ } | ||
this._storageCache?.clear(); | ||
this._codeCache?.clear(); | ||
} | ||
} | ||
//# sourceMappingURL=stateManager.js.map |
{ | ||
"name": "@ethereumjs/statemanager", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "An Ethereum statemanager implementation", | ||
@@ -38,3 +38,3 @@ "keywords": [ | ||
"clean": "../../config/cli/clean-package.sh", | ||
"coverage": "npx vitest run --coverage.enabled --coverage.reporter=lcov", | ||
"coverage": "DEBUG=ethjs npx vitest run --coverage.enabled --coverage.reporter=lcov", | ||
"docs:build": "typedoc --options typedoc.cjs", | ||
@@ -52,4 +52,6 @@ "examples": "ts-node ../../scripts/examples-runner.ts -- statemanager", | ||
"dependencies": { | ||
"@ethereumjs/common": "^4.0.0", | ||
"@ethereumjs/rlp": "^5.0.0", | ||
"@ethereumjs/common": "^4.1.0", | ||
"@ethereumjs/rlp": "^5.0.1", | ||
"@ethereumjs/trie": "^6.0.1", | ||
"@ethereumjs/util": "^9.0.1", | ||
"debug": "^4.3.3", | ||
@@ -62,9 +64,6 @@ "ethereum-cryptography": "^2.1.2", | ||
"devDependencies": { | ||
"@ethereumjs/block": "^5.0.0", | ||
"@ethereumjs/genesis": "^0.1.0", | ||
"@ethereumjs/trie": "^6.0.0", | ||
"@ethereumjs/util": "^9.0.0", | ||
"debug": "^4.3.3", | ||
"ethereum-cryptography": "^2.1.2" | ||
"@ethereumjs/block": "^5.0.1", | ||
"@ethereumjs/genesis": "^0.2.0", | ||
"@types/debug": "^4.1.9" | ||
} | ||
} |
@@ -48,5 +48,5 @@ # @ethereumjs/statemanager | ||
### Account and Storage Caches | ||
### Account, Storage and Code Caches | ||
Starting with the v2 release the StateManager comes with a significantly more elaborate caching mechanism for account and storage 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. | ||
@@ -53,0 +53,0 @@ 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. |
@@ -52,4 +52,4 @@ import { bytesToUnprefixedHex } from '@ethereumjs/util' | ||
_saveCachePreState(cacheKeyHex: string) { | ||
const it = this._diffCache[this._checkpoints].get(cacheKeyHex) | ||
if (it === undefined) { | ||
const diffMap = this._diffCache[this._checkpoints] | ||
if (!diffMap.has(cacheKeyHex)) { | ||
let oldElem: AccountCacheElement | undefined | ||
@@ -61,3 +61,3 @@ if (this._lruCache) { | ||
} | ||
this._diffCache[this._checkpoints].set(cacheKeyHex, oldElem) | ||
diffMap.set(cacheKeyHex, oldElem) | ||
} | ||
@@ -64,0 +64,0 @@ } |
export * from './account.js' | ||
export * from './code.js' | ||
export * from './storage.js' | ||
export * from './types.js' |
@@ -56,8 +56,3 @@ import { bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util' | ||
const addressStoragePreState = this._diffCache[this._checkpoints].get(addressHex) | ||
let diffStorageMap: DiffStorageCacheMap | ||
if (addressStoragePreState === undefined) { | ||
diffStorageMap = new Map() | ||
} else { | ||
diffStorageMap = addressStoragePreState | ||
} | ||
const diffStorageMap: DiffStorageCacheMap = addressStoragePreState ?? new Map() | ||
@@ -358,2 +353,17 @@ if (!diffStorageMap.has(keyHex)) { | ||
} | ||
/** | ||
* Dumps the RLP-encoded storage values for an `account` specified by `address`. | ||
* @param address - The address of the `account` to return storage for | ||
* @returns {StorageCacheMap | undefined} - The storage values for the `account` or undefined if the `account` is not in the cache | ||
*/ | ||
dump(address: Address): StorageCacheMap | undefined { | ||
let storageMap | ||
if (this._lruCache) { | ||
storageMap = this._lruCache!.get(bytesToUnprefixedHex(address.bytes)) | ||
} else { | ||
storageMap = this._orderedMapCache?.getElementByKey(bytesToUnprefixedHex(address.bytes)) | ||
} | ||
return storageMap | ||
} | ||
} |
@@ -185,3 +185,3 @@ import { Trie } from '@ethereumjs/trie' | ||
dumpStorage(address: Address): Promise<StorageDump> { | ||
const storageMap = this._storageCache._lruCache?.get(address.toString()) | ||
const storageMap = this._storageCache.dump(address) | ||
const dump: StorageDump = {} | ||
@@ -188,0 +188,0 @@ if (storageMap !== undefined) { |
@@ -28,3 +28,3 @@ import { Chain, Common } from '@ethereumjs/common' | ||
import { AccountCache, CacheType, StorageCache } from './cache/index.js' | ||
import { AccountCache, CacheType, CodeCache, StorageCache } from './cache/index.js' | ||
import { OriginalStorageCache } from './cache/originalStorageCache.js' | ||
@@ -34,3 +34,3 @@ | ||
import type { StorageRange } from '@ethereumjs/common/src' | ||
import type { PrefixedHexString } from '@ethereumjs/util' | ||
import type { DB, PrefixedHexString } from '@ethereumjs/util' | ||
import type { Debugger } from 'debug' | ||
@@ -82,3 +82,3 @@ const { debug: createDebugLogger } = debugDefault | ||
* | ||
* Default: 100000 (account cache) / 20000 (storage cache) | ||
* Default: 100000 (account cache) / 20000 (storage cache) / 20000 (code cache) | ||
* | ||
@@ -125,2 +125,14 @@ * Note: the cache/trie interplay mechanism is designed in a way that | ||
/** | ||
* 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 | ||
@@ -130,2 +142,4 @@ | ||
codeCacheOpts?: CacheOptions | ||
/** | ||
@@ -151,2 +165,3 @@ * The common to use | ||
protected _storageCache?: StorageCache | ||
protected _codeCache?: CodeCache | ||
@@ -157,7 +172,8 @@ originalStorageCache: OriginalStorageCache | ||
protected _storageTries: { [key: string]: Trie } | ||
protected _codeCache: { [key: string]: Uint8Array } | ||
protected readonly _prefixCodeHashes: boolean | ||
protected readonly _prefixStorageTrieKeys: boolean | ||
protected readonly _accountCacheSettings: CacheSettings | ||
protected readonly _storageCacheSettings: CacheSettings | ||
protected readonly _codeCacheSettings: CacheSettings | ||
@@ -199,3 +215,2 @@ public readonly common: Common | ||
this._storageTries = {} | ||
this._codeCache = {} | ||
@@ -205,4 +220,6 @@ this.originalStorageCache = new OriginalStorageCache(this.getContractStorage.bind(this)) | ||
this._prefixCodeHashes = opts.prefixCodeHashes ?? true | ||
this._prefixStorageTrieKeys = opts.prefixStorageTrieKeys ?? false | ||
this._accountCacheSettings = { | ||
deactivate: opts.accountCacheOpts?.deactivate ?? false, | ||
deactivate: | ||
(opts.accountCacheOpts?.deactivate === true || opts.accountCacheOpts?.size === 0) ?? false, | ||
type: opts.accountCacheOpts?.type ?? CacheType.ORDERED_MAP, | ||
@@ -220,3 +237,4 @@ size: opts.accountCacheOpts?.size ?? 100000, | ||
this._storageCacheSettings = { | ||
deactivate: opts.storageCacheOpts?.deactivate ?? false, | ||
deactivate: | ||
(opts.storageCacheOpts?.deactivate === true || opts.storageCacheOpts?.size === 0) ?? false, | ||
type: opts.storageCacheOpts?.type ?? CacheType.ORDERED_MAP, | ||
@@ -232,2 +250,16 @@ size: opts.storageCacheOpts?.size ?? 20000, | ||
} | ||
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, | ||
}) | ||
} | ||
} | ||
@@ -316,2 +348,5 @@ | ||
} | ||
this._codeCache?.del(address) | ||
if (this._accountCacheSettings.deactivate) { | ||
@@ -334,4 +369,4 @@ await this._trie.del(address.bytes) | ||
async putContractCode(address: Address, value: Uint8Array): Promise<void> { | ||
this._codeCache?.put(address, value) | ||
const codeHash = keccak256(value) | ||
if (equalsBytes(codeHash, KECCAK256_NULL)) { | ||
@@ -341,11 +376,6 @@ return | ||
const key = this._prefixCodeHashes ? concatBytes(CODEHASH_PREFIX, codeHash) : codeHash | ||
await this._trie.database().put(key, value) | ||
const keyHex = bytesToUnprefixedHex(key) | ||
this._codeCache[keyHex] = value | ||
if (this.DEBUG) { | ||
this._debug(`Update codeHash (-> ${short(codeHash)}) for account ${address}`) | ||
} | ||
if ((await this.getAccount(address)) === undefined) { | ||
@@ -364,2 +394,8 @@ await this.putAccount(address, new Account()) | ||
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) | ||
@@ -375,11 +411,8 @@ if (!account) { | ||
: account.codeHash | ||
const code = (await this._trie.database().get(key)) ?? new Uint8Array(0) | ||
const keyHex = bytesToUnprefixedHex(key) | ||
if (keyHex in this._codeCache) { | ||
return this._codeCache[keyHex] | ||
} else { | ||
const code = (await this._trie.database().get(key)) ?? new Uint8Array(0) | ||
this._codeCache[keyHex] = code | ||
return code | ||
if (!this._codeCacheSettings.deactivate) { | ||
this._codeCache!.put(address, code) | ||
} | ||
return code | ||
} | ||
@@ -392,12 +425,24 @@ | ||
*/ | ||
protected async _getStorageTrie(address: Address, account: Account): Promise<Trie> { | ||
// from storage cache | ||
const addressHex = bytesToUnprefixedHex(address.bytes) | ||
const storageTrie = this._storageTries[addressHex] | ||
// 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 ? keccak256(addressOrHash.bytes) : addressOrHash | ||
) | ||
let storageTrie = this._storageTries[addressHex] | ||
if (storageTrie === undefined) { | ||
const storageTrie = this._trie.shallowCopy(false) | ||
storageTrie.root(account.storageRoot) | ||
const keyPrefix = this._prefixStorageTrieKeys | ||
? (addressOrHash instanceof Address ? keccak256(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 | ||
} | ||
@@ -408,2 +453,20 @@ return storageTrie | ||
/** | ||
* Gets the storage trie for an account from the storage | ||
* cache or does a lookup. | ||
* @private | ||
*/ | ||
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 | ||
@@ -433,3 +496,3 @@ * the shortest representation of the stored value. | ||
} | ||
const trie = await this._getStorageTrie(address, account) | ||
const trie = this._getStorageTrie(address, account) | ||
const value = await trie.get(key) | ||
@@ -456,3 +519,3 @@ if (!this._storageCacheSettings.deactivate) { | ||
return new Promise(async (resolve) => { | ||
const storageTrie = await this._getStorageTrie(address, account) | ||
const storageTrie = this._getStorageTrie(address, account) | ||
@@ -554,2 +617,3 @@ modifyTrie(storageTrie, async () => { | ||
this._accountCache?.checkpoint() | ||
this._codeCache?.checkpoint() | ||
this._checkpointCount++ | ||
@@ -567,2 +631,3 @@ } | ||
this._accountCache?.commit() | ||
this._codeCache?.commit() | ||
this._checkpointCount-- | ||
@@ -589,4 +654,5 @@ | ||
this._accountCache?.revert() | ||
this._codeCache?.revert() | ||
this._storageTries = {} | ||
this._codeCache = {} | ||
@@ -605,2 +671,24 @@ this._checkpointCount-- | ||
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 = keccak256(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) { | ||
@@ -649,5 +737,5 @@ const items = this._storageCache!.flush() | ||
address: address.toString(), | ||
balance: '0x', | ||
balance: '0x0', | ||
codeHash: KECCAK256_NULL_S, | ||
nonce: '0x', | ||
nonce: '0x0', | ||
storageHash: KECCAK256_RLP_S, | ||
@@ -663,3 +751,3 @@ accountProof: (await this._trie.createProof(address.bytes)).map((p) => bytesToHex(p)), | ||
const storageProof: StorageProof[] = [] | ||
const storageTrie = await this._getStorageTrie(address, account) | ||
const storageTrie = this._getStorageTrie(address, account) | ||
@@ -795,4 +883,6 @@ for (const storageKey of storageSlots) { | ||
} | ||
if (this._codeCache !== undefined && clearCache) { | ||
this._codeCache!.clear() | ||
} | ||
this._storageTries = {} | ||
this._codeCache = {} | ||
} | ||
@@ -813,19 +903,17 @@ | ||
} | ||
const trie = this._getStorageTrie(address, account) | ||
return new Promise((resolve, reject) => { | ||
this._getStorageTrie(address, account) | ||
.then((trie) => { | ||
const storage: StorageDump = {} | ||
const stream = trie.createReadStream() | ||
const storage: StorageDump = {} | ||
const stream = trie.createReadStream() | ||
stream.on('data', (val: any) => { | ||
storage[bytesToHex(val.key)] = bytesToHex(val.value) | ||
}) | ||
stream.on('end', () => { | ||
resolve(storage) | ||
}) | ||
}) | ||
.catch((e) => { | ||
reject(e) | ||
}) | ||
stream.on('data', (val: any) => { | ||
storage[bytesToHex(val.key)] = bytesToHex(val.value) | ||
}) | ||
stream.on('end', () => { | ||
resolve(storage) | ||
}) | ||
stream.on('error', (e) => { | ||
reject(e) | ||
}) | ||
}) | ||
@@ -853,44 +941,40 @@ } | ||
} | ||
const trie = this._getStorageTrie(address, account) | ||
return new Promise((resolve, reject) => { | ||
this._getStorageTrie(address, account) | ||
.then((trie) => { | ||
let inRange = false | ||
let i = 0 | ||
let inRange = false | ||
let i = 0 | ||
/** Object conforming to {@link StorageRange.storage}. */ | ||
const storageMap: StorageRange['storage'] = {} | ||
const stream = trie.createReadStream() | ||
/** Object conforming to {@link StorageRange.storage}. */ | ||
const storageMap: StorageRange['storage'] = {} | ||
const stream = trie.createReadStream() | ||
stream.on('data', (val: any) => { | ||
if (!inRange) { | ||
// Check if the key is already in the correct range. | ||
if (bytesToBigInt(val.key) >= startKey) { | ||
inRange = true | ||
} else { | ||
return | ||
} | ||
} | ||
stream.on('data', (val: any) => { | ||
if (!inRange) { | ||
// Check if the key is already in the correct range. | ||
if (bytesToBigInt(val.key) >= startKey) { | ||
inRange = true | ||
} else { | ||
return | ||
} | ||
} | ||
if (i < limit) { | ||
storageMap[bytesToHex(val.key)] = { key: null, value: bytesToHex(val.value) } | ||
i++ | ||
} else if (i === limit) { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: bytesToHex(val.key), | ||
}) | ||
} | ||
if (i < limit) { | ||
storageMap[bytesToHex(val.key)] = { key: null, value: bytesToHex(val.value) } | ||
i++ | ||
} else if (i === limit) { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: bytesToHex(val.key), | ||
}) | ||
} | ||
}) | ||
stream.on('end', () => { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: null, | ||
}) | ||
}) | ||
stream.on('end', () => { | ||
resolve({ | ||
storage: storageMap, | ||
nextKey: null, | ||
}) | ||
.catch((e) => { | ||
reject(e) | ||
}) | ||
}) | ||
stream.on('error', (e) => reject(e)) | ||
}) | ||
@@ -949,3 +1033,6 @@ } | ||
* | ||
* Note on caches: | ||
* Caches are downleveled (so: adopted for short-term usage) | ||
* by default. | ||
* | ||
* This means in particular: | ||
* 1. For caches instantiated as an LRU cache type | ||
@@ -956,21 +1043,39 @@ * the copy() method will instantiate with an ORDERED_MAP cache | ||
* a large overhead here. | ||
* 2. Cache values are generally not copied along | ||
* 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. | ||
*/ | ||
shallowCopy(): DefaultStateManager { | ||
const trie = this._trie.shallowCopy(false) | ||
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 (!this._accountCacheSettings.deactivate) { | ||
if (downlevelCaches && !this._accountCacheSettings.deactivate) { | ||
accountCacheOpts = { ...accountCacheOpts, type: CacheType.ORDERED_MAP } | ||
} | ||
let storageCacheOpts = { ...this._storageCacheSettings } | ||
if (!this._storageCacheSettings.deactivate) { | ||
if (downlevelCaches && !this._storageCacheSettings.deactivate) { | ||
storageCacheOpts = { ...storageCacheOpts, type: CacheType.ORDERED_MAP } | ||
} | ||
let codeCacheOpts = { ...this._codeCacheSettings } | ||
if (!this._codeCacheSettings.deactivate) { | ||
codeCacheOpts = { ...codeCacheOpts, type: CacheType.ORDERED_MAP } | ||
} | ||
return new DefaultStateManager({ | ||
common, | ||
trie, | ||
prefixStorageTrieKeys, | ||
prefixCodeHashes, | ||
accountCacheOpts, | ||
storageCacheOpts, | ||
codeCacheOpts, | ||
}) | ||
@@ -985,3 +1090,4 @@ } | ||
this._storageCache?.clear() | ||
this._codeCache?.clear() | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
592916
3
97
8065
9
+ Added@ethereumjs/trie@^6.0.1
+ Added@ethereumjs/util@^9.0.1
+ Added@ethereumjs/trie@6.2.1(transitive)
+ Added@types/node@22.9.0(transitive)
+ Added@types/readable-stream@2.3.15(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedlru-cache@10.1.0(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedsafe-buffer@5.1.25.2.1(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
Updated@ethereumjs/common@^4.1.0
Updated@ethereumjs/rlp@^5.0.1