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