From c56fd28b21341ddf624a7a80bc1d39d382f56514 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 8 Sep 2023 13:20:21 +0200 Subject: [PATCH] lsps2: add integration tests --- .github/workflows/integration_tests.yaml | 5 +- itest/lspd_test.go | 15 +++++ itest/lsps2_buy_test.go | 44 +++++++++++-- itest/lsps2_cltv_test.go | 63 ++++++++++++++++++ itest/lsps2_happy_flow_test.go | 4 ++ itest/lsps2_no_balance_test.go | 62 ++++++++++++++++++ itest/lsps2_zero_conf_utxo_test.go | 81 ++++++++++++++++++++++++ 7 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 itest/lsps2_cltv_test.go create mode 100644 itest/lsps2_no_balance_test.go create mode 100644 itest/lsps2_zero_conf_utxo_test.go diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index 02478d25..8d84a6d1 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -138,7 +138,10 @@ jobs: testLsps2GetVersions, testLsps2GetInfo, testLsps2Buy, - testLsps2HappyFlow + testLsps2HappyFlow, + testLsps2Cltv, + testLsps2NoBalance, + testLsps2ZeroConfUtxo ] implementation: [ CLN diff --git a/itest/lspd_test.go b/itest/lspd_test.go index c0c69101..736a7149 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -199,4 +199,19 @@ var allTestCases = []*testCase{ test: testLsps2HappyFlow, isLsps2: true, }, + { + name: "testLsps2Cltv", + test: testLsps2Cltv, + isLsps2: true, + }, + { + name: "testLsps2NoBalance", + test: testLsps2NoBalance, + isLsps2: true, + }, + { + name: "testLsps2ZeroConfUtxo", + test: testLsps2ZeroConfUtxo, + isLsps2: true, + }, } diff --git a/itest/lsps2_buy_test.go b/itest/lsps2_buy_test.go index b1d6e411..15c62c6e 100644 --- a/itest/lsps2_buy_test.go +++ b/itest/lsps2_buy_test.go @@ -7,7 +7,10 @@ import ( "time" "github.com/breez/lntest" + "github.com/breez/lspd/lightning" "github.com/breez/lspd/lsps0" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/stretchr/testify/assert" ) func testLsps2Buy(p *testParams) { @@ -62,12 +65,12 @@ func testLsps2Buy(p *testParams) { Type: lsps0.Lsps0MessageType, Data: append(append( []byte(`{ - "method": "lsps2.get_info", + "method": "lsps2.buy", "jsonrpc": "2.0", - "id": "example#3cad6a54d302edba4c9ade2f7ffac098", + "id": "example#3cad6a54d302edba4c9ade2f7ffac099", "params": { "version": 1, - "payment_size_msat": "42000", + "payment_size_msat": "42000000", "opening_fee_params":`), pr...), []byte(`}}`)..., @@ -75,7 +78,7 @@ func testLsps2Buy(p *testParams) { }) buyResp := p.BreezClient().ReceiveCustomMessage() - log.Print(string(resp.Data)) + log.Print(string(buyResp.Data)) b := new(struct { Result struct { Jit_channel_scid string `json:"jit_channel_scid"` @@ -85,4 +88,37 @@ func testLsps2Buy(p *testParams) { }) err = json.Unmarshal(buyResp.Data, b) lntest.CheckError(p.t, err) + + pgxPool, err := pgxpool.Connect(p.h.Ctx, p.lsp.PostgresBackend().ConnectionString()) + if err != nil { + p.h.T.Fatalf("Failed to connect to postgres backend: %v", err) + } + defer pgxPool.Close() + + scid, err := lightning.NewShortChannelIDFromString(b.Result.Jit_channel_scid) + lntest.CheckError(p.t, err) + + rows, err := pgxPool.Query( + p.h.Ctx, + `SELECT token + FROM lsps2.buy_registrations + WHERE scid = $1`, + int64(uint64(*scid)), + ) + if err != nil { + p.h.T.Fatalf("Failed to query token: %v", err) + } + + defer rows.Close() + if !rows.Next() { + p.h.T.Fatal("No rows found") + } + + var actual string + err = rows.Scan(&actual) + if err != nil { + p.h.T.Fatalf("Failed to get token from row: %v", err) + } + + assert.Equal(p.h.T, "hello", actual) } diff --git a/itest/lsps2_cltv_test.go b/itest/lsps2_cltv_test.go new file mode 100644 index 00000000..ba46a845 --- /dev/null +++ b/itest/lsps2_cltv_test.go @@ -0,0 +1,63 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + "github.com/breez/lspd/lsps2" + "github.com/stretchr/testify/assert" +) + +func testLsps2Cltv(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + aliceLspScid := alice.WaitForChannelReady(channel) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Calling lsps2.get_info") + info := Lsps2GetInfo(p.BreezClient(), p.Lsp(), lsps2.GetInfoRequest{ + Token: &WorkingToken, + }) + + outerAmountMsat := uint64(2100000) + innerAmountMsat := lsps2CalculateInnerAmountMsat(p.lsp, outerAmountMsat, info.OpeningFeeParamsMenu[0]) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) + + log.Printf("Calling lsps2.buy") + buyResp := Lsps2Buy(p.BreezClient(), p.Lsp(), lsps2.BuyRequest{ + OpeningFeeParams: *info.OpeningFeeParamsMenu[0], + PaymentSizeMsat: &outerAmountMsat, + }) + + log.Printf("Adding bob's invoices") + description := "Please pay me" + _, outerInvoice := GenerateLsps2Invoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }, + buyResp.JitChannelScid) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + log.Printf("Alice paying") + route := constructRoute(p.Lsp().LightningNode(), p.BreezClient().Node(), aliceLspScid, lntest.NewShortChanIDFromString(buyResp.JitChannelScid), outerAmountMsat) + + // Increment the delay by one (should be incremented by 2), so the cltv delta is too little. + route.Hops[0].Delay++ + _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Contains(p.t, err.Error(), "WIRE_INCORRECT_CLTV_EXPIRY") +} diff --git a/itest/lsps2_happy_flow_test.go b/itest/lsps2_happy_flow_test.go index 280667d9..137370a8 100644 --- a/itest/lsps2_happy_flow_test.go +++ b/itest/lsps2_happy_flow_test.go @@ -65,4 +65,8 @@ func testLsps2HappyFlow(p *testParams) { assert.Equal(p.t, 1, len(chans)) c := chans[0] AssertChannelCapacity(p.t, innerAmountMsat, c.CapacityMsat) + + assert.Equal(p.t, c.RemoteReserveMsat, c.CapacityMsat/100) + log.Printf("local reserve: %d, remote reserve: %d", c.LocalReserveMsat, c.RemoteReserveMsat) + assert.Zero(p.t, c.LocalReserveMsat) } diff --git a/itest/lsps2_no_balance_test.go b/itest/lsps2_no_balance_test.go new file mode 100644 index 00000000..06952bcd --- /dev/null +++ b/itest/lsps2_no_balance_test.go @@ -0,0 +1,62 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + "github.com/breez/lspd/lsps2" + "github.com/stretchr/testify/assert" +) + +func testLsps2NoBalance(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + aliceLspScid := alice.WaitForChannelReady(channel) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Calling lsps2.get_info") + info := Lsps2GetInfo(p.BreezClient(), p.Lsp(), lsps2.GetInfoRequest{ + Token: &WorkingToken, + }) + + outerAmountMsat := uint64(2100000) + innerAmountMsat := lsps2CalculateInnerAmountMsat(p.lsp, outerAmountMsat, info.OpeningFeeParamsMenu[0]) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) + + log.Printf("Calling lsps2.buy") + buyResp := Lsps2Buy(p.BreezClient(), p.Lsp(), lsps2.BuyRequest{ + OpeningFeeParams: *info.OpeningFeeParamsMenu[0], + PaymentSizeMsat: &outerAmountMsat, + }) + + log.Printf("Adding bob's invoices") + description := "Please pay me" + _, outerInvoice := GenerateLsps2Invoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }, + buyResp.JitChannelScid) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + log.Printf("Alice paying") + route := constructRoute(p.Lsp().LightningNode(), p.BreezClient().Node(), aliceLspScid, lntest.NewShortChanIDFromString(buyResp.JitChannelScid), outerAmountMsat) + + // Increment the delay by two to support the spec + route.Hops[0].Delay += 2 + _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") +} diff --git a/itest/lsps2_zero_conf_utxo_test.go b/itest/lsps2_zero_conf_utxo_test.go new file mode 100644 index 00000000..b5a1ac5e --- /dev/null +++ b/itest/lsps2_zero_conf_utxo_test.go @@ -0,0 +1,81 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + "github.com/breez/lspd/config" + "github.com/breez/lspd/lsps2" + "github.com/stretchr/testify/assert" +) + +func testLsps2ZeroConfUtxo(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + + minConfs := uint32(0) + lsp := p.lspFunc(p.h, p.m, p.mem, &config.NodeConfig{MinConfs: &minConfs}) + lsp.Start() + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + tempaddr := lsp.LightningNode().GetNewAddress() + p.m.SendToAddress(tempaddr, 210000) + p.m.MineBlocks(6) + lsp.LightningNode().WaitForSync() + + initialHeight := p.m.GetBlockHeight() + addr := lsp.LightningNode().GetNewAddress() + lsp.LightningNode().SendToAddress(addr, 200000) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Calling lsps2.get_info") + info := Lsps2GetInfo(p.BreezClient(), p.Lsp(), lsps2.GetInfoRequest{ + Token: &WorkingToken, + }) + + outerAmountMsat := uint64(2100000) + innerAmountMsat := lsps2CalculateInnerAmountMsat(p.lsp, outerAmountMsat, info.OpeningFeeParamsMenu[0]) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) + + log.Printf("Calling lsps2.buy") + buyResp := Lsps2Buy(p.BreezClient(), p.Lsp(), lsps2.BuyRequest{ + OpeningFeeParams: *info.OpeningFeeParamsMenu[0], + PaymentSizeMsat: &outerAmountMsat, + }) + + log.Printf("Adding bob's invoices") + description := "Please pay me" + _, outerInvoice := GenerateLsps2Invoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }, + buyResp.JitChannelScid) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + log.Printf("Alice paying") + route := constructRoute(lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString(buyResp.JitChannelScid), outerAmountMsat) + payResp, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + lntest.CheckError(p.t, err) + bobInvoice := p.BreezClient().Node().GetInvoice(payResp.PaymentHash) + + assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat) + + // Make sure there's not accidently a block mined in between + finalHeight := p.m.GetBlockHeight() + assert.Equal(p.t, initialHeight, finalHeight) +}