From 0823c79e4ec3c4922a001f68de7365491876f5cf Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Sun, 10 Feb 2019 21:03:23 +0100 Subject: [PATCH] htlcswitch/test: hodl invoice test --- htlcswitch/link_test.go | 166 +++++++++++++++++++++++++++++++++++++++ htlcswitch/test_utils.go | 85 ++++++++++++++++---- 2 files changed, 237 insertions(+), 14 deletions(-) diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index a2d47593..8d41cdd5 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -5589,3 +5589,169 @@ func TestChannelLinkCanceledInvoice(t *testing.T) { t.Fatalf("expected unknown payment hash, but got %v", err) } } + +type hodlInvoiceTestCtx struct { + n *twoHopNetwork + startBandwidthAlice lnwire.MilliSatoshi + startBandwidthBob lnwire.MilliSatoshi + hash lntypes.Hash + preimage lntypes.Preimage + amount lnwire.MilliSatoshi + errChan chan error + + cleanUp func() +} + +func newHodlInvoiceTestCtx(t *testing.T) (*hodlInvoiceTestCtx, error) { + // Setup a alice-bob network. + aliceChannel, bobChannel, cleanUp, err := createTwoClusterChannels( + btcutil.SatoshiPerBitcoin*3, + btcutil.SatoshiPerBitcoin*5, + ) + if err != nil { + t.Fatalf("unable to create channel: %v", err) + } + + n := newTwoHopNetwork(t, aliceChannel, bobChannel, testStartingHeight) + if err := n.start(); err != nil { + t.Fatal(err) + } + + aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() + bobBandwidthBefore := n.bobChannelLink.Bandwidth() + + debug := false + if debug { + // Log message that alice receives. + n.aliceServer.intersect( + createLogFunc("alice", n.aliceChannelLink.ChanID()), + ) + + // Log message that bob receives. + n.bobServer.intersect( + createLogFunc("bob", n.bobChannelLink.ChanID()), + ) + } + + amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) + htlcAmt, totalTimelock, hops := generateHops( + amount, testStartingHeight, n.bobChannelLink, + ) + + // Generate hold invoice preimage. + r, err := generateRandomBytes(sha256.Size) + if err != nil { + t.Fatal(err) + } + preimage, err := lntypes.MakePreimage(r) + if err != nil { + t.Fatal(err) + } + hash := preimage.Hash() + + // Have alice pay the hodl invoice, wait for bob's commitment state to + // be updated and the invoice state to be updated. + receiver := n.bobServer + receiver.registry.settleChan = make(chan lntypes.Hash) + firstHop := n.bobChannelLink.ShortChanID() + errChan := n.makeHoldPayment( + n.aliceServer, receiver, firstHop, hops, amount, htlcAmt, + totalTimelock, preimage, + ) + + select { + case err := <-errChan: + t.Fatalf("no payment result expected: %v", err) + case <-time.After(time.Second): + t.Fatal("timeout") + case h := <-receiver.registry.settleChan: + if hash != h { + t.Fatal("unexpect invoice settled") + } + } + + return &hodlInvoiceTestCtx{ + n: n, + startBandwidthAlice: aliceBandwidthBefore, + startBandwidthBob: bobBandwidthBefore, + preimage: preimage, + hash: hash, + amount: amount, + errChan: errChan, + + cleanUp: func() { + cleanUp() + n.stop() + }, + }, nil +} + +// TestChannelLinkHoldInvoiceSettle asserts that a hodl invoice can be settled. +func TestChannelLinkHoldInvoiceSettle(t *testing.T) { + t.Parallel() + + ctx, err := newHodlInvoiceTestCtx(t) + if err != nil { + t.Fatal(err) + } + defer ctx.cleanUp() + + err = ctx.n.bobServer.registry.SettleHodlInvoice(ctx.preimage) + if err != nil { + t.Fatal(err) + } + + // Wait for payment to succeed. + select { + case err := <-ctx.errChan: + if err != nil { + t.Fatal(err) + } + case <-time.After(5 * time.Second): + t.Fatal("timeout") + } + + // Wait for Bob to receive the revocation. + if ctx.startBandwidthAlice-ctx.amount != + ctx.n.aliceChannelLink.Bandwidth() { + + t.Fatal("alice bandwidth should have decrease on payment " + + "amount") + } + + if ctx.startBandwidthBob+ctx.amount != + ctx.n.bobChannelLink.Bandwidth() { + + t.Fatalf("bob bandwidth isn't match: expected %v, got %v", + ctx.startBandwidthBob+ctx.amount, + ctx.n.bobChannelLink.Bandwidth()) + } +} + +// TestChannelLinkHoldInvoiceSettle asserts that a hodl invoice can be canceled. +func TestChannelLinkHoldInvoiceCancel(t *testing.T) { + t.Parallel() + + ctx, err := newHodlInvoiceTestCtx(t) + if err != nil { + t.Fatal(err) + } + defer ctx.cleanUp() + + err = ctx.n.bobServer.registry.CancelInvoice(ctx.hash) + if err != nil { + t.Fatal(err) + } + + // Wait for payment to succeed. + select { + case err := <-ctx.errChan: + if !strings.Contains(err.Error(), + lnwire.CodeUnknownPaymentHash.String()) { + + t.Fatal("expected unknown payment hash") + } + case <-time.After(5 * time.Second): + t.Fatal("timeout") + } +} diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 97603238..053c99cf 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -520,19 +520,12 @@ func getChanID(msg lnwire.Message) (lnwire.ChannelID, error) { return chanID, nil } -// generatePayment generates the htlc add request by given path blob and +// generateHoldPayment generates the htlc add request by given path blob and // invoice which should be added by destination peer. -func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32, - blob [lnwire.OnionPacketSize]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC, error) { - - var preimage [sha256.Size]byte - r, err := generateRandomBytes(sha256.Size) - if err != nil { - return nil, nil, err - } - copy(preimage[:], r) - - rhash := fastsha256.Sum256(preimage[:]) +func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi, + timelock uint32, blob [lnwire.OnionPacketSize]byte, + preimage, rhash [32]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC, + error) { invoice := &channeldb.Invoice{ CreationDate: time.Now(), @@ -552,6 +545,24 @@ func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32, return invoice, htlc, nil } +// generatePayment generates the htlc add request by given path blob and +// invoice which should be added by destination peer. +func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32, + blob [lnwire.OnionPacketSize]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC, error) { + + var preimage [sha256.Size]byte + r, err := generateRandomBytes(sha256.Size) + if err != nil { + return nil, nil, err + } + copy(preimage[:], r) + + rhash := fastsha256.Sum256(preimage[:]) + return generatePaymentWithPreimage( + invoiceAmt, htlcAmt, timelock, blob, preimage, rhash, + ) +} + // generateRoute generates the path blob by given array of peers. func generateRoute(hops ...ForwardingInfo) ([lnwire.OnionPacketSize]byte, error) { var blob [lnwire.OnionPacketSize]byte @@ -742,7 +753,8 @@ func preparePayment(sendingPeer, receivingPeer lnpeer.Peer, } // Check who is last in the route and add invoice to server registry. - if err := receiver.registry.AddInvoice(*invoice, htlc.PaymentHash); err != nil { + hash := invoice.Terms.PaymentPreimage.Hash() + if err := receiver.registry.AddInvoice(*invoice, hash); err != nil { return nil, nil, err } @@ -1132,7 +1144,7 @@ func newTwoHopNetwork(t testing.TB, } } -// start starts the three hop network alice,bob,carol servers. +// start starts the two hop network alice,bob servers. func (n *twoHopNetwork) start() error { if err := n.aliceServer.Start(); err != nil { return err @@ -1162,3 +1174,48 @@ func (n *twoHopNetwork) stop() { <-done } } + +func (n *twoHopNetwork) makeHoldPayment(sendingPeer, receivingPeer lnpeer.Peer, + firstHop lnwire.ShortChannelID, hops []ForwardingInfo, + invoiceAmt, htlcAmt lnwire.MilliSatoshi, + timelock uint32, preimage lntypes.Preimage) chan error { + + paymentErr := make(chan error, 1) + + sender := sendingPeer.(*mockServer) + receiver := receivingPeer.(*mockServer) + + // Generate route convert it to blob, and return next destination for + // htlc add request. + blob, err := generateRoute(hops...) + if err != nil { + paymentErr <- err + return paymentErr + } + + rhash := preimage.Hash() + + // Generate payment: invoice and htlc. + invoice, htlc, err := generatePaymentWithPreimage(invoiceAmt, htlcAmt, timelock, blob, + channeldb.UnknownPreimage, rhash) + if err != nil { + paymentErr <- err + return paymentErr + } + + // Check who is last in the route and add invoice to server registry. + if err := receiver.registry.AddInvoice(*invoice, rhash); err != nil { + paymentErr <- err + return paymentErr + } + + // Send payment and expose err channel. + go func() { + _, err := sender.htlcSwitch.SendHTLC( + firstHop, htlc, newMockDeobfuscator(), + ) + paymentErr <- err + }() + + return paymentErr +}