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 }