github.com/goldbock/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  }