// 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 ( "math/big" "math/rand" "sync" mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" ) const ( // maxKnownTxs is the maximum transactions hashes to keep in the known list // before starting to randomly evict them. maxKnownTxs = 32768 // maxKnownBlocks is the maximum block hashes to keep in the known list // before starting to randomly evict them. maxKnownBlocks = 1024 // maxQueuedTxs is the maximum number of transactions to queue up before dropping // older broadcasts. maxQueuedTxs = 4096 // maxQueuedTxAnns is the maximum number of transaction announcements to queue up // before dropping older announcements. maxQueuedTxAnns = 4096 // maxQueuedBlocks is the maximum number of block propagations to queue up before // dropping broadcasts. There's not much point in queueing stale blocks, so a few // that might cover uncles should be enough. maxQueuedBlocks = 4 // maxQueuedBlockAnns is the maximum number of block announcements to queue up before // dropping broadcasts. Similarly to block propagations, there's no point to queue // above some healthy uncle limit, so use that. maxQueuedBlockAnns = 4 ) // max is a helper function which returns the larger of the two given integers. func max(a, b int) int { if a > b { return a } return b } // Peer is a collection of relevant information we have about a `eth` peer. type Peer struct { id string // Unique ID for the peer, cached *p2p.Peer // The embedded P2P package peer rw p2p.MsgReadWriter // Input/output streams for snap version uint // Protocol version negotiated head common.Hash // Latest advertised head block hash td *big.Int // Latest advertised head block total difficulty knownBlocks *knownCache // Set of block hashes known to be known by this peer queuedBlocks chan *blockPropagation // Queue of blocks to broadcast to the peer queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer txpool TxPool // Transaction pool used by the broadcasters for liveness checks knownTxs *knownCache // Set of transaction hashes known to be known by this peer txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests reqDispatch chan *request // Dispatch channel to send requests and track then until fulfilment reqCancel chan *cancel // Dispatch channel to cancel pending requests and untrack them resDispatch chan *response // Dispatch channel to fulfil pending requests and untrack them term chan struct{} // Termination channel to stop the broadcasters lock sync.RWMutex // Mutex protecting the internal fields } // NewPeer create a wrapper for a network connection and negotiated protocol // version. func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer { peer := &Peer{ id: p.ID().String(), Peer: p, rw: rw, version: version, knownTxs: newKnownCache(maxKnownTxs), knownBlocks: newKnownCache(maxKnownBlocks), queuedBlocks: make(chan *blockPropagation, maxQueuedBlocks), queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns), txBroadcast: make(chan []common.Hash), txAnnounce: make(chan []common.Hash), reqDispatch: make(chan *request), reqCancel: make(chan *cancel), resDispatch: make(chan *response), txpool: txpool, term: make(chan struct{}), } // Start up all the broadcasters go peer.broadcastBlocks() go peer.broadcastTransactions() go peer.announceTransactions() go peer.dispatcher() return peer } // Close signals the broadcast goroutine to terminate. Only ever call this if // you created the peer yourself via NewPeer. Otherwise let whoever created it // clean it up! func (p *Peer) Close() { close(p.term) } // ID retrieves the peer's unique identifier. func (p *Peer) ID() string { return p.id } // Version retrieves the peer's negotiated `eth` protocol version. func (p *Peer) Version() uint { return p.version } // Head retrieves the current head hash and total difficulty of the peer. func (p *Peer) Head() (hash common.Hash, td *big.Int) { p.lock.RLock() defer p.lock.RUnlock() copy(hash[:], p.head[:]) return hash, new(big.Int).Set(p.td) } // SetHead updates the head hash and total difficulty of the peer. func (p *Peer) SetHead(hash common.Hash, td *big.Int) { p.lock.Lock() defer p.lock.Unlock() copy(p.head[:], hash[:]) p.td.Set(td) } // KnownBlock returns whether peer is known to already have a block. func (p *Peer) KnownBlock(hash common.Hash) bool { return p.knownBlocks.Contains(hash) } // KnownTransaction returns whether peer is known to already have a transaction. func (p *Peer) KnownTransaction(hash common.Hash) bool { return p.knownTxs.Contains(hash) } // markBlock marks a block as known for the peer, ensuring that the block will // never be propagated to this particular peer. func (p *Peer) markBlock(hash common.Hash) { // If we reached the memory allowance, drop a previously known block hash p.knownBlocks.Add(hash) } // markTransaction marks a transaction as known for the peer, ensuring that it // will never be propagated to this particular peer. func (p *Peer) markTransaction(hash common.Hash) { // If we reached the memory allowance, drop a previously known transaction hash p.knownTxs.Add(hash) } // SendTransactions sends transactions to the peer and includes the hashes // in its transaction hash set for future reference. // // This method is a helper used by the async transaction sender. Don't call it // directly as the queueing (memory) and transmission (bandwidth) costs should // not be managed directly. // // The reasons this is public is to allow packages using this protocol to write // tests that directly send messages without having to do the asyn queueing. func (p *Peer) SendTransactions(txs types.Transactions) error { // Mark all the transactions as known, but ensure we don't overflow our limits for _, tx := range txs { p.knownTxs.Add(tx.Hash()) } return p2p.Send(p.rw, TransactionsMsg, txs) } // AsyncSendTransactions queues a list of transactions (by hash) to eventually // propagate to a remote peer. The number of pending sends are capped (new ones // will force old sends to be dropped) func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { select { case p.txBroadcast <- hashes: // Mark all the transactions as known, but ensure we don't overflow our limits p.knownTxs.Add(hashes...) case <-p.term: p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) } } // sendPooledTransactionHashes sends transaction hashes to the peer and includes // them in its transaction hash set for future reference. // // This method is a helper used by the async transaction announcer. Don't call it // directly as the queueing (memory) and transmission (bandwidth) costs should // not be managed directly. func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash) error { // Mark all the transactions as known, but ensure we don't overflow our limits p.knownTxs.Add(hashes...) return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket(hashes)) } // AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually // announce to a remote peer. The number of pending sends are capped (new ones // will force old sends to be dropped) func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { select { case p.txAnnounce <- hashes: // Mark all the transactions as known, but ensure we don't overflow our limits p.knownTxs.Add(hashes...) case <-p.term: p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) } } // ReplyPooledTransactionsRLP is the eth/66 version of SendPooledTransactionsRLP. func (p *Peer) ReplyPooledTransactionsRLP(id uint64, hashes []common.Hash, txs []rlp.RawValue) error { // Mark all the transactions as known, but ensure we don't overflow our limits p.knownTxs.Add(hashes...) // Not packed into PooledTransactionsPacket to avoid RLP decoding return p2p.Send(p.rw, PooledTransactionsMsg, &PooledTransactionsRLPPacket66{ RequestId: id, PooledTransactionsRLPPacket: txs, }) } // SendNewBlockHashes announces the availability of a number of blocks through // a hash notification. func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { // Mark all the block hashes as known, but ensure we don't overflow our limits p.knownBlocks.Add(hashes...) request := make(NewBlockHashesPacket, len(hashes)) for i := 0; i < len(hashes); i++ { request[i].Hash = hashes[i] request[i].Number = numbers[i] } return p2p.Send(p.rw, NewBlockHashesMsg, request) } // AsyncSendNewBlockHash queues the availability of a block for propagation to a // remote peer. If the peer's broadcast queue is full, the event is silently // dropped. func (p *Peer) AsyncSendNewBlockHash(block *types.Block) { select { case p.queuedBlockAnns <- block: // Mark all the block hash as known, but ensure we don't overflow our limits p.knownBlocks.Add(block.Hash()) default: p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash()) } } // SendNewBlock propagates an entire block to a remote peer. func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { // Mark all the block hash as known, but ensure we don't overflow our limits p.knownBlocks.Add(block.Hash()) return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{ Block: block, TD: td, }) } // AsyncSendNewBlock queues an entire block for propagation to a remote peer. If // the peer's broadcast queue is full, the event is silently dropped. func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { select { case p.queuedBlocks <- &blockPropagation{block: block, td: td}: // Mark all the block hash as known, but ensure we don't overflow our limits p.knownBlocks.Add(block.Hash()) default: p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash()) } } // ReplyBlockHeadersRLP is the eth/66 response to GetBlockHeaders. func (p *Peer) ReplyBlockHeadersRLP(id uint64, headers []rlp.RawValue) error { return p2p.Send(p.rw, BlockHeadersMsg, &BlockHeadersRLPPacket66{ RequestId: id, BlockHeadersRLPPacket: headers, }) } // ReplyBlockBodiesRLP is the eth/66 response to GetBlockBodies. func (p *Peer) ReplyBlockBodiesRLP(id uint64, bodies []rlp.RawValue) error { // Not packed into BlockBodiesPacket to avoid RLP decoding return p2p.Send(p.rw, BlockBodiesMsg, &BlockBodiesRLPPacket66{ RequestId: id, BlockBodiesRLPPacket: bodies, }) } // ReplyNodeData is the eth/66 response to GetNodeData. func (p *Peer) ReplyNodeData(id uint64, data [][]byte) error { return p2p.Send(p.rw, NodeDataMsg, &NodeDataPacket66{ RequestId: id, NodeDataPacket: data, }) } // ReplyReceiptsRLP is the eth/66 response to GetReceipts. func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error { return p2p.Send(p.rw, ReceiptsMsg, &ReceiptsRLPPacket66{ RequestId: id, ReceiptsRLPPacket: receipts, }) } // RequestOneHeader is a wrapper around the header query functions to fetch a // single header. It is used solely by the fetcher. func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching single header", "hash", hash) id := rand.Uint64() req := &Request{ id: id, sink: sink, code: GetBlockHeadersMsg, want: BlockHeadersMsg, data: &GetBlockHeadersPacket66{ RequestId: id, GetBlockHeadersPacket: &GetBlockHeadersPacket{ Origin: HashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false, }, }, } if err := p.dispatchRequest(req); err != nil { return nil, err } return req, nil } // RequestHeadersByHash fetches a batch of blocks' headers corresponding to the // specified header query, based on the hash of an origin block. func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) id := rand.Uint64() req := &Request{ id: id, sink: sink, code: GetBlockHeadersMsg, want: BlockHeadersMsg, data: &GetBlockHeadersPacket66{ RequestId: id, GetBlockHeadersPacket: &GetBlockHeadersPacket{ Origin: HashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse, }, }, } if err := p.dispatchRequest(req); err != nil { return nil, err } return req, nil } // RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the // specified header query, based on the number of an origin block. func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) id := rand.Uint64() req := &Request{ id: id, sink: sink, code: GetBlockHeadersMsg, want: BlockHeadersMsg, data: &GetBlockHeadersPacket66{ RequestId: id, GetBlockHeadersPacket: &GetBlockHeadersPacket{ Origin: HashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse, }, }, } if err := p.dispatchRequest(req); err != nil { return nil, err } return req, nil } // RequestBodies fetches a batch of blocks' bodies corresponding to the hashes // specified. func (p *Peer) RequestBodies(hashes []common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) id := rand.Uint64() req := &Request{ id: id, sink: sink, code: GetBlockBodiesMsg, want: BlockBodiesMsg, data: &GetBlockBodiesPacket66{ RequestId: id, GetBlockBodiesPacket: hashes, }, } if err := p.dispatchRequest(req); err != nil { return nil, err } return req, nil } // RequestNodeData fetches a batch of arbitrary data from a node's known state // data, corresponding to the specified hashes. func (p *Peer) RequestNodeData(hashes []common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of state data", "count", len(hashes)) id := rand.Uint64() req := &Request{ id: id, sink: sink, code: GetNodeDataMsg, want: NodeDataMsg, data: &GetNodeDataPacket66{ RequestId: id, GetNodeDataPacket: hashes, }, } if err := p.dispatchRequest(req); err != nil { return nil, err } return req, nil } // RequestReceipts fetches a batch of transaction receipts from a remote node. func (p *Peer) RequestReceipts(hashes []common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) id := rand.Uint64() req := &Request{ id: id, sink: sink, code: GetReceiptsMsg, want: ReceiptsMsg, data: &GetReceiptsPacket66{ RequestId: id, GetReceiptsPacket: hashes, }, } if err := p.dispatchRequest(req); err != nil { return nil, err } return req, nil } // RequestTxs fetches a batch of transactions from a remote node. func (p *Peer) RequestTxs(hashes []common.Hash) error { p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) id := rand.Uint64() requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id) return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket66{ RequestId: id, GetPooledTransactionsPacket: hashes, }) } // knownCache is a cache for known hashes. type knownCache struct { hashes mapset.Set max int } // newKnownCache creates a new knownCache with a max capacity. func newKnownCache(max int) *knownCache { return &knownCache{ max: max, hashes: mapset.NewSet(), } } // Add adds a list of elements to the set. func (k *knownCache) Add(hashes ...common.Hash) { for k.hashes.Cardinality() > max(0, k.max-len(hashes)) { k.hashes.Pop() } for _, hash := range hashes { k.hashes.Add(hash) } } // Contains returns whether the given item is in the set. func (k *knownCache) Contains(hash common.Hash) bool { return k.hashes.Contains(hash) } // Cardinality returns the number of elements in the set. func (k *knownCache) Cardinality() int { return k.hashes.Cardinality() }