lnwallet: add configurable cache for web fee estimator
Add fee.min-update-timeout and fee.max-update-timeout config options to allow configuration of the web fee estimator cache.
This commit is contained in:
1 parent
fa616ee059
commit
3837c3f12e
15 files changed
+202
-55
No files matched your search
@@ -28,14 +28,6 @@ const (
|
||||
// less than this will result in an error.
|
||||
minBlockTarget uint32 = 1
|
||||
|
||||
// minFeeUpdateTimeout represents the minimum interval in which a
|
||||
// WebAPIEstimator will request fresh fees from its API.
|
||||
minFeeUpdateTimeout = 5 * time.Minute
|
||||
|
||||
// maxFeeUpdateTimeout represents the maximum interval in which a
|
||||
// WebAPIEstimator will request fresh fees from its API.
|
||||
maxFeeUpdateTimeout = 20 * time.Minute
|
||||
|
||||
// WebAPIConnectionTimeout specifies the timeout value for connecting
|
||||
// to the api source.
|
||||
WebAPIConnectionTimeout = 5 * time.Second
|
||||
@@ -739,19 +731,43 @@ type WebAPIEstimator struct {
|
||||
// estimates.
|
||||
noCache bool
|
||||
|
||||
// minFeeUpdateTimeout represents the minimum interval in which the
|
||||
// web estimator will request fresh fees from its API.
|
||||
minFeeUpdateTimeout time.Duration
|
||||
|
||||
// minFeeUpdateTimeout represents the maximum interval in which the
|
||||
// web estimator will request fresh fees from its API.
|
||||
maxFeeUpdateTimeout time.Duration
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewWebAPIEstimator creates a new WebAPIEstimator from a given URL and a
|
||||
// fallback default fee. The fees are updated whenever a new block is mined.
|
||||
func NewWebAPIEstimator(api WebAPIFeeSource, noCache bool) *WebAPIEstimator {
|
||||
return &WebAPIEstimator{
|
||||
apiSource: api,
|
||||
feeByBlockTarget: make(map[uint32]uint32),
|
||||
noCache: noCache,
|
||||
quit: make(chan struct{}),
|
||||
func NewWebAPIEstimator(api WebAPIFeeSource, noCache bool,
|
||||
minFeeUpdateTimeout time.Duration,
|
||||
maxFeeUpdateTimeout time.Duration) (*WebAPIEstimator, error) {
|
||||
|
||||
if minFeeUpdateTimeout == 0 || maxFeeUpdateTimeout == 0 {
|
||||
return nil, fmt.Errorf("minFeeUpdateTimeout and " +
|
||||
"maxFeeUpdateTimeout must be greater than 0")
|
||||
}
|
||||
|
||||
if minFeeUpdateTimeout >= maxFeeUpdateTimeout {
|
||||
return nil, fmt.Errorf("minFeeUpdateTimeout target of %v "+
|
||||
"cannot be greater than maxFeeUpdateTimeout of %v",
|
||||
minFeeUpdateTimeout, maxFeeUpdateTimeout)
|
||||
}
|
||||
|
||||
return &WebAPIEstimator{
|
||||
apiSource: api,
|
||||
feeByBlockTarget: make(map[uint32]uint32),
|
||||
noCache: noCache,
|
||||
quit: make(chan struct{}),
|
||||
minFeeUpdateTimeout: minFeeUpdateTimeout,
|
||||
maxFeeUpdateTimeout: maxFeeUpdateTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EstimateFeePerKW takes in a target for the number of blocks until an initial
|
||||
@@ -809,7 +825,12 @@ func (w *WebAPIEstimator) Start() error {
|
||||
w.started.Do(func() {
|
||||
log.Infof("Starting web API fee estimator")
|
||||
|
||||
w.updateFeeTicker = time.NewTicker(w.randomFeeUpdateTimeout())
|
||||
feeUpdateTimeout := w.randomFeeUpdateTimeout()
|
||||
|
||||
log.Infof("Web API fee estimator using update timeout of %v",
|
||||
feeUpdateTimeout)
|
||||
|
||||
w.updateFeeTicker = time.NewTicker(feeUpdateTimeout)
|
||||
w.updateFeeEstimates()
|
||||
|
||||
w.wg.Add(1)
|
||||
@@ -852,9 +873,11 @@ func (w *WebAPIEstimator) RelayFeePerKW() SatPerKWeight {
|
||||
// and maxFeeUpdateTimeout that will be used to determine how often the Estimator
|
||||
// should retrieve fresh fees from its API.
|
||||
func (w *WebAPIEstimator) randomFeeUpdateTimeout() time.Duration {
|
||||
lower := int64(minFeeUpdateTimeout)
|
||||
upper := int64(maxFeeUpdateTimeout)
|
||||
return time.Duration(prand.Int63n(upper-lower) + lower)
|
||||
lower := int64(w.minFeeUpdateTimeout)
|
||||
upper := int64(w.maxFeeUpdateTimeout)
|
||||
return time.Duration(
|
||||
prand.Int63n(upper-lower) + lower, //nolint:gosec
|
||||
).Round(time.Second)
|
||||
}
|
||||
|
||||
// getCachedFee takes a conf target and returns the cached fee rate. When the
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -142,6 +143,9 @@ func TestWebAPIFeeEstimator(t *testing.T) {
|
||||
// Fee rates are in sat/kb.
|
||||
minFeeRate uint32 = 2000 // 500 sat/kw
|
||||
maxFeeRate uint32 = 4000 // 1000 sat/kw
|
||||
|
||||
minFeeUpdateTimeout = 5 * time.Minute
|
||||
maxFeeUpdateTimeout = 20 * time.Minute
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
@@ -199,7 +203,9 @@ func TestWebAPIFeeEstimator(t *testing.T) {
|
||||
feeSource := &mockFeeSource{}
|
||||
feeSource.On("GetFeeMap").Return(feeRateResp, nil)
|
||||
|
||||
estimator := NewWebAPIEstimator(feeSource, false)
|
||||
estimator, _ := NewWebAPIEstimator(
|
||||
feeSource, false, minFeeUpdateTimeout, maxFeeUpdateTimeout,
|
||||
)
|
||||
|
||||
// Test that requesting a fee when no fees have been cached won't fail.
|
||||
feeRate, err := estimator.EstimateFeePerKW(5)
|
||||
@@ -247,10 +253,15 @@ func TestGetCachedFee(t *testing.T) {
|
||||
|
||||
minFeeRate uint32 = 100
|
||||
maxFeeRate uint32 = 1000
|
||||
|
||||
minFeeUpdateTimeout = 5 * time.Minute
|
||||
maxFeeUpdateTimeout = 20 * time.Minute
|
||||
)
|
||||
|
||||
// Create a dummy estimator without WebAPIFeeSource.
|
||||
estimator := NewWebAPIEstimator(nil, false)
|
||||
estimator, _ := NewWebAPIEstimator(
|
||||
nil, false, minFeeUpdateTimeout, maxFeeUpdateTimeout,
|
||||
)
|
||||
|
||||
// When the cache is empty, an error should be returned.
|
||||
cachedFee, err := estimator.getCachedFee(minTarget)
|
||||
@@ -315,3 +326,38 @@ func TestGetCachedFee(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomFeeUpdateTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
minFeeUpdateTimeout = 1 * time.Minute
|
||||
maxFeeUpdateTimeout = 2 * time.Minute
|
||||
)
|
||||
|
||||
estimator, _ := NewWebAPIEstimator(
|
||||
nil, false, minFeeUpdateTimeout, maxFeeUpdateTimeout,
|
||||
)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
timeout := estimator.randomFeeUpdateTimeout()
|
||||
|
||||
require.GreaterOrEqual(t, timeout, minFeeUpdateTimeout)
|
||||
require.LessOrEqual(t, timeout, maxFeeUpdateTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidFeeUpdateTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
minFeeUpdateTimeout = 2 * time.Minute
|
||||
maxFeeUpdateTimeout = 1 * time.Minute
|
||||
)
|
||||
|
||||
_, err := NewWebAPIEstimator(
|
||||
nil, false, minFeeUpdateTimeout, maxFeeUpdateTimeout,
|
||||
)
|
||||
require.Error(t, err, "NewWebAPIEstimator should return an error "+
|
||||
"when minFeeUpdateTimeout > maxFeeUpdateTimeout")
|
||||
}
|
||||
Reference in new issue
Block a user