github.com/zhiqiangxu/go-ethereum@v1.9.16-0.20210824055606-be91cfdebc48/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/zhiqiangxu/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.Timer
    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, bt.lastUpdate = clock.Now(), clock.Now() // Init timestamps
    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.addBalance(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.Stop()
    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  // setCapacity updates the capacity value used for priority calculation
   164  func (bt *balanceTracker) setCapacity(capacity uint64) {
   165  	bt.lock.Lock()
   166  	defer bt.lock.Unlock()
   167  
   168  	bt.capacity = capacity
   169  }
   170  
   171  // getPriority returns the actual priority based on the current balance
   172  func (bt *balanceTracker) getPriority(now mclock.AbsTime) int64 {
   173  	bt.lock.Lock()
   174  	defer bt.lock.Unlock()
   175  
   176  	bt.addBalance(now)
   177  	return bt.balanceToPriority(bt.balance)
   178  }
   179  
   180  // estimatedPriority gives an upper estimate for the priority at a given time in the future.
   181  // If addReqCost is true then an average request cost per time is assumed that is twice the
   182  // average cost per time in the current session. If false, zero request cost is assumed.
   183  func (bt *balanceTracker) estimatedPriority(at mclock.AbsTime, addReqCost bool) int64 {
   184  	bt.lock.Lock()
   185  	defer bt.lock.Unlock()
   186  
   187  	var avgReqCost float64
   188  	if addReqCost {
   189  		dt := time.Duration(bt.lastUpdate - bt.initTime)
   190  		if dt > time.Second {
   191  			avgReqCost = float64(bt.sumReqCost) * 2 / float64(dt)
   192  		}
   193  	}
   194  	return bt.balanceToPriority(bt.reducedBalance(at, avgReqCost))
   195  }
   196  
   197  // addBalance updates balance based on the time factor
   198  func (bt *balanceTracker) addBalance(now mclock.AbsTime) {
   199  	if now > bt.lastUpdate {
   200  		bt.balance = bt.reducedBalance(now, 0)
   201  		bt.lastUpdate = now
   202  	}
   203  }
   204  
   205  // checkCallbacks checks whether the threshold of any of the active callbacks
   206  // have been reached and calls them if necessary. It also sets up or updates
   207  // a scheduled event to ensure that is will be called again just after the next
   208  // threshold has been reached.
   209  // Note: checkCallbacks assumes that the balance has been recently updated.
   210  func (bt *balanceTracker) checkCallbacks(now mclock.AbsTime) {
   211  	if bt.callbackCount == 0 {
   212  		return
   213  	}
   214  	pri := bt.balanceToPriority(bt.balance)
   215  	for bt.callbackCount != 0 && bt.callbacks[bt.callbackCount-1].threshold <= pri {
   216  		bt.callbackCount--
   217  		bt.callbackIndex[bt.callbacks[bt.callbackCount].id] = -1
   218  		go bt.callbacks[bt.callbackCount].callback()
   219  	}
   220  	if bt.callbackCount != 0 {
   221  		d, ok := bt.timeUntil(bt.callbacks[bt.callbackCount-1].threshold)
   222  		if !ok {
   223  			bt.nextUpdate = 0
   224  			bt.updateAfter(0)
   225  			return
   226  		}
   227  		if bt.nextUpdate == 0 || bt.nextUpdate > now+mclock.AbsTime(d) {
   228  			if d > time.Second {
   229  				// Note: if the scheduled update is not in the very near future then we
   230  				// schedule the update a bit earlier. This way we do need to update a few
   231  				// extra times but don't need to reschedule every time a processed request
   232  				// brings the expected firing time a little bit closer.
   233  				d = ((d - time.Second) * 7 / 8) + time.Second
   234  			}
   235  			bt.nextUpdate = now + mclock.AbsTime(d)
   236  			bt.updateAfter(d)
   237  		}
   238  	} else {
   239  		bt.nextUpdate = 0
   240  		bt.updateAfter(0)
   241  	}
   242  }
   243  
   244  // updateAfter schedules a balance update and callback check in the future
   245  func (bt *balanceTracker) updateAfter(dt time.Duration) {
   246  	if bt.updateEvent == nil || bt.updateEvent.Stop() {
   247  		if dt == 0 {
   248  			bt.updateEvent = nil
   249  		} else {
   250  			bt.updateEvent = bt.clock.AfterFunc(dt, func() {
   251  				bt.lock.Lock()
   252  				defer bt.lock.Unlock()
   253  
   254  				if bt.callbackCount != 0 {
   255  					now := bt.clock.Now()
   256  					bt.addBalance(now)
   257  					bt.checkCallbacks(now)
   258  				}
   259  			})
   260  		}
   261  	}
   262  }
   263  
   264  // requestCost should be called after serving a request for the given peer
   265  func (bt *balanceTracker) requestCost(cost uint64) {
   266  	bt.lock.Lock()
   267  	defer bt.lock.Unlock()
   268  
   269  	if bt.stopped {
   270  		return
   271  	}
   272  	now := bt.clock.Now()
   273  	bt.addBalance(now)
   274  	fcost := float64(cost)
   275  
   276  	if bt.balance.pos != 0 {
   277  		if bt.requestFactor != 0 {
   278  			c := uint64(fcost * bt.requestFactor)
   279  			if bt.balance.pos >= c {
   280  				bt.balance.pos -= c
   281  				fcost = 0
   282  			} else {
   283  				fcost *= 1 - float64(bt.balance.pos)/float64(c)
   284  				bt.balance.pos = 0
   285  			}
   286  			bt.checkCallbacks(now)
   287  		} else {
   288  			fcost = 0
   289  		}
   290  	}
   291  	if fcost > 0 {
   292  		if bt.negRequestFactor != 0 {
   293  			bt.balance.neg += uint64(fcost * bt.negRequestFactor)
   294  			bt.checkCallbacks(now)
   295  		}
   296  	}
   297  	bt.sumReqCost += cost
   298  }
   299  
   300  // getBalance returns the current positive and negative balance
   301  func (bt *balanceTracker) getBalance(now mclock.AbsTime) (uint64, uint64) {
   302  	bt.lock.Lock()
   303  	defer bt.lock.Unlock()
   304  
   305  	bt.addBalance(now)
   306  	return bt.balance.pos, bt.balance.neg
   307  }
   308  
   309  // setBalance sets the positive and negative balance to the given values
   310  func (bt *balanceTracker) setBalance(pos, neg uint64) error {
   311  	bt.lock.Lock()
   312  	defer bt.lock.Unlock()
   313  
   314  	now := bt.clock.Now()
   315  	bt.addBalance(now)
   316  	bt.balance.pos = pos
   317  	bt.balance.neg = neg
   318  	bt.checkCallbacks(now)
   319  	return nil
   320  }
   321  
   322  // setFactors sets the price factors. timeFactor is the price of a nanosecond of
   323  // connection while requestFactor is the price of a "realCost" unit.
   324  func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64) {
   325  	bt.lock.Lock()
   326  	defer bt.lock.Unlock()
   327  
   328  	if bt.stopped {
   329  		return
   330  	}
   331  	now := bt.clock.Now()
   332  	bt.addBalance(now)
   333  	if neg {
   334  		bt.negTimeFactor = timeFactor
   335  		bt.negRequestFactor = requestFactor
   336  	} else {
   337  		bt.timeFactor = timeFactor
   338  		bt.requestFactor = requestFactor
   339  	}
   340  	bt.checkCallbacks(now)
   341  }
   342  
   343  // setCallback sets up a one-time callback to be called when priority reaches
   344  // the threshold. If it has already reached the threshold the callback is called
   345  // immediately.
   346  func (bt *balanceTracker) addCallback(id int, threshold int64, callback func()) {
   347  	bt.lock.Lock()
   348  	defer bt.lock.Unlock()
   349  
   350  	bt.removeCb(id)
   351  	idx := 0
   352  	for idx < bt.callbackCount && threshold < bt.callbacks[idx].threshold {
   353  		idx++
   354  	}
   355  	for i := bt.callbackCount - 1; i >= idx; i-- {
   356  		bt.callbackIndex[bt.callbacks[i].id]++
   357  		bt.callbacks[i+1] = bt.callbacks[i]
   358  	}
   359  	bt.callbackCount++
   360  	bt.callbackIndex[id] = idx
   361  	bt.callbacks[idx] = balanceCallback{id, threshold, callback}
   362  	now := bt.clock.Now()
   363  	bt.addBalance(now)
   364  	bt.checkCallbacks(now)
   365  }
   366  
   367  // removeCallback removes the given callback and returns true if it was active
   368  func (bt *balanceTracker) removeCallback(id int) bool {
   369  	bt.lock.Lock()
   370  	defer bt.lock.Unlock()
   371  
   372  	return bt.removeCb(id)
   373  }
   374  
   375  // removeCb removes the given callback and returns true if it was active
   376  // Note: should be called while bt.lock is held
   377  func (bt *balanceTracker) removeCb(id int) bool {
   378  	idx := bt.callbackIndex[id]
   379  	if idx == -1 {
   380  		return false
   381  	}
   382  	bt.callbackIndex[id] = -1
   383  	for i := idx; i < bt.callbackCount-1; i++ {
   384  		bt.callbackIndex[bt.callbacks[i+1].id]--
   385  		bt.callbacks[i] = bt.callbacks[i+1]
   386  	}
   387  	bt.callbackCount--
   388  	return true
   389  }