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::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 show_config_path(&print, &self.config_locator)?;
54 show_data_path(&print)?;
55 show_xdr_version(&print);
56 inspect_networks(&print, &self.config_locator).await?;
57
58 Ok(())
59 }
60}
61
62fn show_config_path(print: &Print, config_locator: &locator::Args) -> Result<(), Error> {
63 let global_path = config_locator.global_config_path()?;
64
65 print.gearln(format!(
66 "Config directory: {}",
67 global_path.to_string_lossy()
68 ));
69
70 Ok(())
71}
72
73fn show_data_path(print: &Print) -> Result<(), Error> {
74 let path = data::data_local_dir()?;
75
76 print.dirln(format!("Data directory: {}", path.to_string_lossy()));
77
78 Ok(())
79}
80
81fn show_xdr_version(print: &Print) {
82 let xdr = stellar_xdr::VERSION;
83
84 print.infoln(format!("XDR version: {}", xdr.xdr_curr));
85}
86
87async fn print_network(
88 default: bool,
89 print: &Print,
90 name: &str,
91 network: &Network,
92) -> Result<(), Error> {
93 let client = network.rpc_client()?;
94 let version_info = client.get_version_info().await?;
95
96 let prefix = if default {
97 "Default network"
98 } else {
99 "Network"
100 };
101
102 print.globeln(format!(
103 "{prefix} {name:?} ({})",
104 redact_url(&network.rpc_url)
105 ));
106 print.blankln(format!("protocol {}", version_info.protocol_version));
107 print.blankln(format!("rpc {}", version_info.version));
108
109 Ok(())
110}
111
112async fn inspect_networks(print: &Print, config_locator: &locator::Args) -> Result<(), Error> {
113 let saved_networks = KeyType::Network.list_paths(&config_locator.local_and_global()?)?;
114 let default_networks = DEFAULT_NETWORKS
115 .into_iter()
116 .map(|(name, network)| ((*name).to_string(), network.into()));
117
118 for (name, network) in default_networks {
119 if name == "mainnet" {
121 continue;
122 }
123
124 if print_network(true, print, &name, &network).await.is_err() {
125 print.warnln(format!(
126 "Default network {name:?} ({}) is unreachable",
127 redact_url(&network.rpc_url)
128 ));
129 }
130 }
131
132 for (name, _) in &saved_networks {
133 if let Ok(network) = config_locator.read_network(name) {
134 if print_network(false, print, name, &network).await.is_err() {
135 print.warnln(format!(
136 "Network {name:?} ({}) is unreachable",
137 redact_url(&network.rpc_url)
138 ));
139 }
140 }
141 }
142
143 Ok(())
144}
145
146async fn check_version(print: &Print) -> Result<(), Error> {
147 if let Ok((upgrade_available, current_version, latest_version)) =
148 has_available_upgrade(false).await
149 {
150 if upgrade_available {
151 print.warnln(format!(
152 "A new release of Stellar CLI is available: {current_version} -> {latest_version}"
153 ));
154 } else {
155 print.checkln(format!(
156 "You are using the latest version of Stellar CLI: {current_version}"
157 ));
158 }
159 }
160
161 Ok(())
162}
163
164fn check_rust_version(print: &Print) {
165 match version() {
166 Ok(rust_version) => {
167 let v184 = Version::parse("1.84.0").unwrap();
168 let v182 = Version::parse("1.82.0").unwrap();
169
170 if rust_version >= v182 && rust_version < v184 {
171 print.errorln(format!(
172 "Rust {rust_version} cannot be used to build contracts"
173 ));
174 } else {
175 print.infoln(format!("Rust version: {rust_version}"));
176 }
177 }
178 Err(_) => {
179 print.warnln("Could not determine Rust version".to_string());
180 }
181 }
182}
183
184fn check_wasm_target(print: &Print) {
185 let expected_target = get_expected_wasm_target();
186
187 let Ok(output) = Command::new("rustup")
188 .args(["target", "list", "--installed"])
189 .output()
190 else {
191 print.warnln("Could not retrieve Rust targets".to_string());
192 return;
193 };
194
195 if output.status.success() {
196 let targets = String::from_utf8_lossy(&output.stdout);
197
198 if targets.lines().any(|line| line.trim() == expected_target) {
199 print.checkln(format!("Rust target `{expected_target}` is installed"));
200 } else {
201 print.errorln(format!("Rust target `{expected_target}` is not installed"));
202 }
203 } else {
204 print.warnln("Could not retrieve Rust targets".to_string());
205 }
206}
207
208fn check_optional_features(print: &Print) {
209 #[cfg(feature = "additional-libs")]
210 {
211 print.checkln("Wasm optimization");
212 print.checkln("Secure store (OS keyring)");
213 print.checkln("Ledger hardware wallet");
214 }
215
216 #[cfg(not(feature = "additional-libs"))]
217 {
218 print.warnln(
219 "The following features are disabled until `--features additional-libs` is used:",
220 );
221 print.blankln("- Wasm optimization");
222 print.blankln("- Secure store (OS keyring)");
223 print.blankln("- Ledger hardware wallet");
224 }
225}
226
227fn get_expected_wasm_target() -> String {
228 let Ok(current_version) = version() else {
229 return "wasm32v1-none".into();
230 };
231
232 let v184 = Version::parse("1.84.0").unwrap();
233
234 if current_version < v184 {
235 "wasm32-unknown-unknown".into()
236 } else {
237 "wasm32v1-none".into()
238 }
239}