Skip to main content

soroban_cli/commands/network/
settings.rs

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