github.com/c4dt/go-ethereum@v1.9.2/les/balance.go (about)

     1  // Copyright 2019 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  	"sync"
    21  	"time"
    22  
    23  	"github.com/ethereum/go-ethereum/common/mclock"
    24  )
    25  
    26  const (
    27  	balanceCallbackQueue = iota
    28  	balanceCallbackZero
    29  	balanceCallbackCount
    30  )
    31  
    32  // balanceTracker keeps track of the positive and negative balances of a connected
    33  // client and calculates actual and projected future priority values required by
    34  // prque.LazyQueue.
    35  type balanceTracker struct {
    36  	lock                             sync.Mutex
    37  	clock                            mclock.Clock
    38  	stopped                          bool
    39  	capacity                         uint64
    40  	balance                          balance
    41  	timeFactor, requestFactor        float64
    42  	negTimeFactor, negRequestFactor  float64
    43  	sumReqCost                       uint64
    44  	lastUpdate, nextUpdate, initTime mclock.AbsTime
    45  	updateEvent                      mclock.Event
    46  	// since only a limited and fixed number of callbacks are needed, they are
    47  	// stored in a fixed size array ordered by priority threshold.
    48  	callbacks [balanceCallbackCount]balanceCallback
    49  	// callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active)
    50  	callbackIndex [balanceCallbackCount]int
    51  	callbackCount int // number of active callbacks
    52  }
    53  
    54  // balance represents a pair of positive and negative balances
    55  type balance struct {
    56  	pos, neg uint64
    57  }
    58  
    59  // balanceCallback represents a single callback that is activated when client priority
    60  // reaches the given threshold
    61  type balanceCallback struct {
    62  	id        int
    63  	threshold int64
    64  	callback  func()
    65  }
    66  
    67  // init initializes balanceTracker
    68  func (bt *balanceTracker) init(clock mclock.Clock, capacity uint64) {
    69  	bt.clock = clock
    70  	bt.initTime = clock.Now()
    71  	for i := range bt.callbackIndex {
    72  		bt.callbackIndex[i] = -1
    73  	}
    74  	bt.capacity = capacity
    75  }
    76  
    77  // stop shuts down the balance tracker
    78  func (bt *balanceTracker) stop(now mclock.AbsTime) {
    79  	bt.lock.Lock()
    80  	defer bt.lock.Unlock()
    81  
    82  	bt.stopped = true
    83  	bt.updateBalance(now)
    84  	bt.negTimeFactor = 0
    85  	bt.negRequestFactor = 0
    86  	bt.timeFactor = 0
    87  	bt.requestFactor = 0
    88  	if bt.updateEvent != nil {
    89  		bt.updateEvent.Cancel()
    90  		bt.updateEvent = nil
    91  	}
    92  }
    93  
    94  // balanceToPriority converts a balance to a priority value. Higher priority means
    95  // first to disconnect. Positive balance translates to negative priority. If positive
    96  // balance is zero then negative balance translates to a positive priority.
    97  func (bt *balanceTracker) balanceToPriority(b balance) int64 {
    98  	if b.pos > 0 {
    99  		return ^int64(b.pos / bt.capacity)
   100  	}
   101  	return int64(b.neg)
   102  }
   103  
   104  // reducedBalance estimates the reduced balance at a given time in the fututre based
   105  // on the current balance, the time factor and an estimated average request cost per time ratio
   106  func (bt *balanceTracker) reducedBalance(at mclock.AbsTime, avgReqCost float64) balance {
   107  	dt := float64(at - bt.lastUpdate)
   108  	b := bt.balance
   109  	if b.pos != 0 {
   110  		factor := bt.timeFactor + bt.requestFactor*avgReqCost
   111  		diff := uint64(dt * factor)
   112  		if diff <= b.pos {
   113  			b.pos -= diff
   114  			dt = 0
   115  		} else {
   116  			dt -= float64(b.pos) / factor
   117  			b.pos = 0
   118  		}
   119  	}
   120  	if dt != 0 {
   121  		factor := bt.negTimeFactor + bt.negRequestFactor*avgReqCost
   122  		b.neg += uint64(dt * factor)
   123  	}
   124  	return b
   125  }
   126  
   127  // timeUntil calculates the remaining time needed to reach a given priority level
   128  // assuming that no requests are processed until then. If the given level is never
   129  // reached then (0, false) is returned.
   130  // Note: the function assumes that the balance has been recently updated and
   131  // calculates the time starting from the last update.
   132  func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) {
   133  	var dt float64
   134  	if bt.balance.pos != 0 {
   135  		if bt.timeFactor < 1e-100 {
   136  			return 0, false
   137  		}
   138  		if priority < 0 {
   139  			newBalance := uint64(^priority) * bt.capacity
   140  			if newBalance > bt.balance.pos {
   141  				return 0, false
   142  			}
   143  			dt = float64(bt.balance.pos-newBalance) / bt.timeFactor
   144  			return time.Duration(dt), true
   145  		} else {
   146  			dt = float64(bt.balance.pos) / bt.timeFactor
   147  		}
   148  	} else {
   149  		if priority < 0 {
   150  			return 0, false
   151  		}
   152  	}
   153  	// if we have a positive balance then dt equals the time needed to get it to zero
   154  	if uint64(priority) > bt.balance.neg {
   155  		if bt.negTimeFactor < 1e-100 {
   156  			return 0, false
   157  		}
   158  		dt += float64(uint64(priority)-bt.balance.neg) / bt.negTimeFactor
   159  	}
   160  	return time.Duration(dt), true
   161  }
   162  
   163  // getPriority returns the actual priority based on the current balance
   164  func (bt *balanceTracker) getPriority(now mclock.AbsTime) int64 {
   165  	bt.lock.Lock()
   166  	defer bt.lock.Unlock()
   167  
   168  	bt.updateBalance(now)
   169  	return bt.balanceToPriority(bt.balance)
   170  }
   171  
   172  // estimatedPriority gives an upper estimate for the priority at a given time in the future.
   173  // If addReqCost is true then an average request cost per time is assumed that is twice the
   174  // average cost per time in the current session. If false, zero request cost is assumed.
   175  func (bt *balanceTracker) estimatedPriority(at mclock.AbsTime, addReqCost bool) int64 {
   176  	bt.lock.Lock()
   177  	defer bt.lock.Unlock()
   178  
   179  	var avgReqCost float64
   180  	if addReqCost {
   181  		dt := time.Duration(bt.lastUpdate - bt.initTime)
   182  		if dt > time.Second {
   183  			avgReqCost = float64(bt.sumReqCost) * 2 / float64(dt)
   184  		}
   185  	}
   186  	return bt.balanceToPriority(bt.reducedBalance(at, avgReqCost))
   187  }
   188  
   189  // updateBalance updates balance based on the time factor
   190  func (bt *balanceTracker) updateBalance(now mclock.AbsTime) {
   191  	if now > bt.lastUpdate {
   192  		bt.balance = bt.reducedBalance(now, 0)
   193  		bt.lastUpdate = now
   194  	}
   195  }
   196  
   197  // checkCallbacks checks whether the threshold of any of the active callbacks
   198  // have been reached and calls them if necessary. It also sets up or updates
   199  // a scheduled event to ensure that is will be called again just after the next
   200  // threshold has been reached.
   201  // Note: checkCallbacks assumes that the balance has been recently updated.
   202  func (bt *balanceTracker) checkCallbacks(now mclock.AbsTime) {
   203  	if bt.callbackCount == 0 {
   204  		return
   205  	}
   206  	pri := bt.balanceToPriority(bt.balance)
   207  	for bt.callbackCount != 0 && bt.callbacks[bt.callbackCount-1].threshold <= pri {
   208  		bt.callbackCount--
   209  		bt.callbackIndex[bt.callbacks[bt.callbackCount].id] = -1
   210  		go bt.callbacks[bt.callbackCount].callback()
   211  	}
   212  	if bt.callbackCount != 0 {
   213  		d, ok := bt.timeUntil(bt.callbacks[bt.callbackCount-1].threshold)
   214  		if !ok {
   215  			bt.nextUpdate = 0
   216  			bt.updateAfter(0)
   217  			return
   218  		}
   219  		if bt.nextUpdate == 0 || bt.nextUpdate > now+mclock.AbsTime(d) {
   220  			if d > time.Second {
   221  				// Note: if the scheduled update is not in the very near future then we
   222  				// schedule the update a bit earlier. This way we do need to update a few
   223  				// extra times but don't need to reschedule every time a processed request
   224  				// brings the expected firing time a little bit closer.
   225  				d = ((d - time.Second) * 7 / 8) + time.Second
   226  			}
   227  			bt.nextUpdate = now + mclock.AbsTime(d)
   228  			bt.updateAfter(d)
   229  		}
   230  	} else {
   231  		bt.nextUpdate = 0
   232  		bt.updateAfter(0)
   233  	}
   234  }
   235  
   236  // updateAfter schedules a balance update and callback check in the future
   237  func (bt *balanceTracker) updateAfter(dt time.Duration) {
   238  	if bt.updateEvent == nil || bt.updateEvent.Cancel() {
   239  		if dt == 0 {
   240  			bt.updateEvent = nil
   241  		} else {
   242  			bt.updateEvent = bt.clock.AfterFunc(dt, func() {
   243  				bt.lock.Lock()
   244  				defer bt.lock.Unlock()
   245  
   246  				if bt.callbackCount != 0 {
   247  					now := bt.clock.Now()
   248  					bt.updateBalance(now)
   249  					bt.checkCallbacks(now)
   250  				}
   251  			})
   252  		}
   253  	}
   254  }
   255  
   256  // requestCost should be called after serving a request for the given peer
   257  func (bt *balanceTracker) requestCost(cost uint64) {
   258  	bt.lock.Lock()
   259  	defer bt.lock.Unlock()
   260  
   261  	if bt.stopped {
   262  		return
   263  	}
   264  	now := bt.clock.Now()
   265  	bt.updateBalance(now)
   266  	fcost := float64(cost)
   267  
   268  	if bt.balance.pos != 0 {
   269  		if bt.requestFactor != 0 {
   270  			c := uint64(fcost * bt.requestFactor)
   271  			if bt.balance.pos >= c {
   272  				bt.balance.pos -= c
   273  				fcost = 0
   274  			} else {
   275  				fcost *= 1 - float64(bt.balance.pos)/float64(c)
   276  				bt.balance.pos = 0
   277  			}
   278  			bt.checkCallbacks(now)
   279  		} else {
   280  			fcost = 0
   281  		}
   282  	}
   283  	if fcost > 0 {
   284  		if bt.negRequestFactor != 0 {
   285  			bt.balance.neg += uint64(fcost * bt.negRequestFactor)
   286  			bt.checkCallbacks(now)
   287  		}
   288  	}
   289  	bt.sumReqCost += cost
   290  }
   291  
   292  // getBalance returns the current positive and negative balance
   293  func (bt *balanceTracker) getBalance(now mclock.AbsTime) (uint64, uint64) {
   294  	bt.lock.Lock()
   295  	defer bt.lock.Unlock()
   296  
   297  	bt.updateBalance(now)
   298  	return bt.balance.pos, bt.balance.neg
   299  }
   300  
   301  // setBalance sets the positive and negative balance to the given values
   302  func (bt *balanceTracker) setBalance(pos, neg uint64) error {
   303  	bt.lock.Lock()
   304  	defer bt.lock.Unlock()
   305  
   306  	now := bt.clock.Now()
   307  	bt.updateBalance(now)
   308  	bt.balance.pos = pos
   309  	bt.balance.neg = neg
   310  	bt.checkCallbacks(now)
   311  	return nil
   312  }
   313  
   314  // setFactors sets the price factors. timeFactor is the price of a nanosecond of
   315  // connection while requestFactor is the price of a "realCost" unit.
   316  func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64) {
   317  	bt.lock.Lock()
   318  	defer bt.lock.Unlock()
   319  
   320  	if bt.stopped {
   321  		return
   322  	}
   323  	now := bt.clock.Now()
   324  	bt.updateBalance(now)
   325  	if neg {
   326  		bt.negTimeFactor = timeFactor
   327  		bt.negRequestFactor = requestFactor
   328  	} else {
   329  		bt.timeFactor = timeFactor
   330  		bt.requestFactor = requestFactor
   331  	}
   332  	bt.checkCallbacks(now)
   333  }
   334  
   335  // setCallback sets up a one-time callback to be called when priority reaches
   336  // the threshold. If it has already reached the threshold the callback is called
   337  // immediately.
   338  func (bt *balanceTracker) addCallback(id int, threshold int64, callback func()) {
   339  	bt.lock.Lock()
   340  	defer bt.lock.Unlock()
   341  
   342  	bt.removeCb(id)
   343  	idx := 0
   344  	for idx < bt.callbackCount && threshold < bt.callbacks[idx].threshold {
   345  		idx++
   346  	}
   347  	for i := bt.callbackCount - 1; i >= idx; i-- {
   348  		bt.callbackIndex[bt.callbacks[i].id]++
   349  		bt.callbacks[i+1] = bt.callbacks[i]
   350  	}
   351  	bt.callbackCount++
   352  	bt.callbackIndex[id] = idx
   353  	bt.callbacks[idx] = balanceCallback{id, threshold, callback}
   354  	now := bt.clock.Now()
   355  	bt.updateBalance(now)
   356  	bt.checkCallbacks(now)
   357  }
   358  
   359  // removeCallback removes the given callback and returns true if it was active
   360  func (bt *balanceTracker) removeCallback(id int) bool {
   361  	bt.lock.Lock()
   362  	defer bt.lock.Unlock()
   363  
   364  	return bt.removeCb(id)
   365  }
   366  
   367  // removeCb removes the given callback and returns true if it was active
   368  // Note: should be called while bt.lock is held
   369  func (bt *balanceTracker) removeCb(id int) bool {
   370  	idx := bt.callbackIndex[id]
   371  	if idx == -1 {
   372  		return false
   373  	}
   374  	bt.callbackIndex[id] = -1
   375  	for i := idx; i < bt.callbackCount-1; i++ {
   376  		bt.callbackIndex[bt.callbacks[i+1].id]--
   377  		bt.callbacks[i] = bt.callbacks[i+1]
   378  	}
   379  	bt.callbackCount--
   380  	return true
   381  }