Aristo uses pre classified tree types cont1 (#2389)

* Provide dedicated functions for deleteing accounts and storage trees

why:
  Storage trees are always linked to an account, so there is no need
  for an application to fiddle about (e.g. re-cycling, unlinking)
  storage tree vertex IDs.

* Remove `delete()` and other cruft from API, `aristo_delete`, etc.

* clean up delete functions

details:
  The delete implementations `deleteImpl()` and `delTreeImpl()` do not
  need to be super generic anymore as all the edge cases are covered by
  the specialised `deleteAccountPayload()`, `deleteGenericData()`, etc.

* Avoid unnecessary re-calculations of account keys

why:
  The function `registerAccountForUpdate()` did extract the storage ID
  (if any) and automatically marked the Merkle keys along the account
  path for re-hashing.

  This would also apply if there was later detected that the account
  or the storage tree did not need to be updated.

  So the `registerAccountForUpdate()` function was split into a part
  which retrieved the storage ID, and another one which marked the
  Merkle keys for re-calculation to be applied only when needed.
This commit is contained in:
Jordan Hrycaj 2024-06-18 19:30:01 +00:00 committed by GitHub
parent 51f02090b8
commit 8727307ef4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 358 additions and 365 deletions

View File

@ -49,41 +49,56 @@ type
## previous layer. The previous transaction is returned if there ## previous layer. The previous transaction is returned if there
## was any. ## was any.
AristoApiDeleteFn* = AristoApiDeleteAccountPayloadFn* =
proc(db: AristoDbRef;
path: openArray[byte];
): Result[void,AristoError]
{.noRaise.}
## 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.
AristoApiDeleteGenericDataFn* =
proc(db: AristoDbRef; proc(db: AristoDbRef;
root: VertexID; root: VertexID;
path: openArray[byte]; path: openArray[byte];
accPath: PathID; ): Result[bool,AristoError]
): Result[bool,(VertexID,AristoError)]
{.noRaise.} {.noRaise.}
## Delete a leaf with path `path` starting at root `root`. ## 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`.
## ##
## For a `root` with `VertexID` greater than `LEAST_FREE_VID`, the ## The return value is `true` if the argument `path` deleted was the last
## sub-tree generated by `payload.root` is considered a storage trie ## one and the tree does not exist anymore.
## linked to an account leaf referred to by a valid `accPath` (i.e.
## different from `VOID_PATH_ID`.) In that case, an account must
## exists. If there is payload of type `AccountData`, its `storageID`
## field must be unset or equal to the `root` vertex ID.
##
## The return code is `true` iff the trie has become empty.
AristoApiDelTreeFn* = AristoApiDeleteGenericTreeFn* =
proc(db: AristoDbRef; proc(db: AristoDbRef;
root: VertexID; root: VertexID;
accPath: PathID; ): Result[void,AristoError]
): Result[void,(VertexID,AristoError)]
{.noRaise.} {.noRaise.}
## Delete sub-trie below `root`. The maximum supported sub-tree size ## Variant of `deleteGenericData()` for purging the whole MPT sub-tree.
## is `SUB_TREE_DISPOSAL_MAX`. Larger tries must be disposed by
## walk-deleting leaf nodes using `left()` or `right()` traversal AristoApiDeleteStorageDataFn* =
## functions. proc(db: AristoDbRef;
## path: openArray[byte];
## For a `root` argument greater than `LEAST_FREE_VID`, the sub-tree accPath: PathID;
## spanned by `root` is considered a storage trie linked to an account ): Result[bool,AristoError]
## leaf referred to by a valid `accPath` (i.e. different from {.noRaise.}
## `VOID_PATH_ID`.) In that case, an account must exists. If there is ## For a given account argument `accPath`, this function deletes the
## payload of type `AccountData`, its `storageID` field must be unset ## argument `path` from the associated storage tree (if any, at all.) If
## or equal to the `hike.root` vertex ID. ## the if the argument `path` 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`.
AristoApiDeleteStorageTreeFn* =
proc(db: AristoDbRef;
accPath: PathID;
): Result[void,AristoError]
{.noRaise.}
## Variant of `deleteStorageData()` for purging the whole storage tree
## associated to the account argument `accPath`.
AristoApiFetchLastSavedStateFn* = AristoApiFetchLastSavedStateFn* =
proc(db: AristoDbRef proc(db: AristoDbRef
@ -363,8 +378,11 @@ type
AristoApiObj* = object of RootObj AristoApiObj* = object of RootObj
## Useful set of `Aristo` fuctions that can be filtered, stacked etc. ## Useful set of `Aristo` fuctions that can be filtered, stacked etc.
commit*: AristoApiCommitFn commit*: AristoApiCommitFn
delete*: AristoApiDeleteFn deleteAccountPayload*: AristoApiDeleteAccountPayloadFn
delTree*: AristoApiDelTreeFn deleteGenericData*: AristoApiDeleteGenericDataFn
deleteGenericTree*: AristoApiDeleteGenericTreeFn
deleteStorageData*: AristoApiDeleteStorageDataFn
deleteStorageTree*: AristoApiDeleteStorageTreeFn
fetchLastSavedState*: AristoApiFetchLastSavedStateFn fetchLastSavedState*: AristoApiFetchLastSavedStateFn
fetchPayload*: AristoApiFetchPayloadFn fetchPayload*: AristoApiFetchPayloadFn
findTx*: AristoApiFindTxFn findTx*: AristoApiFindTxFn
@ -394,42 +412,45 @@ type
## Index/name mapping for profile slots ## Index/name mapping for profile slots
AristoApiProfTotal = "total" AristoApiProfTotal = "total"
AristoApiProfCommitFn = "commit" AristoApiProfCommitFn = "commit"
AristoApiProfDeleteFn = "delete" AristoApiProfDeleteAccountPayloadFn = "deleteAccountPayload"
AristoApiProfDelTreeFn = "delTree" AristoApiProfDeleteGenericDataFn = "deleteGnericData"
AristoApiProfFetchLastSavedStateFn = "fetchPayload" AristoApiProfDeleteGenericTreeFn = "deleteGnericTree"
AristoApiProfFetchPayloadFn = "fetchPayload" AristoApiProfDeleteStorageDataFn = "deleteStorageData"
AristoApiProfFindTxFn = "findTx" AristoApiProfDeleteStorageTreeFn = "deleteStorageTree"
AristoApiProfFinishFn = "finish" AristoApiProfFetchLastSavedStateFn = "fetchPayload"
AristoApiProfForgetFn = "forget" AristoApiProfFetchPayloadFn = "fetchPayload"
AristoApiProfForkTxFn = "forkTx" AristoApiProfFindTxFn = "findTx"
AristoApiProfGetKeyRcFn = "getKeyRc" AristoApiProfFinishFn = "finish"
AristoApiProfHashifyFn = "hashify" AristoApiProfForgetFn = "forget"
AristoApiProfHasPathFn = "hasPath" AristoApiProfForkTxFn = "forkTx"
AristoApiProfHikeUpFn = "hikeUp" AristoApiProfGetKeyRcFn = "getKeyRc"
AristoApiProfIsTopFn = "isTop" AristoApiProfHashifyFn = "hashify"
AristoApiProfLevelFn = "level" AristoApiProfHasPathFn = "hasPath"
AristoApiProfNForkedFn = "nForked" AristoApiProfHikeUpFn = "hikeUp"
AristoApiProfMergeAccountPayloadFn = "mergeAccountPayload" AristoApiProfIsTopFn = "isTop"
AristoApiProfMergeGenericDataFn = "mergeGenericData" AristoApiProfLevelFn = "level"
AristoApiProfMergeStorageDataFn = "mergeStorageData" AristoApiProfNForkedFn = "nForked"
AristoApiProfPathAsBlobFn = "pathAsBlob" AristoApiProfMergeAccountPayloadFn = "mergeAccountPayload"
AristoApiProfPersistFn = "persist" AristoApiProfMergeGenericDataFn = "mergeGenericData"
AristoApiProfReCentreFn = "reCentre" AristoApiProfMergeStorageDataFn = "mergeStorageData"
AristoApiProfRollbackFn = "rollback" AristoApiProfPathAsBlobFn = "pathAsBlob"
AristoApiProfSerialiseFn = "serialise" AristoApiProfPersistFn = "persist"
AristoApiProfTxBeginFn = "txBegin" AristoApiProfReCentreFn = "reCentre"
AristoApiProfTxTopFn = "txTop" AristoApiProfRollbackFn = "rollback"
AristoApiProfSerialiseFn = "serialise"
AristoApiProfTxBeginFn = "txBegin"
AristoApiProfTxTopFn = "txTop"
AristoApiProfBeGetVtxFn = "be/getVtx" AristoApiProfBeGetVtxFn = "be/getVtx"
AristoApiProfBeGetKeyFn = "be/getKey" AristoApiProfBeGetKeyFn = "be/getKey"
AristoApiProfBeGetTuvFn = "be/getTuv" AristoApiProfBeGetTuvFn = "be/getTuv"
AristoApiProfBeGetLstFn = "be/getLst" AristoApiProfBeGetLstFn = "be/getLst"
AristoApiProfBePutVtxFn = "be/putVtx" AristoApiProfBePutVtxFn = "be/putVtx"
AristoApiProfBePutKeyFn = "be/putKey" AristoApiProfBePutKeyFn = "be/putKey"
AristoApiProfBePutTuvFn = "be/putTuv" AristoApiProfBePutTuvFn = "be/putTuv"
AristoApiProfBePutLstFn = "be/putLst" AristoApiProfBePutLstFn = "be/putLst"
AristoApiProfBePutEndFn = "be/putEnd" AristoApiProfBePutEndFn = "be/putEnd"
AristoApiProfRef* = ref object of AristoApiRef AristoApiProfRef* = ref object of AristoApiRef
## Profiling API extension of `AristoApiObj` ## Profiling API extension of `AristoApiObj`
@ -443,8 +464,11 @@ type
when AutoValidateApiHooks: when AutoValidateApiHooks:
proc validate(api: AristoApiObj|AristoApiRef) = proc validate(api: AristoApiObj|AristoApiRef) =
doAssert not api.commit.isNil doAssert not api.commit.isNil
doAssert not api.delete.isNil doAssert not api.deleteAccountPayload.isNil
doAssert not api.delTree.isNil doAssert not api.deleteGenericData.isNil
doAssert not api.deleteGenericTree.isNil
doAssert not api.deleteStorageData.isNil
doAssert not api.deleteStorageTree.isNil
doAssert not api.fetchLastSavedState.isNil doAssert not api.fetchLastSavedState.isNil
doAssert not api.fetchPayload.isNil doAssert not api.fetchPayload.isNil
doAssert not api.findTx.isNil doAssert not api.findTx.isNil
@ -495,8 +519,11 @@ func init*(api: var AristoApiObj) =
when AutoValidateApiHooks: when AutoValidateApiHooks:
api.reset api.reset
api.commit = commit api.commit = commit
api.delete = delete api.deleteAccountPayload = deleteAccountPayload
api.delTree = delTree api.deleteGenericData = deleteGenericData
api.deleteGenericTree = deleteGenericTree
api.deleteStorageData = deleteStorageData
api.deleteStorageTree = deleteStorageTree
api.fetchLastSavedState = fetchLastSavedState api.fetchLastSavedState = fetchLastSavedState
api.fetchPayload = fetchPayload api.fetchPayload = fetchPayload
api.findTx = findTx api.findTx = findTx
@ -529,32 +556,35 @@ func init*(T: type AristoApiRef): T =
func dup*(api: AristoApiRef): AristoApiRef = func dup*(api: AristoApiRef): AristoApiRef =
result = AristoApiRef( result = AristoApiRef(
commit: api.commit, commit: api.commit,
delete: api.delete, deleteAccountPayload: api.deleteAccountPayload,
delTree: api.delTree, deleteGenericData: api.deleteGenericData,
fetchLastSavedState: api.fetchLastSavedState, deleteGenericTree: api.deleteGenericTree,
fetchPayload: api.fetchPayload, deleteStorageData: api.deleteStorageData,
findTx: api.findTx, deleteStorageTree: api.deleteStorageTree,
finish: api.finish, fetchLastSavedState: api.fetchLastSavedState,
forget: api.forget, fetchPayload: api.fetchPayload,
forkTx: api.forkTx, findTx: api.findTx,
getKeyRc: api.getKeyRc, finish: api.finish,
hashify: api.hashify, forget: api.forget,
hasPath: api.hasPath, forkTx: api.forkTx,
hikeUp: api.hikeUp, getKeyRc: api.getKeyRc,
isTop: api.isTop, hashify: api.hashify,
level: api.level, hasPath: api.hasPath,
nForked: api.nForked, hikeUp: api.hikeUp,
mergeAccountPayload: api.mergeAccountPayload, isTop: api.isTop,
mergeGenericData: api.mergeGenericData, level: api.level,
mergeStorageData: api.mergeStorageData, nForked: api.nForked,
pathAsBlob: api.pathAsBlob, mergeAccountPayload: api.mergeAccountPayload,
persist: api.persist, mergeGenericData: api.mergeGenericData,
reCentre: api.reCentre, mergeStorageData: api.mergeStorageData,
rollback: api.rollback, pathAsBlob: api.pathAsBlob,
serialise: api.serialise, persist: api.persist,
txBegin: api.txBegin, reCentre: api.reCentre,
txTop: api.txTop) rollback: api.rollback,
serialise: api.serialise,
txBegin: api.txBegin,
txTop: api.txTop)
when AutoValidateApiHooks: when AutoValidateApiHooks:
api.validate api.validate
@ -590,15 +620,30 @@ func init*(
AristoApiProfCommitFn.profileRunner: AristoApiProfCommitFn.profileRunner:
result = api.commit(a) result = api.commit(a)
profApi.delete = profApi.deleteAccountPayload =
proc(a: AristoDbRef; b: VertexID; c: openArray[byte]; d: PathID): auto = proc(a: AristoDbRef; b: openArray[byte]): auto =
AristoApiProfDeleteFn.profileRunner: AristoApiProfDeleteAccountPayloadFn.profileRunner:
result = api.delete(a, b, c, d) result = api.deleteAccountPayload(a, b)
profApi.delTree = profApi.deleteGenericData =
proc(a: AristoDbRef; b: VertexID; c: PathID): auto = proc(a: AristoDbRef; b: VertexID; c: openArray[byte]): auto =
AristoApiProfDelTreeFn.profileRunner: AristoApiProfDeleteGenericDataFn.profileRunner:
result = api.delTree(a, b, c) result = api.deleteGenericData(a, b, c)
profApi.deleteGenericTree =
proc(a: AristoDbRef; b: VertexID): auto =
AristoApiProfDeleteGenericTreeFn.profileRunner:
result = api.deleteGenericTree(a, b)
profApi.deleteStorageData =
proc(a: AristoDbRef; b: openArray[byte]; c: PathID): auto =
AristoApiProfDeleteStorageDataFn.profileRunner:
result = api.deleteStorageData(a, b, c)
profApi.deleteStorageTree =
proc(a: AristoDbRef; b: PathID): auto =
AristoApiProfDeleteStorageTreeFn.profileRunner:
result = api.deleteStorageTree(a, b)
profApi.fetchLastSavedState = profApi.fetchLastSavedState =
proc(a: AristoDbRef): auto = proc(a: AristoDbRef): auto =

View File

@ -253,28 +253,14 @@ proc collapseLeaf(
proc delSubTreeImpl( proc delSubTreeImpl(
db: AristoDbRef; # Database, top layer db: AristoDbRef; # Database, top layer
root: VertexID; # Root vertex root: VertexID; # Root vertex
accPath: PathID; # Needed for real storage tries ): Result[void,AristoError] =
): Result[void,(VertexID,AristoError)] =
## Implementation of *delete* sub-trie. ## Implementation of *delete* sub-trie.
let wp = block:
if root.distinctBase < LEAST_FREE_VID:
if not root.isValid:
return err((root,DelSubTreeVoidRoot))
if root == VertexID(1):
return err((root,DelSubTreeAccRoot))
VidVtxPair()
else:
let rc = db.registerAccount(root, accPath)
if rc.isErr:
return err((root,rc.error))
else:
rc.value
var var
dispose = @[root] dispose = @[root]
rootVtx = db.getVtxRc(root).valueOr: rootVtx = db.getVtxRc(root).valueOr:
if error == GetVtxNotFound: if error == GetVtxNotFound:
return ok() return ok()
return err((root,error)) return err(error)
follow = @[rootVtx] follow = @[rootVtx]
# Collect list of nodes to delete # Collect list of nodes to delete
@ -283,58 +269,31 @@ proc delSubTreeImpl(
for vtx in follow: for vtx in follow:
for vid in vtx.subVids: for vid in vtx.subVids:
# Exiting here leaves the tree as-is # Exiting here leaves the tree as-is
let vtx = ? db.getVtxRc(vid).mapErr toVae(vid) let vtx = ? db.getVtxRc(vid)
redo.add vtx redo.add vtx
dispose.add vid dispose.add vid
redo.swap follow redo.swap follow
# Mark nodes deleted # Mark collected vertices to be deleted
for vid in dispose: for vid in dispose:
db.disposeOfVtx(root, vid) db.disposeOfVtx(root, vid)
# Make sure that an account leaf has no dangling sub-trie
if wp.vid.isValid:
let leaf = wp.vtx.dup # Dup on modify
leaf.lData.account.storageID = VertexID(0)
db.layersPutVtx(VertexID(1), wp.vid, leaf)
db.layersResKey(VertexID(1), wp.vid)
ok() ok()
proc deleteImpl( proc deleteImpl(
db: AristoDbRef; # Database, top layer db: AristoDbRef; # Database, top layer
hike: Hike; # Fully expanded path hike: Hike; # Fully expanded path
lty: LeafTie; # `Patricia Trie` path root-to-leaf ): Result[void,(VertexID,AristoError)] =
accPath: PathID; # Needed for accounts payload
): Result[bool,(VertexID,AristoError)] =
## Implementation of *delete* functionality. ## Implementation of *delete* functionality.
let wp = block: # Remove leaf entry
if lty.root.distinctBase < LEAST_FREE_VID:
VidVtxPair()
else:
let rc = db.registerAccount(lty.root, accPath)
if rc.isErr:
return err((lty.root,rc.error))
else:
rc.value
# Remove leaf entry on the top
let lf = hike.legs[^1].wp let lf = hike.legs[^1].wp
if lf.vtx.vType != Leaf: if lf.vtx.vType != Leaf:
return err((lf.vid,DelLeafExpexted)) return err((lf.vid,DelLeafExpexted))
if lf.vid in db.pPrf: if lf.vid in db.pPrf:
return err((lf.vid, DelLeafLocked)) return err((lf.vid, DelLeafLocked))
# Verify that there is no dangling storage trie
block:
let data = lf.vtx.lData
if data.pType == AccountData:
let vid = data.account.storageID
if vid.isValid and db.getVtx(vid).isValid:
return err((vid,DelDanglingStoTrie))
db.disposeOfVtx(hike.root, lf.vid) db.disposeOfVtx(hike.root, lf.vid)
if 1 < hike.legs.len: if 1 < hike.legs.len:
@ -350,7 +309,7 @@ proc deleteImpl(
br.vtx.bVid[hike.legs[^2].nibble] = VertexID(0) br.vtx.bVid[hike.legs[^2].nibble] = VertexID(0)
db.layersPutVtx(hike.root, br.vid, br.vtx) db.layersPutVtx(hike.root, br.vid, br.vtx)
# Clear all keys up to the root key # Clear all Merkle hash keys up to the root key
for n in 0 .. hike.legs.len - 2: for n in 0 .. hike.legs.len - 2:
let vid = hike.legs[n].wp.vid let vid = hike.legs[n].wp.vid
if vid in db.top.final.pPrf: if vid in db.top.final.pPrf:
@ -381,97 +340,155 @@ proc deleteImpl(
of Leaf: of Leaf:
? db.collapseLeaf(hike, nibble.byte, nxt.vtx) ? db.collapseLeaf(hike, nibble.byte, nxt.vtx)
let emptySubTreeOk = not db.getVtx(hike.root).isValid ok()
# Make sure that an account leaf has no dangling sub-trie
if emptySubTreeOk and wp.vid.isValid:
let leaf = wp.vtx.dup # Dup on modify
leaf.lData.account.storageID = VertexID(0)
db.layersPutVtx(VertexID(1), wp.vid, leaf)
db.layersResKey(VertexID(1), wp.vid)
ok(emptySubTreeOk)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public functions # Public functions
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc delTree*( proc deleteAccountPayload*(
db: AristoDbRef; # Database, top layer db: AristoDbRef;
root: VertexID; # Root vertex path: openArray[byte];
accPath: PathID; # Needed for real storage tries ): Result[void,AristoError] =
): Result[void,(VertexID,AristoError)] = ## Delete the account leaf entry addressed by the argument `path`. If this
## Delete sub-trie below `root`. ## leaf entry referres to a storage tree, this one will be deleted as well.
## ##
## Note that the accounts trie hinging on `VertexID(1)` cannot be deleted. let
## hike = path.initNibbleRange.hikeUp(VertexID(1), db).valueOr:
## If the `root` argument belongs to a well known sub trie (i.e. it does if error[1] in HikeAcceptableStopsNotFound:
## not exceed `LEAST_FREE_VID`) the `accPath` argument is ignored and the return err(DelPathNotFound)
## sub-trie will just be deleted. return err(error[1])
## stoID = hike.legs[^1].wp.vtx.lData.account.storageID
## Otherwise, a valid `accPath` (i.e. different from `VOID_PATH_ID`.) is
## required relating to an account leaf entry (starting at `VertexID(`)`).
## If the payload of that leaf entry is not of type `AccountData` it is
## ignored. Otherwise its `storageID` field must be equal to the `hike.root`
## vertex ID. This leaf entry `storageID` field will be reset to
## `VertexID(0)` after having deleted the sub-trie.
##
db.delSubTreeImpl(root, accPath)
proc delete*( # Delete storage tree if present
db: AristoDbRef; # Database, top layer if stoID.isValid:
hike: Hike; # Fully expanded chain of vertices ? db.delSubTreeImpl stoID
accPath: PathID; # Needed for accounts payload
): Result[bool,(VertexID,AristoError)] =
## Delete argument `hike` chain of vertices from the database. The return
## code will be `true` iff the sub-trie starting at `hike.root` will have
## become empty.
##
## If the `hike` argument referes to aa account entrie (i.e. `hike.root`
## equals `VertexID(1)`) and the leaf entry has an `AccountData` payload,
## its `storageID` field must have been reset to `VertexID(0)`. the
## `accPath` argument will be ignored.
##
## Otherwise, if the `root` argument belongs to a well known sub trie (i.e.
## it does not exceed `LEAST_FREE_VID`) the `accPath` argument is ignored
## and the entry will just be deleted.
##
## Otherwise, a valid `accPath` (i.e. different from `VOID_PATH_ID`.) is
## required relating to an account leaf entry (starting at `VertexID(`)`).
## If the payload of that leaf entry is not of type `AccountData` it is
## ignored. Otherwise its `storageID` field must be equal to the `hike.root`
## vertex ID. This leaf entry `storageID` field will be reset to
## `VertexID(0)` in case the entry to be deleted will render the sub-trie
## empty.
##
let lty = LeafTie(
root: hike.root,
path: ? hike.to(NibblesSeq).pathToTag().mapErr toVae)
db.deleteImpl(hike, lty, accPath)
proc delete*( db.deleteImpl(hike).isOkOr:
db: AristoDbRef; # Database, top layer return err(error[1])
lty: LeafTie; # `Patricia Trie` path root-to-leaf
accPath: PathID; # Needed for accounts payload
): Result[bool,(VertexID,AristoError)] =
## Variant of `delete()`
##
db.deleteImpl(? lty.hikeUp(db).mapErr toVae, lty, accPath)
proc delete*( ok()
proc deleteGenericData*(
db: AristoDbRef; db: AristoDbRef;
root: VertexID; root: VertexID;
path: openArray[byte]; path: openArray[byte];
accPath: PathID; # Needed for accounts payload ): Result[bool,AristoError] =
): Result[bool,(VertexID,AristoError)] = ## Delete the leaf data entry addressed by the argument `path`. The MPT
## Variant of `delete()` ## 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`.
## ##
let rc = path.initNibbleRange.hikeUp(root, db) ## The return value is `true` if the argument `path` deleted was the last
if rc.isOk: ## one and the tree does not exist anymore.
return db.delete(rc.value, accPath) ##
if rc.error[1] in HikeAcceptableStopsNotFound: # Verify that `root` is neither an accounts tree nor a strorage tree.
return err((rc.error[0], DelPathNotFound)) if not root.isValid:
err((rc.error[0],rc.error[1])) return err(DelRootVidMissing)
elif root == VertexID(1):
return err(DelAccRootNotAccepted)
elif LEAST_FREE_VID <= root.distinctBase:
return err(DelStoRootNotAccepted)
let hike = path.initNibbleRange.hikeUp(root, db).valueOr:
if error[1] in HikeAcceptableStopsNotFound:
return err(DelPathNotFound)
return err(error[1])
db.deleteImpl(hike).isOkOr:
return err(error[1])
ok(not db.getVtx(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;
path: openArray[byte];
accPath: PathID; # Needed for accounts payload
): Result[bool,AristoError] =
## For a given account argument `accPath`, this function deletes the
## argument `path` from the associated storage tree (if any, at all.) If
## the if the argument `path` 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
accHike = ? db.retrieveStoAccHike accPath
wpAcc = accHike.legs[^1].wp
stoID = wpAcc.vtx.lData.account.storageID
if not stoID.isValid:
return err(DelStoRootMissing)
let stoHike = path.initNibbleRange.hikeUp(stoID, db).valueOr:
if error[1] in HikeAcceptableStopsNotFound:
return err(DelPathNotFound)
return err(error[1])
# Mark account path for update for `hashify()`
db.updateAccountForHasher accHike
db.deleteImpl(stoHike).isOkOr:
return err(error[1])
# Make sure that an account leaf has no dangling sub-trie
if db.getVtx(stoID).isValid:
return ok(false)
# De-register the deleted storage tree from the account record
let leaf = wpAcc.vtx.dup # Dup on modify
leaf.lData.account.storageID = VertexID(0)
db.layersPutVtx(VertexID(1), wpAcc.vid, leaf)
db.layersResKey(VertexID(1), wpAcc.vid)
ok(true)
proc deleteStorageTree*(
db: AristoDbRef; # Database, top layer
accPath: PathID; # Needed for accounts payload
): Result[void,AristoError] =
## Variant of `deleteStorageData()` for purging the whole storage tree
## associated to the account argument `accPath`.
##
let
accHike = db.retrieveStoAccHike(accPath).valueOr:
if error == UtilsAccInaccessible:
return err(DelStoAccMissing)
return err(error)
wpAcc = accHike.legs[^1].wp
stoID = wpAcc.vtx.lData.account.storageID
if not stoID.isValid:
return err(DelStoRootMissing)
# Mark account path for update for `hashify()`
db.updateAccountForHasher accHike
? db.delSubTreeImpl stoID
# De-register the deleted storage tree from the accounts record
let leaf = wpAcc.vtx.dup # Dup on modify
leaf.lData.account.storageID = VertexID(0)
db.layersPutVtx(VertexID(1), wpAcc.vid, leaf)
db.layersResKey(VertexID(1), wpAcc.vid)
ok()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# End # End

View File

@ -190,6 +190,7 @@ type
NearbyVidInvalid NearbyVidInvalid
# Deletion of vertices, `delete()` # Deletion of vertices, `delete()`
DelAccRootNotAccepted
DelBranchExpexted DelBranchExpexted
DelBranchLocked DelBranchLocked
DelBranchWithoutRefs DelBranchWithoutRefs
@ -200,6 +201,10 @@ type
DelLeafUnexpected DelLeafUnexpected
DelPathNotFound DelPathNotFound
DelPathTagError DelPathTagError
DelRootVidMissing
DelStoAccMissing
DelStoRootMissing
DelStoRootNotAccepted
DelSubTreeAccRoot DelSubTreeAccRoot
DelSubTreeVoidRoot DelSubTreeVoidRoot
DelVidStaleVtx DelVidStaleVtx

View File

@ -53,6 +53,10 @@ proc mergeAccountPayload*(
## unset/invalid or referring to a existing vertex which will be assumed ## unset/invalid or referring to a existing vertex which will be assumed
## to be a storage tree. ## to be a storage tree.
## ##
## On success, the function returns `true` if the `accPayload` argument was
## merged into the database ot updated, and `false` if it was on the database
## already.
##
let let
pyl = PayloadRef(pType: AccountData, account: accPayload) pyl = PayloadRef(pType: AccountData, account: accPayload)
rc = db.mergePayloadImpl(VertexID(1), accKey, pyl, VidVtxPair()) rc = db.mergePayloadImpl(VertexID(1), accKey, pyl, VidVtxPair())
@ -73,6 +77,10 @@ proc mergeGenericData*(
## Variant of `mergeXXX()` for generic sub-trees, i.e. for arguments ## Variant of `mergeXXX()` for generic sub-trees, i.e. for arguments
## `root` greater than `VertexID(1)` and smaller than `LEAST_FREE_VID`. ## `root` greater than `VertexID(1)` and smaller than `LEAST_FREE_VID`.
## ##
## On success, the function returns `true` if the `data` argument was merged
## into the database ot updated, and `false` if it was on the database
## already.
##
# Verify that `root` is neither an accounts tree nor a strorage tree. # Verify that `root` is neither an accounts tree nor a strorage tree.
if not root.isValid: if not root.isValid:
return err(MergeRootVidMissing) return err(MergeRootVidMissing)
@ -112,7 +120,8 @@ proc mergeStorageData*(
## otherwise `VertexID(0)`. ## otherwise `VertexID(0)`.
## ##
let let
wpAcc = ? db.registerAccountForUpdate accPath accHike = ?db.retrieveStoAccHike accPath
wpAcc = accHike.legs[^1].wp
stoID = wpAcc.vtx.lData.account.storageID stoID = wpAcc.vtx.lData.account.storageID
# Provide new storage ID when needed # Provide new storage ID when needed
@ -123,6 +132,9 @@ proc mergeStorageData*(
rc = db.mergePayloadImpl(useID, stoKey, pyl, wpAcc) rc = db.mergePayloadImpl(useID, stoKey, pyl, wpAcc)
if rc.isOk: if rc.isOk:
# Mark account path for update for `hashify()`
db.updateAccountForHasher accHike
if stoID.isValid: if stoID.isValid:
return ok VertexID(0) return ok VertexID(0)
@ -138,7 +150,8 @@ proc mergeStorageData*(
assert stoID.isValid # debugging only assert stoID.isValid # debugging only
return ok VertexID(0) return ok VertexID(0)
# else # Error: mark account path for update for `hashify()`
db.updateAccountForHasher accHike
err(rc.error) err(rc.error)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -172,69 +172,16 @@ proc subVids*(vtx: VertexRef): seq[VertexID] =
# --------------------- # ---------------------
proc registerAccount*( proc retrieveStoAccHike*(
db: AristoDbRef; # Database, top layer db: AristoDbRef; # Database
stoRoot: VertexID; # Storage root ID accPath: PathID; # Implies a storage ID (if any)
accPath: PathID; # Needed for accounts payload ): Result[Hike,AristoError] =
): Result[VidVtxPair,AristoError] = ## Verify that the `accPath` argument properly referres to a storage root
## Verify that the `stoRoot` argument is properly referred to by the ## vertex ID. The function will reset the keys along the `accPath` for
## account data (if any) implied to by the `accPath` argument. ## being modified.
## ##
## The function will return an account leaf node if there was any, or an empty ## On success, the function will return an account leaf pair with the leaf
## `VidVtxPair()` object. ## vertex and the vertex ID.
##
# Verify storage root and account path
if not stoRoot.isValid:
return err(UtilsStoRootMissing)
if not accPath.isValid:
return err(UtilsAccPathMissing)
# Get account leaf with account data
let hike = LeafTie(root: VertexID(1), path: accPath).hikeUp(db).valueOr:
return err(UtilsAccInaccessible)
# Check account payload
let wp = hike.legs[^1].wp
if wp.vtx.vType != Leaf:
return err(UtilsAccPathWithoutLeaf)
if wp.vtx.lData.pType != AccountData:
return err(UtilsAccLeafPayloadExpected)
# Check whether the `stoRoot` exists on the databse
let stoVtx = block:
let rc = db.getVtxRc stoRoot
if rc.isOk:
rc.value
elif rc.error == GetVtxNotFound:
VertexRef(nil)
else:
return err(rc.error)
# Verify `stoVtx` against storage root
let stoID = wp.vtx.lData.account.storageID
if stoVtx.isValid:
if stoID != stoRoot:
return err(UtilsAccWrongStorageRoot)
else:
if stoID.isValid:
return err(UtilsAccWrongStorageRoot)
# Clear Merkle keys so that `hasify()` can calculate the re-hash forest/tree
for w in hike.legs.mapIt(it.wp.vid):
db.layersResKey(hike.root, w)
# Signal to `hashify()` where to start rebuilding Merkel hashes
db.top.final.dirty.incl hike.root
db.top.final.dirty.incl wp.vid
ok(wp)
proc registerAccountForUpdate*(
db: AristoDbRef; # Database, top layer
accPath: PathID; # Needed for accounts payload
): Result[VidVtxPair,AristoError] =
## ...
## ##
# Expand vertex path to account leaf # Expand vertex path to account leaf
let hike = (@accPath).initNibbleRange.hikeUp(VertexID(1), db).valueOr: let hike = (@accPath).initNibbleRange.hikeUp(VertexID(1), db).valueOr:
@ -253,15 +200,23 @@ proc registerAccountForUpdate*(
discard db.getVtxRc(acc.storageID).valueOr: discard db.getVtxRc(acc.storageID).valueOr:
return err(UtilsStoRootInaccessible) return err(UtilsStoRootInaccessible)
ok(hike)
proc updateAccountForHasher*(
db: AristoDbRef; # Database
hike: Hike; # Return value from `retrieveStorageID()`
) =
## For a successful run of `retrieveStoAccHike()`, the argument `hike` is
## used to mark/reset the keys along the `accPath` for being re-calculated
## by `hashify()`.
##
# Clear Merkle keys so that `hasify()` can calculate the re-hash forest/tree # Clear Merkle keys so that `hasify()` can calculate the re-hash forest/tree
for w in hike.legs.mapIt(it.wp.vid): for w in hike.legs.mapIt(it.wp.vid):
db.layersResKey(hike.root, w) db.layersResKey(hike.root, w)
# Signal to `hashify()` where to start rebuilding Merkel hashes # Signal to `hashify()` where to start rebuilding Merkel hashes
db.top.final.dirty.incl hike.root db.top.final.dirty.incl hike.root
db.top.final.dirty.incl wp.vid db.top.final.dirty.incl hike.legs[^1].wp.vid
ok(wp)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# End # End

View File

@ -256,19 +256,25 @@ proc mptMethods(cMpt: AristoCoreDxMptRef): CoreDbMptFns =
proc mptDelete(key: openArray[byte]): CoreDbRc[void] = proc mptDelete(key: openArray[byte]): CoreDbRc[void] =
const info = "deleteFn()" const info = "deleteFn()"
if not cMpt.mptRoot.isValid and cMpt.accPath.isValid: let rc = block:
# This is insane but legit. A storage column was announced for an account if cMpt.accPath.isValid:
# but no data have been added, yet. api.deleteStorageData(mpt, key, cMpt.accPath)
return ok() else:
let rc = api.delete(mpt, cMpt.mptRoot, key, cMpt.accPath) api.deleteGenericData(mpt, cMpt.mptRoot, key)
if rc.isErr: if rc.isErr:
if rc.error[1] == DelPathNotFound: if rc.error == DelPathNotFound:
return err(rc.error.toError(base, info, MptNotFound)) return err(rc.error.toError(base, info, MptNotFound))
if rc.error == DelStoRootMissing:
# This is insane but legit. A storage column was announced for an
# account but no data have been added, yet.
return ok()
return err(rc.error.toError(base, info)) return err(rc.error.toError(base, info))
if rc.value: if rc.value:
# Column has become empty # Column has become empty
cMpt.mptRoot = VoidVID cMpt.mptRoot = VoidVID
ok() ok()
proc mptHasPath(key: openArray[byte]): CoreDbRc[bool] = proc mptHasPath(key: openArray[byte]): CoreDbRc[bool] =
@ -357,30 +363,21 @@ proc accMethods(cAcc: AristoCoreDxAccRef): CoreDbAccFns =
proc accDelete(address: EthAddress): CoreDbRc[void] = proc accDelete(address: EthAddress): CoreDbRc[void] =
const info = "acc/deleteFn()" const info = "acc/deleteFn()"
let let key = address.keccakHash.data
key = address.keccakHash.data api.deleteAccountPayload(mpt, key).isOkOr:
rc = api.delete(mpt, AccountsVID, key, VOID_PATH_ID) if error == DelPathNotFound:
if rc.isErr: return err(error.toError(base, info, AccNotFound))
if rc.error[1] == DelPathNotFound: return err(error.toError(base, info))
return err(rc.error.toError(base, info, AccNotFound))
return err(rc.error.toError(base, info))
ok() ok()
proc accStoDelete(address: EthAddress): CoreDbRc[void] = proc accStoDelete(address: EthAddress): CoreDbRc[void] =
const info = "stoDeleteFn()" const info = "stoDeleteFn()"
let let rc = api.deleteStorageTree(mpt, address.to(PathID))
key = address.keccakHash.data if rc.isErr and rc.error notin {DelStoRootMissing,DelStoAccMissing}:
pyl = api.fetchPayload(mpt, AccountsVID, key).valueOr: return err(rc.error.toError(base, info))
return ok()
# Use storage ID from account and delete that column
if pyl.pType == AccountData:
let stoID = pyl.account.storageID
if stoID.isValid:
let rc = api.delTree(mpt, stoID, address.to(PathID))
if rc.isErr:
return err(rc.error.toError(base, info))
ok() ok()
proc accHasPath(address: EthAddress): CoreDbRc[bool] = proc accHasPath(address: EthAddress): CoreDbRc[bool] =
@ -500,8 +497,9 @@ proc ctxMethods(cCtx: AristoCoreDbCtxRef): CoreDbCtxFns =
# Reset column. This a emulates the behaviour of a new empty MPT on the # Reset column. This a emulates the behaviour of a new empty MPT on the
# legacy database. # legacy database.
if reset: if reset:
let rc = api.delTree(mpt, newMpt.mptRoot, VOID_PATH_ID) let rc = api.deleteGenericTree(mpt, newMpt.mptRoot)
if rc.isErr: if rc.isErr:
raiseAssert "find me"
return err(rc.error.toError(base, info, AutoFlushFailed)) return err(rc.error.toError(base, info, AutoFlushFailed))
col.reset = false col.reset = false

View File

@ -157,10 +157,7 @@ proc freeStorage*(al: AccountLedger, eAddr: EthAddress) =
proc delete*(al: AccountLedger, eAddr: EthAddress) = proc delete*(al: AccountLedger, eAddr: EthAddress) =
const info = "AccountLedger/delete()" const info = "AccountLedger/delete()"
# Flush associated storage trie # Delete account and associated storage tree (if any)
al.distinctBase.stoDelete(eAddr).isOkOr:
raiseAssert info & $$error
# Clear account
al.distinctBase.delete(eAddr).isOkOr: al.distinctBase.delete(eAddr).isOkOr:
if error.error == MptNotFound: if error.error == MptNotFound:
return return

View File

@ -13,8 +13,7 @@ import
eth/common, eth/common,
stew/endians2, stew/endians2,
../../nimbus/db/aristo/[ ../../nimbus/db/aristo/[
aristo_debug, aristo_desc, aristo_delete, aristo_debug, aristo_desc, aristo_hashify, aristo_hike, aristo_merge],
aristo_hashify, aristo_hike, aristo_merge],
../../nimbus/db/kvstore_rocksdb, ../../nimbus/db/kvstore_rocksdb,
../../nimbus/sync/protocol/snap/snap_types, ../../nimbus/sync/protocol/snap/snap_types,
../replay/[pp, undump_accounts, undump_storages], ../replay/[pp, undump_accounts, undump_storages],
@ -211,42 +210,6 @@ proc hashify*(
else: else:
aristo_hashify.hashify(db) aristo_hashify.hashify(db)
proc delete*(
db: AristoDbRef;
root: VertexID;
path: openArray[byte];
accPath: PathID;
noisy: bool;
): Result[bool,(VertexID,AristoError)] =
when declared(aristo_delete.noisy):
aristo_delete.exec(noisy, aristo_delete.delete(db, root, path, accPath))
else:
aristo_delete.delete(db, root, path, accPath)
proc delete*(
db: AristoDbRef;
lty: LeafTie;
accPath: PathID;
noisy: bool;
): Result[bool,(VertexID,AristoError)] =
when declared(aristo_delete.noisy):
aristo_delete.exec(noisy, aristo_delete.delete(db, lty, accPath))
else:
aristo_delete.delete(db, lty, accPath)
proc delTree*(
db: AristoDbRef;
root: VertexID;
accPath: PathID;
noisy: bool;
): Result[void,(VertexID,AristoError)] =
when declared(aristo_delete.noisy):
aristo_delete.exec(noisy, aristo_delete.delTree(db, root, accPath))
else:
aristo_delete.delTree(db, root, accPath)
proc mergeGenericData*( proc mergeGenericData*(
db: AristoDbRef; # Database, top layer db: AristoDbRef; # Database, top layer
leaf: LeafTiePayload; # Leaf item to add to the database leaf: LeafTiePayload; # Leaf item to add to the database

View File

@ -394,8 +394,8 @@ proc testTxMergeAndDeleteOneByOne*(
# Delete leaf # Delete leaf
block: block:
let rc = db.delete(leaf, VOID_PATH_ID) let rc = db.deleteGenericData(leaf.root, @(leaf.path))
xCheckRc rc.error == (0,0) xCheckRc rc.error == 0
# Update list of remaininf leafs # Update list of remaininf leafs
leafsLeft.excl leaf leafsLeft.excl leaf
@ -492,8 +492,8 @@ proc testTxMergeAndDeleteSubTree*(
"" ""
# Delete sub-tree # Delete sub-tree
block: block:
let rc = db.delTree(testRootVid, VOID_PATH_ID) let rc = db.deleteGenericTree testRootVid
xCheckRc rc.error == (0,0): xCheckRc rc.error == 0:
noisy.say "***", "del(2)", noisy.say "***", "del(2)",
" n=", n, "/", list.len, " n=", n, "/", list.len,
"\n db\n ", db.pp(backendOk=true), "\n db\n ", db.pp(backendOk=true),