lnwallet: add implementation of WebApiFeeSource for external APIs
This enables users to specify an external API for fee estimation. The API is expected to return fees in the JSON format: `{ fee_by_block_target: { a: x, b: y, ... c: z } }` where a, b, c are block targets and x, y, z are fees in sat/kb. Note that a, b, c need not be contiguous.
This commit is contained in:
parent
9b8549011c
commit
4944eb3e54
@ -2,6 +2,7 @@ package lnwallet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/blockchain"
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
"github.com/btcsuite/btcd/rpcclient"
|
"github.com/btcsuite/btcd/rpcclient"
|
||||||
@ -474,3 +475,46 @@ type WebAPIFeeSource interface {
|
|||||||
// specifics are left to the WebAPIFeeSource implementation.
|
// specifics are left to the WebAPIFeeSource implementation.
|
||||||
ParseResponse(r io.Reader) (map[uint32]uint32, error)
|
ParseResponse(r io.Reader) (map[uint32]uint32, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SparseConfFeeSource is an implementation of the WebAPIFeeSource that utilizes
|
||||||
|
// a user-specified fee estimation API for Bitcoin. It expects the response
|
||||||
|
// to be in the JSON format: `fee_by_block_target: { ... }` where the value maps
|
||||||
|
// block targets to fee estimates (in sat per kilovbyte).
|
||||||
|
type SparseConfFeeSource struct {
|
||||||
|
// URL is the fee estimation API specified by the user.
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenQueryURL generates the full query URL. The value returned by this
|
||||||
|
// method should be able to be used directly as a path for an HTTP GET
|
||||||
|
// request.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the WebAPIFeeSource interface.
|
||||||
|
func (s SparseConfFeeSource) GenQueryURL() string {
|
||||||
|
return s.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the WebAPIFeeSource interface.
|
||||||
|
func (s SparseConfFeeSource) ParseResponse(r io.Reader) (map[uint32]uint32, error) {
|
||||||
|
type jsonResp struct {
|
||||||
|
FeeByBlockTarget map[uint32]uint32 `json:"fee_by_block_target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := jsonResp{
|
||||||
|
FeeByBlockTarget: make(map[uint32]uint32),
|
||||||
|
}
|
||||||
|
jsonReader := json.NewDecoder(r)
|
||||||
|
if err := jsonReader.Decode(&resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.FeeByBlockTarget, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compile-time assertion to ensure that SparseConfFeeSource implements the
|
||||||
|
// WebAPIFeeSource interface.
|
||||||
|
var _ WebAPIFeeSource = (*SparseConfFeeSource)(nil)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package lnwallet_test
|
package lnwallet_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
@ -67,8 +70,8 @@ func TestFeeRateTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStaticFeeEstimator checks that the StaticFeeEstimator
|
// TestStaticFeeEstimator checks that the StaticFeeEstimator returns the
|
||||||
// returns the expected fee rate.
|
// expected fee rate.
|
||||||
func TestStaticFeeEstimator(t *testing.T) {
|
func TestStaticFeeEstimator(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@ -89,3 +92,55 @@ func TestStaticFeeEstimator(t *testing.T) {
|
|||||||
t.Fatalf("expected fee rate %v, got %v", feePerKw, feeRate)
|
t.Fatalf("expected fee rate %v, got %v", feePerKw, feeRate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSparseConfFeeSource checks that SparseConfFeeSource generates URLs and
|
||||||
|
// parses API responses as expected.
|
||||||
|
func TestSparseConfFeeSource(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Test that GenQueryURL returns the URL as is.
|
||||||
|
url := "test"
|
||||||
|
feeSource := lnwallet.SparseConfFeeSource{URL: url}
|
||||||
|
queryURL := feeSource.GenQueryURL()
|
||||||
|
if queryURL != url {
|
||||||
|
t.Fatalf("expected query URL of %v, got %v", url, queryURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test parsing a properly formatted JSON API response.
|
||||||
|
// First, create the response as a bytes.Reader.
|
||||||
|
testFees := map[uint32]uint32{
|
||||||
|
1: 12345,
|
||||||
|
2: 42,
|
||||||
|
3: 54321,
|
||||||
|
}
|
||||||
|
testJSON := map[string]map[uint32]uint32{"fee_by_block_target": testFees}
|
||||||
|
jsonResp, err := json.Marshal(testJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to marshal JSON API response: %v", err)
|
||||||
|
}
|
||||||
|
reader := bytes.NewReader(jsonResp)
|
||||||
|
|
||||||
|
// Finally, ensure the expected map is returned without error.
|
||||||
|
fees, err := feeSource.ParseResponse(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to parse API response: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(fees, testFees) {
|
||||||
|
t.Fatalf("expected %v, got %v", testFees, fees)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test parsing an improperly formatted JSON API response.
|
||||||
|
badFees := map[string]uint32{"hi": 12345, "hello": 42, "satoshi": 54321}
|
||||||
|
badJSON := map[string]map[string]uint32{"fee_by_block_target": badFees}
|
||||||
|
jsonResp, err = json.Marshal(badJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to marshal JSON API response: %v", err)
|
||||||
|
}
|
||||||
|
reader = bytes.NewReader(jsonResp)
|
||||||
|
|
||||||
|
// Finally, ensure the improperly formatted fees error.
|
||||||
|
_, err = feeSource.ParseResponse(reader)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected ParseResponse to fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user