github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/les/serverpool.go (about)

     1  // Copyright 2016 The Spectrum Authors
     2  // This file is part of the Spectrum library.
     3  //
     4  // The Spectrum 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 Spectrum 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 Spectrum 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/SmartMeshFoundation/Spectrum/common/mclock"
    31  	"github.com/SmartMeshFoundation/Spectrum/ethdb"
    32  	"github.com/SmartMeshFoundation/Spectrum/log"
    33  	"github.com/SmartMeshFoundation/Spectrum/p2p"
    34  	"github.com/SmartMeshFoundation/Spectrum/p2p/discover"
    35  	"github.com/SmartMeshFoundation/Spectrum/p2p/discv5"
    36  	"github.com/SmartMeshFoundation/Spectrum/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  	topic discv5.Topic
   106  
   107  	discSetPeriod chan time.Duration
   108  	discNodes     chan *discv5.Node
   109  	discLookups   chan bool
   110  
   111  	entries              map[discover.NodeID]*poolEntry
   112  	lock                 sync.Mutex
   113  	timeout, enableRetry chan *poolEntry
   114  	adjustStats          chan poolStatAdjust
   115  
   116  	knownQueue, newQueue       poolEntryQueue
   117  	knownSelect, newSelect     *weightedRandomSelect
   118  	knownSelected, newSelected int
   119  	fastDiscover               bool
   120  }
   121  
   122  // newServerPool creates a new serverPool instance
   123  func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool {
   124  	pool := &serverPool{
   125  		db:           db,
   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  	return pool
   139  }
   140  
   141  func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
   142  	pool.server = server
   143  	pool.topic = topic
   144  	pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
   145  	pool.wg.Add(1)
   146  	pool.loadNodes()
   147  
   148  	if pool.server.DiscV5 != nil {
   149  		pool.discSetPeriod = make(chan time.Duration, 1)
   150  		pool.discNodes = make(chan *discv5.Node, 100)
   151  		pool.discLookups = make(chan bool, 100)
   152  		go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups)
   153  	}
   154  
   155  	go pool.eventLoop()
   156  	pool.checkDial()
   157  }
   158  
   159  // connect should be called upon any incoming connection. If the connection has been
   160  // dialed by the server pool recently, the appropriate pool entry is returned.
   161  // Otherwise, the connection should be rejected.
   162  // Note that whenever a connection has been accepted and a pool entry has been returned,
   163  // disconnect should also always be called.
   164  func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry {
   165  	pool.lock.Lock()
   166  	defer pool.lock.Unlock()
   167  	entry := pool.entries[p.ID()]
   168  	if entry == nil {
   169  		entry = pool.findOrNewNode(p.ID(), ip, port)
   170  	}
   171  	p.Log().Debug("Connecting to new peer", "state", entry.state)
   172  	if entry.state == psConnected || entry.state == psRegistered {
   173  		return nil
   174  	}
   175  	pool.connWg.Add(1)
   176  	entry.peer = p
   177  	entry.state = psConnected
   178  	addr := &poolEntryAddress{
   179  		ip:       ip,
   180  		port:     port,
   181  		lastSeen: mclock.Now(),
   182  	}
   183  	entry.lastConnected = addr
   184  	entry.addr = make(map[string]*poolEntryAddress)
   185  	entry.addr[addr.strKey()] = addr
   186  	entry.addrSelect = *newWeightedRandomSelect()
   187  	entry.addrSelect.update(addr)
   188  	return entry
   189  }
   190  
   191  // registered should be called after a successful handshake
   192  func (pool *serverPool) registered(entry *poolEntry) {
   193  	log.Debug("Registered new entry", "enode", entry.id)
   194  	pool.lock.Lock()
   195  	defer pool.lock.Unlock()
   196  
   197  	entry.state = psRegistered
   198  	entry.regTime = mclock.Now()
   199  	if !entry.known {
   200  		pool.newQueue.remove(entry)
   201  		entry.known = true
   202  	}
   203  	pool.knownQueue.setLatest(entry)
   204  	entry.shortRetry = shortRetryCnt
   205  }
   206  
   207  // disconnect should be called when ending a connection. Service quality statistics
   208  // can be updated optionally (not updated if no registration happened, in this case
   209  // only connection statistics are updated, just like in case of timeout)
   210  func (pool *serverPool) disconnect(entry *poolEntry) {
   211  	log.Debug("Disconnected old entry", "enode", entry.id)
   212  	pool.lock.Lock()
   213  	defer pool.lock.Unlock()
   214  
   215  	if entry.state == psRegistered {
   216  		connTime := mclock.Now() - entry.regTime
   217  		connAdjust := float64(connTime) / float64(targetConnTime)
   218  		if connAdjust > 1 {
   219  			connAdjust = 1
   220  		}
   221  		stopped := false
   222  		select {
   223  		case <-pool.quit:
   224  			stopped = true
   225  		default:
   226  		}
   227  		if stopped {
   228  			entry.connectStats.add(1, connAdjust)
   229  		} else {
   230  			entry.connectStats.add(connAdjust, 1)
   231  		}
   232  	}
   233  
   234  	entry.state = psNotConnected
   235  	if entry.knownSelected {
   236  		pool.knownSelected--
   237  	} else {
   238  		pool.newSelected--
   239  	}
   240  	pool.setRetryDial(entry)
   241  	pool.connWg.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  	for {
   285  		select {
   286  		case entry := <-pool.timeout:
   287  			pool.lock.Lock()
   288  			if !entry.removed {
   289  				pool.checkDialTimeout(entry)
   290  			}
   291  			pool.lock.Unlock()
   292  
   293  		case entry := <-pool.enableRetry:
   294  			pool.lock.Lock()
   295  			if !entry.removed {
   296  				entry.delayedRetry = false
   297  				pool.updateCheckDial(entry)
   298  			}
   299  			pool.lock.Unlock()
   300  
   301  		case adj := <-pool.adjustStats:
   302  			pool.lock.Lock()
   303  			switch adj.adjustType {
   304  			case pseBlockDelay:
   305  				adj.entry.delayStats.add(float64(adj.time), 1)
   306  			case pseResponseTime:
   307  				adj.entry.responseStats.add(float64(adj.time), 1)
   308  				adj.entry.timeoutStats.add(0, 1)
   309  			case pseResponseTimeout:
   310  				adj.entry.timeoutStats.add(1, 1)
   311  			}
   312  			pool.lock.Unlock()
   313  
   314  		case node := <-pool.discNodes:
   315  			pool.lock.Lock()
   316  			entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP)
   317  			pool.updateCheckDial(entry)
   318  			pool.lock.Unlock()
   319  
   320  		case conv := <-pool.discLookups:
   321  			if conv {
   322  				if lookupCnt == 0 {
   323  					convTime = mclock.Now()
   324  				}
   325  				lookupCnt++
   326  				if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) {
   327  					pool.fastDiscover = false
   328  					if pool.discSetPeriod != nil {
   329  						pool.discSetPeriod <- time.Minute
   330  					}
   331  				}
   332  			}
   333  
   334  		case <-pool.quit:
   335  			if pool.discSetPeriod != nil {
   336  				close(pool.discSetPeriod)
   337  			}
   338  			pool.connWg.Wait()
   339  			pool.saveNodes()
   340  			pool.wg.Done()
   341  			return
   342  
   343  		}
   344  	}
   345  }
   346  
   347  func (pool *serverPool) findOrNewNode(id discover.NodeID, ip net.IP, port uint16) *poolEntry {
   348  	now := mclock.Now()
   349  	entry := pool.entries[id]
   350  	if entry == nil {
   351  		log.Debug("Discovered new entry", "id", id)
   352  		entry = &poolEntry{
   353  			id:         id,
   354  			addr:       make(map[string]*poolEntryAddress),
   355  			addrSelect: *newWeightedRandomSelect(),
   356  			shortRetry: shortRetryCnt,
   357  		}
   358  		pool.entries[id] = entry
   359  		// initialize previously unknown peers with good statistics to give a chance to prove themselves
   360  		entry.connectStats.add(1, initStatsWeight)
   361  		entry.delayStats.add(0, initStatsWeight)
   362  		entry.responseStats.add(0, initStatsWeight)
   363  		entry.timeoutStats.add(0, initStatsWeight)
   364  	}
   365  	entry.lastDiscovered = now
   366  	addr := &poolEntryAddress{
   367  		ip:   ip,
   368  		port: port,
   369  	}
   370  	if a, ok := entry.addr[addr.strKey()]; ok {
   371  		addr = a
   372  	} else {
   373  		entry.addr[addr.strKey()] = addr
   374  	}
   375  	addr.lastSeen = now
   376  	entry.addrSelect.update(addr)
   377  	if !entry.known {
   378  		pool.newQueue.setLatest(entry)
   379  	}
   380  	return entry
   381  }
   382  
   383  // loadNodes loads known nodes and their statistics from the database
   384  func (pool *serverPool) loadNodes() {
   385  	enc, err := pool.db.Get(pool.dbKey)
   386  	if err != nil {
   387  		return
   388  	}
   389  	var list []*poolEntry
   390  	err = rlp.DecodeBytes(enc, &list)
   391  	if err != nil {
   392  		log.Debug("Failed to decode node list", "err", err)
   393  		return
   394  	}
   395  	for _, e := range list {
   396  		log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails,
   397  			"conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight),
   398  			"delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight),
   399  			"response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight),
   400  			"timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight))
   401  		pool.entries[e.id] = e
   402  		pool.knownQueue.setLatest(e)
   403  		pool.knownSelect.update((*knownEntry)(e))
   404  	}
   405  }
   406  
   407  // saveNodes saves known nodes and their statistics into the database. Nodes are
   408  // ordered from least to most recently connected.
   409  func (pool *serverPool) saveNodes() {
   410  	list := make([]*poolEntry, len(pool.knownQueue.queue))
   411  	for i := range list {
   412  		list[i] = pool.knownQueue.fetchOldest()
   413  	}
   414  	enc, err := rlp.EncodeToBytes(list)
   415  	if err == nil {
   416  		pool.db.Put(pool.dbKey, enc)
   417  	}
   418  }
   419  
   420  // removeEntry removes a pool entry when the entry count limit is reached.
   421  // Note that it is called by the new/known queues from which the entry has already
   422  // been removed so removing it from the queues is not necessary.
   423  func (pool *serverPool) removeEntry(entry *poolEntry) {
   424  	pool.newSelect.remove((*discoveredEntry)(entry))
   425  	pool.knownSelect.remove((*knownEntry)(entry))
   426  	entry.removed = true
   427  	delete(pool.entries, entry.id)
   428  }
   429  
   430  // setRetryDial starts the timer which will enable dialing a certain node again
   431  func (pool *serverPool) setRetryDial(entry *poolEntry) {
   432  	delay := longRetryDelay
   433  	if entry.shortRetry > 0 {
   434  		entry.shortRetry--
   435  		delay = shortRetryDelay
   436  	}
   437  	delay += time.Duration(rand.Int63n(int64(delay) + 1))
   438  	entry.delayedRetry = true
   439  	go func() {
   440  		select {
   441  		case <-pool.quit:
   442  		case <-time.After(delay):
   443  			select {
   444  			case <-pool.quit:
   445  			case pool.enableRetry <- entry:
   446  			}
   447  		}
   448  	}()
   449  }
   450  
   451  // updateCheckDial is called when an entry can potentially be dialed again. It updates
   452  // its selection weights and checks if new dials can/should be made.
   453  func (pool *serverPool) updateCheckDial(entry *poolEntry) {
   454  	pool.newSelect.update((*discoveredEntry)(entry))
   455  	pool.knownSelect.update((*knownEntry)(entry))
   456  	pool.checkDial()
   457  }
   458  
   459  // checkDial checks if new dials can/should be made. It tries to select servers both
   460  // based on good statistics and recent discovery.
   461  func (pool *serverPool) checkDial() {
   462  	fillWithKnownSelects := !pool.fastDiscover
   463  	for pool.knownSelected < targetKnownSelect {
   464  		entry := pool.knownSelect.choose()
   465  		if entry == nil {
   466  			fillWithKnownSelects = false
   467  			break
   468  		}
   469  		pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   470  	}
   471  	for pool.knownSelected+pool.newSelected < targetServerCount {
   472  		entry := pool.newSelect.choose()
   473  		if entry == nil {
   474  			break
   475  		}
   476  		pool.dial((*poolEntry)(entry.(*discoveredEntry)), false)
   477  	}
   478  	if fillWithKnownSelects {
   479  		// no more newly discovered nodes to select and since fast discover period
   480  		// is over, we probably won't find more in the near future so select more
   481  		// known entries if possible
   482  		for pool.knownSelected < targetServerCount {
   483  			entry := pool.knownSelect.choose()
   484  			if entry == nil {
   485  				break
   486  			}
   487  			pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   488  		}
   489  	}
   490  }
   491  
   492  // dial initiates a new connection
   493  func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
   494  	if pool.server == nil || entry.state != psNotConnected {
   495  		return
   496  	}
   497  	entry.state = psDialed
   498  	entry.knownSelected = knownSelected
   499  	if knownSelected {
   500  		pool.knownSelected++
   501  	} else {
   502  		pool.newSelected++
   503  	}
   504  	addr := entry.addrSelect.choose().(*poolEntryAddress)
   505  	log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected)
   506  	entry.dialed = addr
   507  	go func() {
   508  		pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port))
   509  		select {
   510  		case <-pool.quit:
   511  		case <-time.After(dialTimeout):
   512  			select {
   513  			case <-pool.quit:
   514  			case pool.timeout <- entry:
   515  			}
   516  		}
   517  	}()
   518  }
   519  
   520  // checkDialTimeout checks if the node is still in dialed state and if so, resets it
   521  // and adjusts connection statistics accordingly.
   522  func (pool *serverPool) checkDialTimeout(entry *poolEntry) {
   523  	if entry.state != psDialed {
   524  		return
   525  	}
   526  	log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey())
   527  	entry.state = psNotConnected
   528  	if entry.knownSelected {
   529  		pool.knownSelected--
   530  	} else {
   531  		pool.newSelected--
   532  	}
   533  	entry.connectStats.add(0, 1)
   534  	entry.dialed.fails++
   535  	pool.setRetryDial(entry)
   536  }
   537  
   538  const (
   539  	psNotConnected = iota
   540  	psDialed
   541  	psConnected
   542  	psRegistered
   543  )
   544  
   545  // poolEntry represents a server node and stores its current state and statistics.
   546  type poolEntry struct {
   547  	peer                  *peer
   548  	id                    discover.NodeID
   549  	addr                  map[string]*poolEntryAddress
   550  	lastConnected, dialed *poolEntryAddress
   551  	addrSelect            weightedRandomSelect
   552  
   553  	lastDiscovered              mclock.AbsTime
   554  	known, knownSelected        bool
   555  	connectStats, delayStats    poolStats
   556  	responseStats, timeoutStats poolStats
   557  	state                       int
   558  	regTime                     mclock.AbsTime
   559  	queueIdx                    int
   560  	removed                     bool
   561  
   562  	delayedRetry bool
   563  	shortRetry   int
   564  }
   565  
   566  func (e *poolEntry) EncodeRLP(w io.Writer) error {
   567  	return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats})
   568  }
   569  
   570  func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
   571  	var entry struct {
   572  		ID                         discover.NodeID
   573  		IP                         net.IP
   574  		Port                       uint16
   575  		Fails                      uint
   576  		CStat, DStat, RStat, TStat poolStats
   577  	}
   578  	if err := s.Decode(&entry); err != nil {
   579  		return err
   580  	}
   581  	addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()}
   582  	e.id = entry.ID
   583  	e.addr = make(map[string]*poolEntryAddress)
   584  	e.addr[addr.strKey()] = addr
   585  	e.addrSelect = *newWeightedRandomSelect()
   586  	e.addrSelect.update(addr)
   587  	e.lastConnected = addr
   588  	e.connectStats = entry.CStat
   589  	e.delayStats = entry.DStat
   590  	e.responseStats = entry.RStat
   591  	e.timeoutStats = entry.TStat
   592  	e.shortRetry = shortRetryCnt
   593  	e.known = true
   594  	return nil
   595  }
   596  
   597  // discoveredEntry implements wrsItem
   598  type discoveredEntry poolEntry
   599  
   600  // Weight calculates random selection weight for newly discovered entries
   601  func (e *discoveredEntry) Weight() int64 {
   602  	if e.state != psNotConnected || e.delayedRetry {
   603  		return 0
   604  	}
   605  	t := time.Duration(mclock.Now() - e.lastDiscovered)
   606  	if t <= discoverExpireStart {
   607  		return 1000000000
   608  	} else {
   609  		return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
   610  	}
   611  }
   612  
   613  // knownEntry implements wrsItem
   614  type knownEntry poolEntry
   615  
   616  // Weight calculates random selection weight for known entries
   617  func (e *knownEntry) Weight() int64 {
   618  	if e.state != psNotConnected || !e.known || e.delayedRetry {
   619  		return 0
   620  	}
   621  	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))
   622  }
   623  
   624  // poolEntryAddress is a separate object because currently it is necessary to remember
   625  // multiple potential network addresses for a pool entry. This will be removed after
   626  // the final implementation of v5 discovery which will retrieve signed and serial
   627  // numbered advertisements, making it clear which IP/port is the latest one.
   628  type poolEntryAddress struct {
   629  	ip       net.IP
   630  	port     uint16
   631  	lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db
   632  	fails    uint           // connection failures since last successful connection (persistent)
   633  }
   634  
   635  func (a *poolEntryAddress) Weight() int64 {
   636  	t := time.Duration(mclock.Now() - a.lastSeen)
   637  	return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1
   638  }
   639  
   640  func (a *poolEntryAddress) strKey() string {
   641  	return a.ip.String() + ":" + strconv.Itoa(int(a.port))
   642  }
   643  
   644  // poolStats implement statistics for a certain quantity with a long term average
   645  // and a short term value which is adjusted exponentially with a factor of
   646  // pstatRecentAdjust with each update and also returned exponentially to the
   647  // average with the time constant pstatReturnToMeanTC
   648  type poolStats struct {
   649  	sum, weight, avg, recent float64
   650  	lastRecalc               mclock.AbsTime
   651  }
   652  
   653  // init initializes stats with a long term sum/update count pair retrieved from the database
   654  func (s *poolStats) init(sum, weight float64) {
   655  	s.sum = sum
   656  	s.weight = weight
   657  	var avg float64
   658  	if weight > 0 {
   659  		avg = s.sum / weight
   660  	}
   661  	s.avg = avg
   662  	s.recent = avg
   663  	s.lastRecalc = mclock.Now()
   664  }
   665  
   666  // recalc recalculates recent value return-to-mean and long term average
   667  func (s *poolStats) recalc() {
   668  	now := mclock.Now()
   669  	s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC))
   670  	if s.sum == 0 {
   671  		s.avg = 0
   672  	} else {
   673  		if s.sum > s.weight*1e30 {
   674  			s.avg = 1e30
   675  		} else {
   676  			s.avg = s.sum / s.weight
   677  		}
   678  	}
   679  	s.lastRecalc = now
   680  }
   681  
   682  // add updates the stats with a new value
   683  func (s *poolStats) add(value, weight float64) {
   684  	s.weight += weight
   685  	s.sum += value * weight
   686  	s.recalc()
   687  }
   688  
   689  // recentAvg returns the short-term adjusted average
   690  func (s *poolStats) recentAvg() float64 {
   691  	s.recalc()
   692  	return s.recent
   693  }
   694  
   695  func (s *poolStats) EncodeRLP(w io.Writer) error {
   696  	return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)})
   697  }
   698  
   699  func (s *poolStats) DecodeRLP(st *rlp.Stream) error {
   700  	var stats struct {
   701  		SumUint, WeightUint uint64
   702  	}
   703  	if err := st.Decode(&stats); err != nil {
   704  		return err
   705  	}
   706  	s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint))
   707  	return nil
   708  }
   709  
   710  // poolEntryQueue keeps track of its least recently accessed entries and removes
   711  // them when the number of entries reaches the limit
   712  type poolEntryQueue struct {
   713  	queue                  map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value
   714  	newPtr, oldPtr, maxCnt int
   715  	removeFromPool         func(*poolEntry)
   716  }
   717  
   718  // newPoolEntryQueue returns a new poolEntryQueue
   719  func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue {
   720  	return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool}
   721  }
   722  
   723  // fetchOldest returns and removes the least recently accessed entry
   724  func (q *poolEntryQueue) fetchOldest() *poolEntry {
   725  	if len(q.queue) == 0 {
   726  		return nil
   727  	}
   728  	for {
   729  		if e := q.queue[q.oldPtr]; e != nil {
   730  			delete(q.queue, q.oldPtr)
   731  			q.oldPtr++
   732  			return e
   733  		}
   734  		q.oldPtr++
   735  	}
   736  }
   737  
   738  // remove removes an entry from the queue
   739  func (q *poolEntryQueue) remove(entry *poolEntry) {
   740  	if q.queue[entry.queueIdx] == entry {
   741  		delete(q.queue, entry.queueIdx)
   742  	}
   743  }
   744  
   745  // setLatest adds or updates a recently accessed entry. It also checks if an old entry
   746  // needs to be removed and removes it from the parent pool too with a callback function.
   747  func (q *poolEntryQueue) setLatest(entry *poolEntry) {
   748  	if q.queue[entry.queueIdx] == entry {
   749  		delete(q.queue, entry.queueIdx)
   750  	} else {
   751  		if len(q.queue) == q.maxCnt {
   752  			e := q.fetchOldest()
   753  			q.remove(e)
   754  			q.removeFromPool(e)
   755  		}
   756  	}
   757  	entry.queueIdx = q.newPtr
   758  	q.queue[entry.queueIdx] = entry
   759  	q.newPtr++
   760  }