github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/protocols/snap/snapstream/snapleecher/peer.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Contains the active peer-set of the downloader, maintaining both failures
    18  // as well as reputation metrics to prioritize the block retrievals.
    19  
    20  package snapleecher
    21  
    22  import (
    23  	"errors"
    24  	"sort"
    25  	"sync"
    26  	"sync/atomic"
    27  	"time"
    28  
    29  	"github.com/unicornultrafoundation/go-u2u/common"
    30  	"github.com/unicornultrafoundation/go-u2u/event"
    31  	"github.com/unicornultrafoundation/go-u2u/log"
    32  	"github.com/unicornultrafoundation/go-u2u/p2p/msgrate"
    33  )
    34  
    35  const (
    36  	maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items
    37  
    38  	// ETH protocol constants
    39  	eth65       = 65
    40  	eth66       = 66
    41  	nodeDataMsg = 0x0e
    42  )
    43  
    44  var (
    45  	errAlreadyFetching   = errors.New("already fetching blocks from peer")
    46  	errAlreadyRegistered = errors.New("peer is already registered")
    47  	errNotRegistered     = errors.New("peer is not registered")
    48  )
    49  
    50  // peerConnection represents an active peer from which hashes and blocks are retrieved.
    51  type peerConnection struct {
    52  	id string // Unique identifier of the peer
    53  
    54  	stateIdle int32 // Current node data activity state of the peer (idle = 0, active = 1)
    55  
    56  	stateStarted time.Time // Time instance when the last node data fetch was started
    57  
    58  	rates   *msgrate.Tracker         // Tracker to hone in on the number of items retrievable per second
    59  	lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously)
    60  
    61  	peer Peer
    62  
    63  	version uint       // Eth protocol version number to switch strategies
    64  	log     log.Logger // Contextual logger to add extra infos to peer logs
    65  	lock    sync.RWMutex
    66  }
    67  
    68  // Peer encapsulates the methods required to synchronise with a remote full peer.
    69  type Peer interface {
    70  	RequestNodeData([]common.Hash) error
    71  }
    72  
    73  // newPeerConnection creates a new downloader peer.
    74  func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection {
    75  	return &peerConnection{
    76  		id:      id,
    77  		lacking: make(map[common.Hash]struct{}),
    78  		peer:    peer,
    79  		version: version,
    80  		log:     logger,
    81  	}
    82  }
    83  
    84  // Reset clears the internal state of a peer entity.
    85  func (p *peerConnection) Reset() {
    86  	p.lock.Lock()
    87  	defer p.lock.Unlock()
    88  
    89  	atomic.StoreInt32(&p.stateIdle, 0)
    90  
    91  	p.lacking = make(map[common.Hash]struct{})
    92  }
    93  
    94  // FetchNodeData sends a node state data retrieval request to the remote peer.
    95  func (p *peerConnection) FetchNodeData(hashes []common.Hash) error {
    96  	// Short circuit if the peer is already fetching
    97  	if !atomic.CompareAndSwapInt32(&p.stateIdle, 0, 1) {
    98  		return errAlreadyFetching
    99  	}
   100  	p.stateStarted = time.Now()
   101  
   102  	go p.peer.RequestNodeData(hashes)
   103  
   104  	return nil
   105  }
   106  
   107  // SetNodeDataIdle sets the peer to idle, allowing it to execute new state trie
   108  // data retrieval requests. Its estimated state retrieval throughput is updated
   109  // with that measured just now.
   110  func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) {
   111  	p.rates.Update(nodeDataMsg, deliveryTime.Sub(p.stateStarted), delivered)
   112  	atomic.StoreInt32(&p.stateIdle, 0)
   113  }
   114  
   115  // NodeDataCapacity retrieves the peers state download allowance based on its
   116  // previously discovered throughput.
   117  func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int {
   118  	cap := p.rates.Capacity(nodeDataMsg, targetRTT)
   119  	if cap > MaxStateFetch {
   120  		cap = MaxStateFetch
   121  	}
   122  	return cap
   123  }
   124  
   125  // MarkLacking appends a new entity to the set of items (blocks, receipts, states)
   126  // that a peer is known not to have (i.e. have been requested before). If the
   127  // set reaches its maximum allowed capacity, items are randomly dropped off.
   128  func (p *peerConnection) MarkLacking(hash common.Hash) {
   129  	p.lock.Lock()
   130  	defer p.lock.Unlock()
   131  
   132  	for len(p.lacking) >= maxLackingHashes {
   133  		for drop := range p.lacking {
   134  			delete(p.lacking, drop)
   135  			break
   136  		}
   137  	}
   138  	p.lacking[hash] = struct{}{}
   139  }
   140  
   141  // Lacks retrieves whether the hash of a blockchain item is on the peers lacking
   142  // list (i.e. whether we know that the peer does not have it).
   143  func (p *peerConnection) Lacks(hash common.Hash) bool {
   144  	p.lock.RLock()
   145  	defer p.lock.RUnlock()
   146  
   147  	_, ok := p.lacking[hash]
   148  	return ok
   149  }
   150  
   151  // peerSet represents the collection of active peer participating in the chain
   152  // download procedure.
   153  type peerSet struct {
   154  	peers map[string]*peerConnection
   155  	rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat
   156  
   157  	newPeerFeed  event.Feed
   158  	peerDropFeed event.Feed
   159  
   160  	lock sync.RWMutex
   161  }
   162  
   163  // newPeerSet creates a new peer set top track the active download sources.
   164  func newPeerSet() *peerSet {
   165  	return &peerSet{
   166  		peers: make(map[string]*peerConnection),
   167  		rates: msgrate.NewTrackers(log.New("proto", "eth")),
   168  	}
   169  }
   170  
   171  // SubscribeNewPeers subscribes to peer arrival events.
   172  func (ps *peerSet) SubscribeNewPeers(ch chan<- *peerConnection) event.Subscription {
   173  	return ps.newPeerFeed.Subscribe(ch)
   174  }
   175  
   176  // SubscribePeerDrops subscribes to peer departure events.
   177  func (ps *peerSet) SubscribePeerDrops(ch chan<- *peerConnection) event.Subscription {
   178  	return ps.peerDropFeed.Subscribe(ch)
   179  }
   180  
   181  // Reset iterates over the current peer set, and resets each of the known peers
   182  // to prepare for a next batch of block retrieval.
   183  func (ps *peerSet) Reset() {
   184  	ps.lock.RLock()
   185  	defer ps.lock.RUnlock()
   186  
   187  	for _, peer := range ps.peers {
   188  		peer.Reset()
   189  	}
   190  }
   191  
   192  // Register injects a new peer into the working set, or returns an error if the
   193  // peer is already known.
   194  //
   195  // The method also sets the starting throughput values of the new peer to the
   196  // average of all existing peers, to give it a realistic chance of being used
   197  // for data retrievals.
   198  func (ps *peerSet) Register(p *peerConnection) error {
   199  	// Register the new peer with some meaningful defaults
   200  	ps.lock.Lock()
   201  	if _, ok := ps.peers[p.id]; ok {
   202  		ps.lock.Unlock()
   203  		return errAlreadyRegistered
   204  	}
   205  	p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip())
   206  	if err := ps.rates.Track(p.id, p.rates); err != nil {
   207  		return err
   208  	}
   209  	ps.peers[p.id] = p
   210  	ps.lock.Unlock()
   211  
   212  	ps.newPeerFeed.Send(p)
   213  	return nil
   214  }
   215  
   216  // Unregister removes a remote peer from the active set, disabling any further
   217  // actions to/from that particular entity.
   218  func (ps *peerSet) Unregister(id string) error {
   219  	ps.lock.Lock()
   220  	p, ok := ps.peers[id]
   221  	if !ok {
   222  		ps.lock.Unlock()
   223  		return errNotRegistered
   224  	}
   225  	delete(ps.peers, id)
   226  	ps.rates.Untrack(id)
   227  	ps.lock.Unlock()
   228  
   229  	ps.peerDropFeed.Send(p)
   230  	return nil
   231  }
   232  
   233  // Peer retrieves the registered peer with the given id.
   234  func (ps *peerSet) Peer(id string) *peerConnection {
   235  	ps.lock.RLock()
   236  	defer ps.lock.RUnlock()
   237  
   238  	return ps.peers[id]
   239  }
   240  
   241  // Len returns if the current number of peers in the set.
   242  func (ps *peerSet) Len() int {
   243  	ps.lock.RLock()
   244  	defer ps.lock.RUnlock()
   245  
   246  	return len(ps.peers)
   247  }
   248  
   249  // AllPeers retrieves a flat list of all the peers within the set.
   250  func (ps *peerSet) AllPeers() []*peerConnection {
   251  	ps.lock.RLock()
   252  	defer ps.lock.RUnlock()
   253  
   254  	list := make([]*peerConnection, 0, len(ps.peers))
   255  	for _, p := range ps.peers {
   256  		list = append(list, p)
   257  	}
   258  	return list
   259  }
   260  
   261  // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
   262  // peers within the active peer set, ordered by their reputation.
   263  func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
   264  	idle := func(p *peerConnection) bool {
   265  		return atomic.LoadInt32(&p.stateIdle) == 0
   266  	}
   267  	throughput := func(p *peerConnection) int {
   268  		return p.rates.Capacity(nodeDataMsg, time.Second)
   269  	}
   270  	return ps.idlePeers(eth65, eth66, idle, throughput)
   271  }
   272  
   273  // idlePeers retrieves a flat list of all currently idle peers satisfying the
   274  // protocol version constraints, using the provided function to check idleness.
   275  // The resulting set of peers are sorted by their capacity.
   276  func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, capacity func(*peerConnection) int) ([]*peerConnection, int) {
   277  	ps.lock.RLock()
   278  	defer ps.lock.RUnlock()
   279  
   280  	var (
   281  		total = 0
   282  		idle  = make([]*peerConnection, 0, len(ps.peers))
   283  		tps   = make([]int, 0, len(ps.peers))
   284  	)
   285  	for _, p := range ps.peers {
   286  		if p.version >= minProtocol && p.version <= maxProtocol {
   287  			if idleCheck(p) {
   288  				idle = append(idle, p)
   289  				tps = append(tps, capacity(p))
   290  			}
   291  			total++
   292  		}
   293  	}
   294  
   295  	// And sort them
   296  	sortPeers := &peerCapacitySort{idle, tps}
   297  	sort.Sort(sortPeers)
   298  	return sortPeers.p, total
   299  }
   300  
   301  // peerCapacitySort implements sort.Interface.
   302  // It sorts peer connections by capacity (descending).
   303  type peerCapacitySort struct {
   304  	p  []*peerConnection
   305  	tp []int
   306  }
   307  
   308  func (ps *peerCapacitySort) Len() int {
   309  	return len(ps.p)
   310  }
   311  
   312  func (ps *peerCapacitySort) Less(i, j int) bool {
   313  	return ps.tp[i] > ps.tp[j]
   314  }
   315  
   316  func (ps *peerCapacitySort) Swap(i, j int) {
   317  	ps.p[i], ps.p[j] = ps.p[j], ps.p[i]
   318  	ps.tp[i], ps.tp[j] = ps.tp[j], ps.tp[i]
   319  }