UNPKG

@ignored/statemanager

Version:
200 lines (177 loc) 5.04 kB
import { Account, isFalsy, isTruthy } from '@ignored/util' import type { Address } from '@ignored/util' const Tree = require('functional-red-black-tree') export type getCb = (address: Address) => Promise<Account | undefined> export type putCb = (keyBuf: Buffer, accountRlp: Buffer) => Promise<void> export type deleteCb = (keyBuf: Buffer) => Promise<void> export interface CacheOpts { getCb: getCb putCb: putCb deleteCb: deleteCb } /** * @ignore */ export class Cache { _cache: any _checkpoints: any[] _getCb: getCb _putCb: putCb _deleteCb: deleteCb constructor(opts: CacheOpts) { this._cache = Tree() this._getCb = opts.getCb this._putCb = opts.putCb this._deleteCb = opts.deleteCb this._checkpoints = [] } /** * Puts account to cache under its address. * @param key - Address of account * @param val - Account */ put(key: Address, val: Account, fromTrie: boolean = false): void { const modified = !fromTrie this._update(key, val, modified, false) } /** * Returns the queried account or an empty account. * @param key - Address of account */ get(key: Address): Account { const account = this.lookup(key) return account ?? new Account() } /** * Returns the queried account or undefined. * @param key - Address of account */ lookup(key: Address): Account | undefined { const keyStr = key.buf.toString('hex') const it = this._cache.find(keyStr) if (isTruthy(it.node)) { const rlp = it.value.val const account = Account.fromRlpSerializedAccount(rlp) ;(account as any).virtual = it.value.virtual return account } } /** * Returns true if the key was deleted and thus existed in the cache earlier * @param key - trie key to lookup */ keyIsDeleted(key: Address): boolean { const keyStr = key.buf.toString('hex') const it = this._cache.find(keyStr) if (isTruthy(it.node)) { return it.value.deleted } return false } /** * Looks up address in cache, if not found, looks it up * in the underlying trie. * @param key - Address of account */ async getOrLoad(address: Address): Promise<Account> { let account = this.lookup(address) if (!account) { account = await this._getCb(address) if (account) { this._update(address, account, false, false, false) } else { account = new Account() ;(account as any).virtual = true this._update(address, account, false, false, true) } } return account } /** * Flushes cache by updating accounts that have been modified * and removing accounts that have been deleted. */ async flush(): Promise<void> { const it = this._cache.begin let next = true while (next) { if (isTruthy(it.value) && isTruthy(it.value.modified) && isFalsy(it.value.deleted)) { it.value.modified = false const accountRlp = it.value.val const keyBuf = Buffer.from(it.key, 'hex') await this._putCb(keyBuf, accountRlp) next = it.hasNext it.next() } else if (isTruthy(it.value) && isTruthy(it.value.modified) && isTruthy(it.value.deleted)) { it.value.modified = false it.value.deleted = true it.value.virtual = true it.value.val = new Account().serialize() const keyBuf = Buffer.from(it.key, 'hex') await this._deleteCb(keyBuf) next = it.hasNext it.next() } else { next = it.hasNext it.next() } } } /** * Marks current state of cache as checkpoint, which can * later on be reverted or commited. */ checkpoint(): void { this._checkpoints.push(this._cache) } /** * Revert changes to cache last checkpoint (no effect on trie). */ revert(): void { this._cache = this._checkpoints.pop() } /** * Commits to current state of cache (no effect on trie). */ commit(): void { this._checkpoints.pop() } /** * Clears cache. */ clear(): void { this._cache = Tree() } /** * Marks address as deleted in cache. * @param key - Address */ del(key: Address): void { this._update(key, new Account(), true, true, true) } /** * Generic cache update helper function * * @param key * @param value * @param modified - Has the value been modfied or is it coming unchanged from the trie (also used for deleted accounts) * @param deleted - Delete operation on an account * @param virtual - Account doesn't exist in the underlying trie */ _update( key: Address, value: Account, modified: boolean, deleted: boolean, virtual = false ): void { const keyHex = key.buf.toString('hex') const it = this._cache.find(keyHex) const val = value.serialize() if (isTruthy(it.node)) { this._cache = it.update({ val, modified, deleted, virtual }) } else { this._cache = this._cache.insert(keyHex, { val, modified, deleted, virtual }) } } }