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