Skip to content

Commit 222fba4

Browse files
committed
Tests: Async DaemonRunner for BitcoinD
I've created a DaemonRunner for `bitcoind`. It does support async
1 parent ebf77a2 commit 222fba4

File tree

9 files changed

+423
-22
lines changed

9 files changed

+423
-22
lines changed

Cargo.lock

Lines changed: 38 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ark-testing/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
anyhow.workspace= true
78
bitcoin.workspace = true
9+
env_logger.workspace = true
810
log.workspace = true
11+
portpicker = "0.1.1"
912
rand.workspace = true
13+
regex = "1.10.5"
1014
tokio.workspace = true
15+
which = "6.0.1"

ark-testing/src/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
pub mod env {
22
pub const TEST_LEAVE_INTACT : &str = "TEST_LEAVE_INTACT";
3+
pub const BITCOIND_EXEC: &str = "BITCOIND_EXEC";
4+
pub const BARK_EXEC: &str = "BARK_EXE";
5+
pub const ASPD_EXEC: &str = "ASPD_EXE";
36
}

ark-testing/src/context.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ use crate::constants;
66

77
pub struct TestContext {
88
#[allow(dead_code)]
9-
name: String,
10-
datadir: PathBuf
9+
pub name: String,
10+
pub datadir: PathBuf
1111
}
1212

1313
impl TestContext {
14-
1514
pub fn new(name: String, base_path: PathBuf) -> Self {
1615
fs::create_dir_all(base_path.clone()).unwrap();
16+
crate::util::init_logging().expect("Logging can be initialized");
1717
TestContext { name, datadir: base_path}
1818
}
1919

@@ -31,10 +31,11 @@ impl Drop for TestContext {
3131
// test-data.
3232

3333
if std::env::var(constants::env::TEST_LEAVE_INTACT).is_ok() {
34+
log::info!("Textcontext: Leave intact at {:?}", self.datadir);
3435
return
3536
}
3637
if self.datadir.exists() {
37-
log::info!("Test clean-up");
38+
log::trace!("Testcontext: Clean up datadir at {:?}", self.datadir);
3839
std::fs::remove_dir_all(self.datadir.clone()).unwrap();
3940
}
4041
}

ark-testing/src/daemon/bitcoind.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use crate::daemon::{DaemonWrapper, DaemonHelper};
2+
use crate::constants::env::{BITCOIND_EXEC, TEST_LEAVE_INTACT};
3+
4+
use anyhow::Context;
5+
use std::path::PathBuf;
6+
use std::env::VarError;
7+
use which::which;
8+
use tokio::sync::broadcast;
9+
10+
pub struct BitcoinDHelper {
11+
name : String,
12+
bitcoind_exec: PathBuf,
13+
config: BitcoinDConfig,
14+
state: BitcoinDState,
15+
}
16+
17+
pub struct BitcoinDConfig {
18+
pub datadir: PathBuf,
19+
pub txindex: bool,
20+
pub network: String,
21+
}
22+
23+
impl Default for BitcoinDConfig {
24+
25+
fn default() -> Self {
26+
Self {
27+
datadir: PathBuf::from("~/.bitcoin"),
28+
txindex: false,
29+
network: String::from("regtest")
30+
}
31+
}
32+
}
33+
34+
#[derive(Default)]
35+
pub struct BitcoinDState {
36+
rpc_port: Option<u16>,
37+
p2p_port: Option<u16>,
38+
}
39+
40+
pub type BitcoinD = DaemonWrapper<BitcoinDHelper, BitcoinDState>;
41+
42+
pub fn get_exe_path() -> anyhow::Result<PathBuf> {
43+
match std::env::var(&BITCOIND_EXEC) {
44+
Ok(var) => which(var).context("Failed to find binary path from `BITCOIND_EXEC`"),
45+
Err(VarError::NotPresent) => which("bitcoind").context("Failed to find `bitcoind` installation"),
46+
_ => anyhow::bail!("BITCOIND_EXEC is not valid unicode")
47+
}
48+
}
49+
50+
impl BitcoinD {
51+
52+
pub fn new(name: String, bitcoind_exec: PathBuf, config: BitcoinDConfig) -> Self {
53+
let state = BitcoinDState::default();
54+
let inner = BitcoinDHelper { name, bitcoind_exec, config, state};
55+
DaemonWrapper::wrap(inner)
56+
}
57+
58+
}
59+
60+
impl DaemonHelper for BitcoinDHelper {
61+
type State = BitcoinDState;
62+
63+
fn name(&self) -> &str {
64+
&self.name
65+
}
66+
67+
async fn make_reservations(&mut self) -> anyhow::Result<()> {
68+
self.state.rpc_port = Some(portpicker::pick_unused_port().expect("A port is free"));
69+
self.state.p2p_port = Some(portpicker::pick_unused_port().expect("A port is free"));
70+
71+
Ok(())
72+
}
73+
async fn prepare(&self) -> anyhow::Result<()> {
74+
debug!("Creating bitcoind datadir in {:?}", self.config.datadir.clone());
75+
std::fs::create_dir_all(self.config.datadir.clone())?;
76+
Ok(())
77+
}
78+
79+
async fn get_command(&self) -> anyhow::Result<tokio::process::Command> {
80+
let mut cmd = tokio::process::Command::new(self.bitcoind_exec.clone());
81+
cmd
82+
.arg(format!("--{}", self.config.network))
83+
.arg(format!("-datadir={}", self.config.datadir.display().to_string()))
84+
.arg(format!("-txindex={}", self.config.txindex))
85+
.arg(format!("-rpcport={}", self.state.rpc_port.expect("A port has been picked")))
86+
.arg(format!("-port={}", self.state.p2p_port.expect("A port has been picked")));
87+
88+
Ok(cmd)
89+
}
90+
91+
async fn request_stop(&self) -> anyhow::Result<()> {
92+
Ok(())
93+
}
94+
95+
async fn wait_for_init(&self, stdout_rx: &mut broadcast::Receiver::<String>, _: &mut broadcast::Receiver::<String>) -> anyhow::Result<()> {
96+
// TODO: We can find a better implementation here
97+
98+
// Sleeping 2 seconds is always sufficient
99+
// However, this makes our tests slow
100+
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
101+
102+
// The bitcoind create tries to ping the json-rpc frequently
103+
// It will continue once a call to `getblockchaininfo` has succeeded
104+
105+
// An other approach is to look at the logs and identify the correct port
106+
107+
return Ok(())
108+
}
109+
}

0 commit comments

Comments
 (0)