Merge pull request #1644 from wpaulino/kw-fees

multi: switch to sat/kw fees
This commit is contained in:
Olaoluwa Osuntokun 2018-08-09 20:19:28 -07:00 committed by GitHub
commit d2612e51bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 408 additions and 446 deletions

@ -1015,13 +1015,13 @@ func (b *breachArbiter) createJusticeTx(
spendableOutputs = append(spendableOutputs, input) spendableOutputs = append(spendableOutputs, input)
} }
txVSize := int64(weightEstimate.VSize()) txWeight := int64(weightEstimate.Weight())
return b.sweepSpendableOutputsTxn(txVSize, spendableOutputs...) return b.sweepSpendableOutputsTxn(txWeight, spendableOutputs...)
} }
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of // sweepSpendableOutputsTxn creates a signed transaction from a sequence of
// spendable outputs by sweeping the funds into a single p2wkh output. // spendable outputs by sweeping the funds into a single p2wkh output.
func (b *breachArbiter) sweepSpendableOutputsTxn(txVSize int64, func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
inputs ...SpendableOutput) (*wire.MsgTx, error) { inputs ...SpendableOutput) (*wire.MsgTx, error) {
// First, we obtain a new public key script from the wallet which we'll // First, we obtain a new public key script from the wallet which we'll
@ -1041,11 +1041,11 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txVSize int64,
// We'll actually attempt to target inclusion within the next two // We'll actually attempt to target inclusion within the next two
// blocks as we'd like to sweep these funds back into our wallet ASAP. // blocks as we'd like to sweep these funds back into our wallet ASAP.
feePerVSize, err := b.cfg.Estimator.EstimateFeePerVSize(2) feePerKw, err := b.cfg.Estimator.EstimateFeePerKW(2)
if err != nil { if err != nil {
return nil, err return nil, err
} }
txFee := feePerVSize.FeeForVSize(txVSize) txFee := feePerKw.FeeForWeight(txWeight)
// TODO(roasbeef): already start to siphon their funds into fees // TODO(roasbeef): already start to siphon their funds into fees
sweepAmt := int64(totalAmt - txFee) sweepAmt := int64(totalAmt - txFee)

@ -1351,7 +1351,7 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent,
ba := newBreachArbiter(&BreachConfig{ ba := newBreachArbiter(&BreachConfig{
CloseLink: func(_ *wire.OutPoint, _ htlcswitch.ChannelCloseType) {}, CloseLink: func(_ *wire.OutPoint, _ htlcswitch.ChannelCloseType) {},
DB: db, DB: db,
Estimator: &lnwallet.StaticFeeEstimator{FeeRate: 50}, Estimator: &lnwallet.StaticFeeEstimator{FeePerKW: 12500},
GenSweepScript: func() ([]byte, error) { return nil, nil }, GenSweepScript: func() ([]byte, error) { return nil, nil },
ContractBreaches: contractBreaches, ContractBreaches: contractBreaches,
Signer: signer, Signer: signer,
@ -1491,12 +1491,11 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
return nil, nil, nil, err return nil, nil, nil, err
} }
estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 12500}
feePerVSize, err := estimator.EstimateFeePerVSize(1) feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
feePerKw := feePerVSize.FeePerKWeight()
// TODO(roasbeef): need to factor in commit fee? // TODO(roasbeef): need to factor in commit fee?
aliceCommit := channeldb.ChannelCommitment{ aliceCommit := channeldb.ChannelCommitment{

@ -37,15 +37,21 @@ const (
defaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000) defaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
defaultBitcoinFeeRate = lnwire.MilliSatoshi(1) defaultBitcoinFeeRate = lnwire.MilliSatoshi(1)
defaultBitcoinTimeLockDelta = 144 defaultBitcoinTimeLockDelta = 144
defaultBitcoinStaticFeeRate = lnwallet.SatPerVByte(50)
defaultLitecoinMinHTLCMSat = lnwire.MilliSatoshi(1000) defaultLitecoinMinHTLCMSat = lnwire.MilliSatoshi(1000)
defaultLitecoinBaseFeeMSat = lnwire.MilliSatoshi(1000) defaultLitecoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
defaultLitecoinFeeRate = lnwire.MilliSatoshi(1) defaultLitecoinFeeRate = lnwire.MilliSatoshi(1)
defaultLitecoinTimeLockDelta = 576 defaultLitecoinTimeLockDelta = 576
defaultLitecoinStaticFeeRate = lnwallet.SatPerVByte(200)
defaultLitecoinDustLimit = btcutil.Amount(54600) defaultLitecoinDustLimit = btcutil.Amount(54600)
// defaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte
// expressed in sat/kw.
defaultBitcoinStaticFeePerKW = lnwallet.SatPerKWeight(12500)
// defaultLitecoinStaticFeePerKW is the fee rate of 200 sat/vbyte
// expressed in sat/kw.
defaultLitecoinStaticFeePerKW = lnwallet.SatPerKWeight(50000)
// btcToLtcConversionRate is a fixed ratio used in order to scale up // btcToLtcConversionRate is a fixed ratio used in order to scale up
// payments when running on the Litecoin chain. // payments when running on the Litecoin chain.
btcToLtcConversionRate = 60 btcToLtcConversionRate = 60
@ -141,7 +147,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
TimeLockDelta: cfg.Bitcoin.TimeLockDelta, TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
} }
cc.feeEstimator = lnwallet.StaticFeeEstimator{ cc.feeEstimator = lnwallet.StaticFeeEstimator{
FeeRate: defaultBitcoinStaticFeeRate, FeePerKW: defaultBitcoinStaticFeePerKW,
} }
case litecoinChain: case litecoinChain:
cc.routingPolicy = htlcswitch.ForwardingPolicy{ cc.routingPolicy = htlcswitch.ForwardingPolicy{
@ -151,7 +157,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
TimeLockDelta: cfg.Litecoin.TimeLockDelta, TimeLockDelta: cfg.Litecoin.TimeLockDelta,
} }
cc.feeEstimator = lnwallet.StaticFeeEstimator{ cc.feeEstimator = lnwallet.StaticFeeEstimator{
FeeRate: defaultLitecoinStaticFeeRate, FeePerKW: defaultLitecoinStaticFeePerKW,
} }
default: default:
return nil, nil, fmt.Errorf("Default routing policy for "+ return nil, nil, fmt.Errorf("Default routing policy for "+
@ -337,9 +343,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// if we're using bitcoind as a backend, then we can // if we're using bitcoind as a backend, then we can
// use live fee estimates, rather than a statically // use live fee estimates, rather than a statically
// coded value. // coded value.
fallBackFeeRate := lnwallet.SatPerVByte(25) fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000)
cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator(
*rpcConfig, fallBackFeeRate, *rpcConfig, fallBackFeeRate.FeePerKWeight(),
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -354,9 +360,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// if we're using litecoind as a backend, then we can // if we're using litecoind as a backend, then we can
// use live fee estimates, rather than a statically // use live fee estimates, rather than a statically
// coded value. // coded value.
fallBackFeeRate := lnwallet.SatPerVByte(25) fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000)
cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator(
*rpcConfig, fallBackFeeRate, *rpcConfig, fallBackFeeRate.FeePerKWeight(),
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -457,9 +463,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// if we're using btcd as a backend, then we can use // if we're using btcd as a backend, then we can use
// live fee estimates, rather than a statically coded // live fee estimates, rather than a statically coded
// value. // value.
fallBackFeeRate := lnwallet.SatPerVByte(25) fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000)
cc.feeEstimator, err = lnwallet.NewBtcdFeeEstimator( cc.feeEstimator, err = lnwallet.NewBtcdFeeEstimator(
*rpcConfig, fallBackFeeRate, *rpcConfig, fallBackFeeRate.FeePerKWeight(),
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

@ -451,27 +451,27 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
return nil, err return nil, err
} }
// With out address obtained, we'll query for an // With our address obtained, we'll query for an
// estimate to be confirmed at ease. // estimate to be confirmed at ease.
// //
// TODO(roasbeef): signal up if fee would be too large // TODO(roasbeef): signal up if fee would be too large
// to sweep singly, need to batch // to sweep singly, need to batch
feePerVSize, err := h.FeeEstimator.EstimateFeePerVSize(6) feePerKw, err := h.FeeEstimator.EstimateFeePerKW(6)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("%T(%x): using %v sat/vbyte to sweep htlc"+ log.Debugf("%T(%x): using %v sat/kw to sweep htlc"+
"incoming+remote htlc confirmed", h, "incoming+remote htlc confirmed", h,
h.payHash[:], int64(feePerVSize)) h.payHash[:], int64(feePerKw))
// Using a weight estimator, we'll compute the total // Using a weight estimator, we'll compute the total
// fee required, and from that the value we'll end up // fee required, and from that the value we'll end up
// with. // with.
totalVSize := (&lnwallet.TxWeightEstimator{}). totalWeight := (&lnwallet.TxWeightEstimator{}).
AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize). AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize).
AddP2WKHOutput().VSize() AddP2WKHOutput().Weight()
totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) totalFees := feePerKw.FeeForWeight(int64(totalWeight))
sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value - sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value -
int64(totalFees) int64(totalFees)
@ -1253,18 +1253,18 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
// First, we'll estimate the total weight so we can compute // First, we'll estimate the total weight so we can compute
// fees properly. We'll use a lax estimate, as this output is // fees properly. We'll use a lax estimate, as this output is
// in no immediate danger. // in no immediate danger.
feePerVSize, err := c.FeeEstimator.EstimateFeePerVSize(6) feePerKw, err := c.FeeEstimator.EstimateFeePerKW(6)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("%T(%v): using %v sat/vsize for sweep tx", c, log.Debugf("%T(%v): using %v sat/kw for sweep tx", c,
c.chanPoint, int64(feePerVSize)) c.chanPoint, int64(feePerKw))
totalVSize := (&lnwallet.TxWeightEstimator{}). totalWeight := (&lnwallet.TxWeightEstimator{}).
AddP2PKHInput(). AddP2WKHInput().
AddP2WKHOutput().VSize() AddP2WKHOutput().Weight()
totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) totalFees := feePerKw.FeeForWeight(int64(totalWeight))
sweepAmt := signDesc.Output.Value - int64(totalFees) sweepAmt := signDesc.Output.Value - int64(totalFees)
c.sweepTx = wire.NewMsgTx(2) c.sweepTx = wire.NewMsgTx(2)

@ -8,10 +8,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"google.golang.org/grpc"
"golang.org/x/crypto/salsa20"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
@ -28,6 +24,8 @@ import (
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing"
"golang.org/x/crypto/salsa20"
"google.golang.org/grpc"
) )
const ( const (
@ -68,11 +66,6 @@ const (
// currently accepted on the Litecoin chain within the Lightning // currently accepted on the Litecoin chain within the Lightning
// Protocol. // Protocol.
maxLtcFundingAmount = maxBtcFundingAmount * btcToLtcConversionRate maxLtcFundingAmount = maxBtcFundingAmount * btcToLtcConversionRate
// minCommitFeePerKw is the smallest fee rate that we should propose
// for a new fee update. We'll use this as a fee floor when proposing
// and accepting updates.
minCommitFeePerKw = 253
) )
var ( var (
@ -1047,8 +1040,8 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
reservation, err := f.cfg.Wallet.InitChannelReservation( reservation, err := f.cfg.Wallet.InitChannelReservation(
amt, 0, msg.PushAmount, amt, 0, msg.PushAmount,
lnwallet.SatPerKWeight(msg.FeePerKiloWeight), 0, lnwallet.SatPerKWeight(msg.FeePerKiloWeight), 0,
fmsg.peer.IdentityKey(), fmsg.peer.Address(), fmsg.peer.IdentityKey(), fmsg.peer.Address(), &chainHash,
&chainHash, msg.ChannelFlags, msg.ChannelFlags,
) )
if err != nil { if err != nil {
fndgLog.Errorf("Unable to initialize reservation: %v", err) fndgLog.Errorf("Unable to initialize reservation: %v", err)
@ -2576,23 +2569,12 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
// commitment transaction confirmed by the next few blocks (conf target // commitment transaction confirmed by the next few blocks (conf target
// of 3). We target the near blocks here to ensure that we'll be able // of 3). We target the near blocks here to ensure that we'll be able
// to execute a timely unilateral channel closure if needed. // to execute a timely unilateral channel closure if needed.
feePerVSize, err := f.cfg.FeeEstimator.EstimateFeePerVSize(3) commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3)
if err != nil { if err != nil {
msg.err <- err msg.err <- err
return return
} }
// If the converted fee-per-kw is below the current widely used policy
// floor, then we'll use the floor instead.
commitFeePerKw := feePerVSize.FeePerKWeight()
if commitFeePerKw < minCommitFeePerKw {
fndgLog.Infof("Proposed fee rate of %v sat/kw is below min "+
"of %v sat/kw, using fee floor", int64(commitFeePerKw),
int64(minCommitFeePerKw))
commitFeePerKw = minCommitFeePerKw
}
// We set the channel flags to indicate whether we want this channel to // We set the channel flags to indicate whether we want this channel to
// be announced to the network. // be announced to the network.
var channelFlags lnwire.FundingFlag var channelFlags lnwire.FundingFlag
@ -2606,7 +2588,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
// request will fail, and be aborted. // request will fail, and be aborted.
reservation, err := f.cfg.Wallet.InitChannelReservation( reservation, err := f.cfg.Wallet.InitChannelReservation(
capacity, localAmt, msg.pushAmt, commitFeePerKw, capacity, localAmt, msg.pushAmt, commitFeePerKw,
msg.fundingFeePerVSize, peerKey, msg.peer.Address(), msg.fundingFeePerKw, peerKey, msg.peer.Address(),
&msg.chainHash, channelFlags, &msg.chainHash, channelFlags,
) )
if err != nil { if err != nil {

@ -236,7 +236,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
addr *lnwire.NetAddress, tempTestDir string) (*testNode, error) { addr *lnwire.NetAddress, tempTestDir string) (*testNode, error) {
netParams := activeNetParams.Params netParams := activeNetParams.Params
estimator := lnwallet.StaticFeeEstimator{FeeRate: 250} estimator := lnwallet.StaticFeeEstimator{FeePerKW: 62500}
chainNotifier := &mockNotifier{ chainNotifier := &mockNotifier{
oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1), oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1),

@ -35,11 +35,6 @@ const (
// TODO(roasbeef): must be < default delta // TODO(roasbeef): must be < default delta
expiryGraceDelta = 2 expiryGraceDelta = 2
// minCommitFeePerKw is the smallest fee rate that we should propose
// for a new fee update. We'll use this as a fee floor when proposing
// and accepting updates.
minCommitFeePerKw = 253
// DefaultMinLinkFeeUpdateTimeout represents the minimum interval in // DefaultMinLinkFeeUpdateTimeout represents the minimum interval in
// which a link should propose to update its commitment fee rate. // which a link should propose to update its commitment fee rate.
DefaultMinLinkFeeUpdateTimeout = 10 * time.Minute DefaultMinLinkFeeUpdateTimeout = 10 * time.Minute
@ -495,26 +490,13 @@ func (l *channelLink) EligibleToForward() bool {
// this is the native rate used when computing the fee for commitment // this is the native rate used when computing the fee for commitment
// transactions, and the second-level HTLC transactions. // transactions, and the second-level HTLC transactions.
func (l *channelLink) sampleNetworkFee() (lnwallet.SatPerKWeight, error) { func (l *channelLink) sampleNetworkFee() (lnwallet.SatPerKWeight, error) {
// We'll first query for the sat/vbyte recommended to be confirmed // We'll first query for the sat/kw recommended to be confirmed within 3
// within 3 blocks. // blocks.
feePerVSize, err := l.cfg.FeeEstimator.EstimateFeePerVSize(3) feePerKw, err := l.cfg.FeeEstimator.EstimateFeePerKW(3)
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Once we have this fee rate, we'll convert to sat-per-kw.
feePerKw := feePerVSize.FeePerKWeight()
// If the returned feePerKw is less than the current widely used
// policy, then we'll use that instead as a floor.
if feePerKw < minCommitFeePerKw {
log.Debugf("Proposed fee rate of %v sat/kw is below min "+
"of %v sat/kw, using fee floor", int64(feePerKw),
int64(minCommitFeePerKw))
feePerKw = minCommitFeePerKw
}
log.Debugf("ChannelLink(%v): sampled fee rate for 3 block conf: %v "+ log.Debugf("ChannelLink(%v): sampled fee rate for 3 block conf: %v "+
"sat/kw", l, int64(feePerKw)) "sat/kw", l, int64(feePerKw))

@ -1726,14 +1726,11 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
coreLink.cfg.HodlMask = hodl.MaskFromFlags(hodl.ExitSettle) coreLink.cfg.HodlMask = hodl.MaskFromFlags(hodl.ExitSettle)
coreLink.cfg.DebugHTLC = true coreLink.cfg.DebugHTLC = true
estimator := &lnwallet.StaticFeeEstimator{ estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000}
FeeRate: 24, feePerKw, err := estimator.EstimateFeePerKW(1)
}
feeRate, err := estimator.EstimateFeePerVSize(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight()
htlcFee := lnwire.NewMSatFromSatoshis( htlcFee := lnwire.NewMSatFromSatoshis(
feePerKw.FeeForWeight(lnwallet.HtlcWeight), feePerKw.FeeForWeight(lnwallet.HtlcWeight),
) )
@ -2140,14 +2137,11 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) {
aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs
) )
estimator := &lnwallet.StaticFeeEstimator{ estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000}
FeeRate: 24, feePerKw, err := estimator.EstimateFeePerKW(1)
}
feeRate, err := estimator.EstimateFeePerVSize(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight()
var htlcID uint64 var htlcID uint64
addLinkHTLC := func(id uint64, amt lnwire.MilliSatoshi) [32]byte { addLinkHTLC := func(id uint64, amt lnwire.MilliSatoshi) [32]byte {
@ -2390,17 +2384,15 @@ func TestChannelLinkTrimCircuitsPending(t *testing.T) {
// Compute the static fees that will be used to determine the // Compute the static fees that will be used to determine the
// correctness of Alice's bandwidth when forwarding HTLCs. // correctness of Alice's bandwidth when forwarding HTLCs.
estimator := &lnwallet.StaticFeeEstimator{ estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000}
FeeRate: 24, feePerKw, err := estimator.EstimateFeePerKW(1)
}
feeRate, err := estimator.EstimateFeePerVSize(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
defaultCommitFee := alice.channel.StateSnapshot().CommitFee defaultCommitFee := alice.channel.StateSnapshot().CommitFee
htlcFee := lnwire.NewMSatFromSatoshis( htlcFee := lnwire.NewMSatFromSatoshis(
feeRate.FeePerKWeight().FeeForWeight(lnwallet.HtlcWeight), feePerKw.FeeForWeight(lnwallet.HtlcWeight),
) )
// The starting bandwidth of the channel should be exactly the amount // The starting bandwidth of the channel should be exactly the amount
@ -2666,17 +2658,15 @@ func TestChannelLinkTrimCircuitsNoCommit(t *testing.T) {
// Compute the static fees that will be used to determine the // Compute the static fees that will be used to determine the
// correctness of Alice's bandwidth when forwarding HTLCs. // correctness of Alice's bandwidth when forwarding HTLCs.
estimator := &lnwallet.StaticFeeEstimator{ estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000}
FeeRate: 24, feePerKw, err := estimator.EstimateFeePerKW(1)
}
feeRate, err := estimator.EstimateFeePerVSize(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
defaultCommitFee := alice.channel.StateSnapshot().CommitFee defaultCommitFee := alice.channel.StateSnapshot().CommitFee
htlcFee := lnwire.NewMSatFromSatoshis( htlcFee := lnwire.NewMSatFromSatoshis(
feeRate.FeePerKWeight().FeeForWeight(lnwallet.HtlcWeight), feePerKw.FeeForWeight(lnwallet.HtlcWeight),
) )
// The starting bandwidth of the channel should be exactly the amount // The starting bandwidth of the channel should be exactly the amount
@ -2926,14 +2916,11 @@ func TestChannelLinkBandwidthChanReserve(t *testing.T) {
aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs
) )
estimator := &lnwallet.StaticFeeEstimator{ estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000}
FeeRate: 24, feePerKw, err := estimator.EstimateFeePerKW(1)
}
feeRate, err := estimator.EstimateFeePerVSize(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight()
htlcFee := lnwire.NewMSatFromSatoshis( htlcFee := lnwire.NewMSatFromSatoshis(
feePerKw.FeeForWeight(lnwallet.HtlcWeight), feePerKw.FeeForWeight(lnwallet.HtlcWeight),
) )
@ -3460,15 +3447,9 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) {
startingFeeRate := channels.aliceToBob.CommitFeeRate() startingFeeRate := channels.aliceToBob.CommitFeeRate()
// Convert starting fee rate to sat/vbyte. This is usually a
// lossy conversion, but since the startingFeeRate is
// 6000 sat/kw in this case, we won't lose precision.
startingFeeRateSatPerVByte := lnwallet.SatPerVByte(
startingFeeRate * 4 / 1000)
// Next, we'll send the first fee rate response to Alice. // Next, we'll send the first fee rate response to Alice.
select { select {
case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte: case n.feeEstimator.byteFeeIn <- startingFeeRate:
case <-time.After(time.Second * 5): case <-time.After(time.Second * 5):
t.Fatalf("alice didn't query for the new network fee") t.Fatalf("alice didn't query for the new network fee")
} }
@ -3497,7 +3478,7 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) {
// fee update. // fee update.
newFeeRate := startingFeeRate * 3 newFeeRate := startingFeeRate * 3
select { select {
case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte * 3: case n.feeEstimator.byteFeeIn <- newFeeRate:
case <-time.After(time.Second * 5): case <-time.After(time.Second * 5):
t.Fatalf("alice didn't query for the new network fee") t.Fatalf("alice didn't query for the new network fee")
} }

@ -58,12 +58,14 @@ func (m *mockPreimageCache) SubscribeUpdates() *contractcourt.WitnessSubscriptio
} }
type mockFeeEstimator struct { type mockFeeEstimator struct {
byteFeeIn chan lnwallet.SatPerVByte byteFeeIn chan lnwallet.SatPerKWeight
quit chan struct{} quit chan struct{}
} }
func (m *mockFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (lnwallet.SatPerVByte, error) { func (m *mockFeeEstimator) EstimateFeePerKW(
numBlocks uint32) (lnwallet.SatPerKWeight, error) {
select { select {
case feeRate := <-m.byteFeeIn: case feeRate := <-m.byteFeeIn:
return feeRate, nil return feeRate, nil

@ -271,14 +271,11 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
return nil, nil, nil, nil, err return nil, nil, nil, nil, err
} }
estimator := &lnwallet.StaticFeeEstimator{ estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000}
FeeRate: 24, feePerKw, err := estimator.EstimateFeePerKW(1)
}
feePerVSize, err := estimator.EstimateFeePerVSize(1)
if err != nil { if err != nil {
return nil, nil, nil, nil, err return nil, nil, nil, nil, err
} }
feePerKw := feePerVSize.FeePerKWeight()
commitFee := feePerKw.FeeForWeight(724) commitFee := feePerKw.FeeForWeight(724)
const broadcastHeight = 1 const broadcastHeight = 1
@ -873,7 +870,7 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
carolDecoder := newMockIteratorDecoder() carolDecoder := newMockIteratorDecoder()
feeEstimator := &mockFeeEstimator{ feeEstimator := &mockFeeEstimator{
byteFeeIn: make(chan lnwallet.SatPerVByte), byteFeeIn: make(chan lnwallet.SatPerKWeight),
quit: make(chan struct{}), quit: make(chan struct{}),
} }

@ -172,7 +172,8 @@ func (n *NetworkHarness) SetUp(lndArgs []string) error {
PkScript: addrScript, PkScript: addrScript,
Value: btcutil.SatoshiPerBitcoin, Value: btcutil.SatoshiPerBitcoin,
} }
if _, err := n.Miner.SendOutputs([]*wire.TxOut{output}, 30); err != nil { _, err = n.Miner.SendOutputs([]*wire.TxOut{output}, 7500)
if err != nil {
return err return err
} }
} }
@ -1159,7 +1160,8 @@ func (n *NetworkHarness) sendCoins(ctx context.Context, amt btcutil.Amount,
PkScript: addrScript, PkScript: addrScript,
Value: int64(amt), Value: int64(amt),
} }
if _, err := n.Miner.SendOutputs([]*wire.TxOut{output}, 30); err != nil { _, err = n.Miner.SendOutputs([]*wire.TxOut{output}, 7500)
if err != nil {
return err return err
} }

@ -272,11 +272,11 @@ func (b *BtcWallet) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) {
// //
// This is a part of the WalletController interface. // This is a part of the WalletController interface.
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut, func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
feeRate lnwallet.SatPerVByte) (*chainhash.Hash, error) { feeRate lnwallet.SatPerKWeight) (*chainhash.Hash, error) {
// The fee rate is passed in using units of sat/vbyte, so we'll scale // Convert our fee rate from sat/kw to sat/kb since it's required by
// this up to sat/KB as the SendOutputs method requires this unit. // SendOutputs.
feeSatPerKB := btcutil.Amount(feeRate * 1000) feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB) return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB)
} }

@ -1131,12 +1131,11 @@ func TestHTLCSigNumber(t *testing.T) {
} }
// Calculate two values that will be below and above Bob's dust limit. // Calculate two values that will be below and above Bob's dust limit.
estimator := &StaticFeeEstimator{24} estimator := &StaticFeeEstimator{FeePerKW: 6000}
feePerVSize, err := estimator.EstimateFeePerVSize(1) feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to get fee: %v", err) t.Fatalf("unable to get fee: %v", err)
} }
feePerKw := feePerVSize.FeePerKWeight()
belowDust := btcutil.Amount(500) + htlcTimeoutFee(feePerKw) belowDust := btcutil.Amount(500) + htlcTimeoutFee(feePerKw)
aboveDust := btcutil.Amount(1400) + htlcSuccessFee(feePerKw) aboveDust := btcutil.Amount(1400) + htlcSuccessFee(feePerKw)

@ -8,38 +8,49 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
) )
// SatPerVByte represents a fee rate in satoshis per vbyte. const (
type SatPerVByte btcutil.Amount // FeePerKwFloor is the lowest fee rate in sat/kw that we should use for
// determining transaction fees.
FeePerKwFloor SatPerKWeight = 253
)
// FeeForVSize calculates the fee resulting from this fee rate and // SatPerKVByte represents a fee rate in sat/kb.
// the given vsize in vbytes. type SatPerKVByte btcutil.Amount
func (s SatPerVByte) FeeForVSize(vbytes int64) btcutil.Amount {
return btcutil.Amount(s) * btcutil.Amount(vbytes) // FeeForVSize calculates the fee resulting from this fee rate and the given
// vsize in vbytes.
func (s SatPerKVByte) FeeForVSize(vbytes int64) btcutil.Amount {
return btcutil.Amount(s) * btcutil.Amount(vbytes) / 1000
} }
// FeePerKWeight converts the fee rate into SatPerKWeight. // FeePerKWeight converts the current fee rate from sat/kb to sat/kw.
func (s SatPerVByte) FeePerKWeight() SatPerKWeight { func (s SatPerKVByte) FeePerKWeight() SatPerKWeight {
return SatPerKWeight(s * 1000 / blockchain.WitnessScaleFactor) return SatPerKWeight(s / blockchain.WitnessScaleFactor)
} }
// SatPerKWeight represents a fee rate in satoshis per kilo weight unit. // SatPerKWeight represents a fee rate in sat/kw.
type SatPerKWeight btcutil.Amount type SatPerKWeight btcutil.Amount
// FeeForWeight calculates the fee resulting from this fee rate and the // FeeForWeight calculates the fee resulting from this fee rate and the given
// given weight in weight units (wu). // weight in weight units (wu).
func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount { func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount {
// The resulting fee is rounded down, as specified in BOLT#03. // The resulting fee is rounded down, as specified in BOLT#03.
return btcutil.Amount(s) * btcutil.Amount(wu) / 1000 return btcutil.Amount(s) * btcutil.Amount(wu) / 1000
} }
// FeePerKVByte converts the current fee rate from sat/kw to sat/kb.
func (s SatPerKWeight) FeePerKVByte() SatPerKVByte {
return SatPerKVByte(s * blockchain.WitnessScaleFactor)
}
// FeeEstimator provides the ability to estimate on-chain transaction fees for // FeeEstimator provides the ability to estimate on-chain transaction fees for
// various combinations of transaction sizes and desired confirmation time // various combinations of transaction sizes and desired confirmation time
// (measured by number of blocks). // (measured by number of blocks).
type FeeEstimator interface { type FeeEstimator interface {
// EstimateFeePerVSize takes in a target for the number of blocks until // EstimateFeePerKW takes in a target for the number of blocks until an
// an initial confirmation and returns the estimated fee expressed in // initial confirmation and returns the estimated fee expressed in
// satoshis/vbyte. // sat/kw.
EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error)
// Start signals the FeeEstimator to start any processes or goroutines // Start signals the FeeEstimator to start any processes or goroutines
// it needs to perform its duty. // it needs to perform its duty.
@ -54,16 +65,16 @@ type FeeEstimator interface {
// requests. It is designed to be replaced by a proper fee calculation // requests. It is designed to be replaced by a proper fee calculation
// implementation. // implementation.
type StaticFeeEstimator struct { type StaticFeeEstimator struct {
// FeeRate is the static fee rate in satoshis-per-vbyte that will be // FeePerKW is the static fee rate in satoshis-per-vbyte that will be
// returned by this fee estimator. // returned by this fee estimator.
FeeRate SatPerVByte FeePerKW SatPerKWeight
} }
// EstimateFeePerVSize will return a static value for fee calculations. // EstimateFeePerKW will return a static value for fee calculations.
// //
// NOTE: This method is part of the FeeEstimator interface. // NOTE: This method is part of the FeeEstimator interface.
func (e StaticFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { func (e StaticFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
return e.FeeRate, nil return e.FeePerKW, nil
} }
// Start signals the FeeEstimator to start any processes or goroutines // Start signals the FeeEstimator to start any processes or goroutines
@ -90,16 +101,16 @@ var _ FeeEstimator = (*StaticFeeEstimator)(nil)
// by the RPC interface of an active btcd node. This implementation will proxy // by the RPC interface of an active btcd node. This implementation will proxy
// any fee estimation requests to btcd's RPC interface. // any fee estimation requests to btcd's RPC interface.
type BtcdFeeEstimator struct { type BtcdFeeEstimator struct {
// fallBackFeeRate is the fall back fee rate in satoshis per vbyte that // fallbackFeePerKW is the fall back fee rate in sat/kw that is returned
// is returned if the fee estimator does not yet have enough data to // if the fee estimator does not yet have enough data to actually
// actually produce fee estimates. // produce fee estimates.
fallBackFeeRate SatPerVByte fallbackFeePerKW SatPerKWeight
// minFeeRate is the minimum relay fee, in sat/vbyte, of the backend // minFeePerKW is the minimum fee, in sat/kw, that we should enforce.
// node. This will be used as the default fee rate of a transaction when // This will be used as the default fee rate for a transaction when the
// the estimated fee rate is too low to allow the transaction to // estimated fee rate is too low to allow the transaction to propagate
// propagate through the network. // through the network.
minFeeRate SatPerVByte minFeePerKW SatPerKWeight
btcdConn *rpcclient.Client btcdConn *rpcclient.Client
} }
@ -110,7 +121,7 @@ type BtcdFeeEstimator struct {
// the occasion that the estimator has insufficient data, or returns zero for a // the occasion that the estimator has insufficient data, or returns zero for a
// fee estimate. // fee estimate.
func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig, func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig,
fallBackFeeRate SatPerVByte) (*BtcdFeeEstimator, error) { fallBackFeeRate SatPerKWeight) (*BtcdFeeEstimator, error) {
rpcConfig.DisableConnectOnNew = true rpcConfig.DisableConnectOnNew = true
rpcConfig.DisableAutoReconnect = false rpcConfig.DisableAutoReconnect = false
@ -120,8 +131,8 @@ func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig,
} }
return &BtcdFeeEstimator{ return &BtcdFeeEstimator{
fallBackFeeRate: fallBackFeeRate, fallbackFeePerKW: fallBackFeeRate,
btcdConn: chainConn, btcdConn: chainConn,
}, nil }, nil
} }
@ -146,9 +157,20 @@ func (b *BtcdFeeEstimator) Start() error {
return err return err
} }
// The fee rate is expressed in sat/KB, so we'll manually convert it to // The fee rate is expressed in sat/kb, so we'll manually convert it to
// our desired sat/vbyte rate. // our desired sat/kw rate.
b.minFeeRate = SatPerVByte(relayFee / 1000) minRelayFeePerKw := SatPerKVByte(relayFee).FeePerKWeight()
// By default, we'll use the backend node's minimum relay fee as the
// minimum fee rate we'll propose for transacations. However, if this
// happens to be lower than our fee floor, we'll enforce that instead.
b.minFeePerKW = minRelayFeePerKw
if b.minFeePerKW < FeePerKwFloor {
b.minFeePerKW = FeePerKwFloor
}
walletLog.Debugf("Using minimum fee rate of %v sat/kw",
int64(b.minFeePerKW))
return nil return nil
} }
@ -163,13 +185,12 @@ func (b *BtcdFeeEstimator) Stop() error {
return nil return nil
} }
// EstimateFeePerVSize takes in a target for the number of blocks until an // EstimateFeePerKW takes in a target for the number of blocks until an initial
// initial confirmation and returns the estimated fee expressed in // confirmation and returns the estimated fee expressed in sat/kw.
// satoshis/vbyte.
// //
// NOTE: This method is part of the FeeEstimator interface. // NOTE: This method is part of the FeeEstimator interface.
func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { func (b *BtcdFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) feeEstimate, err := b.fetchEstimate(numBlocks)
switch { switch {
// If the estimator doesn't have enough data, or returns an error, then // 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 // to return a proper value, then we'll return the default fall back
@ -179,16 +200,15 @@ func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, e
fallthrough fallthrough
case feeEstimate == 0: case feeEstimate == 0:
return b.fallBackFeeRate, nil return b.fallbackFeePerKW, nil
} }
return feeEstimate, nil return feeEstimate, nil
} }
// fetchEstimate returns a fee estimate for a transaction to be confirmed in // fetchEstimate returns a fee estimate for a transaction to be confirmed in
// confTarget blocks. The estimate is returned in sat/vbyte. // confTarget blocks. The estimate is returned in sat/kw.
func (b *BtcdFeeEstimator) fetchEstimatePerVSize( func (b *BtcdFeeEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) {
confTarget uint32) (SatPerVByte, error) {
// First, we'll fetch the estimate for our confirmation target. // First, we'll fetch the estimate for our confirmation target.
btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget)) btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget))
if err != nil { if err != nil {
@ -202,23 +222,21 @@ func (b *BtcdFeeEstimator) fetchEstimatePerVSize(
return 0, err return 0, err
} }
// The value returned is expressed in fees per KB, while we want // Since we use fee rates in sat/kw internally, we'll convert the
// fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte // estimated fee rate from its sat/kb representation to sat/kw.
// before returning the estimate. satPerKw := SatPerKVByte(satPerKB).FeePerKWeight()
satPerByte := SatPerVByte(satPerKB / 1000)
// Before proceeding, we'll make sure that this fee rate respects the // Finally, we'll enforce our fee floor.
// minimum relay fee set on the backend node. if satPerKw < b.minFeePerKW {
if satPerByte < b.minFeeRate { walletLog.Debugf("Estimated fee rate of %v sat/kw is too low, "+
walletLog.Debugf("Using backend node's minimum relay fee rate "+ "using fee floor of %v sat/kw instead", b.minFeePerKW)
"of %v sat/vbyte", b.minFeeRate) satPerKw = b.minFeePerKW
satPerByte = b.minFeeRate
} }
walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", walletLog.Debugf("Returning %v sat/kw for conf target of %v",
int64(satPerByte), confTarget) int64(satPerKw), confTarget)
return satPerByte, nil return satPerKw, nil
} }
// A compile-time assertion to ensure that BtcdFeeEstimator implements the // A compile-time assertion to ensure that BtcdFeeEstimator implements the
@ -229,16 +247,16 @@ var _ FeeEstimator = (*BtcdFeeEstimator)(nil)
// backed by the RPC interface of an active bitcoind node. This implementation // backed by the RPC interface of an active bitcoind node. This implementation
// will proxy any fee estimation requests to bitcoind's RPC interface. // will proxy any fee estimation requests to bitcoind's RPC interface.
type BitcoindFeeEstimator struct { type BitcoindFeeEstimator struct {
// fallBackFeeRate is the fall back fee rate in satoshis per vbyte that // fallbackFeePerKW is the fallback fee rate in sat/kw that is returned
// is returned if the fee estimator does not yet have enough data to // if the fee estimator does not yet have enough data to actually
// actually produce fee estimates. // produce fee estimates.
fallBackFeeRate SatPerVByte fallbackFeePerKW SatPerKWeight
// minFeeRate is the minimum relay fee, in sat/vbyte, of the backend // minFeePerKW is the minimum fee, in sat/kw, that we should enforce.
// node. This will be used as the default fee rate of a transaction when // This will be used as the default fee rate for a transaction when the
// the estimated fee rate is too low to allow the transaction to // estimated fee rate is too low to allow the transaction to propagate
// propagate through the network. // through the network.
minFeeRate SatPerVByte minFeePerKW SatPerKWeight
bitcoindConn *rpcclient.Client bitcoindConn *rpcclient.Client
} }
@ -249,7 +267,7 @@ type BitcoindFeeEstimator struct {
// is used in the occasion that the estimator has insufficient data, or returns // is used in the occasion that the estimator has insufficient data, or returns
// zero for a fee estimate. // zero for a fee estimate.
func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig, func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig,
fallBackFeeRate SatPerVByte) (*BitcoindFeeEstimator, error) { fallBackFeeRate SatPerKWeight) (*BitcoindFeeEstimator, error) {
rpcConfig.DisableConnectOnNew = true rpcConfig.DisableConnectOnNew = true
rpcConfig.DisableAutoReconnect = false rpcConfig.DisableAutoReconnect = false
@ -261,8 +279,8 @@ func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig,
} }
return &BitcoindFeeEstimator{ return &BitcoindFeeEstimator{
fallBackFeeRate: fallBackFeeRate, fallbackFeePerKW: fallBackFeeRate,
bitcoindConn: chainConn, bitcoindConn: chainConn,
}, nil }, nil
} }
@ -293,9 +311,20 @@ func (b *BitcoindFeeEstimator) Start() error {
return err return err
} }
// The fee rate is expressed in sat/KB, so we'll manually convert it to // The fee rate is expressed in sat/kb, so we'll manually convert it to
// our desired sat/vbyte rate. // our desired sat/kw rate.
b.minFeeRate = SatPerVByte(relayFee / 1000) minRelayFeePerKw := SatPerKVByte(relayFee).FeePerKWeight()
// By default, we'll use the backend node's minimum relay fee as the
// minimum fee rate we'll propose for transacations. However, if this
// happens to be lower than our fee floor, we'll enforce that instead.
b.minFeePerKW = minRelayFeePerKw
if b.minFeePerKW < FeePerKwFloor {
b.minFeePerKW = FeePerKwFloor
}
walletLog.Debugf("Using minimum fee rate of %v sat/kw",
int64(b.minFeePerKW))
return nil return nil
} }
@ -308,13 +337,12 @@ func (b *BitcoindFeeEstimator) Stop() error {
return nil return nil
} }
// EstimateFeePerVSize takes in a target for the number of blocks until an // EstimateFeePerKW takes in a target for the number of blocks until an initial
// initial confirmation and returns the estimated fee expressed in // confirmation and returns the estimated fee expressed in sat/kw.
// satoshis/vbyte.
// //
// NOTE: This method is part of the FeeEstimator interface. // NOTE: This method is part of the FeeEstimator interface.
func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { func (b *BitcoindFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) feeEstimate, err := b.fetchEstimate(numBlocks)
switch { switch {
// If the estimator doesn't have enough data, or returns an error, then // 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 // to return a proper value, then we'll return the default fall back
@ -324,16 +352,15 @@ func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByt
fallthrough fallthrough
case feeEstimate == 0: case feeEstimate == 0:
return b.fallBackFeeRate, nil return b.fallbackFeePerKW, nil
} }
return feeEstimate, nil return feeEstimate, nil
} }
// fetchEstimatePerVSize returns a fee estimate for a transaction to be confirmed in // fetchEstimate returns a fee estimate for a transaction to be confirmed in
// confTarget blocks. The estimate is returned in sat/vbyte. // confTarget blocks. The estimate is returned in sat/kw.
func (b *BitcoindFeeEstimator) fetchEstimatePerVSize( func (b *BitcoindFeeEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) {
confTarget uint32) (SatPerVByte, error) {
// First, we'll send an "estimatesmartfee" command as a raw request, // First, we'll send an "estimatesmartfee" command as a raw request,
// since it isn't supported by btcd but is available in bitcoind. // since it isn't supported by btcd but is available in bitcoind.
target, err := json.Marshal(uint64(confTarget)) target, err := json.Marshal(uint64(confTarget))
@ -341,45 +368,44 @@ func (b *BitcoindFeeEstimator) fetchEstimatePerVSize(
return 0, err return 0, err
} }
// TODO: Allow selection of economical/conservative modifiers. // TODO: Allow selection of economical/conservative modifiers.
resp, err := b.bitcoindConn.RawRequest("estimatesmartfee", resp, err := b.bitcoindConn.RawRequest(
[]json.RawMessage{target}) "estimatesmartfee", []json.RawMessage{target},
)
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Next, we'll parse the response to get the BTC per KB. // Next, we'll parse the response to get the BTC per KB.
feeEstimate := struct { feeEstimate := struct {
Feerate float64 `json:"feerate"` FeeRate float64 `json:"feerate"`
}{} }{}
err = json.Unmarshal(resp, &feeEstimate) err = json.Unmarshal(resp, &feeEstimate)
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Next, we'll convert the returned value to satoshis, as it's // Next, we'll convert the returned value to satoshis, as it's currently
// currently returned in BTC. // returned in BTC.
satPerKB, err := btcutil.NewAmount(feeEstimate.Feerate) satPerKB, err := btcutil.NewAmount(feeEstimate.FeeRate)
if err != nil { if err != nil {
return 0, err return 0, err
} }
// The value returned is expressed in fees per KB, while we want // Since we use fee rates in sat/kw internally, we'll convert the
// fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte // estimated fee rate from its sat/kb representation to sat/kw.
// before returning the estimate. satPerKw := SatPerKVByte(satPerKB).FeePerKWeight()
satPerByte := SatPerVByte(satPerKB / 1000)
// Before proceeding, we'll make sure that this fee rate respects the // Finally, we'll enforce our fee floor.
// minimum relay fee set on the backend node. if satPerKw < b.minFeePerKW {
if satPerByte < b.minFeeRate { walletLog.Debugf("Estimated fee rate of %v sat/kw is too low, "+
walletLog.Debugf("Using backend node's minimum relay fee rate "+ "using fee floor of %v sat/kw instead", b.minFeePerKW)
"of %v sat/vbyte", b.minFeeRate) satPerKw = b.minFeePerKW
satPerByte = b.minFeeRate
} }
walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", walletLog.Debugf("Returning %v sat/kw for conf target of %v",
int64(satPerByte), confTarget) int64(satPerKw), confTarget)
return satPerByte, nil return satPerKw, nil
} }
// A compile-time assertion to ensure that BitcoindFeeEstimator implements the // A compile-time assertion to ensure that BitcoindFeeEstimator implements the

@ -13,57 +13,56 @@ import (
func TestFeeRateTypes(t *testing.T) { func TestFeeRateTypes(t *testing.T) {
t.Parallel() t.Parallel()
// Let our fee rate be 100 sat/vbyte. // We'll be calculating the transaction fees for the given measurements
feePerVSize := lnwallet.SatPerVByte(100) // using different fee rates and expecting them to match.
const vsize = 300
const weight = vsize * 4
// It is also equivalent to 25000 sat/kw. // Test the conversion from sat/kw to sat/kb.
feePerKw := feePerVSize.FeePerKWeight() for feePerKw := lnwallet.SatPerKWeight(250); feePerKw < 10000; feePerKw += 50 {
if feePerKw != 25000 { feePerKB := feePerKw.FeePerKVByte()
t.Fatalf("expected %d sat/kw, got %d sat/kw", 25000, if feePerKB != lnwallet.SatPerKVByte(feePerKw*4) {
feePerKw) t.Fatalf("expected %d sat/kb, got %d sat/kb when "+
} "converting from %d sat/kw", feePerKw*4,
feePerKB, feePerKw)
const txVSize = 300
// We'll now run through a set of values for the fee per vsize type,
// making sure the conversion to sat/kw and fee calculation is done
// correctly.
for f := lnwallet.SatPerVByte(0); f <= 40; f++ {
fPerKw := f.FeePerKWeight()
// The kw is always 250*vsize.
if fPerKw != lnwallet.SatPerKWeight(f*250) {
t.Fatalf("expected %d sat/kw, got %d sat/kw, when "+
"converting %d sat/vbyte", f*250, fPerKw, f)
} }
// The tx fee should simply be f*txvsize. // The resulting transaction fee should be the same when using
fee := f.FeeForVSize(txVSize) // both rates.
if fee != btcutil.Amount(f*txVSize) { expectedFee := btcutil.Amount(feePerKw * weight / 1000)
t.Fatalf("expected tx fee to be %d sat, was %d sat", fee1 := feePerKw.FeeForWeight(weight)
f*txVSize, fee) if fee1 != expectedFee {
t.Fatalf("expected fee of %d sats, got %d sats",
expectedFee, fee1)
} }
fee2 := feePerKB.FeeForVSize(vsize)
// The weight is 4*vsize. Fee calculation from the fee/kw if fee2 != expectedFee {
// should result in the same fee. t.Fatalf("expected fee of %d sats, got %d sats",
fee2 := fPerKw.FeeForWeight(txVSize * 4) expectedFee, fee2)
if fee != fee2 {
t.Fatalf("fee calculated from vsize (%d) not equal "+
"fee calculated from weight (%d)", fee, fee2)
} }
} }
// Do the same for fee per kw. // Test the conversion from sat/kb to sat/kw.
for f := lnwallet.SatPerKWeight(0); f < 1500; f++ { for feePerKB := lnwallet.SatPerKVByte(1000); feePerKB < 40000; feePerKB += 1000 {
weight := int64(txVSize * 4) feePerKw := feePerKB.FeePerKWeight()
if feePerKw != lnwallet.SatPerKWeight(feePerKB/4) {
t.Fatalf("expected %d sat/kw, got %d sat/kw when "+
"converting from %d sat/kb", feePerKB/4,
feePerKw, feePerKB)
}
// The expected fee is weight*f / 1000, since the fee is // The resulting transaction fee should be the same when using
// denominated per 1000 wu. // both rates.
expFee := btcutil.Amount(weight) * btcutil.Amount(f) / 1000 expectedFee := btcutil.Amount(feePerKB * vsize / 1000)
fee := f.FeeForWeight(weight) fee1 := feePerKB.FeeForVSize(vsize)
if fee != expFee { if fee1 != expectedFee {
t.Fatalf("expected fee to be %d sat, was %d", t.Fatalf("expected fee of %d sats, got %d sats",
fee, expFee) expectedFee, fee1)
}
fee2 := feePerKw.FeeForWeight(weight)
if fee2 != expectedFee {
t.Fatalf("expected fee of %d sats, got %d sats",
expectedFee, fee2)
} }
} }
} }
@ -73,22 +72,22 @@ func TestFeeRateTypes(t *testing.T) {
func TestStaticFeeEstimator(t *testing.T) { func TestStaticFeeEstimator(t *testing.T) {
t.Parallel() t.Parallel()
const feePerVSize = 100 const feePerKw = lnwallet.FeePerKwFloor
feeEstimator := &lnwallet.StaticFeeEstimator{ feeEstimator := &lnwallet.StaticFeeEstimator{
FeeRate: feePerVSize, FeePerKW: feePerKw,
} }
if err := feeEstimator.Start(); err != nil { if err := feeEstimator.Start(); err != nil {
t.Fatalf("unable to start fee estimator: %v", err) t.Fatalf("unable to start fee estimator: %v", err)
} }
defer feeEstimator.Stop() defer feeEstimator.Stop()
feeRate, err := feeEstimator.EstimateFeePerVSize(6) feeRate, err := feeEstimator.EstimateFeePerKW(6)
if err != nil { if err != nil {
t.Fatalf("unable to get fee rate: %v", err) t.Fatalf("unable to get fee rate: %v", err)
} }
if feeRate != feePerVSize { if feeRate != feePerKw {
t.Fatalf("expected fee rate %v, got %v", feePerVSize, feeRate) t.Fatalf("expected fee rate %v, got %v", feePerKw, feeRate)
} }
} }

@ -154,13 +154,13 @@ type WalletController interface {
// error should be returned. // error should be returned.
GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error)
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying
// paying out to the specified outputs. In the case the wallet has // out to the specified outputs. In the case the wallet has insufficient
// insufficient funds, or the outputs are non-standard, an error should // funds, or the outputs are non-standard, an error should be returned.
// be returned. This method also takes the target fee expressed in // This method also takes the target fee expressed in sat/kw that should
// sat/vbyte that should be used when crafting the transaction. // be used when crafting the transaction.
SendOutputs(outputs []*wire.TxOut, SendOutputs(outputs []*wire.TxOut,
feeRate SatPerVByte) (*chainhash.Hash, error) feeRate SatPerKWeight) (*chainhash.Hash, error)
// ListUnspentWitness returns all unspent outputs which are version 0 // ListUnspentWitness returns all unspent outputs which are version 0
// witness programs. The 'confirms' parameter indicates the minimum // witness programs. The 'confirms' parameter indicates the minimum

@ -186,7 +186,7 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
Value: int64(satoshiPerOutput), Value: int64(satoshiPerOutput),
PkScript: script, PkScript: script,
} }
if _, err := miner.SendOutputs([]*wire.TxOut{output}, 10); err != nil { if _, err := miner.SendOutputs([]*wire.TxOut{output}, 2500); err != nil {
return err return err
} }
} }
@ -249,7 +249,7 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
WalletController: wc, WalletController: wc,
Signer: signer, Signer: signer,
ChainIO: bio, ChainIO: bio,
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 10}, FeeEstimator: lnwallet.StaticFeeEstimator{FeePerKW: 2500},
DefaultConstraints: channeldb.ChannelConstraints{ DefaultConstraints: channeldb.ChannelConstraints{
DustLimit: 500, DustLimit: 500,
MaxPendingAmount: lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) * 100, MaxPendingAmount: lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) * 100,
@ -290,14 +290,14 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
// Alice initiates a channel funded with 5 BTC for each side, so 10 BTC // Alice initiates a channel funded with 5 BTC for each side, so 10 BTC
// total. She also generates 2 BTC in change. // total. She also generates 2 BTC in change.
feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight()
aliceChanReservation, err := alice.InitChannelReservation( aliceChanReservation, err := alice.InitChannelReservation(
fundingAmount*2, fundingAmount, 0, feePerKw, feeRate, fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw, bobPub,
bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) bobAddr, chainHash, lnwire.FFAnnounceChannel,
)
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err) t.Fatalf("unable to initialize funding reservation: %v", err)
} }
@ -325,9 +325,10 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
// Bob does the same, generating his own contribution. He then also // Bob does the same, generating his own contribution. He then also
// receives' Alice's contribution, and consumes that so we can continue // receives' Alice's contribution, and consumes that so we can continue
// the funding process. // the funding process.
bobChanReservation, err := bob.InitChannelReservation(fundingAmount*2, bobChanReservation, err := bob.InitChannelReservation(
fundingAmount, 0, feePerKw, feeRate, alicePub, aliceAddr, fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw, alicePub,
chainHash, lnwire.FFAnnounceChannel) aliceAddr, chainHash, lnwire.FFAnnounceChannel,
)
if err != nil { if err != nil {
t.Fatalf("bob unable to init channel reservation: %v", err) t.Fatalf("bob unable to init channel reservation: %v", err)
} }
@ -476,14 +477,13 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
if err != nil { if err != nil {
t.Fatalf("unable to create amt: %v", err) t.Fatalf("unable to create amt: %v", err)
} }
feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight() _, err = alice.InitChannelReservation(
_, err = alice.InitChannelReservation(fundingAmount, fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub,
fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, bobAddr, chainHash, lnwire.FFAnnounceChannel,
lnwire.FFAnnounceChannel,
) )
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation 1: %v", err) t.Fatalf("unable to initialize funding reservation 1: %v", err)
@ -496,9 +496,10 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
if err != nil { if err != nil {
t.Fatalf("unable to create amt: %v", err) t.Fatalf("unable to create amt: %v", err)
} }
failedReservation, err := alice.InitChannelReservation(amt, amt, 0, failedReservation, err := alice.InitChannelReservation(
feePerKw, feeRate, bobPub, bobAddr, chainHash, amt, amt, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash,
lnwire.FFAnnounceChannel) lnwire.FFAnnounceChannel,
)
if err == nil { if err == nil {
t.Fatalf("not error returned, should fail on coin selection") t.Fatalf("not error returned, should fail on coin selection")
} }
@ -513,28 +514,28 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) { alice, _ *lnwallet.LightningWallet, t *testing.T) {
feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight()
// Create a reservation for 44 BTC. // Create a reservation for 44 BTC.
fundingAmount, err := btcutil.NewAmount(44) fundingAmount, err := btcutil.NewAmount(44)
if err != nil { if err != nil {
t.Fatalf("unable to create amt: %v", err) t.Fatalf("unable to create amt: %v", err)
} }
chanReservation, err := alice.InitChannelReservation(fundingAmount, chanReservation, err := alice.InitChannelReservation(
fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub,
lnwire.FFAnnounceChannel) bobAddr, chainHash, lnwire.FFAnnounceChannel,
)
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err) t.Fatalf("unable to initialize funding reservation: %v", err)
} }
// Attempt to create another channel with 44 BTC, this should fail. // Attempt to create another channel with 44 BTC, this should fail.
_, err = alice.InitChannelReservation(fundingAmount, _, err = alice.InitChannelReservation(
fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub,
lnwire.FFAnnounceChannel, bobAddr, chainHash, lnwire.FFAnnounceChannel,
) )
if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok {
t.Fatalf("coin selection succeeded should have insufficient funds: %v", t.Fatalf("coin selection succeeded should have insufficient funds: %v",
@ -565,7 +566,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
// Request to fund a new channel should now succeed. // Request to fund a new channel should now succeed.
_, err = alice.InitChannelReservation(fundingAmount, fundingAmount, _, err = alice.InitChannelReservation(fundingAmount, fundingAmount,
0, feePerKw, feeRate, bobPub, bobAddr, chainHash, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash,
lnwire.FFAnnounceChannel) lnwire.FFAnnounceChannel)
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err) t.Fatalf("unable to initialize funding reservation: %v", err)
@ -575,15 +576,15 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
func testCancelNonExistentReservation(miner *rpctest.Harness, func testCancelNonExistentReservation(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) { alice, _ *lnwallet.LightningWallet, t *testing.T) {
feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
// Create our own reservation, give it some ID. // Create our own reservation, give it some ID.
res, err := lnwallet.NewChannelReservation( res, err := lnwallet.NewChannelReservation(
10000, 10000, feeRate.FeePerKWeight(), alice, 10000, 10000, feePerKw, alice, 22, 10, &testHdSeed,
22, 10, &testHdSeed, lnwire.FFAnnounceChannel, lnwire.FFAnnounceChannel,
) )
if err != nil { if err != nil {
t.Fatalf("unable to create res: %v", err) t.Fatalf("unable to create res: %v", err)
@ -602,14 +603,17 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
// We'll attempt to create a new reservation with an extremely high fee // We'll attempt to create a new reservation with an extremely high fee
// rate. This should push our balance into the negative and result in a // rate. This should push our balance into the negative and result in a
// failure to create the reservation. // failure to create the reservation.
fundingAmount, err := btcutil.NewAmount(4) const numBTC = 4
fundingAmount, err := btcutil.NewAmount(numBTC)
if err != nil { if err != nil {
t.Fatalf("unable to create amt: %v", err) t.Fatalf("unable to create amt: %v", err)
} }
feePerVSize := lnwallet.SatPerVByte(btcutil.SatoshiPerBitcoin * 4 / 100)
feePerKw := feePerVSize.FeePerKWeight() feePerKw := lnwallet.SatPerKWeight(
numBTC * numBTC * btcutil.SatoshiPerBitcoin,
)
_, err = alice.InitChannelReservation( _, err = alice.InitChannelReservation(
fundingAmount, fundingAmount, 0, feePerKw, feePerVSize, bobPub, fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub,
bobAddr, chainHash, lnwire.FFAnnounceChannel, bobAddr, chainHash, lnwire.FFAnnounceChannel,
) )
switch { switch {
@ -677,14 +681,14 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
t.Fatalf("unable to create amt: %v", err) t.Fatalf("unable to create amt: %v", err)
} }
pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight() aliceChanReservation, err := alice.InitChannelReservation(
aliceChanReservation, err := alice.InitChannelReservation(fundingAmt, fundingAmt, fundingAmt, pushAmt, feePerKw, feePerKw, bobPub,
fundingAmt, pushAmt, feePerKw, feeRate, bobPub, bobAddr, chainHash, bobAddr, chainHash, lnwire.FFAnnounceChannel,
lnwire.FFAnnounceChannel) )
if err != nil { if err != nil {
t.Fatalf("unable to init channel reservation: %v", err) t.Fatalf("unable to init channel reservation: %v", err)
} }
@ -712,9 +716,10 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
// Next, Bob receives the initial request, generates a corresponding // Next, Bob receives the initial request, generates a corresponding
// reservation initiation, then consume Alice's contribution. // reservation initiation, then consume Alice's contribution.
bobChanReservation, err := bob.InitChannelReservation(fundingAmt, 0, bobChanReservation, err := bob.InitChannelReservation(
pushAmt, feePerKw, feeRate, alicePub, aliceAddr, chainHash, fundingAmt, 0, pushAmt, feePerKw, feePerKw, alicePub, aliceAddr,
lnwire.FFAnnounceChannel) chainHash, lnwire.FFAnnounceChannel,
)
if err != nil { if err != nil {
t.Fatalf("unable to create bob reservation: %v", err) t.Fatalf("unable to create bob reservation: %v", err)
} }
@ -901,7 +906,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
Value: outputAmt, Value: outputAmt,
PkScript: script, PkScript: script,
} }
txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10) txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
if err != nil { if err != nil {
t.Fatalf("unable to send coinbase: %v", err) t.Fatalf("unable to send coinbase: %v", err)
} }
@ -1004,7 +1009,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
t.Fatalf("unable to make output script: %v", err) t.Fatalf("unable to make output script: %v", err)
} }
burnOutput := wire.NewTxOut(outputAmt, outputScript) burnOutput := wire.NewTxOut(outputAmt, outputScript)
burnTXID, err := alice.SendOutputs([]*wire.TxOut{burnOutput}, 10) burnTXID, err := alice.SendOutputs([]*wire.TxOut{burnOutput}, 2500)
if err != nil { if err != nil {
t.Fatalf("unable to create burn tx: %v", err) t.Fatalf("unable to create burn tx: %v", err)
} }
@ -1118,7 +1123,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
Value: outputAmt, Value: outputAmt,
PkScript: script, PkScript: script,
} }
txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10) txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
if err != nil { if err != nil {
t.Fatalf("unable to send coinbase: %v", err) t.Fatalf("unable to send coinbase: %v", err)
} }
@ -1318,7 +1323,7 @@ func testPublishTransaction(r *rpctest.Harness,
Value: btcutil.SatoshiPerBitcoin, Value: btcutil.SatoshiPerBitcoin,
PkScript: keyScript, PkScript: keyScript,
} }
txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 10) txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 2500)
if err != nil { if err != nil {
t.Fatalf("unable to create output: %v", err) t.Fatalf("unable to create output: %v", err)
} }
@ -1563,7 +1568,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
Value: btcutil.SatoshiPerBitcoin, Value: btcutil.SatoshiPerBitcoin,
PkScript: keyScript, PkScript: keyScript,
} }
txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 10) txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 2500)
if err != nil { if err != nil {
t.Fatalf("unable to create output: %v", err) t.Fatalf("unable to create output: %v", err)
} }
@ -1689,7 +1694,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
Value: 1e8, Value: 1e8,
PkScript: script, PkScript: script,
} }
txid, err := w.SendOutputs([]*wire.TxOut{output}, 10) txid, err := w.SendOutputs([]*wire.TxOut{output}, 2500)
if err != nil { if err != nil {
t.Fatalf("unable to send outputs: %v", err) t.Fatalf("unable to send outputs: %v", err)
} }
@ -2083,7 +2088,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
} }
case "neutrino": case "neutrino":
feeEstimator = lnwallet.StaticFeeEstimator{FeeRate: 250} feeEstimator = lnwallet.StaticFeeEstimator{FeePerKW: 62500}
// Set some package-level variable to speed up // Set some package-level variable to speed up
// operation for tests. // operation for tests.

@ -229,12 +229,11 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error)
return nil, nil, nil, err return nil, nil, nil, err
} }
estimator := &StaticFeeEstimator{24} estimator := &StaticFeeEstimator{FeePerKW: 6000}
feePerVSize, err := estimator.EstimateFeePerVSize(1) feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
feePerKw := feePerVSize.FeePerKWeight()
commitFee := calcStaticFee(0) commitFee := calcStaticFee(0)
aliceCommit := channeldb.ChannelCommitment{ aliceCommit := channeldb.ChannelCommitment{

@ -81,9 +81,9 @@ type initFundingReserveMsg struct {
// paying some multiple of the accepted base fee rate of the network. // paying some multiple of the accepted base fee rate of the network.
commitFeePerKw SatPerKWeight commitFeePerKw SatPerKWeight
// fundingFeePerVSize is the fee rate in sat/vbyte to use for the // fundingFeePerKw is the fee rate in sat/kw to use for the initial
// initial funding transaction. // funding transaction.
fundingFeePerVSize SatPerVByte fundingFeePerKw SatPerKWeight
// pushMSat is the number of milli-satoshis that should be pushed over // pushMSat is the number of milli-satoshis that should be pushed over
// the responder as part of the initial channel creation. // the responder as part of the initial channel creation.
@ -413,7 +413,7 @@ out:
// commitment transaction is valid. // commitment transaction is valid.
func (l *LightningWallet) InitChannelReservation( func (l *LightningWallet) InitChannelReservation(
capacity, ourFundAmt btcutil.Amount, pushMSat lnwire.MilliSatoshi, capacity, ourFundAmt btcutil.Amount, pushMSat lnwire.MilliSatoshi,
commitFeePerKw SatPerKWeight, fundingFeePerVSize SatPerVByte, commitFeePerKw SatPerKWeight, fundingFeePerKw SatPerKWeight,
theirID *btcec.PublicKey, theirAddr net.Addr, theirID *btcec.PublicKey, theirAddr net.Addr,
chainHash *chainhash.Hash, flags lnwire.FundingFlag) (*ChannelReservation, error) { chainHash *chainhash.Hash, flags lnwire.FundingFlag) (*ChannelReservation, error) {
@ -421,17 +421,17 @@ func (l *LightningWallet) InitChannelReservation(
respChan := make(chan *ChannelReservation, 1) respChan := make(chan *ChannelReservation, 1)
l.msgChan <- &initFundingReserveMsg{ l.msgChan <- &initFundingReserveMsg{
chainHash: chainHash, chainHash: chainHash,
nodeID: theirID, nodeID: theirID,
nodeAddr: theirAddr, nodeAddr: theirAddr,
fundingAmount: ourFundAmt, fundingAmount: ourFundAmt,
capacity: capacity, capacity: capacity,
commitFeePerKw: commitFeePerKw, commitFeePerKw: commitFeePerKw,
fundingFeePerVSize: fundingFeePerVSize, fundingFeePerKw: fundingFeePerKw,
pushMSat: pushMSat, pushMSat: pushMSat,
flags: flags, flags: flags,
err: errChan, err: errChan,
resp: respChan, resp: respChan,
} }
return <-respChan, <-errChan return <-respChan, <-errChan
@ -479,10 +479,10 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
// don't need to perform any coin selection. Otherwise, attempt to // don't need to perform any coin selection. Otherwise, attempt to
// obtain enough coins to meet the required funding amount. // obtain enough coins to meet the required funding amount.
if req.fundingAmount != 0 { if req.fundingAmount != 0 {
// Coin selection is done on the basis of sat-per-vbyte, we'll // Coin selection is done on the basis of sat/kw, so we'll use
// use the passed sat/vbyte passed in to perform coin selection. // the fee rate passed in to perform coin selection.
err := l.selectCoinsAndChange( err := l.selectCoinsAndChange(
req.fundingFeePerVSize, req.fundingAmount, req.fundingFeePerKw, req.fundingAmount,
reservation.ourContribution, reservation.ourContribution,
) )
if err != nil { if err != nil {
@ -1266,7 +1266,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
// within the passed contribution's inputs. If necessary, a change address will // within the passed contribution's inputs. If necessary, a change address will
// also be generated. // also be generated.
// TODO(roasbeef): remove hardcoded fees and req'd confs for outputs. // TODO(roasbeef): remove hardcoded fees and req'd confs for outputs.
func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerVByte, func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerKWeight,
amt btcutil.Amount, contribution *ChannelContribution) error { amt btcutil.Amount, contribution *ChannelContribution) error {
// We hold the coin select mutex while querying for outputs, and // We hold the coin select mutex while querying for outputs, and
@ -1276,7 +1276,7 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerVByte,
defer l.coinSelectMtx.Unlock() defer l.coinSelectMtx.Unlock()
walletLog.Infof("Performing funding tx coin selection using %v "+ walletLog.Infof("Performing funding tx coin selection using %v "+
"sat/vbyte as fee rate", int64(feeRate)) "sat/kw as fee rate", int64(feeRate))
// Find all unlocked unspent witness outputs with greater than 1 // Find all unlocked unspent witness outputs with greater than 1
// confirmation. // confirmation.
@ -1385,9 +1385,9 @@ func selectInputs(amt btcutil.Amount, coins []*Utxo) (btcutil.Amount, []*Utxo, e
// coinSelect attempts to select a sufficient amount of coins, including a // coinSelect attempts to select a sufficient amount of coins, including a
// change output to fund amt satoshis, adhering to the specified fee rate. The // change output to fund amt satoshis, adhering to the specified fee rate. The
// specified fee rate should be expressed in sat/vbyte for coin selection to // specified fee rate should be expressed in sat/kw for coin selection to
// function properly. // function properly.
func coinSelect(feeRate SatPerVByte, amt btcutil.Amount, func coinSelect(feeRate SatPerKWeight, amt btcutil.Amount,
coins []*Utxo) ([]*Utxo, btcutil.Amount, error) { coins []*Utxo) ([]*Utxo, btcutil.Amount, error) {
amtNeeded := amt amtNeeded := amt
@ -1431,7 +1431,8 @@ func coinSelect(feeRate SatPerVByte, amt btcutil.Amount,
// amount isn't enough to pay fees, then increase the requested // amount isn't enough to pay fees, then increase the requested
// coin amount by the estimate required fee, performing another // coin amount by the estimate required fee, performing another
// round of coin selection. // round of coin selection.
requiredFee := feeRate.FeeForVSize(int64(weightEstimate.VSize())) totalWeight := int64(weightEstimate.Weight())
requiredFee := feeRate.FeeForWeight(totalWeight)
if overShootAmt < requiredFee { if overShootAmt < requiredFee {
amtNeeded = amt + requiredFee amtNeeded = amt + requiredFee
continue continue

@ -226,7 +226,7 @@ func (*mockWalletController) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, e
} }
func (*mockWalletController) SendOutputs(outputs []*wire.TxOut, func (*mockWalletController) SendOutputs(outputs []*wire.TxOut,
_ lnwallet.SatPerVByte) (*chainhash.Hash, error) { _ lnwallet.SatPerKWeight) (*chainhash.Hash, error) {
return nil, nil return nil, nil
} }

@ -1658,18 +1658,13 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e
// In order to begin fee negotiations, we'll first compute our // In order to begin fee negotiations, we'll first compute our
// target ideal fee-per-kw. We'll set this to a lax value, as // target ideal fee-per-kw. We'll set this to a lax value, as
// we weren't the ones that initiated the channel closure. // we weren't the ones that initiated the channel closure.
feePerVSize, err := p.server.cc.feeEstimator.EstimateFeePerVSize(6) feePerKw, err := p.server.cc.feeEstimator.EstimateFeePerKW(6)
if err != nil { if err != nil {
peerLog.Errorf("unable to query fee estimator: %v", err) peerLog.Errorf("unable to query fee estimator: %v", err)
return nil, fmt.Errorf("unable to estimate fee") return nil, fmt.Errorf("unable to estimate fee")
} }
// We'll then convert the sat per weight to sat per k/w as this
// is the native unit used within the protocol when dealing
// with fees.
targetFeePerKw := feePerVSize.FeePerKWeight()
_, startingHeight, err := p.server.cc.chainIO.GetBestBlock() _, startingHeight, err := p.server.cc.chainIO.GetBestBlock()
if err != nil { if err != nil {
peerLog.Errorf("unable to obtain best block: %v", err) peerLog.Errorf("unable to obtain best block: %v", err)
@ -1685,7 +1680,7 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e
quit: p.quit, quit: p.quit,
}, },
deliveryAddr, deliveryAddr,
targetFeePerKw, feePerKw,
uint32(startingHeight), uint32(startingHeight),
nil, nil,
) )

@ -169,12 +169,11 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) {
dummyDeliveryScript), dummyDeliveryScript),
} }
estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} estimator := lnwallet.StaticFeeEstimator{FeePerKW: 12500}
feeRate, err := estimator.EstimateFeePerVSize(1) feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
feePerKw := feeRate.FeePerKWeight()
fee := responderChan.CalcFee(feePerKw) fee := responderChan.CalcFee(feePerKw)
closeSig, _, _, err := responderChan.CreateCloseProposal(fee, closeSig, _, _, err := responderChan.CreateCloseProposal(fee,
dummyDeliveryScript, initiatorDeliveryScript) dummyDeliveryScript, initiatorDeliveryScript)
@ -460,14 +459,12 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) {
msg: respShutdown, msg: respShutdown,
} }
estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} estimator := lnwallet.StaticFeeEstimator{FeePerKW: 12500}
initiatorIdealFeeRate, err := estimator.EstimateFeePerVSize(1) initiatorIdealFeeRate, err := estimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
t.Fatalf("unable to query fee estimator: %v", err) t.Fatalf("unable to query fee estimator: %v", err)
} }
initiatorIdealFee := responderChan.CalcFee( initiatorIdealFee := responderChan.CalcFee(initiatorIdealFeeRate)
initiatorIdealFeeRate.FeePerKWeight(),
)
increasedFee := btcutil.Amount(float64(initiatorIdealFee) * 2.5) increasedFee := btcutil.Amount(float64(initiatorIdealFee) * 2.5)
closeSig, _, _, err := responderChan.CreateCloseProposal( closeSig, _, _, err := responderChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, initiatorDeliveryScript, increasedFee, dummyDeliveryScript, initiatorDeliveryScript,

@ -80,7 +80,7 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey,
// With the connection established, we'll now establish our connection // With the connection established, we'll now establish our connection
// to the target peer, waiting for the first update before we exit. // to the target peer, waiting for the first update before we exit.
feePerVSize, err := c.server.cc.feeEstimator.EstimateFeePerVSize(3) feePerKw, err := c.server.cc.feeEstimator.EstimateFeePerKW(3)
if err != nil { if err != nil {
return err return err
} }
@ -88,8 +88,9 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey,
// TODO(halseth): make configurable? // TODO(halseth): make configurable?
minHtlc := lnwire.NewMSatFromSatoshis(1) minHtlc := lnwire.NewMSatFromSatoshis(1)
updateStream, errChan := c.server.OpenChannel(target, amt, 0, updateStream, errChan := c.server.OpenChannel(
minHtlc, feePerVSize, false, 0) target, amt, 0, minHtlc, feePerKw, false, 0,
)
select { select {
case err := <-errChan: case err := <-errChan:

@ -92,7 +92,7 @@ func getTestTXID(miner *rpctest.Harness) (*chainhash.Hash, error) {
PkScript: script, PkScript: script,
}, },
} }
return miner.SendOutputs(outputs, 10) return miner.SendOutputs(outputs, 2500)
} }
func locateOutput(tx *wire.MsgTx, script []byte) (*wire.OutPoint, *wire.TxOut, error) { func locateOutput(tx *wire.MsgTx, script []byte) (*wire.OutPoint, *wire.TxOut, error) {

@ -12,11 +12,8 @@ import (
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time"
"gopkg.in/macaroon-bakery.v2/bakery"
"sync/atomic" "sync/atomic"
"time"
"github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
@ -37,6 +34,7 @@ import (
"github.com/lightningnetwork/lnd/zpay32" "github.com/lightningnetwork/lnd/zpay32"
"github.com/tv42/zbase32" "github.com/tv42/zbase32"
"golang.org/x/net/context" "golang.org/x/net/context"
"gopkg.in/macaroon-bakery.v2/bakery"
) )
const ( const (
@ -402,7 +400,7 @@ func addrPairsToOutputs(addrPairs map[string]int64) ([]*wire.TxOut, error) {
// more addresses specified in the passed payment map. The payment map maps an // more addresses specified in the passed payment map. The payment map maps an
// address to a specified output value to be sent to that address. // address to a specified output value to be sent to that address.
func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
feeRate lnwallet.SatPerVByte) (*chainhash.Hash, error) { feeRate lnwallet.SatPerKWeight) (*chainhash.Hash, error) {
outputs, err := addrPairsToOutputs(paymentMap) outputs, err := addrPairsToOutputs(paymentMap)
if err != nil { if err != nil {
@ -412,18 +410,18 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
return r.server.cc.wallet.SendOutputs(outputs, feeRate) return r.server.cc.wallet.SendOutputs(outputs, feeRate)
} }
// determineFeePerVSize will determine the fee in sat/vbyte that should be paid // determineFeePerKw will determine the fee in sat/kw that should be paid given
// given an estimator, a confirmation target, and a manual value for sat/byte. // an estimator, a confirmation target, and a manual value for sat/byte. A value
// A value is chosen based on the two free parameters as one, or both of them // is chosen based on the two free parameters as one, or both of them can be
// can be zero. // zero.
func determineFeePerVSize(feeEstimator lnwallet.FeeEstimator, targetConf int32, func determineFeePerKw(feeEstimator lnwallet.FeeEstimator, targetConf int32,
feePerByte int64) (lnwallet.SatPerVByte, error) { feePerByte int64) (lnwallet.SatPerKWeight, error) {
switch { switch {
// If the target number of confirmations is set, then we'll use that to // If the target number of confirmations is set, then we'll use that to
// consult our fee estimator for an adequate fee. // consult our fee estimator for an adequate fee.
case targetConf != 0: case targetConf != 0:
feePerVSize, err := feeEstimator.EstimateFeePerVSize( feePerKw, err := feeEstimator.EstimateFeePerKW(
uint32(targetConf), uint32(targetConf),
) )
if err != nil { if err != nil {
@ -431,22 +429,24 @@ func determineFeePerVSize(feeEstimator lnwallet.FeeEstimator, targetConf int32,
"estimator: %v", err) "estimator: %v", err)
} }
return feePerVSize, nil return feePerKw, nil
// If a manual sat/byte fee rate is set, then we'll use that directly. // If a manual sat/byte fee rate is set, then we'll use that directly.
// We'll need to convert it to sat/kw as this is what we use internally.
case feePerByte != 0: case feePerByte != 0:
return lnwallet.SatPerVByte(feePerByte), nil feePerKB := lnwallet.SatPerKVByte(feePerByte * 1000)
return feePerKB.FeePerKWeight(), nil
// Otherwise, we'll attempt a relaxed confirmation target for the // Otherwise, we'll attempt a relaxed confirmation target for the
// transaction // transaction
default: default:
feePerVSize, err := feeEstimator.EstimateFeePerVSize(6) feePerKw, err := feeEstimator.EstimateFeePerKW(6)
if err != nil { if err != nil {
return 0, fmt.Errorf("unable to query fee "+ return 0, fmt.Errorf("unable to query fee estimator: "+
"estimator: %v", err) "%v", err)
} }
return feePerVSize, nil return feePerKw, nil
} }
} }
@ -457,18 +457,18 @@ func (r *rpcServer) SendCoins(ctx context.Context,
// Based on the passed fee related parameters, we'll determine an // Based on the passed fee related parameters, we'll determine an
// appropriate fee rate for this transaction. // appropriate fee rate for this transaction.
feeRate, err := determineFeePerVSize( feePerKw, err := determineFeePerKw(
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/vbyte=%v", rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/kw=%v", in.Addr,
in.Addr, btcutil.Amount(in.Amount), int64(feeRate)) btcutil.Amount(in.Amount), int64(feePerKw))
paymentMap := map[string]int64{in.Addr: in.Amount} paymentMap := map[string]int64{in.Addr: in.Amount}
txid, err := r.sendCoinsOnChain(paymentMap, feeRate) txid, err := r.sendCoinsOnChain(paymentMap, feePerKw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -484,18 +484,18 @@ func (r *rpcServer) SendMany(ctx context.Context,
in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) { in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) {
// Based on the passed fee related parameters, we'll determine an // Based on the passed fee related parameters, we'll determine an
// approriate fee rate for this transaction. // appropriate fee rate for this transaction.
feeRate, err := determineFeePerVSize( feePerKw, err := determineFeePerKw(
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
rpcsLog.Infof("[sendmany] outputs=%v, sat/vbyte=%v", rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v",
spew.Sdump(in.AddrToAmount), int64(feeRate)) spew.Sdump(in.AddrToAmount), int64(feePerKw))
txid, err := r.sendCoinsOnChain(in.AddrToAmount, feeRate) txid, err := r.sendCoinsOnChain(in.AddrToAmount, feePerKw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -794,15 +794,15 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
// Based on the passed fee related parameters, we'll determine an // Based on the passed fee related parameters, we'll determine an
// appropriate fee rate for the funding transaction. // appropriate fee rate for the funding transaction.
feeRate, err := determineFeePerVSize( feeRate, err := determineFeePerKw(
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
) )
if err != nil { if err != nil {
return err return err
} }
rpcsLog.Debugf("[openchannel]: using fee of %v sat/vbyte for funding "+ rpcsLog.Debugf("[openchannel]: using fee of %v sat/kw for funding tx",
"tx", int64(feeRate)) int64(feeRate))
// Instruct the server to trigger the necessary events to attempt to // Instruct the server to trigger the necessary events to attempt to
// open a new channel. A stream is returned in place, this stream will // open a new channel. A stream is returned in place, this stream will
@ -925,14 +925,14 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context,
// Based on the passed fee related parameters, we'll determine an // Based on the passed fee related parameters, we'll determine an
// appropriate fee rate for the funding transaction. // appropriate fee rate for the funding transaction.
feeRate, err := determineFeePerVSize( feeRate, err := determineFeePerKw(
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
rpcsLog.Tracef("[openchannel] target sat/vbyte for funding tx: %v", rpcsLog.Tracef("[openchannel] target sat/kw for funding tx: %v",
int64(feeRate)) int64(feeRate))
updateChan, errChan := r.server.OpenChannel( updateChan, errChan := r.server.OpenChannel(
@ -1109,25 +1109,16 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
// Based on the passed fee related parameters, we'll determine // Based on the passed fee related parameters, we'll determine
// an appropriate fee rate for the cooperative closure // an appropriate fee rate for the cooperative closure
// transaction. // transaction.
feeRate, err := determineFeePerVSize( feeRate, err := determineFeePerKw(
r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte,
) )
if err != nil { if err != nil {
return err return err
} }
rpcsLog.Debugf("Target sat/vbyte for closing transaction: %v", rpcsLog.Debugf("Target sat/kw for closing transaction: %v",
int64(feeRate)) int64(feeRate))
if feeRate == 0 {
// If the fee rate returned isn't usable, then we'll
// fall back to a lax fee estimate.
feeRate, err = r.server.cc.feeEstimator.EstimateFeePerVSize(6)
if err != nil {
return err
}
}
// Before we attempt the cooperative channel closure, we'll // Before we attempt the cooperative channel closure, we'll
// examine the channel to ensure that it doesn't have a // examine the channel to ensure that it doesn't have a
// lingering HTLC. // lingering HTLC.
@ -1140,9 +1131,8 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
// cooperative channel closure. So we'll forward the request to // cooperative channel closure. So we'll forward the request to
// the htlc switch which will handle the negotiation and // the htlc switch which will handle the negotiation and
// broadcast details. // broadcast details.
feePerKw := feeRate.FeePerKWeight()
updateChan, errChan = r.server.htlcSwitch.CloseLink( updateChan, errChan = r.server.htlcSwitch.CloseLink(
chanPoint, htlcswitch.CloseRegular, feePerKw, chanPoint, htlcswitch.CloseRegular, feeRate,
) )
} }
out: out:

@ -2537,7 +2537,7 @@ type openChanReq struct {
pushAmt lnwire.MilliSatoshi pushAmt lnwire.MilliSatoshi
fundingFeePerVSize lnwallet.SatPerVByte fundingFeePerKw lnwallet.SatPerKWeight
private bool private bool
@ -2685,7 +2685,7 @@ func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error {
// NOTE: This function is safe for concurrent access. // NOTE: This function is safe for concurrent access.
func (s *server) OpenChannel(nodeKey *btcec.PublicKey, func (s *server) OpenChannel(nodeKey *btcec.PublicKey,
localAmt btcutil.Amount, pushAmt, minHtlc lnwire.MilliSatoshi, localAmt btcutil.Amount, pushAmt, minHtlc lnwire.MilliSatoshi,
fundingFeePerVSize lnwallet.SatPerVByte, private bool, fundingFeePerKw lnwallet.SatPerKWeight, private bool,
remoteCsvDelay uint16) (chan *lnrpc.OpenStatusUpdate, chan error) { remoteCsvDelay uint16) (chan *lnrpc.OpenStatusUpdate, chan error) {
// The updateChan will have a buffer of 2, since we expect a // The updateChan will have a buffer of 2, since we expect a
@ -2723,9 +2723,9 @@ func (s *server) OpenChannel(nodeKey *btcec.PublicKey,
// If the fee rate wasn't specified, then we'll use a default // If the fee rate wasn't specified, then we'll use a default
// confirmation target. // confirmation target.
if fundingFeePerVSize == 0 { if fundingFeePerKw == 0 {
estimator := s.cc.feeEstimator estimator := s.cc.feeEstimator
fundingFeePerVSize, err = estimator.EstimateFeePerVSize(6) fundingFeePerKw, err = estimator.EstimateFeePerKW(6)
if err != nil { if err != nil {
errChan <- err errChan <- err
return updateChan, errChan return updateChan, errChan
@ -2737,16 +2737,16 @@ func (s *server) OpenChannel(nodeKey *btcec.PublicKey,
// instead of blocking on this request which is exported as a // instead of blocking on this request which is exported as a
// synchronous request to the outside world. // synchronous request to the outside world.
req := &openChanReq{ req := &openChanReq{
targetPubkey: nodeKey, targetPubkey: nodeKey,
chainHash: *activeNetParams.GenesisHash, chainHash: *activeNetParams.GenesisHash,
localFundingAmt: localAmt, localFundingAmt: localAmt,
fundingFeePerVSize: fundingFeePerVSize, fundingFeePerKw: fundingFeePerKw,
pushAmt: pushAmt, pushAmt: pushAmt,
private: private, private: private,
minHtlc: minHtlc, minHtlc: minHtlc,
remoteCsvDelay: remoteCsvDelay, remoteCsvDelay: remoteCsvDelay,
updates: updateChan, updates: updateChan,
err: errChan, err: errChan,
} }
// TODO(roasbeef): pass in chan that's closed if/when funding succeeds // TODO(roasbeef): pass in chan that's closed if/when funding succeeds

@ -201,12 +201,11 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
return nil, nil, nil, nil, err return nil, nil, nil, nil, err
} }
estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 12500}
feePerVSize, err := estimator.EstimateFeePerVSize(1) feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil { if err != nil {
return nil, nil, nil, nil, err return nil, nil, nil, nil, err
} }
feePerKw := feePerVSize.FeePerKWeight()
// TODO(roasbeef): need to factor in commit fee? // TODO(roasbeef): need to factor in commit fee?
aliceCommit := channeldb.ChannelCommitment{ aliceCommit := channeldb.ChannelCommitment{

@ -994,15 +994,15 @@ func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput,
utxnLog.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+ utxnLog.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+
"inputs", len(csvOutputs), len(cltvOutputs)) "inputs", len(csvOutputs), len(cltvOutputs))
txVSize := int64(weightEstimate.VSize()) txWeight := int64(weightEstimate.Weight())
return u.populateSweepTx(txVSize, classHeight, csvOutputs, cltvOutputs) return u.populateSweepTx(txWeight, classHeight, csvOutputs, cltvOutputs)
} }
// populateSweepTx populate the final sweeping transaction with all witnesses // populateSweepTx populate the final sweeping transaction with all witnesses
// in place for all inputs using the provided txn fee. The created transaction // in place for all inputs using the provided txn fee. The created transaction
// has a single output sending all the funds back to the source wallet, after // has a single output sending all the funds back to the source wallet, after
// accounting for the fee estimate. // accounting for the fee estimate.
func (u *utxoNursery) populateSweepTx(txVSize int64, classHeight uint32, func (u *utxoNursery) populateSweepTx(txWeight int64, classHeight uint32,
csvInputs []CsvSpendableOutput, csvInputs []CsvSpendableOutput,
cltvInputs []SpendableOutput) (*wire.MsgTx, error) { cltvInputs []SpendableOutput) (*wire.MsgTx, error) {
@ -1022,11 +1022,11 @@ func (u *utxoNursery) populateSweepTx(txVSize int64, classHeight uint32,
} }
// Using the txn weight estimate, compute the required txn fee. // Using the txn weight estimate, compute the required txn fee.
feePerVSize, err := u.cfg.Estimator.EstimateFeePerVSize(6) feePerKw, err := u.cfg.Estimator.EstimateFeePerKW(6)
if err != nil { if err != nil {
return nil, err return nil, err
} }
txFee := feePerVSize.FeeForVSize(txVSize) txFee := feePerKw.FeeForWeight(txWeight)
// Sweep as much possible, after subtracting txn fees. // Sweep as much possible, after subtracting txn fees.
sweepAmt := int64(totalSum - txFee) sweepAmt := int64(totalSum - txFee)