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