github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/les/serverpool.go (about)

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