Simplify LedgerRef: remove unnecessary abstraction (#2826)
This commit is contained in:
parent
6c3bbbf22c
commit
f201eb611e
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
results,
|
results,
|
||||||
../../db/ledger,
|
|
||||||
../../evm/state,
|
../../evm/state,
|
||||||
../../evm/types,
|
../../evm/types,
|
||||||
../executor,
|
../executor,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
|
@ -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
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -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
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -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
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue