pcli/
opt.rs

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    /// The home directory used to store configuration and data.
29    #[clap(long, default_value_t = default_home(), env = "PENUMBRA_PCLI_HOME")]
30    pub home: Utf8PathBuf,
31    /// Override the GRPC URL that will be used to connect to a fullnode.
32    ///
33    /// By default, this URL is provided by pcli's config. See `pcli init` for more information.
34    #[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                    // Without explicitly disabling the `r1cs` target, the ZK proof implementations
45                    // will spend an enormous amount of CPU and memory building useless tracing output.
46                    .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        // Build the custody service...
70        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        // Build the governance custody service...
108        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(), // If no separate custody for validator voting, use the same one
140        };
141
142        // ...and the view service...
143        let view = match (self.cmd.offline(), &config.view_url) {
144            // In offline mode, don't construct a view service at all.
145            (true, _) => None,
146            (false, Some(view_url)) => {
147                // Use a remote view service.
148                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                // Use an in-memory view service.
155                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                // Check if the path exists or set it to none
160                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                // Now build the view and custody clients, doing gRPC with ourselves
175                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}