Skip to content

Commit 5840cd7

Browse files
committed
multi: change rbf coop close from sat/vb to sat/kw
1 parent b8ee2dc commit 5840cd7

File tree

10 files changed

+2436
-2402
lines changed

10 files changed

+2436
-2402
lines changed

itest/lnd_coop_close_rbf_test.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,20 @@ func testCoopCloseRbf(ht *lntest.HarnessTest) {
3030
// off the test.
3131
//
3232
// To start, we'll have Alice try to close the channel, with a fee rate
33-
// of 5 sat/byte.
34-
aliceFeeRate := chainfee.SatPerVByte(5)
33+
// of 1250 sat/kw .
34+
aliceFeeRate := chainfee.SatPerKWeight(1250)
3535
aliceCloseStream, aliceCloseUpdate := ht.CloseChannelAssertPending(
3636
alice, chanPoint, false,
3737
lntest.WithCoopCloseFeeRate(aliceFeeRate),
3838
lntest.WithLocalTxNotify(),
3939
)
4040

41-
// Confirm that this new update was at 5 sat/vb.
41+
// Confirm that this new update was at 1250 sat/kw.
4242
alicePendingUpdate := aliceCloseUpdate.GetClosePending()
4343
require.NotNil(ht, aliceCloseUpdate)
4444
require.Equal(
45-
ht, int64(aliceFeeRate), alicePendingUpdate.FeePerVbyte,
45+
ht, int64(aliceFeeRate),
46+
int64(alicePendingUpdate.FeePerKw),
4647
)
4748
require.True(ht, alicePendingUpdate.LocalCloseTx)
4849

@@ -54,28 +55,38 @@ func testCoopCloseRbf(ht *lntest.HarnessTest) {
5455
lntest.WithLocalTxNotify(),
5556
)
5657

57-
// Confirm that this new update was at 10 sat/vb.
58+
// Confirm that this new update was at 2500 sat/kw.
5859
bobPendingUpdate := bobCloseUpdate.GetClosePending()
5960
require.NotNil(ht, bobCloseUpdate)
60-
require.Equal(ht, bobPendingUpdate.FeePerVbyte, int64(bobFeeRate))
61+
require.Equal(
62+
ht, int64(bobPendingUpdate.FeePerKw),
63+
int64(bobFeeRate),
64+
)
6165
require.True(ht, bobPendingUpdate.LocalCloseTx)
6266

6367
var err error
6468

6569
// Alice should've also received a similar update that Bob has
66-
// increased the closing fee rate to 10 sat/vb with his settled funds.
70+
// increased the closing fee rate to 2500 sat/kw with his settled
71+
// funds.
6772
aliceCloseUpdate, err = ht.ReceiveCloseChannelUpdate(aliceCloseStream)
6873
require.NoError(ht, err)
6974
alicePendingUpdate = aliceCloseUpdate.GetClosePending()
7075
require.NotNil(ht, aliceCloseUpdate)
71-
require.Equal(ht, alicePendingUpdate.FeePerVbyte, int64(bobFeeRate))
76+
// Using InDelta as fee rate calc might differ due to weight estimation
77+
// versuns actual tx weight.
78+
require.InDelta(
79+
ht, int64(alicePendingUpdate.FeePerKw),
80+
int64(bobFeeRate),
81+
15,
82+
)
7283
require.False(ht, alicePendingUpdate.LocalCloseTx)
7384

7485
// We'll now attempt to make a fee update that increases Alice's fee
75-
// rate by 6 sat/vb, which should be rejected as it is too small of an
76-
// increase for the RBF rules. The RPC API however will return the new
77-
// fee. We'll skip the mempool check here as it won't make it in.
78-
aliceRejectedFeeRate := aliceFeeRate + 1
86+
// rate by 1500 sat/kw, which should be rejected as it is too small of
87+
// an increase for the RBF rules. The RPC API however will return the
88+
// new fee. We'll skip the mempool check here as it won't make it in.
89+
aliceRejectedFeeRate := aliceFeeRate + 250
7990
_, aliceCloseUpdate = ht.CloseChannelAssertPending(
8091
alice, chanPoint, false,
8192
lntest.WithCoopCloseFeeRate(aliceRejectedFeeRate),
@@ -84,7 +95,7 @@ func testCoopCloseRbf(ht *lntest.HarnessTest) {
8495
alicePendingUpdate = aliceCloseUpdate.GetClosePending()
8596
require.NotNil(ht, aliceCloseUpdate)
8697
require.Equal(
87-
ht, alicePendingUpdate.FeePerVbyte,
98+
ht, int64(alicePendingUpdate.FeePerKw),
8899
int64(aliceRejectedFeeRate),
89100
)
90101
require.True(ht, alicePendingUpdate.LocalCloseTx)
@@ -108,7 +119,7 @@ func testCoopCloseRbf(ht *lntest.HarnessTest) {
108119
ht.DisconnectNodes(alice, bob)
109120
ht.ConnectNodes(alice, bob)
110121

111-
// Next, we'll have Alice double that fee rate again to 20 sat/vb.
122+
// Next, we'll have Alice double that fee rate again to 5000 sat/kw.
112123
aliceFeeRate = bobFeeRate * 2
113124
aliceCloseStream, aliceCloseUpdate = ht.CloseChannelAssertPending(
114125
alice, chanPoint, false,
@@ -119,7 +130,8 @@ func testCoopCloseRbf(ht *lntest.HarnessTest) {
119130
alicePendingUpdate = aliceCloseUpdate.GetClosePending()
120131
require.NotNil(ht, aliceCloseUpdate)
121132
require.Equal(
122-
ht, alicePendingUpdate.FeePerVbyte, int64(aliceFeeRate),
133+
ht, int64(alicePendingUpdate.FeePerKw),
134+
int64(aliceFeeRate),
123135
)
124136
require.True(ht, alicePendingUpdate.LocalCloseTx)
125137

lnrpc/lightning.pb.go

Lines changed: 2359 additions & 2349 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lnrpc/lightning.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,7 @@ message PendingUpdate {
22092209
uint32 output_index = 2;
22102210
int64 fee_per_vbyte = 3;
22112211
bool local_close_tx = 4;
2212+
uint64 fee_per_kw = 5;
22122213
}
22132214

22142215
message InstantUpdate {

lnrpc/lightning.swagger.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7075,6 +7075,10 @@
70757075
},
70767076
"local_close_tx": {
70777077
"type": "boolean"
7078+
},
7079+
"fee_per_kw": {
7080+
"type": "string",
7081+
"format": "uint64"
70787082
}
70797083
}
70807084
},

lntest/harness.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,7 @@ func (h *HarnessTest) OpenChannelAssertErr(srcNode, destNode *node.HarnessNode,
12281228

12291229
// closeChannelOpts holds the options for closing a channel.
12301230
type closeChannelOpts struct {
1231-
feeRate fn.Option[chainfee.SatPerVByte]
1231+
feeRate fn.Option[chainfee.SatPerKWeight]
12321232

12331233
// localTxOnly is a boolean indicating if we should only attempt to
12341234
// consume close pending notifications for the local transaction.
@@ -1249,7 +1249,7 @@ type CloseChanOpt func(*closeChannelOpts)
12491249

12501250
// WithCoopCloseFeeRate is a functional option to set the fee rate for a coop
12511251
// close attempt.
1252-
func WithCoopCloseFeeRate(rate chainfee.SatPerVByte) CloseChanOpt {
1252+
func WithCoopCloseFeeRate(rate chainfee.SatPerKWeight) CloseChanOpt {
12531253
return func(o *closeChannelOpts) {
12541254
o.feeRate = fn.Some(rate)
12551255
}
@@ -1307,8 +1307,8 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode,
13071307
NoWait: true,
13081308
}
13091309

1310-
closeOpts.feeRate.WhenSome(func(feeRate chainfee.SatPerVByte) {
1311-
closeReq.SatPerVbyte = uint64(feeRate)
1310+
closeOpts.feeRate.WhenSome(func(feeRate chainfee.SatPerKWeight) {
1311+
closeReq.SatPerKw = uint64(feeRate)
13121312
})
13131313

13141314
var (
@@ -1348,9 +1348,9 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode,
13481348
continue
13491349
}
13501350

1351-
notifyRate := pendingClose.ClosePending.FeePerVbyte
1351+
notifyRate := pendingClose.ClosePending.FeePerKw
13521352
if closeOpts.localTxOnly &&
1353-
notifyRate != int64(closeReq.SatPerVbyte) {
1353+
notifyRate != closeReq.SatPerKw {
13541354

13551355
continue
13561356
}

lnwallet/chancloser/rbf_coop_states.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ type SendShutdown struct {
100100

101101
// IdealFeeRate is the ideal fee rate we'd like to use for the closing
102102
// attempt.
103-
IdealFeeRate chainfee.SatPerVByte
103+
IdealFeeRate chainfee.SatPerKWeight
104104
}
105105

106106
// protocolSealed indicates that this struct is a ProtocolEvent instance.
@@ -182,7 +182,7 @@ func (c *ChannelFlushed) protocolSealed() {}
182182
// - toState: LocalOfferSent
183183
type SendOfferEvent struct {
184184
// TargetFeeRate is the fee rate we'll use for the closing transaction.
185-
TargetFeeRate chainfee.SatPerVByte
185+
TargetFeeRate chainfee.SatPerKWeight
186186
}
187187

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

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

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

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

504504
// String returns the name of the state for ChannelFlushing.
@@ -732,7 +732,7 @@ type LocalOfferSent struct {
732732
ProposedFee btcutil.Amount
733733

734734
// ProposedFeeRate is the fee rate we proposed to the remote party.
735-
ProposedFeeRate chainfee.SatPerVByte
735+
ProposedFeeRate chainfee.SatPerKWeight
736736

737737
// LocalSig is the signature we sent to the remote party.
738738
LocalSig lnwire.Sig
@@ -781,7 +781,7 @@ type ClosePending struct {
781781
*CloseChannelTerms
782782

783783
// FeeRate is the fee rate of the closing transaction.
784-
FeeRate chainfee.SatPerVByte
784+
FeeRate chainfee.SatPerKWeight
785785

786786
// Party indicates which party is at this state. This is used to
787787
// implement the state transition properly, based on ShouldRouteTo.

lnwallet/chancloser/rbf_coop_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ func newRbfCloserTestHarness(t *testing.T,
783783
ChanPoint: chanPoint,
784784
ChanID: chanID,
785785
Scid: scid,
786-
DefaultFeeRate: defaultFeeRate.FeePerVByte(),
786+
DefaultFeeRate: defaultFeeRate,
787787
ThawHeight: cfg.thawHeight,
788788
RemoteUpfrontShutdown: cfg.remoteUpfrontAddr,
789789
LocalUpfrontShutdown: cfg.localUpfrontAddr,
@@ -854,7 +854,7 @@ func TestRbfChannelActiveTransitions(t *testing.T) {
854854
localAddr := lnwire.DeliveryAddress(bytes.Repeat([]byte{0x01}, 20))
855855
remoteAddr := lnwire.DeliveryAddress(bytes.Repeat([]byte{0x02}, 20))
856856

857-
feeRate := chainfee.SatPerVByte(1000)
857+
feeRate := chainfee.SatPerKWeight(250000)
858858

859859
// Test that if a spend event is received, the FSM transitions to the
860860
// CloseFin terminal state.
@@ -1050,7 +1050,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
10501050
t.Run("initiator_shutdown_recv_ok", func(t *testing.T) {
10511051
firstState := *startingState
10521052
firstState.IdealFeeRate = fn.Some(
1053-
chainfee.FeePerKwFloor.FeePerVByte(),
1053+
chainfee.FeePerKwFloor,
10541054
)
10551055
firstState.ShutdownScripts = ShutdownScripts{
10561056
LocalDeliveryScript: localAddr,
@@ -1097,7 +1097,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
10971097
t.Run("responder_complete", func(t *testing.T) {
10981098
firstState := *startingState
10991099
firstState.IdealFeeRate = fn.Some(
1100-
chainfee.FeePerKwFloor.FeePerVByte(),
1100+
chainfee.FeePerKwFloor,
11011101
)
11021102
firstState.ShutdownScripts = ShutdownScripts{
11031103
LocalDeliveryScript: localAddr,
@@ -1128,7 +1128,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
11281128
t.Run("early_remote_offer_shutdown_complete", func(t *testing.T) {
11291129
firstState := *startingState
11301130
firstState.IdealFeeRate = fn.Some(
1131-
chainfee.FeePerKwFloor.FeePerVByte(),
1131+
chainfee.FeePerKwFloor,
11321132
)
11331133
firstState.ShutdownScripts = ShutdownScripts{
11341134
LocalDeliveryScript: localAddr,
@@ -1175,7 +1175,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) {
11751175
t.Run("early_remote_offer_shutdown_received", func(t *testing.T) {
11761176
firstState := *startingState
11771177
firstState.IdealFeeRate = fn.Some(
1178-
chainfee.FeePerKwFloor.FeePerVByte(),
1178+
chainfee.FeePerKwFloor,
11791179
)
11801180
firstState.ShutdownScripts = ShutdownScripts{
11811181
LocalDeliveryScript: localAddr,
@@ -1433,7 +1433,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) {
14331433
}
14341434

14351435
sendOfferEvent := &SendOfferEvent{
1436-
TargetFeeRate: chainfee.FeePerKwFloor.FeePerVByte(),
1436+
TargetFeeRate: chainfee.FeePerKwFloor,
14371437
}
14381438

14391439
balanceAfterClose := localBalance.ToSatoshis() - absoluteFee
@@ -1614,7 +1614,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) {
16141614
// Next, we'll send in a new SendOfferEvent event which
16151615
// simulates the user requesting a RBF fee bump. We'll use 10x
16161616
// the fee we used in the last iteration.
1617-
rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10
1617+
rbfFeeBump := chainfee.FeePerKwFloor * 10
16181618
localOffer := &SendOfferEvent{
16191619
TargetFeeRate: rbfFeeBump,
16201620
}
@@ -1649,7 +1649,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) {
16491649
// the amount we have in the channel.
16501650
closeHarness.expectFeeEstimate(btcutil.SatoshiPerBitcoin, 1)
16511651

1652-
rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte()
1652+
rbfFeeBump := chainfee.FeePerKwFloor
16531653
localOffer := &SendOfferEvent{
16541654
TargetFeeRate: rbfFeeBump,
16551655
}
@@ -1997,7 +1997,7 @@ func TestRbfCloseErr(t *testing.T) {
19971997
})
19981998
defer closeHarness.stopAndAssert()
19991999

2000-
rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte()
2000+
rbfFeeBump := chainfee.FeePerKwFloor
20012001
localOffer := &SendOfferEvent{
20022002
TargetFeeRate: rbfFeeBump,
20032003
}

lnwallet/chancloser/rbf_coop_transitions.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"fmt"
66

7+
"github.com/btcsuite/btcd/blockchain"
78
"github.com/btcsuite/btcd/btcec/v2"
89
"github.com/btcsuite/btcd/btcutil"
910
"github.com/btcsuite/btcd/chaincfg"
@@ -509,7 +510,7 @@ func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent,
509510
localTxOut, remoteTxOut := closeTerms.DeriveCloseTxOuts()
510511
absoluteFee := env.FeeEstimator.EstimateFee(
511512
env.ChanType, localTxOut, remoteTxOut,
512-
idealFeeRate.FeePerKWeight(),
513+
idealFeeRate,
513514
)
514515

515516
chancloserLog.Infof("ChannelPoint(%v): using ideal_fee=%v, "+
@@ -752,7 +753,7 @@ func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent,
752753
localTxOut, remoteTxOut := l.DeriveCloseTxOuts()
753754
absoluteFee := env.FeeEstimator.EstimateFee(
754755
env.ChanType, localTxOut, remoteTxOut,
755-
msg.TargetFeeRate.FeePerKWeight(),
756+
msg.TargetFeeRate,
756757
)
757758

758759
// If we can't actually pay for fees here, then we'll just do a
@@ -1128,10 +1129,16 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent,
11281129
// We'll also compute the final fee rate that the remote party
11291130
// paid based off the absolute fee and the size of the closing
11301131
// transaction.
1131-
vSize := mempool.GetTxVirtualSize(btcutil.NewTx(closeTx))
1132-
feeRate := chainfee.SatPerVByte(
1133-
int64(msg.SigMsg.FeeSatoshis) / vSize,
1134-
)
1132+
tx := btcutil.NewTx(closeTx)
1133+
total := int64(tx.MsgTx().SerializeSize())
1134+
base := int64(tx.MsgTx().SerializeSizeStripped())
1135+
den := base*(blockchain.WitnessScaleFactor) + (total - base)
1136+
1137+
// Round to the next integer.
1138+
num := int64(msg.SigMsg.FeeSatoshis) * 1000
1139+
rate := (num + den - 1) / den
1140+
1141+
feeRate := chainfee.SatPerKWeight(rate)
11351142

11361143
// Now that we've extracted the signature, we'll transition to
11371144
// the next state where we'll sign+broadcast the sig.

0 commit comments

Comments
 (0)