diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 54a7577d..bc05bc7c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,13 +63,7 @@ jobs: echo "Container ID: $container_id" docker logs $container_id docker exec $container_id \ - curl -X POST http://0.0.0.0:8000/deposit/init/pod \ - -H "Content-Type: application/json" \ - -d '{ - "auth_key": "f461775606ffc86e3f6e3115ff425d371b0f68cc59ad8cf71375c0e08c2ee8e9", - "token_id": "616f505d-b94c-45cf-b251-833e4fa14fa1", - "signed_token_id": "7401ac1f792f56d5357997f9846b5045656758f4afece4c51b73472bd338e97da3d167733502cf62d77c1169bd89bfaa0c9c5fcc26d75190e7a3a3fd2f83ae0a" - }' + curl http://0.0.0.0:8000/info/config - name: Get Public Key run: | docker exec $(docker ps -qf "name=enclave") \ @@ -112,6 +106,7 @@ jobs: run: | cd clients/apps/nodejs node test_basic_workflow2.js + node test_atomic_swap.js - name: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/clients/apps/nodejs/test_atomic_swap.js b/clients/apps/nodejs/test_atomic_swap.js new file mode 100644 index 00000000..7f046942 --- /dev/null +++ b/clients/apps/nodejs/test_atomic_swap.js @@ -0,0 +1,868 @@ +const assert = require('node:assert/strict'); +const mercurynodejslib = require('mercurynodejslib'); +const { CoinStatus } = require('mercurynodejslib/coin_enum'); +const { sleep, createWallet, generateBlock, depositCoin } = require('./test_utils'); +const client_config = require('./client_config'); + +async function atomicSwapSuccess(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + let options = { + generateBatchId: true + }; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + console.log("transferReceiveResult: ", transferReceiveResult); + assert(transferReceiveResult.isThereBatchLocked === true); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); + + // transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + // received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + // console.log("received_statechain_ids: ", received_statechain_ids_w3); + + // assert(received_statechain_ids_w3.length > 0); + // assert(received_statechain_ids_w3[0] == coin3.statechain_id); +} + +async function atomicSwapWithSecondBatchIdMissing(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + let options = { + generateBatchId: true + }; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin3); + + transfer_address_w3.batch_id = ""; + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + let received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w3); + + assert(received_statechain_ids_w3.length > 0); + assert(received_statechain_ids_w3[0] == coin3.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); +} + +async function atomicSwapWithoutFirstBatchId(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + let options = { + generateBatchId: true + }; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, null); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + let received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w3); + + assert(received_statechain_ids_w3.length > 0); + assert(received_statechain_ids_w3[0] == coin3.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); +} + +async function atomicSwapWithTimeout(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + let options = { + generateBatchId: true + }; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + let received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + await sleep(20000); + + assert(transferReceiveResult.isThereBatchLocked === true); + + let errorMessage; + console.error = (msg) => { + errorMessage = msg; + }; + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + // Assert the captured error message + const expectedMessage = 'Failed to update transfer message'; + assert.ok(errorMessage.includes(expectedMessage)); + + transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin3); + + coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin4); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); + + // transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + // received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + // console.log("received_statechain_ids: ", received_statechain_ids_w3); + + // assert(received_statechain_ids_w3.length > 0); + // assert(received_statechain_ids_w3[0] == coin3.statechain_id); +} + +async function atomicSwapWithFirstPartySteal(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + let options = { + generateBatchId: true + }; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin4); + + let transfer_address_w3_for_steal = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + console.log("transfer address for steal", transfer_address_w3_for_steal); + + let coin_to_steal = undefined; + try { + coin_to_steal = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3_for_steal.transfer_receive, transfer_address_w3); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'expected a string argument, found undefined'; + assert.ok(error.message.includes(expectedMessage)); + } + + console.log("coin to steal transferSend: ", coin_to_steal); + + let received_statechain_ids_w3 = undefined; + try { + received_statechain_ids_w3 = mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + await sleep(3000); + + let received_statechain_ids_w4 = undefined; + try { + received_statechain_ids_w4 = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + + try { + received_statechain_ids_w3 = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } +} + +async function atomicSwapWithSecondPartySteal(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + let options = { + generateBatchId: true + }; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, options); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, transfer_address_w3); + console.log("coin transferSend: ", coin4); + + let transfer_address_w4_for_steal = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, options); + console.log("transfer address for steal", transfer_address_w4_for_steal); + + let coin_to_steal = undefined; + try { + coin_to_steal = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4_for_steal.transfer_receive, transfer_address_w4); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'expected a string argument, found undefined'; + assert.ok(error.message.includes(expectedMessage)); + } + + console.log("coin to steal transferSend: ", coin_to_steal); + + let received_statechain_ids_w3 = undefined; + try { + received_statechain_ids_w3 = mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + + let received_statechain_ids_w4 = undefined; + try { + received_statechain_ids_w4 = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + + try { + received_statechain_ids_w3 = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } +} + +(async () => { + + try { + 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); + 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); + 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); + 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); + 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); + 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); + console.log("Completed test for Second party tries to steal within timeout"); + + process.exit(0); // Exit successfully + } catch (error) { + console.error("Test encountered an error:", error); + process.exit(1); // Exit with failure + } +})(); \ No newline at end of file diff --git a/clients/apps/nodejs/test_basic_workflow2.js b/clients/apps/nodejs/test_basic_workflow2.js index 42aadf80..11313341 100644 --- a/clients/apps/nodejs/test_basic_workflow2.js +++ b/clients/apps/nodejs/test_basic_workflow2.js @@ -4,88 +4,11 @@ const assert = require('node:assert/strict'); const mercurynodejslib = require('mercurynodejslib'); const { CoinStatus } = require('mercurynodejslib/coin_enum'); const client_config = require('./client_config'); -const ElectrumCli = require('@mempool/electrum-client'); -const sqlite3 = require('sqlite3').verbose(); const sqlite_manager = require('../../libs/nodejs/sqlite_manager'); const mercury_wasm = require('mercury-wasm'); const transaction = require('../../libs/nodejs/transaction'); const utils = require('../../libs/nodejs/utils'); - -async function removeDatabase() { - try { - const clientConfig = client_config.load(); - const { stdout, stderr } = await exec(`rm ${clientConfig.databaseFile}`); - // console.log('stdout:', stdout); - // console.error('stderr:', stderr); - } catch (e) { - console.error(e); - } -} - -const getDatabase = async (clientConfig) => { - const databaseFile = clientConfig.databaseFile; - const db = new sqlite3.Database(databaseFile); - await sqlite_manager.createTables(db); - return db; -} - -const sleep = (ms) => { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function createWallet(clientConfig, walletName) { - - let wallet = await mercurynodejslib.createWallet(clientConfig, walletName); - assert.equal(wallet.name, walletName); - - // TODO: add more assertions -} - -const getElectrumClient = async (clientConfig) => { - - const urlElectrum = clientConfig.electrumServer; - const urlElectrumObject = new URL(urlElectrum); - - const electrumPort = parseInt(urlElectrumObject.port, 10); - const electrumHostname = urlElectrumObject.hostname; - const electrumProtocol = urlElectrumObject.protocol.slice(0, -1); - - const electrumClient = new ElectrumCli(electrumPort, electrumHostname, electrumProtocol); - await electrumClient.connect(); - - return electrumClient; -} - -async function generateBlock(numBlocks) { - 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`); - - const clientConfig = client_config.load(); - const electrumClient = await getElectrumClient(clientConfig); - const block_header = await electrumClient.request('blockchain.headers.subscribe'); - const blockheight = block_header.height; - // console.log("Current block height: ", blockheight); -} - -async function depositCoin(clientConfig, wallet_name, amount, deposit_info) { - - deposit_info["amount"] = amount; - // console.log("deposit_coin: ", deposit_info); - - const amountInBtc = amount / 100000000; - - // 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}`; - exec(sendBitcoinCommand); - // console.log(`Sent ${amountInBtc} BTC to ${deposit_info.deposit_address}`); - await generateBlock(3); - } catch (error) { - console.error('Error sending Bitcoin:', error.message); - return; - } -} +const { getDatabase, sleep, createWallet, getElectrumClient, generateBlock, depositCoin } = require('./test_utils'); async function walletTransfersToItselfAndWithdraw(clientConfig, wallet_name) { @@ -586,9 +509,9 @@ async function interruptBeforeSignSecond(clientConfig, wallet_1_name, wallet_2_n const electrumClient = await getElectrumClient(clientConfig); - let options = transfer_address.transfer_receive; + let options = transfer_address; - let batchId = (options && options.batchId) || null; + let batchId = (options && options.batch_id) || null; let wallet = await sqlite_manager.getWallet(db, wallet_1_name); @@ -645,7 +568,28 @@ async function interruptBeforeSignSecond(clientConfig, wallet_1_name, wallet_2_n // const new_x1 = await get_new_x1(clientConfig, statechain_id, signed_statechain_id, new_auth_pubkey, batchId); - coin = await new_transaction(clientConfig, electrumClient, coin, transfer_address.transfer_receive, isWithdrawal, qtBackupTx, block_height, wallet.network); + const signed_tx = await new_transaction(clientConfig, electrumClient, coin, transfer_address.transfer_receive, isWithdrawal, qtBackupTx, block_height, wallet.network); + + transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive); + + console.log("coin ", coin); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids); + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + + // Coin withdrawal + let withdraw_address = "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"; + + let txid = await mercurynodejslib.withdrawCoin(clientConfig, wallet_2_name, coin.statechain_id, withdraw_address, null); + + console.log("txid: ", txid); } async function interruptSignWithElectrumUnavailability(clientConfig, wallet_1_name, wallet_2_name) { @@ -1004,8 +948,6 @@ async function transferSendCoinExpiryBySending(clientConfig, wallet_1_name, wall (async () => { - removeDatabase(); - try { const clientConfig = client_config.load(); @@ -1014,7 +956,6 @@ async function transferSendCoinExpiryBySending(clientConfig, wallet_1_name, wall await createWallet(clientConfig, wallet_1_name); await createWallet(clientConfig, wallet_2_name); await walletTransfersToItselfAndWithdraw(clientConfig, wallet_1_name); - console.log("walletTransfersToItselfAndWithdraw test completed successfully."); // await walletTransfersToAnotherAndBroadcastsBackupTx(clientConfig, wallet_1_name, wallet_2_name); diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js new file mode 100644 index 00000000..93a40913 --- /dev/null +++ b/clients/apps/nodejs/test_utils.js @@ -0,0 +1,87 @@ + +const util = require('node:util'); +const ElectrumCli = require('@mempool/electrum-client'); +const sqlite3 = require('sqlite3').verbose(); +const mercurynodejslib = require('mercurynodejslib'); +const exec = util.promisify(require('node:child_process').exec); +const assert = require('node:assert/strict'); +const client_config = require('./client_config'); +const sqlite_manager = require('../../libs/nodejs/sqlite_manager'); + +const removeDatabase = async () => { + try { + const clientConfig = client_config.load(); + const { stdout, stderr } = await exec(`rm ${clientConfig.databaseFile}`); + // console.log('stdout:', stdout); + // console.error('stderr:', stderr); + } catch (e) { + console.error(e); + } +} + +const getDatabase = async (clientConfig) => { + const databaseFile = clientConfig.databaseFile; + const db = new sqlite3.Database(databaseFile); + await sqlite_manager.createTables(db); + return db; +} + +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +const createWallet = async (clientConfig, walletName) => { + + let wallet = await mercurynodejslib.createWallet(clientConfig, walletName); + assert.equal(wallet.name, walletName); + + // TODO: add more assertions +} + +const getElectrumClient = async (clientConfig) => { + + const urlElectrum = clientConfig.electrumServer; + const urlElectrumObject = new URL(urlElectrum); + + const electrumPort = parseInt(urlElectrumObject.port, 10); + const electrumHostname = urlElectrumObject.hostname; + const electrumProtocol = urlElectrumObject.protocol.slice(0, -1); + + const electrumClient = new ElectrumCli(electrumPort, electrumHostname, electrumProtocol); + await electrumClient.connect(); + + return electrumClient; +} + +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"`; + await exec(generateBlockCommand); + // console.log(`Generated ${numBlocks} blocks`); + + const clientConfig = client_config.load(); + const electrumClient = await getElectrumClient(clientConfig); + const block_header = await electrumClient.request('blockchain.headers.subscribe'); + const blockheight = block_header.height; + // console.log("Current block height: ", blockheight); +} + +const depositCoin = async (clientConfig, wallet_name, amount, deposit_info) => { + + deposit_info["amount"] = amount; + // console.log("deposit_coin: ", deposit_info); + + const amountInBtc = amount / 100000000; + + // 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}`; + exec(sendBitcoinCommand); + // console.log(`Sent ${amountInBtc} BTC to ${deposit_info.deposit_address}`); + await generateBlock(3); + } catch (error) { + console.error('Error sending Bitcoin:', error.message); + return; + } +} + +module.exports = { removeDatabase, getDatabase, sleep, createWallet, getElectrumClient, generateBlock, depositCoin }; diff --git a/clients/libs/nodejs/index.js b/clients/libs/nodejs/index.js index 00a5a240..6d253c48 100644 --- a/clients/libs/nodejs/index.js +++ b/clients/libs/nodejs/index.js @@ -159,7 +159,7 @@ const transferSend = async (clientConfig, walletName, statechainId, toAddress, o const electrumClient = await getElectrumClient(clientConfig); - let batchId = (options && options.batchId) || null; + let batchId = (options && options.batch_id) || null; await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); diff --git a/clients/libs/nodejs/transfer_receive.js b/clients/libs/nodejs/transfer_receive.js index 530ff660..0dc2bd69 100644 --- a/clients/libs/nodejs/transfer_receive.js +++ b/clients/libs/nodejs/transfer_receive.js @@ -80,7 +80,7 @@ const execute = async (clientConfig, electrumClient, db, wallet_name) => { receivedStatechainIds.push(messageResult.statechainId); } } catch (error) { - // console.error(`Error: ${error.message}`); + console.error(`Error: ${error.message}`); continue; } @@ -101,7 +101,7 @@ const execute = async (clientConfig, electrumClient, db, wallet_name) => { } } } catch (error) { - // console.error(`Error: ${error.message}`); + console.error(`Error: ${error.message}`); continue; } } diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 0bdefbc3..7cce6ae7 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -57,6 +57,7 @@ services: LOCKHEIGHT_INIT: 1100 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}]' ports: - "8000:8000" diff --git a/server/.env_example b/server/.env_example index 210c9eb1..ba71e2f1 100644 --- a/server/.env_example +++ b/server/.env_example @@ -2,4 +2,5 @@ NETWORK = LOCKHEIGHT_INIT = LH_DECREMENT = CONNECTION_STRING = +BATCH_TIMEOUT = ENCLAVES =