lsps2: add integration tests

This commit is contained in:
Jesse de Wit
2023-09-04 20:38:26 +02:00
parent 9fed9a1e68
commit ba1e4074eb
11 changed files with 467 additions and 23 deletions

View File

@@ -145,7 +145,11 @@ jobs:
testLsps0GetProtocolVersions,
testLsps2GetVersions,
testLsps2GetInfo,
testLsps2Buy
testLsps2Buy,
testLsps2HappyFlow,
testLsps2Cltv,
testLsps2NoBalance,
testLsps2ZeroConfUtxo
]
implementation: [
CLN

View File

@@ -2,10 +2,16 @@ package itest
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"log"
"math/rand"
"testing"
"github.com/breez/lntest"
"github.com/breez/lspd/lsps0"
"github.com/breez/lspd/lsps0/jsonrpc"
"github.com/breez/lspd/lsps2"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/chaincfg"
@@ -39,6 +45,18 @@ type invoice struct {
}
func GenerateInvoices(n BreezClient, req generateInvoicesRequest) (invoice, invoice) {
return generateInvoices(n, req, lntest.ShortChannelID{
BlockHeight: 1,
TxIndex: 0,
OutputIndex: 0,
}, lspCltvDelta)
}
func GenerateLsps2Invoices(n BreezClient, req generateInvoicesRequest, scid string) (invoice, invoice) {
return generateInvoices(n, req, lntest.NewShortChanIDFromString(scid), lspCltvDelta+2)
}
func generateInvoices(n BreezClient, req generateInvoicesRequest, scid lntest.ShortChannelID, cltvDelta uint16) (invoice, invoice) {
preimage, err := GenerateRandomBytes(32)
lntest.CheckError(n.Harness().T, err)
@@ -47,11 +65,7 @@ func GenerateInvoices(n BreezClient, req generateInvoicesRequest) (invoice, invo
Description: &req.description,
Preimage: &preimage,
})
outerInvoice := AddHopHint(n, innerInvoice.Bolt11, req.lsp, lntest.ShortChannelID{
BlockHeight: 1,
TxIndex: 0,
OutputIndex: 0,
}, &req.outerAmountMsat)
outerInvoice := AddHopHint(n, innerInvoice.Bolt11, req.lsp, scid, &req.outerAmountMsat, cltvDelta)
inner := invoice{
bolt11: innerInvoice.Bolt11,
@@ -76,7 +90,7 @@ func ContainsHopHint(t *testing.T, invoice string) bool {
return len(rawInvoice.RouteHints) > 0
}
func AddHopHint(n BreezClient, invoice string, lsp LspNode, chanid lntest.ShortChannelID, amountMsat *uint64) string {
func AddHopHint(n BreezClient, invoice string, lsp LspNode, chanid lntest.ShortChannelID, amountMsat *uint64, cltvDelta uint16) string {
rawInvoice, err := zpay32.Decode(invoice, &chaincfg.RegressionNetParams)
lntest.CheckError(n.Harness().T, err)
@@ -93,7 +107,7 @@ func AddHopHint(n BreezClient, invoice string, lsp LspNode, chanid lntest.ShortC
ChannelID: chanid.ToUint64(),
FeeBaseMSat: lspBaseFeeMsat,
FeeProportionalMillionths: lspFeeRatePpm,
CLTVExpiryDelta: lspCltvDelta,
CLTVExpiryDelta: cltvDelta,
},
})
@@ -121,3 +135,67 @@ func AddHopHint(n BreezClient, invoice string, lsp LspNode, chanid lntest.ShortC
return newInvoice
}
func Lsps2GetInfo(c BreezClient, l LspNode, req lsps2.GetInfoRequest) lsps2.GetInfoResponse {
req.Version = lsps2.SupportedVersion
r := lsps2RequestResponse(c, l, "lsps2.get_info", req)
var resp lsps2.GetInfoResponse
err := json.Unmarshal(r, &resp)
lntest.CheckError(c.Harness().T, err)
return resp
}
func Lsps2Buy(c BreezClient, l LspNode, req lsps2.BuyRequest) lsps2.BuyResponse {
req.Version = lsps2.SupportedVersion
r := lsps2RequestResponse(c, l, "lsps2.buy", req)
var resp lsps2.BuyResponse
err := json.Unmarshal(r, &resp)
lntest.CheckError(c.Harness().T, err)
return resp
}
func lsps2RequestResponse(c BreezClient, l LspNode, method string, req interface{}) []byte {
id := RandStringBytes(32)
peerId := hex.EncodeToString(l.NodeId())
inner, err := json.Marshal(req)
lntest.CheckError(c.Harness().T, err)
outer, err := json.Marshal(&jsonrpc.Request{
JsonRpc: jsonrpc.Version,
Method: method,
Id: id,
Params: inner,
})
lntest.CheckError(c.Harness().T, err)
log.Printf(string(outer))
c.Node().SendCustomMessage(&lntest.CustomMsgRequest{
PeerId: peerId,
Type: lsps0.Lsps0MessageType,
Data: outer,
})
m := c.ReceiveCustomMessage()
log.Printf(string(m.Data))
var resp jsonrpc.Response
err = json.Unmarshal(m.Data, &resp)
lntest.CheckError(c.Harness().T, err)
if resp.Id != id {
c.Harness().T.Fatalf("Received custom message, but had different id")
}
return resp.Result
}
const letterBytes = "abcdefghijklmnopqrstuvwxyz"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}

View File

@@ -114,6 +114,8 @@ func newLspd(h *lntest.TestHarness, mem *mempoolApi, name string, nodeConfig *co
ChannelMinimumFeeMsat: 2000000,
AdditionalChannelCapacity: 100000,
MaxInactiveDuration: 3888000,
MinPaymentSizeMsat: 600,
MaxPaymentSizeMsat: 4000000000,
Lnd: lnd,
Cln: cln,
}

View File

@@ -18,8 +18,14 @@ func TestLspd(t *testing.T) {
return
}
testCases := allTestCases
runTests(t, testCases, "LND-lsp-CLN-client", lndLspFunc, clnClientFunc)
runTests(t, testCases, "LND-lsp-LND-client", legacyOnionLndLspFunc, lndClientFunc)
var lndTestCases []*testCase
for _, c := range testCases {
if !c.isLsps2 {
lndTestCases = append(lndTestCases, c)
}
}
runTests(t, lndTestCases, "LND-lsp-CLN-client", lndLspFunc, clnClientFunc)
runTests(t, lndTestCases, "LND-lsp-LND-client", legacyOnionLndLspFunc, lndClientFunc)
runTests(t, testCases, "CLN-lsp-CLN-client", clnLspFunc, clnClientFunc)
}
@@ -117,6 +123,7 @@ type testCase struct {
name string
test func(t *testParams)
skipCreateLsp bool
isLsps2 bool
timeout time.Duration
}
@@ -179,19 +186,44 @@ var allTestCases = []*testCase{
test: testOfflineNotificationZeroConfChannel,
},
{
name: "testLsps0GetProtocolVersions",
test: testLsps0GetProtocolVersions,
name: "testLsps0GetProtocolVersions",
test: testLsps0GetProtocolVersions,
isLsps2: true,
},
{
name: "testLsps2GetVersions",
test: testLsps2GetVersions,
name: "testLsps2GetVersions",
test: testLsps2GetVersions,
isLsps2: true,
},
{
name: "testLsps2GetInfo",
test: testLsps2GetInfo,
name: "testLsps2GetInfo",
test: testLsps2GetInfo,
isLsps2: true,
},
{
name: "testLsps2Buy",
test: testLsps2Buy,
name: "testLsps2Buy",
test: testLsps2Buy,
isLsps2: true,
},
{
name: "testLsps2HappyFlow",
test: testLsps2HappyFlow,
isLsps2: true,
},
{
name: "testLsps2Cltv",
test: testLsps2Cltv,
isLsps2: true,
},
{
name: "testLsps2NoBalance",
test: testLsps2NoBalance,
isLsps2: true,
},
{
name: "testLsps2ZeroConfUtxo",
test: testLsps2ZeroConfUtxo,
isLsps2: true,
skipCreateLsp: true,
},
}

View File

@@ -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)
}

63
itest/lsps2_cltv_test.go Normal file
View File

@@ -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")
}

View File

@@ -0,0 +1,72 @@
package itest
import (
"log"
"time"
"github.com/breez/lntest"
"github.com/breez/lspd/lsps2"
"github.com/stretchr/testify/assert"
)
func testLsps2HappyFlow(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,
})
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")
payResp := alice.Pay(outerInvoice.bolt11)
bobInvoice := p.BreezClient().Node().GetInvoice(payResp.PaymentHash)
assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage)
assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat)
// Make sure capacity is correct
chans := p.BreezClient().Node().GetChannels()
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)
}

View File

@@ -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")
}

View File

@@ -0,0 +1,82 @@
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(lsp.LightningNode())
log.Printf("Calling lsps2.get_info")
info := Lsps2GetInfo(p.BreezClient(), lsp, lsps2.GetInfoRequest{
Token: &WorkingToken,
})
outerAmountMsat := uint64(2100000)
innerAmountMsat := lsps2CalculateInnerAmountMsat(lsp, outerAmountMsat, info.OpeningFeeParamsMenu[0])
p.BreezClient().SetHtlcAcceptor(innerAmountMsat)
log.Printf("Calling lsps2.buy")
buyResp := Lsps2Buy(p.BreezClient(), 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: 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)
route.Hops[0].Delay += 2
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)
}

View File

@@ -270,7 +270,7 @@ func testOfflineNotificationZeroConfChannel(p *testParams) {
} else {
id = chans[0].ShortChannelID
}
invoiceWithHint = AddHopHint(p.BreezClient(), bobInvoice.Bolt11, p.Lsp(), id, nil)
invoiceWithHint = AddHopHint(p.BreezClient(), bobInvoice.Bolt11, p.Lsp(), id, nil, lspCltvDelta)
}
log.Printf("Invoice with hint: %s", invoiceWithHint)

View File

@@ -5,10 +5,13 @@ import (
"log"
"testing"
"github.com/breez/lspd/lsps2"
lspd "github.com/breez/lspd/rpc"
"github.com/stretchr/testify/assert"
)
var WorkingToken = "hello"
func GenerateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
@@ -51,4 +54,14 @@ func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64, params *lspd.
return outerAmountMsat - fee
}
func lsps2CalculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64, params *lsps2.OpeningFeeParams) uint64 {
fee := (outerAmountMsat*uint64(params.Proportional) + 999_999) / 1_000_000
if fee < params.MinFeeMsat {
fee = params.MinFeeMsat
}
log.Printf("outer: %v, fee: %v", outerAmountMsat, fee)
return outerAmountMsat - fee
}
var publicChanAmount uint64 = 1000183