Skip to main content

soroban_cli/commands/network/
settings.rs

1use crate::config::network;
2use crate::output::{Format, Output};
3use crate::utils::XDR_DEPTH_LIMIT;
4use crate::{commands::global, config};
5use semver::Version;
6use stellar_xdr::{
7    ConfigSettingId, ConfigUpgradeSet, LedgerEntryData, LedgerKey, LedgerKeyConfigSetting, Limits,
8    WriteXdr as _,
9};
10
11#[derive(thiserror::Error, Debug)]
12pub enum Error {
13    #[error(transparent)]
14    Config(#[from] config::Error),
15    #[error(transparent)]
16    Network(#[from] network::Error),
17    #[error(transparent)]
18    Xdr(#[from] stellar_xdr::Error),
19    #[error(transparent)]
20    Serde(#[from] serde_json::Error),
21    #[error(transparent)]
22    Rpc(#[from] soroban_rpc::Error),
23    #[error(transparent)]
24    Semver(#[from] semver::Error),
25}
26
27#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
28pub enum OutputFormat {
29    /// XDR (`ConfigUpgradeSet` type)
30    Xdr,
31    /// JSON, XDR-JSON of the `ConfigUpgradeSet` XDR type
32    #[default]
33    Json,
34    /// JSON formatted, XDR-JSON of the `ConfigUpgradeSet` XDR type
35    JsonFormatted,
36}
37
38impl From<OutputFormat> for Format {
39    fn from(value: OutputFormat) -> Self {
40        match value {
41            // Xdr is a raw, human-facing rendering handled outside the JSON path.
42            OutputFormat::Xdr => Format::Readable,
43            OutputFormat::Json => Format::Json,
44            OutputFormat::JsonFormatted => Format::JsonFormatted,
45        }
46    }
47}
48
49#[derive(Debug, clap::Parser, Clone)]
50#[group(skip)]
51pub struct Cmd {
52    #[command(flatten)]
53    pub config: config::ArgsLocatorAndNetwork,
54    /// Include internal config settings that are not upgradeable and are internally maintained by
55    /// the network
56    #[arg(long)]
57    pub internal: bool,
58    /// Format of the output
59    #[arg(long, default_value = "json")]
60    pub output: OutputFormat,
61}
62
63impl Cmd {
64    pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
65        let output = Output::new(self.output.into(), global_args.quiet);
66        let rpc = self.config.get_network()?.rpc_client()?;
67
68        // If the network protocol version is ahead of the XDR version (which tracks the protocol
69        // version), there could be config settings defined in the newer protocol version that the
70        // CLI doesn't know about. Warn, because the output of this command might provide an
71        // incomplete view of the network's config settings.
72        let network_version = rpc.get_version_info().await?.protocol_version;
73        let self_version = Version::parse(stellar_xdr::VERSION.pkg)?.major;
74        if self_version < network_version.into() {
75            // Diagnostic about data completeness; emitted regardless of format
76            // (to stderr) so JSON consumers reading stdout are unaffected.
77            output.print().warnln(format!("Network protocol version is {network_version} but the stellar-cli supports {self_version}. The config fetched may not represent the complete config settings for the network. Upgrade the stellar-cli."));
78        }
79
80        // Collect the ledger entries for all the config settings.
81        let keys = ConfigSettingId::variants()
82            .into_iter()
83            .filter(|id| match id {
84                // Internally maintained settings that a network validator cannot vote to change
85                // are not output by this command unless the internal option is specified.
86                ConfigSettingId::LiveSorobanStateSizeWindow | ConfigSettingId::EvictionIterator => {
87                    self.internal
88                }
89                // All other configs can be modified by network upgrades and are always output.
90                _ => true,
91            })
92            .map(|id| {
93                LedgerKey::ConfigSetting(LedgerKeyConfigSetting {
94                    config_setting_id: id,
95                })
96            })
97            .collect::<Vec<_>>();
98        let settings = rpc
99            .get_full_ledger_entries(&keys)
100            .await?
101            .entries
102            .into_iter()
103            .filter_map(|e| match e.val {
104                LedgerEntryData::ConfigSetting(setting) => Some(setting),
105                _ => None,
106            })
107            .collect::<Vec<_>>();
108
109        let config_upgrade_set = ConfigUpgradeSet {
110            updated_entry: settings.try_into().unwrap(),
111        };
112        match self.output {
113            // Xdr is a distinct raw rendering, handled outside the JSON path.
114            OutputFormat::Xdr => println!(
115                "{}",
116                config_upgrade_set.to_xdr_base64(Limits::depth(XDR_DEPTH_LIMIT))?
117            ),
118            OutputFormat::Json | OutputFormat::JsonFormatted => {
119                output.json_value(&config_upgrade_set)?;
120            }
121        }
122        Ok(())
123    }
124}