diff --git a/breacharbiter.go b/breacharbiter.go index 0862acd827..a5ea1a5f96 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -1015,13 +1015,13 @@ func (b *breachArbiter) createJusticeTx( spendableOutputs = append(spendableOutputs, input) } - txVSize := int64(weightEstimate.VSize()) - return b.sweepSpendableOutputsTxn(txVSize, spendableOutputs...) + txWeight := int64(weightEstimate.Weight()) + return b.sweepSpendableOutputsTxn(txWeight, spendableOutputs...) } // sweepSpendableOutputsTxn creates a signed transaction from a sequence of // 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) { // 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 // 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 { return nil, err } - txFee := feePerVSize.FeeForVSize(txVSize) + txFee := feePerKw.FeeForWeight(txWeight) // TODO(roasbeef): already start to siphon their funds into fees sweepAmt := int64(totalAmt - txFee) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 03de6b16fc..8f6c90a33f 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -1351,7 +1351,7 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent, ba := newBreachArbiter(&BreachConfig{ CloseLink: func(_ *wire.OutPoint, _ htlcswitch.ChannelCloseType) {}, DB: db, - Estimator: &lnwallet.StaticFeeEstimator{FeeRate: 50}, + Estimator: &lnwallet.StaticFeeEstimator{FeePerKW: 12500}, GenSweepScript: func() ([]byte, error) { return nil, nil }, ContractBreaches: contractBreaches, Signer: signer, @@ -1491,12 +1491,11 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa return nil, nil, nil, err } - estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 12500} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() // TODO(roasbeef): need to factor in commit fee? aliceCommit := channeldb.ChannelCommitment{ diff --git a/chainregistry.go b/chainregistry.go index 873efaa27c..efde6fba7e 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -37,15 +37,21 @@ const ( defaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000) defaultBitcoinFeeRate = lnwire.MilliSatoshi(1) defaultBitcoinTimeLockDelta = 144 - defaultBitcoinStaticFeeRate = lnwallet.SatPerVByte(50) defaultLitecoinMinHTLCMSat = lnwire.MilliSatoshi(1000) defaultLitecoinBaseFeeMSat = lnwire.MilliSatoshi(1000) defaultLitecoinFeeRate = lnwire.MilliSatoshi(1) defaultLitecoinTimeLockDelta = 576 - defaultLitecoinStaticFeeRate = lnwallet.SatPerVByte(200) 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 // payments when running on the Litecoin chain. btcToLtcConversionRate = 60 @@ -141,7 +147,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, TimeLockDelta: cfg.Bitcoin.TimeLockDelta, } cc.feeEstimator = lnwallet.StaticFeeEstimator{ - FeeRate: defaultBitcoinStaticFeeRate, + FeePerKW: defaultBitcoinStaticFeePerKW, } case litecoinChain: cc.routingPolicy = htlcswitch.ForwardingPolicy{ @@ -151,7 +157,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, TimeLockDelta: cfg.Litecoin.TimeLockDelta, } cc.feeEstimator = lnwallet.StaticFeeEstimator{ - FeeRate: defaultLitecoinStaticFeeRate, + FeePerKW: defaultLitecoinStaticFeePerKW, } default: 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 // use live fee estimates, rather than a statically // coded value. - fallBackFeeRate := lnwallet.SatPerVByte(25) + fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( - *rpcConfig, fallBackFeeRate, + *rpcConfig, fallBackFeeRate.FeePerKWeight(), ) if err != nil { 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 // use live fee estimates, rather than a statically // coded value. - fallBackFeeRate := lnwallet.SatPerVByte(25) + fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( - *rpcConfig, fallBackFeeRate, + *rpcConfig, fallBackFeeRate.FeePerKWeight(), ) if err != nil { 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 // live fee estimates, rather than a statically coded // value. - fallBackFeeRate := lnwallet.SatPerVByte(25) + fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) cc.feeEstimator, err = lnwallet.NewBtcdFeeEstimator( - *rpcConfig, fallBackFeeRate, + *rpcConfig, fallBackFeeRate.FeePerKWeight(), ) if err != nil { return nil, nil, err diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index 086b8d26c0..b525b5dfd0 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -451,27 +451,27 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { 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. // // TODO(roasbeef): signal up if fee would be too large // to sweep singly, need to batch - feePerVSize, err := h.FeeEstimator.EstimateFeePerVSize(6) + feePerKw, err := h.FeeEstimator.EstimateFeePerKW(6) if err != nil { 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, - h.payHash[:], int64(feePerVSize)) + h.payHash[:], int64(feePerKw)) // Using a weight estimator, we'll compute the total // fee required, and from that the value we'll end up // with. - totalVSize := (&lnwallet.TxWeightEstimator{}). + totalWeight := (&lnwallet.TxWeightEstimator{}). AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize). - AddP2WKHOutput().VSize() - totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) + AddP2WKHOutput().Weight() + totalFees := feePerKw.FeeForWeight(int64(totalWeight)) sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value - int64(totalFees) @@ -1253,18 +1253,18 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // First, we'll estimate the total weight so we can compute // fees properly. We'll use a lax estimate, as this output is // in no immediate danger. - feePerVSize, err := c.FeeEstimator.EstimateFeePerVSize(6) + feePerKw, err := c.FeeEstimator.EstimateFeePerKW(6) if err != nil { return nil, err } - log.Debugf("%T(%v): using %v sat/vsize for sweep tx", c, - c.chanPoint, int64(feePerVSize)) + log.Debugf("%T(%v): using %v sat/kw for sweep tx", c, + c.chanPoint, int64(feePerKw)) - totalVSize := (&lnwallet.TxWeightEstimator{}). - AddP2PKHInput(). - AddP2WKHOutput().VSize() - totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) + totalWeight := (&lnwallet.TxWeightEstimator{}). + AddP2WKHInput(). + AddP2WKHOutput().Weight() + totalFees := feePerKw.FeeForWeight(int64(totalWeight)) sweepAmt := signDesc.Output.Value - int64(totalFees) c.sweepTx = wire.NewMsgTx(2) diff --git a/fundingmanager.go b/fundingmanager.go index fa414545a5..8576205c0e 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -8,10 +8,6 @@ import ( "sync/atomic" "time" - "google.golang.org/grpc" - - "golang.org/x/crypto/salsa20" - "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -28,6 +24,8 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "golang.org/x/crypto/salsa20" + "google.golang.org/grpc" ) const ( @@ -68,11 +66,6 @@ const ( // currently accepted on the Litecoin chain within the Lightning // Protocol. 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 ( @@ -1028,8 +1021,8 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { reservation, err := f.cfg.Wallet.InitChannelReservation( amt, 0, msg.PushAmount, lnwallet.SatPerKWeight(msg.FeePerKiloWeight), 0, - fmsg.peer.IdentityKey(), fmsg.peer.Address(), - &chainHash, msg.ChannelFlags, + fmsg.peer.IdentityKey(), fmsg.peer.Address(), &chainHash, + msg.ChannelFlags, ) if err != nil { fndgLog.Errorf("Unable to initialize reservation: %v", err) @@ -2543,23 +2536,12 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // 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 // 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 { msg.err <- err 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 // be announced to the network. var channelFlags lnwire.FundingFlag @@ -2573,7 +2555,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // request will fail, and be aborted. reservation, err := f.cfg.Wallet.InitChannelReservation( capacity, localAmt, msg.pushAmt, commitFeePerKw, - msg.fundingFeePerVSize, peerKey, msg.peer.Address(), + msg.fundingFeePerKw, peerKey, msg.peer.Address(), &msg.chainHash, channelFlags, ) if err != nil { diff --git a/fundingmanager_test.go b/fundingmanager_test.go index deb08f0f2a..d37f219541 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -235,7 +235,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, addr *lnwire.NetAddress, tempTestDir string) (*testNode, error) { netParams := activeNetParams.Params - estimator := lnwallet.StaticFeeEstimator{FeeRate: 250} + estimator := lnwallet.StaticFeeEstimator{FeePerKW: 62500} chainNotifier := &mockNotifier{ oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1), diff --git a/htlcswitch/link.go b/htlcswitch/link.go index b75f3af515..df33da84e6 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -35,11 +35,6 @@ const ( // TODO(roasbeef): must be < default delta 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 // which a link should propose to update its commitment fee rate. 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 // transactions, and the second-level HTLC transactions. func (l *channelLink) sampleNetworkFee() (lnwallet.SatPerKWeight, error) { - // We'll first query for the sat/vbyte recommended to be confirmed - // within 3 blocks. - feePerVSize, err := l.cfg.FeeEstimator.EstimateFeePerVSize(3) + // We'll first query for the sat/kw recommended to be confirmed within 3 + // blocks. + feePerKw, err := l.cfg.FeeEstimator.EstimateFeePerKW(3) if err != nil { 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 "+ "sat/kw", l, int64(feePerKw)) diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index bd4dc5972c..24b80530aa 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -1726,14 +1726,11 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) { coreLink.cfg.HodlMask = hodl.MaskFromFlags(hodl.ExitSettle) coreLink.cfg.DebugHTLC = true - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) @@ -2140,14 +2137,11 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) { aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() var htlcID uint64 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 // correctness of Alice's bandwidth when forwarding HTLCs. - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( - feeRate.FeePerKWeight().FeeForWeight(lnwallet.HtlcWeight), + feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) // 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 // correctness of Alice's bandwidth when forwarding HTLCs. - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( - feeRate.FeePerKWeight().FeeForWeight(lnwallet.HtlcWeight), + feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) // 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 ) - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) @@ -3460,15 +3447,9 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) { 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. select { - case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte: + case n.feeEstimator.byteFeeIn <- startingFeeRate: case <-time.After(time.Second * 5): t.Fatalf("alice didn't query for the new network fee") } @@ -3497,7 +3478,7 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) { // fee update. newFeeRate := startingFeeRate * 3 select { - case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte * 3: + case n.feeEstimator.byteFeeIn <- newFeeRate: case <-time.After(time.Second * 5): t.Fatalf("alice didn't query for the new network fee") } diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 9d3a293895..3dcfa87633 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -58,12 +58,14 @@ func (m *mockPreimageCache) SubscribeUpdates() *contractcourt.WitnessSubscriptio } type mockFeeEstimator struct { - byteFeeIn chan lnwallet.SatPerVByte + byteFeeIn chan lnwallet.SatPerKWeight quit chan struct{} } -func (m *mockFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (lnwallet.SatPerVByte, error) { +func (m *mockFeeEstimator) EstimateFeePerKW( + numBlocks uint32) (lnwallet.SatPerKWeight, error) { + select { case feeRate := <-m.byteFeeIn: return feeRate, nil diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 46bf718f0c..7838e12920 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -271,14 +271,11 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, return nil, nil, nil, nil, err } - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() commitFee := feePerKw.FeeForWeight(724) const broadcastHeight = 1 @@ -873,7 +870,7 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel, carolDecoder := newMockIteratorDecoder() feeEstimator := &mockFeeEstimator{ - byteFeeIn: make(chan lnwallet.SatPerVByte), + byteFeeIn: make(chan lnwallet.SatPerKWeight), quit: make(chan struct{}), } diff --git a/lntest/harness.go b/lntest/harness.go index 517645b2db..60c0f4ef49 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -172,7 +172,8 @@ func (n *NetworkHarness) SetUp(lndArgs []string) error { PkScript: addrScript, 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 } } @@ -1159,7 +1160,8 @@ func (n *NetworkHarness) sendCoins(ctx context.Context, amt btcutil.Amount, PkScript: addrScript, 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 } diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index cc946e428d..92bcbfa25a 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -272,11 +272,11 @@ func (b *BtcWallet) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) { // // This is a part of the WalletController interface. 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 - // this up to sat/KB as the SendOutputs method requires this unit. - feeSatPerKB := btcutil.Amount(feeRate * 1000) + // Convert our fee rate from sat/kw to sat/kb since it's required by + // SendOutputs. + feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte()) return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB) } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 9da2ee9191..b596befbb8 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -1131,12 +1131,11 @@ func TestHTLCSigNumber(t *testing.T) { } // Calculate two values that will be below and above Bob's dust limit. - estimator := &StaticFeeEstimator{24} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to get fee: %v", err) } - feePerKw := feePerVSize.FeePerKWeight() belowDust := btcutil.Amount(500) + htlcTimeoutFee(feePerKw) aboveDust := btcutil.Amount(1400) + htlcSuccessFee(feePerKw) diff --git a/lnwallet/fee_estimator.go b/lnwallet/fee_estimator.go index 078e6db714..c0574c5ddd 100644 --- a/lnwallet/fee_estimator.go +++ b/lnwallet/fee_estimator.go @@ -8,38 +8,49 @@ import ( "github.com/btcsuite/btcutil" ) -// SatPerVByte represents a fee rate in satoshis per vbyte. -type SatPerVByte btcutil.Amount +const ( + // FeePerKwFloor is the lowest fee rate in sat/kw that we should use for + // determining transaction fees. + FeePerKwFloor SatPerKWeight = 253 +) + +// SatPerKVByte represents a fee rate in sat/kb. +type SatPerKVByte btcutil.Amount -// FeeForVSize calculates the fee resulting from this fee rate and -// the given vsize in vbytes. -func (s SatPerVByte) FeeForVSize(vbytes int64) btcutil.Amount { - return btcutil.Amount(s) * btcutil.Amount(vbytes) +// 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. -func (s SatPerVByte) FeePerKWeight() SatPerKWeight { - return SatPerKWeight(s * 1000 / blockchain.WitnessScaleFactor) +// FeePerKWeight converts the current fee rate from sat/kb to sat/kw. +func (s SatPerKVByte) FeePerKWeight() SatPerKWeight { + 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 -// FeeForWeight calculates the fee resulting from this fee rate and the -// given weight in weight units (wu). +// FeeForWeight calculates the fee resulting from this fee rate and the given +// weight in weight units (wu). func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount { // The resulting fee is rounded down, as specified in BOLT#03. return btcutil.Amount(s) * btcutil.Amount(wu) / 1000 } +// 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 // various combinations of transaction sizes and desired confirmation time // (measured by number of blocks). type FeeEstimator interface { - // EstimateFeePerVSize takes in a target for the number of blocks until - // an initial confirmation and returns the estimated fee expressed in - // satoshis/vbyte. - EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) + // EstimateFeePerKW takes in a target for the number of blocks until an + // initial confirmation and returns the estimated fee expressed in + // sat/kw. + EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) // Start signals the FeeEstimator to start any processes or goroutines // 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 // implementation. 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. - 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. -func (e StaticFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { - return e.FeeRate, nil +func (e StaticFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) { + return e.FeePerKW, nil } // 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 // any fee estimation requests to btcd's RPC interface. type BtcdFeeEstimator struct { - // fallBackFeeRate is the fall back fee rate in satoshis per vbyte that - // is returned if the fee estimator does not yet have enough data to - // actually produce fee estimates. - fallBackFeeRate SatPerVByte + // fallbackFeePerKW is the fall back fee rate in sat/kw that is returned + // if the fee estimator does not yet have enough data to actually + // produce fee estimates. + fallbackFeePerKW SatPerKWeight - // minFeeRate is the minimum relay fee, in sat/vbyte, of the backend - // node. This will be used as the default fee rate of a transaction when - // the estimated fee rate is too low to allow the transaction to - // propagate through the network. - minFeeRate SatPerVByte + // minFeePerKW is the minimum fee, in sat/kw, that we should enforce. + // This will be used as the default fee rate for a transaction when the + // estimated fee rate is too low to allow the transaction to propagate + // through the network. + minFeePerKW SatPerKWeight btcdConn *rpcclient.Client } @@ -110,7 +121,7 @@ type BtcdFeeEstimator struct { // the occasion that the estimator has insufficient data, or returns zero for a // fee estimate. func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig, - fallBackFeeRate SatPerVByte) (*BtcdFeeEstimator, error) { + fallBackFeeRate SatPerKWeight) (*BtcdFeeEstimator, error) { rpcConfig.DisableConnectOnNew = true rpcConfig.DisableAutoReconnect = false @@ -120,8 +131,8 @@ func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig, } return &BtcdFeeEstimator{ - fallBackFeeRate: fallBackFeeRate, - btcdConn: chainConn, + fallbackFeePerKW: fallBackFeeRate, + btcdConn: chainConn, }, nil } @@ -146,9 +157,20 @@ func (b *BtcdFeeEstimator) Start() error { return err } - // The fee rate is expressed in sat/KB, so we'll manually convert it to - // our desired sat/vbyte rate. - b.minFeeRate = SatPerVByte(relayFee / 1000) + // The fee rate is expressed in sat/kb, so we'll manually convert it to + // our desired sat/kw rate. + 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 } @@ -163,13 +185,12 @@ func (b *BtcdFeeEstimator) Stop() error { return nil } -// EstimateFeePerVSize takes in a target for the number of blocks until an -// initial confirmation and returns the estimated fee expressed in -// satoshis/vbyte. +// EstimateFeePerKW takes in a target for the number of blocks until an initial +// confirmation and returns the estimated fee expressed in sat/kw. // // NOTE: This method is part of the FeeEstimator interface. -func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { - feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) +func (b *BtcdFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) { + feeEstimate, err := b.fetchEstimate(numBlocks) switch { // If the estimator doesn't have enough data, or returns an error, then // to return a proper value, then we'll return the default fall back @@ -179,16 +200,15 @@ func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, e fallthrough case feeEstimate == 0: - return b.fallBackFeeRate, nil + return b.fallbackFeePerKW, nil } return feeEstimate, nil } // fetchEstimate returns a fee estimate for a transaction to be confirmed in -// confTarget blocks. The estimate is returned in sat/vbyte. -func (b *BtcdFeeEstimator) fetchEstimatePerVSize( - confTarget uint32) (SatPerVByte, error) { +// confTarget blocks. The estimate is returned in sat/kw. +func (b *BtcdFeeEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) { // First, we'll fetch the estimate for our confirmation target. btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget)) if err != nil { @@ -202,23 +222,21 @@ func (b *BtcdFeeEstimator) fetchEstimatePerVSize( return 0, err } - // The value returned is expressed in fees per KB, while we want - // fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte - // before returning the estimate. - satPerByte := SatPerVByte(satPerKB / 1000) - - // Before proceeding, we'll make sure that this fee rate respects the - // minimum relay fee set on the backend node. - if satPerByte < b.minFeeRate { - walletLog.Debugf("Using backend node's minimum relay fee rate "+ - "of %v sat/vbyte", b.minFeeRate) - satPerByte = b.minFeeRate + // Since we use fee rates in sat/kw internally, we'll convert the + // estimated fee rate from its sat/kb representation to sat/kw. + satPerKw := SatPerKVByte(satPerKB).FeePerKWeight() + + // Finally, we'll enforce our fee floor. + if satPerKw < b.minFeePerKW { + walletLog.Debugf("Estimated fee rate of %v sat/kw is too low, "+ + "using fee floor of %v sat/kw instead", b.minFeePerKW) + satPerKw = b.minFeePerKW } - walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", - int64(satPerByte), confTarget) + walletLog.Debugf("Returning %v sat/kw for conf target of %v", + int64(satPerKw), confTarget) - return satPerByte, nil + return satPerKw, nil } // 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 // will proxy any fee estimation requests to bitcoind's RPC interface. type BitcoindFeeEstimator struct { - // fallBackFeeRate is the fall back fee rate in satoshis per vbyte that - // is returned if the fee estimator does not yet have enough data to - // actually produce fee estimates. - fallBackFeeRate SatPerVByte + // fallbackFeePerKW is the fallback fee rate in sat/kw that is returned + // if the fee estimator does not yet have enough data to actually + // produce fee estimates. + fallbackFeePerKW SatPerKWeight - // minFeeRate is the minimum relay fee, in sat/vbyte, of the backend - // node. This will be used as the default fee rate of a transaction when - // the estimated fee rate is too low to allow the transaction to - // propagate through the network. - minFeeRate SatPerVByte + // minFeePerKW is the minimum fee, in sat/kw, that we should enforce. + // This will be used as the default fee rate for a transaction when the + // estimated fee rate is too low to allow the transaction to propagate + // through the network. + minFeePerKW SatPerKWeight bitcoindConn *rpcclient.Client } @@ -249,7 +267,7 @@ type BitcoindFeeEstimator struct { // is used in the occasion that the estimator has insufficient data, or returns // zero for a fee estimate. func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig, - fallBackFeeRate SatPerVByte) (*BitcoindFeeEstimator, error) { + fallBackFeeRate SatPerKWeight) (*BitcoindFeeEstimator, error) { rpcConfig.DisableConnectOnNew = true rpcConfig.DisableAutoReconnect = false @@ -261,8 +279,8 @@ func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig, } return &BitcoindFeeEstimator{ - fallBackFeeRate: fallBackFeeRate, - bitcoindConn: chainConn, + fallbackFeePerKW: fallBackFeeRate, + bitcoindConn: chainConn, }, nil } @@ -293,9 +311,20 @@ func (b *BitcoindFeeEstimator) Start() error { return err } - // The fee rate is expressed in sat/KB, so we'll manually convert it to - // our desired sat/vbyte rate. - b.minFeeRate = SatPerVByte(relayFee / 1000) + // The fee rate is expressed in sat/kb, so we'll manually convert it to + // our desired sat/kw rate. + 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 } @@ -308,13 +337,12 @@ func (b *BitcoindFeeEstimator) Stop() error { return nil } -// EstimateFeePerVSize takes in a target for the number of blocks until an -// initial confirmation and returns the estimated fee expressed in -// satoshis/vbyte. +// EstimateFeePerKW takes in a target for the number of blocks until an initial +// confirmation and returns the estimated fee expressed in sat/kw. // // NOTE: This method is part of the FeeEstimator interface. -func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { - feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) +func (b *BitcoindFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) { + feeEstimate, err := b.fetchEstimate(numBlocks) switch { // If the estimator doesn't have enough data, or returns an error, then // to return a proper value, then we'll return the default fall back @@ -324,16 +352,15 @@ func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByt fallthrough case feeEstimate == 0: - return b.fallBackFeeRate, nil + return b.fallbackFeePerKW, nil } return feeEstimate, nil } -// fetchEstimatePerVSize returns a fee estimate for a transaction to be confirmed in -// confTarget blocks. The estimate is returned in sat/vbyte. -func (b *BitcoindFeeEstimator) fetchEstimatePerVSize( - confTarget uint32) (SatPerVByte, error) { +// fetchEstimate returns a fee estimate for a transaction to be confirmed in +// confTarget blocks. The estimate is returned in sat/kw. +func (b *BitcoindFeeEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) { // First, we'll send an "estimatesmartfee" command as a raw request, // since it isn't supported by btcd but is available in bitcoind. target, err := json.Marshal(uint64(confTarget)) @@ -341,45 +368,44 @@ func (b *BitcoindFeeEstimator) fetchEstimatePerVSize( return 0, err } // TODO: Allow selection of economical/conservative modifiers. - resp, err := b.bitcoindConn.RawRequest("estimatesmartfee", - []json.RawMessage{target}) + resp, err := b.bitcoindConn.RawRequest( + "estimatesmartfee", []json.RawMessage{target}, + ) if err != nil { return 0, err } // Next, we'll parse the response to get the BTC per KB. feeEstimate := struct { - Feerate float64 `json:"feerate"` + FeeRate float64 `json:"feerate"` }{} err = json.Unmarshal(resp, &feeEstimate) if err != nil { return 0, err } - // Next, we'll convert the returned value to satoshis, as it's - // currently returned in BTC. - satPerKB, err := btcutil.NewAmount(feeEstimate.Feerate) + // Next, we'll convert the returned value to satoshis, as it's currently + // returned in BTC. + satPerKB, err := btcutil.NewAmount(feeEstimate.FeeRate) if err != nil { return 0, err } - // The value returned is expressed in fees per KB, while we want - // fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte - // before returning the estimate. - satPerByte := SatPerVByte(satPerKB / 1000) - - // Before proceeding, we'll make sure that this fee rate respects the - // minimum relay fee set on the backend node. - if satPerByte < b.minFeeRate { - walletLog.Debugf("Using backend node's minimum relay fee rate "+ - "of %v sat/vbyte", b.minFeeRate) - satPerByte = b.minFeeRate + // Since we use fee rates in sat/kw internally, we'll convert the + // estimated fee rate from its sat/kb representation to sat/kw. + satPerKw := SatPerKVByte(satPerKB).FeePerKWeight() + + // Finally, we'll enforce our fee floor. + if satPerKw < b.minFeePerKW { + walletLog.Debugf("Estimated fee rate of %v sat/kw is too low, "+ + "using fee floor of %v sat/kw instead", b.minFeePerKW) + satPerKw = b.minFeePerKW } - walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", - int64(satPerByte), confTarget) + walletLog.Debugf("Returning %v sat/kw for conf target of %v", + int64(satPerKw), confTarget) - return satPerByte, nil + return satPerKw, nil } // A compile-time assertion to ensure that BitcoindFeeEstimator implements the diff --git a/lnwallet/fee_estimator_test.go b/lnwallet/fee_estimator_test.go index 0fa97eadb0..d4608b9b28 100644 --- a/lnwallet/fee_estimator_test.go +++ b/lnwallet/fee_estimator_test.go @@ -13,57 +13,56 @@ import ( func TestFeeRateTypes(t *testing.T) { t.Parallel() - // Let our fee rate be 100 sat/vbyte. - feePerVSize := lnwallet.SatPerVByte(100) - - // It is also equivalent to 25000 sat/kw. - feePerKw := feePerVSize.FeePerKWeight() - if feePerKw != 25000 { - t.Fatalf("expected %d sat/kw, got %d sat/kw", 25000, - 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) + // We'll be calculating the transaction fees for the given measurements + // using different fee rates and expecting them to match. + const vsize = 300 + const weight = vsize * 4 + + // Test the conversion from sat/kw to sat/kb. + for feePerKw := lnwallet.SatPerKWeight(250); feePerKw < 10000; feePerKw += 50 { + feePerKB := feePerKw.FeePerKVByte() + if feePerKB != lnwallet.SatPerKVByte(feePerKw*4) { + t.Fatalf("expected %d sat/kb, got %d sat/kb when "+ + "converting from %d sat/kw", feePerKw*4, + feePerKB, feePerKw) } - // The tx fee should simply be f*txvsize. - fee := f.FeeForVSize(txVSize) - if fee != btcutil.Amount(f*txVSize) { - t.Fatalf("expected tx fee to be %d sat, was %d sat", - f*txVSize, fee) + // The resulting transaction fee should be the same when using + // both rates. + expectedFee := btcutil.Amount(feePerKw * weight / 1000) + fee1 := feePerKw.FeeForWeight(weight) + if fee1 != expectedFee { + t.Fatalf("expected fee of %d sats, got %d sats", + expectedFee, fee1) } - - // The weight is 4*vsize. Fee calculation from the fee/kw - // should result in the same fee. - fee2 := fPerKw.FeeForWeight(txVSize * 4) - if fee != fee2 { - t.Fatalf("fee calculated from vsize (%d) not equal "+ - "fee calculated from weight (%d)", fee, fee2) + fee2 := feePerKB.FeeForVSize(vsize) + if fee2 != expectedFee { + t.Fatalf("expected fee of %d sats, got %d sats", + expectedFee, fee2) } } - // Do the same for fee per kw. - for f := lnwallet.SatPerKWeight(0); f < 1500; f++ { - weight := int64(txVSize * 4) + // Test the conversion from sat/kb to sat/kw. + for feePerKB := lnwallet.SatPerKVByte(1000); feePerKB < 40000; feePerKB += 1000 { + 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 - // denominated per 1000 wu. - expFee := btcutil.Amount(weight) * btcutil.Amount(f) / 1000 - fee := f.FeeForWeight(weight) - if fee != expFee { - t.Fatalf("expected fee to be %d sat, was %d", - fee, expFee) + // The resulting transaction fee should be the same when using + // both rates. + expectedFee := btcutil.Amount(feePerKB * vsize / 1000) + fee1 := feePerKB.FeeForVSize(vsize) + if fee1 != expectedFee { + t.Fatalf("expected fee of %d sats, got %d sats", + 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) { t.Parallel() - const feePerVSize = 100 + const feePerKw = lnwallet.FeePerKwFloor feeEstimator := &lnwallet.StaticFeeEstimator{ - FeeRate: feePerVSize, + FeePerKW: feePerKw, } if err := feeEstimator.Start(); err != nil { t.Fatalf("unable to start fee estimator: %v", err) } defer feeEstimator.Stop() - feeRate, err := feeEstimator.EstimateFeePerVSize(6) + feeRate, err := feeEstimator.EstimateFeePerKW(6) if err != nil { t.Fatalf("unable to get fee rate: %v", err) } - if feeRate != feePerVSize { - t.Fatalf("expected fee rate %v, got %v", feePerVSize, feeRate) + if feeRate != feePerKw { + t.Fatalf("expected fee rate %v, got %v", feePerKw, feeRate) } } diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 0aad560ad4..073cd9137d 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -154,13 +154,13 @@ type WalletController interface { // error should be returned. GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) - // SendOutputs funds, signs, and broadcasts a Bitcoin transaction - // paying out to the specified outputs. In the case the wallet has - // insufficient funds, or the outputs are non-standard, an error should - // be returned. This method also takes the target fee expressed in - // sat/vbyte that should be used when crafting the transaction. + // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying + // out to the specified outputs. In the case the wallet has insufficient + // funds, or the outputs are non-standard, an error should be returned. + // This method also takes the target fee expressed in sat/kw that should + // be used when crafting the transaction. SendOutputs(outputs []*wire.TxOut, - feeRate SatPerVByte) (*chainhash.Hash, error) + feeRate SatPerKWeight) (*chainhash.Hash, error) // ListUnspentWitness returns all unspent outputs which are version 0 // witness programs. The 'confirms' parameter indicates the minimum diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index eb3d3b507c..4f304bc150 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -186,7 +186,7 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet, Value: int64(satoshiPerOutput), PkScript: script, } - if _, err := miner.SendOutputs([]*wire.TxOut{output}, 10); err != nil { + if _, err := miner.SendOutputs([]*wire.TxOut{output}, 2500); err != nil { return err } } @@ -249,7 +249,7 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness, WalletController: wc, Signer: signer, ChainIO: bio, - FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 10}, + FeeEstimator: lnwallet.StaticFeeEstimator{FeePerKW: 2500}, DefaultConstraints: channeldb.ChannelConstraints{ DustLimit: 500, 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 // 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 { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() aliceChanReservation, err := alice.InitChannelReservation( - fundingAmount*2, fundingAmount, 0, feePerKw, feeRate, - bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) + fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { 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 // receives' Alice's contribution, and consumes that so we can continue // the funding process. - bobChanReservation, err := bob.InitChannelReservation(fundingAmount*2, - fundingAmount, 0, feePerKw, feeRate, alicePub, aliceAddr, - chainHash, lnwire.FFAnnounceChannel) + bobChanReservation, err := bob.InitChannelReservation( + fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw, alicePub, + aliceAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("bob unable to init channel reservation: %v", err) } @@ -471,14 +472,13 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, if err != nil { 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 { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() - _, err = alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel, + _, err = alice.InitChannelReservation( + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, ) if err != nil { t.Fatalf("unable to initialize funding reservation 1: %v", err) @@ -491,9 +491,10 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, if err != nil { t.Fatalf("unable to create amt: %v", err) } - failedReservation, err := alice.InitChannelReservation(amt, amt, 0, - feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel) + failedReservation, err := alice.InitChannelReservation( + amt, amt, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, + lnwire.FFAnnounceChannel, + ) if err == nil { t.Fatalf("not error returned, should fail on coin selection") } @@ -508,28 +509,28 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, alice, _ *lnwallet.LightningWallet, t *testing.T) { - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() // Create a reservation for 44 BTC. fundingAmount, err := btcutil.NewAmount(44) if err != nil { t.Fatalf("unable to create amt: %v", err) } - chanReservation, err := alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel) + chanReservation, err := alice.InitChannelReservation( + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } // Attempt to create another channel with 44 BTC, this should fail. - _, err = alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel, + _, err = alice.InitChannelReservation( + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, ) if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { t.Fatalf("coin selection succeeded should have insufficient funds: %v", @@ -560,7 +561,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, // Request to fund a new channel should now succeed. _, err = alice.InitChannelReservation(fundingAmount, fundingAmount, - 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, + 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) @@ -570,15 +571,15 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, func testCancelNonExistentReservation(miner *rpctest.Harness, alice, _ *lnwallet.LightningWallet, t *testing.T) { - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } // Create our own reservation, give it some ID. res, err := lnwallet.NewChannelReservation( - 10000, 10000, feeRate.FeePerKWeight(), alice, - 22, 10, &testHdSeed, lnwire.FFAnnounceChannel, + 10000, 10000, feePerKw, alice, 22, 10, &testHdSeed, + lnwire.FFAnnounceChannel, ) if err != nil { t.Fatalf("unable to create res: %v", err) @@ -597,14 +598,17 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness, // 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 // failure to create the reservation. - fundingAmount, err := btcutil.NewAmount(4) + const numBTC = 4 + fundingAmount, err := btcutil.NewAmount(numBTC) if err != nil { 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( - fundingAmount, fundingAmount, 0, feePerKw, feePerVSize, bobPub, + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel, ) switch { @@ -672,14 +676,14 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, t.Fatalf("unable to create amt: %v", err) } pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() - aliceChanReservation, err := alice.InitChannelReservation(fundingAmt, - fundingAmt, pushAmt, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel) + aliceChanReservation, err := alice.InitChannelReservation( + fundingAmt, fundingAmt, pushAmt, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) } @@ -707,9 +711,10 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, // Next, Bob receives the initial request, generates a corresponding // reservation initiation, then consume Alice's contribution. - bobChanReservation, err := bob.InitChannelReservation(fundingAmt, 0, - pushAmt, feePerKw, feeRate, alicePub, aliceAddr, chainHash, - lnwire.FFAnnounceChannel) + bobChanReservation, err := bob.InitChannelReservation( + fundingAmt, 0, pushAmt, feePerKw, feePerKw, alicePub, aliceAddr, + chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("unable to create bob reservation: %v", err) } @@ -891,7 +896,7 @@ func testListTransactionDetails(miner *rpctest.Harness, Value: outputAmt, PkScript: script, } - txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10) + txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500) if err != nil { t.Fatalf("unable to send coinbase: %v", err) } @@ -994,7 +999,7 @@ func testListTransactionDetails(miner *rpctest.Harness, t.Fatalf("unable to make output script: %v", err) } burnOutput := wire.NewTxOut(outputAmt, outputScript) - burnTXID, err := alice.SendOutputs([]*wire.TxOut{burnOutput}, 10) + burnTXID, err := alice.SendOutputs([]*wire.TxOut{burnOutput}, 2500) if err != nil { t.Fatalf("unable to create burn tx: %v", err) } @@ -1108,7 +1113,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness, Value: outputAmt, PkScript: script, } - txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10) + txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500) if err != nil { t.Fatalf("unable to send coinbase: %v", err) } @@ -1308,7 +1313,7 @@ func testPublishTransaction(r *rpctest.Harness, Value: btcutil.SatoshiPerBitcoin, PkScript: keyScript, } - txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 10) + txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 2500) if err != nil { t.Fatalf("unable to create output: %v", err) } @@ -1553,7 +1558,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness, Value: btcutil.SatoshiPerBitcoin, PkScript: keyScript, } - txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 10) + txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 2500) if err != nil { t.Fatalf("unable to create output: %v", err) } @@ -1679,7 +1684,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet, Value: 1e8, PkScript: script, } - txid, err := w.SendOutputs([]*wire.TxOut{output}, 10) + txid, err := w.SendOutputs([]*wire.TxOut{output}, 2500) if err != nil { t.Fatalf("unable to send outputs: %v", err) } @@ -2073,7 +2078,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, } case "neutrino": - feeEstimator = lnwallet.StaticFeeEstimator{FeeRate: 250} + feeEstimator = lnwallet.StaticFeeEstimator{FeePerKW: 62500} // Set some package-level variable to speed up // operation for tests. diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index 03cd851350..c11aa69d4b 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -229,12 +229,11 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error) return nil, nil, nil, err } - estimator := &StaticFeeEstimator{24} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() commitFee := calcStaticFee(0) aliceCommit := channeldb.ChannelCommitment{ diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 8a2d9b1f2a..4f86ba946c 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -81,9 +81,9 @@ type initFundingReserveMsg struct { // paying some multiple of the accepted base fee rate of the network. commitFeePerKw SatPerKWeight - // fundingFeePerVSize is the fee rate in sat/vbyte to use for the - // initial funding transaction. - fundingFeePerVSize SatPerVByte + // fundingFeePerKw is the fee rate in sat/kw to use for the initial + // funding transaction. + fundingFeePerKw SatPerKWeight // pushMSat is the number of milli-satoshis that should be pushed over // the responder as part of the initial channel creation. @@ -413,7 +413,7 @@ out: // commitment transaction is valid. func (l *LightningWallet) InitChannelReservation( capacity, ourFundAmt btcutil.Amount, pushMSat lnwire.MilliSatoshi, - commitFeePerKw SatPerKWeight, fundingFeePerVSize SatPerVByte, + commitFeePerKw SatPerKWeight, fundingFeePerKw SatPerKWeight, theirID *btcec.PublicKey, theirAddr net.Addr, chainHash *chainhash.Hash, flags lnwire.FundingFlag) (*ChannelReservation, error) { @@ -421,17 +421,17 @@ func (l *LightningWallet) InitChannelReservation( respChan := make(chan *ChannelReservation, 1) l.msgChan <- &initFundingReserveMsg{ - chainHash: chainHash, - nodeID: theirID, - nodeAddr: theirAddr, - fundingAmount: ourFundAmt, - capacity: capacity, - commitFeePerKw: commitFeePerKw, - fundingFeePerVSize: fundingFeePerVSize, - pushMSat: pushMSat, - flags: flags, - err: errChan, - resp: respChan, + chainHash: chainHash, + nodeID: theirID, + nodeAddr: theirAddr, + fundingAmount: ourFundAmt, + capacity: capacity, + commitFeePerKw: commitFeePerKw, + fundingFeePerKw: fundingFeePerKw, + pushMSat: pushMSat, + flags: flags, + err: errChan, + resp: respChan, } return <-respChan, <-errChan @@ -479,10 +479,10 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg // don't need to perform any coin selection. Otherwise, attempt to // obtain enough coins to meet the required funding amount. if req.fundingAmount != 0 { - // Coin selection is done on the basis of sat-per-vbyte, we'll - // use the passed sat/vbyte passed in to perform coin selection. + // Coin selection is done on the basis of sat/kw, so we'll use + // the fee rate passed in to perform coin selection. err := l.selectCoinsAndChange( - req.fundingFeePerVSize, req.fundingAmount, + req.fundingFeePerKw, req.fundingAmount, reservation.ourContribution, ) if err != nil { @@ -1276,7 +1276,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { // within the passed contribution's inputs. If necessary, a change address will // also be generated. // 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 { // We hold the coin select mutex while querying for outputs, and @@ -1286,7 +1286,7 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerVByte, defer l.coinSelectMtx.Unlock() 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 // confirmation. @@ -1395,9 +1395,9 @@ func selectInputs(amt btcutil.Amount, coins []*Utxo) (btcutil.Amount, []*Utxo, e // coinSelect attempts to select a sufficient amount of coins, including a // 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. -func coinSelect(feeRate SatPerVByte, amt btcutil.Amount, +func coinSelect(feeRate SatPerKWeight, amt btcutil.Amount, coins []*Utxo) ([]*Utxo, btcutil.Amount, error) { amtNeeded := amt @@ -1441,7 +1441,8 @@ func coinSelect(feeRate SatPerVByte, amt btcutil.Amount, // amount isn't enough to pay fees, then increase the requested // coin amount by the estimate required fee, performing another // round of coin selection. - requiredFee := feeRate.FeeForVSize(int64(weightEstimate.VSize())) + totalWeight := int64(weightEstimate.Weight()) + requiredFee := feeRate.FeeForWeight(totalWeight) if overShootAmt < requiredFee { amtNeeded = amt + requiredFee continue diff --git a/mock.go b/mock.go index cd0461e8c2..e4b563ad76 100644 --- a/mock.go +++ b/mock.go @@ -224,7 +224,7 @@ func (*mockWalletController) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, e } func (*mockWalletController) SendOutputs(outputs []*wire.TxOut, - _ lnwallet.SatPerVByte) (*chainhash.Hash, error) { + _ lnwallet.SatPerKWeight) (*chainhash.Hash, error) { return nil, nil } diff --git a/peer.go b/peer.go index ffc4cd2864..09a172913b 100644 --- a/peer.go +++ b/peer.go @@ -1658,18 +1658,13 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e // 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 // 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 { peerLog.Errorf("unable to query fee estimator: %v", err) 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() if err != nil { 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, }, deliveryAddr, - targetFeePerKw, + feePerKw, uint32(startingHeight), nil, ) diff --git a/peer_test.go b/peer_test.go index 2b71128704..ffeb5951c6 100644 --- a/peer_test.go +++ b/peer_test.go @@ -169,12 +169,11 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) { dummyDeliveryScript), } - estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := lnwallet.StaticFeeEstimator{FeePerKW: 12500} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() fee := responderChan.CalcFee(feePerKw) closeSig, _, _, err := responderChan.CreateCloseProposal(fee, dummyDeliveryScript, initiatorDeliveryScript) @@ -460,14 +459,12 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { msg: respShutdown, } - estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} - initiatorIdealFeeRate, err := estimator.EstimateFeePerVSize(1) + estimator := lnwallet.StaticFeeEstimator{FeePerKW: 12500} + initiatorIdealFeeRate, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - initiatorIdealFee := responderChan.CalcFee( - initiatorIdealFeeRate.FeePerKWeight(), - ) + initiatorIdealFee := responderChan.CalcFee(initiatorIdealFeeRate) increasedFee := btcutil.Amount(float64(initiatorIdealFee) * 2.5) closeSig, _, _, err := responderChan.CreateCloseProposal( increasedFee, dummyDeliveryScript, initiatorDeliveryScript, diff --git a/pilot.go b/pilot.go index e956359ee8..b1d8f0a906 100644 --- a/pilot.go +++ b/pilot.go @@ -80,7 +80,7 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey, // With the connection established, we'll now establish our connection // 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 { return err } @@ -88,8 +88,9 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey, // TODO(halseth): make configurable? minHtlc := lnwire.NewMSatFromSatoshis(1) - updateStream, errChan := c.server.OpenChannel(target, amt, 0, - minHtlc, feePerVSize, false, 0) + updateStream, errChan := c.server.OpenChannel( + target, amt, 0, minHtlc, feePerKw, false, 0, + ) select { case err := <-errChan: diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index 7691daf144..d009503477 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -92,7 +92,7 @@ func getTestTXID(miner *rpctest.Harness) (*chainhash.Hash, error) { PkScript: script, }, } - return miner.SendOutputs(outputs, 10) + return miner.SendOutputs(outputs, 2500) } func locateOutput(tx *wire.MsgTx, script []byte) (*wire.OutPoint, *wire.TxOut, error) { diff --git a/rpcserver.go b/rpcserver.go index 74ee827573..acf58921d4 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -12,11 +12,8 @@ import ( "sort" "strings" "sync" - "time" - - "gopkg.in/macaroon-bakery.v2/bakery" - "sync/atomic" + "time" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec" @@ -37,6 +34,7 @@ import ( "github.com/lightningnetwork/lnd/zpay32" "github.com/tv42/zbase32" "golang.org/x/net/context" + "gopkg.in/macaroon-bakery.v2/bakery" ) 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 // address to a specified output value to be sent to that address. func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, - feeRate lnwallet.SatPerVByte) (*chainhash.Hash, error) { + feeRate lnwallet.SatPerKWeight) (*chainhash.Hash, error) { outputs, err := addrPairsToOutputs(paymentMap) if err != nil { @@ -412,18 +410,18 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, return r.server.cc.wallet.SendOutputs(outputs, feeRate) } -// determineFeePerVSize will determine the fee in sat/vbyte that should be paid -// given an estimator, a confirmation target, and a manual value for sat/byte. -// A value is chosen based on the two free parameters as one, or both of them -// can be zero. -func determineFeePerVSize(feeEstimator lnwallet.FeeEstimator, targetConf int32, - feePerByte int64) (lnwallet.SatPerVByte, error) { +// determineFeePerKw will determine the fee in sat/kw that should be paid given +// an estimator, a confirmation target, and a manual value for sat/byte. A value +// is chosen based on the two free parameters as one, or both of them can be +// zero. +func determineFeePerKw(feeEstimator lnwallet.FeeEstimator, targetConf int32, + feePerByte int64) (lnwallet.SatPerKWeight, error) { switch { // If the target number of confirmations is set, then we'll use that to // consult our fee estimator for an adequate fee. case targetConf != 0: - feePerVSize, err := feeEstimator.EstimateFeePerVSize( + feePerKw, err := feeEstimator.EstimateFeePerKW( uint32(targetConf), ) if err != nil { @@ -431,22 +429,24 @@ func determineFeePerVSize(feeEstimator lnwallet.FeeEstimator, targetConf int32, "estimator: %v", err) } - return feePerVSize, nil + return feePerKw, nil // 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: - return lnwallet.SatPerVByte(feePerByte), nil + feePerKB := lnwallet.SatPerKVByte(feePerByte * 1000) + return feePerKB.FeePerKWeight(), nil // Otherwise, we'll attempt a relaxed confirmation target for the // transaction default: - feePerVSize, err := feeEstimator.EstimateFeePerVSize(6) + feePerKw, err := feeEstimator.EstimateFeePerKW(6) if err != nil { - return 0, fmt.Errorf("unable to query fee "+ - "estimator: %v", err) + return 0, fmt.Errorf("unable to query fee estimator: "+ + "%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 // appropriate fee rate for this transaction. - feeRate, err := determineFeePerVSize( + feePerKw, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/vbyte=%v", - in.Addr, btcutil.Amount(in.Amount), int64(feeRate)) + rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/kw=%v", in.Addr, + btcutil.Amount(in.Amount), int64(feePerKw)) paymentMap := map[string]int64{in.Addr: in.Amount} - txid, err := r.sendCoinsOnChain(paymentMap, feeRate) + txid, err := r.sendCoinsOnChain(paymentMap, feePerKw) if err != nil { return nil, err } @@ -484,18 +484,18 @@ func (r *rpcServer) SendMany(ctx context.Context, in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) { // Based on the passed fee related parameters, we'll determine an - // approriate fee rate for this transaction. - feeRate, err := determineFeePerVSize( + // appropriate fee rate for this transaction. + feePerKw, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Infof("[sendmany] outputs=%v, sat/vbyte=%v", - spew.Sdump(in.AddrToAmount), int64(feeRate)) + rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v", + spew.Sdump(in.AddrToAmount), int64(feePerKw)) - txid, err := r.sendCoinsOnChain(in.AddrToAmount, feeRate) + txid, err := r.sendCoinsOnChain(in.AddrToAmount, feePerKw) if err != nil { 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 // appropriate fee rate for the funding transaction. - feeRate, err := determineFeePerVSize( + feeRate, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return err } - rpcsLog.Debugf("[openchannel]: using fee of %v sat/vbyte for funding "+ - "tx", int64(feeRate)) + rpcsLog.Debugf("[openchannel]: using fee of %v sat/kw for funding tx", + int64(feeRate)) // Instruct the server to trigger the necessary events to attempt to // 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 // appropriate fee rate for the funding transaction. - feeRate, err := determineFeePerVSize( + feeRate, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { 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)) 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 // an appropriate fee rate for the cooperative closure // transaction. - feeRate, err := determineFeePerVSize( + feeRate, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return err } - rpcsLog.Debugf("Target sat/vbyte for closing transaction: %v", + rpcsLog.Debugf("Target sat/kw for closing transaction: %v", 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 // examine the channel to ensure that it doesn't have a // lingering HTLC. @@ -1140,9 +1131,8 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, // cooperative channel closure. So we'll forward the request to // the htlc switch which will handle the negotiation and // broadcast details. - feePerKw := feeRate.FeePerKWeight() updateChan, errChan = r.server.htlcSwitch.CloseLink( - chanPoint, htlcswitch.CloseRegular, feePerKw, + chanPoint, htlcswitch.CloseRegular, feeRate, ) } out: diff --git a/server.go b/server.go index 233fe2d54c..76d54512f4 100644 --- a/server.go +++ b/server.go @@ -2537,7 +2537,7 @@ type openChanReq struct { pushAmt lnwire.MilliSatoshi - fundingFeePerVSize lnwallet.SatPerVByte + fundingFeePerKw lnwallet.SatPerKWeight private bool @@ -2685,7 +2685,7 @@ func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error { // NOTE: This function is safe for concurrent access. func (s *server) OpenChannel(nodeKey *btcec.PublicKey, localAmt btcutil.Amount, pushAmt, minHtlc lnwire.MilliSatoshi, - fundingFeePerVSize lnwallet.SatPerVByte, private bool, + fundingFeePerKw lnwallet.SatPerKWeight, private bool, remoteCsvDelay uint16) (chan *lnrpc.OpenStatusUpdate, chan error) { // 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 // confirmation target. - if fundingFeePerVSize == 0 { + if fundingFeePerKw == 0 { estimator := s.cc.feeEstimator - fundingFeePerVSize, err = estimator.EstimateFeePerVSize(6) + fundingFeePerKw, err = estimator.EstimateFeePerKW(6) if err != nil { errChan <- err 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 // synchronous request to the outside world. req := &openChanReq{ - targetPubkey: nodeKey, - chainHash: *activeNetParams.GenesisHash, - localFundingAmt: localAmt, - fundingFeePerVSize: fundingFeePerVSize, - pushAmt: pushAmt, - private: private, - minHtlc: minHtlc, - remoteCsvDelay: remoteCsvDelay, - updates: updateChan, - err: errChan, + targetPubkey: nodeKey, + chainHash: *activeNetParams.GenesisHash, + localFundingAmt: localAmt, + fundingFeePerKw: fundingFeePerKw, + pushAmt: pushAmt, + private: private, + minHtlc: minHtlc, + remoteCsvDelay: remoteCsvDelay, + updates: updateChan, + err: errChan, } // TODO(roasbeef): pass in chan that's closed if/when funding succeeds diff --git a/test_utils.go b/test_utils.go index 0de7f0f80c..f6338bfb5c 100644 --- a/test_utils.go +++ b/test_utils.go @@ -201,12 +201,11 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, return nil, nil, nil, nil, err } - estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 12500} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() // TODO(roasbeef): need to factor in commit fee? aliceCommit := channeldb.ChannelCommitment{ diff --git a/utxonursery.go b/utxonursery.go index ec2424d3e7..eba9de7576 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -994,15 +994,15 @@ func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput, utxnLog.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+ "inputs", len(csvOutputs), len(cltvOutputs)) - txVSize := int64(weightEstimate.VSize()) - return u.populateSweepTx(txVSize, classHeight, csvOutputs, cltvOutputs) + txWeight := int64(weightEstimate.Weight()) + return u.populateSweepTx(txWeight, classHeight, csvOutputs, cltvOutputs) } // populateSweepTx populate the final sweeping transaction with all witnesses // 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 // accounting for the fee estimate. -func (u *utxoNursery) populateSweepTx(txVSize int64, classHeight uint32, +func (u *utxoNursery) populateSweepTx(txWeight int64, classHeight uint32, csvInputs []CsvSpendableOutput, 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. - feePerVSize, err := u.cfg.Estimator.EstimateFeePerVSize(6) + feePerKw, err := u.cfg.Estimator.EstimateFeePerKW(6) if err != nil { return nil, err } - txFee := feePerVSize.FeeForVSize(txVSize) + txFee := feePerKw.FeeForWeight(txWeight) // Sweep as much possible, after subtracting txn fees. sweepAmt := int64(totalSum - txFee)