diff --git a/test/liquid_pegin_test.go b/test/liquid_pegin_test.go new file mode 100644 index 00000000..ad489f31 --- /dev/null +++ b/test/liquid_pegin_test.go @@ -0,0 +1,138 @@ +package test + +import ( + "fmt" + "math" + "os" + "testing" + + "github.com/elementsproject/peerswap/clightning" + "github.com/elementsproject/peerswap/peerswaprpc" + "github.com/elementsproject/peerswap/swap" + "github.com/stretchr/testify/require" +) + +func isBlindedTx(res interface{}) bool { + m, ok := res.(map[string]interface{}) + if !ok { + return false + } + result, ok := m["decoded"].(map[string]interface{}) + if !ok { + return false + } + vouts, ok := result["vout"].([]interface{}) + if !ok { + return false + } + for _, v := range vouts { + voMap, ok := v.(map[string]interface{}) + if !ok { + continue + } + if _, hasVC := voMap["valuecommitment"]; hasVC { + return true + } + } + return false +} +func Test_ClnCln_Liquid_SwapIn_pegin(t *testing.T) { + IsIntegrationTest(t) + t.Parallel() + + t.Run("claim_normal", func(t *testing.T) { + t.Parallel() + require := require.New(t) + + bitcoind, liquidd, lightningds, scid := clnclnElementsSetupPegin(t, uint64(math.Pow10(9)), 10) + 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) + + swaps := peerswaprpc.ListSwapsResponse{} + err := lightningds[0].Rpc.Request(&clightning.ListSwaps{}, &swaps) + require.NoError(err) + require.Len(swaps.Swaps, 1) + res, err := liquidd.Rpc.Call("loadwallet", "swap2") + _ = res + require.NoError(err) + openingTxID := swaps.Swaps[0].GetOpeningTxId() + liquidd.RpcProxy.UpdateServiceUrl(fmt.Sprintf("http://127.0.0.1:%d/wallet/%s", liquidd.RpcPort, "swap2")) + openingTxRes, err := liquidd.Rpc.Call("gettransaction", openingTxID, true, true) + require.NoError(err, "failed to get opening tx rawtransaction") + require.True(isBlindedTx(openingTxRes.Result), "opening tx must be blinded") + otx, ok := openingTxRes.Result.(map[string]interface{}) + require.True(ok, "failed to parse") + t.Logf("opening tx: %s", otx["hex"]) + + liquidd.RpcProxy.UpdateServiceUrl(fmt.Sprintf("http://127.0.0.1:%d/wallet/%s", liquidd.RpcPort, "swap1")) + claimTxID := swaps.Swaps[0].GetClaimTxId() + claimTxRes, err := liquidd.Rpc.Call("gettransaction", claimTxID, true, true) + require.NoError(err, "failed to get claim tx rawtransaction") + require.True(isBlindedTx(claimTxRes.Result), "claim tx must be blinded") + ctx, ok := claimTxRes.Result.(map[string]interface{}) + require.True(ok, "failed to parse") + t.Logf("claim tx: %s", ctx["hex"]) + }) +} diff --git a/test/setup.go b/test/setup.go index 34b0d868..d572308d 100644 --- a/test/setup.go +++ b/test/setup.go @@ -1719,3 +1719,175 @@ func clnSingleElementsSetup(t *testing.T, elementsConfig map[string]string) (*te return bitcoind, liquidd, lightningd } + +func clnclnElementsSetupPegin(t *testing.T, fundAmt, peginAmt uint64) (*testframework.BitcoinNode, *testframework.LiquidNode, []*CLightningNodeWithLiquid, string) { + makeDataDir := func() string { + tempDir, err := os.MkdirTemp("", "cln-test-") + require.NoError(t, err, "os.MkdirTemp failed") + t.Cleanup(func() { os.RemoveAll(tempDir) }) + return tempDir + } + + testDir := makeDataDir() + + // 1) Create bitcoind + bitcoind, err := testframework.NewBitcoinNode(testDir, 1) + require.NoError(t, err, "failed to create bitcoind") + t.Cleanup(bitcoind.Kill) + require.NoError(t, bitcoind.Run(true), "failed to run bitcoind") + + // 2) Create liquidd with peg-in validation + liquidd, err := testframework.NewLiquidNode( + testDir, bitcoind, + 1, + ) + require.NoError(t, err, "failed to create liquidd") + t.Cleanup(liquidd.Kill) + require.NoError(t, liquidd.Run(true), "failed to run liquidd") + // Maturity blocks on Bitcoin + require.NoError(t, bitcoind.GenerateBlocks(101), "failed to generate blocks for maturity") + + for i := 1; i <= 2; i++ { + walletName := fmt.Sprintf("swap%d", i) + _, err := liquidd.Rpc.Call("createwallet", walletName) + require.NoError(t, err, "failed to create wallet in liquidd") + _, err = liquidd.Rpc.Call("loadwallet", walletName) + require.NoError(t, err, "failed to load wallet in liquidd") + liquidd.RpcProxy.UpdateServiceUrl(fmt.Sprintf("http://127.0.0.1:%d/wallet/%s", liquidd.RpcPort, walletName)) + + peginResp, err := liquidd.Rpc.Call("getpeginaddress") + require.NoError(t, err) + pm, ok := peginResp.Result.(map[string]interface{}) + require.True(t, ok, "failed to parse getpeginaddress") + + mainChainAddr, ok := pm["mainchain_address"].(string) + require.True(t, ok, "no mainchain_address in getpeginaddress") + claimScript, ok := pm["claim_script"].(string) + require.True(t, ok, "no claim_script in getpeginaddress") + + sendResp, err := bitcoind.Rpc.Call("sendtoaddress", mainChainAddr, peginAmt, "", "", false, false, 1, "UNSET") + require.NoError(t, err, "failed to send to pegin address from bitcoind") + txid, ok := sendResp.Result.(string) + require.True(t, ok, "failed to parse sendtoaddress result txid") + + // Maturity blocks on Bitcoin + require.NoError(t, bitcoind.GenerateBlocks(101), "failed to generate blocks for maturity") + + rawResp, err := bitcoind.Rpc.Call("getrawtransaction", txid, 0) + require.NoError(t, err, "failed getrawtransaction") + proofResp, err := bitcoind.Rpc.Call("gettxoutproof", []interface{}{[]string{txid}}) + require.NoError(t, err, "failed gettxoutproof") + + res, err := liquidd.Rpc.Call("claimpegin", rawResp.Result, proofResp.Result, claimScript) + require.NoError(t, err, "failed to claimpegin in liquidd") + raw, err := liquidd.Rpc.Call("gettransaction", res.Result) + require.NoError(t, err, "failed to gettransaction in liquidd") + ptx, ok := raw.Result.(map[string]interface{}) + require.True(t, ok, "failed to parse") + t.Logf("pegin tx: %s", ptx["hex"]) + + // Confirm peg-in on Liquid + require.NoError(t, liquidd.GenerateBlocks(2), "failed to generate Liquid blocks after pegin") + // balance check + res, err = liquidd.Rpc.Call("getbalance") + require.NoError(t, err, "failed to getbalance in liquidd") + _ = res + } + + // 3) Create 2 c-lightning nodes with liquid config + var lightningds []*testframework.CLightningNode + // Get PeerSwap plugin path and test dir + _, filename, _, _ := runtime.Caller(0) + pathToPlugin := filepath.Join(filename, "..", "..", "out", "test-builds", "peerswap") + + for i := 1; i <= 2; i++ { + ln, err := testframework.NewCLightningNode(testDir, bitcoind, i) + require.NoError(t, err, "failed to create lightning node") + t.Cleanup(ln.Kill) + defer printFailedFiltered(t, ln.DaemonProcess) + + require.NoError(t, os.MkdirAll(filepath.Join(ln.GetDataDir(), "peerswap"), os.ModePerm)) + err = os.WriteFile( + filepath.Join(ln.GetDataDir(), "peerswap", "policy.conf"), + []byte("accept_all_peers=1\n"), + os.ModePerm, + ) + require.NoError(t, err, "failed to create policy file") + + walletName := fmt.Sprintf("swap%d", i) + fileConf := struct { + Liquid struct { + RpcUser string + RpcPassword string + RpcHost string + RpcPort uint + RpcWallet string + Enabled bool + } + }{ + Liquid: struct { + RpcUser string + RpcPassword string + RpcHost string + RpcPort uint + RpcWallet string + Enabled bool + }{ + RpcUser: liquidd.RpcUser, + RpcPassword: liquidd.RpcPassword, + RpcHost: "http://127.0.0.1", + RpcPort: uint(liquidd.RpcPort), + RpcWallet: walletName, + Enabled: true, + }, + } + confBytes, err := toml.Marshal(fileConf) + require.NoError(t, err) + require.NoError(t, + os.WriteFile(filepath.Join(ln.GetDataDir(), "peerswap", "peerswap.conf"), confBytes, os.ModePerm), + ) + + ln.WithCmd("lightningd") + ln.AppendCmdLine([]string{ + "--dev-bitcoind-poll=1", + "--dev-fast-gossip", + "--large-channels", + fmt.Sprintf("--plugin=%s", pathToPlugin), + }) + lightningds = append(lightningds, ln) + } + + // Run c-lightning + for _, ln := range lightningds { + require.NoError(t, ln.Run(true, true), "failed to run lightning node") + require.NoError(t, + ln.WaitForLog("peerswap initialized", testframework.TIMEOUT), + "missing 'peerswap initialized' log", + ) + } + + // 4) Open channel + scid, err := lightningds[0].OpenChannel(lightningds[1], fundAmt, 0, true, true, true) + require.NoError(t, err, "failed to open channel") + + t.Logf("openinx tx scid: %s", scid) + // Fund second node to do swaps + _, err = lightningds[1].FundWallet(10*fundAmt, true) + require.NoError(t, err, "failed to fund wallet for LN[1]") + + require.NoError(t, syncPoll(&clnPollableNode{lightningds[0]}, &clnPollableNode{lightningds[1]}), + "failed in syncPoll c-lightning") + + // 5) Peg-in from Bitcoin to Liquid + // Use distinct wallet for each LN + nodes := []*CLightningNodeWithLiquid{{lightningds[0]}, {lightningds[1]}} + + // 6) Check final Liquid wallet balances are > 0 for each LN + for idx, nd := range nodes { + bal, err := nd.GetBtcBalanceSat() + require.NoError(t, err) + require.True(t, bal > 0, "LN[%d] pegged-in balance should be >0, got %d", idx, bal) + } + + return bitcoind, liquidd, nodes, scid +}