use super::{Error, instance::VaraEthInstance};
use jsonrpsee::ws_client::WsClientBuilder;
use std::{
env,
ffi::OsString,
net::{Ipv4Addr, SocketAddrV4},
path::PathBuf,
process::{Command, Stdio},
time::{Duration, Instant},
};
const VARA_ETH_BINARY: &str = "ethexe";
const DEFAULT_ARGS: &[&str] = &["run", "--dev", "--no-network"];
const STARTUP_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Clone, Debug, Default)]
#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
pub struct VaraEth {
program: Option<PathBuf>,
block_time: Option<u32>,
custom_rpc_port: Option<u16>,
timeout: Option<Duration>,
extra_args: Vec<OsString>,
}
impl VaraEth {
pub fn new() -> Self {
Self::default()
}
pub fn at<T: Into<PathBuf>>(path: T) -> Self {
Self::new().path(path)
}
pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
self.program = Some(path.into());
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn push_arg<T: Into<OsString>>(mut self, arg: T) -> Self {
self.extra_args.push(arg.into());
self
}
pub fn push_args<I, T>(mut self, args: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<OsString>,
{
self.extra_args.extend(args.into_iter().map(Into::into));
self
}
pub fn block_time(mut self, block_time: u32) -> Self {
self.block_time = Some(block_time);
self
}
pub fn with_custom_rpc(mut self, port: u16) -> Self {
self.custom_rpc_port = Some(port);
self
}
pub fn spawn_immediate(self) -> Result<VaraEthInstance, Error> {
let program_path = match self.program {
Some(provided_path) => provided_path,
None => which::which(VARA_ETH_BINARY).map_err(Error::BinaryNotFound)?,
};
let mut command = Command::new(program_path.as_os_str());
let mut process = command
.env(
"RUST_LOG",
env::var_os("RUST_LOG").unwrap_or("=ethexe=info".into()),
)
.args(DEFAULT_ARGS.to_vec())
.stderr(Stdio::null())
.stdout(Stdio::null());
let rpc_port = self.custom_rpc_port.unwrap_or(9944);
let rpc_addr = SocketAddrV4::new(Ipv4Addr::LOCALHOST, rpc_port);
process = process.arg("--rpc-port").arg(rpc_port.to_string());
if let Some(block_time) = self.block_time {
process = process.arg("--block-time").arg(block_time.to_string());
}
if !self.extra_args.is_empty() {
process = process.args(self.extra_args);
}
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
process = unsafe {
process.pre_exec(|| {
if libc::setpgid(0, 0) != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
})
};
}
let child = process.spawn().map_err(Error::Spawn)?;
Ok(VaraEthInstance {
rpc_addr,
eth_rpc_addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8545),
child,
})
}
pub async fn spawn_ready(self) -> Result<VaraEthInstance, Error> {
let timeout = self.timeout.unwrap_or(STARTUP_TIMEOUT);
let instance = self.spawn_immediate()?;
wait_for_rpc(instance.ws_endpoint(), timeout).await?;
Ok(instance)
}
}
async fn wait_for_rpc(url: String, timeout: Duration) -> Result<(), Error> {
let start = Instant::now();
loop {
if start + timeout <= Instant::now() {
return Err(Error::Timeout);
}
if WsClientBuilder::new().build(&url).await.is_ok() {
break Ok(());
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
}