Simplify LedgerRef: remove unnecessary abstraction (#2826)

This commit is contained in:
andri lim 2024-11-06 09:01:56 +07:00 committed by GitHub
parent 6c3bbbf22c
commit f201eb611e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 915 additions and 1627 deletions

View File

@ -9,8 +9,7 @@
# according to those terms. # according to those terms.
import import
./db/core_db/base/base_config, ./db/core_db/base/base_config
./db/ledger/base/base_config
func vmName(): string = func vmName(): string =
when defined(evmc_enabled): when defined(evmc_enabled):
@ -29,8 +28,6 @@ const
rc &= ", boehm/gc" rc &= ", boehm/gc"
when 0 < coreDbBaseConfigExtras.len: when 0 < coreDbBaseConfigExtras.len:
rc &= ", " & coreDbBaseConfigExtras rc &= ", " & coreDbBaseConfigExtras
when 0 < ledgerBaseConfigExtras.len:
rc &= ", " & ledgerBaseConfigExtras
rc &= " enabled" rc &= " enabled"
rc rc

View File

@ -12,7 +12,6 @@
import import
results, results,
../../db/ledger,
../../evm/state, ../../evm/state,
../../evm/types, ../../evm/types,
../executor, ../executor,

View File

@ -5,31 +5,926 @@
# http://www.apache.org/licenses/LICENSE-2.0) # http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or # * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT) # http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed # at your option. This file may not be copied, modified, or distributed except
# except according to those terms. # according to those terms.
## Unifies different ledger management APIs. All ledger objects are
## derived from the base objects
## ::
## LedgerSpRef => SavePoint, overloaded SavePoint etc
##
{.push raises: [].} {.push raises: [].}
import import
eth/common, std/[tables, hashes, sets, typetraits],
./core_db, chronicles,
./ledger/backend/accounts_ledger, eth/common/eth_types,
./ledger/base/[base_config, base_desc, base_helpers], results,
./ledger/[base, base_iterators] minilru,
../utils/mergeutils,
../evm/code_bytes,
../stateless/multi_keys,
"/.."/[constants, utils/utils],
./access_list as ac_access_list,
"."/[core_db, storage_types, transient_storage],
./aristo/aristo_blobify
export AccountsLedgerRef, base, base_config, base_iterators export
code_bytes
const
debugLedgerRef = false
codeLruSize = 16*1024
# An LRU cache of 16K items gives roughly 90% hit rate anecdotally on a
# small range of test blocks - this number could be studied in more detail
# Per EIP-170, a the code of a contract can be up to `MAX_CODE_SIZE` = 24kb,
# which would cause a worst case of 386MB memory usage though in reality
# code sizes are much smaller - it would make sense to study these numbers
# in greater detail.
slotsLruSize = 16 * 1024
type
AccountFlag = enum
Alive
IsNew
Dirty
Touched
CodeChanged
StorageChanged
NewlyCreated # EIP-6780: self destruct only in same transaction
AccountFlags = set[AccountFlag]
AccountRef = ref object
statement: CoreDbAccount
accPath: Hash32
flags: AccountFlags
code: CodeBytesRef
originalStorage: TableRef[UInt256, UInt256]
overlayStorage: Table[UInt256, UInt256]
WitnessData* = object
storageKeys*: HashSet[UInt256]
codeTouched*: bool
LedgerRef* = ref object
ledger: CoreDbAccRef # AccountLedger
kvt: CoreDbKvtRef
savePoint: LedgerSpRef
witnessCache: Table[Address, WitnessData]
isDirty: bool
ripemdSpecial: bool
storeSlotHash*: bool
cache: Table[Address, AccountRef]
# Second-level cache for the ledger save point, which is cleared on every
# persist
code: LruCache[Hash32, CodeBytesRef]
## The code cache provides two main benefits:
##
## * duplicate code is shared in memory beween accounts
## * the jump destination table does not have to be recomputed for every
## execution, for commonly called called contracts
##
## The former feature is specially important in the 2.3-2.7M block range
## when underpriced code opcodes are being run en masse - both advantages
## help performance broadly as well.
slots: LruCache[UInt256, Hash32]
## Because the same slots often reappear, we want to avoid writing them
## over and over again to the database to avoid the WAL and compation
## write amplification that ensues
ReadOnlyStateDB* = distinct LedgerRef
TransactionState = enum
Pending
Committed
RolledBack
LedgerSpRef* = ref object
parentSavepoint: LedgerSpRef
cache: Table[Address, AccountRef]
dirty: Table[Address, AccountRef]
selfDestruct: HashSet[Address]
logEntries: seq[Log]
accessList: ac_access_list.AccessList
transientStorage: TransientStorage
state: TransactionState
when debugLedgerRef:
depth: int
const
emptyEthAccount = Account.init()
resetFlags = {
Dirty,
IsNew,
Touched,
CodeChanged,
StorageChanged,
NewlyCreated
}
when debugLedgerRef:
import
stew/byteutils
proc inspectSavePoint(name: string, x: LedgerSpRef) =
debugEcho "*** ", name, ": ", x.depth, " ***"
var sp = x
while sp != nil:
for address, acc in sp.cache:
debugEcho address.toHex, " ", acc.flags
sp = sp.parentSavepoint
template logTxt(info: static[string]): static[string] =
"LedgerRef " & info
template toAccountKey(acc: AccountRef): Hash32 =
acc.accPath
template toAccountKey(eAddr: Address): Hash32 =
eAddr.data.keccak256
proc beginSavepoint*(ac: LedgerRef): LedgerSpRef {.gcsafe.}
proc resetCoreDbAccount(ac: LedgerRef, acc: AccountRef) =
const info = "resetCoreDbAccount(): "
ac.ledger.clearStorage(acc.toAccountKey).isOkOr:
raiseAssert info & $$error
acc.statement.nonce = emptyEthAccount.nonce
acc.statement.balance = emptyEthAccount.balance
acc.statement.codeHash = emptyEthAccount.codeHash
proc getAccount(
ac: LedgerRef;
address: Address;
shouldCreate = true;
): AccountRef =
# search account from layers of cache
var sp = ac.savePoint
while sp != nil:
result = sp.cache.getOrDefault(address)
if not result.isNil:
return
sp = sp.parentSavepoint
if ac.cache.pop(address, result):
# Check second-level cache
ac.savePoint.cache[address] = result
return
# not found in cache, look into state trie
let
accPath = address.toAccountKey
rc = ac.ledger.fetch accPath
if rc.isOk:
result = AccountRef(
statement: rc.value,
accPath: accPath,
flags: {Alive})
elif shouldCreate:
result = AccountRef(
statement: CoreDbAccount(
nonce: emptyEthAccount.nonce,
balance: emptyEthAccount.balance,
codeHash: emptyEthAccount.codeHash),
accPath: accPath,
flags: {Alive, IsNew})
else:
return # ignore, don't cache
# cache the account
ac.savePoint.cache[address] = result
ac.savePoint.dirty[address] = result
proc clone(acc: AccountRef, cloneStorage: bool): AccountRef =
result = AccountRef(
statement: acc.statement,
accPath: acc.accPath,
flags: acc.flags,
code: acc.code)
if cloneStorage:
result.originalStorage = acc.originalStorage
# it's ok to clone a table this way
result.overlayStorage = acc.overlayStorage
proc isEmpty(acc: AccountRef): bool =
acc.statement.nonce == 0 and
acc.statement.balance.isZero and
acc.statement.codeHash == EMPTY_CODE_HASH
template exists(acc: AccountRef): bool =
Alive in acc.flags
proc originalStorageValue(
acc: AccountRef;
slot: UInt256;
ac: LedgerRef;
): UInt256 =
# share the same original storage between multiple
# versions of account
if acc.originalStorage.isNil:
acc.originalStorage = newTable[UInt256, UInt256]()
else:
acc.originalStorage[].withValue(slot, val) do:
return val[]
# Not in the original values cache - go to the DB.
let
slotKey = ac.slots.get(slot).valueOr:
slot.toBytesBE.keccak256
rc = ac.ledger.slotFetch(acc.toAccountKey, slotKey)
if rc.isOk:
result = rc.value
acc.originalStorage[slot] = result
proc storageValue(
acc: AccountRef;
slot: UInt256;
ac: LedgerRef;
): UInt256 =
acc.overlayStorage.withValue(slot, val) do:
return val[]
do:
result = acc.originalStorageValue(slot, ac)
proc kill(ac: LedgerRef, acc: AccountRef) =
acc.flags.excl Alive
acc.overlayStorage.clear()
acc.originalStorage = nil
ac.resetCoreDbAccount acc
acc.code.reset()
type
PersistMode = enum
DoNothing
Update
Remove
proc persistMode(acc: AccountRef): PersistMode =
result = DoNothing
if Alive in acc.flags:
if IsNew in acc.flags or Dirty in acc.flags:
result = Update
else:
if IsNew notin acc.flags:
result = Remove
proc persistCode(acc: AccountRef, ac: LedgerRef) =
if acc.code.len != 0 and not acc.code.persisted:
let rc = ac.kvt.put(
contractHashKey(acc.statement.codeHash).toOpenArray, acc.code.bytes())
if rc.isErr:
warn logTxt "persistCode()",
codeHash=acc.statement.codeHash, error=($$rc.error)
else:
# If the ledger changes rolled back entirely from the database, the ledger
# code cache must also be cleared!
acc.code.persisted = true
proc persistStorage(acc: AccountRef, ac: LedgerRef) =
const info = "persistStorage(): "
if acc.overlayStorage.len == 0:
# TODO: remove the storage too if we figure out
# how to create 'virtual' storage room for each account
return
if acc.originalStorage.isNil:
acc.originalStorage = newTable[UInt256, UInt256]()
# Make sure that there is an account entry on the database. This is needed by
# `Aristo` for updating the account's storage area reference. As a side effect,
# this action also updates the latest statement data.
ac.ledger.merge(acc.toAccountKey, acc.statement).isOkOr:
raiseAssert info & $$error
# Save `overlayStorage[]` on database
for slot, value in acc.overlayStorage:
acc.originalStorage[].withValue(slot, v):
if v[] == value:
continue # Avoid writing A-B-A updates
var cached = true
let slotKey = ac.slots.get(slot).valueOr:
cached = false
let hash = slot.toBytesBE.keccak256
ac.slots.put(slot, hash)
hash
if value > 0:
ac.ledger.slotMerge(acc.toAccountKey, slotKey, value).isOkOr:
raiseAssert info & $$error
# move the overlayStorage to originalStorage, related to EIP2200, EIP1283
acc.originalStorage[slot] = value
else:
ac.ledger.slotDelete(acc.toAccountKey, slotKey).isOkOr:
if error.error != StoNotFound:
raiseAssert info & $$error
discard
acc.originalStorage.del(slot)
if ac.storeSlotHash and not cached:
# Write only if it was not cached to avoid writing the same data over and
# over..
let
key = slotKey.data.slotHashToSlotKey
rc = ac.kvt.put(key.toOpenArray, blobify(slot).data)
if rc.isErr:
warn logTxt "persistStorage()", slot, error=($$rc.error)
acc.overlayStorage.clear()
proc makeDirty(ac: LedgerRef, address: Address, cloneStorage = true): AccountRef =
ac.isDirty = true
result = ac.getAccount(address)
if address in ac.savePoint.cache:
# it's already in latest savepoint
result.flags.incl Dirty
ac.savePoint.dirty[address] = result
return
# put a copy into latest savepoint
result = result.clone(cloneStorage)
result.flags.incl Dirty
ac.savePoint.cache[address] = result
ac.savePoint.dirty[address] = result
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public constructor # Public methods
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc init*(_: type LedgerRef, db: CoreDbRef, storeSlotHash: bool = false): LedgerRef = # The LedgerRef is modeled after TrieDatabase for it's transaction style
LedgerRef(ac: AccountsLedgerRef.init(db, storeSlotHash)).bless(db) proc init*(x: typedesc[LedgerRef], db: CoreDbRef, storeSlotHash: bool): LedgerRef =
new result
result.ledger = db.ctx.getAccounts()
result.kvt = db.ctx.getKvt()
result.witnessCache = Table[Address, WitnessData]()
result.storeSlotHash = storeSlotHash
result.code = typeof(result.code).init(codeLruSize)
result.slots = typeof(result.slots).init(slotsLruSize)
discard result.beginSavepoint
proc init*(x: typedesc[LedgerRef], db: CoreDbRef): LedgerRef =
init(x, db, false)
proc getStateRoot*(ac: LedgerRef): Hash32 =
const info = "state(): "
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
# make sure all cache already committed
doAssert(ac.isDirty == false)
ac.ledger.stateRoot(updateOk=true).valueOr:
raiseAssert info & $$error
proc isTopLevelClean*(ac: LedgerRef): bool =
## Getter, returns `true` if all pending data have been commited.
not ac.isDirty and ac.savePoint.parentSavepoint.isNil
proc beginSavepoint*(ac: LedgerRef): LedgerSpRef =
new result
result.cache = Table[Address, AccountRef]()
result.accessList.init()
result.transientStorage.init()
result.state = Pending
result.parentSavepoint = ac.savePoint
ac.savePoint = result
when debugLedgerRef:
if not result.parentSavePoint.isNil:
result.depth = result.parentSavePoint.depth + 1
inspectSavePoint("snapshot", result)
proc rollback*(ac: LedgerRef, sp: LedgerSpRef) =
# Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before
# its parent transactions:
doAssert ac.savePoint == sp and sp.state == Pending
ac.savePoint = sp.parentSavepoint
sp.state = RolledBack
when debugLedgerRef:
inspectSavePoint("rollback", ac.savePoint)
proc commit*(ac: LedgerRef, sp: LedgerSpRef) =
# Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before
# its parent transactions:
doAssert ac.savePoint == sp and sp.state == Pending
# cannot commit most inner savepoint
doAssert not sp.parentSavepoint.isNil
ac.savePoint = sp.parentSavepoint
ac.savePoint.cache.mergeAndReset(sp.cache)
ac.savePoint.dirty.mergeAndReset(sp.dirty)
ac.savePoint.transientStorage.mergeAndReset(sp.transientStorage)
ac.savePoint.accessList.mergeAndReset(sp.accessList)
ac.savePoint.selfDestruct.mergeAndReset(sp.selfDestruct)
ac.savePoint.logEntries.mergeAndReset(sp.logEntries)
sp.state = Committed
when debugLedgerRef:
inspectSavePoint("commit", ac.savePoint)
proc dispose*(ac: LedgerRef, sp: LedgerSpRef) =
if sp.state == Pending:
ac.rollback(sp)
proc safeDispose*(ac: LedgerRef, sp: LedgerSpRef) =
if (not isNil(sp)) and (sp.state == Pending):
ac.rollback(sp)
proc getCodeHash*(ac: LedgerRef, address: Address): Hash32 =
let acc = ac.getAccount(address, false)
if acc.isNil: emptyEthAccount.codeHash
else: acc.statement.codeHash
proc getBalance*(ac: LedgerRef, address: Address): UInt256 =
let acc = ac.getAccount(address, false)
if acc.isNil: emptyEthAccount.balance
else: acc.statement.balance
proc getNonce*(ac: LedgerRef, address: Address): AccountNonce =
let acc = ac.getAccount(address, false)
if acc.isNil: emptyEthAccount.nonce
else: acc.statement.nonce
proc getCode*(ac: LedgerRef, address: Address): CodeBytesRef =
# Always returns non-nil!
let acc = ac.getAccount(address, false)
if acc.isNil:
return CodeBytesRef()
if acc.code == nil:
acc.code =
if acc.statement.codeHash != EMPTY_CODE_HASH:
ac.code.get(acc.statement.codeHash).valueOr:
var rc = ac.kvt.get(contractHashKey(acc.statement.codeHash).toOpenArray)
if rc.isErr:
warn logTxt "getCode()", codeHash=acc.statement.codeHash, error=($$rc.error)
CodeBytesRef()
else:
let newCode = CodeBytesRef.init(move(rc.value), persisted = true)
ac.code.put(acc.statement.codeHash, newCode)
newCode
else:
CodeBytesRef()
acc.code
proc getCodeSize*(ac: LedgerRef, address: Address): int =
let acc = ac.getAccount(address, false)
if acc.isNil:
return 0
if acc.code == nil:
if acc.statement.codeHash == EMPTY_CODE_HASH:
return 0
acc.code = ac.code.get(acc.statement.codeHash).valueOr:
# On a cache miss, we don't fetch the code - instead, we fetch just the
# length - should the code itself be needed, it will typically remain
# cached and easily accessible in the database layer - this is to prevent
# EXTCODESIZE calls from messing up the code cache and thus causing
# recomputation of the jump destination table
var rc = ac.kvt.len(contractHashKey(acc.statement.codeHash).toOpenArray)
return rc.valueOr:
warn logTxt "getCodeSize()", codeHash=acc.statement.codeHash, error=($$rc.error)
0
acc.code.len()
proc getCommittedStorage*(ac: LedgerRef, address: Address, slot: UInt256): UInt256 =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.originalStorageValue(slot, ac)
proc getStorage*(ac: LedgerRef, address: Address, slot: UInt256): UInt256 =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.storageValue(slot, ac)
proc contractCollision*(ac: LedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.statement.nonce != 0 or
acc.statement.codeHash != EMPTY_CODE_HASH or
not ac.ledger.slotStateEmptyOrVoid(acc.toAccountKey)
proc accountExists*(ac: LedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.exists()
proc isEmptyAccount*(ac: LedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
doAssert not acc.isNil
doAssert acc.exists()
acc.isEmpty()
proc isDeadAccount*(ac: LedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return true
if not acc.exists():
return true
acc.isEmpty()
proc setBalance*(ac: LedgerRef, address: Address, balance: UInt256) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
if acc.statement.balance != balance:
ac.makeDirty(address).statement.balance = balance
proc addBalance*(ac: LedgerRef, address: Address, delta: UInt256) =
# EIP161: We must check emptiness for the objects such that the account
# clearing (0,0,0 objects) can take effect.
if delta.isZero:
let acc = ac.getAccount(address)
if acc.isEmpty:
ac.makeDirty(address).flags.incl Touched
return
ac.setBalance(address, ac.getBalance(address) + delta)
proc subBalance*(ac: LedgerRef, address: Address, delta: UInt256) =
if delta.isZero:
# This zero delta early exit is important as shown in EIP-4788.
# If the account is created, it will change the state.
# But early exit will prevent the account creation.
# In this case, the SYSTEM_ADDRESS
return
ac.setBalance(address, ac.getBalance(address) - delta)
proc setNonce*(ac: LedgerRef, address: Address, nonce: AccountNonce) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
if acc.statement.nonce != nonce:
ac.makeDirty(address).statement.nonce = nonce
proc incNonce*(ac: LedgerRef, address: Address) =
ac.setNonce(address, ac.getNonce(address) + 1)
proc setCode*(ac: LedgerRef, address: Address, code: seq[byte]) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
let codeHash = keccak256(code)
if acc.statement.codeHash != codeHash:
var acc = ac.makeDirty(address)
acc.statement.codeHash = codeHash
# Try to reuse cache entry if it exists, but don't save the code - it's not
# a given that it will be executed within LRU range
acc.code = ac.code.get(codeHash).valueOr(CodeBytesRef.init(code))
acc.flags.incl CodeChanged
proc setStorage*(ac: LedgerRef, address: Address, slot, value: UInt256) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
let oldValue = acc.storageValue(slot, ac)
if oldValue != value:
var acc = ac.makeDirty(address)
acc.overlayStorage[slot] = value
acc.flags.incl StorageChanged
proc clearStorage*(ac: LedgerRef, address: Address) =
const info = "clearStorage(): "
# a.k.a createStateObject. If there is an existing account with
# the given address, it is overwritten.
let acc = ac.getAccount(address)
acc.flags.incl {Alive, NewlyCreated}
let empty = ac.ledger.slotStateEmpty(acc.toAccountKey).valueOr: return
if not empty:
# need to clear the storage from the database first
let acc = ac.makeDirty(address, cloneStorage = false)
ac.ledger.clearStorage(acc.toAccountKey).isOkOr:
raiseAssert info & $$error
# update caches
if acc.originalStorage.isNil.not:
# also clear originalStorage cache, otherwise
# both getStorage and getCommittedStorage will
# return wrong value
acc.originalStorage.clear()
proc deleteAccount*(ac: LedgerRef, address: Address) =
# make sure all savepoints already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
let acc = ac.getAccount(address)
ac.savePoint.dirty[address] = acc
ac.kill acc
proc selfDestruct*(ac: LedgerRef, address: Address) =
ac.setBalance(address, 0.u256)
ac.savePoint.selfDestruct.incl address
proc selfDestruct6780*(ac: LedgerRef, address: Address) =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
if NewlyCreated in acc.flags:
ac.selfDestruct(address)
proc selfDestructLen*(ac: LedgerRef): int =
ac.savePoint.selfDestruct.len
proc addLogEntry*(ac: LedgerRef, log: Log) =
ac.savePoint.logEntries.add log
proc getAndClearLogEntries*(ac: LedgerRef): seq[Log] =
swap(result, ac.savePoint.logEntries)
proc ripemdSpecial*(ac: LedgerRef) =
ac.ripemdSpecial = true
proc deleteEmptyAccount(ac: LedgerRef, address: Address) =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
if not acc.isEmpty:
return
if not acc.exists:
return
ac.savePoint.dirty[address] = acc
ac.kill acc
proc clearEmptyAccounts(ac: LedgerRef) =
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
for acc in ac.savePoint.dirty.values():
if Touched in acc.flags and
acc.isEmpty and acc.exists:
ac.kill acc
# https://github.com/ethereum/EIPs/issues/716
if ac.ripemdSpecial:
ac.deleteEmptyAccount(RIPEMD_ADDR)
ac.ripemdSpecial = false
proc persist*(ac: LedgerRef,
clearEmptyAccount: bool = false,
clearCache = false) =
const info = "persist(): "
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
if clearEmptyAccount:
ac.clearEmptyAccounts()
for address in ac.savePoint.selfDestruct:
ac.deleteAccount(address)
for (eAddr,acc) in ac.savePoint.dirty.pairs(): # This is a hotspot in block processing
case acc.persistMode()
of Update:
if CodeChanged in acc.flags:
acc.persistCode(ac)
if StorageChanged in acc.flags:
acc.persistStorage(ac)
else:
# This one is only necessary unless `persistStorage()` is run which needs
# to `merge()` the latest statement as well.
ac.ledger.merge(acc.toAccountKey, acc.statement).isOkOr:
raiseAssert info & $$error
of Remove:
ac.ledger.delete(acc.toAccountKey).isOkOr:
if error.error != AccNotFound:
raiseAssert info & $$error
ac.savePoint.cache.del eAddr
of DoNothing:
# dead man tell no tales
# remove touched dead account from cache
if Alive notin acc.flags:
ac.savePoint.cache.del eAddr
acc.flags = acc.flags - resetFlags
ac.savePoint.dirty.clear()
if clearCache:
# This overwrites the cache from the previous persist, providing a crude LRU
# scheme with little overhead
# TODO https://github.com/nim-lang/Nim/issues/23759
swap(ac.cache, ac.savePoint.cache)
ac.savePoint.cache.reset()
ac.savePoint.selfDestruct.clear()
# EIP2929
ac.savePoint.accessList.clear()
ac.isDirty = false
iterator addresses*(ac: LedgerRef): Address =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
for address, _ in ac.savePoint.cache:
yield address
iterator accounts*(ac: LedgerRef): Account =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
for _, acc in ac.savePoint.cache:
yield ac.ledger.recast(
acc.toAccountKey, acc.statement, updateOk=true).value
iterator pairs*(ac: LedgerRef): (Address, Account) =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
for address, acc in ac.savePoint.cache:
yield (address, ac.ledger.recast(
acc.toAccountKey, acc.statement, updateOk=true).value)
iterator storage*(
ac: LedgerRef;
eAddr: Address;
): (UInt256, UInt256) =
# beware that if the account not persisted,
# the storage root will not be updated
for (slotHash, value) in ac.ledger.slotPairs eAddr.toAccountKey:
let rc = ac.kvt.get(slotHashToSlotKey(slotHash).toOpenArray)
if rc.isErr:
warn logTxt "storage()", slotHash, error=($$rc.error)
continue
let r = deblobify(rc.value, UInt256)
if r.isErr:
warn logTxt "storage.deblobify", slotHash, msg=r.error
continue
yield (r.value, value)
iterator cachedStorage*(ac: LedgerRef, address: Address): (UInt256, UInt256) =
let acc = ac.getAccount(address, false)
if not acc.isNil:
if not acc.originalStorage.isNil:
for k, v in acc.originalStorage:
yield (k, v)
proc getStorageRoot*(ac: LedgerRef, address: Address): Hash32 =
# beware that if the account not persisted,
# the storage root will not be updated
let acc = ac.getAccount(address, false)
if acc.isNil: EMPTY_ROOT_HASH
else: ac.ledger.slotState(acc.toAccountKey).valueOr: EMPTY_ROOT_HASH
proc update(wd: var WitnessData, acc: AccountRef) =
# once the code is touched make sure it doesn't get reset back to false in another update
if not wd.codeTouched:
wd.codeTouched = CodeChanged in acc.flags or acc.code != nil
if not acc.originalStorage.isNil:
for k, v in acc.originalStorage:
if v.isZero: continue
wd.storageKeys.incl k
for k, v in acc.overlayStorage:
wd.storageKeys.incl k
proc witnessData(acc: AccountRef): WitnessData =
result.storageKeys = HashSet[UInt256]()
update(result, acc)
proc collectWitnessData*(ac: LedgerRef) =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
# usually witness data is collected before we call persist()
for address, acc in ac.savePoint.cache:
ac.witnessCache.withValue(address, val) do:
update(val[], acc)
do:
ac.witnessCache[address] = witnessData(acc)
func multiKeys(slots: HashSet[UInt256]): MultiKeysRef =
if slots.len == 0: return
new result
for x in slots:
result.add x.toBytesBE
result.sort()
proc makeMultiKeys*(ac: LedgerRef): MultiKeysRef =
# this proc is called after we done executing a block
new result
for k, v in ac.witnessCache:
result.add(k, v.codeTouched, multiKeys(v.storageKeys))
result.sort()
proc accessList*(ac: LedgerRef, address: Address) =
ac.savePoint.accessList.add(address)
proc accessList*(ac: LedgerRef, address: Address, slot: UInt256) =
ac.savePoint.accessList.add(address, slot)
func inAccessList*(ac: LedgerRef, address: Address): bool =
var sp = ac.savePoint
while sp != nil:
result = sp.accessList.contains(address)
if result:
return
sp = sp.parentSavepoint
func inAccessList*(ac: LedgerRef, address: Address, slot: UInt256): bool =
var sp = ac.savePoint
while sp != nil:
result = sp.accessList.contains(address, slot)
if result:
return
sp = sp.parentSavepoint
func getTransientStorage*(ac: LedgerRef,
address: Address, slot: UInt256): UInt256 =
var sp = ac.savePoint
while sp != nil:
let (ok, res) = sp.transientStorage.getStorage(address, slot)
if ok:
return res
sp = sp.parentSavepoint
proc setTransientStorage*(ac: LedgerRef,
address: Address, slot, val: UInt256) =
ac.savePoint.transientStorage.setStorage(address, slot, val)
proc clearTransientStorage*(ac: LedgerRef) =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
ac.savePoint.transientStorage.clear()
func getAccessList*(ac: LedgerRef): common.AccessList =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
ac.savePoint.accessList.getAccessList()
proc getEthAccount*(ac: LedgerRef, address: Address): Account =
let acc = ac.getAccount(address, false)
if acc.isNil:
return emptyEthAccount
## Convert to legacy object, will throw an assert if that fails
let rc = ac.ledger.recast(acc.toAccountKey, acc.statement)
if rc.isErr:
raiseAssert "getAccount(): cannot convert account: " & $$rc.error
rc.value
proc getAccountProof*(ac: LedgerRef, address: Address): seq[seq[byte]] =
let accProof = ac.ledger.proof(address.toAccountKey).valueOr:
raiseAssert "Failed to get account proof: " & $$error
accProof[0]
proc getStorageProof*(ac: LedgerRef, address: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] =
var storageProof = newSeqOfCap[seq[seq[byte]]](slots.len)
let
addressHash = address.toAccountKey
accountExists = ac.ledger.hasPath(addressHash).valueOr:
raiseAssert "Call to hasPath failed: " & $$error
for slot in slots:
if not accountExists:
storageProof.add(@[])
continue
let
slotKey = ac.slots.get(slot).valueOr:
slot.toBytesBE.keccak256
slotProof = ac.ledger.slotProof(addressHash, slotKey).valueOr:
if error.aErr == FetchPathNotFound:
storageProof.add(@[])
continue
else:
raiseAssert "Failed to get slot proof: " & $$error
storageProof.add(slotProof[0])
storageProof
# ------------------------------------------------------------------------------
# Public virtual read-only methods
# ------------------------------------------------------------------------------
proc getStateRoot*(db: ReadOnlyStateDB): Hash32 {.borrow.}
proc getCodeHash*(db: ReadOnlyStateDB, address: Address): Hash32 = getCodeHash(distinctBase db, address)
proc getStorageRoot*(db: ReadOnlyStateDB, address: Address): Hash32 = getStorageRoot(distinctBase db, address)
proc getBalance*(db: ReadOnlyStateDB, address: Address): UInt256 = getBalance(distinctBase db, address)
proc getStorage*(db: ReadOnlyStateDB, address: Address, slot: UInt256): UInt256 = getStorage(distinctBase db, address, slot)
proc getNonce*(db: ReadOnlyStateDB, address: Address): AccountNonce = getNonce(distinctBase db, address)
proc getCode*(db: ReadOnlyStateDB, address: Address): CodeBytesRef = getCode(distinctBase db, address)
proc getCodeSize*(db: ReadOnlyStateDB, address: Address): int = getCodeSize(distinctBase db, address)
proc contractCollision*(db: ReadOnlyStateDB, address: Address): bool = contractCollision(distinctBase db, address)
proc accountExists*(db: ReadOnlyStateDB, address: Address): bool = accountExists(distinctBase db, address)
proc isDeadAccount*(db: ReadOnlyStateDB, address: Address): bool = isDeadAccount(distinctBase db, address)
proc isEmptyAccount*(db: ReadOnlyStateDB, address: Address): bool = isEmptyAccount(distinctBase db, address)
proc getCommittedStorage*(db: ReadOnlyStateDB, address: Address, slot: UInt256): UInt256 = getCommittedStorage(distinctBase db, address, slot)
proc inAccessList*(db: ReadOnlyStateDB, address: Address): bool = inAccessList(distinctBase db, address)
proc inAccessList*(db: ReadOnlyStateDB, address: Address, slot: UInt256): bool = inAccessList(distinctBase db, address)
proc getTransientStorage*(db: ReadOnlyStateDB,
address: Address, slot: UInt256): UInt256 = getTransientStorage(distinctBase db, address, slot)
proc getAccountProof*(db: ReadOnlyStateDB, address: Address): seq[seq[byte]] = getAccountProof(distinctBase db, address)
proc getStorageProof*(db: ReadOnlyStateDB, address: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] = getStorageProof(distinctBase db, address, slots)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# End # End

View File

@ -1,916 +0,0 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
{.push raises: [].}
import
std/[tables, hashes, sets, typetraits],
chronicles,
eth/common/eth_types,
results,
minilru,
../../../utils/mergeutils,
../../../evm/code_bytes,
../../../stateless/multi_keys,
"../../.."/[constants, utils/utils],
../../access_list as ac_access_list,
"../.."/[core_db, storage_types, transient_storage],
../../aristo/aristo_blobify
const
debugAccountsLedgerRef = false
codeLruSize = 16*1024
# An LRU cache of 16K items gives roughly 90% hit rate anecdotally on a
# small range of test blocks - this number could be studied in more detail
# Per EIP-170, a the code of a contract can be up to `MAX_CODE_SIZE` = 24kb,
# which would cause a worst case of 386MB memory usage though in reality
# code sizes are much smaller - it would make sense to study these numbers
# in greater detail.
slotsLruSize = 16 * 1024
type
AccountFlag = enum
Alive
IsNew
Dirty
Touched
CodeChanged
StorageChanged
NewlyCreated # EIP-6780: self destruct only in same transaction
AccountFlags = set[AccountFlag]
AccountRef = ref object
statement: CoreDbAccount
accPath: Hash32
flags: AccountFlags
code: CodeBytesRef
originalStorage: TableRef[UInt256, UInt256]
overlayStorage: Table[UInt256, UInt256]
WitnessData* = object
storageKeys*: HashSet[UInt256]
codeTouched*: bool
AccountsLedgerRef* = ref object
ledger: CoreDbAccRef # AccountLedger
kvt: CoreDbKvtRef
savePoint: LedgerSavePoint
witnessCache: Table[Address, WitnessData]
isDirty: bool
ripemdSpecial: bool
storeSlotHash*: bool
cache: Table[Address, AccountRef]
# Second-level cache for the ledger save point, which is cleared on every
# persist
code: LruCache[Hash32, CodeBytesRef]
## The code cache provides two main benefits:
##
## * duplicate code is shared in memory beween accounts
## * the jump destination table does not have to be recomputed for every
## execution, for commonly called called contracts
##
## The former feature is specially important in the 2.3-2.7M block range
## when underpriced code opcodes are being run en masse - both advantages
## help performance broadly as well.
slots: LruCache[UInt256, Hash32]
## Because the same slots often reappear, we want to avoid writing them
## over and over again to the database to avoid the WAL and compation
## write amplification that ensues
ReadOnlyStateDB* = distinct AccountsLedgerRef
TransactionState = enum
Pending
Committed
RolledBack
LedgerSavePoint* = ref object
parentSavepoint: LedgerSavePoint
cache: Table[Address, AccountRef]
dirty: Table[Address, AccountRef]
selfDestruct: HashSet[Address]
logEntries: seq[Log]
accessList: ac_access_list.AccessList
transientStorage: TransientStorage
state: TransactionState
when debugAccountsLedgerRef:
depth: int
const
emptyEthAccount = Account.init()
resetFlags = {
Dirty,
IsNew,
Touched,
CodeChanged,
StorageChanged,
NewlyCreated
}
when debugAccountsLedgerRef:
import
stew/byteutils
proc inspectSavePoint(name: string, x: LedgerSavePoint) =
debugEcho "*** ", name, ": ", x.depth, " ***"
var sp = x
while sp != nil:
for address, acc in sp.cache:
debugEcho address.toHex, " ", acc.flags
sp = sp.parentSavepoint
template logTxt(info: static[string]): static[string] =
"AccountsLedgerRef " & info
template toAccountKey(acc: AccountRef): Hash32 =
acc.accPath
template toAccountKey(eAddr: Address): Hash32 =
eAddr.data.keccak256
proc beginSavepoint*(ac: AccountsLedgerRef): LedgerSavePoint {.gcsafe.}
proc resetCoreDbAccount(ac: AccountsLedgerRef, acc: AccountRef) =
const info = "resetCoreDbAccount(): "
ac.ledger.clearStorage(acc.toAccountKey).isOkOr:
raiseAssert info & $$error
acc.statement.nonce = emptyEthAccount.nonce
acc.statement.balance = emptyEthAccount.balance
acc.statement.codeHash = emptyEthAccount.codeHash
# The AccountsLedgerRef is modeled after TrieDatabase for it's transaction style
proc init*(x: typedesc[AccountsLedgerRef], db: CoreDbRef, storeSlotHash: bool): AccountsLedgerRef =
new result
result.ledger = db.ctx.getAccounts()
result.kvt = db.ctx.getKvt()
result.witnessCache = Table[Address, WitnessData]()
result.storeSlotHash = storeSlotHash
result.code = typeof(result.code).init(codeLruSize)
result.slots = typeof(result.slots).init(slotsLruSize)
discard result.beginSavepoint
proc init*(x: typedesc[AccountsLedgerRef], db: CoreDbRef): AccountsLedgerRef =
init(x, db, EMPTY_ROOT_HASH)
proc getStateRoot*(ac: AccountsLedgerRef): Hash32 =
const info = "state(): "
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
# make sure all cache already committed
doAssert(ac.isDirty == false)
ac.ledger.stateRoot(updateOk=true).valueOr:
raiseAssert info & $$error
proc isTopLevelClean*(ac: AccountsLedgerRef): bool =
## Getter, returns `true` if all pending data have been commited.
not ac.isDirty and ac.savePoint.parentSavepoint.isNil
proc beginSavepoint*(ac: AccountsLedgerRef): LedgerSavePoint =
new result
result.cache = Table[Address, AccountRef]()
result.accessList.init()
result.transientStorage.init()
result.state = Pending
result.parentSavepoint = ac.savePoint
ac.savePoint = result
when debugAccountsLedgerRef:
if not result.parentSavePoint.isNil:
result.depth = result.parentSavePoint.depth + 1
inspectSavePoint("snapshot", result)
proc rollback*(ac: AccountsLedgerRef, sp: LedgerSavePoint) =
# Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before
# its parent transactions:
doAssert ac.savePoint == sp and sp.state == Pending
ac.savePoint = sp.parentSavepoint
sp.state = RolledBack
when debugAccountsLedgerRef:
inspectSavePoint("rollback", ac.savePoint)
proc commit*(ac: AccountsLedgerRef, sp: LedgerSavePoint) =
# Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before
# its parent transactions:
doAssert ac.savePoint == sp and sp.state == Pending
# cannot commit most inner savepoint
doAssert not sp.parentSavepoint.isNil
ac.savePoint = sp.parentSavepoint
ac.savePoint.cache.mergeAndReset(sp.cache)
ac.savePoint.dirty.mergeAndReset(sp.dirty)
ac.savePoint.transientStorage.mergeAndReset(sp.transientStorage)
ac.savePoint.accessList.mergeAndReset(sp.accessList)
ac.savePoint.selfDestruct.mergeAndReset(sp.selfDestruct)
ac.savePoint.logEntries.mergeAndReset(sp.logEntries)
sp.state = Committed
when debugAccountsLedgerRef:
inspectSavePoint("commit", ac.savePoint)
proc dispose*(ac: AccountsLedgerRef, sp: LedgerSavePoint) =
if sp.state == Pending:
ac.rollback(sp)
proc safeDispose*(ac: AccountsLedgerRef, sp: LedgerSavePoint) =
if (not isNil(sp)) and (sp.state == Pending):
ac.rollback(sp)
proc getAccount(
ac: AccountsLedgerRef;
address: Address;
shouldCreate = true;
): AccountRef =
# search account from layers of cache
var sp = ac.savePoint
while sp != nil:
result = sp.cache.getOrDefault(address)
if not result.isNil:
return
sp = sp.parentSavepoint
if ac.cache.pop(address, result):
# Check second-level cache
ac.savePoint.cache[address] = result
return
# not found in cache, look into state trie
let
accPath = address.toAccountKey
rc = ac.ledger.fetch accPath
if rc.isOk:
result = AccountRef(
statement: rc.value,
accPath: accPath,
flags: {Alive})
elif shouldCreate:
result = AccountRef(
statement: CoreDbAccount(
nonce: emptyEthAccount.nonce,
balance: emptyEthAccount.balance,
codeHash: emptyEthAccount.codeHash),
accPath: accPath,
flags: {Alive, IsNew})
else:
return # ignore, don't cache
# cache the account
ac.savePoint.cache[address] = result
ac.savePoint.dirty[address] = result
proc clone(acc: AccountRef, cloneStorage: bool): AccountRef =
result = AccountRef(
statement: acc.statement,
accPath: acc.accPath,
flags: acc.flags,
code: acc.code)
if cloneStorage:
result.originalStorage = acc.originalStorage
# it's ok to clone a table this way
result.overlayStorage = acc.overlayStorage
proc isEmpty(acc: AccountRef): bool =
acc.statement.nonce == 0 and
acc.statement.balance.isZero and
acc.statement.codeHash == EMPTY_CODE_HASH
template exists(acc: AccountRef): bool =
Alive in acc.flags
proc originalStorageValue(
acc: AccountRef;
slot: UInt256;
ac: AccountsLedgerRef;
): UInt256 =
# share the same original storage between multiple
# versions of account
if acc.originalStorage.isNil:
acc.originalStorage = newTable[UInt256, UInt256]()
else:
acc.originalStorage[].withValue(slot, val) do:
return val[]
# Not in the original values cache - go to the DB.
let
slotKey = ac.slots.get(slot).valueOr:
slot.toBytesBE.keccak256
rc = ac.ledger.slotFetch(acc.toAccountKey, slotKey)
if rc.isOk:
result = rc.value
acc.originalStorage[slot] = result
proc storageValue(
acc: AccountRef;
slot: UInt256;
ac: AccountsLedgerRef;
): UInt256 =
acc.overlayStorage.withValue(slot, val) do:
return val[]
do:
result = acc.originalStorageValue(slot, ac)
proc kill(ac: AccountsLedgerRef, acc: AccountRef) =
acc.flags.excl Alive
acc.overlayStorage.clear()
acc.originalStorage = nil
ac.resetCoreDbAccount acc
acc.code.reset()
type
PersistMode = enum
DoNothing
Update
Remove
proc persistMode(acc: AccountRef): PersistMode =
result = DoNothing
if Alive in acc.flags:
if IsNew in acc.flags or Dirty in acc.flags:
result = Update
else:
if IsNew notin acc.flags:
result = Remove
proc persistCode(acc: AccountRef, ac: AccountsLedgerRef) =
if acc.code.len != 0 and not acc.code.persisted:
let rc = ac.kvt.put(
contractHashKey(acc.statement.codeHash).toOpenArray, acc.code.bytes())
if rc.isErr:
warn logTxt "persistCode()",
codeHash=acc.statement.codeHash, error=($$rc.error)
else:
# If the ledger changes rolled back entirely from the database, the ledger
# code cache must also be cleared!
acc.code.persisted = true
proc persistStorage(acc: AccountRef, ac: AccountsLedgerRef) =
const info = "persistStorage(): "
if acc.overlayStorage.len == 0:
# TODO: remove the storage too if we figure out
# how to create 'virtual' storage room for each account
return
if acc.originalStorage.isNil:
acc.originalStorage = newTable[UInt256, UInt256]()
# Make sure that there is an account entry on the database. This is needed by
# `Aristo` for updating the account's storage area reference. As a side effect,
# this action also updates the latest statement data.
ac.ledger.merge(acc.toAccountKey, acc.statement).isOkOr:
raiseAssert info & $$error
# Save `overlayStorage[]` on database
for slot, value in acc.overlayStorage:
acc.originalStorage[].withValue(slot, v):
if v[] == value:
continue # Avoid writing A-B-A updates
var cached = true
let slotKey = ac.slots.get(slot).valueOr:
cached = false
let hash = slot.toBytesBE.keccak256
ac.slots.put(slot, hash)
hash
if value > 0:
ac.ledger.slotMerge(acc.toAccountKey, slotKey, value).isOkOr:
raiseAssert info & $$error
# move the overlayStorage to originalStorage, related to EIP2200, EIP1283
acc.originalStorage[slot] = value
else:
ac.ledger.slotDelete(acc.toAccountKey, slotKey).isOkOr:
if error.error != StoNotFound:
raiseAssert info & $$error
discard
acc.originalStorage.del(slot)
if ac.storeSlotHash and not cached:
# Write only if it was not cached to avoid writing the same data over and
# over..
let
key = slotKey.data.slotHashToSlotKey
rc = ac.kvt.put(key.toOpenArray, blobify(slot).data)
if rc.isErr:
warn logTxt "persistStorage()", slot, error=($$rc.error)
acc.overlayStorage.clear()
proc makeDirty(ac: AccountsLedgerRef, address: Address, cloneStorage = true): AccountRef =
ac.isDirty = true
result = ac.getAccount(address)
if address in ac.savePoint.cache:
# it's already in latest savepoint
result.flags.incl Dirty
ac.savePoint.dirty[address] = result
return
# put a copy into latest savepoint
result = result.clone(cloneStorage)
result.flags.incl Dirty
ac.savePoint.cache[address] = result
ac.savePoint.dirty[address] = result
proc getCodeHash*(ac: AccountsLedgerRef, address: Address): Hash32 =
let acc = ac.getAccount(address, false)
if acc.isNil: emptyEthAccount.codeHash
else: acc.statement.codeHash
proc getBalance*(ac: AccountsLedgerRef, address: Address): UInt256 =
let acc = ac.getAccount(address, false)
if acc.isNil: emptyEthAccount.balance
else: acc.statement.balance
proc getNonce*(ac: AccountsLedgerRef, address: Address): AccountNonce =
let acc = ac.getAccount(address, false)
if acc.isNil: emptyEthAccount.nonce
else: acc.statement.nonce
proc getCode*(ac: AccountsLedgerRef, address: Address): CodeBytesRef =
# Always returns non-nil!
let acc = ac.getAccount(address, false)
if acc.isNil:
return CodeBytesRef()
if acc.code == nil:
acc.code =
if acc.statement.codeHash != EMPTY_CODE_HASH:
ac.code.get(acc.statement.codeHash).valueOr:
var rc = ac.kvt.get(contractHashKey(acc.statement.codeHash).toOpenArray)
if rc.isErr:
warn logTxt "getCode()", codeHash=acc.statement.codeHash, error=($$rc.error)
CodeBytesRef()
else:
let newCode = CodeBytesRef.init(move(rc.value), persisted = true)
ac.code.put(acc.statement.codeHash, newCode)
newCode
else:
CodeBytesRef()
acc.code
proc getCodeSize*(ac: AccountsLedgerRef, address: Address): int =
let acc = ac.getAccount(address, false)
if acc.isNil:
return 0
if acc.code == nil:
if acc.statement.codeHash == EMPTY_CODE_HASH:
return 0
acc.code = ac.code.get(acc.statement.codeHash).valueOr:
# On a cache miss, we don't fetch the code - instead, we fetch just the
# length - should the code itself be needed, it will typically remain
# cached and easily accessible in the database layer - this is to prevent
# EXTCODESIZE calls from messing up the code cache and thus causing
# recomputation of the jump destination table
var rc = ac.kvt.len(contractHashKey(acc.statement.codeHash).toOpenArray)
return rc.valueOr:
warn logTxt "getCodeSize()", codeHash=acc.statement.codeHash, error=($$rc.error)
0
acc.code.len()
proc getCommittedStorage*(ac: AccountsLedgerRef, address: Address, slot: UInt256): UInt256 =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.originalStorageValue(slot, ac)
proc getStorage*(ac: AccountsLedgerRef, address: Address, slot: UInt256): UInt256 =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.storageValue(slot, ac)
proc contractCollision*(ac: AccountsLedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.statement.nonce != 0 or
acc.statement.codeHash != EMPTY_CODE_HASH or
not ac.ledger.slotStateEmptyOrVoid(acc.toAccountKey)
proc accountExists*(ac: AccountsLedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.exists()
proc isEmptyAccount*(ac: AccountsLedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
doAssert not acc.isNil
doAssert acc.exists()
acc.isEmpty()
proc isDeadAccount*(ac: AccountsLedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return true
if not acc.exists():
return true
acc.isEmpty()
proc setBalance*(ac: AccountsLedgerRef, address: Address, balance: UInt256) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
if acc.statement.balance != balance:
ac.makeDirty(address).statement.balance = balance
proc addBalance*(ac: AccountsLedgerRef, address: Address, delta: UInt256) =
# EIP161: We must check emptiness for the objects such that the account
# clearing (0,0,0 objects) can take effect.
if delta.isZero:
let acc = ac.getAccount(address)
if acc.isEmpty:
ac.makeDirty(address).flags.incl Touched
return
ac.setBalance(address, ac.getBalance(address) + delta)
proc subBalance*(ac: AccountsLedgerRef, address: Address, delta: UInt256) =
if delta.isZero:
# This zero delta early exit is important as shown in EIP-4788.
# If the account is created, it will change the state.
# But early exit will prevent the account creation.
# In this case, the SYSTEM_ADDRESS
return
ac.setBalance(address, ac.getBalance(address) - delta)
proc setNonce*(ac: AccountsLedgerRef, address: Address, nonce: AccountNonce) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
if acc.statement.nonce != nonce:
ac.makeDirty(address).statement.nonce = nonce
proc incNonce*(ac: AccountsLedgerRef, address: Address) =
ac.setNonce(address, ac.getNonce(address) + 1)
proc setCode*(ac: AccountsLedgerRef, address: Address, code: seq[byte]) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
let codeHash = keccak256(code)
if acc.statement.codeHash != codeHash:
var acc = ac.makeDirty(address)
acc.statement.codeHash = codeHash
# Try to reuse cache entry if it exists, but don't save the code - it's not
# a given that it will be executed within LRU range
acc.code = ac.code.get(codeHash).valueOr(CodeBytesRef.init(code))
acc.flags.incl CodeChanged
proc setStorage*(ac: AccountsLedgerRef, address: Address, slot, value: UInt256) =
let acc = ac.getAccount(address)
acc.flags.incl {Alive}
let oldValue = acc.storageValue(slot, ac)
if oldValue != value:
var acc = ac.makeDirty(address)
acc.overlayStorage[slot] = value
acc.flags.incl StorageChanged
proc clearStorage*(ac: AccountsLedgerRef, address: Address) =
const info = "clearStorage(): "
# a.k.a createStateObject. If there is an existing account with
# the given address, it is overwritten.
let acc = ac.getAccount(address)
acc.flags.incl {Alive, NewlyCreated}
let empty = ac.ledger.slotStateEmpty(acc.toAccountKey).valueOr: return
if not empty:
# need to clear the storage from the database first
let acc = ac.makeDirty(address, cloneStorage = false)
ac.ledger.clearStorage(acc.toAccountKey).isOkOr:
raiseAssert info & $$error
# update caches
if acc.originalStorage.isNil.not:
# also clear originalStorage cache, otherwise
# both getStorage and getCommittedStorage will
# return wrong value
acc.originalStorage.clear()
proc deleteAccount*(ac: AccountsLedgerRef, address: Address) =
# make sure all savepoints already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
let acc = ac.getAccount(address)
ac.savePoint.dirty[address] = acc
ac.kill acc
proc selfDestruct*(ac: AccountsLedgerRef, address: Address) =
ac.setBalance(address, 0.u256)
ac.savePoint.selfDestruct.incl address
proc selfDestruct6780*(ac: AccountsLedgerRef, address: Address) =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
if NewlyCreated in acc.flags:
ac.selfDestruct(address)
proc selfDestructLen*(ac: AccountsLedgerRef): int =
ac.savePoint.selfDestruct.len
proc addLogEntry*(ac: AccountsLedgerRef, log: Log) =
ac.savePoint.logEntries.add log
proc getAndClearLogEntries*(ac: AccountsLedgerRef): seq[Log] =
swap(result, ac.savePoint.logEntries)
proc ripemdSpecial*(ac: AccountsLedgerRef) =
ac.ripemdSpecial = true
proc deleteEmptyAccount(ac: AccountsLedgerRef, address: Address) =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
if not acc.isEmpty:
return
if not acc.exists:
return
ac.savePoint.dirty[address] = acc
ac.kill acc
proc clearEmptyAccounts(ac: AccountsLedgerRef) =
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
for acc in ac.savePoint.dirty.values():
if Touched in acc.flags and
acc.isEmpty and acc.exists:
ac.kill acc
# https://github.com/ethereum/EIPs/issues/716
if ac.ripemdSpecial:
ac.deleteEmptyAccount(RIPEMD_ADDR)
ac.ripemdSpecial = false
proc persist*(ac: AccountsLedgerRef,
clearEmptyAccount: bool = false,
clearCache = false) =
const info = "persist(): "
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
if clearEmptyAccount:
ac.clearEmptyAccounts()
for address in ac.savePoint.selfDestruct:
ac.deleteAccount(address)
for (eAddr,acc) in ac.savePoint.dirty.pairs(): # This is a hotspot in block processing
case acc.persistMode()
of Update:
if CodeChanged in acc.flags:
acc.persistCode(ac)
if StorageChanged in acc.flags:
acc.persistStorage(ac)
else:
# This one is only necessary unless `persistStorage()` is run which needs
# to `merge()` the latest statement as well.
ac.ledger.merge(acc.toAccountKey, acc.statement).isOkOr:
raiseAssert info & $$error
of Remove:
ac.ledger.delete(acc.toAccountKey).isOkOr:
if error.error != AccNotFound:
raiseAssert info & $$error
ac.savePoint.cache.del eAddr
of DoNothing:
# dead man tell no tales
# remove touched dead account from cache
if Alive notin acc.flags:
ac.savePoint.cache.del eAddr
acc.flags = acc.flags - resetFlags
ac.savePoint.dirty.clear()
if clearCache:
# This overwrites the cache from the previous persist, providing a crude LRU
# scheme with little overhead
# TODO https://github.com/nim-lang/Nim/issues/23759
swap(ac.cache, ac.savePoint.cache)
ac.savePoint.cache.reset()
ac.savePoint.selfDestruct.clear()
# EIP2929
ac.savePoint.accessList.clear()
ac.isDirty = false
iterator addresses*(ac: AccountsLedgerRef): Address =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
for address, _ in ac.savePoint.cache:
yield address
iterator accounts*(ac: AccountsLedgerRef): Account =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
for _, acc in ac.savePoint.cache:
yield ac.ledger.recast(
acc.toAccountKey, acc.statement, updateOk=true).value
iterator pairs*(ac: AccountsLedgerRef): (Address, Account) =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
for address, acc in ac.savePoint.cache:
yield (address, ac.ledger.recast(
acc.toAccountKey, acc.statement, updateOk=true).value)
iterator storage*(
ac: AccountsLedgerRef;
eAddr: Address;
): (UInt256, UInt256) =
# beware that if the account not persisted,
# the storage root will not be updated
for (slotHash, value) in ac.ledger.slotPairs eAddr.toAccountKey:
let rc = ac.kvt.get(slotHashToSlotKey(slotHash).toOpenArray)
if rc.isErr:
warn logTxt "storage()", slotHash, error=($$rc.error)
continue
let r = deblobify(rc.value, UInt256)
if r.isErr:
warn logTxt "storage.deblobify", slotHash, msg=r.error
continue
yield (r.value, value)
iterator cachedStorage*(ac: AccountsLedgerRef, address: Address): (UInt256, UInt256) =
let acc = ac.getAccount(address, false)
if not acc.isNil:
if not acc.originalStorage.isNil:
for k, v in acc.originalStorage:
yield (k, v)
proc getStorageRoot*(ac: AccountsLedgerRef, address: Address): Hash32 =
# beware that if the account not persisted,
# the storage root will not be updated
let acc = ac.getAccount(address, false)
if acc.isNil: EMPTY_ROOT_HASH
else: ac.ledger.slotState(acc.toAccountKey).valueOr: EMPTY_ROOT_HASH
proc update(wd: var WitnessData, acc: AccountRef) =
# once the code is touched make sure it doesn't get reset back to false in another update
if not wd.codeTouched:
wd.codeTouched = CodeChanged in acc.flags or acc.code != nil
if not acc.originalStorage.isNil:
for k, v in acc.originalStorage:
if v.isZero: continue
wd.storageKeys.incl k
for k, v in acc.overlayStorage:
wd.storageKeys.incl k
proc witnessData(acc: AccountRef): WitnessData =
result.storageKeys = HashSet[UInt256]()
update(result, acc)
proc collectWitnessData*(ac: AccountsLedgerRef) =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
# usually witness data is collected before we call persist()
for address, acc in ac.savePoint.cache:
ac.witnessCache.withValue(address, val) do:
update(val[], acc)
do:
ac.witnessCache[address] = witnessData(acc)
func multiKeys(slots: HashSet[UInt256]): MultiKeysRef =
if slots.len == 0: return
new result
for x in slots:
result.add x.toBytesBE
result.sort()
proc makeMultiKeys*(ac: AccountsLedgerRef): MultiKeysRef =
# this proc is called after we done executing a block
new result
for k, v in ac.witnessCache:
result.add(k, v.codeTouched, multiKeys(v.storageKeys))
result.sort()
proc accessList*(ac: AccountsLedgerRef, address: Address) =
ac.savePoint.accessList.add(address)
proc accessList*(ac: AccountsLedgerRef, address: Address, slot: UInt256) =
ac.savePoint.accessList.add(address, slot)
func inAccessList*(ac: AccountsLedgerRef, address: Address): bool =
var sp = ac.savePoint
while sp != nil:
result = sp.accessList.contains(address)
if result:
return
sp = sp.parentSavepoint
func inAccessList*(ac: AccountsLedgerRef, address: Address, slot: UInt256): bool =
var sp = ac.savePoint
while sp != nil:
result = sp.accessList.contains(address, slot)
if result:
return
sp = sp.parentSavepoint
func getTransientStorage*(ac: AccountsLedgerRef,
address: Address, slot: UInt256): UInt256 =
var sp = ac.savePoint
while sp != nil:
let (ok, res) = sp.transientStorage.getStorage(address, slot)
if ok:
return res
sp = sp.parentSavepoint
proc setTransientStorage*(ac: AccountsLedgerRef,
address: Address, slot, val: UInt256) =
ac.savePoint.transientStorage.setStorage(address, slot, val)
proc clearTransientStorage*(ac: AccountsLedgerRef) =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
ac.savePoint.transientStorage.clear()
func getAccessList*(ac: AccountsLedgerRef): common.AccessList =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
ac.savePoint.accessList.getAccessList()
proc getEthAccount*(ac: AccountsLedgerRef, address: Address): Account =
let acc = ac.getAccount(address, false)
if acc.isNil:
return emptyEthAccount
## Convert to legacy object, will throw an assert if that fails
let rc = ac.ledger.recast(acc.toAccountKey, acc.statement)
if rc.isErr:
raiseAssert "getAccount(): cannot convert account: " & $$rc.error
rc.value
proc getAccountProof*(ac: AccountsLedgerRef, address: Address): seq[seq[byte]] =
let accProof = ac.ledger.proof(address.toAccountKey).valueOr:
raiseAssert "Failed to get account proof: " & $$error
accProof[0]
proc getStorageProof*(ac: AccountsLedgerRef, address: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] =
var storageProof = newSeqOfCap[seq[seq[byte]]](slots.len)
let
addressHash = address.toAccountKey
accountExists = ac.ledger.hasPath(addressHash).valueOr:
raiseAssert "Call to hasPath failed: " & $$error
for slot in slots:
if not accountExists:
storageProof.add(@[])
continue
let
slotKey = ac.slots.get(slot).valueOr:
slot.toBytesBE.keccak256
slotProof = ac.ledger.slotProof(addressHash, slotKey).valueOr:
if error.aErr == FetchPathNotFound:
storageProof.add(@[])
continue
else:
raiseAssert "Failed to get slot proof: " & $$error
storageProof.add(slotProof[0])
storageProof
proc getStateRoot*(db: ReadOnlyStateDB): Hash32 {.borrow.}
proc getCodeHash*(db: ReadOnlyStateDB, address: Address): Hash32 = getCodeHash(distinctBase db, address)
proc getStorageRoot*(db: ReadOnlyStateDB, address: Address): Hash32 = getStorageRoot(distinctBase db, address)
proc getBalance*(db: ReadOnlyStateDB, address: Address): UInt256 = getBalance(distinctBase db, address)
proc getStorage*(db: ReadOnlyStateDB, address: Address, slot: UInt256): UInt256 = getStorage(distinctBase db, address, slot)
proc getNonce*(db: ReadOnlyStateDB, address: Address): AccountNonce = getNonce(distinctBase db, address)
proc getCode*(db: ReadOnlyStateDB, address: Address): CodeBytesRef = getCode(distinctBase db, address)
proc getCodeSize*(db: ReadOnlyStateDB, address: Address): int = getCodeSize(distinctBase db, address)
proc contractCollision*(db: ReadOnlyStateDB, address: Address): bool = contractCollision(distinctBase db, address)
proc accountExists*(db: ReadOnlyStateDB, address: Address): bool = accountExists(distinctBase db, address)
proc isDeadAccount*(db: ReadOnlyStateDB, address: Address): bool = isDeadAccount(distinctBase db, address)
proc isEmptyAccount*(db: ReadOnlyStateDB, address: Address): bool = isEmptyAccount(distinctBase db, address)
proc getCommittedStorage*(db: ReadOnlyStateDB, address: Address, slot: UInt256): UInt256 = getCommittedStorage(distinctBase db, address, slot)
proc inAccessList*(db: ReadOnlyStateDB, address: Address): bool = inAccessList(distinctBase db, address)
proc inAccessList*(db: ReadOnlyStateDB, address: Address, slot: UInt256): bool = inAccessList(distinctBase db, address)
proc getTransientStorage*(db: ReadOnlyStateDB,
address: Address, slot: UInt256): UInt256 = getTransientStorage(distinctBase db, address, slot)
proc getAccountProof*(db: ReadOnlyStateDB, address: Address): seq[seq[byte]] = getAccountProof(distinctBase db, address)
proc getStorageProof*(db: ReadOnlyStateDB, address: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] = getStorageProof(distinctBase db, address, slots)

View File

@ -1,329 +0,0 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
## Ledger management APIs.
{.push raises: [].}
import
std/typetraits,
eth/common/[addresses, hashes],
../../evm/code_bytes,
../../stateless/multi_keys,
../core_db,
./backend/accounts_ledger,
./base/[api_tracking, base_config, base_desc]
type
ReadOnlyStateDB* = distinct LedgerRef
export
code_bytes,
LedgerRef,
LedgerSpRef
# ------------------------------------------------------------------------------
# Logging/tracking helpers (some public)
# ------------------------------------------------------------------------------
when LedgerEnableApiTracking:
import
std/times,
chronicles
logScope:
topics = "ledger"
const
apiTxt = "API"
when LedgerEnableApiProfiling:
export
LedgerFnInx,
LedgerProfListRef
# ------------------------------------------------------------------------------
# Public methods
# ------------------------------------------------------------------------------
proc accessList*(ldg: LedgerRef, eAddr: Address) =
ldg.beginTrackApi LdgAccessListFn
ldg.ac.accessList(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr)
proc accessList*(ldg: LedgerRef, eAddr: Address, slot: UInt256) =
ldg.beginTrackApi LdgAccessListFn
ldg.ac.accessList(eAddr, slot)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), slot
proc accountExists*(ldg: LedgerRef, eAddr: Address): bool =
ldg.beginTrackApi LdgAccountExistsFn
result = ldg.ac.accountExists(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc addBalance*(ldg: LedgerRef, eAddr: Address, delta: UInt256) =
ldg.beginTrackApi LdgAddBalanceFn
ldg.ac.addBalance(eAddr, delta)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), delta
proc addLogEntry*(ldg: LedgerRef, log: Log) =
ldg.beginTrackApi LdgAddLogEntryFn
ldg.ac.addLogEntry(log)
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc beginSavepoint*(ldg: LedgerRef): LedgerSpRef =
ldg.beginTrackApi LdgBeginSavepointFn
result = ldg.ac.beginSavepoint()
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc clearStorage*(ldg: LedgerRef, eAddr: Address) =
ldg.beginTrackApi LdgClearStorageFn
ldg.ac.clearStorage(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr)
proc clearTransientStorage*(ldg: LedgerRef) =
ldg.beginTrackApi LdgClearTransientStorageFn
ldg.ac.clearTransientStorage()
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc collectWitnessData*(ldg: LedgerRef) =
ldg.beginTrackApi LdgCollectWitnessDataFn
ldg.ac.collectWitnessData()
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc commit*(ldg: LedgerRef, sp: LedgerSpRef) =
ldg.beginTrackApi LdgCommitFn
ldg.ac.commit(sp)
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc deleteAccount*(ldg: LedgerRef, eAddr: Address) =
ldg.beginTrackApi LdgDeleteAccountFn
ldg.ac.deleteAccount(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr)
proc dispose*(ldg: LedgerRef, sp: LedgerSpRef) =
ldg.beginTrackApi LdgDisposeFn
ldg.ac.dispose(sp)
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc getAndClearLogEntries*(ldg: LedgerRef): seq[Log] =
ldg.beginTrackApi LdgGetAndClearLogEntriesFn
result = ldg.ac.getAndClearLogEntries()
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc getBalance*(ldg: LedgerRef, eAddr: Address): UInt256 =
ldg.beginTrackApi LdgGetBalanceFn
result = ldg.ac.getBalance(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc getCode*(ldg: LedgerRef, eAddr: Address): CodeBytesRef =
ldg.beginTrackApi LdgGetCodeFn
result = ldg.ac.getCode(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc getCodeHash*(ldg: LedgerRef, eAddr: Address): Hash32 =
ldg.beginTrackApi LdgGetCodeHashFn
result = ldg.ac.getCodeHash(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result=($$result)
proc getCodeSize*(ldg: LedgerRef, eAddr: Address): int =
ldg.beginTrackApi LdgGetCodeSizeFn
result = ldg.ac.getCodeSize(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc getCommittedStorage*(
ldg: LedgerRef;
eAddr: Address;
slot: UInt256;
): UInt256 =
ldg.beginTrackApi LdgGetCommittedStorageFn
result = ldg.ac.getCommittedStorage(eAddr, slot)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), slot, result
proc getNonce*(ldg: LedgerRef, eAddr: Address): AccountNonce =
ldg.beginTrackApi LdgGetNonceFn
result = ldg.ac.getNonce(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc getStorage*(ldg: LedgerRef, eAddr: Address, slot: UInt256): UInt256 =
ldg.beginTrackApi LdgGetStorageFn
result = ldg.ac.getStorage(eAddr, slot)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), slot, result
proc getStorageRoot*(ldg: LedgerRef, eAddr: Address): Hash32 =
ldg.beginTrackApi LdgGetStorageRootFn
result = ldg.ac.getStorageRoot(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result=($$result)
proc getTransientStorage*(
ldg: LedgerRef;
eAddr: Address;
slot: UInt256;
): UInt256 =
ldg.beginTrackApi LdgGetTransientStorageFn
result = ldg.ac.getTransientStorage(eAddr, slot)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), slot, result
proc contractCollision*(ldg: LedgerRef, eAddr: Address): bool =
ldg.beginTrackApi LdgContractCollisionFn
result = ldg.ac.contractCollision(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc inAccessList*(ldg: LedgerRef, eAddr: Address): bool =
ldg.beginTrackApi LdgInAccessListFn
result = ldg.ac.inAccessList(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc inAccessList*(ldg: LedgerRef, eAddr: Address, slot: UInt256): bool =
ldg.beginTrackApi LdgInAccessListFn
result = ldg.ac.inAccessList(eAddr, slot)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), slot, result
proc incNonce*(ldg: LedgerRef, eAddr: Address) =
ldg.beginTrackApi LdgIncNonceFn
ldg.ac.incNonce(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr)
proc isDeadAccount*(ldg: LedgerRef, eAddr: Address): bool =
ldg.beginTrackApi LdgIsDeadAccountFn
result = ldg.ac.isDeadAccount(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc isEmptyAccount*(ldg: LedgerRef, eAddr: Address): bool =
ldg.beginTrackApi LdgIsEmptyAccountFn
result = ldg.ac.isEmptyAccount(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), result
proc isTopLevelClean*(ldg: LedgerRef): bool =
ldg.beginTrackApi LdgIsTopLevelCleanFn
result = ldg.ac.isTopLevelClean()
ldg.ifTrackApi: debug apiTxt, api, elapsed, result
proc makeMultiKeys*(ldg: LedgerRef): MultiKeysRef =
ldg.beginTrackApi LdgMakeMultiKeysFn
result = ldg.ac.makeMultiKeys()
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc persist*(ldg: LedgerRef, clearEmptyAccount = false, clearCache = false) =
ldg.beginTrackApi LdgPersistFn
ldg.ac.persist(clearEmptyAccount, clearCache)
ldg.ifTrackApi: debug apiTxt, api, elapsed, clearEmptyAccount, clearCache
proc ripemdSpecial*(ldg: LedgerRef) =
ldg.beginTrackApi LdgRipemdSpecialFn
ldg.ac.ripemdSpecial()
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc rollback*(ldg: LedgerRef, sp: LedgerSpRef) =
ldg.beginTrackApi LdgRollbackFn
ldg.ac.rollback(sp)
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc safeDispose*(ldg: LedgerRef, sp: LedgerSpRef) =
ldg.beginTrackApi LdgSafeDisposeFn
ldg.ac.safeDispose(sp)
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc selfDestruct*(ldg: LedgerRef, eAddr: Address) =
ldg.beginTrackApi LdgSelfDestructFn
ldg.ac.selfDestruct(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc selfDestruct6780*(ldg: LedgerRef, eAddr: Address) =
ldg.beginTrackApi LdgSelfDestruct6780Fn
ldg.ac.selfDestruct6780(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc selfDestructLen*(ldg: LedgerRef): int =
ldg.beginTrackApi LdgSelfDestructLenFn
result = ldg.ac.selfDestructLen()
ldg.ifTrackApi: debug apiTxt, api, elapsed, result
proc setBalance*(ldg: LedgerRef, eAddr: Address, balance: UInt256) =
ldg.beginTrackApi LdgSetBalanceFn
ldg.ac.setBalance(eAddr, balance)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), balance
proc setCode*(ldg: LedgerRef, eAddr: Address, code: seq[byte]) =
ldg.beginTrackApi LdgSetCodeFn
ldg.ac.setCode(eAddr, code)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), code
proc setNonce*(ldg: LedgerRef, eAddr: Address, nonce: AccountNonce) =
ldg.beginTrackApi LdgSetNonceFn
ldg.ac.setNonce(eAddr, nonce)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), nonce
proc setStorage*(ldg: LedgerRef, eAddr: Address, slot, val: UInt256) =
ldg.beginTrackApi LdgSetStorageFn
ldg.ac.setStorage(eAddr, slot, val)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), slot, val
proc setTransientStorage*(
ldg: LedgerRef;
eAddr: Address;
slot: UInt256;
val: UInt256;
) =
ldg.beginTrackApi LdgSetTransientStorageFn
ldg.ac.setTransientStorage(eAddr, slot, val)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), slot, val
proc getStateRoot*(ldg: LedgerRef): Hash32 =
ldg.beginTrackApi LdgStateFn
result = ldg.ac.getStateRoot()
ldg.ifTrackApi: debug apiTxt, api, elapsed, result
proc subBalance*(ldg: LedgerRef, eAddr: Address, delta: UInt256) =
ldg.beginTrackApi LdgSubBalanceFn
ldg.ac.subBalance(eAddr, delta)
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr), delta
proc getAccessList*(ldg: LedgerRef): AccessList =
ldg.beginTrackApi LdgGetAccessListFn
result = ldg.ac.getAccessList()
ldg.ifTrackApi: debug apiTxt, api, elapsed
proc getEthAccount*(ldg: LedgerRef, eAddr: Address): Account =
ldg.beginTrackApi LdgGetAthAccountFn
result = ldg.ac.getEthAccount(eAddr)
ldg.ifTrackApi: debug apiTxt, api, elapsed, result
proc getAccountProof*(ldg: LedgerRef, eAddr: Address): seq[seq[byte]] =
result = ldg.ac.getAccountProof(eAddr)
proc getStorageProof*(ldg: LedgerRef, eAddr: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] =
result = ldg.ac.getStorageProof(eAddr, slots)
# ------------------------------------------------------------------------------
# Public virtual read-only methods
# ------------------------------------------------------------------------------
proc getStateRoot*(db: ReadOnlyStateDB): Hash32 {.borrow.}
proc getCodeHash*(db: ReadOnlyStateDB, address: Address): Hash32 = getCodeHash(distinctBase db, address)
proc getStorageRoot*(db: ReadOnlyStateDB, address: Address): Hash32 = getStorageRoot(distinctBase db, address)
proc getBalance*(db: ReadOnlyStateDB, address: Address): UInt256 = getBalance(distinctBase db, address)
proc getStorage*(db: ReadOnlyStateDB, address: Address, slot: UInt256): UInt256 = getStorage(distinctBase db, address, slot)
proc getNonce*(db: ReadOnlyStateDB, address: Address): AccountNonce = getNonce(distinctBase db, address)
proc getCode*(db: ReadOnlyStateDB, address: Address): CodeBytesRef = getCode(distinctBase db, address)
proc getCodeSize*(db: ReadOnlyStateDB, address: Address): int = getCodeSize(distinctBase db, address)
proc contractCollision*(db: ReadOnlyStateDB, address: Address): bool = contractCollision(distinctBase db, address)
proc accountExists*(db: ReadOnlyStateDB, address: Address): bool = accountExists(distinctBase db, address)
proc isDeadAccount*(db: ReadOnlyStateDB, address: Address): bool = isDeadAccount(distinctBase db, address)
proc isEmptyAccount*(db: ReadOnlyStateDB, address: Address): bool = isEmptyAccount(distinctBase db, address)
proc getCommittedStorage*(db: ReadOnlyStateDB, address: Address, slot: UInt256): UInt256 = getCommittedStorage(distinctBase db, address, slot)
proc inAccessList*(db: ReadOnlyStateDB, address: Address): bool = inAccessList(distinctBase db, address)
proc inAccessList*(db: ReadOnlyStateDB, address: Address, slot: UInt256): bool = inAccessList(distinctBase db, address)
proc getTransientStorage*(db: ReadOnlyStateDB,
address: Address, slot: UInt256): UInt256 = getTransientStorage(distinctBase db, address, slot)
proc getAccountProof*(db: ReadOnlyStateDB, address: Address): seq[seq[byte]] = getAccountProof(distinctBase db, address)
proc getStorageProof*(db: ReadOnlyStateDB, address: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] = getStorageProof(distinctBase db, address, slots)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -1,148 +0,0 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
{.push raises: [].}
import
std/[strutils, times],
eth/common,
stew/byteutils,
../../../evm/code_bytes,
../../aristo/aristo_profile,
../../core_db,
"."/[base_config, base_desc]
type
Elapsed* = distinct Duration
## Needed for local `$` as it would be ambiguous for `Duration`
LedgerFnInx* = enum
## Profiling table index
SummaryItem = "total"
LdgAccessListFn = "accessList"
LdgAccountExistsFn = "accountExists"
LdgAddBalanceFn = "addBalance"
LdgAddLogEntryFn = "addLogEntry"
LdgBeginSavepointFn = "beginSavepoint"
LdgClearStorageFn = "clearStorage"
LdgClearTransientStorageFn = "clearTransientStorage"
LdgCollectWitnessDataFn = "collectWitnessData"
LdgCommitFn = "commit"
LdgContractCollisionFn = "contractCollision"
LdgDeleteAccountFn = "deleteAccount"
LdgDisposeFn = "dispose"
LdgGetAccessListFn = "getAcessList"
LdgGetAccountFn = "getAccount"
LdgGetAndClearLogEntriesFn = "getAndClearLogEntries"
LdgGetBalanceFn = "getBalance"
LdgGetCodeFn = "getCode"
LdgGetCodeHashFn = "getCodeHash"
LdgGetCodeSizeFn = "getCodeSize"
LdgGetCommittedStorageFn = "getCommittedStorage"
LdgGetNonceFn = "getNonce"
LdgGetStorageFn = "getStorage"
LdgGetStorageRootFn = "getStorageRoot"
LdgGetTransientStorageFn = "getTransientStorage"
LdgGetAthAccountFn = "getEthAccount"
LdgInAccessListFn = "inAccessList"
LdgIncNonceFn = "incNonce"
LdgIsDeadAccountFn = "isDeadAccount"
LdgIsEmptyAccountFn = "isEmptyAccount"
LdgIsTopLevelCleanFn = "isTopLevelClean"
LdgMakeMultiKeysFn = "makeMultiKeys"
LdgPersistFn = "persist"
LdgRipemdSpecialFn = "ripemdSpecial"
LdgRollbackFn = "rollback"
LdgSafeDisposeFn = "safeDispose"
LdgSelfDestruct6780Fn = "selfDestruct6780"
LdgSelfDestructFn = "selfDestruct"
LdgSelfDestructLenFn = "selfDestructLen"
LdgSetBalanceFn = "setBalance"
LdgSetCodeFn = "setCode"
LdgSetNonceFn = "setNonce"
LdgSetStorageFn = "setStorage"
LdgSetTransientStorageFn = "setTransientStorage"
LdgStateFn = "state"
LdgSubBalanceFn = "subBalance"
LdgAccountsIt = "accounts"
LdgAdressesIt = "addresses"
LdgCachedStorageIt = "cachedStorage"
LdgPairsIt = "pairs"
LdgStorageIt = "storage"
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
func oaToStr(w: openArray[byte]): string =
w.toHex.toLowerAscii
func toStr(w: Address): string =
w.toHex
func toStr(w: Hash32): string =
w.toHex
func toStr(w: CodeBytesRef): string =
if w.isNil: "nil"
else: "[" & $w.bytes.len & "]"
func toStr(w: seq[byte]): string =
if 0 < w.len and w.len < 5: "<" & w.oaToStr & ">"
else: "seq[byte][" & $w.len & "]"
func toStr(w: seq[Log]): string =
"Logs[" & $w.len & "]"
func toStr(ela: Duration): string =
aristo_profile.toStr(ela)
# ------------------------------------------------------------------------------
# Public API logging helpers
# ------------------------------------------------------------------------------
func `$`*(w: CodeBytesRef): string {.used.} = w.toStr
func `$`*(e: Elapsed): string = e.Duration.toStr
func `$`*(l: seq[Log]): string = l.toStr
func `$`*(b: seq[byte]): string = b.toStr
func `$$`*(a: Address): string = a.toStr # otherwise collision w/existing `$`
func `$$`*(h: Hash32): string = h.toStr # otherwise collision w/existing `$`
# ------------------------------------------------------------------------------
# Public API logging framework
# ------------------------------------------------------------------------------
template beginTrackApi*(ldg: LedgerRef; s: LedgerFnInx) =
when LedgerEnableApiTracking:
const api {.inject,used.} = s # Generally available
let baStart {.inject.} = getTime() # Local use only
template ifTrackApi*(ldg: LedgerRef; code: untyped) =
when LedgerEnableApiTracking:
when LedgerEnableApiProfiling:
let elapsed {.inject,used.} = (getTime() - baStart).Elapsed
aristo_profile.update(ldg.profTab, api.ord, elapsed.Duration)
if ldg.trackApi:
when not LedgerEnableApiProfiling: # otherwise use variable above
let elapsed {.inject,used.} = (getTime() - baStart).Elapsed
code
# ------------------------------------------------------------------------------
# Public helpers
# ------------------------------------------------------------------------------
func init*(T: type LedgerProfListRef): T =
T(list: newSeq[LedgerProfData](1 + high(LedgerFnInx).ord))
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -1,48 +0,0 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
{.push raises: [].}
import
../../core_db/base/base_config
# Configuration section
const
EnableApiTracking = false
## When enabled, API functions are logged. Tracking is enabled by setting
## the `trackApi` flag to `true`. This setting is typically inherited from
## the `CoreDb` descriptor flag `trackLedgerApi` (which is only available
## if the flag `CoreDbEnableApiTracking` is set `true`.
EnableApiProfiling = false
## Enable API functions profiling. This setting is only effective if the
## flag `CoreDbEnableApiJumpTable` is set `true`.
# Exportable constants (leave alone this section)
const
LedgerEnableApiTracking* = EnableApiTracking and CoreDbEnableApiTracking
LedgerEnableApiProfiling* = EnableApiProfiling and CoreDbEnableApiJumpTable
# Support warning about extra compile time options. For production, non of
# the above features should be enabled.
import strutils
const ledgerBaseConfigExtras* = block:
var s: seq[string]
when LedgerEnableApiTracking:
s.add "logging"
when LedgerEnableApiProfiling:
s.add "profiling"
if s.len == 0:
""
else:
"Ledger(" & s.join(", ") & ")"
# End

View File

@ -1,33 +0,0 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
{.push raises: [].}
import
../../aristo/aristo_profile,
../backend/accounts_ledger
type
LedgerProfListRef* = AristoDbProfListRef
## Borrowed from `aristo_profile`, only used in profiling mode
LedgerProfData* = AristoDbProfData
## Borrowed from `aristo_profile`, only used in profiling mode
LedgerSpRef* = LedgerSavePoint
## Object for check point or save point
LedgerRef* = ref object of RootRef
## Root object with closures
trackApi*: bool ## For debugging
profTab*: LedgerProfListRef ## Profiling data (if any)
ac*: AccountsLedgerRef
# End

View File

@ -1,43 +0,0 @@
# Nimbus
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
import
../../core_db,
"."/[base_config, base_desc]
# ------------------------------------------------------------------------------
# Public constructor helper
# ------------------------------------------------------------------------------
when LedgerEnableApiProfiling:
import api_tracking
proc ldgProfData*(db: CoreDbRef): LedgerProfListRef =
## Return profiling data table (only available in profiling mode). If
## available (i.e. non-nil), result data can be organised by the functions
## available with `aristo_profile`.
##
## Note that profiling these data have accumulated over several ledger
## sessions running on the same `CoreDb` instance.
##
if db.ledgerHook.isNil:
db.ledgerHook = LedgerProfListRef.init()
cast[LedgerProfListRef](db.ledgerHook)
proc bless*(ldg: LedgerRef; db: CoreDbRef): LedgerRef =
when LedgerEnableApiTracking:
ldg.trackApi = db.trackLedgerApi
when LedgerEnableApiProfiling:
ldg.profTab = db.ldgProfData()
ldg
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -1,71 +0,0 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
{.push raises: [].}
import
eth/common,
../core_db,
./backend/accounts_ledger,
./base/[api_tracking, base_config, base_desc]
when LedgerEnableApiTracking:
import
std/times,
chronicles
logScope:
topics = "ledger"
const
apiTxt = "API"
# ------------------------------------------------------------------------------
# Public iterators
# ------------------------------------------------------------------------------
iterator accounts*(ldg: LedgerRef): Account =
ldg.beginTrackApi LdgAccountsIt
for w in ldg.ac.accounts():
yield w
ldg.ifTrackApi: debug apiTxt, api, elapsed
iterator addresses*(ldg: LedgerRef): Address =
ldg.beginTrackApi LdgAdressesIt
for w in ldg.ac.addresses():
yield w
ldg.ifTrackApi: debug apiTxt, api, elapsed
iterator cachedStorage*(ldg: LedgerRef, eAddr: Address): (UInt256,UInt256) =
ldg.beginTrackApi LdgCachedStorageIt
for w in ldg.ac.cachedStorage(eAddr):
yield w
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr)
iterator pairs*(ldg: LedgerRef): (Address,Account) =
ldg.beginTrackApi LdgPairsIt
for w in ldg.ac.pairs():
yield w
ldg.ifTrackApi: debug apiTxt, api, elapsed
iterator storage*(
ldg: LedgerRef;
eAddr: Address;
): (UInt256,UInt256) =
ldg.beginTrackApi LdgStorageIt
for w in ldg.ac.storage(eAddr):
yield w
ldg.ifTrackApi: debug apiTxt, api, elapsed, eAddr=($$eAddr)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -110,7 +110,7 @@ proc reinit*(self: BaseVMState; ## Object descriptor
com = self.com com = self.com
db = com.db db = com.db
ac = if linear or self.stateDB.getStateRoot() == parent.stateRoot: self.stateDB ac = if linear or self.stateDB.getStateRoot() == parent.stateRoot: self.stateDB
else: LedgerRef.init(db, self.stateDB.ac.storeSlotHash) else: LedgerRef.init(db, self.stateDB.storeSlotHash)
flags = self.flags flags = self.flags
self[].reset self[].reset
self.init( self.init(

View File

@ -19,8 +19,7 @@ import
../replay/[pp, undump_blocks, undump_blocks_era1, xcheck], ../replay/[pp, undump_blocks, undump_blocks_era1, xcheck],
./test_helpers ./test_helpers
when CoreDbEnableProfiling or when CoreDbEnableProfiling:
LedgerEnableApiProfiling:
import import
std/sequtils std/sequtils
@ -33,12 +32,6 @@ when CoreDbEnableProfiling:
kvtProfData: KvtDbProfListRef kvtProfData: KvtDbProfListRef
cdbProfData: CoreDbProfListRef cdbProfData: CoreDbProfListRef
when LedgerEnableApiProfiling:
import
../../nimbus/db/ledger/base/base_helpers
var
ldgProfData: LedgerProfListRef
const const
EnableExtraLoggingControl = true EnableExtraLoggingControl = true
var var
@ -125,11 +118,6 @@ proc test_chainSyncProfilingPrint*(
else: "" else: ""
discard info discard info
var blurb: seq[string] var blurb: seq[string]
when LedgerEnableApiProfiling:
blurb.add ldgProfData.profilingPrinter(
names = LedgerFnInx.toSeq.mapIt($it),
header = "Ledger profiling results" & info,
indent)
when CoreDbEnableProfiling: when CoreDbEnableProfiling:
blurb.add cdbProfData.profilingPrinter( blurb.add cdbProfData.profilingPrinter(
names = CoreDbFnInx.toSeq.mapIt($it), names = CoreDbFnInx.toSeq.mapIt($it),
@ -192,8 +180,6 @@ proc test_chainSync*(
aristoProfData = com.db.ariApi.AristoApiProfRef.data aristoProfData = com.db.ariApi.AristoApiProfRef.data
kvtProfData = com.db.kvtApi.KvtApiProfRef.data kvtProfData = com.db.kvtApi.KvtApiProfRef.data
cdbProfData = com.db.profTab cdbProfData = com.db.profTab
when LedgerEnableApiProfiling:
ldgProfData = com.db.ldgProfData()
# This will enable printing the `era1` covered block ranges (if any) # This will enable printing the `era1` covered block ranges (if any)
undump_blocks_era1.noisy = noisy undump_blocks_era1.noisy = noisy

View File

@ -14,7 +14,6 @@ import
stew/byteutils, stew/byteutils,
stew/endians2, stew/endians2,
../nimbus/config, ../nimbus/config,
../nimbus/db/ledger,
../nimbus/db/storage_types, ../nimbus/db/storage_types,
../nimbus/common/common, ../nimbus/common/common,
../nimbus/core/chain, ../nimbus/core/chain,
@ -22,7 +21,7 @@ import
../nimbus/core/casper, ../nimbus/core/casper,
../nimbus/transaction, ../nimbus/transaction,
../nimbus/constants, ../nimbus/constants,
../nimbus/db/ledger/backend/accounts_ledger {.all.}, # import all private symbols ../nimbus/db/ledger {.all.}, # import all private symbols
unittest2 unittest2
const const