github.com/jeffallen/go-ethereum@v1.1.4-0.20150910155051-571d3236c49c/eth/downloader/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 downloader
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"math"
    26  	"sync"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"gopkg.in/fatih/set.v0"
    32  )
    33  
    34  type relativeHashFetcherFn func(common.Hash) error
    35  type absoluteHashFetcherFn func(uint64, int) error
    36  type blockFetcherFn func([]common.Hash) error
    37  
    38  var (
    39  	errAlreadyFetching   = errors.New("already fetching blocks from peer")
    40  	errAlreadyRegistered = errors.New("peer is already registered")
    41  	errNotRegistered     = errors.New("peer is not registered")
    42  )
    43  
    44  // peer represents an active peer from which hashes and blocks are retrieved.
    45  type peer struct {
    46  	id   string      // Unique identifier of the peer
    47  	head common.Hash // Hash of the peers latest known block
    48  
    49  	idle int32 // Current activity state of the peer (idle = 0, active = 1)
    50  	rep  int32 // Simple peer reputation
    51  
    52  	capacity int32     // Number of blocks allowed to fetch per request
    53  	started  time.Time // Time instance when the last fetch was started
    54  
    55  	ignored *set.Set // Set of hashes not to request (didn't have previously)
    56  
    57  	getRelHashes relativeHashFetcherFn // Method to retrieve a batch of hashes from an origin hash
    58  	getAbsHashes absoluteHashFetcherFn // Method to retrieve a batch of hashes from an absolute position
    59  	getBlocks    blockFetcherFn        // Method to retrieve a batch of blocks
    60  
    61  	version int // Eth protocol version number to switch strategies
    62  }
    63  
    64  // newPeer create a new downloader peer, with specific hash and block retrieval
    65  // mechanisms.
    66  func newPeer(id string, version int, head common.Hash, getRelHashes relativeHashFetcherFn, getAbsHashes absoluteHashFetcherFn, getBlocks blockFetcherFn) *peer {
    67  	return &peer{
    68  		id:           id,
    69  		head:         head,
    70  		capacity:     1,
    71  		getRelHashes: getRelHashes,
    72  		getAbsHashes: getAbsHashes,
    73  		getBlocks:    getBlocks,
    74  		ignored:      set.New(),
    75  		version:      version,
    76  	}
    77  }
    78  
    79  // Reset clears the internal state of a peer entity.
    80  func (p *peer) Reset() {
    81  	atomic.StoreInt32(&p.idle, 0)
    82  	atomic.StoreInt32(&p.capacity, 1)
    83  	p.ignored.Clear()
    84  }
    85  
    86  // Fetch sends a block retrieval request to the remote peer.
    87  func (p *peer) Fetch(request *fetchRequest) error {
    88  	// Short circuit if the peer is already fetching
    89  	if !atomic.CompareAndSwapInt32(&p.idle, 0, 1) {
    90  		return errAlreadyFetching
    91  	}
    92  	p.started = time.Now()
    93  
    94  	// Convert the hash set to a retrievable slice
    95  	hashes := make([]common.Hash, 0, len(request.Hashes))
    96  	for hash, _ := range request.Hashes {
    97  		hashes = append(hashes, hash)
    98  	}
    99  	go p.getBlocks(hashes)
   100  
   101  	return nil
   102  }
   103  
   104  // SetIdle sets the peer to idle, allowing it to execute new retrieval requests.
   105  // Its block retrieval allowance will also be updated either up- or downwards,
   106  // depending on whether the previous fetch completed in time or not.
   107  func (p *peer) SetIdle() {
   108  	// Update the peer's download allowance based on previous performance
   109  	scale := 2.0
   110  	if time.Since(p.started) > blockSoftTTL {
   111  		scale = 0.5
   112  		if time.Since(p.started) > blockHardTTL {
   113  			scale = 1 / float64(MaxBlockFetch) // reduces capacity to 1
   114  		}
   115  	}
   116  	for {
   117  		// Calculate the new download bandwidth allowance
   118  		prev := atomic.LoadInt32(&p.capacity)
   119  		next := int32(math.Max(1, math.Min(float64(MaxBlockFetch), float64(prev)*scale)))
   120  
   121  		// Try to update the old value
   122  		if atomic.CompareAndSwapInt32(&p.capacity, prev, next) {
   123  			// If we're having problems at 1 capacity, try to find better peers
   124  			if next == 1 {
   125  				p.Demote()
   126  			}
   127  			break
   128  		}
   129  	}
   130  	// Set the peer to idle to allow further block requests
   131  	atomic.StoreInt32(&p.idle, 0)
   132  }
   133  
   134  // Capacity retrieves the peers block download allowance based on its previously
   135  // discovered bandwidth capacity.
   136  func (p *peer) Capacity() int {
   137  	return int(atomic.LoadInt32(&p.capacity))
   138  }
   139  
   140  // Promote increases the peer's reputation.
   141  func (p *peer) Promote() {
   142  	atomic.AddInt32(&p.rep, 1)
   143  }
   144  
   145  // Demote decreases the peer's reputation or leaves it at 0.
   146  func (p *peer) Demote() {
   147  	for {
   148  		// Calculate the new reputation value
   149  		prev := atomic.LoadInt32(&p.rep)
   150  		next := prev / 2
   151  
   152  		// Try to update the old value
   153  		if atomic.CompareAndSwapInt32(&p.rep, prev, next) {
   154  			return
   155  		}
   156  	}
   157  }
   158  
   159  // String implements fmt.Stringer.
   160  func (p *peer) String() string {
   161  	return fmt.Sprintf("Peer %s [%s]", p.id,
   162  		fmt.Sprintf("reputation %3d, ", atomic.LoadInt32(&p.rep))+
   163  			fmt.Sprintf("capacity %3d, ", atomic.LoadInt32(&p.capacity))+
   164  			fmt.Sprintf("ignored %4d", p.ignored.Size()),
   165  	)
   166  }
   167  
   168  // peerSet represents the collection of active peer participating in the block
   169  // download procedure.
   170  type peerSet struct {
   171  	peers map[string]*peer
   172  	lock  sync.RWMutex
   173  }
   174  
   175  // newPeerSet creates a new peer set top track the active download sources.
   176  func newPeerSet() *peerSet {
   177  	return &peerSet{
   178  		peers: make(map[string]*peer),
   179  	}
   180  }
   181  
   182  // Reset iterates over the current peer set, and resets each of the known peers
   183  // to prepare for a next batch of block retrieval.
   184  func (ps *peerSet) Reset() {
   185  	ps.lock.RLock()
   186  	defer ps.lock.RUnlock()
   187  
   188  	for _, peer := range ps.peers {
   189  		peer.Reset()
   190  	}
   191  }
   192  
   193  // Register injects a new peer into the working set, or returns an error if the
   194  // peer is already known.
   195  func (ps *peerSet) Register(p *peer) error {
   196  	ps.lock.Lock()
   197  	defer ps.lock.Unlock()
   198  
   199  	if _, ok := ps.peers[p.id]; ok {
   200  		return errAlreadyRegistered
   201  	}
   202  	ps.peers[p.id] = p
   203  	return nil
   204  }
   205  
   206  // Unregister removes a remote peer from the active set, disabling any further
   207  // actions to/from that particular entity.
   208  func (ps *peerSet) Unregister(id string) error {
   209  	ps.lock.Lock()
   210  	defer ps.lock.Unlock()
   211  
   212  	if _, ok := ps.peers[id]; !ok {
   213  		return errNotRegistered
   214  	}
   215  	delete(ps.peers, id)
   216  	return nil
   217  }
   218  
   219  // Peer retrieves the registered peer with the given id.
   220  func (ps *peerSet) Peer(id string) *peer {
   221  	ps.lock.RLock()
   222  	defer ps.lock.RUnlock()
   223  
   224  	return ps.peers[id]
   225  }
   226  
   227  // Len returns if the current number of peers in the set.
   228  func (ps *peerSet) Len() int {
   229  	ps.lock.RLock()
   230  	defer ps.lock.RUnlock()
   231  
   232  	return len(ps.peers)
   233  }
   234  
   235  // AllPeers retrieves a flat list of all the peers within the set.
   236  func (ps *peerSet) AllPeers() []*peer {
   237  	ps.lock.RLock()
   238  	defer ps.lock.RUnlock()
   239  
   240  	list := make([]*peer, 0, len(ps.peers))
   241  	for _, p := range ps.peers {
   242  		list = append(list, p)
   243  	}
   244  	return list
   245  }
   246  
   247  // IdlePeers retrieves a flat list of all the currently idle peers within the
   248  // active peer set, ordered by their reputation.
   249  func (ps *peerSet) IdlePeers() []*peer {
   250  	ps.lock.RLock()
   251  	defer ps.lock.RUnlock()
   252  
   253  	list := make([]*peer, 0, len(ps.peers))
   254  	for _, p := range ps.peers {
   255  		if atomic.LoadInt32(&p.idle) == 0 {
   256  			list = append(list, p)
   257  		}
   258  	}
   259  	for i := 0; i < len(list); i++ {
   260  		for j := i + 1; j < len(list); j++ {
   261  			if atomic.LoadInt32(&list[i].rep) < atomic.LoadInt32(&list[j].rep) {
   262  				list[i], list[j] = list[j], list[i]
   263  			}
   264  		}
   265  	}
   266  	return list
   267  }