diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 6307395..e97d9d6 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -13,7 +13,7 @@ jobs: ELECTRS_VER: 0.8.10 strategy: matrix: - features: ["", "--features trigger"] + features: ["--features use-p2p", "--features use-p2p,trigger"] steps: - name: Checkout @@ -27,30 +27,40 @@ jobs: ~/.cargo/git ~/.cargo/registry target + electrs/target/release/electrs bitcoin-${{ env.BITCOIN_VER }} - key: ${{ runner.os }}-test-electrsd-${{ env.BITCOIN_VER }}-${{ env.ELECTRS_VER }}-${{ hashFiles('Cargo.toml','Cargo.lock') }} + key: ${{ runner.os }}-test-electrsd1-${{ env.BITCOIN_VER }}-${{ env.ELECTRS_VER }}-${{ hashFiles('Cargo.toml','Cargo.lock') }} - name: Setup rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - name: Set ELECTRS_EXE env - run: echo "ELECTRS_EXE=$HOME/.cargo/bin/electrs" >> $GITHUB_ENV - - name: Install electrs + run: echo "ELECTRS_EXE=${{ github.workspace }}/electrs/target/release/electrs" >> $GITHUB_ENV + - name: Build electrs if: steps.cache-step.outputs.cache-hit != 'true' - uses: actions-rs/cargo@v1 - with: - command: install - args: electrs --version ${{ env.ELECTRS_VER }} + run: git clone https://github.com/romanz/electrs && cd electrs && git checkout df513cfb18297d3d76097bb62dba93676bbf9a94 && cargo build --release --no-default-features - name: Show electrs options - run: electrs --help + run: ${{ env.ELECTRS_EXE }} --help - name: Set BITCOIND_EXE env run: echo "BITCOIND_EXE=${{ github.workspace }}/bitcoin-${{ env.BITCOIN_VER }}/bin/bitcoind" >> $GITHUB_ENV - name: Install bitcoind if: steps.cache-step.outputs.cache-hit != 'true' run: curl https://bitcoincore.org/bin/bitcoin-core-$BITCOIN_VER/bitcoin-$BITCOIN_VER-x86_64-linux-gnu.tar.gz | tar -xvz bitcoin-$BITCOIN_VER/bin/bitcoind - name: Test electrsd - uses: actions-rs/cargo@v1 + run: RUST_LOG=electrs=debug cargo test --verbose --no-default-features ${{ matrix.features }} + + cosmetics: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 with: - command: test - args: --verbose ${{ matrix.features }} \ No newline at end of file + toolchain: stable + override: true + profile: minimal + components: rustfmt, clippy + - name: fmt + run: cargo fmt -- --check + - name: clippy + run: cargo clippy -- -D warnings diff --git a/Cargo.toml b/Cargo.toml index 07e840b..30f2c75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,18 @@ license = "MIT" edition = "2018" [dependencies] -bitcoind = { version = "0.7.0" } +bitcoind = "0.10.0" electrum-client = { version="0.7.0", default-features = false } nix = { version="0.20.0", optional = true } +log = "0.4.14" + +[dev-dependencies] +env_logger = "0.8.3" [features] -trigger = ["nix"] \ No newline at end of file +default = ["use-monitoring"] # mandatory in electrs <= 0.8.10 +trigger = ["nix"] + +# electrs features +use-p2p = [] # required in electrs version > 0.8.10 +use-monitoring = [] # required in electrs version <= 0.8.10 built without default features \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6e7f0bd..9b1673a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,19 +6,22 @@ //! Utility to run a regtest electrsd process, useful in integration testing environment //! +use bitcoind::bitcoincore_rpc::RpcApi; use bitcoind::tempfile::TempDir; use bitcoind::{get_available_port, BitcoinD}; use electrum_client::raw_client::{ElectrumPlaintextStream, RawClient}; +use log::debug; use std::ffi::OsStr; use std::process::{Child, Command, Stdio}; use std::time::Duration; +// re-export bitcoind +pub use bitcoind; + /// Struct representing the bitcoind process with related information pub struct ElectrsD { /// Process child handle, used to terminate the process when this struct is dropped process: Child, - /// bitcoind process connected to this electrs - pub bitcoind: BitcoinD, /// Electrum client connected to the electrs process pub client: RawClient, /// DB directory, where electrs store indexes. It is kept in the struct so that @@ -45,6 +48,10 @@ pub enum Error { /// Wrapper of bitcoincore_rpc Error BitcoinCoreRpc(bitcoind::bitcoincore_rpc::Error), + /// Electrs requires bitcoind started with p2p networking, this error is thrown if the node + /// starts without p2p + BitcoinNodeHasNoP2P, + #[cfg(feature = "trigger")] /// Wrapper of nix Error Nix(nix::Error), @@ -52,15 +59,28 @@ pub enum Error { impl ElectrsD { /// Create a new electrs process connected with the given bitcoind + /// One block will be generated in bitcoind if in IBD pub fn new>( exe: S, - bitcoind: BitcoinD, + bitcoind: &BitcoinD, view_stderr: bool, http_enabled: bool, ) -> Result { - let mut args = vec![]; - - args.push("-vvv"); + if bitcoind + .client + .get_blockchain_info()? + .initial_block_download + { + // electrum will remain idle until bitcoind is in IBD + // bitcoind will remain in IBD if doesn't see a block from a long time, thus adding a block + let node_address = bitcoind.client.get_new_address(None, None).unwrap(); + bitcoind + .client + .generate_to_address(1, &node_address) + .unwrap(); + } + + let mut args = vec!["-vvv"]; let _db_dir = TempDir::new()?; let db_dir = format!("{}", _db_dir.path().display()); @@ -71,29 +91,53 @@ impl ElectrsD { args.push("regtest"); args.push("--cookie-file"); - let cookie_file = format!("{}", bitcoind.cookie_file.display()); + let cookie_file = format!("{}", bitcoind.config.cookie_file.display()); args.push(&cookie_file); args.push("--daemon-rpc-addr"); - let rpc_socket = bitcoind.rpc_socket.to_string(); + let rpc_socket = bitcoind.config.rpc_socket.to_string(); args.push(&rpc_socket); + #[cfg(feature = "use-p2p")] + let p2p_socket; + #[cfg(feature = "use-p2p")] + { + p2p_socket = bitcoind + .config + .p2p_socket + .ok_or(Error::BitcoinNodeHasNoP2P)? + .to_string(); + args.push("--daemon-p2p-addr"); + args.push(&p2p_socket); + } + + #[cfg(feature = "use-monitoring")] + let monitoring_address; + #[cfg(feature = "use-monitoring")] + { + monitoring_address = format!("0.0.0.0:{}", get_available_port()?); + args.push("--monitoring-addr"); + args.push(&monitoring_address); + } + + // `--daemon-dir` isn't necessary since we use `--jsonrpc-import` however better to see the + // correct value in the logs and it may be used in the future + args.push("--daemon-dir"); + let daemon_dir = format!("{}", bitcoind.config.datadir.display()); + args.push(&daemon_dir); + args.push("--jsonrpc-import"); let electrum_url = format!("0.0.0.0:{}", get_available_port()?); args.push("--electrum-rpc-addr"); args.push(&electrum_url); - // would be better to disable it, didn't found a flag - let monitoring = format!("0.0.0.0:{}", get_available_port()?); - args.push("--monitoring-addr"); - args.push(&monitoring); - let esplora_url_string; let esplora_url = if http_enabled { esplora_url_string = format!("0.0.0.0:{}", get_available_port()?); args.push("--http-addr"); args.push(&esplora_url_string); + #[allow(clippy::redundant_clone)] Some(esplora_url_string.clone()) } else { None @@ -105,7 +149,7 @@ impl ElectrsD { Stdio::null() }; - eprintln!("args: {:?}", args); + debug!("args: {:?}", args); let process = Command::new(exe).args(args).stderr(view_stderr).spawn()?; let client = loop { @@ -116,9 +160,8 @@ impl ElectrsD { }; Ok(ElectrsD { - client, - bitcoind, process, + client, _db_dir, electrum_url, esplora_url, @@ -182,22 +225,17 @@ mod test { #[test] fn test_electrsd() { + env_logger::try_init().unwrap(); + let bitcoind_exe = env::var("BITCOIND_EXE").expect("BITCOIND_EXE env var must be set"); let electrs_exe = env::var("ELECTRS_EXE").expect("ELECTRS_EXE env var must be set"); - let bitcoind = BitcoinD::with_args(bitcoind_exe, vec![], true, bitcoind::P2P::No).unwrap(); - let electrsd = ElectrsD::new(electrs_exe, bitcoind, true, false).unwrap(); + let bitcoind = + BitcoinD::with_args(bitcoind_exe.clone(), vec![], true, bitcoind::P2P::Yes).unwrap(); + let electrsd = ElectrsD::new(electrs_exe.clone(), &bitcoind, true, false).unwrap(); let header = electrsd.client.block_headers_subscribe().unwrap(); - assert_eq!(header.height, 0); - let address = electrsd - .bitcoind - .client - .get_new_address(None, None) - .unwrap(); - electrsd - .bitcoind - .client - .generate_to_address(101, &address) - .unwrap(); + assert_eq!(header.height, 1); + let address = bitcoind.client.get_new_address(None, None).unwrap(); + bitcoind.client.generate_to_address(100, &address).unwrap(); #[cfg(feature = "trigger")] electrsd.trigger().unwrap(); @@ -205,10 +243,15 @@ mod test { let header = loop { std::thread::sleep(std::time::Duration::from_secs(1)); let header = electrsd.client.block_headers_subscribe().unwrap(); - if header.height > 0 { + if header.height > 100 { break header; } }; assert_eq!(header.height, 101); + + // launch another instance to check there are no fixed port used + let electrsd = ElectrsD::new(electrs_exe.clone(), &bitcoind, true, false).unwrap(); + let header = electrsd.client.block_headers_subscribe().unwrap(); + assert_eq!(header.height, 101); } }