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