#![deny(clippy::unwrap_used)]
#![allow(clippy::clone_on_copy)]
use std::fs;
use anyhow::{Context, Result};
use clap::Parser;
use futures::StreamExt;
use command::*;
use config::PcliConfig;
use opt::Opt;
use penumbra_proto::box_grpc_svc::BoxGrpcService;
use penumbra_proto::{
custody::v1::custody_service_client::CustodyServiceClient,
view::v1::view_service_client::ViewServiceClient,
};
use penumbra_view::ViewClient;
mod command;
mod config;
mod dex_utils;
mod network;
mod opt;
mod terminal;
mod transaction_view_ext;
mod warning;
const CONFIG_FILE_NAME: &str = "config.toml";
const VIEW_FILE_NAME: &str = "pcli-view.sqlite";
#[derive(Debug)]
pub struct App {
pub view: Option<ViewServiceClient<BoxGrpcService>>,
pub custody: CustodyServiceClient<BoxGrpcService>,
pub governance_custody: CustodyServiceClient<BoxGrpcService>,
pub config: PcliConfig,
}
impl App {
pub fn view(&mut self) -> &mut impl ViewClient {
self.view.as_mut().expect("view service initialized")
}
async fn sync(&mut self) -> Result<()> {
let mut status_stream =
ViewClient::status_stream(self.view.as_mut().expect("view service initialized"))
.await?;
let initial_status = status_stream
.next()
.await
.transpose()?
.ok_or_else(|| anyhow::anyhow!("view service did not report sync status"))?;
eprintln!(
"Scanning blocks from last sync height {} to latest height {}",
initial_status.full_sync_height, initial_status.latest_known_block_height,
);
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
let progress_bar = ProgressBar::with_draw_target(
initial_status.latest_known_block_height - initial_status.full_sync_height,
ProgressDrawTarget::stdout(),
)
.with_style(
ProgressStyle::default_bar()
.template("[{elapsed}] {bar:50.cyan/blue} {pos:>7}/{len:7} {per_sec} ETA: {eta}"),
);
progress_bar.set_position(0);
while let Some(status) = status_stream.next().await.transpose()? {
progress_bar.set_position(status.full_sync_height - initial_status.full_sync_height);
}
progress_bar.finish();
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<()> {
if std::env::var("PCLI_UNLEASH_DANGER").is_err() {
warning::display();
}
let mut opt = Opt::parse();
opt.init_tracing();
fs::create_dir_all(&opt.home)
.with_context(|| format!("Failed to create home directory {}", opt.home))?;
if let Command::Init(init_cmd) = &opt.cmd {
init_cmd.exec(opt.home.as_path()).await?;
return Ok(());
}
if let Command::View(ViewCmd::Reset(reset)) = &opt.cmd {
reset.exec(opt.home.as_path())?;
return Ok(());
}
if let Command::Debug(debug_cmd) = &opt.cmd {
let dd = opt.home.into_std_path_buf();
debug_cmd.exec(dd)?;
return Ok(());
}
let (mut app, cmd) = opt.into_app().await?;
if !cmd.offline() {
app.sync().await?;
}
match &cmd {
Command::Init(_) => unreachable!("init command already executed"),
Command::Debug(_) => unreachable!("debug command already executed"),
Command::Transaction(tx_cmd) => tx_cmd.exec(&mut app).await?,
Command::View(view_cmd) => view_cmd.exec(&mut app).await?,
Command::Validator(cmd) => cmd.exec(&mut app).await?,
Command::Query(cmd) => cmd.exec(&mut app).await?,
Command::Ceremony(cmd) => cmd.exec(&mut app).await?,
Command::Threshold(cmd) => cmd.exec(&mut app).await?,
}
Ok(())
}