mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
les: rework expired value
This commit is contained in:
parent
622becc45e
commit
095b052ecb
6 changed files with 260 additions and 139 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
95
les/expiredvalue.go
Normal 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
116
les/expiredvalue_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue