htlcswitch+lntest: modify Switch to persist resolution messages
Include unit tests for the Switch, and integration tests that exercise the persistence logic.
This commit is contained in:
8 files changed
+518
-26
No files matched your search
@@ -0,0 +1,200 @@
|
||||
package itest
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// testResHandoff tests that the contractcourt is able to properly hand-off
|
||||
// resolution messages to the switch.
|
||||
func testResHandoff(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
const (
|
||||
chanAmt = btcutil.Amount(1000000)
|
||||
paymentAmt = 50000
|
||||
)
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
// First we'll create a channel between Alice and Bob.
|
||||
net.EnsureConnected(t.t, net.Alice, net.Bob)
|
||||
|
||||
chanPointAlice := openChannelAndAssert(
|
||||
t, net, net.Alice, net.Bob,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
defer closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false)
|
||||
|
||||
// Wait for Alice and Bob to receive the channel edge from the funding
|
||||
// manager.
|
||||
err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
err = net.Bob.WaitForNetworkChannelOpen(chanPointAlice)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Create a new node Carol that will be in hodl mode. This is used to
|
||||
// trigger the behavior of checkRemoteDanglingActions in the
|
||||
// contractcourt. This will cause Bob to fail the HTLC back to Alice.
|
||||
carol := net.NewNode(t.t, "Carol", []string{"--hodl.commit"})
|
||||
defer shutdownAndAssert(net, t, carol)
|
||||
|
||||
// Connect Bob to Carol.
|
||||
net.ConnectNodes(t.t, net.Bob, carol)
|
||||
|
||||
// Open a channel between Bob and Carol.
|
||||
chanPointCarol := openChannelAndAssert(
|
||||
t, net, net.Bob, carol,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
|
||||
// Wait for Bob and Carol to receive the channel edge from the funding
|
||||
// manager.
|
||||
err = net.Bob.WaitForNetworkChannelOpen(chanPointCarol)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
err = carol.WaitForNetworkChannelOpen(chanPointCarol)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Wait for Alice to see the channel edge in the graph.
|
||||
err = net.Alice.WaitForNetworkChannelOpen(chanPointCarol)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// We'll create an invoice for Carol that Alice will attempt to pay.
|
||||
// Since Carol is in hodl.commit mode, she won't send back any commit
|
||||
// sigs.
|
||||
carolPayReqs, _, _, err := createPayReqs(
|
||||
carol, paymentAmt, 1,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Alice will now attempt to fulfill the invoice.
|
||||
err = completePaymentRequests(
|
||||
net.Alice, net.Alice.RouterClient, carolPayReqs, false,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Wait until Carol has received the Add, CommitSig from Bob, and has
|
||||
// responded with a RevokeAndAck. We expect NumUpdates to be 1 meaning
|
||||
// Carol's CommitHeight is 1.
|
||||
err = wait.Predicate(func() bool {
|
||||
carolInfo, err := getChanInfo(carol)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return carolInfo.NumUpdates == 1
|
||||
}, defaultTimeout)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Before we shutdown Alice, we'll assert that she only has 1 update.
|
||||
err = wait.Predicate(func() bool {
|
||||
aliceInfo, err := getChanInfo(net.Alice)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return aliceInfo.NumUpdates == 1
|
||||
}, defaultTimeout)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// We'll shutdown Alice so that Bob can't connect to her.
|
||||
restartAlice, err := net.SuspendNode(net.Alice)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Bob will now force close his channel with Carol such that resolution
|
||||
// messages are created and forwarded backwards to Alice.
|
||||
_, _, err = net.CloseChannel(net.Bob, chanPointCarol, true)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// The channel should be listed in the PendingChannels result.
|
||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
pendingChansRequest := &lnrpc.PendingChannelsRequest{}
|
||||
pendingChanResp, err := net.Bob.PendingChannels(
|
||||
ctxt, pendingChansRequest,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
require.NoError(t.t, checkNumWaitingCloseChannels(pendingChanResp, 1))
|
||||
|
||||
// We'll mine a block to confirm the force close transaction and to
|
||||
// advance Bob's contract state with Carol to StateContractClosed.
|
||||
mineBlocks(t, net, 1, 1)
|
||||
|
||||
// We sleep here so we can be sure that the hand-off has occurred from
|
||||
// Bob's contractcourt to Bob's htlcswitch. This sleep could be removed
|
||||
// if there was some feedback (i.e. API in switch) that allowed for
|
||||
// querying the state of resolution messages.
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Mine blocks until Bob has no waiting close channels. This tests
|
||||
// that the circuit-deletion logic is skipped if a resolution message
|
||||
// exists.
|
||||
for {
|
||||
_, err = net.Miner.Client.Generate(1)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
pendingChanResp, err = net.Bob.PendingChannels(
|
||||
ctxt, pendingChansRequest,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
isErr := checkNumForceClosedChannels(pendingChanResp, 0)
|
||||
if isErr == nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
}
|
||||
|
||||
// We will now restart Bob so that we can test whether the resolution
|
||||
// messages are re-forwarded on start-up.
|
||||
restartBob, err := net.SuspendNode(net.Bob)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
err = restartBob()
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// We'll now also restart Alice and connect her with Bob.
|
||||
err = restartAlice()
|
||||
require.NoError(t.t, err)
|
||||
|
||||
net.EnsureConnected(t.t, net.Alice, net.Bob)
|
||||
|
||||
// We'll assert that Alice has received the failure resolution
|
||||
// message.
|
||||
err = wait.Predicate(func() bool {
|
||||
aliceInfo, err := getChanInfo(net.Alice)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return aliceInfo.NumUpdates == 2
|
||||
}, defaultTimeout)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Assert that Alice's payment failed.
|
||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||
paymentsResp, err := net.Alice.ListPayments(
|
||||
ctxt, &lnrpc.ListPaymentsRequest{
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
require.Equal(t.t, 1, len(paymentsResp.Payments))
|
||||
|
||||
htlcs := paymentsResp.Payments[0].Htlcs
|
||||
|
||||
require.Equal(t.t, 1, len(htlcs))
|
||||
require.Equal(t.t, lnrpc.HTLCAttempt_FAILED, htlcs[0].Status)
|
||||
}
|
||||
@@ -399,4 +399,8 @@ var allTestCases = []*testCase{
|
||||
name: "addpeer config",
|
||||
test: testAddPeerConfig,
|
||||
},
|
||||
{
|
||||
name: "resolution handoff",
|
||||
test: testResHandoff,
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user