From 8ce332e738bc226766388696ce812dbb1c253e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Granh=C3=A3o?= Date: Wed, 5 Mar 2025 19:07:56 +0000 Subject: [PATCH] Run tests on regtest --- .github/workflows/build.yaml | 2 +- .github/workflows/test.yaml | 30 ++ .gitmodules | 3 + Cargo.toml | 6 + Makefile | 28 +- regtest/boltz | 1 + regtest/proxy/Dockerfile | 17 ++ regtest/proxy/docker-compose.yml | 4 + regtest/proxy/requirements.txt | 5 + regtest/proxy/ssl_proxy.py | 106 +++++++ regtest/start.sh | 9 + regtest/stop.sh | 8 + set_regtest_env.sh | 6 + src/error.rs | 4 +- src/network/electrum.rs | 63 ++-- src/network/esplora.rs | 51 ++-- src/swaps/boltz.rs | 3 +- src/util/mod.rs | 6 +- tests/mod.rs | 3 + tests/{ => regtest}/bitcoin.rs | 204 +++++++------ tests/{ => regtest}/chain_swaps.rs | 198 +++++++------ tests/{ => regtest}/liquid.rs | 455 ++++++++++++----------------- tests/regtest/mod.rs | 6 + tests/test_framework/mod.rs | 3 + tests/test_utils.rs | 12 - tests/{regtest.rs => txs.rs} | 0 tests/utils.rs | 199 +++++++++++++ 27 files changed, 910 insertions(+), 522 deletions(-) create mode 100644 .gitmodules create mode 160000 regtest/boltz create mode 100644 regtest/proxy/Dockerfile create mode 100644 regtest/proxy/docker-compose.yml create mode 100644 regtest/proxy/requirements.txt create mode 100644 regtest/proxy/ssl_proxy.py create mode 100755 regtest/start.sh create mode 100755 regtest/stop.sh create mode 100644 set_regtest_env.sh create mode 100644 tests/mod.rs rename tests/{ => regtest}/bitcoin.rs (77%) rename tests/{ => regtest}/chain_swaps.rs (75%) rename tests/{ => regtest}/liquid.rs (60%) create mode 100644 tests/regtest/mod.rs delete mode 100644 tests/test_utils.rs rename tests/{regtest.rs => txs.rs} (100%) create mode 100644 tests/utils.rs diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 265a65b..b123666 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,7 +22,7 @@ jobs: - name: Generate cache key run: echo "${{ matrix.rust }}" | tee .cache_key - name: cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.cargo/registry diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8718ca5..97e48c5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,3 +25,33 @@ jobs: run: make cargo-test - name: Run wasm-pack test run: make wasm-test + + regtest-test: + name: Run regtest tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up Docker + uses: docker/setup-buildx-action@v2 + - name: Set up Docker Compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + - name: Set default toolchain + run: rustup default nightly + - name: Set profile + run: rustup set profile minimal + - name: Add wasm target + run: rustup target add wasm32-unknown-unknown + - name: Install wasm-pack + run: cargo install wasm-pack + - name: Start regtest environment + working-directory: regtest + run: sh start.sh + - name: Run cargo regtest tests + run: make cargo-regtest-test + - name: Run WASM regtest tests + run: make wasm-regtest-test diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1773754 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "regtest/boltz"] + path = regtest/boltz + url = https://github.com/BoltzExchange/regtest diff --git a/Cargo.toml b/Cargo.toml index 5fb0e2c..0117ee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ secp256k1-zkp = { git = "https://github.com/danielgranhao/rust-secp256k1-zkp.git [dev-dependencies] futures-util = "0.3.31" +serial_test = "3.2.0" [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dev-dependencies] bitcoind = { version = "0.36.0", features = ["25_0"] } @@ -65,9 +66,14 @@ tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread"] } [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies] wasm-bindgen-test = "0.3.50" +wasm-bindgen = "0.2.100" +gloo-timers = { version = "0.3.0", features = ["futures"] } +futures = "0.3.31" [features] default = ["esplora"] lnurl = ["dep:lnurl-rs"] esplora = [] electrum = ["dep:electrum-client"] +# Feature to enable tests that require previous initialization of a regtest environment +regtest = [] diff --git a/Makefile b/Makefile index 1afccf6..3f39272 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,10 @@ ifeq ($(UNAME), Darwin) CLANG_PREFIX += AR=$(shell brew --prefix llvm)/bin/llvm-ar CC=$(shell brew --prefix llvm)/bin/clang endif +LND_MACAROON_HEX=$(shell xxd -p regtest/boltz/data/lnd1/data/chain/bitcoin/regtest/admin.macaroon | tr -d '\n') +BITCOIND_COOKIE=$(shell cat regtest/boltz/data/bitcoind/regtest/.cookie) +REGTEST_PREFIX = LND_MACAROON_HEX=$(LND_MACAROON_HEX) BITCOIND_COOKIE=$(BITCOIND_COOKIE) + init: cargo install wasm-pack @@ -19,20 +23,36 @@ clippy: cargo-clippy wasm-clippy test: cargo-test wasm-test +regtest-test: cargo-regtest-test wasm-regtest-test + cargo-clippy: cargo clippy --all-targets --all-features -- -D warnings cargo-test: - cargo test -- --nocapture + cargo test --features "esplora, electrum, lnurl" -- --nocapture + +cargo-regtest-test: + $(REGTEST_PREFIX) cargo test regtest --features "electrum, regtest" -- --nocapture wasm-clippy: $(CLANG_PREFIX) cargo clippy --target=wasm32-unknown-unknown --all-features -- -D warnings +BROWSER ?= firefox + wasm-test: - $(CLANG_PREFIX) wasm-pack test --headless --firefox + $(CLANG_PREFIX) wasm-pack test --headless --$(BROWSER) wasm-test-chrome: - $(CLANG_PREFIX) wasm-pack test --headless --chrome + BROWSER=chrome $(MAKE) wasm-test wasm-test-safari: - $(CLANG_PREFIX) wasm-pack test --headless --safari + BROWSER=safari $(MAKE) wasm-test + +wasm-regtest-test: + $(CLANG_PREFIX) $(REGTEST_PREFIX) WASM_BINDGEN_TEST_TIMEOUT=500 wasm-pack test --headless --$(BROWSER) --features regtest -- regtest + +wasm-regtest-test-chrome: + BROWSER=chrome $(MAKE) wasm-regtest-test + +wasm-regtest-test-safari: + BROWSER=safari $(MAKE) wasm-regtest-test diff --git a/regtest/boltz b/regtest/boltz new file mode 160000 index 0000000..0379ce3 --- /dev/null +++ b/regtest/boltz @@ -0,0 +1 @@ +Subproject commit 0379ce3174dae022b6bd0b266b7286c6b83bffd3 diff --git a/regtest/proxy/Dockerfile b/regtest/proxy/Dockerfile new file mode 100644 index 0000000..09c9be8 --- /dev/null +++ b/regtest/proxy/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.10-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --upgrade pip + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY ssl_proxy.py . + +EXPOSE 51234 + +CMD ["gunicorn", "--bind", "0.0.0.0:51234", "--workers", "2", "ssl_proxy:app"] diff --git a/regtest/proxy/docker-compose.yml b/regtest/proxy/docker-compose.yml new file mode 100644 index 0000000..d28a73d --- /dev/null +++ b/regtest/proxy/docker-compose.yml @@ -0,0 +1,4 @@ +services: + ssl-proxy: + network_mode: "host" + build: . diff --git a/regtest/proxy/requirements.txt b/regtest/proxy/requirements.txt new file mode 100644 index 0000000..30bb4e9 --- /dev/null +++ b/regtest/proxy/requirements.txt @@ -0,0 +1,5 @@ +flask==2.2.5 +flask-cors==3.0.10 +requests==2.28.2 +gunicorn==20.1.0 +werkzeug==2.2.3 diff --git a/regtest/proxy/ssl_proxy.py b/regtest/proxy/ssl_proxy.py new file mode 100644 index 0000000..fec9fe0 --- /dev/null +++ b/regtest/proxy/ssl_proxy.py @@ -0,0 +1,106 @@ +from flask import Flask, request, json, Response +from flask_cors import CORS +import requests +import logging + +app = Flask(__name__) +CORS(app) # Allow all origins + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +@app.route('/proxy', methods=['GET', 'POST', 'PUT', 'DELETE']) +def proxy_request(): + target_url = request.headers.get('X-Proxy-URL') + + if not target_url: + return Response( + json.dumps({"error": "No target URL provided"}), + status=400, + mimetype='application/json' + ) + + try: + # Log the incoming request details + logger.info(f"Proxy request to: {target_url}") + logger.info(f"Request method: {request.method}") + logger.info(f"Request headers: {dict(request.headers)}") + + # Try to log request body + try: + request_body = request.get_json(silent=True) + logger.info(f"Request body: {request_body}") + except Exception as body_log_error: + logger.error(f"Could not log request body: {body_log_error}") + + # Prepare headers, excluding Flask-specific ones + headers = { + key: value for (key, value) in request.headers + if key not in ['Host', 'X-Proxy-URL', 'Content-Length'] + } + + # Determine the method and make the request + methods = { + 'GET': requests.get, + 'POST': requests.post, + 'PUT': requests.put, + 'DELETE': requests.delete + } + + method = methods.get(request.method) + if not method: + return Response( + json.dumps({"error": "Unsupported HTTP method"}), + status=405, + mimetype='application/json' + ) + + # Prepare request arguments + kwargs = { + 'url': target_url, + 'headers': headers, + 'verify': False # Bypass SSL verification + } + + # Add data for methods that support it + if request.method in ['POST', 'PUT']: + # Try to parse request data as JSON if possible + request_json = request.get_json(silent=True) + if request_json: + kwargs['json'] = request_json + else: + kwargs['data'] = request.get_data() + + # Make the request + try: + response = method(**kwargs) + except Exception as request_error: + logger.error(f"Request error: {request_error}") + return Response( + json.dumps({ + "error": "Failed to make request to target service", + "details": str(request_error) + }), + status=500, + mimetype='application/json' + ) + + return Response( + response.text, + status=response.status_code, + mimetype='application/json' + ) + + except Exception as e: + logger.error(f"Unexpected error: {e}") + return Response( + json.dumps({"error": str(e)}), + status=500, + mimetype='application/json' + ) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=51234) diff --git a/regtest/start.sh b/regtest/start.sh new file mode 100755 index 0000000..7c964f7 --- /dev/null +++ b/regtest/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -xe + +cd proxy +docker compose down +docker compose up --remove-orphans -d + +cd ../boltz +./start.sh diff --git a/regtest/stop.sh b/regtest/stop.sh new file mode 100755 index 0000000..7cd0f31 --- /dev/null +++ b/regtest/stop.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -xe + +cd boltz +./stop.sh + +cd ../proxy +docker compose down --volumes diff --git a/set_regtest_env.sh b/set_regtest_env.sh new file mode 100644 index 0000000..d53a4b4 --- /dev/null +++ b/set_regtest_env.sh @@ -0,0 +1,6 @@ +#!/bin/bash +export LND_MACAROON_HEX=$(xxd -p regtest/boltz/data/lnd1/data/chain/bitcoin/regtest/admin.macaroon | tr -d '\n') +export BITCOIND_COOKIE=$(), Taproot(String), Musig2(String), Generic(String), @@ -186,7 +186,7 @@ impl From for Error { impl From for Error { fn from(value: tokio_tungstenite_wasm::Error) -> Self { - Self::WebSocket(value) + Self::WebSocket(value.into()) } } diff --git a/src/network/electrum.rs b/src/network/electrum.rs index 1f8d4aa..7debb63 100644 --- a/src/network/electrum.rs +++ b/src/network/electrum.rs @@ -7,10 +7,13 @@ use electrum_client::{ElectrumApi, GetHistoryRes}; use elements::encode::{serialize, Decodable}; use std::collections::HashMap; -pub const DEFAULT_TESTNET_NODE: &str = "electrum.blockstream.info:60002"; pub const DEFAULT_MAINNET_NODE: &str = "wes.bullbitcoin.com:50002"; -pub const DEFAULT_LIQUID_TESTNET_NODE: &str = "blockstream.info:465"; +pub const DEFAULT_TESTNET_NODE: &str = "electrum.blockstream.info:60002"; +pub const DEFAULT_REGTEST_NODE: &str = "localhost:19001"; pub const DEFAULT_LIQUID_MAINNET_NODE: &str = "blockstream.info:995"; +pub const DEFAULT_LIQUID_TESTNET_NODE: &str = "blockstream.info:465"; +pub const DEFAULT_LIQUID_REGTEST_NODE: &str = "localhost:19002"; + pub const DEFAULT_ELECTRUM_TIMEOUT: u8 = 10; #[derive(Debug, Clone)] @@ -42,57 +45,50 @@ pub struct ElectrumConfig { } impl ElectrumConfig { - pub fn default(chain: Chain, regtest_url: Option) -> Result { - if (chain == Chain::LiquidRegtest || chain == Chain::BitcoinRegtest) - && regtest_url.is_none() - { - return Err(Error::Electrum(electrum_client::Error::Message( - "Regtest requires using a custom url".to_string(), - ))); - } + pub fn default(chain: Chain, regtest_url: Option<&str>) -> Self { match chain { - Chain::Bitcoin => Ok(ElectrumConfig::new( + Chain::Bitcoin => ElectrumConfig::new( Chain::Bitcoin, DEFAULT_MAINNET_NODE, true, true, DEFAULT_ELECTRUM_TIMEOUT, - )), - Chain::BitcoinTestnet => Ok(ElectrumConfig::new( + ), + Chain::BitcoinTestnet => ElectrumConfig::new( Chain::BitcoinTestnet, DEFAULT_TESTNET_NODE, true, true, DEFAULT_ELECTRUM_TIMEOUT, - )), - Chain::BitcoinRegtest => Ok(ElectrumConfig::new( - Chain::BitcoinTestnet, - ®test_url.unwrap(), - true, - true, + ), + Chain::BitcoinRegtest => ElectrumConfig::new( + Chain::BitcoinRegtest, + regtest_url.unwrap_or(DEFAULT_REGTEST_NODE), + false, + false, DEFAULT_ELECTRUM_TIMEOUT, - )), - Chain::Liquid => Ok(ElectrumConfig::new( + ), + Chain::Liquid => ElectrumConfig::new( Chain::Liquid, DEFAULT_LIQUID_MAINNET_NODE, true, true, DEFAULT_ELECTRUM_TIMEOUT, - )), - Chain::LiquidTestnet => Ok(ElectrumConfig::new( + ), + Chain::LiquidTestnet => ElectrumConfig::new( Chain::LiquidTestnet, DEFAULT_LIQUID_TESTNET_NODE, true, true, DEFAULT_ELECTRUM_TIMEOUT, - )), - Chain::LiquidRegtest => Ok(ElectrumConfig::new( - Chain::BitcoinTestnet, - ®test_url.unwrap(), - true, - true, + ), + Chain::LiquidRegtest => ElectrumConfig::new( + Chain::LiquidRegtest, + regtest_url.unwrap_or(DEFAULT_LIQUID_REGTEST_NODE), + false, + false, DEFAULT_ELECTRUM_TIMEOUT, - )), + ), } } @@ -307,11 +303,11 @@ mod tests { #[test] fn test_electrum_default_clients() { // let network_config = ElectrumConfig::default(Chain::Bitcoin, None).unwrap(); - let network_config = ElectrumConfig::default(Chain::Bitcoin, None).unwrap(); + let network_config = ElectrumConfig::default(Chain::Bitcoin, None); let electrum_client = network_config.build_bitcoin_client().unwrap(); assert!(electrum_client.inner.ping().is_ok()); - let network_config = ElectrumConfig::default(Chain::Liquid, None).unwrap(); + let network_config = ElectrumConfig::default(Chain::Liquid, None); let electrum_client = network_config.build_liquid_client().unwrap(); assert!(electrum_client.inner.ping().is_ok()); } @@ -323,7 +319,6 @@ mod tests { let network_config = ElectrumConfig::default_bitcoin(); let electrum_client = network_config.build_bitcoin_client().unwrap(); - print!("{:?}", electrum_client.inner.block_header(1).unwrap()); assert!(electrum_client.inner.ping().is_ok()); let network_config = ElectrumConfig::default_liquid(); @@ -333,7 +328,7 @@ mod tests { #[test] #[ignore] fn test_raw_electrum_calls() { - let network_config = ElectrumConfig::default(Chain::Liquid, None).unwrap(); + let network_config = ElectrumConfig::default(Chain::Liquid, None); let electrum_client = network_config.build_liquid_client().unwrap(); let numblocks = "blockchain.numblocks.subscribe"; let blockheight = electrum_client.inner.raw_call(numblocks, []).unwrap(); diff --git a/src/network/esplora.rs b/src/network/esplora.rs index 612bc70..e358657 100644 --- a/src/network/esplora.rs +++ b/src/network/esplora.rs @@ -12,10 +12,12 @@ use std::fmt::format; use std::str::FromStr; use std::time::Duration; -pub const DEFAULT_TESTNET_NODE: &str = "https://blockstream.info/testnet/api"; pub const DEFAULT_MAINNET_NODE: &str = "https://blockstream.info/api"; -pub const DEFAULT_LIQUID_TESTNET_NODE: &str = "https://blockstream.info/liquidtestnet/api"; +pub const DEFAULT_TESTNET_NODE: &str = "https://blockstream.info/testnet/api"; +pub const DEFAULT_REGTEST_NODE: &str = "http://localhost:4002/api"; pub const DEFAULT_LIQUID_MAINNET_NODE: &str = "https://blockstream.info/liquid/api"; +pub const DEFAULT_LIQUID_TESTNET_NODE: &str = "https://blockstream.info/liquidtestnet/api"; +pub const DEFAULT_LIQUID_REGTEST_NODE: &str = "http://localhost:4003/api"; pub const DEFAULT_ESPLORA_TIMEOUT_SECS: u64 = 30; @@ -34,45 +36,38 @@ impl EsploraConfig { timeout, } } - pub fn default(chain: Chain, regtest_url: Option) -> Result { - if (chain == Chain::LiquidRegtest || chain == Chain::BitcoinRegtest) - && regtest_url.is_none() - { - return Err(Error::Esplora( - "Regtest requires using a custom url".to_string(), - )); - } + pub fn default(chain: Chain, regtest_url: Option<&str>) -> Self { match chain { - Chain::Bitcoin => Ok(Self::new( + Chain::Bitcoin => Self::new( Chain::Bitcoin, DEFAULT_MAINNET_NODE, DEFAULT_ESPLORA_TIMEOUT_SECS, - )), - Chain::BitcoinTestnet => Ok(Self::new( + ), + Chain::BitcoinTestnet => Self::new( Chain::BitcoinTestnet, DEFAULT_TESTNET_NODE, DEFAULT_ESPLORA_TIMEOUT_SECS, - )), - Chain::BitcoinRegtest => Ok(Self::new( - Chain::BitcoinTestnet, - ®test_url.unwrap(), + ), + Chain::BitcoinRegtest => Self::new( + Chain::BitcoinRegtest, + regtest_url.unwrap_or(DEFAULT_REGTEST_NODE), DEFAULT_ESPLORA_TIMEOUT_SECS, - )), - Chain::Liquid => Ok(Self::new( + ), + Chain::Liquid => Self::new( Chain::Liquid, DEFAULT_LIQUID_MAINNET_NODE, DEFAULT_ESPLORA_TIMEOUT_SECS, - )), - Chain::LiquidTestnet => Ok(Self::new( + ), + Chain::LiquidTestnet => Self::new( Chain::LiquidTestnet, DEFAULT_LIQUID_TESTNET_NODE, DEFAULT_ESPLORA_TIMEOUT_SECS, - )), - Chain::LiquidRegtest => Ok(Self::new( - Chain::BitcoinTestnet, - ®test_url.unwrap(), + ), + Chain::LiquidRegtest => Self::new( + Chain::LiquidRegtest, + regtest_url.unwrap_or(DEFAULT_LIQUID_REGTEST_NODE), DEFAULT_ESPLORA_TIMEOUT_SECS, - )), + ), } } @@ -434,7 +429,7 @@ mod tests { #[macros::async_test_all] async fn test_esplora_default_clients() { - let network_config = EsploraConfig::default(Chain::Bitcoin, None).unwrap(); + let network_config = EsploraConfig::default(Chain::Bitcoin, None); let esplora_client = network_config.build_bitcoin_client().unwrap(); assert!(esplora_client .get_address_balance( @@ -445,7 +440,7 @@ mod tests { .await .is_ok()); - let network_config = EsploraConfig::default(Chain::Liquid, None).unwrap(); + let network_config = EsploraConfig::default(Chain::Liquid, None); let esplora_client = network_config.build_liquid_client().unwrap(); assert_eq!( esplora_client.get_genesis_hash().await.unwrap().to_hex(), diff --git a/src/swaps/boltz.rs b/src/swaps/boltz.rs index b5f3492..8247da4 100644 --- a/src/swaps/boltz.rs +++ b/src/swaps/boltz.rs @@ -34,7 +34,7 @@ use crate::{BtcSwapScript, LBtcSwapScript}; pub const BOLTZ_TESTNET_URL_V2: &str = "https://api.testnet.boltz.exchange/v2"; pub const BOLTZ_MAINNET_URL_V2: &str = "https://api.boltz.exchange/v2"; -pub const BOLTZ_REGTEST: &str = "http://127.0.0.1:9001/v2"; +pub const BOLTZ_REGTEST: &str = "http://localhost:9001/v2"; use url::Url; @@ -1468,7 +1468,6 @@ mod tests { let client = BoltzApiClientV2::new(BOLTZ_MAINNET_URL_V2); let id = "G6c6GJJY8eXz"; let result = client.get_swap(id).await; - println!("{:#?}", result); assert!(result.is_ok(), "Failed to get swap status"); } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 4b7fd6e..f3b77b6 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -12,14 +12,18 @@ pub mod fees; pub mod lnurl; pub mod secrets; +static INIT: Once = Once::new(); + /// Setup function that will only run once, even if called multiple times. pub fn setup_logger() { - Once::new().call_once(|| { + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + INIT.call_once(|| { env_logger::Builder::from_env( env_logger::Env::default() .default_filter_or("debug") .default_write_style_or("always"), ) + .filter_module("serial_test", log::LevelFilter::Error) // .is_test(true) .init(); }); diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 0000000..6d26f64 --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,3 @@ +mod regtest; +mod test_framework; +mod utils; diff --git a/tests/bitcoin.rs b/tests/regtest/bitcoin.rs similarity index 77% rename from tests/bitcoin.rs rename to tests/regtest/bitcoin.rs index e70cf65..a504134 100644 --- a/tests/bitcoin.rs +++ b/tests/regtest/bitcoin.rs @@ -3,14 +3,14 @@ use boltz_client::network::electrum::ElectrumConfig; #[cfg(feature = "esplora")] use boltz_client::network::esplora::EsploraConfig; -use std::{str::FromStr, time::Duration}; +use std::str::FromStr; use boltz_client::{ network::Chain, swaps::{ boltz::{ BoltzApiClientV2, Cooperative, CreateReverseRequest, CreateSubmarineRequest, - Subscription, SwapUpdate, BOLTZ_TESTNET_URL_V2, + Subscription, SwapUpdate, }, magic_routing::{check_for_mrh, sign_address}, }, @@ -18,6 +18,8 @@ use boltz_client::{ Bolt11Invoice, BtcSwapScript, BtcSwapTx, Secp256k1, }; +use crate::regtest::TIMEOUT_MS; +use crate::utils; use bitcoin::{ hashes::{sha256, Hash}, hex::FromHex, @@ -25,34 +27,40 @@ use bitcoin::{ secp256k1::Keypair, PublicKey, }; +use boltz_client::boltz::BOLTZ_REGTEST; use boltz_client::fees::Fee; +use boltz_client::network::esplora::async_sleep; use boltz_client::network::{BitcoinClient, BitcoinNetworkConfig}; use futures_util::{SinkExt, StreamExt}; +use serial_test::serial; use tokio_tungstenite_wasm::Message; -pub mod test_utils; - #[cfg(all(target_family = "wasm", target_os = "unknown"))] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +const CHAIN: Chain = Chain::BitcoinRegtest; + #[macros::async_test] +#[serial] #[cfg(feature = "electrum")] -#[ignore = "Requires testnet invoice and refund address"] async fn bitcoin_v2_submarine_electrum() { - let bitcoin_network_config = ElectrumConfig::default_bitcoin(); - bitcoin_v2_submarine(bitcoin_network_config).await + let bitcoin_network_config = ElectrumConfig::default(CHAIN, None); + bitcoin_v2_submarine(&bitcoin_network_config, false).await; + bitcoin_v2_submarine(&bitcoin_network_config, true).await; } #[macros::async_test_all] +#[serial] #[cfg(feature = "esplora")] -#[ignore = "Requires testnet invoice and refund address"] async fn bitcoin_v2_submarine_esplora() { - let bitcoin_network_config = EsploraConfig::default_bitcoin(); - bitcoin_v2_submarine(bitcoin_network_config).await + let bitcoin_network_config = EsploraConfig::default(CHAIN, None); + bitcoin_v2_submarine(&bitcoin_network_config, false).await; + bitcoin_v2_submarine(&bitcoin_network_config, true).await; } async fn bitcoin_v2_submarine>( - bitcoin_network_config: BN, + bitcoin_network_config: &BN, + underpay: bool, ) { setup_logger(); @@ -65,14 +73,14 @@ async fn bitcoin_v2_submarine>( }; // Set a new invoice string and refund address for each test. - let invoice = "lntb5125180n1pnwmtnvpp5rt2ptzc4329nr8f9qnaeg8nszs2w40rr922wnqe6uw6rt6sfz0escqpjsp5lcvu233r6wmd6tvqvpnwpe97tjwzh0kygtjz9htw2y7j6h5grgkq9q7sqqqqqqqqqqqqqqqqqqqsqqqqqysgqdqqmqz9gxqyjw5qrzjqwfn3p9278ttzzpe0e00uhyxhned3j5d9acqak5emwfpflp8z2cnflctr6qq3f9n3gqqqqlgqqqqqeqqjqmrtu79yvjazp5tcn6nscf27arhevexq64yd0jjmkc8hxlqkh5ywzwk209xvmf484uutvjqv5rtgq0aulm9e4al72wwljm97a3vdcgxcq4vcmxq".to_string(); - let refund_address = "tb1qq20a7gqewc0un9mxxlqyqwn7ut7zjrj9y3d0mu".to_string(); + let invoice = utils::generate_invoice_lnd(50_000).await.unwrap(); + let refund_address = utils::generate_address_bitcoind().await.unwrap(); - let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_TESTNET_URL_V2); + let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_REGTEST); // If there is MRH send directly to that address // let (bip21_addrs, amount) = - // check_for_mrh(&boltz_api_v2, &invoice, Chain::BitcoinTestnet).unwrap(); + // check_for_mrh(&boltz_api_v2, &invoice, CHAIN).unwrap(); // log::info!("Found MRH in invoice"); // log::info!("Send {} to {}", amount, bip21_addrs); // return; @@ -127,9 +135,9 @@ async fn bitcoin_v2_submarine>( channel, args, } => { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == swap_id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!(args.first().expect("expected"), swap_id); log::info!( "Successfully subscribed for Swap updates. Swap ID : {}", swap_id @@ -141,10 +149,10 @@ async fn bitcoin_v2_submarine>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == *swap_id); + assert_eq!(update.id, *swap_id); log::info!("Got Update from server: {}", update.status); // Invoice is Set. Waiting for us to send onchain tx. @@ -155,8 +163,17 @@ async fn bitcoin_v2_submarine>( create_swap_response.address ); - // Test Cooperative Refund. - // Send 1 sat less to than expected amount to Boltz, and let Boltz fail the swap. + let amount = match underpay { + true => create_swap_response.expected_amount - 1, + false => create_swap_response.expected_amount, + }; + utils::send_to_address_bitcoind(&create_swap_response.address, amount) + .await + .unwrap(); + } + + if update.status == "transaction.mempool" { + utils::mine_blocks(1).await.unwrap(); } // Boltz has paid the invoice, and waiting for our partial sig. @@ -166,8 +183,8 @@ async fn bitcoin_v2_submarine>( let swap_tx = BtcSwapTx::new_refund( swap_script.clone(), &refund_address, - &bitcoin_network_config, - BOLTZ_TESTNET_URL_V2.to_owned(), + bitcoin_network_config, + BOLTZ_REGTEST.to_owned(), swap_id.to_owned(), ) .await @@ -186,7 +203,7 @@ async fn bitcoin_v2_submarine>( let preimage_hash = sha256::Hash::hash(&preimage); let invoice = Bolt11Invoice::from_str(&create_swap_req.invoice).unwrap(); let invoice_payment_hash = invoice.payment_hash(); - assert!(invoice_payment_hash.to_string() == preimage_hash.to_string()); + assert_eq!(invoice_payment_hash.to_string(), preimage_hash.to_string()); log::info!("Correct Hash preimage received from Boltz."); // Compute and send Musig2 partial sig @@ -214,17 +231,18 @@ async fn bitcoin_v2_submarine>( if update.status == "transaction.lockupFailed" || update.status == "invoice.failedToPay" { + async_sleep(TIMEOUT_MS).await; let swap_tx = BtcSwapTx::new_refund( swap_script.clone(), &refund_address, - &bitcoin_network_config, - BOLTZ_TESTNET_URL_V2.to_owned(), + bitcoin_network_config, + BOLTZ_REGTEST.to_owned(), swap_id.to_owned(), ) .await .expect("Funding UTXO not found"); - match swap_tx + let tx = swap_tx .sign_refund( &our_keys, Fee::Absolute(1000), @@ -236,32 +254,28 @@ async fn bitcoin_v2_submarine>( }), ) .await - { - Ok(tx) => { - let txid = swap_tx - .broadcast(&tx, &bitcoin_network_config) - .await - .unwrap(); - log::info!("Cooperative Refund Successfully broadcasted: {}", txid); - } - Err(e) => { - log::info!("Cooperative refund failed. {:?}", e); - log::info!("Attempting Non-cooperative refund."); - - let tx = swap_tx - .sign_refund(&our_keys, Fee::Absolute(1000), None) - .await - .unwrap(); - let txid = swap_tx - .broadcast(&tx, &bitcoin_network_config) - .await - .unwrap(); - log::info!( - "Non-cooperative Refund Successfully broadcasted: {}", - txid - ); - } - } + .unwrap(); + + let txid = swap_tx + .broadcast(&tx, bitcoin_network_config) + .await + .unwrap(); + log::info!("Cooperative Refund Successfully broadcasted: {}", txid); + + // Non cooperative refund requires expired swap + /*log::info!("Cooperative refund failed. {:?}", e); + log::info!("Attempting Non-cooperative refund."); + + let tx = swap_tx + .sign_refund(&our_keys, Fee::Absolute(1000), None) + .await + .unwrap(); + let txid = swap_tx + .broadcast(&tx, bitcoin_network_config) + .await + .unwrap(); + log::info!("Non-cooperative Refund Successfully broadcasted: {}", txid);*/ + break; } } @@ -270,8 +284,8 @@ async fn bitcoin_v2_submarine>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); log::error!( "Got Boltz response error : {} for swap: {}", @@ -285,18 +299,18 @@ async fn bitcoin_v2_submarine>( } #[macros::async_test] +#[serial] #[cfg(feature = "electrum")] -#[ignore = "Requires testnet invoice and refund address"] async fn bitcoin_v2_reverse_electrum() { - let bitcoin_network_config = ElectrumConfig::default_bitcoin(); + let bitcoin_network_config = ElectrumConfig::default(CHAIN, None); bitcoin_v2_reverse(bitcoin_network_config).await } #[macros::async_test_all] +#[serial] #[cfg(feature = "esplora")] -#[ignore = "Requires testnet invoice and refund address"] async fn bitcoin_v2_reverse_esplora() { - let bitcoin_network_config = EsploraConfig::default_bitcoin(); + let bitcoin_network_config = EsploraConfig::default(CHAIN, None); bitcoin_v2_reverse(bitcoin_network_config).await } @@ -315,7 +329,7 @@ async fn bitcoin_v2_reverse>( }; // Give a valid claim address or else funds will be lost. - let claim_address = "tb1qq20a7gqewc0un9mxxlqyqwn7ut7zjrj9y3d0mu".to_string(); + let claim_address = utils::generate_address_bitcoind().await.unwrap(); let addrs_sig = sign_address(&claim_address, &our_keys).unwrap(); let create_reverse_req = CreateReverseRequest { @@ -332,14 +346,14 @@ async fn bitcoin_v2_reverse>( webhook: None, }; - let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_TESTNET_URL_V2); + let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_REGTEST); let reverse_resp = boltz_api_v2 .post_reverse_req(create_reverse_req) .await .unwrap(); - let _ = check_for_mrh(&boltz_api_v2, &reverse_resp.invoice, Chain::BitcoinTestnet) + let _ = check_for_mrh(&boltz_api_v2, &reverse_resp.invoice, CHAIN) .await .unwrap() .unwrap(); @@ -375,9 +389,9 @@ async fn bitcoin_v2_reverse>( channel, args, } => { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == &swap_id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!(args.first().expect("expected"), &swap_id); log::info!("Subscription successful for swap : {}", &swap_id); } @@ -386,27 +400,31 @@ async fn bitcoin_v2_reverse>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == swap_id); + assert_eq!(update.id, swap_id); log::info!("Got Update from server: {}", update.status); if update.status == "swap.created" { log::info!("Waiting for Invoice to be paid: {}", &reverse_resp.invoice); + + let invoice = reverse_resp.invoice.clone(); + utils::start_pay_invoice_lnd(invoice); + continue; } if update.status == "transaction.mempool" { log::info!("Boltz broadcasted funding tx"); - std::thread::sleep(Duration::from_secs(15)); + async_sleep(TIMEOUT_MS).await; let claim_tx = BtcSwapTx::new_claim( swap_script.clone(), claim_address.clone(), &bitcoin_network_config, - BOLTZ_TESTNET_URL_V2.to_owned(), + BOLTZ_REGTEST.to_owned(), swap_id.clone(), ) .await @@ -447,8 +465,8 @@ async fn bitcoin_v2_reverse>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); println!("Got error : {} for swap: {}", error.error, error.id); } @@ -458,18 +476,18 @@ async fn bitcoin_v2_reverse>( } #[macros::async_test] +#[serial] #[cfg(feature = "electrum")] -#[ignore = "Requires testnet invoice and refund address"] async fn bitcoin_v2_reverse_script_path_electrum() { - let bitcoin_network_config = ElectrumConfig::default_bitcoin(); + let bitcoin_network_config = ElectrumConfig::default(CHAIN, None); bitcoin_v2_reverse_script_path(bitcoin_network_config).await } #[macros::async_test_all] +#[serial] #[cfg(feature = "esplora")] -#[ignore = "Requires testnet invoice and refund address"] async fn bitcoin_v2_reverse_script_path_esplora() { - let bitcoin_network_config = EsploraConfig::default_bitcoin(); + let bitcoin_network_config = EsploraConfig::default(CHAIN, None); bitcoin_v2_reverse_script_path(bitcoin_network_config).await } @@ -488,7 +506,7 @@ async fn bitcoin_v2_reverse_script_path { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == &swap_id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!(args.first().expect("expected"), &swap_id); log::info!("Subscription successful for swap : {}", &swap_id); } @@ -561,27 +579,31 @@ async fn bitcoin_v2_reverse_script_path { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == swap_id); + assert_eq!(update.id, swap_id); log::info!("Got Update from server: {}", update.status); if update.status == "swap.created" { log::info!("Waiting for Invoice to be paid: {}", &reverse_resp.invoice); + + let invoice = reverse_resp.invoice.clone(); + utils::start_pay_invoice_lnd(invoice); + continue; } if update.status == "transaction.mempool" { log::info!("Boltz broadcasted funding tx"); - std::thread::sleep(Duration::from_secs(15)); + async_sleep(TIMEOUT_MS).await; let claim_tx = BtcSwapTx::new_claim( swap_script.clone(), claim_address.clone(), &bitcoin_network_config, - BOLTZ_TESTNET_URL_V2.to_owned(), + BOLTZ_REGTEST.to_owned(), swap_id, ) .await @@ -612,8 +634,8 @@ async fn bitcoin_v2_reverse_script_path { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); println!("Got error : {} for swap: {}", error.error, error.id); } diff --git a/tests/chain_swaps.rs b/tests/regtest/chain_swaps.rs similarity index 75% rename from tests/chain_swaps.rs rename to tests/regtest/chain_swaps.rs index 0afca31..fbb2243 100644 --- a/tests/chain_swaps.rs +++ b/tests/regtest/chain_swaps.rs @@ -1,12 +1,14 @@ +use crate::utils; use bitcoin::{key::rand::thread_rng, PublicKey}; use boltz_client::boltz::{ BoltzApiClientV2, ChainSwapDetails, Cooperative, CreateChainRequest, Side, Subscription, - SwapUpdate, BOLTZ_TESTNET_URL_V2, + SwapUpdate, BOLTZ_REGTEST, BOLTZ_TESTNET_URL_V2, }; use boltz_client::fees::Fee; #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] #[cfg(feature = "electrum")] use boltz_client::network::electrum::ElectrumConfig; +use boltz_client::network::esplora::async_sleep; #[cfg(feature = "esplora")] use boltz_client::network::esplora::EsploraConfig; use boltz_client::network::{ @@ -19,29 +21,36 @@ use boltz_client::{ }; use elements::Address as EAddress; use futures_util::{SinkExt, StreamExt}; +use serial_test::serial; use std::str::FromStr; -use std::time::Duration; use tokio_tungstenite_wasm::Message; #[cfg(all(target_family = "wasm", target_os = "unknown"))] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +const BITCOIN_CHAIN: Chain = Chain::BitcoinRegtest; +const LIQUID_CHAIN: Chain = Chain::LiquidRegtest; + #[macros::async_test] +#[serial] #[cfg(feature = "electrum")] -#[ignore] async fn bitcoin_liquid_v2_chain_electrum() { - let bitcoin_network_config = ElectrumConfig::default_bitcoin(); - let liquid_network_config = ElectrumConfig::default_liquid(); - bitcoin_liquid_v2_chain(bitcoin_network_config, liquid_network_config).await + setup_logger(); + let bitcoin_network_config = ElectrumConfig::default(BITCOIN_CHAIN, None); + let liquid_network_config = ElectrumConfig::default(LIQUID_CHAIN, None); + bitcoin_liquid_v2_chain(&bitcoin_network_config, &liquid_network_config, false).await; + bitcoin_liquid_v2_chain(&bitcoin_network_config, &liquid_network_config, true).await; } #[macros::async_test_all] +#[serial] #[cfg(feature = "esplora")] -#[ignore] async fn bitcoin_liquid_v2_chain_esplora() { - let bitcoin_network_config = EsploraConfig::default_bitcoin(); - let liquid_network_config = EsploraConfig::default_liquid(); - bitcoin_liquid_v2_chain(bitcoin_network_config, liquid_network_config).await + setup_logger(); + let bitcoin_network_config = EsploraConfig::default(BITCOIN_CHAIN, None); + let liquid_network_config = EsploraConfig::default(LIQUID_CHAIN, None); + bitcoin_liquid_v2_chain(&bitcoin_network_config, &liquid_network_config, false).await; + bitcoin_liquid_v2_chain(&bitcoin_network_config, &liquid_network_config, true).await; } async fn bitcoin_liquid_v2_chain< @@ -50,11 +59,11 @@ async fn bitcoin_liquid_v2_chain< LC: LiquidClient, LN: LiquidNetworkConfig, >( - bitcoin_network_config: BN, - liquid_network_config: LN, + bitcoin_network_config: &BN, + liquid_network_config: &LN, + underpay: bool, ) { - setup_logger(); - let network = Chain::BitcoinTestnet; + let network = BITCOIN_CHAIN; let secp = Secp256k1::new(); let preimage = Preimage::new(); log::info!("{:#?}", preimage); @@ -79,13 +88,13 @@ async fn bitcoin_liquid_v2_chain< claim_public_key: Some(claim_public_key), refund_public_key: Some(refund_public_key), referral_id: None, - user_lock_amount: Some(1000000), + user_lock_amount: Some(50_000), server_lock_amount: None, pair_hash: None, // Add address signature here. webhook: None, }; - let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_TESTNET_URL_V2); + let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_REGTEST); let create_chain_response = boltz_api_v2.post_chain_req(create_chain_req).await.unwrap(); let swap_id = create_chain_response.clone().id; @@ -112,7 +121,7 @@ async fn bitcoin_liquid_v2_chain< lockup_address.clone().to_string(), lockup_details.clone().lockup_address.to_string() ); - let refund_address = "tb1qra2cdypld3hyq3f84630cvj9d0lmzv66vn4k28".to_string(); + let refund_address = utils::generate_address_bitcoind().await.unwrap(); let claim_details: ChainSwapDetails = create_chain_response.claim_details; @@ -120,7 +129,7 @@ async fn bitcoin_liquid_v2_chain< LBtcSwapScript::chain_from_swap_resp(Side::Claim, claim_details.clone(), claim_public_key) .unwrap(); - let claim_address = "tlq1qq0y3xudhc909fur3ktaws0yrhjv3ld9c2fk5hqzjfmgqurl0cy4z8yc8d9h54lj7ddwatzegwamyqhp4vttxj26wml4s9vecx".to_string(); + let claim_address = utils::generate_address_elementsd().await.unwrap(); let lq_address = EAddress::from_str(&claim_address).unwrap(); log::debug!("{:#?}", lq_address); // let claim_address = claim_script.to_address(network).unwrap(); @@ -157,9 +166,9 @@ async fn bitcoin_liquid_v2_chain< channel, args, } => { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == &swap_id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!(args.first().expect("expected"), &swap_id); log::info!( "Successfully subscribed for Swap updates. Swap ID : {}", swap_id @@ -171,34 +180,42 @@ async fn bitcoin_liquid_v2_chain< channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == swap_id); + assert_eq!(update.id, swap_id); log::info!("Got Update from server: {}", update.status); if update.status == "swap.created" { - log::info!( - "Send {} sats to BTC address {}", - create_chain_response.lockup_details.clone().amount, - create_chain_response.lockup_details.clone().lockup_address - ); - log::info!( - "TO TRIGGER REFUND: Send 50,000 sats to BTC address {}", - create_chain_response.lockup_details.clone().lockup_address - ); + let amount = match underpay { + true => create_chain_response.lockup_details.amount / 2, + false => create_chain_response.lockup_details.amount, + }; + let address = create_chain_response.lockup_details.clone().lockup_address; + + log::info!("Sending {} sats to BTC address {}", amount, address); + + utils::send_to_address_bitcoind(&address, amount) + .await + .unwrap(); + } + + if update.status == "transaction.mempool" + || update.status == "transaction.server.mempool" + { + utils::mine_blocks(1).await.unwrap(); } if update.status == "transaction.server.confirmed" { log::info!("Server lockup tx is confirmed!"); - std::thread::sleep(Duration::from_secs(10)); + async_sleep(10_000).await; log::info!("Claiming!"); let claim_tx = LBtcSwapTx::new_claim( claim_script.clone(), claim_address.clone(), - &liquid_network_config, + liquid_network_config, BOLTZ_TESTNET_URL_V2.to_string(), swap_id.clone(), ) @@ -207,7 +224,7 @@ async fn bitcoin_liquid_v2_chain< let refund_tx = BtcSwapTx::new_refund( lockup_script.clone(), &refund_address, - &bitcoin_network_config, + bitcoin_network_config, BOLTZ_TESTNET_URL_V2.to_owned(), swap_id.clone(), ) @@ -241,7 +258,7 @@ async fn bitcoin_liquid_v2_chain< .unwrap(); claim_tx - .broadcast(&tx, &liquid_network_config, None) + .broadcast(&tx, liquid_network_config, None) .await .unwrap(); @@ -256,7 +273,7 @@ async fn bitcoin_liquid_v2_chain< // This means the funding transaction was rejected by Boltz for whatever reason, and we need to get // fund back via refund. if update.status == "transaction.lockupFailed" { - std::thread::sleep(Duration::from_secs(10)); + async_sleep(10_000).await; log::info!("REFUNDING!"); refund_bitcoin_liquid_v2_chain( lockup_script.clone(), @@ -265,7 +282,7 @@ async fn bitcoin_liquid_v2_chain< our_refund_keys, boltz_api_v2.clone(), 100, - &bitcoin_network_config, + bitcoin_network_config, ) .await; log::info!("REFUNDING with higher fee"); @@ -276,9 +293,10 @@ async fn bitcoin_liquid_v2_chain< our_refund_keys, boltz_api_v2.clone(), 1000, - &bitcoin_network_config, + bitcoin_network_config, ) .await; + break; } } @@ -287,8 +305,8 @@ async fn bitcoin_liquid_v2_chain< channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); log::error!( "Got Boltz response error : {} for swap: {}", @@ -343,21 +361,25 @@ async fn refund_bitcoin_liquid_v2_chain, >( - bitcoin_network_config: BN, - liquid_network_config: LN, + bitcoin_network_config: &BN, + liquid_network_config: &LN, + underpay: bool, ) { - setup_logger(); - let network = Chain::LiquidTestnet; + let network = LIQUID_CHAIN; let secp = Secp256k1::new(); let preimage = Preimage::new(); log::info!("{:#?}", preimage); @@ -395,13 +417,13 @@ async fn liquid_bitcoin_v2_chain< claim_public_key: Some(claim_public_key), refund_public_key: Some(refund_public_key), referral_id: None, - user_lock_amount: Some(1000000), + user_lock_amount: Some(50_000), server_lock_amount: None, pair_hash: None, // Add address signature here. webhook: None, }; - let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_TESTNET_URL_V2); + let boltz_api_v2 = BoltzApiClientV2::new(BOLTZ_REGTEST); let create_chain_response = boltz_api_v2.post_chain_req(create_chain_req).await.unwrap(); let swap_id = create_chain_response.clone().id; @@ -432,7 +454,7 @@ async fn liquid_bitcoin_v2_chain< lockup_address.clone().to_string(), lockup_details.clone().lockup_address.to_string() ); - let refund_address = "tlq1qq0y3xudhc909fur3ktaws0yrhjv3ld9c2fk5hqzjfmgqurl0cy4z8yc8d9h54lj7ddwatzegwamyqhp4vttxj26wml4s9vecx".to_string(); + let refund_address = utils::generate_address_elementsd().await.unwrap(); let claim_details: ChainSwapDetails = create_chain_response.claim_details; @@ -440,7 +462,7 @@ async fn liquid_bitcoin_v2_chain< BtcSwapScript::chain_from_swap_resp(Side::Claim, claim_details.clone(), claim_public_key) .unwrap(); - let claim_address = "tb1qra2cdypld3hyq3f84630cvj9d0lmzv66vn4k28".to_string(); + let claim_address = utils::generate_address_bitcoind().await.unwrap(); let (mut sender, mut receiver) = boltz_api_v2.connect_ws().await.unwrap().split(); @@ -465,9 +487,9 @@ async fn liquid_bitcoin_v2_chain< channel, args, } => { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == &swap_id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!(args.first().expect("expected"), &swap_id); log::info!( "Successfully subscribed for Swap updates. Swap ID : {}", swap_id @@ -479,34 +501,42 @@ async fn liquid_bitcoin_v2_chain< channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == swap_id); + assert_eq!(update.id, swap_id); log::info!("Got Update from server: {}", update.status); if update.status == "swap.created" { - log::info!( - "Send {} sats to L-BTC address {}", - create_chain_response.lockup_details.clone().amount, - create_chain_response.lockup_details.clone().lockup_address - ); - log::info!( - "TO TRIGGER REFUND: Send 10,000 sats to L-BTC address {}", - create_chain_response.lockup_details.clone().lockup_address - ); + let amount = match underpay { + true => create_chain_response.lockup_details.amount / 2, + false => create_chain_response.lockup_details.amount, + }; + let address = create_chain_response.lockup_details.clone().lockup_address; + + log::info!("Sending {} sats to L-BTC address {}", amount, address); + + utils::send_to_address_elementsd(&address, amount) + .await + .unwrap(); + } + + if update.status == "transaction.mempool" + || update.status == "transaction.server.mempool" + { + utils::mine_blocks(1).await.unwrap(); } if update.status == "transaction.server.confirmed" { log::info!("Server lockup tx is confirmed!"); - std::thread::sleep(Duration::from_secs(10)); + async_sleep(10_000).await; log::info!("Claiming!"); let claim_tx = BtcSwapTx::new_claim( claim_script.clone(), claim_address.clone(), - &bitcoin_network_config, + bitcoin_network_config, BOLTZ_TESTNET_URL_V2.to_owned(), swap_id.clone(), ) @@ -515,7 +545,7 @@ async fn liquid_bitcoin_v2_chain< let refund_tx = LBtcSwapTx::new_refund( lockup_script.clone(), &refund_address, - &liquid_network_config, + liquid_network_config, BOLTZ_TESTNET_URL_V2.to_string(), swap_id.clone(), ) @@ -548,11 +578,11 @@ async fn liquid_bitcoin_v2_chain< .unwrap(); claim_tx - .broadcast(&tx, &bitcoin_network_config) + .broadcast(&tx, bitcoin_network_config) .await .unwrap(); - log::info!("Succesfully broadcasted claim tx!"); + log::info!("Successfully broadcasted claim tx!"); } if update.status == "transaction.claimed" { @@ -563,11 +593,12 @@ async fn liquid_bitcoin_v2_chain< // This means the funding transaction was rejected by Boltz for whatever reason, and we need to get // fund back via refund. if update.status == "transaction.lockupFailed" { + async_sleep(10_000).await; log::info!("REFUNDING!"); let refund_tx = LBtcSwapTx::new_refund( lockup_script.clone(), &refund_address, - &liquid_network_config, + liquid_network_config, BOLTZ_TESTNET_URL_V2.to_string(), swap_id.clone(), ) @@ -589,12 +620,13 @@ async fn liquid_bitcoin_v2_chain< .unwrap(); refund_tx - .broadcast(&tx, &liquid_network_config, None) + .broadcast(&tx, liquid_network_config, None) .await .unwrap(); - log::info!("Succesfully broadcasted claim tx!"); + log::info!("Successfully broadcasted claim tx!"); log::debug!("Claim Tx {:?}", tx); + break; } } @@ -603,8 +635,8 @@ async fn liquid_bitcoin_v2_chain< channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); log::error!( "Got Boltz response error : {} for swap: {}", diff --git a/tests/liquid.rs b/tests/regtest/liquid.rs similarity index 60% rename from tests/liquid.rs rename to tests/regtest/liquid.rs index ed15f54..2fc8156 100644 --- a/tests/liquid.rs +++ b/tests/regtest/liquid.rs @@ -3,60 +3,66 @@ use boltz_client::network::electrum::ElectrumConfig; #[cfg(feature = "esplora")] use boltz_client::network::esplora::EsploraConfig; -use std::{str::FromStr, time::Duration}; - use boltz_client::{ network::Chain, swaps::{ boltz::{ BoltzApiClientV2, Cooperative, CreateReverseRequest, CreateSubmarineRequest, - Subscription, SwapType, SwapUpdate, BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2, + Subscription, SwapUpdate, }, magic_routing::{check_for_mrh, sign_address}, }, util::{secrets::Preimage, setup_logger}, - Bolt11Invoice, Hash as BCHash, LBtcSwapScript, LBtcSwapTx, Secp256k1, Serialize, + Bolt11Invoice, LBtcSwapScript, LBtcSwapTx, Secp256k1, }; +use std::str::FromStr; +use crate::regtest::TIMEOUT_MS; +use crate::utils; use bitcoin::{ hashes::{sha256, Hash}, - hex::{DisplayHex, FromHex}, + hex::FromHex, key::rand::thread_rng, secp256k1::Keypair, PublicKey, }; +use boltz_client::boltz::BOLTZ_REGTEST; use boltz_client::fees::Fee; +use boltz_client::network::esplora::async_sleep; use boltz_client::network::{LiquidClient, LiquidNetworkConfig}; -use elements::encode::serialize; use futures_util::{SinkExt, StreamExt}; +use serial_test::serial; use tokio_tungstenite_wasm::Message; -pub mod test_utils; - #[cfg(all(target_family = "wasm", target_os = "unknown"))] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +const CHAIN: Chain = Chain::LiquidRegtest; + #[macros::async_test] +#[serial] #[cfg(feature = "electrum")] -#[ignore = "Requires testnet invoice and refund address"] async fn liquid_v2_submarine_electrum() { - let liquid_network_config = ElectrumConfig::default_liquid(); - liquid_v2_submarine(liquid_network_config).await + setup_logger(); + let liquid_network_config = ElectrumConfig::default(CHAIN, None); + liquid_v2_submarine(&liquid_network_config, false).await; + liquid_v2_submarine(&liquid_network_config, true).await; } #[macros::async_test_all] +#[serial] #[cfg(feature = "esplora")] -#[ignore = "Requires testnet invoice and refund address"] async fn liquid_v2_submarine_esplora() { - let liquid_network_config = EsploraConfig::default_liquid(); - liquid_v2_submarine(liquid_network_config).await + setup_logger(); + let liquid_network_config = EsploraConfig::default(CHAIN, None); + liquid_v2_submarine(&liquid_network_config, false).await; + liquid_v2_submarine(&liquid_network_config, true).await; } async fn liquid_v2_submarine>( - liquid_network_config: LN, + liquid_network_config: &LN, + underpay: bool, ) { - setup_logger(); - let secp = Secp256k1::new(); let our_keys = Keypair::new(&secp, &mut thread_rng()); let refund_public_key = PublicKey { @@ -65,15 +71,15 @@ async fn liquid_v2_submarine>( }; // Set a new invoice string and refund address for each test. - let invoice = "lnbc12320n1pn9q9hlpp5v30gg9ylrpkgyn6pctthd22xvu0lsuctw9nee7t3emljvh5ty2nscqpjsp54p8gweqlqdnstedcmzy8ktgup4auaq6wy6lcryu0085kvr6a77gs9q7sqqqqqqqqqqqqqqqqqqqsqqqqqysgqdqqmqz9gxqyjw5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glcllard4vsfze0gsqqqqlgqqqqqeqqjq3c4qzawwh62kzj3cdykcaszjd9l4wfcwlxhq4afwhvsjllu27pen26rsxaa0gfx602nl7feh87c4s39n5p47lfsu2k38vgfjc8nvhrspg50t63".to_string(); - let refund_address = "tlq1qq05cvratqvsfmvdkag7rat2sav97f2aq3x9mzqhcgavygjnnetgy57zhurzvwwklexrxtdecw0ejfgwcjsxvqmrptdf25vrj8".to_string(); - let boltz_url = BOLTZ_TESTNET_URL_V2; - let chain = Chain::Liquid; + let invoice = utils::generate_invoice_lnd(50_000).await.unwrap(); + let refund_address = utils::generate_address_elementsd().await.unwrap(); + let boltz_url = BOLTZ_REGTEST; + let chain = CHAIN; let boltz_api_v2 = BoltzApiClientV2::new(boltz_url); // If there is MRH send directly to that address // if let Some((bip21_addrs, amount)) = - // check_for_mrh(&boltz_api_v2, &invoice, Chain::BitcoinTestnet).unwrap() + // check_for_mrh(&boltz_api_v2, &invoice, CHAIN).unwrap() // { // log::info!("Found MRH in invoice"); // log::info!("Send {} to {}", amount, bip21_addrs); @@ -132,9 +138,12 @@ async fn liquid_v2_submarine>( channel, args, } => { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == &create_swap_response.clone().id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!( + args.first().expect("expected"), + &create_swap_response.clone().id + ); log::info!( "Successfully subscribed for Swap updates. Swap ID : {}", create_swap_response.clone().id @@ -146,19 +155,31 @@ async fn liquid_v2_submarine>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == create_swap_response.clone().id); + assert_eq!(update.id, create_swap_response.clone().id); log::info!("Got Update from server: {}", update.status); // Invoice is Set. Waiting for us to send onchain tx. if update.status == "invoice.set" { log::info!( - "Send {} sats to BTC address {}", + "Send {} sats to Liquid address {}", create_swap_response.expected_amount, create_swap_response.address ); + + let amount = match underpay { + true => create_swap_response.expected_amount - 1, + false => create_swap_response.expected_amount, + }; + utils::send_to_address_elementsd(&create_swap_response.address, amount) + .await + .unwrap(); + } + + if update.status == "transaction.mempool" { + utils::mine_blocks(1).await.unwrap(); } // Boltz has paid the invoice, and waiting for our partial sig. @@ -167,7 +188,7 @@ async fn liquid_v2_submarine>( let swap_tx = LBtcSwapTx::new_refund( swap_script.clone(), &refund_address, - &liquid_network_config, + liquid_network_config, boltz_url.to_string(), create_swap_response.clone().id, ) @@ -188,7 +209,7 @@ async fn liquid_v2_submarine>( let preimage_hash = sha256::Hash::hash(&preimage); let invoice = Bolt11Invoice::from_str(&create_swap_req.invoice).unwrap(); let invoice_payment_hash = invoice.payment_hash(); - assert!(invoice_payment_hash.to_string() == preimage_hash.to_string()); + assert_eq!(invoice_payment_hash.to_string(), preimage_hash.to_string()); log::info!("Correct Hash preimage received from Boltz."); // Compute and send Musig2 partial sig @@ -215,57 +236,53 @@ async fn liquid_v2_submarine>( if update.status == "transaction.lockupFailed" || update.status == "invoice.failedToPay" { + async_sleep(TIMEOUT_MS).await; let swap_tx = LBtcSwapTx::new_refund( swap_script.clone(), &refund_address, - &liquid_network_config, + liquid_network_config, boltz_url.to_string(), create_swap_response.clone().id, ) .await .unwrap(); - match swap_tx + // Coop refund + let tx = swap_tx .sign_refund( &our_keys, Fee::Absolute(1000), - None, - // Some(Cooperative { - // boltz_api: &boltz_api_v2, - // swap_id: create_swap_response.id.clone(), - // pub_nonce: None, - // partial_sig: None, - // }), + Some(Cooperative { + boltz_api: &boltz_api_v2, + swap_id: create_swap_response.id.clone(), + pub_nonce: None, + partial_sig: None, + }), false, ) .await - { - Ok(tx) => { - println!("{}", tx.serialize().to_lower_hex_string()); - let txid = swap_tx - .broadcast(&tx, &liquid_network_config, None) - .await - .unwrap(); - log::info!("Cooperative Refund Successfully broadcasted: {}", txid); - } - Err(e) => { - log::info!("Cooperative refund failed. {:?}", e); - log::info!("Attempting Non-cooperative refund."); + .unwrap(); - let tx = swap_tx - .sign_refund(&our_keys, Fee::Absolute(1000), None, false) - .await - .unwrap(); - let txid = swap_tx - .broadcast(&tx, &liquid_network_config, None) - .await - .unwrap(); - log::info!( - "Non-cooperative Refund Successfully broadcasted: {}", - txid - ); - } - } + let txid = swap_tx + .broadcast(&tx, liquid_network_config, None) + .await + .unwrap(); + log::info!("Cooperative Refund Successfully broadcasted: {}", txid); + + // Non cooperative refund requires expired swap + /*log::info!("Attempting Non-cooperative refund."); + + let tx = swap_tx + .sign_refund(&our_keys, Fee::Absolute(1000), None, false) + .await + .unwrap(); + let txid = swap_tx + .broadcast(&tx, liquid_network_config, None) + .await + .unwrap(); + log::info!("Non-cooperative Refund Successfully broadcasted: {}", txid); + */ + break; } if update.status == "transaction.claimed" { @@ -279,8 +296,8 @@ async fn liquid_v2_submarine>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); log::error!( "Got Boltz response error : {} for swap: {}", @@ -294,39 +311,42 @@ async fn liquid_v2_submarine>( } #[macros::async_test] +#[serial] #[cfg(feature = "electrum")] -#[ignore = "Requires testnet invoice and refund address"] async fn liquid_v2_reverse_electrum() { - let liquid_network_config = ElectrumConfig::default_liquid(); - liquid_v2_reverse(liquid_network_config).await + setup_logger(); + let liquid_network_config = ElectrumConfig::default(CHAIN, None); + liquid_v2_reverse(&liquid_network_config, false).await; + liquid_v2_reverse(&liquid_network_config, true).await; } #[macros::async_test_all] +#[serial] #[cfg(feature = "esplora")] -#[ignore = "Requires testnet invoice and refund address"] async fn liquid_v2_reverse_esplora() { - let liquid_network_config = EsploraConfig::default_liquid(); - liquid_v2_reverse(liquid_network_config).await + setup_logger(); + let liquid_network_config = EsploraConfig::default(CHAIN, None); + liquid_v2_reverse(&liquid_network_config, false).await; + liquid_v2_reverse(&liquid_network_config, true).await; } async fn liquid_v2_reverse>( - liquid_network_config: LN, + liquid_network_config: &LN, + lowball: bool, ) { - setup_logger(); - let secp = Secp256k1::new(); let preimage = Preimage::new(); let our_keys = Keypair::new(&secp, &mut thread_rng()); - let invoice_amount = 1111; + let invoice_amount = 50_000; let claim_public_key = PublicKey { compressed: true, inner: our_keys.public_key(), }; // Give a valid claim address or else funds will be lost. - let claim_address = "tlq1qq05cvratqvsfmvdkag7rat2sav97f2aq3x9mzqhcgavygjnnetgy57zhurzvwwklexrxtdecw0ejfgwcjsxvqmrptdf25vrj8".to_string(); - let boltz_url = BOLTZ_MAINNET_URL_V2; - let chain = Chain::Liquid; + let claim_address = utils::generate_address_elementsd().await.unwrap(); + let boltz_url = BOLTZ_REGTEST; + let chain = CHAIN; let boltz_api_v2 = BoltzApiClientV2::new(boltz_url); let addrs_sig = sign_address(&claim_address, &our_keys).unwrap(); @@ -356,7 +376,7 @@ async fn liquid_v2_reverse>( let swap_id = reverse_resp.clone().id; - let _ = check_for_mrh(&boltz_api_v2, &reverse_resp.invoice, Chain::BitcoinTestnet) + let _ = check_for_mrh(&boltz_api_v2, &reverse_resp.invoice, CHAIN) .await .unwrap() .unwrap(); @@ -365,7 +385,7 @@ async fn liquid_v2_reverse>( let swap_script = LBtcSwapScript::reverse_from_swap_resp(&reverse_resp, claim_public_key).unwrap(); - swap_script.to_address(Chain::LiquidTestnet).unwrap(); + swap_script.to_address(CHAIN).unwrap(); // Subscribe to wss status updates let (mut sender, mut receiver) = boltz_api_v2.connect_ws().await.unwrap().split(); @@ -393,9 +413,9 @@ async fn liquid_v2_reverse>( channel, args, } => { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == &swap_id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!(args.first().expect("expected"), &swap_id); log::info!("Subscription successful for swap : {}", &swap_id); } @@ -404,27 +424,31 @@ async fn liquid_v2_reverse>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == swap_id); + assert_eq!(update.id, swap_id); log::info!("Got Update from server: {}", update.status); if update.status == "swap.created" { log::info!("Waiting for Invoice to be paid: {}", &reverse_resp.invoice); + + let invoice = reverse_resp.invoice.clone(); + utils::start_pay_invoice_lnd(invoice); + continue; } if update.status == "transaction.mempool" { log::info!("Boltz broadcasted funding tx"); - std::thread::sleep(Duration::from_secs(5)); + async_sleep(5_000).await; let claim_tx = LBtcSwapTx::new_claim( swap_script.clone(), claim_address.clone(), - &liquid_network_config, - BOLTZ_TESTNET_URL_V2.to_string(), + liquid_network_config, + BOLTZ_REGTEST.to_string(), swap_id.clone(), ) .await @@ -447,21 +471,26 @@ async fn liquid_v2_reverse>( .await .unwrap(); - claim_tx - .broadcast(&tx, &liquid_network_config, None) - .await - .unwrap(); - - // To test Lowball broadcast uncomment below line - // claim_tx - // .broadcast( - // &tx, - // &ElectrumConfig::default_liquid(), - // Some((&boltz_api_v2, boltz_client::network::Chain::LiquidTestnet)), - // ) - // .unwrap(); - - log::info!("Succesfully broadcasted claim tx!"); + match lowball { + true => { + claim_tx + .broadcast( + &tx, + liquid_network_config, + Some((&boltz_api_v2, CHAIN)), + ) + .await + .unwrap(); + log::info!("Successfully broadcasted claim tx using lowball!"); + } + false => { + claim_tx + .broadcast(&tx, liquid_network_config, None) + .await + .unwrap(); + log::info!("Successfully broadcasted claim tx!"); + } + } log::debug!("Claim Tx {:?}", tx); } @@ -476,8 +505,8 @@ async fn liquid_v2_reverse>( channel, args, } => { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); println!("Got error : {} for swap: {}", error.error, error.id); } @@ -487,39 +516,42 @@ async fn liquid_v2_reverse>( } #[macros::async_test] +#[serial] #[cfg(feature = "electrum")] -#[ignore = "Requires testnet invoice and refund address"] async fn liquid_v2_reverse_script_path_electrum() { - let liquid_network_config = ElectrumConfig::default_liquid(); - liquid_v2_reverse_script_path(liquid_network_config).await + setup_logger(); + let liquid_network_config = ElectrumConfig::default(CHAIN, None); + liquid_v2_reverse_script_path(&liquid_network_config, false).await; + liquid_v2_reverse_script_path(&liquid_network_config, true).await; } #[macros::async_test_all] +#[serial] #[cfg(feature = "esplora")] -#[ignore = "Requires testnet invoice and refund address"] async fn liquid_v2_reverse_script_path_esplora() { - let liquid_network_config = EsploraConfig::default_liquid(); - liquid_v2_reverse_script_path(liquid_network_config).await + setup_logger(); + let liquid_network_config = EsploraConfig::default(CHAIN, None); + liquid_v2_reverse_script_path(&liquid_network_config, false).await; + liquid_v2_reverse_script_path(&liquid_network_config, true).await; } async fn liquid_v2_reverse_script_path>( - liquid_network_config: LN, + liquid_network_config: &LN, + lowball: bool, ) { - setup_logger(); - let secp = Secp256k1::new(); let preimage = Preimage::new(); let our_keys = Keypair::new(&secp, &mut thread_rng()); - let invoice_amount = 11110; + let invoice_amount = 50_000; let claim_public_key = PublicKey { compressed: true, inner: our_keys.public_key(), }; // Give a valid claim address or else funds will be lost. - let claim_address = "tlq1qq05cvratqvsfmvdkag7rat2sav97f2aq3x9mzqhcgavygjnnetgy57zhurzvwwklexrxtdecw0ejfgwcjsxvqmrptdf25vrj8".to_string(); - let boltz_url = BOLTZ_TESTNET_URL_V2; - let chain = Chain::LiquidTestnet; + let claim_address = utils::generate_address_elementsd().await.unwrap(); + let boltz_url = BOLTZ_REGTEST; + let chain = CHAIN; let boltz_api_v2 = BoltzApiClientV2::new(boltz_url); let addrs_sig = sign_address(&claim_address, &our_keys).unwrap(); @@ -549,7 +581,7 @@ async fn liquid_v2_reverse_script_path { - assert!(event == "subscribe"); - assert!(channel == "swap.update"); - assert!(args.first().expect("expected") == &swap_id); + assert_eq!(event, "subscribe"); + assert_eq!(channel, "swap.update"); + assert_eq!(args.first().expect("expected"), &swap_id); log::info!("Subscription successful for swap : {}", &swap_id); } @@ -597,27 +629,31 @@ async fn liquid_v2_reverse_script_path { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let update = args.first().expect("expected"); - assert!(update.id == swap_id); + assert_eq!(update.id, swap_id); log::info!("Got Update from server: {}", update.status); if update.status == "swap.created" { log::info!("Waiting for Invoice to be paid: {}", &reverse_resp.invoice); + + let invoice = reverse_resp.invoice.clone(); + utils::start_pay_invoice_lnd(invoice); + continue; } if update.status == "transaction.mempool" { log::info!("Boltz broadcasted funding tx"); - std::thread::sleep(Duration::from_secs(5)); + async_sleep(5_000).await; let claim_tx = LBtcSwapTx::new_claim( swap_script.clone(), claim_address.clone(), - &liquid_network_config, - BOLTZ_TESTNET_URL_V2.to_string(), + liquid_network_config, + BOLTZ_REGTEST.to_string(), swap_id.clone(), ) .await @@ -628,21 +664,26 @@ async fn liquid_v2_reverse_script_path { + claim_tx + .broadcast( + &tx, + liquid_network_config, + Some((&boltz_api_v2, CHAIN)), + ) + .await + .unwrap(); + log::info!("Successfully broadcasted claim tx using lowball!"); + } + false => { + claim_tx + .broadcast(&tx, liquid_network_config, None) + .await + .unwrap(); + log::info!("Successfully broadcasted claim tx!"); + } + } log::debug!("Claim Tx {:?}", tx); } @@ -657,8 +698,8 @@ async fn liquid_v2_reverse_script_path { - assert!(event == "update"); - assert!(channel == "swap.update"); + assert_eq!(event, "update"); + assert_eq!(channel, "swap.update"); let error = args.first().expect("expected"); println!("Got error : {} for swap: {}", error.error, error.id); } @@ -666,117 +707,3 @@ async fn liquid_v2_reverse_script_path>( - liquid_network_config: LN, -) { - setup_logger(); - - let id = "xJ9E5spWSbmw".to_string(); - let secp = Secp256k1::new(); - const RETURN_ADDRESS: &str = "lq1qqf0la7qlx5un0ssn6h2s3s4m3wlgyqlkprf3lzd8utjfvd95dc5ljhsk2684ahr842dse89whesfcgtm4vkazdjzc7e42lg0c"; - let _out_amount = 50_000; - let keypair = Keypair::from_seckey_str( - &secp, - "5c2c8120ff354ed8b6440121c621b0395d7cccc839a6200cf0b8208e19483ed1", - ) - .unwrap(); - - let our_pubkey = "0273daf3d9728b7bd7a716fdf4b05b4b12a0febd570f8b708bfcffc1026e2c0f47"; - assert!(keypair.public_key().to_string() == our_pubkey); - let locktime = 2868372; - let preimage = Preimage::from_sha256_str( - "451f1df5a5ccc1e487cf691c21c1b90737557a9df3171a69aaa6dc1578bc6be4", - ) - .unwrap(); - let boltz_pubkey = - "0329724923c9a845eb044fa4ff323f850af6b995185b2cc18335d896011f894acd".to_string(); - - let script_address = "lq1pqvngcdxu9c2dprw8m2kye22m78358pug3chgk29a2wk9kh4kn0axxvjwfznzrxvvvwf59xm35kxdzv44ctjdsjzpxg0804l9vknzd2x0d8eh36d54zce".to_string(); - let blinding_key = - "1b581bed3300b146c61bdb3e5b58413f85299b71ab36401a8e02ec38d57925aa".to_string(); - let absolute_fees = 1_200; - let network_config = liquid_network_config; - let swap_script: LBtcSwapScript = create_swap_script_v2( - script_address, - preimage.hash160.to_string(), - boltz_pubkey, - keypair.public_key().to_string(), - locktime, - blinding_key, - ); - - let rev_swap_tx = LBtcSwapTx::new_refund( - swap_script, - RETURN_ADDRESS, - &network_config, - BOLTZ_MAINNET_URL_V2.to_string(), - id.clone(), - ) - .await - .unwrap(); - let client = BoltzApiClientV2::new(BOLTZ_MAINNET_URL_V2); - let coop = Some(Cooperative { - boltz_api: &client, - swap_id: id, - pub_nonce: None, - partial_sig: None, - }); - let signed_tx = rev_swap_tx - .sign_refund(&keypair, Fee::Absolute(absolute_fees), coop, false) - .await - .unwrap(); - let tx_hex = serialize(&signed_tx).to_lower_hex_string(); - log::info!("TX_HEX: {}", tx_hex); - - let txid = rev_swap_tx - .broadcast(&signed_tx, &network_config, None) - .await - .unwrap(); - println!("{}", txid); -} - -fn create_swap_script_v2( - address: String, - hashlock: String, - receiver_pub: String, - sender_pub: String, - locktime: u32, - blinding_key: String, -) -> LBtcSwapScript { - let address = elements::Address::from_str(&address).unwrap(); - let hashlock = BCHash::from_str(&hashlock).unwrap(); - let receiver_pubkey = PublicKey::from_str(&receiver_pub).unwrap(); - let sender_pubkey = PublicKey::from_str(&sender_pub).unwrap(); - let locktime = boltz_client::ElementsLockTime::from_height(locktime).unwrap(); - let blinding_key = Keypair::from_str(&blinding_key).unwrap(); - - LBtcSwapScript { - swap_type: SwapType::Submarine, - side: None, - funding_addrs: Some(address), - hashlock, - receiver_pubkey, - locktime, - sender_pubkey, - blinding_key, - } -} diff --git a/tests/regtest/mod.rs b/tests/regtest/mod.rs new file mode 100644 index 0000000..b0f9133 --- /dev/null +++ b/tests/regtest/mod.rs @@ -0,0 +1,6 @@ +#![cfg(feature = "regtest")] +mod bitcoin; +mod chain_swaps; +mod liquid; + +const TIMEOUT_MS: i32 = 10_000; diff --git a/tests/test_framework/mod.rs b/tests/test_framework/mod.rs index 62b3298..ea53b12 100644 --- a/tests/test_framework/mod.rs +++ b/tests/test_framework/mod.rs @@ -1,3 +1,6 @@ +#![cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#![allow(dead_code)] + use std::str::FromStr; use bitcoind::{ diff --git a/tests/test_utils.rs b/tests/test_utils.rs deleted file mode 100644 index a77cc23..0000000 --- a/tests/test_utils.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::io::{self, Write}; - -pub fn pause_and_wait(msg: &str) { - let stdin = io::stdin(); - let mut stdout = io::stdout(); - writeln!(stdout).unwrap(); - write!(stdout, "******{msg}******").unwrap(); - writeln!(stdout).unwrap(); - write!(stdout, "Press Enter to continue...").unwrap(); - stdout.flush().unwrap(); - let _ = stdin.read_line(&mut String::new()).unwrap(); -} diff --git a/tests/regtest.rs b/tests/txs.rs similarity index 100% rename from tests/regtest.rs rename to tests/txs.rs diff --git a/tests/utils.rs b/tests/utils.rs new file mode 100644 index 0000000..3052a8e --- /dev/null +++ b/tests/utils.rs @@ -0,0 +1,199 @@ +#![allow(dead_code)] +use bitcoin::base64; +use bitcoin::base64::Engine; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] +use futures::FutureExt; +use reqwest::Client; +use serde_json::{json, Value}; +use std::error::Error; + +// Constants for the JSON-RPC URLs +const BITCOIND_URL: &str = "http://localhost:18443/wallet/client"; +const ELEMENTSD_URL: &str = "http://localhost:18884/wallet/client"; +const LND_URL: &str = "https://localhost:8081"; + +const PROXY_URL: &str = "http://localhost:51234/proxy"; + +// Paths to authentication cookies +const BITCOIND_COOKIE: Option<&str> = option_env!("BITCOIND_COOKIE"); +const ELEMENTSD_COOKIE: &str = "regtest:regtest"; +const LND_MACAROON_HEX: Option<&str> = option_env!("LND_MACAROON_HEX"); + +async fn json_rpc_request( + url: &str, + cookie: &str, + method: &str, + params: Value, +) -> Result> { + let client = Client::new(); + + let req_body = json!({ + "jsonrpc": "1.0", + "id": "curltest", + "method": method, + "params": params + }); + + let res = client + .post(PROXY_URL) + .header( + "Authorization", + format!( + "Basic {}", + base64::engine::general_purpose::STANDARD.encode(cookie) + ), + ) + .header("X-Proxy-URL", url) + .json(&req_body) + .send() + .await? + .json::() + .await?; + + res.get("result") + .cloned() + .ok_or_else(|| "Invalid response".into()) +} + +async fn lnd_request(method: &str, params: Value) -> Result> { + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + let client = Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + let client = Client::builder().build()?; + let url = format!("{}/{}", LND_URL, method); + + let res = client + .post(PROXY_URL) + .header("Grpc-Metadata-macaroon", LND_MACAROON_HEX.unwrap()) + .header("X-Proxy-URL", url) + .json(¶ms) + .send() + .await? + .json::() + .await?; + + Ok(res) +} + +pub async fn generate_address_bitcoind() -> Result> { + let response = json_rpc_request( + BITCOIND_URL, + BITCOIND_COOKIE.unwrap(), + "getnewaddress", + json!([]), + ) + .await?; + + println!("Bitcoind response: {:?}", response); // Debug output + + response + .as_str() + .map(|s| s.to_string()) + .ok_or_else(|| "Invalid response".into()) +} + +pub async fn send_to_address_bitcoind( + address: &str, + sat_amount: u64, +) -> Result> { + let btc_amount = (sat_amount as f64) / 100_000_000.0; + json_rpc_request( + BITCOIND_URL, + BITCOIND_COOKIE.unwrap(), + "sendtoaddress", + json!([address, format!("{:.8}", btc_amount)]), + ) + .await? + .as_str() + .map(|s| s.to_string()) + .ok_or_else(|| "Invalid response".into()) +} + +pub async fn generate_address_elementsd() -> Result> { + json_rpc_request(ELEMENTSD_URL, ELEMENTSD_COOKIE, "getnewaddress", json!([])) + .await? + .as_str() + .map(|s| s.to_string()) + .ok_or_else(|| "Invalid response".into()) +} + +pub async fn send_to_address_elementsd( + address: &str, + sat_amount: u64, +) -> Result> { + let btc_amount = (sat_amount as f64) / 100_000_000.0; + json_rpc_request( + ELEMENTSD_URL, + ELEMENTSD_COOKIE, + "sendtoaddress", + json!([address, format!("{:.8}", btc_amount)]), + ) + .await? + .as_str() + .map(|s| s.to_string()) + .ok_or_else(|| "Invalid response".into()) +} + +pub async fn generate_invoice_lnd(amount_sat: u64) -> Result> { + let response = lnd_request("v1/invoices", json!({ "value": amount_sat })).await?; + response["payment_request"] + .as_str() + .map(|s| s.to_string()) + .ok_or_else(|| "Missing payment_request field".into()) +} + +pub async fn pay_invoice_lnd_inner(invoice: &str) -> Result<(), Box> { + lnd_request( + "v2/router/send", + json!({ "payment_request": invoice, "timeout_seconds": 1 }), + ) + .await?; + Ok(()) +} + +pub fn start_pay_invoice_lnd(invoice: String) { + let task = async move { + pay_invoice_lnd_inner(&invoice).await.unwrap(); + }; + + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + { + tokio::spawn(task); + } + + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + { + wasm_bindgen_futures::spawn_local(async { + let timeout_future = gloo_timers::future::TimeoutFuture::new(5000); + let _ = futures::select! { + _ = task.fuse() => {}, + _ = timeout_future.fuse() => {}, + }; + }); + } +} + +pub async fn mine_blocks(n_blocks: u64) -> Result<(), Box> { + let address_btc = generate_address_bitcoind().await?; + let address_lqd = generate_address_elementsd().await?; + + json_rpc_request( + BITCOIND_URL, + BITCOIND_COOKIE.unwrap(), + "generatetoaddress", + json!([n_blocks, address_btc]), + ) + .await?; + + json_rpc_request( + ELEMENTSD_URL, + ELEMENTSD_COOKIE, + "generatetoaddress", + json!([n_blocks, address_lqd]), + ) + .await?; + + Ok(()) +}