nimbus-eth1/nimbus/db/aristo/aristo_merge.nim

431 lines
13 KiB
Nim
Raw Normal View History

# 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.
{.push raises: [].}
import
std/tables,
chronicles,
eth/[common, trie/nibbles],
stew/results,
../../sync/snap/range_desc,
./aristo_debug,
"."/[aristo_desc, aristo_error, aristo_get, aristo_hike, aristo_path,
aristo_vid]
logScope:
topics = "aristo-leaf"
# ------------------------------------------------------------------------------
# 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
# -----------
proc insertBranch(
db: AristoDbRef; # Database, top layer
hike: Hike;
linkID: VertexID;
linkVtx: VertexRef;
payload: PayloadRef; # Leaf data payload
): Hike =
##
## Insert `Extension->Branch` vertex chain or just a `Branch` vertex
##
## --(linkID)--> <linkVtx>
##
## will become either
##
## --(linkID)-->
## <extVtx> --(local1)-->
## <forkVtx>[linkInx] --(local2)--> <linkVtx>
## [leafInx] --(local3)--> <leafVtx>
##
## or in case that there is no common prefix
##
## --(linkID)-->
## <forkVtx>[linkInx] --(local2)--> <linkVtx>
## [leafInx] --(local3)--> <leafVtx>
##
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:
let local = db.vidFetch
# Update vertex path lookup
if linkVtx.vType == Leaf:
let
path = hike.legsTo(NibblesSeq) & linkVtx.lPfx
rc = path.pathToTag()
if rc.isErr:
error "Branch link leaf path garbled", linkID, path
return Hike(error: MergeBrLinkLeafGarbled)
db.lTab[rc.value] = local # update leaf path lookup cache
forkVtx.bVid[linkInx] = local
db.sTab[local] = linkVtx
linkVtx.xPfx = linkVtx.xPfx.slice(1+n)
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)
db.sTab[local] = leafLeg.wp.vtx
# 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)
db.sTab[linkID] = extVtx
result.legs.add Leg(
nibble: -1,
wp: VidVtxPair(
vid: linkID,
vtx: extVtx))
db.sTab[extVtx.eVid] = forkVtx
result.legs.add Leg(
nibble: leafInx.int8,
wp: VidVtxPair(
vid: extVtx.eVid,
vtx: forkVtx))
else:
db.sTab[linkID] = forkVtx
result.legs.add Leg(
nibble: leafInx.int8,
wp: VidVtxPair(
vid: linkID,
vtx: forkVtx))
result.legs.add leafLeg
proc appendBranchAndLeaf(
db: AristoDbRef; # Database, top layer
hike: Hike; # Path top has a `Branch` vertex
brID: VertexID; # Branch vertex ID from from `Hike` top
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`.
if hike.tail.len == 0:
return Hike(error: MergeBranchGarbledTail)
let nibble = hike.tail[0].int8
if not brVtx.bVid[nibble].isZero:
return Hike(error: MergeRootBranchLinkBusy)
# Append branch node
result = Hike(root: hike.root, legs: hike.legs)
result.legs.add Leg(wp: VidVtxPair(vtx: brVtx, vid: brID), nibble: nibble)
# Append leaf node
let
vid = db.vidFetch
vtx = VertexRef(
vType: Leaf,
lPfx: hike.tail.slice(1),
lData: payload)
brVtx.bVid[nibble] = vid
db.sTab[vid] = vtx
result.legs.add Leg(wp: VidVtxPair(vtx: vtx, vid: vid), nibble: -1)
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc hikeTopBranchAppendLeaf(
db: AristoDbRef; # Database, top layer
hike: Hike; # Path top has a `Branch` vertex
payload: PayloadRef; # Leaf data payload
proofMode: bool; # May have dangling links
): 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]
# Busy slot, check for dangling link
linkVtx = block:
let rc = db.getVtxCascaded linkID
if rc.isErr and not proofMode:
# Not much else that can be done here
error "Dangling leaf link, reused", branch=hike.legs[^1].wp.vid,
nibble, linkID, leafPfx=hike.tail
if rc.isErr or rc.value.isNil:
# Reuse placeholder entry in table
let vtx = VertexRef(
vType: Leaf,
lPfx: hike.tail,
lData: payload)
db.sTab[linkID] = vtx
result = Hike(root: hike.root, legs: hike.legs)
result.legs.add Leg(wp: VidVtxPair(vid: linkID, vtx: vtx), nibble: -1)
return
rc.value
# Slot link to a branch vertex should be handled by `hikeUp()`
if linkVtx.vType == Branch:
return db.appendBranchAndLeaf(hike, linkID, linkVtx, payload)
db.insertBranch(hike, linkID, linkVtx, payload)
proc hikeTopExtensionAppendLeaf(
db: AristoDbRef; # Database, top layer
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
parVtx = hike.legs[^1].wp.vtx
parID = hike.legs[^1].wp.vid
brVtx = db.getVtx parVtx.eVid
result = Hike(root: hike.root, legs: hike.legs)
if brVtx.isNil:
# Blind vertex, promote to leaf node.
let vtx = VertexRef(
vType: Leaf,
lPfx: parVtx.ePfx & hike.tail,
lData: payload)
db.sTab[parID] = vtx
result.legs[^1].wp.vtx = vtx
elif brVtx.vType != Branch:
return Hike(error: MergeBranchRootExpected)
else:
let nibble = hike.tail[0].int8
if not brVtx.bVid[nibble].isZero:
return Hike(error: MergeRootBranchLinkBusy)
let
vid = db.vidFetch
vtx = VertexRef(
vType: Leaf,
lPfx: hike.tail.slice(1),
lData: payload)
brVtx.bVid[nibble] = vid
db.sTab[vid] = vtx
result.legs[^1].nibble = nibble
result.legs.add Leg(wp: VidVtxPair(vtx: vtx, vid: vid), nibble: -1)
proc emptyHikeAppendLeaf(
db: AristoDbRef; # Database, top layer
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:
let nibble = hike.tail[0].int8
if not rootVtx.bVid[nibble].isZero:
return Hike(error: MergeRootBranchLinkBusy)
let
leafVid = db.vidFetch
leafVtx = VertexRef(
vType: Leaf,
lPfx: hike.tail.slice(1),
lData: payload)
rootVtx.bVid[nibble] = leafVid
db.sTab[leafVid] = leafVtx
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)
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc merge*(
db: AristoDbRef; # Database, top layer
pathTag: NodeTag; # `Patricia Trie` path root-to-leaf
payload: PayloadRef; # Leaf data payload
root = VertexID(0); # Root node reference
proofMode = false; # May have dangling links
noisy = false;
): Hike =
## Merge the argument `leaf` record into the top level vertex table of the
## database `db`. The argument `pathKey` is used to index the leaf on the
## `Patricia Tree`. The argument `payload` is stored with the leaf vertex in
## the database unless the leaf vertex exists already.
proc setUpAsRoot(vid: VertexID): Hike =
let
vtx = VertexRef(
vType: Leaf,
lPfx: pathTag.pathAsNibbles,
lData: payload)
wp = VidVtxPair(vid: vid, vtx: vtx)
db.sTab[vid] = vtx
Hike(root: vid, legs: @[Leg(wp: wp, nibble: -1)])
if root.isZero:
if noisy: echo ">>> merge (1)"
result = db.vidFetch.setUpAsRoot() # bootstrap: new root ID
else:
let hike = pathTag.hikeUp(root, db)
if noisy: echo "<<< merge (2) >>>", "\n ", hike.pp(db)
if 0 < hike.legs.len:
case hike.legs[^1].wp.vtx.vType:
of Branch:
if noisy: echo ">>> merge (3)"
result = db.hikeTopBranchAppendLeaf(hike, payload, proofMode)
of Leaf:
if noisy: echo ">>> merge (4)"
if 0 < hike.tail.len: # `Leaf` vertex problem?
return Hike(error: MergeLeafGarbledHike)
result = hike
of Extension:
if noisy: echo ">>> merge (5)"
result = db.hikeTopExtensionAppendLeaf(hike, payload)
else:
# Empty hike
let rootVtx = db.getVtx root
if rootVtx.isNil:
if noisy: echo ">>> merge (6)"
result = root.setUpAsRoot() # bootstrap for existing root ID
else:
if noisy: echo ">>> merge (7)"
result = db.emptyHikeAppendLeaf(hike, rootVtx, payload)
# Update leaf acccess cache
if result.error == AristoError(0):
db.lTab[pathTag] = result.legs[^1].wp.vid
proc merge*(
db: AristoDbRef; # Database, top layer
nodeKey: NodeKey; # Merkel hash of node
node: NodeRef; # Node derived from RLP representation
): Result[VertexID,AristoError] =
## Merge a node key expanded from its RLP representation into the database.
##
## There is some rudimentaty check whether the `node` is consistent. It is
## *not* checked, whether the vertex IDs have been allocated, already. If
## the node comes straight from the `decode()` RLP decoder, these vertex IDs
## will be all zero.
proc register(key: NodeKey): VertexID =
db.pAmk.withValue(key,vidPtr):
return vidPtr[]
let vid = db.vidFetch
db.pAmk[key] = vid
db.kMap[vid] = key
vid
# Check whether the record is correct
if node.error != AristoError(0):
return err(node.error)
# Verify `nodeKey`
if nodeKey.isZero:
return err(MergeNodeKeyZero)
# Check whether the node exists, already
db.pAmk.withValue(nodeKey,vidPtr):
if db.sTab.hasKey vidPtr[]:
return ok vidPtr[]
let
vid = nodeKey.register
vtx = node.to(VertexRef) # the vertex IDs need to be set up now (if any)
case node.vType:
of Leaf:
discard
of Extension:
if not node.key[0].isZero:
db.pAmk.withValue(node.key[0],vidPtr):
vtx.eVid = vidPtr[]
do:
vtx.eVid = node.key[0].register
of Branch:
for n in 0..15:
if not node.key[n].isZero:
db.pAmk.withValue(node.key[n],vidPtr):
vtx.bVid[n] = vidPtr[]
do:
vtx.bVid[n] = node.key[n].register
db.sTab[vid] = vtx
ok vid
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------