use core::error;
use core::fmt;
use core::net::Ipv4Addr;
use core::net::SocketAddr;
use std::net::TcpListener;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use tempfile::TempDir;
pub use bitcoind::BitcoinD;
pub use utreexod::UtreexoD;
pub mod bitcoind;
pub mod utreexod;
const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
pub const NODE_BUILDING_MAX_RETRIES: u8 = 5;
pub const POLL_INTERVAL: Duration = Duration::from_millis(100);
pub const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
pub trait Node {
fn get_name() -> &'static str;
fn get_height(&self) -> Result<u32, Error>;
fn poll_interval() -> Duration {
POLL_INTERVAL
}
fn wait_timeout() -> Duration {
WAIT_TIMEOUT
}
}
#[rustfmt::skip]
impl Node for BitcoinD {
fn get_name() -> &'static str { "bitcoind" }
fn get_height(&self) -> Result<u32, Error> { self.get_height() }
}
#[rustfmt::skip]
impl Node for UtreexoD {
fn get_name() -> &'static str { "utreexod" }
fn get_height(&self) -> Result<u32, Error> { self.get_height() }
fn poll_interval() -> Duration { 2 * POLL_INTERVAL }
fn wait_timeout() -> Duration { 2 * WAIT_TIMEOUT }
}
pub fn wait_for_height<N: Node>(node: &N, height: u32) -> Result<(), Error> {
let start = Instant::now();
while start.elapsed() < N::wait_timeout() {
if node.get_height().unwrap() >= height {
return Ok(());
}
thread::sleep(N::poll_interval());
}
let curr_height = node.get_height().unwrap();
Err(Error::ChainSyncTimeOut((
height,
curr_height,
N::wait_timeout(),
)))
}
pub fn wait_for_height_with_timeout<N: Node>(
node: &N,
height: u32,
timeout: Duration,
) -> Result<(), Error> {
let start = Instant::now();
while start.elapsed() < timeout {
if node.get_height().unwrap() >= height {
return Ok(());
}
thread::sleep(N::poll_interval());
}
let curr_height = node.get_height().unwrap();
Err(Error::ChainSyncTimeOut((height, curr_height, timeout)))
}
#[inline]
pub fn get_available_port() -> u16 {
TcpListener::bind((LOCALHOST, 0))
.unwrap()
.local_addr()
.unwrap()
.port()
}
#[derive(Debug)]
pub enum DataDir {
Persistent(PathBuf),
Temporary(TempDir),
}
impl DataDir {
pub fn path(&self) -> PathBuf {
match self {
Self::Persistent(path) => path.to_owned(),
Self::Temporary(tmp_dir) => tmp_dir.path().to_path_buf(),
}
}
}
#[derive(Debug)]
pub enum Error {
BinaryNotFound(PathBuf),
FailedToSpawn(std::io::Error),
ExhaustedNodeBuildingRetries,
FailedToStop(corepc_client::client_sync::Error),
Io(std::io::Error),
JsonRpc(corepc_client::client_sync::Error),
PeerConnectionTimeout((SocketAddr, SocketAddr)),
BothDirsSpecified,
UnresponsiveBitcoinD(corepc_client::client_sync::Error),
UnresponsiveUtreexoD(corepc_client::client_sync::Error),
CookieFileTimeout(PathBuf),
RpcClientSetupTimeout,
UnexpectedResponse,
ChainSyncTimeOut((u32, u32, Duration)), }
#[rustfmt::skip]
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Error::*;
match self {
BinaryNotFound(path) => write!(f, "The `utreexod` binary was not found at the expected location: {}", path.display()),
FailedToSpawn(err) => write!(f, "Failed to spawn a process for the node: {err:?}"),
ExhaustedNodeBuildingRetries => write!(f, "Failed to instantiate the node after {} attempts", NODE_BUILDING_MAX_RETRIES),
FailedToStop(err) => write!(f, "Failed to stop the node over JSON-RPC: {err:?}"),
Io(err) => write!(f, "I/O Error: {err:?}"),
JsonRpc(err) => write!(f, "JSON-RPC Error: {err:?}"),
PeerConnectionTimeout((local_socket, remote_socket)) => write!(f, "Timed out whilst waiting for connection between local={local_socket} and remote={remote_socket}"),
BothDirsSpecified => write!(f, "Both `tempdir` and `workdir` were specified. You must choose one and only one"),
UnresponsiveBitcoinD(err) => write!(f, "`BitcoinD` is unresponsive to JSON-RPC calls: {err:?}"),
UnresponsiveUtreexoD(err) => write!(f, "`UtreexoD` is unresponsive to JSON-RPC calls: {err:?}"),
CookieFileTimeout(cookie_path) => write!(f, "Timed out whilst waiting for the cookie={} to be generated", cookie_path.display()),
RpcClientSetupTimeout => write!(f, "Timed out whilst waiting for the JSON-RPC client to be ready"),
UnexpectedResponse => write!(f, "Received an unexpected response from the JSON-RPC server"),
ChainSyncTimeOut((target_height, current_height, t)) => write!(
f,
"Timed out after {} seconds whilst waiting for the node's chain to synchronize to height={} (current height={})",
target_height, current_height, t.as_secs()
),
}
}
}
impl error::Error for Error {}