mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-15 03:51:23 +01:00
test: add network test case for repeated multi-hop invoice payments
This commit is contained in:
parent
bf0945a6bf
commit
80d38d6bbc
1 changed files with 196 additions and 0 deletions
196
lnd_test.go
196
lnd_test.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -384,11 +385,206 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) {
|
||||||
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint)
|
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
||||||
|
const chanAmt = btcutil.Amount(100000)
|
||||||
|
ctxb := context.Background()
|
||||||
|
timeout := time.Duration(time.Second * 5)
|
||||||
|
|
||||||
|
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||||
|
// being the sole funder of the channel.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
|
chanPointAlice := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob,
|
||||||
|
chanAmt)
|
||||||
|
aliceChanTXID, err := wire.NewShaHash(chanPointAlice.FundingTxid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create sha hash: %v", err)
|
||||||
|
}
|
||||||
|
aliceFundPoint := wire.OutPoint{
|
||||||
|
Hash: *aliceChanTXID,
|
||||||
|
Index: chanPointAlice.OutputIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new node (Carol), load her with some funds, then establish
|
||||||
|
// a connection between Carol and Alice with a channel that has
|
||||||
|
// identical capacity to the one created above.
|
||||||
|
//
|
||||||
|
// The network topology should now look like: Carol -> Alice -> Bob
|
||||||
|
carol, err := net.NewNode(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create new nodes: %v", err)
|
||||||
|
}
|
||||||
|
if err := net.ConnectNodes(ctxb, carol, net.Alice); err != nil {
|
||||||
|
t.Fatalf("unable to connect carol to alice: %v", err)
|
||||||
|
}
|
||||||
|
err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, carol)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to send coins to carol: %v", err)
|
||||||
|
}
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
|
chanPointCarol := openChannelAndAssert(t, net, ctxt, carol, net.Alice,
|
||||||
|
chanAmt)
|
||||||
|
carolChanTXID, err := wire.NewShaHash(chanPointCarol.FundingTxid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create sha hash: %v", err)
|
||||||
|
}
|
||||||
|
carolFundPoint := wire.OutPoint{
|
||||||
|
Hash: *carolChanTXID,
|
||||||
|
Index: chanPointCarol.OutputIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 5 invoices for Bob, which expect a payment from Carol for 1k
|
||||||
|
// satoshis with a different preimage each time.
|
||||||
|
const numPayments = 5
|
||||||
|
const paymentAmt = 1000
|
||||||
|
rHashes := make([][]byte, numPayments)
|
||||||
|
for i := 0; i < numPayments; i++ {
|
||||||
|
preimage := bytes.Repeat([]byte{byte(i)}, 32)
|
||||||
|
invoice := &lnrpc.Invoice{
|
||||||
|
Memo: "testing",
|
||||||
|
RPreimage: preimage,
|
||||||
|
Value: paymentAmt,
|
||||||
|
}
|
||||||
|
resp, err := net.Bob.AddInvoice(ctxb, invoice)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to add invoice: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rHashes[i] = resp.RHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carol's routing table should show a path from Carol -> Alice -> Bob,
|
||||||
|
// with the two channels above recognized as the only links within the
|
||||||
|
// network.
|
||||||
|
req := &lnrpc.ShowRoutingTableRequest{}
|
||||||
|
routingResp, err := carol.ShowRoutingTable(ctxb, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to query for carol's routing table: %v", err)
|
||||||
|
}
|
||||||
|
if len(routingResp.Channels) != 2 {
|
||||||
|
t.Fatalf("only two channels should be seen as active in the "+
|
||||||
|
"network, instead %v are", len(routingResp.Channels))
|
||||||
|
}
|
||||||
|
for _, link := range routingResp.Channels {
|
||||||
|
switch {
|
||||||
|
case link.Outpoint == aliceFundPoint.String():
|
||||||
|
switch {
|
||||||
|
case link.Id1 == net.Alice.PubKeyStr &&
|
||||||
|
link.Id2 == net.Bob.PubKeyStr:
|
||||||
|
continue
|
||||||
|
case link.Id1 == net.Bob.PubKeyStr &&
|
||||||
|
link.Id2 == net.Alice.PubKeyStr:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
t.Fatalf("unkown link within routing "+
|
||||||
|
"table: %v", spew.Sdump(link))
|
||||||
|
}
|
||||||
|
case link.Outpoint == carolFundPoint.String():
|
||||||
|
switch {
|
||||||
|
case link.Id1 == net.Alice.PubKeyStr &&
|
||||||
|
link.Id2 == carol.PubKeyStr:
|
||||||
|
continue
|
||||||
|
case link.Id1 == carol.PubKeyStr &&
|
||||||
|
link.Id2 == net.Alice.PubKeyStr:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
t.Fatalf("unkown link within routing "+
|
||||||
|
"table: %v", spew.Sdump(link))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("unkown channel %v found in routing table, "+
|
||||||
|
"only %v and %v should exist", link.Outpoint,
|
||||||
|
aliceFundPoint, carolFundPoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using Carol as the source, pay to the 5 invoices from Bob created above.
|
||||||
|
carolPayStream, err := carol.SendPayment(ctxb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create payment stream for carol: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concurrently pay off all 5 of Bob's invoices. Each of the goroutines
|
||||||
|
// will unblock on the recv once the HTLC it sent has been fully
|
||||||
|
// settled.
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, rHash := range rHashes {
|
||||||
|
sendReq := &lnrpc.SendRequest{
|
||||||
|
PaymentHash: rHash,
|
||||||
|
Dest: net.Bob.PubKey[:],
|
||||||
|
Amt: paymentAmt,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
if err := carolPayStream.Send(sendReq); err != nil {
|
||||||
|
t.Fatalf("unable to send payment: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := carolPayStream.Recv(); err != nil {
|
||||||
|
t.Fatalf("unable to recv pay resp: %v", err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
finClear := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(finClear)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
t.Fatalf("HLTC's not cleared after 10 seconds")
|
||||||
|
case <-finClear:
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAsymmetricBalance := func(node *lightningNode,
|
||||||
|
chanPoint *wire.OutPoint, localBalance, remoteBalance int64) {
|
||||||
|
listReq := &lnrpc.ListChannelsRequest{}
|
||||||
|
resp, err := node.ListChannels(ctxb, listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to for node's channels: %v", err)
|
||||||
|
}
|
||||||
|
for _, channel := range resp.Channels {
|
||||||
|
if channel.ChannelPoint != chanPoint.String() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.LocalBalance != localBalance ||
|
||||||
|
channel.RemoteBalance != remoteBalance {
|
||||||
|
t.Fatalf("incorrect balances: %v",
|
||||||
|
spew.Sdump(channel))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("channel not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point all the channels within our proto network should be
|
||||||
|
// shifted by 5k satoshis in the direction of Bob, the sink within the
|
||||||
|
// payment flow generated above.
|
||||||
|
// TODO(roasbeef): remove sleep after invoice notification hooks are in
|
||||||
|
// place
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
const sourceBal = int64(95000)
|
||||||
|
const sinkBal = int64(5000)
|
||||||
|
assertAsymmetricBalance(carol, &carolFundPoint, sourceBal, sinkBal)
|
||||||
|
assertAsymmetricBalance(net.Alice, &carolFundPoint, sinkBal, sourceBal)
|
||||||
|
assertAsymmetricBalance(net.Alice, &aliceFundPoint, sourceBal, sinkBal)
|
||||||
|
assertAsymmetricBalance(net.Bob, &aliceFundPoint, sinkBal, sourceBal)
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
|
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPointAlice)
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
|
closeChannelAndAssert(t, net, ctxt, carol, chanPointCarol)
|
||||||
|
}
|
||||||
|
|
||||||
var lndTestCases = map[string]lndTestCase{
|
var lndTestCases = map[string]lndTestCase{
|
||||||
"basic funding flow": testBasicChannelFunding,
|
"basic funding flow": testBasicChannelFunding,
|
||||||
"channel force closure": testChannelForceClosure,
|
"channel force closure": testChannelForceClosure,
|
||||||
"channel balance": testChannelBalance,
|
"channel balance": testChannelBalance,
|
||||||
"single hop invoice": testSingleHopInvoice,
|
"single hop invoice": testSingleHopInvoice,
|
||||||
|
"test mult-hop payments": testMultiHopPayments,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
||||||
|
|
Loading…
Add table
Reference in a new issue