diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 71a49cab..b2fee531 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -82,22 +82,13 @@ jobs: matrix: blockchain: - name: electrum - container: bitcoindevkit/electrs:0.4.0 - start: /root/electrs --network regtest --cookie-file $GITHUB_WORKSPACE/.bitcoin/regtest/.cookie --jsonrpc-import - - name: esplora - container: bitcoindevkit/esplora:0.4.0 - start: /root/electrs --network regtest -vvv --daemon-dir $GITHUB_WORKSPACE/.bitcoin --jsonrpc-import --electrum-rpc-addr=0.0.0.0:60401 --http-addr 0.0.0.0:3002 + container: bitcoindevkit/electrs - name: rpc - container: bitcoindevkit/electrs:0.4.0 - start: /root/electrs --network regtest --cookie-file $GITHUB_WORKSPACE/.bitcoin/regtest/.cookie --jsonrpc-import + container: bitcoindevkit/electrs container: ${{ matrix.blockchain.container }} env: - BDK_RPC_AUTH: COOKIEFILE - BDK_RPC_COOKIEFILE: ${{ github.workspace }}/.bitcoin/regtest/.cookie - BDK_RPC_URL: 127.0.0.1:18443 - BDK_RPC_WALLET: bdk-test - BDK_ELECTRUM_URL: tcp://127.0.0.1:60401 - BDK_ESPLORA_URL: http://127.0.0.1:3002 + ELECTRS_EXE: /root/electrs + BITCOIND_EXE: /root/bitcoind steps: - name: Checkout uses: actions/checkout@v2 @@ -119,10 +110,6 @@ jobs: run: $HOME/.cargo/bin/rustup set profile minimal - name: Update toolchain run: $HOME/.cargo/bin/rustup update - - name: Start core - run: ./ci/start-core.sh - - name: start ${{ matrix.blockchain.name }} - run: nohup ${{ matrix.blockchain.start }} & sleep 5 - name: Test run: $HOME/.cargo/bin/cargo test --features test-${{ matrix.blockchain.name }},test-blockchains --no-default-features ${{ matrix.blockchain.name }}::bdk_blockchain_tests diff --git a/Cargo.toml b/Cargo.toml index df6d39f6..d30587bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ env_logger = "0.7" clap = "2.33" serial_test = "0.4" bitcoind = "0.11.0" +electrsd = "0.3.0" [[example]] name = "address_validator" diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index 4d8926af..45deb3f7 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -169,9 +169,10 @@ impl ConfigurableBlockchain for ElectrumBlockchain { } } +#[cfg(test)] #[cfg(feature = "test-blockchains")] crate::bdk_blockchain_tests! { - fn test_instance() -> ElectrumBlockchain { - ElectrumBlockchain::from(Client::new(&testutils::blockchain_tests::get_electrum_url()).unwrap()) + fn test_instance(test_client: &TestClient) -> ElectrumBlockchain { + ElectrumBlockchain::from(Client::new(&test_client.electrsd.electrum_url).unwrap()) } } diff --git a/src/blockchain/esplora.rs b/src/blockchain/esplora.rs index ff85f22f..d866992d 100644 --- a/src/blockchain/esplora.rs +++ b/src/blockchain/esplora.rs @@ -415,9 +415,10 @@ impl_error!(std::num::ParseIntError, Parsing, EsploraError); impl_error!(consensus::encode::Error, BitcoinEncoding, EsploraError); impl_error!(bitcoin::hashes::hex::Error, Hex, EsploraError); +#[cfg(test)] #[cfg(feature = "test-blockchains")] crate::bdk_blockchain_tests! { - fn test_instance() -> EsploraBlockchain { - EsploraBlockchain::new(std::env::var("BDK_ESPLORA_URL").unwrap_or("127.0.0.1:3002".into()).as_str(), None) + fn test_instance(test_client: &TestClient) -> EsploraBlockchain { + EsploraBlockchain::new(test_client.electrsd.esplora_url.as_ref().unwrap(), None) } } diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index fbd204cb..63664c68 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -419,27 +419,14 @@ fn list_wallet_dir(client: &Client) -> Result, Error> { Ok(result.wallets.into_iter().map(|n| n.name).collect()) } +#[cfg(test)] #[cfg(feature = "test-blockchains")] crate::bdk_blockchain_tests! { - fn test_instance() -> RpcBlockchain { - let url = std::env::var("BDK_RPC_URL").unwrap_or_else(|_| "127.0.0.1:18443".to_string()); - let url = format!("http://{}", url); - - // TODO same code in `fn get_auth` in testutils, make it public there - let auth = match std::env::var("BDK_RPC_AUTH").as_ref().map(String::as_ref) { - Ok("USER_PASS") => Auth::UserPass( - std::env::var("BDK_RPC_USER").unwrap(), - std::env::var("BDK_RPC_PASS").unwrap(), - ), - _ => Auth::CookieFile(std::path::PathBuf::from( - std::env::var("BDK_RPC_COOKIEFILE") - .unwrap_or_else(|_| "/home/user/.bitcoin/regtest/.cookie".to_string()), - )), - }; + fn test_instance(test_client: &TestClient) -> RpcBlockchain { let config = RpcConfig { - url, - auth, + url: test_client.bitcoind.rpc_url(), + auth: Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone()), network: Network::Regtest, wallet_name: format!("client-wallet-test-{:?}", std::time::SystemTime::now() ), skip_blocks: None, @@ -486,10 +473,8 @@ mod test { } fn create_bitcoind(args: Vec<&str>) -> BitcoinD { let exe = std::env::var("BITCOIND_EXE").unwrap(); - let conf = Conf { - args, - ..Default::default() - }; + let mut conf = Conf::default(); + conf.args.extend(args); bitcoind::BitcoinD::with_conf(exe, &conf).unwrap() } @@ -498,7 +483,7 @@ mod test { #[test] fn test_rpc_wallet_setup() { - env_logger::try_init().unwrap(); + let _ = env_logger::try_init(); let bitcoind = create_bitcoind(vec![]); let node_address = bitcoind.client.get_new_address(None, None).unwrap(); let blockchain = create_rpc(&bitcoind, DESCRIPTOR_PUB, Network::Regtest).unwrap(); diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 776d0f5e..713c0ef6 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -5,31 +5,37 @@ use bitcoin::hashes::sha256d; use bitcoin::{Address, Amount, Script, Transaction, Txid}; pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType; pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi}; +use bitcoind::BitcoinD; use core::str::FromStr; +use electrsd::ElectrsD; pub use electrum_client::{Client as ElectrumClient, ElectrumApi}; #[allow(unused_imports)] use log::{debug, error, info, trace}; use std::collections::HashMap; use std::env; use std::ops::Deref; -use std::path::PathBuf; use std::time::Duration; pub struct TestClient { - client: RpcClient, - electrum: ElectrumClient, + pub bitcoind: BitcoinD, + pub electrsd: ElectrsD, } impl TestClient { - pub fn new(rpc_host_and_wallet: String, rpc_wallet_name: String) -> Self { - let client = RpcClient::new( - format!("http://{}/wallet/{}", rpc_host_and_wallet, rpc_wallet_name), - get_auth(), - ) - .unwrap(); - let electrum = ElectrumClient::new(&get_electrum_url()).unwrap(); + pub fn new(bitcoind_exe: String, electrs_exe: String) -> Self { + debug!("launching {} and {}", &bitcoind_exe, &electrs_exe); + let bitcoind = BitcoinD::new(bitcoind_exe).unwrap(); + let electrsd = ElectrsD::new(electrs_exe, &bitcoind, false, false).unwrap(); // TODO http_enabled should be true only for esplora - TestClient { client, electrum } + let node_address = bitcoind.client.get_new_address(None, None).unwrap(); + bitcoind + .client + .generate_to_address(101, &node_address) + .unwrap(); + + let mut test_client = TestClient { bitcoind, electrsd }; + TestClient::wait_for_block(&mut test_client, 101); + test_client } fn wait_for_tx(&mut self, txid: Txid, monitor_script: &Script) { @@ -37,7 +43,8 @@ impl TestClient { exponential_backoff_poll(|| { trace!("wait_for_tx {}", txid); - self.electrum + self.electrsd + .client .script_get_history(monitor_script) .unwrap() .iter() @@ -46,12 +53,12 @@ impl TestClient { } fn wait_for_block(&mut self, min_height: usize) { - self.electrum.block_headers_subscribe().unwrap(); + self.electrsd.client.block_headers_subscribe().unwrap(); loop { let header = exponential_backoff_poll(|| { - self.electrum.ping().unwrap(); - self.electrum.block_headers_pop().unwrap() + self.electrsd.client.ping().unwrap(); + self.electrsd.client.block_headers_pop().unwrap() }); if header.height >= min_height { break; @@ -96,10 +103,13 @@ impl TestClient { .unwrap(); // broadcast through electrum so that it caches the tx immediately + let txid = self - .electrum + .electrsd + .client .transaction_broadcast(&deserialize(&tx.hex).unwrap()) .unwrap(); + debug!("broadcasted to electrum {}", txid); if let Some(num) = meta_tx.min_confirmations { self.generate(num, None); @@ -209,7 +219,7 @@ impl TestClient { let block_hex: String = serialize(&block).to_hex(); debug!("generated block hex: {}", block_hex); - self.electrum.block_headers_subscribe().unwrap(); + self.electrsd.client.block_headers_subscribe().unwrap(); let submit_result: serde_json::Value = self.call("submitblock", &[block_hex.into()]).unwrap(); @@ -237,7 +247,7 @@ impl TestClient { } pub fn invalidate(&mut self, num_blocks: u64) { - self.electrum.block_headers_subscribe().unwrap(); + self.electrsd.client.block_headers_subscribe().unwrap(); let best_hash = self.get_best_block_hash().unwrap(); let initial_height = self.get_block_info(&best_hash).unwrap().height; @@ -288,16 +298,16 @@ impl Deref for TestClient { type Target = RpcClient; fn deref(&self) -> &Self::Target { - &self.client + &self.bitcoind.client } } impl Default for TestClient { fn default() -> Self { - let rpc_host_and_port = - env::var("BDK_RPC_URL").unwrap_or_else(|_| "127.0.0.1:18443".to_string()); - let wallet = env::var("BDK_RPC_WALLET").unwrap_or_else(|_| "bdk-test".to_string()); - Self::new(rpc_host_and_port, wallet) + let bitcoind_exe = + env::var("BITCOIND_EXE").unwrap_or_else(|_| "/root/bitcoind".to_string()); + let electrs_exe = env::var("ELECTRS_EXE").unwrap_or_else(|_| "/root/electrs".to_string()); + Self::new(bitcoind_exe, electrs_exe) } } @@ -317,20 +327,6 @@ where } } -// TODO: we currently only support env vars, we could also parse a toml file -fn get_auth() -> Auth { - match env::var("BDK_RPC_AUTH").as_ref().map(String::as_ref) { - Ok("USER_PASS") => Auth::UserPass( - env::var("BDK_RPC_USER").unwrap(), - env::var("BDK_RPC_PASS").unwrap(), - ), - _ => Auth::CookieFile(PathBuf::from( - env::var("BDK_RPC_COOKIEFILE") - .unwrap_or_else(|_| "/home/user/.bitcoin/regtest/.cookie".to_string()), - )), - } -} - /// This macro runs blockchain tests against a `Blockchain` implementation. It requires access to a /// Bitcoin core wallet via RPC. At the moment you have to dig into the code yourself and look at /// the setup required to run the tests yourself. @@ -590,7 +586,6 @@ macro_rules! bdk_blockchain_tests { let tx = psbt.extract_tx(); println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); wallet.broadcast(tx).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send"); diff --git a/src/testutils/mod.rs b/src/testutils/mod.rs index 5d7146bd..3e4dfec2 100644 --- a/src/testutils/mod.rs +++ b/src/testutils/mod.rs @@ -10,6 +10,7 @@ // licenses. #![allow(missing_docs)] +#[cfg(test)] #[cfg(feature = "test-blockchains")] pub mod blockchain_tests;