2023-05-30 11:47:47 +00:00
|
|
|
# nimbus-eth1
|
|
|
|
# Copyright (c) 2021 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.
|
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
## Aristo DB -- Patricia Trie builder, raw node insertion
|
|
|
|
## ======================================================
|
|
|
|
##
|
|
|
|
## This module merges `NodeTag` values as hexary lookup paths into the
|
|
|
|
## `Patricia Trie`. When changing vertices (aka nodes without Merkle hashes),
|
|
|
|
## associated (but separated) Merkle hashes will be deleted unless locked.
|
|
|
|
## Instead of deleting locked hashes error handling is applied.
|
|
|
|
##
|
|
|
|
## Also, nodes (vertices plus merkle hashes) can be added which is needed for
|
|
|
|
## boundary proofing after `snap/1` download. The vertices are split from the
|
|
|
|
## nodes and stored as-is on the table holding `Patricia Trie` entries. The
|
|
|
|
## hashes are stored iin a separate table and the vertices are labelled
|
|
|
|
## `locked`.
|
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
|
|
|
import
|
2023-05-30 21:21:15 +00:00
|
|
|
std/[sequtils, sets, tables],
|
2023-05-30 11:47:47 +00:00
|
|
|
chronicles,
|
|
|
|
eth/[common, trie/nibbles],
|
|
|
|
stew/results,
|
2023-05-30 21:21:15 +00:00
|
|
|
../../sync/protocol,
|
2023-06-12 13:48:47 +00:00
|
|
|
"."/[aristo_desc, aristo_get, aristo_hike, aristo_path, aristo_transcode,
|
|
|
|
aristo_vid]
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
logScope:
|
2023-05-30 21:21:15 +00:00
|
|
|
topics = "aristo-merge"
|
|
|
|
|
|
|
|
type
|
2023-06-12 13:48:47 +00:00
|
|
|
LeafTiePayload* = object
|
2023-06-09 11:17:37 +00:00
|
|
|
## Generalised key-value pair for a sub-trie. The main trie is the
|
|
|
|
## sub-trie with `root=VertexID(1)`.
|
2023-06-12 13:48:47 +00:00
|
|
|
leafTie*: LeafTie ## Full `Patricia Trie` path root-to-leaf
|
2023-05-30 21:21:15 +00:00
|
|
|
payload*: PayloadRef ## Leaf data payload
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private getters & setters
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc xPfx(vtx: VertexRef): NibblesSeq =
|
|
|
|
case vtx.vType:
|
|
|
|
of Leaf:
|
|
|
|
return vtx.lPfx
|
|
|
|
of Extension:
|
|
|
|
return vtx.ePfx
|
|
|
|
of Branch:
|
|
|
|
doAssert vtx.vType != Branch # Ooops
|
|
|
|
|
|
|
|
proc `xPfx=`(vtx: VertexRef, val: NibblesSeq) =
|
|
|
|
case vtx.vType:
|
|
|
|
of Leaf:
|
|
|
|
vtx.lPfx = val
|
|
|
|
of Extension:
|
|
|
|
vtx.ePfx = val
|
|
|
|
of Branch:
|
|
|
|
doAssert vtx.vType != Branch # Ooops
|
|
|
|
|
2023-06-02 19:21:46 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
2023-05-30 21:21:15 +00:00
|
|
|
|
|
|
|
proc clearMerkleKeys(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-05-30 21:21:15 +00:00
|
|
|
hike: Hike; # Implied vertex IDs to clear hashes for
|
|
|
|
vid: VertexID; # Additionall vertex IDs to clear
|
|
|
|
) =
|
|
|
|
for vid in hike.legs.mapIt(it.wp.vid) & @[vid]:
|
2023-06-12 13:48:47 +00:00
|
|
|
let key = db.top.kMap.getOrVoid vid
|
|
|
|
if key.isValid:
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.kMap.del vid
|
|
|
|
db.top.pAmk.del key
|
|
|
|
elif db.getKeyBackend(vid).isOK:
|
|
|
|
# Register for deleting on backend
|
|
|
|
db.top.dKey.incl vid
|
2023-05-30 21:21:15 +00:00
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
# -----------
|
|
|
|
|
|
|
|
proc insertBranch(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
|
|
|
hike: Hike; # Current state
|
|
|
|
linkID: VertexID; # Vertex ID to insert
|
|
|
|
linkVtx: VertexRef; # Vertex to insert
|
2023-05-30 11:47:47 +00:00
|
|
|
payload: PayloadRef; # Leaf data payload
|
|
|
|
): Hike =
|
|
|
|
##
|
|
|
|
## Insert `Extension->Branch` vertex chain or just a `Branch` vertex
|
|
|
|
##
|
2023-05-30 21:21:15 +00:00
|
|
|
## ... --(linkID)--> <linkVtx>
|
|
|
|
##
|
|
|
|
## <-- immutable --> <---- mutable ----> ..
|
2023-05-30 11:47:47 +00:00
|
|
|
##
|
|
|
|
## will become either
|
|
|
|
##
|
|
|
|
## --(linkID)-->
|
|
|
|
## <extVtx> --(local1)-->
|
2023-05-30 21:21:15 +00:00
|
|
|
## <forkVtx>[linkInx] --(local2)--> <linkVtx*>
|
2023-05-30 11:47:47 +00:00
|
|
|
## [leafInx] --(local3)--> <leafVtx>
|
|
|
|
##
|
|
|
|
## or in case that there is no common prefix
|
|
|
|
##
|
|
|
|
## --(linkID)-->
|
2023-05-30 21:21:15 +00:00
|
|
|
## <forkVtx>[linkInx] --(local2)--> <linkVtx*>
|
2023-05-30 11:47:47 +00:00
|
|
|
## [leafInx] --(local3)--> <leafVtx>
|
|
|
|
##
|
2023-05-30 21:21:15 +00:00
|
|
|
## *) vertex was slightly modified or removed if obsolete `Extension`
|
|
|
|
##
|
2023-05-30 11:47:47 +00:00
|
|
|
let n = linkVtx.xPfx.sharedPrefixLen hike.tail
|
|
|
|
|
|
|
|
# Verify minimum requirements
|
|
|
|
if hike.tail.len == n:
|
|
|
|
# Should have been tackeld by `hikeUp()`, already
|
|
|
|
return Hike(error: MergeLeafGarbledHike)
|
|
|
|
if linkVtx.xPfx.len == n:
|
|
|
|
return Hike(error: MergeBrLinkVtxPfxTooShort)
|
|
|
|
|
|
|
|
# Provide and install `forkVtx`
|
|
|
|
let
|
|
|
|
forkVtx = VertexRef(vType: Branch)
|
|
|
|
linkInx = linkVtx.xPfx[n]
|
|
|
|
leafInx = hike.tail[n]
|
|
|
|
var
|
|
|
|
leafLeg = Leg(nibble: -1)
|
|
|
|
|
|
|
|
# Install `forkVtx`
|
|
|
|
block:
|
2023-05-30 21:21:15 +00:00
|
|
|
# Clear Merkle hashes (aka node keys) unless proof mode.
|
2023-06-09 11:17:37 +00:00
|
|
|
if db.top.pPrf.len == 0:
|
2023-05-30 21:21:15 +00:00
|
|
|
db.clearMerkleKeys(hike, linkID)
|
2023-06-09 11:17:37 +00:00
|
|
|
elif linkID in db.top.pPrf:
|
2023-05-30 21:21:15 +00:00
|
|
|
return Hike(error: MergeNonBranchProofModeLock)
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
if linkVtx.vType == Leaf:
|
2023-05-30 21:21:15 +00:00
|
|
|
# Update vertex path lookup
|
2023-05-30 11:47:47 +00:00
|
|
|
let
|
|
|
|
path = hike.legsTo(NibblesSeq) & linkVtx.lPfx
|
|
|
|
rc = path.pathToTag()
|
|
|
|
if rc.isErr:
|
2023-05-30 21:21:15 +00:00
|
|
|
debug "Branch link leaf path garbled", linkID, path
|
2023-05-30 11:47:47 +00:00
|
|
|
return Hike(error: MergeBrLinkLeafGarbled)
|
2023-05-30 21:21:15 +00:00
|
|
|
|
2023-06-09 11:17:37 +00:00
|
|
|
let
|
|
|
|
local = db.vidFetch
|
2023-06-12 13:48:47 +00:00
|
|
|
lty = LeafTie(root: hike.root, path: rc.value)
|
|
|
|
db.top.lTab[lty] = local # update leaf path lookup cache
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[local] = linkVtx
|
2023-05-30 21:21:15 +00:00
|
|
|
linkVtx.lPfx = linkVtx.lPfx.slice(1+n)
|
|
|
|
forkVtx.bVid[linkInx] = local
|
|
|
|
|
|
|
|
elif linkVtx.ePfx.len == n + 1:
|
|
|
|
# This extension `linkVtx` becomes obsolete
|
|
|
|
forkVtx.bVid[linkInx] = linkVtx.eVid
|
|
|
|
|
|
|
|
else:
|
|
|
|
let local = db.vidFetch
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[local] = linkVtx
|
2023-05-30 21:21:15 +00:00
|
|
|
linkVtx.ePfx = linkVtx.ePfx.slice(1+n)
|
|
|
|
forkVtx.bVid[linkInx] = local
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
block:
|
|
|
|
let local = db.vidFetch
|
|
|
|
forkVtx.bVid[leafInx] = local
|
|
|
|
leafLeg.wp.vid = local
|
|
|
|
leafLeg.wp.vtx = VertexRef(
|
|
|
|
vType: Leaf,
|
|
|
|
lPfx: hike.tail.slice(1+n),
|
|
|
|
lData: payload)
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[local] = leafLeg.wp.vtx
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
# Update branch leg, ready to append more legs
|
|
|
|
result = Hike(root: hike.root, legs: hike.legs)
|
|
|
|
|
|
|
|
# Update in-beween glue linking `branch --[..]--> forkVtx`
|
|
|
|
if 0 < n:
|
|
|
|
let extVtx = VertexRef(
|
|
|
|
vType: Extension,
|
|
|
|
ePfx: hike.tail.slice(0,n),
|
|
|
|
eVid: db.vidFetch)
|
|
|
|
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[linkID] = extVtx
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
result.legs.add Leg(
|
|
|
|
nibble: -1,
|
|
|
|
wp: VidVtxPair(
|
|
|
|
vid: linkID,
|
|
|
|
vtx: extVtx))
|
|
|
|
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[extVtx.eVid] = forkVtx
|
2023-05-30 11:47:47 +00:00
|
|
|
result.legs.add Leg(
|
|
|
|
nibble: leafInx.int8,
|
|
|
|
wp: VidVtxPair(
|
|
|
|
vid: extVtx.eVid,
|
|
|
|
vtx: forkVtx))
|
|
|
|
else:
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[linkID] = forkVtx
|
2023-05-30 11:47:47 +00:00
|
|
|
result.legs.add Leg(
|
|
|
|
nibble: leafInx.int8,
|
|
|
|
wp: VidVtxPair(
|
|
|
|
vid: linkID,
|
|
|
|
vtx: forkVtx))
|
|
|
|
|
|
|
|
result.legs.add leafLeg
|
|
|
|
|
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
proc concatBranchAndLeaf(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-05-30 11:47:47 +00:00
|
|
|
hike: Hike; # Path top has a `Branch` vertex
|
2023-05-30 21:21:15 +00:00
|
|
|
brVid: VertexID; # Branch vertex ID from from `Hike` top
|
2023-05-30 11:47:47 +00:00
|
|
|
brVtx: VertexRef; # Branch vertex, linked to from `Hike`
|
|
|
|
payload: PayloadRef; # Leaf data payload
|
|
|
|
): Hike =
|
|
|
|
## Append argument branch vertex passed as argument `(brID,brVtx)` and then
|
|
|
|
## a `Leaf` vertex derived from the argument `payload`.
|
2023-05-30 21:21:15 +00:00
|
|
|
##
|
2023-05-30 11:47:47 +00:00
|
|
|
if hike.tail.len == 0:
|
|
|
|
return Hike(error: MergeBranchGarbledTail)
|
2023-05-30 21:21:15 +00:00
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
let nibble = hike.tail[0].int8
|
2023-06-12 13:48:47 +00:00
|
|
|
if brVtx.bVid[nibble].isValid:
|
2023-05-30 11:47:47 +00:00
|
|
|
return Hike(error: MergeRootBranchLinkBusy)
|
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
# Clear Merkle hashes (aka node keys) unless proof mode.
|
2023-06-09 11:17:37 +00:00
|
|
|
if db.top.pPrf.len == 0:
|
2023-05-30 21:21:15 +00:00
|
|
|
db.clearMerkleKeys(hike, brVid)
|
2023-06-09 11:17:37 +00:00
|
|
|
elif brVid in db.top.pPrf:
|
2023-05-30 21:21:15 +00:00
|
|
|
return Hike(error: MergeBranchProofModeLock) # Ooops
|
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
# Append branch node
|
|
|
|
result = Hike(root: hike.root, legs: hike.legs)
|
2023-05-30 21:21:15 +00:00
|
|
|
result.legs.add Leg(wp: VidVtxPair(vtx: brVtx, vid: brVid), nibble: nibble)
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
# Append leaf node
|
|
|
|
let
|
|
|
|
vid = db.vidFetch
|
|
|
|
vtx = VertexRef(
|
|
|
|
vType: Leaf,
|
|
|
|
lPfx: hike.tail.slice(1),
|
|
|
|
lData: payload)
|
|
|
|
brVtx.bVid[nibble] = vid
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[vid] = vtx
|
2023-05-30 11:47:47 +00:00
|
|
|
result.legs.add Leg(wp: VidVtxPair(vtx: vtx, vid: vid), nibble: -1)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2023-06-12 13:48:47 +00:00
|
|
|
# Private functions: add Particia Trie leaf vertex
|
2023-05-30 11:47:47 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
proc topIsBranchAddLeaf(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-05-30 11:47:47 +00:00
|
|
|
hike: Hike; # Path top has a `Branch` vertex
|
|
|
|
payload: PayloadRef; # Leaf data payload
|
|
|
|
): Hike =
|
|
|
|
## Append a `Leaf` vertex derived from the argument `payload` after the top
|
|
|
|
## leg of the `hike` argument which is assumend to refert to a `Branch`
|
|
|
|
## vertex. If successful, the function returns the updated `hike` trail.
|
|
|
|
if hike.tail.len == 0:
|
|
|
|
return Hike(error: MergeBranchGarbledTail)
|
|
|
|
|
|
|
|
let nibble = hike.legs[^1].nibble
|
|
|
|
if nibble < 0:
|
|
|
|
return Hike(error: MergeBranchGarbledNibble)
|
|
|
|
|
|
|
|
let
|
|
|
|
branch = hike.legs[^1].wp.vtx
|
|
|
|
linkID = branch.bVid[nibble]
|
2023-05-30 21:21:15 +00:00
|
|
|
linkVtx = db.getVtx linkID
|
|
|
|
|
2023-06-12 13:48:47 +00:00
|
|
|
if not linkVtx.isValid:
|
2023-05-30 21:21:15 +00:00
|
|
|
#
|
|
|
|
# .. <branch>[nibble] --(linkID)--> nil
|
|
|
|
#
|
|
|
|
# <-------- immutable ------------> <---- mutable ----> ..
|
|
|
|
#
|
2023-06-09 11:17:37 +00:00
|
|
|
if db.top.pPrf.len == 0:
|
2023-05-30 21:21:15 +00:00
|
|
|
# Not much else that can be done here
|
|
|
|
debug "Dangling leaf link, reused", branch=hike.legs[^1].wp.vid,
|
|
|
|
nibble, linkID, leafPfx=hike.tail
|
|
|
|
|
|
|
|
# Reuse placeholder entry in table
|
|
|
|
let vtx = VertexRef(
|
|
|
|
vType: Leaf,
|
|
|
|
lPfx: hike.tail,
|
|
|
|
lData: payload)
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[linkID] = vtx
|
2023-05-30 21:21:15 +00:00
|
|
|
result = Hike(root: hike.root, legs: hike.legs)
|
|
|
|
result.legs.add Leg(wp: VidVtxPair(vid: linkID, vtx: vtx), nibble: -1)
|
|
|
|
return
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
if linkVtx.vType == Branch:
|
2023-05-30 21:21:15 +00:00
|
|
|
# Slot link to a branch vertex should be handled by `hikeUp()`
|
|
|
|
#
|
|
|
|
# .. <branch>[nibble] --(linkID)--> <linkVtx>[]
|
|
|
|
#
|
|
|
|
# <-------- immutable ------------> <---- mutable ----> ..
|
|
|
|
#
|
|
|
|
return db.concatBranchAndLeaf(hike, linkID, linkVtx, payload)
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
db.insertBranch(hike, linkID, linkVtx, payload)
|
|
|
|
|
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
proc topIsExtAddLeaf(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-05-30 11:47:47 +00:00
|
|
|
hike: Hike; # Path top has an `Extension` vertex
|
|
|
|
payload: PayloadRef; # Leaf data payload
|
|
|
|
): Hike =
|
|
|
|
## Append a `Leaf` vertex derived from the argument `payload` after the top
|
|
|
|
## leg of the `hike` argument which is assumend to refert to a `Extension`
|
|
|
|
## vertex. If successful, the function returns the
|
|
|
|
## updated `hike` trail.
|
|
|
|
let
|
2023-05-30 21:21:15 +00:00
|
|
|
extVtx = hike.legs[^1].wp.vtx
|
|
|
|
extVid = hike.legs[^1].wp.vid
|
|
|
|
brVid = extVtx.eVid
|
|
|
|
brVtx = db.getVtx brVid
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
result = Hike(root: hike.root, legs: hike.legs)
|
|
|
|
|
2023-06-12 13:48:47 +00:00
|
|
|
if not brVtx.isValid:
|
2023-05-30 11:47:47 +00:00
|
|
|
# Blind vertex, promote to leaf node.
|
2023-05-30 21:21:15 +00:00
|
|
|
#
|
|
|
|
# --(extVid)--> <extVtx> --(brVid)--> nil
|
|
|
|
#
|
|
|
|
# <-------- immutable -------------->
|
|
|
|
#
|
2023-05-30 11:47:47 +00:00
|
|
|
let vtx = VertexRef(
|
|
|
|
vType: Leaf,
|
2023-05-30 21:21:15 +00:00
|
|
|
lPfx: extVtx.ePfx & hike.tail,
|
2023-05-30 11:47:47 +00:00
|
|
|
lData: payload)
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[extVid] = vtx
|
2023-05-30 11:47:47 +00:00
|
|
|
result.legs[^1].wp.vtx = vtx
|
|
|
|
|
|
|
|
elif brVtx.vType != Branch:
|
|
|
|
return Hike(error: MergeBranchRootExpected)
|
|
|
|
|
|
|
|
else:
|
2023-05-30 21:21:15 +00:00
|
|
|
let
|
|
|
|
nibble = hike.tail[0].int8
|
|
|
|
linkID = brVtx.bVid[nibble]
|
|
|
|
#
|
|
|
|
# Required
|
|
|
|
#
|
|
|
|
# --(extVid)--> <extVtx> --(brVid)--> <brVtx>[nibble] --(linkID)--> nil
|
|
|
|
#
|
|
|
|
# <-------- immutable --------------> <-------- mutable ----------> ..
|
|
|
|
#
|
2023-06-12 13:48:47 +00:00
|
|
|
if linkID.isValid:
|
2023-05-30 11:47:47 +00:00
|
|
|
return Hike(error: MergeRootBranchLinkBusy)
|
2023-05-30 21:21:15 +00:00
|
|
|
|
|
|
|
# Clear Merkle hashes (aka node keys) unless proof mode
|
2023-06-09 11:17:37 +00:00
|
|
|
if db.top.pPrf.len == 0:
|
2023-05-30 21:21:15 +00:00
|
|
|
db.clearMerkleKeys(hike, brVid)
|
2023-06-09 11:17:37 +00:00
|
|
|
elif brVid in db.top.pPrf:
|
2023-05-30 21:21:15 +00:00
|
|
|
return Hike(error: MergeBranchProofModeLock)
|
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
let
|
|
|
|
vid = db.vidFetch
|
|
|
|
vtx = VertexRef(
|
|
|
|
vType: Leaf,
|
|
|
|
lPfx: hike.tail.slice(1),
|
|
|
|
lData: payload)
|
|
|
|
brVtx.bVid[nibble] = vid
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[vid] = vtx
|
2023-05-30 11:47:47 +00:00
|
|
|
result.legs[^1].nibble = nibble
|
|
|
|
result.legs.add Leg(wp: VidVtxPair(vtx: vtx, vid: vid), nibble: -1)
|
|
|
|
|
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
proc topIsEmptyAddLeaf(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-05-30 11:47:47 +00:00
|
|
|
hike: Hike; # No path legs
|
|
|
|
rootVtx: VertexRef; # Root vertex
|
|
|
|
payload: PayloadRef; # Leaf data payload
|
|
|
|
): Hike =
|
|
|
|
## Append a `Leaf` vertex derived from the argument `payload` after the
|
|
|
|
## argument vertex `rootVtx` and append both the empty arguent `hike`.
|
|
|
|
if rootVtx.vType == Branch:
|
2023-06-09 11:17:37 +00:00
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
let nibble = hike.tail[0].int8
|
2023-06-12 13:48:47 +00:00
|
|
|
if rootVtx.bVid[nibble].isValid:
|
2023-05-30 11:47:47 +00:00
|
|
|
return Hike(error: MergeRootBranchLinkBusy)
|
2023-06-02 10:04:29 +00:00
|
|
|
|
|
|
|
# Clear Merkle hashes (aka node keys) unless proof mode
|
2023-06-09 11:17:37 +00:00
|
|
|
if db.top.pPrf.len == 0:
|
2023-06-02 10:04:29 +00:00
|
|
|
db.clearMerkleKeys(hike, hike.root)
|
2023-06-09 11:17:37 +00:00
|
|
|
elif hike.root in db.top.pPrf:
|
2023-06-02 10:04:29 +00:00
|
|
|
return Hike(error: MergeBranchProofModeLock)
|
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
let
|
|
|
|
leafVid = db.vidFetch
|
|
|
|
leafVtx = VertexRef(
|
|
|
|
vType: Leaf,
|
|
|
|
lPfx: hike.tail.slice(1),
|
|
|
|
lData: payload)
|
|
|
|
rootVtx.bVid[nibble] = leafVid
|
2023-06-09 11:17:37 +00:00
|
|
|
db.top.sTab[leafVid] = leafVtx
|
2023-05-30 11:47:47 +00:00
|
|
|
return Hike(
|
|
|
|
root: hike.root,
|
|
|
|
legs: @[Leg(wp: VidVtxPair(vtx: rootVtx, vid: hike.root), nibble: nibble),
|
|
|
|
Leg(wp: VidVtxPair(vtx: leafVtx, vid: leafVid), nibble: -1)])
|
|
|
|
|
|
|
|
db.insertBranch(hike, hike.root, rootVtx, payload)
|
|
|
|
|
2023-06-12 13:48:47 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private functions: add Merkle proof node
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc mergeNodeImpl(
|
|
|
|
db: AristoDb; # Database, top layer
|
|
|
|
nodeKey: NodeKey; # Merkel hash of node
|
|
|
|
node: NodeRef; # Node derived from RLP representation
|
|
|
|
rootVid: VertexID; # Current sub-trie
|
|
|
|
): Result[VertexID,AristoError] =
|
|
|
|
## The function merges a node key `nodeKey` expanded from its RLP
|
|
|
|
## representation into the `Aristo Trie` database. The vertex is split off
|
|
|
|
## from the node and stored separately. So are the Merkle hashes. The
|
|
|
|
## vertex is labelled `locked`.
|
|
|
|
##
|
|
|
|
## The `node` argument is *not* checked, whether the vertex IDs have been
|
|
|
|
## allocated, already. If the node comes straight from the `decode()` RLP
|
|
|
|
## decoder as expected, these vertex IDs will be all zero.
|
|
|
|
##
|
|
|
|
if node.error != AristoError(0):
|
|
|
|
return err(node.error)
|
|
|
|
if not rootVid.isValid:
|
|
|
|
return err(MergeRootKeyEmpty)
|
|
|
|
|
|
|
|
# Verify `nodeKey`
|
|
|
|
if not nodeKey.isValid:
|
|
|
|
return err(MergeNodeKeyEmpty)
|
|
|
|
|
|
|
|
# Check whether the node exists, already. If not then create a new vertex ID
|
|
|
|
var
|
|
|
|
nodeLbl = HashLabel(root: rootVid, key: nodeKey)
|
|
|
|
vid = db.top.pAmk.getOrVoid nodeLbl
|
|
|
|
if not vid.isValid:
|
|
|
|
vid = db.vidAttach nodeLbl
|
|
|
|
else:
|
|
|
|
let lbl = db.top.kMap.getOrVoid vid
|
|
|
|
if lbl == nodeLbl:
|
|
|
|
if db.top.sTab.hasKey vid:
|
|
|
|
# This is tyically considered OK
|
|
|
|
return err(MergeNodeKeyCachedAlready)
|
|
|
|
# Otherwise proceed
|
|
|
|
elif lbl.isValid:
|
|
|
|
# Different key assigned => error
|
|
|
|
return err(MergeNodeKeyDiffersFromCached)
|
|
|
|
|
|
|
|
let vtx = node.to(VertexRef) # the vertex IDs need to be set up now (if any)
|
|
|
|
|
|
|
|
case node.vType:
|
|
|
|
of Leaf:
|
|
|
|
discard
|
|
|
|
of Extension:
|
|
|
|
if node.key[0].isValid:
|
|
|
|
let
|
|
|
|
eLbl = HashLabel(root: rootVid, key: node.key[0])
|
|
|
|
eVid = db.top.pAmk.getOrVoid eLbl
|
|
|
|
if eVid.isValid:
|
|
|
|
vtx.eVid = eVid
|
|
|
|
else:
|
|
|
|
vtx.eVid = db.vidAttach eLbl
|
|
|
|
of Branch:
|
|
|
|
for n in 0..15:
|
|
|
|
if node.key[n].isValid:
|
|
|
|
let
|
|
|
|
bLbl = HashLabel(root: rootVid, key: node.key[n])
|
|
|
|
bVid = db.top.pAmk.getOrVoid bLbl
|
|
|
|
if bVid.isValid:
|
|
|
|
vtx.bVid[n] = bVid
|
|
|
|
else:
|
|
|
|
vtx.bVid[n] = db.vidAttach bLbl
|
|
|
|
|
|
|
|
db.top.pPrf.incl vid
|
|
|
|
db.top.sTab[vid] = vtx
|
|
|
|
ok vid
|
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc merge*(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-06-12 13:48:47 +00:00
|
|
|
leaf: LeafTiePayload; # Leaf item to add to the database
|
2023-05-30 11:47:47 +00:00
|
|
|
): Hike =
|
2023-05-30 21:21:15 +00:00
|
|
|
## Merge the argument `leaf` key-value-pair into the top level vertex table
|
|
|
|
## of the database `db`. The field `pathKey` of the `leaf` argument is used
|
|
|
|
## to index the leaf vertex on the `Patricia Trie`. The field `payload` is
|
|
|
|
## stored with the leaf vertex in the database unless the leaf vertex exists
|
|
|
|
## already.
|
|
|
|
##
|
2023-06-12 13:48:47 +00:00
|
|
|
if db.top.lTab.hasKey leaf.leafTie:
|
2023-05-30 21:21:15 +00:00
|
|
|
result.error = MergeLeafPathCachedAlready
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
else:
|
2023-06-12 13:48:47 +00:00
|
|
|
let hike = leaf.leafTie.hikeUp(db)
|
2023-05-30 11:47:47 +00:00
|
|
|
if 0 < hike.legs.len:
|
|
|
|
case hike.legs[^1].wp.vtx.vType:
|
|
|
|
of Branch:
|
2023-05-30 21:21:15 +00:00
|
|
|
result = db.topIsBranchAddLeaf(hike, leaf.payload)
|
2023-05-30 11:47:47 +00:00
|
|
|
of Leaf:
|
|
|
|
if 0 < hike.tail.len: # `Leaf` vertex problem?
|
|
|
|
return Hike(error: MergeLeafGarbledHike)
|
|
|
|
result = hike
|
|
|
|
of Extension:
|
2023-05-30 21:21:15 +00:00
|
|
|
result = db.topIsExtAddLeaf(hike, leaf.payload)
|
2023-05-30 11:47:47 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
# Empty hike
|
2023-06-09 11:17:37 +00:00
|
|
|
let rootVtx = db.getVtx hike.root
|
2023-05-30 11:47:47 +00:00
|
|
|
|
2023-06-12 13:48:47 +00:00
|
|
|
if rootVtx.isValid:
|
2023-05-30 21:21:15 +00:00
|
|
|
result = db.topIsEmptyAddLeaf(hike,rootVtx,leaf.payload)
|
2023-06-09 11:17:37 +00:00
|
|
|
else:
|
|
|
|
# Bootstrap for existing root ID
|
|
|
|
let wp = VidVtxPair(
|
|
|
|
vid: hike.root,
|
|
|
|
vtx: VertexRef(
|
|
|
|
vType: Leaf,
|
2023-06-12 13:48:47 +00:00
|
|
|
lPfx: leaf.leafTie.path.pathAsNibbles,
|
2023-06-09 11:17:37 +00:00
|
|
|
lData: leaf.payload))
|
|
|
|
db.top.sTab[wp.vid] = wp.vtx
|
|
|
|
result = Hike(root: wp.vid, legs: @[Leg(wp: wp, nibble: -1)])
|
|
|
|
|
|
|
|
# Update leaf acccess cache
|
|
|
|
if result.error == AristoError(0):
|
2023-06-12 13:48:47 +00:00
|
|
|
db.top.lTab[leaf.leafTie] = result.legs[^1].wp.vid
|
2023-06-09 11:17:37 +00:00
|
|
|
|
|
|
|
# End else (1st level)
|
2023-05-30 21:21:15 +00:00
|
|
|
|
|
|
|
proc merge*(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-06-12 13:48:47 +00:00
|
|
|
leafs: openArray[LeafTiePayload]; # Leaf items to add to the database
|
2023-05-30 21:21:15 +00:00
|
|
|
): tuple[merged: int, dups: int, error: AristoError] =
|
|
|
|
## Variant of `merge()` for leaf lists.
|
|
|
|
var (merged, dups) = (0, 0)
|
|
|
|
for n,w in leafs:
|
2023-06-09 11:17:37 +00:00
|
|
|
let hike = db.merge(w)
|
|
|
|
if hike.error == AristoError(0):
|
|
|
|
merged.inc
|
|
|
|
elif hike.error == MergeLeafPathCachedAlready:
|
|
|
|
dups.inc
|
|
|
|
else:
|
|
|
|
return (n,dups,hike.error)
|
|
|
|
|
|
|
|
(merged, dups, AristoError(0))
|
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
# ---------------------
|
2023-05-30 11:47:47 +00:00
|
|
|
|
2023-05-30 21:21:15 +00:00
|
|
|
proc merge*(
|
2023-06-09 11:17:37 +00:00
|
|
|
db: AristoDb; # Database, top layer
|
2023-05-30 21:21:15 +00:00
|
|
|
proof: openArray[SnapProof]; # RLP encoded node records
|
2023-06-12 13:48:47 +00:00
|
|
|
rootVid: VertexID; # Current sub-trie
|
2023-05-30 21:21:15 +00:00
|
|
|
): tuple[merged: int, dups: int, error: AristoError]
|
|
|
|
{.gcsafe, raises: [RlpError].} =
|
|
|
|
## The function merges the argument `proof` list of RLP encoded node records
|
|
|
|
## into the `Aristo Trie` database. This function is intended to be used with
|
|
|
|
## the proof nodes as returened by `snap/1` messages.
|
|
|
|
var (merged, dups) = (0, 0)
|
|
|
|
for n,w in proof:
|
|
|
|
let
|
|
|
|
key = w.Blob.digestTo(NodeKey)
|
|
|
|
node = w.Blob.decode(NodeRef)
|
2023-06-12 13:48:47 +00:00
|
|
|
rc = db.mergeNodeImpl(key, node, rootVid)
|
2023-05-30 21:21:15 +00:00
|
|
|
if rc.isOK:
|
|
|
|
merged.inc
|
|
|
|
elif rc.error == MergeNodeKeyCachedAlready:
|
|
|
|
dups.inc
|
|
|
|
else:
|
|
|
|
return (n, dups, rc.error)
|
|
|
|
|
|
|
|
(merged, dups, AristoError(0))
|
|
|
|
|
2023-06-09 11:17:37 +00:00
|
|
|
proc merge*(
|
|
|
|
db: AristoDb; # Database, top layer
|
|
|
|
rootKey: NodeKey; # Merkle hash for root
|
|
|
|
rootVid = VertexID(0) # Optionally, force root vertex ID
|
|
|
|
): Result[VertexID,AristoError] =
|
|
|
|
## Set up a `rootKey` associated with a vertex ID.
|
|
|
|
##
|
|
|
|
## If argument `rootVid` is unset (defaults to `VertexID(0)`) then the main
|
|
|
|
## trie is tested for `VertexID(1)`. If assigned with a different Merkle key
|
|
|
|
## already, a new vertex ID is created and the argument root key is assigned
|
|
|
|
## to this vertex ID.
|
|
|
|
##
|
|
|
|
## If the argument `rootVid` is set (to a value different from `VertexID(0)`),
|
|
|
|
## then a sub-trie with root `rootVid` is checked for. If it exists with a
|
|
|
|
## diffent root key assigned, then an error is returned. Otherwise a new
|
|
|
|
## vertex ID is created and the argument root key is assigned.
|
|
|
|
##
|
|
|
|
## Upon successful return, the vertex ID assigned to the root key is returned.
|
|
|
|
##
|
2023-06-12 13:48:47 +00:00
|
|
|
if not rootKey.isValid:
|
2023-06-09 11:17:37 +00:00
|
|
|
return err(MergeRootKeyEmpty)
|
|
|
|
|
2023-06-12 13:48:47 +00:00
|
|
|
if rootVid.isValid and rootVid != VertexID(1):
|
|
|
|
let key = db.getKey rootVid
|
|
|
|
if key == rootKey:
|
|
|
|
return ok rootVid
|
|
|
|
|
|
|
|
if not key.isValid:
|
|
|
|
db.vidAttach(HashLabel(root: rootVid, key: rootKey), rootVid)
|
|
|
|
return ok rootVid
|
|
|
|
else:
|
2023-06-09 11:17:37 +00:00
|
|
|
let key = db.getKey VertexID(1)
|
|
|
|
if key == rootKey:
|
|
|
|
return ok VertexID(1)
|
|
|
|
|
2023-06-12 13:48:47 +00:00
|
|
|
# Otherwise assign unless valid
|
|
|
|
if not key.isValid:
|
|
|
|
db.vidAttach(HashLabel(root: VertexID(1), key: rootKey), VertexID(1))
|
2023-06-09 11:17:37 +00:00
|
|
|
return ok VertexID(1)
|
|
|
|
|
2023-06-12 13:48:47 +00:00
|
|
|
# Create and assign a new root key
|
|
|
|
if not rootVid.isValid:
|
|
|
|
return ok db.vidRoot(rootKey)
|
2023-06-09 11:17:37 +00:00
|
|
|
|
|
|
|
err(MergeRootKeyDiffersForVid)
|
|
|
|
|
2023-05-30 11:47:47 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|