// AssertChannelExists asserts that an active channel identified by // channelPoint is known to exist from the point-of-view of node.. func (n *networkHarness) AssertChannelExists(ctx context.Context, node *lightningNode, chanPoint *wire.OutPoint) error { req := &lnrpc.ListChannelsRequest{} resp, err := node.ListChannels(ctx, req) if err != nil { return fmt.Errorf("unable fetch node's channels: %v", err) } for _, channel := range resp.Channels { if channel.ChannelPoint == chanPoint.String() { return nil } } return fmt.Errorf("channel not found") }
func testMultiHopPayments(net *networkHarness, t *harnessTest) { 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. time.Sleep(time.Second) 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("HTLC's not cleared after 10 seconds") case <-finClear: } assertAsymmetricBalance := func(node *lightningNode, chanPoint wire.OutPoint, localBalance, remoteBalance int64) { channelName := "" switch chanPoint { case carolFundPoint: channelName = "Carol(local) => Alice(remote)" case aliceFundPoint: channelName = "Alice(local) => Bob(remote)" } checkBalance := func() error { listReq := &lnrpc.ListChannelsRequest{} resp, err := node.ListChannels(ctxb, listReq) if err != nil { return fmt.Errorf("unable to for node's "+ "channels: %v", err) } for _, channel := range resp.Channels { if channel.ChannelPoint != chanPoint.String() { continue } if channel.LocalBalance != localBalance { return fmt.Errorf("%v: incorrect local "+ "balances: %v != %v", channelName, channel.LocalBalance, localBalance) } if channel.RemoteBalance != remoteBalance { return fmt.Errorf("%v: incorrect remote "+ "balances: %v != %v", channelName, channel.RemoteBalance, remoteBalance) } return nil } return fmt.Errorf("channel not found") } // As far as HTLC inclusion in commitment transaction might be // postponed we will try to check the balance couple of // times, and then if after some period of time we receive wrong // balance return the error. // TODO(roasbeef): remove sleep after invoice notification hooks // are in place var timeover uint32 go func() { <-time.After(time.Second * 20) atomic.StoreUint32(&timeover, 1) }() for { isTimeover := atomic.LoadUint32(&timeover) == 1 if err := checkBalance(); err != nil { if isTimeover { t.Fatalf("Check balance failed: %v", err) } } else { break } } } // 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. The order of asserts corresponds to // increasing of time is needed to embed the HTLC in commitment // transaction, in channel Carol->Alice->Bob, order is Bob,Alice,Carol. const sourceBal = int64(95000) const sinkBal = int64(5000) assertAsymmetricBalance(net.Bob, aliceFundPoint, sinkBal, sourceBal) assertAsymmetricBalance(net.Alice, aliceFundPoint, sourceBal, sinkBal) assertAsymmetricBalance(net.Alice, carolFundPoint, sinkBal, sourceBal) assertAsymmetricBalance(carol, carolFundPoint, sourceBal, sinkBal) ctxt, _ = context.WithTimeout(ctxb, timeout) closeChannelAndAssert(t, net, ctxt, net.Alice, chanPointAlice) ctxt, _ = context.WithTimeout(ctxb, timeout) closeChannelAndAssert(t, net, ctxt, carol, chanPointCarol) }