From af2d2b20db5454287578042cc7401925b89689bc Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 15 Aug 2024 10:55:15 +0530 Subject: [PATCH] feat: add test for invoice for lightning latch --- clients/tests/web/test-utils.js | 45 ++++- .../test/tb03-simple-atomic-transfer.test.js | 2 +- .../test/tb04-simple-lightning-latch.test.js | 176 +++++++++++++++++- 3 files changed, 218 insertions(+), 5 deletions(-) diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index 5839e567..7609de91 100644 --- a/clients/tests/web/test-utils.js +++ b/clients/tests/web/test-utils.js @@ -1,4 +1,5 @@ import axios from 'axios'; +const exec = util.promisify(require('node:child_process').exec); const generateBlocks = async (blocks) => { const body = { @@ -41,4 +42,46 @@ const sleep = (ms) => { return new Promise(resolve => setTimeout(resolve, ms)); } -export { generateBlocks, depositCoin, sleep }; +const generateInvoice = async (paymentHash, amountInSats) => { + + const generateInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-alice-1") lncli -n regtest addholdinvoice ${paymentHash} --amt ${amountInSats}`; + const { stdout, stderr } = await exec(generateInvoiceCommand); + if (stderr) { + console.error('Error:', stderr); + return null; + } + + try { + const response = JSON.parse(stdout.trim()); + return response; + } catch (error) { + console.error('Error parsing JSON:', error); + return null; + } +} + +const payInvoice = async (paymentRequest) => { + + const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-bob-1") lncli -n regtest payinvoice --force ${paymentRequest}`; + const { stdout, stderr } = await exec(payInvoiceCommand); + if (stderr) { + console.error('Error:', stderr); + return null; + } + console.log('stdout:', stdout.trim()); + return stdout.trim(); +} + +const payHoldInvoice = (paymentRequest) => { + + const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-bob-1") lncli -n regtest payinvoice --force ${paymentRequest}`; + exec(payInvoiceCommand); +} + +const settleInvoice = async (preimage) => { + + const settleInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-alice-1") lncli -n regtest settleinvoice ${preimage}`; + await exec(settleInvoiceCommand); +} + +export { generateBlocks, depositCoin, sleep, generateInvoice, payInvoice, payHoldInvoice, settleInvoice }; diff --git a/clients/tests/web/test/tb03-simple-atomic-transfer.test.js b/clients/tests/web/test/tb03-simple-atomic-transfer.test.js index e0d3906c..e4de80c6 100644 --- a/clients/tests/web/test/tb03-simple-atomic-transfer.test.js +++ b/clients/tests/web/test/tb03-simple-atomic-transfer.test.js @@ -579,7 +579,7 @@ describe('TB03 - Atomic swap with second party steal', () => { const toAddress4_for_steal = await mercuryweblib.newTransferAddress(wallet4.name, true); try { - await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId1, toAddress4_for_steal.transfer_receive, false, toAddress4.batch_id); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4_for_steal.transfer_receive, false, toAddress4.batch_id); } catch (error) { // Assert the captured error message const expectedMessage = 'Request failed'; diff --git a/clients/tests/web/test/tb04-simple-lightning-latch.test.js b/clients/tests/web/test/tb04-simple-lightning-latch.test.js index 5322fac3..e8c4c21a 100644 --- a/clients/tests/web/test/tb04-simple-lightning-latch.test.js +++ b/clients/tests/web/test/tb04-simple-lightning-latch.test.js @@ -3,7 +3,7 @@ import { describe, test, expect } from "vitest"; import CoinStatus from 'mercuryweblib/coin_enum.js'; import clientConfig from '../ClientConfig.js'; import mercuryweblib from 'mercuryweblib'; -import { generateBlocks, depositCoin } from '../test-utils.js'; +import { generateBlocks, depositCoin, sleep, generateInvoice, payInvoice, payHoldInvoice, settleInvoice } from '../test-utils.js'; async function sha256(preimage) { let buffer; @@ -198,7 +198,8 @@ describe('TB04 - The sender tries to get the pre-image before the batch is unloc let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; - await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId1, toAddress, null, null); + await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId2, toAddress, null, null); const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId1, paymentHash1.batchId); @@ -313,7 +314,8 @@ describe('TB04 - Statecoin sender can recover (resend their coin) after batch ti let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; - await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId1, toAddress, null, null); + await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId2, toAddress, null, null); const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId1, paymentHash1.batchId); @@ -322,3 +324,171 @@ describe('TB04 - Statecoin sender can recover (resend their coin) after batch ti expect(hashPreImage).toEqual(paymentHash1.hash); }); }, 50000); + +describe('TB04 - Statecoin trade with invoice creation, payment and settlement', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb04"); + localStorage.removeItem("mercury-layer:wallet2_tb04"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb04"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb04"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); + + const invoice = await generateInvoice(paymentHash.hash, amount); + + payInvoice(invoice.payment_request); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, paymentHash.batchId ); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(true); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId); + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + + let hashPreImage = await sha256(preimage); + + expect(hashPreImage).toEqual(paymentHash.hash); + + await settleInvoice(preimage); + }); +}, 50000); + +describe('TB04 - Receiver tries to transfer invoice amount to another invoice before preimage retrieval should fail', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb04"); + localStorage.removeItem("mercury-layer:wallet2_tb04"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb04"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb04"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); + + const invoice = await generateInvoice(paymentHash.hash, amount); + + payHoldInvoice(invoice.payment_request); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, paymentHash.batchId ); + + const hashFromServer = await mercurynodejslib.getPaymentHash(clientConfig, paymentHash.batchId); + + expect(hashFromServer).to.equal(paymentHash.hash); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(true); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId); + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + + let hashPreImage = await sha256(preimage); + + expect(hashPreImage).toEqual(paymentHash.hash); + + const paymentHashSecond = "4f67f0a4bc4a8a6a8ecb944e9b748ed7c27655fbdb4c4d3f045d7f18c1e4de64" + const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + + try { + await payInvoice(invoiceSecond.payment_request); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + }); +}, 50000); \ No newline at end of file