github.com/energicryptocurrency/go-energi@v1.1.7/les/serverpool.go (about)

     1  // Copyright 2016 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 implements the Light Ethereum Subprotocol.
    18  package les
    19  
    20  import (
    21  	"crypto/ecdsa"
    22  	"fmt"
    23  	"io"
    24  	"math"
    25  	"math/rand"
    26  	"net"
    27  	"strconv"
    28  	"sync"
    29  	"time"
    30  
    31  	"github.com/energicryptocurrency/go-energi/common/mclock"
    32  	"github.com/energicryptocurrency/go-energi/crypto"
    33  	"github.com/energicryptocurrency/go-energi/ethdb"
    34  	"github.com/energicryptocurrency/go-energi/log"
    35  	"github.com/energicryptocurrency/go-energi/p2p"
    36  	"github.com/energicryptocurrency/go-energi/p2p/discv5"
    37  	"github.com/energicryptocurrency/go-energi/p2p/enode"
    38  	"github.com/energicryptocurrency/go-energi/rlp"
    39  )
    40  
    41  const (
    42  	// After a connection has been ended or timed out, there is a waiting period
    43  	// before it can be selected for connection again.
    44  	// waiting period = base delay * (1 + random(1))
    45  	// base delay = shortRetryDelay for the first shortRetryCnt times after a
    46  	// successful connection, after that longRetryDelay is applied
    47  	shortRetryCnt   = 5
    48  	shortRetryDelay = time.Second * 5
    49  	longRetryDelay  = time.Minute * 10
    50  	// maxNewEntries is the maximum number of newly discovered (never connected) nodes.
    51  	// If the limit is reached, the least recently discovered one is thrown out.
    52  	maxNewEntries = 1000
    53  	// maxKnownEntries is the maximum number of known (already connected) nodes.
    54  	// If the limit is reached, the least recently connected one is thrown out.
    55  	// (not that unlike new entries, known entries are persistent)
    56  	maxKnownEntries = 1000
    57  	// target for simultaneously connected servers
    58  	targetServerCount = 5
    59  	// target for servers selected from the known table
    60  	// (we leave room for trying new ones if there is any)
    61  	targetKnownSelect = 3
    62  	// after dialTimeout, consider the server unavailable and adjust statistics
    63  	dialTimeout = time.Second * 30
    64  	// targetConnTime is the minimum expected connection duration before a server
    65  	// drops a client without any specific reason
    66  	targetConnTime = time.Minute * 10
    67  	// new entry selection weight calculation based on most recent discovery time:
    68  	// unity until discoverExpireStart, then exponential decay with discoverExpireConst
    69  	discoverExpireStart = time.Minute * 20
    70  	discoverExpireConst = time.Minute * 20
    71  	// known entry selection weight is dropped by a factor of exp(-failDropLn) after
    72  	// each unsuccessful connection (restored after a successful one)
    73  	failDropLn = 0.1
    74  	// known node connection success and quality statistics have a long term average
    75  	// and a short term value which is adjusted exponentially with a factor of
    76  	// pstatRecentAdjust with each dial/connection and also returned exponentially
    77  	// to the average with the time constant pstatReturnToMeanTC
    78  	pstatReturnToMeanTC = time.Hour
    79  	// node address selection weight is dropped by a factor of exp(-addrFailDropLn) after
    80  	// each unsuccessful connection (restored after a successful one)
    81  	addrFailDropLn = math.Ln2
    82  	// responseScoreTC and delayScoreTC are exponential decay time constants for
    83  	// calculating selection chances from response times and block delay times
    84  	responseScoreTC = time.Millisecond * 100
    85  	delayScoreTC    = time.Second * 5
    86  	timeoutPow      = 10
    87  	// initStatsWeight is used to initialize previously unknown peers with good
    88  	// statistics to give a chance to prove themselves
    89  	initStatsWeight = 1
    90  )
    91  
    92  // connReq represents a request for peer connection.
    93  type connReq struct {
    94  	p      *peer
    95  	node   *enode.Node
    96  	result chan *poolEntry
    97  }
    98  
    99  // disconnReq represents a request for peer disconnection.
   100  type disconnReq struct {
   101  	entry   *poolEntry
   102  	stopped bool
   103  	done    chan struct{}
   104  }
   105  
   106  // registerReq represents a request for peer registration.
   107  type registerReq struct {
   108  	entry *poolEntry
   109  	done  chan struct{}
   110  }
   111  
   112  // serverPool implements a pool for storing and selecting newly discovered and already
   113  // known light server nodes. It received discovered nodes, stores statistics about
   114  // known nodes and takes care of always having enough good quality servers connected.
   115  type serverPool struct {
   116  	db     ethdb.Database
   117  	dbKey  []byte
   118  	server *p2p.Server
   119  	quit   chan struct{}
   120  	wg     *sync.WaitGroup
   121  	connWg sync.WaitGroup
   122  
   123  	topic discv5.Topic
   124  
   125  	discSetPeriod chan time.Duration
   126  	discNodes     chan *enode.Node
   127  	discLookups   chan bool
   128  
   129  	entries              map[enode.ID]*poolEntry
   130  	timeout, enableRetry chan *poolEntry
   131  	adjustStats          chan poolStatAdjust
   132  
   133  	connCh     chan *connReq
   134  	disconnCh  chan *disconnReq
   135  	registerCh chan *registerReq
   136  
   137  	knownQueue, newQueue       poolEntryQueue
   138  	knownSelect, newSelect     *weightedRandomSelect
   139  	knownSelected, newSelected int
   140  	fastDiscover               bool
   141  }
   142  
   143  // newServerPool creates a new serverPool instance
   144  func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool {
   145  	pool := &serverPool{
   146  		db:           db,
   147  		quit:         quit,
   148  		wg:           wg,
   149  		entries:      make(map[enode.ID]*poolEntry),
   150  		timeout:      make(chan *poolEntry, 1),
   151  		adjustStats:  make(chan poolStatAdjust, 100),
   152  		enableRetry:  make(chan *poolEntry, 1),
   153  		connCh:       make(chan *connReq),
   154  		disconnCh:    make(chan *disconnReq),
   155  		registerCh:   make(chan *registerReq),
   156  		knownSelect:  newWeightedRandomSelect(),
   157  		newSelect:    newWeightedRandomSelect(),
   158  		fastDiscover: true,
   159  	}
   160  	pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry)
   161  	pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry)
   162  	return pool
   163  }
   164  
   165  func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
   166  	pool.server = server
   167  	pool.topic = topic
   168  	pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
   169  	pool.wg.Add(1)
   170  	pool.loadNodes()
   171  
   172  	if pool.server.DiscV5 != nil {
   173  		pool.discSetPeriod = make(chan time.Duration, 1)
   174  		pool.discNodes = make(chan *enode.Node, 100)
   175  		pool.discLookups = make(chan bool, 100)
   176  		go pool.discoverNodes()
   177  	}
   178  	pool.checkDial()
   179  	go pool.eventLoop()
   180  }
   181  
   182  // discoverNodes wraps SearchTopic, converting result nodes to enode.Node.
   183  func (pool *serverPool) discoverNodes() {
   184  	ch := make(chan *discv5.Node)
   185  	go func() {
   186  		pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, ch, pool.discLookups)
   187  		close(ch)
   188  	}()
   189  	for n := range ch {
   190  		pubkey, err := decodePubkey64(n.ID[:])
   191  		if err != nil {
   192  			continue
   193  		}
   194  		pool.discNodes <- enode.NewV4(pubkey, n.IP, int(n.TCP), int(n.UDP))
   195  	}
   196  }
   197  
   198  // connect should be called upon any incoming connection. If the connection has been
   199  // dialed by the server pool recently, the appropriate pool entry is returned.
   200  // Otherwise, the connection should be rejected.
   201  // Note that whenever a connection has been accepted and a pool entry has been returned,
   202  // disconnect should also always be called.
   203  func (pool *serverPool) connect(p *peer, node *enode.Node) *poolEntry {
   204  	log.Debug("Connect new entry", "enode", p.id)
   205  	req := &connReq{p: p, node: node, result: make(chan *poolEntry, 1)}
   206  	select {
   207  	case pool.connCh <- req:
   208  	case <-pool.quit:
   209  		return nil
   210  	}
   211  	return <-req.result
   212  }
   213  
   214  // registered should be called after a successful handshake
   215  func (pool *serverPool) registered(entry *poolEntry) {
   216  	log.Debug("Registered new entry", "enode", entry.node.ID())
   217  	req := &registerReq{entry: entry, done: make(chan struct{})}
   218  	select {
   219  	case pool.registerCh <- req:
   220  	case <-pool.quit:
   221  		return
   222  	}
   223  	<-req.done
   224  }
   225  
   226  // disconnect should be called when ending a connection. Service quality statistics
   227  // can be updated optionally (not updated if no registration happened, in this case
   228  // only connection statistics are updated, just like in case of timeout)
   229  func (pool *serverPool) disconnect(entry *poolEntry) {
   230  	stopped := false
   231  	select {
   232  	case <-pool.quit:
   233  		stopped = true
   234  	default:
   235  	}
   236  	log.Debug("Disconnected old entry", "enode", entry.node.ID())
   237  	req := &disconnReq{entry: entry, stopped: stopped, done: make(chan struct{})}
   238  
   239  	// Block until disconnection request is served.
   240  	pool.disconnCh <- req
   241  	<-req.done
   242  }
   243  
   244  const (
   245  	pseBlockDelay = iota
   246  	pseResponseTime
   247  	pseResponseTimeout
   248  )
   249  
   250  // poolStatAdjust records are sent to adjust peer block delay/response time statistics
   251  type poolStatAdjust struct {
   252  	adjustType int
   253  	entry      *poolEntry
   254  	time       time.Duration
   255  }
   256  
   257  // adjustBlockDelay adjusts the block announce delay statistics of a node
   258  func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) {
   259  	if entry == nil {
   260  		return
   261  	}
   262  	pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time}
   263  }
   264  
   265  // adjustResponseTime adjusts the request response time statistics of a node
   266  func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) {
   267  	if entry == nil {
   268  		return
   269  	}
   270  	if timeout {
   271  		pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time}
   272  	} else {
   273  		pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time}
   274  	}
   275  }
   276  
   277  // eventLoop handles pool events and mutex locking for all internal functions
   278  func (pool *serverPool) eventLoop() {
   279  	lookupCnt := 0
   280  	var convTime mclock.AbsTime
   281  	if pool.discSetPeriod != nil {
   282  		pool.discSetPeriod <- time.Millisecond * 100
   283  	}
   284  
   285  	// disconnect updates service quality statistics depending on the connection time
   286  	// and disconnection initiator.
   287  	disconnect := func(req *disconnReq, stopped bool) {
   288  		// Handle peer disconnection requests.
   289  		entry := req.entry
   290  		if entry.state == psRegistered {
   291  			connAdjust := float64(mclock.Now()-entry.regTime) / float64(targetConnTime)
   292  			if connAdjust > 1 {
   293  				connAdjust = 1
   294  			}
   295  			if stopped {
   296  				// disconnect requested by ourselves.
   297  				entry.connectStats.add(1, connAdjust)
   298  			} else {
   299  				// disconnect requested by server side.
   300  				entry.connectStats.add(connAdjust, 1)
   301  			}
   302  		}
   303  		entry.state = psNotConnected
   304  
   305  		if entry.knownSelected {
   306  			pool.knownSelected--
   307  		} else {
   308  			pool.newSelected--
   309  		}
   310  		pool.setRetryDial(entry)
   311  		pool.connWg.Done()
   312  		close(req.done)
   313  	}
   314  
   315  	for {
   316  		select {
   317  		case entry := <-pool.timeout:
   318  			if !entry.removed {
   319  				pool.checkDialTimeout(entry)
   320  			}
   321  
   322  		case entry := <-pool.enableRetry:
   323  			if !entry.removed {
   324  				entry.delayedRetry = false
   325  				pool.updateCheckDial(entry)
   326  			}
   327  
   328  		case adj := <-pool.adjustStats:
   329  			switch adj.adjustType {
   330  			case pseBlockDelay:
   331  				adj.entry.delayStats.add(float64(adj.time), 1)
   332  			case pseResponseTime:
   333  				adj.entry.responseStats.add(float64(adj.time), 1)
   334  				adj.entry.timeoutStats.add(0, 1)
   335  			case pseResponseTimeout:
   336  				adj.entry.timeoutStats.add(1, 1)
   337  			}
   338  
   339  		case node := <-pool.discNodes:
   340  			entry := pool.findOrNewNode(node)
   341  			pool.updateCheckDial(entry)
   342  
   343  		case conv := <-pool.discLookups:
   344  			if conv {
   345  				if lookupCnt == 0 {
   346  					convTime = mclock.Now()
   347  				}
   348  				lookupCnt++
   349  				if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) {
   350  					pool.fastDiscover = false
   351  					if pool.discSetPeriod != nil {
   352  						pool.discSetPeriod <- time.Minute
   353  					}
   354  				}
   355  			}
   356  
   357  		case req := <-pool.connCh:
   358  			// Handle peer connection requests.
   359  			entry := pool.entries[req.p.ID()]
   360  			if entry == nil {
   361  				entry = pool.findOrNewNode(req.node)
   362  			}
   363  			if entry.state == psConnected || entry.state == psRegistered {
   364  				req.result <- nil
   365  				continue
   366  			}
   367  			pool.connWg.Add(1)
   368  			entry.peer = req.p
   369  			entry.state = psConnected
   370  			addr := &poolEntryAddress{
   371  				ip:       req.node.IP(),
   372  				port:     uint16(req.node.TCP()),
   373  				lastSeen: mclock.Now(),
   374  			}
   375  			entry.lastConnected = addr
   376  			entry.addr = make(map[string]*poolEntryAddress)
   377  			entry.addr[addr.strKey()] = addr
   378  			entry.addrSelect = *newWeightedRandomSelect()
   379  			entry.addrSelect.update(addr)
   380  			req.result <- entry
   381  
   382  		case req := <-pool.registerCh:
   383  			// Handle peer registration requests.
   384  			entry := req.entry
   385  			entry.state = psRegistered
   386  			entry.regTime = mclock.Now()
   387  			if !entry.known {
   388  				pool.newQueue.remove(entry)
   389  				entry.known = true
   390  			}
   391  			pool.knownQueue.setLatest(entry)
   392  			entry.shortRetry = shortRetryCnt
   393  			close(req.done)
   394  
   395  		case req := <-pool.disconnCh:
   396  			// Handle peer disconnection requests.
   397  			disconnect(req, req.stopped)
   398  
   399  		case <-pool.quit:
   400  			if pool.discSetPeriod != nil {
   401  				close(pool.discSetPeriod)
   402  			}
   403  
   404  			// Spawn a goroutine to close the disconnCh after all connections are disconnected.
   405  			go func() {
   406  				pool.connWg.Wait()
   407  				close(pool.disconnCh)
   408  			}()
   409  
   410  			// Handle all remaining disconnection requests before exit.
   411  			for req := range pool.disconnCh {
   412  				disconnect(req, true)
   413  			}
   414  			pool.saveNodes()
   415  			pool.wg.Done()
   416  			return
   417  		}
   418  	}
   419  }
   420  
   421  func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry {
   422  	now := mclock.Now()
   423  	entry := pool.entries[node.ID()]
   424  	if entry == nil {
   425  		log.Debug("Discovered new entry", "id", node.ID())
   426  		entry = &poolEntry{
   427  			node:       node,
   428  			addr:       make(map[string]*poolEntryAddress),
   429  			addrSelect: *newWeightedRandomSelect(),
   430  			shortRetry: shortRetryCnt,
   431  		}
   432  		pool.entries[node.ID()] = entry
   433  		// initialize previously unknown peers with good statistics to give a chance to prove themselves
   434  		entry.connectStats.add(1, initStatsWeight)
   435  		entry.delayStats.add(0, initStatsWeight)
   436  		entry.responseStats.add(0, initStatsWeight)
   437  		entry.timeoutStats.add(0, initStatsWeight)
   438  	}
   439  	entry.lastDiscovered = now
   440  	addr := &poolEntryAddress{ip: node.IP(), port: uint16(node.TCP())}
   441  	if a, ok := entry.addr[addr.strKey()]; ok {
   442  		addr = a
   443  	} else {
   444  		entry.addr[addr.strKey()] = addr
   445  	}
   446  	addr.lastSeen = now
   447  	entry.addrSelect.update(addr)
   448  	if !entry.known {
   449  		pool.newQueue.setLatest(entry)
   450  	}
   451  	return entry
   452  }
   453  
   454  // loadNodes loads known nodes and their statistics from the database
   455  func (pool *serverPool) loadNodes() {
   456  	enc, err := pool.db.Get(pool.dbKey)
   457  	if err != nil {
   458  		return
   459  	}
   460  	var list []*poolEntry
   461  	err = rlp.DecodeBytes(enc, &list)
   462  	if err != nil {
   463  		log.Debug("Failed to decode node list", "err", err)
   464  		return
   465  	}
   466  	for _, e := range list {
   467  		log.Debug("Loaded server stats", "id", e.node.ID(), "fails", e.lastConnected.fails,
   468  			"conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight),
   469  			"delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight),
   470  			"response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight),
   471  			"timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight))
   472  		pool.entries[e.node.ID()] = e
   473  		pool.knownQueue.setLatest(e)
   474  		pool.knownSelect.update((*knownEntry)(e))
   475  	}
   476  }
   477  
   478  // saveNodes saves known nodes and their statistics into the database. Nodes are
   479  // ordered from least to most recently connected.
   480  func (pool *serverPool) saveNodes() {
   481  	list := make([]*poolEntry, len(pool.knownQueue.queue))
   482  	for i := range list {
   483  		list[i] = pool.knownQueue.fetchOldest()
   484  	}
   485  	enc, err := rlp.EncodeToBytes(list)
   486  	if err == nil {
   487  		pool.db.Put(pool.dbKey, enc)
   488  	}
   489  }
   490  
   491  // removeEntry removes a pool entry when the entry count limit is reached.
   492  // Note that it is called by the new/known queues from which the entry has already
   493  // been removed so removing it from the queues is not necessary.
   494  func (pool *serverPool) removeEntry(entry *poolEntry) {
   495  	pool.newSelect.remove((*discoveredEntry)(entry))
   496  	pool.knownSelect.remove((*knownEntry)(entry))
   497  	entry.removed = true
   498  	delete(pool.entries, entry.node.ID())
   499  }
   500  
   501  // setRetryDial starts the timer which will enable dialing a certain node again
   502  func (pool *serverPool) setRetryDial(entry *poolEntry) {
   503  	delay := longRetryDelay
   504  	if entry.shortRetry > 0 {
   505  		entry.shortRetry--
   506  		delay = shortRetryDelay
   507  	}
   508  	delay += time.Duration(rand.Int63n(int64(delay) + 1))
   509  	entry.delayedRetry = true
   510  	go func() {
   511  		select {
   512  		case <-pool.quit:
   513  		case <-time.After(delay):
   514  			select {
   515  			case <-pool.quit:
   516  			case pool.enableRetry <- entry:
   517  			}
   518  		}
   519  	}()
   520  }
   521  
   522  // updateCheckDial is called when an entry can potentially be dialed again. It updates
   523  // its selection weights and checks if new dials can/should be made.
   524  func (pool *serverPool) updateCheckDial(entry *poolEntry) {
   525  	pool.newSelect.update((*discoveredEntry)(entry))
   526  	pool.knownSelect.update((*knownEntry)(entry))
   527  	pool.checkDial()
   528  }
   529  
   530  // checkDial checks if new dials can/should be made. It tries to select servers both
   531  // based on good statistics and recent discovery.
   532  func (pool *serverPool) checkDial() {
   533  	fillWithKnownSelects := !pool.fastDiscover
   534  	for pool.knownSelected < targetKnownSelect {
   535  		entry := pool.knownSelect.choose()
   536  		if entry == nil {
   537  			fillWithKnownSelects = false
   538  			break
   539  		}
   540  		pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   541  	}
   542  	for pool.knownSelected+pool.newSelected < targetServerCount {
   543  		entry := pool.newSelect.choose()
   544  		if entry == nil {
   545  			break
   546  		}
   547  		pool.dial((*poolEntry)(entry.(*discoveredEntry)), false)
   548  	}
   549  	if fillWithKnownSelects {
   550  		// no more newly discovered nodes to select and since fast discover period
   551  		// is over, we probably won't find more in the near future so select more
   552  		// known entries if possible
   553  		for pool.knownSelected < targetServerCount {
   554  			entry := pool.knownSelect.choose()
   555  			if entry == nil {
   556  				break
   557  			}
   558  			pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   559  		}
   560  	}
   561  }
   562  
   563  // dial initiates a new connection
   564  func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
   565  	if pool.server == nil || entry.state != psNotConnected {
   566  		return
   567  	}
   568  	entry.state = psDialed
   569  	entry.knownSelected = knownSelected
   570  	if knownSelected {
   571  		pool.knownSelected++
   572  	} else {
   573  		pool.newSelected++
   574  	}
   575  	addr := entry.addrSelect.choose().(*poolEntryAddress)
   576  	log.Debug("Dialing new peer", "lesaddr", entry.node.ID().String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected)
   577  	entry.dialed = addr
   578  	go func() {
   579  		pool.server.AddPeer(entry.node)
   580  		select {
   581  		case <-pool.quit:
   582  		case <-time.After(dialTimeout):
   583  			select {
   584  			case <-pool.quit:
   585  			case pool.timeout <- entry:
   586  			}
   587  		}
   588  	}()
   589  }
   590  
   591  // checkDialTimeout checks if the node is still in dialed state and if so, resets it
   592  // and adjusts connection statistics accordingly.
   593  func (pool *serverPool) checkDialTimeout(entry *poolEntry) {
   594  	if entry.state != psDialed {
   595  		return
   596  	}
   597  	log.Debug("Dial timeout", "lesaddr", entry.node.ID().String()+"@"+entry.dialed.strKey())
   598  	entry.state = psNotConnected
   599  	if entry.knownSelected {
   600  		pool.knownSelected--
   601  	} else {
   602  		pool.newSelected--
   603  	}
   604  	entry.connectStats.add(0, 1)
   605  	entry.dialed.fails++
   606  	pool.setRetryDial(entry)
   607  }
   608  
   609  const (
   610  	psNotConnected = iota
   611  	psDialed
   612  	psConnected
   613  	psRegistered
   614  )
   615  
   616  // poolEntry represents a server node and stores its current state and statistics.
   617  type poolEntry struct {
   618  	peer                  *peer
   619  	addr                  map[string]*poolEntryAddress
   620  	node                  *enode.Node
   621  	lastConnected, dialed *poolEntryAddress
   622  	addrSelect            weightedRandomSelect
   623  
   624  	lastDiscovered              mclock.AbsTime
   625  	known, knownSelected        bool
   626  	connectStats, delayStats    poolStats
   627  	responseStats, timeoutStats poolStats
   628  	state                       int
   629  	regTime                     mclock.AbsTime
   630  	queueIdx                    int
   631  	removed                     bool
   632  
   633  	delayedRetry bool
   634  	shortRetry   int
   635  }
   636  
   637  // poolEntryEnc is the RLP encoding of poolEntry.
   638  type poolEntryEnc struct {
   639  	Pubkey                     []byte
   640  	IP                         net.IP
   641  	Port                       uint16
   642  	Fails                      uint
   643  	CStat, DStat, RStat, TStat poolStats
   644  }
   645  
   646  func (e *poolEntry) EncodeRLP(w io.Writer) error {
   647  	return rlp.Encode(w, &poolEntryEnc{
   648  		Pubkey: encodePubkey64(e.node.Pubkey()),
   649  		IP:     e.lastConnected.ip,
   650  		Port:   e.lastConnected.port,
   651  		Fails:  e.lastConnected.fails,
   652  		CStat:  e.connectStats,
   653  		DStat:  e.delayStats,
   654  		RStat:  e.responseStats,
   655  		TStat:  e.timeoutStats,
   656  	})
   657  }
   658  
   659  func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
   660  	var entry poolEntryEnc
   661  	if err := s.Decode(&entry); err != nil {
   662  		return err
   663  	}
   664  	pubkey, err := decodePubkey64(entry.Pubkey)
   665  	if err != nil {
   666  		return err
   667  	}
   668  	addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()}
   669  	e.node = enode.NewV4(pubkey, entry.IP, int(entry.Port), int(entry.Port))
   670  	e.addr = make(map[string]*poolEntryAddress)
   671  	e.addr[addr.strKey()] = addr
   672  	e.addrSelect = *newWeightedRandomSelect()
   673  	e.addrSelect.update(addr)
   674  	e.lastConnected = addr
   675  	e.connectStats = entry.CStat
   676  	e.delayStats = entry.DStat
   677  	e.responseStats = entry.RStat
   678  	e.timeoutStats = entry.TStat
   679  	e.shortRetry = shortRetryCnt
   680  	e.known = true
   681  	return nil
   682  }
   683  
   684  func encodePubkey64(pub *ecdsa.PublicKey) []byte {
   685  	return crypto.FromECDSAPub(pub)[1:]
   686  }
   687  
   688  func decodePubkey64(b []byte) (*ecdsa.PublicKey, error) {
   689  	return crypto.UnmarshalPubkey(append([]byte{0x04}, b...))
   690  }
   691  
   692  // discoveredEntry implements wrsItem
   693  type discoveredEntry poolEntry
   694  
   695  // Weight calculates random selection weight for newly discovered entries
   696  func (e *discoveredEntry) Weight() int64 {
   697  	if e.state != psNotConnected || e.delayedRetry {
   698  		return 0
   699  	}
   700  	t := time.Duration(mclock.Now() - e.lastDiscovered)
   701  	if t <= discoverExpireStart {
   702  		return 1000000000
   703  	}
   704  	return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
   705  }
   706  
   707  // knownEntry implements wrsItem
   708  type knownEntry poolEntry
   709  
   710  // Weight calculates random selection weight for known entries
   711  func (e *knownEntry) Weight() int64 {
   712  	if e.state != psNotConnected || !e.known || e.delayedRetry {
   713  		return 0
   714  	}
   715  	return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow(1-e.timeoutStats.recentAvg(), timeoutPow))
   716  }
   717  
   718  // poolEntryAddress is a separate object because currently it is necessary to remember
   719  // multiple potential network addresses for a pool entry. This will be removed after
   720  // the final implementation of v5 discovery which will retrieve signed and serial
   721  // numbered advertisements, making it clear which IP/port is the latest one.
   722  type poolEntryAddress struct {
   723  	ip       net.IP
   724  	port     uint16
   725  	lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db
   726  	fails    uint           // connection failures since last successful connection (persistent)
   727  }
   728  
   729  func (a *poolEntryAddress) Weight() int64 {
   730  	t := time.Duration(mclock.Now() - a.lastSeen)
   731  	return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1
   732  }
   733  
   734  func (a *poolEntryAddress) strKey() string {
   735  	return a.ip.String() + ":" + strconv.Itoa(int(a.port))
   736  }
   737  
   738  // poolStats implement statistics for a certain quantity with a long term average
   739  // and a short term value which is adjusted exponentially with a factor of
   740  // pstatRecentAdjust with each update and also returned exponentially to the
   741  // average with the time constant pstatReturnToMeanTC
   742  type poolStats struct {
   743  	sum, weight, avg, recent float64
   744  	lastRecalc               mclock.AbsTime
   745  }
   746  
   747  // init initializes stats with a long term sum/update count pair retrieved from the database
   748  func (s *poolStats) init(sum, weight float64) {
   749  	s.sum = sum
   750  	s.weight = weight
   751  	var avg float64
   752  	if weight > 0 {
   753  		avg = s.sum / weight
   754  	}
   755  	s.avg = avg
   756  	s.recent = avg
   757  	s.lastRecalc = mclock.Now()
   758  }
   759  
   760  // recalc recalculates recent value return-to-mean and long term average
   761  func (s *poolStats) recalc() {
   762  	now := mclock.Now()
   763  	s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC))
   764  	if s.sum == 0 {
   765  		s.avg = 0
   766  	} else {
   767  		if s.sum > s.weight*1e30 {
   768  			s.avg = 1e30
   769  		} else {
   770  			s.avg = s.sum / s.weight
   771  		}
   772  	}
   773  	s.lastRecalc = now
   774  }
   775  
   776  // add updates the stats with a new value
   777  func (s *poolStats) add(value, weight float64) {
   778  	s.weight += weight
   779  	s.sum += value * weight
   780  	s.recalc()
   781  }
   782  
   783  // recentAvg returns the short-term adjusted average
   784  func (s *poolStats) recentAvg() float64 {
   785  	s.recalc()
   786  	return s.recent
   787  }
   788  
   789  func (s *poolStats) EncodeRLP(w io.Writer) error {
   790  	return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)})
   791  }
   792  
   793  func (s *poolStats) DecodeRLP(st *rlp.Stream) error {
   794  	var stats struct {
   795  		SumUint, WeightUint uint64
   796  	}
   797  	if err := st.Decode(&stats); err != nil {
   798  		return err
   799  	}
   800  	s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint))
   801  	return nil
   802  }
   803  
   804  // poolEntryQueue keeps track of its least recently accessed entries and removes
   805  // them when the number of entries reaches the limit
   806  type poolEntryQueue struct {
   807  	queue                  map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value
   808  	newPtr, oldPtr, maxCnt int
   809  	removeFromPool         func(*poolEntry)
   810  }
   811  
   812  // newPoolEntryQueue returns a new poolEntryQueue
   813  func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue {
   814  	return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool}
   815  }
   816  
   817  // fetchOldest returns and removes the least recently accessed entry
   818  func (q *poolEntryQueue) fetchOldest() *poolEntry {
   819  	if len(q.queue) == 0 {
   820  		return nil
   821  	}
   822  	for {
   823  		if e := q.queue[q.oldPtr]; e != nil {
   824  			delete(q.queue, q.oldPtr)
   825  			q.oldPtr++
   826  			return e
   827  		}
   828  		q.oldPtr++
   829  	}
   830  }
   831  
   832  // remove removes an entry from the queue
   833  func (q *poolEntryQueue) remove(entry *poolEntry) {
   834  	if q.queue[entry.queueIdx] == entry {
   835  		delete(q.queue, entry.queueIdx)
   836  	}
   837  }
   838  
   839  // setLatest adds or updates a recently accessed entry. It also checks if an old entry
   840  // needs to be removed and removes it from the parent pool too with a callback function.
   841  func (q *poolEntryQueue) setLatest(entry *poolEntry) {
   842  	if q.queue[entry.queueIdx] == entry {
   843  		delete(q.queue, entry.queueIdx)
   844  	} else {
   845  		if len(q.queue) == q.maxCnt {
   846  			e := q.fetchOldest()
   847  			q.remove(e)
   848  			q.removeFromPool(e)
   849  		}
   850  	}
   851  	entry.queueIdx = q.newPtr
   852  	q.queue[entry.queueIdx] = entry
   853  	q.newPtr++
   854  }