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};
18
19#[derive(Parser, Debug, Clone)]
20#[group(skip)]
21pub struct Cmd {
22 #[command(flatten)]
23 pub config_locator: locator::Args,
24}
25
26#[derive(thiserror::Error, Debug)]
27pub enum Error {
28 #[error(transparent)]
29 Locator(#[from] locator::Error),
30
31 #[error(transparent)]
32 Network(#[from] config::network::Error),
33
34 #[error(transparent)]
35 RpcClient(#[from] rpc::Error),
36
37 #[error(transparent)]
38 Io(#[from] std::io::Error),
39
40 #[error(transparent)]
41 Data(#[from] data::Error),
42}
43
44impl Cmd {
45 pub async fn run(&self, _global_args: &global::Args) -> Result<(), Error> {
46 let print = Print::new(false);
47
48 check_version(&print).await?;
49 check_rust_version(&print);
50 check_wasm_target(&print);
51 show_config_path(&print, &self.config_locator)?;
52 show_data_path(&print)?;
53 show_xdr_version(&print);
54 inspect_networks(&print, &self.config_locator).await?;
55
56 Ok(())
57 }
58}
59
60fn show_config_path(print: &Print, config_locator: &locator::Args) -> Result<(), Error> {
61 let global_path = config_locator.global_config_path()?;
62
63 print.gearln(format!(
64 "Config directory: {}",
65 global_path.to_string_lossy()
66 ));
67
68 Ok(())
69}
70
71fn show_data_path(print: &Print) -> Result<(), Error> {
72 let path = data::data_local_dir()?;
73
74 print.dirln(format!("Data directory: {}", path.to_string_lossy()));
75
76 Ok(())
77}
78
79fn show_xdr_version(print: &Print) {
80 let xdr = stellar_xdr::VERSION;
81
82 print.infoln(format!("XDR version: {}", xdr.xdr_curr));
83}
84
85async fn print_network(
86 default: bool,
87 print: &Print,
88 name: &str,
89 network: &Network,
90) -> Result<(), Error> {
91 let client = network.rpc_client()?;
92 let version_info = client.get_version_info().await?;
93
94 let prefix = if default {
95 "Default network"
96 } else {
97 "Network"
98 };
99
100 print.globeln(format!("{prefix} {name:?} ({})", network.rpc_url,));
101 print.blankln(format!(" protocol {}", version_info.protocol_version));
102 print.blankln(format!(" rpc {}", version_info.version));
103
104 Ok(())
105}
106
107async fn inspect_networks(print: &Print, config_locator: &locator::Args) -> Result<(), Error> {
108 let saved_networks = KeyType::Network.list_paths(&config_locator.local_and_global()?)?;
109 let default_networks = DEFAULT_NETWORKS
110 .into_iter()
111 .map(|(name, network)| ((*name).to_string(), network.into()));
112
113 for (name, network) in default_networks {
114 if name == "mainnet" {
116 continue;
117 }
118
119 if print_network(true, print, &name, &network).await.is_err() {
120 print.warnln(format!(
121 "Default network {name:?} ({}) is unreachable",
122 network.rpc_url
123 ));
124 }
125 }
126
127 for (name, _) in &saved_networks {
128 if let Ok(network) = config_locator.read_network(name) {
129 if print_network(false, print, name, &network).await.is_err() {
130 print.warnln(format!(
131 "Network {name:?} ({}) is unreachable",
132 network.rpc_url
133 ));
134 }
135 }
136 }
137
138 Ok(())
139}
140
141async fn check_version(print: &Print) -> Result<(), Error> {
142 if let Ok((upgrade_available, current_version, latest_version)) =
143 has_available_upgrade(false).await
144 {
145 if upgrade_available {
146 print.warnln(format!(
147 "A new release of Stellar CLI is available: {current_version} -> {latest_version}"
148 ));
149 } else {
150 print.checkln(format!(
151 "You are using the latest version of Stellar CLI: {current_version}"
152 ));
153 }
154 }
155
156 Ok(())
157}
158
159fn check_rust_version(print: &Print) {
160 match version() {
161 Ok(rust_version) => {
162 let v184 = Version::parse("1.84.0").unwrap();
163 let v182 = Version::parse("1.82.0").unwrap();
164
165 if rust_version >= v182 && rust_version < v184 {
166 print.errorln(format!(
167 "Rust {rust_version} cannot be used to build contracts"
168 ));
169 } else {
170 print.infoln(format!("Rust version: {rust_version}"));
171 }
172 }
173 Err(_) => {
174 print.warnln("Could not determine Rust version".to_string());
175 }
176 }
177}
178
179fn check_wasm_target(print: &Print) {
180 let expected_target = get_expected_wasm_target();
181
182 let Ok(output) = Command::new("rustup")
183 .args(["target", "list", "--installed"])
184 .output()
185 else {
186 print.warnln("Could not retrieve Rust targets".to_string());
187 return;
188 };
189
190 if output.status.success() {
191 let targets = String::from_utf8_lossy(&output.stdout);
192
193 if targets.lines().any(|line| line.trim() == expected_target) {
194 print.checkln(format!("Rust target `{expected_target}` is installed"));
195 } else {
196 print.errorln(format!("Rust target `{expected_target}` is not installed"));
197 }
198 } else {
199 print.warnln("Could not retrieve Rust targets".to_string());
200 }
201}
202
203fn get_expected_wasm_target() -> String {
204 let Ok(current_version) = version() else {
205 return "wasm32v1-none".into();
206 };
207
208 let v184 = Version::parse("1.84.0").unwrap();
209
210 if current_version < v184 {
211 "wasm32-unknown-unknown".into()
212 } else {
213 "wasm32v1-none".into()
214 }
215}