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  }