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