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