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