Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions .github/workflows/cont_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }}
toolchain: stable
override: true
profile: minimal
components: rustfmt, clippy
- name: fmt
run: cargo fmt -- --check
- name: clippy
run: cargo clippy -- -D warnings
13 changes: 11 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
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
103 changes: 73 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ElectrumPlaintextStream>,
/// DB directory, where electrs store indexes. It is kept in the struct so that
Expand All @@ -45,22 +48,39 @@ 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),
}

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<S: AsRef<OsStr>>(
exe: S,
bitcoind: BitcoinD,
bitcoind: &BitcoinD,
view_stderr: bool,
http_enabled: bool,
) -> Result<ElectrsD, Error> {
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());
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -116,9 +160,8 @@ impl ElectrsD {
};

Ok(ElectrsD {
client,
bitcoind,
process,
client,
_db_dir,
electrum_url,
esplora_url,
Expand Down Expand Up @@ -182,33 +225,33 @@ 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();

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);
}
}