github.com/m3shine/gochain@v2.2.26+incompatible/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/gochain-io/gochain/common"
    31  	"github.com/gochain-io/gochain/common/mclock"
    32  	"github.com/gochain-io/gochain/log"
    33  	"github.com/gochain-io/gochain/p2p"
    34  	"github.com/gochain-io/gochain/p2p/discover"
    35  	"github.com/gochain-io/gochain/p2p/discv5"
    36  	"github.com/gochain-io/gochain/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     common.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 common.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.GlobalTable().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  		log.Error("Cannot encode server pool known queue", "err", err)
   417  		return
   418  	}
   419  	if err := pool.db.GlobalTable().Put(pool.dbKey, enc); err != nil {
   420  		log.Error("Cannot put server pool known queue", "err", err)
   421  	}
   422  }
   423  
   424  // removeEntry removes a pool entry when the entry count limit is reached.
   425  // Note that it is called by the new/known queues from which the entry has already
   426  // been removed so removing it from the queues is not necessary.
   427  func (pool *serverPool) removeEntry(entry *poolEntry) {
   428  	pool.newSelect.remove((*discoveredEntry)(entry))
   429  	pool.knownSelect.remove((*knownEntry)(entry))
   430  	entry.removed = true
   431  	delete(pool.entries, entry.id)
   432  }
   433  
   434  // setRetryDial starts the timer which will enable dialing a certain node again
   435  func (pool *serverPool) setRetryDial(entry *poolEntry) {
   436  	delay := longRetryDelay
   437  	if entry.shortRetry > 0 {
   438  		entry.shortRetry--
   439  		delay = shortRetryDelay
   440  	}
   441  	delay += time.Duration(rand.Int63n(int64(delay) + 1))
   442  	entry.delayedRetry = true
   443  	go func() {
   444  		select {
   445  		case <-pool.quit:
   446  		case <-time.After(delay):
   447  			select {
   448  			case <-pool.quit:
   449  			case pool.enableRetry <- entry:
   450  			}
   451  		}
   452  	}()
   453  }
   454  
   455  // updateCheckDial is called when an entry can potentially be dialed again. It updates
   456  // its selection weights and checks if new dials can/should be made.
   457  func (pool *serverPool) updateCheckDial(entry *poolEntry) {
   458  	pool.newSelect.update((*discoveredEntry)(entry))
   459  	pool.knownSelect.update((*knownEntry)(entry))
   460  	pool.checkDial()
   461  }
   462  
   463  // checkDial checks if new dials can/should be made. It tries to select servers both
   464  // based on good statistics and recent discovery.
   465  func (pool *serverPool) checkDial() {
   466  	fillWithKnownSelects := !pool.fastDiscover
   467  	for pool.knownSelected < targetKnownSelect {
   468  		entry := pool.knownSelect.choose()
   469  		if entry == nil {
   470  			fillWithKnownSelects = false
   471  			break
   472  		}
   473  		pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   474  	}
   475  	for pool.knownSelected+pool.newSelected < targetServerCount {
   476  		entry := pool.newSelect.choose()
   477  		if entry == nil {
   478  			break
   479  		}
   480  		pool.dial((*poolEntry)(entry.(*discoveredEntry)), false)
   481  	}
   482  	if fillWithKnownSelects {
   483  		// no more newly discovered nodes to select and since fast discover period
   484  		// is over, we probably won't find more in the near future so select more
   485  		// known entries if possible
   486  		for pool.knownSelected < targetServerCount {
   487  			entry := pool.knownSelect.choose()
   488  			if entry == nil {
   489  				break
   490  			}
   491  			pool.dial((*poolEntry)(entry.(*knownEntry)), true)
   492  		}
   493  	}
   494  }
   495  
   496  // dial initiates a new connection
   497  func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
   498  	if pool.server == nil || entry.state != psNotConnected {
   499  		return
   500  	}
   501  	entry.state = psDialed
   502  	entry.knownSelected = knownSelected
   503  	if knownSelected {
   504  		pool.knownSelected++
   505  	} else {
   506  		pool.newSelected++
   507  	}
   508  	addr := entry.addrSelect.choose().(*poolEntryAddress)
   509  	log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected)
   510  	entry.dialed = addr
   511  	go func() {
   512  		pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port))
   513  		select {
   514  		case <-pool.quit:
   515  		case <-time.After(dialTimeout):
   516  			select {
   517  			case <-pool.quit:
   518  			case pool.timeout <- entry:
   519  			}
   520  		}
   521  	}()
   522  }
   523  
   524  // checkDialTimeout checks if the node is still in dialed state and if so, resets it
   525  // and adjusts connection statistics accordingly.
   526  func (pool *serverPool) checkDialTimeout(entry *poolEntry) {
   527  	if entry.state != psDialed {
   528  		return
   529  	}
   530  	log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey())
   531  	entry.state = psNotConnected
   532  	if entry.knownSelected {
   533  		pool.knownSelected--
   534  	} else {
   535  		pool.newSelected--
   536  	}
   537  	entry.connectStats.add(0, 1)
   538  	entry.dialed.fails++
   539  	pool.setRetryDial(entry)
   540  }
   541  
   542  const (
   543  	psNotConnected = iota
   544  	psDialed
   545  	psConnected
   546  	psRegistered
   547  )
   548  
   549  // poolEntry represents a server node and stores its current state and statistics.
   550  type poolEntry struct {
   551  	peer                  *peer
   552  	id                    discover.NodeID
   553  	addr                  map[string]*poolEntryAddress
   554  	lastConnected, dialed *poolEntryAddress
   555  	addrSelect            weightedRandomSelect
   556  
   557  	lastDiscovered              mclock.AbsTime
   558  	known, knownSelected        bool
   559  	connectStats, delayStats    poolStats
   560  	responseStats, timeoutStats poolStats
   561  	state                       int
   562  	regTime                     mclock.AbsTime
   563  	queueIdx                    int
   564  	removed                     bool
   565  
   566  	delayedRetry bool
   567  	shortRetry   int
   568  }
   569  
   570  func (e *poolEntry) EncodeRLP(w io.Writer) error {
   571  	return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats})
   572  }
   573  
   574  func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
   575  	var entry struct {
   576  		ID                         discover.NodeID
   577  		IP                         net.IP
   578  		Port                       uint16
   579  		Fails                      uint
   580  		CStat, DStat, RStat, TStat poolStats
   581  	}
   582  	if err := s.Decode(&entry); err != nil {
   583  		return err
   584  	}
   585  	addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()}
   586  	e.id = entry.ID
   587  	e.addr = make(map[string]*poolEntryAddress)
   588  	e.addr[addr.strKey()] = addr
   589  	e.addrSelect = *newWeightedRandomSelect()
   590  	e.addrSelect.update(addr)
   591  	e.lastConnected = addr
   592  	e.connectStats = entry.CStat
   593  	e.delayStats = entry.DStat
   594  	e.responseStats = entry.RStat
   595  	e.timeoutStats = entry.TStat
   596  	e.shortRetry = shortRetryCnt
   597  	e.known = true
   598  	return nil
   599  }
   600  
   601  // discoveredEntry implements wrsItem
   602  type discoveredEntry poolEntry
   603  
   604  // Weight calculates random selection weight for newly discovered entries
   605  func (e *discoveredEntry) Weight() int64 {
   606  	if e.state != psNotConnected || e.delayedRetry {
   607  		return 0
   608  	}
   609  	t := time.Duration(mclock.Now() - e.lastDiscovered)
   610  	if t <= discoverExpireStart {
   611  		return 1000000000
   612  	} else {
   613  		return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
   614  	}
   615  }
   616  
   617  // knownEntry implements wrsItem
   618  type knownEntry poolEntry
   619  
   620  // Weight calculates random selection weight for known entries
   621  func (e *knownEntry) Weight() int64 {
   622  	if e.state != psNotConnected || !e.known || e.delayedRetry {
   623  		return 0
   624  	}
   625  	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))
   626  }
   627  
   628  // poolEntryAddress is a separate object because currently it is necessary to remember
   629  // multiple potential network addresses for a pool entry. This will be removed after
   630  // the final implementation of v5 discovery which will retrieve signed and serial
   631  // numbered advertisements, making it clear which IP/port is the latest one.
   632  type poolEntryAddress struct {
   633  	ip       net.IP
   634  	port     uint16
   635  	lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db
   636  	fails    uint           // connection failures since last successful connection (persistent)
   637  }
   638  
   639  func (a *poolEntryAddress) Weight() int64 {
   640  	t := time.Duration(mclock.Now() - a.lastSeen)
   641  	return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1
   642  }
   643  
   644  func (a *poolEntryAddress) strKey() string {
   645  	return a.ip.String() + ":" + strconv.Itoa(int(a.port))
   646  }
   647  
   648  // poolStats implement statistics for a certain quantity with a long term average
   649  // and a short term value which is adjusted exponentially with a factor of
   650  // pstatRecentAdjust with each update and also returned exponentially to the
   651  // average with the time constant pstatReturnToMeanTC
   652  type poolStats struct {
   653  	sum, weight, avg, recent float64
   654  	lastRecalc               mclock.AbsTime
   655  }
   656  
   657  // init initializes stats with a long term sum/update count pair retrieved from the database
   658  func (s *poolStats) init(sum, weight float64) {
   659  	s.sum = sum
   660  	s.weight = weight
   661  	var avg float64
   662  	if weight > 0 {
   663  		avg = s.sum / weight
   664  	}
   665  	s.avg = avg
   666  	s.recent = avg
   667  	s.lastRecalc = mclock.Now()
   668  }
   669  
   670  // recalc recalculates recent value return-to-mean and long term average
   671  func (s *poolStats) recalc() {
   672  	now := mclock.Now()
   673  	s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC))
   674  	if s.sum == 0 {
   675  		s.avg = 0
   676  	} else {
   677  		if s.sum > s.weight*1e30 {
   678  			s.avg = 1e30
   679  		} else {
   680  			s.avg = s.sum / s.weight
   681  		}
   682  	}
   683  	s.lastRecalc = now
   684  }
   685  
   686  // add updates the stats with a new value
   687  func (s *poolStats) add(value, weight float64) {
   688  	s.weight += weight
   689  	s.sum += value * weight
   690  	s.recalc()
   691  }
   692  
   693  // recentAvg returns the short-term adjusted average
   694  func (s *poolStats) recentAvg() float64 {
   695  	s.recalc()
   696  	return s.recent
   697  }
   698  
   699  func (s *poolStats) EncodeRLP(w io.Writer) error {
   700  	return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)})
   701  }
   702  
   703  func (s *poolStats) DecodeRLP(st *rlp.Stream) error {
   704  	var stats struct {
   705  		SumUint, WeightUint uint64
   706  	}
   707  	if err := st.Decode(&stats); err != nil {
   708  		return err
   709  	}
   710  	s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint))
   711  	return nil
   712  }
   713  
   714  // poolEntryQueue keeps track of its least recently accessed entries and removes
   715  // them when the number of entries reaches the limit
   716  type poolEntryQueue struct {
   717  	queue                  map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value
   718  	newPtr, oldPtr, maxCnt int
   719  	removeFromPool         func(*poolEntry)
   720  }
   721  
   722  // newPoolEntryQueue returns a new poolEntryQueue
   723  func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue {
   724  	return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool}
   725  }
   726  
   727  // fetchOldest returns and removes the least recently accessed entry
   728  func (q *poolEntryQueue) fetchOldest() *poolEntry {
   729  	if len(q.queue) == 0 {
   730  		return nil
   731  	}
   732  	for {
   733  		if e := q.queue[q.oldPtr]; e != nil {
   734  			delete(q.queue, q.oldPtr)
   735  			q.oldPtr++
   736  			return e
   737  		}
   738  		q.oldPtr++
   739  	}
   740  }
   741  
   742  // remove removes an entry from the queue
   743  func (q *poolEntryQueue) remove(entry *poolEntry) {
   744  	if q.queue[entry.queueIdx] == entry {
   745  		delete(q.queue, entry.queueIdx)
   746  	}
   747  }
   748  
   749  // setLatest adds or updates a recently accessed entry. It also checks if an old entry
   750  // needs to be removed and removes it from the parent pool too with a callback function.
   751  func (q *poolEntryQueue) setLatest(entry *poolEntry) {
   752  	if q.queue[entry.queueIdx] == entry {
   753  		delete(q.queue, entry.queueIdx)
   754  	} else {
   755  		if len(q.queue) == q.maxCnt {
   756  			e := q.fetchOldest()
   757  			q.remove(e)
   758  			q.removeFromPool(e)
   759  		}
   760  	}
   761  	entry.queueIdx = q.newPtr
   762  	q.queue[entry.queueIdx] = entry
   763  	q.newPtr++
   764  }