github.com/tdcblockchain/tdcblockchain@v0.0.0-20191111034745-805c65ade158/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 // 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 now := mclock.Now() 370 f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { 371 c := data.(*clientInfo) 372 f.dropClient(c, now, true) 373 return f.connectedCapacity > f.capacityLimit || f.connectedQueue.Size() > f.countLimit 374 }) 375 } 376 377 // requestCost feeds request cost after serving a request from the given peer. 378 func (f *clientPool) requestCost(p *peer, cost uint64) { 379 f.lock.Lock() 380 defer f.lock.Unlock() 381 382 info, exist := f.connectedMap[p.ID()] 383 if !exist || f.closed { 384 return 385 } 386 info.balanceTracker.requestCost(cost) 387 } 388 389 // logOffset calculates the time-dependent offset for the logarithmic 390 // representation of negative balance 391 func (f *clientPool) logOffset(now mclock.AbsTime) int64 { 392 // Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor 393 // is to avoid int64 overflow. We assume that int64(negBalanceExpTC) >> fixedPointMultiplier. 394 logDecay := int64((time.Duration(now - f.startupTime)) / (negBalanceExpTC / fixedPointMultiplier)) 395 return f.logOffsetAtStartup + logDecay 396 } 397 398 // setPriceFactors changes pricing factors for both positive and negative balances. 399 // Applies to connected clients and also future connections. 400 func (f *clientPool) setPriceFactors(posFactors, negFactors priceFactors) { 401 f.lock.Lock() 402 defer f.lock.Unlock() 403 404 f.posFactors, f.negFactors = posFactors, negFactors 405 for _, c := range f.connectedMap { 406 f.setClientPriceFactors(c) 407 } 408 } 409 410 // setClientPriceFactors sets the pricing factors for an individual connected client 411 func (f *clientPool) setClientPriceFactors(c *clientInfo) { 412 c.balanceTracker.setFactors(true, f.negFactors.timeFactor+float64(c.capacity)*f.negFactors.capacityFactor/1000000, f.negFactors.requestFactor) 413 c.balanceTracker.setFactors(false, f.posFactors.timeFactor+float64(c.capacity)*f.posFactors.capacityFactor/1000000, f.posFactors.requestFactor) 414 } 415 416 // clientPoolStorage is the RLP representation of the pool's database storage 417 type clientPoolStorage struct { 418 LogOffset uint64 419 List []*negBalance 420 } 421 422 // loadFromDb restores pool status from the database storage 423 // (automatically called at initialization) 424 func (f *clientPool) loadFromDb() { 425 enc, err := f.db.Get(clientPoolDbKey) 426 if err != nil { 427 return 428 } 429 var storage clientPoolStorage 430 err = rlp.DecodeBytes(enc, &storage) 431 if err != nil { 432 log.Error("Failed to decode client list", "err", err) 433 return 434 } 435 f.logOffsetAtStartup = int64(storage.LogOffset) 436 f.startupTime = f.clock.Now() 437 for _, e := range storage.List { 438 log.Debug("Loaded free client record", "address", e.address, "logValue", e.logValue) 439 f.negBalanceMap[e.address] = e 440 f.negBalanceQueue.Push(e, -e.logValue) 441 } 442 } 443 444 // saveToDb saves pool status to the database storage 445 // (automatically called during shutdown) 446 func (f *clientPool) saveToDb() { 447 now := f.clock.Now() 448 storage := clientPoolStorage{ 449 LogOffset: uint64(f.logOffset(now)), 450 } 451 for _, c := range f.connectedMap { 452 f.finalizeBalance(c, now) 453 } 454 i := 0 455 storage.List = make([]*negBalance, len(f.negBalanceMap)) 456 for _, e := range f.negBalanceMap { 457 storage.List[i] = e 458 i++ 459 } 460 enc, err := rlp.EncodeToBytes(storage) 461 if err != nil { 462 log.Error("Failed to encode negative balance list", "err", err) 463 } else { 464 f.db.Put(clientPoolDbKey, enc) 465 } 466 } 467 468 // storePosBalance stores a single positive balance entry in the database 469 func (f *clientPool) storePosBalance(b *posBalance) { 470 if b.value == b.lastStored { 471 return 472 } 473 enc, err := rlp.EncodeToBytes(b) 474 if err != nil { 475 log.Error("Failed to encode client balance", "err", err) 476 } else { 477 f.db.Put(append(clientBalanceDbKey, b.id[:]...), enc) 478 b.lastStored = b.value 479 } 480 } 481 482 // getPosBalance retrieves a single positive balance entry from cache or the database 483 func (f *clientPool) getPosBalance(id enode.ID) *posBalance { 484 if b, ok := f.posBalanceMap[id]; ok { 485 f.posBalanceQueue.Remove(b.queueIndex) 486 f.posBalanceAccessCounter-- 487 f.posBalanceQueue.Push(b, f.posBalanceAccessCounter) 488 return b 489 } 490 balance := &posBalance{} 491 if enc, err := f.db.Get(append(clientBalanceDbKey, id[:]...)); err == nil { 492 if err := rlp.DecodeBytes(enc, balance); err != nil { 493 log.Error("Failed to decode client balance", "err", err) 494 balance = &posBalance{} 495 } 496 } 497 balance.id = id 498 balance.queueIndex = -1 499 if f.posBalanceQueue.Size() >= f.queueLimit { 500 b := f.posBalanceQueue.PopItem().(*posBalance) 501 f.storePosBalance(b) 502 delete(f.posBalanceMap, b.id) 503 } 504 f.posBalanceAccessCounter-- 505 f.posBalanceQueue.Push(balance, f.posBalanceAccessCounter) 506 f.posBalanceMap[id] = balance 507 return balance 508 } 509 510 // addBalance updates the positive balance of a client. 511 // If setTotal is false then the given amount is added to the balance. 512 // If setTotal is true then amount represents the total amount ever added to the 513 // given ID and positive balance is increased by (amount-lastTotal) while lastTotal 514 // is updated to amount. This method also allows removing positive balance. 515 func (f *clientPool) addBalance(id enode.ID, amount uint64, setTotal bool) { 516 f.lock.Lock() 517 defer f.lock.Unlock() 518 519 pb := f.getPosBalance(id) 520 c := f.connectedMap[id] 521 var negBalance uint64 522 if c != nil { 523 pb.value, negBalance = c.balanceTracker.getBalance(f.clock.Now()) 524 } 525 if setTotal { 526 if pb.value+amount > pb.lastTotal { 527 pb.value += amount - pb.lastTotal 528 } else { 529 pb.value = 0 530 } 531 pb.lastTotal = amount 532 } else { 533 pb.value += amount 534 pb.lastTotal += amount 535 } 536 f.storePosBalance(pb) 537 if c != nil { 538 c.balanceTracker.setBalance(pb.value, negBalance) 539 if !c.priority && pb.value > 0 { 540 c.priority = true 541 c.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) 542 } 543 } 544 } 545 546 // posBalance represents a recently accessed positive balance entry 547 type posBalance struct { 548 id enode.ID 549 value, lastStored, lastTotal uint64 550 queueIndex int // position in posBalanceQueue 551 } 552 553 // EncodeRLP implements rlp.Encoder 554 func (e *posBalance) EncodeRLP(w io.Writer) error { 555 return rlp.Encode(w, []interface{}{e.value, e.lastTotal}) 556 } 557 558 // DecodeRLP implements rlp.Decoder 559 func (e *posBalance) DecodeRLP(s *rlp.Stream) error { 560 var entry struct { 561 Value, LastTotal uint64 562 } 563 if err := s.Decode(&entry); err != nil { 564 return err 565 } 566 e.value = entry.Value 567 e.lastStored = entry.Value 568 e.lastTotal = entry.LastTotal 569 return nil 570 } 571 572 // posSetIndex callback updates posBalance item index in posBalanceQueue 573 func posSetIndex(a interface{}, index int) { 574 a.(*posBalance).queueIndex = index 575 } 576 577 // negBalance represents a negative balance entry of a disconnected client 578 type negBalance struct { 579 address string 580 logValue int64 581 queueIndex int // position in negBalanceQueue 582 } 583 584 // EncodeRLP implements rlp.Encoder 585 func (e *negBalance) EncodeRLP(w io.Writer) error { 586 return rlp.Encode(w, []interface{}{e.address, uint64(e.logValue)}) 587 } 588 589 // DecodeRLP implements rlp.Decoder 590 func (e *negBalance) DecodeRLP(s *rlp.Stream) error { 591 var entry struct { 592 Address string 593 LogValue uint64 594 } 595 if err := s.Decode(&entry); err != nil { 596 return err 597 } 598 e.address = entry.Address 599 e.logValue = int64(entry.LogValue) 600 e.queueIndex = -1 601 return nil 602 } 603 604 // negSetIndex callback updates negBalance item index in negBalanceQueue 605 func negSetIndex(a interface{}, index int) { 606 a.(*negBalance).queueIndex = index 607 }