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            #[cfg(feature = "ledger")]
106            CustodyConfig::Ledger(config) => {
107                tracing::info!("using ledger custody service");
108                let service = penumbra_sdk_custody_ledger_usb::Service::new(config.clone());
109                let custody_svc = CustodyServiceServer::new(service);
110                CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
111            }
112        };
113
114        // Build the governance custody service...
115        let governance_custody = match &config.governance_custody {
116            Some(separate_governance_custody) => match separate_governance_custody {
117                GovernanceCustodyConfig::SoftKms(config) => {
118                    tracing::info!(
119                        "using separate software KMS custody service for validator voting"
120                    );
121                    let soft_kms = SoftKms::new(config.clone());
122                    let custody_svc = CustodyServiceServer::new(soft_kms);
123                    CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
124                }
125                GovernanceCustodyConfig::Threshold(config) => {
126                    tracing::info!(
127                        "using separate manual threshold custody service for validator voting"
128                    );
129                    let threshold_kms = penumbra_sdk_custody::threshold::Threshold::new(
130                        config.clone(),
131                        ActualTerminal { fvk: Some(fvk) },
132                    );
133                    let custody_svc = CustodyServiceServer::new(threshold_kms);
134                    CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
135                }
136                GovernanceCustodyConfig::Encrypted { config, .. } => {
137                    tracing::info!("using separate encrypted custody service for validator voting");
138                    let encrypted_kms = penumbra_sdk_custody::encrypted::Encrypted::new(
139                        config.clone(),
140                        ActualTerminal { fvk: Some(fvk) },
141                    );
142                    let custody_svc = CustodyServiceServer::new(encrypted_kms);
143                    CustodyServiceClient::new(box_grpc_svc::local(custody_svc))
144                }
145            },
146            None => custody.clone(), // If no separate custody for validator voting, use the same one
147        };
148
149        // ...and the view service...
150        let view = match (self.cmd.offline(), &config.view_url) {
151            // In offline mode, don't construct a view service at all.
152            (true, _) => None,
153            (false, Some(view_url)) => {
154                // Use a remote view service.
155                tracing::info!(%view_url, "using remote view service");
156
157                let ep = tonic::transport::Endpoint::new(view_url.to_string())?;
158                Some(ViewServiceClient::new(box_grpc_svc::connect(ep).await?))
159            }
160            (false, None) => {
161                // Use an in-memory view service.
162                let path = self.home.join(crate::VIEW_FILE_NAME);
163                tracing::info!(%path, "using local view service");
164
165                let registry_path = self.home.join("registry.json");
166                // Check if the path exists or set it to none
167                let registry_path = if registry_path.exists() {
168                    Some(registry_path)
169                } else {
170                    None
171                };
172
173                let svc = ViewServer::load_or_initialize(
174                    Some(path),
175                    registry_path,
176                    &config.full_viewing_key,
177                    config.grpc_url.clone(),
178                )
179                .await?;
180
181                // Now build the view and custody clients, doing gRPC with ourselves
182                let svc = ViewServiceServer::new(svc);
183                Some(ViewServiceClient::new(box_grpc_svc::local(svc)))
184            }
185        };
186
187        let app = App {
188            view,
189            custody,
190            governance_custody,
191            config,
192            save_transaction_here_instead: None,
193        };
194        Ok((app, self.cmd))
195    }
196}