1use crate::{
2 config::{CustodyConfig, GovernanceCustodyConfig, PcliConfig},
3 default_home,
4 terminal::ActualTerminal,
5 App, Command,
6};
7use anyhow::Result;
8use camino::Utf8PathBuf;
9use clap::Parser;
10use penumbra_sdk_custody::{null_kms::NullKms, soft_kms::SoftKms};
11use penumbra_sdk_proto::box_grpc_svc;
12use penumbra_sdk_proto::{
13 custody::v1::{
14 custody_service_client::CustodyServiceClient, custody_service_server::CustodyServiceServer,
15 },
16 view::v1::{view_service_client::ViewServiceClient, view_service_server::ViewServiceServer},
17};
18use penumbra_sdk_view::ViewServer;
19use std::io::IsTerminal as _;
20use tracing_subscriber::EnvFilter;
21use url::Url;
22
23#[derive(Debug, Parser)]
24#[clap(name = "pcli", about = "The Penumbra command-line interface.", version)]
25pub struct Opt {
26 #[clap(subcommand)]
27 pub cmd: Command,
28 #[clap(long, default_value_t = default_home(), env = "PENUMBRA_PCLI_HOME")]
30 pub home: Utf8PathBuf,
31 #[clap(long, parse(try_from_str = Url::parse))]
35 pub grpc_url: Option<Url>,
36}
37
38impl Opt {
39 pub fn init_tracing(&mut self) {
40 tracing_subscriber::fmt()
41 .with_ansi(std::io::stdout().is_terminal())
42 .with_env_filter(
43 EnvFilter::from_default_env()
44 .add_directive(
47 "r1cs=off"
48 .parse()
49 .expect("rics=off is a valid filter directive"),
50 ),
51 )
52 .with_writer(std::io::stderr)
53 .init();
54 }
55
56 pub fn load_config(&self) -> Result<PcliConfig> {
57 let path = self.home.join(crate::CONFIG_FILE_NAME);
58 let mut config = PcliConfig::load(path)?;
59 if let Some(grpc_url) = &self.grpc_url {
60 config.grpc_url = grpc_url.clone();
61 }
62 Ok(config)
63 }
64
65 pub async fn into_app(self) -> Result<(App, Command)> {
66 let config = self.load_config()?;
67 let fvk = config.full_viewing_key.clone();
68
69 let custody = match &config.custody {
71 CustodyConfig::ViewOnly => {
72 tracing::info!("using view-only custody service");
73 let null_kms = NullKms::default();
74 let custody_svc = CustodyServiceServer::new(null_kms);
75 CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
76 }
77 CustodyConfig::SoftKms(config) => {
78 tracing::info!("using software KMS custody service");
79 let soft_kms = SoftKms::new(config.clone());
80 let custody_svc = CustodyServiceServer::new(soft_kms);
81 CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
82 }
83 CustodyConfig::Threshold(config) => {
84 tracing::info!("using manual threshold custody service");
85 let threshold_kms = penumbra_sdk_custody::threshold::Threshold::new(
86 config.clone(),
87 ActualTerminal {
88 fvk: Some(fvk.clone()),
89 },
90 );
91 let custody_svc = CustodyServiceServer::new(threshold_kms);
92 CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
93 }
94 CustodyConfig::Encrypted(config) => {
95 tracing::info!("using encrypted custody service");
96 let encrypted_kms = penumbra_sdk_custody::encrypted::Encrypted::new(
97 config.clone(),
98 ActualTerminal {
99 fvk: Some(fvk.clone()),
100 },
101 );
102 let custody_svc = CustodyServiceServer::new(encrypted_kms);
103 CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
104 }
105 };
106
107 let governance_custody = match &config.governance_custody {
109 Some(separate_governance_custody) => match separate_governance_custody {
110 GovernanceCustodyConfig::SoftKms(config) => {
111 tracing::info!(
112 "using separate software KMS custody service for validator voting"
113 );
114 let soft_kms = SoftKms::new(config.clone());
115 let custody_svc = CustodyServiceServer::new(soft_kms);
116 CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
117 }
118 GovernanceCustodyConfig::Threshold(config) => {
119 tracing::info!(
120 "using separate manual threshold custody service for validator voting"
121 );
122 let threshold_kms = penumbra_sdk_custody::threshold::Threshold::new(
123 config.clone(),
124 ActualTerminal { fvk: Some(fvk) },
125 );
126 let custody_svc = CustodyServiceServer::new(threshold_kms);
127 CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
128 }
129 GovernanceCustodyConfig::Encrypted { config, .. } => {
130 tracing::info!("using separate encrypted custody service for validator voting");
131 let encrypted_kms = penumbra_sdk_custody::encrypted::Encrypted::new(
132 config.clone(),
133 ActualTerminal { fvk: Some(fvk) },
134 );
135 let custody_svc = CustodyServiceServer::new(encrypted_kms);
136 CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
137 }
138 },
139 None => custody.clone(), };
141
142 let view = match (self.cmd.offline(), &config.view_url) {
144 (true, _) => None,
146 (false, Some(view_url)) => {
147 tracing::info!(%view_url, "using remote view service");
149
150 let ep = tonic::transport::Endpoint::new(view_url.to_string())?;
151 Some(ViewServiceClient::new(box_grpc_svc::connect(ep).await?))
152 }
153 (false, None) => {
154 let path = self.home.join(crate::VIEW_FILE_NAME);
156 tracing::info!(%path, "using local view service");
157
158 let registry_path = self.home.join("registry.json");
159 let registry_path = if registry_path.exists() {
161 Some(registry_path)
162 } else {
163 None
164 };
165
166 let svc = ViewServer::load_or_initialize(
167 Some(path),
168 registry_path,
169 &config.full_viewing_key,
170 config.grpc_url.clone(),
171 )
172 .await?;
173
174 let svc = ViewServiceServer::new(svc);
176 Some(ViewServiceClient::new(box_grpc_svc::local(svc)))
177 }
178 };
179
180 let app = App {
181 view,
182 custody,
183 governance_custody,
184 config,
185 save_transaction_here_instead: None,
186 };
187 Ok((app, self.cmd))
188 }
189}