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