diff --git a/breacharbiter.go b/breacharbiter.go index 1771b81f..0987e9b6 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -1050,13 +1050,13 @@ func (b *breachArbiter) createJusticeTx( spendableOutputs = append(spendableOutputs, input) } - txWeight := uint64(weightEstimate.Weight()) - return b.sweepSpendableOutputsTxn(txWeight, spendableOutputs...) + txVSize := int64(weightEstimate.VSize()) + return b.sweepSpendableOutputsTxn(txVSize, 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(txWeight uint64, +func (b *breachArbiter) sweepSpendableOutputsTxn(txVSize int64, inputs ...SpendableOutput) (*wire.MsgTx, error) { // First, we obtain a new public key script from the wallet which we'll @@ -1076,11 +1076,11 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64, // 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. - feePerWeight, err := b.cfg.Estimator.EstimateFeePerWeight(2) + feePerVSize, err := b.cfg.Estimator.EstimateFeePerVSize(2) if err != nil { return nil, err } - txFee := btcutil.Amount(txWeight * uint64(feePerWeight)) + txFee := feePerVSize.FeeForVSize(txVSize) // 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 7502415a..0fd093ee 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -1331,18 +1331,18 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa } estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} - feePerWeight, err := estimator.EstimateFeePerWeight(1) + feePerVSize, err := estimator.EstimateFeePerVSize(1) if err != nil { return nil, nil, nil, err } - feePerKw := feePerWeight * 1000 + feePerKw := feePerVSize.FeePerKWeight() // TODO(roasbeef): need to factor in commit fee? aliceCommit := channeldb.ChannelCommitment{ CommitHeight: 0, LocalBalance: lnwire.NewMSatFromSatoshis(channelBal), RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal), - FeePerKw: feePerKw, + FeePerKw: btcutil.Amount(feePerKw), CommitFee: 8688, CommitTx: aliceCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), @@ -1351,7 +1351,7 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa CommitHeight: 0, LocalBalance: lnwire.NewMSatFromSatoshis(channelBal), RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal), - FeePerKw: feePerKw, + FeePerKw: btcutil.Amount(feePerKw), CommitFee: 8688, CommitTx: bobCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), diff --git a/chainregistry.go b/chainregistry.go index 7cbed86e..909d12f7 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -24,7 +24,6 @@ import ( "github.com/lightningnetwork/lnd/routing/chainview" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/rpcclient" - "github.com/roasbeef/btcutil" "github.com/roasbeef/btcwallet/chain" "github.com/roasbeef/btcwallet/walletdb" ) @@ -306,7 +305,7 @@ 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 := btcutil.Amount(25) + fallBackFeeRate := lnwallet.SatPerVByte(25) cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( *rpcConfig, fallBackFeeRate, ) @@ -410,7 +409,7 @@ 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 := btcutil.Amount(25) + fallBackFeeRate := lnwallet.SatPerVByte(25) cc.feeEstimator, err = lnwallet.NewBtcdFeeEstimator( *rpcConfig, fallBackFeeRate, ) diff --git a/chancloser.go b/chancloser.go index 24dc18e5..667495c3 100644 --- a/chancloser.go +++ b/chancloser.go @@ -150,7 +150,7 @@ type channelCloser struct { // passed configuration, and delivery+fee preference. The final argument should // only be populated iff, we're the initiator of this closing request. func newChannelCloser(cfg chanCloseCfg, deliveryScript []byte, - idealFeePerkw btcutil.Amount, negotiationHeight uint32, + idealFeePerKw lnwallet.SatPerKWeight, negotiationHeight uint32, closeReq *htlcswitch.ChanClose, closeCtx *contractcourt.CooperativeCloseCtx) *channelCloser { @@ -158,9 +158,7 @@ func newChannelCloser(cfg chanCloseCfg, deliveryScript []byte, // fee will be starting at for this fee negotiation. // // TODO(roasbeef): should factor in minimal commit - idealFeeSat := btcutil.Amount( - cfg.channel.CalcFee(uint64(idealFeePerkw)), - ) + idealFeeSat := cfg.channel.CalcFee(idealFeePerKw) // If this fee is greater than the fee currently present within the // commitment transaction, then we'll clamp it down to be within the diff --git a/channeldb/channel.go b/channeldb/channel.go index 151feabb..23469596 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -249,6 +249,10 @@ type ChannelCommitment struct { // the commitment transaction for the entire duration of the channel's // lifetime. This field may be updated during normal operation of the // channel as on-chain conditions change. + // + // TODO(halseth): make this SatPerKWeight. Cannot be done atm because + // this will cause the import cycle lnwallet<->channeldb. Fee + // estimation stuff should be in its own package. FeePerKw btcutil.Amount // CommitTx is the latest version of the commitment state, broadcast diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index 5cc9ffa6..6cb8063c 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -482,23 +482,24 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { // // TODO(roasbeef): signal up if fee would be too large // to sweep singly, need to batch - satWeight, err := h.FeeEstimator.EstimateFeePerWeight(6) + feePerVSize, err := h.FeeEstimator.EstimateFeePerVSize(6) if err != nil { return nil, err } - log.Debugf("%T(%x): using %v sat/weight to sweep htlc"+ + log.Debugf("%T(%x): using %v sat/vbyte to sweep htlc"+ "incoming+remote htlc confirmed", h, - h.payHash[:], int64(satWeight)) + h.payHash[:], int64(feePerVSize)) // Using a weight estimator, we'll compute the total // fee required, and from that the value we'll end up // with. - totalWeight := (&lnwallet.TxWeightEstimator{}). + totalVSize := (&lnwallet.TxWeightEstimator{}). AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize). - AddP2WKHOutput().Weight() - totalFees := int64(totalWeight) * int64(satWeight) - sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value - totalFees + AddP2WKHOutput().VSize() + totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) + sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value - + int64(totalFees) // With the fee computation finished, we'll now // construct the sweep transaction. @@ -1252,19 +1253,19 @@ 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. - satWeight, err := c.FeeEstimator.EstimateFeePerWeight(6) + feePerVSize, err := c.FeeEstimator.EstimateFeePerVSize(6) if err != nil { return nil, err } - log.Debugf("%T(%v): using %v sat/weight for sweep tx", c, - c.chanPoint, int64(satWeight)) + log.Debugf("%T(%v): using %v sat/vsize for sweep tx", c, + c.chanPoint, int64(feePerVSize)) - totalWeight := (&lnwallet.TxWeightEstimator{}). + totalVSize := (&lnwallet.TxWeightEstimator{}). AddP2PKHInput(). - AddP2WKHOutput().Weight() - totalFees := int64(totalWeight) * int64(satWeight) - sweepAmt := signDesc.Output.Value - totalFees + AddP2WKHOutput().VSize() + totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) + sweepAmt := signDesc.Output.Value - int64(totalFees) c.sweepTx = wire.NewMsgTx(2) c.sweepTx.AddTxIn(&wire.TxIn{ diff --git a/fundingmanager.go b/fundingmanager.go index fcd8d7ce..4614c18b 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -865,7 +865,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // port with default advertised port chainHash := chainhash.Hash(msg.ChainHash) reservation, err := f.cfg.Wallet.InitChannelReservation(amt, 0, - msg.PushAmount, btcutil.Amount(msg.FeePerKiloWeight), 0, + msg.PushAmount, lnwallet.SatPerKWeight(msg.FeePerKiloWeight), 0, fmsg.peerAddress.IdentityKey, fmsg.peerAddress.Address, &chainHash, msg.ChannelFlags) if err != nil { @@ -2349,7 +2349,7 @@ 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. - feePerWeight, err := f.cfg.FeeEstimator.EstimateFeePerWeight(3) + feePerVSize, err := f.cfg.FeeEstimator.EstimateFeePerVSize(3) if err != nil { msg.err <- err return @@ -2357,7 +2357,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // The protocol currently operates on the basis of fee-per-kw, so we'll // multiply the computed sat/weight by 1000 to arrive at fee-per-kw. - commitFeePerKw := feePerWeight * 1000 + commitFeePerKw := feePerVSize.FeePerKWeight() // We set the channel flags to indicate whether we want this channel // to be announced to the network. @@ -2371,8 +2371,9 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // wallet doesn't have enough funds to commit to this channel, then the // request will fail, and be aborted. reservation, err := f.cfg.Wallet.InitChannelReservation(capacity, - localAmt, msg.pushAmt, commitFeePerKw, msg.fundingFeePerWeight, - peerKey, msg.peerAddress.Address.(*net.TCPAddr), &msg.chainHash, channelFlags) + localAmt, msg.pushAmt, commitFeePerKw, msg.fundingFeePerVSize, + peerKey, msg.peerAddress.Address.(*net.TCPAddr), + &msg.chainHash, channelFlags) if err != nil { msg.err <- err return diff --git a/htlcswitch/link.go b/htlcswitch/link.go index e78eab0f..89887a67 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -18,7 +18,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/chaincfg/chainhash" - "github.com/roasbeef/btcutil" ) const ( @@ -387,16 +386,16 @@ func (l *channelLink) EligibleToForward() bool { // chain in a timely manner. The returned value is expressed in fee-per-kw, as // this is the native rate used when computing the fee for commitment // transactions, and the second-level HTLC transactions. -func (l *channelLink) sampleNetworkFee() (btcutil.Amount, error) { - // We'll first query for the sat/weight recommended to be confirmed - // within 3blocks. - feePerWeight, err := l.cfg.FeeEstimator.EstimateFeePerWeight(3) +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) if err != nil { return 0, err } // Once we have this fee rate, we'll convert to sat-per-kw. - feePerKw := feePerWeight * 1000 + feePerKw := feePerVSize.FeePerKWeight() log.Debugf("ChannelLink(%v): sampled fee rate for 3 block conf: %v "+ "sat/kw", l, int64(feePerKw)) @@ -407,7 +406,7 @@ func (l *channelLink) sampleNetworkFee() (btcutil.Amount, error) { // shouldAdjustCommitFee returns true if we should update our commitment fee to // match that of the network fee. We'll only update our commitment fee if the // network fee is +/- 10% to our network fee. -func shouldAdjustCommitFee(netFee, chanFee btcutil.Amount) bool { +func shouldAdjustCommitFee(netFee, chanFee lnwallet.SatPerKWeight) bool { switch { // If the network fee is greater than the commitment fee, then we'll // switch to it if it's at least 10% greater than the commit fee. @@ -1148,7 +1147,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { case *lnwire.UpdateFee: // We received fee update from peer. If we are the initiator we // will fail the channel, if not we will apply the update. - fee := btcutil.Amount(msg.FeePerKw) + fee := lnwallet.SatPerKWeight(msg.FeePerKw) if err := l.channel.ReceiveUpdateFee(fee); err != nil { l.fail("error receiving fee update: %v", err) return @@ -1348,7 +1347,7 @@ func (l *channelLink) HandleChannelUpdate(message lnwire.Message) { // updateChannelFee updates the commitment fee-per-kw on this channel by // committing to an update_fee message. -func (l *channelLink) updateChannelFee(feePerKw btcutil.Amount) error { +func (l *channelLink) updateChannelFee(feePerKw lnwallet.SatPerKWeight) error { log.Infof("ChannelPoint(%v): updating commit fee to %v sat/kw", l, feePerKw) diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index aa6ade23..8add4e84 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -1683,13 +1683,13 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) { estimator := &lnwallet.StaticFeeEstimator{ FeeRate: 24, } - feePerWeight, err := estimator.EstimateFeePerWeight(1) + feeRate, err := estimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() htlcFee := lnwire.NewMSatFromSatoshis( - btcutil.Amount((int64(feePerKw) * lnwallet.HtlcWeight) / 1000), + feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) // The starting bandwidth of the channel should be exactly the amount @@ -2010,11 +2010,11 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) { estimator := &lnwallet.StaticFeeEstimator{ FeeRate: 24, } - feePerWeight, err := estimator.EstimateFeePerWeight(1) + feeRate, err := estimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() // The starting bandwidth of the channel should be exactly the amount // that we created the channel between her and Bob. @@ -2077,7 +2077,7 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) { time.Sleep(time.Second * 1) commitWeight := lnwallet.CommitWeight + lnwallet.HtlcWeight*numHTLCs htlcFee := lnwire.NewMSatFromSatoshis( - btcutil.Amount((int64(feePerKw) * commitWeight) / 1000), + feePerKw.FeeForWeight(commitWeight), ) expectedBandwidth = aliceStartingBandwidth - totalHtlcAmt - htlcFee expectedBandwidth += lnwire.NewMSatFromSatoshis(defaultCommitFee) @@ -2222,13 +2222,13 @@ func TestChannelLinkBandwidthChanReserve(t *testing.T) { estimator := &lnwallet.StaticFeeEstimator{ FeeRate: 24, } - feePerWeight, err := estimator.EstimateFeePerWeight(1) + feeRate, err := estimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() htlcFee := lnwire.NewMSatFromSatoshis( - btcutil.Amount((int64(feePerKw) * lnwallet.HtlcWeight) / 1000), + feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) // The starting bandwidth of the channel should be exactly the amount @@ -2595,8 +2595,8 @@ func TestChannelRetransmission(t *testing.T) { // deviates from our current fee by more 10% or more. func TestShouldAdjustCommitFee(t *testing.T) { tests := []struct { - netFee btcutil.Amount - chanFee btcutil.Amount + netFee lnwallet.SatPerKWeight + chanFee lnwallet.SatPerKWeight shouldAdjust bool }{ @@ -2754,9 +2754,15 @@ 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.weightFeeIn <- startingFeeRate / 1000: + case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte: case <-time.After(time.Second * 5): t.Fatalf("alice didn't query for the new " + "network fee") @@ -2803,7 +2809,7 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) { // fee update. newFeeRate := startingFeeRate * 3 select { - case n.feeEstimator.weightFeeIn <- newFeeRate: + case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte * 3: case <-time.After(time.Second * 5): t.Fatalf("alice didn't query for the new " + "network fee") @@ -2813,19 +2819,15 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) { // At this point, Alice should've triggered a new fee update that // increased the fee rate to match the new rate. - // - // We'll scale the new fee rate by 100 as we deal with units of fee - // per-kw. - expectedFeeRate := newFeeRate * 1000 aliceFeeRate = channels.aliceToBob.CommitFeeRate() bobFeeRate = channels.bobToAlice.CommitFeeRate() - if aliceFeeRate != expectedFeeRate { + if aliceFeeRate != newFeeRate { t.Fatalf("alice's fee rate didn't change: expected %v, got %v", - expectedFeeRate, aliceFeeRate) + newFeeRate, aliceFeeRate) } - if bobFeeRate != expectedFeeRate { + if bobFeeRate != newFeeRate { t.Fatalf("bob's fee rate didn't change: expected %v, got %v", - expectedFeeRate, aliceFeeRate) + newFeeRate, aliceFeeRate) } if aliceFeeRate != bobFeeRate { t.Fatalf("fee rates don't match: expected %v got %v", diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index f5ba055b..55df1e49 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -24,7 +24,6 @@ import ( "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" - "github.com/roasbeef/btcutil" ) type mockPreimageCache struct { @@ -57,13 +56,12 @@ func (m *mockPreimageCache) SubscribeUpdates() *contractcourt.WitnessSubscriptio } type mockFeeEstimator struct { - byteFeeIn chan btcutil.Amount - weightFeeIn chan btcutil.Amount + byteFeeIn chan lnwallet.SatPerVByte quit chan struct{} } -func (m *mockFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, error) { +func (m *mockFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (lnwallet.SatPerVByte, error) { select { case feeRate := <-m.byteFeeIn: return feeRate, nil @@ -72,15 +70,6 @@ func (m *mockFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, } } -func (m *mockFeeEstimator) EstimateFeePerWeight(numBlocks uint32) (btcutil.Amount, error) { - select { - case feeRate := <-m.weightFeeIn: - return feeRate, nil - case <-m.quit: - return 0, fmt.Errorf("exiting") - } -} - func (m *mockFeeEstimator) Start() error { return nil } diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index fb46465d..f1656f12 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -81,7 +81,7 @@ type ChanClose struct { // This value is only utilized if the closure type is CloseRegular. // This will be the starting offered fee when the fee negotiation // process for the cooperative closure transaction kicks off. - TargetFeePerKw btcutil.Amount + TargetFeePerKw lnwallet.SatPerKWeight // Updates is used by request creator to receive the notifications about // execution of the close channel request. @@ -741,9 +741,9 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { // directing the specified closure type. If the closure type if CloseRegular, // then the last parameter should be the ideal fee-per-kw that will be used as // a starting point for close negotiation. -func (s *Switch) CloseLink(chanPoint *wire.OutPoint, - closeType ChannelCloseType, - targetFeePerKw btcutil.Amount) (chan *lnrpc.CloseStatusUpdate, chan error) { +func (s *Switch) CloseLink(chanPoint *wire.OutPoint, closeType ChannelCloseType, + targetFeePerKw lnwallet.SatPerKWeight) (chan *lnrpc.CloseStatusUpdate, + chan error) { // TODO(roasbeef) abstract out the close updates. updateChan := make(chan *lnrpc.CloseStatusUpdate, 2) diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index bc3b0191..7549568d 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -188,12 +188,12 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, estimator := &lnwallet.StaticFeeEstimator{ FeeRate: 24, } - feePerWeight, err := estimator.EstimateFeePerWeight(1) + feePerVSize, err := estimator.EstimateFeePerVSize(1) if err != nil { return nil, nil, nil, nil, err } - feePerKw := btcutil.Amount(feePerWeight * 1000) - commitFee := (feePerKw * btcutil.Amount(724)) / 1000 + feePerKw := feePerVSize.FeePerKWeight() + commitFee := feePerKw.FeeForWeight(724) const broadcastHeight = 1 bobAddr := &net.TCPAddr{ @@ -211,7 +211,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, LocalBalance: lnwire.NewMSatFromSatoshis(aliceAmount - commitFee), RemoteBalance: lnwire.NewMSatFromSatoshis(bobAmount), CommitFee: commitFee, - FeePerKw: feePerKw, + FeePerKw: btcutil.Amount(feePerKw), CommitTx: aliceCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), } @@ -220,7 +220,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, LocalBalance: lnwire.NewMSatFromSatoshis(bobAmount), RemoteBalance: lnwire.NewMSatFromSatoshis(aliceAmount - commitFee), CommitFee: commitFee, - FeePerKw: feePerKw, + FeePerKw: btcutil.Amount(feePerKw), CommitTx: bobCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), } @@ -744,9 +744,8 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel, decoder := &mockIteratorDecoder{} feeEstimator := &mockFeeEstimator{ - byteFeeIn: make(chan btcutil.Amount), - weightFeeIn: make(chan btcutil.Amount), - quit: make(chan struct{}), + byteFeeIn: make(chan lnwallet.SatPerVByte), + quit: make(chan struct{}), } pCache := &mockPreimageCache{ diff --git a/lnd_test.go b/lnd_test.go index 15bb0294..28406603 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -366,7 +366,7 @@ func calcStaticFee(numHTLCs int) btcutil.Amount { const ( commitWeight = btcutil.Amount(724) htlcWeight = 172 - feePerKw = btcutil.Amount(50/4) * 1000 + feePerKw = btcutil.Amount(50 * 1000 / 4) ) return feePerKw * (commitWeight + btcutil.Amount(htlcWeight*numHTLCs)) / 1000 @@ -4635,8 +4635,9 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // amount of payments, between Alice and Bob, at the end of the test // Alice should send all money from her side to Bob. ctxt, _ := context.WithTimeout(ctxb, timeout) + channelCapacity := btcutil.Amount(paymentAmt * 2000) chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, net.Bob, - paymentAmt*2000, 0) + channelCapacity, 0) info, err := getChanInfo(net.Alice) if err != nil { @@ -4645,8 +4646,10 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // Calculate the number of invoices. We will deplete the channel // all the way down to the channel reserve. - chanReserve := info.LocalBalance / 100 - numInvoices := int((info.LocalBalance - chanReserve) / paymentAmt) + chanReserve := channelCapacity / 100 + availableBalance := btcutil.Amount(info.LocalBalance) - chanReserve + numInvoices := int(availableBalance / paymentAmt) + bobAmt := int64(numInvoices * paymentAmt) aliceAmt := info.LocalBalance - bobAmt diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 5ba512db..d5b1af00 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -316,11 +316,11 @@ func (b *BtcWallet) FetchRootKey() (*btcec.PrivateKey, error) { // // This is a part of the WalletController interface. func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut, - feeSatPerByte btcutil.Amount) (*chainhash.Hash, error) { + feeRate lnwallet.SatPerVByte) (*chainhash.Hash, error) { - // The fee rate is passed in using units of sat/byte, so we'll scale + // 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 := feeSatPerByte * 1024 + feeSatPerKB := btcutil.Amount(feeRate * 1000) return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB) } diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 1f45805c..650de152 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -349,7 +349,7 @@ type commitment struct { // feePerKw is the fee per kw used to calculate this commitment // transaction's fee. - feePerKw btcutil.Amount + feePerKw SatPerKWeight // dustLimit is the limit on the commitment transaction such that no // output values should be below this amount. @@ -532,7 +532,7 @@ func (c *commitment) toDiskCommit(ourCommit bool) *channeldb.ChannelCommitment { LocalBalance: c.ourBalance, RemoteBalance: c.theirBalance, CommitFee: c.fee, - FeePerKw: c.feePerKw, + FeePerKw: btcutil.Amount(c.feePerKw), CommitTx: c.txn, CommitSig: c.sig, Htlcs: make([]channeldb.HTLC, 0, numHtlcs), @@ -596,7 +596,7 @@ func (c *commitment) toDiskCommit(ourCommit bool) *channeldb.ChannelCommitment { // commitment struct and updateLog. This function is used when we need to // restore commitment state written do disk back into memory once we need to // restart a channel session. -func (lc *LightningChannel) diskHtlcToPayDesc(feeRate btcutil.Amount, +func (lc *LightningChannel) diskHtlcToPayDesc(feeRate SatPerKWeight, commitHeight uint64, isPendingCommit bool, htlc *channeldb.HTLC, localCommitKeys, remoteCommitKeys *CommitmentKeyRing) (PaymentDescriptor, error) { @@ -673,7 +673,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate btcutil.Amount, // these payment descriptors can be re-inserted into the in-memory updateLog // for each side. func (lc *LightningChannel) extractPayDescs(commitHeight uint64, - isPendingCommit bool, feeRate btcutil.Amount, + isPendingCommit bool, feeRate SatPerKWeight, htlcs []channeldb.HTLC, localCommitKeys *CommitmentKeyRing, remoteCommitKeys *CommitmentKeyRing) ([]PaymentDescriptor, []PaymentDescriptor, error) { @@ -735,7 +735,7 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal, isPendingCommit bool, // update log. incomingHtlcs, outgoingHtlcs, err := lc.extractPayDescs( diskCommit.CommitHeight, isPendingCommit, - diskCommit.FeePerKw, diskCommit.Htlcs, + SatPerKWeight(diskCommit.FeePerKw), diskCommit.Htlcs, localCommitKeys, remoteCommitKeys, ) if err != nil { @@ -756,7 +756,7 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal, isPendingCommit bool, txn: diskCommit.CommitTx, sig: diskCommit.CommitSig, fee: diskCommit.CommitFee, - feePerKw: diskCommit.FeePerKw, + feePerKw: SatPerKWeight(diskCommit.FeePerKw), incomingHTLCs: incomingHtlcs, outgoingHTLCs: outgoingHtlcs, } @@ -1181,12 +1181,12 @@ type LightningChannel struct { // channel initiator) or received (if non-initiator) in an update fee // message, which haven't yet been included in a commitment. It will // be nil if no fee update is un-committed. - pendingFeeUpdate *btcutil.Amount + pendingFeeUpdate *SatPerKWeight // pendingAckFeeUpdate is set to the last committed fee update which is // not yet ACKed. This value will be nil if a fee update hasn't been // initiated. - pendingAckFeeUpdate *btcutil.Amount + pendingAckFeeUpdate *SatPerKWeight // LocalFundingKey is the public key under control by the wallet that // was used for the 2-of-2 funding output which created this channel. @@ -1355,7 +1355,7 @@ func (lc *LightningChannel) ResetState() { // if nothing happened. func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, remoteUpdateLog *updateLog, commitHeight uint64, - feeRate btcutil.Amount, remoteCommitKeys *CommitmentKeyRing, + feeRate SatPerKWeight, remoteCommitKeys *CommitmentKeyRing, remoteDustLimit btcutil.Amount) (*PaymentDescriptor, error) { // Depending on the type of update message we'll map that to a distinct @@ -1626,14 +1626,14 @@ func (lc *LightningChannel) restoreStateLogs( // entry within the updateLog, so we'll just apply it and move // on. if feeUpdate, ok := logUpdate.UpdateMsg.(*lnwire.UpdateFee); ok { - newFeeRate := btcutil.Amount(feeUpdate.FeePerKw) + newFeeRate := SatPerKWeight(feeUpdate.FeePerKw) lc.pendingAckFeeUpdate = &newFeeRate continue } payDesc, err := lc.logUpdateToPayDesc( &logUpdate, lc.remoteUpdateLog, pendingHeight, - pendingCommit.FeePerKw, pendingRemoteKeys, + SatPerKWeight(pendingCommit.FeePerKw), pendingRemoteKeys, lc.channelState.RemoteChanCfg.DustLimit, ) if err != nil { @@ -1930,14 +1930,14 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // htlcTimeoutFee returns the fee in satoshis required for an HTLC timeout // transaction based on the current fee rate. -func htlcTimeoutFee(feePerKw btcutil.Amount) btcutil.Amount { - return (feePerKw * HtlcTimeoutWeight) / 1000 +func htlcTimeoutFee(feePerKw SatPerKWeight) btcutil.Amount { + return feePerKw.FeeForWeight(HtlcTimeoutWeight) } // htlcSuccessFee returns the fee in satoshis required for an HTLC success // transaction based on the current fee rate. -func htlcSuccessFee(feePerKw btcutil.Amount) btcutil.Amount { - return (feePerKw * HtlcSuccessWeight) / 1000 +func htlcSuccessFee(feePerKw SatPerKWeight) btcutil.Amount { + return feePerKw.FeeForWeight(HtlcSuccessWeight) } // htlcIsDust determines if an HTLC output is dust or not depending on two @@ -1946,8 +1946,8 @@ func htlcSuccessFee(feePerKw btcutil.Amount) btcutil.Amount { // require as we currently used second-level HTLC transactions as off-chain // covenants. Depending on the two bits, we'll either be using a timeout or // success transaction which have different weights. -func htlcIsDust(incoming, ourCommit bool, - feePerKw, htlcAmt, dustLimit btcutil.Amount) bool { +func htlcIsDust(incoming, ourCommit bool, feePerKw SatPerKWeight, + htlcAmt, dustLimit btcutil.Amount) bool { // First we'll determine the fee required for this HTLC based on if this is // an incoming HTLC or not, and also on whose commitment transaction it @@ -2135,7 +2135,7 @@ func (lc *LightningChannel) createCommitmentTx(c *commitment, // With the weight known, we can now calculate the commitment fee, // ensuring that we account for any dust outputs trimmed above. - commitFee := btcutil.Amount((int64(c.feePerKw) * totalCommitWeight) / 1000) + commitFee := c.feePerKw.FeeForWeight(totalCommitWeight) // Currently, within the protocol, the initiator always pays the fees. // So we'll subtract the fee amount from the balance of the current @@ -3049,7 +3049,7 @@ func (lc *LightningChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) { // of the HTLCs will be set to the next commitment height. func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, updateState bool) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, int64, - *htlcView, btcutil.Amount) { + *htlcView, SatPerKWeight) { commitChain := lc.localCommitChain dustLimit := lc.localChanCfg.DustLimit @@ -3176,7 +3176,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // Calculate the commitment fee, and subtract it from the // initiator's balance. - commitFee := btcutil.Amount((int64(feePerKw) * commitWeight) / 1000) + commitFee := feePerKw.FeeForWeight(commitWeight) if lc.channelState.IsInitiator { ourBalance -= lnwire.NewMSatFromSatoshis(commitFee) } else { @@ -4229,7 +4229,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer Signer, // Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we // had on their commitment transaction. htlcResolutions, err := extractHtlcResolutions( - remoteCommit.FeePerKw, false, signer, remoteCommit.Htlcs, + SatPerKWeight(remoteCommit.FeePerKw), false, signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, *commitSpend.SpenderTxHash, pCache, ) @@ -4403,7 +4403,7 @@ type HtlcResolutions struct { // the remote party's commitment transaction. func newOutgoingHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, - feePewKw, dustLimit btcutil.Amount, csvDelay uint32, localCommit bool, + feePerKw SatPerKWeight, dustLimit btcutil.Amount, csvDelay uint32, localCommit bool, ) (*OutgoingHtlcResolution, error) { op := wire.OutPoint{ @@ -4453,7 +4453,7 @@ func newOutgoingHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelCon // In order to properly reconstruct the HTLC transaction, we'll need to // re-calculate the fee required at this state, so we can add the // correct output value amount to the transaction. - htlcFee := htlcTimeoutFee(feePewKw) + htlcFee := htlcTimeoutFee(feePerKw) secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee // With the fee calculated, re-construct the second level timeout @@ -4541,9 +4541,8 @@ func newOutgoingHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelCon // TODO(roasbeef) consolidate code with above func func newIncomingHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, - feePewKw, dustLimit btcutil.Amount, csvDelay uint32, localCommit bool, - preimage [32]byte, -) (*IncomingHtlcResolution, error) { + feePerKw SatPerKWeight, dustLimit btcutil.Amount, csvDelay uint32, + localCommit bool, preimage [32]byte) (*IncomingHtlcResolution, error) { op := wire.OutPoint{ Hash: commitHash, @@ -4590,7 +4589,7 @@ func newIncomingHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelCon // First, we'll reconstruct the original HTLC success transaction, // taking into account the fee rate used. - htlcFee := htlcSuccessFee(feePewKw) + htlcFee := htlcSuccessFee(feePerKw) secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee successTx, err := createHtlcSuccessTx( op, secondLevelOutputAmt, csvDelay, @@ -4672,7 +4671,7 @@ func newIncomingHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelCon // extractHtlcResolutions creates a series of outgoing HTLC resolutions, and // the local key used when generating the HTLC scrips. This function is to be // used in two cases: force close, or a unilateral close. -func extractHtlcResolutions(feePerKw btcutil.Amount, ourCommit bool, +func extractHtlcResolutions(feePerKw SatPerKWeight, ourCommit bool, signer Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash, pCache PreimageCache) (*HtlcResolutions, error) { @@ -4872,8 +4871,9 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) { // outgoing HTLC's that we'll need to claim as well. txHash := commitTx.TxHash() htlcResolutions, err := extractHtlcResolutions( - localCommitment.FeePerKw, true, lc.signer, localCommitment.Htlcs, - keyRing, lc.localChanCfg, lc.remoteChanCfg, txHash, lc.pCache) + SatPerKWeight(localCommitment.FeePerKw), true, lc.signer, + localCommitment.Htlcs, keyRing, lc.localChanCfg, + lc.remoteChanCfg, txHash, lc.pCache) if err != nil { return nil, err } @@ -5075,7 +5075,7 @@ func (lc *LightningChannel) availableBalance() (lnwire.MilliSatoshi, int64) { // If we are the channel initiator, we must remember to subtract the // commitment fee from our available balance. - commitFee := btcutil.Amount((int64(feePerKw) * commitWeight) / 1000) + commitFee := feePerKw.FeeForWeight(commitWeight) if lc.channelState.IsInitiator { ourBalance -= lnwire.NewMSatFromSatoshis(commitFee) } @@ -5095,7 +5095,7 @@ func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot { // validateFeeRate ensures that if the passed fee is applied to the channel, // and a new commitment is created (which evaluates this fee), then the // initiator of the channel does not dip below their reserve. -func (lc *LightningChannel) validateFeeRate(feePerKw btcutil.Amount) error { +func (lc *LightningChannel) validateFeeRate(feePerKw SatPerKWeight) error { // We'll ensure that we can accommodate this new fee change, yet still // be above our reserve balance. Otherwise, we'll reject the fee // update. @@ -5105,7 +5105,7 @@ func (lc *LightningChannel) validateFeeRate(feePerKw btcutil.Amount) error { // a commitment now, we'll compute our remaining balance if we apply // this new fee update. newFee := lnwire.NewMSatFromSatoshis( - btcutil.Amount((int64(feePerKw) * txWeight) / 1000), + feePerKw.FeeForWeight(txWeight), ) balanceAfterFee := availableBalance - newFee @@ -5128,7 +5128,7 @@ func (lc *LightningChannel) validateFeeRate(feePerKw btcutil.Amount) error { // UpdateFee initiates a fee update for this channel. Must only be called by // the channel initiator, and must be called before sending update_fee to // the remote. -func (lc *LightningChannel) UpdateFee(feePerKw btcutil.Amount) error { +func (lc *LightningChannel) UpdateFee(feePerKw SatPerKWeight) error { lc.Lock() defer lc.Unlock() @@ -5150,7 +5150,7 @@ func (lc *LightningChannel) UpdateFee(feePerKw btcutil.Amount) error { // ReceiveUpdateFee handles an updated fee sent from remote. This method will // return an error if called as channel initiator. -func (lc *LightningChannel) ReceiveUpdateFee(feePerKw btcutil.Amount) error { +func (lc *LightningChannel) ReceiveUpdateFee(feePerKw SatPerKWeight) error { lc.Lock() defer lc.Unlock() @@ -5301,8 +5301,8 @@ func CreateCooperativeCloseTx(fundingTxIn wire.TxIn, // CalcFee returns the commitment fee to use for the given // fee rate (fee-per-kw). -func (lc *LightningChannel) CalcFee(feeRate uint64) uint64 { - return (feeRate * uint64(CommitWeight)) / 1000 +func (lc *LightningChannel) CalcFee(feeRate SatPerKWeight) btcutil.Amount { + return feeRate.FeeForWeight(CommitWeight) } // RemoteNextRevocation returns the channelState's RemoteNextRevocation. @@ -5325,11 +5325,11 @@ func (lc *LightningChannel) IsInitiator() bool { // CommitFeeRate returns the current fee rate of the commitment transaction in // units of sat-per-kw. -func (lc *LightningChannel) CommitFeeRate() btcutil.Amount { +func (lc *LightningChannel) CommitFeeRate() SatPerKWeight { lc.RLock() defer lc.RUnlock() - return lc.channelState.LocalCommitment.FeePerKw + return SatPerKWeight(lc.channelState.LocalCommitment.FeePerKw) } // IsPending returns true if the channel's funding transaction has been fully diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index d809c4a0..50b6f9a3 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -231,11 +231,11 @@ func createTestChannels(revocationWindow int) (*LightningChannel, } estimator := &StaticFeeEstimator{24} - feePerWeight, err := estimator.EstimateFeePerWeight(1) + feePerVSize, err := estimator.EstimateFeePerVSize(1) if err != nil { return nil, nil, nil, err } - feePerKw := feePerWeight * 1000 + feePerKw := feePerVSize.FeePerKWeight() commitFee := calcStaticFee(0) aliceCommit := channeldb.ChannelCommitment{ @@ -243,7 +243,7 @@ func createTestChannels(revocationWindow int) (*LightningChannel, LocalBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee), RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal), CommitFee: commitFee, - FeePerKw: feePerKw, + FeePerKw: btcutil.Amount(feePerKw), CommitTx: aliceCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), } @@ -252,7 +252,7 @@ func createTestChannels(revocationWindow int) (*LightningChannel, LocalBalance: lnwire.NewMSatFromSatoshis(channelBal), RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee), CommitFee: commitFee, - FeePerKw: feePerKw, + FeePerKw: btcutil.Amount(feePerKw), CommitTx: bobCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), } @@ -747,12 +747,12 @@ func TestCooperativeChannelClosure(t *testing.T) { aliceDeliveryScript := bobsPrivKey[:] bobDeliveryScript := testHdSeed[:] - aliceFeeRate := uint64(aliceChannel.channelState.LocalCommitment.FeePerKw) - bobFeeRate := uint64(bobChannel.channelState.LocalCommitment.FeePerKw) + aliceFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) + bobFeeRate := SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) // We'll store with both Alice and Bob creating a new close proposal // with the same fee. - aliceFee := btcutil.Amount(aliceChannel.CalcFee(aliceFeeRate)) + aliceFee := aliceChannel.CalcFee(aliceFeeRate) aliceSig, _, _, err := aliceChannel.CreateCloseProposal( aliceFee, aliceDeliveryScript, bobDeliveryScript, ) @@ -761,7 +761,7 @@ func TestCooperativeChannelClosure(t *testing.T) { } aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll)) - bobFee := btcutil.Amount(bobChannel.CalcFee(bobFeeRate)) + bobFee := bobChannel.CalcFee(bobFeeRate) bobSig, _, _, err := bobChannel.CreateCloseProposal( bobFee, bobDeliveryScript, aliceDeliveryScript, ) @@ -889,8 +889,8 @@ func TestForceClose(t *testing.T) { // Factoring in the fee rate, Alice's amount should properly reflect // that we've added two additional HTLC to the commitment transaction. totalCommitWeight := CommitWeight + (HtlcWeight * 2) - feePerKw := aliceChannel.channelState.LocalCommitment.FeePerKw - commitFee := btcutil.Amount((int64(feePerKw) * totalCommitWeight) / 1000) + feePerKw := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) + commitFee := feePerKw.FeeForWeight(totalCommitWeight) expectedAmount := (aliceChannel.Capacity / 2) - htlcAmount.ToSatoshis() - commitFee if aliceCommitResolution.SelfOutputSignDesc.Output.Value != int64(expectedAmount) { t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+ @@ -1297,8 +1297,8 @@ func TestHTLCDustLimit(t *testing.T) { // The amount of the HTLC should be above Alice's dust limit and below // Bob's dust limit. - htlcSat := (btcutil.Amount(500) + - htlcTimeoutFee(aliceChannel.channelState.LocalCommitment.FeePerKw)) + htlcSat := (btcutil.Amount(500) + htlcTimeoutFee( + SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw))) htlcAmount := lnwire.NewMSatFromSatoshis(htlcSat) htlc, preimage := createHTLC(0, htlcAmount) @@ -1392,7 +1392,7 @@ func TestChannelBalanceDustLimit(t *testing.T) { aliceBalance := aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis() htlcSat := aliceBalance - defaultFee htlcSat += htlcSuccessFee( - aliceChannel.channelState.LocalCommitment.FeePerKw, + SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw), ) htlcAmount := lnwire.NewMSatFromSatoshis(htlcSat) @@ -1853,8 +1853,8 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { } defer cleanUp() - aliceFeeRate := uint64(aliceChannel.channelState.LocalCommitment.FeePerKw) - bobFeeRate := uint64(bobChannel.channelState.LocalCommitment.FeePerKw) + aliceFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) + bobFeeRate := SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) setDustLimit := func(dustVal btcutil.Amount) { aliceChannel.channelState.LocalChanCfg.DustLimit = dustVal @@ -2069,7 +2069,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { } // Simulate Alice sending update fee message to bob. - fee := btcutil.Amount(111) + fee := SatPerKWeight(111) aliceChannel.UpdateFee(fee) bobChannel.ReceiveUpdateFee(fee) @@ -2089,7 +2089,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { t.Fatalf("bob unable to process alice's new commitment: %v", err) } - if bobChannel.channelState.LocalCommitment.FeePerKw == fee { + if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee { t.Fatalf("bob's feePerKw was unexpectedly locked in") } @@ -2100,7 +2100,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { t.Fatalf("unable to generate bob revocation: %v", err) } - if bobChannel.channelState.LocalCommitment.FeePerKw != fee { + if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee { t.Fatalf("bob's feePerKw was not locked in") } @@ -2125,7 +2125,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { t.Fatalf("alice unable to process bob's new commitment: %v", err) } - if aliceChannel.channelState.LocalCommitment.FeePerKw == fee { + if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) == fee { t.Fatalf("alice's feePerKw was unexpectedly locked in") } @@ -2136,7 +2136,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { t.Fatalf("unable to revoke alice channel: %v", err) } - if aliceChannel.channelState.LocalCommitment.FeePerKw != fee { + if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != fee { t.Fatalf("alice's feePerKw was not locked in") } @@ -2181,7 +2181,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { } // Simulate Alice sending update fee message to bob - fee := btcutil.Amount(111) + fee := SatPerKWeight(111) aliceChannel.UpdateFee(fee) bobChannel.ReceiveUpdateFee(fee) @@ -2228,7 +2228,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { t.Fatalf("alice unable to process bob's new commitment: %v", err) } - if bobChannel.channelState.LocalCommitment.FeePerKw == fee { + if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee { t.Fatalf("bob's feePerKw was unexpectedly locked in") } @@ -2240,7 +2240,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { t.Fatalf("unable to revoke alice channel: %v", err) } - if bobChannel.channelState.LocalCommitment.FeePerKw != fee { + if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee { t.Fatalf("bob's feePerKw was not locked in") } @@ -2264,7 +2264,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { t.Fatalf("alice unable to process bob's new commitment: %v", err) } - if aliceChannel.channelState.LocalCommitment.FeePerKw == fee { + if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) == fee { t.Fatalf("alice's feePerKw was unexpectedly locked in") } @@ -2275,7 +2275,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { t.Fatalf("unable to generate bob revocation: %v", err) } - if aliceChannel.channelState.LocalCommitment.FeePerKw != fee { + if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != fee { t.Fatalf("Alice's feePerKw was not locked in") } @@ -2302,7 +2302,7 @@ func TestUpdateFeeReceiverSendsUpdate(t *testing.T) { // Since Alice is the channel initiator, she should fail when receiving // fee update - fee := btcutil.Amount(111) + fee := SatPerKWeight(111) err = aliceChannel.ReceiveUpdateFee(fee) if err == nil { t.Fatalf("expected alice to fail receiving fee update") @@ -2330,9 +2330,9 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { defer cleanUp() // Simulate Alice sending update fee message to bob. - fee1 := btcutil.Amount(111) - fee2 := btcutil.Amount(222) - fee := btcutil.Amount(333) + fee1 := SatPerKWeight(111) + fee2 := SatPerKWeight(222) + fee := SatPerKWeight(333) aliceChannel.UpdateFee(fee1) aliceChannel.UpdateFee(fee2) aliceChannel.UpdateFee(fee) @@ -2357,15 +2357,15 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { t.Fatalf("bob unable to process alice's new commitment: %v", err) } - if bobChannel.channelState.LocalCommitment.FeePerKw == fee { + if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee { t.Fatalf("bob's feePerKw was unexpectedly locked in") } // Alice sending more fee updates now should not mess up the old fee // they both committed to. - fee3 := btcutil.Amount(444) - fee4 := btcutil.Amount(555) - fee5 := btcutil.Amount(666) + fee3 := SatPerKWeight(444) + fee4 := SatPerKWeight(555) + fee5 := SatPerKWeight(666) aliceChannel.UpdateFee(fee3) aliceChannel.UpdateFee(fee4) aliceChannel.UpdateFee(fee5) @@ -2380,7 +2380,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { t.Fatalf("unable to generate bob revocation: %v", err) } - if bobChannel.channelState.LocalCommitment.FeePerKw != fee { + if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee { t.Fatalf("bob's feePerKw was not locked in") } @@ -2404,7 +2404,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { t.Fatalf("alice unable to process bob's new commitment: %v", err) } - if aliceChannel.channelState.LocalCommitment.FeePerKw == fee { + if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) == fee { t.Fatalf("alice's feePerKw was unexpectedly locked in") } @@ -2415,7 +2415,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { t.Fatalf("unable to revoke alice channel: %v", err) } - if aliceChannel.channelState.LocalCommitment.FeePerKw != fee { + if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != fee { t.Fatalf("alice's feePerKw was not locked in") } @@ -3498,7 +3498,7 @@ func TestFeeUpdateRejectInsaneFee(t *testing.T) { // Next, we'll try to add a fee rate to Alice which is 1,000,000x her // starting fee rate. - startingFeeRate := aliceChannel.channelState.LocalCommitment.FeePerKw + startingFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) newFeeRate := startingFeeRate * 1000000 // Both Alice and Bob should reject this new fee rate as it it far too @@ -3524,7 +3524,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { // First, we'll fetch the current fee rate present within the // commitment transactions. - startingFeeRate := aliceChannel.channelState.LocalCommitment.FeePerKw + startingFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) // Next, we'll start a commitment update, with Alice sending a new // update to double the fee rate of the commitment. @@ -3664,10 +3664,10 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { } // Both parties should now have the latest fee rate locked-in. - if aliceChannel.channelState.LocalCommitment.FeePerKw != newFeeRate { + if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != newFeeRate { t.Fatalf("alice's feePerKw was not locked in") } - if bobChannel.channelState.LocalCommitment.FeePerKw != newFeeRate { + if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != newFeeRate { t.Fatalf("bob's feePerKw was not locked in") } diff --git a/lnwallet/fee_estimator.go b/lnwallet/fee_estimator.go index 28012a40..774d31e7 100644 --- a/lnwallet/fee_estimator.go +++ b/lnwallet/fee_estimator.go @@ -8,19 +8,38 @@ import ( "github.com/roasbeef/btcutil" ) +// SatPerVByte represents a fee rate in satoshis per vbyte. +type SatPerVByte btcutil.Amount + +// FeeForVSize calculates the fee resulting from this fee rate and +// the given vsize in vbytes. +func (s SatPerVByte) FeeForVSize(vbytes int64) btcutil.Amount { + return btcutil.Amount(s) * btcutil.Amount(vbytes) +} + +// FeePerKWeight converts the fee rate into SatPerKWeight. +func (s SatPerVByte) FeePerKWeight() SatPerKWeight { + return SatPerKWeight(s * 1000 / blockchain.WitnessScaleFactor) +} + +// SatPerKWeight represents a fee rate in satoshis per kilo weight unit. +type SatPerKWeight btcutil.Amount + +// FeeForWeight calculates the fee resulting from this fee rate and the +// given weight in weight units (wu). +func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount { + // The resulting fee is rounded down, as specified in BOLT#03. + return btcutil.Amount(s) * btcutil.Amount(wu) / 1000 +} + // FeeEstimator provides the ability to estimate on-chain transaction fees for // various combinations of transaction sizes and desired confirmation time // (measured by number of blocks). type FeeEstimator interface { - // EstimateFeePerByte takes in a target for the number of blocks until + // EstimateFeePerVSize takes in a target for the number of blocks until // an initial confirmation and returns the estimated fee expressed in - // satoshis/byte. - EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, error) - - // EstimateFeePerWeight takes in a target for the number of blocks - // until an initial confirmation and returns the estimated fee - // expressed in satoshis/weight. - EstimateFeePerWeight(numBlocks uint32) (btcutil.Amount, error) + // satoshis/vbyte. + EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) // Start signals the FeeEstimator to start any processes or goroutines // it needs to perform its duty. @@ -35,26 +54,18 @@ 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-byte that will be - // returned by this fee estimator. Queries for the fee rate in weight - // units will be scaled accordingly. - FeeRate btcutil.Amount + // FeeRate is the static fee rate in satoshis-per-vbyte that will be + // returned by this fee estimator. + FeeRate SatPerVByte } -// EstimateFeePerByte will return a static value for fee calculations. +// EstimateFeePerVSize will return a static value for fee calculations. // // NOTE: This method is part of the FeeEstimator interface. -func (e StaticFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, error) { +func (e StaticFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { return e.FeeRate, nil } -// EstimateFeePerWeight will return a static value for fee calculations. -// -// NOTE: This method is part of the FeeEstimator interface. -func (e StaticFeeEstimator) EstimateFeePerWeight(numBlocks uint32) (btcutil.Amount, error) { - return e.FeeRate / blockchain.WitnessScaleFactor, nil -} - // Start signals the FeeEstimator to start any processes or goroutines // it needs to perform its duty. // @@ -79,10 +90,10 @@ 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 byte that + // 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 btcutil.Amount + fallBackFeeRate SatPerVByte btcdConn *rpcclient.Client } @@ -93,7 +104,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 btcutil.Amount) (*BtcdFeeEstimator, error) { + fallBackFeeRate SatPerVByte) (*BtcdFeeEstimator, error) { rpcConfig.DisableConnectOnNew = true rpcConfig.DisableAutoReconnect = false @@ -130,11 +141,13 @@ func (b *BtcdFeeEstimator) Stop() error { return nil } -// EstimateFeePerByte takes in a target for the number of blocks until an +// EstimateFeePerVSize takes in a target for the number of blocks until an // initial confirmation and returns the estimated fee expressed in -// satoshis/byte. -func (b *BtcdFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, error) { - feeEstimate, err := b.fetchEstimatePerByte(numBlocks) +// satoshis/vbyte. +// +// NOTE: This method is part of the FeeEstimator interface. +func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { + feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) switch { // If the estimator doesn't have enough data, or returns an error, then // to return a proper value, then we'll return the default fall back @@ -150,31 +163,10 @@ func (b *BtcdFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, return feeEstimate, nil } -// EstimateFeePerWeight takes in a target for the number of blocks until an -// initial confirmation and returns the estimated fee expressed in -// satoshis/weight. -func (b *BtcdFeeEstimator) EstimateFeePerWeight(numBlocks uint32) (btcutil.Amount, error) { - feePerByte, err := b.EstimateFeePerByte(numBlocks) - if err != nil { - return 0, err - } - - // We'll scale down the fee per byte to fee per weight, as for each raw - // byte, there's 1/4 unit of weight mapped to it. - satWeight := feePerByte / blockchain.WitnessScaleFactor - - // If this ends up scaling down to a zero sat/weight amount, then we'll - // use the default fallback fee rate. - if satWeight == 0 { - return b.fallBackFeeRate / blockchain.WitnessScaleFactor, nil - } - - return satWeight, nil -} - // fetchEstimate returns a fee estimate for a transaction be be confirmed in -// confTarget blocks. The estimate is returned in sat/byte. -func (b *BtcdFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil.Amount, error) { +// confTarget blocks. The estimate is returned in sat/vbyte. +func (b *BtcdFeeEstimator) fetchEstimatePerVSize( + confTarget uint32) (SatPerVByte, error) { // First, we'll fetch the estimate for our confirmation target. btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget)) if err != nil { @@ -189,14 +181,14 @@ func (b *BtcdFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil.Amou } // The value returned is expressed in fees per KB, while we want - // fee-per-byte, so we'll divide by 1024 to map to satoshis-per-byte + // fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte // before returning the estimate. - satPerByte := satPerKB / 1024 + satPerByte := satPerKB / 1000 - walletLog.Debugf("Returning %v sat/byte for conf target of %v", + walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", int64(satPerByte), confTarget) - return satPerByte, nil + return SatPerVByte(satPerByte), nil } // A compile-time assertion to ensure that BtcdFeeEstimator implements the @@ -207,10 +199,10 @@ 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 byte that + // 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 btcutil.Amount + fallBackFeeRate SatPerVByte bitcoindConn *rpcclient.Client } @@ -221,7 +213,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 btcutil.Amount) (*BitcoindFeeEstimator, error) { + fallBackFeeRate SatPerVByte) (*BitcoindFeeEstimator, error) { rpcConfig.DisableConnectOnNew = true rpcConfig.DisableAutoReconnect = false @@ -254,11 +246,13 @@ func (b *BitcoindFeeEstimator) Stop() error { return nil } -// EstimateFeePerByte takes in a target for the number of blocks until an +// EstimateFeePerVSize takes in a target for the number of blocks until an // initial confirmation and returns the estimated fee expressed in -// satoshis/byte. -func (b *BitcoindFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, error) { - feeEstimate, err := b.fetchEstimatePerByte(numBlocks) +// satoshis/vbyte. +// +// NOTE: This method is part of the FeeEstimator interface. +func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { + feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) switch { // If the estimator doesn't have enough data, or returns an error, then // to return a proper value, then we'll return the default fall back @@ -274,33 +268,10 @@ func (b *BitcoindFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amo return feeEstimate, nil } -// EstimateFeePerWeight takes in a target for the number of blocks until an -// initial confirmation and returns the estimated fee expressed in -// satoshis/weight. -func (b *BitcoindFeeEstimator) EstimateFeePerWeight(numBlocks uint32) (btcutil.Amount, error) { - feePerByte, err := b.EstimateFeePerByte(numBlocks) - if err != nil { - return 0, err - } - - // We'll scale down the fee per byte to fee per weight, as for each raw - // byte, there's 1/4 unit of weight mapped to it. - satWeight := feePerByte / blockchain.WitnessScaleFactor - - // If this ends up scaling down to a zero sat/weight amount, then we'll - // use the default fallback fee rate. - // TODO(aakselrod): maybe use the per-byte rate if it's non-zero? - // Otherwise, we can return a higher sat/byte than sat/weight. - if satWeight == 0 { - return b.fallBackFeeRate / blockchain.WitnessScaleFactor, nil - } - - return satWeight, nil -} - -// fetchEstimate returns a fee estimate for a transaction be be confirmed in -// confTarget blocks. The estimate is returned in sat/byte. -func (b *BitcoindFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil.Amount, error) { +// fetchEstimatePerVSize returns a fee estimate for a transaction be be confirmed in +// confTarget blocks. The estimate is returned in sat/vbyte. +func (b *BitcoindFeeEstimator) fetchEstimatePerVSize( + confTarget uint32) (SatPerVByte, error) { // First, we'll send an "estimatesmartfee" command as a raw request, // since it isn't supported by btcd but is available in bitcoind. target, err := json.Marshal(uint64(confTarget)) @@ -335,10 +306,10 @@ func (b *BitcoindFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil. // before returning the estimate. satPerByte := satPerKB / 1000 - walletLog.Debugf("Returning %v sat/byte for conf target of %v", + walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", int64(satPerByte), confTarget) - return satPerByte, nil + return SatPerVByte(satPerByte), 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 new file mode 100644 index 00000000..3b1d9301 --- /dev/null +++ b/lnwallet/fee_estimator_test.go @@ -0,0 +1,94 @@ +package lnwallet_test + +import ( + "testing" + + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/roasbeef/btcutil" +) + +// TestFeeRateTypes checks that converting fee rates between the +// different types that represent fee rates and calculating fees +// work as expected. +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) + } + + // 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 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) + } + } + + // Do the same for fee per kw. + for f := lnwallet.SatPerKWeight(0); f < 1500; f++ { + weight := int64(txVSize * 4) + + // 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) + } + } +} + +// TestStaticFeeEstimator checks that the StaticFeeEstimator +// returns the expected fee rate. +func TestStaticFeeEstimator(t *testing.T) { + t.Parallel() + + const feePerVSize = 100 + + feeEstimator := &lnwallet.StaticFeeEstimator{ + FeeRate: feePerVSize, + } + if err := feeEstimator.Start(); err != nil { + t.Fatalf("unable to start fee estimator: %v", err) + } + defer feeEstimator.Stop() + + feeRate, err := feeEstimator.EstimateFeePerVSize(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) + } +} diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 21cb35d0..17fec68c 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -161,9 +161,9 @@ type WalletController interface { // 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/byte that should be used when crafting the transaction. + // sat/vbyte that should be used when crafting the transaction. SendOutputs(outputs []*wire.TxOut, - feeSatPerByte btcutil.Amount) (*chainhash.Hash, error) + feeRate SatPerVByte) (*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 94ac3825..ed15c8d7 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -283,13 +283,13 @@ 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. - feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1) + feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() aliceChanReservation, err := alice.InitChannelReservation( - fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw, + fundingAmount*2, fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) @@ -313,7 +313,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, // receives' Alice's contribution, and consumes that so we can continue // the funding process. bobChanReservation, err := bob.InitChannelReservation(fundingAmount*2, - fundingAmount, 0, feePerKw, feePerKw, alicePub, aliceAddr, + fundingAmount, 0, feePerKw, feeRate, alicePub, aliceAddr, chainHash, lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("bob unable to init channel reservation: %v", err) @@ -449,13 +449,13 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, // Create a single channel asking for 16 BTC total. fundingAmount := btcutil.Amount(8 * 1e8) - feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1) + feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() _, err = alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, + fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel, ) if err != nil { @@ -467,7 +467,8 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, // that aren't locked, so this should fail. amt := btcutil.Amount(900 * 1e8) failedReservation, err := alice.InitChannelReservation(amt, amt, 0, - feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) + feePerKw, feeRate, bobPub, bobAddr, chainHash, + lnwire.FFAnnounceChannel) if err == nil { t.Fatalf("not error returned, should fail on coin selection") } @@ -482,16 +483,16 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, alice, _ *lnwallet.LightningWallet, t *testing.T) { - feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1) + feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() // Create a reservation for 44 BTC. fundingAmount := btcutil.Amount(44 * 1e8) chanReservation, err := alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, + fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) @@ -499,7 +500,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, // Attempt to create another channel with 44 BTC, this should fail. _, err = alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, + fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel, ) if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { @@ -530,8 +531,9 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, // attempting coin selection. // Request to fund a new channel should now succeed. - _, err = alice.InitChannelReservation(fundingAmount, fundingAmount, 0, - feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) + _, err = alice.InitChannelReservation(fundingAmount, fundingAmount, + 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, + lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } @@ -540,14 +542,15 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, func testCancelNonExistentReservation(miner *rpctest.Harness, alice, _ *lnwallet.LightningWallet, t *testing.T) { - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1) + feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(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, alice, 22, 10, &testHdSeed, lnwire.FFAnnounceChannel, + 10000, 10000, feeRate.FeePerKWeight(), alice, + 22, 10, &testHdSeed, lnwire.FFAnnounceChannel, ) if err != nil { t.Fatalf("unable to create res: %v", err) @@ -567,9 +570,10 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness, // rate. This should push our balance into the negative and result in a // failure to create the reservation. fundingAmount := btcutil.Amount(4 * 1e8) - feePerKw := btcutil.Amount(btcutil.SatoshiPerBitcoin * 10) + feePerVSize := lnwallet.SatPerVByte(btcutil.SatoshiPerBitcoin * 4 / 100) + feePerKw := feePerVSize.FeePerKWeight() _, err := alice.InitChannelReservation( - fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + fundingAmount, fundingAmount, 0, feePerKw, feePerVSize, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel, ) switch { @@ -636,13 +640,13 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, // towards Bob's side. fundingAmt := btcutil.Amount(4 * 1e8) pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) - feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1) + feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() aliceChanReservation, err := alice.InitChannelReservation(fundingAmt, - fundingAmt, pushAmt, feePerKw, feePerKw, bobPub, bobAddr, chainHash, + fundingAmt, pushAmt, feePerKw, feeRate, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) @@ -666,7 +670,7 @@ 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, feePerKw, alicePub, aliceAddr, chainHash, + pushAmt, feePerKw, feeRate, alicePub, aliceAddr, chainHash, lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("unable to create bob reservation: %v", err) diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 0465531b..1ea28015 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -135,9 +135,9 @@ type ChannelReservation struct { // used only internally by lnwallet. In order to concurrent safety, the // creation of all channel reservations should be carried out via the // lnwallet.InitChannelReservation interface. -func NewChannelReservation(capacity, fundingAmt, commitFeePerKw btcutil.Amount, - wallet *LightningWallet, id uint64, pushMSat lnwire.MilliSatoshi, - chainHash *chainhash.Hash, +func NewChannelReservation(capacity, fundingAmt btcutil.Amount, + commitFeePerKw SatPerKWeight, wallet *LightningWallet, + id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash, flags lnwire.FundingFlag) (*ChannelReservation, error) { var ( @@ -146,10 +146,7 @@ func NewChannelReservation(capacity, fundingAmt, commitFeePerKw btcutil.Amount, initiator bool ) - commitFee := btcutil.Amount( - (int64(commitFeePerKw) * CommitWeight) / 1000, - ) - + commitFee := commitFeePerKw.FeeForWeight(CommitWeight) fundingMSat := lnwire.NewMSatFromSatoshis(fundingAmt) capacityMSat := lnwire.NewMSatFromSatoshis(capacity) feeMSat := lnwire.NewMSatFromSatoshis(commitFee) @@ -229,13 +226,13 @@ func NewChannelReservation(capacity, fundingAmt, commitFeePerKw btcutil.Amount, LocalCommitment: channeldb.ChannelCommitment{ LocalBalance: ourBalance, RemoteBalance: theirBalance, - FeePerKw: commitFeePerKw, + FeePerKw: btcutil.Amount(commitFeePerKw), CommitFee: commitFee, }, RemoteCommitment: channeldb.ChannelCommitment{ LocalBalance: ourBalance, RemoteBalance: theirBalance, - FeePerKw: commitFeePerKw, + FeePerKw: btcutil.Amount(commitFeePerKw), CommitFee: commitFee, }, Db: wallet.Cfg.Database, diff --git a/lnwallet/size.go b/lnwallet/size.go index d633c390..d6821eac 100644 --- a/lnwallet/size.go +++ b/lnwallet/size.go @@ -513,3 +513,9 @@ func (twe *TxWeightEstimator) Weight() int { } return weight } + +// VSize gets the estimated virtual size of the transactions, in vbytes. +func (twe *TxWeightEstimator) VSize() int { + // A tx's vsize is 1/4 of the weight, rounded up. + return (twe.Weight() + witnessScaleFactor - 1) / witnessScaleFactor +} diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index babe84e2..ecd4bf23 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -787,7 +787,7 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { height: test.commitment.CommitHeight, ourBalance: test.commitment.LocalBalance, theirBalance: test.commitment.RemoteBalance, - feePerKw: test.commitment.FeePerKw, + feePerKw: SatPerKWeight(test.commitment.FeePerKw), dustLimit: tc.dustLimit, isOurs: true, } @@ -829,8 +829,8 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { // Generate second-level HTLC transactions for HTLCs in // commitment tx. htlcResolutions, err := extractHtlcResolutions( - test.commitment.FeePerKw, true, signer, htlcs, keys, - channel.localChanCfg, channel.remoteChanCfg, + SatPerKWeight(test.commitment.FeePerKw), true, signer, + htlcs, keys, channel.localChanCfg, channel.remoteChanCfg, commitTx.TxHash(), pCache, ) if err != nil { diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 8a1924ca..c394ff2d 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -97,11 +97,11 @@ type initFundingReserveMsg struct { // of initial commitment transactions. In order to ensure timely // confirmation, it is recommended that this fee should be generous, // paying some multiple of the accepted base fee rate of the network. - commitFeePerKw btcutil.Amount + commitFeePerKw SatPerKWeight - // fundingFeePerWeight is the fee rate in satoshis per eight unit to - // use for the initial funding transaction. - fundingFeePerWeight btcutil.Amount + // fundingFeePerVSize is the fee rate in sat/vbyte to use for the + // initial funding transaction. + fundingFeePerVSize SatPerVByte // pushMSat is the number of milli-satoshis that should be pushed over // the responder as part of the initial channel creation. @@ -450,7 +450,7 @@ out: // commitment transaction is valid. func (l *LightningWallet) InitChannelReservation( capacity, ourFundAmt btcutil.Amount, pushMSat lnwire.MilliSatoshi, - commitFeePerKw, fundingFeePerWeight btcutil.Amount, + commitFeePerKw SatPerKWeight, fundingFeePerVSize SatPerVByte, theirID *btcec.PublicKey, theirAddr net.Addr, chainHash *chainhash.Hash, flags lnwire.FundingFlag) (*ChannelReservation, error) { @@ -458,17 +458,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, - fundingFeePerWeight: fundingFeePerWeight, - pushMSat: pushMSat, - flags: flags, - err: errChan, - resp: respChan, + chainHash: chainHash, + nodeID: theirID, + nodeAddr: theirAddr, + fundingAmount: ourFundAmt, + capacity: capacity, + commitFeePerKw: commitFeePerKw, + fundingFeePerVSize: fundingFeePerVSize, + pushMSat: pushMSat, + flags: flags, + err: errChan, + resp: respChan, } return <-respChan, <-errChan @@ -516,10 +516,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-weight, we'll - // use the passed sat/byte passed in to perform coin selection. + // Coin selection is done on the basis of sat-per-vbyte, we'll + // use the passed sat/vbyte passed in to perform coin selection. err := l.selectCoinsAndChange( - req.fundingFeePerWeight, req.fundingAmount, + req.fundingFeePerVSize, req.fundingAmount, reservation.ourContribution, ) if err != nil { @@ -1284,7 +1284,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(feeRatePerWeight btcutil.Amount, +func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerVByte, amt btcutil.Amount, contribution *ChannelContribution) error { // We hold the coin select mutex while querying for outputs, and @@ -1294,7 +1294,7 @@ func (l *LightningWallet) selectCoinsAndChange(feeRatePerWeight btcutil.Amount, defer l.coinSelectMtx.Unlock() walletLog.Infof("Performing funding tx coin selection using %v "+ - "sat/weight as fee rate", int64(feeRatePerWeight)) + "sat/vbyte as fee rate", int64(feeRate)) // Find all unlocked unspent witness outputs with greater than 1 // confirmation. @@ -1307,7 +1307,7 @@ func (l *LightningWallet) selectCoinsAndChange(feeRatePerWeight btcutil.Amount, // Perform coin selection over our available, unlocked unspent outputs // in order to find enough coins to meet the funding amount // requirements. - selectedCoins, changeAmt, err := coinSelect(feeRatePerWeight, amt, coins) + selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins) if err != nil { return err } @@ -1415,9 +1415,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/byte for coin selection to +// specified fee rate should be expressed in sat/vbyte for coin selection to // function properly. -func coinSelect(feeRatePerWeight, amt btcutil.Amount, +func coinSelect(feeRate SatPerVByte, amt btcutil.Amount, coins []*Utxo) ([]*Utxo, btcutil.Amount, error) { amtNeeded := amt @@ -1461,9 +1461,7 @@ func coinSelect(feeRatePerWeight, 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 := btcutil.Amount( - uint64(weightEstimate.Weight()) * uint64(feeRatePerWeight), - ) + requiredFee := feeRate.FeeForVSize(int64(weightEstimate.VSize())) if overShootAmt < requiredFee { amtNeeded = amt + requiredFee continue diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go index 950752eb..35d80038 100644 --- a/lnwire/open_channel.go +++ b/lnwire/open_channel.go @@ -68,6 +68,9 @@ type OpenChannel struct { // FeePerKiloWeight is the initial fee rate that the initiator suggests // for both commitment transaction. This value is expressed in sat per // kilo-weight. + // + // TODO(halseth): make SatPerKWeight when fee estimation is in own + // package. Currently this will cause an import cycle. FeePerKiloWeight uint32 // CsvDelay is the number of blocks to use for the relative time lock diff --git a/lnwire/update_fee.go b/lnwire/update_fee.go index 9937c3ab..255028dc 100644 --- a/lnwire/update_fee.go +++ b/lnwire/update_fee.go @@ -1,6 +1,8 @@ package lnwire -import "io" +import ( + "io" +) // UpdateFee is the message the channel initiator sends to the other peer if // the channel commitment fee needs to be updated. @@ -10,6 +12,9 @@ type UpdateFee struct { // FeePerKw is the fee-per-kw on commit transactions that the sender of // this message wants to use for this channel. + // + // TODO(halseth): make SatPerKWeight when fee estimation is moved to + // own package. Currently this will cause an import cycle. FeePerKw uint32 } diff --git a/mock.go b/mock.go index 25528042..39a2eba2 100644 --- a/mock.go +++ b/mock.go @@ -229,7 +229,7 @@ func (m *mockWalletController) FetchRootKey() (*btcec.PrivateKey, error) { return m.rootKey, nil } func (*mockWalletController) SendOutputs(outputs []*wire.TxOut, - _ btcutil.Amount) (*chainhash.Hash, error) { + _ lnwallet.SatPerVByte) (*chainhash.Hash, error) { return nil, nil } diff --git a/peer.go b/peer.go index 2a5de8bd..a2f92738 100644 --- a/peer.go +++ b/peer.go @@ -1415,7 +1415,7 @@ 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. - satPerWight, err := p.server.cc.feeEstimator.EstimateFeePerWeight(6) + feePerVSize, err := p.server.cc.feeEstimator.EstimateFeePerVSize(6) if err != nil { return nil, fmt.Errorf("unable to query fee "+ "estimator: %v", err) @@ -1424,7 +1424,7 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e // 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 := satPerWight * 1000 + targetFeePerKw := feePerVSize.FeePerKWeight() _, startingHeight, err := p.server.cc.chainIO.GetBestBlock() if err != nil { diff --git a/peer_test.go b/peer_test.go index 4e9f93f0..47399dbc 100644 --- a/peer_test.go +++ b/peer_test.go @@ -139,7 +139,7 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) { CloseType: htlcswitch.CloseRegular, ChanPoint: initiatorChan.ChannelPoint(), Updates: updateChan, - TargetFeePerKw: 12000, + TargetFeePerKw: 12500, Err: errChan, } initiator.localCloseChanReqs <- closeCommand @@ -170,11 +170,12 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) { } estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} - feeRate, err := estimator.EstimateFeePerWeight(1) + feeRate, err := estimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - fee := btcutil.Amount(responderChan.CalcFee(uint64(feeRate * 1000))) + feePerKw := feeRate.FeePerKWeight() + fee := responderChan.CalcFee(feePerKw) closeSig, _, _, err := responderChan.CreateCloseProposal(fee, dummyDeliveryScript, initiatorDeliveryScript) if err != nil { @@ -428,7 +429,7 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { CloseType: htlcswitch.CloseRegular, ChanPoint: initiatorChan.ChannelPoint(), Updates: updateChan, - TargetFeePerKw: 12000, + TargetFeePerKw: 12500, Err: errChan, } @@ -460,12 +461,12 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { } estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} - initiatorIdealFeeRate, err := estimator.EstimateFeePerWeight(1) + initiatorIdealFeeRate, err := estimator.EstimateFeePerVSize(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } initiatorIdealFee := responderChan.CalcFee( - uint64(initiatorIdealFeeRate * 1000), + initiatorIdealFeeRate.FeePerKWeight(), ) increasedFee := btcutil.Amount(float64(initiatorIdealFee) * 2.5) closeSig, _, _, err := responderChan.CreateCloseProposal( @@ -499,7 +500,7 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { if !ok { t.Fatalf("expected ClosingSigned message, got %T", msg) } - if uint64(closingSignedMsg.FeeSatoshis) != initiatorIdealFee { + if closingSignedMsg.FeeSatoshis != initiatorIdealFee { t.Fatalf("expected ClosingSigned fee to be %v, instead got %v", initiatorIdealFee, closingSignedMsg.FeeSatoshis) } diff --git a/pilot.go b/pilot.go index 220caf97..c82b0f17 100644 --- a/pilot.go +++ b/pilot.go @@ -85,7 +85,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. - feePerWeight, err := c.server.cc.feeEstimator.EstimateFeePerWeight(3) + feePerVSize, err := c.server.cc.feeEstimator.EstimateFeePerVSize(3) if err != nil { return err } @@ -94,7 +94,7 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey, minHtlc := lnwire.NewMSatFromSatoshis(1) updateStream, errChan := c.server.OpenChannel(target, amt, 0, - minHtlc, feePerWeight, false) + minHtlc, feePerVSize, false) select { case err := <-errChan: diff --git a/rpcserver.go b/rpcserver.go index 0991921b..61ddc81a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -344,28 +344,28 @@ 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, - feePerByte btcutil.Amount) (*chainhash.Hash, error) { + feeRate lnwallet.SatPerVByte) (*chainhash.Hash, error) { outputs, err := addrPairsToOutputs(paymentMap) if err != nil { return nil, err } - return r.server.cc.wallet.SendOutputs(outputs, feePerByte) + return r.server.cc.wallet.SendOutputs(outputs, feeRate) } -// determineFeePerByte will determine the fee in sat/byte that should be paid +// 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 determineFeePerByte(feeEstimator lnwallet.FeeEstimator, targetConf int32, - satPerByte int64) (btcutil.Amount, error) { +func determineFeePerVSize(feeEstimator lnwallet.FeeEstimator, targetConf int32, + feePerByte int64) (lnwallet.SatPerVByte, 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: - satPerByte, err := feeEstimator.EstimateFeePerByte( + feePerVSize, err := feeEstimator.EstimateFeePerVSize( uint32(targetConf), ) if err != nil { @@ -373,22 +373,22 @@ func determineFeePerByte(feeEstimator lnwallet.FeeEstimator, targetConf int32, "estimator: %v", err) } - return btcutil.Amount(satPerByte), nil + return feePerVSize, nil // If a manual sat/byte fee rate is set, then we'll use that directly. - case satPerByte != 0: - return btcutil.Amount(satPerByte), nil + case feePerByte != 0: + return lnwallet.SatPerVByte(feePerByte), nil // Otherwise, we'll attempt a relaxed confirmation target for the // transaction default: - satPerByte, err := feeEstimator.EstimateFeePerByte(6) + feePerVSize, err := feeEstimator.EstimateFeePerVSize(6) if err != nil { return 0, fmt.Errorf("unable to query fee "+ "estimator: %v", err) } - return satPerByte, nil + return feePerVSize, nil } } @@ -399,18 +399,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. - feePerByte, err := determineFeePerByte( + feeRate, err := determineFeePerVSize( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/byte=%v", - in.Addr, btcutil.Amount(in.Amount), int64(feePerByte)) + rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/vbyte=%v", + in.Addr, btcutil.Amount(in.Amount), int64(feeRate)) paymentMap := map[string]int64{in.Addr: in.Amount} - txid, err := r.sendCoinsOnChain(paymentMap, feePerByte) + txid, err := r.sendCoinsOnChain(paymentMap, feeRate) if err != nil { return nil, err } @@ -427,17 +427,17 @@ func (r *rpcServer) SendMany(ctx context.Context, // Based on the passed fee related parameters, we'll determine an // approriate fee rate for this transaction. - feePerByte, err := determineFeePerByte( + feeRate, err := determineFeePerVSize( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Infof("[sendmany] outputs=%v, sat/byte=%v", - spew.Sdump(in.AddrToAmount), int64(feePerByte)) + rpcsLog.Infof("[sendmany] outputs=%v, sat/vbyte=%v", + spew.Sdump(in.AddrToAmount), int64(feeRate)) - txid, err := r.sendCoinsOnChain(in.AddrToAmount, feePerByte) + txid, err := r.sendCoinsOnChain(in.AddrToAmount, feeRate) if err != nil { return nil, err } @@ -742,15 +742,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. - feePerByte, err := determineFeePerByte( + feeRate, err := determineFeePerVSize( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return err } - rpcsLog.Debugf("[openchannel]: using fee of %v sat/byte for funding "+ - "tx", int64(feePerByte)) + rpcsLog.Debugf("[openchannel]: using fee of %v sat/vbyte 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 @@ -758,7 +758,7 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest, updateChan, errChan := r.server.OpenChannel( nodePubKey, localFundingAmt, lnwire.NewMSatFromSatoshis(remoteInitialBalance), - minHtlc, feePerByte, in.Private, + minHtlc, feeRate, in.Private, ) var outpoint wire.OutPoint @@ -864,20 +864,20 @@ 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. - feePerByte, err := determineFeePerByte( + feeRate, err := determineFeePerVSize( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Tracef("[openchannel] target sat/byte for funding tx: %v", - int64(feePerByte)) + rpcsLog.Tracef("[openchannel] target sat/vbyte for funding tx: %v", + int64(feeRate)) updateChan, errChan := r.server.OpenChannel( nodepubKey, localFundingAmt, lnwire.NewMSatFromSatoshis(remoteInitialBalance), - minHtlc, feePerByte, in.Private, + minHtlc, feeRate, in.Private, ) select { @@ -1045,24 +1045,20 @@ 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. - feePerByte, err := determineFeePerByte( + feeRate, err := determineFeePerVSize( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return err } - rpcsLog.Debugf("Target sat/byte for closing transaction: %v", - int64(feePerByte)) + rpcsLog.Debugf("Target sat/vbyte for closing transaction: %v", + int64(feeRate)) - // When crating commitment transaction, or closure - // transactions, we typically deal in fees per-kw, so we'll - // convert now before passing the close request to the switch. - feePerWeight := (feePerByte / blockchain.WitnessScaleFactor) - if feePerWeight == 0 { + if feeRate == 0 { // If the fee rate returned isn't usable, then we'll // fall back to an lax fee estimate. - feePerWeight, err = r.server.cc.feeEstimator.EstimateFeePerWeight(6) + feeRate, err = r.server.cc.feeEstimator.EstimateFeePerVSize(6) if err != nil { return err } @@ -1072,7 +1068,7 @@ 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 := feePerWeight * 1000 + feePerKw := feeRate.FeePerKWeight() updateChan, errChan = r.server.htlcSwitch.CloseLink(chanPoint, htlcswitch.CloseRegular, feePerKw) } diff --git a/server.go b/server.go index 176ab57e..8573b7d4 100644 --- a/server.go +++ b/server.go @@ -25,7 +25,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" - "github.com/roasbeef/btcd/blockchain" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/connmgr" @@ -1651,7 +1650,7 @@ type openChanReq struct { pushAmt lnwire.MilliSatoshi - fundingFeePerWeight btcutil.Amount + fundingFeePerVSize lnwallet.SatPerVByte private bool @@ -1779,7 +1778,7 @@ func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error { func (s *server) OpenChannel(nodeKey *btcec.PublicKey, localAmt btcutil.Amount, pushAmt lnwire.MilliSatoshi, minHtlc lnwire.MilliSatoshi, - fundingFeePerByte btcutil.Amount, + fundingFeePerVSize lnwallet.SatPerVByte, private bool) (chan *lnrpc.OpenStatusUpdate, chan error) { updateChan := make(chan *lnrpc.OpenStatusUpdate, 1) @@ -1811,15 +1810,11 @@ func (s *server) OpenChannel(nodeKey *btcec.PublicKey, return updateChan, errChan } - // We'll scale the sat/byte set as the fee rate to sat/weight as this - // is what's used internally when deciding upon coin selection. - fundingFeePerWeight := fundingFeePerByte / blockchain.WitnessScaleFactor - - // If the fee rate wasn't high enough to cleanly convert to weight, - // then we'll use a default confirmation target. - if fundingFeePerWeight == 0 { + // If the fee rate wasn't specified, then we'll use a default + // confirmation target. + if fundingFeePerVSize == 0 { estimator := s.cc.feeEstimator - fundingFeePerWeight, err = estimator.EstimateFeePerWeight(6) + fundingFeePerVSize, err = estimator.EstimateFeePerVSize(6) if err != nil { errChan <- err return updateChan, errChan @@ -1831,15 +1826,15 @@ 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, - fundingFeePerWeight: fundingFeePerWeight, - pushAmt: pushAmt, - private: private, - minHtlc: minHtlc, - updates: updateChan, - err: errChan, + targetPubkey: nodeKey, + chainHash: *activeNetParams.GenesisHash, + localFundingAmt: localAmt, + fundingFeePerVSize: fundingFeePerVSize, + pushAmt: pushAmt, + private: private, + minHtlc: minHtlc, + 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 c5d6bf90..2f279c30 100644 --- a/test_utils.go +++ b/test_utils.go @@ -138,19 +138,19 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, } estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} - feePerWeight, err := estimator.EstimateFeePerWeight(1) + feePerVSize, err := estimator.EstimateFeePerVSize(1) if err != nil { return nil, nil, nil, nil, err } - feePerKw := feePerWeight * 1000 + feePerKw := feePerVSize.FeePerKWeight() // TODO(roasbeef): need to factor in commit fee? aliceCommit := channeldb.ChannelCommitment{ CommitHeight: 0, LocalBalance: lnwire.NewMSatFromSatoshis(channelBal), RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal), - FeePerKw: feePerKw, - CommitFee: 8688, + FeePerKw: btcutil.Amount(feePerKw), + CommitFee: feePerKw.FeeForWeight(lnwallet.CommitWeight), CommitTx: aliceCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), } @@ -158,8 +158,8 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, CommitHeight: 0, LocalBalance: lnwire.NewMSatFromSatoshis(channelBal), RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal), - FeePerKw: feePerKw, - CommitFee: 8688, + FeePerKw: btcutil.Amount(feePerKw), + CommitFee: feePerKw.FeeForWeight(lnwallet.CommitWeight), CommitTx: bobCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), } diff --git a/utxonursery.go b/utxonursery.go index 1f0045d7..d9f52a50 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)) - txWeight := uint64(weightEstimate.Weight()) - return u.populateSweepTx(txWeight, classHeight, csvOutputs, cltvOutputs) + txVSize := int64(weightEstimate.VSize()) + return u.populateSweepTx(txVSize, 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(txWeight uint64, classHeight uint32, +func (u *utxoNursery) populateSweepTx(txVSize int64, classHeight uint32, csvInputs []CsvSpendableOutput, cltvInputs []SpendableOutput) (*wire.MsgTx, error) { @@ -1022,11 +1022,11 @@ func (u *utxoNursery) populateSweepTx(txWeight uint64, classHeight uint32, } // Using the txn weight estimate, compute the required txn fee. - feePerWeight, err := u.cfg.Estimator.EstimateFeePerWeight(6) + feePerVSize, err := u.cfg.Estimator.EstimateFeePerVSize(6) if err != nil { return nil, err } - txFee := btcutil.Amount(txWeight) * feePerWeight + txFee := feePerVSize.FeeForVSize(txVSize) // Sweep as much possible, after subtracting txn fees. sweepAmt := int64(totalSum - txFee)