mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
507 lines
18 KiB
Go
507 lines
18 KiB
Go
package itest
|
|
|
|
import (
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lntest/node"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
numPayments = 5
|
|
paymentAmt = 1000
|
|
baseFee = 1
|
|
)
|
|
|
|
// testSwitchCircuitPersistence creates a multihop network to ensure the sender
|
|
// and intermediaries are persisting their open payment circuits. After
|
|
// forwarding a packet via an outgoing link, all are restarted, and expected to
|
|
// forward a response back from the receiver once back online.
|
|
//
|
|
// The general flow of this test:
|
|
// 1. Carol --> Dave --> Alice --> Bob forward payment
|
|
// 2. X X X Bob restart sender and intermediaries
|
|
// 3. Carol <-- Dave <-- Alice <-- Bob expect settle to propagate
|
|
//
|
|
//nolint:dupword
|
|
func testSwitchCircuitPersistence(ht *lntest.HarnessTest) {
|
|
// Setup our test scenario. We should now have four nodes running with
|
|
// three channels.
|
|
s := setupScenarioFourNodes(ht)
|
|
defer s.cleanUp()
|
|
|
|
// Restart the intermediaries and the sender.
|
|
ht.RestartNode(s.dave)
|
|
ht.RestartNode(s.alice)
|
|
ht.RestartNode(s.bob)
|
|
|
|
// Ensure all of the intermediate links are reconnected.
|
|
ht.EnsureConnected(s.alice, s.dave)
|
|
ht.EnsureConnected(s.bob, s.alice)
|
|
|
|
// Ensure all nodes in the network still have 5 outstanding htlcs.
|
|
s.assertHTLCs(ht, numPayments)
|
|
|
|
// Now restart carol without hodl mode, to settle back the outstanding
|
|
// payments.
|
|
s.carol.SetExtraArgs(nil)
|
|
ht.RestartNode(s.carol)
|
|
|
|
ht.EnsureConnected(s.dave, s.carol)
|
|
|
|
// After the payments settle, there should be no active htlcs on any of
|
|
// the nodes in the network.
|
|
s.assertHTLCs(ht, 0)
|
|
|
|
// When asserting the amount of satoshis moved, we'll factor in the
|
|
// default base fee, as we didn't modify the fee structure when
|
|
// creating the seed nodes in the network.
|
|
|
|
// At this point all the channels within our proto network should be
|
|
// shifted by 5k satoshis in the direction of Carol, the sink within
|
|
// the payment flow generated above. The order of asserts corresponds
|
|
// to increasing of time is needed to embed the HTLC in commitment
|
|
// transaction, in channel Bob->Alice->David->Carol, order is Carol,
|
|
// David, Alice, Bob.
|
|
var amountPaid = int64(5000)
|
|
s.assertAmoutPaid(ht, amountPaid, numPayments)
|
|
|
|
// Lastly, we will send one more payment to ensure all channels are
|
|
// still functioning properly.
|
|
finalInvoice := &lnrpc.Invoice{
|
|
Memo: "testing",
|
|
Value: paymentAmt,
|
|
}
|
|
resp := s.carol.RPC.AddInvoice(finalInvoice)
|
|
payReqs := []string{resp.PaymentRequest}
|
|
|
|
// Using Carol as the source, pay to the 5 invoices from Bob created
|
|
// above.
|
|
ht.CompletePaymentRequests(s.bob, payReqs)
|
|
|
|
amountPaid = int64(6000)
|
|
s.assertAmoutPaid(ht, amountPaid, numPayments+1)
|
|
}
|
|
|
|
// testSwitchOfflineDelivery constructs a set of multihop payments, and tests
|
|
// that the returning payments are not lost if a peer on the backwards path is
|
|
// offline when the settle/fails are received. We expect the payments to be
|
|
// buffered in memory, and transmitted as soon as the disconnect link comes back
|
|
// online.
|
|
//
|
|
// The general flow of this test:
|
|
// 1. Carol --> Dave --> Alice --> Bob forward payment
|
|
// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries
|
|
// 3. Carol --- Dave X Alice <-- Bob settle last hop
|
|
// 4. Carol <-- Dave <-- Alice --- Bob reconnect, expect settle to propagate
|
|
func testSwitchOfflineDelivery(ht *lntest.HarnessTest) {
|
|
// Setup our test scenario. We should now have four nodes running with
|
|
// three channels.
|
|
s := setupScenarioFourNodes(ht)
|
|
defer s.cleanUp()
|
|
|
|
// First, disconnect Dave and Alice so that their link is broken.
|
|
ht.DisconnectNodes(s.dave, s.alice)
|
|
|
|
// Then, reconnect them to ensure Dave doesn't just fail back the htlc.
|
|
ht.ConnectNodes(s.dave, s.alice)
|
|
|
|
// Wait to ensure that the payment remain are not failed back after
|
|
// reconnecting. All node should report the number payments initiated
|
|
// for the duration of the interval.
|
|
s.assertHTLCs(ht, numPayments)
|
|
|
|
// Now, disconnect Dave from Alice again before settling back the
|
|
// payment.
|
|
ht.DisconnectNodes(s.dave, s.alice)
|
|
|
|
// Now restart carol without hodl mode, to settle back the outstanding
|
|
// payments.
|
|
s.carol.SetExtraArgs(nil)
|
|
ht.RestartNode(s.carol)
|
|
|
|
// Wait for Carol to report no outstanding htlcs.
|
|
ht.AssertNumActiveHtlcs(s.carol, 0)
|
|
|
|
// Now that the settles have reached Dave, reconnect him with Alice,
|
|
// allowing the settles to return to the sender.
|
|
ht.EnsureConnected(s.dave, s.alice)
|
|
|
|
// Wait until all outstanding htlcs in the network have been settled.
|
|
s.assertHTLCs(ht, 0)
|
|
|
|
// At this point all the channels within our proto network should be
|
|
// shifted by 5k satoshis in the direction of Carol, the sink within the
|
|
// payment flow generated above. The order of asserts corresponds to
|
|
// increasing of time is needed to embed the HTLC in commitment
|
|
// transaction, in channel Bob->Alice->David->Carol, order is Carol,
|
|
// David, Alice, Bob.
|
|
var amountPaid = int64(5000)
|
|
s.assertAmoutPaid(ht, amountPaid, numPayments)
|
|
|
|
// Lastly, we will send one more payment to ensure all channels are
|
|
// still functioning properly.
|
|
finalInvoice := &lnrpc.Invoice{
|
|
Memo: "testing",
|
|
Value: paymentAmt,
|
|
}
|
|
resp := s.carol.RPC.AddInvoice(finalInvoice)
|
|
payReqs := []string{resp.PaymentRequest}
|
|
|
|
// Using Carol as the source, pay to the 5 invoices from Bob created
|
|
// above.
|
|
ht.CompletePaymentRequests(s.bob, payReqs)
|
|
|
|
amountPaid = int64(6000)
|
|
s.assertAmoutPaid(ht, amountPaid, numPayments+1)
|
|
}
|
|
|
|
// testSwitchOfflineDeliveryPersistence constructs a set of multihop payments,
|
|
// and tests that the returning payments are not lost if a peer on the backwards
|
|
// path is offline when the settle/fails are received AND the peer buffering the
|
|
// responses is completely restarts. We expect the payments to be reloaded from
|
|
// disk, and transmitted as soon as the intermediaries are reconnected.
|
|
//
|
|
// The general flow of this test:
|
|
// 1. Carol --> Dave --> Alice --> Bob forward payment
|
|
// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries
|
|
// 3. Carol --- Dave X Alice <-- Bob settle last hop
|
|
// 4. Carol --- Dave X X Bob restart Alice
|
|
// 5. Carol <-- Dave <-- Alice --- Bob expect settle to propagate
|
|
//
|
|
//nolint:dupword
|
|
func testSwitchOfflineDeliveryPersistence(ht *lntest.HarnessTest) {
|
|
// Setup our test scenario. We should now have four nodes running with
|
|
// three channels.
|
|
s := setupScenarioFourNodes(ht)
|
|
defer s.cleanUp()
|
|
|
|
// Disconnect the two intermediaries, Alice and Dave, by shutting down
|
|
// Alice.
|
|
restartAlice := ht.SuspendNode(s.alice)
|
|
|
|
// Now restart carol without hodl mode, to settle back the outstanding
|
|
// payments.
|
|
s.carol.SetExtraArgs(nil)
|
|
ht.RestartNode(s.carol)
|
|
|
|
// Make Carol and Dave are reconnected before waiting for the htlcs to
|
|
// clear.
|
|
ht.EnsureConnected(s.dave, s.carol)
|
|
|
|
// Wait for Carol to report no outstanding htlcs, and also for Dave to
|
|
// receive all the settles from Carol.
|
|
ht.AssertNumActiveHtlcs(s.carol, 0)
|
|
// As an intermediate node, Dave should now have zero outgoing HTLCs
|
|
// and 5 incoming HTLCs from Alice.
|
|
ht.AssertNumActiveHtlcs(s.dave, numPayments)
|
|
|
|
// Finally, restart dave who received the settles, but was unable to
|
|
// deliver them to Alice since they were disconnected.
|
|
ht.RestartNode(s.dave)
|
|
require.NoError(ht, restartAlice(), "restart alice failed")
|
|
|
|
// Force Dave and Alice to reconnect before waiting for the htlcs to
|
|
// clear.
|
|
ht.EnsureConnected(s.dave, s.alice)
|
|
|
|
// After reconnection succeeds, the settles should be propagated all
|
|
// the way back to the sender. All nodes should report no active htlcs.
|
|
s.assertHTLCs(ht, 0)
|
|
|
|
// When asserting the amount of satoshis moved, we'll factor in the
|
|
// default base fee, as we didn't modify the fee structure when
|
|
// creating the seed nodes in the network.
|
|
|
|
// At this point all the channels within our proto network should be
|
|
// shifted by 5k satoshis in the direction of Carol, the sink within the
|
|
// payment flow generated above. The order of asserts corresponds to
|
|
// increasing of time is needed to embed the HTLC in commitment
|
|
// transaction, in channel Bob->Alice->David->Carol, order is Carol,
|
|
// David, Alice, Bob.
|
|
var amountPaid = int64(5000)
|
|
s.assertAmoutPaid(ht, amountPaid, numPayments)
|
|
|
|
// Lastly, we will send one more payment to ensure all channels are
|
|
// still functioning properly.
|
|
finalInvoice := &lnrpc.Invoice{
|
|
Memo: "testing",
|
|
Value: paymentAmt,
|
|
}
|
|
resp := s.carol.RPC.AddInvoice(finalInvoice)
|
|
payReqs := []string{resp.PaymentRequest}
|
|
|
|
// Before completing the final payment request, ensure that the
|
|
// connection between Dave and Carol has been healed.
|
|
ht.EnsureConnected(s.dave, s.carol)
|
|
|
|
// Using Carol as the source, pay to the 5 invoices from Bob created
|
|
// above.
|
|
ht.CompletePaymentRequests(s.bob, payReqs)
|
|
|
|
amountPaid = int64(6000)
|
|
s.assertAmoutPaid(ht, amountPaid, numPayments+1)
|
|
}
|
|
|
|
// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop
|
|
// payments, and tests that the returning payments are not lost if a peer on
|
|
// the backwards path is offline when the settle/fails are received AND the
|
|
// peer buffering the responses is completely restarts. We expect the payments
|
|
// to be reloaded from disk, and transmitted as soon as the intermediaries are
|
|
// reconnected.
|
|
//
|
|
// The general flow of this test:
|
|
// 1. Carol --> Dave --> Alice --> Bob forward payment
|
|
// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries
|
|
// 3. Carol --- Dave X Alice <-- Bob settle last hop
|
|
// 4. Carol --- Dave X X shutdown Bob, restart Alice
|
|
// 5. Carol <-- Dave <-- Alice X expect settle to propagate
|
|
//
|
|
//nolint:dupword
|
|
func testSwitchOfflineDeliveryOutgoingOffline(ht *lntest.HarnessTest) {
|
|
// Setup our test scenario. We should now have four nodes running with
|
|
// three channels. Note that we won't call the cleanUp function here as
|
|
// we will manually stop the node Carol and her channel.
|
|
s := setupScenarioFourNodes(ht)
|
|
defer s.cleanUp()
|
|
|
|
// Disconnect the two intermediaries, Alice and Dave, so that when carol
|
|
// restarts, the response will be held by Dave.
|
|
restartAlice := ht.SuspendNode(s.alice)
|
|
|
|
// Now restart carol without hodl mode, to settle back the outstanding
|
|
// payments.
|
|
s.carol.SetExtraArgs(nil)
|
|
ht.RestartNode(s.carol)
|
|
|
|
// Wait for Carol to report no outstanding htlcs.
|
|
ht.AssertNumActiveHtlcs(s.carol, 0)
|
|
// As an intermediate node, Dave should now have zero outgoing HTLCs
|
|
// and 5 incoming HTLCs from Alice.
|
|
ht.AssertNumActiveHtlcs(s.dave, numPayments)
|
|
|
|
// Now check that the total amount was transferred from Dave to Carol.
|
|
// The amount transferred should be exactly equal to the invoice total
|
|
// payment amount, 5k satsohis.
|
|
const amountPaid = int64(5000)
|
|
ht.AssertAmountPaid(
|
|
"Dave(local) => Carol(remote)", s.carol,
|
|
s.chanPointCarolDave, int64(0), amountPaid,
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Dave(local) => Carol(remote)", s.dave,
|
|
s.chanPointCarolDave, amountPaid, int64(0),
|
|
)
|
|
|
|
// Shutdown carol and leave her offline for the rest of the test. This
|
|
// is critical, as we wish to see if Dave can propragate settles even if
|
|
// the outgoing link is never revived.
|
|
restartCarol := ht.SuspendNode(s.carol)
|
|
|
|
// Now restart Dave, ensuring he is both persisting the settles, and is
|
|
// able to reforward them to Alice after recovering from a restart.
|
|
ht.RestartNode(s.dave)
|
|
require.NoErrorf(ht, restartAlice(), "restart alice failed")
|
|
|
|
// Ensure that Dave is reconnected to Alice before waiting for the
|
|
// htlcs to clear.
|
|
ht.EnsureConnected(s.dave, s.alice)
|
|
|
|
// Since Carol has been shutdown permanently, we will wait until all
|
|
// other nodes in the network report no active htlcs.
|
|
ht.AssertNumActiveHtlcs(s.alice, 0)
|
|
ht.AssertNumActiveHtlcs(s.bob, 0)
|
|
ht.AssertNumActiveHtlcs(s.dave, 0)
|
|
|
|
// When asserting the amount of satoshis moved, we'll factor in the
|
|
// default base fee, as we didn't modify the fee structure when
|
|
// creating the seed nodes in the network.
|
|
|
|
// At this point, all channels (minus Carol, who is shutdown) should
|
|
// show a shift of 5k satoshis towards Carol. The order of asserts
|
|
// corresponds to increasing of time is needed to embed the HTLC in
|
|
// commitment transaction, in channel Bob->Alice->David, order is
|
|
// David, Alice, Bob.
|
|
ht.AssertAmountPaid(
|
|
"Alice(local) => Dave(remote)", s.dave, s.chanPointDaveAlice,
|
|
int64(0), amountPaid+(baseFee*numPayments),
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Alice(local) => Dave(remote)", s.alice, s.chanPointDaveAlice,
|
|
amountPaid+(baseFee*numPayments), int64(0),
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Bob(local) => Alice(remote)", s.alice, s.chanPointAliceBob,
|
|
int64(0), amountPaid+((baseFee*numPayments)*2),
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Bob(local) => Alice(remote)", s.bob, s.chanPointAliceBob,
|
|
amountPaid+(baseFee*numPayments)*2, int64(0),
|
|
)
|
|
|
|
// Finally, restart Carol so the cleanup process can be finished.
|
|
require.NoError(ht, restartCarol())
|
|
}
|
|
|
|
// scenarioFourNodes specifies a scenario which we have a topology that has
|
|
// four nodes and three channels.
|
|
type scenarioFourNodes struct {
|
|
alice *node.HarnessNode
|
|
bob *node.HarnessNode
|
|
carol *node.HarnessNode
|
|
dave *node.HarnessNode
|
|
|
|
chanPointAliceBob *lnrpc.ChannelPoint
|
|
chanPointCarolDave *lnrpc.ChannelPoint
|
|
chanPointDaveAlice *lnrpc.ChannelPoint
|
|
|
|
cleanUp func()
|
|
}
|
|
|
|
// setupScenarioFourNodes creates a topology for switch tests. It will create
|
|
// two new nodes: Carol and Dave, such that there will be a 4 nodes, 3 channel
|
|
// topology. Dave will make a channel with Alice, and Carol with Dave. After
|
|
// this setup, the network topology should now look like:
|
|
//
|
|
// Carol -> Dave -> Alice -> Bob
|
|
//
|
|
// Once the network is created, Carol will generate 5 invoices and Bob will pay
|
|
// them using the above path.
|
|
//
|
|
// NOTE: caller needs to call cleanUp to clean the nodes and channels created
|
|
// from this setup.
|
|
func setupScenarioFourNodes(ht *lntest.HarnessTest) *scenarioFourNodes {
|
|
const (
|
|
chanAmt = btcutil.Amount(1000000)
|
|
pushAmt = btcutil.Amount(900000)
|
|
)
|
|
|
|
params := lntest.OpenChannelParams{
|
|
Amt: chanAmt,
|
|
PushAmt: pushAmt,
|
|
}
|
|
|
|
// Grab the standby nodes.
|
|
alice, bob := ht.Alice, ht.Bob
|
|
|
|
// As preliminary setup, we'll create two new nodes: Carol and Dave,
|
|
// such that we now have a 4 node, 3 channel topology. Dave will make
|
|
// a channel with Alice, and Carol with Dave. After this setup, the
|
|
// network topology should now look like:
|
|
// Carol -> Dave -> Alice -> Bob
|
|
//
|
|
// First, we'll create Dave and establish a channel to Alice.
|
|
dave := ht.NewNode("Dave", nil)
|
|
ht.ConnectNodes(dave, alice)
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
|
|
|
// Next, we'll create Carol and establish a channel to from her to
|
|
// Dave. Carol is started in htlchodl mode so that we can disconnect
|
|
// the intermediary hops before starting the settle.
|
|
carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"})
|
|
ht.ConnectNodes(carol, dave)
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
|
|
|
// Open channels in batch to save blocks mined.
|
|
reqs := []*lntest.OpenChannelRequest{
|
|
{Local: alice, Remote: bob, Param: params},
|
|
{Local: dave, Remote: alice, Param: params},
|
|
{Local: carol, Remote: dave, Param: params},
|
|
}
|
|
resp := ht.OpenMultiChannelsAsync(reqs)
|
|
|
|
// Wait for all nodes to have seen all channels.
|
|
nodes := []*node.HarnessNode{alice, bob, carol, dave}
|
|
for _, chanPoint := range resp {
|
|
for _, node := range nodes {
|
|
ht.AssertTopologyChannelOpen(node, chanPoint)
|
|
}
|
|
}
|
|
|
|
chanPointAliceBob := resp[0]
|
|
chanPointDaveAlice := resp[1]
|
|
chanPointCarolDave := resp[2]
|
|
|
|
// Create 5 invoices for Carol, which expect a payment from Bob for 1k
|
|
// satoshis with a different preimage each time.
|
|
payReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numPayments)
|
|
|
|
// Using Carol as the source, pay to the 5 invoices from Bob created
|
|
// above.
|
|
ht.CompletePaymentRequestsNoWait(bob, payReqs, chanPointAliceBob)
|
|
|
|
// Create a cleanUp to wipe the states.
|
|
cleanUp := func() {
|
|
if ht.Failed() {
|
|
ht.Skip("Skipped cleanup for failed test")
|
|
return
|
|
}
|
|
|
|
ht.CloseChannel(alice, chanPointAliceBob)
|
|
ht.CloseChannel(dave, chanPointDaveAlice)
|
|
ht.CloseChannel(carol, chanPointCarolDave)
|
|
}
|
|
|
|
s := &scenarioFourNodes{
|
|
alice, bob, carol, dave, chanPointAliceBob,
|
|
chanPointCarolDave, chanPointDaveAlice, cleanUp,
|
|
}
|
|
|
|
// Wait until all nodes in the network have 5 outstanding htlcs.
|
|
s.assertHTLCs(ht, numPayments)
|
|
|
|
return s
|
|
}
|
|
|
|
// assertHTLCs is a helper function which asserts the desired num of
|
|
// HTLCs has been seen in the nodes.
|
|
func (s *scenarioFourNodes) assertHTLCs(ht *lntest.HarnessTest, num int) {
|
|
// Alice should have both the same number of outgoing and
|
|
// incoming HTLCs.
|
|
ht.AssertNumActiveHtlcs(s.alice, num*2)
|
|
// Bob should have num of incoming HTLCs.
|
|
ht.AssertNumActiveHtlcs(s.bob, num)
|
|
// Dave should have both the same number of outgoing and
|
|
// incoming HTLCs.
|
|
ht.AssertNumActiveHtlcs(s.dave, num*2)
|
|
// Carol should have the num of outgoing HTLCs.
|
|
ht.AssertNumActiveHtlcs(s.carol, num)
|
|
}
|
|
|
|
// assertAmoutPaid is a helper method which takes a given paid amount
|
|
// and number of payments and asserts the desired payments are made in
|
|
// the four nodes.
|
|
func (s *scenarioFourNodes) assertAmoutPaid(ht *lntest.HarnessTest,
|
|
amt int64, num int64) {
|
|
|
|
ht.AssertAmountPaid(
|
|
"Dave(local) => Carol(remote)", s.carol,
|
|
s.chanPointCarolDave, int64(0), amt,
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Dave(local) => Carol(remote)", s.dave,
|
|
s.chanPointCarolDave, amt, int64(0),
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Alice(local) => Dave(remote)", s.dave,
|
|
s.chanPointDaveAlice,
|
|
int64(0), amt+(baseFee*num),
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Alice(local) => Dave(remote)", s.alice,
|
|
s.chanPointDaveAlice,
|
|
amt+(baseFee*num), int64(0),
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Bob(local) => Alice(remote)", s.alice,
|
|
s.chanPointAliceBob,
|
|
int64(0), amt+((baseFee*num)*2),
|
|
)
|
|
ht.AssertAmountPaid(
|
|
"Bob(local) => Alice(remote)", s.bob,
|
|
s.chanPointAliceBob,
|
|
amt+(baseFee*num)*2, int64(0),
|
|
)
|
|
}
|