2023-01-30 17:50:58 +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-03 20:01:59 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2023-01-30 17:50:58 +00:00
|
|
|
import
|
|
|
|
std/[sequtils, sets, tables],
|
2023-03-17 14:46:50 +00:00
|
|
|
chronicles,
|
2023-03-10 17:10:30 +00:00
|
|
|
chronos,
|
2023-02-14 23:38:33 +00:00
|
|
|
eth/[common, p2p, trie/nibbles],
|
2023-01-30 17:50:58 +00:00
|
|
|
stew/[byteutils, interval_set],
|
2023-02-15 10:14:40 +00:00
|
|
|
../../../protocol,
|
2023-01-30 17:50:58 +00:00
|
|
|
../../range_desc,
|
|
|
|
"."/[hexary_desc, hexary_error, hexary_nearby, hexary_paths]
|
|
|
|
|
|
|
|
type
|
|
|
|
RangeLeaf* = object
|
|
|
|
key*: NodeKey ## Leaf node path
|
|
|
|
data*: Blob ## Leaf node data
|
|
|
|
|
|
|
|
RangeProof* = object
|
2023-03-07 14:23:22 +00:00
|
|
|
base*: NodeTag ## No node between `base` and `leafs[0]`
|
|
|
|
leafs*: seq[RangeLeaf] ## List of consecutive leaf nodes
|
2023-03-10 17:10:30 +00:00
|
|
|
leafsLast*: bool ## If no leaf exceeds `max(base,leafs[])`
|
2023-03-07 14:23:22 +00:00
|
|
|
leafsSize*: int ## RLP encoded size of `leafs` on wire
|
|
|
|
proof*: seq[SnapProof] ## Boundary proof
|
|
|
|
proofSize*: int ## RLP encoded size of `proof` on wire
|
2023-02-15 10:14:40 +00:00
|
|
|
|
2023-03-10 17:10:30 +00:00
|
|
|
const
|
|
|
|
proofNodeSizeMax = 532
|
|
|
|
## Branch node with all branches `high(UInt256)` within RLP list
|
|
|
|
|
|
|
|
veryLongDuration = 60.weeks
|
|
|
|
## Longer than any collection of data will probably take
|
|
|
|
|
2023-02-15 10:14:40 +00:00
|
|
|
proc hexaryRangeRlpLeafListSize*(blobLen: int; lstLen = 0): (int,int) {.gcsafe.}
|
|
|
|
proc hexaryRangeRlpSize*(blobLen: int): int {.gcsafe.}
|
2023-01-30 17:50:58 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc convertTo(key: RepairKey; T: type NodeKey): T =
|
|
|
|
## Might be lossy, check before use (if at all, unless debugging)
|
|
|
|
(addr result.ByteArray32[0]).copyMem(unsafeAddr key.ByteArray33[1], 32)
|
|
|
|
|
2023-02-15 10:14:40 +00:00
|
|
|
proc rlpPairSize(aLen: int; bRlpLen: int): int =
|
|
|
|
## Size caclualation for an RLP encoded pair `[<a>,<rb>]` for blobs `a` and
|
|
|
|
## rlp encoded `rb` argument length `aLen` and `bRlpLen`.
|
|
|
|
let aRlpLen = hexaryRangeRlpSize(aLen)
|
|
|
|
if bRlpLen < high(int) - aRlpLen:
|
|
|
|
hexaryRangeRlpSize(aRlpLen + bRlpLen)
|
|
|
|
else:
|
|
|
|
high(int)
|
|
|
|
|
2023-03-10 17:10:30 +00:00
|
|
|
proc timeIsOver(stopAt: Moment): bool =
|
|
|
|
## Helper (avoids `chronos` import when running generic function)
|
|
|
|
stopAt <= chronos.Moment.now()
|
|
|
|
|
|
|
|
proc stopAt(timeout: chronos.Duration): Moment =
|
|
|
|
## Helper (avoids `chronos` import when running generic function)
|
|
|
|
chronos.Moment.now() + timeout
|
|
|
|
|
2023-03-02 09:57:58 +00:00
|
|
|
proc nonLeafPathNodes(
|
2023-03-07 14:23:22 +00:00
|
|
|
nodeTag: NodeTag; # Left boundary
|
2023-03-02 09:57:58 +00:00
|
|
|
rootKey: NodeKey|RepairKey; # State root
|
|
|
|
db: HexaryGetFn|HexaryTreeDbRef; # Database abstraction
|
2023-03-03 20:01:59 +00:00
|
|
|
): HashSet[SnapProof]
|
2023-03-02 09:57:58 +00:00
|
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
|
|
## Helper for `updateProof()`
|
2023-03-07 14:23:22 +00:00
|
|
|
nodeTag
|
2023-03-02 09:57:58 +00:00
|
|
|
.hexaryPath(rootKey, db)
|
|
|
|
.path
|
|
|
|
.mapIt(it.node)
|
|
|
|
.filterIt(it.kind != Leaf)
|
2023-03-03 20:01:59 +00:00
|
|
|
.mapIt(it.convertTo(Blob).to(SnapProof))
|
2023-03-02 09:57:58 +00:00
|
|
|
.toHashSet
|
|
|
|
|
2023-03-07 14:23:22 +00:00
|
|
|
proc allPathNodes(
|
|
|
|
nodeTag: NodeTag; # Left boundary
|
|
|
|
rootKey: NodeKey|RepairKey; # State root
|
|
|
|
db: HexaryGetFn|HexaryTreeDbRef; # Database abstraction
|
|
|
|
): HashSet[SnapProof]
|
|
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
|
|
## Helper for `updateProof()`
|
|
|
|
nodeTag
|
|
|
|
.hexaryPath(rootKey, db)
|
|
|
|
.path
|
|
|
|
.mapIt(it.node)
|
|
|
|
.mapIt(it.convertTo(Blob).to(SnapProof))
|
|
|
|
.toHashSet
|
|
|
|
|
2023-01-30 17:50:58 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
template collectLeafs(
|
|
|
|
db: HexaryGetFn|HexaryTreeDbRef; # Database abstraction
|
2023-02-02 13:27:09 +00:00
|
|
|
rootKey: NodeKey|RepairKey; # State root
|
|
|
|
iv: NodeTagRange; # Proofed range of leaf paths
|
2023-02-15 10:14:40 +00:00
|
|
|
nSizeLimit: int; # List of RLP encoded data must be smaller
|
2023-03-10 17:10:30 +00:00
|
|
|
stopAt: Moment; # limit search time
|
2023-01-30 17:50:58 +00:00
|
|
|
): auto =
|
|
|
|
## Collect trie database leafs prototype. This directive is provided as
|
|
|
|
## `template` for avoiding varying exceprion annotations.
|
2023-03-17 14:46:50 +00:00
|
|
|
var
|
|
|
|
rc: Result[RangeProof,HexaryError]
|
|
|
|
ttd = stopAt
|
2023-01-30 17:50:58 +00:00
|
|
|
block body:
|
2023-03-07 14:23:22 +00:00
|
|
|
let
|
|
|
|
nodeMax = maxPt(iv) # `inject` is for debugging (if any)
|
2023-01-30 17:50:58 +00:00
|
|
|
var
|
2023-02-14 23:38:33 +00:00
|
|
|
nodeTag = minPt(iv)
|
2023-01-30 17:50:58 +00:00
|
|
|
prevTag: NodeTag
|
2023-03-07 14:23:22 +00:00
|
|
|
rls: RangeProof
|
|
|
|
|
|
|
|
# Set up base node, the nearest node before `iv.minPt`
|
2023-03-10 17:10:30 +00:00
|
|
|
if 0.to(NodeTag) < nodeTag:
|
2023-03-17 14:46:50 +00:00
|
|
|
let rx = nodeTag.hexaryNearbyLeft(rootKey, db)
|
2023-03-07 14:23:22 +00:00
|
|
|
if rx.isOk:
|
2023-03-17 14:46:50 +00:00
|
|
|
rls.base = rx.value
|
2023-03-25 10:44:48 +00:00
|
|
|
elif rx.error != NearbyBeyondRange:
|
2023-03-07 14:23:22 +00:00
|
|
|
rc = typeof(rc).err(rx.error)
|
|
|
|
break body
|
2023-01-30 17:50:58 +00:00
|
|
|
|
2023-03-10 17:10:30 +00:00
|
|
|
# Fill leaf nodes (at least one) from interval range unless size reached
|
|
|
|
while nodeTag <= nodeMax or rls.leafs.len == 0:
|
2023-01-30 17:50:58 +00:00
|
|
|
# The following logic might be sub-optimal. A strict version of the
|
|
|
|
# `next()` function that stops with an error at dangling links could
|
|
|
|
# be faster if the leaf nodes are not too far apart on the hexary trie.
|
2023-04-14 22:28:57 +00:00
|
|
|
let
|
2023-01-30 17:50:58 +00:00
|
|
|
xPath = block:
|
|
|
|
let rx = nodeTag.hexaryPath(rootKey,db).hexaryNearbyRight(db)
|
|
|
|
if rx.isErr:
|
2023-03-25 10:44:48 +00:00
|
|
|
if rx.error != NearbyBeyondRange:
|
2023-03-10 17:10:30 +00:00
|
|
|
rc = typeof(rc).err(rx.error)
|
|
|
|
else:
|
|
|
|
rls.leafsLast = true
|
|
|
|
rc = typeof(rc).ok(rls) # done ok, last node reached
|
2023-01-30 17:50:58 +00:00
|
|
|
break body
|
|
|
|
rx.value
|
2023-03-02 09:57:58 +00:00
|
|
|
rightKey = getPartialPath(xPath).convertTo(NodeKey)
|
2023-01-30 17:50:58 +00:00
|
|
|
rightTag = rightKey.to(NodeTag)
|
|
|
|
|
|
|
|
# Prevents from semi-endless looping
|
2023-03-07 14:23:22 +00:00
|
|
|
if rightTag <= prevTag and 0 < rls.leafs.len:
|
2023-03-17 14:46:50 +00:00
|
|
|
# Oops, should have been tackled by `hexaryNearbyRight()`
|
2023-01-30 17:50:58 +00:00
|
|
|
rc = typeof(rc).err(FailedNextNode)
|
|
|
|
break body # stop here
|
|
|
|
|
2023-02-15 10:14:40 +00:00
|
|
|
let (pairLen,listLen) =
|
2023-03-07 14:23:22 +00:00
|
|
|
hexaryRangeRlpLeafListSize(xPath.leafData.len, rls.leafsSize)
|
|
|
|
|
2023-03-10 17:10:30 +00:00
|
|
|
if listLen <= nSizeLimit:
|
2023-03-07 14:23:22 +00:00
|
|
|
rls.leafsSize += pairLen
|
2023-02-15 10:14:40 +00:00
|
|
|
else:
|
2023-03-10 17:10:30 +00:00
|
|
|
break # collected enough
|
2023-02-15 10:14:40 +00:00
|
|
|
|
2023-03-07 14:23:22 +00:00
|
|
|
rls.leafs.add RangeLeaf(
|
2023-01-30 17:50:58 +00:00
|
|
|
key: rightKey,
|
|
|
|
data: xPath.leafData)
|
|
|
|
|
2023-03-17 14:46:50 +00:00
|
|
|
if timeIsOver(ttd):
|
2023-03-10 17:10:30 +00:00
|
|
|
break # timout
|
|
|
|
|
2023-01-30 17:50:58 +00:00
|
|
|
prevTag = nodeTag
|
|
|
|
nodeTag = rightTag + 1.u256
|
2023-03-07 14:23:22 +00:00
|
|
|
# End loop
|
|
|
|
|
|
|
|
# Count outer RLP wrapper
|
|
|
|
if 0 < rls.leafs.len:
|
|
|
|
rls.leafsSize = hexaryRangeRlpSize rls.leafsSize
|
2023-01-30 17:50:58 +00:00
|
|
|
|
|
|
|
rc = typeof(rc).ok(rls)
|
|
|
|
# End body
|
|
|
|
|
|
|
|
rc
|
|
|
|
|
|
|
|
|
|
|
|
template updateProof(
|
2023-02-02 13:27:09 +00:00
|
|
|
db: HexaryGetFn|HexaryTreeDbRef; # Database abstraction
|
|
|
|
rootKey: NodeKey|RepairKey; # State root
|
2023-03-07 14:23:22 +00:00
|
|
|
rls: RangeProof; # Set of collected leafs and a `base`
|
2023-01-30 17:50:58 +00:00
|
|
|
): auto =
|
2023-02-02 13:27:09 +00:00
|
|
|
## Complement leafs list by adding proof nodes. This directive is provided as
|
2023-01-30 17:50:58 +00:00
|
|
|
## `template` for avoiding varying exceprion annotations.
|
2023-03-07 14:23:22 +00:00
|
|
|
var rp = rls
|
2023-03-10 17:10:30 +00:00
|
|
|
|
|
|
|
if 0.to(NodeTag) < rp.base or not rp.leafsLast:
|
|
|
|
var proof = allPathNodes(rls.base, rootKey, db)
|
|
|
|
if 0 < rls.leafs.len:
|
|
|
|
proof.incl nonLeafPathNodes(rls.leafs[^1].key.to(NodeTag), rootKey, db)
|
|
|
|
|
|
|
|
rp.proof = toSeq(proof)
|
|
|
|
rp.proofSize = hexaryRangeRlpSize rp.proof.foldl(a + b.to(Blob).len, 0)
|
2023-02-15 10:14:40 +00:00
|
|
|
|
|
|
|
rp
|
2023-01-30 17:50:58 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc hexaryRangeLeafsProof*(
|
2023-02-14 23:38:33 +00:00
|
|
|
db: HexaryGetFn|HexaryTreeDbRef; # Database abstraction
|
2023-01-30 17:50:58 +00:00
|
|
|
rootKey: NodeKey; # State root
|
2023-02-02 13:27:09 +00:00
|
|
|
iv: NodeTagRange; # Proofed range of leaf paths
|
2023-02-15 10:14:40 +00:00
|
|
|
nSizeLimit = high(int); # List of RLP encoded data must be smaller
|
2023-03-10 17:10:30 +00:00
|
|
|
timeout = veryLongDuration; # Limit retrieval time
|
2023-01-30 17:50:58 +00:00
|
|
|
): Result[RangeProof,HexaryError]
|
2023-02-14 23:38:33 +00:00
|
|
|
{.gcsafe, raises: [CatchableError]} =
|
2023-02-02 13:27:09 +00:00
|
|
|
## Collect trie database leafs prototype and add proof.
|
2023-03-10 17:10:30 +00:00
|
|
|
let rc = db.collectLeafs(rootKey, iv, nSizeLimit, stopAt(timeout))
|
2023-01-30 17:50:58 +00:00
|
|
|
if rc.isErr:
|
|
|
|
err(rc.error)
|
|
|
|
else:
|
2023-03-07 14:23:22 +00:00
|
|
|
ok(db.updateProof(rootKey, rc.value))
|
2023-01-30 17:50:58 +00:00
|
|
|
|
|
|
|
proc hexaryRangeLeafsProof*(
|
2023-02-14 23:38:33 +00:00
|
|
|
db: HexaryGetFn|HexaryTreeDbRef; # Database abstraction
|
2023-02-02 13:27:09 +00:00
|
|
|
rootKey: NodeKey; # State root
|
2023-03-07 14:23:22 +00:00
|
|
|
rp: RangeProof; # Set of collected leafs and a `base`
|
2023-01-30 17:50:58 +00:00
|
|
|
): RangeProof
|
2023-02-14 23:38:33 +00:00
|
|
|
{.gcsafe, raises: [CatchableError]} =
|
2023-02-02 13:27:09 +00:00
|
|
|
## Complement leafs list by adding proof nodes to the argument list
|
|
|
|
## `leafList`.
|
2023-03-07 14:23:22 +00:00
|
|
|
db.updateProof(rootKey, rp)
|
2023-02-15 10:14:40 +00:00
|
|
|
|
2023-03-25 10:44:48 +00:00
|
|
|
|
|
|
|
proc hexaryRangeInflate*(
|
|
|
|
db: HexaryGetFn|HexaryTreeDbRef; # Database abstraction
|
|
|
|
rootKey: NodeKey; # State root
|
|
|
|
nodeKey: NodeTag; # Centre of inflated interval
|
|
|
|
): NodeTagRange
|
|
|
|
{.gcsafe, raises: [CatchableError]} =
|
|
|
|
## Calculate the largest leaf range interval containing only the argument
|
|
|
|
## `nodeKey`.
|
|
|
|
##
|
|
|
|
## If the database is fully allocated, then the returned interval ends right
|
|
|
|
## before or after the next neighbour leaf node, or at the range type
|
|
|
|
## boundaries `low(NodeTag)` or `high(NodeTag)`.
|
|
|
|
##
|
|
|
|
## If the database is partially allocated only and some of the neighbour
|
|
|
|
## nodes are missing, the returned interval is not extended towards this
|
|
|
|
## end.
|
|
|
|
var
|
|
|
|
leftPt = nodeKey
|
|
|
|
rightPt = nodeKey
|
|
|
|
|
|
|
|
if low(NodeTag) < nodeKey:
|
|
|
|
let
|
|
|
|
pt = nodeKey - 1.u256
|
|
|
|
rc = pt.hexaryPath(rootKey,db).hexaryNearbyLeft(db)
|
|
|
|
if rc.isOk:
|
|
|
|
leftPt = rc.value.getPartialPath.convertTo(NodeKey).to(NodeTag) + 1.u256
|
|
|
|
elif rc.error == NearbyBeyondRange:
|
|
|
|
leftPt = low(NodeTag)
|
|
|
|
|
|
|
|
if nodeKey < high(NodeTag):
|
|
|
|
let
|
|
|
|
pt = nodeKey + 1.u256
|
|
|
|
rc = pt.hexaryPath(rootKey,db).hexaryNearbyRight(db)
|
|
|
|
if rc.isOk:
|
|
|
|
rightPt = rc.value.getPartialPath.convertTo(NodeKey).to(NodeTag) - 1.u256
|
|
|
|
elif rc.error == NearbyBeyondRange:
|
|
|
|
rightPt = high(NodeTag)
|
|
|
|
|
|
|
|
NodeTagRange.new(leftPt, rightPt)
|
|
|
|
|
2023-02-15 10:14:40 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc hexaryRangeRlpSize*(blobLen: int): int =
|
|
|
|
## Returns the size of RLP encoded <blob> of argument length `blobLen`.
|
|
|
|
if blobLen < 56:
|
|
|
|
return blobLen + 1
|
|
|
|
if blobLen < (1 shl (8 * 1)):
|
|
|
|
return blobLen + 2
|
|
|
|
if blobLen < (1 shl (8 * 2)):
|
|
|
|
return blobLen + 3
|
|
|
|
if blobLen < (1 shl (8 * 3)):
|
|
|
|
return blobLen + 4
|
|
|
|
|
|
|
|
when sizeof(int) < 8:
|
|
|
|
if blobLen < (1 shl (8 * 4)):
|
|
|
|
return blobLen + 5
|
|
|
|
if blobLen < (1 shl (8 * 5)):
|
|
|
|
return blobLen + 6
|
|
|
|
if blobLen < (1 shl (8 * 6)):
|
|
|
|
return blobLen + 7
|
|
|
|
if blobLen < (1 shl (8 * 7)):
|
|
|
|
return blobLen + 8
|
|
|
|
|
|
|
|
if blobLen < high(int) - (1 + sizeof(int)):
|
|
|
|
blobLen + 1 + sizeof(int)
|
|
|
|
else:
|
|
|
|
high(int)
|
|
|
|
|
|
|
|
proc hexaryRangeRlpLeafListSize*(blobLen: int; lstLen = 0): (int,int) =
|
|
|
|
## Size caclualation for an RLP encoded list `[[<key>,<blob>],a,b,..]`
|
|
|
|
## where a,b,.. are from a sequence of the same format `[<keyA>,<blobA>]`,
|
|
|
|
## `[<keyB>,<blobB>]`,... The size of blob is the argument size `blobLen`,
|
|
|
|
## and the toral size of the sequence is `listLen`.
|
|
|
|
##
|
|
|
|
## The fuction returns `(x,y)`, the size `x` of the RLP encoded pair
|
|
|
|
## `[<key>,<blob>]` and the total size `y` of the complete RLP encoded list
|
|
|
|
## `[[<key>,<blob>],a,b,..]`.
|
|
|
|
let pairLen = blobLen.rlpPairSize(33)
|
|
|
|
if lstLen == 0:
|
|
|
|
(pairLen, hexaryRangeRlpSize(pairLen))
|
|
|
|
elif lstLen < high(int) - lstLen:
|
|
|
|
(pairLen, hexaryRangeRlpSize(pairLen + lstLen))
|
|
|
|
else:
|
|
|
|
(pairLen, high(int))
|
2023-01-30 17:50:58 +00:00
|
|
|
|
2023-03-10 17:10:30 +00:00
|
|
|
proc hexaryRangeRlpNodesListSizeMax*(n: int): int =
|
|
|
|
## Maximal size needs to RLP encode `n` nodes (handy for calculating the
|
|
|
|
## space needed to store proof nodes.)
|
|
|
|
const nMax = high(int) div proofNodeSizeMax
|
|
|
|
if n <= nMax:
|
|
|
|
hexaryRangeRlpSize(n * proofNodeSizeMax)
|
|
|
|
else:
|
|
|
|
high(int)
|
|
|
|
|
2023-01-30 17:50:58 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|