github.com/n1ghtfa1l/go-vnt@v0.6.4-alpha.6/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 VNT Subprotocol.
    18  package les
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"io"
    24  	"math"
    25  	"math/rand"
    26  	"net"
    27  	"strconv"
    28  	"sync"
    29  	"time"
    30  
    31  	libp2p "github.com/libp2p/go-libp2p-peer"
    32  	"github.com/vntchain/go-vnt/common/mclock"
    33  	"github.com/vntchain/go-vnt/log"
    34  	"github.com/vntchain/go-vnt/rlp"
    35  	"github.com/vntchain/go-vnt/vntdb"
    36  	"github.com/vntchain/go-vnt/vntp2p"
    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     vntdb.Database
    95  	dbKey  []byte
    96  	server *vntp2p.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[libp2p.ID]*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 vntdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool {
   120  	pool := &serverPool{
   121  		db:           db,
   122  		quit:         quit,
   123  		wg:           wg,
   124  		entries:      make(map[libp2p.ID]*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  func (pool *serverPool) start(server *vntp2p.Server) {
   139  	pool.server = server
   140  
   141  	// pool.topic = topic
   142  	// pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
   143  
   144  	pool.dbKey = append([]byte("serverPool/"))
   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.RemoteID()]
   168  	if entry == nil {
   169  		entry = pool.findOrNewNode(p.RemoteID(), 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", "vnode", 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", "vnode", 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  		// case node := <-pool.discNodes:
   314  		// 	pool.lock.Lock()
   315  		// 	entry := pool.findOrNewNode(vntp2p.NodeID(node.ID), node.IP, node.TCP)
   316  		// 	pool.updateCheckDial(entry)
   317  		// 	pool.lock.Unlock()
   318  
   319  		case conv := <-pool.discLookups:
   320  			if conv {
   321  				if lookupCnt == 0 {
   322  					convTime = mclock.Now()
   323  				}
   324  				lookupCnt++
   325  				if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) {
   326  					pool.fastDiscover = false
   327  					if pool.discSetPeriod != nil {
   328  						pool.discSetPeriod <- time.Minute
   329  					}
   330  				}
   331  			}
   332  
   333  		case <-pool.quit:
   334  			if pool.discSetPeriod != nil {
   335  				close(pool.discSetPeriod)
   336  			}
   337  			pool.connWg.Wait()
   338  			pool.saveNodes()
   339  			pool.wg.Done()
   340  			return
   341  
   342  		}
   343  	}
   344  }
   345  
   346  func (pool *serverPool) findOrNewNode(id libp2p.ID, ip net.IP, port uint16) *poolEntry {
   347  	now := mclock.Now()
   348  	entry := pool.entries[id]
   349  	if entry == nil {
   350  		log.Debug("Discovered new entry", "id", id)
   351  		entry = &poolEntry{
   352  			id:         id,
   353  			addr:       make(map[string]*poolEntryAddress),
   354  			addrSelect: *newWeightedRandomSelect(),
   355  			shortRetry: shortRetryCnt,
   356  		}
   357  		pool.entries[id] = entry
   358  		// initialize previously unknown peers with good statistics to give a chance to prove themselves
   359  		entry.connectStats.add(1, initStatsWeight)
   360  		entry.delayStats.add(0, initStatsWeight)
   361  		entry.responseStats.add(0, initStatsWeight)
   362  		entry.timeoutStats.add(0, initStatsWeight)
   363  	}
   364  	entry.lastDiscovered = now
   365  	addr := &poolEntryAddress{
   366  		ip:   ip,
   367  		port: port,
   368  	}
   369  	if a, ok := entry.addr[addr.strKey()]; ok {
   370  		addr = a
   371  	} else {
   372  		entry.addr[addr.strKey()] = addr
   373  	}
   374  	addr.lastSeen = now
   375  	entry.addrSelect.update(addr)
   376  	if !entry.known {
   377  		pool.newQueue.setLatest(entry)
   378  	}
   379  	return entry
   380  }
   381  
   382  // loadNodes loads known nodes and their statistics from the database
   383  func (pool *serverPool) loadNodes() {
   384  	enc, err := pool.db.Get(pool.dbKey)
   385  	if err != nil {
   386  		return
   387  	}
   388  	var list []*poolEntry
   389  	err = rlp.DecodeBytes(enc, &list)
   390  	if err != nil {
   391  		log.Debug("Failed to decode node list", "err", err)
   392  		return
   393  	}
   394  	for _, e := range list {
   395  		log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails,
   396  			"conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight),
   397  			"delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight),
   398  			"response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight),
   399  			"timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight))
   400  		pool.entries[e.id] = e
   401  		pool.knownQueue.setLatest(e)
   402  		pool.knownSelect.update((*knownEntry)(e))
   403  	}
   404  }
   405  
   406  // saveNodes saves known nodes and their statistics into the database. Nodes are
   407  // ordered from least to most recently connected.
   408  func (pool *serverPool) saveNodes() {
   409  	list := make([]*poolEntry, len(pool.knownQueue.queue))
   410  	for i := range list {
   411  		list[i] = pool.knownQueue.fetchOldest()
   412  	}
   413  	enc, err := rlp.EncodeToBytes(list)
   414  	if err == nil {
   415  		pool.db.Put(pool.dbKey, enc)
   416  	}
   417  }
   418  
   419  // removeEntry removes a pool entry when the entry count limit is reached.
   420  // Note that it is called by the new/known queues from which the entry has already
   421  // been removed so removing it from the queues is not necessary.
   422  func (pool *serverPool) removeEntry(entry *poolEntry) {
   423  	pool.newSelect.remove((*discoveredEntry)(entry))
   424  	pool.knownSelect.remove((*knownEntry)(entry))
   425  	entry.removed = true
   426  	delete(pool.entries, entry.id)
   427  }
   428  
   429  // setRetryDial starts the timer which will enable dialing a certain node again
   430  func (pool *serverPool) setRetryDial(entry *poolEntry) {
   431  	delay := longRetryDelay
   432  	if entry.shortRetry > 0 {
   433  		entry.shortRetry--
   434  		delay = shortRetryDelay
   435  	}
   436  	delay += time.Duration(rand.Int63n(int64(delay) + 1))
   437  	entry.delayedRetry = true
   438  	go func() {
   439  		select {
   440  		case <-pool.quit:
   441  		case <-time.After(delay):
   442  			select {
   443  			case <-pool.quit:
   444  			case pool.enableRetry <- entry:
   445  			}
   446  		}
   447  	}()
   448  }
   449  
   450  // updateCheckDial is called when an entry can potentially be dialed again. It updates
   451  // its selection weights and checks if new dials can/should be made.
   452  func (pool *serverPool) updateCheckDial(entry *poolEntry) {
   453  	pool.newSelect.update((*discoveredEntry)(entry))
   454  	pool.knownSelect.update((*knownEntry)(entry))
   455  	pool.checkDial()
   456  }
   457  
   458  // checkDial checks if new dials can/should be made. It tries to select servers both
   459  // based on good statistics and recent discovery.
   460  func (pool *serverPool) checkDial() {
   461  	fillWithKnownSelects := !pool.fastDiscover
   462  	for pool.knownSelected < targetKnownSelect {
   463  		entry := pool.knownSelect.choose()
   464  		if entry == nil {
   465  			fillWithKnownSelects = false
   466  			break
   467  		}
   468  		pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   469  	}
   470  	for pool.knownSelected+pool.newSelected < targetServerCount {
   471  		entry := pool.newSelect.choose()
   472  		if entry == nil {
   473  			break
   474  		}
   475  		pool.dial((*poolEntry)(entry.(*discoveredEntry)), false)
   476  	}
   477  	if fillWithKnownSelects {
   478  		// no more newly discovered nodes to select and since fast discover period
   479  		// is over, we probably won't find more in the near future so select more
   480  		// known entries if possible
   481  		for pool.knownSelected < targetServerCount {
   482  			entry := pool.knownSelect.choose()
   483  			if entry == nil {
   484  				break
   485  			}
   486  			pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   487  		}
   488  	}
   489  }
   490  
   491  // dial initiates a new connection
   492  func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
   493  	if pool.server == nil || entry.state != psNotConnected {
   494  		return
   495  	}
   496  	ctx := context.Background()
   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(ctx, vntp2p.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                    libp2p.ID
   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                         libp2p.ID
   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  	}
   609  	return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
   610  }
   611  
   612  // knownEntry implements wrsItem
   613  type knownEntry poolEntry
   614  
   615  // Weight calculates random selection weight for known entries
   616  func (e *knownEntry) Weight() int64 {
   617  	if e.state != psNotConnected || !e.known || e.delayedRetry {
   618  		return 0
   619  	}
   620  	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))
   621  }
   622  
   623  // poolEntryAddress is a separate object because currently it is necessary to remember
   624  // multiple potential network addresses for a pool entry. This will be removed after
   625  // the final implementation of v5 discovery which will retrieve signed and serial
   626  // numbered advertisements, making it clear which IP/port is the latest one.
   627  type poolEntryAddress struct {
   628  	ip       net.IP
   629  	port     uint16
   630  	lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db
   631  	fails    uint           // connection failures since last successful connection (persistent)
   632  }
   633  
   634  func (a *poolEntryAddress) Weight() int64 {
   635  	t := time.Duration(mclock.Now() - a.lastSeen)
   636  	return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1
   637  }
   638  
   639  func (a *poolEntryAddress) strKey() string {
   640  	return a.ip.String() + ":" + strconv.Itoa(int(a.port))
   641  }
   642  
   643  // poolStats implement statistics for a certain quantity with a long term average
   644  // and a short term value which is adjusted exponentially with a factor of
   645  // pstatRecentAdjust with each update and also returned exponentially to the
   646  // average with the time constant pstatReturnToMeanTC
   647  type poolStats struct {
   648  	sum, weight, avg, recent float64
   649  	lastRecalc               mclock.AbsTime
   650  }
   651  
   652  // init initializes stats with a long term sum/update count pair retrieved from the database
   653  func (s *poolStats) init(sum, weight float64) {
   654  	s.sum = sum
   655  	s.weight = weight
   656  	var avg float64
   657  	if weight > 0 {
   658  		avg = s.sum / weight
   659  	}
   660  	s.avg = avg
   661  	s.recent = avg
   662  	s.lastRecalc = mclock.Now()
   663  }
   664  
   665  // recalc recalculates recent value return-to-mean and long term average
   666  func (s *poolStats) recalc() {
   667  	now := mclock.Now()
   668  	s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC))
   669  	if s.sum == 0 {
   670  		s.avg = 0
   671  	} else {
   672  		if s.sum > s.weight*1e30 {
   673  			s.avg = 1e30
   674  		} else {
   675  			s.avg = s.sum / s.weight
   676  		}
   677  	}
   678  	s.lastRecalc = now
   679  }
   680  
   681  // add updates the stats with a new value
   682  func (s *poolStats) add(value, weight float64) {
   683  	s.weight += weight
   684  	s.sum += value * weight
   685  	s.recalc()
   686  }
   687  
   688  // recentAvg returns the short-term adjusted average
   689  func (s *poolStats) recentAvg() float64 {
   690  	s.recalc()
   691  	return s.recent
   692  }
   693  
   694  func (s *poolStats) EncodeRLP(w io.Writer) error {
   695  	return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)})
   696  }
   697  
   698  func (s *poolStats) DecodeRLP(st *rlp.Stream) error {
   699  	var stats struct {
   700  		SumUint, WeightUint uint64
   701  	}
   702  	if err := st.Decode(&stats); err != nil {
   703  		return err
   704  	}
   705  	s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint))
   706  	return nil
   707  }
   708  
   709  // poolEntryQueue keeps track of its least recently accessed entries and removes
   710  // them when the number of entries reaches the limit
   711  type poolEntryQueue struct {
   712  	queue                  map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value
   713  	newPtr, oldPtr, maxCnt int
   714  	removeFromPool         func(*poolEntry)
   715  }
   716  
   717  // newPoolEntryQueue returns a new poolEntryQueue
   718  func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue {
   719  	return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool}
   720  }
   721  
   722  // fetchOldest returns and removes the least recently accessed entry
   723  func (q *poolEntryQueue) fetchOldest() *poolEntry {
   724  	if len(q.queue) == 0 {
   725  		return nil
   726  	}
   727  	for {
   728  		if e := q.queue[q.oldPtr]; e != nil {
   729  			delete(q.queue, q.oldPtr)
   730  			q.oldPtr++
   731  			return e
   732  		}
   733  		q.oldPtr++
   734  	}
   735  }
   736  
   737  // remove removes an entry from the queue
   738  func (q *poolEntryQueue) remove(entry *poolEntry) {
   739  	if q.queue[entry.queueIdx] == entry {
   740  		delete(q.queue, entry.queueIdx)
   741  	}
   742  }
   743  
   744  // setLatest adds or updates a recently accessed entry. It also checks if an old entry
   745  // needs to be removed and removes it from the parent pool too with a callback function.
   746  func (q *poolEntryQueue) setLatest(entry *poolEntry) {
   747  	if q.queue[entry.queueIdx] == entry {
   748  		delete(q.queue, entry.queueIdx)
   749  	} else {
   750  		if len(q.queue) == q.maxCnt {
   751  			e := q.fetchOldest()
   752  			q.remove(e)
   753  			q.removeFromPool(e)
   754  		}
   755  	}
   756  	entry.queueIdx = q.newPtr
   757  	q.queue[entry.queueIdx] = entry
   758  	q.newPtr++
   759  }