303 lines
9.1 KiB
Nim
303 lines
9.1 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 -- Patricia Trie delete funcionality
|
|
## ==============================================
|
|
##
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/typetraits,
|
|
eth/common,
|
|
results,
|
|
./aristo_delete/delete_subtree,
|
|
"."/[aristo_desc, aristo_fetch, aristo_get, aristo_hike, aristo_layers]
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private heplers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc branchStillNeeded(vtx: VertexRef, removed: int8): Result[int8,void] =
|
|
## Returns the nibble if there is only one reference left.
|
|
var nibble = -1'i8
|
|
for n in 0'i8 .. 15'i8:
|
|
if n == removed:
|
|
continue
|
|
|
|
if vtx.bVid(uint8 n).isValid:
|
|
if 0 <= nibble:
|
|
return ok(-1)
|
|
nibble = n
|
|
if 0 <= nibble:
|
|
return ok(nibble)
|
|
# Oops, degenerated branch node
|
|
err()
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc deleteImpl(
|
|
db: AristoDbRef; # Database, top layer
|
|
hike: Hike; # Fully expanded path
|
|
): Result[VertexRef,AristoError] =
|
|
## Removes the last node in the hike and returns the updated leaf in case
|
|
## a branch collapsed
|
|
|
|
# Remove leaf entry
|
|
let lf = hike.legs[^1].wp
|
|
if lf.vtx.vType != Leaf:
|
|
return err(DelLeafExpexted)
|
|
|
|
db.layersResVtx((hike.root, lf.vid))
|
|
|
|
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(nil)
|
|
|
|
if hike.legs[^2].wp.vtx.vType != Branch:
|
|
return err(DelBranchExpexted)
|
|
|
|
# Get current `Branch` vertex `br`
|
|
let
|
|
br = hike.legs[^2].wp
|
|
nbl = br.vtx.branchStillNeeded(hike.legs[^2].nibble).valueOr:
|
|
return err(DelBranchWithoutRefs)
|
|
|
|
# Clear all Merkle hash keys up to the root key
|
|
for n in 0 .. hike.legs.len - 2:
|
|
let wp = hike.legs[n].wp
|
|
db.layersResKey((hike.root, wp.vid), wp.vtx)
|
|
|
|
if 0 <= nbl:
|
|
# Branch has only one entry - move that entry to where the branch was and
|
|
# update its path
|
|
|
|
# Get child vertex (there must be one after a `Branch` node)
|
|
let
|
|
vid = br.vtx.bVid(uint8 nbl)
|
|
nxt = db.getVtx (hike.root, vid)
|
|
if not nxt.isValid:
|
|
return err(DelVidStaleVtx)
|
|
|
|
db.layersResVtx((hike.root, vid))
|
|
|
|
let vtx =
|
|
case nxt.vType
|
|
of Leaf:
|
|
VertexRef(
|
|
vType: Leaf,
|
|
pfx: br.vtx.pfx & NibblesBuf.nibble(nbl.byte) & nxt.pfx,
|
|
lData: nxt.lData)
|
|
|
|
of Branch:
|
|
VertexRef(
|
|
vType: Branch,
|
|
pfx: br.vtx.pfx & NibblesBuf.nibble(nbl.byte) & nxt.pfx,
|
|
startVid: nxt.startVid,
|
|
used: nxt.used)
|
|
|
|
# Put the new vertex at the id of the obsolete branch
|
|
db.layersPutVtx((hike.root, br.vid), vtx)
|
|
|
|
if vtx.vType == Leaf:
|
|
ok(vtx)
|
|
else:
|
|
ok(nil)
|
|
else:
|
|
# Clear the removed leaf from the branch (that still contains other children)
|
|
let brDup = br.vtx.dup
|
|
discard brDup.setUsed(uint8 hike.legs[^2].nibble, false)
|
|
db.layersPutVtx((hike.root, br.vid), brDup)
|
|
|
|
ok(nil)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc deleteAccountRecord*(
|
|
db: AristoDbRef;
|
|
accPath: Hash32;
|
|
): Result[void,AristoError] =
|
|
## Delete the account leaf entry addressed by the argument `path`. If this
|
|
## leaf entry referres to a storage tree, this one will be deleted as well.
|
|
##
|
|
var accHike: Hike
|
|
db.fetchAccountHike(accPath, accHike).isOkOr:
|
|
if error == FetchAccInaccessible:
|
|
return err(DelPathNotFound)
|
|
return err(error)
|
|
let
|
|
stoID = accHike.legs[^1].wp.vtx.lData.stoID
|
|
|
|
# Delete storage tree if present
|
|
if stoID.isValid:
|
|
? db.delStoTreeImpl((stoID.vid, stoID.vid), accPath)
|
|
|
|
let otherLeaf = ?db.deleteImpl(accHike)
|
|
|
|
db.layersPutAccLeaf(accPath, nil)
|
|
|
|
if otherLeaf.isValid:
|
|
db.layersPutAccLeaf(
|
|
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
|
|
stoPath: Hash32;
|
|
): Result[bool,AristoError] =
|
|
## For a given account argument `accPath`, this function deletes the
|
|
## argument `stoPath` from the associated storage tree (if any, at all.) If
|
|
## the if the argument `stoPath` deleted was the last one on the storage tree,
|
|
## account leaf referred to by `accPath` will be updated so that it will
|
|
## not refer to a storage tree anymore. In the latter case only the function
|
|
## will return `true`.
|
|
##
|
|
|
|
let
|
|
mixPath = mixUp(accPath, stoPath)
|
|
stoLeaf = db.cachedStoLeaf(mixPath)
|
|
|
|
if stoLeaf == Opt.some(nil):
|
|
return err(DelPathNotFound)
|
|
|
|
var accHike: Hike
|
|
db.fetchAccountHike(accPath, accHike).isOkOr:
|
|
if error == FetchAccInaccessible:
|
|
return err(DelStoAccMissing)
|
|
return err(error)
|
|
|
|
let
|
|
wpAcc = accHike.legs[^1].wp
|
|
stoID = wpAcc.vtx.lData.stoID
|
|
|
|
if not stoID.isValid:
|
|
return err(DelStoRootMissing)
|
|
|
|
let stoNibbles = NibblesBuf.fromBytes(stoPath.data)
|
|
var stoHike: Hike
|
|
stoNibbles.hikeUp(stoID.vid, db, stoLeaf, stoHike).isOkOr:
|
|
if error[1] in HikeAcceptableStopsNotFound:
|
|
return err(DelPathNotFound)
|
|
return err(error[1])
|
|
|
|
# Mark account path Merkle keys for update
|
|
db.layersResKeys accHike
|
|
|
|
let otherLeaf = ?db.deleteImpl(stoHike)
|
|
db.layersPutStoLeaf(mixPath, nil)
|
|
|
|
if otherLeaf.isValid:
|
|
let leafMixPath = mixUp(
|
|
accPath,
|
|
Hash32(getBytes(stoNibbles.replaceSuffix(otherLeaf.pfx))))
|
|
db.layersPutStoLeaf(leafMixPath, otherLeaf)
|
|
|
|
# If there was only one item (that got deleted), update the account as well
|
|
if stoHike.legs.len > 1:
|
|
return ok(false)
|
|
|
|
# De-register the deleted storage tree from the account record
|
|
let leaf = wpAcc.vtx.dup # Dup on modify
|
|
leaf.lData.stoID.isValid = false
|
|
db.layersPutAccLeaf(accPath, leaf)
|
|
db.layersPutVtx((accHike.root, wpAcc.vid), leaf)
|
|
ok(true)
|
|
|
|
proc deleteStorageTree*(
|
|
db: AristoDbRef; # Database, top layer
|
|
accPath: Hash32; # Implies storage data tree
|
|
): Result[void,AristoError] =
|
|
## Variant of `deleteStorageData()` for purging the whole storage tree
|
|
## associated to the account argument `accPath`.
|
|
##
|
|
var accHike: Hike
|
|
db.fetchAccountHike(accPath, accHike).isOkOr:
|
|
if error == FetchAccInaccessible:
|
|
return err(DelStoAccMissing)
|
|
return err(error)
|
|
|
|
let
|
|
wpAcc = accHike.legs[^1].wp
|
|
stoID = wpAcc.vtx.lData.stoID
|
|
|
|
if not stoID.isValid:
|
|
return err(DelStoRootMissing)
|
|
|
|
# Mark account path Merkle keys for update
|
|
db.layersResKeys accHike
|
|
|
|
? db.delStoTreeImpl((stoID.vid, stoID.vid), accPath)
|
|
|
|
# De-register the deleted storage tree from the accounts record
|
|
let leaf = wpAcc.vtx.dup # Dup on modify
|
|
leaf.lData.stoID.isValid = false
|
|
db.layersPutAccLeaf(accPath, leaf)
|
|
db.layersPutVtx((accHike.root, wpAcc.vid), leaf)
|
|
ok()
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|