#![deny(clippy::unwrap_used)]
#![allow(clippy::clone_on_copy)]
use std::fs;
use anyhow::{Context, Result};
use clap::Parser;
use futures::StreamExt;
use penumbra_keys::FullViewingKey;
use penumbra_proto::{
custody::v1alpha1::custody_protocol_service_client::CustodyProtocolServiceClient,
view::v1alpha1::view_protocol_service_client::ViewProtocolServiceClient,
};
use penumbra_view::ViewClient;
use url::Url;
mod box_grpc_svc;
mod command;
mod dex_utils;
mod network;
mod opt;
mod warning;
use opt::Opt;
use penumbra_wallet::KeyStore;
use box_grpc_svc::BoxGrpcService;
use command::*;
const CUSTODY_FILE_NAME: &str = "custody.json";
const VIEW_FILE_NAME: &str = "pcli-view.sqlite";
#[derive(Debug)]
pub struct App {
pub view: Option<ViewProtocolServiceClient<BoxGrpcService>>,
pub custody: CustodyProtocolServiceClient<BoxGrpcService>,
pub fvk: FullViewingKey,
pub wallet: KeyStore,
pub pd_url: Url,
}
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"),
self.fvk.account_group_id(),
)
.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.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.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.sync_height - initial_status.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::Keys(keys_cmd) = &opt.cmd {
keys_cmd.exec(opt.home.as_path())?;
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::Keys(_) => unreachable!("wallet 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?,
}
Ok(())
}