diff --git a/nimbus/db/ledger/verkle_accounts_cache.nim b/nimbus/db/ledger/verkle_accounts_cache.nim index 3b26d3311..cc397bb36 100644 --- a/nimbus/db/ledger/verkle_accounts_cache.nim +++ b/nimbus/db/ledger/verkle_accounts_cache.nim @@ -217,3 +217,459 @@ proc getAccount(ac: AccountsCache, address: EthAddress, shouldCreate = true): Re # cache the account ac.savePoint.cache[address] = result +proc clone(acc: RefAccount, cloneStorage: bool): RefAccount = + new(result) + result.account = acc.account + result.flags = acc.flags + result.code = acc.code + + if cloneStorage: + result.originalStorage = acc.originalStorage + # it's ok to clone a table this way + result.overlayStorage = acc.overlayStorage + +# TODO: Verify logic +proc isEmpty(acc: RefAccount): bool = + result = acc.account.codeHash == EMPTY_SHA3 and # in verkle codeHash might not be be == EMPTY_CODE_HASH + acc.account.balance.isZero and + acc.account.nonce == 0 + +template exists(acc: RefAccount): bool = + Alive in acc.flags + +proc originalStorageValue(acc: RefAccount, address: EthAddress, slot: UInt256, db: VerkleTrie): 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. + var (slotAsKey, subIndex) = createTrieKeyFromSlot(slot.toBytesBE()) + discard subIndex + + let foundRecord = db.getSlotBytes(address, slotAsKey.toBytesBE()) + result = UInt256.fromBytesBE(foundRecord) + + acc.originalStorage[slot] = result + +proc storageValue(acc: RefAccount, address: EthAddress, slot: UInt256, db: VerkleTrie): UInt256 = + acc.overlayStorage.withValue(slot, val) do: + return val[] + do: + result = acc.originalStorageValue(address, slot, db) + +proc kill(acc: RefAccount) = + acc.flags.excl Alive + acc.overlayStorage.clear() + acc.originalStorage = nil + acc.account = newAccount() + acc.code = default(seq[byte]) + +type + PersistMode = enum + DoNothing + Update + Remove + +proc persistMode(acc: RefAccount): 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: RefAccount, address: EthAddress, db: VerkleTrie) = + if acc.code.len != 0: + when defined(geth): + VerkleTrieRef(db).updateContractCode(address, acc.account.codeHash, acc.code) + else: + VerkleTrieRef(db).updateContractCode(address, acc.account.codeHash, acc.code) + +proc persistStorage(acc: RefAccount, address: EthAddress, db: VerkleTrie, clearCache: bool) = + if acc.overlayStorage.len == 0: + return + + if not clearCache and acc.originalStorage.isNil: + acc.originalStorage = newTable[UInt256, UInt256]() + + for slot, value in acc.overlayStorage: + var (slotAsKey, subIndex) = createTrieKeyFromSlot(slot.toBytesBE()) + discard subIndex + + if value > 0: + var slotBytes = slotAsKey.toBytesBE() + var valueToBytes = value.toBytesBE() + db.putSlotBytes(address, slotBytes, valueToBytes) + + else: + ## No delete operation for now in Kaustinen + discard + + if not clearCache: + # if we preserve cache, move the overlayStorage + # to originalStorage, related to EIP2200, EIP1283 + for slot, value in acc.overlayStorage: + if value > 0: + acc.originalStorage[slot] = value + else: + acc.originalStorage.del(slot) + acc.overlayStorage.clear() + +proc getBalance*(ac: AccountsCache, address: EthAddress): UInt256 {.inline.} = + let acc = ac.getAccount(address, false) + if acc.isNil: emptyAcc.balance + else: acc.account.balance + +proc getNonce*(ac: AccountsCache, address: EthAddress): AccountNonce {.inline.} = + let acc = ac.getAccount(address, false) + if acc.isNil: emptyAcc.nonce + else: acc.account.nonce + +proc getCodeSize*(ac: AccountsCache, address: EthAddress): int {.inline.} = + let acc = ac.getAccount(address, false) + if acc.isNil: 0 + else: acc.code.len + +proc getCodeHash*(ac: AccountsCache, address: EthAddress): Hash256 {.inline.} = + let acc = ac.getAccount(address, false) + if acc.isNil: emptyAcc.codeHash + else: acc.account.codeHash + +proc makeDirty(ac: AccountsCache, address: EthAddress, cloneStorage = true): RefAccount = + ac.isDirty = true + result = ac.getAccount(address) + if address in ac.savePoint.cache: + # it's already in latest savepoint + result.flags.incl Dirty + return + + # put a copy into latest savepoint + result = result.clone(cloneStorage) + result.flags.incl Dirty + ac.savePoint.cache[address] = result + +# TODO : Verify the logic of empty hash for verkle +proc hasCodeOrNonce*(ac: AccountsCache, address: EthAddress): bool {.inline.} = + let acc = ac.getAccount(address, false) + if acc.isNil: + return + acc.account.nonce != 0 or acc.account.codeHash != EMPTY_SHA3 # for verkle this might not be the EMPTY_CODE_HASH + +proc accountExists*(ac: AccountsCache, address: EthAddress): bool {.inline.} = + let acc = ac.getAccount(address, false) + if acc.isNil: + return + acc.exists() + +proc isEmptyAccount*(ac: AccountsCache, address: EthAddress): bool {.inline.} = + let acc = ac.getAccount(address, false) + doAssert not acc.isNil + doAssert acc.exists() + acc.isEmpty() + +proc isDeadAccount*(ac: AccountsCache, address: EthAddress): bool = + let acc = ac.getAccount(address, false) + if acc.isNil: + return true + if not acc.exists(): + return true + acc.isEmpty() + +proc setBalance*(ac: AccountsCache, address: EthAddress, balance: UInt256) = + let acc = ac.getAccount(address) + acc.flags.incl {Alive} + if acc.account.balance != balance: + ac.makeDirty(address).account.balance = balance + +proc addBalance*(ac: AccountsCache, address: EthAddress, delta: UInt256) {.inline.} = + # 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: AccountsCache, address: EthAddress, delta: UInt256) {.inline.} = + 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: AccountsCache, address: EthAddress, nonce: AccountNonce) = + let acc = ac.getAccount(address) + acc.flags.incl {Alive} + if acc.account.nonce != nonce: + ac.makeDirty(address).account.nonce = nonce + +proc incNonce*(ac: AccountsCache, address: EthAddress) {.inline.} = + ac.setNonce(address, ac.getNonce(address) + 1) + +proc setStorage*(ac: AccountsCache, address: EthAddress, slot, value: UInt256) = + let acc = ac.getAccount(address) + acc.flags.incl {Alive} + let oldValue = acc.storageValue(address, slot, ac.trie) + if oldValue != value: + var acc = ac.makeDirty(address) + acc.overlayStorage[slot] = value + acc.flags.incl StorageChanged + +proc clearStorage*(ac: AccountsCache, address: EthAddress) = + # 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} + if acc.account.storageRoot != EMPTY_ROOT_HASH: + # there is no point to clone the storage since we want to remove it + let acc = ac.makeDirty(address, cloneStorage = false) + acc.account.storageRoot = EMPTY_ROOT_HASH + if acc.originalStorage.isNil.not: + # also clear originalStorage cache, otherwise + # both getStorage and getCommittedStorage will + # return wrong value + acc.originalStorage.clear() + +proc setCode*(ac: AccountsCache, address: EthAddress, code: seq[byte]) = + let acc = ac.getAccount(address) + acc.flags.incl {Alive} + let codeHash = keccakHash(code) + if acc.account.codeHash != codeHash: + var acc = ac.makeDirty(address) + acc.account.codeHash = codeHash + acc.code = code + acc.flags.incl CodeChanged + +proc deleteAccount*(ac: AccountsCache, address: EthAddress) = + # make sure all savepoints already committed + doAssert(ac.savePoint.parentSavepoint.isNil) + let acc = ac.getAccount(address) + acc.kill() + +proc selfDestruct*(ac: AccountsCache, address: EthAddress) = + ac.setBalance(address, 0.u256) + ac.savePoint.selfDestruct.incl address + +proc selfDestruct6780*(ac: AccountsCache, address: EthAddress) = + let acc = ac.getAccount(address, false) + if acc.isNil: + return + + if NewlyCreated in acc.flags: + ac.selfDestruct(address) + +proc selfDestructLen*(ac: AccountsCache): int = + ac.savePoint.selfDestruct.len + +proc addLogEntry*(ac: AccountsCache, log: Log) = + ac.savePoint.logEntries.add log + +proc logEntries*(ac: AccountsCache): seq[Log] = + ac.savePoint.logEntries + +proc getAndClearLogEntries*(ac: AccountsCache): seq[Log] = + result = ac.savePoint.logEntries + ac.savePoint.logEntries.setLen(0) + +proc ripemdSpecial*(ac: AccountsCache) = + ac.ripemdSpecial = true + +proc deleteEmptyAccount(ac: AccountsCache, address: EthAddress) = + let acc = ac.getAccount(address, false) + if acc.isNil: + return + if not acc.isEmpty: + return + if not acc.exists: + return + acc.kill() + +proc clearEmptyAccounts(ac: AccountsCache) = + for address, acc in ac.savePoint.cache: + if Touched in acc.flags and + acc.isEmpty and acc.exists: + acc.kill() + + # https://github.com/ethereum/EIPs/issues/716 + if ac.ripemdSpecial: + ac.deleteEmptyAccount(RIPEMD_ADDR) + ac.ripemdSpecial = false + + +iterator addresses*(ac: AccountsCache): EthAddress = + # make sure all savepoint already committed + doAssert(ac.savePoint.parentSavepoint.isNil) + for address, _ in ac.savePoint.cache: + yield address + +iterator accounts*(ac: AccountsCache): Account = + # make sure all savepoint already committed + doAssert(ac.savePoint.parentSavepoint.isNil) + for _, account in ac.savePoint.cache: + yield account.account + +iterator pairs*(ac: AccountsCache): (EthAddress, Account) = + # make sure all savepoint already committed + doAssert(ac.savePoint.parentSavepoint.isNil) + for address, account in ac.savePoint.cache: + yield (address, account.account) + +# Storage iterator missing @agnxsh + +iterator cachedStorage*(ac: AccountsCache, address: EthAddress): (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 accessList*(ac: AccountsCache, address: EthAddress) {.inline.} = + ac.savePoint.accessList.add(address) + +proc accessList*(ac: AccountsCache, address: EthAddress, slot: UInt256) {.inline.} = + ac.savePoint.accessList.add(address, slot) + +func inAccessList*(ac: AccountsCache, address: EthAddress): bool = + var sp = ac.savePoint + while sp != nil: + result = sp.accessList.contains(address) + if result: + return + sp = sp.parentSavepoint + +func inAccessList*(ac: AccountsCache, address: EthAddress, 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: AccountsCache, + address: EthAddress, 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: AccountsCache, + address: EthAddress, slot, val: UInt256) = + ac.savePoint.transientStorage.setStorage(address, slot, val) + +proc clearTransientStorage*(ac: AccountsCache) {.inline.} = + # make sure all savepoint already committed + doAssert(ac.savePoint.parentSavepoint.isNil) + ac.savePoint.transientStorage.clear() + +func getAccessList*(ac: AccountsCache): common.AccessList = + # make sure all savepoint already committed + doAssert(ac.savePoint.parentSavepoint.isNil) + ac.savePoint.accessList.getAccessList() + + +proc persist*(ac: AccountsCache, + clearEmptyAccount: bool = false, + clearCache: bool = true) = + # make sure all savepoint already committed + doAssert(ac.savePoint.parentSavepoint.isNil) + var cleanAccounts = initHashSet[EthAddress]() + + if clearEmptyAccount: + ac.clearEmptyAccounts() + + for address in ac.savePoint.selfDestruct: + ac.deleteAccount(address) + + for address, acc in ac.savePoint.cache: + case acc.persistMode() + of Update: + if CodeChanged in acc.flags: + acc.persistCode(address, ac.trie) + if StorageChanged in acc.flags: + # storageRoot must be updated first + # before persisting account into merkle trie + acc.persistStorage(address, ac.trie, clearCache) + ac.trie.putAccountBytes address, acc.account + of Remove: + VerkleTrieRef(ac.trie).delAccountBytes address + if not clearCache: + cleanAccounts.incl address + of DoNothing: + # dead man tell no tales + # remove touched dead account from cache + if not clearCache and Alive notin acc.flags: + cleanAccounts.incl address + + acc.flags = acc.flags - resetFlags + + if clearCache: + ac.savePoint.cache.clear() + else: + for x in cleanAccounts: + ac.savePoint.cache.del x + + ac.savePoint.selfDestruct.clear() + + # EIP2929 + ac.savePoint.accessList.clear() + + ac.isDirty = false + +proc getStorageRoot*(ac: AccountsCache, address: EthAddress): Hash256 = + # beware that if the account not persisted, + # the storage root will not be updated + let acc = ac.getAccount(address, false) + if acc.isNil: emptyAcc.storageRoot + else: acc.account.storageRoot + +func update(wd: var WitnessData, acc: RefAccount) = + # 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 CodeLoaded in acc.flags + + 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 + +func witnessData(acc: RefAccount): WitnessData = + result.storageKeys = initHashSet[UInt256]() + update(result, acc) + +proc collectWitnessData*(ac: AccountsCache) = + # 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: AccountsCache): 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() \ No newline at end of file