Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

les: first part of les4 purposed changed #4

Merged
merged 6 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions les/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,16 @@ func (api *PrivateLightServerAPI) clientInfo(c *clientInfo, id enode.ID) map[str
info["capacity"] = c.capacity
pb, nb := c.balanceTracker.getBalance(now)
info["pricing/balance"], info["pricing/negBalance"] = pb, nb
info["pricing/balanceMeta"] = c.balanceMetaInfo

cb := api.server.clientPool.ndb.getCurrencyBalance(id)
info["pricing/currency"] = cb.amount
info["priority"] = pb.base != 0
} else {
info["isConnected"] = false
pb := api.server.clientPool.ndb.getOrNewPB(id)
info["pricing/balance"], info["pricing/balanceMeta"] = pb.value, pb.meta
pb := api.server.clientPool.ndb.getOrNewBalance(id.Bytes(), false)

cb := api.server.clientPool.ndb.getCurrencyBalance(id)
info["pricing/balance"], info["pricing/currency"] = pb.value, cb.amount
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual pricing/balance should be pb.value.value(logOffset). It's fixed by latter commit

info["priority"] = pb.value.base != 0
}
return info
Expand Down Expand Up @@ -150,7 +154,7 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien
setFactor(&negFactors.requestFactor)
case !defParams && name == "capacity":
if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity {
_, _, err = api.server.clientPool.setCapacity(client.id, client.freeID, uint64(capacity), 0, true)
_, _, err = api.server.clientPool.setCapacity(client.id, client.address, uint64(capacity), 0, true)
// Don't have to call factor update explicitly. It's already done
// in setCapacity function.
} else {
Expand All @@ -172,8 +176,8 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien

// AddBalance updates the balance of a client (either overwrites it or adds to it).
// It also updates the balance meta info string.
func (api *PrivateLightServerAPI) AddBalance(id enode.ID, value int64, meta string) ([2]uint64, error) {
oldBalance, newBalance, err := api.server.clientPool.addBalance(id, value, meta)
func (api *PrivateLightServerAPI) AddBalance(id enode.ID, value int64) ([2]uint64, error) {
oldBalance, newBalance, err := api.server.clientPool.addBalance(id, value)
return [2]uint64{oldBalance, newBalance}, err
}

Expand All @@ -184,7 +188,7 @@ func (api *PrivateLightServerAPI) SetClientParams(ids []enode.ID, params map[str
if client != nil {
update, err := api.setParams(params, client, nil, nil)
if update {
updatePriceFactors(&client.balanceTracker, client.posFactors, client.negFactors, client.capacity)
updatePriceFactors(&client.balanceTracker, client.posFactors, client.negFactors)
}
return err
} else {
Expand Down
130 changes: 38 additions & 92 deletions les/balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,74 +35,25 @@ const (
// expirationController controls the exponential expiration of positive and negative
// balances
type expirationController interface {
posExpiration(mclock.AbsTime) uint64
negExpiration(mclock.AbsTime) uint64
posExpiration(mclock.AbsTime) fixed64
negExpiration(mclock.AbsTime) fixed64
}

// expiredValue is a scalar value that is continuously expired (decreased exponentially)
// based on the logarithmic expiration offset value provided by the expirationController.
// The actual value can be calculated as base*2^(exp-logOffset/log2Multiplier).
//
// Note: this representation is basically a floating point value capable of handling
// 64 bit exponents. The operations are not general purpose floating point operations
// though because a monotonously increasing exponent is assumed and therefore if an
// operation is performed with two different exponents then simply the higher one is used.
type expiredValue struct {
base, exp uint64
// priceFactors determine the pricing policy (may apply either to positive or
// negative balances which may have different factors).
// - timeFactor is cost unit per nanosecond of connection time
// - capacityFactor is cost unit per nanosecond of connection time per 1000000 capacity
// - requestFactor is cost unit per request "realCost" unit
type priceFactors struct {
timeFactor, capacityFactor, requestFactor float64
}

// value calculates the value at the given moment
func (e expiredValue) value(logOffset uint64) uint64 {
return uint64(math.Exp(float64(int64(e.exp*log2Multiplier-logOffset))/logMultiplier) * float64(e.base))
func (p priceFactors) timePrice(cap uint64) float64 {
return p.timeFactor + float64(cap)*p.capacityFactor/1000000
}

// add adds a signed value at the given moment
func (e *expiredValue) add(amount int64, logOffset uint64) int64 {
addExp := logOffset / log2Multiplier
baseMul := math.Exp(float64(logOffset%log2Multiplier) / logMultiplier)
if addExp < e.exp {
baseMul /= math.Pow(2, float64(e.exp-addExp))
}
if addExp > e.exp {
e.base >>= (addExp - e.exp)
e.exp = addExp
}
add := int64(float64(amount) * baseMul)
if add >= 0 || uint64(-add) <= e.base {
e.base += uint64(add)
return amount
} else {
e.base = 0
return int64(-float64(e.base) / baseMul)
}
}

// addExp adds another expiredValue
func (e *expiredValue) addExp(a expiredValue) {
if e.exp > a.exp {
a.base >>= (e.exp - a.exp)
}
if e.exp < a.exp {
e.base >>= (a.exp - e.exp)
e.exp = a.exp
}
e.base += a.base
}

// subExp subtracts another expiredValue
func (e *expiredValue) subExp(a expiredValue) {
if e.exp > a.exp {
a.base >>= (e.exp - a.exp)
}
if e.exp < a.exp {
e.base >>= (a.exp - e.exp)
e.exp = a.exp
}
if e.base > a.base {
e.base -= a.base
} else {
e.base = 0
}
func (p priceFactors) reqPrice() float64 {
return p.requestFactor
}

// balanceTracker keeps track of the positive and negative balances of a connected
Expand All @@ -115,8 +66,7 @@ type balanceTracker struct {
stopped bool
capacity uint64
balance balance
timeFactor, requestFactor float64
negTimeFactor, negRequestFactor float64
posFactor, negFactor priceFactors
sumReqCost uint64
lastUpdate, nextUpdate, initTime mclock.AbsTime
updateEvent mclock.Timer
Expand Down Expand Up @@ -159,10 +109,8 @@ func (bt *balanceTracker) stop(now mclock.AbsTime) {

bt.stopped = true
bt.addBalance(now)
bt.negTimeFactor = 0
bt.negRequestFactor = 0
bt.timeFactor = 0
bt.requestFactor = 0
bt.posFactor = priceFactors{0, 0, 0}
bt.negFactor = priceFactors{0, 0, 0}
if bt.updateEvent != nil {
bt.updateEvent.Stop()
bt.updateEvent = nil
Expand All @@ -185,21 +133,23 @@ func (bt *balanceTracker) balanceToPriority(b balance) int64 {
func (bt *balanceTracker) posBalanceMissing(targetPriority int64, targetCapacity uint64, after time.Duration) uint64 {
now := bt.clock.Now()
if targetPriority > 0 {
negPrice := uint64(float64(after) * bt.negTimeFactor)
timePrice := bt.negFactor.timePrice(targetCapacity)
timeCost := uint64(float64(after) * timePrice)
negBalance := bt.balance.neg.value(bt.exp.negExpiration(now))
if negPrice+negBalance < uint64(targetPriority) {
if timeCost+negBalance < uint64(targetPriority) {
return 0
}
if uint64(targetPriority) > negBalance && bt.negTimeFactor > 1e-100 {
if negTime := time.Duration(float64(uint64(targetPriority)-negBalance) / bt.negTimeFactor); negTime < after {
if uint64(targetPriority) > negBalance && timePrice > 1e-100 {
if negTime := time.Duration(float64(uint64(targetPriority)-negBalance) / timePrice); negTime < after {
after -= negTime
} else {
after = 0
}
}
targetPriority = 0
}
posRequired := uint64(float64(-targetPriority)*float64(targetCapacity)+float64(after)*bt.timeFactor) + 1
timePrice := bt.posFactor.timePrice(targetCapacity)
posRequired := uint64(float64(-targetPriority)*float64(targetCapacity)+float64(after)*timePrice) + 1
if posRequired >= maxBalance {
return math.MaxUint64 // target not reachable
}
Expand All @@ -216,7 +166,7 @@ func (bt *balanceTracker) reducedBalance(at mclock.AbsTime, avgReqCost float64)
dt := float64(at - bt.lastUpdate)
b := bt.balance
if b.pos.base != 0 {
factor := bt.timeFactor + bt.requestFactor*avgReqCost
factor := bt.posFactor.timePrice(bt.capacity) + bt.posFactor.reqPrice()*avgReqCost
diff := -int64(dt * factor)
dd := b.pos.add(diff, bt.exp.posExpiration(at))
if dd == diff {
Expand All @@ -226,7 +176,7 @@ func (bt *balanceTracker) reducedBalance(at mclock.AbsTime, avgReqCost float64)
}
}
if dt > 0 {
factor := bt.negTimeFactor + bt.negRequestFactor*avgReqCost
factor := bt.negFactor.timePrice(bt.capacity) + bt.negFactor.reqPrice()*avgReqCost
b.neg.add(int64(dt*factor), bt.exp.negExpiration(at))
}
return b
Expand All @@ -242,18 +192,19 @@ func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) {
var dt float64
if bt.balance.pos.base != 0 {
posBalance := bt.balance.pos.value(bt.exp.posExpiration(now))
if bt.timeFactor < 1e-100 {
timePrice := bt.posFactor.timePrice(bt.capacity)
if timePrice < 1e-100 {
return 0, false
}
if priority < 0 {
newBalance := uint64(-priority) * bt.capacity
if newBalance > posBalance {
return 0, false
}
dt = float64(posBalance-newBalance) / bt.timeFactor
dt = float64(posBalance-newBalance) / timePrice
return time.Duration(dt), true
} else {
dt = float64(posBalance) / bt.timeFactor
dt = float64(posBalance) / timePrice
}
} else {
if priority < 0 {
Expand All @@ -262,11 +213,12 @@ func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) {
}
// if we have a positive balance then dt equals the time needed to get it to zero
negBalance := bt.balance.neg.value(bt.exp.negExpiration(now))
timePrice := bt.negFactor.timePrice(bt.capacity)
if uint64(priority) > negBalance {
if bt.negTimeFactor < 1e-100 {
if timePrice < 1e-100 {
return 0, false
}
dt += float64(uint64(priority)-negBalance) / bt.negTimeFactor
dt += float64(uint64(priority)-negBalance) / timePrice
}
return time.Duration(dt), true
}
Expand Down Expand Up @@ -387,8 +339,8 @@ func (bt *balanceTracker) requestCost(cost uint64) uint64 {

posExp := bt.exp.posExpiration(now)
if bt.balance.pos.base != 0 {
if bt.requestFactor != 0 {
c := -int64(fcost * bt.requestFactor)
if bt.posFactor.reqPrice() != 0 {
c := -int64(fcost * bt.posFactor.reqPrice())
cc := bt.balance.pos.add(c, posExp)
if c == cc {
fcost = 0
Expand All @@ -401,8 +353,8 @@ func (bt *balanceTracker) requestCost(cost uint64) uint64 {
}
}
if fcost > 0 {
if bt.negRequestFactor != 0 {
bt.balance.neg.add(int64(fcost*bt.negRequestFactor), bt.exp.negExpiration(now))
if bt.negFactor.reqPrice() != 0 {
bt.balance.neg.add(int64(fcost*bt.negFactor.reqPrice()), bt.exp.negExpiration(now))
bt.checkCallbacks(now)
}
}
Expand Down Expand Up @@ -434,7 +386,7 @@ func (bt *balanceTracker) setBalance(pos, neg expiredValue) error {

// setFactors sets the price factors. timeFactor is the price of a nanosecond of
// connection while requestFactor is the price of a "realCost" unit.
func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64) {
func (bt *balanceTracker) setFactors(posFactor, negFactor priceFactors) {
bt.lock.Lock()
defer bt.lock.Unlock()

Expand All @@ -443,13 +395,7 @@ func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64
}
now := bt.clock.Now()
bt.addBalance(now)
if neg {
bt.negTimeFactor = timeFactor
bt.negRequestFactor = requestFactor
} else {
bt.timeFactor = timeFactor
bt.requestFactor = requestFactor
}
bt.posFactor, bt.negFactor = posFactor, negFactor
bt.checkCallbacks(now)
}

Expand Down
22 changes: 8 additions & 14 deletions les/balance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import (

type zeroExpCtrl struct{}

func (z zeroExpCtrl) posExpiration(mclock.AbsTime) uint64 {
func (z zeroExpCtrl) posExpiration(mclock.AbsTime) fixed64 {
return 0
}

func (z zeroExpCtrl) negExpiration(mclock.AbsTime) uint64 {
func (z zeroExpCtrl) negExpiration(mclock.AbsTime) fixed64 {
return 0
}

Expand Down Expand Up @@ -70,8 +70,7 @@ func TestBalanceTimeCost(t *testing.T) {
)
tracker.init(clock, 1000)
defer tracker.stop(clock.Now())
tracker.setFactors(false, 1, 1)
tracker.setFactors(true, 1, 1)
tracker.setFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})

tracker.setBalance(expval(uint64(time.Minute)), expval(0)) // 1 minute time allowance

Expand Down Expand Up @@ -114,8 +113,7 @@ func TestBalanceReqCost(t *testing.T) {
)
tracker.init(clock, 1000)
defer tracker.stop(clock.Now())
tracker.setFactors(false, 1, 1)
tracker.setFactors(true, 1, 1)
tracker.setFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})

tracker.setBalance(expval(uint64(time.Minute)), expval(0)) // 1 minute time serving time allowance
var inputs = []struct {
Expand Down Expand Up @@ -146,8 +144,7 @@ func TestBalanceToPriority(t *testing.T) {
)
tracker.init(clock, 1000) // cap = 1000
defer tracker.stop(clock.Now())
tracker.setFactors(false, 1, 1)
tracker.setFactors(true, 1, 1)
tracker.setFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})

var inputs = []struct {
pos uint64
Expand Down Expand Up @@ -175,8 +172,7 @@ func TestEstimatedPriority(t *testing.T) {
)
tracker.init(clock, 1000000000) // cap = 1000,000,000
defer tracker.stop(clock.Now())
tracker.setFactors(false, 1, 1)
tracker.setFactors(true, 1, 1)
tracker.setFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})

tracker.setBalance(expval(uint64(time.Minute)), expval(0))
var inputs = []struct {
Expand Down Expand Up @@ -219,8 +215,7 @@ func TestCallbackChecking(t *testing.T) {
)
tracker.init(clock, 1000000) // cap = 1000,000
defer tracker.stop(clock.Now())
tracker.setFactors(false, 1, 1)
tracker.setFactors(true, 1, 1)
tracker.setFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})

var inputs = []struct {
priority int64
Expand All @@ -246,8 +241,7 @@ func TestCallback(t *testing.T) {
)
tracker.init(clock, 1000) // cap = 1000
defer tracker.stop(clock.Now())
tracker.setFactors(false, 1, 1)
tracker.setFactors(true, 1, 1)
tracker.setFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})

callCh := make(chan struct{}, 1)
tracker.setBalance(expval(uint64(time.Minute)), expval(0))
Expand Down
Loading