// Copyright 2020 The go-ethereum Authors // 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 . package eth import ( "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) const ( // softResponseLimit is the target maximum size of replies to data retrievals. softResponseLimit = 2 * 1024 * 1024 // estHeaderSize is the approximate size of an RLP encoded block header. estHeaderSize = 500 // maxHeadersServe is the maximum number of block headers to serve. This number // is there to limit the number of disk lookups. maxHeadersServe = 1024 // maxBodiesServe is the maximum number of block bodies to serve. This number // is mostly there to limit the number of disk lookups. With 24KB block sizes // nowadays, the practical limit will always be softResponseLimit. maxBodiesServe = 1024 // maxNodeDataServe is the maximum number of state trie nodes to serve. This // number is there to limit the number of disk lookups. maxNodeDataServe = 1024 // maxReceiptsServe is the maximum number of block receipts to serve. This // number is mostly there to limit the number of disk lookups. With block // containing 200+ transactions nowadays, the practical limit will always // be softResponseLimit. maxReceiptsServe = 1024 ) // Handler is a callback to invoke from an outside runner after the boilerplate // exchanges have passed. type Handler func(peer *Peer) error // Backend defines the data retrieval methods to serve remote requests and the // callback methods to invoke on remote deliveries. type Backend interface { // Chain retrieves the blockchain object to serve data. Chain() *core.BlockChain // StateBloom retrieves the bloom filter - if any - for state trie nodes. StateBloom() *trie.SyncBloom // TxPool retrieves the transaction pool object to serve data. TxPool() TxPool // AcceptTxs retrieves whether transaction processing is enabled on the node // or if inbound transactions should simply be dropped. AcceptTxs() bool // RunPeer is invoked when a peer joins on the `eth` protocol. The handler // should do any peer maintenance work, handshakes and validations. If all // is passed, control should be given back to the `handler` to process the // inbound messages going forward. RunPeer(peer *Peer, handler Handler) error // PeerInfo retrieves all known `eth` information about a peer. PeerInfo(id enode.ID) interface{} // Handle is a callback to be invoked when a data packet is received from // the remote peer. Only packets not consumed by the protocol handler will // be forwarded to the backend. Handle(peer *Peer, packet Packet) error } // TxPool defines the methods needed by the protocol handler to serve transactions. type TxPool interface { // Get retrieves the the transaction from the local txpool with the given hash. Get(hash common.Hash) *types.Transaction } // MakeProtocols constructs the P2P protocol definitions for `eth`. func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol { protocols := make([]p2p.Protocol, len(ProtocolVersions)) for i, version := range ProtocolVersions { version := version // Closure protocols[i] = p2p.Protocol{ Name: ProtocolName, Version: version, Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { peer := NewPeer(version, p, rw, backend.TxPool()) defer peer.Close() return backend.RunPeer(peer, func(peer *Peer) error { return Handle(backend, peer) }) }, NodeInfo: func() interface{} { return nodeInfo(backend.Chain(), network) }, PeerInfo: func(id enode.ID) interface{} { return backend.PeerInfo(id) }, Attributes: []enr.Entry{currentENREntry(backend.Chain())}, DialCandidates: dnsdisc, } } return protocols } // NodeInfo represents a short summary of the `eth` sub-protocol metadata // known about the host peer. type NodeInfo struct { Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules Head common.Hash `json:"head"` // Hex hash of the host's best owned block } // nodeInfo retrieves some `eth` protocol metadata about the running host node. func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo { head := chain.CurrentBlock() return &NodeInfo{ Network: network, Difficulty: chain.GetTd(head.Hash(), head.NumberU64()), Genesis: chain.Genesis().Hash(), Config: chain.Config(), Head: head.Hash(), } } // Handle is invoked whenever an `eth` connection is made that successfully passes // the protocol handshake. This method will keep processing messages until the // connection is torn down. func Handle(backend Backend, peer *Peer) error { for { if err := handleMessage(backend, peer); err != nil { peer.Log().Debug("Message handling failed in `eth`", "err", err) return err } } } type msgHandler func(backend Backend, msg Decoder, peer *Peer) error type Decoder interface { Decode(val interface{}) error Time() time.Time } var eth64 = map[uint64]msgHandler{ GetBlockHeadersMsg: handleGetBlockHeaders, BlockHeadersMsg: handleBlockHeaders, GetBlockBodiesMsg: handleGetBlockBodies, BlockBodiesMsg: handleBlockBodies, GetNodeDataMsg: handleGetNodeData, NodeDataMsg: handleNodeData, GetReceiptsMsg: handleGetReceipts, ReceiptsMsg: handleReceipts, NewBlockHashesMsg: handleNewBlockhashes, NewBlockMsg: handleNewBlock, TransactionsMsg: handleTransactions, } var eth65 = map[uint64]msgHandler{ // old 64 messages GetBlockHeadersMsg: handleGetBlockHeaders, BlockHeadersMsg: handleBlockHeaders, GetBlockBodiesMsg: handleGetBlockBodies, BlockBodiesMsg: handleBlockBodies, GetNodeDataMsg: handleGetNodeData, NodeDataMsg: handleNodeData, GetReceiptsMsg: handleGetReceipts, ReceiptsMsg: handleReceipts, NewBlockHashesMsg: handleNewBlockhashes, NewBlockMsg: handleNewBlock, TransactionsMsg: handleTransactions, // New eth65 messages NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, GetPooledTransactionsMsg: handleGetPooledTransactions, PooledTransactionsMsg: handlePooledTransactions, } var eth66 = map[uint64]msgHandler{ // eth64 announcement messages (no id) NewBlockHashesMsg: handleNewBlockhashes, NewBlockMsg: handleNewBlock, TransactionsMsg: handleTransactions, // eth65 announcement messages (no id) NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, // eth66 messages with request-id GetBlockHeadersMsg: handleGetBlockHeaders66, BlockHeadersMsg: handleBlockHeaders66, GetBlockBodiesMsg: handleGetBlockBodies66, BlockBodiesMsg: handleBlockBodies66, GetNodeDataMsg: handleGetNodeData66, NodeDataMsg: handleNodeData66, GetReceiptsMsg: handleGetReceipts66, ReceiptsMsg: handleReceipts66, GetPooledTransactionsMsg: handleGetPooledTransactions66, PooledTransactionsMsg: handlePooledTransactions66, } // handleMessage is invoked whenever an inbound message is received from a remote // peer. The remote connection is torn down upon returning any error. func handleMessage(backend Backend, peer *Peer) error { // Read the next message from the remote peer, and ensure it's fully consumed msg, err := peer.rw.ReadMsg() if err != nil { return err } if msg.Size > maxMessageSize { return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) } defer msg.Discard() var handlers = eth64 if peer.Version() == ETH65 { handlers = eth65 } else if peer.Version() >= ETH66 { handlers = eth66 } if handler := handlers[msg.Code]; handler != nil { return handler(backend, msg, peer) } return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) }