2022-08-15 15:51:50 +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-03-17 14:46:50 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2022-08-15 15:51:50 +00:00
|
|
|
import
|
2023-03-22 20:11:49 +00:00
|
|
|
std/[hashes, sets, tables],
|
2023-03-17 14:46:50 +00:00
|
|
|
eth/[common, trie/nibbles],
|
2023-09-13 02:32:38 +00:00
|
|
|
stew/endians2,
|
2022-08-15 15:51:50 +00:00
|
|
|
stint,
|
2023-03-22 20:11:49 +00:00
|
|
|
"../.."/[constants, range_desc],
|
2022-10-14 16:40:32 +00:00
|
|
|
./hexary_error
|
2022-08-15 15:51:50 +00:00
|
|
|
|
|
|
|
type
|
2023-02-14 23:38:33 +00:00
|
|
|
HexaryPpFn* =
|
|
|
|
proc(key: RepairKey): string {.gcsafe, raises: [CatchableError].}
|
|
|
|
## For testing/debugging: key pretty printer function
|
2022-08-15 15:51:50 +00:00
|
|
|
|
|
|
|
ByteArray33* = array[33,byte]
|
|
|
|
## Used for 31 byte database keys, i.e. <marker> + <32-byte-key>
|
|
|
|
|
|
|
|
RepairKey* = distinct ByteArray33
|
|
|
|
## Byte prefixed `NodeKey` for internal DB records
|
|
|
|
|
|
|
|
# Example trie from https://eth.wiki/en/fundamentals/patricia-tree
|
|
|
|
#
|
|
|
|
# lookup data:
|
|
|
|
# "do": "verb"
|
|
|
|
# "dog": "puppy"
|
|
|
|
# "dodge": "coin"
|
|
|
|
# "horse": "stallion"
|
|
|
|
#
|
|
|
|
# trie DB:
|
|
|
|
# root: [16 A]
|
|
|
|
# A: [* * * * B * * * [20+"orse" "stallion"] * * * * * * * *]
|
|
|
|
# B: [00+"o" D]
|
|
|
|
# D: [* * * * * * E * * * * * * * * * "verb"]
|
|
|
|
# E: [17 [* * * * * * [35 "coin"] * * * * * * * * * "puppy"]]
|
|
|
|
#
|
|
|
|
# with first nibble of two-column rows:
|
|
|
|
# hex bits | node type length
|
|
|
|
# ---------+------------------
|
|
|
|
# 0 0000 | extension even
|
|
|
|
# 1 0001 | extension odd
|
|
|
|
# 2 0010 | leaf even
|
|
|
|
# 3 0011 | leaf odd
|
|
|
|
#
|
|
|
|
# and key path:
|
|
|
|
# "do": 6 4 6 f
|
|
|
|
# "dog": 6 4 6 f 6 7
|
|
|
|
# "dodge": 6 4 6 f 6 7 6 5
|
|
|
|
# "horse": 6 8 6 f 7 2 7 3 6 5
|
|
|
|
|
2022-08-24 13:44:18 +00:00
|
|
|
NodeKind* = enum
|
|
|
|
Branch
|
|
|
|
Extension
|
|
|
|
Leaf
|
|
|
|
|
|
|
|
RNodeState* = enum
|
|
|
|
Static = 0 ## Inserted as proof record
|
|
|
|
Locked ## Like `Static`, only added on-the-fly
|
|
|
|
Mutable ## Open for modification
|
2022-09-02 18:16:09 +00:00
|
|
|
TmpRoot ## Mutable root node
|
2022-08-24 13:44:18 +00:00
|
|
|
|
2022-08-15 15:51:50 +00:00
|
|
|
RNodeRef* = ref object
|
2022-08-24 13:44:18 +00:00
|
|
|
## Node for building a temporary hexary trie coined `repair tree`.
|
|
|
|
state*: RNodeState ## `Static` if added from proof data set
|
|
|
|
case kind*: NodeKind
|
2022-08-15 15:51:50 +00:00
|
|
|
of Leaf:
|
2022-08-24 13:44:18 +00:00
|
|
|
lPfx*: NibblesSeq ## Portion of path segment
|
2022-08-15 15:51:50 +00:00
|
|
|
lData*: Blob
|
|
|
|
of Extension:
|
2022-08-24 13:44:18 +00:00
|
|
|
ePfx*: NibblesSeq ## Portion of path segment
|
|
|
|
eLink*: RepairKey ## Single down link
|
2022-08-15 15:51:50 +00:00
|
|
|
of Branch:
|
2022-08-24 13:44:18 +00:00
|
|
|
bLink*: array[16,RepairKey] ## Down links
|
|
|
|
#
|
|
|
|
# Paraphrased comment from Andri's `stateless/readme.md` file in chapter
|
|
|
|
# `Deviation from yellow paper`, (also found here
|
|
|
|
# github.com/status-im/nimbus-eth1
|
|
|
|
# /tree/master/stateless#deviation-from-yellow-paper)
|
|
|
|
# [..] In the Yellow Paper, the 17th elem of the branch node can contain
|
|
|
|
# a value. But it is always empty in a real Ethereum state trie. The
|
|
|
|
# block witness spec also ignores this 17th elem when encoding or
|
|
|
|
# decoding a branch node. This can happen because in a Ethereum secure
|
|
|
|
# hexary trie, every keys have uniform length of 32 bytes or 64 nibbles.
|
|
|
|
# With the absence of the 17th element, a branch node will never contain
|
|
|
|
# a leaf value.
|
2022-08-15 15:51:50 +00:00
|
|
|
bData*: Blob
|
|
|
|
|
2022-08-24 13:44:18 +00:00
|
|
|
XNodeObj* = object
|
|
|
|
## Simplified version of `RNodeRef` to be used as a node for `XPathStep`
|
|
|
|
case kind*: NodeKind
|
|
|
|
of Leaf:
|
|
|
|
lPfx*: NibblesSeq ## Portion of path segment
|
|
|
|
lData*: Blob
|
|
|
|
of Extension:
|
|
|
|
ePfx*: NibblesSeq ## Portion of path segment
|
|
|
|
eLink*: Blob ## Single down link
|
|
|
|
of Branch:
|
|
|
|
bLink*: array[17,Blob] ## Down links followed by data
|
2022-08-15 15:51:50 +00:00
|
|
|
|
2022-08-24 13:44:18 +00:00
|
|
|
RPathStep* = object
|
|
|
|
## For constructing a repair tree traversal path `RPath`
|
|
|
|
key*: RepairKey ## Tree label, node hash
|
|
|
|
node*: RNodeRef ## Referes to data record
|
|
|
|
nibble*: int8 ## Branch node selector (if any)
|
2022-08-15 15:51:50 +00:00
|
|
|
|
|
|
|
RPath* = object
|
2023-03-17 14:46:50 +00:00
|
|
|
root*: RepairKey ## Root node needed when `path.len == 0`
|
2022-08-15 15:51:50 +00:00
|
|
|
path*: seq[RPathStep]
|
2022-08-24 13:44:18 +00:00
|
|
|
tail*: NibblesSeq ## Stands for non completed leaf path
|
|
|
|
|
|
|
|
XPathStep* = object
|
|
|
|
## Similar to `RPathStep` for an arbitrary (sort of transparent) trie
|
|
|
|
key*: Blob ## Node hash implied by `node` data
|
|
|
|
node*: XNodeObj
|
|
|
|
nibble*: int8 ## Branch node selector (if any)
|
|
|
|
|
|
|
|
XPath* = object
|
2023-03-17 14:46:50 +00:00
|
|
|
root*: NodeKey ## Root node needed when `path.len == 0`
|
2022-08-24 13:44:18 +00:00
|
|
|
path*: seq[XPathStep]
|
|
|
|
tail*: NibblesSeq ## Stands for non completed leaf path
|
|
|
|
depth*: int ## May indicate path length (typically 64)
|
2022-08-15 15:51:50 +00:00
|
|
|
|
|
|
|
RLeafSpecs* = object
|
|
|
|
## Temporarily stashed leaf data (as for an account.) Proper records
|
|
|
|
## have non-empty payload. Records with empty payload are administrative
|
|
|
|
## items, e.g. lower boundary records.
|
2022-08-24 13:44:18 +00:00
|
|
|
pathTag*: NodeTag ## Equivalent to account hash
|
|
|
|
nodeKey*: RepairKey ## Leaf hash into hexary repair table
|
|
|
|
payload*: Blob ## Data payload
|
2022-08-15 15:51:50 +00:00
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
HexaryTreeDbRef* = ref object
|
|
|
|
## Hexary trie plus helper structures
|
2022-09-02 18:16:09 +00:00
|
|
|
tab*: Table[RepairKey,RNodeRef] ## key-value trie table, in-memory db
|
2022-08-24 13:44:18 +00:00
|
|
|
repairKeyGen*: uint64 ## Unique tmp key generator
|
2022-09-02 18:16:09 +00:00
|
|
|
keyPp*: HexaryPpFn ## For debugging, might go away
|
2022-08-15 15:51:50 +00:00
|
|
|
|
2023-02-14 23:38:33 +00:00
|
|
|
HexaryGetFn* = proc(key: openArray[byte]): Blob
|
|
|
|
{.gcsafe, raises: [CatchableError].}
|
|
|
|
## Persistent database `get()` function. For read-only cases, this
|
|
|
|
## function can be seen as the persistent alternative to ``tab[]` on
|
|
|
|
## a `HexaryTreeDbRef` descriptor.
|
2022-09-16 07:24:12 +00:00
|
|
|
|
2022-10-14 16:40:32 +00:00
|
|
|
HexaryNodeReport* = object
|
|
|
|
## Return code for single node operations
|
|
|
|
slot*: Option[int] ## May refer to indexed argument slots
|
|
|
|
kind*: Option[NodeKind] ## Node type (if any)
|
2022-11-08 18:56:04 +00:00
|
|
|
dangling*: seq[NodeSpecs] ## Missing inner sub-tries
|
2023-04-24 20:24:07 +00:00
|
|
|
error*: HexaryError ## Error code, or `HexaryError(0)`
|
2022-09-16 07:24:12 +00:00
|
|
|
|
2022-08-15 15:51:50 +00:00
|
|
|
static:
|
|
|
|
# Not that there is no doubt about this ...
|
|
|
|
doAssert NodeKey.default.ByteArray32.initNibbleRange.len == 64
|
|
|
|
|
2023-02-01 18:56:06 +00:00
|
|
|
proc isNodeKey*(a: RepairKey): bool {.gcsafe.}
|
|
|
|
proc isZero*(a: RepairKey): bool {.gcsafe.}
|
|
|
|
|
2022-08-15 15:51:50 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2023-02-15 10:14:40 +00:00
|
|
|
proc append(writer: var RlpWriter, node: RNodeRef) =
|
|
|
|
## Mixin for RLP writer
|
|
|
|
proc appendOk(writer: var RlpWriter; key: RepairKey): bool =
|
|
|
|
if key.isZero:
|
2023-03-22 20:11:49 +00:00
|
|
|
writer.append(EmptyBlob)
|
2023-02-15 10:14:40 +00:00
|
|
|
elif key.isNodeKey:
|
|
|
|
var hash: Hash256
|
|
|
|
(addr hash.data[0]).copyMem(unsafeAddr key.ByteArray33[1], 32)
|
|
|
|
writer.append(hash)
|
|
|
|
else:
|
|
|
|
return false
|
|
|
|
true
|
|
|
|
|
|
|
|
case node.kind:
|
|
|
|
of Branch:
|
|
|
|
writer.startList(17)
|
|
|
|
for n in 0 ..< 16:
|
|
|
|
if not writer.appendOk(node.bLink[n]):
|
|
|
|
return # empty `Blob`
|
|
|
|
writer.append(node.bData)
|
|
|
|
of Extension:
|
|
|
|
writer.startList(2)
|
|
|
|
writer.append(node.ePfx.hexPrefixEncode(isleaf = false))
|
|
|
|
if not writer.appendOk(node.eLink):
|
|
|
|
return # empty `Blob`
|
|
|
|
of Leaf:
|
|
|
|
writer.startList(2)
|
|
|
|
writer.append(node.lPfx.hexPrefixEncode(isleaf = true))
|
|
|
|
writer.append(node.lData)
|
|
|
|
|
|
|
|
|
|
|
|
proc append(writer: var RlpWriter, node: XNodeObj) =
|
|
|
|
## Mixin for RLP writer
|
|
|
|
case node.kind:
|
|
|
|
of Branch:
|
|
|
|
writer.append(node.bLink)
|
|
|
|
of Extension:
|
|
|
|
writer.startList(2)
|
|
|
|
writer.append(node.ePfx.hexPrefixEncode(isleaf = false))
|
|
|
|
writer.append(node.eLink)
|
|
|
|
of Leaf:
|
|
|
|
writer.startList(2)
|
|
|
|
writer.append(node.lPfx.hexPrefixEncode(isleaf = true))
|
|
|
|
writer.append(node.lData)
|
|
|
|
|
2022-08-15 15:51:50 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public constructor (or similar)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc init*(key: var RepairKey; data: openArray[byte]): bool =
|
2023-03-17 14:46:50 +00:00
|
|
|
key.reset
|
|
|
|
if 0 < data.len and data.len <= 33:
|
|
|
|
let trg = addr key.ByteArray33[33 - data.len]
|
|
|
|
trg.copyMem(unsafeAddr data[0], data.len)
|
|
|
|
return true
|
2022-08-15 15:51:50 +00:00
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
proc newRepairKey*(db: HexaryTreeDbRef): RepairKey =
|
2022-08-15 15:51:50 +00:00
|
|
|
db.repairKeyGen.inc
|
2023-02-01 18:56:06 +00:00
|
|
|
# Storing in proper endian handy for debugging (but not really important)
|
|
|
|
when cpuEndian == bigEndian:
|
|
|
|
var src = db.repairKeyGen.toBytesBE
|
|
|
|
else:
|
|
|
|
var src = db.repairKeyGen.toBytesLE
|
2022-08-15 15:51:50 +00:00
|
|
|
(addr result.ByteArray33[25]).copyMem(addr src[0], 8)
|
|
|
|
result.ByteArray33[0] = 1
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc hash*(a: RepairKey): Hash =
|
|
|
|
## Tables mixin
|
|
|
|
a.ByteArray33.hash
|
|
|
|
|
|
|
|
proc `==`*(a, b: RepairKey): bool =
|
|
|
|
## Tables mixin
|
|
|
|
a.ByteArray33 == b.ByteArray33
|
|
|
|
|
|
|
|
proc to*(key: NodeKey; T: type NibblesSeq): T =
|
|
|
|
key.ByteArray32.initNibbleRange
|
|
|
|
|
|
|
|
proc to*(key: NodeKey; T: type RepairKey): T =
|
|
|
|
(addr result.ByteArray33[1]).copyMem(unsafeAddr key.ByteArray32[0], 32)
|
|
|
|
|
2023-02-01 18:56:06 +00:00
|
|
|
proc isZero*(a: RepairKey): bool =
|
|
|
|
a == typeof(a).default
|
|
|
|
|
|
|
|
proc isZero*[T: NodeTag|NodeKey](a: T): bool =
|
|
|
|
a == typeof(a).default
|
2022-08-15 15:51:50 +00:00
|
|
|
|
|
|
|
proc isNodeKey*(a: RepairKey): bool =
|
|
|
|
a.ByteArray33[0] == 0
|
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
proc convertTo*(data: Blob; T: type NodeKey): T =
|
2022-08-15 15:51:50 +00:00
|
|
|
## Probably lossy conversion, use `init()` for safe conversion
|
|
|
|
discard result.init(data)
|
|
|
|
|
2022-12-06 17:35:56 +00:00
|
|
|
proc convertTo*(data: Blob; T: type NodeTag): T =
|
|
|
|
## Ditto for node tag
|
|
|
|
data.convertTo(NodeKey).to(NodeTag)
|
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
proc convertTo*(data: Blob; T: type RepairKey): T =
|
|
|
|
## Probably lossy conversion, use `init()` for safe conversion
|
2023-03-17 14:46:50 +00:00
|
|
|
discard result.init(data)
|
2022-09-16 07:24:12 +00:00
|
|
|
|
2022-08-15 15:51:50 +00:00
|
|
|
proc convertTo*(node: RNodeRef; T: type Blob): T =
|
|
|
|
## Write the node as an RLP-encoded blob
|
|
|
|
var writer = initRlpWriter()
|
2023-02-15 10:14:40 +00:00
|
|
|
writer.append node
|
2022-08-15 15:51:50 +00:00
|
|
|
writer.finish()
|
|
|
|
|
2023-01-30 17:50:58 +00:00
|
|
|
proc convertTo*(node: XNodeObj; T: type Blob): T =
|
2023-02-15 10:14:40 +00:00
|
|
|
## Variant of `convertTo()` for `XNodeObj` nodes.
|
2023-01-30 17:50:58 +00:00
|
|
|
var writer = initRlpWriter()
|
2023-02-15 10:14:40 +00:00
|
|
|
writer.append node
|
2023-01-30 17:50:58 +00:00
|
|
|
writer.finish()
|
|
|
|
|
2023-02-15 10:14:40 +00:00
|
|
|
proc convertTo*(nodeList: openArray[XNodeObj]; T: type Blob): T =
|
|
|
|
## Variant of `convertTo()` for a list of `XNodeObj` nodes.
|
|
|
|
var writer = initRlpList(nodeList.len)
|
|
|
|
for w in nodeList:
|
|
|
|
writer.append w
|
|
|
|
writer.finish
|
|
|
|
|
2022-08-15 15:51:50 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|