github.com/gnattishness/bazel-go-ethereum@v0.0.0-20190929123618-7022a154f56d/les/clientpool.go (about) 1 // Copyright 2019 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 "sync" 23 "time" 24 25 "github.com/ethereum/go-ethereum/common/mclock" 26 "github.com/ethereum/go-ethereum/common/prque" 27 "github.com/ethereum/go-ethereum/ethdb" 28 "github.com/ethereum/go-ethereum/log" 29 "github.com/ethereum/go-ethereum/p2p/enode" 30 "github.com/ethereum/go-ethereum/rlp" 31 ) 32 33 const ( 34 negBalanceExpTC = time.Hour // time constant for exponentially reducing negative balance 35 fixedPointMultiplier = 0x1000000 // constant to convert logarithms to fixed point format 36 connectedBias = time.Minute * 5 // this bias is applied in favor of already connected clients in order to avoid kicking them out very soon 37 lazyQueueRefresh = time.Second * 10 // refresh period of the connected queue 38 ) 39 40 var ( 41 clientPoolDbKey = []byte("clientPool") 42 clientBalanceDbKey = []byte("clientPool-balance") 43 ) 44 45 // clientPool implements a client database that assigns a priority to each client 46 // based on a positive and negative balance. Positive balance is externally assigned 47 // to prioritized clients and is decreased with connection time and processed 48 // requests (unless the price factors are zero). If the positive balance is zero 49 // then negative balance is accumulated. Balance tracking and priority calculation 50 // for connected clients is done by balanceTracker. connectedQueue ensures that 51 // clients with the lowest positive or highest negative balance get evicted when 52 // the total capacity allowance is full and new clients with a better balance want 53 // to connect. Already connected nodes receive a small bias in their favor in order 54 // to avoid accepting and instantly kicking out clients. 55 // Balances of disconnected clients are stored in posBalanceQueue and negBalanceQueue 56 // and are also saved in the database. Negative balance is transformed into a 57 // logarithmic form with a constantly shifting linear offset in order to implement 58 // an exponential decrease. negBalanceQueue has a limited size and drops the smallest 59 // values when necessary. Positive balances are stored in the database as long as 60 // they exist, posBalanceQueue only acts as a cache for recently accessed entries. 61 type clientPool struct { 62 db ethdb.Database 63 lock sync.Mutex 64 clock mclock.Clock 65 stopCh chan chan struct{} 66 closed bool 67 removePeer func(enode.ID) 68 69 queueLimit, countLimit int 70 freeClientCap, capacityLimit, connectedCapacity uint64 71 72 connectedMap map[enode.ID]*clientInfo 73 posBalanceMap map[enode.ID]*posBalance 74 negBalanceMap map[string]*negBalance 75 connectedQueue *prque.LazyQueue 76 posBalanceQueue, negBalanceQueue *prque.Prque 77 posFactors, negFactors priceFactors 78 posBalanceAccessCounter int64 79 startupTime mclock.AbsTime 80 logOffsetAtStartup int64 81 } 82 83 // clientPeer represents a client in the pool. 84 // Positive balances are assigned to node key while negative balances are assigned 85 // to freeClientId. Currently network IP address without port is used because 86 // clients have a limited access to IP addresses while new node keys can be easily 87 // generated so it would be useless to assign a negative value to them. 88 type clientPeer interface { 89 ID() enode.ID 90 freeClientId() string 91 updateCapacity(uint64) 92 } 93 94 // clientInfo represents a connected client 95 type clientInfo struct { 96 address string 97 id enode.ID 98 capacity uint64 99 priority bool 100 pool *clientPool 101 peer clientPeer 102 queueIndex int // position in connectedQueue 103 balanceTracker balanceTracker 104 } 105 106 // connSetIndex callback updates clientInfo item index in connectedQueue 107 func connSetIndex(a interface{}, index int) { 108 a.(*clientInfo).queueIndex = index 109 } 110 111 // connPriority callback returns actual priority of clientInfo item in connectedQueue 112 func connPriority(a interface{}, now mclock.AbsTime) int64 { 113 c := a.(*clientInfo) 114 return c.balanceTracker.getPriority(now) 115 } 116 117 // connMaxPriority callback returns estimated maximum priority of clientInfo item in connectedQueue 118 func connMaxPriority(a interface{}, until mclock.AbsTime) int64 { 119 c := a.(*clientInfo) 120 pri := c.balanceTracker.estimatedPriority(until, true) 121 c.balanceTracker.addCallback(balanceCallbackQueue, pri+1, func() { 122 c.pool.lock.Lock() 123 if c.queueIndex != -1 { 124 c.pool.connectedQueue.Update(c.queueIndex) 125 } 126 c.pool.lock.Unlock() 127 }) 128 return pri 129 } 130 131 // priceFactors determine the pricing policy (may apply either to positive or 132 // negative balances which may have different factors). 133 // - timeFactor is cost unit per nanosecond of connection time 134 // - capacityFactor is cost unit per nanosecond of connection time per 1000000 capacity 135 // - requestFactor is cost unit per request "realCost" unit 136 type priceFactors struct { 137 timeFactor, capacityFactor, requestFactor float64 138 } 139 140 // newClientPool creates a new client pool 141 func newClientPool(db ethdb.Database, freeClientCap uint64, queueLimit int, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { 142 pool := &clientPool{ 143 db: db, 144 clock: clock, 145 connectedMap: make(map[enode.ID]*clientInfo), 146 posBalanceMap: make(map[enode.ID]*posBalance), 147 negBalanceMap: make(map[string]*negBalance), 148 connectedQueue: prque.NewLazyQueue(connSetIndex, connPriority, connMaxPriority, clock, lazyQueueRefresh), 149 negBalanceQueue: prque.New(negSetIndex), 150 posBalanceQueue: prque.New(posSetIndex), 151 freeClientCap: freeClientCap, 152 queueLimit: queueLimit, 153 removePeer: removePeer, 154 stopCh: make(chan chan struct{}), 155 } 156 pool.loadFromDb() 157 go func() { 158 for { 159 select { 160 case <-clock.After(lazyQueueRefresh): 161 pool.lock.Lock() 162 pool.connectedQueue.Refresh() 163 pool.lock.Unlock() 164 case stop := <-pool.stopCh: 165 close(stop) 166 return 167 } 168 } 169 }() 170 return pool 171 } 172 173 // stop shuts the client pool down 174 func (f *clientPool) stop() { 175 stop := make(chan struct{}) 176 f.stopCh <- stop 177 <-stop 178 f.lock.Lock() 179 f.closed = true 180 f.saveToDb() 181 f.lock.Unlock() 182 } 183 184 // connect should be called after a successful handshake. If the connection was 185 // rejected, there is no need to call disconnect. 186 func (f *clientPool) connect(peer clientPeer, capacity uint64) bool { 187 f.lock.Lock() 188 defer f.lock.Unlock() 189 190 // Short circuit is clientPool is already closed. 191 if f.closed { 192 return false 193 } 194 // Dedup connected peers. 195 id, freeID := peer.ID(), peer.freeClientId() 196 if _, ok := f.connectedMap[id]; ok { 197 clientRejectedMeter.Mark(1) 198 log.Debug("Client already connected", "address", freeID, "id", peerIdToString(id)) 199 return false 200 } 201 // Create a clientInfo but do not add it yet 202 now := f.clock.Now() 203 posBalance := f.getPosBalance(id).value 204 e := &clientInfo{pool: f, peer: peer, address: freeID, queueIndex: -1, id: id, priority: posBalance != 0} 205 206 var negBalance uint64 207 nb := f.negBalanceMap[freeID] 208 if nb != nil { 209 negBalance = uint64(math.Exp(float64(nb.logValue-f.logOffset(now)) / fixedPointMultiplier)) 210 } 211 // If the client is a free client, assign with a low free capacity, 212 // Otherwise assign with the given value(priority client) 213 if !e.priority { 214 capacity = f.freeClientCap 215 } 216 // Ensure the capacity will never lower than the free capacity. 217 if capacity < f.freeClientCap { 218 capacity = f.freeClientCap 219 } 220 e.capacity = capacity 221 222 e.balanceTracker.init(f.clock, capacity) 223 e.balanceTracker.setBalance(posBalance, negBalance) 224 f.setClientPriceFactors(e) 225 226 // If the number of clients already connected in the clientpool exceeds its 227 // capacity, evict some clients with lowest priority. 228 // 229 // If the priority of the newly added client is lower than the priority of 230 // all connected clients, the client is rejected. 231 newCapacity := f.connectedCapacity + capacity 232 newCount := f.connectedQueue.Size() + 1 233 if newCapacity > f.capacityLimit || newCount > f.countLimit { 234 var ( 235 kickList []*clientInfo 236 kickPriority int64 237 ) 238 f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { 239 c := data.(*clientInfo) 240 kickList = append(kickList, c) 241 kickPriority = priority 242 newCapacity -= c.capacity 243 newCount-- 244 return newCapacity > f.capacityLimit || newCount > f.countLimit 245 }) 246 if newCapacity > f.capacityLimit || newCount > f.countLimit || (e.balanceTracker.estimatedPriority(now+mclock.AbsTime(connectedBias), false)-kickPriority) > 0 { 247 // reject client 248 for _, c := range kickList { 249 f.connectedQueue.Push(c) 250 } 251 clientRejectedMeter.Mark(1) 252 log.Debug("Client rejected", "address", freeID, "id", peerIdToString(id)) 253 return false 254 } 255 // accept new client, drop old ones 256 for _, c := range kickList { 257 f.dropClient(c, now, true) 258 } 259 } 260 // client accepted, finish setting it up 261 if nb != nil { 262 delete(f.negBalanceMap, freeID) 263 f.negBalanceQueue.Remove(nb.queueIndex) 264 } 265 if e.priority { 266 e.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) 267 } 268 f.connectedMap[id] = e 269 f.connectedQueue.Push(e) 270 f.connectedCapacity += e.capacity 271 totalConnectedGauge.Update(int64(f.connectedCapacity)) 272 if e.capacity != f.freeClientCap { 273 e.peer.updateCapacity(e.capacity) 274 } 275 clientConnectedMeter.Mark(1) 276 log.Debug("Client accepted", "address", freeID) 277 return true 278 } 279 280 // disconnect should be called when a connection is terminated. If the disconnection 281 // was initiated by the pool itself using disconnectFn then calling disconnect is 282 // not necessary but permitted. 283 func (f *clientPool) disconnect(p clientPeer) { 284 f.lock.Lock() 285 defer f.lock.Unlock() 286 287 if f.closed { 288 return 289 } 290 address := p.freeClientId() 291 id := p.ID() 292 // Short circuit if the peer hasn't been registered. 293 e := f.connectedMap[id] 294 if e == nil { 295 log.Debug("Client not connected", "address", address, "id", peerIdToString(id)) 296 return 297 } 298 f.dropClient(e, f.clock.Now(), false) 299 } 300 301 // dropClient removes a client from the connected queue and finalizes its balance. 302 // If kick is true then it also initiates the disconnection. 303 func (f *clientPool) dropClient(e *clientInfo, now mclock.AbsTime, kick bool) { 304 if _, ok := f.connectedMap[e.id]; !ok { 305 return 306 } 307 f.finalizeBalance(e, now) 308 f.connectedQueue.Remove(e.queueIndex) 309 delete(f.connectedMap, e.id) 310 f.connectedCapacity -= e.capacity 311 totalConnectedGauge.Update(int64(f.connectedCapacity)) 312 if kick { 313 clientKickedMeter.Mark(1) 314 log.Debug("Client kicked out", "address", e.address) 315 f.removePeer(e.id) 316 } else { 317 clientDisconnectedMeter.Mark(1) 318 log.Debug("Client disconnected", "address", e.address) 319 } 320 } 321 322 // finalizeBalance stops the balance tracker, retrieves the final balances and 323 // stores them in posBalanceQueue and negBalanceQueue 324 func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) { 325 c.balanceTracker.stop(now) 326 pos, neg := c.balanceTracker.getBalance(now) 327 pb := f.getPosBalance(c.id) 328 pb.value = pos 329 f.storePosBalance(pb) 330 if neg < 1 { 331 neg = 1 332 } 333 nb := &negBalance{address: c.address, queueIndex: -1, logValue: int64(math.Log(float64(neg))*fixedPointMultiplier) + f.logOffset(now)} 334 f.negBalanceMap[c.address] = nb 335 f.negBalanceQueue.Push(nb, -nb.logValue) 336 if f.negBalanceQueue.Size() > f.queueLimit { 337 nn := f.negBalanceQueue.PopItem().(*negBalance) 338 delete(f.negBalanceMap, nn.address) 339 } 340 } 341 342 // balanceExhausted callback is called by balanceTracker when positive balance is exhausted. 343 // It revokes priority status and also reduces the client capacity if necessary. 344 func (f *clientPool) balanceExhausted(id enode.ID) { 345 f.lock.Lock() 346 defer f.lock.Unlock() 347 348 c := f.connectedMap[id] 349 if c == nil || !c.priority { 350 return 351 } 352 c.priority = false 353 if c.capacity != f.freeClientCap { 354 f.connectedCapacity += f.freeClientCap - c.capacity 355 totalConnectedGauge.Update(int64(f.connectedCapacity)) 356 c.capacity = f.freeClientCap 357 c.peer.updateCapacity(c.capacity) 358 } 359 } 360 361 // setConnLimit sets the maximum number and total capacity of connected clients, 362 // dropping some of them if necessary. 363 func (f *clientPool) setLimits(count int, totalCap uint64) { 364 f.lock.Lock() 365 defer f.lock.Unlock() 366 367 f.countLimit = count 368 f.capacityLimit = totalCap 369 if f.connectedCapacity > f.capacityLimit || f.connectedQueue.Size() > f.countLimit { 370 now := mclock.Now() 371 f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { 372 c := data.(*clientInfo) 373 f.dropClient(c, now, true) 374 return f.connectedCapacity > f.capacityLimit || f.connectedQueue.Size() > f.countLimit 375 }) 376 } 377 } 378 379 // requestCost feeds request cost after serving a request from the given peer. 380 func (f *clientPool) requestCost(p *peer, cost uint64) { 381 f.lock.Lock() 382 defer f.lock.Unlock() 383 384 info, exist := f.connectedMap[p.ID()] 385 if !exist || f.closed { 386 return 387 } 388 info.balanceTracker.requestCost(cost) 389 } 390 391 // logOffset calculates the time-dependent offset for the logarithmic 392 // representation of negative balance 393 func (f *clientPool) logOffset(now mclock.AbsTime) int64 { 394 // Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor 395 // is to avoid int64 overflow. We assume that int64(negBalanceExpTC) >> fixedPointMultiplier. 396 logDecay := int64((time.Duration(now - f.startupTime)) / (negBalanceExpTC / fixedPointMultiplier)) 397 return f.logOffsetAtStartup + logDecay 398 } 399 400 // setPriceFactors changes pricing factors for both positive and negative balances. 401 // Applies to connected clients and also future connections. 402 func (f *clientPool) setPriceFactors(posFactors, negFactors priceFactors) { 403 f.lock.Lock() 404 defer f.lock.Unlock() 405 406 f.posFactors, f.negFactors = posFactors, negFactors 407 for _, c := range f.connectedMap { 408 f.setClientPriceFactors(c) 409 } 410 } 411 412 // setClientPriceFactors sets the pricing factors for an individual connected client 413 func (f *clientPool) setClientPriceFactors(c *clientInfo) { 414 c.balanceTracker.setFactors(true, f.negFactors.timeFactor+float64(c.capacity)*f.negFactors.capacityFactor/1000000, f.negFactors.requestFactor) 415 c.balanceTracker.setFactors(false, f.posFactors.timeFactor+float64(c.capacity)*f.posFactors.capacityFactor/1000000, f.posFactors.requestFactor) 416 } 417 418 // clientPoolStorage is the RLP representation of the pool's database storage 419 type clientPoolStorage struct { 420 LogOffset uint64 421 List []*negBalance 422 } 423 424 // loadFromDb restores pool status from the database storage 425 // (automatically called at initialization) 426 func (f *clientPool) loadFromDb() { 427 enc, err := f.db.Get(clientPoolDbKey) 428 if err != nil { 429 return 430 } 431 var storage clientPoolStorage 432 err = rlp.DecodeBytes(enc, &storage) 433 if err != nil { 434 log.Error("Failed to decode client list", "err", err) 435 return 436 } 437 f.logOffsetAtStartup = int64(storage.LogOffset) 438 f.startupTime = f.clock.Now() 439 for _, e := range storage.List { 440 log.Debug("Loaded free client record", "address", e.address, "logValue", e.logValue) 441 f.negBalanceMap[e.address] = e 442 f.negBalanceQueue.Push(e, -e.logValue) 443 } 444 } 445 446 // saveToDb saves pool status to the database storage 447 // (automatically called during shutdown) 448 func (f *clientPool) saveToDb() { 449 now := f.clock.Now() 450 storage := clientPoolStorage{ 451 LogOffset: uint64(f.logOffset(now)), 452 } 453 for _, c := range f.connectedMap { 454 f.finalizeBalance(c, now) 455 } 456 i := 0 457 storage.List = make([]*negBalance, len(f.negBalanceMap)) 458 for _, e := range f.negBalanceMap { 459 storage.List[i] = e 460 i++ 461 } 462 enc, err := rlp.EncodeToBytes(storage) 463 if err != nil { 464 log.Error("Failed to encode negative balance list", "err", err) 465 } else { 466 f.db.Put(clientPoolDbKey, enc) 467 } 468 } 469 470 // storePosBalance stores a single positive balance entry in the database 471 func (f *clientPool) storePosBalance(b *posBalance) { 472 if b.value == b.lastStored { 473 return 474 } 475 enc, err := rlp.EncodeToBytes(b) 476 if err != nil { 477 log.Error("Failed to encode client balance", "err", err) 478 } else { 479 f.db.Put(append(clientBalanceDbKey, b.id[:]...), enc) 480 b.lastStored = b.value 481 } 482 } 483 484 // getPosBalance retrieves a single positive balance entry from cache or the database 485 func (f *clientPool) getPosBalance(id enode.ID) *posBalance { 486 if b, ok := f.posBalanceMap[id]; ok { 487 f.posBalanceQueue.Remove(b.queueIndex) 488 f.posBalanceAccessCounter-- 489 f.posBalanceQueue.Push(b, f.posBalanceAccessCounter) 490 return b 491 } 492 balance := &posBalance{} 493 if enc, err := f.db.Get(append(clientBalanceDbKey, id[:]...)); err == nil { 494 if err := rlp.DecodeBytes(enc, balance); err != nil { 495 log.Error("Failed to decode client balance", "err", err) 496 balance = &posBalance{} 497 } 498 } 499 balance.id = id 500 balance.queueIndex = -1 501 if f.posBalanceQueue.Size() >= f.queueLimit { 502 b := f.posBalanceQueue.PopItem().(*posBalance) 503 f.storePosBalance(b) 504 delete(f.posBalanceMap, b.id) 505 } 506 f.posBalanceAccessCounter-- 507 f.posBalanceQueue.Push(balance, f.posBalanceAccessCounter) 508 f.posBalanceMap[id] = balance 509 return balance 510 } 511 512 // addBalance updates the positive balance of a client. 513 // If setTotal is false then the given amount is added to the balance. 514 // If setTotal is true then amount represents the total amount ever added to the 515 // given ID and positive balance is increased by (amount-lastTotal) while lastTotal 516 // is updated to amount. This method also allows removing positive balance. 517 func (f *clientPool) addBalance(id enode.ID, amount uint64, setTotal bool) { 518 f.lock.Lock() 519 defer f.lock.Unlock() 520 521 pb := f.getPosBalance(id) 522 c := f.connectedMap[id] 523 var negBalance uint64 524 if c != nil { 525 pb.value, negBalance = c.balanceTracker.getBalance(f.clock.Now()) 526 } 527 if setTotal { 528 if pb.value+amount > pb.lastTotal { 529 pb.value += amount - pb.lastTotal 530 } else { 531 pb.value = 0 532 } 533 pb.lastTotal = amount 534 } else { 535 pb.value += amount 536 pb.lastTotal += amount 537 } 538 f.storePosBalance(pb) 539 if c != nil { 540 c.balanceTracker.setBalance(pb.value, negBalance) 541 if !c.priority && pb.value > 0 { 542 c.priority = true 543 c.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) 544 } 545 } 546 } 547 548 // posBalance represents a recently accessed positive balance entry 549 type posBalance struct { 550 id enode.ID 551 value, lastStored, lastTotal uint64 552 queueIndex int // position in posBalanceQueue 553 } 554 555 // EncodeRLP implements rlp.Encoder 556 func (e *posBalance) EncodeRLP(w io.Writer) error { 557 return rlp.Encode(w, []interface{}{e.value, e.lastTotal}) 558 } 559 560 // DecodeRLP implements rlp.Decoder 561 func (e *posBalance) DecodeRLP(s *rlp.Stream) error { 562 var entry struct { 563 Value, LastTotal uint64 564 } 565 if err := s.Decode(&entry); err != nil { 566 return err 567 } 568 e.value = entry.Value 569 e.lastStored = entry.Value 570 e.lastTotal = entry.LastTotal 571 return nil 572 } 573 574 // posSetIndex callback updates posBalance item index in posBalanceQueue 575 func posSetIndex(a interface{}, index int) { 576 a.(*posBalance).queueIndex = index 577 } 578 579 // negBalance represents a negative balance entry of a disconnected client 580 type negBalance struct { 581 address string 582 logValue int64 583 queueIndex int // position in negBalanceQueue 584 } 585 586 // EncodeRLP implements rlp.Encoder 587 func (e *negBalance) EncodeRLP(w io.Writer) error { 588 return rlp.Encode(w, []interface{}{e.address, uint64(e.logValue)}) 589 } 590 591 // DecodeRLP implements rlp.Decoder 592 func (e *negBalance) DecodeRLP(s *rlp.Stream) error { 593 var entry struct { 594 Address string 595 LogValue uint64 596 } 597 if err := s.Decode(&entry); err != nil { 598 return err 599 } 600 e.address = entry.Address 601 e.logValue = int64(entry.LogValue) 602 e.queueIndex = -1 603 return nil 604 } 605 606 // negSetIndex callback updates negBalance item index in negBalanceQueue 607 func negSetIndex(a interface{}, index int) { 608 a.(*negBalance).queueIndex = index 609 }