use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use anyhow::Context;
use clap;
use log::{debug, info};
use ark::{ArkInfo, Vtxo, VtxoId};
use ark::encode::ProtocolEncoding;
use bark_json::primitives::VtxoInfo;
use server_rpc as rpc;
use bark_cli::wallet::open_wallet;
use crate::util::{self, output_json};
#[derive(clap::Subcommand)]
pub enum DevCommand {
#[command(subcommand)]
Vtxo(VtxoCommand),
#[command()]
ArkInfo {
ark_address: String,
},
}
pub async fn execute_dev_command(
command: DevCommand,
datadir: PathBuf,
) -> anyhow::Result<()> {
match command {
DevCommand::Vtxo(c) => execute_vtxo_command(&datadir, c).await?,
DevCommand::ArkInfo { ark_address } => {
let mut srv = connect_server(ark_address).await
.context("failed to connect to server")?;
let res = srv.get_ark_info(rpc::protos::Empty {}).await
.context("ark_info request failed")?;
let info = ArkInfo::try_from(res.into_inner())
.context("invalid ark info from ark server")?;
output_json(&bark_json::cli::ArkInfo::from(info));
},
}
Ok(())
}
#[derive(clap::Subcommand)]
pub enum VtxoCommand {
#[command()]
Decode {
vtxo: String,
},
#[command()]
Drop {
#[arg(long = "dangerous")]
dangerous: bool,
#[arg(long = "all")]
all: bool,
#[arg(long= "vtxo")]
vtxo: Vec<VtxoId>,
}
}
async fn execute_vtxo_command(datadir: &Path, command: VtxoCommand) -> anyhow::Result<()> {
match command {
VtxoCommand::Decode { vtxo } => {
let vtxo = Vtxo::deserialize_hex(&vtxo).context("invalid vtxo")?;
debug!("{:#?}", vtxo);
let info = VtxoInfo::from(vtxo);
output_json(&info);
},
VtxoCommand::Drop { dangerous, all, vtxo} => {
if !dangerous {
bail!("You must acknowledge the danger. Run again with --dangerous")
}
let (wallet, _onchain) = open_wallet(&datadir).await
.context("Failed to open wallet")?;
if all {
log::info!("Dropping all vtxos");
wallet.dangerous_drop_all_vtxos().await
.context("Failed to drop vtxos")?;
}
for v in vtxo {
log::info!("Dropping vtxo {}", v);
wallet.dangerous_drop_vtxo(v).await
.context("Failed to drop vtxo")?;
}
}
}
Ok(())
}
fn create_server_endpoint(address: &str) -> anyhow::Result<tonic::transport::Endpoint> {
let uri = tonic::transport::Uri::from_str(address)
.context("failed to parse Ark server as a URI")?;
let scheme = uri.scheme_str().unwrap_or("");
if scheme != "http" && scheme != "https" {
bail!("Ark server scheme must be either http or https. Found: {}", scheme);
}
let mut endpoint = tonic::transport::Channel::builder(uri.clone())
.keep_alive_timeout(Duration::from_secs(600))
.timeout(Duration::from_secs(600));
if scheme == "https" {
info!("Connecting to Ark server using TLS...");
let uri_auth = uri.clone().into_parts().authority
.context("Ark server URI is missing an authority part")?;
let domain = uri_auth.host();
let tls_config = tonic::transport::ClientTlsConfig::new()
.with_enabled_roots()
.domain_name(domain);
endpoint = endpoint.tls_config(tls_config)?
} else {
info!("Connecting to Ark server without TLS...");
};
Ok(endpoint)
}
pub async fn connect_server(
address: String,
) -> anyhow::Result<rpc::ArkServiceClient<tonic::transport::Channel>> {
let address = util::default_scheme("https", address)?;
let endpoint = create_server_endpoint(&address)?;
let channel = endpoint.connect().await
.context("couldn't connect to Ark server")?;
Ok(rpc::ArkServiceClient::new(channel))
}