mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-09 20:06:52 +00:00
9d91191154
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.
325 lines
9.6 KiB
Nim
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
|
|
# ------------------------------------------------------------------------------
|