Skip to content

Commit

Permalink
Smaller leaf types
Browse files Browse the repository at this point in the history
  • Loading branch information
arnetheduck committed Dec 6, 2024
1 parent 14723c8 commit 8f1d0af
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 151 deletions.
80 changes: 17 additions & 63 deletions nimbus/db/aristo/aristo_delete.nim
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ proc branchStillNeeded(vtx: VertexRef, removed: int8): Result[int8,void] =
proc deleteImpl(
db: AristoDbRef; # Database, top layer
hike: Hike; # Fully expanded path
): Result[VertexRef,AristoError] =
T: type
): Result[Opt[T],AristoError] =
## Removes the last node in the hike and returns the updated leaf in case
## a branch collapsed

Expand All @@ -62,7 +63,7 @@ proc deleteImpl(
if hike.legs.len == 1:
# This was the last node in the trie, meaning we don't have any branches or
# leaves to update
return ok(default(VertexRef))
return ok(default(Opt[T]))

if hike.legs[^2].wp.vtx.vType != Branch:
return err(DelBranchExpexted)
Expand Down Expand Up @@ -111,16 +112,16 @@ proc deleteImpl(
db.layersPutVtx((hike.root, br.vid), vtx)

if vtx.vType == Leaf:
ok(vtx)
ok(Opt.some(vtx.to(T)))
else:
ok(default(VertexRef))
ok(Opt.none(T))
else:
# Clear the removed leaf from the branch (that still contains other children)
var brDup = br.vtx.dup
discard brDup.setUsed(uint8 hike.legs[^2].nibble, false)
db.layersPutVtx((hike.root, br.vid), brDup)

ok(default(VertexRef))
ok(Opt.none(T))

# ------------------------------------------------------------------------------
# Public functions
Expand All @@ -145,64 +146,17 @@ proc deleteAccountRecord*(
if stoID.isValid:
? db.delStoTreeImpl((stoID.vid, stoID.vid), accPath)

let otherLeaf = ?db.deleteImpl(accHike)
let otherLeaf = ?db.deleteImpl(accHike, AccountLeaf)

db.layersPutAccLeaf(accPath, default(VertexRef))
db.layersPutAccLeaf(accPath, default(Opt[AccountLeaf]))

if otherLeaf.isValid:
if otherLeaf.isSome:
db.layersPutAccLeaf(
Hash32(getBytes(NibblesBuf.fromBytes(accPath.data).replaceSuffix(otherLeaf.pfx))),
Hash32(getBytes(NibblesBuf.fromBytes(accPath.data).replaceSuffix(otherLeaf[].pfx))),
otherLeaf)

ok()

proc deleteGenericData*(
db: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[bool,AristoError] =
## Delete the leaf data entry addressed by the argument `path`. The MPT
## sub-tree the leaf data entry is subsumed under is passed as argument
## `root` which must be greater than `VertexID(1)` and smaller than
## `LEAST_FREE_VID`.
##
## The return value is `true` if the argument `path` deleted was the last
## one and the tree does not exist anymore.
##
# Verify that `root` is neither an accounts tree nor a strorage tree.
if not root.isValid:
return err(DelRootVidMissing)
elif root == VertexID(1):
return err(DelAccRootNotAccepted)
elif LEAST_FREE_VID <= root.distinctBase:
return err(DelStoRootNotAccepted)

var hike: Hike
path.hikeUp(root, db, Opt.none(VertexRef), hike).isOkOr:
if error[1] in HikeAcceptableStopsNotFound:
return err(DelPathNotFound)
return err(error[1])

discard ?db.deleteImpl(hike)

ok(not db.getVtx((root, root)).isValid)

proc deleteGenericTree*(
db: AristoDbRef; # Database, top layer
root: VertexID; # Root vertex
): Result[void,AristoError] =
## Variant of `deleteGenericData()` for purging the whole MPT sub-tree.
##
# Verify that `root` is neither an accounts tree nor a strorage tree.
if not root.isValid:
return err(DelRootVidMissing)
elif root == VertexID(1):
return err(DelAccRootNotAccepted)
elif LEAST_FREE_VID <= root.distinctBase:
return err(DelStoRootNotAccepted)

db.delSubTreeImpl root

proc deleteStorageData*(
db: AristoDbRef;
accPath: Hash32; # Implies storage data tree
Expand All @@ -220,7 +174,7 @@ proc deleteStorageData*(
mixPath = mixUp(accPath, stoPath)
stoLeaf = db.cachedStoLeaf(mixPath)

if stoLeaf == Opt.some(default(VertexRef)):
if stoLeaf == Opt.some(default(Opt[StoLeaf])):
return err(DelPathNotFound)

var accHike: Hike
Expand All @@ -246,13 +200,13 @@ proc deleteStorageData*(
# Mark account path Merkle keys for update
db.layersResKeys accHike

let otherLeaf = ?db.deleteImpl(stoHike)
db.layersPutStoLeaf(mixPath, default(VertexRef))
let otherLeaf = ?db.deleteImpl(stoHike, StoLeaf)
db.layersPutStoLeaf(mixPath, default(Opt[StoLeaf]))

if otherLeaf.isValid:
if otherLeaf.isSome:
let leafMixPath = mixUp(
accPath,
Hash32(getBytes(stoNibbles.replaceSuffix(otherLeaf.pfx))))
Hash32(getBytes(stoNibbles.replaceSuffix(otherLeaf[].pfx))))
db.layersPutStoLeaf(leafMixPath, otherLeaf)

# If there was only one item (that got deleted), update the account as well
Expand All @@ -262,7 +216,7 @@ proc deleteStorageData*(
# De-register the deleted storage tree from the account record
var leaf = wpAcc.vtx.dup # Dup on modify
leaf.lData.stoID.isValid = false
db.layersPutAccLeaf(accPath, leaf)
db.layersPutAccLeaf(accPath, Opt.some(leaf.to(AccountLeaf)))
db.layersPutVtx((accHike.root, wpAcc.vid), leaf)
ok(true)

Expand Down Expand Up @@ -294,7 +248,7 @@ proc deleteStorageTree*(
# De-register the deleted storage tree from the accounts record
var leaf = wpAcc.vtx.dup # Dup on modify
leaf.lData.stoID.isValid = false
db.layersPutAccLeaf(accPath, leaf)
db.layersPutAccLeaf(accPath, Opt.some(leaf.to(AccountLeaf)))
db.layersPutVtx((accHike.root, wpAcc.vid), leaf)
ok()

Expand Down
2 changes: 1 addition & 1 deletion nimbus/db/aristo/aristo_delete/delete_subtree.nim
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ proc delStoTreeNow(

of Leaf:
let stoPath = Hash32((stoPath & vtx.pfx).getBytes())
db.layersPutStoLeaf(mixUp(accPath, stoPath), default(VertexRef))
db.layersPutStoLeaf(mixUp(accPath, stoPath), default(Opt[StoLeaf]))

db.layersResVtx(rvid)

Expand Down
4 changes: 2 additions & 2 deletions nimbus/db/aristo/aristo_desc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ type
txUidGen*: uint ## Tx-relative unique number generator
dudes: DudesRef ## Related DB descriptors

accLeaves*: LruCache[Hash32, VertexRef]
accLeaves*: LruCache[Hash32, Opt[AccountLeaf]]
## Account path to payload cache - accounts are frequently accessed by
## account path when contracts interact with them - this cache ensures
## that we don't have to re-traverse the storage trie for every such
## interaction
## TODO a better solution would probably be to cache this in a type
## exposed to the high-level API

stoLeaves*: LruCache[Hash32, VertexRef]
stoLeaves*: LruCache[Hash32, Opt[StoLeaf]]
## Mixed account/storage path to payload cache - same as above but caches
## the full lookup of storage slots

Expand Down
25 changes: 23 additions & 2 deletions nimbus/db/aristo/aristo_desc/desc_structural.nim
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ type
startVid*: VertexID
used*: uint16

AccountLeaf* = object
pfx*: NibblesBuf
account*: AristoAccount
stoID*: StorageID

StoLeaf* = object
pfx*: NibblesBuf
stoData*: UInt256

NodeRef* = ref object of RootRef
## Combined record for a *traditional* ``Merkle Patricia Tree` node merged
## with a structural `VertexRef` type object.
Expand Down Expand Up @@ -125,8 +134,8 @@ type
kMap*: Table[RootedVertexID,HashKey] ## Merkle hash key mapping
vTop*: VertexID ## Last used vertex ID

accLeaves*: Table[Hash32, VertexRef] ## Account path -> VertexRef
stoLeaves*: Table[Hash32, VertexRef] ## Storage path -> VertexRef
accLeaves*: Table[Hash32, Opt[AccountLeaf]] ## Account path -> VertexRef
stoLeaves*: Table[Hash32, Opt[StoLeaf]] ## Storage path -> VertexRef

txUid*: uint ## Transaction identifier if positive

Expand Down Expand Up @@ -196,6 +205,18 @@ proc `==`*(a, b: VertexRef): bool =
return false
true

func to*(v: VertexRef, _: type AccountLeaf): AccountLeaf =
AccountLeaf(pfx: v.pfx, account: v.lData.account, stoID: v.lData.stoID)

func to*(v: VertexRef, _: type StoLeaf): StoLeaf =
StoLeaf(pfx: v.pfx, stoData: v.lData.stoData)

func to*(v: AccountLeaf, _: type VertexRef): VertexRef =
VertexRef(pfx: v.pfx, vType: Leaf, lData: LeafPayload(pType: AccountData, account: v.account, stoID: v.stoID))

func to*(v: StoLeaf, _: type VertexRef): VertexRef =
VertexRef(pfx: v.pfx, vType: Leaf, lData: LeafPayload(pType: StoData, stoData: v.stoData))

iterator pairs*(vtx: VertexRef): tuple[nibble: uint8, vid: VertexID] =
## Iterates over the sub-vids of a branch (does nothing for leaves)
case vtx.vType:
Expand Down
48 changes: 24 additions & 24 deletions nimbus/db/aristo/aristo_fetch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,50 +27,51 @@ proc retrieveLeaf(
db: AristoDbRef;
root: VertexID;
path: Hash32;
): Result[VertexRef,AristoError] =
T: type
): Result[T,AristoError] =
for step in stepUp(NibblesBuf.fromBytes(path.data), root, db):
let vtx = step.valueOr:
if error in HikeAcceptableStopsNotFound:
return err(FetchPathNotFound)
return err(error)

if vtx.vType == Leaf:
return ok vtx
return ok vtx.to(T)

return err(FetchPathNotFound)

proc cachedAccLeaf*(db: AristoDbRef; accPath: Hash32): Opt[VertexRef] =
proc cachedAccLeaf*(db: AristoDbRef; accPath: Hash32): Opt[Opt[AccountLeaf]] =
# Return vertex from layers or cache, `nil` if it's known to not exist and
# none otherwise
db.layersGetAccLeaf(accPath) or
db.accLeaves.get(accPath) or
Opt.none(VertexRef)
Opt.none(Opt[AccountLeaf])

proc cachedStoLeaf*(db: AristoDbRef; mixPath: Hash32): Opt[VertexRef] =
proc cachedStoLeaf*(db: AristoDbRef; mixPath: Hash32): Opt[Opt[StoLeaf]] =
# Return vertex from layers or cache, `nil` if it's known to not exist and
# none otherwise
db.layersGetStoLeaf(mixPath) or
db.stoLeaves.get(mixPath) or
Opt.none(VertexRef)
Opt.none(Opt[StoLeaf])

proc retrieveAccountLeaf(
db: AristoDbRef;
accPath: Hash32;
): Result[VertexRef,AristoError] =
): Result[AccountLeaf,AristoError] =
if (let leafVtx = db.cachedAccLeaf(accPath); leafVtx.isSome()):
if not leafVtx[].isValid():
if not leafVtx[].isSome():
return err(FetchPathNotFound)
return ok leafVtx[]
return ok leafVtx[][]

# 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).valueOr:
leafVtx = db.retrieveLeaf(VertexID(1), accPath, AccountLeaf).valueOr:
if error == FetchPathNotFound:
db.accLeaves.put(accPath, default(VertexRef))
db.accLeaves.put(accPath, default(Opt[AccountLeaf]))
return err(error)

db.accLeaves.put(accPath, leafVtx)
db.accLeaves.put(accPath, Opt.some(leafVtx))

ok leafVtx

Expand Down Expand Up @@ -105,7 +106,7 @@ proc fetchStorageIdImpl(
## Helper function for retrieving a storage (vertex) ID for a given account.
let
leafVtx = ?db.retrieveAccountLeaf(accPath)
stoID = leafVtx.lData.stoID
stoID = leafVtx.stoID

if stoID.isValid:
ok stoID.vid
Expand All @@ -126,11 +127,11 @@ proc fetchAccountHike*(
## Expand account path to account leaf or return failure

# Prefer the leaf cache so as not to burden the lower layers
let leaf = db.cachedAccLeaf(accPath)
if leaf == Opt.some(default(VertexRef)):
let accLeaf = db.cachedAccLeaf(accPath)
if accLeaf == Opt.some(Opt.none(AccountLeaf)):
return err(FetchAccInaccessible)

accPath.hikeUp(VertexID(1), db, leaf, accHike).isOkOr:
accPath.hikeUp(VertexID(1), db, accLeaf, accHike).isOkOr:
return err(FetchAccInaccessible)

# Extract the account payload from the leaf
Expand Down Expand Up @@ -159,20 +160,20 @@ proc retrieveStoragePayload(
let mixPath = mixUp(accPath, stoPath)

if (let leafVtx = db.cachedStoLeaf(mixPath); leafVtx.isSome()):
if not leafVtx[].isValid():
if not leafVtx[].isSome():
return err(FetchPathNotFound)
return ok leafVtx[].lData.stoData
return ok leafVtx[][].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.fetchStorageIdImpl(accPath), stoPath).valueOr:
let leafVtx = db.retrieveLeaf(? db.fetchStorageIdImpl(accPath), stoPath, StoLeaf).valueOr:
if error == FetchPathNotFound:
db.stoLeaves.put(mixPath, default(VertexRef))
db.stoLeaves.put(mixPath, default(Opt[StoLeaf]))
return err(error)

db.stoLeaves.put(mixPath, leafVtx)
db.stoLeaves.put(mixPath, Opt.some(leafVtx))

ok leafVtx.lData.stoData
ok leafVtx.stoData

proc hasStoragePayload(
db: AristoDbRef;
Expand Down Expand Up @@ -205,9 +206,8 @@ proc fetchAccountRecord*(
## Fetch an account record from the database indexed by `accPath`.
##
let leafVtx = ? db.retrieveAccountLeaf(accPath)
assert leafVtx.lData.pType == AccountData # debugging only

ok leafVtx.lData.account
ok leafVtx.account

proc fetchStateRoot*(
db: AristoDbRef;
Expand Down
Loading

0 comments on commit 8f1d0af

Please sign in to comment.