soroban_cli/commands/
doctor.rs1use clap::Parser;
2use rustc_version::version;
3use semver::Version;
4use std::fmt::Debug;
5use std::process::Command;
6
7use crate::{
8 commands::{container::shared::Engine, global},
9 config::{
10 self, data,
11 locator::{self, KeyType},
12 network::{Network, DEFAULTS as DEFAULT_NETWORKS},
13 },
14 print::Print,
15 rpc,
16 upgrade_check::has_available_upgrade,
17 utils::url::redact_url,
18};
19
20#[derive(Parser, Debug, Clone)]
21#[group(skip)]
22pub struct Cmd {
23 #[command(flatten)]
24 pub config_locator: locator::Args,
25}
26
27#[derive(thiserror::Error, Debug)]
28pub enum Error {
29 #[error(transparent)]
30 Locator(#[from] locator::Error),
31
32 #[error(transparent)]
33 Network(#[from] config::network::Error),
34
35 #[error(transparent)]
36 RpcClient(#[from] rpc::Error),
37
38 #[error(transparent)]
39 Io(#[from] std::io::Error),
40
41 #[error(transparent)]
42 Data(#[from] data::Error),
43}
44
45impl Cmd {
46 pub async fn run(&self, _global_args: &global::Args) -> Result<(), Error> {
47 let print = Print::new(false);
48
49 check_version(&print).await?;
50 check_rust_version(&print);
51 check_wasm_target(&print);
52 check_optional_features(&print);
53 check_container_engine(&print);
54 show_config_path(&print, &self.config_locator)?;
55 show_data_path(&print)?;
56 show_xdr_version(&print);
57 inspect_networks(&print, &self.config_locator).await?;
58
59 Ok(())
60 }
61}
62
63fn show_config_path(print: &Print, config_locator: &locator::Args) -> Result<(), Error> {
64 let global_path = config_locator.global_config_path()?;
65
66 print.gearln(format!(
67 "Config directory: {}",
68 global_path.to_string_lossy()
69 ));
70
71 Ok(())
72}
73
74fn show_data_path(print: &Print) -> Result<(), Error> {
75 let path = data::data_local_dir()?;
76
77 print.dirln(format!("Data directory: {}", path.to_string_lossy()));
78
79 Ok(())
80}
81
82fn show_xdr_version(print: &Print) {
83 let xdr = stellar_xdr::VERSION;
84
85 print.infoln(format!("XDR version: {}", xdr.xdr));
86}
87
88async fn print_network(
89 default: bool,
90 print: &Print,
91 name: &str,
92 network: &Network,
93) -> Result<(), Error> {
94 let client = network.rpc_client()?;
95 let version_info = client.get_version_info().await?;
96
97 let prefix = if default {
98 "Default network"
99 } else {
100 "Network"
101 };
102
103 print.globeln(format!(
104 "{prefix} {name:?} ({})",
105 redact_url(&network.rpc_url)
106 ));
107 print.blankln(format!("protocol {}", version_info.protocol_version));
108 print.blankln(format!("rpc {}", version_info.version));
109
110 Ok(())
111}
112
113async fn inspect_networks(print: &Print, config_locator: &locator::Args) -> Result<(), Error> {
114 let saved_networks = KeyType::Network.list_paths(&config_locator.local_and_global()?)?;
115 let default_networks = DEFAULT_NETWORKS
116 .into_iter()
117 .map(|(name, network)| ((*name).to_string(), network.into()));
118
119 for (name, network) in default_networks {
120 if name == "mainnet" {
122 continue;
123 }
124
125 if print_network(true, print, &name, &network).await.is_err() {
126 print.warnln(format!(
127 "Default network {name:?} ({}) is unreachable",
128 redact_url(&network.rpc_url)
129 ));
130 }
131 }
132
133 for (name, _) in &saved_networks {
134 if let Ok(network) = config_locator.read_network(name) {
135 if print_network(false, print, name, &network).await.is_err() {
136 print.warnln(format!(
137 "Network {name:?} ({}) is unreachable",
138 redact_url(&network.rpc_url)
139 ));
140 }
141 }
142 }
143
144 Ok(())
145}
146
147async fn check_version(print: &Print) -> Result<(), Error> {
148 if let Ok((upgrade_available, current_version, latest_version)) =
149 has_available_upgrade(false).await
150 {
151 if upgrade_available {
152 print.warnln(format!(
153 "A new release of Stellar CLI is available: {current_version} -> {latest_version}"
154 ));
155 } else {
156 print.checkln(format!(
157 "You are using the latest version of Stellar CLI: {current_version}"
158 ));
159 }
160 }
161
162 Ok(())
163}
164
165fn check_rust_version(print: &Print) {
166 match version() {
167 Ok(rust_version) => {
168 let v184 = Version::parse("1.84.0").unwrap();
169 let v182 = Version::parse("1.82.0").unwrap();
170
171 if rust_version >= v182 && rust_version < v184 {
172 print.errorln(format!(
173 "Rust {rust_version} cannot be used to build contracts"
174 ));
175 } else {
176 print.infoln(format!("Rust version: {rust_version}"));
177 }
178 }
179 Err(_) => {
180 print.warnln("Could not determine Rust version".to_string());
181 }
182 }
183}
184
185fn check_wasm_target(print: &Print) {
186 let expected_target = get_expected_wasm_target();
187
188 let Ok(output) = Command::new("rustup")
189 .args(["target", "list", "--installed"])
190 .output()
191 else {
192 print.warnln("Could not retrieve Rust targets".to_string());
193 return;
194 };
195
196 if output.status.success() {
197 let targets = String::from_utf8_lossy(&output.stdout);
198
199 if targets.lines().any(|line| line.trim() == expected_target) {
200 print.checkln(format!("Rust target `{expected_target}` is installed"));
201 } else {
202 print.errorln(format!("Rust target `{expected_target}` is not installed"));
203 }
204 } else {
205 print.warnln("Could not retrieve Rust targets".to_string());
206 }
207}
208
209fn check_container_engine(print: &Print) {
210 if !Engine::is_valid_engine() {
214 let value = std::env::var("STELLAR_CONTAINER_ENGINE").unwrap_or_default();
215 print.warnln(format!(
216 "Unknown container engine `{value}`; expected one of: {}",
217 Engine::supported_engines()
218 ));
219 return;
220 }
221
222 let engine = Engine::resolved_default();
223
224 if Command::new(engine.program())
227 .arg("--version")
228 .output()
229 .is_ok()
230 {
231 print.checkln(format!("Container engine `{engine}` is available"));
232 } else {
233 print.warnln(format!("Container engine `{engine}` is not installed"));
234 }
235}
236
237fn check_optional_features(print: &Print) {
238 #[cfg(feature = "additional-libs")]
239 {
240 print.checkln("Wasm optimization");
241 print.checkln("Secure store (OS keyring)");
242 print.checkln("Ledger hardware wallet");
243 }
244
245 #[cfg(not(feature = "additional-libs"))]
246 {
247 print.warnln(
248 "The following features are disabled until `--features additional-libs` is used:",
249 );
250 print.blankln("- Wasm optimization");
251 print.blankln("- Secure store (OS keyring)");
252 print.blankln("- Ledger hardware wallet");
253 }
254}
255
256fn get_expected_wasm_target() -> String {
257 let Ok(current_version) = version() else {
258 return "wasm32v1-none".into();
259 };
260
261 let v184 = Version::parse("1.84.0").unwrap();
262
263 if current_version < v184 {
264 "wasm32-unknown-unknown".into()
265 } else {
266 "wasm32v1-none".into()
267 }
268}