multi: add itest to check race between channel_ready and update_add_htlc

This commit adds a new itest case to check the race condition found in
issue #7401. In order to control the funding manager's state, a new dev
config for the funding manager is introduced to specify a duration we
should hold before processing remote node's channel_ready message.

A new development config, `DevConfig` is introduced in `lncfg` and will
only have effect if built with flag `integration`. This can also be
extended for future integration tests if more dev-only flags are needed.
This commit is contained in:
yyforyongyu committed 2023-08-09 01:29:19 +08:00
1 parent 6b41289538
commit 0dd2aa0aef
7 files changed
+286

No files matched your search

+4
View File
@@ -546,4 +546,8 @@ var allTestCases = []*lntest.TestCase{
Name: "utxo selection funding",
TestFunc: testChannelUtxoSelection,
},
{
Name: "update pending open channels",
TestFunc: testUpdateOnPendingOpenChannels,
},
}
+189
View File
@@ -10,6 +10,7 @@ import (
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/rpc"
@@ -488,6 +489,194 @@ func runBasicChannelCreationAndUpdates(ht *lntest.HarnessTest,
)
}
// testUpdateOnPendingOpenChannels checks that `update_add_htlc` followed by
// `channel_ready` is properly handled. In specific, when a node is in a state
// that it's still processing a remote `channel_ready` message, meanwhile an
// `update_add_htlc` is received, this HTLC message is cached and settled once
// processing `channel_ready` is complete.
func testUpdateOnPendingOpenChannels(ht *lntest.HarnessTest) {
// Test funder's behavior. Funder sees the channel pending, but fundee
// sees it active and sends an HTLC.
ht.Run("pending on funder side", func(t *testing.T) {
st := ht.Subtest(t)
testUpdateOnFunderPendingOpenChannels(st)
})
// Test fundee's behavior. Fundee sees the channel pending, but funder
// sees it active and sends an HTLC.
ht.Run("pending on fundee side", func(t *testing.T) {
st := ht.Subtest(t)
testUpdateOnFundeePendingOpenChannels(st)
})
}
// testUpdateOnFunderPendingOpenChannels checks that when the fundee sends an
// `update_add_htlc` followed by `channel_ready` while the funder is still
// processing the fundee's `channel_ready`, the HTLC will be cached and
// eventually settled.
func testUpdateOnFunderPendingOpenChannels(ht *lntest.HarnessTest) {
// Grab the channel participants.
alice, bob := ht.Alice, ht.Bob
// Restart Alice with the config so she won't process Bob's
// channel_ready msg immediately.
ht.RestartNodeWithExtraArgs(alice, []string{
"--dev.processchannelreadywait=10s",
})
// Make sure Alice and Bob are connected.
ht.EnsureConnected(alice, bob)
// Create a new channel that requires 1 confs before it's considered
// open.
params := lntest.OpenChannelParams{
Amt: funding.MaxBtcFundingAmount,
PushAmt: funding.MaxBtcFundingAmount / 2,
}
pendingChan := ht.OpenChannelAssertPending(alice, bob, params)
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: pendingChan.Txid,
},
OutputIndex: pendingChan.OutputIndex,
}
// Alice and Bob should both consider the channel pending open.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(bob, 1)
// Mine one block to confirm the funding transaction.
ht.MineBlocksAndAssertNumTxes(1, 1)
// TODO(yy): we've prematurely marked the channel as open before
// processing channel ready messages. We need to mark it as open after
// we've processed channel ready messages and change the check to,
// ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(alice, 0)
// Bob will consider the channel open as there's no wait time to send
// and receive Alice's channel_ready message.
ht.AssertNumPendingOpenChannels(bob, 0)
// Alice and Bob now have different view of the channel. For Bob,
// since the channel_ready messages are processed, he will have a
// working link to route HTLCs. For Alice, because she hasn't handled
// Bob's channel_ready, there's no active link yet.
//
// Alice now adds an invoice.
req := &lnrpc.Invoice{
RPreimage: ht.Random32Bytes(),
Value: 10_000,
}
invoice := alice.RPC.AddInvoice(req)
// Bob sends an `update_add_htlc`, which would result in this message
// being cached in Alice's `peer.Brontide` and the payment will stay
// in-flight instead of being failed by Alice.
bobReq := &routerrpc.SendPaymentRequest{
PaymentRequest: invoice.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
bobStream := bob.RPC.SendPayment(bobReq)
ht.AssertPaymentStatusFromStream(bobStream, lnrpc.Payment_IN_FLIGHT)
// Wait until Alice finishes processing Bob's channel_ready.
//
// NOTE: no effect before fixing the above TODO.
ht.AssertNumPendingOpenChannels(alice, 0)
// Once Alice sees the channel as active, she will process the cached
// premature `update_add_htlc` and settles the payment.
ht.AssertPaymentStatusFromStream(bobStream, lnrpc.Payment_SUCCEEDED)
// Close the channel.
ht.CloseChannel(alice, chanPoint)
}
// testUpdateOnFundeePendingOpenChannels checks that when the funder sends an
// `update_add_htlc` followed by `channel_ready` while the fundee is still
// processing the funder's `channel_ready`, the HTLC will be cached and
// eventually settled.
func testUpdateOnFundeePendingOpenChannels(ht *lntest.HarnessTest) {
// Grab the channel participants.
alice, bob := ht.Alice, ht.Bob
// Restart Bob with the config so he won't process Alice's
// channel_ready msg immediately.
ht.RestartNodeWithExtraArgs(bob, []string{
"--dev.processchannelreadywait=10s",
})
// Make sure Alice and Bob are connected.
ht.EnsureConnected(alice, bob)
// Create a new channel that requires 1 confs before it's considered
// open.
params := lntest.OpenChannelParams{
Amt: funding.MaxBtcFundingAmount,
}
pendingChan := ht.OpenChannelAssertPending(alice, bob, params)
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: pendingChan.Txid,
},
OutputIndex: pendingChan.OutputIndex,
}
// Alice and Bob should both consider the channel pending open.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(bob, 1)
// Mine one block to confirm the funding transaction.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Alice will consider the channel open as there's no wait time to send
// and receive Bob's channel_ready message.
ht.AssertNumPendingOpenChannels(alice, 0)
// TODO(yy): we've prematurely marked the channel as open before
// processing channel ready messages. We need to mark it as open after
// we've processed channel ready messages and change the check to,
// ht.AssertNumPendingOpenChannels(bob, 1)
ht.AssertNumPendingOpenChannels(bob, 0)
// Alice and Bob now have different view of the channel. For Alice,
// since the channel_ready messages are processed, she will have a
// working link to route HTLCs. For Bob, because he hasn't handled
// Alice's channel_ready, there's no active link yet.
//
// Bob now adds an invoice.
req := &lnrpc.Invoice{
RPreimage: ht.Random32Bytes(),
Value: 10_000,
}
bobInvoice := bob.RPC.AddInvoice(req)
// Alice sends an `update_add_htlc`, which would result in this message
// being cached in Bob's `peer.Brontide` and the payment will stay
// in-flight instead of being failed by Bob.
aliceReq := &routerrpc.SendPaymentRequest{
PaymentRequest: bobInvoice.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
aliceStream := alice.RPC.SendPayment(aliceReq)
ht.AssertPaymentStatusFromStream(aliceStream, lnrpc.Payment_IN_FLIGHT)
// Wait until Bob finishes processing Alice's channel_ready.
//
// NOTE: no effect before fixing the above TODO.
ht.AssertNumPendingOpenChannels(bob, 0)
// Once Bob sees the channel as active, he will process the cached
// premature `update_add_htlc` and settles the payment.
ht.AssertPaymentStatusFromStream(aliceStream, lnrpc.Payment_SUCCEEDED)
// Close the channel.
ht.CloseChannel(alice, chanPoint)
}
// verifyCloseUpdate is used to verify that a closed channel update is of the
// expected type.
func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate,