-
Notifications
You must be signed in to change notification settings - Fork 2.3k
multi: integrate new RBF co-op flow into the server+peer #9575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b20e9ad
f6a6c65
bcfcaae
92b876d
b0cca60
e6c71b5
daa437f
6bace55
40969aa
7e2a7a6
8144ff2
db4fe04
bc0ab9d
53b6b60
ee18438
678f243
a3fe66d
1381d57
0b530fb
7cc9704
8093cfe
67f9f38
3801f3a
7060765
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| package itest | ||
|
|
||
| import ( | ||
| "github.com/btcsuite/btcd/btcutil" | ||
| "github.com/lightningnetwork/lnd/lntest" | ||
| "github.com/lightningnetwork/lnd/lnwallet/chainfee" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func testCoopCloseRbf(ht *lntest.HarnessTest) { | ||
| rbfCoopFlags := []string{"--protocol.rbf-coop-close"} | ||
|
|
||
| // Set the fee estimate to 1sat/vbyte. This ensures that our manually | ||
| // initiated RBF attempts will always be successful. | ||
| ht.SetFeeEstimate(250) | ||
| ht.SetFeeEstimateWithConf(250, 6) | ||
|
|
||
| // To kick things off, we'll create two new nodes, then fund them with | ||
| // enough coins to make a 50/50 channel. | ||
| cfgs := [][]string{rbfCoopFlags, rbfCoopFlags} | ||
| params := lntest.OpenChannelParams{ | ||
| Amt: btcutil.Amount(1000000), | ||
| PushAmt: btcutil.Amount(1000000 / 2), | ||
| } | ||
| chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params) | ||
| alice, bob := nodes[0], nodes[1] | ||
| chanPoint := chanPoints[0] | ||
|
|
||
| // Now that both sides are active with a funded channel, we can kick | ||
| // off the test. | ||
| // | ||
| // To start, we'll have Alice try to close the channel, with a fee rate | ||
| // of 5 sat/byte. | ||
| aliceFeeRate := chainfee.SatPerVByte(5) | ||
| aliceCloseStream, aliceCloseUpdate := ht.CloseChannelAssertPending( | ||
| alice, chanPoint, false, | ||
| lntest.WithCoopCloseFeeRate(aliceFeeRate), | ||
| lntest.WithLocalTxNotify(), | ||
| ) | ||
|
|
||
| // Confirm that this new update was at 5 sat/vb. | ||
| alicePendingUpdate := aliceCloseUpdate.GetClosePending() | ||
| require.NotNil(ht, aliceCloseUpdate) | ||
| require.Equal( | ||
| ht, int64(aliceFeeRate), alicePendingUpdate.FeePerVbyte, | ||
| ) | ||
| require.True(ht, alicePendingUpdate.LocalCloseTx) | ||
|
|
||
| // Now, we'll have Bob attempt to RBF the close transaction with a | ||
| // higher fee rate, double that of Alice's. | ||
| bobFeeRate := aliceFeeRate * 2 | ||
| bobCloseStream, bobCloseUpdate := ht.CloseChannelAssertPending( | ||
| bob, chanPoint, false, lntest.WithCoopCloseFeeRate(bobFeeRate), | ||
| lntest.WithLocalTxNotify(), | ||
| ) | ||
|
|
||
| // Confirm that this new update was at 10 sat/vb. | ||
| bobPendingUpdate := bobCloseUpdate.GetClosePending() | ||
| require.NotNil(ht, bobCloseUpdate) | ||
| require.Equal(ht, bobPendingUpdate.FeePerVbyte, int64(bobFeeRate)) | ||
| require.True(ht, bobPendingUpdate.LocalCloseTx) | ||
|
|
||
| var err error | ||
|
|
||
| // Alice should've also received a similar update that Bob has | ||
| // increased the closing fee rate to 10 sat/vb with his settled funds. | ||
| aliceCloseUpdate, err = ht.ReceiveCloseChannelUpdate(aliceCloseStream) | ||
| require.NoError(ht, err) | ||
| alicePendingUpdate = aliceCloseUpdate.GetClosePending() | ||
| require.NotNil(ht, aliceCloseUpdate) | ||
| require.Equal(ht, alicePendingUpdate.FeePerVbyte, int64(bobFeeRate)) | ||
| require.False(ht, alicePendingUpdate.LocalCloseTx) | ||
|
|
||
| // We'll now attempt to make a fee update that increases Alice's fee | ||
| // rate by 6 sat/vb, which should be rejected as it is too small of an | ||
| // increase for the RBF rules. The RPC API however will return the new | ||
| // fee. We'll skip the mempool check here as it won't make it in. | ||
| aliceRejectedFeeRate := aliceFeeRate + 1 | ||
| _, aliceCloseUpdate = ht.CloseChannelAssertPending( | ||
| alice, chanPoint, false, | ||
| lntest.WithCoopCloseFeeRate(aliceRejectedFeeRate), | ||
| lntest.WithLocalTxNotify(), lntest.WithSkipMempoolCheck(), | ||
| ) | ||
| alicePendingUpdate = aliceCloseUpdate.GetClosePending() | ||
| require.NotNil(ht, aliceCloseUpdate) | ||
| require.Equal( | ||
| ht, alicePendingUpdate.FeePerVbyte, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This got me to think maybe there should be a field to tell the user the RBF is failed? sth like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue with that is that we can't always detect if it failed or not. For Neutrino, we have no idea. One option we have is to always try to do a fee bump if the user wants one. The side effect of that is that we may end up paying more than the user expect, eg: they try to do 5 sat/vb, but 8 sat/vb is actually what'll trigger a replacement. I'm open to modifying the broadcast flow in another PR. It'll need some changes in the |
||
| int64(aliceRejectedFeeRate), | ||
| ) | ||
| require.True(ht, alicePendingUpdate.LocalCloseTx) | ||
|
|
||
| _, err = ht.ReceiveCloseChannelUpdate(bobCloseStream) | ||
| require.NoError(ht, err) | ||
|
|
||
| // We'll now attempt a fee update that we can't actually pay for. This | ||
| // will actually show up as an error to the remote party. | ||
| aliceRejectedFeeRate = 100_000 | ||
Crypt-iQ marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _, _ = ht.CloseChannelAssertPending( | ||
| alice, chanPoint, false, | ||
| lntest.WithCoopCloseFeeRate(aliceRejectedFeeRate), | ||
| lntest.WithLocalTxNotify(), | ||
| lntest.WithExpectedErrString("cannot pay for fee"), | ||
| ) | ||
|
|
||
| // At this point, we'll have Alice+Bob reconnect so we can ensure that | ||
| // we can continue to do RBF bumps even after a reconnection. | ||
| ht.DisconnectNodes(alice, bob) | ||
| ht.ConnectNodes(alice, bob) | ||
|
|
||
| // Next, we'll have Alice double that fee rate again to 20 sat/vb. | ||
| aliceFeeRate = bobFeeRate * 2 | ||
| aliceCloseStream, aliceCloseUpdate = ht.CloseChannelAssertPending( | ||
| alice, chanPoint, false, | ||
| lntest.WithCoopCloseFeeRate(aliceFeeRate), | ||
| lntest.WithLocalTxNotify(), | ||
| ) | ||
|
|
||
| alicePendingUpdate = aliceCloseUpdate.GetClosePending() | ||
| require.NotNil(ht, aliceCloseUpdate) | ||
| require.Equal( | ||
| ht, alicePendingUpdate.FeePerVbyte, int64(aliceFeeRate), | ||
| ) | ||
| require.True(ht, alicePendingUpdate.LocalCloseTx) | ||
|
|
||
| // To conclude, we'll mine a block which should now confirm Alice's | ||
| // version of the coop close transaction. | ||
| block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] | ||
|
|
||
| // Both Alice and Bob should trigger a final close update to signal the | ||
| // closing transaction has confirmed. | ||
| aliceClosingTxid := ht.WaitForChannelCloseEvent(aliceCloseStream) | ||
| ht.AssertTxInBlock(block, aliceClosingTxid) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the coop close would fail, there's no need to skip the mempool check because the old txid is still in the mempool?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need this here, just to skip the final assertion in
CloseChannelAssertPending. With the API, close only fails (gRPC error) when we can't pay for the specified fee. Spurious RBF updates are surpressed in the form of a response that returns the target fee rate.