From ad7f7f18d1a3c596729c329d89f4061561ef81e3 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Fri, 19 Jul 2024 12:01:25 +0530 Subject: [PATCH 01/47] feat: add LND containers for testing --- .github/workflows/tests.yml | 1 + docker-compose-test.yml | 50 ++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc05bc7c..f4ff939f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -107,6 +107,7 @@ jobs: cd clients/apps/nodejs node test_basic_workflow2.js node test_atomic_swap.js + node ./test/tb04-simple-lightning-latch.mjs - name: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 7cce6ae7..07c303bf 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -64,6 +64,54 @@ services: depends_on: - postgres + alice: + image: lightninglabs/lnd:v0.18.0-beta + user: root + hostname: lnd + entrypoint: + - sh + - -c + - | + mkdir -p /data/.lnd + if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then + touch "/data/.lnd/umbrel-lnd.conf" + fi + lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf + ports: + - "9735:9735" + volumes: + - alice-data:/data/.lnd + restart: unless-stopped + environment: + HOME: /data + depends_on: + - bitcoind + + bob: + image: lightninglabs/lnd:v0.18.0-beta + user: root + hostname: lnd + entrypoint: + - sh + - -c + - | + mkdir -p /data/.lnd + if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then + touch "/data/.lnd/umbrel-lnd.conf" + fi + lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf + ports: + - "9736:9735" + volumes: + - bob-data:/data/.lnd + restart: unless-stopped + environment: + HOME: /data + depends_on: + - bitcoind + volumes: bitcoin_data: - electrs-data: \ No newline at end of file + electrs-data: + alice-data: + bob-data: From 885ce442b777cca8223aa75fa18827bf2a2753ab Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Fri, 19 Jul 2024 16:35:03 +0530 Subject: [PATCH 02/47] fix: lightning latch test run on actions --- .github/workflows/tests.yml | 2 +- .../test/tb04-simple-lightning-latch.mjs | 77 ++++--------------- clients/apps/nodejs/test_basic_workflow2.js | 2 - 3 files changed, 18 insertions(+), 63 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f4ff939f..cf8bccf3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -107,7 +107,7 @@ jobs: cd clients/apps/nodejs node test_basic_workflow2.js node test_atomic_swap.js - node ./test/tb04-simple-lightning-latch.mjs + mocha ./test/tb04-simple-lightning-latch.mjs - name: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index bcdf4705..d54cf2db 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -1,80 +1,37 @@ import { expect } from 'chai'; import client_config from '../client_config.js'; import mercurynodejslib from 'mercurynodejslib'; -import { promisify } from 'node:util'; -import { exec as execCallback } from 'node:child_process'; import { CoinStatus } from 'mercurynodejslib/coin_enum.js'; import crypto from 'crypto'; - -const exec = promisify(execCallback); - -async function removeDatabase() { - const clientConfig = client_config.load(); - await exec(`rm ${clientConfig.databaseFile}`); -} - -async function getnewaddress() { - const generateBlockCommand = `docker exec $(docker ps -qf "name=lnd_docker-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass getnewaddress`; - const { stdout, stderr } = await exec(generateBlockCommand); - if (stderr) { - console.error('Error:', stderr); - return null; - } - return stdout.trim(); -} - -async function generateBlock(numBlocks, address) { - const generateBlockCommand = `docker exec $(docker ps -qf "name=lnd_docker-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress ${numBlocks} ${address}`; - await exec(generateBlockCommand); -} - -async function depositCoin(deposit_address, amountInSats) { - const amountInBtc = amountInSats / 100000000; - - const sendBitcoinCommand = `docker exec $(docker ps -qf "name=lnd_docker-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress ${deposit_address} ${amountInBtc}`; - await exec(sendBitcoinCommand); - -} - -async function createWallet(clientConfig, walletName) { - - let wallet = await mercurynodejslib.createWallet(clientConfig, walletName); - expect(wallet.name).to.equal(walletName) - return wallet; - - // TODO: add more assertions -} +import { getDatabase, sleep, createWallet, getElectrumClient, generateBlock, depositCoin, connectElectr, disconnectElectr, disconnectMercuryServer, connectMercuryServer } from '../test_utils.js'; describe('TB04 - Lightning Latch', function() { + this.timeout(3000); context('Simple Transfer', () => { it('should complete successfully', async () => { - await removeDatabase(); + // await removeDatabase(); const clientConfig = client_config.load(); let wallet_1_name = "w1"; let wallet_2_name = "w2"; - let wallet1 = await createWallet(clientConfig, wallet_1_name); - let wallet2 = await createWallet(clientConfig, wallet_2_name); + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); - const token = await mercurynodejslib.newToken(clientConfig, wallet1.name); + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); const tokenId = token.token_id; const amount = 10000; - const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); - const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet1.name); + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); const usedToken = tokenList.find(token => token.token_id === tokenId); expect(usedToken.spent).is.true; - await depositCoin(depositInfo.deposit_address, amount); - - const coreWalletAddress = await getnewaddress(); - - await generateBlock(clientConfig.confirmationTarget, coreWalletAddress); + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); - const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet1.name); + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); expect(listCoins.length).to.equal(1); @@ -82,25 +39,25 @@ describe('TB04 - Lightning Latch', function() { expect(coin.status).to.equal(CoinStatus.CONFIRMED); - const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet1.name, coin.statechain_id); + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); - const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet2.name, null); + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); - await mercurynodejslib.transferSend(clientConfig, wallet1.name, coin.statechain_id, transferAddress.transfer_receive, paymentHash.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, paymentHash.batchId); - let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet2.name); + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); expect(transferReceiveResult.isThereBatchLocked).is.true; expect(transferReceiveResult.receivedStatechainIds).empty; - await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet1.name, coin.statechain_id); + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); - transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet2.name); + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); expect(transferReceiveResult.isThereBatchLocked).is.false; expect(transferReceiveResult.receivedStatechainIds).not.empty; - const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet1.name, coin.statechain_id, paymentHash.batchId); + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); const hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) diff --git a/clients/apps/nodejs/test_basic_workflow2.js b/clients/apps/nodejs/test_basic_workflow2.js index ea8f0d66..fb4570c7 100644 --- a/clients/apps/nodejs/test_basic_workflow2.js +++ b/clients/apps/nodejs/test_basic_workflow2.js @@ -1,5 +1,3 @@ -const util = require('node:util'); -const exec = util.promisify(require('node:child_process').exec); const assert = require('node:assert/strict'); const mercurynodejslib = require('mercurynodejslib'); const { CoinStatus } = require('mercurynodejslib/coin_enum'); From 35ca4ea34feed66be161e167c9c87f610168b146 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Fri, 19 Jul 2024 16:59:34 +0530 Subject: [PATCH 03/47] fix: install mocha --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cf8bccf3..913b3623 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -98,6 +98,7 @@ jobs: run: | cd clients/apps/nodejs npm install + npm install mocha -g - name: Install Node.js dependencies for lib run: | cd clients/libs/nodejs From cf6cea44a651b3176b10d3daba4a712d55cbe0ec Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 22 Jul 2024 13:29:11 +0530 Subject: [PATCH 04/47] fix: wallet names --- .../test/tb04-simple-lightning-latch.mjs | 4 +- clients/apps/nodejs/test_atomic_swap.js | 108 +++++++++--------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index d54cf2db..dfacc715 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -13,8 +13,8 @@ describe('TB04 - Lightning Latch', function() { // await removeDatabase(); const clientConfig = client_config.load(); - let wallet_1_name = "w1"; - let wallet_2_name = "w2"; + let wallet_1_name = "w_ln_1"; + let wallet_2_name = "w_ln_2"; await createWallet(clientConfig, wallet_1_name); await createWallet(clientConfig, wallet_2_name); diff --git a/clients/apps/nodejs/test_atomic_swap.js b/clients/apps/nodejs/test_atomic_swap.js index 533620e0..e8da23e2 100644 --- a/clients/apps/nodejs/test_atomic_swap.js +++ b/clients/apps/nodejs/test_atomic_swap.js @@ -773,79 +773,79 @@ async function atomicSwapWithSecondPartySteal(clientConfig, wallet_1_name, walle const clientConfig = client_config.load(); // Successful test - all transfers complete within batch_time complete. - let wallet_27_name = "w27"; - let wallet_28_name = "w28"; - let wallet_29_name = "w29"; - let wallet_30_name = "w30"; - await createWallet(clientConfig, wallet_27_name); - await createWallet(clientConfig, wallet_28_name); - await createWallet(clientConfig, wallet_29_name); - await createWallet(clientConfig, wallet_30_name); - await atomicSwapSuccess(clientConfig, wallet_27_name, wallet_28_name, wallet_29_name, wallet_30_name); + let wallet_1_name = "w_atomic_1"; + let wallet_2_name = "w_atomic_2"; + let wallet_3_name = "w_atomic_3"; + let wallet_4_name = "w_atomic_4"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + await createWallet(clientConfig, wallet_3_name); + await createWallet(clientConfig, wallet_4_name); + await atomicSwapSuccess(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name); console.log("Completed test for Successful test - all transfers complete within batch_time complete."); // Second party performs transfer-sender with incorrect or missing batch-id. First party should still receive OK. - let wallet_31_name = "w31"; - let wallet_32_name = "w32"; - let wallet_33_name = "w33"; - let wallet_34_name = "w34"; - await createWallet(clientConfig, wallet_31_name); - await createWallet(clientConfig, wallet_32_name); - await createWallet(clientConfig, wallet_33_name); - await createWallet(clientConfig, wallet_34_name); - await atomicSwapWithSecondBatchIdMissing(clientConfig, wallet_31_name, wallet_32_name, wallet_33_name, wallet_34_name); + let wallet_5_name = "w_atomic_5"; + let wallet_6_name = "w_atomic_6"; + let wallet_7_name = "w_atomic_7"; + let wallet_8_name = "w_atomic_8"; + await createWallet(clientConfig, wallet_5_name); + await createWallet(clientConfig, wallet_6_name); + await createWallet(clientConfig, wallet_7_name); + await createWallet(clientConfig, wallet_8_name); + await atomicSwapWithSecondBatchIdMissing(clientConfig, wallet_5_name, wallet_6_name, wallet_7_name, wallet_8_name); console.log("Completed test for Second party performs transfer-sender with incorrect or missing batch-id. First party should still receive OK."); // First party performs transfer-sender without batch_id. - let wallet_35_name = "w35"; - let wallet_36_name = "w36"; - let wallet_37_name = "w37"; - let wallet_38_name = "w38"; - await createWallet(clientConfig, wallet_35_name); - await createWallet(clientConfig, wallet_36_name); - await createWallet(clientConfig, wallet_37_name); - await createWallet(clientConfig, wallet_38_name); - await atomicSwapWithoutFirstBatchId(clientConfig, wallet_35_name, wallet_36_name, wallet_37_name, wallet_38_name); + let wallet_9_name = "w_atomic_9"; + let wallet_10_name = "w_atomic_10"; + let wallet_11_name = "w_atomic_11"; + let wallet_12_name = "w_atomic_12"; + await createWallet(clientConfig, wallet_9_name); + await createWallet(clientConfig, wallet_10_name); + await createWallet(clientConfig, wallet_11_name); + await createWallet(clientConfig, wallet_12_name); + await atomicSwapWithoutFirstBatchId(clientConfig, wallet_9_name, wallet_10_name, wallet_11_name, wallet_12_name); console.log("Completed test for First party performs transfer-sender without batch_id."); // One party doesn't complete transfer-receiver before the timeout. // Both wallets should be able to repeat transfer-sender and transfer-receiver back to new addresses without error, // after the timeout. - let wallet_39_name = "w39"; - let wallet_40_name = "w40"; - let wallet_41_name = "w41"; - let wallet_42_name = "w42"; - await createWallet(clientConfig, wallet_39_name); - await createWallet(clientConfig, wallet_40_name); - await createWallet(clientConfig, wallet_41_name); - await createWallet(clientConfig, wallet_42_name); - await atomicSwapWithTimeout(clientConfig, wallet_39_name, wallet_40_name, wallet_41_name, wallet_42_name); + let wallet_13_name = "w_atomic_13"; + let wallet_14_name = "w_atomic_14"; + let wallet_15_name = "w_atomic_15"; + let wallet_16_name = "w_atomic_16"; + await createWallet(clientConfig, wallet_13_name); + await createWallet(clientConfig, wallet_14_name); + await createWallet(clientConfig, wallet_15_name); + await createWallet(clientConfig, wallet_16_name); + await atomicSwapWithTimeout(clientConfig, wallet_13_name, wallet_14_name, wallet_15_name, wallet_16_name); console.log("Completed test for One party doesn't complete transfer-receiver before the timeout."); // First party tries to steal within timeout // they perform transfer-sender a second time sending back to one of their own addresses - should fail. - let wallet_43_name = "w43"; - let wallet_44_name = "w44"; - let wallet_45_name = "w45"; - let wallet_46_name = "w46"; - await createWallet(clientConfig, wallet_43_name); - await createWallet(clientConfig, wallet_44_name); - await createWallet(clientConfig, wallet_45_name); - await createWallet(clientConfig, wallet_46_name); - await atomicSwapWithFirstPartySteal(clientConfig, wallet_43_name, wallet_44_name, wallet_45_name, wallet_46_name); + let wallet_17_name = "w_atomic_17"; + let wallet_18_name = "w_atomic_18"; + let wallet_19_name = "w_atomic_19"; + let wallet_20_name = "w_atomic_20"; + await createWallet(clientConfig, wallet_17_name); + await createWallet(clientConfig, wallet_18_name); + await createWallet(clientConfig, wallet_19_name); + await createWallet(clientConfig, wallet_20_name); + await atomicSwapWithFirstPartySteal(clientConfig, wallet_17_name, wallet_18_name, wallet_19_name, wallet_20_name); console.log("Completed test for First party tries to steal within timeout"); // Second party tries to steal within timeout // they perform transfer-sender a second time sending back to one of their own addresses - should fail. - let wallet_47_name = "w47"; - let wallet_48_name = "w48"; - let wallet_49_name = "w49"; - let wallet_50_name = "w50"; - await createWallet(clientConfig, wallet_47_name); - await createWallet(clientConfig, wallet_48_name); - await createWallet(clientConfig, wallet_49_name); - await createWallet(clientConfig, wallet_50_name); - await atomicSwapWithSecondPartySteal(clientConfig, wallet_47_name, wallet_48_name, wallet_49_name, wallet_50_name); + let wallet_21_name = "w_atomic_21"; + let wallet_22_name = "w_atomic_22"; + let wallet_23_name = "w_atomic_23"; + let wallet_24_name = "w_atomic_24"; + await createWallet(clientConfig, wallet_21_name); + await createWallet(clientConfig, wallet_22_name); + await createWallet(clientConfig, wallet_23_name); + await createWallet(clientConfig, wallet_24_name); + await atomicSwapWithSecondPartySteal(clientConfig, wallet_21_name, wallet_22_name, wallet_23_name, wallet_24_name); console.log("Completed test for Second party tries to steal within timeout"); process.exit(0); // Exit successfully From 88f504bd4186059e43e1c80690a0cb454bd5e78e Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 22 Jul 2024 19:57:11 +0530 Subject: [PATCH 05/47] feat: add test to get preimage before batch unlock --- .github/workflows/tests.yml | 2 +- .../test/tb04-simple-lightning-latch.mjs | 102 ++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 913b3623..ab569a40 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -108,7 +108,7 @@ jobs: cd clients/apps/nodejs node test_basic_workflow2.js node test_atomic_swap.js - mocha ./test/tb04-simple-lightning-latch.mjs + mocha ./test/tb04-simple-lightning-latch.mjs --exit - name: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index dfacc715..870c0d70 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -66,4 +66,106 @@ describe('TB04 - Lightning Latch', function() { expect(hash).to.equal(paymentHash.hash); }) }) + + context('The sender tries to get the pre-image before the batch is unlocked', () => { + it('must fail', async () => { + + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_3"; + let wallet_2_name = "w_ln_4"; + let wallet_3_name = "w_ln_5"; + let wallet_4_name = "w_ln_6"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + await createWallet(clientConfig, wallet_3_name); + await createWallet(clientConfig, wallet_4_name); + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let depositInfo = undefined; + let tokenList = undefined; + let usedToken = undefined; + let listCoins = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin1 = listCoins[0]; + + expect(coin1.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash1 = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin1.statechain_id); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_2_name, amount, depositInfo); + + listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + expect(listCoins.length).to.equal(1); + + const coin2 = listCoins[0]; + + expect(coin2.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash2 = await mercurynodejslib.paymentHash(clientConfig, wallet_2_name, coin2.statechain_id); + + const transferAddress1 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); + const transferAddress2 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress1.transfer_receive, paymentHash1.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress2.transfer_receive, paymentHash2.batchId); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); + + expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.receivedStatechainIds).empty; + + try { + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin1.statechain_id, paymentHash1.batchId); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'Request failed with status code 404'; + expect(error.message).to.equal(expectedMessage); + } + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin1.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin1.statechain_id, paymentHash1.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash.hash); + }) + }) + }) From 8eff04be138d8fb383f1ca7948007ab103882b23 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 23 Jul 2024 13:24:45 +0530 Subject: [PATCH 06/47] feat: add test for sender recover coin after batch timeout --- .../test/tb04-simple-lightning-latch.mjs | 125 +++++++++++++++++- 1 file changed, 118 insertions(+), 7 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 870c0d70..c9b95d35 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -67,18 +67,14 @@ describe('TB04 - Lightning Latch', function() { }) }) - context('The sender tries to get the pre-image before the batch is unlocked', () => { - it('must fail', async () => { + context('The sender tries to get the pre-image before the batch is unlocked should fail', () => { + it('should complete successfully', async () => { const clientConfig = client_config.load(); let wallet_1_name = "w_ln_3"; let wallet_2_name = "w_ln_4"; - let wallet_3_name = "w_ln_5"; - let wallet_4_name = "w_ln_6"; await createWallet(clientConfig, wallet_1_name); await createWallet(clientConfig, wallet_2_name); - await createWallet(clientConfig, wallet_3_name); - await createWallet(clientConfig, wallet_4_name); const amount = 10000; let token = undefined; @@ -152,6 +148,7 @@ describe('TB04 - Lightning Latch', function() { } await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin1.statechain_id); + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_2_name, coin2.statechain_id); transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); @@ -164,7 +161,121 @@ describe('TB04 - Lightning Latch', function() { .update(Buffer.from(preimage, 'hex')) .digest('hex') - expect(hash).to.equal(paymentHash.hash); + expect(hash).to.equal(paymentHash1.hash); + }) + }) + + context('Statecoin sender can recover (resend their coin) after batch timeout without completion', () => { + it('should complete successfully', async () => { + + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_5"; + let wallet_2_name = "w_ln_6"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let depositInfo = undefined; + let tokenList = undefined; + let usedToken = undefined; + let listCoins = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin1 = listCoins[0]; + + expect(coin1.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash1 = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin1.statechain_id); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_2_name, amount, depositInfo); + + listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + expect(listCoins.length).to.equal(1); + + const coin2 = listCoins[0]; + + expect(coin2.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash2 = await mercurynodejslib.paymentHash(clientConfig, wallet_2_name, coin2.statechain_id); + + const transferAddress1 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); + const transferAddress2 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress1.transfer_receive, paymentHash1.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress2.transfer_receive, paymentHash1.batchId); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); + + expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.receivedStatechainIds).empty; + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin1.statechain_id); + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_2_name, coin2.statechain_id); + + await sleep(20000); + + let errorMessage; + console.error = (msg) => { + errorMessage = msg; + }; + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + // Assert the captured error message + const expectedMessage = 'Failed to update transfer message'; + expect(errorMessage).contains(expectedMessage); + + const transferAddress3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); + const transferAddress4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress3.transfer_receive, paymentHash2.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress4.transfer_receive, paymentHash2.batchId); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin1.statechain_id); + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_2_name, coin2.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin1.statechain_id, paymentHash1.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash1.hash); }) }) From 53792b400e4062d5fe3166151e28970509ae629d Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 23 Jul 2024 13:38:10 +0530 Subject: [PATCH 07/47] fix: increase test timeout --- clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index c9b95d35..5b35cf20 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -6,7 +6,7 @@ import crypto from 'crypto'; import { getDatabase, sleep, createWallet, getElectrumClient, generateBlock, depositCoin, connectElectr, disconnectElectr, disconnectMercuryServer, connectMercuryServer } from '../test_utils.js'; describe('TB04 - Lightning Latch', function() { - this.timeout(3000); + this.timeout(30000); context('Simple Transfer', () => { it('should complete successfully', async () => { From 3d9ddac02170458d77a15f7bc71f549b6251b99a Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Fri, 26 Jul 2024 20:01:38 +0530 Subject: [PATCH 08/47] feat: wallet creation on lnd nodes --- .github/workflows/tests.yml | 28 ++++++++- .../test/tb04-simple-lightning-latch.mjs | 58 +++++++++++++++++++ docker-compose-test.yml | 8 ++- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ab569a40..431eaa7c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: - name: Wait for services to be ready run: | sleep 80 # Adjust time as necessary for services to initialize - - name: Verify Bitcoin daemon Service with Curl + - name: Verify Bitcoin daemon Service and create wallet run: | container_id=$(docker ps -qf "name=mercurylayer_bitcoind_1") echo "Container ID: $container_id" @@ -47,6 +47,32 @@ jobs: echo "New Wallet Address: $address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress 101 "$address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001 + - name: Verify LND nodes and create a channel between them + run: | + container_id_alice=$(docker ps -qf "name=mercurylayer_alice_1") + echo "Container ID: $container_id_alice" + docker exec $container_id_alice \ + curl -X POST http://0.0.0.0:8080/v1/initwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password", "stateless_init": "true"}' + container_id_bob=$(docker ps -qf "name=mercurylayer_bob_1") + echo "Container ID: $container_id_bob" + docker exec $container_id_bob \ + curl -X POST http://0.0.0.0:8080/v1/initwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password", "stateless_init": "true"}' + docker exec $container_id_alice \ + curl -X POST http://0.0.0.0:8080/v1/unlockwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password"}' + docker exec $container_id_bob \ + curl -X POST http://0.0.0.0:8080/v1/unlockwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password"}' + docker exec $container_id_alice lncli -n regtest getinfo + docker exec $container_id_bob lncli -n regtest getinfo + docker logs $container_id_alice + docker logs $container_id_bob - name: Verify ElectrumX Service with Curl run: | container_id=$(docker ps -qf "name=mercurylayer_electrs_1") diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 5b35cf20..e15ae08f 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -279,4 +279,62 @@ describe('TB04 - Lightning Latch', function() { }) }) + context('Receiver tries to transfer invoice amount to another invoice before preimage retrieval should fail', () => { + it('should complete successfully', async () => { + + // await removeDatabase(); + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_1"; + let wallet_2_name = "w_ln_2"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin = listCoins[0]; + + expect(coin.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, paymentHash.batchId); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.receivedStatechainIds).empty; + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash.hash); + }) + }) }) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 07c303bf..0099ccf8 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -78,7 +78,9 @@ services: fi lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf ports: - - "9735:9735" + - "9735:9735" + - "10009:10009" + - "8080:8080" volumes: - alice-data:/data/.lnd restart: unless-stopped @@ -101,7 +103,9 @@ services: fi lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf ports: - - "9736:9735" + - "9736:9735" + - "10010:10009" + - "8081:8080" volumes: - bob-data:/data/.lnd restart: unless-stopped From 408afbaeffa0978cc535d6be66b341c736467add Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Fri, 26 Jul 2024 20:01:38 +0530 Subject: [PATCH 09/47] feat: wallet creation on lnd nodes --- .github/workflows/tests.yml | 28 ++++++++- .../test/tb04-simple-lightning-latch.mjs | 58 +++++++++++++++++++ docker-compose-test.yml | 8 ++- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ab569a40..431eaa7c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: - name: Wait for services to be ready run: | sleep 80 # Adjust time as necessary for services to initialize - - name: Verify Bitcoin daemon Service with Curl + - name: Verify Bitcoin daemon Service and create wallet run: | container_id=$(docker ps -qf "name=mercurylayer_bitcoind_1") echo "Container ID: $container_id" @@ -47,6 +47,32 @@ jobs: echo "New Wallet Address: $address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress 101 "$address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001 + - name: Verify LND nodes and create a channel between them + run: | + container_id_alice=$(docker ps -qf "name=mercurylayer_alice_1") + echo "Container ID: $container_id_alice" + docker exec $container_id_alice \ + curl -X POST http://0.0.0.0:8080/v1/initwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password", "stateless_init": "true"}' + container_id_bob=$(docker ps -qf "name=mercurylayer_bob_1") + echo "Container ID: $container_id_bob" + docker exec $container_id_bob \ + curl -X POST http://0.0.0.0:8080/v1/initwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password", "stateless_init": "true"}' + docker exec $container_id_alice \ + curl -X POST http://0.0.0.0:8080/v1/unlockwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password"}' + docker exec $container_id_bob \ + curl -X POST http://0.0.0.0:8080/v1/unlockwallet \ + -H "Content-Type: application/json" \ + -d '{"wallet_password": "password"}' + docker exec $container_id_alice lncli -n regtest getinfo + docker exec $container_id_bob lncli -n regtest getinfo + docker logs $container_id_alice + docker logs $container_id_bob - name: Verify ElectrumX Service with Curl run: | container_id=$(docker ps -qf "name=mercurylayer_electrs_1") diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 5b35cf20..e15ae08f 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -279,4 +279,62 @@ describe('TB04 - Lightning Latch', function() { }) }) + context('Receiver tries to transfer invoice amount to another invoice before preimage retrieval should fail', () => { + it('should complete successfully', async () => { + + // await removeDatabase(); + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_1"; + let wallet_2_name = "w_ln_2"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin = listCoins[0]; + + expect(coin.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, paymentHash.batchId); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.receivedStatechainIds).empty; + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash.hash); + }) + }) }) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 07c303bf..0099ccf8 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -78,7 +78,9 @@ services: fi lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf ports: - - "9735:9735" + - "9735:9735" + - "10009:10009" + - "8080:8080" volumes: - alice-data:/data/.lnd restart: unless-stopped @@ -101,7 +103,9 @@ services: fi lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf ports: - - "9736:9735" + - "9736:9735" + - "10010:10009" + - "8081:8080" volumes: - bob-data:/data/.lnd restart: unless-stopped From 550a6e520a3a1c74761a76d91a34c56009eef698 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 29 Jul 2024 15:09:37 +0530 Subject: [PATCH 10/47] fix: wallet creation on start of lnd --- .github/workflows/tests.yml | 16 ---------------- docker-compose-test.yml | 38 ++++++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 431eaa7c..957947f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,24 +51,8 @@ jobs: run: | container_id_alice=$(docker ps -qf "name=mercurylayer_alice_1") echo "Container ID: $container_id_alice" - docker exec $container_id_alice \ - curl -X POST http://0.0.0.0:8080/v1/initwallet \ - -H "Content-Type: application/json" \ - -d '{"wallet_password": "password", "stateless_init": "true"}' container_id_bob=$(docker ps -qf "name=mercurylayer_bob_1") echo "Container ID: $container_id_bob" - docker exec $container_id_bob \ - curl -X POST http://0.0.0.0:8080/v1/initwallet \ - -H "Content-Type: application/json" \ - -d '{"wallet_password": "password", "stateless_init": "true"}' - docker exec $container_id_alice \ - curl -X POST http://0.0.0.0:8080/v1/unlockwallet \ - -H "Content-Type: application/json" \ - -d '{"wallet_password": "password"}' - docker exec $container_id_bob \ - curl -X POST http://0.0.0.0:8080/v1/unlockwallet \ - -H "Content-Type: application/json" \ - -d '{"wallet_password": "password"}' docker exec $container_id_alice lncli -n regtest getinfo docker exec $container_id_bob lncli -n regtest getinfo docker logs $container_id_alice diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 0099ccf8..674e32e0 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -11,9 +11,11 @@ services: bitcoind: image: lncm/bitcoind:v22.0@sha256:37a1adb29b3abc9f972f0d981f45e41e5fca2e22816a023faa9fdc0084aa4507 user: root - command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=user:63cf03615adebaa9356591f95b07ec7b$$920588e53f94798bda636acac1b6a77e10e3ee7fe57e414d62f3ee9e580cd27a -fallbackfee=0.0001 + command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=user:63cf03615adebaa9356591f95b07ec7b$$920588e53f94798bda636acac1b6a77e10e3ee7fe57e414d62f3ee9e580cd27a -fallbackfee=0.0001 -zmqpubhashblock=tcp://0.0.0.0:28332 -zmqpubhashtx=tcp://0.0.0.0:28333 ports: - "18443:18443" + - "28332:28332" + - "28333:28333" volumes: - bitcoin_data:/root/.bitcoin @@ -65,18 +67,30 @@ services: - postgres alice: - image: lightninglabs/lnd:v0.18.0-beta + image: lightninglabs/lndinit:v0.1.3-beta-lnd-v0.14.2-beta user: root hostname: lnd entrypoint: - sh - -c - | + if [[ ! -f /data/seed.txt ]]; then + lndinit gen-seed > /data/seed.txt + fi + if [[ ! -f /data/walletpassword.txt ]]; then + lndinit gen-password > /data/walletpassword.txt + fi + lndinit -v init-wallet \ + --secret-source=file \ + --file.seed=/data/seed.txt \ + --file.wallet-password=/data/walletpassword.txt \ + --init-file.output-wallet-dir=/root/.lnd/data/chain/bitcoin/regtest \ + --init-file.validate-password mkdir -p /data/.lnd if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then touch "/data/.lnd/umbrel-lnd.conf" fi - lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf + lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create=true ports: - "9735:9735" - "10009:10009" @@ -86,22 +100,35 @@ services: restart: unless-stopped environment: HOME: /data + command: [ '/init-wallet-k8s.sh' ] depends_on: - bitcoind bob: - image: lightninglabs/lnd:v0.18.0-beta + image: lightninglabs/lndinit:v0.1.3-beta-lnd-v0.14.2-beta user: root hostname: lnd entrypoint: - sh - -c - | + if [[ ! -f /data/seed.txt ]]; then + lndinit gen-seed > /data/seed.txt + fi + if [[ ! -f /data/walletpassword.txt ]]; then + lndinit gen-password > /data/walletpassword.txt + fi + lndinit -v init-wallet \ + --secret-source=file \ + --file.seed=/data/seed.txt \ + --file.wallet-password=/data/walletpassword.txt \ + --init-file.output-wallet-dir=/root/.lnd/data/chain/bitcoin/regtest \ + --init-file.validate-password mkdir -p /data/.lnd if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then touch "/data/.lnd/umbrel-lnd.conf" fi - lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf + lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create=true ports: - "9736:9735" - "10010:10009" @@ -111,6 +138,7 @@ services: restart: unless-stopped environment: HOME: /data + command: [ '/init-wallet-k8s.sh' ] depends_on: - bitcoind From 4c0f05cc02bc3407a80b134b61995ed5ba3ce098 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 30 Jul 2024 18:56:16 +0530 Subject: [PATCH 11/47] fix: wallet unlock flag --- .github/workflows/tests.yml | 4 +++- docker-compose-test.yml | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 957947f0..1ac4ca33 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -53,7 +53,9 @@ jobs: echo "Container ID: $container_id_alice" container_id_bob=$(docker ps -qf "name=mercurylayer_bob_1") echo "Container ID: $container_id_bob" - docker exec $container_id_alice lncli -n regtest getinfo + identity_pubkey=$(docker exec $container_id_bob lncli -n regtest getinfo | jq -r '.identity_pubkey') + docker exec $container_id_alice lncli -n regtest connect $identity_pubkey@0.0.0.0:9736 + docker exec $container_id_alice lncli -n regtest listpeers docker exec $container_id_bob lncli -n regtest getinfo docker logs $container_id_alice docker logs $container_id_bob diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 674e32e0..6771bf46 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -90,7 +90,7 @@ services: if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then touch "/data/.lnd/umbrel-lnd.conf" fi - lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create=true + lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create ports: - "9735:9735" - "10009:10009" @@ -128,7 +128,7 @@ services: if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then touch "/data/.lnd/umbrel-lnd.conf" fi - lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create=true + lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create ports: - "9736:9735" - "10010:10009" From 97abb9299a410bc2c46cc637b7bdb60f11416b44 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 30 Jul 2024 19:12:02 +0530 Subject: [PATCH 12/47] debug: lnd node logs --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1ac4ca33..4f81af12 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -53,7 +53,10 @@ jobs: echo "Container ID: $container_id_alice" container_id_bob=$(docker ps -qf "name=mercurylayer_bob_1") echo "Container ID: $container_id_bob" + docker logs $container_id_alice + docker logs $container_id_bob identity_pubkey=$(docker exec $container_id_bob lncli -n regtest getinfo | jq -r '.identity_pubkey') + echo "Pubkey: $identity_pubkey" docker exec $container_id_alice lncli -n regtest connect $identity_pubkey@0.0.0.0:9736 docker exec $container_id_alice lncli -n regtest listpeers docker exec $container_id_bob lncli -n regtest getinfo From 48734a6b6347d39284a605fe4203d8c1b5cf8c89 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 30 Jul 2024 19:43:55 +0530 Subject: [PATCH 13/47] fix: add sleep for lnd to sync with latest block --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f81af12..aec1bbed 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,6 +47,9 @@ jobs: echo "New Wallet Address: $address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress 101 "$address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001 + - name: Wait for services to be ready + run: | + sleep 60 # Wait for lnd to sync with latest block - name: Verify LND nodes and create a channel between them run: | container_id_alice=$(docker ps -qf "name=mercurylayer_alice_1") From 328d63e5b837377e08221cc38d5e6fc31bdf01c4 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 30 Jul 2024 21:42:29 +0530 Subject: [PATCH 14/47] fix: upgrade lnd version --- docker-compose-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 6771bf46..13b66ed3 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -67,7 +67,7 @@ services: - postgres alice: - image: lightninglabs/lndinit:v0.1.3-beta-lnd-v0.14.2-beta + image: lightninglabs/lndinit:v0.1.21-beta-lnd-v0.18.0-beta user: root hostname: lnd entrypoint: @@ -105,7 +105,7 @@ services: - bitcoind bob: - image: lightninglabs/lndinit:v0.1.3-beta-lnd-v0.14.2-beta + image: lightninglabs/lndinit:v0.1.21-beta-lnd-v0.18.0-beta user: root hostname: lnd entrypoint: From 680555b12d8bb7658ece18db9b346f73c4444d05 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 30 Jul 2024 22:45:44 +0530 Subject: [PATCH 15/47] fix: zmq flags in bitcoind config --- docker-compose-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 13b66ed3..f0c7baa2 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -11,7 +11,7 @@ services: bitcoind: image: lncm/bitcoind:v22.0@sha256:37a1adb29b3abc9f972f0d981f45e41e5fca2e22816a023faa9fdc0084aa4507 user: root - command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=user:63cf03615adebaa9356591f95b07ec7b$$920588e53f94798bda636acac1b6a77e10e3ee7fe57e414d62f3ee9e580cd27a -fallbackfee=0.0001 -zmqpubhashblock=tcp://0.0.0.0:28332 -zmqpubhashtx=tcp://0.0.0.0:28333 + command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=user:63cf03615adebaa9356591f95b07ec7b$$920588e53f94798bda636acac1b6a77e10e3ee7fe57e414d62f3ee9e580cd27a -fallbackfee=0.0001 -zmqpubrawblock=tcp://0.0.0.0:28332 -zmqpubrawtx=tcp://0.0.0.0:28333 ports: - "18443:18443" - "28332:28332" @@ -128,9 +128,9 @@ services: if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then touch "/data/.lnd/umbrel-lnd.conf" fi - lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create + lnd --listen=0.0.0.0:9736 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create ports: - - "9736:9735" + - "9736:9736" - "10010:10009" - "8081:8080" volumes: From 3bbebd2da97715ba1e93386d1ad20031e67fb982 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 30 Jul 2024 23:14:05 +0530 Subject: [PATCH 16/47] fix: port for node second to listen --- .github/workflows/tests.yml | 2 +- docker-compose-test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aec1bbed..0754f546 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,7 +60,7 @@ jobs: docker logs $container_id_bob identity_pubkey=$(docker exec $container_id_bob lncli -n regtest getinfo | jq -r '.identity_pubkey') echo "Pubkey: $identity_pubkey" - docker exec $container_id_alice lncli -n regtest connect $identity_pubkey@0.0.0.0:9736 + docker exec $container_id_alice lncli -n regtest connect $identity_pubkey@bob:9736 docker exec $container_id_alice lncli -n regtest listpeers docker exec $container_id_bob lncli -n regtest getinfo docker logs $container_id_alice diff --git a/docker-compose-test.yml b/docker-compose-test.yml index f0c7baa2..32bc784c 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -128,9 +128,9 @@ services: if [ ! -f "/data/.lnd/umbrel-lnd.conf" ]; then touch "/data/.lnd/umbrel-lnd.conf" fi - lnd --listen=0.0.0.0:9736 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create + lnd --listen=0.0.0.0:9735 --rpclisten=0.0.0.0:10009 --restlisten=0.0.0.0:8080 --bitcoin.active --bitcoin.regtest --bitcoin.node=bitcoind --bitcoind.rpchost=bitcoind --bitcoind.rpcuser=user --bitcoind.rpcpass=pass --bitcoind.zmqpubrawblock=tcp://bitcoind:28332 --bitcoind.zmqpubrawtx=tcp://bitcoind:28333 --configfile=/data/.lnd/umbrel-lnd.conf --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create ports: - - "9736:9736" + - "9736:9735" - "10010:10009" - "8081:8080" volumes: From 604c0acf26a8319b3d908c7b3a1a0c5fd8dca7d3 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 31 Jul 2024 13:44:16 +0530 Subject: [PATCH 17/47] feat: add script to open channel --- .github/workflows/tests.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0754f546..0609892e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,11 +58,17 @@ jobs: echo "Container ID: $container_id_bob" docker logs $container_id_alice docker logs $container_id_bob - identity_pubkey=$(docker exec $container_id_bob lncli -n regtest getinfo | jq -r '.identity_pubkey') - echo "Pubkey: $identity_pubkey" - docker exec $container_id_alice lncli -n regtest connect $identity_pubkey@bob:9736 + identity_pubkey_bob=$(docker exec $container_id_bob lncli -n regtest getinfo | jq -r '.identity_pubkey') + echo "Pubkey: $identity_pubkey_bob" + docker exec $container_id_alice lncli -n regtest connect $identity_pubkey_bob@bob:9735 docker exec $container_id_alice lncli -n regtest listpeers - docker exec $container_id_bob lncli -n regtest getinfo + address=$(docker exec $container_id_bob lncli -n regtest newaddress p2wkh | jq -r '.address') + container_id_bitcoind=$(docker ps -qf "name=mercurylayer_bitcoind_1") + docker exec $container_id_bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress $address 0.5 + docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 6 + identity_pubkey_alice=$(docker exec $container_id_alice lncli -n regtest getinfo | jq -r '.identity_pubkey') + docker exec $container_id_bob lncli -n regtest openchannel $identity_pubkey_alice 100000 + docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 5 docker logs $container_id_alice docker logs $container_id_bob - name: Verify ElectrumX Service with Curl From a3a4ee3c5a1ef91062cea65f818951b39584b383 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 31 Jul 2024 21:09:51 +0530 Subject: [PATCH 18/47] feat: add fn to generate invoice --- .../nodejs/test/tb04-simple-lightning-latch.mjs | 4 +++- clients/apps/nodejs/test_utils.js | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 1d296b3b..93631765 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -3,7 +3,7 @@ import client_config from '../client_config.js'; import mercurynodejslib from 'mercurynodejslib'; import { CoinStatus } from 'mercurynodejslib/coin_enum.js'; import crypto from 'crypto'; -import { createWallet, removeDatabase, getnewaddress, generateBlock, depositCoin } from './test-utils.mjs'; +import { createWallet, depositCoin, generateInvoice } from '../test_utils.js'; describe('TB04 - Lightning Latch', function() { this.timeout(30000); @@ -312,6 +312,8 @@ describe('TB04 - Lightning Latch', function() { const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + const invoice = await generateInvoice(paymentHash.hash, amount); + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, false, paymentHash.batchId); diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js index e6972cc0..211a3022 100644 --- a/clients/apps/nodejs/test_utils.js +++ b/clients/apps/nodejs/test_utils.js @@ -100,6 +100,17 @@ const connectElectr = async () => { await exec("docker network connect mercurylayer_default mercurylayer_electrs_1"); } +async function generateInvoice(paymentHash, amountInSats) { + + const generateInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer_alice_1") lncli addinvoice ${paymentHash} --amt ${amountInSats}`; + const { stdout, stderr } = await exec(generateInvoiceCommand); + if (stderr) { + console.error('Error:', stderr); + return null; + } + return stdout.trim(); +} + module.exports = { removeDatabase, getDatabase, @@ -111,5 +122,6 @@ module.exports = { connectElectr, disconnectElectr, connectMercuryServer, - disconnectMercuryServer + disconnectMercuryServer , + generateInvoice }; From 8767ede4cc6340402e69fc74fe4efc7b0018da14 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 1 Aug 2024 20:04:16 +0530 Subject: [PATCH 19/47] fix: test failure --- .../test/tb04-simple-lightning-latch.mjs | 22 ++++++++++--------- clients/apps/nodejs/test_utils.js | 18 ++++++++++++--- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 93631765..d94d73ed 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -3,7 +3,7 @@ import client_config from '../client_config.js'; import mercurynodejslib from 'mercurynodejslib'; import { CoinStatus } from 'mercurynodejslib/coin_enum.js'; import crypto from 'crypto'; -import { createWallet, depositCoin, generateInvoice } from '../test_utils.js'; +import { createWallet, depositCoin, generateInvoice, payInvoice } from '../test_utils.js'; describe('TB04 - Lightning Latch', function() { this.timeout(30000); @@ -43,7 +43,7 @@ describe('TB04 - Lightning Latch', function() { const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); - await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, paymentHash.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, false, paymentHash.batchId); let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); @@ -131,8 +131,8 @@ describe('TB04 - Lightning Latch', function() { const transferAddress1 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); const transferAddress2 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); - await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress1.transfer_receive, paymentHash1.batchId); - await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress2.transfer_receive, paymentHash2.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress1.transfer_receive, false, paymentHash1.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress2.transfer_receive, false, paymentHash2.batchId); let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); @@ -229,8 +229,8 @@ describe('TB04 - Lightning Latch', function() { const transferAddress1 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); const transferAddress2 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); - await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress1.transfer_receive, paymentHash1.batchId); - await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress2.transfer_receive, paymentHash1.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress1.transfer_receive, false, paymentHash1.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress2.transfer_receive, false, paymentHash1.batchId); let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); @@ -256,8 +256,8 @@ describe('TB04 - Lightning Latch', function() { const transferAddress3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); const transferAddress4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); - await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress3.transfer_receive, paymentHash2.batchId); - await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress4.transfer_receive, paymentHash2.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transferAddress3.transfer_receive, false, paymentHash2.batchId); + await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transferAddress4.transfer_receive, false, paymentHash2.batchId); transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); @@ -284,8 +284,8 @@ describe('TB04 - Lightning Latch', function() { // await removeDatabase(); const clientConfig = client_config.load(); - let wallet_1_name = "w_ln_1"; - let wallet_2_name = "w_ln_2"; + let wallet_1_name = "w_ln_7"; + let wallet_2_name = "w_ln_8"; await createWallet(clientConfig, wallet_1_name); await createWallet(clientConfig, wallet_2_name); @@ -314,6 +314,8 @@ describe('TB04 - Lightning Latch', function() { const invoice = await generateInvoice(paymentHash.hash, amount); + await payInvoice(invoice.payment_request); + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, false, paymentHash.batchId); diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js index 211a3022..9c72cc2f 100644 --- a/clients/apps/nodejs/test_utils.js +++ b/clients/apps/nodejs/test_utils.js @@ -100,9 +100,9 @@ const connectElectr = async () => { await exec("docker network connect mercurylayer_default mercurylayer_electrs_1"); } -async function generateInvoice(paymentHash, amountInSats) { +const generateInvoice = async (paymentHash, amountInSats) => { - const generateInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer_alice_1") lncli addinvoice ${paymentHash} --amt ${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); @@ -111,6 +111,17 @@ async function generateInvoice(paymentHash, amountInSats) { return stdout.trim(); } +const payInvoice = async (paymentRequest) => { + + const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer_bob_1") lncli -n regtest payinvoice ${paymentRequest}`; + const { stdout, stderr } = await exec(payInvoiceCommand); + if (stderr) { + console.error('Error:', stderr); + return null; + } + return stdout.trim(); +} + module.exports = { removeDatabase, getDatabase, @@ -123,5 +134,6 @@ module.exports = { disconnectElectr, connectMercuryServer, disconnectMercuryServer , - generateInvoice + generateInvoice, + payInvoice }; From 6b13af4f8512c77ff6a53319b1e6ff711811a697 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 1 Aug 2024 21:47:06 +0530 Subject: [PATCH 20/47] feat: add test to steal hold invoice amount --- .../test/tb04-simple-lightning-latch.mjs | 76 ++++++++++++++++++- clients/apps/nodejs/test_utils.js | 29 ++++--- 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index d94d73ed..774f48b5 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -3,7 +3,7 @@ import client_config from '../client_config.js'; import mercurynodejslib from 'mercurynodejslib'; import { CoinStatus } from 'mercurynodejslib/coin_enum.js'; import crypto from 'crypto'; -import { createWallet, depositCoin, generateInvoice, payInvoice } from '../test_utils.js'; +import { sleep, createWallet, depositCoin, generateInvoice, payInvoice, settleInvoice } from '../test_utils.js'; describe('TB04 - Lightning Latch', function() { this.timeout(30000); @@ -279,7 +279,7 @@ describe('TB04 - Lightning Latch', function() { }) }) - context('Receiver tries to transfer invoice amount to another invoice before preimage retrieval should fail', () => { + context('Statecoin trade with invoice creation, payment and settlement', () => { it('should complete successfully', async () => { // await removeDatabase(); @@ -314,7 +314,7 @@ describe('TB04 - Lightning Latch', function() { const invoice = await generateInvoice(paymentHash.hash, amount); - await payInvoice(invoice.payment_request); + payInvoice(invoice.payment_request); const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); @@ -339,6 +339,76 @@ describe('TB04 - Lightning Latch', function() { .digest('hex') expect(hash).to.equal(paymentHash.hash); + + await settleInvoice(preimage); + }) + }) + + context('Receiver tries to transfer invoice amount to another invoice before preimage retrieval should fail', () => { + it('should complete successfully', async () => { + + // await removeDatabase(); + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_7"; + let wallet_2_name = "w_ln_8"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 60000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin = listCoins[0]; + + expect(coin.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + + const invoice = await generateInvoice(paymentHash.hash, amount); + + payInvoice(invoice.payment_request); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, false, paymentHash.batchId); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.receivedStatechainIds).empty; + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash.hash); + + const paymentHashSecond = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + const invoiceSecond = await generateInvoice(paymentHashSecond.hash, amount); + + payInvoice(invoiceSecond.payment_request); }) }) }) diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js index 9c72cc2f..1a22b2b1 100644 --- a/clients/apps/nodejs/test_utils.js +++ b/clients/apps/nodejs/test_utils.js @@ -108,18 +108,26 @@ const generateInvoice = async (paymentHash, amountInSats) => { console.error('Error:', stderr); return null; } - return stdout.trim(); -} - -const payInvoice = async (paymentRequest) => { - const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer_bob_1") lncli -n regtest payinvoice ${paymentRequest}`; - const { stdout, stderr } = await exec(payInvoiceCommand); - if (stderr) { - console.error('Error:', stderr); + try { + const response = JSON.parse(stdout.trim()); + return response; + } catch (error) { + console.error('Error parsing JSON:', error); return null; } - return stdout.trim(); +} + +const payInvoice = (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); } module.exports = { @@ -135,5 +143,6 @@ module.exports = { connectMercuryServer, disconnectMercuryServer , generateInvoice, - payInvoice + payInvoice, + settleInvoice }; From 4ccdfe9f62e38beedbcff561ab1bd5904fcfc071 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Fri, 2 Aug 2024 21:53:27 +0530 Subject: [PATCH 21/47] fix: await for hold invoice payment --- .../nodejs/test/tb04-simple-lightning-latch.mjs | 12 ++++++------ clients/apps/nodejs/test_utils.js | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 774f48b5..4aa14660 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -349,8 +349,8 @@ describe('TB04 - Lightning Latch', function() { // await removeDatabase(); const clientConfig = client_config.load(); - let wallet_1_name = "w_ln_7"; - let wallet_2_name = "w_ln_8"; + let wallet_1_name = "w_ln_9"; + let wallet_2_name = "w_ln_10"; await createWallet(clientConfig, wallet_1_name); await createWallet(clientConfig, wallet_2_name); @@ -379,7 +379,7 @@ describe('TB04 - Lightning Latch', function() { const invoice = await generateInvoice(paymentHash.hash, amount); - payInvoice(invoice.payment_request); + payHoldInvoice(invoice.payment_request); const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); @@ -405,10 +405,10 @@ describe('TB04 - Lightning Latch', function() { expect(hash).to.equal(paymentHash.hash); - const paymentHashSecond = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); - const invoiceSecond = await generateInvoice(paymentHashSecond.hash, amount); + const paymentHashSecond = "f768c404215f9fb5731c32c00fe7a057fc181d7695de447b334380d90674db34" + const invoiceSecond = await generateInvoice(paymentHashSecond, amount); - payInvoice(invoiceSecond.payment_request); + await payInvoice(invoiceSecond.payment_request); }) }) }) diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js index 1a22b2b1..45a968ef 100644 --- a/clients/apps/nodejs/test_utils.js +++ b/clients/apps/nodejs/test_utils.js @@ -118,7 +118,19 @@ const generateInvoice = async (paymentHash, amountInSats) => { } } -const payInvoice = (paymentRequest) => { +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); @@ -144,5 +156,6 @@ module.exports = { disconnectMercuryServer , generateInvoice, payInvoice, + payHoldInvoice, settleInvoice }; From 996530d60998b42389c02775f9256ef05bac90ee Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 5 Aug 2024 11:55:57 +0530 Subject: [PATCH 22/47] fix: import error --- clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 4aa14660..070bab71 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -3,7 +3,7 @@ import client_config from '../client_config.js'; import mercurynodejslib from 'mercurynodejslib'; import { CoinStatus } from 'mercurynodejslib/coin_enum.js'; import crypto from 'crypto'; -import { sleep, createWallet, depositCoin, generateInvoice, payInvoice, settleInvoice } from '../test_utils.js'; +import { sleep, createWallet, depositCoin, generateInvoice, payHoldInvoice, payInvoice, settleInvoice } from '../test_utils.js'; describe('TB04 - Lightning Latch', function() { this.timeout(30000); From d10eacc0cc40038f9e59cccc11e9602dd1c9306d Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 5 Aug 2024 12:39:40 +0530 Subject: [PATCH 23/47] fix: upgrade docker image version in actions --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0609892e..6dc2fae9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: services: docker: - image: docker:19.03.12 + image: docker:24.0.5 options: --privileged ports: - 5432:5432 From 10b42e33cd9d731b03806a354382c522a4214bb6 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 5 Aug 2024 12:41:45 +0530 Subject: [PATCH 24/47] fix: install docker compose --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6dc2fae9..354aebf6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,6 +32,8 @@ jobs: - name: Set up Docker Compose run: | + sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose docker-compose -f docker-compose-test.yml up --build -d - name: Wait for services to be ready run: | From 5ee3e9c517bbc3801790f310fcf87e0ccdf43505 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 5 Aug 2024 14:57:25 +0530 Subject: [PATCH 25/47] fix: container name --- .github/workflows/tests.yml | 28 ++++++++++----------- clients/apps/nodejs/test_basic_workflow2.js | 20 +++++++-------- clients/apps/nodejs/test_utils.js | 20 +++++++-------- docker-compose-test.yml | 2 +- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 354aebf6..1a1c7642 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,7 +40,7 @@ jobs: sleep 80 # Adjust time as necessary for services to initialize - name: Verify Bitcoin daemon Service and create wallet run: | - container_id=$(docker ps -qf "name=mercurylayer_bitcoind_1") + container_id=$(docker ps -qf "name=mercurylayer-bitcoind-1") echo "Container ID: $container_id" docker logs $container_id wallet_name="new_wallet" @@ -54,9 +54,9 @@ jobs: sleep 60 # Wait for lnd to sync with latest block - name: Verify LND nodes and create a channel between them run: | - container_id_alice=$(docker ps -qf "name=mercurylayer_alice_1") + container_id_alice=$(docker ps -qf "name=mercurylayer-alice-1") echo "Container ID: $container_id_alice" - container_id_bob=$(docker ps -qf "name=mercurylayer_bob_1") + container_id_bob=$(docker ps -qf "name=mercurylayer-bob-1") echo "Container ID: $container_id_bob" docker logs $container_id_alice docker logs $container_id_bob @@ -65,27 +65,27 @@ jobs: docker exec $container_id_alice lncli -n regtest connect $identity_pubkey_bob@bob:9735 docker exec $container_id_alice lncli -n regtest listpeers address=$(docker exec $container_id_bob lncli -n regtest newaddress p2wkh | jq -r '.address') - container_id_bitcoind=$(docker ps -qf "name=mercurylayer_bitcoind_1") + container_id_bitcoind=$(docker ps -qf "name=mercurylayer-bitcoind-1") docker exec $container_id_bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress $address 0.5 - docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 6 + docker exec $(docker ps -qf "name=mercurylayer-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 6 identity_pubkey_alice=$(docker exec $container_id_alice lncli -n regtest getinfo | jq -r '.identity_pubkey') docker exec $container_id_bob lncli -n regtest openchannel $identity_pubkey_alice 100000 - docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 5 + docker exec $(docker ps -qf "name=mercurylayer-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 5 docker logs $container_id_alice docker logs $container_id_bob - name: Verify ElectrumX Service with Curl run: | - container_id=$(docker ps -qf "name=mercurylayer_electrs_1") + container_id=$(docker ps -qf "name=mercurylayer-electrs-1") echo "Container ID: $container_id" docker logs $container_id - name: Verify Enclave Service with Curl run: | - container_id=$(docker ps -qf "name=mercurylayer_enclave-sgx_1") + container_id=$(docker ps -qf "name=mercurylayer-enclave-sgx-1") echo "Container ID: $container_id" docker logs $container_id - name: Verify Mercury Service with Curl run: | - container_id=$(docker ps -qf "name=mercurylayer_mercury_1") + container_id=$(docker ps -qf "name=mercurylayer-mercury-1") echo "Container ID: $container_id" docker logs $container_id docker exec $container_id \ @@ -100,11 +100,11 @@ jobs: - name: Check connectivity between containers run: | # Get container IDs - enclave_container=$(docker ps -qf "name=mercurylayer_enclave-sgx_1") - mercury_container=$(docker ps -qf "name=mercurylayer_mercury_1") + enclave_container=$(docker ps -qf "name=mercurylayer-enclave-sgx-1") + mercury_container=$(docker ps -qf "name=mercurylayer-mercury-1") - # Check if mercurylayer_mercury_1 can reach mercurylayer_enclave-sgx_1 - docker exec $mercury_container curl -v http://mercurylayer_enclave-sgx_1:18080/get_public_key \ + # Check if mercurylayer-mercury-1 can reach mercurylayer-enclave-sgx-1 + docker exec $mercury_container curl -v http://mercurylayer-enclave-sgx-1:18080/get_public_key \ -H "Content-Type: application/json" \ -d '{"statechain_id":"550e8400e29b41d4a716446655440000"}' @@ -114,7 +114,7 @@ jobs: -H "Content-Type: application/json" \ -d '{"statechain_id":"550e8400e29b41d4a716446655440000"}' - docker inspect mercurylayer_mercury_1 + docker inspect mercurylayer-mercury-1 - name: Set up Node.js uses: actions/setup-node@v2 with: diff --git a/clients/apps/nodejs/test_basic_workflow2.js b/clients/apps/nodejs/test_basic_workflow2.js index d669fe00..faf64184 100644 --- a/clients/apps/nodejs/test_basic_workflow2.js +++ b/clients/apps/nodejs/test_basic_workflow2.js @@ -395,7 +395,7 @@ async function interruptBeforeSignFirst(clientConfig, wallet_1_name, wallet_2_na let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); - console.log("Disconnect mercurylayer_mercury_1 from network"); + console.log("Disconnect mercurylayer-mercury-1 from network"); await disconnectMercuryServer(); try { @@ -406,7 +406,7 @@ async function interruptBeforeSignFirst(clientConfig, wallet_1_name, wallet_2_na assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:8000"), `Unexpected error message: ${error.message}`); } - console.log("Connect mercurylayer_mercury_1 from network"); + console.log("Connect mercurylayer-mercury-1 from network"); await connectMercuryServer(); } @@ -447,7 +447,7 @@ const new_transaction = async(clientConfig, electrumClient, coin, toAddress, isW const serverPartialSigRequest = partialSigRequest.partial_signature_request_payload; - console.log("Disconnect mercurylayer_mercury_1 from network"); + console.log("Disconnect mercurylayer-mercury-1 from network"); await disconnectMercuryServer(); let serverPartialSig; @@ -461,7 +461,7 @@ const new_transaction = async(clientConfig, electrumClient, coin, toAddress, isW `Unexpected error message: ${error.message}`); } - console.log("Connect mercurylayer_mercury_1 from network"); + console.log("Connect mercurylayer-mercury-1 from network"); await connectMercuryServer(); } @@ -630,7 +630,7 @@ async function interruptSignWithElectrumUnavailability(clientConfig, wallet_1_na await sleep(5000); // wait for Electrum to disconnect - console.log("Disconnect mercurylayer_electrs_1 from network"); + console.log("Disconnect mercurylayer-electrs-1 from network"); await disconnectElectr(); try { @@ -641,7 +641,7 @@ async function interruptSignWithElectrumUnavailability(clientConfig, wallet_1_na assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:50001"), `Unexpected error message: ${error.message}`); } - console.log("Connect mercurylayer_electrs_1 from network"); + console.log("Connect mercurylayer-electrs-1 from network"); await connectElectr(); await sleep(5000); // wait for Electrum to connect @@ -690,7 +690,7 @@ async function interruptTransferReceiveWithElectrumUnavailability(clientConfig, await sleep(5000); // wait for Electrum to disconnect - console.log("Disconnect mercurylayer_electrs_1 from network"); + console.log("Disconnect mercurylayer-electrs-1 from network"); await disconnectElectr(); try { @@ -701,7 +701,7 @@ async function interruptTransferReceiveWithElectrumUnavailability(clientConfig, assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:50001"), `Unexpected error message: ${error.message}`); } - console.log("Connect mercurylayer_electrs_1 from network"); + console.log("Connect mercurylayer-electrs-1 from network"); await connectElectr(); await sleep(5000); // wait for Electrum to connect @@ -748,7 +748,7 @@ async function interruptTransferReceiveWithMercuryServerUnavailability(clientCon coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); - console.log("Disconnect mercurylayer_mercury_1 from network"); + console.log("Disconnect mercurylayer-mercury-1 from network"); await disconnectMercuryServer(); try { @@ -759,7 +759,7 @@ async function interruptTransferReceiveWithMercuryServerUnavailability(clientCon assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:8000"), `Unexpected error message: ${error.message}`); } - console.log("Connect mercurylayer_mercury_1 from network"); + console.log("Connect mercurylayer-mercury-1 from network"); await connectMercuryServer(); } diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js index 45a968ef..2ee4ae98 100644 --- a/clients/apps/nodejs/test_utils.js +++ b/clients/apps/nodejs/test_utils.js @@ -54,7 +54,7 @@ const getElectrumClient = async (clientConfig) => { } const generateBlock = async (numBlocks) => { - const generateBlockCommand = `docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress ${numBlocks} "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"`; + const generateBlockCommand = `docker exec $(docker ps -qf "name=mercurylayer-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress ${numBlocks} "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"`; await exec(generateBlockCommand); // console.log(`Generated ${numBlocks} blocks`); @@ -74,7 +74,7 @@ const depositCoin = async (clientConfig, wallet_name, amount, deposit_info) => { // Sending Bitcoin using bitcoin-cli try { - const sendBitcoinCommand = `docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress ${deposit_info.deposit_address} ${amountInBtc}`; + const sendBitcoinCommand = `docker exec $(docker ps -qf "name=mercurylayer-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress ${deposit_info.deposit_address} ${amountInBtc}`; await exec(sendBitcoinCommand); // console.log(`Sent ${amountInBtc} BTC to ${deposit_info.deposit_address}`); await generateBlock(3); @@ -85,24 +85,24 @@ const depositCoin = async (clientConfig, wallet_name, amount, deposit_info) => { } const disconnectMercuryServer = async () => { - await exec("docker network disconnect mercurylayer_default mercurylayer_mercury_1"); + await exec("docker network disconnect mercurylayer-default mercurylayer-mercury-1"); } const connectMercuryServer = async () => { - await exec("docker network connect mercurylayer_default mercurylayer_mercury_1"); + await exec("docker network connect mercurylayer-default mercurylayer-mercury-1"); } const disconnectElectr = async () => { - await exec("docker network disconnect mercurylayer_default mercurylayer_electrs_1"); + await exec("docker network disconnect mercurylayer-default mercurylayer-electrs-1"); } const connectElectr = async () => { - await exec("docker network connect mercurylayer_default mercurylayer_electrs_1"); + await exec("docker network connect mercurylayer-default mercurylayer-electrs-1"); } const generateInvoice = async (paymentHash, amountInSats) => { - const generateInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer_alice_1") lncli -n regtest addholdinvoice ${paymentHash} --amt ${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); @@ -120,7 +120,7 @@ const generateInvoice = async (paymentHash, amountInSats) => { const payInvoice = async (paymentRequest) => { - const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer_bob_1") lncli -n regtest payinvoice --force ${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); @@ -132,13 +132,13 @@ const payInvoice = async (paymentRequest) => { const payHoldInvoice = (paymentRequest) => { - const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer_bob_1") lncli -n regtest payinvoice --force ${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}`; + const settleInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-alice-1") lncli -n regtest settleinvoice ${preimage}`; await exec(settleInvoiceCommand); } diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 32bc784c..d8bd5fc4 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -60,7 +60,7 @@ services: LH_DECREMENT: 1 CONNECTION_STRING: postgres://postgres:pgpassword@postgres:5432/postgres BATCH_TIMEOUT: 20 - ENCLAVES: '[{"url": "http://mercurylayer_enclave-sgx_1:18080", "allow_deposit": true}]' + ENCLAVES: '[{"url": "http://mercurylayer-enclave-sgx-1:18080", "allow_deposit": true}]' ports: - "8000:8000" depends_on: From b7d203d9f6de7ca886598e093332dba50259b4fd Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 5 Aug 2024 15:34:07 +0530 Subject: [PATCH 26/47] fix: network name --- clients/apps/nodejs/test_utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js index 2ee4ae98..fe1ece6b 100644 --- a/clients/apps/nodejs/test_utils.js +++ b/clients/apps/nodejs/test_utils.js @@ -85,19 +85,19 @@ const depositCoin = async (clientConfig, wallet_name, amount, deposit_info) => { } const disconnectMercuryServer = async () => { - await exec("docker network disconnect mercurylayer-default mercurylayer-mercury-1"); + await exec("docker network disconnect mercurylayer_default mercurylayer-mercury-1"); } const connectMercuryServer = async () => { - await exec("docker network connect mercurylayer-default mercurylayer-mercury-1"); + await exec("docker network connect mercurylayer_default mercurylayer-mercury-1"); } const disconnectElectr = async () => { - await exec("docker network disconnect mercurylayer-default mercurylayer-electrs-1"); + await exec("docker network disconnect mercurylayer_default mercurylayer-electrs-1"); } const connectElectr = async () => { - await exec("docker network connect mercurylayer-default mercurylayer-electrs-1"); + await exec("docker network connect mercurylayer_default mercurylayer-electrs-1"); } const generateInvoice = async (paymentHash, amountInSats) => { From cd29822b5be01880f62546464dc9cedb8e75d599 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 5 Aug 2024 16:20:42 +0530 Subject: [PATCH 27/47] fix: add assertion for invoice payment failure --- clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 070bab71..fc27819f 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -408,7 +408,11 @@ describe('TB04 - Lightning Latch', function() { const paymentHashSecond = "f768c404215f9fb5731c32c00fe7a057fc181d7695de447b334380d90674db34" const invoiceSecond = await generateInvoice(paymentHashSecond, amount); - await payInvoice(invoiceSecond.payment_request); - }) + try { + await payInvoice(invoiceSecond.payment_request); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } }) }) From 6f1f09d7d1b9a2aa809a8697e196aa168146d6b1 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Mon, 5 Aug 2024 21:36:12 +0530 Subject: [PATCH 28/47] feat: add test for sender sends coin without batch_id --- .../test/tb04-simple-lightning-latch.mjs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index fc27819f..6f719c52 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -414,5 +414,65 @@ describe('TB04 - Lightning Latch', function() { console.error('Error:', error); expect(error.message).to.include('failed'); } + }) + }) + + context('Statecoin sender sends coin without batch_id (receiver should still be able to receive, but no pre-image revealed)', () => { + it('should complete successfully', async () => { + + // await removeDatabase(); + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_11"; + let wallet_2_name = "w_ln_12"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin = listCoins[0]; + + expect(coin.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.receivedStatechainIds).empty; + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash.hash); + }) }) }) From 7f25d5b1273575e63cb9c4cf8e69cf0d22bd2911 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 6 Aug 2024 19:32:00 +0530 Subject: [PATCH 29/47] feat: add fn for invoice verification against payment hash --- .../test/tb04-simple-lightning-latch.mjs | 67 ++++++++++++++++++- clients/libs/nodejs/index.js | 35 +++++++++- docker-compose-test.yml | 6 ++ 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 6f719c52..80f9db31 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -456,7 +456,72 @@ describe('TB04 - Lightning Latch', function() { let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); - expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).empty; + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash.hash); + }) + }) + + context('Sender sends coin without batch_id, and then resends to a different address (to attempt to steal), and then attempts to retrieve the pre-image, should fail (and LN payment cannot be claimed)', () => { + it('should complete successfully', async () => { + + // await removeDatabase(); + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_13"; + let wallet_2_name = "w_ln_14"; + let wallet_3_name = "w_ln_15"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + await createWallet(clientConfig, wallet_3_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin = listCoins[0]; + + expect(coin.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddress.transfer_receive, false, null); + + const transferAddressSecond = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transferAddressSecond.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; expect(transferReceiveResult.receivedStatechainIds).empty; await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); diff --git a/clients/libs/nodejs/index.js b/clients/libs/nodejs/index.js index 5cf3ade3..bd7d0013 100644 --- a/clients/libs/nodejs/index.js +++ b/clients/libs/nodejs/index.js @@ -16,6 +16,7 @@ const sqlite_manager = require('./sqlite_manager'); const { v4: uuidv4 } = require('uuid'); const wallet_manager = require('./wallet'); +const bitcoinjs = require("bitcoinjs-lib"); const getDatabase = async (clientConfig) => { const databaseFile = clientConfig.databaseFile; @@ -247,6 +248,37 @@ const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) return preImage; } +const verifyInvoice = async (clientConfig, walletName, statechainId, batchId, paymentRequest) => { + + const decodedInvoice = bitcoinjs.decode(paymentRequest); + + const db = await getDatabase(clientConfig); + let wallet = await sqlite_manager.getWallet(db, walletName); + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + let paymentHashPayload = { + statechain_id: statechainId, + auth_sig: coin.signed_statechain_id, + batch_id: batchId + }; + + let paymentHash = await lightningLatch.sendPaymentHash(clientConfig, paymentHashPayload); + + return paymentHash === decodedInvoice.tagsObject.payment_hash; +} + module.exports = { createWallet, newToken, @@ -260,5 +292,6 @@ module.exports = { transferReceive, paymentHash, confirmPendingInvoice, - retrievePreImage + retrievePreImage, + verifyInvoice }; diff --git a/docker-compose-test.yml b/docker-compose-test.yml index d8bd5fc4..54b320c2 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -3,6 +3,7 @@ version: '3.8' services: postgres: image: postgres + container_name: mercurylayer-postgres-1 environment: POSTGRES_PASSWORD: pgpassword ports: @@ -10,6 +11,7 @@ services: bitcoind: image: lncm/bitcoind:v22.0@sha256:37a1adb29b3abc9f972f0d981f45e41e5fca2e22816a023faa9fdc0084aa4507 + container_name: mercurylayer-bitcoind-1 user: root command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=user:63cf03615adebaa9356591f95b07ec7b$$920588e53f94798bda636acac1b6a77e10e3ee7fe57e414d62f3ee9e580cd27a -fallbackfee=0.0001 -zmqpubrawblock=tcp://0.0.0.0:28332 -zmqpubrawtx=tcp://0.0.0.0:28333 ports: @@ -21,6 +23,7 @@ services: electrs: image: getumbrel/electrs:v0.9.4@sha256:b1590ac6cfb0e5b481c6a7af7f0626d76cbb91c63702b0f5c47e2829e9c37997 + container_name: mercurylayer-electrs-1 user: root environment: ELECTRS_LOG_FILTERS: "INFO" @@ -41,6 +44,7 @@ services: build: context: enclave dockerfile: Dockerfiles/SIM/Dockerfile + container_name: mercurylayer-enclave-sgx-1 depends_on: - postgres environment: @@ -68,6 +72,7 @@ services: alice: image: lightninglabs/lndinit:v0.1.21-beta-lnd-v0.18.0-beta + container_name: mercurylayer-alice-1 user: root hostname: lnd entrypoint: @@ -106,6 +111,7 @@ services: bob: image: lightninglabs/lndinit:v0.1.21-beta-lnd-v0.18.0-beta + container_name: mercurylayer-bob-1 user: root hostname: lnd entrypoint: From eeb5b5f96a4d7d76366f4c3a8aba2138910bda1d Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 6 Aug 2024 21:13:16 +0530 Subject: [PATCH 30/47] feat: add test for invoice verification fix: walletname for invoice verification test fix: payment hash for invoice verification test --- .../test/tb04-simple-lightning-latch.mjs | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 6846bb3c..6b025146 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -461,16 +461,17 @@ describe('TB04 - Lightning Latch', function() { let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); expect(transferReceiveResult.isThereBatchLocked).is.false; - expect(transferReceiveResult.receivedStatechainIds).empty; + expect(transferReceiveResult.receivedStatechainIds).not.empty; await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); expect(transferReceiveResult.isThereBatchLocked).is.false; - expect(transferReceiveResult.receivedStatechainIds).not.empty; + expect(transferReceiveResult.receivedStatechainIds).is.empty; const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + console.log("Preimage: ", preimage); const hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) @@ -526,16 +527,17 @@ describe('TB04 - Lightning Latch', function() { let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); expect(transferReceiveResult.isThereBatchLocked).is.false; - expect(transferReceiveResult.receivedStatechainIds).empty; + expect(transferReceiveResult.receivedStatechainIds).not.empty; await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_1_name, coin.statechain_id); transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); expect(transferReceiveResult.isThereBatchLocked).is.false; - expect(transferReceiveResult.receivedStatechainIds).not.empty; + expect(transferReceiveResult.receivedStatechainIds).is.empty; const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + console.log("Preimage: ", preimage); const hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) @@ -544,4 +546,43 @@ describe('TB04 - Lightning Latch', function() { expect(hash).to.equal(paymentHash.hash); }) }) + + context('Coin receiver creates a non hold invoice, and sends to sender (i.e. an invoice with the a different payment hash). Sender should be able to determine this.', () => { + it('should complete successfully', async () => { + + // await removeDatabase(); + const clientConfig = client_config.load(); + let wallet_1_name = "w_ln_16"; + await createWallet(clientConfig, wallet_1_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(clientConfig, wallet_1_name, amount, depositInfo); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + expect(listCoins.length).to.equal(1); + + const coin = listCoins[0]; + + expect(coin.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); + + const paymentHashSecond = "ddddf1cd1e2ca1986e29b20401b555fbaa4a8502626a7afc37c47ae06a46d918" + const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + + const isInvoiceValid = await mercurynodejslib.verifyInvoice(clientConfig, paymentHash.batchId, invoiceSecond.payment_request); + expect(isInvoiceValid).is.false; + }) + }) }) From f7d2e2abd3d473f52a5ef8e33a82f59a51c577d7 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 7 Aug 2024 21:37:09 +0530 Subject: [PATCH 31/47] feat: setup web client tests on actions --- .github/workflows/tests.yml | 15 ++++++++++++++- clients/libs/web/vite.config.js | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a1c7642..74fa99f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -124,17 +124,30 @@ jobs: run: | cd clients/apps/nodejs npm install - npm install mocha -g + - name: Install Node.js dependencies for web client + run: | + cd clients/tests/web + npm install + npx playwright install + npx playwright install-deps - name: Install Node.js dependencies for lib run: | cd clients/libs/nodejs npm install + - name: Install Node.js dependencies for web + run: | + cd clients/libs/web + npm install - name: Run Client-Side Tests run: | cd clients/apps/nodejs node test_basic_workflow2.js node test_atomic_swap.js mocha ./test/tb04-simple-lightning-latch.mjs --exit + - name: Run web client Tests + run: | + cd clients/libs/web + npx vitest --browser.name=chromium --browser.headless - name: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/clients/libs/web/vite.config.js b/clients/libs/web/vite.config.js index a1a47266..275a30da 100644 --- a/clients/libs/web/vite.config.js +++ b/clients/libs/web/vite.config.js @@ -1,6 +1,5 @@ import { defineConfig } from 'vite' import { resolve } from 'path' -import { nodePolyfills } from 'vite-plugin-node-polyfills' // https://vitejs.dev/config/ export default defineConfig({ From f22c7b73676ab3cfb6a7a0aa707ebe72eda8975f Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 8 Aug 2024 14:15:22 +0530 Subject: [PATCH 32/47] feat: setup esplora for actions fix: esplora and node server for web client fix: host for node server for web client fix: dir for node server for web client fix: volume for esplora and node server fix: node server volume fix: explora volume mount fix: bitcoin cli commands for esplora fix: host for node server debug: esplora and node server requests debug: host for esplora debug: containers fix: node server exit debug: node server logs fix: node server restart debug: esplora logs fix: restart policy for esplora debug: node server start command fix: add npm install before node start fix: install and start cmd for node server --- .github/workflows/tests.yml | 18 +++++++++++++- clients/libs/nodejs/index.js | 4 +-- clients/libs/nodejs/package.json | 1 + clients/libs/web/wallet.js | 2 +- clients/tests/web/ClientConfig.js | 4 +-- clients/tests/web/server-regtest.cjs | 6 ++--- clients/tests/web/start-test-components.sh | 2 +- clients/tests/web/test-utils.js | 4 +-- docker-compose-test.yml | 29 ++++++++++++++++++++++ 9 files changed, 58 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 74fa99f8..eacc1afe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,6 +97,21 @@ jobs: -H "Content-Type: application/json" \ -d '{"statechain_id":"550e8400e29b41d4a716446655440000"}' docker logs $(docker ps -qf "name=enclave") + - name: Verify Node Service with Curl + run: | + docker ps -a + docker logs $(docker ps -qf "name=node-server") + docker exec $(docker ps -qf "name=node-server") \ + curl -X POST http://node-server:3000/deposit_amount \ + -H 'Content-Type: application/json' \ + -d '{"address":"bcrt1pkygl356c6fvk6ptx72c64hrjkhcxecj4hjzfzc430svzczuv6m0s42lvwx","amount":1000}' + docker logs $(docker ps -qf "name=node-server") + - name: Verify Esplora Service with Curl + run: | + docker logs $(docker ps -qf "name=esplora-container") + docker exec $(docker ps -qf "name=esplora-container") \ + curl http://esplora-container:8094/regtest/api/blocks/tip/height + docker logs $(docker ps -qf "name=esplora-container") - name: Check connectivity between containers run: | # Get container IDs @@ -124,6 +139,7 @@ jobs: run: | cd clients/apps/nodejs npm install + npm install mocha -g - name: Install Node.js dependencies for web client run: | cd clients/tests/web @@ -146,7 +162,7 @@ jobs: mocha ./test/tb04-simple-lightning-latch.mjs --exit - name: Run web client Tests run: | - cd clients/libs/web + cd clients/tests/web npx vitest --browser.name=chromium --browser.headless - name: Tear Down run: | diff --git a/clients/libs/nodejs/index.js b/clients/libs/nodejs/index.js index d04949b0..3a46ddc5 100644 --- a/clients/libs/nodejs/index.js +++ b/clients/libs/nodejs/index.js @@ -16,7 +16,7 @@ const sqlite_manager = require('./sqlite_manager'); const { v4: uuidv4 } = require('uuid'); const wallet_manager = require('./wallet'); -const bitcoinjs = require("bitcoinjs-lib"); +const lightningPayReq = require("bolt11"); const getDatabase = async (clientConfig) => { const databaseFile = clientConfig.databaseFile; @@ -250,7 +250,7 @@ const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) const verifyInvoice = async (clientConfig, batchId, paymentRequest) => { - const decodedInvoice = bitcoinjs.decode(paymentRequest); + const decodedInvoice = lightningPayReq.decode(paymentRequest); let paymentHash = await getPaymentHash(clientConfig, batchId); return paymentHash === decodedInvoice.tagsObject.payment_hash; diff --git a/clients/libs/nodejs/package.json b/clients/libs/nodejs/package.json index 955545c7..9a45dcdb 100644 --- a/clients/libs/nodejs/package.json +++ b/clients/libs/nodejs/package.json @@ -13,6 +13,7 @@ "@mempool/electrum-client": "^1.1.9", "axios": "^1.5.1", "bitcoinjs-lib": "^6.1.5", + "bolt11": "^1.4.1", "commander": "^11.1.0", "config": "^3.3.9", "mercury-wasm": "file:../../../wasm/node_pkg/debug", diff --git a/clients/libs/web/wallet.js b/clients/libs/web/wallet.js index 25b443b4..b2eed4e1 100644 --- a/clients/libs/web/wallet.js +++ b/clients/libs/web/wallet.js @@ -7,7 +7,7 @@ import utils from './utils.js'; const createWallet = async (clientConfig, name) => { await init(wasmUrl); - + console.log(`${clientConfig.esploraServer}/api/blocks/tip/height`) const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); const blockheight = response.data; diff --git a/clients/tests/web/ClientConfig.js b/clients/tests/web/ClientConfig.js index a0023153..1dd7ea9c 100644 --- a/clients/tests/web/ClientConfig.js +++ b/clients/tests/web/ClientConfig.js @@ -8,8 +8,8 @@ // }; const clientConfig = { - esploraServer: "http://localhost:8094/regtest", - statechainEntity: "http://127.0.0.1:8000", + esploraServer: "http://0.0.0.0:8094/regtest", + statechainEntity: "http://0.0.0.0:8000", network: "regtest", feeRateTolerance: 5, confirmationTarget: 2, diff --git a/clients/tests/web/server-regtest.cjs b/clients/tests/web/server-regtest.cjs index b68d64e6..e11c26ec 100644 --- a/clients/tests/web/server-regtest.cjs +++ b/clients/tests/web/server-regtest.cjs @@ -10,7 +10,7 @@ app.use(bodyParser.json()) app.use(cors()) async function getnewaddress() { - const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli getnewaddress`; + const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli -rpcconnect=bitcoind -rpcuser=user -rpcpassword=pass getnewaddress`; const { stdout, stderr } = await exec(generateBlockCommand); if (stderr) { throw new Error(`Error: ${stderr}`); @@ -20,13 +20,13 @@ async function getnewaddress() { async function generateBlocks(numBlocks) { const address = await getnewaddress(); - const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli generatetoaddress ${numBlocks} ${address}`; + const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli -rpcconnect=bitcoind -rpcuser=user -rpcpassword=pass generatetoaddress ${numBlocks} ${address}`; await exec(generateBlockCommand); } async function depositCoin(amount, address) { const amountInBtc = amount / 100000000; - const sendBitcoinCommand = `docker exec $(docker ps -qf "name=esplora-container") cli sendtoaddress ${address} ${amountInBtc}`; + const sendBitcoinCommand = `docker exec $(docker ps -qf "name=esplora-container") cli -rpcconnect=bitcoind -rpcuser=user -rpcpassword=pass sendtoaddress ${address} ${amountInBtc}`; await exec(sendBitcoinCommand); } diff --git a/clients/tests/web/start-test-components.sh b/clients/tests/web/start-test-components.sh index f530b563..9292921a 100755 --- a/clients/tests/web/start-test-components.sh +++ b/clients/tests/web/start-test-components.sh @@ -22,7 +22,7 @@ node server-regtest.cjs & NODE_PID=$! # Run docker command in the background without -it flags -docker run --name esplora-container -p 50001:50001 -p 8094:80 \ +docker run --name esplora-container -p 50002:50002 -p 8094:80 \ --volume $PWD/data_bitcoin_regtest:/data \ --env CORS_ALLOW='*' --rm \ blockstream/esplora \ diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index 7ea3f9b8..778853c4 100644 --- a/clients/tests/web/test-utils.js +++ b/clients/tests/web/test-utils.js @@ -5,7 +5,7 @@ const generateBlocks = async (blocks) => { blocks }; - const url = `http://localhost:3000/generate_blocks`; + const url = `http://node-server:3000/generate_blocks`; let response = await axios.post(url, body); @@ -21,7 +21,7 @@ const depositCoin = async (address, amount) => { amount }; - const url = `http://localhost:3000/deposit_amount`; + const url = `http://node-server:3000/deposit_amount`; let response = await axios.post(url, body); diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 54b320c2..f2c2a616 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -148,8 +148,37 @@ services: depends_on: - bitcoind + esplora: + image: blockstream/esplora + container_name: esplora-container + command: /srv/explorer/run.sh bitcoin-regtest explorer + ports: + - "50002:50002" + - "8094:80" + volumes: + - esplora-data:/data + environment: + CORS_ALLOW: '*' + restart: unless-stopped + depends_on: + - bitcoind + + node_server: + image: node:20.12.2 + container_name: node-server + volumes: + - .:/usr/src/app + working_dir: /usr/src/app + command: ["/bin/sh", "-c", "cd ./clients/tests/web && npm install && node server-regtest.cjs"] + restart: unless-stopped + depends_on: + - esplora + ports: + - "3000:3000" + volumes: bitcoin_data: electrs-data: alice-data: bob-data: + esplora-data: From bd5f677098000495369067a836b66e75a7d04325 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 13 Aug 2024 12:43:29 +0530 Subject: [PATCH 33/47] feat: add dockerfile for node server fix: dir with node server build fix: dockerfile for node server fix: setup script for web client tests fix: step for script execution for web client fix: add npm install in script for web client debug: post request for node server fix: post request for node server --- .github/workflows/tests.yml | 11 +++++---- clients/tests/web/start-test-components.sh | 3 ++- docker-compose-test.yml | 28 ---------------------- 3 files changed, 8 insertions(+), 34 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index eacc1afe..bf800a59 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,6 +38,11 @@ jobs: - name: Wait for services to be ready run: | sleep 80 # Adjust time as necessary for services to initialize + - name: Run start-test-components.sh for web client tests + run: | + cd clients/tests/web + chmod +x start-test-components.sh + ./start-test-components.sh - name: Verify Bitcoin daemon Service and create wallet run: | container_id=$(docker ps -qf "name=mercurylayer-bitcoind-1") @@ -99,13 +104,9 @@ jobs: docker logs $(docker ps -qf "name=enclave") - name: Verify Node Service with Curl run: | - docker ps -a - docker logs $(docker ps -qf "name=node-server") - docker exec $(docker ps -qf "name=node-server") \ - curl -X POST http://node-server:3000/deposit_amount \ + curl -X POST http://0.0.0.0:3000/deposit_amount \ -H 'Content-Type: application/json' \ -d '{"address":"bcrt1pkygl356c6fvk6ptx72c64hrjkhcxecj4hjzfzc430svzczuv6m0s42lvwx","amount":1000}' - docker logs $(docker ps -qf "name=node-server") - name: Verify Esplora Service with Curl run: | docker logs $(docker ps -qf "name=esplora-container") diff --git a/clients/tests/web/start-test-components.sh b/clients/tests/web/start-test-components.sh index 9292921a..f9e285f7 100755 --- a/clients/tests/web/start-test-components.sh +++ b/clients/tests/web/start-test-components.sh @@ -18,6 +18,7 @@ cleanup() { trap cleanup SIGINT SIGTERM # Run node server in the background and capture its PID +npm install node server-regtest.cjs & NODE_PID=$! @@ -30,4 +31,4 @@ docker run --name esplora-container -p 50002:50002 -p 8094:80 \ DOCKER_PID=$! # Wait for both processes to finish -wait $NODE_PID $DOCKER_PID +# wait $NODE_PID $DOCKER_PID diff --git a/docker-compose-test.yml b/docker-compose-test.yml index f2c2a616..c629574c 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -148,34 +148,6 @@ services: depends_on: - bitcoind - esplora: - image: blockstream/esplora - container_name: esplora-container - command: /srv/explorer/run.sh bitcoin-regtest explorer - ports: - - "50002:50002" - - "8094:80" - volumes: - - esplora-data:/data - environment: - CORS_ALLOW: '*' - restart: unless-stopped - depends_on: - - bitcoind - - node_server: - image: node:20.12.2 - container_name: node-server - volumes: - - .:/usr/src/app - working_dir: /usr/src/app - command: ["/bin/sh", "-c", "cd ./clients/tests/web && npm install && node server-regtest.cjs"] - restart: unless-stopped - depends_on: - - esplora - ports: - - "3000:3000" - volumes: bitcoin_data: electrs-data: From abd513824055315d1e6e6892466b25ed2e03b4f3 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Tue, 13 Aug 2024 22:38:17 +0530 Subject: [PATCH 34/47] feat: add wallet creation for esplora fix: order for running script for web client fix: add step to generate block on esplora fix: host for esplora debug: get request for esplora fix: permission error fix: permission issue for data_bitcoin_regtest folder fix: web client tests order --- .github/workflows/tests.yml | 32 ++++++++++++++-------- clients/tests/web/server-regtest.cjs | 6 ++-- clients/tests/web/start-test-components.sh | 1 + clients/tests/web/test-utils.js | 4 +-- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bf800a59..1faf9161 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,14 +35,14 @@ jobs: sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose docker-compose -f docker-compose-test.yml up --build -d - - name: Wait for services to be ready - run: | - sleep 80 # Adjust time as necessary for services to initialize - name: Run start-test-components.sh for web client tests run: | cd clients/tests/web chmod +x start-test-components.sh ./start-test-components.sh + - name: Wait for services to be ready + run: | + sleep 80 # Adjust time as necessary for services to initialize - name: Verify Bitcoin daemon Service and create wallet run: | container_id=$(docker ps -qf "name=mercurylayer-bitcoind-1") @@ -54,6 +54,17 @@ jobs: echo "New Wallet Address: $address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress 101 "$address" docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001 + - name: Create wallet for esplora + run: | + container_id=$(docker ps -qf "name=esplora-container") + echo "Container ID: $container_id" + docker logs $container_id + wallet_name="esplora_wallet" + docker exec $container_id cli createwallet $wallet_name + address=$(docker exec $container_id cli getnewaddress $wallet_name) + echo "New Wallet Address: $address" + docker exec $container_id cli generatetoaddress 101 "$address" + docker exec $container_id cli sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001 - name: Wait for services to be ready run: | sleep 60 # Wait for lnd to sync with latest block @@ -109,9 +120,7 @@ jobs: -d '{"address":"bcrt1pkygl356c6fvk6ptx72c64hrjkhcxecj4hjzfzc430svzczuv6m0s42lvwx","amount":1000}' - name: Verify Esplora Service with Curl run: | - docker logs $(docker ps -qf "name=esplora-container") - docker exec $(docker ps -qf "name=esplora-container") \ - curl http://esplora-container:8094/regtest/api/blocks/tip/height + curl http://0.0.0.0:8094/regtest/api/blocks/tip/height docker logs $(docker ps -qf "name=esplora-container") - name: Check connectivity between containers run: | @@ -154,17 +163,18 @@ jobs: - name: Install Node.js dependencies for web run: | cd clients/libs/web - npm install + npm install + - name: Run web client Tests + run: | + cd clients/tests/web + sudo chmod -R 755 ./data_bitcoin_regtest + npx vitest --browser.name=chromium --browser.headless - name: Run Client-Side Tests run: | cd clients/apps/nodejs node test_basic_workflow2.js node test_atomic_swap.js mocha ./test/tb04-simple-lightning-latch.mjs --exit - - name: Run web client Tests - run: | - cd clients/tests/web - npx vitest --browser.name=chromium --browser.headless - name: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/clients/tests/web/server-regtest.cjs b/clients/tests/web/server-regtest.cjs index e11c26ec..b68d64e6 100644 --- a/clients/tests/web/server-regtest.cjs +++ b/clients/tests/web/server-regtest.cjs @@ -10,7 +10,7 @@ app.use(bodyParser.json()) app.use(cors()) async function getnewaddress() { - const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli -rpcconnect=bitcoind -rpcuser=user -rpcpassword=pass getnewaddress`; + const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli getnewaddress`; const { stdout, stderr } = await exec(generateBlockCommand); if (stderr) { throw new Error(`Error: ${stderr}`); @@ -20,13 +20,13 @@ async function getnewaddress() { async function generateBlocks(numBlocks) { const address = await getnewaddress(); - const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli -rpcconnect=bitcoind -rpcuser=user -rpcpassword=pass generatetoaddress ${numBlocks} ${address}`; + const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli generatetoaddress ${numBlocks} ${address}`; await exec(generateBlockCommand); } async function depositCoin(amount, address) { const amountInBtc = amount / 100000000; - const sendBitcoinCommand = `docker exec $(docker ps -qf "name=esplora-container") cli -rpcconnect=bitcoind -rpcuser=user -rpcpassword=pass sendtoaddress ${address} ${amountInBtc}`; + const sendBitcoinCommand = `docker exec $(docker ps -qf "name=esplora-container") cli sendtoaddress ${address} ${amountInBtc}`; await exec(sendBitcoinCommand); } diff --git a/clients/tests/web/start-test-components.sh b/clients/tests/web/start-test-components.sh index f9e285f7..51f9e540 100755 --- a/clients/tests/web/start-test-components.sh +++ b/clients/tests/web/start-test-components.sh @@ -30,5 +30,6 @@ docker run --name esplora-container -p 50002:50002 -p 8094:80 \ /srv/explorer/run.sh bitcoin-regtest explorer & DOCKER_PID=$! + # Wait for both processes to finish # wait $NODE_PID $DOCKER_PID diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index 778853c4..88ae5c9d 100644 --- a/clients/tests/web/test-utils.js +++ b/clients/tests/web/test-utils.js @@ -5,7 +5,7 @@ const generateBlocks = async (blocks) => { blocks }; - const url = `http://node-server:3000/generate_blocks`; + const url = `http://0.0.0.0:3000/generate_blocks`; let response = await axios.post(url, body); @@ -21,7 +21,7 @@ const depositCoin = async (address, amount) => { amount }; - const url = `http://node-server:3000/deposit_amount`; + const url = `http://0.0.0.0:3000/deposit_amount`; let response = await axios.post(url, body); From 991149b915e24248cbfe7e074a7a54aac7915147 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 14 Aug 2024 13:13:21 +0530 Subject: [PATCH 35/47] feat: add test for atomic swap for web client --- clients/libs/web/wallet.js | 2 +- clients/tests/web/test-utils.js | 6 +- .../test/tb03-simple-atomic-transfer.test.js | 301 +++++++++++++++++- 3 files changed, 306 insertions(+), 3 deletions(-) diff --git a/clients/libs/web/wallet.js b/clients/libs/web/wallet.js index b2eed4e1..25b443b4 100644 --- a/clients/libs/web/wallet.js +++ b/clients/libs/web/wallet.js @@ -7,7 +7,7 @@ import utils from './utils.js'; const createWallet = async (clientConfig, name) => { await init(wasmUrl); - console.log(`${clientConfig.esploraServer}/api/blocks/tip/height`) + const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); const blockheight = response.data; diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index 88ae5c9d..5839e567 100644 --- a/clients/tests/web/test-utils.js +++ b/clients/tests/web/test-utils.js @@ -37,4 +37,8 @@ const testEsplora = async () => { console.log(block_header); } -export { generateBlocks, depositCoin }; +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export { generateBlocks, depositCoin, sleep }; 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 a93adf0b..b23e90e3 100644 --- a/clients/tests/web/test/tb03-simple-atomic-transfer.test.js +++ b/clients/tests/web/test/tb03-simple-atomic-transfer.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 } from '../test-utils.js'; describe('TB03 - Simple Atomic Transfer', () => { test("expected flow", async () => { @@ -94,5 +94,304 @@ describe('TB03 - Simple Atomic Transfer', () => { await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); + }); +}, 50000); + +describe('TB03 - Atomic swap with second batchid missing', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb03"); + localStorage.removeItem("mercury-layer:wallet2_tb03"); + localStorage.removeItem("mercury-layer:wallet3_tb03"); + localStorage.removeItem("mercury-layer:wallet4_tb03"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb03"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb03"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_tb03"); + let wallet4 = await mercuryweblib.createWallet(clientConfig, "wallet4_tb03"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + const toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, toAddress3.batch_id); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, ""); + + let transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(true); + + const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + + expect(transferReceive4.isThereBatchLocked).toBe(false); + + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(false); + + const toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet3.name, statechainId1, toAddress, null, null); + + await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); + + }); +}, 50000); + +describe('TB03 - Atomic swap without first batchid', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb03"); + localStorage.removeItem("mercury-layer:wallet2_tb03"); + localStorage.removeItem("mercury-layer:wallet3_tb03"); + localStorage.removeItem("mercury-layer:wallet4_tb03"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb03"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb03"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_tb03"); + let wallet4 = await mercuryweblib.createWallet(clientConfig, "wallet4_tb03"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + const toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, null); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); + + let transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(true); + + const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + + expect(transferReceive4.isThereBatchLocked).toBe(false); + + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(false); + + const toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet3.name, statechainId1, toAddress, null, null); + + await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); + + }); +}, 50000); + +describe('TB03 - Atomic swap with timeout', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb03"); + localStorage.removeItem("mercury-layer:wallet2_tb03"); + localStorage.removeItem("mercury-layer:wallet3_tb03"); + localStorage.removeItem("mercury-layer:wallet4_tb03"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb03"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb03"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_tb03"); + let wallet4 = await mercuryweblib.createWallet(clientConfig, "wallet4_tb03"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + let toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + let toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, toAddress3.batch_id); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); + + let transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(true); + + await sleep(20000); + + let errorMessage; + console.error = (msg) => { + errorMessage = msg; + }; + + let transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + + // Assert the captured error message + const expectedMessage = 'Failed to update transfer message'; + expect(errorMessage).contains(expectedMessage); + + toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, null); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); + + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(true); + + transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + + expect(transferReceive4.isThereBatchLocked).toBe(false); + + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(false); + + const toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet3.name, statechainId1, toAddress, null, null); + + await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); + }); }, 50000); \ No newline at end of file From 3057150c2cdaa2b41f5dd8f5dfa83366126db5c8 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 14 Aug 2024 13:50:34 +0530 Subject: [PATCH 36/47] feat: add test for coin steal in atomic swap for web client --- .../test/tb03-simple-atomic-transfer.test.js | 226 +++++++++++++++++- 1 file changed, 222 insertions(+), 4 deletions(-) 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 b23e90e3..0760a55d 100644 --- a/clients/tests/web/test/tb03-simple-atomic-transfer.test.js +++ b/clients/tests/web/test/tb03-simple-atomic-transfer.test.js @@ -170,7 +170,7 @@ describe('TB03 - Atomic swap with second batchid missing', () => { let transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); - expect(transferReceive3.isThereBatchLocked).toBe(true); + expect(transferReceive3.isThereBatchLocked).toBe(false); const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); @@ -262,7 +262,7 @@ describe('TB03 - Atomic swap without first batchid', () => { let transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); - expect(transferReceive3.isThereBatchLocked).toBe(true); + expect(transferReceive3.isThereBatchLocked).toBe(false); const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); @@ -372,7 +372,7 @@ describe('TB03 - Atomic swap with timeout', () => { toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); - await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, null); + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, toAddress3.batch_id); await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); @@ -394,4 +394,222 @@ describe('TB03 - Atomic swap with timeout', () => { await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); }); -}, 50000); \ No newline at end of file +}, 50000); + +describe('TB03 - Atomic swap with first party steal', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb03"); + localStorage.removeItem("mercury-layer:wallet2_tb03"); + localStorage.removeItem("mercury-layer:wallet3_tb03"); + localStorage.removeItem("mercury-layer:wallet4_tb03"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb03"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb03"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_tb03"); + let wallet4 = await mercuryweblib.createWallet(clientConfig, "wallet4_tb03"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + const toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, toAddress3.batch_id); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); + + const toAddress3_for_steal = await mercuryweblib.newTransferAddress(wallet3.name, true); + + try { + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3_for_steal.transfer_receive, false, toAddress3.batch_id); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'expected a string argument, found undefined'; + expect(error.message).contains(expectedMessage); + } + + let transferReceive3 = undefined; + try { + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + expect(error.message).contains(expectedMessage); + } + await sleep(3000); + + try { + const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + expect(error.message).contains(expectedMessage); + } + + try { + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + expect(error.message).contains(expectedMessage); + } + }); +}, 50000); + +describe('TB03 - Atomic swap with second party steal', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb03"); + localStorage.removeItem("mercury-layer:wallet2_tb03"); + localStorage.removeItem("mercury-layer:wallet3_tb03"); + localStorage.removeItem("mercury-layer:wallet4_tb03"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb03"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb03"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_tb03"); + let wallet4 = await mercuryweblib.createWallet(clientConfig, "wallet4_tb03"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + const toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, toAddress3.batch_id); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); + + 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); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'expected a string argument, found undefined'; + expect(error.message).contains(expectedMessage); + } + + let transferReceive3 = undefined; + try { + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + expect(error.message).contains(expectedMessage); + } + await sleep(3000); + + try { + const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + expect(error.message).contains(expectedMessage); + } + + try { + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + expect(error.message).contains(expectedMessage); + } + }); +}, 50000); From 28763e6dd5fdb2da74b1286f284fa3ca02c69c77 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 14 Aug 2024 20:41:20 +0530 Subject: [PATCH 37/47] feat: add test for lightning latch --- .../test/tb03-simple-atomic-transfer.test.js | 4 +- .../test/tb04-simple-lightning-latch.test.js | 218 +++++++++++++++++- 2 files changed, 219 insertions(+), 3 deletions(-) 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 0760a55d..e0d3906c 100644 --- a/clients/tests/web/test/tb03-simple-atomic-transfer.test.js +++ b/clients/tests/web/test/tb03-simple-atomic-transfer.test.js @@ -473,7 +473,7 @@ describe('TB03 - Atomic swap with first party steal', () => { await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3_for_steal.transfer_receive, false, toAddress3.batch_id); } catch (error) { // Assert the captured error message - const expectedMessage = 'expected a string argument, found undefined'; + const expectedMessage = 'Request failed'; expect(error.message).contains(expectedMessage); } @@ -582,7 +582,7 @@ describe('TB03 - Atomic swap with second party steal', () => { await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId1, toAddress4_for_steal.transfer_receive, false, toAddress4.batch_id); } catch (error) { // Assert the captured error message - const expectedMessage = 'expected a string argument, found undefined'; + const expectedMessage = 'Request failed'; expect(error.message).contains(expectedMessage); } 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 ef9e030b..5322fac3 100644 --- a/clients/tests/web/test/tb04-simple-lightning-latch.test.js +++ b/clients/tests/web/test/tb04-simple-lightning-latch.test.js @@ -105,4 +105,220 @@ describe('TB04 - Simple Lightning Latch', () => { expect(hashPreImage).toEqual(paymentHash.hash); }); -}, 50000); \ No newline at end of file +}, 50000); + +describe('TB04 - The sender tries to get the pre-image before the batch is unlocked 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); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash1 = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId1); + const paymentHash2 = await mercuryweblib.paymentHash(clientConfig, wallet2.name, statechainId2); + + let transferAddress1 = await mercuryweblib.newTransferAddress(wallet2.name); + let transferAddress2 = await mercuryweblib.newTransferAddress(wallet1.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, transferAddress1.transfer_receive, false, paymentHash1.batchId ); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, transferAddress2.transfer_receive, false, paymentHash2.batchId); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(true); + + try { + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId1, paymentHash1.batchId); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'Request failed with status code 404'; + expect(error.message).to.equal(expectedMessage); + } + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId1); + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet2.name, statechainId2); + + 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, statechainId1, paymentHash1.batchId); + + let hashPreImage = await sha256(preimage); + + expect(hashPreImage).toEqual(paymentHash1.hash); + }); +}, 50000); + +describe('TB04 - Statecoin sender can recover (resend their coin) after batch timeout without completion', () => { + 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); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash1 = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId1); + const paymentHash2 = await mercuryweblib.paymentHash(clientConfig, wallet2.name, statechainId2); + + let transferAddress1 = await mercuryweblib.newTransferAddress(wallet2.name); + let transferAddress2 = await mercuryweblib.newTransferAddress(wallet1.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, transferAddress1.transfer_receive, false, paymentHash1.batchId ); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, transferAddress2.transfer_receive, false, paymentHash2.batchId); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(true); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId1); + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet2.name, statechainId2); + + await sleep(20000); + + let errorMessage; + console.error = (msg) => { + errorMessage = msg; + }; + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + // Assert the captured error message + const expectedMessage = 'Failed to update transfer message'; + expect(errorMessage).contains(expectedMessage); + + let transferAddress3 = await mercuryweblib.newTransferAddress(wallet2.name); + let transferAddress4 = await mercuryweblib.newTransferAddress(wallet1.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, transferAddress3.transfer_receive, false, paymentHash2.batchId ); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, transferAddress4.transfer_receive, false, paymentHash2.batchId); + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet1.name); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId1); + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet2.name, statechainId2); + + 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, statechainId1, paymentHash1.batchId); + + let hashPreImage = await sha256(preimage); + + expect(hashPreImage).toEqual(paymentHash1.hash); + }); +}, 50000); From af2d2b20db5454287578042cc7401925b89689bc Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 15 Aug 2024 10:55:15 +0530 Subject: [PATCH 38/47] 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 From e644c64ce4a03695fa4947a920c4b8f841e9f367 Mon Sep 17 00:00:00 2001 From: Dhananjay Purohit Date: Mon, 12 Aug 2024 17:00:52 +0530 Subject: [PATCH 39/47] Fix/dockerfile token server (#85) * fix: token server dockerfile * fix: build release for token server * fix: upgrade sqlx version --- token-server/Cargo.toml | 2 +- token-server/Dockerfile | 39 ++++++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/token-server/Cargo.toml b/token-server/Cargo.toml index d43f7f28..829e905f 100644 --- a/token-server/Cargo.toml +++ b/token-server/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" config = "0.13.1" reqwest = { version = "0.11.16", features = ["blocking", "json", "socks"] } rocket = { version = "0.5.0-rc", features = ["json"] } -sqlx = { version = "0.7", features = [ "runtime-tokio", "postgres", "time", "uuid", "tls-rustls" ] } +sqlx = { version = "0.8.0", features = [ "runtime-tokio", "postgres", "time", "uuid", "tls-rustls" ] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" schemars = { version = "0.8.12", features = ["chrono", "uuid"] } diff --git a/token-server/Dockerfile b/token-server/Dockerfile index e62903cd..dcb7831e 100644 --- a/token-server/Dockerfile +++ b/token-server/Dockerfile @@ -1,14 +1,35 @@ -FROM rust:1.77.0 +# Use a base image that has the required GLIBC version +FROM debian:bullseye-slim as builder -# Set working directory -WORKDIR /app/lib -COPY ../lib . +# Install Rust, protobuf compiler, and library dependencies +RUN apt-get update && apt-get install -y curl build-essential protobuf-compiler libprotobuf-dev pkg-config libssl-dev +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -# Copy the project code -WORKDIR /app -COPY . . +# Set the environment path for Rust +ENV PATH="/root/.cargo/bin:${PATH}" + +# Create a working directory +WORKDIR /mercurylayer + +RUN echo "[workspace]\nmembers = [\"token-server\", \"lib\"]" > ./Cargo.toml +COPY ./Cargo.lock ./Cargo.lock + +# Copy the server source code +COPY ./token-server ./token-server +COPY ./lib ./lib +COPY ./Rocket.toml ./token-server/Rocket.toml + +# Make sure to copy Cargo.lock +COPY ./token-server/Cargo.lock ./token-server/Cargo.lock + +WORKDIR /mercurylayer/token-server + +# RUN cargo build --verbose + +# # Build your Rust project. Since the source files are now present, +# # the Rust compiler should be able to find and compile them. +RUN CARGO_TARGET_DIR=target cargo build --release -RUN cargo build --release EXPOSE 8001 -# Run the application +# # Command to run the token server by default when the container starts CMD ["cargo", "run", "--bin", "token-server"] From 5b32154fa74063f31e162801ec17626f9123ef40 Mon Sep 17 00:00:00 2001 From: "S. Santos" Date: Mon, 12 Aug 2024 09:40:04 -0300 Subject: [PATCH 40/47] Add payment hash verification to web client --- clients/libs/web/lightning-latch.js | 21 ++++++++++++++++++- clients/libs/web/main.js | 8 ++++++- clients/tests/web/package.json | 3 ++- .../test/tb04-simple-lightning-latch.test.js | 4 ++++ clients/tests/web/vitest.workspace.js | 9 ++++---- token-server/Cargo.toml | 2 +- 6 files changed, 39 insertions(+), 8 deletions(-) diff --git a/clients/libs/web/lightning-latch.js b/clients/libs/web/lightning-latch.js index f032f731..f105890d 100644 --- a/clients/libs/web/lightning-latch.js +++ b/clients/libs/web/lightning-latch.js @@ -113,4 +113,23 @@ const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) return { preimage: response?.data?.preimage }; } -export default { createPreImage, confirmPendingInvoice, retrievePreImage }; +const getPaymentHash = async (clientConfig, batchId) => { + + const url = `${clientConfig.statechainEntity}/transfer/paymenthash/${batchId}`; + + try { + let response = await axios.get(url); + + return response?.data?.hash; + + } catch (error) { + if (error.response.status == 401) { + return null; + } else { + throw new Error(`Failed to retrieve payment hash: ${JSON.stringify(error.response.data)}`); + } + } + +} + +export default { createPreImage, confirmPendingInvoice, retrievePreImage, getPaymentHash }; diff --git a/clients/libs/web/main.js b/clients/libs/web/main.js index 6d4930f9..5da25a5b 100644 --- a/clients/libs/web/main.js +++ b/clients/libs/web/main.js @@ -122,6 +122,11 @@ const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) return await lightningLatch.retrievePreImage(clientConfig, walletName, statechainId, batchId); } +const getPaymentHash = async (clientConfig, batchId) => { + + return await lightningLatch.getPaymentHash(clientConfig, batchId); +} + export default { greet, createWallet, @@ -135,5 +140,6 @@ export default { transferReceive, paymentHash, confirmPendingInvoice, - retrievePreImage + retrievePreImage, + getPaymentHash } diff --git a/clients/tests/web/package.json b/clients/tests/web/package.json index 6e021223..466c1efb 100644 --- a/clients/tests/web/package.json +++ b/clients/tests/web/package.json @@ -8,7 +8,8 @@ "build": "vite build", "preview": "vite preview", "test": "vitest", - "test:browser": "vitest --workspace=vitest.workspace.js" + "test:browser": "vitest --workspace=vitest.workspace.js", + "test:headless": "vitest --browser.name=chromium --browser.headless --exclude '**/data_bitcoin_regtest/**'" }, "dependencies": { "axios": "^1.7.2", 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 e8c4c21a..d8513fa6 100644 --- a/clients/tests/web/test/tb04-simple-lightning-latch.test.js +++ b/clients/tests/web/test/tb04-simple-lightning-latch.test.js @@ -85,6 +85,10 @@ describe('TB04 - Simple Lightning Latch', () => { await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, paymentHash.batchId ); + const hashFromServer = await mercuryweblib.getPaymentHash(clientConfig, paymentHash.batchId); + + expect(hashFromServer).to.equal(paymentHash.hash); + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); expect(transferReceive.isThereBatchLocked).toBe(true); diff --git a/clients/tests/web/vitest.workspace.js b/clients/tests/web/vitest.workspace.js index c72e861d..c7917684 100644 --- a/clients/tests/web/vitest.workspace.js +++ b/clients/tests/web/vitest.workspace.js @@ -16,10 +16,11 @@ export default defineWorkspace([ // https://playwright.dev providerOptions: {}, }, + exclude:[ + ...configDefaults.exclude, + '**/data_bitcoin_regtest/**' + ], }, - exclude:[ - ...configDefaults.exclude, - './data_bitcoin_regtest/*' - ], + }, ]) diff --git a/token-server/Cargo.toml b/token-server/Cargo.toml index 829e905f..d43f7f28 100644 --- a/token-server/Cargo.toml +++ b/token-server/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" config = "0.13.1" reqwest = { version = "0.11.16", features = ["blocking", "json", "socks"] } rocket = { version = "0.5.0-rc", features = ["json"] } -sqlx = { version = "0.8.0", features = [ "runtime-tokio", "postgres", "time", "uuid", "tls-rustls" ] } +sqlx = { version = "0.7", features = [ "runtime-tokio", "postgres", "time", "uuid", "tls-rustls" ] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" schemars = { version = "0.8.12", features = ["chrono", "uuid"] } From b02551b970f847079ff832719f786010d5f82d0c Mon Sep 17 00:00:00 2001 From: "S. Santos" Date: Mon, 12 Aug 2024 10:49:41 -0300 Subject: [PATCH 41/47] Add log crate to the server --- Cargo.lock | 50 ++++++++++++++++++++++++++++++++++++++++------ server/Cargo.toml | 2 ++ server/src/main.rs | 20 ++++++++++++++----- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a339f0cd..665fff40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,23 +107,24 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -862,6 +863,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1262,6 +1286,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.27" @@ -1386,6 +1416,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.11.0" @@ -1522,9 +1558,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "loom" @@ -1585,7 +1621,9 @@ dependencies = [ "bitcoin", "chrono", "config", + "env_logger", "hex", + "log", "mercurylib", "rand", "reqwest", diff --git a/server/Cargo.toml b/server/Cargo.toml index e3814d91..908af0fb 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -22,3 +22,5 @@ secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", b mercurylib = { path = "../lib" } chrono = "0.4.31" sha2 = "0.10.8" +log = "0.4.22" +env_logger = "0.11.5" diff --git a/server/src/main.rs b/server/src/main.rs index 1a3461bc..90a4277c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -11,24 +11,34 @@ use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::Header; use server::StateChainEntity; +use log::error; + #[catch(500)] -fn internal_error() -> Value { - json!("Internal server error") +fn internal_error(req: &Request) -> Value { + let message = format!("500 - Internal Server Error: {}", req.uri()); + error!("{}", message); + json!(message) } #[catch(400)] -fn bad_request() -> Value { - json!("Bad request") +fn bad_request(req: &Request) -> Value { + let message = format!("400 - Bad request: {}", req.uri()); + error!("{}", message); + json!(message) } #[catch(404)] fn not_found(req: &Request) -> Value { - json!(format!("Not found! Unknown route '{}'.", req.uri())) + let message = format!("404 - Not Found: {}", req.uri()); + error!("{}", message); + json!(message) } #[rocket::main] async fn main() { + env_logger::init(); + server_config::ServerConfig::load(); let statechain_entity = StateChainEntity::new().await; From 6e2241aeedbf05741a9d3d87c7906c23fc0c3335 Mon Sep 17 00:00:00 2001 From: "S. Santos" Date: Tue, 13 Aug 2024 19:26:03 -0300 Subject: [PATCH 42/47] fix: The pre-image is now only revealed if the transfer is unlocked. --- server/src/database/lightning_latch.rs | 3 ++- server/src/database/sign.rs | 4 ---- server/src/endpoints/sign.rs | 4 ---- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/server/src/database/lightning_latch.rs b/server/src/database/lightning_latch.rs index 1ac82488..8a2b3282 100644 --- a/server/src/database/lightning_latch.rs +++ b/server/src/database/lightning_latch.rs @@ -57,7 +57,8 @@ pub async fn get_preimage(pool: &sqlx::PgPool, statechain_id: &str, sender_auth_ lightning_latch \ WHERE statechain_id = $1 \ AND sender_auth_xonly_public_key = $2 \ - AND batch_id = $3"; + AND batch_id = $3 + AND locked = false"; let row = sqlx::query(query) .bind(statechain_id) diff --git a/server/src/database/sign.rs b/server/src/database/sign.rs index 1e27c50e..d77f7c2f 100644 --- a/server/src/database/sign.rs +++ b/server/src/database/sign.rs @@ -66,10 +66,6 @@ pub async fn insert_new_signature_data(pool: &sqlx::PgPool, server_pubnonce: &st pub async fn update_signature_data_challenge(pool: &sqlx::PgPool, server_pub_nonce: &str, challenge: &str, statechain_id: &str) { - println!("server_pub_nonce: {}", server_pub_nonce); - println!("challenge: {}", challenge); - println!("statechain_id: {}", statechain_id); - let query = "\ UPDATE statechain_signature_data \ SET challenge = $1 \ diff --git a/server/src/endpoints/sign.rs b/server/src/endpoints/sign.rs index 37050196..a49324cd 100644 --- a/server/src/endpoints/sign.rs +++ b/server/src/endpoints/sign.rs @@ -78,8 +78,6 @@ pub async fn sign_first(statechain_entity: &State, sign_first_ }, }; - println!("value: {}", value); - let response: mercurylib::transaction::SignFirstResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); let mut server_pubnonce_hex = response.server_pubnonce.clone(); @@ -153,8 +151,6 @@ pub async fn sign_second (statechain_entity: &State, partial_s text }, Err(err) => { - println!("ERROR sig: {}", err); - let response_body = json!({ "error": "Internal Server Error", "message": err.to_string() From 9e3b93124237b96689e6534dc08c5c1ee963d16d Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 15 Aug 2024 11:15:28 +0530 Subject: [PATCH 43/47] fix: import error --- clients/tests/web/test-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index 7609de91..f6f27f88 100644 --- a/clients/tests/web/test-utils.js +++ b/clients/tests/web/test-utils.js @@ -1,4 +1,5 @@ import axios from 'axios'; +const util = require('node:util'); const exec = util.promisify(require('node:child_process').exec); const generateBlocks = async (blocks) => { From c4dbe45607639bd97a5ab303e73e2d4655561cfb Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 15 Aug 2024 12:43:17 +0530 Subject: [PATCH 44/47] feat: add endpoints for invoice ops on node server fix: invoice generation from node server --- clients/tests/web/server-regtest.cjs | 102 +++++++++++++++++++++++++++ clients/tests/web/test-utils.js | 72 ++++++++++++------- 2 files changed, 148 insertions(+), 26 deletions(-) diff --git a/clients/tests/web/server-regtest.cjs b/clients/tests/web/server-regtest.cjs index b68d64e6..2e4caa1d 100644 --- a/clients/tests/web/server-regtest.cjs +++ b/clients/tests/web/server-regtest.cjs @@ -30,6 +30,32 @@ async function depositCoin(amount, address) { await exec(sendBitcoinCommand); } +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; + } + return stdout.trim(); +} + +const payInvoice = async (paymentRequest) => { + const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-bob-1") lncli -n regtest payinvoice --force ${paymentRequest}`; + await exec(payInvoiceCommand); +} + +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); +} + app.post('/deposit_amount', async (req, res) => { const { address, amount } = req.body @@ -63,6 +89,82 @@ app.post('/generate_blocks', async (req, res) => { } }) +app.post('/generate_invoice', async (req, res) => { + const { paymentHash, amountInSats } = req.body + + if (typeof paymentHash === 'string' && Number.isInteger(amountInSats)) { + console.log(`Generating invoice ...`) + + try { + const invoice = await generateInvoice(paymentHash, amountInSats); + res.status(200).send({ message: 'Invoice generated successfully', invoice }) + } catch (error) { + console.log(error.message); + res.status(500).send({ message: error.message }) + } + + } else { + res.status(400).send({ message: 'Invalid input' }) + } +}) + +app.post('/pay_invoice', async (req, res) => { + const { paymentRequest } = req.body + + if (typeof paymentRequest === 'string') { + console.log(`Paying invoice ...`) + + try { + await payInvoice(paymentRequest); + } catch (error) { + console.log(error.message); + res.status(500).send({ message: error.message }) + } + + res.status(200).send({ message: 'Invoice paid successfully' }) + } else { + res.status(400).send({ message: 'Invalid input' }) + } +}) + +app.post('/pay_holdinvoice', async (req, res) => { + const { paymentRequest } = req.body + + if (typeof paymentRequest === 'string') { + console.log(`Paying invoice ...`) + + try { + payHoldInvoice(paymentRequest); + } catch (error) { + console.log(error.message); + res.status(500).send({ message: error.message }) + } + + res.status(200).send({ message: 'Invoice paid successfully' }) + } else { + res.status(400).send({ message: 'Invalid input' }) + } +}) + +app.post('/settle_invoice', async (req, res) => { + const { preimage } = req.body + + if (typeof preimage === 'string') { + console.log(`Settling invoice ...`) + + try { + await settleInvoice(preimage); + } catch (error) { + console.log(error.message); + res.status(500).send({ message: error.message }) + } + + res.status(200).send({ message: 'Invoice settled successfully' }) + } else { + res.status(400).send({ message: 'Invalid input' }) + } +}) + app.listen(port, () => { console.log(`Docker server listening on port ${port}`) }) \ No newline at end of file diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index f6f27f88..42f39dfe 100644 --- a/clients/tests/web/test-utils.js +++ b/clients/tests/web/test-utils.js @@ -1,6 +1,4 @@ import axios from 'axios'; -const util = require('node:util'); -const exec = util.promisify(require('node:child_process').exec); const generateBlocks = async (blocks) => { const body = { @@ -45,44 +43,66 @@ const sleep = (ms) => { 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; - } + const body = { + paymentHash, + amountInSats + }; + + const url = `http://0.0.0.0:3000/generate_invoice`; - try { - const response = JSON.parse(stdout.trim()); - return response; - } catch (error) { - console.error('Error parsing JSON:', error); - return null; + let response = await axios.post(url, body); + + if (response.status == 200) { + const invoice = JSON.parse(response.data.invoice); + return invoice; + } else { + throw new Error(`Failed to generate invoice`); } } const payInvoice = async (paymentRequest) => { + + const body = { + paymentRequest + }; + + const url = `http://0.0.0.0:3000/pay_invoice`; - 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; + let response = await axios.post(url, body); + + if (response.status != 200) { + throw new Error(`Failed to pay invoice`); } - console.log('stdout:', stdout.trim()); - return stdout.trim(); } -const payHoldInvoice = (paymentRequest) => { +const payHoldInvoice = async (paymentRequest) => { + + const body = { + paymentRequest + }; + + const url = `http://0.0.0.0:3000/pay_holdinvoice`; - const payInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-bob-1") lncli -n regtest payinvoice --force ${paymentRequest}`; - exec(payInvoiceCommand); + let response = await axios.post(url, body); + + if (response.status != 200) { + throw new Error(`Failed to pay hold invoice`); + } } const settleInvoice = async (preimage) => { - const settleInvoiceCommand = `docker exec $(docker ps -qf "name=mercurylayer-alice-1") lncli -n regtest settleinvoice ${preimage}`; - await exec(settleInvoiceCommand); + const body = { + preimage + }; + + const url = `http://0.0.0.0:3000/settle_invoice`; + + let response = await axios.post(url, body); + + if (response.status != 200) { + throw new Error(`Failed to settle invoice`); + } } export { generateBlocks, depositCoin, sleep, generateInvoice, payInvoice, payHoldInvoice, settleInvoice }; From 82ef89e4d001bc3638b429fe63f3877b565eefd0 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 15 Aug 2024 14:17:17 +0530 Subject: [PATCH 45/47] feat: add test for invoice handling for lightning latch fix: import error fix: verifyInvoice fn fix: add bolt11 as package fix: bolt11 import error --- .../test/tb04-simple-lightning-latch.mjs | 24 +- clients/libs/web/lightning-latch.js | 2 +- clients/libs/web/main.js | 12 +- clients/libs/web/package.json | 3 +- .../test/tb04-simple-lightning-latch.test.js | 223 +++++++++++++++++- 5 files changed, 248 insertions(+), 16 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 6b025146..14474dcc 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -470,12 +470,16 @@ describe('TB04 - Lightning Latch', function() { expect(transferReceiveResult.isThereBatchLocked).is.false; expect(transferReceiveResult.receivedStatechainIds).is.empty; - const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); - console.log("Preimage: ", preimage); - - const hash = crypto.createHash('sha256') + let hash; + try { + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) .digest('hex') + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } expect(hash).to.equal(paymentHash.hash); }) @@ -536,12 +540,16 @@ describe('TB04 - Lightning Latch', function() { expect(transferReceiveResult.isThereBatchLocked).is.false; expect(transferReceiveResult.receivedStatechainIds).is.empty; - const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); - console.log("Preimage: ", preimage); - - const hash = crypto.createHash('sha256') + let hash; + try { + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) .digest('hex') + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } expect(hash).to.equal(paymentHash.hash); }) diff --git a/clients/libs/web/lightning-latch.js b/clients/libs/web/lightning-latch.js index f105890d..1b083209 100644 --- a/clients/libs/web/lightning-latch.js +++ b/clients/libs/web/lightning-latch.js @@ -132,4 +132,4 @@ const getPaymentHash = async (clientConfig, batchId) => { } -export default { createPreImage, confirmPendingInvoice, retrievePreImage, getPaymentHash }; +export default { createPreImage, confirmPendingInvoice, retrievePreImage, getPaymentHash}; diff --git a/clients/libs/web/main.js b/clients/libs/web/main.js index 5da25a5b..dc50464a 100644 --- a/clients/libs/web/main.js +++ b/clients/libs/web/main.js @@ -9,6 +9,7 @@ import transfer_send from './transfer_send.js'; import transfer_receive from './transfer_receive.js'; import lightningLatch from './lightning-latch.js'; import { v4 as uuidv4 } from 'uuid'; +import * as lightningPayReq from 'bolt11'; const greet = async () => { @@ -127,6 +128,14 @@ const getPaymentHash = async (clientConfig, batchId) => { return await lightningLatch.getPaymentHash(clientConfig, batchId); } +const verifyInvoice = async (clientConfig, batchId, paymentRequest) => { + + const decodedInvoice = lightningPayReq.decode(paymentRequest); + let paymentHash = await getPaymentHash(clientConfig, batchId); + + return paymentHash === decodedInvoice.tagsObject.payment_hash; +} + export default { greet, createWallet, @@ -141,5 +150,6 @@ export default { paymentHash, confirmPendingInvoice, retrievePreImage, - getPaymentHash + getPaymentHash, + verifyInvoice } diff --git a/clients/libs/web/package.json b/clients/libs/web/package.json index 66d889a9..bc325938 100644 --- a/clients/libs/web/package.json +++ b/clients/libs/web/package.json @@ -15,6 +15,7 @@ "dependencies": { "axios": "^1.7.2", "mercury-wasm": "file:../../../wasm/web_pkg/debug", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "bolt11": "^1.4.1" } } 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 d8513fa6..5c03a57c 100644 --- a/clients/tests/web/test/tb04-simple-lightning-latch.test.js +++ b/clients/tests/web/test/tb04-simple-lightning-latch.test.js @@ -203,7 +203,6 @@ describe('TB04 - The sender tries to get the pre-image before the batch is unloc let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; 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); @@ -281,7 +280,7 @@ describe('TB04 - Statecoin sender can recover (resend their coin) after batch ti let transferAddress2 = await mercuryweblib.newTransferAddress(wallet1.name); await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, transferAddress1.transfer_receive, false, paymentHash1.batchId ); - await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, transferAddress2.transfer_receive, false, paymentHash2.batchId); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, transferAddress2.transfer_receive, false, paymentHash1.batchId); let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); @@ -314,7 +313,7 @@ describe('TB04 - Statecoin sender can recover (resend their coin) after batch ti await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId1); await mercuryweblib.confirmPendingInvoice(clientConfig, wallet2.name, statechainId2); - expect(transferReceive.isThereBatchLocked).toBe(false); + expect(transferReceive.isThereBatchLocked).toBe(true); let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; @@ -461,7 +460,7 @@ describe('TB04 - Receiver tries to transfer invoice amount to another invoice be await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, paymentHash.batchId ); - const hashFromServer = await mercurynodejslib.getPaymentHash(clientConfig, paymentHash.batchId); + const hashFromServer = await mercuryweblib.getPaymentHash(clientConfig, paymentHash.batchId); expect(hashFromServer).to.equal(paymentHash.hash); @@ -495,4 +494,218 @@ describe('TB04 - Receiver tries to transfer invoice amount to another invoice be expect(error.message).to.include('failed'); } }); -}, 50000); \ No newline at end of file +}, 50000); + +describe('Statecoin sender sends coin without batch_id (receiver should still be able to receive, but no pre-image revealed)', () => { + 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); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, null); + + const hashFromServer = await mercuryweblib.getPaymentHash(clientConfig, paymentHash.batchId); + + expect(hashFromServer).to.equal(paymentHash.hash); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + 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); + + let hashPreImage; + try { + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + hashPreImage = await sha256(preimage); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + + expect(hashPreImage).toEqual(paymentHash.hash); + }); +}, 50000); + +describe('TB04 - Sender sends coin without batch_id, and then resends to a different address (to attempt to steal), and then attempts to retrieve the pre-image, should fail (and LN payment cannot be claimed)', () => { + 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"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_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); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, null); + + const transferAddressSecond = await mercuryweblib.newTransferAddress(wallet3.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddressSecond.transfer_receive, false, null); + + const hashFromServer = await mercuryweblib.getPaymentHash(clientConfig, paymentHash.batchId); + + expect(hashFromServer).to.equal(paymentHash.hash); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId); + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + let hashPreImage; + try { + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + hashPreImage = await sha256(preimage); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + + expect(hashPreImage).toEqual(paymentHash.hash); + }); +}, 50000); + +describe('Coin receiver creates a non hold invoice, and sends to sender (i.e. an invoice with the a different payment hash). Sender should be able to determine this.', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb04"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_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 paymentHashSecond = "b4eab5e663aebe5fc645865b27b33c04c4e057e7c844fa61519df6de1398cdb3" + const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + + const isInvoiceValid = await mercuryweblib.verifyInvoice(clientConfig, paymentHash.batchId, invoiceSecond.payment_request); + expect(isInvoiceValid).is.false; + }); +}, 50000); From d8a31d4e4a00ce78d1192a88bf5871728d9d1653 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 15 Aug 2024 19:09:08 +0530 Subject: [PATCH 46/47] feat: add endpoint for decode invoice fix: invoice from post request fix: invoice payment hash debug: invoice decode fix: payment hash in invoice generation debug: invoice verification fix: extract hash from decoded invoice fix: long running test due to hold invoice fix: payment hashes fix: web client tests running order fix: verify invoice test --- .github/workflows/tests.yml | 4 ++-- .../test/tb04-simple-lightning-latch.mjs | 10 ++++---- clients/libs/web/main.js | 10 ++++---- clients/libs/web/package.json | 3 +-- clients/tests/web/package.json | 1 + clients/tests/web/server-regtest.cjs | 20 ++++++++++++++++ clients/tests/web/test-utils.js | 19 ++++++++++++++- .../test/tb04-simple-lightning-latch.test.js | 24 +++++++------------ 8 files changed, 61 insertions(+), 30 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1faf9161..c94282d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -168,13 +168,13 @@ jobs: run: | cd clients/tests/web sudo chmod -R 755 ./data_bitcoin_regtest - npx vitest --browser.name=chromium --browser.headless + npx vitest --browser.name=chromium --browser.headless - name: Run Client-Side Tests run: | cd clients/apps/nodejs node test_basic_workflow2.js node test_atomic_swap.js - mocha ./test/tb04-simple-lightning-latch.mjs --exit + mocha ./test/tb04-simple-lightning-latch.mjs --exit - name: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 14474dcc..0ed4e187 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -409,7 +409,7 @@ describe('TB04 - Lightning Latch', function() { expect(hash).to.equal(paymentHash.hash); - const paymentHashSecond = "f768c404215f9fb5731c32c00fe7a057fc181d7695de447b334380d90674db34" + const paymentHashSecond = "b1f55a2f2eabb08ed9d6e15a053a6ac84d04d1c017de5a42caaec98b8d2ff738" const invoiceSecond = await generateInvoice(paymentHashSecond, amount); try { @@ -476,12 +476,11 @@ describe('TB04 - Lightning Latch', function() { hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) .digest('hex') + expect(hash).to.equal(paymentHash.hash); } catch (error) { console.error('Error:', error); expect(error.message).to.include('failed'); } - - expect(hash).to.equal(paymentHash.hash); }) }) @@ -546,12 +545,11 @@ describe('TB04 - Lightning Latch', function() { hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) .digest('hex') + expect(hash).to.equal(paymentHash.hash); } catch (error) { console.error('Error:', error); expect(error.message).to.include('failed'); } - - expect(hash).to.equal(paymentHash.hash); }) }) @@ -586,7 +584,7 @@ describe('TB04 - Lightning Latch', function() { const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet_1_name, coin.statechain_id); - const paymentHashSecond = "ddddf1cd1e2ca1986e29b20401b555fbaa4a8502626a7afc37c47ae06a46d918" + const paymentHashSecond = "a3b5f72d4e8cb07cd9a6e17c054a7ac84d05e1c018fe5b43cbbef98a9d3ff839" const invoiceSecond = await generateInvoice(paymentHashSecond, amount); const isInvoiceValid = await mercurynodejslib.verifyInvoice(clientConfig, paymentHash.batchId, invoiceSecond.payment_request); diff --git a/clients/libs/web/main.js b/clients/libs/web/main.js index dc50464a..8d3abec7 100644 --- a/clients/libs/web/main.js +++ b/clients/libs/web/main.js @@ -9,7 +9,7 @@ import transfer_send from './transfer_send.js'; import transfer_receive from './transfer_receive.js'; import lightningLatch from './lightning-latch.js'; import { v4 as uuidv4 } from 'uuid'; -import * as lightningPayReq from 'bolt11'; +import { decodeInvoice } from '../../tests/web/test-utils.js'; const greet = async () => { @@ -130,10 +130,12 @@ const getPaymentHash = async (clientConfig, batchId) => { const verifyInvoice = async (clientConfig, batchId, paymentRequest) => { - const decodedInvoice = lightningPayReq.decode(paymentRequest); + const decodedInvoice = await decodeInvoice(paymentRequest); let paymentHash = await getPaymentHash(clientConfig, batchId); - - return paymentHash === decodedInvoice.tagsObject.payment_hash; + console.log("Decoded invoice: ", decodedInvoice); + + const paymentHashFromInvoice = decodedInvoice.tags.find(tag => tag.tagName === "payment_hash")?.data; + return paymentHash === paymentHashFromInvoice; } export default { diff --git a/clients/libs/web/package.json b/clients/libs/web/package.json index bc325938..66d889a9 100644 --- a/clients/libs/web/package.json +++ b/clients/libs/web/package.json @@ -15,7 +15,6 @@ "dependencies": { "axios": "^1.7.2", "mercury-wasm": "file:../../../wasm/web_pkg/debug", - "uuid": "^10.0.0", - "bolt11": "^1.4.1" + "uuid": "^10.0.0" } } diff --git a/clients/tests/web/package.json b/clients/tests/web/package.json index 466c1efb..66de7bfe 100644 --- a/clients/tests/web/package.json +++ b/clients/tests/web/package.json @@ -14,6 +14,7 @@ "dependencies": { "axios": "^1.7.2", "body-parser": "^1.20.2", + "bolt11": "^1.4.1", "cors": "^2.8.5", "express": "^4.19.2", "install": "^0.13.0", diff --git a/clients/tests/web/server-regtest.cjs b/clients/tests/web/server-regtest.cjs index 2e4caa1d..de77656b 100644 --- a/clients/tests/web/server-regtest.cjs +++ b/clients/tests/web/server-regtest.cjs @@ -5,6 +5,7 @@ const cors = require('cors') const port = 3000 const util = require('node:util'); const exec = util.promisify(require('node:child_process').exec); +const lightningPayReq = require('bolt11'); app.use(bodyParser.json()) app.use(cors()) @@ -165,6 +166,25 @@ app.post('/settle_invoice', async (req, res) => { } }) +app.post('/decode_invoice', async (req, res) => { + const { paymentRequest } = req.body + + if (typeof paymentRequest === 'string') { + console.log(`Decoding invoice ...`) + + try { + const invoice = lightningPayReq.decode(paymentRequest); + res.status(200).send({ message: 'Invoice generated successfully', invoice }) + } catch (error) { + console.log(error.message); + res.status(500).send({ message: error.message }) + } + + } else { + res.status(400).send({ message: 'Invalid input' }) + } +}) + app.listen(port, () => { console.log(`Docker server listening on port ${port}`) }) \ No newline at end of file diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index 42f39dfe..9b953c45 100644 --- a/clients/tests/web/test-utils.js +++ b/clients/tests/web/test-utils.js @@ -105,4 +105,21 @@ const settleInvoice = async (preimage) => { } } -export { generateBlocks, depositCoin, sleep, generateInvoice, payInvoice, payHoldInvoice, settleInvoice }; +const decodeInvoice = async (paymentRequest) => { + const body = { + paymentRequest + }; + + const url = `http://0.0.0.0:3000/decode_invoice`; + + let response = await axios.post(url, body); + + if (response.status == 200) { + const invoice = response.data.invoice; + return invoice; + } else { + throw new Error(`Failed to decode invoice`); + } +} + +export { generateBlocks, depositCoin, sleep, generateInvoice, payInvoice, payHoldInvoice, settleInvoice, decodeInvoice }; 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 5c03a57c..70e3934e 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, sleep, generateInvoice, payInvoice, payHoldInvoice, settleInvoice } from '../test-utils.js'; +import { generateBlocks, depositCoin, sleep, generateInvoice, payInvoice, payHoldInvoice, settleInvoice, decodeInvoice } from '../test-utils.js'; async function sha256(preimage) { let buffer; @@ -315,11 +315,6 @@ describe('TB04 - Statecoin sender can recover (resend their coin) after batch ti expect(transferReceive.isThereBatchLocked).toBe(true); - let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; - - 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); let hashPreImage = await sha256(preimage); @@ -454,7 +449,8 @@ describe('TB04 - Receiver tries to transfer invoice amount to another invoice be const invoice = await generateInvoice(paymentHash.hash, amount); - payHoldInvoice(invoice.payment_request); + await payHoldInvoice(invoice.payment_request); + console.log(invoice); let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); @@ -484,8 +480,8 @@ describe('TB04 - Receiver tries to transfer invoice amount to another invoice be expect(hashPreImage).toEqual(paymentHash.hash); - const paymentHashSecond = "4f67f0a4bc4a8a6a8ecb944e9b748ed7c27655fbdb4c4d3f045d7f18c1e4de64" - const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + const paymentRequest = "lnbcrt10u1pnt70qdpp55w6lwt2w3jc8ekdxu97q2jn6epxstcwqrrl9ks7thmuc48fllqusdqqcqzzsxqyz5vqsp54ay50wuys4sjjmzsxms3hv6pf294rnys8y9k93t8dz6edpqvue5s9qxpqysgqwpp3m8a77cc6nt2yvxvkcql5avzf50ga95dxrtugfv2p5wdqfjy4fd44srh7undwaa7tkju88c0z9zf9ryp5tdr3hv7t9699l4qfpccpp7fqx7" + const invoiceSecond = await decodeInvoice(paymentRequest); try { await payInvoice(invoiceSecond.payment_request); @@ -568,12 +564,11 @@ describe('Statecoin sender sends coin without batch_id (receiver should still be try { const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); hashPreImage = await sha256(preimage); + expect(hashPreImage).toEqual(paymentHash.hash); } catch (error) { console.error('Error:', error); expect(error.message).to.include('failed'); } - - expect(hashPreImage).toEqual(paymentHash.hash); }); }, 50000); @@ -649,12 +644,11 @@ describe('TB04 - Sender sends coin without batch_id, and then resends to a diffe try { const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); hashPreImage = await sha256(preimage); + expect(hashPreImage).toEqual(paymentHash.hash); } catch (error) { console.error('Error:', error); expect(error.message).to.include('failed'); } - - expect(hashPreImage).toEqual(paymentHash.hash); }); }, 50000); @@ -702,8 +696,8 @@ describe('Coin receiver creates a non hold invoice, and sends to sender (i.e. an const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); - const paymentHashSecond = "b4eab5e663aebe5fc645865b27b33c04c4e057e7c844fa61519df6de1398cdb3" - const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + const paymentRequest = "lnbcrt10u1pnt70fjpp53gj23vyghz5ggpc3ppkttxkqkywfz2dfwgalsa9ynylwe28lscqqdqqcqzzsxqyz5vqsp5l640fse8wx9773rpxlqdgv95t4swhpeueuta358404q6exvhzk5q9qxpqysgqzytwwj9n6s5dt4jd6vgvy9rmtcq4cwhe4h98asrpe3u3pqp3tqkrcvz5fm6uvr76akr9ml07sz70cdx45d64h9dpmnd2ua5qdpp79rcpqpucyd" + const invoiceSecond = await decodeInvoice(paymentRequest); const isInvoiceValid = await mercuryweblib.verifyInvoice(clientConfig, paymentHash.batchId, invoiceSecond.payment_request); expect(isInvoiceValid).is.false; From c96e69033963d105b7b7f785225fd7c8ab982e66 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Fri, 16 Aug 2024 21:25:19 +0530 Subject: [PATCH 47/47] refactor: verify invoice test --- clients/tests/web/test/tb04-simple-lightning-latch.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 70e3934e..e5b7c158 100644 --- a/clients/tests/web/test/tb04-simple-lightning-latch.test.js +++ b/clients/tests/web/test/tb04-simple-lightning-latch.test.js @@ -697,9 +697,8 @@ describe('Coin receiver creates a non hold invoice, and sends to sender (i.e. an const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); const paymentRequest = "lnbcrt10u1pnt70fjpp53gj23vyghz5ggpc3ppkttxkqkywfz2dfwgalsa9ynylwe28lscqqdqqcqzzsxqyz5vqsp5l640fse8wx9773rpxlqdgv95t4swhpeueuta358404q6exvhzk5q9qxpqysgqzytwwj9n6s5dt4jd6vgvy9rmtcq4cwhe4h98asrpe3u3pqp3tqkrcvz5fm6uvr76akr9ml07sz70cdx45d64h9dpmnd2ua5qdpp79rcpqpucyd" - const invoiceSecond = await decodeInvoice(paymentRequest); - const isInvoiceValid = await mercuryweblib.verifyInvoice(clientConfig, paymentHash.batchId, invoiceSecond.payment_request); + const isInvoiceValid = await mercuryweblib.verifyInvoice(clientConfig, paymentHash.batchId, paymentRequest); expect(isInvoiceValid).is.false; }); }, 50000);