use crate::CONNECTION_INTERVAL;
use crate::CONNECTION_TIMEOUT;
use crate::POLL_INTERVAL;
use crate::WAIT_TIMEOUT;
use crate::error::Error;
use core::net::SocketAddr;
use core::time::Duration;
use corepc_client::bitcoin::BlockHash;
use std::thread::sleep;
use std::time::Instant;
use tracing::debug;
use tracing::info;
pub trait Node {
fn get_name() -> &'static str;
fn get_bin_name() -> &'static str;
fn get_chain_tip(&self) -> Result<u32, Error>;
fn get_filter_tip(&self) -> Result<u32, Error>;
fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error>;
fn call(&self, method: &str, args: &[serde_json::Value]) -> Result<serde_json::Value, Error>;
fn get_p2p_socket(&self) -> SocketAddr;
fn has_peer(&self, socket: SocketAddr) -> Result<bool, Error>;
fn add_peer(&self, socket: SocketAddr) -> Result<(), Error>;
fn get_peer_count(&self) -> Result<u32, Error>;
fn poll_interval() -> Duration {
POLL_INTERVAL
}
fn wait_timeout() -> Duration {
WAIT_TIMEOUT
}
}
pub fn connect<A: Node, B: Node>(a: &A, b: &B) -> Result<(), Error> {
let socket_a = a.get_p2p_socket();
let socket_b = b.get_p2p_socket();
debug!(
"Connecting {} at socket={} to {} at socket={}",
A::get_bin_name(),
socket_a,
B::get_bin_name(),
socket_b
);
a.add_peer(socket_b)?;
let is_connected =
|| -> Result<bool, Error> { Ok(a.has_peer(socket_b)? || b.has_peer(socket_a)?) };
let start = Instant::now();
while start.elapsed() < CONNECTION_TIMEOUT {
if is_connected()? {
sleep(CONNECTION_INTERVAL * 4);
if is_connected()? {
info!(
"Connecting {} at socket={} to {} at socket={}",
A::get_bin_name(),
socket_a,
B::get_bin_name(),
socket_b
);
return Ok(());
}
}
sleep(CONNECTION_INTERVAL);
}
Err(Error::ConnectionTimeout(CONNECTION_TIMEOUT))
}
pub fn connect_and_sync<A: Node, B: Node>(a: &A, b: &B) -> Result<(), Error> {
connect(a, b)?;
let height_a = a.get_chain_tip()?;
let height_b = b.get_chain_tip()?;
let max_height = std::cmp::max(height_a, height_b);
wait_for_height(a, max_height)?;
wait_for_height(b, max_height)?;
Ok(())
}
pub fn wait_for_height<N: Node>(node: &N, height: u32) -> Result<(), Error> {
debug!("Waiting for {} to reach height={}", N::get_name(), height);
let start = Instant::now();
while start.elapsed() < N::wait_timeout() {
if node.get_chain_tip().unwrap_or(0) >= height {
info!("{} to reached height={}", N::get_name(), height);
return Ok(());
}
sleep(N::poll_interval());
}
let curr_height = node.get_chain_tip().unwrap_or(0);
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> {
debug!(
"Waiting for {} to reach height={} with timeout={}seconds)",
N::get_name(),
height,
timeout.as_secs()
);
let start = Instant::now();
while start.elapsed() < timeout {
if node.get_chain_tip().unwrap_or(0) >= height {
return Ok(());
}
sleep(N::poll_interval());
}
let curr_height = node.get_chain_tip().unwrap_or(0);
Err(Error::ChainSyncTimeOut((height, curr_height, timeout)))
}
pub fn wait_for_filter_height<N: Node>(node: &N, filter_height: u32) -> Result<(), Error> {
debug!(
"Waiting for {} to reach filter_height={}",
N::get_name(),
filter_height
);
let start = Instant::now();
while start.elapsed() < N::wait_timeout() {
if node.get_filter_tip().unwrap_or(0) >= filter_height {
info!(
"{} to reached filter_height={}",
N::get_name(),
filter_height
);
return Ok(());
}
sleep(N::poll_interval());
}
let curr_filter_height = node.get_filter_tip().unwrap_or(0);
Err(Error::ChainSyncTimeOut((
filter_height,
curr_filter_height,
N::wait_timeout(),
)))
}