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