mirror of
https://github.com/aljazceru/lspd.git
synced 2025-12-20 15:24:23 +01:00
add itests
This commit is contained in:
43
go.mod
43
go.mod
@@ -4,11 +4,14 @@ go 1.19
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go v1.30.20
|
github.com/aws/aws-sdk-go v1.30.20
|
||||||
|
github.com/breez/lntest v0.0.3
|
||||||
github.com/btcsuite/btcd v0.23.1
|
github.com/btcsuite/btcd v0.23.1
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.1
|
github.com/btcsuite/btcd/btcec/v2 v2.2.1
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
|
||||||
github.com/caddyserver/certmagic v0.11.2
|
github.com/caddyserver/certmagic v0.11.2
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
|
||||||
|
github.com/docker/docker v20.10.21+incompatible
|
||||||
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||||
github.com/jackc/pgtype v1.8.1
|
github.com/jackc/pgtype v1.8.1
|
||||||
@@ -17,7 +20,23 @@ require (
|
|||||||
github.com/lightningnetwork/lnd v0.15.1-beta
|
github.com/lightningnetwork/lnd v0.15.1-beta
|
||||||
github.com/niftynei/glightning v0.8.2
|
github.com/niftynei/glightning v0.8.2
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
google.golang.org/grpc v1.38.0
|
google.golang.org/grpc v1.50.1
|
||||||
|
gotest.tools v2.2.0+incompatible
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||||
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.8 // indirect
|
||||||
|
github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
golang.org/x/mod v0.6.0 // indirect
|
||||||
|
golang.org/x/tools v0.2.0 // indirect
|
||||||
|
gotest.tools/v3 v3.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -88,7 +107,7 @@ require (
|
|||||||
github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect
|
github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect
|
||||||
github.com/lightningnetwork/lnd/queue v1.1.0 // indirect
|
github.com/lightningnetwork/lnd/queue v1.1.0 // indirect
|
||||||
github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect
|
github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect
|
||||||
github.com/lightningnetwork/lnd/tlv v1.0.3 // indirect
|
github.com/lightningnetwork/lnd/tlv v1.0.3
|
||||||
github.com/lightningnetwork/lnd/tor v1.0.1 // indirect
|
github.com/lightningnetwork/lnd/tor v1.0.1 // indirect
|
||||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
|
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
@@ -108,8 +127,8 @@ require (
|
|||||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/objx v0.2.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/stretchr/testify v1.7.1 // indirect
|
github.com/stretchr/testify v1.8.1 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||||
@@ -134,27 +153,27 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.8.0 // indirect
|
||||||
go.uber.org/zap v1.17.0 // indirect
|
go.uber.org/zap v1.17.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
golang.org/x/crypto v0.1.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221114191408-850992195362
|
golang.org/x/exp v0.0.0-20221114191408-850992195362
|
||||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
golang.org/x/net v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.1.0 // indirect
|
golang.org/x/sys v0.1.0 // indirect
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
golang.org/x/term v0.1.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.4.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
|
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
|
||||||
google.golang.org/protobuf v1.26.0 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/errgo.v1 v1.0.1 // indirect
|
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||||
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
|
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
|
||||||
gopkg.in/macaroon.v2 v2.0.0 // indirect
|
gopkg.in/macaroon.v2 v2.0.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a
|
replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a
|
||||||
|
|
||||||
replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20220822151439-7bb360481467
|
replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221124075140-383be4672b47
|
||||||
|
|||||||
141
itest/intercept_zero_conf_test.go
Normal file
141
itest/intercept_zero_conf_test.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/breez/lntest"
|
||||||
|
lspd "github.com/breez/lspd/rpc"
|
||||||
|
"gotest.tools/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOpenZeroConfChannelOnReceive(t *testing.T) {
|
||||||
|
harness := lntest.NewTestHarness(t)
|
||||||
|
defer harness.TearDown()
|
||||||
|
|
||||||
|
timeout := time.Now().Add(time.Minute)
|
||||||
|
|
||||||
|
miner := lntest.NewMiner(harness)
|
||||||
|
alice := lntest.NewCoreLightningNode(harness, miner, "Alice", timeout)
|
||||||
|
bob := NewZeroConfNode(harness, miner, "Bob", timeout)
|
||||||
|
lsp := NewLspdNode(harness, miner, "Lsp", timeout)
|
||||||
|
|
||||||
|
alice.Fund(10000000, timeout)
|
||||||
|
lsp.lightningNode.Fund(10000000, timeout)
|
||||||
|
|
||||||
|
log.Print("Opening channel between Alice and the lsp")
|
||||||
|
alice.OpenChannelAndWait(lsp.lightningNode, &lntest.OpenChannelOptions{
|
||||||
|
AmountSat: 1000000,
|
||||||
|
}, timeout)
|
||||||
|
|
||||||
|
log.Printf("Adding bob's invoices")
|
||||||
|
outerAmountMsat := uint64(2100000)
|
||||||
|
innerAmountMsat := uint64(2100000)
|
||||||
|
description := "Please pay me"
|
||||||
|
innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{
|
||||||
|
innerAmountMsat: innerAmountMsat,
|
||||||
|
outerAmountMsat: outerAmountMsat,
|
||||||
|
description: description,
|
||||||
|
lsp: lsp,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Print("Connecting bob to lspd")
|
||||||
|
bob.lightningNode.ConnectPeer(lsp.lightningNode)
|
||||||
|
|
||||||
|
// NOTE: We pretend to be paying fees to the lsp, but actually we won't.
|
||||||
|
log.Printf("Registering payment with lsp")
|
||||||
|
pretendAmount := outerAmountMsat - 2000000
|
||||||
|
lsp.RegisterPayment(&lspd.PaymentInformation{
|
||||||
|
PaymentHash: innerInvoice.paymentHash,
|
||||||
|
PaymentSecret: innerInvoice.paymentSecret,
|
||||||
|
Destination: bob.lightningNode.NodeId(),
|
||||||
|
IncomingAmountMsat: int64(outerAmountMsat),
|
||||||
|
OutgoingAmountMsat: int64(pretendAmount),
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("Alice paying")
|
||||||
|
payResp := alice.Pay(outerInvoice.bolt11, timeout)
|
||||||
|
bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash)
|
||||||
|
|
||||||
|
assert.DeepEqual(t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage)
|
||||||
|
assert.Equal(t, outerAmountMsat, bobInvoice.AmountReceivedMsat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenZeroConfSingleHtlc(t *testing.T) {
|
||||||
|
harness := lntest.NewTestHarness(t)
|
||||||
|
defer harness.TearDown()
|
||||||
|
|
||||||
|
timeout := time.Now().Add(time.Minute)
|
||||||
|
|
||||||
|
miner := lntest.NewMiner(harness)
|
||||||
|
alice := lntest.NewCoreLightningNode(harness, miner, "Alice", timeout)
|
||||||
|
bob := NewZeroConfNode(harness, miner, "Bob", timeout)
|
||||||
|
lsp := NewLspdNode(harness, miner, "Lsp", timeout)
|
||||||
|
|
||||||
|
alice.Fund(10000000, timeout)
|
||||||
|
lsp.lightningNode.Fund(10000000, timeout)
|
||||||
|
|
||||||
|
log.Print("Opening channel between Alice and the lsp")
|
||||||
|
channel := alice.OpenChannelAndWait(lsp.lightningNode, &lntest.OpenChannelOptions{
|
||||||
|
AmountSat: 1000000,
|
||||||
|
}, timeout)
|
||||||
|
|
||||||
|
log.Printf("Adding bob's invoices")
|
||||||
|
outerAmountMsat := uint64(2100000)
|
||||||
|
innerAmountMsat := uint64(2100000)
|
||||||
|
description := "Please pay me"
|
||||||
|
innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{
|
||||||
|
innerAmountMsat: innerAmountMsat,
|
||||||
|
outerAmountMsat: outerAmountMsat,
|
||||||
|
description: description,
|
||||||
|
lsp: lsp,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Print("Connecting bob to lspd")
|
||||||
|
bob.lightningNode.ConnectPeer(lsp.lightningNode)
|
||||||
|
|
||||||
|
// NOTE: We pretend to be paying fees to the lsp, but actually we won't.
|
||||||
|
log.Printf("Registering payment with lsp")
|
||||||
|
pretendAmount := outerAmountMsat - 2000000
|
||||||
|
lsp.RegisterPayment(&lspd.PaymentInformation{
|
||||||
|
PaymentHash: innerInvoice.paymentHash,
|
||||||
|
PaymentSecret: innerInvoice.paymentSecret,
|
||||||
|
Destination: bob.lightningNode.NodeId(),
|
||||||
|
IncomingAmountMsat: int64(outerAmountMsat),
|
||||||
|
OutgoingAmountMsat: int64(pretendAmount),
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("Alice paying")
|
||||||
|
route := constructRoute(lsp.lightningNode, bob.lightningNode, channel.ChannelId, "1x0x0", outerAmountMsat)
|
||||||
|
alice.StartPayPartViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, 0, route)
|
||||||
|
payResp := alice.WaitForPaymentPart(outerInvoice.paymentHash, timeout, 0)
|
||||||
|
bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash)
|
||||||
|
|
||||||
|
assert.DeepEqual(t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage)
|
||||||
|
assert.Equal(t, outerAmountMsat, bobInvoice.AmountReceivedMsat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructRoute(
|
||||||
|
lsp *lntest.CoreLightningNode,
|
||||||
|
bob *lntest.CoreLightningNode,
|
||||||
|
aliceLspChannel string,
|
||||||
|
lspBobChannel string,
|
||||||
|
amountMsat uint64) *lntest.Route {
|
||||||
|
return &lntest.Route{
|
||||||
|
Route: []*lntest.Hop{
|
||||||
|
{
|
||||||
|
Id: lsp.NodeId(),
|
||||||
|
Channel: aliceLspChannel,
|
||||||
|
AmountMsat: amountMsat + uint64(lspBaseFeeMsat) + (amountMsat * uint64(lspFeeRatePpm) / 1000000),
|
||||||
|
Delay: 144 + lspCltvDelta,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: bob.NodeId(),
|
||||||
|
Channel: lspBobChannel,
|
||||||
|
AmountMsat: amountMsat,
|
||||||
|
Delay: 144,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
200
itest/lspd_node.go
Normal file
200
itest/lspd_node.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
context "context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/breez/lntest"
|
||||||
|
"github.com/breez/lspd/btceclegacy"
|
||||||
|
lspd "github.com/breez/lspd/rpc"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lspdExecutable = flag.String(
|
||||||
|
"lspdexec", "", "full path to lpsd plugin binary",
|
||||||
|
)
|
||||||
|
lspdMigrationsDir = flag.String(
|
||||||
|
"lspdmigrationsdir", "", "full path to lspd sql migrations directory",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lspBaseFeeMsat uint32 = 1000
|
||||||
|
lspFeeRatePpm uint32 = 1
|
||||||
|
lspCltvDelta uint16 = 40
|
||||||
|
)
|
||||||
|
|
||||||
|
type LspNode struct {
|
||||||
|
harness *lntest.TestHarness
|
||||||
|
lightningNode *lntest.CoreLightningNode
|
||||||
|
rpc lspd.ChannelOpenerClient
|
||||||
|
rpcPort uint32
|
||||||
|
rpcHost string
|
||||||
|
privateKey btcec.PrivateKey
|
||||||
|
publicKey btcec.PublicKey
|
||||||
|
postgresBackend *PostgresContainer
|
||||||
|
scriptDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) *LspNode {
|
||||||
|
scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name))
|
||||||
|
migrationsDir, err := GetMigrationsDir()
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
pgLogfile := filepath.Join(scriptDir, "postgres.log")
|
||||||
|
postgresBackend := StartPostgresContainer(h.T, h.Ctx, pgLogfile)
|
||||||
|
postgresBackend.RunMigrations(h.T, h.Ctx, migrationsDir)
|
||||||
|
|
||||||
|
lspdBinary, err := GetLspdBinary()
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
lspdPort, err := lntest.GetPort()
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
lspdPrivateKeyBytes, err := GenerateRandomBytes(32)
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
priv, publ := btcec.PrivKeyFromBytes(lspdPrivateKeyBytes)
|
||||||
|
|
||||||
|
host := "localhost"
|
||||||
|
grpcAddress := fmt.Sprintf("%s:%d", host, lspdPort)
|
||||||
|
env := []string{
|
||||||
|
"NODE_NAME=lsp",
|
||||||
|
"NODE_PUBKEY=dunno",
|
||||||
|
"NODE_HOST=host:port",
|
||||||
|
"RUN_CLN=true",
|
||||||
|
"TOKEN=hello",
|
||||||
|
fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()),
|
||||||
|
fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress),
|
||||||
|
fmt.Sprintf("LSPD_PRIVATE_KEY=%x", lspdPrivateKeyBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptFilePath := filepath.Join(scriptDir, "start-lspd.sh")
|
||||||
|
log.Printf("Creating lspd startup script at %s", scriptFilePath)
|
||||||
|
scriptFile, err := os.OpenFile(scriptFilePath, os.O_CREATE|os.O_WRONLY, 0755)
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
writer := bufio.NewWriter(scriptFile)
|
||||||
|
_, err = writer.WriteString("#!/bin/bash\n")
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
for _, str := range env {
|
||||||
|
_, err = writer.WriteString("export " + str + "\n")
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = writer.WriteString(lspdBinary + "\n")
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
err = writer.Flush()
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
scriptFile.Close()
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
fmt.Sprintf("--plugin=%s", scriptFilePath),
|
||||||
|
fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat),
|
||||||
|
fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm),
|
||||||
|
fmt.Sprintf("--cltv-delta=%d", lspCltvDelta),
|
||||||
|
}
|
||||||
|
|
||||||
|
lightningNode := lntest.NewCoreLightningNode(h, m, name, timeout, args...)
|
||||||
|
|
||||||
|
conn, err := grpc.Dial(
|
||||||
|
grpcAddress,
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithPerRPCCredentials(&token{token: "hello"}),
|
||||||
|
)
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
client := lspd.NewChannelOpenerClient(conn)
|
||||||
|
|
||||||
|
lspNode := &LspNode{
|
||||||
|
harness: h,
|
||||||
|
lightningNode: lightningNode,
|
||||||
|
rpc: client,
|
||||||
|
rpcPort: lspdPort,
|
||||||
|
rpcHost: host,
|
||||||
|
privateKey: *priv,
|
||||||
|
publicKey: *publ,
|
||||||
|
postgresBackend: postgresBackend,
|
||||||
|
scriptDir: scriptDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
h.AddStoppable(lspNode)
|
||||||
|
h.AddCleanable(lspNode)
|
||||||
|
h.RegisterLogfile(pgLogfile, fmt.Sprintf("%s-postgres", name))
|
||||||
|
return lspNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LspNode) RegisterPayment(paymentInfo *lspd.PaymentInformation) {
|
||||||
|
serialized, err := proto.Marshal(paymentInfo)
|
||||||
|
lntest.CheckError(l.harness.T, err)
|
||||||
|
|
||||||
|
encrypted, err := btceclegacy.Encrypt(&l.publicKey, serialized)
|
||||||
|
lntest.CheckError(l.harness.T, err)
|
||||||
|
|
||||||
|
log.Printf("Registering payment")
|
||||||
|
_, err = l.rpc.RegisterPayment(
|
||||||
|
l.harness.Ctx,
|
||||||
|
&lspd.RegisterPaymentRequest{
|
||||||
|
Blob: encrypted,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
lntest.CheckError(l.harness.T, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LspNode) TearDown() error {
|
||||||
|
// NOTE: The lightningnode will be torn down on its own.
|
||||||
|
return l.postgresBackend.Shutdown(l.harness.Ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LspNode) Cleanup() error {
|
||||||
|
return l.postgresBackend.Cleanup(l.harness.Ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LspNode) NodeId() []byte {
|
||||||
|
return l.lightningNode.NodeId()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLspdBinary() (string, error) {
|
||||||
|
if lspdExecutable != nil {
|
||||||
|
return *lspdExecutable, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec.LookPath("lspd")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMigrationsDir() (string, error) {
|
||||||
|
if lspdMigrationsDir != nil {
|
||||||
|
return *lspdMigrationsDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec.LookPath("lspdmigrationsdir")
|
||||||
|
}
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
m["authorization"] = "Bearer " + t.token
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireTransportSecurity indicates whether the credentials requires
|
||||||
|
// transport security.
|
||||||
|
func (t *token) RequireTransportSecurity() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
192
itest/postgres.go
Normal file
192
itest/postgres.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/breez/lntest"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostgresContainer struct {
|
||||||
|
id string
|
||||||
|
password string
|
||||||
|
port uint32
|
||||||
|
cli *client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartPostgresContainer(t *testing.T, ctx context.Context, logfile string) *PostgresContainer {
|
||||||
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
|
||||||
|
image := "postgres:15"
|
||||||
|
_, _, err = cli.ImageInspectWithRaw(ctx, image)
|
||||||
|
if err != nil {
|
||||||
|
if !client.IsErrNotFound(err) {
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pullReader, err := cli.ImagePull(ctx, image, types.ImagePullOptions{})
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
_, err = io.Copy(io.Discard, pullReader)
|
||||||
|
pullReader.Close()
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := lntest.GetPort()
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
createResp, err := cli.ContainerCreate(ctx, &container.Config{
|
||||||
|
Image: image,
|
||||||
|
Cmd: []string{
|
||||||
|
"postgres",
|
||||||
|
"-c",
|
||||||
|
"log_statement=all",
|
||||||
|
},
|
||||||
|
Env: []string{
|
||||||
|
"POSTGRES_DB=postgres",
|
||||||
|
"POSTGRES_PASSWORD=pgpassword",
|
||||||
|
"POSTGRES_USER=postgres",
|
||||||
|
},
|
||||||
|
Healthcheck: &container.HealthConfig{
|
||||||
|
Test: []string{"CMD-SHELL", "pg_isready -U postgres"},
|
||||||
|
Interval: time.Second,
|
||||||
|
Timeout: time.Second,
|
||||||
|
Retries: 10,
|
||||||
|
},
|
||||||
|
}, &container.HostConfig{
|
||||||
|
PortBindings: nat.PortMap{
|
||||||
|
"5432/tcp": []nat.PortBinding{
|
||||||
|
{HostPort: strconv.FormatUint(uint64(port), 10)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
|
||||||
|
err = cli.ContainerStart(ctx, createResp.ID, types.ContainerStartOptions{})
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
|
||||||
|
HealthCheck:
|
||||||
|
for {
|
||||||
|
inspect, err := cli.ContainerInspect(ctx, createResp.ID)
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
|
||||||
|
status := inspect.State.Health.Status
|
||||||
|
switch status {
|
||||||
|
case "unhealthy":
|
||||||
|
lntest.CheckError(t, errors.New("container unhealthy"))
|
||||||
|
case "healthy":
|
||||||
|
break HealthCheck
|
||||||
|
default:
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ct := &PostgresContainer{
|
||||||
|
id: createResp.ID,
|
||||||
|
password: "pgpassword",
|
||||||
|
port: port,
|
||||||
|
cli: cli,
|
||||||
|
}
|
||||||
|
|
||||||
|
go ct.monitorLogs(logfile)
|
||||||
|
return ct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PostgresContainer) monitorLogs(logfile string) {
|
||||||
|
i, err := c.cli.ContainerLogs(context.Background(), c.id, types.ContainerLogsOptions{
|
||||||
|
ShowStderr: true,
|
||||||
|
ShowStdout: true,
|
||||||
|
Timestamps: false,
|
||||||
|
Follow: true,
|
||||||
|
Tail: "40",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not get container logs: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer i.Close()
|
||||||
|
|
||||||
|
file, err := os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not create container log file: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
hdr := make([]byte, 8)
|
||||||
|
for {
|
||||||
|
_, err := i.Read(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count := binary.BigEndian.Uint32(hdr[4:])
|
||||||
|
dat := make([]byte, count)
|
||||||
|
_, err = i.Read(dat)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = file.Write(dat)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PostgresContainer) ConnectionString() string {
|
||||||
|
return fmt.Sprintf("postgres://postgres:%s@127.0.0.1:%d/postgres", c.password, c.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PostgresContainer) Shutdown(ctx context.Context) error {
|
||||||
|
defer c.cli.Close()
|
||||||
|
timeout := time.Second
|
||||||
|
err := c.cli.ContainerStop(ctx, c.id, &timeout)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PostgresContainer) Cleanup(ctx context.Context) error {
|
||||||
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
return cli.ContainerRemove(ctx, c.id, types.ContainerRemoveOptions{
|
||||||
|
Force: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PostgresContainer) RunMigrations(t *testing.T, ctx context.Context, migrationDir string) {
|
||||||
|
filenames, err := filepath.Glob(filepath.Join(migrationDir, "*.up.sql"))
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
|
||||||
|
sort.Strings(filenames)
|
||||||
|
|
||||||
|
pgxPool, err := pgxpool.Connect(context.Background(), c.ConnectionString())
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
defer pgxPool.Close()
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
|
||||||
|
_, err = pgxPool.Exec(ctx, string(data))
|
||||||
|
lntest.CheckError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
itest/test_common.go
Normal file
14
itest/test_common.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import "crypto/rand"
|
||||||
|
|
||||||
|
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
// Note that err == nil only if we read len(b) bytes.
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
175
itest/zero_conf_node.go
Normal file
175
itest/zero_conf_node.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/breez/lntest"
|
||||||
|
btcec "github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ZeroConfNode struct {
|
||||||
|
name string
|
||||||
|
harness *lntest.TestHarness
|
||||||
|
lightningNode *lntest.CoreLightningNode
|
||||||
|
privKey *secp256k1.PrivateKey
|
||||||
|
scriptDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginContent string = `#!/usr/bin/env python3
|
||||||
|
"""Use the openchannel hook to selectively opt-into zeroconf
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyln.client import Plugin
|
||||||
|
|
||||||
|
plugin = Plugin()
|
||||||
|
|
||||||
|
|
||||||
|
@plugin.hook('openchannel')
|
||||||
|
def on_openchannel(openchannel, plugin, **kwargs):
|
||||||
|
plugin.log(repr(openchannel))
|
||||||
|
mindepth = int(0)
|
||||||
|
|
||||||
|
plugin.log(f"This peer is in the zeroconf allowlist, setting mindepth={mindepth}")
|
||||||
|
return {'result': 'continue', 'mindepth': mindepth}
|
||||||
|
|
||||||
|
plugin.run()
|
||||||
|
`
|
||||||
|
|
||||||
|
var pluginStartupContent string = `python3 -m venv %s > /dev/null 2>&1
|
||||||
|
source %s > /dev/null 2>&1
|
||||||
|
pip install pyln-client > /dev/null 2>&1
|
||||||
|
python %s
|
||||||
|
`
|
||||||
|
|
||||||
|
func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) *ZeroConfNode {
|
||||||
|
privKey, err := btcec.NewPrivateKey()
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
s := privKey.Serialize()
|
||||||
|
|
||||||
|
scriptDir, err := os.MkdirTemp(h.Dir, name)
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
pythonFilePath := filepath.Join(scriptDir, "zero_conf_plugin.py")
|
||||||
|
pythonFile, err := os.OpenFile(pythonFilePath, os.O_CREATE|os.O_WRONLY, 0755)
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
pythonWriter := bufio.NewWriter(pythonFile)
|
||||||
|
_, err = pythonWriter.WriteString(pluginContent)
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
err = pythonWriter.Flush()
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
pythonFile.Close()
|
||||||
|
|
||||||
|
pluginFilePath := filepath.Join(scriptDir, "start_zero_conf_plugin.sh")
|
||||||
|
pluginFile, err := os.OpenFile(pluginFilePath, os.O_CREATE|os.O_WRONLY, 0755)
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
pluginWriter := bufio.NewWriter(pluginFile)
|
||||||
|
venvDir := filepath.Join(scriptDir, "venv")
|
||||||
|
activatePath := filepath.Join(venvDir, "bin", "activate")
|
||||||
|
_, err = pluginWriter.WriteString(fmt.Sprintf(pluginStartupContent, venvDir, activatePath, pythonFilePath))
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
|
||||||
|
err = pluginWriter.Flush()
|
||||||
|
lntest.CheckError(h.T, err)
|
||||||
|
pluginFile.Close()
|
||||||
|
|
||||||
|
node := lntest.NewCoreLightningNode(
|
||||||
|
h,
|
||||||
|
m,
|
||||||
|
name,
|
||||||
|
timeout,
|
||||||
|
fmt.Sprintf("--dev-force-privkey=%x", s),
|
||||||
|
fmt.Sprintf("--plugin=%s", pluginFilePath),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &ZeroConfNode{
|
||||||
|
name: name,
|
||||||
|
harness: h,
|
||||||
|
lightningNode: node,
|
||||||
|
scriptDir: scriptDir,
|
||||||
|
privKey: privKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type generateInvoicesRequest struct {
|
||||||
|
innerAmountMsat uint64
|
||||||
|
outerAmountMsat uint64
|
||||||
|
description string
|
||||||
|
lsp *LspNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type invoice struct {
|
||||||
|
bolt11 string
|
||||||
|
paymentHash []byte
|
||||||
|
paymentSecret []byte
|
||||||
|
paymentPreimage []byte
|
||||||
|
expiresAt uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ZeroConfNode) GenerateInvoices(req generateInvoicesRequest) (invoice, invoice) {
|
||||||
|
preimage, err := GenerateRandomBytes(32)
|
||||||
|
lntest.CheckError(n.harness.T, err)
|
||||||
|
|
||||||
|
lspNodeId, err := btcec.ParsePubKey(req.lsp.lightningNode.NodeId())
|
||||||
|
lntest.CheckError(n.harness.T, err)
|
||||||
|
|
||||||
|
log.Printf("Adding bob's invoices")
|
||||||
|
innerInvoice := n.lightningNode.CreateBolt11Invoice(&lntest.CreateInvoiceOptions{
|
||||||
|
AmountMsat: req.innerAmountMsat,
|
||||||
|
Description: &req.description,
|
||||||
|
Preimage: &preimage,
|
||||||
|
})
|
||||||
|
outerInvoiceRaw, err := zpay32.Decode(innerInvoice.Bolt11, &chaincfg.RegressionNetParams)
|
||||||
|
lntest.CheckError(n.harness.T, err)
|
||||||
|
|
||||||
|
milliSat := lnwire.MilliSatoshi(req.outerAmountMsat)
|
||||||
|
outerInvoiceRaw.MilliSat = &milliSat
|
||||||
|
fakeChanId := &lnwire.ShortChannelID{BlockHeight: 1, TxIndex: 0, TxPosition: 0}
|
||||||
|
outerInvoiceRaw.RouteHints = append(outerInvoiceRaw.RouteHints, []zpay32.HopHint{
|
||||||
|
{
|
||||||
|
NodeID: lspNodeId,
|
||||||
|
ChannelID: fakeChanId.ToUint64(),
|
||||||
|
FeeBaseMSat: lspBaseFeeMsat,
|
||||||
|
FeeProportionalMillionths: lspFeeRatePpm,
|
||||||
|
CLTVExpiryDelta: lspCltvDelta,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
outerInvoice, err := outerInvoiceRaw.Encode(zpay32.MessageSigner{
|
||||||
|
SignCompact: func(msg []byte) ([]byte, error) {
|
||||||
|
hash := sha256.Sum256(msg)
|
||||||
|
return ecdsa.SignCompact(n.privKey, hash[:], true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
lntest.CheckError(n.harness.T, err)
|
||||||
|
|
||||||
|
inner := invoice{
|
||||||
|
bolt11: innerInvoice.Bolt11,
|
||||||
|
paymentHash: innerInvoice.PaymentHash,
|
||||||
|
paymentSecret: innerInvoice.PaymentSecret,
|
||||||
|
paymentPreimage: preimage,
|
||||||
|
expiresAt: innerInvoice.ExpiresAt,
|
||||||
|
}
|
||||||
|
outer := invoice{
|
||||||
|
bolt11: outerInvoice,
|
||||||
|
paymentHash: outerInvoiceRaw.PaymentHash[:],
|
||||||
|
paymentSecret: innerInvoice.PaymentSecret,
|
||||||
|
paymentPreimage: preimage,
|
||||||
|
expiresAt: innerInvoice.ExpiresAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return inner, outer
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user