github.com/bloxroute-labs/bor@v0.1.4/les/freeclient.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  	"io"
    21  	"math"
    22  	"net"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/maticnetwork/bor/common/mclock"
    27  	"github.com/maticnetwork/bor/common/prque"
    28  	"github.com/maticnetwork/bor/ethdb"
    29  	"github.com/maticnetwork/bor/log"
    30  	"github.com/maticnetwork/bor/rlp"
    31  )
    32  
    33  // freeClientPool implements a client database that limits the connection time
    34  // of each client and manages accepting/rejecting incoming connections and even
    35  // kicking out some connected clients. The pool calculates recent usage time
    36  // for each known client (a value that increases linearly when the client is
    37  // connected and decreases exponentially when not connected). Clients with lower
    38  // recent usage are preferred, unknown nodes have the highest priority. Already
    39  // connected nodes receive a small bias in their favor in order to avoid accepting
    40  // and instantly kicking out clients.
    41  //
    42  // Note: the pool can use any string for client identification. Using signature
    43  // keys for that purpose would not make sense when being known has a negative
    44  // value for the client. Currently the LES protocol manager uses IP addresses
    45  // (without port address) to identify clients.
    46  type freeClientPool struct {
    47  	db         ethdb.Database
    48  	lock       sync.Mutex
    49  	clock      mclock.Clock
    50  	closed     bool
    51  	removePeer func(string)
    52  
    53  	connectedLimit, totalLimit int
    54  	freeClientCap              uint64
    55  	connectedCap               uint64
    56  
    57  	addressMap            map[string]*freeClientPoolEntry
    58  	connPool, disconnPool *prque.Prque
    59  	startupTime           mclock.AbsTime
    60  	logOffsetAtStartup    int64
    61  }
    62  
    63  const (
    64  	recentUsageExpTC     = time.Hour   // time constant of the exponential weighting window for "recent" server usage
    65  	fixedPointMultiplier = 0x1000000   // constant to convert logarithms to fixed point format
    66  	connectedBias        = time.Minute // this bias is applied in favor of already connected clients in order to avoid kicking them out very soon
    67  )
    68  
    69  // newFreeClientPool creates a new free client pool
    70  func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int, clock mclock.Clock, removePeer func(string)) *freeClientPool {
    71  	pool := &freeClientPool{
    72  		db:            db,
    73  		clock:         clock,
    74  		addressMap:    make(map[string]*freeClientPoolEntry),
    75  		connPool:      prque.New(poolSetIndex),
    76  		disconnPool:   prque.New(poolSetIndex),
    77  		freeClientCap: freeClientCap,
    78  		totalLimit:    totalLimit,
    79  		removePeer:    removePeer,
    80  	}
    81  	pool.loadFromDb()
    82  	return pool
    83  }
    84  
    85  func (f *freeClientPool) stop() {
    86  	f.lock.Lock()
    87  	f.closed = true
    88  	f.saveToDb()
    89  	f.lock.Unlock()
    90  }
    91  
    92  // freeClientId returns a string identifier for the peer. Multiple peers with the
    93  // same identifier can not be in the free client pool simultaneously.
    94  func freeClientId(p *peer) string {
    95  	if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok {
    96  		if addr.IP.IsLoopback() {
    97  			// using peer id instead of loopback ip address allows multiple free
    98  			// connections from local machine to own server
    99  			return p.id
   100  		} else {
   101  			return addr.IP.String()
   102  		}
   103  	}
   104  	return ""
   105  }
   106  
   107  // registerPeer implements clientPool
   108  func (f *freeClientPool) registerPeer(p *peer) {
   109  	if freeId := freeClientId(p); freeId != "" {
   110  		if !f.connect(freeId, p.id) {
   111  			f.removePeer(p.id)
   112  		}
   113  	}
   114  }
   115  
   116  // connect should be called after a successful handshake. If the connection was
   117  // rejected, there is no need to call disconnect.
   118  func (f *freeClientPool) connect(address, id string) bool {
   119  	f.lock.Lock()
   120  	defer f.lock.Unlock()
   121  
   122  	if f.closed {
   123  		return false
   124  	}
   125  	if f.connectedLimit == 0 {
   126  		log.Debug("Client rejected", "address", address)
   127  		return false
   128  	}
   129  	e := f.addressMap[address]
   130  	now := f.clock.Now()
   131  	var recentUsage int64
   132  	if e == nil {
   133  		e = &freeClientPoolEntry{address: address, index: -1, id: id}
   134  		f.addressMap[address] = e
   135  	} else {
   136  		if e.connected {
   137  			log.Debug("Client already connected", "address", address)
   138  			return false
   139  		}
   140  		recentUsage = int64(math.Exp(float64(e.logUsage-f.logOffset(now)) / fixedPointMultiplier))
   141  	}
   142  	e.linUsage = recentUsage - int64(now)
   143  	// check whether (linUsage+connectedBias) is smaller than the highest entry in the connected pool
   144  	if f.connPool.Size() == f.connectedLimit {
   145  		i := f.connPool.PopItem().(*freeClientPoolEntry)
   146  		if e.linUsage+int64(connectedBias)-i.linUsage < 0 {
   147  			// kick it out and accept the new client
   148  			f.dropClient(i, now)
   149  			clientKickedMeter.Mark(1)
   150  			f.connectedCap -= f.freeClientCap
   151  		} else {
   152  			// keep the old client and reject the new one
   153  			f.connPool.Push(i, i.linUsage)
   154  			log.Debug("Client rejected", "address", address)
   155  			clientRejectedMeter.Mark(1)
   156  			return false
   157  		}
   158  	}
   159  	f.disconnPool.Remove(e.index)
   160  	e.connected = true
   161  	e.id = id
   162  	f.connPool.Push(e, e.linUsage)
   163  	if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit {
   164  		f.disconnPool.Pop()
   165  	}
   166  	f.connectedCap += f.freeClientCap
   167  	totalConnectedGauge.Update(int64(f.connectedCap))
   168  	clientConnectedMeter.Mark(1)
   169  	log.Debug("Client accepted", "address", address)
   170  	return true
   171  }
   172  
   173  // unregisterPeer implements clientPool
   174  func (f *freeClientPool) unregisterPeer(p *peer) {
   175  	if freeId := freeClientId(p); freeId != "" {
   176  		f.disconnect(freeId)
   177  	}
   178  }
   179  
   180  // disconnect should be called when a connection is terminated. If the disconnection
   181  // was initiated by the pool itself using disconnectFn then calling disconnect is
   182  // not necessary but permitted.
   183  func (f *freeClientPool) disconnect(address string) {
   184  	f.lock.Lock()
   185  	defer f.lock.Unlock()
   186  
   187  	if f.closed {
   188  		return
   189  	}
   190  	// Short circuit if the peer hasn't been registered.
   191  	e := f.addressMap[address]
   192  	if e == nil {
   193  		return
   194  	}
   195  	now := f.clock.Now()
   196  	if !e.connected {
   197  		log.Debug("Client already disconnected", "address", address)
   198  		return
   199  	}
   200  	f.connPool.Remove(e.index)
   201  	f.calcLogUsage(e, now)
   202  	e.connected = false
   203  	f.disconnPool.Push(e, -e.logUsage)
   204  	f.connectedCap -= f.freeClientCap
   205  	totalConnectedGauge.Update(int64(f.connectedCap))
   206  	log.Debug("Client disconnected", "address", address)
   207  }
   208  
   209  // setConnLimit sets the maximum number of free client slots and also drops
   210  // some peers if necessary
   211  func (f *freeClientPool) setLimits(count int, totalCap uint64) {
   212  	f.lock.Lock()
   213  	defer f.lock.Unlock()
   214  
   215  	f.connectedLimit = int(totalCap / f.freeClientCap)
   216  	if count < f.connectedLimit {
   217  		f.connectedLimit = count
   218  	}
   219  	now := mclock.Now()
   220  	for f.connPool.Size() > f.connectedLimit {
   221  		i := f.connPool.PopItem().(*freeClientPoolEntry)
   222  		f.dropClient(i, now)
   223  		f.connectedCap -= f.freeClientCap
   224  	}
   225  	totalConnectedGauge.Update(int64(f.connectedCap))
   226  }
   227  
   228  // dropClient disconnects a client and also moves it from the connected to the
   229  // disconnected pool
   230  func (f *freeClientPool) dropClient(i *freeClientPoolEntry, now mclock.AbsTime) {
   231  	f.connPool.Remove(i.index)
   232  	f.calcLogUsage(i, now)
   233  	i.connected = false
   234  	f.disconnPool.Push(i, -i.logUsage)
   235  	log.Debug("Client kicked out", "address", i.address)
   236  	f.removePeer(i.id)
   237  }
   238  
   239  // logOffset calculates the time-dependent offset for the logarithmic
   240  // representation of recent usage
   241  func (f *freeClientPool) logOffset(now mclock.AbsTime) int64 {
   242  	// Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor
   243  	// is to avoid int64 overflow. We assume that int64(recentUsageExpTC) >> fixedPointMultiplier.
   244  	logDecay := int64((time.Duration(now - f.startupTime)) / (recentUsageExpTC / fixedPointMultiplier))
   245  	return f.logOffsetAtStartup + logDecay
   246  }
   247  
   248  // calcLogUsage converts recent usage from linear to logarithmic representation
   249  // when disconnecting a peer or closing the client pool
   250  func (f *freeClientPool) calcLogUsage(e *freeClientPoolEntry, now mclock.AbsTime) {
   251  	dt := e.linUsage + int64(now)
   252  	if dt < 1 {
   253  		dt = 1
   254  	}
   255  	e.logUsage = int64(math.Log(float64(dt))*fixedPointMultiplier) + f.logOffset(now)
   256  }
   257  
   258  // freeClientPoolStorage is the RLP representation of the pool's database storage
   259  type freeClientPoolStorage struct {
   260  	LogOffset uint64
   261  	List      []*freeClientPoolEntry
   262  }
   263  
   264  // loadFromDb restores pool status from the database storage
   265  // (automatically called at initialization)
   266  func (f *freeClientPool) loadFromDb() {
   267  	enc, err := f.db.Get([]byte("freeClientPool"))
   268  	if err != nil {
   269  		return
   270  	}
   271  	var storage freeClientPoolStorage
   272  	err = rlp.DecodeBytes(enc, &storage)
   273  	if err != nil {
   274  		log.Error("Failed to decode client list", "err", err)
   275  		return
   276  	}
   277  	f.logOffsetAtStartup = int64(storage.LogOffset)
   278  	f.startupTime = f.clock.Now()
   279  	for _, e := range storage.List {
   280  		log.Debug("Loaded free client record", "address", e.address, "logUsage", e.logUsage)
   281  		f.addressMap[e.address] = e
   282  		f.disconnPool.Push(e, -e.logUsage)
   283  	}
   284  }
   285  
   286  // saveToDb saves pool status to the database storage
   287  // (automatically called during shutdown)
   288  func (f *freeClientPool) saveToDb() {
   289  	now := f.clock.Now()
   290  	storage := freeClientPoolStorage{
   291  		LogOffset: uint64(f.logOffset(now)),
   292  		List:      make([]*freeClientPoolEntry, len(f.addressMap)),
   293  	}
   294  	i := 0
   295  	for _, e := range f.addressMap {
   296  		if e.connected {
   297  			f.calcLogUsage(e, now)
   298  		}
   299  		storage.List[i] = e
   300  		i++
   301  	}
   302  	enc, err := rlp.EncodeToBytes(storage)
   303  	if err != nil {
   304  		log.Error("Failed to encode client list", "err", err)
   305  	} else {
   306  		f.db.Put([]byte("freeClientPool"), enc)
   307  	}
   308  }
   309  
   310  // freeClientPoolEntry represents a client address known by the pool.
   311  // When connected, recent usage is calculated as linUsage + int64(clock.Now())
   312  // When disconnected, it is calculated as exp(logUsage - logOffset) where logOffset
   313  // also grows linearly with time while the server is running.
   314  // Conversion between linear and logarithmic representation happens when connecting
   315  // or disconnecting the node.
   316  //
   317  // Note: linUsage and logUsage are values used with constantly growing offsets so
   318  // even though they are close to each other at any time they may wrap around int64
   319  // limits over time. Comparison should be performed accordingly.
   320  type freeClientPoolEntry struct {
   321  	address, id        string
   322  	connected          bool
   323  	disconnectFn       func()
   324  	linUsage, logUsage int64
   325  	index              int
   326  }
   327  
   328  func (e *freeClientPoolEntry) EncodeRLP(w io.Writer) error {
   329  	return rlp.Encode(w, []interface{}{e.address, uint64(e.logUsage)})
   330  }
   331  
   332  func (e *freeClientPoolEntry) DecodeRLP(s *rlp.Stream) error {
   333  	var entry struct {
   334  		Address  string
   335  		LogUsage uint64
   336  	}
   337  	if err := s.Decode(&entry); err != nil {
   338  		return err
   339  	}
   340  	e.address = entry.Address
   341  	e.logUsage = int64(entry.LogUsage)
   342  	e.connected = false
   343  	e.index = -1
   344  	return nil
   345  }
   346  
   347  // poolSetIndex callback is used by both priority queues to set/update the index of
   348  // the element in the queue. Index is needed to remove elements other than the top one.
   349  func poolSetIndex(a interface{}, i int) {
   350  	a.(*freeClientPoolEntry).index = i
   351  }