2016-11-09 01:01:56 +00:00
// Copyright 2016 The go-ethereum Authors
2016-10-14 03:51:29 +00:00
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"encoding/binary"
2018-06-05 07:23:00 +00:00
"encoding/json"
2019-06-12 11:09:40 +00:00
"errors"
2016-10-14 03:51:29 +00:00
"fmt"
"math/big"
"sync"
2019-06-12 11:09:40 +00:00
"sync/atomic"
2016-12-15 10:13:52 +00:00
"time"
2016-10-14 03:51:29 +00:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
2018-05-07 11:35:06 +00:00
"github.com/ethereum/go-ethereum/core/rawdb"
2016-10-14 03:51:29 +00:00
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
2017-10-24 13:19:09 +00:00
"github.com/ethereum/go-ethereum/light"
2017-02-22 12:10:07 +00:00
"github.com/ethereum/go-ethereum/log"
2016-10-14 03:51:29 +00:00
"github.com/ethereum/go-ethereum/p2p"
2016-10-19 11:04:55 +00:00
"github.com/ethereum/go-ethereum/p2p/discv5"
2016-10-20 11:36:29 +00:00
"github.com/ethereum/go-ethereum/params"
2016-10-14 03:51:29 +00:00
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
2019-06-12 11:09:40 +00:00
var errTooManyInvalidRequest = errors . New ( "too many invalid requests made" )
2016-10-14 03:51:29 +00:00
const (
softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
ethVersion = 63 // equivalent eth version for the downloader
2017-10-24 13:19:09 +00:00
MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request
MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request
MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request
MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request
MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
MaxHelperTrieProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
MaxTxSend = 64 // Amount of transactions to be send per request
MaxTxStatus = 256 // Amount of transactions to queried per request
2016-10-14 03:51:29 +00:00
2016-11-30 05:02:08 +00:00
disableClientRemovePeer = false
2016-10-14 03:51:29 +00:00
)
func errResp ( code errCode , format string , v ... interface { } ) error {
return fmt . Errorf ( "%v - %v" , code , fmt . Sprintf ( format , v ... ) )
}
type BlockChain interface {
2017-12-28 13:18:34 +00:00
Config ( ) * params . ChainConfig
2017-09-09 16:03:07 +00:00
HasHeader ( hash common . Hash , number uint64 ) bool
2016-10-14 03:51:29 +00:00
GetHeader ( hash common . Hash , number uint64 ) * types . Header
GetHeaderByHash ( hash common . Hash ) * types . Header
CurrentHeader ( ) * types . Header
2018-01-30 16:39:32 +00:00
GetTd ( hash common . Hash , number uint64 ) * big . Int
2019-03-18 11:19:40 +00:00
StateCache ( ) state . Database
2016-10-14 03:51:29 +00:00
InsertHeaderChain ( chain [ ] * types . Header , checkFreq int ) ( int , error )
Rollback ( chain [ ] common . Hash )
GetHeaderByNumber ( number uint64 ) * types . Header
2018-06-12 13:52:54 +00:00
GetAncestor ( hash common . Hash , number , ancestor uint64 , maxNonCanonical * uint64 ) ( common . Hash , uint64 )
2016-10-14 03:51:29 +00:00
Genesis ( ) * types . Block
2017-08-18 10:58:36 +00:00
SubscribeChainHeadEvent ( ch chan <- core . ChainHeadEvent ) event . Subscription
2016-10-14 03:51:29 +00:00
}
type txPool interface {
2017-10-25 09:18:44 +00:00
AddRemotes ( txs [ ] * types . Transaction ) [ ] error
Status ( hashes [ ] common . Hash ) [ ] core . TxStatus
2016-10-14 03:51:29 +00:00
}
type ProtocolManager struct {
2019-05-26 16:15:05 +00:00
// Configs
chainConfig * params . ChainConfig
iConfig * light . IndexerConfig
client bool // The indicator whether the node is light client
maxPeers int // The maximum number peers allowed to connect.
networkId uint64 // The identity of network.
2019-02-26 11:32:48 +00:00
txpool txPool
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing
cmd, les, node: remove callback mechanism
cmd, node: remove callback definition
les: simplify the registrar
les: expose checkpoint rpc services in the light client
les, light: don't store untrusted receipt
cmd, contracts, les: discard stale checkpoint
cmd, contracts/registrar: loose restriction of registeration
cmd, contracts: add replay-protection
all: off-chain multi-signature contract
params: deploy checkpoint contract for rinkeby
cmd/registrar: add raw signing mode for registrar
cmd/registrar, contracts/registrar, les: fixed messages
* cmd/registrar, contracts/registrar: fix lints
* accounts/abi/bind, les: address comments
* cmd, contracts, les, light, params: minor checkpoint sync cleanups
* cmd, eth, les, light: move checkpoint config to config file
* cmd, eth, les, params: address comments
* eth, les, params: address comments
* cmd: polish up the checkpoint admin CLI
* cmd, contracts, params: deploy new version contract
* cmd/checkpoint-admin: add another flag for clef mode signing
* cmd, contracts, les: rename and regen checkpoint oracle with abigen
2019-06-28 07:34:02 +00:00
txrelay * lesTxRelay
2019-02-26 11:32:48 +00:00
blockchain BlockChain
chainDb ethdb . Database
odr * LesOdr
server * LesServer
serverPool * serverPool
lesTopic discv5 . Topic
reqDist * requestDistributor
retriever * retrieveManager
servingQueue * servingQueue
2019-05-26 16:15:05 +00:00
downloader * downloader . Downloader
fetcher * lightFetcher
ulc * ulc
peers * peerSet
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing
cmd, les, node: remove callback mechanism
cmd, node: remove callback definition
les: simplify the registrar
les: expose checkpoint rpc services in the light client
les, light: don't store untrusted receipt
cmd, contracts, les: discard stale checkpoint
cmd, contracts/registrar: loose restriction of registeration
cmd, contracts: add replay-protection
all: off-chain multi-signature contract
params: deploy checkpoint contract for rinkeby
cmd/registrar: add raw signing mode for registrar
cmd/registrar, contracts/registrar, les: fixed messages
* cmd/registrar, contracts/registrar: fix lints
* accounts/abi/bind, les: address comments
* cmd, contracts, les, light, params: minor checkpoint sync cleanups
* cmd, eth, les, light: move checkpoint config to config file
* cmd, eth, les, params: address comments
* eth, les, params: address comments
* cmd: polish up the checkpoint admin CLI
* cmd, contracts, params: deploy new version contract
* cmd/checkpoint-admin: add another flag for clef mode signing
* cmd, contracts, les: rename and regen checkpoint oracle with abigen
2019-06-28 07:34:02 +00:00
checkpoint * params . TrustedCheckpoint
reg * checkpointOracle // If reg == nil, it means the checkpoint registrar is not activated
2016-10-14 03:51:29 +00:00
// channels for fetcher, syncer, txsyncLoop
newPeerCh chan * peer
quitSync chan struct { }
noMorePeers chan struct { }
2019-05-26 16:15:05 +00:00
wg * sync . WaitGroup
eventMux * event . TypeMux
// Callbacks
synced func ( ) bool
2016-10-14 03:51:29 +00:00
}
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
// with the ethereum network.
2019-07-09 17:30:24 +00:00
func NewProtocolManager ( chainConfig * params . ChainConfig , checkpoint * params . TrustedCheckpoint , indexerConfig * light . IndexerConfig , ulcServers [ ] string , ulcFraction int , client bool , networkId uint64 , mux * event . TypeMux , peers * peerSet , blockchain BlockChain , txpool txPool , chainDb ethdb . Database , odr * LesOdr , serverPool * serverPool , registrar * checkpointOracle , quitSync chan struct { } , wg * sync . WaitGroup , synced func ( ) bool ) ( * ProtocolManager , error ) {
2016-10-14 03:51:29 +00:00
// Create the protocol manager with the base fields
manager := & ProtocolManager {
2019-05-26 16:15:05 +00:00
client : client ,
2016-10-14 03:51:29 +00:00
eventMux : mux ,
blockchain : blockchain ,
chainConfig : chainConfig ,
2018-08-28 07:08:16 +00:00
iConfig : indexerConfig ,
2016-10-14 03:51:29 +00:00
chainDb : chainDb ,
2017-06-21 10:27:38 +00:00
odr : odr ,
2016-10-14 03:51:29 +00:00
networkId : networkId ,
txpool : txpool ,
2018-06-12 11:00:52 +00:00
serverPool : serverPool ,
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing
cmd, les, node: remove callback mechanism
cmd, node: remove callback definition
les: simplify the registrar
les: expose checkpoint rpc services in the light client
les, light: don't store untrusted receipt
cmd, contracts, les: discard stale checkpoint
cmd, contracts/registrar: loose restriction of registeration
cmd, contracts: add replay-protection
all: off-chain multi-signature contract
params: deploy checkpoint contract for rinkeby
cmd/registrar: add raw signing mode for registrar
cmd/registrar, contracts/registrar, les: fixed messages
* cmd/registrar, contracts/registrar: fix lints
* accounts/abi/bind, les: address comments
* cmd, contracts, les, light, params: minor checkpoint sync cleanups
* cmd, eth, les, light: move checkpoint config to config file
* cmd, eth, les, params: address comments
* eth, les, params: address comments
* cmd: polish up the checkpoint admin CLI
* cmd, contracts, params: deploy new version contract
* cmd/checkpoint-admin: add another flag for clef mode signing
* cmd, contracts, les: rename and regen checkpoint oracle with abigen
2019-06-28 07:34:02 +00:00
reg : registrar ,
2017-06-21 10:27:38 +00:00
peers : peers ,
2016-10-14 03:51:29 +00:00
newPeerCh : make ( chan * peer ) ,
2017-06-21 10:27:38 +00:00
quitSync : quitSync ,
wg : wg ,
2016-10-14 03:51:29 +00:00
noMorePeers : make ( chan struct { } ) ,
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing
cmd, les, node: remove callback mechanism
cmd, node: remove callback definition
les: simplify the registrar
les: expose checkpoint rpc services in the light client
les, light: don't store untrusted receipt
cmd, contracts, les: discard stale checkpoint
cmd, contracts/registrar: loose restriction of registeration
cmd, contracts: add replay-protection
all: off-chain multi-signature contract
params: deploy checkpoint contract for rinkeby
cmd/registrar: add raw signing mode for registrar
cmd/registrar, contracts/registrar, les: fixed messages
* cmd/registrar, contracts/registrar: fix lints
* accounts/abi/bind, les: address comments
* cmd, contracts, les, light, params: minor checkpoint sync cleanups
* cmd, eth, les, light: move checkpoint config to config file
* cmd, eth, les, params: address comments
* eth, les, params: address comments
* cmd: polish up the checkpoint admin CLI
* cmd, contracts, params: deploy new version contract
* cmd/checkpoint-admin: add another flag for clef mode signing
* cmd, contracts, les: rename and regen checkpoint oracle with abigen
2019-06-28 07:34:02 +00:00
checkpoint : checkpoint ,
2019-05-26 16:15:05 +00:00
synced : synced ,
2016-10-14 03:51:29 +00:00
}
2017-06-21 10:27:38 +00:00
if odr != nil {
manager . retriever = odr . retriever
manager . reqDist = odr . retriever . dist
}
2017-10-24 13:19:09 +00:00
2019-07-09 17:30:24 +00:00
if ulcServers != nil {
ulc , err := newULC ( ulcServers , ulcFraction )
if err != nil {
log . Warn ( "Failed to initialize ultra light client" , "err" , err )
} else {
manager . ulc = ulc
}
2019-01-24 11:18:26 +00:00
}
2016-10-14 03:51:29 +00:00
removePeer := manager . removePeer
if disableClientRemovePeer {
removePeer = func ( id string ) { }
}
2019-05-26 16:15:05 +00:00
if client {
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing
cmd, les, node: remove callback mechanism
cmd, node: remove callback definition
les: simplify the registrar
les: expose checkpoint rpc services in the light client
les, light: don't store untrusted receipt
cmd, contracts, les: discard stale checkpoint
cmd, contracts/registrar: loose restriction of registeration
cmd, contracts: add replay-protection
all: off-chain multi-signature contract
params: deploy checkpoint contract for rinkeby
cmd/registrar: add raw signing mode for registrar
cmd/registrar, contracts/registrar, les: fixed messages
* cmd/registrar, contracts/registrar: fix lints
* accounts/abi/bind, les: address comments
* cmd, contracts, les, light, params: minor checkpoint sync cleanups
* cmd, eth, les, light: move checkpoint config to config file
* cmd, eth, les, params: address comments
* eth, les, params: address comments
* cmd: polish up the checkpoint admin CLI
* cmd, contracts, params: deploy new version contract
* cmd/checkpoint-admin: add another flag for clef mode signing
* cmd, contracts, les: rename and regen checkpoint oracle with abigen
2019-06-28 07:34:02 +00:00
var checkpointNumber uint64
if checkpoint != nil {
checkpointNumber = ( checkpoint . SectionIndex + 1 ) * params . CHTFrequency - 1
2019-04-16 10:20:38 +00:00
}
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing
cmd, les, node: remove callback mechanism
cmd, node: remove callback definition
les: simplify the registrar
les: expose checkpoint rpc services in the light client
les, light: don't store untrusted receipt
cmd, contracts, les: discard stale checkpoint
cmd, contracts/registrar: loose restriction of registeration
cmd, contracts: add replay-protection
all: off-chain multi-signature contract
params: deploy checkpoint contract for rinkeby
cmd/registrar: add raw signing mode for registrar
cmd/registrar, contracts/registrar, les: fixed messages
* cmd/registrar, contracts/registrar: fix lints
* accounts/abi/bind, les: address comments
* cmd, contracts, les, light, params: minor checkpoint sync cleanups
* cmd, eth, les, light: move checkpoint config to config file
* cmd, eth, les, params: address comments
* eth, les, params: address comments
* cmd: polish up the checkpoint admin CLI
* cmd, contracts, params: deploy new version contract
* cmd/checkpoint-admin: add another flag for clef mode signing
* cmd, contracts, les: rename and regen checkpoint oracle with abigen
2019-06-28 07:34:02 +00:00
manager . downloader = downloader . New ( checkpointNumber , chainDb , nil , manager . eventMux , nil , blockchain , removePeer )
2017-06-21 10:27:38 +00:00
manager . peers . notify ( ( * downloaderPeerNotify ) ( manager ) )
manager . fetcher = newLightFetcher ( manager )
2016-10-14 03:51:29 +00:00
}
return manager , nil
}
2017-06-21 10:27:38 +00:00
// removePeer initiates disconnection from a peer by removing it from the peer set
2016-10-14 03:51:29 +00:00
func ( pm * ProtocolManager ) removePeer ( id string ) {
2017-06-21 10:27:38 +00:00
pm . peers . Unregister ( id )
2016-10-14 03:51:29 +00:00
}
2018-02-05 13:41:53 +00:00
func ( pm * ProtocolManager ) Start ( maxPeers int ) {
pm . maxPeers = maxPeers
2019-05-26 16:15:05 +00:00
if pm . client {
2016-10-14 03:51:29 +00:00
go pm . syncer ( )
} else {
go func ( ) {
for range pm . newPeerCh {
}
} ( )
}
}
func ( pm * ProtocolManager ) Stop ( ) {
// Showing a log message. During download / process this could actually
// take between 5 to 10 seconds and therefor feedback is required.
2017-03-03 09:41:52 +00:00
log . Info ( "Stopping light Ethereum protocol" )
2016-10-14 03:51:29 +00:00
// Quit the sync loop.
// After this send has completed, no new peers will be accepted.
pm . noMorePeers <- struct { } { }
close ( pm . quitSync ) // quits syncer, fetcher
2019-02-26 11:32:48 +00:00
if pm . servingQueue != nil {
pm . servingQueue . stop ( )
2018-08-14 20:44:46 +00:00
}
2016-10-14 03:51:29 +00:00
// Disconnect existing sessions.
// This also closes the gate for any new registrations on the peer set.
// sessions which are already established but not added to pm.peers yet
// will exit when they try to register.
pm . peers . Close ( )
// Wait for any process action
pm . wg . Wait ( )
2017-03-03 09:41:52 +00:00
log . Info ( "Light Ethereum protocol stopped" )
2016-10-14 03:51:29 +00:00
}
2018-08-17 10:21:53 +00:00
// runPeer is the p2p protocol run function for the given version.
func ( pm * ProtocolManager ) runPeer ( version uint , p * p2p . Peer , rw p2p . MsgReadWriter ) error {
var entry * poolEntry
peer := pm . newPeer ( int ( version ) , pm . networkId , p , rw )
if pm . serverPool != nil {
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-24 22:59:00 +00:00
entry = pm . serverPool . connect ( peer , peer . Node ( ) )
2018-08-17 10:21:53 +00:00
}
peer . poolEntry = entry
select {
case pm . newPeerCh <- peer :
pm . wg . Add ( 1 )
defer pm . wg . Done ( )
err := pm . handle ( peer )
if entry != nil {
pm . serverPool . disconnect ( entry )
}
return err
case <- pm . quitSync :
if entry != nil {
pm . serverPool . disconnect ( entry )
}
return p2p . DiscQuitting
}
}
2017-04-25 11:31:15 +00:00
func ( pm * ProtocolManager ) newPeer ( pv int , nv uint64 , p * p2p . Peer , rw p2p . MsgReadWriter ) * peer {
2019-07-09 17:30:24 +00:00
var trusted bool
if pm . ulc != nil {
trusted = pm . ulc . trusted ( p . ID ( ) )
2019-01-24 11:18:26 +00:00
}
2019-07-09 17:30:24 +00:00
return newPeer ( pv , nv , trusted , p , newMeteredMsgWriter ( rw ) )
2016-10-14 03:51:29 +00:00
}
// handle is the callback invoked to manage the life cycle of a les peer. When
// this function terminates, the peer is disconnected.
func ( pm * ProtocolManager ) handle ( p * peer ) error {
2018-02-27 10:52:59 +00:00
// Ignore maxPeers if this is a trusted peer
2018-08-14 20:44:46 +00:00
// In server mode we try to check into the client pool after handshake
2019-05-26 16:15:05 +00:00
if pm . client && pm . peers . Len ( ) >= pm . maxPeers && ! p . Peer . Info ( ) . Network . Trusted {
2019-07-03 18:23:06 +00:00
clientRejectedMeter . Mark ( 1 )
2018-02-05 13:41:53 +00:00
return p2p . DiscTooManyPeers
}
2019-05-26 16:15:05 +00:00
// Reject light clients if server is not synced.
if ! pm . client && ! pm . synced ( ) {
2019-07-03 18:23:06 +00:00
clientRejectedMeter . Mark ( 1 )
2019-05-26 16:15:05 +00:00
return p2p . DiscRequested
}
2017-03-03 09:41:52 +00:00
p . Log ( ) . Debug ( "Light Ethereum peer connected" , "name" , p . Name ( ) )
2016-10-14 03:51:29 +00:00
// Execute the LES handshake
2018-01-30 16:39:32 +00:00
var (
genesis = pm . blockchain . Genesis ( )
head = pm . blockchain . CurrentHeader ( )
hash = head . Hash ( )
number = head . Number . Uint64 ( )
td = pm . blockchain . GetTd ( hash , number )
)
if err := p . Handshake ( td , hash , number , genesis . Hash ( ) , pm . server ) ; err != nil {
2017-03-03 09:41:52 +00:00
p . Log ( ) . Debug ( "Light Ethereum handshake failed" , "err" , err )
2019-07-03 18:23:06 +00:00
clientErrorMeter . Mark ( 1 )
2016-10-14 03:51:29 +00:00
return err
}
2019-02-26 11:32:48 +00:00
if p . fcClient != nil {
defer p . fcClient . Disconnect ( )
2018-08-14 20:44:46 +00:00
}
2016-10-14 03:51:29 +00:00
if rw , ok := p . rw . ( * meteredMsgReadWriter ) ; ok {
rw . Init ( p . version )
}
2019-01-24 11:18:26 +00:00
2016-10-14 03:51:29 +00:00
// Register the peer locally
if err := pm . peers . Register ( p ) ; err != nil {
2019-07-03 18:23:06 +00:00
clientErrorMeter . Mark ( 1 )
2017-03-03 09:41:52 +00:00
p . Log ( ) . Error ( "Light Ethereum peer registration failed" , "err" , err )
2016-10-14 03:51:29 +00:00
return err
}
2019-07-03 18:23:06 +00:00
connectedAt := time . Now ( )
2016-10-14 03:51:29 +00:00
defer func ( ) {
pm . removePeer ( p . id )
2019-07-03 18:23:06 +00:00
connectionTimer . UpdateSince ( connectedAt )
2016-10-14 03:51:29 +00:00
} ( )
2019-01-24 11:18:26 +00:00
2016-10-14 03:51:29 +00:00
// Register the peer in the downloader. If the downloader considers it banned, we disconnect
2019-05-26 16:15:05 +00:00
if pm . client {
2016-11-30 05:02:08 +00:00
p . lock . Lock ( )
head := p . headInfo
p . lock . Unlock ( )
if pm . fetcher != nil {
pm . fetcher . announce ( p , head )
}
2016-11-17 14:54:24 +00:00
if p . poolEntry != nil {
pm . serverPool . registered ( p . poolEntry )
}
2016-10-14 03:51:29 +00:00
}
// main loop. handle incoming messages.
for {
if err := pm . handleMsg ( p ) ; err != nil {
2017-03-03 09:41:52 +00:00
p . Log ( ) . Debug ( "Light Ethereum message handling failed" , "err" , err )
2019-02-26 11:32:48 +00:00
if p . fcServer != nil {
p . fcServer . DumpLogs ( )
}
2016-10-14 03:51:29 +00:00
return err
}
}
}
// handleMsg is invoked whenever an inbound message is received from a remote
// peer. The remote connection is torn down upon returning any error.
func ( pm * ProtocolManager ) handleMsg ( p * peer ) error {
2019-03-20 08:35:05 +00:00
select {
case err := <- p . errCh :
return err
default :
}
2016-10-14 03:51:29 +00:00
// Read the next message from the remote peer, and ensure it's fully consumed
msg , err := p . rw . ReadMsg ( )
if err != nil {
return err
}
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Light Ethereum message arrived" , "code" , msg . Code , "bytes" , msg . Size )
2016-12-15 10:13:52 +00:00
2019-02-26 11:32:48 +00:00
p . responseCount ++
responseCount := p . responseCount
var (
maxCost uint64
task * servingTask
)
accept := func ( reqID , reqCnt , maxCnt uint64 ) bool {
2019-05-30 18:51:13 +00:00
inSizeCost := func ( ) uint64 {
if pm . server . costTracker != nil {
return pm . server . costTracker . realCost ( 0 , msg . Size , 0 )
}
return 0
2016-10-14 03:51:29 +00:00
}
2019-05-30 18:51:13 +00:00
if p . isFrozen ( ) || reqCnt == 0 || p . fcClient == nil || reqCnt > maxCnt {
p . fcClient . OneTimeCost ( inSizeCost ( ) )
2019-02-26 11:32:48 +00:00
return false
2016-10-14 03:51:29 +00:00
}
2019-05-30 18:51:13 +00:00
maxCost = p . fcCosts . getMaxCost ( msg . Code , reqCnt )
gf := float64 ( 1 )
if pm . server . costTracker != nil {
gf = pm . server . costTracker . globalFactor ( )
if gf < 0.001 {
p . Log ( ) . Error ( "Invalid global cost factor" , "globalFactor" , gf )
gf = 1
}
}
maxTime := uint64 ( float64 ( maxCost ) / gf )
2019-02-26 11:32:48 +00:00
if accepted , bufShort , servingPriority := p . fcClient . AcceptRequest ( reqID , responseCount , maxCost ) ; ! accepted {
2019-05-30 18:51:13 +00:00
p . freezeClient ( )
p . Log ( ) . Warn ( "Request came too early" , "remaining" , common . PrettyDuration ( time . Duration ( bufShort * 1000000 / p . fcParams . MinRecharge ) ) )
p . fcClient . OneTimeCost ( inSizeCost ( ) )
2019-02-26 11:32:48 +00:00
return false
} else {
2019-05-30 18:51:13 +00:00
task = pm . servingQueue . newTask ( p , maxTime , servingPriority )
}
if task . start ( ) {
return true
2016-10-14 03:51:29 +00:00
}
2019-05-30 18:51:13 +00:00
p . fcClient . RequestProcessed ( reqID , responseCount , maxCost , inSizeCost ( ) )
return false
2016-10-14 03:51:29 +00:00
}
if msg . Size > ProtocolMaxMsgSize {
return errResp ( ErrMsgTooLarge , "%v > %v" , msg . Size , ProtocolMaxMsgSize )
}
defer msg . Discard ( )
var deliverMsg * Msg
2019-02-26 11:32:48 +00:00
sendResponse := func ( reqID , amount uint64 , reply * reply , servingTime uint64 ) {
p . responseLock . Lock ( )
defer p . responseLock . Unlock ( )
2019-05-30 18:51:13 +00:00
if p . isFrozen ( ) {
amount = 0
reply = nil
}
2019-02-26 11:32:48 +00:00
var replySize uint32
if reply != nil {
replySize = reply . size ( )
}
var realCost uint64
if pm . server . costTracker != nil {
realCost = pm . server . costTracker . realCost ( servingTime , msg . Size , replySize )
2019-05-30 18:51:13 +00:00
if amount != 0 {
pm . server . costTracker . updateStats ( msg . Code , amount , servingTime , realCost )
}
2019-02-26 11:32:48 +00:00
} else {
realCost = maxCost
}
bv := p . fcClient . RequestProcessed ( reqID , responseCount , maxCost , realCost )
if reply != nil {
p . queueSend ( func ( ) {
if err := reply . send ( bv ) ; err != nil {
2019-03-20 08:35:05 +00:00
select {
case p . errCh <- err :
default :
}
2019-02-26 11:32:48 +00:00
}
} )
}
}
2016-10-14 03:51:29 +00:00
// Handle the message depending on its contents
switch msg . Code {
case StatusMsg :
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received status message" )
2016-10-14 03:51:29 +00:00
// Status messages should never arrive after the handshake
return errResp ( ErrExtraStatusMsg , "uncontrolled status message" )
// Block header query, collect the requested headers and reply
case AnnounceMsg :
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received announce message" )
2016-10-14 03:51:29 +00:00
var req announceData
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "%v: %v" , msg , err )
}
2019-07-08 09:42:22 +00:00
if err := req . sanityCheck ( ) ; err != nil {
return err
}
2019-02-26 11:32:48 +00:00
update , size := req . Update . decode ( )
if p . rejectUpdate ( size ) {
return errResp ( ErrRequestRejected , "" )
2017-10-24 13:19:09 +00:00
}
2019-02-26 11:32:48 +00:00
p . updateFlowControl ( update )
2017-10-24 13:19:09 +00:00
2019-02-26 11:32:48 +00:00
if req . Hash != ( common . Hash { } ) {
if p . announceType == announceTypeNone {
return errResp ( ErrUnexpectedResponse , "" )
}
if p . announceType == announceTypeSigned {
if err := req . checkSignature ( p . ID ( ) , update ) ; err != nil {
p . Log ( ) . Trace ( "Invalid announcement signature" , "err" , err )
return err
}
p . Log ( ) . Trace ( "Valid announcement signature" )
}
p . Log ( ) . Trace ( "Announce message content" , "number" , req . Number , "hash" , req . Hash , "td" , req . Td , "reorg" , req . ReorgDepth )
if pm . fetcher != nil {
pm . fetcher . announce ( p , & req )
}
2016-11-30 05:02:08 +00:00
}
2016-10-14 03:51:29 +00:00
case GetBlockHeadersMsg :
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received block header request" )
2016-10-14 03:51:29 +00:00
// Decode the complex header query
var req struct {
ReqID uint64
Query getBlockHeadersData
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "%v: %v" , msg , err )
}
query := req . Query
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , query . Amount , MaxHeaderFetch ) {
go func ( ) {
hashMode := query . Origin . Hash != ( common . Hash { } )
first := true
maxNonCanonical := uint64 ( 100 )
// Gather headers until the fetch or network limits is reached
var (
bytes common . StorageSize
headers [ ] * types . Header
unknown bool
)
for ! unknown && len ( headers ) < int ( query . Amount ) && bytes < softResponseLimit {
if ! first && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
}
// Retrieve the next header satisfying the query
var origin * types . Header
if hashMode {
if first {
origin = pm . blockchain . GetHeaderByHash ( query . Origin . Hash )
if origin != nil {
query . Origin . Number = origin . Number . Uint64 ( )
}
} else {
origin = pm . blockchain . GetHeader ( query . Origin . Hash , query . Origin . Number )
2019-02-26 11:32:48 +00:00
}
} else {
2019-05-30 18:51:13 +00:00
origin = pm . blockchain . GetHeaderByNumber ( query . Origin . Number )
2018-06-12 13:52:54 +00:00
}
2019-05-30 18:51:13 +00:00
if origin == nil {
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-05-30 18:51:13 +00:00
break
2019-02-26 11:32:48 +00:00
}
2019-05-30 18:51:13 +00:00
headers = append ( headers , origin )
bytes += estHeaderRlpSize
// Advance to the next header of the query
switch {
case hashMode && query . Reverse :
// Hash based traversal towards the genesis block
ancestor := query . Skip + 1
if ancestor == 0 {
unknown = true
} else {
query . Origin . Hash , query . Origin . Number = pm . blockchain . GetAncestor ( query . Origin . Hash , query . Origin . Number , ancestor , & maxNonCanonical )
unknown = ( query . Origin . Hash == common . Hash { } )
}
case hashMode && ! query . Reverse :
// Hash based traversal towards the leaf block
var (
current = origin . Number . Uint64 ( )
next = current + query . Skip + 1
)
if next <= current {
infos , _ := json . MarshalIndent ( p . Peer . Info ( ) , "" , " " )
p . Log ( ) . Warn ( "GetBlockHeaders skip overflow attack" , "current" , current , "skip" , query . Skip , "next" , next , "attacker" , infos )
unknown = true
} else {
if header := pm . blockchain . GetHeaderByNumber ( next ) ; header != nil {
nextHash := header . Hash ( )
expOldHash , _ := pm . blockchain . GetAncestor ( nextHash , next , query . Skip + 1 , & maxNonCanonical )
if expOldHash == query . Origin . Hash {
query . Origin . Hash , query . Origin . Number = nextHash , next
} else {
unknown = true
}
2019-02-26 11:32:48 +00:00
} else {
unknown = true
}
2019-05-30 18:51:13 +00:00
}
case query . Reverse :
// Number based traversal towards the genesis block
if query . Origin . Number >= query . Skip + 1 {
query . Origin . Number -= query . Skip + 1
2018-06-05 07:23:00 +00:00
} else {
unknown = true
}
2019-05-30 18:51:13 +00:00
case ! query . Reverse :
// Number based traversal towards the leaf block
query . Origin . Number += query . Skip + 1
}
first = false
2019-02-26 11:32:48 +00:00
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , query . Amount , p . ReplyBlockHeaders ( req . ReqID , headers ) , task . done ( ) )
} ( )
}
2016-10-14 03:51:29 +00:00
case BlockHeadersMsg :
if pm . downloader == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received block header response message" )
2016-10-14 03:51:29 +00:00
// A batch of headers arrived to one of our previous requests
var resp struct {
ReqID , BV uint64
Headers [ ] * types . Header
}
if err := msg . Decode ( & resp ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2019-02-26 11:32:48 +00:00
p . fcServer . ReceivedReply ( resp . ReqID , resp . BV )
2016-11-30 05:02:08 +00:00
if pm . fetcher != nil && pm . fetcher . requestedID ( resp . ReqID ) {
2016-11-29 22:55:35 +00:00
pm . fetcher . deliverHeaders ( p , resp . ReqID , resp . Headers )
2016-10-14 03:51:29 +00:00
} else {
err := pm . downloader . DeliverHeaders ( p . id , resp . Headers )
if err != nil {
2017-02-22 12:10:07 +00:00
log . Debug ( fmt . Sprint ( err ) )
2016-10-14 03:51:29 +00:00
}
}
case GetBlockBodiesMsg :
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received block bodies request" )
2016-10-14 03:51:29 +00:00
// Decode the retrieval message
var req struct {
ReqID uint64
Hashes [ ] common . Hash
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
// Gather blocks until the fetch or network limits is reached
var (
bytes int
bodies [ ] rlp . RawValue
)
2016-12-15 10:13:52 +00:00
reqCnt := len ( req . Hashes )
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , uint64 ( reqCnt ) , MaxBodyFetch ) {
go func ( ) {
for i , hash := range req . Hashes {
if i != 0 && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
}
2019-06-12 11:09:40 +00:00
// Retrieve the requested block body, stopping if enough was found
2019-05-30 18:51:13 +00:00
if bytes >= softResponseLimit {
break
}
2019-06-12 11:09:40 +00:00
number := rawdb . ReadHeaderNumber ( pm . chainDb , hash )
if number == nil {
atomic . AddUint32 ( & p . invalidCount , 1 )
continue
}
if data := rawdb . ReadBodyRLP ( pm . chainDb , hash , * number ) ; len ( data ) != 0 {
bodies = append ( bodies , data )
bytes += len ( data )
2019-02-26 11:32:48 +00:00
}
2018-05-07 11:35:06 +00:00
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , uint64 ( reqCnt ) , p . ReplyBlockBodiesRLP ( req . ReqID , bodies ) , task . done ( ) )
} ( )
}
2016-10-14 03:51:29 +00:00
case BlockBodiesMsg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received block bodies response" )
2016-10-14 03:51:29 +00:00
// A batch of block bodies arrived to one of our previous requests
var resp struct {
ReqID , BV uint64
Data [ ] * types . Body
}
if err := msg . Decode ( & resp ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2019-02-26 11:32:48 +00:00
p . fcServer . ReceivedReply ( resp . ReqID , resp . BV )
2016-10-14 03:51:29 +00:00
deliverMsg = & Msg {
MsgType : MsgBlockBodies ,
ReqID : resp . ReqID ,
Obj : resp . Data ,
}
case GetCodeMsg :
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received code request" )
2016-10-14 03:51:29 +00:00
// Decode the retrieval message
var req struct {
ReqID uint64
Reqs [ ] CodeReq
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
// Gather state data until the fetch or network limits is reached
var (
bytes int
data [ ] [ ] byte
)
2016-12-15 10:13:52 +00:00
reqCnt := len ( req . Reqs )
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , uint64 ( reqCnt ) , MaxCodeFetch ) {
go func ( ) {
for i , request := range req . Reqs {
if i != 0 && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
}
// Look up the root hash belonging to the request
number := rawdb . ReadHeaderNumber ( pm . chainDb , request . BHash )
if number == nil {
p . Log ( ) . Warn ( "Failed to retrieve block num for code" , "hash" , request . BHash )
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-05-30 18:51:13 +00:00
continue
}
header := rawdb . ReadHeader ( pm . chainDb , request . BHash , * number )
if header == nil {
p . Log ( ) . Warn ( "Failed to retrieve header for code" , "block" , * number , "hash" , request . BHash )
continue
}
2019-06-11 07:40:32 +00:00
// Refuse to search stale state data in the database since looking for
// a non-exist key is kind of expensive.
local := pm . blockchain . CurrentHeader ( ) . Number . Uint64 ( )
if ! pm . server . archiveMode && header . Number . Uint64 ( ) + core . TriesInMemory <= local {
p . Log ( ) . Debug ( "Reject stale code request" , "number" , header . Number . Uint64 ( ) , "head" , local )
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-06-11 07:40:32 +00:00
continue
}
2019-05-30 18:51:13 +00:00
triedb := pm . blockchain . StateCache ( ) . TrieDB ( )
2018-02-05 16:40:32 +00:00
2019-05-30 18:51:13 +00:00
account , err := pm . getAccount ( triedb , header . Root , common . BytesToHash ( request . AccKey ) )
if err != nil {
p . Log ( ) . Warn ( "Failed to retrieve account for code" , "block" , header . Number , "hash" , header . Hash ( ) , "account" , common . BytesToHash ( request . AccKey ) , "err" , err )
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-05-30 18:51:13 +00:00
continue
}
code , err := triedb . Node ( common . BytesToHash ( account . CodeHash ) )
if err != nil {
p . Log ( ) . Warn ( "Failed to retrieve account code" , "block" , header . Number , "hash" , header . Hash ( ) , "account" , common . BytesToHash ( request . AccKey ) , "codehash" , common . BytesToHash ( account . CodeHash ) , "err" , err )
continue
}
// Accumulate the code and abort if enough data was retrieved
data = append ( data , code )
if bytes += len ( code ) ; bytes >= softResponseLimit {
break
}
2016-10-14 03:51:29 +00:00
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , uint64 ( reqCnt ) , p . ReplyCode ( req . ReqID , data ) , task . done ( ) )
} ( )
}
2016-10-14 03:51:29 +00:00
case CodeMsg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received code response" )
2016-10-14 03:51:29 +00:00
// A batch of node state data arrived to one of our previous requests
var resp struct {
ReqID , BV uint64
Data [ ] [ ] byte
}
if err := msg . Decode ( & resp ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2019-02-26 11:32:48 +00:00
p . fcServer . ReceivedReply ( resp . ReqID , resp . BV )
2016-10-14 03:51:29 +00:00
deliverMsg = & Msg {
MsgType : MsgCode ,
ReqID : resp . ReqID ,
Obj : resp . Data ,
}
case GetReceiptsMsg :
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received receipts request" )
2016-10-14 03:51:29 +00:00
// Decode the retrieval message
var req struct {
ReqID uint64
Hashes [ ] common . Hash
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
// Gather state data until the fetch or network limits is reached
var (
bytes int
receipts [ ] rlp . RawValue
)
2016-12-15 10:13:52 +00:00
reqCnt := len ( req . Hashes )
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , uint64 ( reqCnt ) , MaxReceiptFetch ) {
go func ( ) {
for i , hash := range req . Hashes {
if i != 0 && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
}
if bytes >= softResponseLimit {
break
}
// Retrieve the requested block's receipts, skipping if unknown to us
var results types . Receipts
2019-06-12 11:09:40 +00:00
number := rawdb . ReadHeaderNumber ( pm . chainDb , hash )
if number == nil {
atomic . AddUint32 ( & p . invalidCount , 1 )
continue
2019-05-30 18:51:13 +00:00
}
2019-06-12 11:09:40 +00:00
results = rawdb . ReadRawReceipts ( pm . chainDb , hash , * number )
2019-05-30 18:51:13 +00:00
if results == nil {
if header := pm . blockchain . GetHeaderByHash ( hash ) ; header == nil || header . ReceiptHash != types . EmptyRootHash {
continue
}
}
// If known, encode and queue for response packet
if encoded , err := rlp . EncodeToBytes ( results ) ; err != nil {
log . Error ( "Failed to encode receipt" , "err" , err )
} else {
receipts = append ( receipts , encoded )
bytes += len ( encoded )
2019-02-26 11:32:48 +00:00
}
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , uint64 ( reqCnt ) , p . ReplyReceiptsRLP ( req . ReqID , receipts ) , task . done ( ) )
} ( )
}
2016-10-14 03:51:29 +00:00
case ReceiptsMsg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received receipts response" )
2016-10-14 03:51:29 +00:00
// A batch of receipts arrived to one of our previous requests
var resp struct {
ReqID , BV uint64
Receipts [ ] types . Receipts
}
if err := msg . Decode ( & resp ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2019-02-26 11:32:48 +00:00
p . fcServer . ReceivedReply ( resp . ReqID , resp . BV )
2016-10-14 03:51:29 +00:00
deliverMsg = & Msg {
MsgType : MsgReceipts ,
ReqID : resp . ReqID ,
Obj : resp . Receipts ,
}
2017-10-24 13:19:09 +00:00
case GetProofsV2Msg :
p . Log ( ) . Trace ( "Received les/2 proofs request" )
// Decode the retrieval message
var req struct {
ReqID uint64
Reqs [ ] ProofReq
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
// Gather state data until the fetch or network limits is reached
var (
2018-02-05 16:40:32 +00:00
lastBHash common . Hash
root common . Hash
2017-10-24 13:19:09 +00:00
)
reqCnt := len ( req . Reqs )
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , uint64 ( reqCnt ) , MaxProofsFetch ) {
go func ( ) {
nodes := light . NewNodeSet ( )
for i , request := range req . Reqs {
if i != 0 && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
2018-05-07 11:35:06 +00:00
}
2019-05-30 18:51:13 +00:00
// Look up the root hash belonging to the request
var (
number * uint64
header * types . Header
trie state . Trie
)
if request . BHash != lastBHash {
root , lastBHash = common . Hash { } , request . BHash
if number = rawdb . ReadHeaderNumber ( pm . chainDb , request . BHash ) ; number == nil {
p . Log ( ) . Warn ( "Failed to retrieve block num for proof" , "hash" , request . BHash )
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-05-30 18:51:13 +00:00
continue
}
if header = rawdb . ReadHeader ( pm . chainDb , request . BHash , * number ) ; header == nil {
p . Log ( ) . Warn ( "Failed to retrieve header for proof" , "block" , * number , "hash" , request . BHash )
continue
}
2019-06-11 07:40:32 +00:00
// Refuse to search stale state data in the database since looking for
// a non-exist key is kind of expensive.
local := pm . blockchain . CurrentHeader ( ) . Number . Uint64 ( )
if ! pm . server . archiveMode && header . Number . Uint64 ( ) + core . TriesInMemory <= local {
p . Log ( ) . Debug ( "Reject stale trie request" , "number" , header . Number . Uint64 ( ) , "head" , local )
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-06-11 07:40:32 +00:00
continue
}
2019-05-30 18:51:13 +00:00
root = header . Root
2019-03-18 11:19:40 +00:00
}
2019-06-12 08:57:00 +00:00
// If a header lookup failed (non existent), ignore subsequent requests for the same header
if root == ( common . Hash { } ) {
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-06-12 08:57:00 +00:00
continue
}
2019-05-30 18:51:13 +00:00
// Open the account or storage trie for the request
statedb := pm . blockchain . StateCache ( )
switch len ( request . AccKey ) {
case 0 :
// No account key specified, open an account trie
trie , err = statedb . OpenTrie ( root )
if trie == nil || err != nil {
p . Log ( ) . Warn ( "Failed to open storage trie for proof" , "block" , header . Number , "hash" , header . Hash ( ) , "root" , root , "err" , err )
continue
}
default :
// Account key specified, open a storage trie
account , err := pm . getAccount ( statedb . TrieDB ( ) , root , common . BytesToHash ( request . AccKey ) )
if err != nil {
p . Log ( ) . Warn ( "Failed to retrieve account for proof" , "block" , header . Number , "hash" , header . Hash ( ) , "account" , common . BytesToHash ( request . AccKey ) , "err" , err )
2019-06-12 11:09:40 +00:00
atomic . AddUint32 ( & p . invalidCount , 1 )
2019-05-30 18:51:13 +00:00
continue
}
trie , err = statedb . OpenStorageTrie ( common . BytesToHash ( request . AccKey ) , account . Root )
if trie == nil || err != nil {
p . Log ( ) . Warn ( "Failed to open storage trie for proof" , "block" , header . Number , "hash" , header . Hash ( ) , "account" , common . BytesToHash ( request . AccKey ) , "root" , account . Root , "err" , err )
continue
}
2019-03-18 11:19:40 +00:00
}
2019-05-30 18:51:13 +00:00
// Prove the user's request from the account or stroage trie
if err := trie . Prove ( request . Key , request . FromLevel , nodes ) ; err != nil {
p . Log ( ) . Warn ( "Failed to prove state request" , "block" , header . Number , "hash" , header . Hash ( ) , "err" , err )
2019-03-18 11:19:40 +00:00
continue
}
2019-05-30 18:51:13 +00:00
if nodes . DataSize ( ) >= softResponseLimit {
break
2019-02-26 11:32:48 +00:00
}
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , uint64 ( reqCnt ) , p . ReplyProofsV2 ( req . ReqID , nodes . NodeList ( ) ) , task . done ( ) )
} ( )
}
2017-10-24 13:19:09 +00:00
case ProofsV2Msg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
p . Log ( ) . Trace ( "Received les/2 proofs response" )
// A batch of merkle proofs arrived to one of our previous requests
var resp struct {
ReqID , BV uint64
Data light . NodeList
}
if err := msg . Decode ( & resp ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2019-02-26 11:32:48 +00:00
p . fcServer . ReceivedReply ( resp . ReqID , resp . BV )
2017-10-24 13:19:09 +00:00
deliverMsg = & Msg {
MsgType : MsgProofsV2 ,
2016-10-14 03:51:29 +00:00
ReqID : resp . ReqID ,
Obj : resp . Data ,
}
2017-10-24 13:19:09 +00:00
case GetHelperTrieProofsMsg :
p . Log ( ) . Trace ( "Received helper trie proof request" )
// Decode the retrieval message
var req struct {
ReqID uint64
Reqs [ ] HelperTrieReq
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
// Gather state data until the fetch or network limits is reached
var (
auxBytes int
auxData [ ] [ ] byte
)
reqCnt := len ( req . Reqs )
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , uint64 ( reqCnt ) , MaxHelperTrieProofsFetch ) {
go func ( ) {
2019-02-26 11:32:48 +00:00
2019-05-30 18:51:13 +00:00
var (
lastIdx uint64
lastType uint
root common . Hash
auxTrie * trie . Trie
)
nodes := light . NewNodeSet ( )
for i , request := range req . Reqs {
if i != 0 && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
2019-02-26 11:32:48 +00:00
}
2019-05-30 18:51:13 +00:00
if auxTrie == nil || request . Type != lastType || request . TrieIdx != lastIdx {
auxTrie , lastType , lastIdx = nil , request . Type , request . TrieIdx
var prefix string
if root , prefix = pm . getHelperTrie ( request . Type , request . TrieIdx ) ; root != ( common . Hash { } ) {
auxTrie , _ = trie . New ( root , trie . NewDatabase ( rawdb . NewTable ( pm . chainDb , prefix ) ) )
}
2019-02-26 11:32:48 +00:00
}
2019-05-30 18:51:13 +00:00
if request . AuxReq == auxRoot {
var data [ ] byte
if root != ( common . Hash { } ) {
data = root [ : ]
}
2019-02-26 11:32:48 +00:00
auxData = append ( auxData , data )
auxBytes += len ( data )
2019-05-30 18:51:13 +00:00
} else {
if auxTrie != nil {
auxTrie . Prove ( request . Key , request . FromLevel , nodes )
}
if request . AuxReq != 0 {
data := pm . getHelperTrieAuxData ( request )
auxData = append ( auxData , data )
auxBytes += len ( data )
}
}
if nodes . DataSize ( ) + auxBytes >= softResponseLimit {
break
2019-02-26 11:32:48 +00:00
}
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , uint64 ( reqCnt ) , p . ReplyHelperTrieProofs ( req . ReqID , HelperTrieResps { Proofs : nodes . NodeList ( ) , AuxData : auxData } ) , task . done ( ) )
} ( )
}
2017-10-24 13:19:09 +00:00
case HelperTrieProofsMsg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
p . Log ( ) . Trace ( "Received helper trie proof response" )
var resp struct {
ReqID , BV uint64
Data HelperTrieResps
}
if err := msg . Decode ( & resp ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2019-02-26 11:32:48 +00:00
p . fcServer . ReceivedReply ( resp . ReqID , resp . BV )
2017-10-24 13:19:09 +00:00
deliverMsg = & Msg {
MsgType : MsgHelperTrieProofs ,
ReqID : resp . ReqID ,
Obj : resp . Data ,
}
case SendTxV2Msg :
if pm . txpool == nil {
return errResp ( ErrRequestRejected , "" )
}
// Transactions arrived, parse all of them and deliver to the pool
var req struct {
ReqID uint64
Txs [ ] * types . Transaction
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
reqCnt := len ( req . Txs )
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , uint64 ( reqCnt ) , MaxTxSend ) {
go func ( ) {
stats := make ( [ ] light . TxStatus , len ( req . Txs ) )
for i , tx := range req . Txs {
if i != 0 && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
2019-02-26 11:32:48 +00:00
}
2019-05-30 18:51:13 +00:00
hash := tx . Hash ( )
2019-02-26 11:32:48 +00:00
stats [ i ] = pm . txStatus ( hash )
2019-05-30 18:51:13 +00:00
if stats [ i ] . Status == core . TxStatusUnknown {
if errs := pm . txpool . AddRemotes ( [ ] * types . Transaction { tx } ) ; errs [ 0 ] != nil {
stats [ i ] . Error = errs [ 0 ] . Error ( )
continue
}
stats [ i ] = pm . txStatus ( hash )
}
2017-10-25 09:18:44 +00:00
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , uint64 ( reqCnt ) , p . ReplyTxStatus ( req . ReqID , stats ) , task . done ( ) )
} ( )
}
2017-10-24 13:19:09 +00:00
case GetTxStatusMsg :
if pm . txpool == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
// Transactions arrived, parse all of them and deliver to the pool
var req struct {
2017-10-25 09:18:44 +00:00
ReqID uint64
Hashes [ ] common . Hash
2017-10-24 13:19:09 +00:00
}
if err := msg . Decode ( & req ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2017-10-25 09:18:44 +00:00
reqCnt := len ( req . Hashes )
2019-05-30 18:51:13 +00:00
if accept ( req . ReqID , uint64 ( reqCnt ) , MaxTxStatus ) {
go func ( ) {
stats := make ( [ ] light . TxStatus , len ( req . Hashes ) )
for i , hash := range req . Hashes {
if i != 0 && ! task . waitOrStop ( ) {
sendResponse ( req . ReqID , 0 , nil , task . servingTime )
return
}
stats [ i ] = pm . txStatus ( hash )
2019-02-26 11:32:48 +00:00
}
2019-05-30 18:51:13 +00:00
sendResponse ( req . ReqID , uint64 ( reqCnt ) , p . ReplyTxStatus ( req . ReqID , stats ) , task . done ( ) )
} ( )
}
2017-10-24 13:19:09 +00:00
case TxStatusMsg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
p . Log ( ) . Trace ( "Received tx status response" )
var resp struct {
ReqID , BV uint64
2019-05-13 11:41:10 +00:00
Status [ ] light . TxStatus
2017-10-24 13:19:09 +00:00
}
if err := msg . Decode ( & resp ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
2019-02-26 11:32:48 +00:00
p . fcServer . ReceivedReply ( resp . ReqID , resp . BV )
2017-10-24 13:19:09 +00:00
2019-05-13 11:41:10 +00:00
p . Log ( ) . Trace ( "Received helper trie proof response" )
deliverMsg = & Msg {
MsgType : MsgTxStatus ,
ReqID : resp . ReqID ,
Obj : resp . Status ,
}
2019-05-30 18:51:13 +00:00
case StopMsg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
p . freezeServer ( true )
pm . retriever . frozen ( p )
p . Log ( ) . Warn ( "Service stopped" )
case ResumeMsg :
if pm . odr == nil {
return errResp ( ErrUnexpectedResponse , "" )
}
var bv uint64
if err := msg . Decode ( & bv ) ; err != nil {
return errResp ( ErrDecode , "msg %v: %v" , msg , err )
}
p . fcServer . ResumeFreeze ( bv )
p . freezeServer ( false )
p . Log ( ) . Warn ( "Service resumed" )
2016-10-14 03:51:29 +00:00
default :
2017-03-03 09:41:52 +00:00
p . Log ( ) . Trace ( "Received unknown message" , "code" , msg . Code )
2016-10-14 03:51:29 +00:00
return errResp ( ErrInvalidMsgCode , "%v" , msg . Code )
}
if deliverMsg != nil {
2017-06-21 10:27:38 +00:00
err := pm . retriever . deliver ( p , deliverMsg )
2017-03-22 19:44:22 +00:00
if err != nil {
p . responseErrors ++
if p . responseErrors > maxResponseErrors {
return err
}
}
2016-10-14 03:51:29 +00:00
}
2019-06-12 11:09:40 +00:00
// If the client has made too much invalid request(e.g. request a non-exist data),
// reject them to prevent SPAM attack.
if atomic . LoadUint32 ( & p . invalidCount ) > maxRequestErrors {
return errTooManyInvalidRequest
}
2016-10-14 03:51:29 +00:00
return nil
}
2018-02-05 16:40:32 +00:00
// getAccount retrieves an account from the state based at root.
2019-03-18 11:19:40 +00:00
func ( pm * ProtocolManager ) getAccount ( triedb * trie . Database , root , hash common . Hash ) ( state . Account , error ) {
trie , err := trie . New ( root , triedb )
2018-02-05 16:40:32 +00:00
if err != nil {
return state . Account { } , err
}
blob , err := trie . TryGet ( hash [ : ] )
if err != nil {
return state . Account { } , err
}
var account state . Account
if err = rlp . DecodeBytes ( blob , & account ) ; err != nil {
return state . Account { } , err
}
return account , nil
}
2017-10-24 13:19:09 +00:00
// getHelperTrie returns the post-processed trie root for the given trie ID and section index
func ( pm * ProtocolManager ) getHelperTrie ( id uint , idx uint64 ) ( common . Hash , string ) {
switch id {
case htCanonical :
2019-04-05 15:40:03 +00:00
sectionHead := rawdb . ReadCanonicalHash ( pm . chainDb , ( idx + 1 ) * pm . iConfig . ChtSize - 1 )
return light . GetChtRoot ( pm . chainDb , idx , sectionHead ) , light . ChtTablePrefix
2017-10-24 13:19:09 +00:00
case htBloomBits :
2018-08-28 07:08:16 +00:00
sectionHead := rawdb . ReadCanonicalHash ( pm . chainDb , ( idx + 1 ) * pm . iConfig . BloomTrieSize - 1 )
2017-10-24 13:19:09 +00:00
return light . GetBloomTrieRoot ( pm . chainDb , idx , sectionHead ) , light . BloomTrieTablePrefix
}
return common . Hash { } , ""
}
// getHelperTrieAuxData returns requested auxiliary data for the given HelperTrie request
func ( pm * ProtocolManager ) getHelperTrieAuxData ( req HelperTrieReq ) [ ] byte {
2018-07-30 09:30:09 +00:00
if req . Type == htCanonical && req . AuxReq == auxHeader && len ( req . Key ) == 8 {
2017-10-24 13:19:09 +00:00
blockNum := binary . BigEndian . Uint64 ( req . Key )
2018-05-07 11:35:06 +00:00
hash := rawdb . ReadCanonicalHash ( pm . chainDb , blockNum )
return rawdb . ReadHeaderRLP ( pm . chainDb , hash , blockNum )
2017-10-24 13:19:09 +00:00
}
return nil
}
2019-05-13 11:41:10 +00:00
func ( pm * ProtocolManager ) txStatus ( hash common . Hash ) light . TxStatus {
var stat light . TxStatus
2019-02-26 11:32:48 +00:00
stat . Status = pm . txpool . Status ( [ ] common . Hash { hash } ) [ 0 ]
// If the transaction is unknown to the pool, try looking it up locally
if stat . Status == core . TxStatusUnknown {
if tx , blockHash , blockNumber , txIndex := rawdb . ReadTransaction ( pm . chainDb , hash ) ; tx != nil {
stat . Status = core . TxStatusIncluded
stat . Lookup = & rawdb . LegacyTxLookupEntry { BlockHash : blockHash , BlockIndex : blockNumber , Index : txIndex }
2017-10-24 13:19:09 +00:00
}
}
2019-02-26 11:32:48 +00:00
return stat
2017-10-24 13:19:09 +00:00
}
2017-06-21 10:27:38 +00:00
// downloaderPeerNotify implements peerSetNotify
type downloaderPeerNotify ProtocolManager
2017-06-28 12:25:08 +00:00
type peerConnection struct {
manager * ProtocolManager
peer * peer
}
2017-06-21 10:27:38 +00:00
2017-06-28 12:25:08 +00:00
func ( pc * peerConnection ) Head ( ) ( common . Hash , * big . Int ) {
return pc . peer . HeadAndTd ( )
}
func ( pc * peerConnection ) RequestHeadersByHash ( origin common . Hash , amount int , skip int , reverse bool ) error {
reqID := genReqID ( )
rq := & distReq {
getCost : func ( dp distPeer ) uint64 {
peer := dp . ( * peer )
return peer . GetRequestCost ( GetBlockHeadersMsg , amount )
} ,
canSend : func ( dp distPeer ) bool {
return dp . ( * peer ) == pc . peer
} ,
request : func ( dp distPeer ) func ( ) {
peer := dp . ( * peer )
cost := peer . GetRequestCost ( GetBlockHeadersMsg , amount )
2019-02-26 11:32:48 +00:00
peer . fcServer . QueuedRequest ( reqID , cost )
2017-06-28 12:25:08 +00:00
return func ( ) { peer . RequestHeadersByHash ( reqID , cost , origin , amount , skip , reverse ) }
} ,
2017-06-21 10:27:38 +00:00
}
2017-06-28 12:25:08 +00:00
_ , ok := <- pc . manager . reqDist . queue ( rq )
if ! ok {
2018-08-15 20:25:46 +00:00
return light . ErrNoPeers
2017-06-28 12:25:08 +00:00
}
return nil
}
func ( pc * peerConnection ) RequestHeadersByNumber ( origin uint64 , amount int , skip int , reverse bool ) error {
reqID := genReqID ( )
rq := & distReq {
getCost : func ( dp distPeer ) uint64 {
peer := dp . ( * peer )
return peer . GetRequestCost ( GetBlockHeadersMsg , amount )
} ,
canSend : func ( dp distPeer ) bool {
return dp . ( * peer ) == pc . peer
} ,
request : func ( dp distPeer ) func ( ) {
peer := dp . ( * peer )
cost := peer . GetRequestCost ( GetBlockHeadersMsg , amount )
2019-02-26 11:32:48 +00:00
peer . fcServer . QueuedRequest ( reqID , cost )
2017-06-28 12:25:08 +00:00
return func ( ) { peer . RequestHeadersByNumber ( reqID , cost , origin , amount , skip , reverse ) }
} ,
}
_ , ok := <- pc . manager . reqDist . queue ( rq )
if ! ok {
2018-08-15 20:25:46 +00:00
return light . ErrNoPeers
2017-06-21 10:27:38 +00:00
}
2017-06-28 12:25:08 +00:00
return nil
}
func ( d * downloaderPeerNotify ) registerPeer ( p * peer ) {
pm := ( * ProtocolManager ) ( d )
pc := & peerConnection {
manager : pm ,
peer : p ,
}
2017-07-03 14:17:12 +00:00
pm . downloader . RegisterLightPeer ( p . id , ethVersion , pc )
2017-06-21 10:27:38 +00:00
}
func ( d * downloaderPeerNotify ) unregisterPeer ( p * peer ) {
pm := ( * ProtocolManager ) ( d )
pm . downloader . UnregisterPeer ( p . id )
}