mirror of
https://github.com/aljazceru/lspd.git
synced 2025-12-18 22:34:22 +01:00
360 lines
9.2 KiB
Go
360 lines
9.2 KiB
Go
package itest
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/breez/lntest"
|
|
lspd "github.com/breez/lspd/rpc"
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
ecies "github.com/ecies/go/v2"
|
|
"github.com/golang/protobuf/proto"
|
|
"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 interface {
|
|
Harness() *lntest.TestHarness
|
|
PublicKey() *btcec.PublicKey
|
|
EciesPublicKey() *ecies.PublicKey
|
|
Rpc() lspd.ChannelOpenerClient
|
|
NodeId() []byte
|
|
LightningNode() lntest.LightningNode
|
|
SupportsChargingFees() bool
|
|
}
|
|
|
|
type ClnLspNode struct {
|
|
harness *lntest.TestHarness
|
|
lightningNode *lntest.ClnNode
|
|
rpc lspd.ChannelOpenerClient
|
|
publicKey btcec.PublicKey
|
|
eciesPublicKey ecies.PublicKey
|
|
postgresBackend *PostgresContainer
|
|
}
|
|
|
|
func (c *ClnLspNode) Harness() *lntest.TestHarness {
|
|
return c.harness
|
|
}
|
|
|
|
func (c *ClnLspNode) PublicKey() *btcec.PublicKey {
|
|
return &c.publicKey
|
|
}
|
|
|
|
func (c *ClnLspNode) EciesPublicKey() *ecies.PublicKey {
|
|
return &c.eciesPublicKey
|
|
}
|
|
|
|
func (c *ClnLspNode) Rpc() lspd.ChannelOpenerClient {
|
|
return c.rpc
|
|
}
|
|
|
|
func (l *ClnLspNode) TearDown() error {
|
|
// NOTE: The lightningnode will be torn down on its own.
|
|
return l.postgresBackend.Shutdown(context.Background())
|
|
}
|
|
|
|
func (l *ClnLspNode) Cleanup() error {
|
|
return l.postgresBackend.Cleanup(context.Background())
|
|
}
|
|
|
|
func (l *ClnLspNode) NodeId() []byte {
|
|
return l.lightningNode.NodeId()
|
|
}
|
|
|
|
func (l *ClnLspNode) LightningNode() lntest.LightningNode {
|
|
return l.lightningNode
|
|
}
|
|
|
|
func (l *ClnLspNode) SupportsChargingFees() bool {
|
|
return false
|
|
}
|
|
|
|
type LndLspNode struct {
|
|
harness *lntest.TestHarness
|
|
lightningNode *lntest.LndNode
|
|
rpc lspd.ChannelOpenerClient
|
|
publicKey btcec.PublicKey
|
|
eciesPublicKey ecies.PublicKey
|
|
postgresBackend *PostgresContainer
|
|
logFile *os.File
|
|
lspdCmd *exec.Cmd
|
|
}
|
|
|
|
func (c *LndLspNode) Harness() *lntest.TestHarness {
|
|
return c.harness
|
|
}
|
|
|
|
func (c *LndLspNode) PublicKey() *btcec.PublicKey {
|
|
return &c.publicKey
|
|
}
|
|
|
|
func (c *LndLspNode) EciesPublicKey() *ecies.PublicKey {
|
|
return &c.eciesPublicKey
|
|
}
|
|
|
|
func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient {
|
|
return c.rpc
|
|
}
|
|
func (l *LndLspNode) SupportsChargingFees() bool {
|
|
return true
|
|
}
|
|
|
|
func (l *LndLspNode) TearDown() error {
|
|
// NOTE: The lightningnode will be torn down on its own.
|
|
if l.lspdCmd != nil && l.lspdCmd.Process != nil {
|
|
err := l.lspdCmd.Process.Kill()
|
|
if err != nil {
|
|
log.Printf("error stopping lspd process: %v", err)
|
|
}
|
|
}
|
|
|
|
if l.logFile != nil {
|
|
err := l.logFile.Close()
|
|
if err != nil {
|
|
log.Printf("error closing logfile: %v", err)
|
|
}
|
|
}
|
|
|
|
return l.postgresBackend.Shutdown(context.Background())
|
|
}
|
|
|
|
func (l *LndLspNode) Cleanup() error {
|
|
return l.postgresBackend.Cleanup(context.Background())
|
|
}
|
|
|
|
func (l *LndLspNode) NodeId() []byte {
|
|
return l.lightningNode.NodeId()
|
|
}
|
|
|
|
func (l *LndLspNode) LightningNode() lntest.LightningNode {
|
|
return l.lightningNode
|
|
}
|
|
|
|
func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode {
|
|
scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend := setupLspd(h, name, "RUN_CLN=true")
|
|
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),
|
|
"--max-concurrent-htlcs=30",
|
|
"--dev-allowdustreserve=true",
|
|
}
|
|
|
|
lightningNode := lntest.NewClnNode(h, m, name, 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 := &ClnLspNode{
|
|
harness: h,
|
|
lightningNode: lightningNode,
|
|
rpc: client,
|
|
publicKey: *publ,
|
|
eciesPublicKey: *eciesPubl,
|
|
postgresBackend: postgresBackend,
|
|
}
|
|
|
|
h.AddStoppable(lspNode)
|
|
h.AddCleanable(lspNode)
|
|
return lspNode
|
|
}
|
|
|
|
func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode {
|
|
args := []string{
|
|
"--protocol.zero-conf",
|
|
"--protocol.option-scid-alias",
|
|
"--requireinterceptor",
|
|
"--bitcoin.defaultchanconfs=0",
|
|
fmt.Sprintf("--bitcoin.chanreservescript=\"0 if (chanAmt != %d) else chanAmt/100\"", publicChanAmount),
|
|
fmt.Sprintf("--bitcoin.basefee=%d", lspBaseFeeMsat),
|
|
fmt.Sprintf("--bitcoin.feerate=%d", lspFeeRatePpm),
|
|
fmt.Sprintf("--bitcoin.timelockdelta=%d", lspCltvDelta),
|
|
}
|
|
|
|
lightningNode := lntest.NewLndNode(h, m, name, args...)
|
|
tlsCert := strings.Replace(string(lightningNode.TlsCert()), "\n", "\\n", -1)
|
|
scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend := setupLspd(h, name,
|
|
"RUN_LND=true",
|
|
fmt.Sprintf("LND_CERT=\"%s\"", tlsCert),
|
|
fmt.Sprintf("LND_ADDRESS=%s", lightningNode.GrpcHost()),
|
|
fmt.Sprintf("LND_MACAROON_HEX=%x", lightningNode.Macaroon()),
|
|
)
|
|
scriptDir := filepath.Dir(scriptFilePath)
|
|
logFilePath := filepath.Join(scriptDir, "lspd.log")
|
|
h.RegisterLogfile(logFilePath, fmt.Sprintf("lspd-%s", name))
|
|
|
|
lspdCmd := exec.CommandContext(h.Ctx, scriptFilePath)
|
|
logFile, err := os.Create(logFilePath)
|
|
lntest.CheckError(h.T, err)
|
|
|
|
lspdCmd.Stdout = logFile
|
|
lspdCmd.Stderr = logFile
|
|
|
|
log.Printf("%s: starting lspd %s", name, scriptFilePath)
|
|
err = lspdCmd.Start()
|
|
lntest.CheckError(h.T, err)
|
|
|
|
conn, err := grpc.Dial(
|
|
grpcAddress,
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
grpc.WithPerRPCCredentials(&token{token: "hello"}),
|
|
)
|
|
lntest.CheckError(h.T, err)
|
|
|
|
client := lspd.NewChannelOpenerClient(conn)
|
|
|
|
lspNode := &LndLspNode{
|
|
harness: h,
|
|
lightningNode: lightningNode,
|
|
rpc: client,
|
|
publicKey: *publ,
|
|
eciesPublicKey: *eciesPubl,
|
|
postgresBackend: postgresBackend,
|
|
logFile: logFile,
|
|
lspdCmd: lspdCmd,
|
|
}
|
|
|
|
h.AddStoppable(lspNode)
|
|
h.AddCleanable(lspNode)
|
|
return lspNode
|
|
}
|
|
|
|
func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, string, *secp256k1.PublicKey, *ecies.PublicKey, *PostgresContainer) {
|
|
scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name))
|
|
log.Printf("%s: Creating LSPD in dir %s", name, scriptDir)
|
|
migrationsDir, err := getMigrationsDir()
|
|
lntest.CheckError(h.T, err)
|
|
|
|
pgLogfile := filepath.Join(scriptDir, "postgres.log")
|
|
h.RegisterLogfile(pgLogfile, fmt.Sprintf("%s-postgres", name))
|
|
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)
|
|
|
|
_, publ := btcec.PrivKeyFromBytes(lspdPrivateKeyBytes)
|
|
eciesPubl := ecies.NewPrivateKeyFromBytes(lspdPrivateKeyBytes).PublicKey
|
|
host := "localhost"
|
|
grpcAddress := fmt.Sprintf("%s:%d", host, lspdPort)
|
|
env := []string{
|
|
"NODE_NAME=lsp",
|
|
"NODE_PUBKEY=dunno",
|
|
"NODE_HOST=host:port",
|
|
"TOKEN=hello",
|
|
fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()),
|
|
fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress),
|
|
fmt.Sprintf("LSPD_PRIVATE_KEY=%x", lspdPrivateKeyBytes),
|
|
}
|
|
|
|
env = append(env, envExt...)
|
|
|
|
scriptFilePath := filepath.Join(scriptDir, "start-lspd.sh")
|
|
log.Printf("%s: Creating lspd startup script at %s", name, 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()
|
|
|
|
return scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend
|
|
}
|
|
|
|
func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation) {
|
|
serialized, err := proto.Marshal(paymentInfo)
|
|
lntest.CheckError(l.Harness().T, err)
|
|
|
|
encrypted, err := ecies.Encrypt(l.EciesPublicKey(), 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 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
|
|
}
|