add itests

This commit is contained in:
Jesse de Wit
2022-11-21 14:25:06 +01:00
parent 4cc9fcbc1c
commit d186e06323
6 changed files with 753 additions and 12 deletions

43
go.mod
View File

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

View 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
View 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
View 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
View 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
View 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
}