nimbus-eth1/nimbus/db/aristo/aristo_hike.nim
Jacek Sieka 81e75622cf
storage: store root id together with vid, for better locality of refe… (#2449)
The state and account MPT:s currenty share key space in the database
based on that vertex id:s are assigned essentially randomly, which means
that when two adjacent slot values from the same contract are accessed,
they might reside at large distance from each other.

Here, we prefix each vertex id by its root causing them to be sorted
together thus bringing all data belonging to a particular contract
closer together - the same effect also happens for the main state MPT
whose nodes now end up clustered together more tightly.

In the future, the prefix given to the storage keys can also be used to
perform range operations such as reading all the storage at once and/or
deleting an account with a batch operation.

Notably, parts of the API already supported this rooting concept while
parts didn't - this PR makes the API consistent by always working with a
root+vid.
2024-07-04 15:46:52 +02:00

218 lines
6.4 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.
{.push raises: [].}
import
eth/common,
results,
"."/[aristo_desc, aristo_get]
type
Leg* = object
## For constructing a `VertexPath`
wp*: VidVtxPair ## Vertex ID and data ref
nibble*: int8 ## Next vertex selector for `Branch` (if any)
Hike* = object
## Trie traversal path
root*: VertexID ## Handy for some fringe cases
legs*: seq[Leg] ## Chain of vertices and IDs
tail*: NibblesBuf ## Portion of non completed path
const
HikeAcceptableStopsNotFound* = {
HikeBranchTailEmpty,
HikeBranchMissingEdge,
HikeExtTailEmpty,
HikeExtTailMismatch,
HikeLeafUnexpected,
HikeNoLegs}
## When trying to find a leaf vertex the Patricia tree, there are several
## conditions where the search stops which do not constitute a problem
## with the trie (aka sysetm error.)
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
func getNibblesImpl(hike: Hike; start = 0; maxLen = high(int)): NibblesBuf =
## May be needed for partial rebuild, as well
for n in start ..< min(hike.legs.len, maxLen):
let leg = hike.legs[n]
case leg.wp.vtx.vType:
of Branch:
result = result & NibblesBuf.nibble(leg.nibble.byte)
of Extension:
result = result & leg.wp.vtx.ePfx
of Leaf:
result = result & leg.wp.vtx.lPfx
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
func to*(rc: Result[Hike,(VertexID,AristoError,Hike)]; T: type Hike): T =
## Extract `Hike` from either ok ot error part of argument `rc`.
if rc.isOk: rc.value else: rc.error[2]
func to*(hike: Hike; T: type NibblesBuf): T =
## Convert back
hike.getNibblesImpl() & hike.tail
func legsTo*(hike: Hike; T: type NibblesBuf): T =
## Convert back
hike.getNibblesImpl()
func legsTo*(hike: Hike; numLegs: int; T: type NibblesBuf): T =
## variant of `legsTo()`
hike.getNibblesImpl(0, numLegs)
# --------
proc step*(
path: NibblesBuf, rvid: RootedVertexID, db: AristoDbRef
): Result[(VertexRef, NibblesBuf, VertexID), AristoError] =
# Fetch next vertex
let vtx = db.getVtxRc(rvid).valueOr:
if error != GetVtxNotFound:
return err(error)
# The vertex ID `vid` was a follow up from a parent vertex, but there is
# no child vertex on the database. So `vid` is a dangling link which is
# allowed only if there is a partial trie (e.g. with `snap` sync.)
return err(HikeDanglingEdge)
case vtx.vType:
of Leaf:
# This must be the last vertex, so there cannot be any `tail` left.
if path.len != path.sharedPrefixLen(vtx.lPfx):
return err(HikeLeafUnexpected)
ok (vtx, NibblesBuf(), VertexID(0))
of Branch:
# There must be some more data (aka `tail`) after a `Branch` vertex.
if path.len == 0:
return err(HikeBranchTailEmpty)
let
nibble = path[0].int8
nextVid = vtx.bVid[nibble]
if not nextVid.isValid:
return err(HikeBranchMissingEdge)
ok (vtx, path.slice(1), nextVid)
of Extension:
# There must be some more data (aka `tail`) after an `Extension` vertex.
if path.len == 0:
return err(HikeBranchTailEmpty)
if vtx.ePfx.len != path.sharedPrefixLen(vtx.ePfx):
return err(HikeExtTailMismatch) # Need to branch from here
let nextVid = vtx.eVid
if not nextVid.isValid:
return err(HikeExtMissingEdge)
ok (vtx, path.slice(vtx.ePfx.len), nextVid)
iterator stepUp*(
path: NibblesBuf; # Partial path
root: VertexID; # Start vertex
db: AristoDbRef; # Database
): Result[VertexRef, AristoError] =
## For the argument `path`, iterate over the logest possible path in the
## argument database `db`.
var
path = path
next = root
vtx: VertexRef
block iter:
while true:
(vtx, path, next) = step(path, (root, next), db).valueOr:
yield Result[VertexRef, AristoError].err(error)
break iter
yield Result[VertexRef, AristoError].ok(vtx)
if path.len == 0:
break
proc hikeUp*(
path: NibblesBuf; # Partial path
root: VertexID; # Start vertex
db: AristoDbRef; # Database
): Result[Hike,(VertexID,AristoError,Hike)] =
## For the argument `path`, find and return the logest possible path in the
## argument database `db`.
var hike = Hike(
root: root,
tail: path)
if not root.isValid:
return err((VertexID(0),HikeRootMissing,hike))
if path.len == 0:
return err((VertexID(0),HikeEmptyPath,hike))
var vid = root
while true:
let (vtx, path, next) = step(hike.tail, (root, vid), db).valueOr:
return err((vid,error,hike))
let wp = VidVtxPair(vid:vid, vtx:vtx)
case vtx.vType
of Leaf:
hike.legs.add Leg(wp: wp, nibble: -1)
hike.tail = path
break
of Extension:
hike.legs.add Leg(wp: wp, nibble: -1)
of Branch:
hike.legs.add Leg(wp: wp, nibble: int8 hike.tail[0])
hike.tail = path
vid = next
ok hike
proc hikeUp*(
lty: LeafTie;
db: AristoDbRef;
): Result[Hike,(VertexID,AristoError,Hike)] =
## Variant of `hike()`
lty.path.to(NibblesBuf).hikeUp(lty.root, db)
proc hikeUp*(
path: openArray[byte];
root: VertexID;
db: AristoDbRef;
): Result[Hike,(VertexID,AristoError,Hike)] =
## Variant of `hike()`
NibblesBuf.fromBytes(path).hikeUp(root, db)
proc hikeUp*(
path: Hash256;
root: VertexID;
db: AristoDbRef;
): Result[Hike,(VertexID,AristoError,Hike)] =
## Variant of `hike()`
NibblesBuf.fromBytes(path.data).hikeUp(root, db)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------