github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/les/freeclient.go (about) 1 // Copyright 2016 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 package les 18 19 import ( 20 "io" 21 "math" 22 "net" 23 "sync" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common/mclock" 27 "github.com/ethereum/go-ethereum/common/prque" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/rlp" 31 ) 32 33 // freeClientPool implements a client database that limits the connection time 34 // of each client and manages accepting/rejecting incoming connections and even 35 // kicking out some connected clients. The pool calculates recent usage time 36 // for each known client (a value that increases linearly when the client is 37 // connected and decreases exponentially when not connected). Clients with lower 38 // recent usage are preferred, unknown nodes have the highest priority. Already 39 // connected nodes receive a small bias in their favor in order to avoid accepting 40 // and instantly kicking out clients. 41 // 42 // Note: the pool can use any string for client identification. Using signature 43 // keys for that purpose would not make sense when being known has a negative 44 // value for the client. Currently the LES protocol manager uses IP addresses 45 // (without port address) to identify clients. 46 type freeClientPool struct { 47 db ethdb.Database 48 lock sync.Mutex 49 clock mclock.Clock 50 closed bool 51 removePeer func(string) 52 53 connectedLimit, totalLimit int 54 freeClientCap uint64 55 56 addressMap map[string]*freeClientPoolEntry 57 connPool, disconnPool *prque.Prque 58 startupTime mclock.AbsTime 59 logOffsetAtStartup int64 60 } 61 62 const ( 63 recentUsageExpTC = time.Hour // time constant of the exponential weighting window for "recent" server usage 64 fixedPointMultiplier = 0x1000000 // constant to convert logarithms to fixed point format 65 connectedBias = time.Minute // this bias is applied in favor of already connected clients in order to avoid kicking them out very soon 66 ) 67 68 // newFreeClientPool creates a new free client pool 69 func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int, clock mclock.Clock, removePeer func(string)) *freeClientPool { 70 pool := &freeClientPool{ 71 db: db, 72 clock: clock, 73 addressMap: make(map[string]*freeClientPoolEntry), 74 connPool: prque.New(poolSetIndex), 75 disconnPool: prque.New(poolSetIndex), 76 freeClientCap: freeClientCap, 77 totalLimit: totalLimit, 78 removePeer: removePeer, 79 } 80 pool.loadFromDb() 81 return pool 82 } 83 84 func (f *freeClientPool) stop() { 85 f.lock.Lock() 86 f.closed = true 87 f.saveToDb() 88 f.lock.Unlock() 89 } 90 91 // registerPeer implements clientPool 92 func (f *freeClientPool) registerPeer(p *peer) { 93 if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok { 94 if !f.connect(addr.IP.String(), p.id) { 95 f.removePeer(p.id) 96 } 97 } 98 } 99 100 // connect should be called after a successful handshake. If the connection was 101 // rejected, there is no need to call disconnect. 102 func (f *freeClientPool) connect(address, id string) bool { 103 f.lock.Lock() 104 defer f.lock.Unlock() 105 106 if f.closed { 107 return false 108 } 109 110 if f.connectedLimit == 0 { 111 log.Debug("Client rejected", "address", address) 112 return false 113 } 114 e := f.addressMap[address] 115 now := f.clock.Now() 116 var recentUsage int64 117 if e == nil { 118 e = &freeClientPoolEntry{address: address, index: -1, id: id} 119 f.addressMap[address] = e 120 } else { 121 if e.connected { 122 log.Debug("Client already connected", "address", address) 123 return false 124 } 125 recentUsage = int64(math.Exp(float64(e.logUsage-f.logOffset(now)) / fixedPointMultiplier)) 126 } 127 e.linUsage = recentUsage - int64(now) 128 // check whether (linUsage+connectedBias) is smaller than the highest entry in the connected pool 129 if f.connPool.Size() == f.connectedLimit { 130 i := f.connPool.PopItem().(*freeClientPoolEntry) 131 if e.linUsage+int64(connectedBias)-i.linUsage < 0 { 132 // kick it out and accept the new client 133 f.dropClient(i, now) 134 } else { 135 // keep the old client and reject the new one 136 f.connPool.Push(i, i.linUsage) 137 log.Debug("Client rejected", "address", address) 138 return false 139 } 140 } 141 f.disconnPool.Remove(e.index) 142 e.connected = true 143 e.id = id 144 f.connPool.Push(e, e.linUsage) 145 if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit { 146 f.disconnPool.Pop() 147 } 148 log.Debug("Client accepted", "address", address) 149 return true 150 } 151 152 // unregisterPeer implements clientPool 153 func (f *freeClientPool) unregisterPeer(p *peer) { 154 if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok { 155 f.disconnect(addr.IP.String()) 156 } 157 } 158 159 // disconnect should be called when a connection is terminated. If the disconnection 160 // was initiated by the pool itself using disconnectFn then calling disconnect is 161 // not necessary but permitted. 162 func (f *freeClientPool) disconnect(address string) { 163 f.lock.Lock() 164 defer f.lock.Unlock() 165 166 if f.closed { 167 return 168 } 169 e := f.addressMap[address] 170 now := f.clock.Now() 171 if !e.connected { 172 log.Debug("Client already disconnected", "address", address) 173 return 174 } 175 176 f.connPool.Remove(e.index) 177 f.calcLogUsage(e, now) 178 e.connected = false 179 f.disconnPool.Push(e, -e.logUsage) 180 log.Debug("Client disconnected", "address", address) 181 } 182 183 // setConnLimit sets the maximum number of free client slots and also drops 184 // some peers if necessary 185 func (f *freeClientPool) setLimits(count int, totalCap uint64) { 186 f.lock.Lock() 187 defer f.lock.Unlock() 188 189 f.connectedLimit = int(totalCap / f.freeClientCap) 190 if count < f.connectedLimit { 191 f.connectedLimit = count 192 } 193 now := mclock.Now() 194 for f.connPool.Size() > f.connectedLimit { 195 i := f.connPool.PopItem().(*freeClientPoolEntry) 196 f.dropClient(i, now) 197 } 198 } 199 200 // dropClient disconnects a client and also moves it from the connected to the 201 // disconnected pool 202 func (f *freeClientPool) dropClient(i *freeClientPoolEntry, now mclock.AbsTime) { 203 f.connPool.Remove(i.index) 204 f.calcLogUsage(i, now) 205 i.connected = false 206 f.disconnPool.Push(i, -i.logUsage) 207 log.Debug("Client kicked out", "address", i.address) 208 f.removePeer(i.id) 209 } 210 211 // logOffset calculates the time-dependent offset for the logarithmic 212 // representation of recent usage 213 func (f *freeClientPool) logOffset(now mclock.AbsTime) int64 { 214 // Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor 215 // is to avoid int64 overflow. We assume that int64(recentUsageExpTC) >> fixedPointMultiplier. 216 logDecay := int64((time.Duration(now - f.startupTime)) / (recentUsageExpTC / fixedPointMultiplier)) 217 return f.logOffsetAtStartup + logDecay 218 } 219 220 // calcLogUsage converts recent usage from linear to logarithmic representation 221 // when disconnecting a peer or closing the client pool 222 func (f *freeClientPool) calcLogUsage(e *freeClientPoolEntry, now mclock.AbsTime) { 223 dt := e.linUsage + int64(now) 224 if dt < 1 { 225 dt = 1 226 } 227 e.logUsage = int64(math.Log(float64(dt))*fixedPointMultiplier) + f.logOffset(now) 228 } 229 230 // freeClientPoolStorage is the RLP representation of the pool's database storage 231 type freeClientPoolStorage struct { 232 LogOffset uint64 233 List []*freeClientPoolEntry 234 } 235 236 // loadFromDb restores pool status from the database storage 237 // (automatically called at initialization) 238 func (f *freeClientPool) loadFromDb() { 239 enc, err := f.db.Get([]byte("freeClientPool")) 240 if err != nil { 241 return 242 } 243 var storage freeClientPoolStorage 244 err = rlp.DecodeBytes(enc, &storage) 245 if err != nil { 246 log.Error("Failed to decode client list", "err", err) 247 return 248 } 249 f.logOffsetAtStartup = int64(storage.LogOffset) 250 f.startupTime = f.clock.Now() 251 for _, e := range storage.List { 252 log.Debug("Loaded free client record", "address", e.address, "logUsage", e.logUsage) 253 f.addressMap[e.address] = e 254 f.disconnPool.Push(e, -e.logUsage) 255 } 256 } 257 258 // saveToDb saves pool status to the database storage 259 // (automatically called during shutdown) 260 func (f *freeClientPool) saveToDb() { 261 now := f.clock.Now() 262 storage := freeClientPoolStorage{ 263 LogOffset: uint64(f.logOffset(now)), 264 List: make([]*freeClientPoolEntry, len(f.addressMap)), 265 } 266 i := 0 267 for _, e := range f.addressMap { 268 if e.connected { 269 f.calcLogUsage(e, now) 270 } 271 storage.List[i] = e 272 i++ 273 } 274 enc, err := rlp.EncodeToBytes(storage) 275 if err != nil { 276 log.Error("Failed to encode client list", "err", err) 277 } else { 278 f.db.Put([]byte("freeClientPool"), enc) 279 } 280 } 281 282 // freeClientPoolEntry represents a client address known by the pool. 283 // When connected, recent usage is calculated as linUsage + int64(clock.Now()) 284 // When disconnected, it is calculated as exp(logUsage - logOffset) where logOffset 285 // also grows linearly with time while the server is running. 286 // Conversion between linear and logarithmic representation happens when connecting 287 // or disconnecting the node. 288 // 289 // Note: linUsage and logUsage are values used with constantly growing offsets so 290 // even though they are close to each other at any time they may wrap around int64 291 // limits over time. Comparison should be performed accordingly. 292 type freeClientPoolEntry struct { 293 address, id string 294 connected bool 295 disconnectFn func() 296 linUsage, logUsage int64 297 index int 298 } 299 300 func (e *freeClientPoolEntry) EncodeRLP(w io.Writer) error { 301 return rlp.Encode(w, []interface{}{e.address, uint64(e.logUsage)}) 302 } 303 304 func (e *freeClientPoolEntry) DecodeRLP(s *rlp.Stream) error { 305 var entry struct { 306 Address string 307 LogUsage uint64 308 } 309 if err := s.Decode(&entry); err != nil { 310 return err 311 } 312 e.address = entry.Address 313 e.logUsage = int64(entry.LogUsage) 314 e.connected = false 315 e.index = -1 316 return nil 317 } 318 319 // poolSetIndex callback is used by both priority queues to set/update the index of 320 // the element in the queue. Index is needed to remove elements other than the top one. 321 func poolSetIndex(a interface{}, i int) { 322 a.(*freeClientPoolEntry).index = i 323 }