nimbus-eth1/nimbus/db/aristo/aristo_fetch.nim
Jacek Sieka 9d91191154
storage hike cache (#2484)
This PR adds a storage hike cache similar to the account hike cache
already present - this cache is less efficient because account storage
is already partically cached in the account ledger but nonetheless helps
keep hiking down.

Notably, there's an opportunity to optimise this cache and the others so
that they cooperate better insteado of overlapping, which is left for a
future PR.

This PR also fixes an O(N) memory usage for storage slots where the
delete would keep the full storage in a work list which on mainnet can
grow very large - the work list is replaced with a more conventional
recursive `O(log N)` approach.
2024-07-14 19:12:10 +02:00

325 lines
9.6 KiB
Nim

# nimbus-eth1
# 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.
## Aristo DB -- Obects Retrival Via Traversal Path
## ===============================================
##
{.push raises: [].}
import
std/typetraits,
eth/common,
results,
"."/[aristo_compute, aristo_desc, aristo_get, aristo_layers, aristo_hike]
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
func mustBeGeneric(
root: VertexID;
): Result[void,AristoError] =
## Verify that `root` is neither from an accounts tree nor a strorage tree.
if not root.isValid:
return err(FetchRootVidMissing)
elif root == VertexID(1):
return err(FetchAccRootNotAccepted)
elif LEAST_FREE_VID <= root.distinctBase:
return err(FetchStoRootNotAccepted)
ok()
proc retrieveLeaf(
db: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[VertexRef,AristoError] =
if path.len == 0:
return err(FetchPathInvalid)
for step in stepUp(NibblesBuf.fromBytes(path), root, db):
let vtx = step.valueOr:
if error in HikeAcceptableStopsNotFound:
return err(FetchPathNotFound)
return err(error)
if vtx.vType == Leaf:
return ok vtx
return err(FetchPathNotFound)
proc retrieveAccountPayload(
db: AristoDbRef;
accPath: Hash256;
): Result[LeafPayload,AristoError] =
if (let leafVtx = db.layersGetAccLeaf(accPath); leafVtx.isSome()):
if not leafVtx[].isValid():
return err(FetchPathNotFound)
return ok leafVtx[].lData
let accKey = accPath.to(AccountKey)
if (let leafVtx = db.accLeaves.lruFetch(accKey); leafVtx.isSome()):
if not leafVtx[].isValid():
return err(FetchPathNotFound)
return ok leafVtx[].lData
# Updated payloads are stored in the layers so if we didn't find them there,
# it must have been in the database
let
leafVtx = db.retrieveLeaf(VertexID(1), accPath.data).valueOr:
if error == FetchAccInaccessible:
return err(FetchPathNotFound)
return err(error)
ok db.accLeaves.lruAppend(accKey, leafVtx, accLruSize).lData
proc retrieveMerkleHash(
db: AristoDbRef;
root: VertexID;
updateOk: bool;
): Result[Hash256,AristoError] =
let key = block:
if updateOk:
db.computeKey((root, root)).valueOr:
if error == GetVtxNotFound:
return ok(EMPTY_ROOT_HASH)
return err(error)
else:
db.getKeyRc((root, root)).valueOr:
if error == GetKeyNotFound:
return ok(EMPTY_ROOT_HASH) # empty sub-tree
return err(error)
ok key.to(Hash256)
proc hasPayload(
db: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[bool,AristoError] =
let error = db.retrieveLeaf(root, path).errorOr:
return ok(true)
if error == FetchPathNotFound:
return ok(false)
err(error)
proc hasAccountPayload(
db: AristoDbRef;
accPath: Hash256;
): Result[bool,AristoError] =
let error = db.retrieveAccountPayload(accPath).errorOr:
return ok(true)
if error == FetchPathNotFound:
return ok(false)
err(error)
# ------------------------------------------------------------------------------
# Public helpers
# ------------------------------------------------------------------------------
proc fetchAccountHike*(
db: AristoDbRef; # Database
accPath: Hash256; # Implies a storage ID (if any)
): Result[Hike,AristoError] =
## Verify that the `accPath` argument properly referres to a storage root
## vertex ID. The function will reset the keys along the `accPath` for
## being modified.
##
## On success, the function will return an account leaf pair with the leaf
## vertex and the vertex ID.
##
# Expand vertex path to account leaf
var hike = accPath.hikeUp(VertexID(1), db).valueOr:
return err(FetchAccInaccessible)
# Extract the account payload from the leaf
let wp = hike.legs[^1].wp
if wp.vtx.vType != Leaf:
return err(FetchAccPathWithoutLeaf)
assert wp.vtx.lData.pType == AccountData # debugging only
ok(move(hike))
proc fetchStorageID*(
db: AristoDbRef;
accPath: Hash256;
): Result[VertexID,AristoError] =
## Public helper function for retrieving a storage (vertex) ID for a
## given account.
let
payload = ?db.retrieveAccountPayload(accPath)
stoID = payload.stoID
if not stoID.isValid:
return err(FetchPathNotFound)
ok stoID
proc retrieveStoragePayload(
db: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[UInt256,AristoError] =
let mixPath = AccountKey.mixUp(accPath, stoPath)
if (let leafVtx = db.layersGetStoLeaf(mixPath); leafVtx.isSome()):
if not leafVtx[].isValid():
return err(FetchPathNotFound)
return ok leafVtx[].lData.stoData
let mixKey = mixPath.to(AccountKey)
if (let leafVtx = db.stoLeaves.lruFetch(mixKey); leafVtx.isSome()):
if not leafVtx[].isValid():
return err(FetchPathNotFound)
return ok leafVtx[].lData.stoData
# Updated payloads are stored in the layers so if we didn't find them there,
# it must have been in the database
let
leafVtx = db.retrieveLeaf(? db.fetchStorageID(accPath), stoPath.data).valueOr:
return err(error)
ok db.stoLeaves.lruAppend(mixKey, leafVtx, accLruSize).lData.stoData
proc hasStoragePayload(
db: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[bool,AristoError] =
let error = db.retrieveStoragePayload(accPath, stoPath).errorOr:
return ok(true)
if error == FetchPathNotFound:
return ok(false)
err(error)
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc fetchLastSavedState*(
db: AristoDbRef;
): Result[SavedState,AristoError] =
## Wrapper around `getLstUbe()`. The function returns the state of the last
## saved state. This is a Merkle hash tag for vertex with ID 1 and a bespoke
## `uint64` identifier (may be interpreted as block number.)
db.getLstUbe()
proc fetchAccountRecord*(
db: AristoDbRef;
accPath: Hash256;
): Result[AristoAccount,AristoError] =
## Fetch an account record from the database indexed by `accPath`.
##
let pyl = ? db.retrieveAccountPayload(accPath)
assert pyl.pType == AccountData # debugging only
ok pyl.account
proc fetchAccountState*(
db: AristoDbRef;
updateOk: bool;
): Result[Hash256,AristoError] =
## Fetch the Merkle hash of the account root.
db.retrieveMerkleHash(VertexID(1), updateOk)
proc hasPathAccount*(
db: AristoDbRef;
accPath: Hash256;
): Result[bool,AristoError] =
## For an account record indexed by `accPath` query whether this record exists
## on the database.
##
db.hasAccountPayload(accPath)
proc fetchGenericData*(
db: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[Blob,AristoError] =
## For a generic sub-tree starting at `root`, fetch the data record
## indexed by `path`.
##
? root.mustBeGeneric()
let pyl = ? db.retrieveLeaf(root, path)
assert pyl.lData.pType == RawData # debugging only
ok pyl.lData.rawBlob
proc fetchGenericState*(
db: AristoDbRef;
root: VertexID;
updateOk: bool;
): Result[Hash256,AristoError] =
## Fetch the Merkle hash of the argument `root`.
db.retrieveMerkleHash(root, updateOk)
proc hasPathGeneric*(
db: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[bool,AristoError] =
## For a generic sub-tree starting at `root` and indexed by `path`, query
## whether this record exists on the database.
##
? root.mustBeGeneric()
db.hasPayload(root, path)
proc fetchStorageData*(
db: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[UInt256,AristoError] =
## For a storage tree related to account `accPath`, fetch the data record
## from the database indexed by `path`.
##
let leafVtx = ? db.retrieveLeaf(? db.fetchStorageID accPath, stoPath.data)
assert leafVtx.lData.pType == StoData # debugging only
ok leafVtx.lData.stoData
proc fetchStorageState*(
db: AristoDbRef;
accPath: Hash256;
updateOk: bool;
): Result[Hash256,AristoError] =
## Fetch the Merkle hash of the storage root related to `accPath`.
let stoID = db.fetchStorageID(accPath).valueOr:
if error == FetchPathNotFound:
return ok(EMPTY_ROOT_HASH) # no sub-tree
return err(error)
db.retrieveMerkleHash(stoID, updateOk)
proc hasPathStorage*(
db: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[bool,AristoError] =
## For a storage tree related to account `accPath`, query whether the data
## record indexed by `path` exists on the database.
##
db.hasStoragePayload(accPath, stoPath)
proc hasStorageData*(
db: AristoDbRef;
accPath: Hash256;
): Result[bool,AristoError] =
## For a storage tree related to account `accPath`, query whether there
## is a non-empty data storage area at all.
##
let stoID = db.fetchStorageID(accPath).valueOr:
if error == FetchPathNotFound:
return ok(false) # no sub-tree
return err(error)
ok stoID.isValid
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------