Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/release-notes/release-notes-0.21.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@

## Code Health

* [Extends sat/kw support to rbf cooperative channel closes](https://github.com/lightningnetwork/lnd/pull/10425), ensuring consistent fee handling across the
Comment thread
MPins marked this conversation as resolved.
closing flow and adding `fee_per_kw` to the RPC message `PendingUpdate`.

* [Update taproot detection](https://github.com/lightningnetwork/lnd/pull/10683)
to accommodate buried activation (and modified RPC `getdeploymentinfo`
response) beginning in Bitcoin Core v32.
Expand Down
14 changes: 8 additions & 6 deletions itest/lnd_coop_close_rbf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest,
alicePendingUpdate := aliceCloseUpdate.GetClosePending()
require.NotNil(st, aliceCloseUpdate)
require.Equal(
st, int64(aliceFeeRate), alicePendingUpdate.FeePerVbyte,
st, int64(aliceFeeRate), int64(alicePendingUpdate.FeePerKw/250),
)
require.True(st, alicePendingUpdate.LocalCloseTx)

Expand All @@ -48,7 +48,9 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest,
// Confirm that this new update was at 10 sat/vb.
bobPendingUpdate := bobCloseUpdate.GetClosePending()
require.NotNil(st, bobCloseUpdate)
require.Equal(st, bobPendingUpdate.FeePerVbyte, int64(bobFeeRate))
require.Equal(
st, int64(bobPendingUpdate.FeePerKw/250), int64(bobFeeRate),
)
require.True(st, bobPendingUpdate.LocalCloseTx)

var err error
Expand All @@ -69,11 +71,11 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest,
// calculation for taproot.
require.InDelta(
st, int64(bobFeeRate),
alicePendingUpdate.FeePerVbyte, 1,
int64(alicePendingUpdate.FeePerKw/250), 1,
)
} else {
require.Equal(
st, alicePendingUpdate.FeePerVbyte,
st, int64(alicePendingUpdate.FeePerKw/250),
int64(bobFeeRate),
)
}
Expand All @@ -92,7 +94,7 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest,
alicePendingUpdate = aliceCloseUpdate.GetClosePending()
require.NotNil(st, aliceCloseUpdate)
require.Equal(
st, alicePendingUpdate.FeePerVbyte,
st, int64(alicePendingUpdate.FeePerKw/250),
int64(aliceRejectedFeeRate),
)
require.True(st, alicePendingUpdate.LocalCloseTx)
Expand Down Expand Up @@ -126,7 +128,7 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest,
alicePendingUpdate = aliceCloseUpdate.GetClosePending()
require.NotNil(st, aliceCloseUpdate)
require.Equal(
st, alicePendingUpdate.FeePerVbyte, int64(aliceFeeRate),
st, int64(alicePendingUpdate.FeePerKw/250), int64(aliceFeeRate),
)
require.True(st, alicePendingUpdate.LocalCloseTx)

Expand Down
25 changes: 18 additions & 7 deletions lnrpc/lightning.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lnrpc/lightning.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2286,7 +2286,9 @@ message CloseStatusUpdate {
message PendingUpdate {
bytes txid = 1;
uint32 output_index = 2;
// The update carries the fee rate in sat/vbyte and sat/kw
int64 fee_per_vbyte = 3;
Comment thread
MPins marked this conversation as resolved.
uint64 fee_per_kw = 5;
Comment thread
MPins marked this conversation as resolved.
bool local_close_tx = 4;
}

Expand Down
7 changes: 6 additions & 1 deletion lnrpc/lightning.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -7207,7 +7207,12 @@
},
"fee_per_vbyte": {
"type": "string",
"format": "int64"
"format": "int64",
"title": "The update carries the fee rate in sat/vbyte and sat/kw"
},
"fee_per_kw": {
"type": "string",
"format": "uint64"
},
"local_close_tx": {
"type": "boolean"
Expand Down
9 changes: 7 additions & 2 deletions lntest/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -1348,9 +1348,14 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode,
continue
}

notifyRate := pendingClose.ClosePending.FeePerVbyte
// Close pending notification in FeePerKw overwrites
// FeePerVbyte.
notifyRate := uint64(pendingClose.ClosePending.FeePerVbyte)
if pendingClose.ClosePending.FeePerKw != 0 {
notifyRate = pendingClose.ClosePending.FeePerKw / 250
}
if closeOpts.localTxOnly &&
notifyRate != int64(closeReq.SatPerVbyte) {
notifyRate != closeReq.SatPerVbyte {

continue
}
Expand Down
14 changes: 7 additions & 7 deletions lnwallet/chancloser/rbf_coop_states.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ type SendShutdown struct {

// IdealFeeRate is the ideal fee rate we'd like to use for the closing
// attempt.
IdealFeeRate chainfee.SatPerVByte
IdealFeeRate chainfee.SatPerKWeight

// CloseeNonce is the nonce we'll send in the shutdown message. The
// remote party will use this when they create their closing transaction
Expand Down Expand Up @@ -197,7 +197,7 @@ func (c *ChannelFlushed) protocolSealed() {}
// - toState: LocalOfferSent
type SendOfferEvent struct {
// TargetFeeRate is the fee rate we'll use for the closing transaction.
TargetFeeRate chainfee.SatPerVByte
TargetFeeRate chainfee.SatPerKWeight
}

// protocolSealed indicates that this struct is a ProtocolEvent instance.
Expand Down Expand Up @@ -319,7 +319,7 @@ type Environment struct {
// DefaultFeeRate is the fee we'll use for the closing transaction if
// the user didn't specify an ideal fee rate. This may happen if the
// remote party is the one that initiates the co-op close.
DefaultFeeRate chainfee.SatPerVByte
DefaultFeeRate chainfee.SatPerKWeight

// ThawHeight is the height at which the channel will be thawed. If
// this is None, then co-op close can occur at any moment.
Expand Down Expand Up @@ -484,7 +484,7 @@ type ShutdownPending struct {

// IdealFeeRate is the ideal fee rate we'd like to use for the closing
// attempt.
IdealFeeRate fn.Option[chainfee.SatPerVByte]
IdealFeeRate fn.Option[chainfee.SatPerKWeight]

// EarlyRemoteOffer is the offer we received from the remote party
// before we received their shutdown message. We'll stash it to process
Expand Down Expand Up @@ -533,7 +533,7 @@ type ChannelFlushing struct {
// IdealFeeRate is the ideal fee rate we'd like to use for the closing
// transaction. Once the channel has been flushed, we'll use this as
// our target fee rate.
IdealFeeRate fn.Option[chainfee.SatPerVByte]
IdealFeeRate fn.Option[chainfee.SatPerKWeight]

// NonceState tracks the nonces exchanged during shutdown for taproot
// channels.
Expand Down Expand Up @@ -788,7 +788,7 @@ type LocalOfferSent struct {
ProposedFee btcutil.Amount

// ProposedFeeRate is the fee rate we proposed to the remote party.
ProposedFeeRate chainfee.SatPerVByte
ProposedFeeRate chainfee.SatPerKWeight

// LocalSig is the signature we sent to the remote party.
LocalSig lnwire.Sig
Expand Down Expand Up @@ -843,7 +843,7 @@ type ClosePending struct {
*CloseChannelTerms

// FeeRate is the fee rate of the closing transaction.
FeeRate chainfee.SatPerVByte
FeeRate chainfee.SatPerKWeight

// Party indicates which party is at this state. This is used to
// implement the state transition properly, based on ShouldRouteTo.
Expand Down
28 changes: 14 additions & 14 deletions lnwallet/chancloser/rbf_coop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@ func newRbfCloserTestHarness(t *testing.T,
ChanPoint: chanPoint,
ChanID: chanID,
Scid: scid,
DefaultFeeRate: defaultFeeRate.FeePerVByte(),
DefaultFeeRate: defaultFeeRate,
ThawHeight: cfg.thawHeight,
RemoteUpfrontShutdown: cfg.remoteUpfrontAddr,
LocalUpfrontShutdown: cfg.localUpfrontAddr,
Expand Down Expand Up @@ -1069,7 +1069,7 @@ func testInitiatorShutdownRecvOkNonTap(t *testing.T, ctx context.Context,
t.Run("non_taproot", func(t *testing.T) {
firstState := *startingState
firstState.IdealFeeRate = fn.Some(
chainfee.FeePerKwFloor.FeePerVByte(),
chainfee.FeePerKwFloor,
)
firstState.ShutdownScripts = ShutdownScripts{
LocalDeliveryScript: localAddr,
Expand Down Expand Up @@ -1128,7 +1128,7 @@ func testInitiatorShutdownRecvOkTaproot(t *testing.T, ctx context.Context,
t.Run("taproot", func(t *testing.T) {
firstState := *startingState
firstState.IdealFeeRate = fn.Some(
chainfee.FeePerKwFloor.FeePerVByte(),
chainfee.FeePerKwFloor,
)
firstState.ShutdownScripts = ShutdownScripts{
LocalDeliveryScript: localAddr,
Expand Down Expand Up @@ -1377,7 +1377,7 @@ func TestRbfChannelActiveTransitions(t *testing.T) {
localAddr := lnwire.DeliveryAddress(bytes.Repeat([]byte{0x01}, 20))
remoteAddr := lnwire.DeliveryAddress(bytes.Repeat([]byte{0x02}, 20))

feeRate := chainfee.SatPerVByte(1000)
feeRate := chainfee.SatPerKWeight(250_000)

// Test that if a spend event is received, the FSM transitions to the
// CloseFin terminal state.
Expand Down Expand Up @@ -1580,7 +1580,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
t.Run("initiator_shutdown_recv_taproot_no_nonce_fail", func(t *testing.T) { //nolint:ll
firstState := *startingState
firstState.IdealFeeRate = fn.Some(
chainfee.FeePerKwFloor.FeePerVByte(),
chainfee.FeePerKwFloor,
)
firstState.ShutdownScripts = ShutdownScripts{
LocalDeliveryScript: localAddr,
Expand Down Expand Up @@ -1632,7 +1632,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
t.Run("responder_complete", func(t *testing.T) {
firstState := *startingState
firstState.IdealFeeRate = fn.Some(
chainfee.FeePerKwFloor.FeePerVByte(),
chainfee.FeePerKwFloor,
)
firstState.ShutdownScripts = ShutdownScripts{
LocalDeliveryScript: localAddr,
Expand Down Expand Up @@ -1663,7 +1663,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
t.Run("early_remote_offer_shutdown_complete", func(t *testing.T) {
firstState := *startingState
firstState.IdealFeeRate = fn.Some(
chainfee.FeePerKwFloor.FeePerVByte(),
chainfee.FeePerKwFloor,
)
firstState.ShutdownScripts = ShutdownScripts{
LocalDeliveryScript: localAddr,
Expand Down Expand Up @@ -1710,7 +1710,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
t.Run("early_remote_offer_shutdown_received", func(t *testing.T) {
firstState := *startingState
firstState.IdealFeeRate = fn.Some(
chainfee.FeePerKwFloor.FeePerVByte(),
chainfee.FeePerKwFloor,
)
firstState.ShutdownScripts = ShutdownScripts{
LocalDeliveryScript: localAddr,
Expand Down Expand Up @@ -1970,7 +1970,7 @@ func testSendOfferRbfIterationLoopNonTap(t *testing.T,
noDustExpect, false,
)

rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10
rbfFeeBump := chainfee.FeePerKwFloor * 10
localOffer := &SendOfferEvent{
TargetFeeRate: rbfFeeBump,
}
Expand Down Expand Up @@ -2033,7 +2033,7 @@ func testSendOfferRbfIterationLoopTaproot(t *testing.T,
lnwire.Musig2Nonce{7, 8, 9},
)

rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10
rbfFeeBump := chainfee.FeePerKwFloor * 10
localOffer := &SendOfferEvent{
TargetFeeRate: rbfFeeBump,
}
Expand Down Expand Up @@ -2331,7 +2331,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) {
}

sendOfferEvent := &SendOfferEvent{
TargetFeeRate: chainfee.FeePerKwFloor.FeePerVByte(),
TargetFeeRate: chainfee.FeePerKwFloor,
}

balanceAfterClose := localBalance.ToSatoshis() - absoluteFee
Expand Down Expand Up @@ -2519,7 +2519,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) {
// the amount we have in the channel.
closeHarness.expectFeeEstimate(btcutil.SatoshiPerBitcoin, 1)

rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte()
rbfFeeBump := chainfee.FeePerKwFloor
localOffer := &SendOfferEvent{
TargetFeeRate: rbfFeeBump,
}
Expand Down Expand Up @@ -2960,7 +2960,7 @@ func TestRbfCloseErr(t *testing.T) {
})
defer closeHarness.stopAndAssert()

rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte()
rbfFeeBump := chainfee.FeePerKwFloor
localOffer := &SendOfferEvent{
TargetFeeRate: rbfFeeBump,
}
Expand Down Expand Up @@ -3293,7 +3293,7 @@ func TestLocalOfferSentUsesStoredSig(t *testing.T) {
localOfferSent := &LocalOfferSent{
CloseChannelTerms: closeTerms,
ProposedFee: btcutil.Amount(1000),
ProposedFeeRate: chainfee.FeePerKwFloor.FeePerVByte(),
ProposedFeeRate: chainfee.FeePerKwFloor,
LocalSig: localSchnorrSig,
LocalMusigSig: fn.Some(lnwallet.MusigPartialSig{}),
}
Expand Down
16 changes: 11 additions & 5 deletions lnwallet/chancloser/rbf_coop_transitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
Expand Down Expand Up @@ -607,7 +608,7 @@ func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, env *Environment,
localTxOut, remoteTxOut := closeTerms.DeriveCloseTxOuts()
absoluteFee := env.FeeEstimator.EstimateFee(
env.ChanType, localTxOut, remoteTxOut,
idealFeeRate.FeePerKWeight(),
idealFeeRate,
)

chancloserLog.Infof("ChannelPoint(%v): using ideal_fee=%v, "+
Expand Down Expand Up @@ -1119,7 +1120,7 @@ func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment,
localTxOut, remoteTxOut := l.DeriveCloseTxOuts()
absoluteFee := env.FeeEstimator.EstimateFee(
env.ChanType, localTxOut, remoteTxOut,
msg.TargetFeeRate.FeePerKWeight(),
msg.TargetFeeRate,
)

// If we can't actually pay for fees here, then we'll just do a
Expand Down Expand Up @@ -2115,11 +2116,16 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment,
// We'll also compute the final fee rate that the remote party
// paid based off the absolute fee and the size of the closing
// transaction.
vSize := mempool.GetTxVirtualSize(btcutil.NewTx(closeTx))
feeRate := chainfee.SatPerVByte(
int64(msg.SigMsg.FeeSatoshis) / vSize,
weight := blockchain.GetTransactionWeight(
btcutil.NewTx(closeTx),
)

// Convert absolute fee to a fee rate in sat/kw, rounding up.
fee := int64(msg.SigMsg.FeeSatoshis)
rate := ((fee * 1000) + weight - 1) / weight
Comment thread
MPins marked this conversation as resolved.

feeRate := chainfee.SatPerKWeight(rate)

// Now that we've extracted the signature, we'll transition to
// the next state where we'll sign+broadcast the sig.
return &CloseStateTransition{
Expand Down
Loading
Loading