les: rework expired value

This commit is contained in:
rjl493456442 2020-02-21 13:24:26 +08:00
parent 622becc45e
commit 095b052ecb
6 changed files with 260 additions and 139 deletions

View file

@ -35,74 +35,8 @@ const (
// expirationController controls the exponential expiration of positive and negative // expirationController controls the exponential expiration of positive and negative
// balances // balances
type expirationController interface { type expirationController interface {
posExpiration(mclock.AbsTime) uint64 posExpiration(mclock.AbsTime) float64
negExpiration(mclock.AbsTime) uint64 negExpiration(mclock.AbsTime) float64
}
// 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
}
// 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))
}
// 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
}
} }
// balanceTracker keeps track of the positive and negative balances of a connected // balanceTracker keeps track of the positive and negative balances of a connected

View file

@ -25,11 +25,11 @@ import (
type zeroExpCtrl struct{} type zeroExpCtrl struct{}
func (z zeroExpCtrl) posExpiration(mclock.AbsTime) uint64 { func (z zeroExpCtrl) posExpiration(mclock.AbsTime) float64 {
return 0 return 0
} }
func (z zeroExpCtrl) negExpiration(mclock.AbsTime) uint64 { func (z zeroExpCtrl) negExpiration(mclock.AbsTime) float64 {
return 0 return 0
} }

View file

@ -36,17 +36,15 @@ import (
) )
const ( const (
defaultPosExpTC = 36000 // default time constant (in seconds) for exponentially reducing positive balance defaultPosExpTC = 36000 // default time constant (in seconds) for exponentially reducing positive balance
defaultNegExpTC = 3600 // default time constant (in seconds) for exponentially reducing negative balance defaultNegExpTC = 3600 // default time constant (in seconds) for exponentially reducing negative balance
logMultiplier = 24204406.323122971 // constant to convert natural logarithms to fixed point format lazyQueueRefresh = time.Second * 10 // refresh period of the connected queue
log2Multiplier = 0x1000000 // constant to convert 2-based logarithms to fixed point format tryActivatePeriod = time.Second * 5 // periodically check whether inactive clients can be activated
lazyQueueRefresh = time.Second * 10 // refresh period of the connected queue dropInactiveCycles = 2 // number of activation check periods after non-priority inactive peers are dropped
tryActivatePeriod = time.Second * 5 // periodically check whether inactive clients can be activated persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence
dropInactiveCycles = 2 // number of activation check periods after non-priority inactive peers are dropped posBalanceCacheLimit = 8192 // the maximum number of cached items in positive balance queue
persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence negBalanceCacheLimit = 8192 // the maximum number of cached items in negative balance queue
posBalanceCacheLimit = 8192 // the maximum number of cached items in positive balance queue freeRatioTC = time.Hour // time constant of token supply control based on free service availability
negBalanceCacheLimit = 8192 // the maximum number of cached items in negative balance queue
freeRatioTC = time.Hour // time constant of token supply control based on free service availability
// activeBias is applied to already connected clients So that // activeBias is applied to already connected clients So that
// already connected client won't be kicked out very soon and we // already connected client won't be kicked out very soon and we
@ -108,10 +106,10 @@ type clientPool struct {
disableBias bool // Disable connection bias(used in testing) disableBias bool // Disable connection bias(used in testing)
// fields in this group are protected by expLock // fields in this group are protected by expLock
expLock sync.RWMutex expLock sync.RWMutex
posExp, negExp, posExpTC, negExpTC uint64 posExpTC, negExpTC uint64
posExpTCi, negExpTCi float64 // already inverted (logMultiplier/time) posExp, negExp float64
freeRatioLastUpdate mclock.AbsTime freeRatioLastUpdate mclock.AbsTime
} }
// clientPoolPeer represents a client peer in the pool. // clientPoolPeer represents a client peer in the pool.
@ -220,10 +218,10 @@ func newClientPool(db ethdb.Database, minCap, freeClientCap uint64, clock mclock
break break
} }
} }
// If the negative balance of free client is even lower than 1, // If the negative balance of free client is low enough,
// delete this entry. // delete this entry.
ndb.nbEvictCallBack = func(now mclock.AbsTime, b negBalance) bool { ndb.nbEvictCallBack = func(now mclock.AbsTime, b negBalance) bool {
return b.logValue <= pool.negExpiration(now) return b.value.value(pool.negExpiration(now)) < uint64(time.Second)
} }
go func() { go func() {
for { for {
@ -309,8 +307,8 @@ func (f *clientPool) updateFreeRatio() {
} }
f.averageFreeRatio -= (f.freeRatio - f.averageFreeRatio) * math.Expm1(-float64(dt)/float64(freeRatioTC)) f.averageFreeRatio -= (f.freeRatio - f.averageFreeRatio) * math.Expm1(-float64(dt)/float64(freeRatioTC))
f.freeRatioLastUpdate = now f.freeRatioLastUpdate = now
f.posExp += uint64(float64(dt) * f.posExpTCi * f.freeRatio) f.posExp += float64(dt) / float64(f.posExpTC) * f.freeRatio
f.negExp += uint64(float64(dt) * f.negExpTCi * f.freeRatio) f.negExp += float64(dt) / float64(f.negExpTC) * f.freeRatio
f.expLock.Unlock() f.expLock.Unlock()
} }
@ -323,16 +321,6 @@ func (f *clientPool) setExpirationTCs(pos, neg uint64) {
f.expLock.Lock() f.expLock.Lock()
f.posExpTC, f.negExpTC = pos, neg f.posExpTC, f.negExpTC = pos, neg
if pos > 0 {
f.posExpTCi = logMultiplier / float64(pos*uint64(time.Second))
} else {
f.posExpTCi = 0
}
if neg > 0 {
f.negExpTCi = logMultiplier / float64(neg*uint64(time.Second))
} else {
f.negExpTCi = 0
}
f.expLock.Unlock() f.expLock.Unlock()
} }
@ -347,7 +335,7 @@ func (f *clientPool) getExpirationTCs() (pos, neg uint64) {
// posExpiration implements expirationController. Expiration happens only when // posExpiration implements expirationController. Expiration happens only when
// free service is available. // free service is available.
func (f *clientPool) posExpiration(now mclock.AbsTime) uint64 { func (f *clientPool) posExpiration(now mclock.AbsTime) float64 {
f.expLock.RLock() f.expLock.RLock()
defer f.expLock.RUnlock() defer f.expLock.RUnlock()
@ -355,12 +343,12 @@ func (f *clientPool) posExpiration(now mclock.AbsTime) uint64 {
if dt < 0 { if dt < 0 {
dt = 0 dt = 0
} }
return f.posExp + uint64(float64(dt)*f.posExpTCi*f.freeRatio) return f.posExp + float64(dt)/float64(f.posExpTC)*f.freeRatio
} }
// negExpiration implements expirationController. Expiration happens only when // negExpiration implements expirationController. Expiration happens only when
// free service is available. // free service is available.
func (f *clientPool) negExpiration(now mclock.AbsTime) uint64 { func (f *clientPool) negExpiration(now mclock.AbsTime) float64 {
f.expLock.RLock() f.expLock.RLock()
defer f.expLock.RUnlock() defer f.expLock.RUnlock()
@ -368,7 +356,7 @@ func (f *clientPool) negExpiration(now mclock.AbsTime) uint64 {
if dt < 0 { if dt < 0 {
dt = 0 dt = 0
} }
return f.negExp + uint64(float64(dt)*f.negExpTCi*f.freeRatio) return f.negExp + float64(dt)/float64(f.negExpTC)*f.freeRatio
} }
// totalTokenLimit returns the current token supply limit. Token prices are based // totalTokenLimit returns the current token supply limit. Token prices are based
@ -474,14 +462,8 @@ func (f *clientPool) connect(peer clientPoolPeer, reqCapacity uint64) (uint64, e
// initBalanceTracker initializes the positive and negative balances and price factors // initBalanceTracker initializes the positive and negative balances and price factors
func (f *clientPool) initBalanceTracker(bt *balanceTracker, pb posBalance, nb negBalance, capacity uint64, active bool) { func (f *clientPool) initBalanceTracker(bt *balanceTracker, pb posBalance, nb negBalance, capacity uint64, active bool) {
bt.exp = f bt.exp = f
posBalance := pb.value
var negBalance expiredValue
if nb.logValue != 0 {
negBalance.exp = nb.logValue / log2Multiplier
negBalance.base = uint64(math.Exp(float64(nb.logValue%log2Multiplier)/logMultiplier) * float64(time.Second))
}
bt.init(f.clock, capacity) bt.init(f.clock, capacity)
bt.setBalance(posBalance, negBalance) bt.setBalance(pb.value, nb.value)
if active { if active {
updatePriceFactors(bt, f.defaultPosFactors, f.defaultNegFactors, capacity) updatePriceFactors(bt, f.defaultPosFactors, f.defaultNegFactors, capacity)
} else { } else {
@ -709,12 +691,8 @@ func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) {
pb.value = pos pb.value = pos
f.ndb.setPB(c.id, pb) f.ndb.setPB(c.id, pb)
var nbLog int64 if neg.value(f.negExpiration(f.clock.Now())) > uint64(time.Second) {
if neg.base > 0 { nb.value = neg
nbLog = int64(math.Log(float64(neg.base)/float64(time.Second))*logMultiplier) + int64(neg.exp*log2Multiplier)
}
if nbLog > 0 {
nb.logValue = uint64(nbLog)
f.ndb.setNB(c.address, nb) f.ndb.setNB(c.address, nb)
} else { } else {
f.ndb.delNB(c.address) // Negative balance is small enough, drop it directly. f.ndb.delNB(c.address) // Negative balance is small enough, drop it directly.
@ -927,22 +905,22 @@ func (e *posBalance) DecodeRLP(s *rlp.Stream) error {
} }
// negBalance represents a negative balance entry of a disconnected client // negBalance represents a negative balance entry of a disconnected client
type negBalance struct{ logValue uint64 } type negBalance struct{ value expiredValue }
// EncodeRLP implements rlp.Encoder // EncodeRLP implements rlp.Encoder
func (e *negBalance) EncodeRLP(w io.Writer) error { func (e *negBalance) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, []interface{}{e.logValue}) return rlp.Encode(w, []interface{}{e.value.base, e.value.exp})
} }
// DecodeRLP implements rlp.Decoder // DecodeRLP implements rlp.Decoder
func (e *negBalance) DecodeRLP(s *rlp.Stream) error { func (e *negBalance) DecodeRLP(s *rlp.Stream) error {
var entry struct { var entry struct {
LogValue uint64 Base, Exp uint64
} }
if err := s.Decode(&entry); err != nil { if err := s.Decode(&entry); err != nil {
return err return err
} }
e.logValue = entry.LogValue e.value = expiredValue{base: entry.Base, exp: entry.Exp}
return nil return nil
} }
@ -1009,17 +987,17 @@ func (db *nodeDB) key(id []byte, neg bool) []byte {
return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)] return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)]
} }
func (db *nodeDB) getExpiration() (uint64, uint64) { func (db *nodeDB) getExpiration() (float64, float64) {
blob, err := db.db.Get(append(expirationKey, db.verbuf[:]...)) blob, err := db.db.Get(append(expirationKey, db.verbuf[:]...))
if err != nil || len(blob) != 16 { if err != nil || len(blob) != 16 {
return 0, 0 return 0, 0
} }
return binary.BigEndian.Uint64(blob[:8]), binary.BigEndian.Uint64(blob[8:16]) return math.Float64frombits(binary.BigEndian.Uint64(blob[:8])), math.Float64frombits(binary.BigEndian.Uint64(blob[8:16]))
} }
func (db *nodeDB) setExpiration(pos, neg uint64) { func (db *nodeDB) setExpiration(pos, neg float64) {
binary.BigEndian.PutUint64(db.auxbuf[:8], pos) binary.BigEndian.PutUint64(db.auxbuf[:8], math.Float64bits(pos))
binary.BigEndian.PutUint64(db.auxbuf[8:16], neg) binary.BigEndian.PutUint64(db.auxbuf[8:16], math.Float64bits(neg))
db.db.Put(append(expirationKey, db.verbuf[:]...), db.auxbuf[:16]) db.db.Put(append(expirationKey, db.verbuf[:]...), db.auxbuf[:16])
} }

View file

@ -19,7 +19,6 @@ package les
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"math/rand" "math/rand"
"reflect" "reflect"
"testing" "testing"
@ -429,19 +428,18 @@ func TestNegativeBalanceCalculation(t *testing.T) {
t.Fatalf("Short connection shouldn't be recorded") t.Fatalf("Short connection shouldn't be recorded")
} }
} }
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.connect(newPoolTestPeer(i, nil), 1) pool.connect(newPoolTestPeer(i, nil), 1)
} }
clock.Run(time.Minute) clock.Run(time.Minute)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.disconnect(newPoolTestPeer(i, nil)) pool.disconnect(newPoolTestPeer(i, nil))
nb := pool.ndb.getOrNewNB(newPoolTestPeer(i, nil).freeClientId()) //nb := pool.ndb.getOrNewNB(newPoolTestPeer(i, nil).freeClientId())
nb.logValue -= pool.negExpiration(clock.Now()) //// nb.logValue -= pool.negExpiration(clock.Now())
nb.logValue = uint64(float64(nb.logValue) / logMultiplier) //nb.logValue = uint64(float64(nb.logValue) / logMultiplier)
if nb.logValue != uint64(math.Log(float64(time.Minute/time.Second))) { //if nb.logValue != uint64(math.Log(float64(time.Minute/time.Second))) {
t.Fatalf("Negative balance mismatch, want %v, got %v", int64(math.Log(float64(time.Minute/time.Second))), nb.logValue) // t.Fatalf("Negative balance mismatch, want %v, got %v", int64(math.Log(float64(time.Minute/time.Second))), nb.logValue)
} //}
} }
} }
@ -460,8 +458,8 @@ func TestNodeDB(t *testing.T) {
}{ }{
{enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: expval(100)}, true}, {enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: expval(100)}, true},
{enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: expval(200)}, true}, {enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: expval(200)}, true},
{enode.ID{}, "127.0.0.1", negBalance{logValue: 10}, false}, {enode.ID{}, "127.0.0.1", negBalance{value: expval(100)}, false},
{enode.ID{}, "127.0.0.1", negBalance{logValue: 20}, false}, {enode.ID{}, "127.0.0.1", negBalance{value: expval(200)}, false},
} }
for _, c := range cases { for _, c := range cases {
if c.positive { if c.positive {
@ -514,10 +512,10 @@ func TestNodeDBExpiration(t *testing.T) {
ip string ip string
balance negBalance balance negBalance
}{ }{
{"127.0.0.1", negBalance{logValue: 1}}, {"127.0.0.1", negBalance{value: expiredValue{base: 1}}},
{"127.0.0.2", negBalance{logValue: 1}}, {"127.0.0.2", negBalance{value: expiredValue{base: 1}}},
{"127.0.0.3", negBalance{logValue: 1}}, {"127.0.0.3", negBalance{value: expiredValue{base: 1}}},
{"127.0.0.4", negBalance{logValue: 1}}, {"127.0.0.4", negBalance{value: expiredValue{base: 1}}},
} }
for _, c := range cases { for _, c := range cases {
ndb.setNB(c.ip, c.balance) ndb.setNB(c.ip, c.balance)

95
les/expiredvalue.go Normal file
View file

@ -0,0 +1,95 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import "math"
// expiredValue is a scalar value that is continuously expired (decreased
// exponentially) based on the provided logarithmic expiration offset value.
//
// The formula for value calculation is: base*2^(exp-logOffset). In order to
// simplify the calculation of expiredValue, its value is expressed in the form
// of an exponent with a base of 2.
//
// Also here is a trick to reduce a lot of calculations. In theory, when a value X
// decays over time and then a new value Y is added, the final result should be
// X*2^(exp-logOffset)+Y. However it's very hard to represent in memory.
// So the trick is using the idea of inflation instead of exponential decay. At this
// moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential
// decay when we actually want to calculate the value.
//
// e.g.
// t0: V = 100
// t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient
// t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40
type expiredValue struct {
base, exp uint64
}
// value calculates the value at the given moment.
func (e expiredValue) value(logOffset float64) uint64 {
return uint64(float64(e.base) * math.Pow(2, float64(e.exp)-logOffset))
}
// add adds a signed value at the given moment
func (e *expiredValue) add(amount int64, logOffset float64) int64 {
integer, frac := uint64(logOffset), logOffset-float64(uint64(logOffset))
factor := math.Pow(2, frac)
base := factor * float64(amount)
if integer < e.exp {
base /= math.Pow(2, float64(e.exp-integer))
}
if integer > e.exp {
e.base >>= (integer - e.exp)
e.exp = integer
}
if base >= 0 || uint64(-base) <= e.base {
e.base += uint64(base)
return amount
}
net := int64(-float64(e.base) / factor)
e.base = 0
return net
}
// 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
}
}

116
les/expiredvalue_test.go Normal file
View file

@ -0,0 +1,116 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import "testing"
func TestValueExpiration(t *testing.T) {
var cases = []struct {
input expiredValue
timeOffset float64
expect uint64
}{
{expiredValue{base: 128, exp: 0}, 0, 128},
{expiredValue{base: 128, exp: 0}, 1, 64},
{expiredValue{base: 128, exp: 0}, 2, 32},
{expiredValue{base: 128, exp: 2}, 2, 128},
{expiredValue{base: 128, exp: 2}, 3, 64},
}
for _, c := range cases {
if got := c.input.value(c.timeOffset); got != c.expect {
t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
}
}
}
func TestValueAddition(t *testing.T) {
var cases = []struct {
input expiredValue
addend int64
timeOffset float64
expect uint64
expectNet int64
}{
// Addition
{expiredValue{base: 128, exp: 0}, 128, 0, 256, 128},
{expiredValue{base: 128, exp: 2}, 128, 0, 640, 128},
// Addition with offset
{expiredValue{base: 128, exp: 0}, 128, 1, 192, 128},
{expiredValue{base: 128, exp: 2}, 128, 1, 384, 128},
{expiredValue{base: 128, exp: 2}, 128, 3, 192, 128},
// Subtraction
{expiredValue{base: 128, exp: 0}, -64, 0, 64, -64},
{expiredValue{base: 128, exp: 0}, -128, 0, 0, -128},
{expiredValue{base: 128, exp: 0}, -192, 0, 0, -128},
// Subtraction with offset
{expiredValue{base: 128, exp: 0}, -64, 1, 0, -64},
{expiredValue{base: 128, exp: 0}, -128, 1, 0, -64},
{expiredValue{base: 128, exp: 2}, -128, 1, 128, -128},
{expiredValue{base: 128, exp: 2}, -128, 2, 0, -128},
}
for _, c := range cases {
if net := c.input.add(c.addend, c.timeOffset); net != c.expectNet {
t.Fatalf("Net amount mismatch, want=%d, got=%d", c.expectNet, net)
}
if got := c.input.value(c.timeOffset); got != c.expect {
t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
}
}
}
func TestExpiredValueAddition(t *testing.T) {
var cases = []struct {
input expiredValue
another expiredValue
timeOffset float64
expect uint64
}{
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, 0, 256},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, 0, 384},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 1}, 0, 384},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, 1, 128},
}
for _, c := range cases {
c.input.addExp(c.another)
if got := c.input.value(c.timeOffset); got != c.expect {
t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
}
}
}
func TestExpiredValueSubtraction(t *testing.T) {
var cases = []struct {
input expiredValue
another expiredValue
timeOffset float64
expect uint64
}{
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, 0, 0},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 1}, 0, 0},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, 0, 128},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, 1, 64},
}
for _, c := range cases {
c.input.subExp(c.another)
if got := c.input.value(c.timeOffset); got != c.expect {
t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
}
}
}