From f6b3031ee88a751b3194f33c69fbdddc8af76a4c Mon Sep 17 00:00:00 2001 From: bruwbird Date: Wed, 27 Mar 2024 08:56:50 +0900 Subject: [PATCH] test: add integration test for LWK The integration test for LWK is added to ensure that the swap functionality works as expected with the LWK and electrs support. The setup functions are implemented to create the necessary test environment, including lwk, Bitcoin and Liquid nodes, as well as two lightning nodes with the PeerSwap. This setup is crucial for testing the swap-in process in an environment that closely mimics production. also, added Electrs and LWK structs for testing framework. - Implement `Electrs` struct to manage electrs daemon processes - Implement `LWK` struct to manage LWK daemon processes - Provide constructors for both structs to initialize with dynamic ports and configurations - Include methods to run the processes and connect --- go.mod | 2 +- test/lwk_cln_test.go | 88 ++++++++++++++++++++ test/setup.go | 172 ++++++++++++++++++++++++++++++++++++++- testframework/electrs.go | 48 +++++++++++ testframework/lwk.go | 32 ++++++++ 5 files changed, 337 insertions(+), 5 deletions(-) create mode 100644 test/lwk_cln_test.go create mode 100644 testframework/electrs.go create mode 100644 testframework/lwk.go diff --git a/go.mod b/go.mod index 4eca5b02..76bde1d7 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,7 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.13.0 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -147,7 +148,6 @@ require ( ) require ( - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/lightningnetwork/lnd/clock v1.1.0 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect diff --git a/test/lwk_cln_test.go b/test/lwk_cln_test.go new file mode 100644 index 00000000..1aa06b66 --- /dev/null +++ b/test/lwk_cln_test.go @@ -0,0 +1,88 @@ +package test + +import ( + "math" + "os" + "testing" + + "github.com/elementsproject/peerswap/clightning" + "github.com/elementsproject/peerswap/swap" + "github.com/stretchr/testify/require" +) + +func Test_ClnCln_LWK_SwapIn(t *testing.T) { + t.Skip("Skipping test until we have lwk and electrs support on nix") + IsIntegrationTest(t) + t.Parallel() + + t.Run("claim_normal", func(t *testing.T) { + t.Parallel() + require := require.New(t) + + bitcoind, liquidd, lightningds, scid := clnclnLWKSetup(t, uint64(math.Pow10(9))) + defer func() { + if t.Failed() { + filter := os.Getenv("PEERSWAP_TEST_FILTER") + pprintFail( + tailableProcess{ + p: bitcoind.DaemonProcess, + lines: defaultLines, + }, + tailableProcess{ + p: liquidd.DaemonProcess, + lines: defaultLines, + }, + tailableProcess{ + p: lightningds[0].DaemonProcess, + filter: filter, + lines: defaultLines, + }, + tailableProcess{ + p: lightningds[1].DaemonProcess, + filter: filter, + lines: defaultLines, + }, + ) + } + }() + + var channelBalances []uint64 + var walletBalances []uint64 + for _, lightningd := range lightningds { + b, err := lightningd.GetBtcBalanceSat() + require.NoError(err) + walletBalances = append(walletBalances, b) + + b, err = lightningd.GetChannelBalanceSat(scid) + require.NoError(err) + channelBalances = append(channelBalances, b) + } + + params := &testParams{ + swapAmt: channelBalances[0] / 2, + scid: scid, + origTakerWallet: walletBalances[0], + origMakerWallet: walletBalances[1], + origTakerBalance: channelBalances[0], + origMakerBalance: channelBalances[1], + takerNode: lightningds[0], + makerNode: lightningds[1], + takerPeerswap: lightningds[0].DaemonProcess, + makerPeerswap: lightningds[1].DaemonProcess, + chainRpc: liquidd.RpcProxy, + chaind: liquidd, + confirms: LiquidConfirms, + csv: LiquidCsv, + swapType: swap.SWAPTYPE_IN, + } + asset := "lbtc" + + // Do swap. + go func() { + var response map[string]interface{} + lightningds[1].Rpc.Request(&clightning.SwapIn{SatAmt: params.swapAmt, ShortChannelId: params.scid, Asset: asset}, &response) + + }() + preimageClaimTest(t, params) + }) +} diff --git a/test/setup.go b/test/setup.go index e9a6b787..2a571d71 100644 --- a/test/setup.go +++ b/test/setup.go @@ -81,7 +81,7 @@ func clnclnSetupWithConfig(t *testing.T, fundAmt, pushAmt uint64, clnConf []stri os.ModePerm, ) - // Use lightningd with --developer turned on + // Use lightningd with --developer turned on lightningd.WithCmd("lightningd") // Add plugin to cmd line options @@ -243,7 +243,7 @@ func mixedSetup(t *testing.T, fundAmt uint64, funder fundingNode) (*testframewor os.ModePerm, ) - // Use lightningd with --developer turned on + // Use lightningd with --developer turned on cln.WithCmd("lightningd") // Add plugin to cmd line options @@ -405,7 +405,7 @@ func clnclnElementsSetup(t *testing.T, fundAmt uint64) (*testframework.BitcoinNo os.ModePerm, ) - // Use lightningd with --developer turned on + // Use lightningd with --developer turned on lightningd.WithCmd("lightningd") // Add plugin to cmd line options @@ -665,7 +665,7 @@ func mixedElementsSetup(t *testing.T, fundAmt uint64, funder fundingNode) (*test os.ModePerm, ) - // Use lightningd with --developer turned on + // Use lightningd with --developer turned on cln.WithCmd("lightningd") // Add plugin to cmd line options @@ -799,3 +799,167 @@ func (n *LndNodeWithLiquid) GetBtcBalanceSat() (uint64, error) { } return r.SatAmount, nil } + +func clnclnLWKSetup(t *testing.T, fundAmt uint64) (*testframework.BitcoinNode, *testframework.LiquidNode, []*CLightningNodeWithLiquid, string) { + /// Get PeerSwap plugin path and test dir + _, filename, _, _ := runtime.Caller(0) + pathToPlugin := filepath.Join(filename, "..", "..", "out", "test-builds", "peerswap") + testDir := t.TempDir() + + // Setup nodes (1 bitcoind, 1 liquidd, 2 lightningd) + bitcoind, err := testframework.NewBitcoinNode(testDir, 1) + if err != nil { + t.Fatalf("could not create bitcoind %v", err) + } + t.Cleanup(bitcoind.Kill) + + liquidd, err := testframework.NewLiquidNode(testDir, bitcoind, 1) + if err != nil { + t.Fatal("error creating liquidd node", err) + } + t.Cleanup(liquidd.Kill) + + electrsd, err := testframework.NewElectrs(testDir, 1, liquidd) + if err != nil { + t.Fatal("error creating electrsd node", err) + } + t.Cleanup(electrsd.Process.Kill) + + lwk, err := testframework.NewLWK(testDir, 1, electrsd) + if err != nil { + t.Fatal("error creating electrsd node", err) + } + t.Cleanup(lwk.Process.Kill) + + var lightningds []*testframework.CLightningNode + for i := 1; i <= 2; i++ { + lightningd, err := testframework.NewCLightningNode(testDir, bitcoind, i) + if err != nil { + t.Fatalf("could not create liquidd %v", err) + } + t.Cleanup(lightningd.Kill) + defer printFailedFiltered(t, lightningd.DaemonProcess) + + // Create policy file and accept all peers + err = os.MkdirAll(filepath.Join(lightningd.GetDataDir(), "peerswap"), os.ModePerm) + if err != nil { + t.Fatal("could not create dir", err) + } + err = os.WriteFile(filepath.Join(lightningd.GetDataDir(), "peerswap", "policy.conf"), []byte("accept_all_peers=1\n"), os.ModePerm) + if err != nil { + t.Fatal("could not create policy file", err) + } + + // Set wallet name + walletName := fmt.Sprintf("swap%d", i) + + // Create config file + fileConf := struct { + LWK struct { + SignerName string + WalletName string + LWKEndpoint string + ElectrumEndpoint string + Network string + LiquidSwaps bool + } + }{ + LWK: struct { + SignerName string + WalletName string + LWKEndpoint string + ElectrumEndpoint string + Network string + LiquidSwaps bool + }{ + WalletName: walletName, + SignerName: walletName + "-" + "signer", + LiquidSwaps: true, + }, + } + data, err := toml.Marshal(fileConf) + require.NoError(t, err) + + configPath := filepath.Join(lightningd.GetDataDir(), "peerswap", "peerswap.conf") + os.WriteFile( + configPath, + data, + os.ModePerm, + ) + + // Use lightningd with --developer turned on + lightningd.WithCmd("lightningd") + + // Add plugin to cmd line options + lightningd.AppendCmdLine([]string{ + "--dev-bitcoind-poll=1", + "--dev-fast-gossip", + "--large-channels", + fmt.Sprint("--plugin=", pathToPlugin), + }) + + lightningds = append(lightningds, lightningd) + } + + // Start nodes + err = bitcoind.Run(true) + if err != nil { + t.Fatalf("bitcoind.Run() got err %v", err) + } + + err = liquidd.Run(true) + if err != nil { + t.Fatalf("Run() got err %v", err) + } + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, testframework.TIMEOUT) + defer cancel() + require.NoError(t, electrsd.Run(ctx)) + lwk.Process.Run() + + for _, lightningd := range lightningds { + err = lightningd.Run(true, true) + if err != nil { + t.Fatalf("lightningd.Run() got err %v", err) + } + err = lightningd.WaitForLog("peerswap initialized", testframework.TIMEOUT) + if err != nil { + t.Fatalf("lightningd.WaitForLog() got err %v", err) + } + } + + // Give liquid funds to nodes to have something to swap. + for _, lightningd := range lightningds { + var result clightning.GetAddressResponse + lightningd.Rpc.Request(&clightning.LiquidGetAddress{}, &result) + _ = liquidd.GenerateBlocks(20) + _, err = liquidd.Rpc.Call("sendtoaddress", result.LiquidAddress, 10., "", "", false, false, 1, "UNSET") + require.NoError(t, err) + } + + // Lock txs. + _, err = liquidd.Rpc.Call("generatetoaddress", 1, testframework.LBTC_BURN) + require.NoError(t, err) + + // Setup channel ([0] fundAmt(10^7) ---- 0 [1]). + scid, err := lightningds[0].OpenChannel(lightningds[1], fundAmt, 0, true, true, true) + if err != nil { + t.Fatalf("lightingds[0].OpenChannel() %v", err) + } + + // Sync peer polling + var result interface{} + err = lightningds[0].Rpc.Request(&clightning.ReloadPolicyFile{}, &result) + if err != nil { + t.Fatalf("ListPeers %v", err) + } + err = lightningds[1].Rpc.Request(&clightning.ReloadPolicyFile{}, &result) + if err != nil { + t.Fatalf("ListPeers %v", err) + } + + syncPoll(&clnPollableNode{lightningds[0]}, &clnPollableNode{lightningds[1]}) + + return bitcoind, liquidd, []*CLightningNodeWithLiquid{{lightningds[0]}, {lightningds[1]}}, scid +} diff --git a/testframework/electrs.go b/testframework/electrs.go new file mode 100644 index 00000000..e4a5da3f --- /dev/null +++ b/testframework/electrs.go @@ -0,0 +1,48 @@ +package testframework + +import ( + "context" + "fmt" + "net/url" + + "github.com/checksum0/go-electrum/electrum" +) + +type Electrs struct { + Process *DaemonProcess + rpcURL *url.URL +} + +func NewElectrs(testDir string, id int, elements *LiquidNode) (*Electrs, error) { + rpcPort, err := GetFreePort() + if err != nil { + return nil, err + } + u, err := url.Parse(fmt.Sprintf("127.0.0.1:%d", rpcPort)) + if err != nil { + return nil, err + } + cmdLine := []string{ + "electrs", + "-v", + "--network=liquidregtest", + fmt.Sprintf("daemon-rpc-addr=%s:%d", elements.rpcHost, elements.rpcPort), + fmt.Sprintf("electrum-rpc-addr=:%s", u.String()), + fmt.Sprintf("cookie=%s", elements.RpcUser+":"+elements.RpcPassword), + fmt.Sprintf("daemon-dir=%s", elements.DataDir), + "- --jsonrpc-import", + } + return &Electrs{ + Process: NewDaemonProcess(cmdLine, fmt.Sprintf("electrs-%d", id)), + rpcURL: u, + }, nil +} + +func (e *Electrs) Run(ctx context.Context) error { + e.Process.Run() + ec, err := electrum.NewClientTCP(ctx, e.rpcURL.String()) + if err != nil { + return err + } + return ec.Ping(ctx) +} diff --git a/testframework/lwk.go b/testframework/lwk.go new file mode 100644 index 00000000..575c0951 --- /dev/null +++ b/testframework/lwk.go @@ -0,0 +1,32 @@ +package testframework + +import ( + "fmt" + "net/url" +) + +type LWK struct { + Process *DaemonProcess +} + +func NewLWK(testDir string, id int, electrs *Electrs) (*Electrs, error) { + rpcPort, err := GetFreePort() + if err != nil { + return nil, err + } + u, err := url.Parse(fmt.Sprintf("127.0.0.1:%d", rpcPort)) + if err != nil { + return nil, err + } + cmdLine := []string{ + "lwk_cli", + "--network=regtest", + fmt.Sprintf("--addr=%s", u.String()), + "server", + "start", + fmt.Sprintf("--electrum-url=%s", electrs.rpcURL.String()), + } + return &Electrs{ + Process: NewDaemonProcess(cmdLine, fmt.Sprintf("lwk-%d", id)), + }, nil +}