nimbus-eth1/nimbus/db/ledger/distinct_ledgers.nim
Jordan Hrycaj 2c35390bdf
Core db and aristo maintenance update (#2014)
* Aristo: Update error return code

why:
  Failing of `Aristo` function `delete()` might fail because there is
  no such data item on the db. This must return a single error code
  as is done with `fetch()`.

* Ledger: Better error handling

why:
  The `expect()` clauses have been replaced by raising asserts indicating
  the error from the database backend.

   Also, `delete()` failures are legitimate if the item to delete does not
   exist.

* Aristo: Delete function must always leave a label on DB for `hashify()`

why:
  The `hashify()` uses the labels left bu `merge()` and `delete()` to
  compile (and optimise) a scheduler for subsequent hashing.

  Originally, the labels were not used for deleted entries and `delete()`
  still had some edge case where the deletion label was not properly
  handled.

* Aristo: Update `hashify()` scheduler, remove buggy optimisation

why:
  Was left over from version without virtual state roots which did not
  know about account payload leaf vertices referring to storage roots.

* Aristo: Label storage trie account in `delete()` similar to `merge()`

details;
  The `delete()` function applied to a non-static state root (assumed
  to be a storage root) will check the payload of an accounts leaf
  and mark its Merkle keys to be re-checked when runninh `hashify()`

* Aristo: Clean up and re-org recycled vertex IDs in `hashify()`

why:
  Re-organising the recycled vertex IDs list intends to reduce the size of the
  list.

  This list is organised as a LIFO (or stack.) By reorganising it in a way
  so that the least vertex ID numbers are on top, the list will be kept
  smaller as observed on some examples (less than 30%.)

* CoreDb: Accept storage trie deletion requests in non-initialised state

why:
  Due to lazy initialisation, the root vertex ID might not yet exist. So
  the `Aristo` database handlers would reject this call with an error and
  this condition needs to be handled by the API (which realises the lazy
  feature.)

* Cosmetics & code massage, prettify logging

* fix missing import
2024-02-08 16:32:16 +00:00

202 lines
6.5 KiB
Nim

# 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.
# The point of this file is just to give a little more type-safety
# and clarity to our use of SecureHexaryTrie, by having distinct
# types for the big trie containing all the accounts and the little
# tries containing the storage for an individual account.
#
# It's nice to have all the accesses go through "getAccountBytes"
# rather than just "get" (which is hard to search for). Plus we
# may want to put in assertions to make sure that the nodes for
# the account are all present (in stateless mode), etc.
{.push raises: [].}
## Re-write of `distinct_tries.nim` to be imported into `accounts_ledger.nim`
## for using new database API.
##
import
std/[algorithm, sequtils, strutils, tables, typetraits],
chronicles,
eth/common,
results,
".."/[core_db, storage_types]
type
AccountLedger* = distinct CoreDxAccRef
StorageLedger* = distinct CoreDxPhkRef
SomeLedger* = AccountLedger | StorageLedger
# ------------------------------------------------------------------------------
# Public debugging helpers
# ------------------------------------------------------------------------------
proc toSvp*(sl: StorageLedger): seq[(UInt256,UInt256)] =
## Dump as slot id-value pair sequence
let
db = sl.distinctBase.parent
save = db.trackNewApi
db.trackNewApi = false
defer: db.trackNewApi = save
let kvt = db.newKvt
var kvp: Table[UInt256,UInt256]
try:
for (slotHash,val) in sl.distinctBase.toMpt.pairs:
let rc = kvt.get(slotHashToSlotKey(slotHash).toOpenArray)
if rc.isErr:
warn "StorageLedger.dump()", slotHash, error=($$rc.error)
else:
kvp[rlp.decode(rc.value,UInt256)] = rlp.decode(val,UInt256)
except CatchableError as e:
raiseAssert "Ooops(" & $e.name & "): " & e.msg
kvp.keys.toSeq.sorted.mapIt((it,kvp.getOrDefault(it,high UInt256)))
proc toStr*(w: seq[(UInt256,UInt256)]): string =
"[" & w.mapIt("(" & it[0].toHex & "," & it[1].toHex & ")").join(", ") & "]"
# ------------------------------------------------------------------------------
# Public helpers
# ------------------------------------------------------------------------------
proc db*(t: SomeLedger): CoreDbRef =
t.distinctBase.parent
proc rootHash*(t: SomeLedger): Hash256 =
const info = "SomeLedger/rootHash(): "
let rc = t.distinctBase.getTrie().rootHash()
if rc.isErr:
raiseAssert info & $$rc.error
rc.value
proc getTrie*(t: SomeLedger): CoreDbTrieRef =
t.distinctBase.getTrie()
# ------------------------------------------------------------------------------
# Public functions: accounts ledger
# ------------------------------------------------------------------------------
proc init*(
T: type AccountLedger;
db: CoreDbRef;
root: Hash256;
pruneOk = true;
): T =
db.newAccMpt(root, pruneOk, Shared).T
proc init*(
T: type AccountLedger;
db: CoreDbRef;
pruneOk = true;
): T =
db.newAccMpt(EMPTY_ROOT_HASH, pruneOk, Shared).AccountLedger
proc fetch*(al: AccountLedger; eAddr: EthAddress): Result[CoreDbAccount,void] =
## Using `fetch()` for trie data retrieval
let rc = al.distinctBase.fetch(eAddr)
if rc.isErr:
return err()
ok rc.value
proc merge*(al: AccountLedger; account: CoreDbAccount) =
## Using `merge()` for trie data storage
const info = "AccountLedger/merge(): "
al.distinctBase.merge(account).isOkOr:
raiseAssert info & $$error
proc delete*(al: AccountLedger, eAddr: EthAddress) =
const info = "AccountLedger/delete()"
al.distinctBase.delete(eAddr).isOkOr:
if error.error == MptNotFound:
return
raiseAssert info & $$error
proc persistent*(al: AccountLedger) =
let rc = al.distinctBase.persistent()
if rc.isErr:
if rc.error.error != AccTxPending:
raiseAssert "persistent oops, error=" & $$rc.error
discard al.distinctBase.getTrie.rootHash.valueOr:
raiseAssert "re-hash oops, error=" & $$error
# ------------------------------------------------------------------------------
# Public functions: storage ledger
# ------------------------------------------------------------------------------
proc init*(
T: type StorageLedger;
al: AccountLedger;
account: CoreDbAccount;
reHashOk = true;
pruneOk = false;
): T =
## Storage trie constructor.
##
## Note that the argument `pruneOk` should be left `false` on the legacy
## `CoreDb` backend. Otherwise, pruning might kill some unwanted entries from
## storage tries ending up with an unstable database leading to crashes (see
## https://github.com/status-im/nimbus-eth1/issues/932.)
const
info = "StorageLedger/init(): "
let
db = al.distinctBase.parent
stt = account.stoTrie
if not stt.isNil and reHashOk:
let rc = al.distinctBase.getTrie.rootHash
if rc.isErr:
raiseAssert "re-hash oops, error=" & $$rc.error
let
trie = if stt.isNil: db.getTrie(account.address) else: stt
mpt = block:
let rc = db.newMpt(trie, pruneOk, Shared)
if rc.isErr:
raiseAssert info & $$rc.error
rc.value
mpt.toPhk.T
proc fetch*(sl: StorageLedger, slot: UInt256): Result[Blob,void] =
let rc = sl.distinctBase.fetch(slot.toBytesBE)
if rc.isErr:
return err()
ok rc.value
proc merge*(sl: StorageLedger, slot: UInt256, value: openArray[byte]) =
const info = "StorageLedger/merge(): "
sl.distinctBase.merge(slot.toBytesBE, value).isOkOr:
raiseAssert info & $$error
proc delete*(sl: StorageLedger, slot: UInt256) =
const info = "StorageLedger/delete(): "
sl.distinctBase.delete(slot.toBytesBE).isOkOr:
if error.error == MptNotFound:
return
raiseAssert info & $$error
iterator storage*(
al: AccountLedger;
account: CoreDbAccount;
): (Blob,Blob)
{.gcsafe, raises: [CoreDbApiError].} =
## For given account, iterate over storage slots
const
info = "storage(): "
let trie = account.stoTrie
if not trie.isNil:
let mpt = al.distinctBase.parent.newMpt(trie, saveMode=Shared).valueOr:
raiseAssert info & $$error
for (key,val) in mpt.pairs:
yield (key,val)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------