mirror of
https://github.com/trezor/blockbook.git
synced 2026-02-20 00:51:39 +01:00
* feat: support new alternative_estimate_fee option - mempoolspacemedian * chore: introduce a fallback median fee rate in case it comes zero from mempool space response * chore: add and test util function for rounding to significant digits * chore: use rounding of medianFee to 3 significant digits * chore: make the new alternative_estimate_fee be configurable, change its name from Median to Block * chore: return 1000 sats/kB fee for MempoolSpaceBlock estimation method when block not identified * chore: make the fallback fee rate configurable, improve tests, improve function names
109 lines
2.9 KiB
Go
109 lines
2.9 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"math"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
"github.com/golang/glog"
|
|
"github.com/juju/errors"
|
|
)
|
|
|
|
// TickAndDebounce calls function f on trigger channel or with tickTime period (whatever is sooner) with debounce
|
|
func TickAndDebounce(tickTime time.Duration, debounceTime time.Duration, trigger chan struct{}, f func()) {
|
|
timer := time.NewTimer(tickTime)
|
|
var firstDebounce time.Time
|
|
Loop:
|
|
for {
|
|
select {
|
|
case _, ok := <-trigger:
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
// exit loop on closed input channel
|
|
if !ok {
|
|
break Loop
|
|
}
|
|
if firstDebounce.IsZero() {
|
|
firstDebounce = time.Now()
|
|
}
|
|
// debounce for up to debounceTime period
|
|
// afterwards execute immediately
|
|
if firstDebounce.Add(debounceTime).After(time.Now()) {
|
|
timer.Reset(debounceTime)
|
|
} else {
|
|
timer.Reset(0)
|
|
}
|
|
case <-timer.C:
|
|
// do the action, if not in shutdown, then start the loop again
|
|
if !IsInShutdown() {
|
|
f()
|
|
}
|
|
timer.Reset(tickTime)
|
|
firstDebounce = time.Time{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SafeDecodeResponseFromReader reads from io.ReadCloser safely, with recovery from panic
|
|
func SafeDecodeResponseFromReader(body io.ReadCloser, res interface{}) (err error) {
|
|
var data []byte
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
|
|
debug.PrintStack()
|
|
if len(data) > 0 && len(data) < 2048 {
|
|
err = errors.Errorf("Error: %v", string(data))
|
|
} else {
|
|
err = errors.New("Internal error")
|
|
}
|
|
}
|
|
}()
|
|
data, err = io.ReadAll(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, &res)
|
|
}
|
|
|
|
// RoundToSignificantDigits rounds a float64 number `n` to the specified number of significant figures `digits`.
|
|
// For example, RoundToSignificantDigits(1234, 3) returns 1230
|
|
//
|
|
// This function works by shifting the number's decimal point to make the desired significant figures
|
|
// into whole numbers, rounding, and then shifting back.
|
|
//
|
|
// Example for n = 1234, digits = 3:
|
|
//
|
|
// log10(1234) ≈ 3.09 → ceil = 4
|
|
// power = 3 - 4 = -1
|
|
// magnitude = 10^-1 = 0.1
|
|
// n * magnitude = 1234 * 0.1 = 123.4
|
|
// round(123.4) = 123
|
|
// 123 / 0.1 = 1230
|
|
//
|
|
// Returns the number rounded to the desired number of significant figures.
|
|
func RoundToSignificantDigits(n float64, digits int) float64 {
|
|
if n == 0 {
|
|
return 0
|
|
}
|
|
|
|
// Step 1: Compute how many digits are before the decimal point.
|
|
// For 1234 → log10(1234) ≈ 3.09 → ceil = 4
|
|
d := math.Ceil(math.Log10(math.Abs(n)))
|
|
|
|
// Step 2: Calculate how much we need to shift the number to bring
|
|
// the significant digits into the integer part.
|
|
// For digits=3 and d=4 → power = -1
|
|
power := digits - int(d)
|
|
|
|
// Step 3: Compute 10^power to scale the number
|
|
// 10^-1 = 0.1
|
|
magnitude := math.Pow(10, float64(power))
|
|
|
|
// Step 4: Scale, round, and scale back
|
|
// 1234 * 0.1 = 123.4 → round = 123 → 123 / 0.1 = 1230
|
|
return math.Round(n*magnitude) / magnitude
|
|
}
|