github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/les/vflux/server/balance.go (about)

     1  // Copyright 2021 The adkgo Authors
     2  // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8).
     3  //
     4  // the adkgo 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 adkgo 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 adkgo library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package server
    18  
    19  import (
    20  	"errors"
    21  	"math"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/aidoskuneen/adk-node/common/mclock"
    26  	"github.com/aidoskuneen/adk-node/les/utils"
    27  	"github.com/aidoskuneen/adk-node/p2p/enode"
    28  	"github.com/aidoskuneen/adk-node/p2p/nodestate"
    29  )
    30  
    31  var errBalanceOverflow = errors.New("balance overflow")
    32  
    33  const maxBalance = math.MaxInt64 // maximum allowed balance value
    34  
    35  const (
    36  	balanceCallbackUpdate = iota // called when priority drops below the last minimum estimate
    37  	balanceCallbackZero          // called when priority drops to zero (positive balance exhausted)
    38  	balanceCallbackCount         // total number of balance callbacks
    39  )
    40  
    41  // PriceFactors determine the pricing policy (may apply either to positive or
    42  // negative balances which may have different factors).
    43  // - TimeFactor is cost unit per nanosecond of connection time
    44  // - CapacityFactor is cost unit per nanosecond of connection time per 1000000 capacity
    45  // - RequestFactor is cost unit per request "realCost" unit
    46  type PriceFactors struct {
    47  	TimeFactor, CapacityFactor, RequestFactor float64
    48  }
    49  
    50  // connectionPrice returns the price of connection per nanosecond at the given capacity
    51  // and the estimated average request cost.
    52  func (p PriceFactors) connectionPrice(cap uint64, avgReqCost float64) float64 {
    53  	return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 + p.RequestFactor*avgReqCost
    54  }
    55  
    56  type (
    57  	// nodePriority interface provides current and estimated future priorities on demand
    58  	nodePriority interface {
    59  		// priority should return the current priority of the node (higher is better)
    60  		priority(cap uint64) int64
    61  		// estimatePriority should return a lower estimate for the minimum of the node priority
    62  		// value starting from the current moment until the given time. If the priority goes
    63  		// under the returned estimate before the specified moment then it is the caller's
    64  		// responsibility to signal with updateFlag.
    65  		estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64
    66  	}
    67  
    68  	// ReadOnlyBalance provides read-only operations on the node balance
    69  	ReadOnlyBalance interface {
    70  		nodePriority
    71  		GetBalance() (uint64, uint64)
    72  		GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue)
    73  		GetPriceFactors() (posFactor, negFactor PriceFactors)
    74  	}
    75  
    76  	// ConnectedBalance provides operations permitted on connected nodes (non-read-only
    77  	// operations are not permitted inside a BalanceOperation)
    78  	ConnectedBalance interface {
    79  		ReadOnlyBalance
    80  		SetPriceFactors(posFactor, negFactor PriceFactors)
    81  		RequestServed(cost uint64) uint64
    82  	}
    83  
    84  	// AtomicBalanceOperator provides operations permitted in an atomic BalanceOperation
    85  	AtomicBalanceOperator interface {
    86  		ReadOnlyBalance
    87  		AddBalance(amount int64) (uint64, uint64, error)
    88  		SetBalance(pos, neg uint64) error
    89  	}
    90  )
    91  
    92  // nodeBalance keeps track of the positive and negative balances of a connected
    93  // client and calculates actual and projected future priority values.
    94  // Implements nodePriority interface.
    95  type nodeBalance struct {
    96  	bt                               *balanceTracker
    97  	lock                             sync.RWMutex
    98  	node                             *enode.Node
    99  	connAddress                      string
   100  	active, hasPriority, setFlags    bool
   101  	capacity                         uint64
   102  	balance                          balance
   103  	posFactor, negFactor             PriceFactors
   104  	sumReqCost                       uint64
   105  	lastUpdate, nextUpdate, initTime mclock.AbsTime
   106  	updateEvent                      mclock.Timer
   107  	// since only a limited and fixed number of callbacks are needed, they are
   108  	// stored in a fixed size array ordered by priority threshold.
   109  	callbacks [balanceCallbackCount]balanceCallback
   110  	// callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active)
   111  	callbackIndex [balanceCallbackCount]int
   112  	callbackCount int // number of active callbacks
   113  }
   114  
   115  // balance represents a pair of positive and negative balances
   116  type balance struct {
   117  	pos, neg       utils.ExpiredValue
   118  	posExp, negExp utils.ValueExpirer
   119  }
   120  
   121  // posValue returns the value of positive balance at a given timestamp.
   122  func (b balance) posValue(now mclock.AbsTime) uint64 {
   123  	return b.pos.Value(b.posExp.LogOffset(now))
   124  }
   125  
   126  // negValue returns the value of negative balance at a given timestamp.
   127  func (b balance) negValue(now mclock.AbsTime) uint64 {
   128  	return b.neg.Value(b.negExp.LogOffset(now))
   129  }
   130  
   131  // addValue adds the value of a given amount to the balance. The original value and
   132  // updated value will also be returned if the addition is successful.
   133  // Returns the error if the given value is too large and the value overflows.
   134  func (b *balance) addValue(now mclock.AbsTime, amount int64, pos bool, force bool) (uint64, uint64, int64, error) {
   135  	var (
   136  		val    utils.ExpiredValue
   137  		offset utils.Fixed64
   138  	)
   139  	if pos {
   140  		offset, val = b.posExp.LogOffset(now), b.pos
   141  	} else {
   142  		offset, val = b.negExp.LogOffset(now), b.neg
   143  	}
   144  	old := val.Value(offset)
   145  	if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) {
   146  		if !force {
   147  			return old, 0, 0, errBalanceOverflow
   148  		}
   149  		val = utils.ExpiredValue{}
   150  		amount = maxBalance
   151  	}
   152  	net := val.Add(amount, offset)
   153  	if pos {
   154  		b.pos = val
   155  	} else {
   156  		b.neg = val
   157  	}
   158  	return old, val.Value(offset), net, nil
   159  }
   160  
   161  // setValue sets the internal balance amount to the given values. Returns the
   162  // error if the given value is too large.
   163  func (b *balance) setValue(now mclock.AbsTime, pos uint64, neg uint64) error {
   164  	if pos > maxBalance || neg > maxBalance {
   165  		return errBalanceOverflow
   166  	}
   167  	var pb, nb utils.ExpiredValue
   168  	pb.Add(int64(pos), b.posExp.LogOffset(now))
   169  	nb.Add(int64(neg), b.negExp.LogOffset(now))
   170  	b.pos = pb
   171  	b.neg = nb
   172  	return nil
   173  }
   174  
   175  // balanceCallback represents a single callback that is activated when client priority
   176  // reaches the given threshold
   177  type balanceCallback struct {
   178  	id        int
   179  	threshold int64
   180  	callback  func()
   181  }
   182  
   183  // GetBalance returns the current positive and negative balance.
   184  func (n *nodeBalance) GetBalance() (uint64, uint64) {
   185  	n.lock.Lock()
   186  	defer n.lock.Unlock()
   187  
   188  	now := n.bt.clock.Now()
   189  	n.updateBalance(now)
   190  	return n.balance.posValue(now), n.balance.negValue(now)
   191  }
   192  
   193  // GetRawBalance returns the current positive and negative balance
   194  // but in the raw(expired value) format.
   195  func (n *nodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) {
   196  	n.lock.Lock()
   197  	defer n.lock.Unlock()
   198  
   199  	now := n.bt.clock.Now()
   200  	n.updateBalance(now)
   201  	return n.balance.pos, n.balance.neg
   202  }
   203  
   204  // AddBalance adds the given amount to the positive balance and returns the balance
   205  // before and after the operation. Exceeding maxBalance results in an error (balance is
   206  // unchanged) while adding a negative amount higher than the current balance results in
   207  // zero balance.
   208  // Note: this function should run inside a NodeStateMachine operation
   209  func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) {
   210  	var (
   211  		err         error
   212  		old, new    uint64
   213  		now         = n.bt.clock.Now()
   214  		callbacks   []func()
   215  		setPriority bool
   216  	)
   217  	// Operation with holding the lock
   218  	n.bt.updateTotalBalance(n, func() bool {
   219  		n.updateBalance(now)
   220  		if old, new, _, err = n.balance.addValue(now, amount, true, false); err != nil {
   221  			return false
   222  		}
   223  		callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus()
   224  		n.storeBalance(true, false)
   225  		return true
   226  	})
   227  	if err != nil {
   228  		return old, old, err
   229  	}
   230  	// Operation without holding the lock
   231  	for _, cb := range callbacks {
   232  		cb()
   233  	}
   234  	if n.setFlags {
   235  		if setPriority {
   236  			n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
   237  		}
   238  		// Note: priority flag is automatically removed by the zero priority callback if necessary
   239  		n.signalPriorityUpdate()
   240  	}
   241  	return old, new, nil
   242  }
   243  
   244  // SetBalance sets the positive and negative balance to the given values
   245  // Note: this function should run inside a NodeStateMachine operation
   246  func (n *nodeBalance) SetBalance(pos, neg uint64) error {
   247  	var (
   248  		now         = n.bt.clock.Now()
   249  		callbacks   []func()
   250  		setPriority bool
   251  	)
   252  	// Operation with holding the lock
   253  	n.bt.updateTotalBalance(n, func() bool {
   254  		n.updateBalance(now)
   255  		if err := n.balance.setValue(now, pos, neg); err != nil {
   256  			return false
   257  		}
   258  		callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus()
   259  		n.storeBalance(true, true)
   260  		return true
   261  	})
   262  	// Operation without holding the lock
   263  	for _, cb := range callbacks {
   264  		cb()
   265  	}
   266  	if n.setFlags {
   267  		if setPriority {
   268  			n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
   269  		}
   270  		// Note: priority flag is automatically removed by the zero priority callback if necessary
   271  		n.signalPriorityUpdate()
   272  	}
   273  	return nil
   274  }
   275  
   276  // RequestServed should be called after serving a request for the given peer
   277  func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) {
   278  	n.lock.Lock()
   279  
   280  	var (
   281  		check bool
   282  		fcost = float64(cost)
   283  		now   = n.bt.clock.Now()
   284  	)
   285  	n.updateBalance(now)
   286  	if !n.balance.pos.IsZero() {
   287  		posCost := -int64(fcost * n.posFactor.RequestFactor)
   288  		if posCost == 0 {
   289  			fcost = 0
   290  			newBalance = n.balance.posValue(now)
   291  		} else {
   292  			var net int64
   293  			_, newBalance, net, _ = n.balance.addValue(now, posCost, true, false)
   294  			if posCost == net {
   295  				fcost = 0
   296  			} else {
   297  				fcost *= 1 - float64(net)/float64(posCost)
   298  			}
   299  			check = true
   300  		}
   301  	}
   302  	if fcost > 0 && n.negFactor.RequestFactor != 0 {
   303  		n.balance.addValue(now, int64(fcost*n.negFactor.RequestFactor), false, false)
   304  		check = true
   305  	}
   306  	n.sumReqCost += cost
   307  
   308  	var callbacks []func()
   309  	if check {
   310  		callbacks = n.checkCallbacks(now)
   311  	}
   312  	n.lock.Unlock()
   313  
   314  	if callbacks != nil {
   315  		n.bt.ns.Operation(func() {
   316  			for _, cb := range callbacks {
   317  				cb()
   318  			}
   319  		})
   320  	}
   321  	return
   322  }
   323  
   324  // priority returns the actual priority based on the current balance
   325  func (n *nodeBalance) priority(capacity uint64) int64 {
   326  	n.lock.Lock()
   327  	defer n.lock.Unlock()
   328  
   329  	now := n.bt.clock.Now()
   330  	n.updateBalance(now)
   331  	return n.balanceToPriority(now, n.balance, capacity)
   332  }
   333  
   334  // EstMinPriority gives a lower estimate for the priority at a given time in the future.
   335  // An average request cost per time is assumed that is twice the average cost per time
   336  // in the current session.
   337  // If update is true then a priority callback is added that turns updateFlag on and off
   338  // in case the priority goes below the estimated minimum.
   339  func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 {
   340  	n.lock.Lock()
   341  	defer n.lock.Unlock()
   342  
   343  	now := n.bt.clock.Now()
   344  	n.updateBalance(now)
   345  
   346  	b := n.balance // copy the balance
   347  	if addBalance != 0 {
   348  		b.addValue(now, addBalance, true, true)
   349  	}
   350  	if future > 0 {
   351  		var avgReqCost float64
   352  		dt := time.Duration(n.lastUpdate - n.initTime)
   353  		if dt > time.Second {
   354  			avgReqCost = float64(n.sumReqCost) * 2 / float64(dt)
   355  		}
   356  		b = n.reducedBalance(b, now, future, capacity, avgReqCost)
   357  	}
   358  	if bias > 0 {
   359  		b = n.reducedBalance(b, now+mclock.AbsTime(future), bias, capacity, 0)
   360  	}
   361  	pri := n.balanceToPriority(now, b, capacity)
   362  	// Ensure that biased estimates are always lower than actual priorities, even if
   363  	// the bias is very small.
   364  	// This ensures that two nodes will not ping-pong update signals forever if both of
   365  	// them have zero estimated priority drop in the projected future.
   366  	current := n.balanceToPriority(now, n.balance, capacity)
   367  	if pri >= current {
   368  		pri = current - 1
   369  	}
   370  	if update {
   371  		n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate)
   372  	}
   373  	return pri
   374  }
   375  
   376  // SetPriceFactors sets the price factors. TimeFactor is the price of a nanosecond of
   377  // connection while RequestFactor is the price of a request cost unit.
   378  func (n *nodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) {
   379  	n.lock.Lock()
   380  	now := n.bt.clock.Now()
   381  	n.updateBalance(now)
   382  	n.posFactor, n.negFactor = posFactor, negFactor
   383  	callbacks := n.checkCallbacks(now)
   384  	n.lock.Unlock()
   385  	if callbacks != nil {
   386  		n.bt.ns.Operation(func() {
   387  			for _, cb := range callbacks {
   388  				cb()
   389  			}
   390  		})
   391  	}
   392  }
   393  
   394  // GetPriceFactors returns the price factors
   395  func (n *nodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) {
   396  	n.lock.Lock()
   397  	defer n.lock.Unlock()
   398  
   399  	return n.posFactor, n.negFactor
   400  }
   401  
   402  // activate starts time/capacity cost deduction.
   403  func (n *nodeBalance) activate() {
   404  	n.bt.updateTotalBalance(n, func() bool {
   405  		if n.active {
   406  			return false
   407  		}
   408  		n.active = true
   409  		n.lastUpdate = n.bt.clock.Now()
   410  		return true
   411  	})
   412  }
   413  
   414  // deactivate stops time/capacity cost deduction and saves the balances in the database
   415  func (n *nodeBalance) deactivate() {
   416  	n.bt.updateTotalBalance(n, func() bool {
   417  		if !n.active {
   418  			return false
   419  		}
   420  		n.updateBalance(n.bt.clock.Now())
   421  		if n.updateEvent != nil {
   422  			n.updateEvent.Stop()
   423  			n.updateEvent = nil
   424  		}
   425  		n.storeBalance(true, true)
   426  		n.active = false
   427  		return true
   428  	})
   429  }
   430  
   431  // updateBalance updates balance based on the time factor
   432  func (n *nodeBalance) updateBalance(now mclock.AbsTime) {
   433  	if n.active && now > n.lastUpdate {
   434  		n.balance = n.reducedBalance(n.balance, n.lastUpdate, time.Duration(now-n.lastUpdate), n.capacity, 0)
   435  		n.lastUpdate = now
   436  	}
   437  }
   438  
   439  // storeBalance stores the positive and/or negative balance of the node in the database
   440  func (n *nodeBalance) storeBalance(pos, neg bool) {
   441  	if pos {
   442  		n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos)
   443  	}
   444  	if neg {
   445  		n.bt.storeBalance([]byte(n.connAddress), true, n.balance.neg)
   446  	}
   447  }
   448  
   449  // addCallback sets up a one-time callback to be called when priority reaches
   450  // the threshold. If it has already reached the threshold the callback is called
   451  // immediately.
   452  // Note: should be called while n.lock is held
   453  // Note 2: the callback function runs inside a NodeStateMachine operation
   454  func (n *nodeBalance) addCallback(id int, threshold int64, callback func()) {
   455  	n.removeCallback(id)
   456  	idx := 0
   457  	for idx < n.callbackCount && threshold > n.callbacks[idx].threshold {
   458  		idx++
   459  	}
   460  	for i := n.callbackCount - 1; i >= idx; i-- {
   461  		n.callbackIndex[n.callbacks[i].id]++
   462  		n.callbacks[i+1] = n.callbacks[i]
   463  	}
   464  	n.callbackCount++
   465  	n.callbackIndex[id] = idx
   466  	n.callbacks[idx] = balanceCallback{id, threshold, callback}
   467  	now := n.bt.clock.Now()
   468  	n.updateBalance(now)
   469  	n.scheduleCheck(now)
   470  }
   471  
   472  // removeCallback removes the given callback and returns true if it was active
   473  // Note: should be called while n.lock is held
   474  func (n *nodeBalance) removeCallback(id int) bool {
   475  	idx := n.callbackIndex[id]
   476  	if idx == -1 {
   477  		return false
   478  	}
   479  	n.callbackIndex[id] = -1
   480  	for i := idx; i < n.callbackCount-1; i++ {
   481  		n.callbackIndex[n.callbacks[i+1].id]--
   482  		n.callbacks[i] = n.callbacks[i+1]
   483  	}
   484  	n.callbackCount--
   485  	return true
   486  }
   487  
   488  // checkCallbacks checks whether the threshold of any of the active callbacks
   489  // have been reached and returns triggered callbacks.
   490  // Note: checkCallbacks assumes that the balance has been recently updated.
   491  func (n *nodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) {
   492  	if n.callbackCount == 0 || n.capacity == 0 {
   493  		return
   494  	}
   495  	pri := n.balanceToPriority(now, n.balance, n.capacity)
   496  	for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri {
   497  		n.callbackCount--
   498  		n.callbackIndex[n.callbacks[n.callbackCount].id] = -1
   499  		callbacks = append(callbacks, n.callbacks[n.callbackCount].callback)
   500  	}
   501  	n.scheduleCheck(now)
   502  	return
   503  }
   504  
   505  // scheduleCheck sets up or updates a scheduled event to ensure that it will be called
   506  // again just after the next threshold has been reached.
   507  func (n *nodeBalance) scheduleCheck(now mclock.AbsTime) {
   508  	if n.callbackCount != 0 {
   509  		d, ok := n.timeUntil(n.callbacks[n.callbackCount-1].threshold)
   510  		if !ok {
   511  			n.nextUpdate = 0
   512  			n.updateAfter(0)
   513  			return
   514  		}
   515  		if n.nextUpdate == 0 || n.nextUpdate > now+mclock.AbsTime(d) {
   516  			if d > time.Second {
   517  				// Note: if the scheduled update is not in the very near future then we
   518  				// schedule the update a bit earlier. This way we do need to update a few
   519  				// extra times but don't need to reschedule every time a processed request
   520  				// brings the expected firing time a little bit closer.
   521  				d = ((d - time.Second) * 7 / 8) + time.Second
   522  			}
   523  			n.nextUpdate = now + mclock.AbsTime(d)
   524  			n.updateAfter(d)
   525  		}
   526  	} else {
   527  		n.nextUpdate = 0
   528  		n.updateAfter(0)
   529  	}
   530  }
   531  
   532  // updateAfter schedules a balance update and callback check in the future
   533  func (n *nodeBalance) updateAfter(dt time.Duration) {
   534  	if n.updateEvent == nil || n.updateEvent.Stop() {
   535  		if dt == 0 {
   536  			n.updateEvent = nil
   537  		} else {
   538  			n.updateEvent = n.bt.clock.AfterFunc(dt, func() {
   539  				var callbacks []func()
   540  				n.lock.Lock()
   541  				if n.callbackCount != 0 {
   542  					now := n.bt.clock.Now()
   543  					n.updateBalance(now)
   544  					callbacks = n.checkCallbacks(now)
   545  				}
   546  				n.lock.Unlock()
   547  				if callbacks != nil {
   548  					n.bt.ns.Operation(func() {
   549  						for _, cb := range callbacks {
   550  							cb()
   551  						}
   552  					})
   553  				}
   554  			})
   555  		}
   556  	}
   557  }
   558  
   559  // balanceExhausted should be called when the positive balance is exhausted (priority goes to zero/negative)
   560  // Note: this function should run inside a NodeStateMachine operation
   561  func (n *nodeBalance) balanceExhausted() {
   562  	n.lock.Lock()
   563  	n.storeBalance(true, false)
   564  	n.hasPriority = false
   565  	n.lock.Unlock()
   566  	if n.setFlags {
   567  		n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.priorityFlag, 0)
   568  	}
   569  }
   570  
   571  // checkPriorityStatus checks whether the node has gained priority status and sets the priority
   572  // callback and flag if necessary. It assumes that the balance has been recently updated.
   573  // Note that the priority flag has to be set by the caller after the mutex has been released.
   574  func (n *nodeBalance) checkPriorityStatus() bool {
   575  	if !n.hasPriority && !n.balance.pos.IsZero() {
   576  		n.hasPriority = true
   577  		n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() })
   578  		return true
   579  	}
   580  	return false
   581  }
   582  
   583  // signalPriorityUpdate signals that the priority fell below the previous minimum estimate
   584  // Note: this function should run inside a NodeStateMachine operation
   585  func (n *nodeBalance) signalPriorityUpdate() {
   586  	n.bt.ns.SetStateSub(n.node, n.bt.setup.updateFlag, nodestate.Flags{}, 0)
   587  	n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.updateFlag, 0)
   588  }
   589  
   590  // setCapacity updates the capacity value used for priority calculation
   591  // Note: capacity should never be zero
   592  // Note 2: this function should run inside a NodeStateMachine operation
   593  func (n *nodeBalance) setCapacity(capacity uint64) {
   594  	n.lock.Lock()
   595  	now := n.bt.clock.Now()
   596  	n.updateBalance(now)
   597  	n.capacity = capacity
   598  	callbacks := n.checkCallbacks(now)
   599  	n.lock.Unlock()
   600  	for _, cb := range callbacks {
   601  		cb()
   602  	}
   603  }
   604  
   605  // balanceToPriority converts a balance to a priority value. Lower priority means
   606  // first to disconnect. Positive balance translates to positive priority. If positive
   607  // balance is zero then negative balance translates to a negative priority.
   608  func (n *nodeBalance) balanceToPriority(now mclock.AbsTime, b balance, capacity uint64) int64 {
   609  	pos := b.posValue(now)
   610  	if pos > 0 {
   611  		return int64(pos / capacity)
   612  	}
   613  	return -int64(b.negValue(now))
   614  }
   615  
   616  // priorityToBalance converts a target priority to a requested balance value.
   617  // If the priority is negative, then minimal negative balance is returned;
   618  // otherwise the minimal positive balance is returned.
   619  func (n *nodeBalance) priorityToBalance(priority int64, capacity uint64) (uint64, uint64) {
   620  	if priority > 0 {
   621  		return uint64(priority) * n.capacity, 0
   622  	}
   623  	return 0, uint64(-priority)
   624  }
   625  
   626  // reducedBalance estimates the reduced balance at a given time in the fututre based
   627  // on the given balance, the time factor and an estimated average request cost per time ratio
   628  func (n *nodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance {
   629  	// since the costs are applied continuously during the dt time period we calculate
   630  	// the expiration offset at the middle of the period
   631  	var (
   632  		at  = start + mclock.AbsTime(dt/2)
   633  		dtf = float64(dt)
   634  	)
   635  	if !b.pos.IsZero() {
   636  		factor := n.posFactor.connectionPrice(capacity, avgReqCost)
   637  		diff := -int64(dtf * factor)
   638  		_, _, net, _ := b.addValue(at, diff, true, false)
   639  		if net == diff {
   640  			dtf = 0
   641  		} else {
   642  			dtf += float64(net) / factor
   643  		}
   644  	}
   645  	if dtf > 0 {
   646  		factor := n.negFactor.connectionPrice(capacity, avgReqCost)
   647  		b.addValue(at, int64(dtf*factor), false, false)
   648  	}
   649  	return b
   650  }
   651  
   652  // timeUntil calculates the remaining time needed to reach a given priority level
   653  // assuming that no requests are processed until then. If the given level is never
   654  // reached then (0, false) is returned. If it has already been reached then (0, true)
   655  // is returned.
   656  // Note: the function assumes that the balance has been recently updated and
   657  // calculates the time starting from the last update.
   658  func (n *nodeBalance) timeUntil(priority int64) (time.Duration, bool) {
   659  	var (
   660  		now                  = n.bt.clock.Now()
   661  		pos                  = n.balance.posValue(now)
   662  		targetPos, targetNeg = n.priorityToBalance(priority, n.capacity)
   663  		diffTime             float64
   664  	)
   665  	if pos > 0 {
   666  		timePrice := n.posFactor.connectionPrice(n.capacity, 0)
   667  		if timePrice < 1e-100 {
   668  			return 0, false
   669  		}
   670  		if targetPos > 0 {
   671  			if targetPos > pos {
   672  				return 0, true
   673  			}
   674  			diffTime = float64(pos-targetPos) / timePrice
   675  			return time.Duration(diffTime), true
   676  		} else {
   677  			diffTime = float64(pos) / timePrice
   678  		}
   679  	} else {
   680  		if targetPos > 0 {
   681  			return 0, true
   682  		}
   683  	}
   684  	neg := n.balance.negValue(now)
   685  	if targetNeg > neg {
   686  		timePrice := n.negFactor.connectionPrice(n.capacity, 0)
   687  		if timePrice < 1e-100 {
   688  			return 0, false
   689  		}
   690  		diffTime += float64(targetNeg-neg) / timePrice
   691  	}
   692  	return time.Duration(diffTime), true
   693  }