github.com/c2s/go-ethereum@v1.9.7/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 "encoding/binary" 21 "io" 22 "math" 23 "sync" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/mclock" 28 "github.com/ethereum/go-ethereum/common/prque" 29 "github.com/ethereum/go-ethereum/ethdb" 30 "github.com/ethereum/go-ethereum/log" 31 "github.com/ethereum/go-ethereum/p2p/enode" 32 "github.com/ethereum/go-ethereum/rlp" 33 "github.com/hashicorp/golang-lru" 34 ) 35 36 const ( 37 negBalanceExpTC = time.Hour // time constant for exponentially reducing negative balance 38 fixedPointMultiplier = 0x1000000 // constant to convert logarithms to fixed point format 39 lazyQueueRefresh = time.Second * 10 // refresh period of the connected queue 40 persistCumulativeTimeRefresh = time.Minute * 5 // refresh period of the cumulative running time persistence 41 posBalanceCacheLimit = 8192 // the maximum number of cached items in positive balance queue 42 negBalanceCacheLimit = 8192 // the maximum number of cached items in negative balance queue 43 44 // connectedBias is applied to already connected clients So that 45 // already connected client won't be kicked out very soon and we 46 // can ensure all connected clients can have enough time to request 47 // or sync some data. 48 // 49 // todo(rjl493456442) make it configurable. It can be the option of 50 // free trial time! 51 connectedBias = time.Minute * 3 52 ) 53 54 // clientPool implements a client database that assigns a priority to each client 55 // based on a positive and negative balance. Positive balance is externally assigned 56 // to prioritized clients and is decreased with connection time and processed 57 // requests (unless the price factors are zero). If the positive balance is zero 58 // then negative balance is accumulated. 59 // 60 // Balance tracking and priority calculation for connected clients is done by 61 // balanceTracker. connectedQueue ensures that clients with the lowest positive or 62 // highest negative balance get evicted when the total capacity allowance is full 63 // and new clients with a better balance want to connect. 64 // 65 // Already connected nodes receive a small bias in their favor in order to avoid 66 // accepting and instantly kicking out clients. In theory, we try to ensure that 67 // each client can have several minutes of connection time. 68 // 69 // Balances of disconnected clients are stored in nodeDB including positive balance 70 // and negative banalce. Negative balance is transformed into a logarithmic form 71 // with a constantly shifting linear offset in order to implement an exponential 72 // decrease. Besides nodeDB will have a background thread to check the negative 73 // balance of disconnected client. If the balance is low enough, then the record 74 // will be dropped. 75 type clientPool struct { 76 ndb *nodeDB 77 lock sync.Mutex 78 clock mclock.Clock 79 stopCh chan struct{} 80 closed bool 81 removePeer func(enode.ID) 82 83 connectedMap map[enode.ID]*clientInfo 84 connectedQueue *prque.LazyQueue 85 86 posFactors, negFactors priceFactors 87 88 connLimit int // The maximum number of connections that clientpool can support 89 capLimit uint64 // The maximum cumulative capacity that clientpool can support 90 connectedCap uint64 // The sum of the capacity of the current clientpool connected 91 freeClientCap uint64 // The capacity value of each free client 92 startTime mclock.AbsTime // The timestamp at which the clientpool started running 93 cumulativeTime int64 // The cumulative running time of clientpool at the start point. 94 disableBias bool // Disable connection bias(used in testing) 95 } 96 97 // clientPeer represents a client in the pool. 98 // Positive balances are assigned to node key while negative balances are assigned 99 // to freeClientId. Currently network IP address without port is used because 100 // clients have a limited access to IP addresses while new node keys can be easily 101 // generated so it would be useless to assign a negative value to them. 102 type clientPeer interface { 103 ID() enode.ID 104 freeClientId() string 105 updateCapacity(uint64) 106 } 107 108 // clientInfo represents a connected client 109 type clientInfo struct { 110 address string 111 id enode.ID 112 capacity uint64 113 priority bool 114 pool *clientPool 115 peer clientPeer 116 queueIndex int // position in connectedQueue 117 balanceTracker balanceTracker 118 } 119 120 // connSetIndex callback updates clientInfo item index in connectedQueue 121 func connSetIndex(a interface{}, index int) { 122 a.(*clientInfo).queueIndex = index 123 } 124 125 // connPriority callback returns actual priority of clientInfo item in connectedQueue 126 func connPriority(a interface{}, now mclock.AbsTime) int64 { 127 c := a.(*clientInfo) 128 return c.balanceTracker.getPriority(now) 129 } 130 131 // connMaxPriority callback returns estimated maximum priority of clientInfo item in connectedQueue 132 func connMaxPriority(a interface{}, until mclock.AbsTime) int64 { 133 c := a.(*clientInfo) 134 pri := c.balanceTracker.estimatedPriority(until, true) 135 c.balanceTracker.addCallback(balanceCallbackQueue, pri+1, func() { 136 c.pool.lock.Lock() 137 if c.queueIndex != -1 { 138 c.pool.connectedQueue.Update(c.queueIndex) 139 } 140 c.pool.lock.Unlock() 141 }) 142 return pri 143 } 144 145 // priceFactors determine the pricing policy (may apply either to positive or 146 // negative balances which may have different factors). 147 // - timeFactor is cost unit per nanosecond of connection time 148 // - capacityFactor is cost unit per nanosecond of connection time per 1000000 capacity 149 // - requestFactor is cost unit per request "realCost" unit 150 type priceFactors struct { 151 timeFactor, capacityFactor, requestFactor float64 152 } 153 154 // newClientPool creates a new client pool 155 func newClientPool(db ethdb.Database, freeClientCap uint64, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { 156 ndb := newNodeDB(db, clock) 157 pool := &clientPool{ 158 ndb: ndb, 159 clock: clock, 160 connectedMap: make(map[enode.ID]*clientInfo), 161 connectedQueue: prque.NewLazyQueue(connSetIndex, connPriority, connMaxPriority, clock, lazyQueueRefresh), 162 freeClientCap: freeClientCap, 163 removePeer: removePeer, 164 startTime: clock.Now(), 165 cumulativeTime: ndb.getCumulativeTime(), 166 stopCh: make(chan struct{}), 167 } 168 // If the negative balance of free client is even lower than 1, 169 // delete this entry. 170 ndb.nbEvictCallBack = func(now mclock.AbsTime, b negBalance) bool { 171 balance := math.Exp(float64(b.logValue-pool.logOffset(now)) / fixedPointMultiplier) 172 return balance <= 1 173 } 174 go func() { 175 for { 176 select { 177 case <-clock.After(lazyQueueRefresh): 178 pool.lock.Lock() 179 pool.connectedQueue.Refresh() 180 pool.lock.Unlock() 181 case <-clock.After(persistCumulativeTimeRefresh): 182 pool.ndb.setCumulativeTime(pool.logOffset(clock.Now())) 183 case <-pool.stopCh: 184 return 185 } 186 } 187 }() 188 return pool 189 } 190 191 // stop shuts the client pool down 192 func (f *clientPool) stop() { 193 close(f.stopCh) 194 f.lock.Lock() 195 f.closed = true 196 f.lock.Unlock() 197 f.ndb.setCumulativeTime(f.logOffset(f.clock.Now())) 198 f.ndb.close() 199 } 200 201 // connect should be called after a successful handshake. If the connection was 202 // rejected, there is no need to call disconnect. 203 func (f *clientPool) connect(peer clientPeer, capacity uint64) bool { 204 f.lock.Lock() 205 defer f.lock.Unlock() 206 207 // Short circuit if clientPool is already closed. 208 if f.closed { 209 return false 210 } 211 // Dedup connected peers. 212 id, freeID := peer.ID(), peer.freeClientId() 213 if _, ok := f.connectedMap[id]; ok { 214 clientRejectedMeter.Mark(1) 215 log.Debug("Client already connected", "address", freeID, "id", peerIdToString(id)) 216 return false 217 } 218 // Create a clientInfo but do not add it yet 219 var ( 220 posBalance uint64 221 negBalance uint64 222 now = f.clock.Now() 223 ) 224 pb := f.ndb.getOrNewPB(id) 225 posBalance = pb.value 226 e := &clientInfo{pool: f, peer: peer, address: freeID, queueIndex: -1, id: id, priority: posBalance != 0} 227 228 nb := f.ndb.getOrNewNB(freeID) 229 if nb.logValue != 0 { 230 negBalance = uint64(math.Exp(float64(nb.logValue-f.logOffset(now)) / fixedPointMultiplier)) 231 negBalance *= uint64(time.Second) 232 } 233 // If the client is a free client, assign with a low free capacity, 234 // Otherwise assign with the given value(priority client) 235 if !e.priority { 236 capacity = f.freeClientCap 237 } 238 // Ensure the capacity will never lower than the free capacity. 239 if capacity < f.freeClientCap { 240 capacity = f.freeClientCap 241 } 242 e.capacity = capacity 243 244 // Starts a balance tracker 245 e.balanceTracker.init(f.clock, capacity) 246 e.balanceTracker.setBalance(posBalance, negBalance) 247 f.setClientPriceFactors(e) 248 249 // If the number of clients already connected in the clientpool exceeds its 250 // capacity, evict some clients with lowest priority. 251 // 252 // If the priority of the newly added client is lower than the priority of 253 // all connected clients, the client is rejected. 254 newCapacity := f.connectedCap + capacity 255 newCount := f.connectedQueue.Size() + 1 256 if newCapacity > f.capLimit || newCount > f.connLimit { 257 var ( 258 kickList []*clientInfo 259 kickPriority int64 260 ) 261 f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { 262 c := data.(*clientInfo) 263 kickList = append(kickList, c) 264 kickPriority = priority 265 newCapacity -= c.capacity 266 newCount-- 267 return newCapacity > f.capLimit || newCount > f.connLimit 268 }) 269 bias := connectedBias 270 if f.disableBias { 271 bias = 0 272 } 273 if newCapacity > f.capLimit || newCount > f.connLimit || (e.balanceTracker.estimatedPriority(now+mclock.AbsTime(bias), false)-kickPriority) > 0 { 274 for _, c := range kickList { 275 f.connectedQueue.Push(c) 276 } 277 clientRejectedMeter.Mark(1) 278 log.Debug("Client rejected", "address", freeID, "id", peerIdToString(id)) 279 return false 280 } 281 // accept new client, drop old ones 282 for _, c := range kickList { 283 f.dropClient(c, now, true) 284 } 285 } 286 // Register new client to connection queue. 287 f.connectedMap[id] = e 288 f.connectedQueue.Push(e) 289 f.connectedCap += e.capacity 290 291 // If the current client is a paid client, monitor the status of client, 292 // downgrade it to normal client if positive balance is used up. 293 if e.priority { 294 e.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) 295 } 296 // If the capacity of client is not the default value(free capacity), notify 297 // it to update capacity. 298 if e.capacity != f.freeClientCap { 299 e.peer.updateCapacity(e.capacity) 300 } 301 totalConnectedGauge.Update(int64(f.connectedCap)) 302 clientConnectedMeter.Mark(1) 303 log.Debug("Client accepted", "address", freeID) 304 return true 305 } 306 307 // disconnect should be called when a connection is terminated. If the disconnection 308 // was initiated by the pool itself using disconnectFn then calling disconnect is 309 // not necessary but permitted. 310 func (f *clientPool) disconnect(p clientPeer) { 311 f.lock.Lock() 312 defer f.lock.Unlock() 313 314 // Short circuit if client pool is already closed. 315 if f.closed { 316 return 317 } 318 // Short circuit if the peer hasn't been registered. 319 e := f.connectedMap[p.ID()] 320 if e == nil { 321 log.Debug("Client not connected", "address", p.freeClientId(), "id", peerIdToString(p.ID())) 322 return 323 } 324 f.dropClient(e, f.clock.Now(), false) 325 } 326 327 // dropClient removes a client from the connected queue and finalizes its balance. 328 // If kick is true then it also initiates the disconnection. 329 func (f *clientPool) dropClient(e *clientInfo, now mclock.AbsTime, kick bool) { 330 if _, ok := f.connectedMap[e.id]; !ok { 331 return 332 } 333 f.finalizeBalance(e, now) 334 f.connectedQueue.Remove(e.queueIndex) 335 delete(f.connectedMap, e.id) 336 f.connectedCap -= e.capacity 337 totalConnectedGauge.Update(int64(f.connectedCap)) 338 if kick { 339 clientKickedMeter.Mark(1) 340 log.Debug("Client kicked out", "address", e.address) 341 f.removePeer(e.id) 342 } else { 343 clientDisconnectedMeter.Mark(1) 344 log.Debug("Client disconnected", "address", e.address) 345 } 346 } 347 348 // finalizeBalance stops the balance tracker, retrieves the final balances and 349 // stores them in posBalanceQueue and negBalanceQueue 350 func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) { 351 c.balanceTracker.stop(now) 352 pos, neg := c.balanceTracker.getBalance(now) 353 354 pb, nb := f.ndb.getOrNewPB(c.id), f.ndb.getOrNewNB(c.address) 355 pb.value = pos 356 f.ndb.setPB(c.id, pb) 357 358 neg /= uint64(time.Second) // Convert the expanse to second level. 359 if neg > 1 { 360 nb.logValue = int64(math.Log(float64(neg))*fixedPointMultiplier) + f.logOffset(now) 361 f.ndb.setNB(c.address, nb) 362 } else { 363 f.ndb.delNB(c.address) // Negative balance is small enough, drop it directly. 364 } 365 } 366 367 // balanceExhausted callback is called by balanceTracker when positive balance is exhausted. 368 // It revokes priority status and also reduces the client capacity if necessary. 369 func (f *clientPool) balanceExhausted(id enode.ID) { 370 f.lock.Lock() 371 defer f.lock.Unlock() 372 373 c := f.connectedMap[id] 374 if c == nil || !c.priority { 375 return 376 } 377 c.priority = false 378 if c.capacity != f.freeClientCap { 379 f.connectedCap += f.freeClientCap - c.capacity 380 totalConnectedGauge.Update(int64(f.connectedCap)) 381 c.capacity = f.freeClientCap 382 c.peer.updateCapacity(c.capacity) 383 } 384 f.ndb.delPB(id) 385 } 386 387 // setConnLimit sets the maximum number and total capacity of connected clients, 388 // dropping some of them if necessary. 389 func (f *clientPool) setLimits(totalConn int, totalCap uint64) { 390 f.lock.Lock() 391 defer f.lock.Unlock() 392 393 f.connLimit = totalConn 394 f.capLimit = totalCap 395 if f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit { 396 f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { 397 f.dropClient(data.(*clientInfo), mclock.Now(), true) 398 return f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit 399 }) 400 } 401 } 402 403 // requestCost feeds request cost after serving a request from the given peer. 404 func (f *clientPool) requestCost(p *peer, cost uint64) { 405 f.lock.Lock() 406 defer f.lock.Unlock() 407 408 info, exist := f.connectedMap[p.ID()] 409 if !exist || f.closed { 410 return 411 } 412 info.balanceTracker.requestCost(cost) 413 } 414 415 // logOffset calculates the time-dependent offset for the logarithmic 416 // representation of negative balance 417 // 418 // From another point of view, the result returned by the function represents 419 // the total time that the clientpool is cumulatively running(total_hours/multiplier). 420 func (f *clientPool) logOffset(now mclock.AbsTime) int64 { 421 // Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor 422 // is to avoid int64 overflow. We assume that int64(negBalanceExpTC) >> fixedPointMultiplier. 423 cumulativeTime := int64((time.Duration(now - f.startTime)) / (negBalanceExpTC / fixedPointMultiplier)) 424 return f.cumulativeTime + cumulativeTime 425 } 426 427 // setPriceFactors changes pricing factors for both positive and negative balances. 428 // Applies to connected clients and also future connections. 429 func (f *clientPool) setPriceFactors(posFactors, negFactors priceFactors) { 430 f.lock.Lock() 431 defer f.lock.Unlock() 432 433 f.posFactors, f.negFactors = posFactors, negFactors 434 for _, c := range f.connectedMap { 435 f.setClientPriceFactors(c) 436 } 437 } 438 439 // setClientPriceFactors sets the pricing factors for an individual connected client 440 func (f *clientPool) setClientPriceFactors(c *clientInfo) { 441 c.balanceTracker.setFactors(true, f.negFactors.timeFactor+float64(c.capacity)*f.negFactors.capacityFactor/1000000, f.negFactors.requestFactor) 442 c.balanceTracker.setFactors(false, f.posFactors.timeFactor+float64(c.capacity)*f.posFactors.capacityFactor/1000000, f.posFactors.requestFactor) 443 } 444 445 // addBalance updates the positive balance of a client. 446 // If setTotal is false then the given amount is added to the balance. 447 // If setTotal is true then amount represents the total amount ever added to the 448 // given ID and positive balance is increased by (amount-lastTotal) while lastTotal 449 // is updated to amount. This method also allows removing positive balance. 450 func (f *clientPool) addBalance(id enode.ID, amount uint64, setTotal bool) { 451 f.lock.Lock() 452 defer f.lock.Unlock() 453 454 pb := f.ndb.getOrNewPB(id) 455 c := f.connectedMap[id] 456 if c != nil { 457 posBalance, negBalance := c.balanceTracker.getBalance(f.clock.Now()) 458 pb.value = posBalance 459 defer func() { 460 c.balanceTracker.setBalance(pb.value, negBalance) 461 if !c.priority && pb.value > 0 { 462 // The capacity should be adjusted based on the requirement, 463 // but we have no idea about the new capacity, need a second 464 // call to udpate it. 465 c.priority = true 466 c.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) 467 } 468 }() 469 } 470 if setTotal { 471 if pb.value+amount > pb.lastTotal { 472 pb.value += amount - pb.lastTotal 473 } else { 474 pb.value = 0 475 } 476 pb.lastTotal = amount 477 } else { 478 pb.value += amount 479 pb.lastTotal += amount 480 } 481 f.ndb.setPB(id, pb) 482 } 483 484 // posBalance represents a recently accessed positive balance entry 485 type posBalance struct { 486 value, lastTotal uint64 487 } 488 489 // EncodeRLP implements rlp.Encoder 490 func (e *posBalance) EncodeRLP(w io.Writer) error { 491 return rlp.Encode(w, []interface{}{e.value, e.lastTotal}) 492 } 493 494 // DecodeRLP implements rlp.Decoder 495 func (e *posBalance) DecodeRLP(s *rlp.Stream) error { 496 var entry struct { 497 Value, LastTotal uint64 498 } 499 if err := s.Decode(&entry); err != nil { 500 return err 501 } 502 e.value = entry.Value 503 e.lastTotal = entry.LastTotal 504 return nil 505 } 506 507 // negBalance represents a negative balance entry of a disconnected client 508 type negBalance struct{ logValue int64 } 509 510 // EncodeRLP implements rlp.Encoder 511 func (e *negBalance) EncodeRLP(w io.Writer) error { 512 return rlp.Encode(w, []interface{}{uint64(e.logValue)}) 513 } 514 515 // DecodeRLP implements rlp.Decoder 516 func (e *negBalance) DecodeRLP(s *rlp.Stream) error { 517 var entry struct { 518 LogValue uint64 519 } 520 if err := s.Decode(&entry); err != nil { 521 return err 522 } 523 e.logValue = int64(entry.LogValue) 524 return nil 525 } 526 527 const ( 528 // nodeDBVersion is the version identifier of the node data in db 529 nodeDBVersion = 0 530 531 // dbCleanupCycle is the cycle of db for useless data cleanup 532 dbCleanupCycle = time.Hour 533 ) 534 535 var ( 536 positiveBalancePrefix = []byte("pb:") // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance 537 negativeBalancePrefix = []byte("nb:") // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance 538 cumulativeRunningTimeKey = []byte("cumulativeTime:") // dbVersion(uint16 big endian) + cumulativeRunningTimeKey -> cumulativeTime 539 ) 540 541 type nodeDB struct { 542 db ethdb.Database 543 pcache *lru.Cache 544 ncache *lru.Cache 545 auxbuf []byte // 37-byte auxiliary buffer for key encoding 546 verbuf [2]byte // 2-byte auxiliary buffer for db version 547 nbEvictCallBack func(mclock.AbsTime, negBalance) bool // Callback to determine whether the negative balance can be evicted. 548 clock mclock.Clock 549 closeCh chan struct{} 550 cleanupHook func() // Test hook used for testing 551 } 552 553 func newNodeDB(db ethdb.Database, clock mclock.Clock) *nodeDB { 554 pcache, _ := lru.New(posBalanceCacheLimit) 555 ncache, _ := lru.New(negBalanceCacheLimit) 556 ndb := &nodeDB{ 557 db: db, 558 pcache: pcache, 559 ncache: ncache, 560 auxbuf: make([]byte, 37), 561 clock: clock, 562 closeCh: make(chan struct{}), 563 } 564 binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion)) 565 go ndb.expirer() 566 return ndb 567 } 568 569 func (db *nodeDB) close() { 570 close(db.closeCh) 571 } 572 573 func (db *nodeDB) key(id []byte, neg bool) []byte { 574 prefix := positiveBalancePrefix 575 if neg { 576 prefix = negativeBalancePrefix 577 } 578 if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) { 579 db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...) 580 } 581 copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:]) 582 copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix) 583 copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id) 584 return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)] 585 } 586 587 func (db *nodeDB) getCumulativeTime() int64 { 588 blob, err := db.db.Get(append(cumulativeRunningTimeKey, db.verbuf[:]...)) 589 if err != nil || len(blob) == 0 { 590 return 0 591 } 592 return int64(binary.BigEndian.Uint64(blob)) 593 } 594 595 func (db *nodeDB) setCumulativeTime(v int64) { 596 binary.BigEndian.PutUint64(db.auxbuf[:8], uint64(v)) 597 db.db.Put(append(cumulativeRunningTimeKey, db.verbuf[:]...), db.auxbuf[:8]) 598 } 599 600 func (db *nodeDB) getOrNewPB(id enode.ID) posBalance { 601 key := db.key(id.Bytes(), false) 602 item, exist := db.pcache.Get(string(key)) 603 if exist { 604 return item.(posBalance) 605 } 606 var balance posBalance 607 if enc, err := db.db.Get(key); err == nil { 608 if err := rlp.DecodeBytes(enc, &balance); err != nil { 609 log.Error("Failed to decode positive balance", "err", err) 610 } 611 } 612 db.pcache.Add(string(key), balance) 613 return balance 614 } 615 616 func (db *nodeDB) setPB(id enode.ID, b posBalance) { 617 key := db.key(id.Bytes(), false) 618 enc, err := rlp.EncodeToBytes(&(b)) 619 if err != nil { 620 log.Error("Failed to encode positive balance", "err", err) 621 return 622 } 623 db.db.Put(key, enc) 624 db.pcache.Add(string(key), b) 625 } 626 627 func (db *nodeDB) delPB(id enode.ID) { 628 key := db.key(id.Bytes(), false) 629 db.db.Delete(key) 630 db.pcache.Remove(string(key)) 631 } 632 633 func (db *nodeDB) getOrNewNB(id string) negBalance { 634 key := db.key([]byte(id), true) 635 item, exist := db.ncache.Get(string(key)) 636 if exist { 637 return item.(negBalance) 638 } 639 var balance negBalance 640 if enc, err := db.db.Get(key); err == nil { 641 if err := rlp.DecodeBytes(enc, &balance); err != nil { 642 log.Error("Failed to decode negative balance", "err", err) 643 } 644 } 645 db.ncache.Add(string(key), balance) 646 return balance 647 } 648 649 func (db *nodeDB) setNB(id string, b negBalance) { 650 key := db.key([]byte(id), true) 651 enc, err := rlp.EncodeToBytes(&(b)) 652 if err != nil { 653 log.Error("Failed to encode negative balance", "err", err) 654 return 655 } 656 db.db.Put(key, enc) 657 db.ncache.Add(string(key), b) 658 } 659 660 func (db *nodeDB) delNB(id string) { 661 key := db.key([]byte(id), true) 662 db.db.Delete(key) 663 db.ncache.Remove(string(key)) 664 } 665 666 func (db *nodeDB) expirer() { 667 for { 668 select { 669 case <-db.clock.After(dbCleanupCycle): 670 db.expireNodes() 671 case <-db.closeCh: 672 return 673 } 674 } 675 } 676 677 // expireNodes iterates the whole node db and checks whether the negative balance 678 // entry can deleted. 679 // 680 // The rationale behind this is: server doesn't need to keep the negative balance 681 // records if they are low enough. 682 func (db *nodeDB) expireNodes() { 683 var ( 684 visited int 685 deleted int 686 start = time.Now() 687 ) 688 iter := db.db.NewIteratorWithPrefix(append(db.verbuf[:], negativeBalancePrefix...)) 689 for iter.Next() { 690 visited += 1 691 var balance negBalance 692 if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil { 693 log.Error("Failed to decode negative balance", "err", err) 694 continue 695 } 696 if db.nbEvictCallBack != nil && db.nbEvictCallBack(db.clock.Now(), balance) { 697 deleted += 1 698 db.db.Delete(iter.Key()) 699 } 700 } 701 // Invoke testing hook if it's not nil. 702 if db.cleanupHook != nil { 703 db.cleanupHook() 704 } 705 log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start))) 706 }