use std::{net::SocketAddr, path::PathBuf, str::FromStr};
use structopt::StructOpt;
use thiserror::Error;
use zakura_chain::block::Height;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Backend {
Zakurad,
Zcashd,
}
impl FromStr for Backend {
type Err = InvalidBackendError;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string.to_lowercase().as_str() {
"zakurad" => Ok(Backend::Zakurad),
"zcashd" => Ok(Backend::Zcashd),
_ => Err(InvalidBackendError(string.to_owned())),
}
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[error("Invalid backend: {0}")]
pub struct InvalidBackendError(String);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Transport {
Cli,
Direct,
}
impl FromStr for Transport {
type Err = InvalidTransportError;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string.to_lowercase().as_str() {
"cli" | "zcash-cli" | "zcashcli" | "zcli" | "z-cli" => Ok(Transport::Cli),
"direct" => Ok(Transport::Direct),
_ => Err(InvalidTransportError(string.to_owned())),
}
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[error("Invalid transport: {0}")]
pub struct InvalidTransportError(String);
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
pub struct Args {
#[structopt(default_value = "zakurad", short, long)]
pub backend: Backend,
#[structopt(default_value = "cli", short, long)]
pub transport: Transport,
#[structopt(default_value = "zcash-cli", short, long)]
pub cli: String,
#[structopt(short, long)]
pub addr: Option<SocketAddr>,
#[structopt(short, long)]
pub last_checkpoint: Option<Height>,
#[structopt(long, parse(from_os_str))]
pub state_cache_dir: Option<PathBuf>,
#[structopt(long, parse(from_os_str))]
pub mainnet_frontier_output: Option<PathBuf>,
#[structopt(long)]
pub full_list: bool,
#[structopt(last = true)]
pub zcli_args: Vec<String>,
}
impl Args {
pub fn validate_mode(&self) -> Result<(), String> {
if self.state_cache_dir.is_some() {
if self.addr.is_some() {
return Err(
"--state-cache-dir reads the database directly: remove --addr".to_string(),
);
}
if !self.zcli_args.is_empty() {
return Err(
"--state-cache-dir reads the database directly: remove zcash-cli passthrough \
arguments"
.to_string(),
);
}
if self.full_list && self.last_checkpoint.is_some() {
return Err(
"--full-list extends the embedded checkpoint list: remove --last-checkpoint"
.to_string(),
);
}
if self.full_list && self.mainnet_frontier_output.is_none() {
return Err(
"--full-list emits a replacement main-checkpoints.txt, which must ship with \
its coupled frontier: add --mainnet-frontier-output"
.to_string(),
);
}
} else {
if self.mainnet_frontier_output.is_some() {
return Err("--mainnet-frontier-output requires --state-cache-dir".to_string());
}
if self.full_list {
return Err("--full-list requires --state-cache-dir".to_string());
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rpc_args() -> Args {
Args {
backend: Backend::Zakurad,
transport: Transport::Cli,
cli: "zcash-cli".to_string(),
addr: None,
last_checkpoint: None,
state_cache_dir: None,
mainnet_frontier_output: None,
full_list: false,
zcli_args: Vec::new(),
}
}
#[test]
fn rpc_mode_flag_combinations() {
assert_eq!(rpc_args().validate_mode(), Ok(()));
let mut frontier_without_state = rpc_args();
frontier_without_state.mainnet_frontier_output = Some(PathBuf::from("frontier.bin"));
assert!(frontier_without_state.validate_mode().is_err());
let mut full_list_without_state = rpc_args();
full_list_without_state.full_list = true;
assert!(full_list_without_state.validate_mode().is_err());
}
#[test]
fn offline_mode_flag_combinations() {
let mut offline = rpc_args();
offline.state_cache_dir = Some(PathBuf::from("state"));
offline.mainnet_frontier_output = Some(PathBuf::from("frontier.bin"));
offline.full_list = true;
assert_eq!(offline.validate_mode(), Ok(()));
let mut offline_with_addr = offline.clone();
offline_with_addr.addr = Some("127.0.0.1:8232".parse().expect("valid address"));
assert!(offline_with_addr.validate_mode().is_err());
let mut offline_with_zcli_args = offline.clone();
offline_with_zcli_args.zcli_args = vec!["-testnet".to_string()];
assert!(offline_with_zcli_args.validate_mode().is_err());
let mut full_list_with_last = offline.clone();
full_list_with_last.last_checkpoint = Some(Height(100));
assert!(full_list_with_last.validate_mode().is_err());
let mut full_list_without_frontier = offline.clone();
full_list_without_frontier.mainnet_frontier_output = None;
assert!(
full_list_without_frontier.validate_mode().is_err(),
"a replacement checkpoint list must ship with its coupled frontier"
);
let mut resume_without_full_list = offline;
resume_without_full_list.full_list = false;
resume_without_full_list.last_checkpoint = Some(Height(100));
assert_eq!(resume_without_full_list.validate_mode(), Ok(()));
}
}