chainfee: allow specifying min relay feerate from API source

This commit adds a new expected field, `min_relay_feerate`, in the
response body returned from the API source, allowing the API to specify
a min relay feerate to be used instead of the FeePerKwFloor.

This change is backwards compatible as for an old API source which
doesn't specify the `min_relay_feerate`, it will be interpreted as zero.
This commit is contained in:
yyforyongyu committed 2024-07-24 20:05:00 +08:00
1 parent b10ebb2692
commit ce43e4bab7
5 files changed
+124 -32

No files matched your search

+52 -21
View File
@@ -628,9 +628,10 @@ var _ Estimator = (*BitcoindEstimator)(nil)
// implementation of this interface in order to allow the WebAPIEstimator to
// be fully generic in its logic.
type WebAPIFeeSource interface {
// GetFeeMap will query the web API, parse the response and return a
// map of confirmation targets to sat/kw fees.
GetFeeMap() (map[uint32]uint32, error)
// GetFeeInfo will query the web API, parse the response into a
// WebAPIResponse which contains a map of confirmation targets to
// sat/kw fees and min relay feerate.
GetFeeInfo() (WebAPIResponse, error)
}
// SparseConfFeeSource is an implementation of the WebAPIFeeSource that utilizes
@@ -642,30 +643,43 @@ type SparseConfFeeSource struct {
URL string
}
// WebAPIResponse is the response returned by the fee estimation API.
type WebAPIResponse struct {
// FeeByBlockTarget is a map of confirmation targets to sat/kvb fees.
FeeByBlockTarget map[uint32]uint32 `json:"fee_by_block_target"`
// MinRelayFeerate is the minimum relay fee in sat/kvb.
MinRelayFeerate SatPerKVByte `json:"min_relay_feerate"`
}
// parseResponse attempts to parse the body of the response generated by the
// above query URL. Typically this will be JSON, but the specifics are left to
// the WebAPIFeeSource implementation.
func (s SparseConfFeeSource) parseResponse(r io.Reader) (
map[uint32]uint32, error) {
WebAPIResponse, error) {
type jsonResp struct {
FeeByBlockTarget map[uint32]uint32 `json:"fee_by_block_target"`
}
resp := jsonResp{
resp := WebAPIResponse{
FeeByBlockTarget: make(map[uint32]uint32),
MinRelayFeerate: 0,
}
jsonReader := json.NewDecoder(r)
if err := jsonReader.Decode(&resp); err != nil {
return nil, err
return WebAPIResponse{}, err
}
return resp.FeeByBlockTarget, nil
if resp.MinRelayFeerate == 0 {
log.Errorf("No min relay fee rate available, using default %v",
FeePerKwFloor)
resp.MinRelayFeerate = FeePerKwFloor.FeePerKVByte()
}
return resp, nil
}
// GetFeeMap will query the web API, parse the response and return a map of
// confirmation targets to sat/kw fees.
func (s SparseConfFeeSource) GetFeeMap() (map[uint32]uint32, error) {
// GetFeeInfo will query the web API, parse the response and return a map of
// confirmation targets to sat/kw fees and min relay feerate in a parsed
// response.
func (s SparseConfFeeSource) GetFeeInfo() (WebAPIResponse, error) {
// Rather than use the default http.Client, we'll make a custom one
// which will allow us to control how long we'll wait to read the
// response from the service. This way, if the service is down or
@@ -688,20 +702,20 @@ func (s SparseConfFeeSource) GetFeeMap() (map[uint32]uint32, error) {
if err != nil {
log.Errorf("unable to query web api for fee response: %v",
err)
return nil, err
return WebAPIResponse{}, err
}
defer resp.Body.Close()
// Once we've obtained the response, we'll instruct the WebAPIFeeSource
// to parse out the body to obtain our final result.
feesByBlockTarget, err := s.parseResponse(resp.Body)
parsedResp, err := s.parseResponse(resp.Body)
if err != nil {
log.Errorf("unable to parse fee api response: %v", err)
return nil, err
return WebAPIResponse{}, err
}
return feesByBlockTarget, nil
return parsedResp, nil
}
// A compile-time assertion to ensure that SparseConfFeeSource implements the
@@ -726,6 +740,7 @@ type WebAPIEstimator struct {
// rather than re-querying the API, to prevent an inadvertent DoS attack.
feesMtx sync.Mutex
feeByBlockTarget map[uint32]uint32
minRelayFeerate SatPerKVByte
// noCache determines whether the web estimator should cache fee
// estimates.
@@ -837,6 +852,7 @@ func (w *WebAPIEstimator) Start() error {
go w.feeUpdateManager()
})
return err
}
@@ -866,7 +882,15 @@ func (w *WebAPIEstimator) Stop() error {
//
// NOTE: This method is part of the Estimator interface.
func (w *WebAPIEstimator) RelayFeePerKW() SatPerKWeight {
return FeePerKwFloor
// Get fee estimates now if we don't refresh periodically.
if w.noCache {
w.updateFeeEstimates()
}
log.Infof("Web API returning %v for min relay feerate",
w.minRelayFeerate)
return w.minRelayFeerate.FeePerKWeight()
}
// randomFeeUpdateTimeout returns a random timeout between minFeeUpdateTimeout
@@ -956,14 +980,21 @@ func (w *WebAPIEstimator) getCachedFee(numBlocks uint32) (uint32, error) {
func (w *WebAPIEstimator) updateFeeEstimates() {
// Once we've obtained the response, we'll instruct the WebAPIFeeSource
// to parse out the body to obtain our final result.
feesByBlockTarget, err := w.apiSource.GetFeeMap()
resp, err := w.apiSource.GetFeeInfo()
if err != nil {
log.Errorf("unable to get fee response: %v", err)
return
}
log.Debugf("Received response from source: %s", newLogClosure(
func() string {
resp, _ := json.Marshal(resp)
return string(resp)
}))
w.feesMtx.Lock()
w.feeByBlockTarget = feesByBlockTarget
w.feeByBlockTarget = resp.FeeByBlockTarget
w.minRelayFeerate = resp.MinRelayFeerate
w.feesMtx.Unlock()
}