lnd/lnwallet/fee_estimator.go

318 lines
10 KiB
Go

package lnwallet
import (
"encoding/json"
"github.com/roasbeef/btcd/blockchain"
"github.com/roasbeef/btcd/rpcclient"
"github.com/roasbeef/btcutil"
)
// SatPerVByte represents a fee rate in satoshis per vbyte.
type SatPerVByte btcutil.Amount
// FeeForVSize calculates the fee resulting from this fee rate and
// the given vsize in vbytes.
func (s SatPerVByte) FeeForVSize(vbytes int64) btcutil.Amount {
return btcutil.Amount(s) * btcutil.Amount(vbytes)
}
// FeePerKWeight converts the fee rate into SatPerKWeight.
func (s SatPerVByte) FeePerKWeight() SatPerKWeight {
return SatPerKWeight(s * 1000 / blockchain.WitnessScaleFactor)
}
// SatPerKWeight represents a fee rate in satoshis per kilo weight unit.
type SatPerKWeight btcutil.Amount
// FeeForWeight calculates the fee resulting from this fee rate and the
// given weight in weight units (wu).
func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount {
// The resulting fee is rounded down, as specified in BOLT#03.
return btcutil.Amount(s) * btcutil.Amount(wu) / 1000
}
// FeeEstimator provides the ability to estimate on-chain transaction fees for
// various combinations of transaction sizes and desired confirmation time
// (measured by number of blocks).
type FeeEstimator interface {
// EstimateFeePerVSize takes in a target for the number of blocks until
// an initial confirmation and returns the estimated fee expressed in
// satoshis/vbyte.
EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error)
// Start signals the FeeEstimator to start any processes or goroutines
// it needs to perform its duty.
Start() error
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
Stop() error
}
// StaticFeeEstimator will return a static value for all fee calculation
// requests. It is designed to be replaced by a proper fee calculation
// implementation.
type StaticFeeEstimator struct {
// FeeRate is the static fee rate in satoshis-per-vbyte that will be
// returned by this fee estimator.
FeeRate SatPerVByte
}
// EstimateFeePerVSize will return a static value for fee calculations.
//
// NOTE: This method is part of the FeeEstimator interface.
func (e StaticFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) {
return e.FeeRate, nil
}
// Start signals the FeeEstimator to start any processes or goroutines
// it needs to perform its duty.
//
// NOTE: This method is part of the FeeEstimator interface.
func (e StaticFeeEstimator) Start() error {
return nil
}
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
//
// NOTE: This method is part of the FeeEstimator interface.
func (e StaticFeeEstimator) Stop() error {
return nil
}
// A compile-time assertion to ensure that StaticFeeEstimator implements the
// FeeEstimator interface.
var _ FeeEstimator = (*StaticFeeEstimator)(nil)
// BtcdFeeEstimator is an implementation of the FeeEstimator interface backed
// by the RPC interface of an active btcd node. This implementation will proxy
// any fee estimation requests to btcd's RPC interface.
type BtcdFeeEstimator struct {
// fallBackFeeRate is the fall back fee rate in satoshis per vbyte that
// is returned if the fee estimator does not yet have enough data to
// actually produce fee estimates.
fallBackFeeRate SatPerVByte
btcdConn *rpcclient.Client
}
// NewBtcdFeeEstimator creates a new BtcdFeeEstimator given a fully populated
// rpc config that is able to successfully connect and authenticate with the
// btcd node, and also a fall back fee rate. The fallback fee rate is used in
// the occasion that the estimator has insufficient data, or returns zero for a
// fee estimate.
func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig,
fallBackFeeRate SatPerVByte) (*BtcdFeeEstimator, error) {
rpcConfig.DisableConnectOnNew = true
rpcConfig.DisableAutoReconnect = false
chainConn, err := rpcclient.New(&rpcConfig, nil)
if err != nil {
return nil, err
}
return &BtcdFeeEstimator{
fallBackFeeRate: fallBackFeeRate,
btcdConn: chainConn,
}, nil
}
// Start signals the FeeEstimator to start any processes or goroutines
// it needs to perform its duty.
//
// NOTE: This method is part of the FeeEstimator interface.
func (b *BtcdFeeEstimator) Start() error {
if err := b.btcdConn.Connect(20); err != nil {
return err
}
return nil
}
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
//
// NOTE: This method is part of the FeeEstimator interface.
func (b *BtcdFeeEstimator) Stop() error {
b.btcdConn.Shutdown()
return nil
}
// EstimateFeePerVSize takes in a target for the number of blocks until an
// initial confirmation and returns the estimated fee expressed in
// satoshis/vbyte.
//
// NOTE: This method is part of the FeeEstimator interface.
func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) {
feeEstimate, err := b.fetchEstimatePerVSize(numBlocks)
switch {
// If the estimator doesn't have enough data, or returns an error, then
// to return a proper value, then we'll return the default fall back
// fee rate.
case err != nil:
walletLog.Errorf("unable to query estimator: %v", err)
fallthrough
case feeEstimate == 0:
return b.fallBackFeeRate, nil
}
return feeEstimate, nil
}
// fetchEstimate returns a fee estimate for a transaction be be confirmed in
// confTarget blocks. The estimate is returned in sat/vbyte.
func (b *BtcdFeeEstimator) fetchEstimatePerVSize(
confTarget uint32) (SatPerVByte, error) {
// First, we'll fetch the estimate for our confirmation target.
btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget))
if err != nil {
return 0, err
}
// Next, we'll convert the returned value to satoshis, as it's
// currently returned in BTC.
satPerKB, err := btcutil.NewAmount(btcPerKB)
if err != nil {
return 0, err
}
// The value returned is expressed in fees per KB, while we want
// fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte
// before returning the estimate.
satPerByte := satPerKB / 1000
walletLog.Debugf("Returning %v sat/vbyte for conf target of %v",
int64(satPerByte), confTarget)
return SatPerVByte(satPerByte), nil
}
// A compile-time assertion to ensure that BtcdFeeEstimator implements the
// FeeEstimator interface.
var _ FeeEstimator = (*BtcdFeeEstimator)(nil)
// BitcoindFeeEstimator is an implementation of the FeeEstimator interface
// backed by the RPC interface of an active bitcoind node. This implementation
// will proxy any fee estimation requests to bitcoind's RPC interface.
type BitcoindFeeEstimator struct {
// fallBackFeeRate is the fall back fee rate in satoshis per vbyte that
// is returned if the fee estimator does not yet have enough data to
// actually produce fee estimates.
fallBackFeeRate SatPerVByte
bitcoindConn *rpcclient.Client
}
// NewBitcoindFeeEstimator creates a new BitcoindFeeEstimator given a fully
// populated rpc config that is able to successfully connect and authenticate
// with the bitcoind node, and also a fall back fee rate. The fallback fee rate
// is used in the occasion that the estimator has insufficient data, or returns
// zero for a fee estimate.
func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig,
fallBackFeeRate SatPerVByte) (*BitcoindFeeEstimator, error) {
rpcConfig.DisableConnectOnNew = true
rpcConfig.DisableAutoReconnect = false
rpcConfig.DisableTLS = true
rpcConfig.HTTPPostMode = true
chainConn, err := rpcclient.New(&rpcConfig, nil)
if err != nil {
return nil, err
}
return &BitcoindFeeEstimator{
fallBackFeeRate: fallBackFeeRate,
bitcoindConn: chainConn,
}, nil
}
// Start signals the FeeEstimator to start any processes or goroutines
// it needs to perform its duty.
//
// NOTE: This method is part of the FeeEstimator interface.
func (b *BitcoindFeeEstimator) Start() error {
return nil
}
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
//
// NOTE: This method is part of the FeeEstimator interface.
func (b *BitcoindFeeEstimator) Stop() error {
return nil
}
// EstimateFeePerVSize takes in a target for the number of blocks until an
// initial confirmation and returns the estimated fee expressed in
// satoshis/vbyte.
//
// NOTE: This method is part of the FeeEstimator interface.
func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) {
feeEstimate, err := b.fetchEstimatePerVSize(numBlocks)
switch {
// If the estimator doesn't have enough data, or returns an error, then
// to return a proper value, then we'll return the default fall back
// fee rate.
case err != nil:
walletLog.Errorf("unable to query estimator: %v", err)
fallthrough
case feeEstimate == 0:
return b.fallBackFeeRate, nil
}
return feeEstimate, nil
}
// fetchEstimatePerVSize returns a fee estimate for a transaction be be confirmed in
// confTarget blocks. The estimate is returned in sat/vbyte.
func (b *BitcoindFeeEstimator) fetchEstimatePerVSize(
confTarget uint32) (SatPerVByte, error) {
// First, we'll send an "estimatesmartfee" command as a raw request,
// since it isn't supported by btcd but is available in bitcoind.
target, err := json.Marshal(uint64(confTarget))
if err != nil {
return 0, err
}
// TODO: Allow selection of economical/conservative modifiers.
resp, err := b.bitcoindConn.RawRequest("estimatesmartfee",
[]json.RawMessage{target})
if err != nil {
return 0, err
}
// Next, we'll parse the response to get the BTC per KB.
feeEstimate := struct {
Feerate float64 `json:"feerate"`
}{}
err = json.Unmarshal(resp, &feeEstimate)
if err != nil {
return 0, err
}
// Next, we'll convert the returned value to satoshis, as it's
// currently returned in BTC.
satPerKB, err := btcutil.NewAmount(feeEstimate.Feerate)
if err != nil {
return 0, err
}
// The value returned is expressed in fees per KB, while we want
// fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte
// before returning the estimate.
satPerByte := satPerKB / 1000
walletLog.Debugf("Returning %v sat/vbyte for conf target of %v",
int64(satPerByte), confTarget)
return SatPerVByte(satPerByte), nil
}
// A compile-time assertion to ensure that BitcoindFeeEstimator implements the
// FeeEstimator interface.
var _ FeeEstimator = (*BitcoindFeeEstimator)(nil)