mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-13 13:55:45 +00:00
Prepare snap server client test scenario cont3 (#1491)
* Handle last/all node(s) proof conditions at leaf node extractor detail: Flag whether the maximum extracted node is the last one in database No proof needed if the full tree was extracted * Clean up some helpers & definitions details: Move entities to more plausible locations, e.g. `Account` object need not be dealt with in the range extractor as it applies to any kind of leaf data. * Fix next/prev database walk fringe condition details: First check needed might be for a leaf node which was done too late. * Homogenise snap/1 protocol function prototypes why: The range arguments `origin` and `limit` data types differed in various function prototypes (`Hash256` vs. `openArray[byte]`.) * Implement `GetStorageRange` handler * Implement server timeout for leaf node retrieval why: This feature leaves control on the server for probably costly action invoked by the network * Implement maximal reply size for snap service why: This feature leaves control on the server for probably costly action invoked by the network.
This commit is contained in:
parent
d8a1adacaa
commit
2f7f2dba2d
@ -13,12 +13,13 @@
|
|||||||
import
|
import
|
||||||
std/sequtils,
|
std/sequtils,
|
||||||
chronicles,
|
chronicles,
|
||||||
eth/p2p,
|
chronos,
|
||||||
stew/interval_set,
|
eth/[p2p, trie/trie_defs],
|
||||||
|
stew/[byteutils, interval_set],
|
||||||
../../db/db_chain,
|
../../db/db_chain,
|
||||||
../../core/chain,
|
../../core/chain,
|
||||||
../snap/range_desc,
|
../snap/[constants, range_desc],
|
||||||
../snap/worker/db/[hexary_desc, hexary_range],
|
../snap/worker/db/[hexary_desc, hexary_paths, hexary_range],
|
||||||
../protocol,
|
../protocol,
|
||||||
../protocol/snap/snap_types
|
../protocol/snap/snap_types
|
||||||
|
|
||||||
@ -28,13 +29,26 @@ logScope:
|
|||||||
type
|
type
|
||||||
SnapWireRef* = ref object of SnapWireBase
|
SnapWireRef* = ref object of SnapWireBase
|
||||||
chain: ChainRef
|
chain: ChainRef
|
||||||
|
elaFetchMax: chronos.Duration
|
||||||
|
dataSizeMax: int
|
||||||
peerPool: PeerPool
|
peerPool: PeerPool
|
||||||
|
|
||||||
const
|
const
|
||||||
proofNodeSizeMax = 532
|
extraTraceMessages = false or true
|
||||||
## Branch node with all branches `high(UInt256)` within RLP list
|
## Enabled additional logging noise
|
||||||
|
|
||||||
proc proofNodesSizeMax*(n: int): int {.gcsafe.}
|
estimatedProofSize = hexaryRangeRlpNodesListSizeMax(10)
|
||||||
|
## Some expected upper limit, typically not mote than 10 proof nodes
|
||||||
|
|
||||||
|
emptySnapStorageList = seq[SnapStorage].default
|
||||||
|
## Dummy list for empty slots
|
||||||
|
|
||||||
|
defaultElaFetchMax = 1500.milliseconds
|
||||||
|
## Fetching accounts or slots can be extensive, stop in the middle if
|
||||||
|
## it takes too long
|
||||||
|
|
||||||
|
defaultDataSizeMax = fetchRequestBytesLimit
|
||||||
|
## Truncate maximum data size
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Private functions: helpers
|
# Private functions: helpers
|
||||||
@ -46,38 +60,85 @@ template logTxt(info: static[string]): static[string] =
|
|||||||
proc notImplemented(name: string) {.used.} =
|
proc notImplemented(name: string) {.used.} =
|
||||||
debug "Wire handler method not implemented", meth=name
|
debug "Wire handler method not implemented", meth=name
|
||||||
|
|
||||||
proc getAccountFn(chain: ChainRef): HexaryGetFn {.gcsafe.} =
|
# ----------------------------------
|
||||||
|
|
||||||
|
proc getAccountFn(
|
||||||
|
chain: ChainRef;
|
||||||
|
): HexaryGetFn
|
||||||
|
{.gcsafe.} =
|
||||||
let db = chain.com.db.db
|
let db = chain.com.db.db
|
||||||
return proc(key: openArray[byte]): Blob = db.get(key)
|
return proc(key: openArray[byte]): Blob =
|
||||||
|
db.get(key)
|
||||||
|
|
||||||
|
proc getStorageSlotsFn(
|
||||||
|
chain: ChainRef;
|
||||||
|
accKey: NodeKey;
|
||||||
|
): HexaryGetFn
|
||||||
|
{.gcsafe.} =
|
||||||
|
let db = chain.com.db.db
|
||||||
|
return proc(key: openArray[byte]): Blob =
|
||||||
|
db.get(key)
|
||||||
|
|
||||||
|
# ----------------------------------
|
||||||
|
|
||||||
|
proc to(
|
||||||
|
rl: RangeLeaf;
|
||||||
|
T: type SnapAccount;
|
||||||
|
): T
|
||||||
|
{.gcsafe, raises: [RlpError].} =
|
||||||
|
## Convert the generic `RangeLeaf` argument to payload type.
|
||||||
|
T(accHash: rl.key.to(Hash256),
|
||||||
|
accBody: rl.data.decode(Account))
|
||||||
|
|
||||||
|
proc to(
|
||||||
|
rl: RangeLeaf;
|
||||||
|
T: type SnapStorage;
|
||||||
|
): T
|
||||||
|
{.gcsafe.} =
|
||||||
|
## Convert the generic `RangeLeaf` argument to payload type.
|
||||||
|
T(slotHash: rl.key.to(Hash256),
|
||||||
|
slotData: rl.data)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Private functions: fetch leaf range
|
# Private functions: fetch leaf range
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc mkNodeTagRange(
|
||||||
|
origin: openArray[byte];
|
||||||
|
limit: openArray[byte];
|
||||||
|
): Result[NodeTagRange,void] =
|
||||||
|
var (minPt, maxPt) = (low(NodeTag), high(NodeTag))
|
||||||
|
|
||||||
|
if 0 < origin.len or 0 < limit.len:
|
||||||
|
if not minPt.init(origin) or not maxPt.init(limit) or maxPt <= minPt:
|
||||||
|
when extraTraceMessages:
|
||||||
|
trace logTxt "mkNodeTagRange: malformed range", origin, limit
|
||||||
|
return err()
|
||||||
|
|
||||||
|
ok(NodeTagRange.new(minPt, maxPt))
|
||||||
|
|
||||||
|
|
||||||
proc fetchLeafRange(
|
proc fetchLeafRange(
|
||||||
ctx: SnapWireRef; # Handler descriptor
|
ctx: SnapWireRef; # Handler descriptor
|
||||||
db: HexaryGetFn; # Database abstraction
|
db: HexaryGetFn; # Database abstraction
|
||||||
root: Hash256; # State root
|
root: Hash256; # State root
|
||||||
iv: NodeTagRange; # Proofed range of leaf paths
|
iv: NodeTagRange; # Proofed range of leaf paths
|
||||||
replySizeMax: int; # Updated size counter for the raw list
|
replySizeMax: int; # Updated size counter for the raw list
|
||||||
|
stopAt: Moment; # Implies timeout
|
||||||
): Result[RangeProof,void]
|
): Result[RangeProof,void]
|
||||||
{.gcsafe, raises: [CatchableError].} =
|
{.gcsafe, raises: [CatchableError].} =
|
||||||
let
|
|
||||||
rootKey = root.to(NodeKey)
|
|
||||||
estimatedProofSize = proofNodesSizeMax(10) # some expected upper limit
|
|
||||||
|
|
||||||
if replySizeMax <= estimatedProofSize:
|
|
||||||
trace logTxt "fetchLeafRange(): data size too small", iv, replySizeMax
|
|
||||||
return err() # package size too small
|
|
||||||
|
|
||||||
# Assemble result Note that the size limit is the size of the leaf nodes
|
# Assemble result Note that the size limit is the size of the leaf nodes
|
||||||
# on wire. So the `sizeMax` is the argument size `replySizeMax` with some
|
# on wire. So the `sizeMax` is the argument size `replySizeMax` with some
|
||||||
# space removed to accomodate for the proof nodes.
|
# space removed to accomodate for the proof nodes.
|
||||||
let
|
let
|
||||||
|
rootKey = root.to(NodeKey)
|
||||||
sizeMax = replySizeMax - estimatedProofSize
|
sizeMax = replySizeMax - estimatedProofSize
|
||||||
rc = db.hexaryRangeLeafsProof(rootKey, iv, sizeMax)
|
now = Moment.now()
|
||||||
|
timeout = if now < stopAt: stopAt - now else: 1.milliseconds
|
||||||
|
rc = db.hexaryRangeLeafsProof(rootKey, iv, sizeMax, timeout)
|
||||||
if rc.isErr:
|
if rc.isErr:
|
||||||
error logTxt "fetchLeafRange(): database problem",
|
debug logTxt "fetchLeafRange: database problem",
|
||||||
iv, replySizeMax, error=rc.error
|
iv, replySizeMax, error=rc.error
|
||||||
return err() # database error
|
return err() # database error
|
||||||
let sizeOnWire = rc.value.leafsSize + rc.value.proofSize
|
let sizeOnWire = rc.value.leafsSize + rc.value.proofSize
|
||||||
@ -98,7 +159,7 @@ proc fetchLeafRange(
|
|||||||
tailSize += rpl.leafs[leafsTop - tailItems].data.len + extraSize
|
tailSize += rpl.leafs[leafsTop - tailItems].data.len + extraSize
|
||||||
tailItems.inc
|
tailItems.inc
|
||||||
if leafsTop <= tailItems:
|
if leafsTop <= tailItems:
|
||||||
trace logTxt "fetchLeafRange(): stripping leaf list failed",
|
debug logTxt "fetchLeafRange: stripping leaf list failed",
|
||||||
iv, replySizeMax, leafsTop, tailItems
|
iv, replySizeMax, leafsTop, tailItems
|
||||||
return err() # package size too small
|
return err() # package size too small
|
||||||
|
|
||||||
@ -109,7 +170,7 @@ proc fetchLeafRange(
|
|||||||
if strippedSizeOnWire <= replySizeMax:
|
if strippedSizeOnWire <= replySizeMax:
|
||||||
return ok(leafProof)
|
return ok(leafProof)
|
||||||
|
|
||||||
trace logTxt "fetchLeafRange(): data size problem",
|
debug logTxt "fetchLeafRange: data size problem",
|
||||||
iv, replySizeMax, leafsTop, tailItems, strippedSizeOnWire
|
iv, replySizeMax, leafsTop, tailItems, strippedSizeOnWire
|
||||||
|
|
||||||
err()
|
err()
|
||||||
@ -149,6 +210,8 @@ proc init*(
|
|||||||
## Constructor (uses `init()` as suggested in style guide.)
|
## Constructor (uses `init()` as suggested in style guide.)
|
||||||
let ctx = T(
|
let ctx = T(
|
||||||
chain: chain,
|
chain: chain,
|
||||||
|
elaFetchMax: defaultElaFetchMax,
|
||||||
|
dataSizeMax: defaultDataSizeMax,
|
||||||
peerPool: peerPool)
|
peerPool: peerPool)
|
||||||
|
|
||||||
#ctx.setupPeerObserver()
|
#ctx.setupPeerObserver()
|
||||||
@ -158,16 +221,6 @@ proc init*(
|
|||||||
# Public functions: helpers
|
# Public functions: helpers
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
proc proofNodesSizeMax*(n: int): int =
|
|
||||||
## Max number of bytes needed to store a list of `n` RLP encoded hexary
|
|
||||||
## nodes which is a `Branch` node where every link reference is set to
|
|
||||||
## `high(UInt256)`.
|
|
||||||
const nMax = high(int) div proofNodeSizeMax
|
|
||||||
if n <= nMax:
|
|
||||||
hexaryRangeRlpSize(n * proofNodeSizeMax)
|
|
||||||
else:
|
|
||||||
high(int)
|
|
||||||
|
|
||||||
proc proofEncode*(proof: seq[SnapProof]): Blob =
|
proc proofEncode*(proof: seq[SnapProof]): Blob =
|
||||||
var writer = initRlpWriter()
|
var writer = initRlpWriter()
|
||||||
writer.snapAppend SnapProofNodes(nodes: proof)
|
writer.snapAppend SnapProofNodes(nodes: proof)
|
||||||
@ -184,23 +237,41 @@ proc proofDecode*(data: Blob): seq[SnapProof] {.gcsafe, raises: [RlpError].} =
|
|||||||
method getAccountRange*(
|
method getAccountRange*(
|
||||||
ctx: SnapWireRef;
|
ctx: SnapWireRef;
|
||||||
root: Hash256;
|
root: Hash256;
|
||||||
origin: Hash256;
|
origin: openArray[byte];
|
||||||
limit: Hash256;
|
limit: openArray[byte];
|
||||||
replySizeMax: uint64;
|
replySizeMax: uint64;
|
||||||
): (seq[SnapAccount], SnapProofNodes)
|
): (seq[SnapAccount], SnapProofNodes)
|
||||||
{.gcsafe, raises: [CatchableError].} =
|
{.gcsafe, raises: [CatchableError].} =
|
||||||
## Fetch accounts list from database
|
## Fetch accounts list from database
|
||||||
|
let sizeMax = min(replySizeMax, ctx.dataSizeMax.uint64).int
|
||||||
|
if sizeMax <= estimatedProofSize:
|
||||||
|
when extraTraceMessages:
|
||||||
|
trace logTxt "getAccountRange: max data size too small",
|
||||||
|
origin=origin.toHex, limit=limit.toHex, sizeMax
|
||||||
|
return # package size too small
|
||||||
|
|
||||||
let
|
let
|
||||||
|
iv = block: # Calculate effective accounts range (if any)
|
||||||
|
let rc = origin.mkNodeTagRange limit
|
||||||
|
if rc.isErr:
|
||||||
|
return
|
||||||
|
rc.value # malformed interval
|
||||||
|
|
||||||
db = ctx.chain.getAccountFn
|
db = ctx.chain.getAccountFn
|
||||||
iv = NodeTagRange.new(origin.to(NodeTag), limit.to(NodeTag))
|
stopAt = Moment.now() + ctx.elaFetchMax
|
||||||
sizeMax = min(replySizeMax,high(int).uint64).int
|
rc = ctx.fetchLeafRange(db, root, iv, sizeMax, stopAt)
|
||||||
|
|
||||||
trace logTxt "getAccountRange(): request data range", iv, replySizeMax
|
if rc.isErr:
|
||||||
|
return # extraction failed
|
||||||
|
let
|
||||||
|
accounts = rc.value.leafs.mapIt(it.to(SnapAccount))
|
||||||
|
proof = rc.value.proof
|
||||||
|
|
||||||
let rc = ctx.fetchLeafRange(db, root, iv, sizeMax)
|
#when extraTraceMessages:
|
||||||
if rc.isOk:
|
# trace logTxt "getAccountRange: done", iv, replySizeMax,
|
||||||
result[0] = rc.value.leafs.mapIt(it.to(SnapAccount))
|
# nAccounts=accounts.len, nProof=proof.len
|
||||||
result[1] = SnapProofNodes(nodes: rc.value.proof)
|
|
||||||
|
(accounts, SnapProofNodes(nodes: proof))
|
||||||
|
|
||||||
|
|
||||||
method getStorageRanges*(
|
method getStorageRanges*(
|
||||||
@ -211,8 +282,91 @@ method getStorageRanges*(
|
|||||||
limit: openArray[byte];
|
limit: openArray[byte];
|
||||||
replySizeMax: uint64;
|
replySizeMax: uint64;
|
||||||
): (seq[seq[SnapStorage]], SnapProofNodes)
|
): (seq[seq[SnapStorage]], SnapProofNodes)
|
||||||
{.gcsafe.} =
|
{.gcsafe, raises: [CatchableError].} =
|
||||||
notImplemented("getStorageRanges")
|
## Fetch storage slots list from database
|
||||||
|
let sizeMax = min(replySizeMax, ctx.dataSizeMax.uint64).int
|
||||||
|
if sizeMax <= estimatedProofSize:
|
||||||
|
when extraTraceMessages:
|
||||||
|
trace logTxt "getStorageRanges: max data size too small",
|
||||||
|
origin=origin.toHex, limit=limit.toHex, sizeMax
|
||||||
|
return # package size too small
|
||||||
|
|
||||||
|
let
|
||||||
|
iv = block: # Calculate effective slots range (if any)
|
||||||
|
let rc = origin.mkNodeTagRange limit
|
||||||
|
if rc.isErr:
|
||||||
|
return
|
||||||
|
rc.value # malformed interval
|
||||||
|
|
||||||
|
accGetFn = ctx.chain.getAccountFn
|
||||||
|
rootKey = root.to(NodeKey)
|
||||||
|
stopAt = Moment.now() + ctx.elaFetchMax
|
||||||
|
|
||||||
|
# Loop over accounts
|
||||||
|
var
|
||||||
|
dataAllocated = 0
|
||||||
|
timeExceeded = false
|
||||||
|
slotLists: seq[seq[SnapStorage]]
|
||||||
|
proof: seq[SnapProof]
|
||||||
|
for accHash in accounts:
|
||||||
|
let
|
||||||
|
accKey = accHash.to(NodeKey)
|
||||||
|
accData = accKey.hexaryPath(rootKey, accGetFn).leafData
|
||||||
|
|
||||||
|
# Ignore missing account entry
|
||||||
|
if accData.len == 0:
|
||||||
|
slotLists.add emptySnapStorageList
|
||||||
|
dataAllocated.inc # empty list
|
||||||
|
when extraTraceMessages:
|
||||||
|
trace logTxt "getStorageRanges: no data", iv, sizeMax, dataAllocated,
|
||||||
|
accDataLen=accData.len
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ignore empty storage list
|
||||||
|
let stoRoot = rlp.decode(accData,Account).storageRoot
|
||||||
|
if stoRoot == emptyRlpHash:
|
||||||
|
slotLists.add emptySnapStorageList
|
||||||
|
dataAllocated.inc # empty list
|
||||||
|
trace logTxt "getStorageRanges: no slots", iv, sizeMax, dataAllocated,
|
||||||
|
accDataLen=accData.len, stoRoot
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Collect data slots for this account
|
||||||
|
let
|
||||||
|
db = ctx.chain.getStorageSlotsFn(accKey)
|
||||||
|
rc = ctx.fetchLeafRange(db, stoRoot, iv, sizeMax - dataAllocated, stopAt)
|
||||||
|
if rc.isErr:
|
||||||
|
when extraTraceMessages:
|
||||||
|
trace logTxt "getStorageRanges: failed", iv, sizeMax, dataAllocated,
|
||||||
|
accDataLen=accData.len, stoRoot
|
||||||
|
return # extraction failed
|
||||||
|
|
||||||
|
# Process data slots for this account
|
||||||
|
dataAllocated += rc.value.leafsSize
|
||||||
|
|
||||||
|
#trace logTxt "getStorageRanges: data slots", iv, sizeMax, dataAllocated,
|
||||||
|
# accKey, stoRoot, nSlots=rc.value.leafs.len, nProof=rc.value.proof.len
|
||||||
|
|
||||||
|
slotLists.add rc.value.leafs.mapIt(it.to(SnapStorage))
|
||||||
|
if 0 < rc.value.proof.len:
|
||||||
|
proof = rc.value.proof
|
||||||
|
break # only last entry has a proof
|
||||||
|
|
||||||
|
# Stop unless there is enough space left
|
||||||
|
if sizeMax - dataAllocated <= estimatedProofSize:
|
||||||
|
break
|
||||||
|
|
||||||
|
if stopAt <= Moment.now():
|
||||||
|
timeExceeded = true
|
||||||
|
break
|
||||||
|
|
||||||
|
when extraTraceMessages:
|
||||||
|
trace logTxt "getStorageRanges: done", iv, sizeMax, dataAllocated,
|
||||||
|
nAccounts=accounts.len, nLeafLists=slotLists.len, nProof=proof.len,
|
||||||
|
timeExceeded
|
||||||
|
|
||||||
|
(slotLists, SnapProofNodes(nodes: proof))
|
||||||
|
|
||||||
|
|
||||||
method getByteCodes*(
|
method getByteCodes*(
|
||||||
ctx: SnapWireRef;
|
ctx: SnapWireRef;
|
||||||
|
@ -155,8 +155,8 @@ proc notImplemented(name: string) =
|
|||||||
method getAccountRange*(
|
method getAccountRange*(
|
||||||
ctx: SnapWireBase;
|
ctx: SnapWireBase;
|
||||||
root: Hash256;
|
root: Hash256;
|
||||||
origin: Hash256;
|
origin: openArray[byte];
|
||||||
limit: Hash256;
|
limit: openArray[byte];
|
||||||
replySizeMax: uint64;
|
replySizeMax: uint64;
|
||||||
): (seq[SnapAccount], SnapProofNodes)
|
): (seq[SnapAccount], SnapProofNodes)
|
||||||
{.base, raises: [CatchableError].} =
|
{.base, raises: [CatchableError].} =
|
||||||
@ -170,7 +170,7 @@ method getStorageRanges*(
|
|||||||
limit: openArray[byte];
|
limit: openArray[byte];
|
||||||
replySizeMax: uint64;
|
replySizeMax: uint64;
|
||||||
): (seq[seq[SnapStorage]], SnapProofNodes)
|
): (seq[seq[SnapStorage]], SnapProofNodes)
|
||||||
{.base.} =
|
{.base, raises: [CatchableError].} =
|
||||||
notImplemented("getStorageRanges")
|
notImplemented("getStorageRanges")
|
||||||
|
|
||||||
method getByteCodes*(
|
method getByteCodes*(
|
||||||
|
@ -9,129 +9,9 @@
|
|||||||
# at your option. This file may not be copied, modified, or distributed
|
# at your option. This file may not be copied, modified, or distributed
|
||||||
# except according to those terms.
|
# except according to those terms.
|
||||||
|
|
||||||
## This module implements `snap/1`, the `Ethereum Snapshot Protocol (SNAP)
|
## This module implements Ethereum Snapshot Protocol version 1, `snap/1`.
|
||||||
## <https://github.com/ethereum/devp2p/blob/master/caps/snap.md>`_.
|
## Specification:
|
||||||
##
|
## `snap/1 <https://github.com/ethereum/devp2p/blob/master/caps/snap.md>`_
|
||||||
## Modified `GetStorageRanges` (0x02) message syntax
|
|
||||||
## -------------------------------------------------
|
|
||||||
## As implementes here, the request message is encoded as
|
|
||||||
##
|
|
||||||
## `[reqID, rootHash, accountHashes, origin, limit, responseBytes]`
|
|
||||||
##
|
|
||||||
## It requests the storage slots of multiple accounts' storage tries. Since
|
|
||||||
## certain contracts have huge state, the method can also request storage
|
|
||||||
## slots from a single account, starting at a specific storage key hash.
|
|
||||||
## The intended purpose of this message is to fetch a large number of
|
|
||||||
## subsequent storage slots from a remote node and reconstruct a state
|
|
||||||
## subtrie locally.
|
|
||||||
##
|
|
||||||
## * `reqID`: Request ID to match up responses with
|
|
||||||
## * `rootHash`: 32 byte root hash of the account trie to serve
|
|
||||||
## * `accountHashes`: Array of 32 byte account hashes of the storage tries to serve
|
|
||||||
## * `origin`: Storage slot hash fragment of the first to retrieve (see below)
|
|
||||||
## * `limit`: Storage slot hash fragment after which to stop serving (see below)
|
|
||||||
## * `responseBytes`: 64 bit number soft limit at which to stop returning data
|
|
||||||
##
|
|
||||||
## Discussion of *Geth* `GetStorageRanges` behaviour
|
|
||||||
## ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
## - Parameters `origin` and `limit` may each be empty blobs, which mean "all
|
|
||||||
## zeros" (0x00000...) or "no limit" (0xfffff...) respectively.
|
|
||||||
##
|
|
||||||
## (Blobs shorter than 32 bytes can also be given, and they are extended with
|
|
||||||
## zero bytes; longer than 32 bytes can be given and are truncated, but this
|
|
||||||
## is *Geth* being too accepting, and shouldn't be used.)
|
|
||||||
##
|
|
||||||
## - In the `slots` reply, the last account's storage list may be empty even if
|
|
||||||
## that account has non-empty storage.
|
|
||||||
##
|
|
||||||
## This happens when the bytes threshold is reached just after finishing
|
|
||||||
## storage for the previous account, or when `origin` is greater than the
|
|
||||||
## first account's last storage slot. When either of these happens, `proof`
|
|
||||||
## is non-empty. In the case of `origin` zero or empty, the non-empty proof
|
|
||||||
## only contains the left-side boundary proof, because it meets the condition
|
|
||||||
## for omitting the right-side proof described in the next point.
|
|
||||||
##
|
|
||||||
## - In the `proof` reply, the right-side boundary proof is only included if
|
|
||||||
## the last returned storage slot has non-zero path and `origin != 0`, or if
|
|
||||||
## the result stops due to reaching the bytes threshold.
|
|
||||||
##
|
|
||||||
## Because there's only one proof anyway if left-side and right-side are the
|
|
||||||
## same path, this works out to mean the right-side proof is omitted in cases
|
|
||||||
## where `origin == 0` and the result stops at a slot `>= limit` before
|
|
||||||
## reaching the bytes threshold.
|
|
||||||
##
|
|
||||||
## Although the specification doesn't say anything about `limit`, this is
|
|
||||||
## against the spirit of the specification rule, which says the right-side
|
|
||||||
## proof is always included if the last returned path differs from the
|
|
||||||
## starting hash.
|
|
||||||
##
|
|
||||||
## The omitted right-side proof can cause problems when using `limit`.
|
|
||||||
## In other words, when doing range queries, or merging results from
|
|
||||||
## pipelining where different `stateRoot` hashes are used as time progresses.
|
|
||||||
## Workarounds:
|
|
||||||
##
|
|
||||||
## * Fetch the proof using a second `GetStorageRanges` query with non-zero
|
|
||||||
## `origin` (perhaps equal to `limit`; use `origin = 1` if `limit == 0`).
|
|
||||||
##
|
|
||||||
## * Avoid the condition by using `origin >= 1` when using `limit`.
|
|
||||||
##
|
|
||||||
## * Use trie node traversal (`snap` `GetTrieNodes`) to obtain the omitted proof.
|
|
||||||
##
|
|
||||||
## - When multiple accounts are requested with `origin > 0`, only one account's
|
|
||||||
## storage is returned. There is no point requesting multiple accounts with
|
|
||||||
## `origin > 0`. (It might be useful if it treated `origin` as applying to
|
|
||||||
## only the first account, but it doesn't.)
|
|
||||||
##
|
|
||||||
## - When multiple accounts are requested with non-default `limit` and
|
|
||||||
## `origin == 0`, and the first account result stops at a slot `>= limit`
|
|
||||||
## before reaching the bytes threshold, storage for the other accounts in the
|
|
||||||
## request are returned as well. The other accounts are not limited by
|
|
||||||
## `limit`, only the bytes threshold. The right-side proof is omitted from
|
|
||||||
## `proof` when this happens, because this is the same condition as described
|
|
||||||
## earlier for omitting the right-side proof. (It might be useful if it
|
|
||||||
## treated `origin` as applying to only the first account and `limit` to only
|
|
||||||
## the last account, but it doesn't.)
|
|
||||||
##
|
|
||||||
##
|
|
||||||
## Performance benefits
|
|
||||||
## --------------------
|
|
||||||
## `snap` is used for much higher performance transfer of the entire Ethereum
|
|
||||||
## execution state (accounts, storage, bytecode) compared with hexary trie
|
|
||||||
## traversal using the now obsolete `eth/66` `GetNodeData`.
|
|
||||||
##
|
|
||||||
## It improves both network and local storage performance. The benefits are
|
|
||||||
## substantial, and summarised here:
|
|
||||||
##
|
|
||||||
## - `Ethereum Snapshot Protocol (SNAP) - Expected results
|
|
||||||
## <https://github.com/ethereum/devp2p/blob/master/caps/snap.md>`_
|
|
||||||
## - `Geth v1.10.0 - Snap sync
|
|
||||||
## <https://blog.ethereum.org/2021/03/03/geth-v1-10-0/#snap-sync>`_
|
|
||||||
##
|
|
||||||
## In the Snap sync model, local storage benefits require clients to adopt a
|
|
||||||
## different representation of Ethereum state than the trie storage that *Geth*
|
|
||||||
## (and most clients) traditionally used, and still do in archive mode,
|
|
||||||
##
|
|
||||||
## However, Nimbus's sync method obtains similar local storage benefits
|
|
||||||
## whichever network protocol is used. Nimbus uses `snap` protocol because it
|
|
||||||
## is a more efficient network protocol.
|
|
||||||
##
|
|
||||||
## Distributed hash table (DHT) building block
|
|
||||||
## -------------------------------------------
|
|
||||||
## Although `snap` was designed for bootstrapping clients with the entire
|
|
||||||
## Ethereum state, it is well suited to fetching only a subset of path ranges.
|
|
||||||
## This may be useful for bootstrapping distributed hash tables (DHTs).
|
|
||||||
##
|
|
||||||
## Path range metadata benefits
|
|
||||||
## ----------------------------
|
|
||||||
## Because data is handled in path ranges, this allows a compact metadata
|
|
||||||
## representation of what data is stored locally and what isn't, compared with
|
|
||||||
## the size of a representation of partially completed trie traversal with
|
|
||||||
## `eth` `GetNodeData`. Due to the smaller metadata, after aborting a partial
|
|
||||||
## sync and restarting, it is possible to resume quickly, without waiting for
|
|
||||||
## the very slow local database scan associated with older versions of *Geth*.
|
|
||||||
##
|
|
||||||
## However, Nimbus's sync method uses this principle as inspiration to
|
|
||||||
## obtain similar metadata benefits whichever network protocol is used.
|
|
||||||
|
|
||||||
import
|
import
|
||||||
std/options,
|
std/options,
|
||||||
@ -192,16 +72,15 @@ p2pProtocol snap1(version = snapVersion,
|
|||||||
|
|
||||||
requestResponse:
|
requestResponse:
|
||||||
# User message 0x00: GetAccountRange.
|
# User message 0x00: GetAccountRange.
|
||||||
# Note: `origin` and `limit` differs from the specification to match Geth.
|
|
||||||
proc getAccountRange(
|
proc getAccountRange(
|
||||||
peer: Peer;
|
peer: Peer;
|
||||||
root: Hash256;
|
root: Hash256;
|
||||||
origin: Hash256;
|
origin: openArray[byte];
|
||||||
limit: Hash256;
|
limit: openArray[byte];
|
||||||
replySizeMax: uint64;
|
replySizeMax: uint64;
|
||||||
) =
|
) =
|
||||||
trace trSnapRecvReceived & "GetAccountRange (0x00)", peer, root,
|
trace trSnapRecvReceived & "GetAccountRange (0x00)", peer, root,
|
||||||
origin, limit, replySizeMax
|
nOrigin=origin.len, nLimit=limit.len, replySizeMax
|
||||||
|
|
||||||
let
|
let
|
||||||
ctx = peer.networkState()
|
ctx = peer.networkState()
|
||||||
@ -229,7 +108,6 @@ p2pProtocol snap1(version = snapVersion,
|
|||||||
|
|
||||||
requestResponse:
|
requestResponse:
|
||||||
# User message 0x02: GetStorageRanges.
|
# User message 0x02: GetStorageRanges.
|
||||||
# Note: `origin` and `limit` differs from the specification to match Geth.
|
|
||||||
proc getStorageRanges(
|
proc getStorageRanges(
|
||||||
peer: Peer;
|
peer: Peer;
|
||||||
root: Hash256;
|
root: Hash256;
|
||||||
|
@ -45,7 +45,8 @@ proc getAccountRangeReq(
|
|||||||
peer = buddy.peer
|
peer = buddy.peer
|
||||||
try:
|
try:
|
||||||
let reply = await peer.getAccountRange(
|
let reply = await peer.getAccountRange(
|
||||||
root, iv.minPt.to(Hash256), iv.maxPt.to(Hash256), fetchRequestBytesLimit)
|
root, iv.minPt.to(Hash256).data, iv.maxPt.to(Hash256).data,
|
||||||
|
fetchRequestBytesLimit)
|
||||||
return ok(reply)
|
return ok(reply)
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
let error {.used.} = e.msg
|
let error {.used.} = e.msg
|
||||||
|
@ -393,8 +393,6 @@ proc hexaryNearbyRight*(
|
|||||||
if topLink.isZero or not db.tab.hasKey(topLink):
|
if topLink.isZero or not db.tab.hasKey(topLink):
|
||||||
return err(NearbyDanglingLink) # error
|
return err(NearbyDanglingLink) # error
|
||||||
|
|
||||||
let nextNibble = rPath.tail[0].int8
|
|
||||||
if start and nextNibble < 15:
|
|
||||||
let nextNode = db.tab[topLink]
|
let nextNode = db.tab[topLink]
|
||||||
case nextNode.kind
|
case nextNode.kind
|
||||||
of Leaf:
|
of Leaf:
|
||||||
@ -404,6 +402,8 @@ proc hexaryNearbyRight*(
|
|||||||
if rPath.tail <= nextNode.ePfx:
|
if rPath.tail <= nextNode.ePfx:
|
||||||
return rPath.completeLeast(topLink, db)
|
return rPath.completeLeast(topLink, db)
|
||||||
of Branch:
|
of Branch:
|
||||||
|
let nextNibble = rPath.tail[0].int8
|
||||||
|
if start and nextNibble < 15:
|
||||||
# Step down and complete with a branch link on the child node
|
# Step down and complete with a branch link on the child node
|
||||||
step = RPathStep(
|
step = RPathStep(
|
||||||
key: topLink,
|
key: topLink,
|
||||||
@ -484,14 +484,14 @@ proc hexaryNearbyRight*(
|
|||||||
if topLink.len == 0 or topLink.getFn().len == 0:
|
if topLink.len == 0 or topLink.getFn().len == 0:
|
||||||
return err(NearbyDanglingLink) # error
|
return err(NearbyDanglingLink) # error
|
||||||
|
|
||||||
let nextNibble = xPath.tail[0].int8
|
|
||||||
if nextNibble < 15:
|
|
||||||
let nextNodeRlp = rlpFromBytes topLink.getFn()
|
let nextNodeRlp = rlpFromBytes topLink.getFn()
|
||||||
case nextNodeRlp.listLen:
|
case nextNodeRlp.listLen:
|
||||||
of 2:
|
of 2:
|
||||||
if xPath.tail <= nextNodeRlp.listElem(0).toBytes.hexPrefixDecode[1]:
|
if xPath.tail <= nextNodeRlp.listElem(0).toBytes.hexPrefixDecode[1]:
|
||||||
return xPath.completeLeast(topLink, getFn)
|
return xPath.completeLeast(topLink, getFn)
|
||||||
of 17:
|
of 17:
|
||||||
|
let nextNibble = xPath.tail[0].int8
|
||||||
|
if nextNibble < 15:
|
||||||
# Step down and complete with a branch link on the child node
|
# Step down and complete with a branch link on the child node
|
||||||
step = XPathStep(
|
step = XPathStep(
|
||||||
key: topLink,
|
key: topLink,
|
||||||
@ -616,8 +616,6 @@ proc hexaryNearbyLeft*(
|
|||||||
if topLink.isZero or not db.tab.hasKey(topLink):
|
if topLink.isZero or not db.tab.hasKey(topLink):
|
||||||
return err(NearbyDanglingLink) # error
|
return err(NearbyDanglingLink) # error
|
||||||
|
|
||||||
let nextNibble = rPath.tail[0].int8
|
|
||||||
if 0 < nextNibble:
|
|
||||||
let nextNode = db.tab[topLink]
|
let nextNode = db.tab[topLink]
|
||||||
case nextNode.kind
|
case nextNode.kind
|
||||||
of Leaf:
|
of Leaf:
|
||||||
@ -627,6 +625,8 @@ proc hexaryNearbyLeft*(
|
|||||||
if nextNode.ePfx <= rPath.tail:
|
if nextNode.ePfx <= rPath.tail:
|
||||||
return rPath.completeMost(topLink, db)
|
return rPath.completeMost(topLink, db)
|
||||||
of Branch:
|
of Branch:
|
||||||
|
let nextNibble = rPath.tail[0].int8
|
||||||
|
if 0 < nextNibble:
|
||||||
# Step down and complete with a branch link on the child node
|
# Step down and complete with a branch link on the child node
|
||||||
step = RPathStep(
|
step = RPathStep(
|
||||||
key: topLink,
|
key: topLink,
|
||||||
@ -708,14 +708,14 @@ proc hexaryNearbyLeft*(
|
|||||||
if topLink.len == 0 or topLink.getFn().len == 0:
|
if topLink.len == 0 or topLink.getFn().len == 0:
|
||||||
return err(NearbyDanglingLink) # error
|
return err(NearbyDanglingLink) # error
|
||||||
|
|
||||||
let nextNibble = xPath.tail[0].int8
|
|
||||||
if 0 < nextNibble:
|
|
||||||
let nextNodeRlp = rlpFromBytes topLink.getFn()
|
let nextNodeRlp = rlpFromBytes topLink.getFn()
|
||||||
case nextNodeRlp.listLen:
|
case nextNodeRlp.listLen:
|
||||||
of 2:
|
of 2:
|
||||||
if nextNodeRlp.listElem(0).toBytes.hexPrefixDecode[1] <= xPath.tail:
|
if nextNodeRlp.listElem(0).toBytes.hexPrefixDecode[1] <= xPath.tail:
|
||||||
return xPath.completeMost(topLink, getFn)
|
return xPath.completeMost(topLink, getFn)
|
||||||
of 17:
|
of 17:
|
||||||
|
let nextNibble = xPath.tail[0].int8
|
||||||
|
if 0 < nextNibble:
|
||||||
# Step down and complete with a branch link on the child node
|
# Step down and complete with a branch link on the child node
|
||||||
step = XPathStep(
|
step = XPathStep(
|
||||||
key: topLink,
|
key: topLink,
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
import
|
import
|
||||||
std/[sequtils, sets, tables],
|
std/[sequtils, sets, tables],
|
||||||
|
chronos,
|
||||||
eth/[common, p2p, trie/nibbles],
|
eth/[common, p2p, trie/nibbles],
|
||||||
stew/[byteutils, interval_set],
|
stew/[byteutils, interval_set],
|
||||||
../../../protocol,
|
../../../protocol,
|
||||||
@ -26,10 +27,18 @@ type
|
|||||||
RangeProof* = object
|
RangeProof* = object
|
||||||
base*: NodeTag ## No node between `base` and `leafs[0]`
|
base*: NodeTag ## No node between `base` and `leafs[0]`
|
||||||
leafs*: seq[RangeLeaf] ## List of consecutive leaf nodes
|
leafs*: seq[RangeLeaf] ## List of consecutive leaf nodes
|
||||||
|
leafsLast*: bool ## If no leaf exceeds `max(base,leafs[])`
|
||||||
leafsSize*: int ## RLP encoded size of `leafs` on wire
|
leafsSize*: int ## RLP encoded size of `leafs` on wire
|
||||||
proof*: seq[SnapProof] ## Boundary proof
|
proof*: seq[SnapProof] ## Boundary proof
|
||||||
proofSize*: int ## RLP encoded size of `proof` on wire
|
proofSize*: int ## RLP encoded size of `proof` on wire
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
proc hexaryRangeRlpLeafListSize*(blobLen: int; lstLen = 0): (int,int) {.gcsafe.}
|
proc hexaryRangeRlpLeafListSize*(blobLen: int; lstLen = 0): (int,int) {.gcsafe.}
|
||||||
proc hexaryRangeRlpSize*(blobLen: int): int {.gcsafe.}
|
proc hexaryRangeRlpSize*(blobLen: int): int {.gcsafe.}
|
||||||
|
|
||||||
@ -50,6 +59,14 @@ proc rlpPairSize(aLen: int; bRlpLen: int): int =
|
|||||||
else:
|
else:
|
||||||
high(int)
|
high(int)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
proc nonLeafPathNodes(
|
proc nonLeafPathNodes(
|
||||||
nodeTag: NodeTag; # Left boundary
|
nodeTag: NodeTag; # Left boundary
|
||||||
rootKey: NodeKey|RepairKey; # State root
|
rootKey: NodeKey|RepairKey; # State root
|
||||||
@ -88,6 +105,7 @@ template collectLeafs(
|
|||||||
rootKey: NodeKey|RepairKey; # State root
|
rootKey: NodeKey|RepairKey; # State root
|
||||||
iv: NodeTagRange; # Proofed range of leaf paths
|
iv: NodeTagRange; # Proofed range of leaf paths
|
||||||
nSizeLimit: int; # List of RLP encoded data must be smaller
|
nSizeLimit: int; # List of RLP encoded data must be smaller
|
||||||
|
stopAt: Moment; # limit search time
|
||||||
): auto =
|
): auto =
|
||||||
## Collect trie database leafs prototype. This directive is provided as
|
## Collect trie database leafs prototype. This directive is provided as
|
||||||
## `template` for avoiding varying exceprion annotations.
|
## `template` for avoiding varying exceprion annotations.
|
||||||
@ -102,16 +120,16 @@ template collectLeafs(
|
|||||||
rls: RangeProof
|
rls: RangeProof
|
||||||
|
|
||||||
# Set up base node, the nearest node before `iv.minPt`
|
# Set up base node, the nearest node before `iv.minPt`
|
||||||
block:
|
if 0.to(NodeTag) < nodeTag:
|
||||||
let rx = nodeTag.hexaryPath(rootKey,db).hexaryNearbyLeft(db)
|
let rx = nodeTag.hexaryPath(rootKey,db).hexaryNearbyLeft(db)
|
||||||
if rx.isOk:
|
if rx.isOk:
|
||||||
rls.base = getPartialPath(rx.value).convertTo(NodeKey).to(NodeTag)
|
rls.base = getPartialPath(rx.value).convertTo(NodeKey).to(NodeTag)
|
||||||
elif rx.error != NearbyFailed:
|
elif rx.error notin {NearbyFailed,NearbyEmptyPath}:
|
||||||
rc = typeof(rc).err(rx.error)
|
rc = typeof(rc).err(rx.error)
|
||||||
break body
|
break body
|
||||||
|
|
||||||
# Fill leaf nodes from interval range unless size reached
|
# Fill leaf nodes (at least one) from interval range unless size reached
|
||||||
while nodeTag <= nodeMax:
|
while nodeTag <= nodeMax or rls.leafs.len == 0:
|
||||||
# The following logic might be sub-optimal. A strict version of the
|
# The following logic might be sub-optimal. A strict version of the
|
||||||
# `next()` function that stops with an error at dangling links could
|
# `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.
|
# be faster if the leaf nodes are not too far apart on the hexary trie.
|
||||||
@ -119,7 +137,11 @@ template collectLeafs(
|
|||||||
xPath = block:
|
xPath = block:
|
||||||
let rx = nodeTag.hexaryPath(rootKey,db).hexaryNearbyRight(db)
|
let rx = nodeTag.hexaryPath(rootKey,db).hexaryNearbyRight(db)
|
||||||
if rx.isErr:
|
if rx.isErr:
|
||||||
|
if rx.error notin {NearbyFailed,NearbyEmptyPath}:
|
||||||
rc = typeof(rc).err(rx.error)
|
rc = typeof(rc).err(rx.error)
|
||||||
|
else:
|
||||||
|
rls.leafsLast = true
|
||||||
|
rc = typeof(rc).ok(rls) # done ok, last node reached
|
||||||
break body
|
break body
|
||||||
rx.value
|
rx.value
|
||||||
rightKey = getPartialPath(xPath).convertTo(NodeKey)
|
rightKey = getPartialPath(xPath).convertTo(NodeKey)
|
||||||
@ -134,15 +156,18 @@ template collectLeafs(
|
|||||||
let (pairLen,listLen) =
|
let (pairLen,listLen) =
|
||||||
hexaryRangeRlpLeafListSize(xPath.leafData.len, rls.leafsSize)
|
hexaryRangeRlpLeafListSize(xPath.leafData.len, rls.leafsSize)
|
||||||
|
|
||||||
if listLen < nSizeLimit:
|
if listLen <= nSizeLimit:
|
||||||
rls.leafsSize += pairLen
|
rls.leafsSize += pairLen
|
||||||
else:
|
else:
|
||||||
break
|
break # collected enough
|
||||||
|
|
||||||
rls.leafs.add RangeLeaf(
|
rls.leafs.add RangeLeaf(
|
||||||
key: rightKey,
|
key: rightKey,
|
||||||
data: xPath.leafData)
|
data: xPath.leafData)
|
||||||
|
|
||||||
|
if timeIsOver(stopAt):
|
||||||
|
break # timout
|
||||||
|
|
||||||
prevTag = nodeTag
|
prevTag = nodeTag
|
||||||
nodeTag = rightTag + 1.u256
|
nodeTag = rightTag + 1.u256
|
||||||
# End loop
|
# End loop
|
||||||
@ -164,11 +189,13 @@ template updateProof(
|
|||||||
): auto =
|
): auto =
|
||||||
## Complement leafs list by adding proof nodes. This directive is provided as
|
## Complement leafs list by adding proof nodes. This directive is provided as
|
||||||
## `template` for avoiding varying exceprion annotations.
|
## `template` for avoiding varying exceprion annotations.
|
||||||
|
var rp = rls
|
||||||
|
|
||||||
|
if 0.to(NodeTag) < rp.base or not rp.leafsLast:
|
||||||
var proof = allPathNodes(rls.base, rootKey, db)
|
var proof = allPathNodes(rls.base, rootKey, db)
|
||||||
if 0 < rls.leafs.len:
|
if 0 < rls.leafs.len:
|
||||||
proof.incl nonLeafPathNodes(rls.leafs[^1].key.to(NodeTag), rootKey, db)
|
proof.incl nonLeafPathNodes(rls.leafs[^1].key.to(NodeTag), rootKey, db)
|
||||||
|
|
||||||
var rp = rls
|
|
||||||
rp.proof = toSeq(proof)
|
rp.proof = toSeq(proof)
|
||||||
rp.proofSize = hexaryRangeRlpSize rp.proof.foldl(a + b.to(Blob).len, 0)
|
rp.proofSize = hexaryRangeRlpSize rp.proof.foldl(a + b.to(Blob).len, 0)
|
||||||
|
|
||||||
@ -183,10 +210,11 @@ proc hexaryRangeLeafsProof*(
|
|||||||
rootKey: NodeKey; # State root
|
rootKey: NodeKey; # State root
|
||||||
iv: NodeTagRange; # Proofed range of leaf paths
|
iv: NodeTagRange; # Proofed range of leaf paths
|
||||||
nSizeLimit = high(int); # List of RLP encoded data must be smaller
|
nSizeLimit = high(int); # List of RLP encoded data must be smaller
|
||||||
|
timeout = veryLongDuration; # Limit retrieval time
|
||||||
): Result[RangeProof,HexaryError]
|
): Result[RangeProof,HexaryError]
|
||||||
{.gcsafe, raises: [CatchableError]} =
|
{.gcsafe, raises: [CatchableError]} =
|
||||||
## Collect trie database leafs prototype and add proof.
|
## Collect trie database leafs prototype and add proof.
|
||||||
let rc = db.collectLeafs(rootKey, iv, nSizeLimit)
|
let rc = db.collectLeafs(rootKey, iv, nSizeLimit, stopAt(timeout))
|
||||||
if rc.isErr:
|
if rc.isErr:
|
||||||
err(rc.error)
|
err(rc.error)
|
||||||
else:
|
else:
|
||||||
@ -206,16 +234,6 @@ proc hexaryRangeLeafsProof*(
|
|||||||
# Public helpers
|
# Public helpers
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
proc to*(
|
|
||||||
rl: RangeLeaf;
|
|
||||||
T: type SnapAccount;
|
|
||||||
): T
|
|
||||||
{.gcsafe, raises: [RlpError]} =
|
|
||||||
## Convert the generic `RangeLeaf` argument to payload type.
|
|
||||||
T(accHash: rl.key.to(Hash256),
|
|
||||||
accBody: rl.data.decode(Account))
|
|
||||||
|
|
||||||
|
|
||||||
proc hexaryRangeRlpSize*(blobLen: int): int =
|
proc hexaryRangeRlpSize*(blobLen: int): int =
|
||||||
## Returns the size of RLP encoded <blob> of argument length `blobLen`.
|
## Returns the size of RLP encoded <blob> of argument length `blobLen`.
|
||||||
if blobLen < 56:
|
if blobLen < 56:
|
||||||
@ -259,6 +277,15 @@ proc hexaryRangeRlpLeafListSize*(blobLen: int; lstLen = 0): (int,int) =
|
|||||||
else:
|
else:
|
||||||
(pairLen, high(int))
|
(pairLen, high(int))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# End
|
# End
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -137,6 +137,13 @@ proc init*(
|
|||||||
## Constructor variant
|
## Constructor variant
|
||||||
HexaryTreeDbRef.init(ps.base)
|
HexaryTreeDbRef.init(ps.base)
|
||||||
|
|
||||||
|
proc init*(
|
||||||
|
T: type HexaryTreeDbRef;
|
||||||
|
): T =
|
||||||
|
## Constructor variant. It provides a `HexaryTreeDbRef()` with a key key cache attached
|
||||||
|
## for pretty printing. So this one is mainly for debugging.
|
||||||
|
HexaryTreeDbRef.init(SnapDbRef())
|
||||||
|
|
||||||
# ---------------
|
# ---------------
|
||||||
|
|
||||||
proc init*(
|
proc init*(
|
||||||
|
@ -136,8 +136,9 @@ proc accountsRangefetchImpl(
|
|||||||
let error = rc.error
|
let error = rc.error
|
||||||
if await buddy.ctrl.stopAfterSeriousComError(error, buddy.only.errors):
|
if await buddy.ctrl.stopAfterSeriousComError(error, buddy.only.errors):
|
||||||
when extraTraceMessages:
|
when extraTraceMessages:
|
||||||
|
let reqLen {.used.} = $iv
|
||||||
trace logTxt "fetch error", peer, ctx=buddy.fetchCtx(env),
|
trace logTxt "fetch error", peer, ctx=buddy.fetchCtx(env),
|
||||||
reqLen=iv.len, error
|
reqLen, error
|
||||||
return
|
return
|
||||||
rc.value
|
rc.value
|
||||||
|
|
||||||
@ -168,8 +169,9 @@ proc accountsRangefetchImpl(
|
|||||||
# Bad data, just try another peer
|
# Bad data, just try another peer
|
||||||
buddy.ctrl.zombie = true
|
buddy.ctrl.zombie = true
|
||||||
when extraTraceMessages:
|
when extraTraceMessages:
|
||||||
|
let reqLen {.used.} = $iv
|
||||||
trace logTxt "import failed", peer, ctx=buddy.fetchCtx(env),
|
trace logTxt "import failed", peer, ctx=buddy.fetchCtx(env),
|
||||||
gotAccounts, gotStorage, reqLen=iv.len, covered, error=rc.error
|
gotAccounts, gotStorage, reqLen, covered, error=rc.error
|
||||||
return
|
return
|
||||||
rc.value
|
rc.value
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ proc test_calcProofsListSizes*() =
|
|||||||
#echo "+++ ", n, " ", nodeBlobsEncoded.rlpFromBytes.inspect
|
#echo "+++ ", n, " ", nodeBlobsEncoded.rlpFromBytes.inspect
|
||||||
#echo ">>> ", n, " ", nodeBlobsHex
|
#echo ">>> ", n, " ", nodeBlobsHex
|
||||||
#echo "<<< ", n, " ", brNodesHex
|
#echo "<<< ", n, " ", brNodesHex
|
||||||
check nodeBlobsEncoded.len == n.proofNodesSizeMax
|
check nodeBlobsEncoded.len == n.hexaryRangeRlpNodesListSizeMax
|
||||||
check nodeBlobsDecoded == nodeSample
|
check nodeBlobsDecoded == nodeSample
|
||||||
check nodeBlobsHex == brNodesHex
|
check nodeBlobsHex == brNodesHex
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user