nimbus-eth1/nimbus/sync/snap/worker/com/get_trie_nodes.nim

167 lines
5.2 KiB
Nim

# Nimbus
# Copyright (c) 2018-2021 Status Research & Development GmbH
# Licensed and distributed 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.
import
std/[options, sequtils],
chronos,
eth/[common, p2p],
"../../.."/[protocol, protocol/trace_config],
"../.."/[constants, range_desc, worker_desc],
./com_error
{.push raises: [].}
logScope:
topics = "snap-fetch"
type
# SnapTrieNodes = object
# nodes*: seq[Blob]
GetTrieNodes* = object
leftOver*: seq[seq[Blob]]
nodes*: seq[NodeSpecs] ## `nodeKey` field unused with `NodeSpecs`
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc getTrieNodesReq(
buddy: SnapBuddyRef;
stateRoot: Hash256;
paths: seq[seq[Blob]];
pivot: string;
): Future[Result[Option[SnapTrieNodes],void]]
{.async.} =
let
peer = buddy.peer
try:
let reply = await peer.getTrieNodes(
stateRoot, paths, fetchRequestBytesLimit)
return ok(reply)
except CatchableError as e:
let error {.used.} = e.msg
trace trSnapRecvError & "waiting for GetByteCodes reply", peer, pivot,
error
return err()
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc getTrieNodes*(
buddy: SnapBuddyRef;
stateRoot: Hash256; ## Current DB base (see `pivot` for logging)
paths: seq[seq[Blob]]; ## Nodes to fetch
pivot: string; ## For logging, instead of `stateRoot`
): Future[Result[GetTrieNodes,ComError]]
{.async.} =
## Fetch data using the `snap#` protocol, returns the trie nodes requested
## (if any.)
let
peer {.used.} = buddy.peer
nPaths = paths.len
if nPaths == 0:
return err(ComEmptyRequestArguments)
let nTotal = paths.mapIt(it.len).foldl(a+b, 0)
if trSnapTracePacketsOk:
trace trSnapSendSending & "GetTrieNodes", peer, pivot, nPaths, nTotal
let trieNodes = block:
let rc = await buddy.getTrieNodesReq(stateRoot, paths, pivot)
if rc.isErr:
return err(ComNetworkProblem)
if rc.value.isNone:
trace trSnapRecvTimeoutWaiting & "for TrieNodes", peer, pivot, nPaths
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.
trace trSnapRecvReceived & "empty TrieNodes", peer, pivot, nPaths, nNodes
return err(ComNoByteCodesAvailable)
# Assemble return value
var dd = GetTrieNodes()
# For each request group/sub-sequence, analyse the results
var nInx = 0
block loop:
for n in 0 ..< nPaths:
let pathLen = paths[n].len
# Account node request
if pathLen < 2:
if trieNodes[nInx].len == 0:
dd.leftOver.add paths[n]
else:
dd.nodes.add NodeSpecs(
partialPath: paths[n][0],
data: trieNodes[nInx])
nInx.inc
if nInx < nNodes:
continue
# all the rest needs to be re-processed
dd.leftOver = dd.leftOver & paths[n+1 ..< nPaths]
break loop
# Storage request for account followed by storage slot paths
if 1 < pathLen:
var pushBack: seq[Blob]
for i in 1 ..< pathLen:
if trieNodes[nInx].len == 0:
pushBack.add paths[n][i]
else:
dd.nodes.add NodeSpecs(
partialPath: paths[n][i],
data: trieNodes[nInx])
nInx.inc
if nInx < nNodes:
continue
# all the rest needs to be re-processed
#
# add: account & pushBack & rest ...
dd.leftOver.add paths[n][0] & pushBack & paths[n][i+1 ..< pathLen]
dd.leftOver = dd.leftOver & paths[n+1 ..< nPaths]
break loop
if 0 < pushBack.len:
dd.leftOver.add paths[n][0] & pushBack
trace trSnapRecvReceived & "TrieNodes", peer, pivot,
nPaths, nNodes, nLeftOver=dd.leftOver.len
return ok(dd)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------