Merge pull request #6246 from Crypt-iQ/settle_pipeline_check

htlcswitch: only pipeline settle if add is locked-in
This commit is contained in:
Olaoluwa Osuntokun
2022-03-02 17:07:14 -08:00
committed by GitHub
3 changed files with 200 additions and 3 deletions

View File

@@ -5500,8 +5500,8 @@ func TestChannelLinkFail(t *testing.T) {
false,
},
{
// Test that we force close the channel if we receive
// an invalid Settle message.
// Test that we don't force close the channel if we
// receive an invalid Settle message.
func(c *channelLink) {
},
func(t *testing.T, c *channelLink, _ *lnwallet.LightningChannel) {
@@ -5513,7 +5513,7 @@ func TestChannelLinkFail(t *testing.T) {
}
c.HandleChannelUpdate(htlcSettle)
},
true,
false,
false,
},
{
@@ -6684,6 +6684,179 @@ func TestShutdownIfChannelClean(t *testing.T) {
}
}
// TestPipelineSettle tests that a link should only pipeline a settle if the
// related add is fully locked-in meaning it is on both sides' commitment txns.
func TestPipelineSettle(t *testing.T) {
t.Parallel()
const chanAmt = btcutil.SatoshiPerBitcoin * 5
const chanReserve = btcutil.SatoshiPerBitcoin * 1
aliceLink, bobChannel, _, start, cleanUp, restore, err :=
newSingleLinkTestHarness(chanAmt, chanReserve)
require.NoError(t, err)
defer cleanUp()
alice := newPersistentLinkHarness(
t, aliceLink, nil, restore,
)
linkErrors := make(chan LinkFailureError, 1)
// Modify OnChannelFailure so we are notified when the link is failed.
alice.coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID,
_ lnwire.ShortChannelID, linkErr LinkFailureError) {
linkErrors <- linkErr
}
// Modify ForwardPackets so we are notified if a settle packet is
// erroneously forwarded. If the forwardChan is closed before the last
// step, then the test will fail.
forwardChan := make(chan struct{})
fwdPkts := func(c chan struct{}, hp ...*htlcPacket) error {
close(forwardChan)
return nil
}
alice.coreLink.cfg.ForwardPackets = fwdPkts
// Put Alice in ExitSettle mode, so we can simulate a multi-hop route
// without actually doing so. This allows us to test the locked-in add
// logic without having the add being removed by Alice sending a
// settle.
alice.coreLink.cfg.HodlMask = hodl.Mask(hodl.ExitSettle)
err = start()
require.NoError(t, err)
ctx := linkTestContext{
t: t,
aliceLink: alice.link,
bobChannel: bobChannel,
aliceMsgs: alice.msgs,
}
// First lock in an HTLC from Bob to Alice.
htlc1, invoice1 := generateHtlcAndInvoice(t, 0)
preimage1 := invoice1.Terms.PaymentPreimage
// Add the invoice to Alice's registry so she expects it.
aliceReg := alice.coreLink.cfg.Registry.(*mockInvoiceRegistry)
err = aliceReg.AddInvoice(*invoice1, htlc1.PaymentHash)
require.NoError(t, err)
// <---add-----
ctx.sendHtlcBobToAlice(htlc1)
// <---sig-----
ctx.sendCommitSigBobToAlice(1)
// ----rev---->
ctx.receiveRevAndAckAliceToBob()
// ----sig---->
ctx.receiveCommitSigAliceToBob(1)
// <---rev-----
ctx.sendRevAndAckBobToAlice()
// Bob will send the preimage for the HTLC he just sent. This will test
// the check that the HTLC is locked-in. The channel should not be
// force closed if everything is working correctly.
settle1 := &lnwire.UpdateFulfillHTLC{
ID: 0,
PaymentPreimage: *preimage1,
}
ctx.aliceLink.HandleChannelUpdate(settle1)
// ForceClose should be false.
select {
case linkErr := <-linkErrors:
require.False(t, linkErr.ForceClose)
case <-forwardChan:
t.Fatal("packet was erroneously forwarded")
}
// Restart Alice's link with the hodl.ExitSettle and hodl.Commit flags.
alice.restart(false, false, hodl.ExitSettle, hodl.Commit)
ctx.aliceLink = alice.link
ctx.aliceMsgs = alice.msgs
alice.coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID,
_ lnwire.ShortChannelID, linkErr LinkFailureError) {
linkErrors <- linkErr
}
alice.coreLink.cfg.ForwardPackets = fwdPkts
// Alice will now send an HTLC to Bob, but won't sign a commitment for
// it. This HTLC will have the same payment hash as the one above.
htlc2 := htlc1
// ----add--->
ctx.sendHtlcAliceToBob(0, htlc2)
ctx.receiveHtlcAliceToBob()
// Now Bob will send a settle backwards before the HTLC is locked in
// and the link should be failed again.
settle2 := &lnwire.UpdateFulfillHTLC{
ID: 0,
PaymentPreimage: *preimage1,
}
ctx.aliceLink.HandleChannelUpdate(settle2)
// ForceClose should be false.
select {
case linkErr := <-linkErrors:
require.False(t, linkErr.ForceClose)
case <-forwardChan:
t.Fatal("packet was erroneously forwarded")
}
// Restart Alice's link without the hodl.Commit flag.
alice.restart(false, false, hodl.ExitSettle)
ctx.aliceLink = alice.link
ctx.aliceMsgs = alice.msgs
alice.coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID,
_ lnwire.ShortChannelID, linkErr LinkFailureError) {
linkErrors <- linkErr
}
alice.coreLink.cfg.ForwardPackets = fwdPkts
// Alice's mailbox should give the link the HTLC to send again.
select {
case msg := <-ctx.aliceMsgs:
_, ok := msg.(*lnwire.UpdateAddHTLC)
require.True(t, ok)
case <-time.After(5 * time.Second):
t.Fatal("did not receive htlc from alice")
}
// Trigger the BatchTicker.
select {
case alice.batchTicker <- time.Now():
case <-time.After(5 * time.Second):
t.Fatalf("could not force commit sig")
}
// ----sig--->
ctx.receiveCommitSigAliceToBob(2)
// <---rev----
ctx.sendRevAndAckBobToAlice()
// <---sig----
ctx.sendCommitSigBobToAlice(2)
// ----rev--->
ctx.receiveRevAndAckAliceToBob()
// Bob should now be able to send the settle to Alice without making
// the link fail.
ctx.aliceLink.HandleChannelUpdate(settle2)
select {
case <-linkErrors:
t.Fatal("should not have received a link error")
case <-forwardChan:
// success
}
}
// assertFailureCode asserts that an error is of type ClearTextError and that
// the failure code is as expected.
func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) {