From 73c9bd4fcf5dc55ea2ea8967b9b5653fedf2a9c8 Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Wed, 28 Aug 2024 13:59:48 +0100 Subject: [PATCH] release 0.1.1 (#88) * Organize better the REact client * Centralize the Electrum client management, you simplifying the process of connecting to and disconnecting from the Electrum server * Add broadcast backup function * Add withdrawal function * Add transfer-send function * Add transfer-receive function * Added automatic coin update function, but is not being used because asynchronous calls are not currently supported * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * Changing React application structure to handle concurrency * add token server * fixes * Remove thunks * Improve backup transactions database and view * Improve GUI * Fix some warnings * Fix some warnings * Improve update function * Improve GUI * Remove DB transaction to avoid race condition * config * config * add token server * Update tokens.md * Update Token struct * Add WASM packages * Improve UI * Change react-app config file * chore: add changes to get locofy-ui working * chore: add key to wizard * chore: add deposit process ui * chore: add token ui logic * chore: huge locofy ai changes, remove old code as wasn't scalable, learned new locofy formats * chore: fix up icons * chore: hook token info card and deposit page * chore: update token card UI * chore: add more validation to deposit process, and modal windows * chore: update deposit pages * add expiry time * fix * fix * chore: finish the tab ui logic * chore: add changes to deposit, hook up live methods to the deposit process, add loading spinners and validation * add key check * fix serialisation * chore: add token insertion into wallet object, various other changes, wasm error in deposit 2 * chore: fix wallet load code * chore: add back in validation for wizard page 2, cleanup the main header panel ui * chore: refactor, add a logged in wallet hook, add validation to wallet page 1 * chore: show seed key in wizard states * chore: fix error if coins / activities is null * Modify Token struct * chore: fix error message ordering in wizard page * chore: move order of destructure variable in main page * chore: fix coin item, display some info to user about their statecoin * chore: add changes to main header panel, add up btc values * chore: fix deposit addresses * chore: change to testnet server, add withdraw ui components * chore: fix more withdrawal logic, fix bug in deposit 3 page * chore: do not show withdrawn coins * chore: add send page and logic * chore: fix receive page * chore: fix activities table * chore: fix some navigation errors and settings page errors * Add Settings property to wallet * chore: add password encrypted db wallets, add error message handling for it, remove certain coins from UI * chore: add backup from file/load into db * chore: add changes to main load wallet * chore: get latest password * chore: change settings page * chore: remove manage transactions from settings page as will be in modal * chore: add settings ui hooked with redux state and notifications * chore: add the reloading of wallets within load wallet page * chore: remove debugging, add check on wizard for encrypted wallets instead of live wallets * chore: fix loading from backup file to include backup tx * chore: add more responsive main bar page, and add modal ui * chore: fix coin modal and help page * chore: remove amount * chore: downgrade sql * chore: enable debug mode * chore: replace icon of app * chore: huge fix for resources in wallet * chore: add build * chore: add dev branch * chore: fix clearnet config issues, start a build * chore: change actions * chore: remove mainnet * chore: remove mainnet * chore: change github actions * chore: change app icon * chore: change app icon * chore: remove unused files * version: update to 0.1.1 * DB file path * Update atomic_transfer.md * Update atomic_transfer.md typo * Update atomic_transfer.md Fixed typo * Update atomic_transfer.md * Update protocol.md * Update transfer_sender_sequence.md * Update transfer_receiver_sequence.md * Update transfer_sender_sequence.md * Update transfer_receiver_sequence.md * Update protocol.md * Add ChaCha20-Poly1305 encryption scheme This commit adds ChaCha20-Poly1305 algorithm to be used to encrypt any enclave data instead of using the SGX built-in functions. This way, the data can be decrypted on another machine as long the same encryption key be used. SGX functions are still used to seal and unseal the encryption key. * Add password to add-mnemonic call * Add Dockerfiles to SGX SIM mode * Add docker files to the server * Update protocol.md * Update README.md * Update deposit_sequence.md * Update transfer_sender_sequence.md * Update transfer_receiver_sequence.md * Add docker files to the enclave - HW mode * Remove r2 and blind commitments * Change HRP to 'ml' and 'tml' * Add address validation and remove standalone rust client * Update WASM and rename lib * Update atomic_transfer.md * Update atomic_transfer.md * Add UniFFI * Re-add nodejs client * Add kotlin client * Use TransferReceiverPostResponsePayload * Add Atomic transfer * Use statechain_info to verify transfers * Add automatic seed generation and replication * Update react-app package.json * Increase the maximum amount of time to spend waiting for a connection to 30s * Allow coin to be withdrawn if in IN_TRANSFER status * Fix minor errors regarding IN_TRANSFER status * Update Kotlin client * fix: server config with env for mercury & token server * feat: add docker build for github actions * feat: add dev branch to yaml * fix: token server port * fix: expose port in dockerfile for token-server * fix: move Rocket.toml from sub folder to parent folder * fix: add tls feature to sqlx * fix: route for endpoint /info * feat: add explorer * feat: add keylist cronjob * fix: version for docker images in ci/cd * fix: add dev2 branch to ci/cd * fix: import error with token server * fix: mercury server start cmd * fix: start cmd for mercury server * debug: mercury server dockerfile * fix: config for mercury and token server * fix: mercury server dockerfile * fix: dockerfile for mercury server * Fix the mainnet address conversion * Add address reuse functionality * Create rust client lib * Create nodejs client lib * Update ECIES package and restructure project folders * Add a new test file that uses the nodejs lib * Fix docker error preventing Mercury server from starting * Move signature validation from rust client lib to Mercury lib * Refactor `clients/libs/rust/src/transfer_receiver.rs`. No change in behavior * Use `validateSignatureScheme` in `clients/libs/nodejs/transfer_receive.js` * Use `validateSignatureScheme` in `clients/apps/kotlin/TransferReceive.kt` * Add web client lib * Add `max_fee_rate` setting to rust lib * Add `max_fee_rate` setting to nodejs lib * Create client_guide.md * Add `max_fee_rate` setting to web lib * Update client_guide.md * Update client_guide.md * Update client_guide.md * Add `max_fee_rate` setting to Kotlin client * Add multiple enclave support * Remove outdated react-app * Handle fee rate < 0 in broadcast command * Feat/fee unit * Add integration tests on regtest mode ------- Co-authored-by: DhananjayPurohit * Add Lightning Latch atomic transfer * Improve transfer message verification * Add basic Rust integration tests * Add `batchtimeout` to `/info/config` * fix: directory for Rocket.toml * feat: add tests for coin expiry * fix: add test for transfer-sender that make coin expired by sending * fix: assertion for broadcast tx * fix: add dev3 for integration tests * Add TB02 - Transfer Address Reuse * Add TM01 - Sender Double Spends * chore: remove dev3 branch from integration tests * Add TA01 - 'signSecond not called' and return the pubnonce if the challenge is null * Refactor transfer-receive to make it non-blocking * Refactor transfer-receive in nodeJS library to make it non-blocking * Adjust tests to the new transferReceive function * Add web lib tests * Fix minor error in nodeJS transfer-receive * Add lightning latch functions to the nodeJS client * Add Rust lightning latch test * Add nodeJs lightning latch test * Feat/tests for atomic swap (#72) --------- Co-authored-by: S. Santos * Add lightning latch functions to web client * Improve nodeJS API parameters * Change fee rate to f64 instead of u64 * fix: ci build on main branch only (#78) * Handle multiple deposit transactions * Add multiple deposit support to the nodeJS client * Update test_basic_workflow2 to support duplicated deposits * Update web client to support multiple deposits * Automate web client testing * Add Vitest to web client tests * Update Dockerfile (#81) * Update Dockerfile * Create Rocket2.toml * Update Dockerfile * Revert "Update Dockerfile (#81)" (#83) This reverts commit e37b62725f8c54b20be36383791a147035f6e5c9. * Add GET `/transfer/paymenthash/` * Add payment hash verification to nodeJS client * Fix/dockerfile token server (#85) * fix: token server dockerfile * fix: build release for token server * fix: upgrade sqlx version * Add payment hash verification to web client * Add log crate to the server * fix: The pre-image is now only revealed if the transfer is unlocked. * Set getrandom version * Add rust-toolchain file to wasm project * feat: add LND containers for testing (#77) * feat: add LND containers for testing * fix: lightning latch test run on actions * fix: install mocha * fix: wallet names * feat: add test to get preimage before batch unlock * feat: add test for sender recover coin after batch timeout * fix: increase test timeout * feat: wallet creation on lnd nodes * feat: wallet creation on lnd nodes * fix: wallet creation on start of lnd * fix: wallet unlock flag * debug: lnd node logs * fix: add sleep for lnd to sync with latest block * fix: upgrade lnd version * fix: zmq flags in bitcoind config * fix: port for node second to listen * feat: add script to open channel * feat: add fn to generate invoice * fix: test failure * feat: add test to steal hold invoice amount * fix: await for hold invoice payment * fix: import error * fix: upgrade docker image version in actions * fix: install docker compose * fix: container name * fix: network name * fix: add assertion for invoice payment failure * feat: add test for sender sends coin without batch_id * feat: add fn for invoice verification against payment hash * feat: add test for invoice verification fix: walletname for invoice verification test fix: payment hash for invoice verification test * feat: setup web client tests on actions * 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 * 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 * 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 * feat: add test for atomic swap for web client * feat: add test for coin steal in atomic swap for web client * feat: add test for lightning latch * feat: add test for invoice for lightning latch * Fix/dockerfile token server (#85) * fix: token server dockerfile * fix: build release for token server * fix: upgrade sqlx version * Add payment hash verification to web client * Add log crate to the server * fix: The pre-image is now only revealed if the transfer is unlocked. * fix: import error * feat: add endpoints for invoice ops on node server fix: invoice generation from node server * feat: add test for invoice handling for lightning latch fix: import error fix: verifyInvoice fn fix: add bolt11 as package fix: bolt11 import error * 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 * refactor: verify invoice test --------- Co-authored-by: S. Santos * Update Cargo.toml * fix: upgrade sqlx version * Update sqlx and toolchain --------- Co-authored-by: S. Santos Co-authored-by: Thomas Trevethan Co-authored-by: rk16449 Co-authored-by: rk16449 <32778886+rk16449@users.noreply.github.com> Co-authored-by: Nicholas Gregory Co-authored-by: DhananjayPurohit Co-authored-by: S. Santos <128342304+ssantos21@users.noreply.github.com> --- .github/workflows/tests.yml | 90 ++- Cargo.lock | 449 +++++++++---- clients/apps/nodejs/index.js | 10 + .../test/tb04-simple-lightning-latch.mjs | 562 +++++++++++++++- clients/apps/nodejs/test_atomic_swap.js | 108 ++-- clients/apps/nodejs/test_basic_workflow2.js | 22 +- clients/apps/nodejs/test_utils.js | 60 +- clients/apps/rust/Cargo.toml | 2 +- clients/apps/rust/rust-toolchain.toml | 2 +- clients/apps/rust/src/main.rs | 11 + clients/libs/nodejs/index.js | 18 +- clients/libs/nodejs/lightning-latch.js | 27 +- clients/libs/nodejs/package.json | 1 + clients/libs/rust/Cargo.toml | 2 +- clients/libs/rust/rust-toolchain.toml | 2 +- clients/libs/rust/src/lightning_latch.rs | 24 + clients/libs/web/lightning-latch.js | 21 +- clients/libs/web/main.js | 20 +- clients/libs/web/vite.config.js | 1 - clients/tests/rust/rust-toolchain.toml | 2 +- .../rust/src/tb04_simple_lightning_latch.rs | 6 + clients/tests/web/ClientConfig.js | 4 +- clients/tests/web/package.json | 4 +- clients/tests/web/server-regtest.cjs | 122 ++++ clients/tests/web/start-test-components.sh | 6 +- clients/tests/web/test-utils.js | 91 ++- .../test/tb03-simple-atomic-transfer.test.js | 521 ++++++++++++++- .../test/tb04-simple-lightning-latch.test.js | 600 +++++++++++++++++- clients/tests/web/vitest.workspace.js | 9 +- docker-compose-test.yml | 93 ++- lib/rust-toolchain.toml | 2 +- server/Cargo.toml | 6 +- server/rust-toolchain.toml | 2 +- server/src/database/lightning_latch.rs | 27 +- server/src/database/sign.rs | 4 - server/src/endpoints/lightning_latch.rs | 33 +- server/src/endpoints/sign.rs | 6 +- server/src/main.rs | 23 +- token-server/Cargo.toml | 2 +- token-server/Dockerfile | 39 +- token-server/rust-toolchain.toml | 2 + wasm/Cargo.toml | 1 + wasm/rust-toolchain.toml | 2 + 43 files changed, 2760 insertions(+), 279 deletions(-) create mode 100644 token-server/rust-toolchain.toml create mode 100644 wasm/rust-toolchain.toml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc05bc7c..c94282d9 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 @@ -32,13 +32,20 @@ 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: 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 with Curl + - 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" @@ -47,19 +54,54 @@ 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 + - 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" + 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_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 + 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 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 \ @@ -71,14 +113,23 @@ jobs: -H "Content-Type: application/json" \ -d '{"statechain_id":"550e8400e29b41d4a716446655440000"}' docker logs $(docker ps -qf "name=enclave") + - name: Verify Node Service with Curl + run: | + curl -X POST http://0.0.0.0:3000/deposit_amount \ + -H 'Content-Type: application/json' \ + -d '{"address":"bcrt1pkygl356c6fvk6ptx72c64hrjkhcxecj4hjzfzc430svzczuv6m0s42lvwx","amount":1000}' + - name: Verify Esplora Service with Curl + run: | + 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: | # 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"}' @@ -88,7 +139,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: @@ -98,15 +149,32 @@ 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 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: Tear Down run: | docker-compose -f docker-compose-test.yml down diff --git a/Cargo.lock b/Cargo.lock index a339f0cd..e5f02b0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,14 +65,14 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -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" @@ -140,7 +141,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -150,7 +151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -296,6 +297,12 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -474,11 +481,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -489,16 +496,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -539,7 +546,7 @@ version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.60", @@ -582,6 +589,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.13.3" @@ -731,9 +747,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] [[package]] name = "devise" @@ -845,7 +864,7 @@ dependencies = [ "byteorder", "libc", "log", - "rustls", + "rustls 0.21.7", "serde", "serde_json", "webpki", @@ -862,6 +881,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" @@ -870,12 +912,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -886,14 +928,19 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" @@ -1157,21 +1204,21 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", "allocator-api2", ] [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown 0.14.1", + "hashbrown 0.14.5", ] [[package]] @@ -1179,9 +1226,12 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -1225,7 +1275,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1262,6 +1312,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" @@ -1350,7 +1406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.5", "serde", ] @@ -1383,9 +1439,15 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] +[[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" @@ -1432,9 +1494,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -1489,9 +1551,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.26.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -1506,9 +1568,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -1522,9 +1584,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" @@ -1580,12 +1642,14 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mercury-server" -version = "0.1.0" +version = "0.1.1" dependencies = [ "bitcoin", "chrono", "config", + "env_logger", "hex", + "log", "mercurylib", "rand", "reqwest", @@ -1697,7 +1761,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1775,6 +1839,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -1907,6 +1977,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1927,7 +2003,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec 1.11.1", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2088,6 +2164,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2299,7 +2381,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2450,15 +2532,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.18" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2469,19 +2551,40 @@ checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring 0.16.20", - "rustls-webpki", + "rustls-webpki 0.101.6", "sct", ] +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring 0.17.3", + "rustls-pki-types", + "rustls-webpki 0.102.7", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64 0.21.4", + "base64 0.22.1", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + [[package]] name = "rustls-webpki" version = "0.101.6" @@ -2492,6 +2595,17 @@ dependencies = [ "untrusted 0.7.1", ] +[[package]] +name = "rustls-webpki" +version = "0.102.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +dependencies = [ + "ring 0.17.3", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2510,7 +2624,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2759,6 +2873,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2807,6 +2927,9 @@ name = "smallvec" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +dependencies = [ + "serde", +] [[package]] name = "smawk" @@ -2831,7 +2954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2872,9 +2995,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2885,18 +3008,16 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" dependencies = [ - "ahash 0.8.3", "atoi", "byteorder", "bytes", "chrono", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener", "futures-channel", @@ -2904,6 +3025,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.14.5", "hashlink", "hex", "indexmap 2.0.2", @@ -2912,7 +3034,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls", + "rustls 0.23.12", "rustls-pemfile", "serde", "serde_json", @@ -2926,31 +3048,31 @@ dependencies = [ "tracing", "url", "uuid 1.4.1", - "webpki-roots 0.24.0", + "webpki-roots 0.26.3", ] [[package]] name = "sqlx-macros" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.60", ] [[package]] name = "sqlx-macros-core" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -2962,7 +3084,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.60", "tempfile", "tokio", "url", @@ -2970,12 +3092,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12" dependencies = [ "atoi", - "base64 0.21.4", + "base64 0.22.1", "bitflags 2.4.0", "byteorder", "bytes", @@ -3015,12 +3137,12 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" dependencies = [ "atoi", - "base64 0.21.4", + "base64 0.22.1", "bitflags 2.4.0", "byteorder", "chrono", @@ -3043,7 +3165,6 @@ dependencies = [ "rand", "serde", "serde_json", - "sha1", "sha2", "smallvec 1.11.1", "sqlx-core", @@ -3057,9 +3178,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" dependencies = [ "atoi", "chrono", @@ -3073,6 +3194,7 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "time", "tracing", @@ -3172,15 +3294,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "once_cell", "rustix", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -3226,12 +3348,14 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -3245,10 +3369,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -3284,7 +3409,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.4", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3530,12 +3655,6 @@ dependencies = [ "smallvec 0.6.14", ] -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - [[package]] name = "unicode-width" version = "0.1.12" @@ -3582,7 +3701,7 @@ dependencies = [ "fs-err", "glob", "goblin", - "heck", + "heck 0.4.1", "once_cell", "paste", "serde", @@ -3884,11 +4003,11 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.24.0" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ - "rustls-webpki", + "rustls-pki-types", ] [[package]] @@ -3934,7 +4053,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3943,7 +4062,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3952,7 +4071,25 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -3961,13 +4098,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3976,42 +4129,90 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.5.16" @@ -4028,7 +4229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4049,8 +4250,28 @@ dependencies = [ "is-terminal", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "zeroize" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/clients/apps/nodejs/index.js b/clients/apps/nodejs/index.js index 8363924e..7c4ade41 100644 --- a/clients/apps/nodejs/index.js +++ b/clients/apps/nodejs/index.js @@ -190,6 +190,16 @@ async function main() { console.log(JSON.stringify(res, null, 2)); }); + + program.command('get-payment-hash') + .description('Confirm a pending invoice for lightning latch') + .argument('', 'transfer batch id') + .action(async (batch_id) => { + + let res = await mercurynodejslib.getPaymentHash(clientConfig, batch_id); + + console.log(JSON.stringify(res, null, 2)); + }); program.parse(); diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 5c588e6a..0ed4e187 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -3,40 +3,371 @@ 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 { sleep, createWallet, depositCoin, generateInvoice, payHoldInvoice, payInvoice, settleInvoice } from '../test_utils.js'; describe('TB04 - Lightning Latch', function() { + this.timeout(30000); context('Simple Transfer', () => { 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, 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); + }) + }) + + 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(); - await removeDatabase(clientConfig); + let wallet_1_name = "w_ln_3"; + let wallet_2_name = "w_ln_4"; + 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, 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); + + 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); + 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); + }) + }) + + 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, 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); + + 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, 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); + + 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); + }) + }) + + context('Statecoin trade with invoice creation, payment and settlement', () => { + it('should complete successfully', async () => { - 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 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, 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); + 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); + + 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_9"; + let wallet_2_name = "w_ln_10"; + 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 coreWalletAddress = await getnewaddress(); + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; - 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); @@ -44,31 +375,220 @@ 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 invoice = await generateInvoice(paymentHash.hash, amount); - await mercurynodejslib.transferSend(clientConfig, wallet1.name, coin.statechain_id, transferAddress.transfer_receive, false, paymentHash.batchId); + payHoldInvoice(invoice.payment_request); - let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet2.name); + 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); + + const hashFromServer = await mercurynodejslib.getPaymentHash(clientConfig, paymentHash.batchId); + + expect(hashFromServer).to.equal(paymentHash.hash); + + 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')) .digest('hex') expect(hash).to.equal(paymentHash.hash); + + const paymentHashSecond = "b1f55a2f2eabb08ed9d6e15a053a6ac84d04d1c017de5a42caaec98b8d2ff738" + const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + + try { + await payInvoice(invoiceSecond.payment_request); + } catch (error) { + 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.false; + 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).is.empty; + + 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') + expect(hash).to.equal(paymentHash.hash); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + }) + }) + + 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).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).is.empty; + + 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') + expect(hash).to.equal(paymentHash.hash); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + }) + }) + + 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 = "a3b5f72d4e8cb07cd9a6e17c054a7ac84d05e1c018fe5b43cbbef98a9d3ff839" + const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + + const isInvoiceValid = await mercurynodejslib.verifyInvoice(clientConfig, paymentHash.batchId, invoiceSecond.payment_request); + expect(isInvoiceValid).is.false; }) }) }) diff --git a/clients/apps/nodejs/test_atomic_swap.js b/clients/apps/nodejs/test_atomic_swap.js index c3bb5f3c..6156a89f 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 diff --git a/clients/apps/nodejs/test_basic_workflow2.js b/clients/apps/nodejs/test_basic_workflow2.js index 75bf20ad..faf64184 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'); @@ -397,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 { @@ -408,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(); } @@ -449,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; @@ -463,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(); } @@ -632,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 { @@ -643,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 @@ -692,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 { @@ -703,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 @@ -750,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 { @@ -761,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 e6972cc0..fe1ece6b 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,19 +85,61 @@ 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 { 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); } module.exports = { @@ -111,5 +153,9 @@ module.exports = { connectElectr, disconnectElectr, connectMercuryServer, - disconnectMercuryServer + disconnectMercuryServer , + generateInvoice, + payInvoice, + payHoldInvoice, + settleInvoice }; diff --git a/clients/apps/rust/Cargo.toml b/clients/apps/rust/Cargo.toml index 3a2837e2..1113f7a7 100644 --- a/clients/apps/rust/Cargo.toml +++ b/clients/apps/rust/Cargo.toml @@ -21,7 +21,7 @@ schemars = { version = "0.8.12", features = ["chrono", "uuid"] } secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", branch = "blinded-musig-scheme", features = [ "rand-std", "bitcoin_hashes", "std" ] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" -sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } +sqlx = { version = "0.8.1", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } tokio = { version = "1.27.0", features = ["full"] } uuid = { version = "1.3.1", features = ["v4", "serde"] } mercuryrustlib = { path = "../../libs/rust" } diff --git a/clients/apps/rust/rust-toolchain.toml b/clients/apps/rust/rust-toolchain.toml index 624eb0ea..a56a283d 100644 --- a/clients/apps/rust/rust-toolchain.toml +++ b/clients/apps/rust/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.76.0" +channel = "1.80.1" diff --git a/clients/apps/rust/src/main.rs b/clients/apps/rust/src/main.rs index 8772573b..9649be1d 100644 --- a/clients/apps/rust/src/main.rs +++ b/clients/apps/rust/src/main.rs @@ -77,6 +77,10 @@ enum Commands { statechain_id: String, batch_id: String, }, + /// Get the payment hash by batch id + GetPaymentHash { + batch_id: String, + } } #[tokio::main(flavor = "current_thread")] @@ -202,6 +206,13 @@ async fn main() -> Result<()> { let obj = json!({"pre_image": pre_image}); + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + }, + Commands::GetPaymentHash { batch_id } => { + let payment_hash = mercuryrustlib::lightning_latch::get_payment_hash(&client_config, &batch_id).await?; + + let obj = json!({"payment_hash": payment_hash}); + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); } } diff --git a/clients/libs/nodejs/index.js b/clients/libs/nodejs/index.js index 5cf3ade3..3a46ddc5 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 lightningPayReq = require("bolt11"); const getDatabase = async (clientConfig) => { const databaseFile = clientConfig.databaseFile; @@ -247,6 +248,19 @@ const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) return preImage; } +const verifyInvoice = async (clientConfig, batchId, paymentRequest) => { + + const decodedInvoice = lightningPayReq.decode(paymentRequest); + let paymentHash = await getPaymentHash(clientConfig, batchId); + + return paymentHash === decodedInvoice.tagsObject.payment_hash; +} + +const getPaymentHash = async (clientConfig, batchId) => { + + return await lightningLatch.getPaymentHash(clientConfig, batchId); +} + module.exports = { createWallet, newToken, @@ -260,5 +274,7 @@ module.exports = { transferReceive, paymentHash, confirmPendingInvoice, - retrievePreImage + retrievePreImage, + verifyInvoice, + getPaymentHash }; diff --git a/clients/libs/nodejs/lightning-latch.js b/clients/libs/nodejs/lightning-latch.js index d391268b..abf98bf7 100644 --- a/clients/libs/nodejs/lightning-latch.js +++ b/clients/libs/nodejs/lightning-latch.js @@ -135,4 +135,29 @@ const retrievePreImage = async (clientConfig, db, walletName, statechainId, batc return { preimage: response?.data?.preimage }; } -module.exports = { createPreImage, confirmPendingInvoice, retrievePreImage }; +const getPaymentHash = async (clientConfig, batchId) => { + + const url = `${clientConfig.statechainEntity}/transfer/paymenthash/${batchId}`; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + try { + const response = await axios.get(url, socksAgent); + 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)}`); + } + } +} + +module.exports = { createPreImage, confirmPendingInvoice, retrievePreImage, getPaymentHash }; 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/rust/Cargo.toml b/clients/libs/rust/Cargo.toml index 89b57b43..c502f3e9 100644 --- a/clients/libs/rust/Cargo.toml +++ b/clients/libs/rust/Cargo.toml @@ -21,7 +21,7 @@ schemars = { version = "0.8.12", features = ["chrono", "uuid"] } secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", branch = "blinded-musig-scheme", features = [ "rand-std", "bitcoin_hashes", "std" ] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" -sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } +sqlx = { version = "0.8.1", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } tokio = { version = "1.27.0", features = ["full"] } uuid = { version = "1.3.1", features = ["v4", "serde"] } mercurylib = { path = "../../../lib" } diff --git a/clients/libs/rust/rust-toolchain.toml b/clients/libs/rust/rust-toolchain.toml index 624eb0ea..a56a283d 100644 --- a/clients/libs/rust/rust-toolchain.toml +++ b/clients/libs/rust/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.76.0" +channel = "1.80.1" diff --git a/clients/libs/rust/src/lightning_latch.rs b/clients/libs/rust/src/lightning_latch.rs index b1f98f0c..46ee6e81 100644 --- a/clients/libs/rust/src/lightning_latch.rs +++ b/clients/libs/rust/src/lightning_latch.rs @@ -145,3 +145,27 @@ pub async fn retrieve_pre_image(client_config: &ClientConfig, wallet_name: &str, Ok(transfer_preimage_response_payload.preimage) } + + +pub async fn get_payment_hash(client_config: &ClientConfig, batch_id: &str) -> Result> { + + let path = format!("transfer/paymenthash/{}", batch_id); + + let client = client_config.get_reqwest_client()?; + let request = client.get(&format!("{}/{}", client_config.statechain_entity, path)); + + let response = request.send().await?; + + if response.status() == 401 { + return Ok(None); + } else if response.status() != 200 { + let response_body = response.text().await?; + return Err(anyhow!(response_body)); + } + + let value = response.text().await?; + + let payment_hash_response_payload: PaymentHashResponsePayload = serde_json::from_str(value.as_str())?; + + Ok(Some(payment_hash_response_payload.hash)) +} 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..8d3abec7 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 { decodeInvoice } from '../../tests/web/test-utils.js'; const greet = async () => { @@ -122,6 +123,21 @@ 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); +} + +const verifyInvoice = async (clientConfig, batchId, paymentRequest) => { + + const decodedInvoice = await decodeInvoice(paymentRequest); + let paymentHash = await getPaymentHash(clientConfig, batchId); + console.log("Decoded invoice: ", decodedInvoice); + + const paymentHashFromInvoice = decodedInvoice.tags.find(tag => tag.tagName === "payment_hash")?.data; + return paymentHash === paymentHashFromInvoice; +} + export default { greet, createWallet, @@ -135,5 +151,7 @@ export default { transferReceive, paymentHash, confirmPendingInvoice, - retrievePreImage + retrievePreImage, + getPaymentHash, + verifyInvoice } 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({ diff --git a/clients/tests/rust/rust-toolchain.toml b/clients/tests/rust/rust-toolchain.toml index 624eb0ea..a56a283d 100644 --- a/clients/tests/rust/rust-toolchain.toml +++ b/clients/tests/rust/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.76.0" +channel = "1.80.1" diff --git a/clients/tests/rust/src/tb04_simple_lightning_latch.rs b/clients/tests/rust/src/tb04_simple_lightning_latch.rs index c185642f..eb4f1e27 100644 --- a/clients/tests/rust/src/tb04_simple_lightning_latch.rs +++ b/clients/tests/rust/src/tb04_simple_lightning_latch.rs @@ -46,6 +46,12 @@ pub async fn tb04(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wall mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, statechain_id, force_send, Some(batch_id.clone())).await?; + // receiver confirms that payment hash was generated by the server + let response = mercuryrustlib::lightning_latch::get_payment_hash(&client_config, &batch_id).await?; + + assert!(response.is_some()); + assert!(response.unwrap() == hash); + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet2.name).await?; assert!(transfer_receive_result.is_there_batch_locked); 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/package.json b/clients/tests/web/package.json index 6e021223..66de7bfe 100644 --- a/clients/tests/web/package.json +++ b/clients/tests/web/package.json @@ -8,11 +8,13 @@ "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", "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 b68d64e6..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()) @@ -30,6 +31,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 +90,101 @@ 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.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/start-test-components.sh b/clients/tests/web/start-test-components.sh index f530b563..51f9e540 100755 --- a/clients/tests/web/start-test-components.sh +++ b/clients/tests/web/start-test-components.sh @@ -18,16 +18,18 @@ cleanup() { trap cleanup SIGINT SIGTERM # Run node server in the background and capture its PID +npm install 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 \ /srv/explorer/run.sh bitcoin-regtest explorer & DOCKER_PID=$! + # Wait for both processes to finish -wait $NODE_PID $DOCKER_PID +# wait $NODE_PID $DOCKER_PID diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js index 7ea3f9b8..9b953c45 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://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://localhost:3000/deposit_amount`; + const url = `http://0.0.0.0:3000/deposit_amount`; let response = await axios.post(url, body); @@ -37,4 +37,89 @@ const testEsplora = async () => { console.log(block_header); } -export { generateBlocks, depositCoin }; +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +const generateInvoice = async (paymentHash, amountInSats) => { + + const body = { + paymentHash, + amountInSats + }; + + const url = `http://0.0.0.0:3000/generate_invoice`; + + 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`; + + let response = await axios.post(url, body); + + if (response.status != 200) { + throw new Error(`Failed to pay invoice`); + } +} + +const payHoldInvoice = async (paymentRequest) => { + + const body = { + paymentRequest + }; + + const url = `http://0.0.0.0:3000/pay_holdinvoice`; + + let response = await axios.post(url, body); + + if (response.status != 200) { + throw new Error(`Failed to pay hold invoice`); + } +} + +const settleInvoice = async (preimage) => { + + 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`); + } +} + +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/tb03-simple-atomic-transfer.test.js b/clients/tests/web/test/tb03-simple-atomic-transfer.test.js index a93adf0b..e4de80c6 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 () => { @@ -95,4 +95,521 @@ describe('TB03 - Simple Atomic Transfer', () => { await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); }); -}, 50000); \ No newline at end of file +}, 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(false); + + 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(false); + + 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, toAddress3.batch_id); + 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); + +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 = 'Request failed'; + 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, statechainId2, toAddress4_for_steal.transfer_receive, false, toAddress4.batch_id); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'Request failed'; + 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); 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..e5b7c158 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, decodeInvoice } from '../test-utils.js'; async function sha256(preimage) { let buffer; @@ -85,6 +85,381 @@ 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); + + 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); + }); +}, 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, statechainId1, 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, paymentHash1.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(true); + + 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 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); + + await payHoldInvoice(invoice.payment_request); + console.log(invoice); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + 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); @@ -104,5 +479,226 @@ describe('TB04 - Simple Lightning Latch', () => { let hashPreImage = await sha256(preimage); expect(hashPreImage).toEqual(paymentHash.hash); + + const paymentRequest = "lnbcrt10u1pnt70qdpp55w6lwt2w3jc8ekdxu97q2jn6epxstcwqrrl9ks7thmuc48fllqusdqqcqzzsxqyz5vqsp54ay50wuys4sjjmzsxms3hv6pf294rnys8y9k93t8dz6edpqvue5s9qxpqysgqwpp3m8a77cc6nt2yvxvkcql5avzf50ga95dxrtugfv2p5wdqfjy4fd44srh7undwaa7tkju88c0z9zf9ryp5tdr3hv7t9699l4qfpccpp7fqx7" + const invoiceSecond = await decodeInvoice(paymentRequest); + + try { + await payInvoice(invoiceSecond.payment_request); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + }); +}, 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); + expect(hashPreImage).toEqual(paymentHash.hash); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + }); +}, 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); + expect(hashPreImage).toEqual(paymentHash.hash); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + }); +}, 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 paymentRequest = "lnbcrt10u1pnt70fjpp53gj23vyghz5ggpc3ppkttxkqkywfz2dfwgalsa9ynylwe28lscqqdqqcqzzsxqyz5vqsp5l640fse8wx9773rpxlqdgv95t4swhpeueuta358404q6exvhzk5q9qxpqysgqzytwwj9n6s5dt4jd6vgvy9rmtcq4cwhe4h98asrpe3u3pqp3tqkrcvz5fm6uvr76akr9ml07sz70cdx45d64h9dpmnd2ua5qdpp79rcpqpucyd" + + const isInvoiceValid = await mercuryweblib.verifyInvoice(clientConfig, paymentHash.batchId, paymentRequest); + expect(isInvoiceValid).is.false; }); -}, 50000); \ No newline at end of file +}, 50000); 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/docker-compose-test.yml b/docker-compose-test.yml index 7cce6ae7..c629574c 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,15 +11,19 @@ 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 + 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" + - "28333:28333" volumes: - bitcoin_data:/root/.bitcoin electrs: image: getumbrel/electrs:v0.9.4@sha256:b1590ac6cfb0e5b481c6a7af7f0626d76cbb91c63702b0f5c47e2829e9c37997 + container_name: mercurylayer-electrs-1 user: root environment: ELECTRS_LOG_FILTERS: "INFO" @@ -39,6 +44,7 @@ services: build: context: enclave dockerfile: Dockerfiles/SIM/Dockerfile + container_name: mercurylayer-enclave-sgx-1 depends_on: - postgres environment: @@ -58,12 +64,93 @@ 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: - postgres + alice: + image: lightninglabs/lndinit:v0.1.21-beta-lnd-v0.18.0-beta + container_name: mercurylayer-alice-1 + 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 --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create + ports: + - "9735:9735" + - "10009:10009" + - "8080:8080" + volumes: + - alice-data:/data/.lnd + restart: unless-stopped + environment: + HOME: /data + command: [ '/init-wallet-k8s.sh' ] + depends_on: + - bitcoind + + bob: + image: lightninglabs/lndinit:v0.1.21-beta-lnd-v0.18.0-beta + container_name: mercurylayer-bob-1 + 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 --wallet-unlock-password-file=/data/walletpassword.txt --wallet-unlock-allow-create + ports: + - "9736:9735" + - "10010:10009" + - "8081:8080" + volumes: + - bob-data:/data/.lnd + restart: unless-stopped + environment: + HOME: /data + command: [ '/init-wallet-k8s.sh' ] + depends_on: + - bitcoind + volumes: bitcoin_data: - electrs-data: \ No newline at end of file + electrs-data: + alice-data: + bob-data: + esplora-data: diff --git a/lib/rust-toolchain.toml b/lib/rust-toolchain.toml index 624eb0ea..a56a283d 100644 --- a/lib/rust-toolchain.toml +++ b/lib/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.76.0" +channel = "1.80.1" diff --git a/server/Cargo.toml b/server/Cargo.toml index e3814d91..0a7df756 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mercury-server" -version = "0.1.0" +version = "0.1.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,7 +10,7 @@ bitcoin = { version = "0.30.1", features = ["serde", "base64", "rand-std", "std" config = "0.13.1" reqwest = { version = "0.11.16", features = ["blocking", "json", "socks"] } rocket = { version = "0.5.0", features = ["json"] } -sqlx = { version = "0.7", features = [ "runtime-tokio", "postgres", "time", "uuid", "chrono", "tls-rustls" ] } +sqlx = { version = "0.8.1", features = [ "runtime-tokio", "postgres", "time", "uuid", "chrono", "tls-rustls" ] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" schemars = { version = "0.8.12", features = ["chrono", "uuid"] } @@ -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/rust-toolchain.toml b/server/rust-toolchain.toml index 624eb0ea..a56a283d 100644 --- a/server/rust-toolchain.toml +++ b/server/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.76.0" +channel = "1.80.1" diff --git a/server/src/database/lightning_latch.rs b/server/src/database/lightning_latch.rs index 1b0b56bd..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) @@ -79,3 +80,27 @@ pub async fn get_preimage(pool: &sqlx::PgPool, statechain_id: &str, sender_auth_ Some(pre_image) } + +pub async fn get_preimage_by_batch_id(pool: &sqlx::PgPool, batch_id: &str) -> Option { + + let query = "SELECT pre_image FROM \ + lightning_latch \ + WHERE batch_id = $1"; + + let row = sqlx::query(query) + .bind(batch_id) + .fetch_optional(pool) + .await + .unwrap(); + + if row.is_none() + { + return None; + } + + let row = row.unwrap(); + + let pre_image: String = row.get(0); + + Some(pre_image) +} 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/lightning_latch.rs b/server/src/endpoints/lightning_latch.rs index 5cf2a9a5..4d7a8445 100644 --- a/server/src/endpoints/lightning_latch.rs +++ b/server/src/endpoints/lightning_latch.rs @@ -11,8 +11,39 @@ use sha2::{Sha256, Digest}; use crate::server::StateChainEntity; +#[get("/transfer/paymenthash/")] +pub async fn get_paymenthash(statechain_entity: &State, batch_id: &str) -> status::Custom> { + + let pre_image = crate::database::lightning_latch::get_preimage_by_batch_id(&statechain_entity.pool, batch_id).await; + + if pre_image.is_none() { + let response_body = json!({ + "message": "Pre-image not found" + }); + + return status::Custom(Status::NotFound, Json(response_body)); + } + + let pre_image = pre_image.unwrap(); + let buffer = hex::decode(pre_image).unwrap(); + + let mut hasher = Sha256::new(); + hasher.update(buffer); + let result = hasher.finalize(); + + let payment_hash = hex::encode(result); + + let payment_hash_response_payload = PaymentHashResponsePayload { + hash: payment_hash, + }; + + let response_body = json!(payment_hash_response_payload); + + return status::Custom(Status::Ok, Json(response_body)); +} + #[post("/transfer/paymenthash", format = "json", data = "")] -pub async fn paymenthash(statechain_entity: &State, payment_hash_payload: Json) -> status::Custom> { +pub async fn post_paymenthash(statechain_entity: &State, payment_hash_payload: Json) -> status::Custom> { let statechain_id = payment_hash_payload.0.statechain_id.clone(); let signed_statechain_id = payment_hash_payload.0.auth_sig.clone(); diff --git a/server/src/endpoints/sign.rs b/server/src/endpoints/sign.rs index f5d8b985..a49324cd 100644 --- a/server/src/endpoints/sign.rs +++ b/server/src/endpoints/sign.rs @@ -1,5 +1,5 @@ use mercurylib::transaction::SignFirstRequestPayload; -use rocket::{http::Status, response::status::{self, Unauthorized}, serde::json::Json, State}; +use rocket::{http::Status, response::status, serde::json::Json, State}; use secp256k1_zkp::musig::MusigSession; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -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() diff --git a/server/src/main.rs b/server/src/main.rs index 5ce5c96c..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; @@ -45,7 +55,8 @@ async fn main() { endpoints::deposit::token_init, endpoints::sign::sign_first, endpoints::sign::sign_second, - endpoints::lightning_latch::paymenthash, + endpoints::lightning_latch::get_paymenthash, + endpoints::lightning_latch::post_paymenthash, endpoints::lightning_latch::transfer_preimage, endpoints::transfer_sender::transfer_sender, endpoints::transfer_sender::transfer_update_msg, diff --git a/token-server/Cargo.toml b/token-server/Cargo.toml index d43f7f28..ea0880ab 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.1", 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"] diff --git a/token-server/rust-toolchain.toml b/token-server/rust-toolchain.toml new file mode 100644 index 00000000..a56a283d --- /dev/null +++ b/token-server/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.80.1" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 79ac9a21..806d0933 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -46,4 +46,5 @@ wasm-bindgen-test = "0.3.34" opt-level = "s" [dependencies.getrandom] +version = "0.2.10" features = ["js"] diff --git a/wasm/rust-toolchain.toml b/wasm/rust-toolchain.toml new file mode 100644 index 00000000..a56a283d --- /dev/null +++ b/wasm/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.80.1"