github.com/snowblossomcoin/go-ethereum@v1.9.25/les/utils/expiredvalue.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package utils
    18  
    19  import (
    20  	"math"
    21  	"sync"
    22  
    23  	"github.com/ethereum/go-ethereum/common/mclock"
    24  )
    25  
    26  // ExpiredValue is a scalar value that is continuously expired (decreased
    27  // exponentially) based on the provided logarithmic expiration offset value.
    28  //
    29  // The formula for value calculation is: base*2^(exp-logOffset). In order to
    30  // simplify the calculation of ExpiredValue, its value is expressed in the form
    31  // of an exponent with a base of 2.
    32  //
    33  // Also here is a trick to reduce a lot of calculations. In theory, when a value X
    34  // decays over time and then a new value Y is added, the final result should be
    35  // X*2^(exp-logOffset)+Y. However it's very hard to represent in memory.
    36  // So the trick is using the idea of inflation instead of exponential decay. At this
    37  // moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential
    38  // decay when we actually want to calculate the value.
    39  //
    40  // e.g.
    41  // t0: V = 100
    42  // t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient
    43  // t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40
    44  type ExpiredValue struct {
    45  	Base, Exp uint64 // rlp encoding works by default
    46  }
    47  
    48  // ExpirationFactor is calculated from logOffset. 1 <= Factor < 2 and Factor*2^Exp
    49  // describes the multiplier applicable for additions and the divider for readouts.
    50  // If logOffset changes slowly then it saves some expensive operations to not calculate
    51  // them for each addition and readout but cache this intermediate form for some time.
    52  // It is also useful for structures where multiple values are expired with the same
    53  // Expirer.
    54  type ExpirationFactor struct {
    55  	Exp    uint64
    56  	Factor float64
    57  }
    58  
    59  // ExpFactor calculates ExpirationFactor based on logOffset
    60  func ExpFactor(logOffset Fixed64) ExpirationFactor {
    61  	return ExpirationFactor{Exp: logOffset.ToUint64(), Factor: logOffset.Fraction().Pow2()}
    62  }
    63  
    64  // Value calculates the expired value based on a floating point base and integer
    65  // power-of-2 exponent. This function should be used by multi-value expired structures.
    66  func (e ExpirationFactor) Value(base float64, exp uint64) float64 {
    67  	return base / e.Factor * math.Pow(2, float64(int64(exp-e.Exp)))
    68  }
    69  
    70  // value calculates the value at the given moment.
    71  func (e ExpiredValue) Value(logOffset Fixed64) uint64 {
    72  	offset := Uint64ToFixed64(e.Exp) - logOffset
    73  	return uint64(float64(e.Base) * offset.Pow2())
    74  }
    75  
    76  // add adds a signed value at the given moment
    77  func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 {
    78  	integer, frac := logOffset.ToUint64(), logOffset.Fraction()
    79  	factor := frac.Pow2()
    80  	base := factor * float64(amount)
    81  	if integer < e.Exp {
    82  		base /= math.Pow(2, float64(e.Exp-integer))
    83  	}
    84  	if integer > e.Exp {
    85  		e.Base >>= (integer - e.Exp)
    86  		e.Exp = integer
    87  	}
    88  	if base >= 0 || uint64(-base) <= e.Base {
    89  		// This is a temporary fix to circumvent a golang
    90  		// uint conversion issue on arm64, which needs to
    91  		// be investigated further. FIXME
    92  		e.Base = uint64(int64(e.Base) + int64(base))
    93  		return amount
    94  	}
    95  	net := int64(-float64(e.Base) / factor)
    96  	e.Base = 0
    97  	return net
    98  }
    99  
   100  // addExp adds another ExpiredValue
   101  func (e *ExpiredValue) AddExp(a ExpiredValue) {
   102  	if e.Exp > a.Exp {
   103  		a.Base >>= (e.Exp - a.Exp)
   104  	}
   105  	if e.Exp < a.Exp {
   106  		e.Base >>= (a.Exp - e.Exp)
   107  		e.Exp = a.Exp
   108  	}
   109  	e.Base += a.Base
   110  }
   111  
   112  // subExp subtracts another ExpiredValue
   113  func (e *ExpiredValue) SubExp(a ExpiredValue) {
   114  	if e.Exp > a.Exp {
   115  		a.Base >>= (e.Exp - a.Exp)
   116  	}
   117  	if e.Exp < a.Exp {
   118  		e.Base >>= (a.Exp - e.Exp)
   119  		e.Exp = a.Exp
   120  	}
   121  	if e.Base > a.Base {
   122  		e.Base -= a.Base
   123  	} else {
   124  		e.Base = 0
   125  	}
   126  }
   127  
   128  // IsZero returns true if the value is zero
   129  func (e *ExpiredValue) IsZero() bool {
   130  	return e.Base == 0
   131  }
   132  
   133  // LinearExpiredValue is very similar with the expiredValue which the value
   134  // will continuously expired. But the different part is it's expired linearly.
   135  type LinearExpiredValue struct {
   136  	Offset uint64         // The latest time offset
   137  	Val    uint64         // The remaining value, can never be negative
   138  	Rate   mclock.AbsTime `rlp:"-"` // Expiration rate(by nanosecond), will ignored by RLP
   139  }
   140  
   141  // value calculates the value at the given moment. This function always has the
   142  // assumption that the given timestamp shouldn't less than the recorded one.
   143  func (e LinearExpiredValue) Value(now mclock.AbsTime) uint64 {
   144  	offset := uint64(now / e.Rate)
   145  	if e.Offset < offset {
   146  		diff := offset - e.Offset
   147  		if e.Val >= diff {
   148  			e.Val -= diff
   149  		} else {
   150  			e.Val = 0
   151  		}
   152  	}
   153  	return e.Val
   154  }
   155  
   156  // add adds a signed value at the given moment. This function always has the
   157  // assumption that the given timestamp shouldn't less than the recorded one.
   158  func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 {
   159  	offset := uint64(now / e.Rate)
   160  	if e.Offset < offset {
   161  		diff := offset - e.Offset
   162  		if e.Val >= diff {
   163  			e.Val -= diff
   164  		} else {
   165  			e.Val = 0
   166  		}
   167  		e.Offset = offset
   168  	}
   169  	if amount < 0 && uint64(-amount) > e.Val {
   170  		e.Val = 0
   171  	} else {
   172  		e.Val = uint64(int64(e.Val) + amount)
   173  	}
   174  	return e.Val
   175  }
   176  
   177  // ValueExpirer controls value expiration rate
   178  type ValueExpirer interface {
   179  	SetRate(now mclock.AbsTime, rate float64)
   180  	SetLogOffset(now mclock.AbsTime, logOffset Fixed64)
   181  	LogOffset(now mclock.AbsTime) Fixed64
   182  }
   183  
   184  // Expirer changes logOffset with a linear rate which can be changed during operation.
   185  // It is not thread safe, if access by multiple goroutines is needed then it should be
   186  // encapsulated into a locked structure.
   187  // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset
   188  // is thread safe.
   189  type Expirer struct {
   190  	lock       sync.RWMutex
   191  	logOffset  Fixed64
   192  	rate       float64
   193  	lastUpdate mclock.AbsTime
   194  }
   195  
   196  // SetRate changes the expiration rate which is the inverse of the time constant in
   197  // nanoseconds.
   198  func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) {
   199  	e.lock.Lock()
   200  	defer e.lock.Unlock()
   201  
   202  	dt := now - e.lastUpdate
   203  	if dt > 0 {
   204  		e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate)
   205  	}
   206  	e.lastUpdate = now
   207  	e.rate = rate
   208  }
   209  
   210  // SetLogOffset sets logOffset instantly.
   211  func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) {
   212  	e.lock.Lock()
   213  	defer e.lock.Unlock()
   214  
   215  	e.lastUpdate = now
   216  	e.logOffset = logOffset
   217  }
   218  
   219  // LogOffset returns the current logarithmic offset.
   220  func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 {
   221  	e.lock.RLock()
   222  	defer e.lock.RUnlock()
   223  
   224  	dt := now - e.lastUpdate
   225  	if dt <= 0 {
   226  		return e.logOffset
   227  	}
   228  	return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate)
   229  }
   230  
   231  // fixedFactor is the fixed point multiplier factor used by Fixed64.
   232  const fixedFactor = 0x1000000
   233  
   234  // Fixed64 implements 64-bit fixed point arithmetic functions.
   235  type Fixed64 int64
   236  
   237  // Uint64ToFixed64 converts uint64 integer to Fixed64 format.
   238  func Uint64ToFixed64(f uint64) Fixed64 {
   239  	return Fixed64(f * fixedFactor)
   240  }
   241  
   242  // float64ToFixed64 converts float64 to Fixed64 format.
   243  func Float64ToFixed64(f float64) Fixed64 {
   244  	return Fixed64(f * fixedFactor)
   245  }
   246  
   247  // toUint64 converts Fixed64 format to uint64.
   248  func (f64 Fixed64) ToUint64() uint64 {
   249  	return uint64(f64) / fixedFactor
   250  }
   251  
   252  // fraction returns the fractional part of a Fixed64 value.
   253  func (f64 Fixed64) Fraction() Fixed64 {
   254  	return f64 % fixedFactor
   255  }
   256  
   257  var (
   258  	logToFixedFactor = float64(fixedFactor) / math.Log(2)
   259  	fixedToLogFactor = math.Log(2) / float64(fixedFactor)
   260  )
   261  
   262  // pow2Fixed returns the base 2 power of the fixed point value.
   263  func (f64 Fixed64) Pow2() float64 {
   264  	return math.Exp(float64(f64) * fixedToLogFactor)
   265  }