From 8a33bb42e64d58551440992bed46eb2d5a7900ce Mon Sep 17 00:00:00 2001 From: yse <70684173+hydra-yse@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:27:13 +0200 Subject: [PATCH] feat: switch Esplora client url to Breez server instance (#845) Co-authored-by: Ross Savage --- lib/Cargo.lock | 4 ++-- lib/core/Cargo.toml | 4 ++-- lib/core/src/chain/bitcoin/esplora.rs | 1 - lib/core/src/chain/liquid/esplora.rs | 27 ++++++++++++++++++------- lib/core/src/model.rs | 20 ++++++++++-------- lib/core/src/sdk.rs | 2 +- lib/core/src/swapper/boltz/client.rs | 29 +++++++++++++++++++++++++-- lib/core/src/test_utils/swapper.rs | 5 +++-- lib/core/src/wallet.rs | 28 +++++++++++++++++--------- 9 files changed, 86 insertions(+), 34 deletions(-) diff --git a/lib/Cargo.lock b/lib/Cargo.lock index ee28c69..c21565f 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -760,7 +760,7 @@ checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567" [[package]] name = "boltz-client" version = "0.3.0" -source = "git+https://github.com/SatoshiPortal/boltz-rust?rev=f78e159fe72e1c357e7830bc08d2b9e42a65362c#f78e159fe72e1c357e7830bc08d2b9e42a65362c" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=b54f181e218d#b54f181e218d6c7d44d7bfd3eabda3681006fbc0" dependencies = [ "async-trait", "bip39", @@ -3182,7 +3182,7 @@ dependencies = [ [[package]] name = "macros" version = "0.0.0" -source = "git+https://github.com/SatoshiPortal/boltz-rust?rev=f78e159fe72e1c357e7830bc08d2b9e42a65362c#f78e159fe72e1c357e7830bc08d2b9e42a65362c" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=b54f181e218d#b54f181e218d6c7d44d7bfd3eabda3681006fbc0" dependencies = [ "proc-macro2", "quote", diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index c8b5103..a526dc3 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -76,7 +76,7 @@ maybe-sync = { version = "0.1.1", features = ["sync"] } prost = "^0.11" tonic = { version = "^0.8", features = ["tls", "tls-webpki-roots"] } uuid = { version = "1.8.0", features = ["v4"] } -boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "f78e159fe72e1c357e7830bc08d2b9e42a65362c", features = ["electrum"] } +boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "b54f181e218d", features = ["electrum"] } rusqlite = { git = "https://github.com/Spxg/rusqlite", rev = "e36644127f31fa6e7ea0999b59432deb4a07f220", features = [ "backup", "bundled", @@ -95,7 +95,7 @@ tonic = { version = "0.12", default-features = false, features = [ "codegen", "prost", ] } -boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "f78e159fe72e1c357e7830bc08d2b9e42a65362c" } +boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "b54f181e218d" } rusqlite = { git = "https://github.com/Spxg/rusqlite", rev = "e36644127f31fa6e7ea0999b59432deb4a07f220", features = [ "backup", "bundled", diff --git a/lib/core/src/chain/bitcoin/esplora.rs b/lib/core/src/chain/bitcoin/esplora.rs index bc83c7d..a59091b 100644 --- a/lib/core/src/chain/bitcoin/esplora.rs +++ b/lib/core/src/chain/bitcoin/esplora.rs @@ -52,7 +52,6 @@ impl EsploraBitcoinChainService { .timeout(3) .max_retries(10) .build_async()?; - let client = self.client.get_or_init(|| client); Ok(client) } diff --git a/lib/core/src/chain/liquid/esplora.rs b/lib/core/src/chain/liquid/esplora.rs index e77f850..639f805 100644 --- a/lib/core/src/chain/liquid/esplora.rs +++ b/lib/core/src/chain/liquid/esplora.rs @@ -1,16 +1,16 @@ use std::{sync::OnceLock, time::Duration}; -use anyhow::{anyhow, Context as _, Result}; +use anyhow::{anyhow, bail, Context as _, Result}; use tokio::sync::RwLock; use tokio_with_wasm::alias as tokio; use crate::{ elements::{Address, OutPoint, Script, Transaction, Txid}, - model::{BlockchainExplorer, Config, Utxo}, + model::{BlockchainExplorer, Config, Utxo, BREEZ_LIQUID_ESPLORA_URL}, utils, }; -use log::info; +use log::{error, info}; use lwk_wollet::{ asyncr::EsploraClientBuilder, clients::asyncr::EsploraClient, elements::hex::FromHex as _, }; @@ -40,10 +40,23 @@ impl EsploraLiquidChainService { BlockchainExplorer::Esplora { url, use_waterfalls, - } => EsploraClientBuilder::new(url, self.config.network.into()) - .timeout(3) - .waterfalls(*use_waterfalls) - .build(), + } => { + let mut builder = EsploraClientBuilder::new(url, self.config.network.into()); + if url == BREEZ_LIQUID_ESPLORA_URL { + match &self.config.breez_api_key { + Some(api_key) => { + builder = builder + .header("authorization".to_string(), format!("Bearer {api_key}")); + } + None => { + let err = "Cannot start Breez Esplora client: Breez API key is not set"; + error!("{err}"); + bail!(err) + } + }; + } + builder.timeout(3).waterfalls(*use_waterfalls).build() + } #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BlockchainExplorer::Electrum { .. } => { anyhow::bail!("Cannot start Liquid Esplora chain service without an Esplora url") diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 01e29ee..0642404 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use bitcoin::{bip32, ScriptBuf}; use boltz_client::{ boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_REGTEST, BOLTZ_TESTNET_URL_V2}, @@ -40,6 +40,7 @@ use crate::{ pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1; pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32; pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology"; +pub const BREEZ_LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api"; const SIDESWAP_API_KEY: &str = "97fb6a1dfa37ee6656af92ef79675cc03b8ac4c52e04655f41edbd5af888dcc2"; @@ -128,7 +129,7 @@ impl Config { pub fn mainnet_esplora(breez_api_key: Option) -> Self { Config { liquid_explorer: BlockchainExplorer::Esplora { - url: "https://waterfalls.liquidwebwallet.org/liquid/api".to_string(), + url: BREEZ_LIQUID_ESPLORA_URL.to_string(), use_waterfalls: true, }, bitcoin_explorer: BlockchainExplorer::Esplora { @@ -314,15 +315,18 @@ impl Config { } } - pub(crate) fn liquid_chain_service(&self) -> Arc { - match self.liquid_explorer { - BlockchainExplorer::Esplora { .. } => { - Arc::new(EsploraLiquidChainService::new(self.clone())) + pub(crate) fn liquid_chain_service(&self) -> Result> { + match &self.liquid_explorer { + BlockchainExplorer::Esplora { url, .. } => { + if url == BREEZ_LIQUID_ESPLORA_URL && self.breez_api_key.is_none() { + bail!("Cannot start the Breez Esplora chain service without providing a valid API key. See https://sdk-doc-liquid.breez.technology/guide/getting_started.html#api-key") + } + Ok(Arc::new(EsploraLiquidChainService::new(self.clone()))) } #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] - BlockchainExplorer::Electrum { .. } => Arc::new( + BlockchainExplorer::Electrum { .. } => Ok(Arc::new( crate::chain::liquid::electrum::ElectrumLiquidChainService::new(self.clone()), - ), + )), } } diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index d700899..30f1c36 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -221,7 +221,7 @@ impl LiquidSdkBuilder { let liquid_chain_service: Arc = match self.liquid_chain_service.clone() { Some(liquid_chain_service) => liquid_chain_service, - None => self.config.liquid_chain_service(), + None => self.config.liquid_chain_service()?, }; let onchain_wallet: Arc = match self.onchain_wallet.clone() { diff --git a/lib/core/src/swapper/boltz/client.rs b/lib/core/src/swapper/boltz/client.rs index 768c992..76491cf 100644 --- a/lib/core/src/swapper/boltz/client.rs +++ b/lib/core/src/swapper/boltz/client.rs @@ -1,6 +1,6 @@ use crate::{ bitcoin, elements, - model::{BlockchainExplorer, Config}, + model::{BlockchainExplorer, Config, BREEZ_LIQUID_ESPLORA_URL}, }; use boltz_client::{ error::Error, @@ -9,7 +9,9 @@ use boltz_client::{ BitcoinChain, BitcoinClient as BoltzBitcoinClient, LiquidChain, LiquidClient as BoltzLiquidClient, }, + reqwest, }; +use log::error; use sdk_macros::async_trait; const BOLTZ_CONNECTION_TIMEOUT: u8 = 100; @@ -37,7 +39,30 @@ impl LiquidClient { )) } BlockchainExplorer::Esplora { url, .. } => { - Self::Esplora(Box::new(EsploraLiquidClient::new( + let mut builder = reqwest::Client::builder(); + if url == BREEZ_LIQUID_ESPLORA_URL { + match &config.breez_api_key { + Some(api_key) => { + let mut headers = reqwest::header::HeaderMap::new(); + let api_key = format!("Bearer {api_key}").parse().map_err(|err| { + Error::Generic(format!("Could not set api key header: {err}")) + })?; + headers.insert(reqwest::header::AUTHORIZATION, api_key); + builder = builder.default_headers(headers) + } + None => { + let err = "Cannot start Breez Esplora client: Breez API key is not set"; + error!("{err}"); + return Err(Error::Generic(err.to_string())); + } + }; + } + let client = builder.build().map_err(|err| { + Error::Generic(format!("Could not initialize HTTP client: {err}")) + })?; + + Self::Esplora(Box::new(EsploraLiquidClient::with_client( + client, config.network.into(), url, BOLTZ_CONNECTION_TIMEOUT as u64, diff --git a/lib/core/src/test_utils/swapper.rs b/lib/core/src/test_utils/swapper.rs index c8cc271..c6c0e6f 100644 --- a/lib/core/src/test_utils/swapper.rs +++ b/lib/core/src/test_utils/swapper.rs @@ -4,7 +4,7 @@ use boltz_client::{ ChainFees, ChainMinerFees, ChainPair, ChainSwapDetails, CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, PairLimits, PairMinerFees, ReverseFees, ReverseLimits, ReversePair, SubmarineClaimTxResponse, SubmarineFees, - SubmarinePair, SwapTree, + SubmarinePair, SubmarinePairLimits, SwapTree, }, util::secrets::Preimage, Amount, PublicKey, @@ -187,10 +187,11 @@ impl Swapper for MockSwapper { Ok(Some(SubmarinePair { hash: generate_random_string(10), rate: 0.0, - limits: PairLimits { + limits: SubmarinePairLimits { maximal: 25_000_000, minimal: 1_000, maximal_zero_conf: 250_000, + minimal_batched: None, }, fees: SubmarineFees { percentage: 0.1, diff --git a/lib/core/src/wallet.rs b/lib/core/src/wallet.rs index ea09d11..0211148 100644 --- a/lib/core/src/wallet.rs +++ b/lib/core/src/wallet.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use std::io::Write; use std::str::FromStr; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use boltz_client::ElementsAddress; -use log::{debug, info, warn}; +use log::{debug, error, info, warn}; use lwk_common::Signer as LwkSigner; use lwk_common::{singlesig_desc, Singlesig}; use lwk_wollet::asyncr::{EsploraClient, EsploraClientBuilder}; @@ -22,7 +22,7 @@ use sdk_common::lightning::util::message_signing::verify; use tokio::sync::Mutex; use web_time::Instant; -use crate::model::{BlockchainExplorer, Signer}; +use crate::model::{BlockchainExplorer, Signer, BREEZ_LIQUID_ESPLORA_URL}; use crate::persist::Persister; use crate::signer::SdkLwkSigner; use crate::{ @@ -133,12 +133,22 @@ impl WalletClient { url, use_waterfalls, } => { - let client = Box::new( - EsploraClientBuilder::new(url, config.network.into()) - .timeout(3) - .waterfalls(*use_waterfalls) - .build(), - ); + let waterfalls = *use_waterfalls; + let mut builder = EsploraClientBuilder::new(url, config.network.into()); + if url == BREEZ_LIQUID_ESPLORA_URL { + match &config.breez_api_key { + Some(api_key) => { + builder = builder + .header("authorization".to_string(), format!("Bearer {api_key}")); + } + None => { + let err = "Cannot start Breez Esplora client: Breez API key is not set"; + error!("{err}"); + bail!(err) + } + }; + } + let client = Box::new(builder.timeout(3).waterfalls(waterfalls).build()); Ok(Self::Esplora(client)) } }