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