2022-10-08 17:20:50 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2018-2021 Status Research & Development GmbH
|
|
|
|
# Licensed and distributed under either of
|
2022-09-16 07:24:12 +00:00
|
|
|
# * 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-22 20:11:49 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
import
|
|
|
|
std/[options, sequtils],
|
|
|
|
chronos,
|
2022-10-20 16:59:54 +00:00
|
|
|
eth/[common, p2p],
|
2022-09-16 07:24:12 +00:00
|
|
|
"../../.."/[protocol, protocol/trace_config],
|
2022-11-01 15:07:44 +00:00
|
|
|
"../.."/[constants, range_desc, worker_desc],
|
2022-10-08 17:20:50 +00:00
|
|
|
./com_error
|
2022-09-16 07:24:12 +00:00
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "snap-fetch"
|
|
|
|
|
|
|
|
type
|
|
|
|
# SnapTrieNodes = object
|
|
|
|
# nodes*: seq[Blob]
|
|
|
|
|
|
|
|
GetTrieNodes* = object
|
2023-03-22 20:11:49 +00:00
|
|
|
leftOver*: seq[SnapTriePaths] ## Unprocessed data
|
|
|
|
nodes*: seq[NodeSpecs] ## `nodeKey` field unused with `NodeSpecs`
|
|
|
|
|
|
|
|
ProcessReplyStep = object
|
|
|
|
leftOver: SnapTriePaths # Unprocessed data sets
|
|
|
|
nodes: seq[NodeSpecs] # Processed nodes
|
|
|
|
topInx: int # Index of first unprocessed item
|
2022-09-16 07:24:12 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc getTrieNodesReq(
|
|
|
|
buddy: SnapBuddyRef;
|
|
|
|
stateRoot: Hash256;
|
2023-03-22 20:11:49 +00:00
|
|
|
paths: seq[SnapTriePaths];
|
2022-11-01 15:07:44 +00:00
|
|
|
pivot: string;
|
2022-09-16 07:24:12 +00:00
|
|
|
): Future[Result[Option[SnapTrieNodes],void]]
|
|
|
|
{.async.} =
|
|
|
|
let
|
|
|
|
peer = buddy.peer
|
|
|
|
try:
|
2022-12-24 09:54:18 +00:00
|
|
|
let reply = await peer.getTrieNodes(
|
|
|
|
stateRoot, paths, fetchRequestBytesLimit)
|
2022-09-16 07:24:12 +00:00
|
|
|
return ok(reply)
|
|
|
|
|
|
|
|
except CatchableError as e:
|
2023-01-30 22:10:23 +00:00
|
|
|
let error {.used.} = e.msg
|
2022-11-01 15:07:44 +00:00
|
|
|
trace trSnapRecvError & "waiting for GetByteCodes reply", peer, pivot,
|
2023-01-30 22:10:23 +00:00
|
|
|
error
|
2022-09-16 07:24:12 +00:00
|
|
|
return err()
|
|
|
|
|
2023-03-22 20:11:49 +00:00
|
|
|
|
|
|
|
proc processReplyStep(
|
|
|
|
paths: SnapTriePaths;
|
|
|
|
nodeBlobs: seq[Blob];
|
|
|
|
startInx: int
|
|
|
|
): ProcessReplyStep =
|
|
|
|
## Process reply item, return unprocessed remainder
|
|
|
|
# Account node request
|
|
|
|
if paths.slotPaths.len == 0:
|
|
|
|
if nodeBlobs[startInx].len == 0:
|
|
|
|
result.leftOver.accPath = paths.accPath
|
|
|
|
else:
|
|
|
|
result.nodes.add NodeSpecs(
|
|
|
|
partialPath: paths.accPath,
|
|
|
|
data: nodeBlobs[startInx])
|
|
|
|
result.topInx = startInx + 1
|
|
|
|
return
|
|
|
|
|
|
|
|
# Storage paths request
|
|
|
|
let
|
|
|
|
nSlotPaths = paths.slotPaths.len
|
|
|
|
maxLen = min(nSlotPaths, nodeBlobs.len - startInx)
|
|
|
|
|
|
|
|
# Fill up nodes
|
|
|
|
for n in 0 ..< maxlen:
|
|
|
|
let nodeBlob = nodeBlobs[startInx + n]
|
|
|
|
if 0 < nodeBlob.len:
|
|
|
|
result.nodes.add NodeSpecs(
|
|
|
|
partialPath: paths.slotPaths[n],
|
|
|
|
data: nodeBlob)
|
|
|
|
else:
|
|
|
|
result.leftOver.slotPaths.add paths.slotPaths[n]
|
|
|
|
result.topInx = startInx + maxLen
|
|
|
|
|
|
|
|
# Was that all for this step? Otherwise add some left over.
|
|
|
|
if maxLen < nSlotPaths:
|
|
|
|
result.leftOver.slotPaths &= paths.slotPaths[maxLen ..< nSlotPaths]
|
|
|
|
|
|
|
|
if 0 < result.leftOver.slotPaths.len:
|
|
|
|
result.leftOver.accPath = paths.accPath
|
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc getTrieNodes*(
|
|
|
|
buddy: SnapBuddyRef;
|
2023-03-22 20:11:49 +00:00
|
|
|
stateRoot: Hash256; # Current DB base (see `pivot` for logging)
|
|
|
|
paths: seq[SnapTriePaths]; # Nodes to fetch
|
|
|
|
pivot: string; # For logging, instead of `stateRoot`
|
2022-09-16 07:24:12 +00:00
|
|
|
): Future[Result[GetTrieNodes,ComError]]
|
|
|
|
{.async.} =
|
|
|
|
## Fetch data using the `snap#` protocol, returns the trie nodes requested
|
|
|
|
## (if any.)
|
|
|
|
let
|
2023-01-30 22:10:23 +00:00
|
|
|
peer {.used.} = buddy.peer
|
2022-09-16 07:24:12 +00:00
|
|
|
nPaths = paths.len
|
|
|
|
|
|
|
|
if nPaths == 0:
|
|
|
|
return err(ComEmptyRequestArguments)
|
|
|
|
|
2023-03-22 20:11:49 +00:00
|
|
|
let nTotal = paths.mapIt(min(1,it.slotPaths.len)).foldl(a+b, 0)
|
2022-09-16 07:24:12 +00:00
|
|
|
|
|
|
|
if trSnapTracePacketsOk:
|
2022-12-24 09:54:18 +00:00
|
|
|
trace trSnapSendSending & "GetTrieNodes", peer, pivot, nPaths, nTotal
|
2022-09-16 07:24:12 +00:00
|
|
|
|
|
|
|
let trieNodes = block:
|
2022-11-01 15:07:44 +00:00
|
|
|
let rc = await buddy.getTrieNodesReq(stateRoot, paths, pivot)
|
2022-09-16 07:24:12 +00:00
|
|
|
if rc.isErr:
|
|
|
|
return err(ComNetworkProblem)
|
|
|
|
if rc.value.isNone:
|
2022-11-01 15:07:44 +00:00
|
|
|
trace trSnapRecvTimeoutWaiting & "for TrieNodes", peer, pivot, nPaths
|
2022-09-16 07:24:12 +00:00
|
|
|
return err(ComResponseTimeout)
|
|
|
|
let blobs = rc.value.get.nodes
|
|
|
|
if nTotal < blobs.len:
|
|
|
|
# Ooops, makes no sense
|
|
|
|
return err(ComTooManyTrieNodes)
|
|
|
|
blobs
|
|
|
|
|
|
|
|
let
|
|
|
|
nNodes = trieNodes.len
|
|
|
|
|
|
|
|
if nNodes == 0:
|
|
|
|
# github.com/ethereum/devp2p/blob/master/caps/snap.md#gettrienodes-0x06
|
|
|
|
#
|
|
|
|
# Notes:
|
|
|
|
# * Nodes must always respond to the query.
|
|
|
|
# * The returned nodes must be in the request order.
|
|
|
|
# * If the node does not have the state for the requested state root or for
|
|
|
|
# any requested account paths, it must return an empty reply. It is the
|
|
|
|
# responsibility of the caller to query an state not older than 128
|
|
|
|
# blocks; and the caller is expected to only ever query existing trie
|
|
|
|
# nodes.
|
|
|
|
# * The responding node is allowed to return less data than requested
|
|
|
|
# (serving QoS limits), but the node must return at least one trie node.
|
2022-11-01 15:07:44 +00:00
|
|
|
trace trSnapRecvReceived & "empty TrieNodes", peer, pivot, nPaths, nNodes
|
2022-09-16 07:24:12 +00:00
|
|
|
return err(ComNoByteCodesAvailable)
|
|
|
|
|
|
|
|
# Assemble return value
|
2023-03-22 20:11:49 +00:00
|
|
|
var
|
|
|
|
dd = GetTrieNodes()
|
|
|
|
inx = 0
|
|
|
|
for p in paths:
|
|
|
|
let step = p.processReplyStep(trieNodes, inx)
|
|
|
|
if 0 < step.leftOver.accPath.len or
|
|
|
|
0 < step.leftOver.slotPaths.len:
|
|
|
|
dd.leftOver.add step.leftOver
|
|
|
|
if 0 < step.nodes.len:
|
|
|
|
dd.nodes &= step.nodes
|
|
|
|
inx = step.topInx
|
|
|
|
if trieNodes.len <= inx:
|
|
|
|
break
|
2022-09-16 07:24:12 +00:00
|
|
|
|
2022-11-01 15:07:44 +00:00
|
|
|
trace trSnapRecvReceived & "TrieNodes", peer, pivot,
|
2022-09-16 07:24:12 +00:00
|
|
|
nPaths, nNodes, nLeftOver=dd.leftOver.len
|
|
|
|
|
|
|
|
return ok(dd)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|