338 lines
12 KiB
Nim
338 lines
12 KiB
Nim
# 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.
|
|
|
|
## Find node paths in hexary tries.
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/[sequtils, sets, tables],
|
|
eth/[common, trie/nibbles],
|
|
stew/[byteutils, interval_set],
|
|
"../.."/[constants, range_desc],
|
|
"."/[hexary_desc, hexary_nodes_helper]
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private debugging helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
#proc pp(w: Blob; db: HexaryTreeDbRef): string =
|
|
# w.convertTo(RepairKey).pp(db)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc to(a: RepairKey; T: type RepairKey): RepairKey =
|
|
## Needed for generic function
|
|
a
|
|
|
|
proc convertTo(key: RepairKey; T: type NodeKey): T =
|
|
## Might be lossy, check before use
|
|
discard result.init(key.ByteArray33[1 .. 32])
|
|
|
|
proc getNibblesImpl(path: XPath|RPath; start = 0): NibblesSeq =
|
|
## Re-build the key path
|
|
for n in start ..< path.path.len:
|
|
let it = path.path[n]
|
|
case it.node.kind:
|
|
of Branch:
|
|
result = result & @[it.nibble.byte].initNibbleRange.slice(1)
|
|
of Extension:
|
|
result = result & it.node.ePfx
|
|
of Leaf:
|
|
result = result & it.node.lPfx
|
|
result = result & path.tail
|
|
|
|
proc getNibblesImpl(path: XPath|RPath; start, maxLen: int): NibblesSeq =
|
|
## Variant of `getNibblesImpl()` for partial rebuild
|
|
for n in start ..< min(path.path.len, maxLen):
|
|
let it = path.path[n]
|
|
case it.node.kind:
|
|
of Branch:
|
|
result = result & @[it.nibble.byte].initNibbleRange.slice(1)
|
|
of Extension:
|
|
result = result & it.node.ePfx
|
|
of Leaf:
|
|
result = result & it.node.lPfx
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc rootPathExtend(
|
|
path: RPath|XPath; # Partially expanded path
|
|
db: HexaryTreeDbRef|HexaryGetFn; # Database abstraction
|
|
): auto
|
|
{.gcsafe, raises: [CatchableError].} =
|
|
## For the given path, extend to the longest possible `db` database
|
|
## path following the argument `path.tail`.
|
|
result = path
|
|
|
|
when typeof(path) is RPath:
|
|
var key = path.root
|
|
else:
|
|
var key = path.root.to(Blob)
|
|
|
|
while true:
|
|
let rc = key.getNode(db)
|
|
if rc.isErr:
|
|
break
|
|
let node = rc.value
|
|
|
|
case node.kind:
|
|
of Leaf:
|
|
if result.tail.len == result.tail.sharedPrefixLen(node.lPfx):
|
|
# Bingo, got full path
|
|
result.path.add typeof(path.path[0])(key: key, node: node, nibble: -1)
|
|
result.tail = EmptyNibbleSeq
|
|
return
|
|
|
|
of Branch:
|
|
if result.tail.len == 0:
|
|
result.path.add typeof(path.path[0])(key: key, node: node, nibble: -1)
|
|
return
|
|
let nibble = result.tail[0].int8
|
|
if node.bLink[nibble].isZeroLink:
|
|
return
|
|
result.path.add typeof(path.path[0])(key: key, node: node, nibble: nibble)
|
|
result.tail = result.tail.slice(1)
|
|
key = node.bLink[nibble]
|
|
|
|
of Extension:
|
|
if result.tail.len == 0:
|
|
result.path.add typeof(path.path[0])(key: key, node: node, nibble: -1)
|
|
result.tail = EmptyNibbleSeq # clean up internal indexing
|
|
return
|
|
if node.ePfx.len != result.tail.sharedPrefixLen(node.ePfx):
|
|
return
|
|
result.path.add typeof(path.path[0])(key: key, node: node, nibble: -1)
|
|
result.tail = result.tail.slice(node.ePfx.len)
|
|
key = node.eLink
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc getNibbles*(path: XPath|RPath; start = 0): NibblesSeq =
|
|
## Re-build the key path
|
|
path.getNibblesImpl(start)
|
|
|
|
proc getNibbles*(path: XPath|RPath; start, maxLen: int): NibblesSeq =
|
|
## Variant of `getNibbles()`
|
|
path.getNibblesImpl(start, maxLen)
|
|
|
|
|
|
proc getPartialPath*(path: XPath|RPath): Blob =
|
|
## Convert to hex encoded partial path as used in `eth` or `snap` protocol
|
|
## where full leaf paths of nibble length 64 are encoded as 32 byte `Blob`
|
|
## and non-leaf partial paths are *compact encoded* (i.e. per the Ethereum
|
|
## wire protocol.)
|
|
let
|
|
isLeaf = (0 < path.path.len and path.path[^1].node.kind == Leaf)
|
|
nibbles = path.getNibbles
|
|
if isLeaf and nibbles.len == 64:
|
|
nibbles.getBytes
|
|
else:
|
|
nibbles.hexPrefixEncode(isLeaf)
|
|
|
|
|
|
proc leafData*(path: XPath): Blob =
|
|
## Return the leaf data from a successful `XPath` computation (if any.)
|
|
if path.tail.len == 0 and 0 < path.path.len:
|
|
let node = path.path[^1].node
|
|
case node.kind:
|
|
of Branch:
|
|
return node.bLink[16]
|
|
of Leaf:
|
|
return node.lData
|
|
of Extension:
|
|
discard
|
|
|
|
proc leafData*(path: RPath): Blob =
|
|
## Return the leaf data from a successful `RPath` computation (if any.)
|
|
if path.tail.len == 0 and 0 < path.path.len:
|
|
let node = path.path[^1].node
|
|
case node.kind:
|
|
of Branch:
|
|
return node.bData
|
|
of Leaf:
|
|
return node.lData
|
|
of Extension:
|
|
discard
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions, hexary path constructors
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc hexaryPath*(
|
|
partialPath: NibblesSeq; # partial path to resolve
|
|
rootKey: NodeKey|RepairKey; # State root
|
|
db: HexaryTreeDbRef; # Database
|
|
): RPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Compute the longest possible repair tree `db` path matching the `nodeKey`
|
|
## nibbles. The `nodeNey` path argument comes before the `db` one for
|
|
## supporting a more functional notation.
|
|
RPath(root: rootKey.to(RepairKey), tail: partialPath).rootPathExtend(db)
|
|
|
|
proc hexaryPath*(
|
|
nodeKey: NodeKey;
|
|
rootKey: NodeKey|RepairKey;
|
|
db: HexaryTreeDbRef;
|
|
): RPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPath` for a node key.
|
|
nodeKey.to(NibblesSeq).hexaryPath(rootKey, db)
|
|
|
|
proc hexaryPath*(
|
|
nodeTag: NodeTag;
|
|
rootKey: NodeKey|RepairKey;
|
|
db: HexaryTreeDbRef;
|
|
): RPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPath` for a node tag.
|
|
nodeTag.to(NodeKey).hexaryPath(rootKey, db)
|
|
|
|
proc hexaryPath*(
|
|
partialPath: Blob;
|
|
rootKey: NodeKey|RepairKey;
|
|
db: HexaryTreeDbRef;
|
|
): RPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPath` for a hex encoded partial path.
|
|
partialPath.hexPrefixDecode[1].hexaryPath(rootKey, db)
|
|
|
|
|
|
proc hexaryPath*(
|
|
partialPath: NibblesSeq; # partial path to resolve
|
|
rootKey: NodeKey; # State root
|
|
getFn: HexaryGetFn; # Database abstraction
|
|
): XPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Compute the longest possible path on an arbitrary hexary trie.
|
|
XPath(root: rootKey, tail: partialPath).rootPathExtend(getFn)
|
|
|
|
proc hexaryPath*(
|
|
nodeKey: NodeKey;
|
|
rootKey: NodeKey;
|
|
getFn: HexaryGetFn;
|
|
): XPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPath` for a node key..
|
|
nodeKey.to(NibblesSeq).hexaryPath(rootKey, getFn)
|
|
|
|
proc hexaryPath*(
|
|
nodeTag: NodeTag;
|
|
rootKey: NodeKey;
|
|
getFn: HexaryGetFn;
|
|
): XPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPath` for a node tag..
|
|
nodeTag.to(NodeKey).hexaryPath(rootKey, getFn)
|
|
|
|
proc hexaryPath*(
|
|
partialPath: Blob;
|
|
rootKey: NodeKey;
|
|
getFn: HexaryGetFn;
|
|
): XPath
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPath` for a hex encoded partial path.
|
|
partialPath.hexPrefixDecode[1].hexaryPath(rootKey, getFn)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public helpers, partial paths resolvers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc hexaryPathNodeKey*(
|
|
partialPath: NibblesSeq; # Hex encoded partial path
|
|
rootKey: NodeKey|RepairKey; # State root
|
|
db: HexaryTreeDbRef; # Database
|
|
missingOk = false; # Also return key for missing node
|
|
): Result[NodeKey,void]
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Returns the `NodeKey` equivalent for the argment `partialPath` if this
|
|
## node is available in the database. If the argument flag `missingOk` is
|
|
## set`true` and the last node addressed by the argument path is missing,
|
|
## its key is returned as well.
|
|
let steps = partialPath.hexaryPath(rootKey, db)
|
|
if 0 < steps.path.len and steps.tail.len == 0:
|
|
let top = steps.path[^1]
|
|
# If the path was fully exhaused and the node exists for a `Branch` node,
|
|
# then the `nibble` is `-1`.
|
|
if top.nibble < 0 and top.key.isNodeKey:
|
|
return ok(top.key.convertTo(NodeKey))
|
|
if missingOk:
|
|
let link = top.node.bLink[top.nibble]
|
|
if not link.isZero and link.isNodeKey:
|
|
return ok(link.convertTo(NodeKey))
|
|
err()
|
|
|
|
proc hexaryPathNodeKey*(
|
|
partialPath: Blob; # Hex encoded partial path
|
|
rootKey: NodeKey|RepairKey; # State root
|
|
db: HexaryTreeDbRef; # Database
|
|
missingOk = false; # Also return key for missing node
|
|
): Result[NodeKey,void]
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPathNodeKey()` for hex encoded partial path.
|
|
partialPath.hexPrefixDecode[1].hexaryPathNodeKey(rootKey, db, missingOk)
|
|
|
|
|
|
proc hexaryPathNodeKey*(
|
|
partialPath: NibblesSeq; # Hex encoded partial path
|
|
rootKey: NodeKey; # State root
|
|
getFn: HexaryGetFn; # Database abstraction
|
|
missingOk = false; # Also return key for missing node
|
|
): Result[NodeKey,void]
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPathNodeKey()` for persistent database.
|
|
let steps = partialPath.hexaryPath(rootKey, getFn)
|
|
if 0 < steps.path.len and steps.tail.len == 0:
|
|
let top = steps.path[^1]
|
|
# If the path was fully exhaused and the node exists for a `Branch` node,
|
|
# then the `nibble` is `-1`.
|
|
if top.nibble < 0:
|
|
return ok(top.key.convertTo(NodeKey))
|
|
if missingOk:
|
|
let link = top.node.bLink[top.nibble]
|
|
if 0 < link.len:
|
|
return ok(link.convertTo(NodeKey))
|
|
err()
|
|
|
|
proc hexaryPathNodeKey*(
|
|
partialPath: Blob; # Partial database path
|
|
rootKey: NodeKey; # State root
|
|
getFn: HexaryGetFn; # Database abstraction
|
|
missingOk = false; # Also return key for missing node
|
|
): Result[NodeKey,void]
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Variant of `hexaryPathNodeKey()` for persistent database and
|
|
## hex encoded partial path.
|
|
partialPath.hexPrefixDecode[1].hexaryPathNodeKey(rootKey, getFn, missingOk)
|
|
|
|
proc hexaryPathNodeKeys*(
|
|
partialPaths: seq[Blob]; # Partial paths segments
|
|
rootKey: NodeKey|RepairKey; # State root
|
|
db: HexaryTreeDbRef; # Database
|
|
missingOk = false; # Also return key for missing node
|
|
): HashSet[NodeKey]
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
## Convert a list of path segments to a set of node keys
|
|
partialPaths.toSeq
|
|
.mapIt(it.hexaryPathNodeKey(rootKey, db, missingOk))
|
|
.filterIt(it.isOk)
|
|
.mapIt(it.value)
|
|
.toHashSet
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|