2022-06-16 08:58:50 +00:00
|
|
|
# Nimbus - Fetch account and storage states from peers by snapshot traversal
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
|
|
|
## This module fetches the Ethereum account state trie from network peers by
|
|
|
|
## traversing leaves of the trie in leaf path order, making network requests
|
|
|
|
## using the `snap` protocol.
|
|
|
|
|
|
|
|
import
|
|
|
|
chronos,
|
|
|
|
eth/[common/eth_types, p2p],
|
2022-07-01 11:42:17 +00:00
|
|
|
stew/interval_set,
|
2022-08-04 08:04:30 +00:00
|
|
|
"../.."/[protocol, protocol/trace_config],
|
|
|
|
".."/[range_desc, worker_desc]
|
2022-06-16 08:58:50 +00:00
|
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "snap-fetch"
|
|
|
|
|
2022-07-01 11:42:17 +00:00
|
|
|
type
|
2022-08-04 08:04:30 +00:00
|
|
|
GetAccountRangeError* = enum
|
|
|
|
NothingSerious
|
|
|
|
MissingProof
|
|
|
|
AccountsMinTooSmall
|
|
|
|
AccountsMaxTooLarge
|
|
|
|
NoAccountsForStateRoot
|
2022-07-01 11:42:17 +00:00
|
|
|
NetworkProblem
|
2022-08-04 08:04:30 +00:00
|
|
|
ResponseTimeout
|
2022-07-01 11:42:17 +00:00
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
GetAccountRange* = object
|
|
|
|
consumed*: LeafRange ## Real accounts interval covered
|
|
|
|
data*: SnapAccountRange ## reply data
|
2022-07-01 11:42:17 +00:00
|
|
|
|
2022-06-16 08:58:50 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc getAccountRangeReq(
|
|
|
|
buddy: SnapBuddyRef;
|
|
|
|
root: Hash256;
|
2022-06-16 08:58:50 +00:00
|
|
|
iv: LeafRange
|
2022-08-04 08:04:30 +00:00
|
|
|
): Future[Result[Option[SnapAccountRange],void]] {.async.} =
|
|
|
|
let
|
|
|
|
peer = buddy.peer
|
2022-06-16 08:58:50 +00:00
|
|
|
try:
|
2022-08-04 08:04:30 +00:00
|
|
|
let reply = await peer.getAccountRange(
|
|
|
|
root, iv.minPt.to(Hash256), iv.maxPt.to(Hash256), snapRequestBytesLimit)
|
2022-06-16 08:58:50 +00:00
|
|
|
return ok(reply)
|
|
|
|
except CatchableError as e:
|
2022-08-04 08:04:30 +00:00
|
|
|
trace trSnapRecvError & "waiting for GetAccountRange reply", peer,
|
2022-06-16 08:58:50 +00:00
|
|
|
error=e.msg
|
|
|
|
return err()
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc getAccountRange*(
|
|
|
|
buddy: SnapBuddyRef;
|
|
|
|
stateRoot: Hash256;
|
2022-06-16 08:58:50 +00:00
|
|
|
iv: LeafRange
|
2022-08-04 08:04:30 +00:00
|
|
|
): Future[Result[GetAccountRange,GetAccountRangeError]] {.async.} =
|
2022-06-16 08:58:50 +00:00
|
|
|
## Fetch data using the `snap#` protocol, returns the range covered.
|
2022-08-04 08:04:30 +00:00
|
|
|
let
|
|
|
|
peer = buddy.peer
|
2022-06-16 08:58:50 +00:00
|
|
|
if trSnapTracePacketsOk:
|
|
|
|
trace trSnapSendSending & "GetAccountRange", peer,
|
|
|
|
accRange=iv, stateRoot, bytesLimit=snapRequestBytesLimit
|
|
|
|
|
2022-07-01 11:42:17 +00:00
|
|
|
var dd = block:
|
2022-08-04 08:04:30 +00:00
|
|
|
let rc = await buddy.getAccountRangeReq(stateRoot, iv)
|
2022-06-16 08:58:50 +00:00
|
|
|
if rc.isErr:
|
2022-07-01 11:42:17 +00:00
|
|
|
return err(NetworkProblem)
|
2022-06-16 08:58:50 +00:00
|
|
|
if rc.value.isNone:
|
|
|
|
trace trSnapRecvTimeoutWaiting & "for reply to GetAccountRange", peer
|
2022-08-04 08:04:30 +00:00
|
|
|
return err(ResponseTimeout)
|
|
|
|
GetAccountRange(
|
|
|
|
consumed: iv,
|
2022-07-01 11:42:17 +00:00
|
|
|
data: rc.value.get)
|
2022-06-16 08:58:50 +00:00
|
|
|
|
|
|
|
let
|
2022-07-01 11:42:17 +00:00
|
|
|
nAccounts = dd.data.accounts.len
|
|
|
|
nProof = dd.data.proof.len
|
2022-06-16 08:58:50 +00:00
|
|
|
|
|
|
|
if nAccounts == 0:
|
2022-07-01 11:42:17 +00:00
|
|
|
# github.com/ethereum/devp2p/blob/master/caps/snap.md#getaccountrange-0x00:
|
|
|
|
# Notes:
|
|
|
|
# * Nodes must always respond to the query.
|
|
|
|
# * If the node does not have the state for the requested state root, it
|
|
|
|
# must return an empty reply. It is the responsibility of the caller to
|
|
|
|
# query an state not older than 128 blocks.
|
|
|
|
# * The responding node is allowed to return less data than requested (own
|
|
|
|
# QoS limits), but the node must return at least one account. If no
|
|
|
|
# accounts exist between startingHash and limitHash, then the first (if
|
|
|
|
# any) account after limitHash must be provided.
|
2022-06-16 08:58:50 +00:00
|
|
|
if nProof == 0:
|
2022-07-01 11:42:17 +00:00
|
|
|
# Maybe try another peer
|
2022-08-04 08:04:30 +00:00
|
|
|
trace trSnapRecvReceived & "empty AccountRange", peer,
|
2022-06-16 08:58:50 +00:00
|
|
|
nAccounts, nProof, accRange="n/a", reqRange=iv, stateRoot
|
2022-07-01 11:42:17 +00:00
|
|
|
return err(NoAccountsForStateRoot)
|
|
|
|
|
|
|
|
# So there is no data, otherwise an account beyond the interval end
|
|
|
|
# `iv.maxPt` would have been returned.
|
2022-08-04 08:04:30 +00:00
|
|
|
dd.consumed = LeafRange.new(iv.minPt, high(NodeTag))
|
|
|
|
trace trSnapRecvReceived & "terminal AccountRange", peer,
|
|
|
|
nAccounts, nProof, accRange=dd.consumed, reqRange=iv, stateRoot
|
2022-07-01 11:42:17 +00:00
|
|
|
return ok(dd)
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
let (accMinPt, accMaxPt) = (
|
|
|
|
dd.data.accounts[0].accHash.to(NodeTag),
|
|
|
|
dd.data.accounts[^1].accHash.to(NodeTag))
|
2022-07-01 11:42:17 +00:00
|
|
|
|
|
|
|
if nProof == 0:
|
|
|
|
# github.com/ethereum/devp2p/blob/master/caps/snap.md#accountrange-0x01
|
|
|
|
# Notes:
|
|
|
|
# * If the account range is the entire state (requested origin was 0x00..0
|
|
|
|
# and all accounts fit into the response), no proofs should be sent along
|
|
|
|
# the response. This is unlikely for accounts, but since it's a common
|
|
|
|
# situation for storage slots, this clause keeps the behavior the same
|
|
|
|
# across both.
|
|
|
|
if 0.to(NodeTag) < iv.minPt:
|
2022-08-04 08:04:30 +00:00
|
|
|
trace trSnapRecvProtocolViolation & "proof-less AccountRange", peer,
|
2022-07-01 11:42:17 +00:00
|
|
|
nAccounts, nProof, accRange=LeafRange.new(iv.minPt, accMaxPt),
|
|
|
|
reqRange=iv, stateRoot
|
|
|
|
return err(MissingProof)
|
|
|
|
|
|
|
|
if accMinPt < iv.minPt:
|
|
|
|
# Not allowed
|
|
|
|
trace trSnapRecvProtocolViolation & "min too small in AccountRange", peer,
|
|
|
|
nAccounts, nProof, accRange=LeafRange.new(accMinPt, accMaxPt),
|
|
|
|
reqRange=iv, stateRoot
|
|
|
|
return err(AccountsMinTooSmall)
|
|
|
|
|
|
|
|
if iv.maxPt < accMaxPt:
|
|
|
|
# github.com/ethereum/devp2p/blob/master/caps/snap.md#getaccountrange-0x00:
|
|
|
|
# Notes:
|
|
|
|
# * [..]
|
|
|
|
# * [..]
|
|
|
|
# * [..] If no accounts exist between startingHash and limitHash, then the
|
|
|
|
# first (if any) account after limitHash must be provided.
|
|
|
|
if 1 < nAccounts:
|
2022-08-04 08:04:30 +00:00
|
|
|
# Geth always seems to allow the last account to be larger than the
|
|
|
|
# limit (seen with Geth/v1.10.18-unstable-4b309c70-20220517.)
|
|
|
|
if iv.maxPt < dd.data.accounts[^2].accHash.to(NodeTag):
|
|
|
|
# The segcond largest should not excceed the top one requested.
|
|
|
|
trace trSnapRecvProtocolViolation & "AccountRange top exceeded", peer,
|
|
|
|
nAccounts, nProof, accRange=LeafRange.new(iv.minPt, accMaxPt),
|
|
|
|
reqRange=iv, stateRoot
|
|
|
|
return err(AccountsMaxTooLarge)
|
|
|
|
|
2022-08-17 07:30:11 +00:00
|
|
|
dd.consumed = LeafRange.new(iv.minPt, max(iv.maxPt,accMaxPt))
|
2022-08-04 08:04:30 +00:00
|
|
|
trace trSnapRecvReceived & "AccountRange", peer,
|
|
|
|
nAccounts, nProof, accRange=dd.consumed, reqRange=iv, stateRoot
|
2022-07-01 11:42:17 +00:00
|
|
|
return ok(dd)
|
2022-06-16 08:58:50 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|