diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs index 680bcb536..79eb7f88d 100644 --- a/cmd/soroban-cli/src/commands/config/locator.rs +++ b/cmd/soroban-cli/src/commands/config/locator.rs @@ -199,10 +199,23 @@ impl Args { pub fn read_network(&self, name: &str) -> Result { let res = KeyType::Network.read_with_global(name, &self.local_config()?); if let Err(Error::ConfigMissing(_, _)) = &res { - if name == "futurenet" { - let network = Network::futurenet(); - self.write_network(name, &network)?; - return Ok(network); + match name { + "pubnet" => { + let network = Network::pubnet(); + self.write_network(name, &network)?; + return Ok(network); + } + "testnet" => { + let network = Network::testnet(); + self.write_network(name, &network)?; + return Ok(network); + } + "futurenet" => { + let network = Network::futurenet(); + self.write_network(name, &network)?; + return Ok(network); + } + _ => {} } } res diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 0b944c78a..83d4ecc41 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -22,7 +22,7 @@ use crate::commands::{ NetworkRunnable, }; use crate::{ - commands::{config, contract::install, HEADING_RPC}, + commands::{config, contract::install, HEADING_NETWORK}, rpc::{self, Client}, utils, wasm, }; @@ -44,7 +44,7 @@ pub struct Cmd { /// Custom salt 32-byte salt for the token id #[arg( long, - help_heading = HEADING_RPC, + help_heading = HEADING_NETWORK, )] salt: Option, #[command(flatten)] diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index df4ed72e1..fd5caea9f 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -17,7 +17,7 @@ pub mod version; pub mod txn_result; -pub const HEADING_RPC: &str = "Options (RPC)"; +pub const HEADING_NETWORK: &str = "Options (Network)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. Stellar Docs: https://developers.stellar.org @@ -134,7 +134,7 @@ pub enum Cmd { /// Start and configure networks #[command(subcommand)] Network(network::Cmd), - /// Download a snapshot of a ledger. + /// Download a snapshot of a ledger from an archive. Snapshot(snapshot::Cmd), /// Print version information Version(version::Cmd), diff --git a/cmd/soroban-cli/src/commands/network/mod.rs b/cmd/soroban-cli/src/commands/network/mod.rs index 581b965a0..c42d7f4a4 100644 --- a/cmd/soroban-cli/src/commands/network/mod.rs +++ b/cmd/soroban-cli/src/commands/network/mod.rs @@ -1,12 +1,13 @@ use std::str::FromStr; use clap::{arg, Parser}; +use http::Uri; use serde::{Deserialize, Serialize}; use serde_json::Value; use stellar_strkey::ed25519::PublicKey; use crate::{ - commands::HEADING_RPC, + commands::HEADING_NETWORK, rpc::{self, Client}, }; @@ -89,6 +90,8 @@ pub enum Error { InproperResponse(String), #[error("Currently not supported on windows. Please visit:\n{0}")] WindowsNotSupported(String), + #[error("Archive URL not configured")] + ArchiveUrlNotConfigured, } impl Cmd { @@ -123,7 +126,7 @@ pub struct Args { requires = "network_passphrase", required_unless_present = "network", env = "STELLAR_RPC_URL", - help_heading = HEADING_RPC, + help_heading = HEADING_NETWORK, )] pub rpc_url: Option, /// Network passphrase to sign the transaction sent to the rpc server @@ -132,15 +135,23 @@ pub struct Args { requires = "rpc_url", required_unless_present = "network", env = "STELLAR_NETWORK_PASSPHRASE", - help_heading = HEADING_RPC, + help_heading = HEADING_NETWORK, )] pub network_passphrase: Option, + /// Archive URL + #[arg( + long = "archive-url", + requires = "network_passphrase", + env = "STELLAR_ARCHIVE_URL", + help_heading = HEADING_NETWORK, + )] + pub archive_url: Option, /// Name of network to use from config #[arg( long, required_unless_present = "rpc_url", env = "STELLAR_NETWORK", - help_heading = HEADING_RPC, + help_heading = HEADING_NETWORK, )] pub network: Option, } @@ -158,6 +169,7 @@ impl Args { Ok(Network { rpc_url, network_passphrase, + archive_url: self.archive_url.clone(), }) } else { Err(Error::Network) @@ -172,21 +184,27 @@ pub struct Network { #[arg( long = "rpc-url", env = "STELLAR_RPC_URL", - help_heading = HEADING_RPC, + help_heading = HEADING_NETWORK, )] pub rpc_url: String, /// Network passphrase to sign the transaction sent to the rpc server #[arg( - long, - env = "STELLAR_NETWORK_PASSPHRASE", - help_heading = HEADING_RPC, - )] + long, + env = "STELLAR_NETWORK_PASSPHRASE", + help_heading = HEADING_NETWORK, + )] pub network_passphrase: String, + /// Archive URL + #[arg( + long = "archive-url", + env = "STELLAR_ARCHIVE_URL", + help_heading = HEADING_NETWORK, + )] + pub archive_url: Option, } impl Network { pub async fn helper_url(&self, addr: &str) -> Result { - use http::Uri; tracing::debug!("address {addr:?}"); let rpc_uri = Uri::from_str(&self.rpc_url) .map_err(|_| Error::InvalidUrl(self.rpc_url.to_string()))?; @@ -211,6 +229,30 @@ impl Network { } } + pub fn archive_url(&self) -> Result { + // Return the configured archive URL, or if one is not configured, guess + // at an appropriate archive URL given the network passphrase. + self.archive_url + .as_deref() + .or(match self.network_passphrase.as_str() { + "Public Global Stellar Network ; September 2015" => { + Some("https://history.stellar.org/prd/core-live/core_live_001") + } + "Test SDF Network ; September 2015" => { + Some("https://history.stellar.org/prd/core-testnet/core_testnet_001") + } + "Test SDF Future Network ; October 2022" => { + Some("https://history-futurenet.stellar.org") + } + _ => None, + }) + .ok_or(Error::ArchiveUrlNotConfigured) + .and_then(|archive_url| { + Uri::from_str(archive_url) + .map_err(|_| Error::InvalidUrl((*archive_url).to_string())) + }) + } + #[allow(clippy::similar_names)] pub async fn fund_address(&self, addr: &PublicKey) -> Result<(), Error> { let uri = self.helper_url(&addr.to_string()).await?; @@ -248,10 +290,25 @@ impl Network { } impl Network { + pub fn pubnet() -> Self { + Network { + rpc_url: String::new(), + network_passphrase: "Public Global Stellar Network ; September 2015".to_owned(), + archive_url: None, + } + } + pub fn testnet() -> Self { + Network { + rpc_url: "https://soroban-testnet.stellar.org:443".to_owned(), + network_passphrase: "Test SDF Network ; September 2015".to_owned(), + archive_url: None, + } + } pub fn futurenet() -> Self { Network { rpc_url: "https://rpc-futurenet.stellar.org:443".to_owned(), network_passphrase: "Test SDF Future Network ; October 2022".to_owned(), + archive_url: None, } } } diff --git a/cmd/soroban-cli/src/commands/snapshot.rs b/cmd/soroban-cli/src/commands/snapshot.rs index 92f77d4f0..69c011db8 100644 --- a/cmd/soroban-cli/src/commands/snapshot.rs +++ b/cmd/soroban-cli/src/commands/snapshot.rs @@ -57,8 +57,8 @@ pub struct Cmd { out: PathBuf, #[command(flatten)] locator: locator::Args, - // #[command(flatten)] - // network: network::Args, + #[command(flatten)] + network: network::Args, } #[derive(thiserror::Error, Debug)] @@ -99,7 +99,7 @@ const CHECKPOINT_FREQUENCY: u32 = 64; impl Cmd { pub async fn run(&self) -> Result<(), Error> { - const BASE_URL: &str = "http://history.stellar.org/prd/core-live/core_live_001"; + let archive_url = self.network.get(&self.locator)?.archive_url()?.to_string(); let start = Instant::now(); @@ -120,9 +120,9 @@ impl Cmd { let ledger_hex_0 = &ledger_hex[0..=1]; let ledger_hex_1 = &ledger_hex[2..=3]; let ledger_hex_2 = &ledger_hex[4..=5]; - format!("{BASE_URL}/history/{ledger_hex_0}/{ledger_hex_1}/{ledger_hex_2}/history-{ledger_hex}.json") + format!("{archive_url}/history/{ledger_hex_0}/{ledger_hex_1}/{ledger_hex_2}/history-{ledger_hex}.json") } else { - format!("{BASE_URL}/.well-known/stellar-history.json") + format!("{archive_url}/.well-known/stellar-history.json") }; let history_url = Uri::from_str(&history_url).unwrap(); @@ -196,7 +196,7 @@ impl Cmd { let bucket_1 = &bucket[2..=3]; let bucket_2 = &bucket[4..=5]; let bucket_url = format!( - "{BASE_URL}/bucket/{bucket_0}/{bucket_1}/{bucket_2}/bucket-{bucket}.xdr.gz" + "{archive_url}/bucket/{bucket_0}/{bucket_1}/{bucket_2}/bucket-{bucket}.xdr.gz" ); print!("🪣 Downloading bucket {i} {bucket}"); let bucket_url = Uri::from_str(&bucket_url).map_err(Error::ParsingBucketUrl)?; diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index ab057a587..c56a96dc5 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -3,25 +3,25 @@ use clap::arg; use soroban_env_host::xdr; use soroban_rpc::Assembled; -use crate::commands::HEADING_RPC; +use crate::commands::HEADING_NETWORK; #[derive(Debug, clap::Args, Clone)] #[group(skip)] pub struct Args { /// fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm - #[arg(long, default_value = "100", env = "STELLAR_FEE", help_heading = HEADING_RPC)] + #[arg(long, default_value = "100", env = "STELLAR_FEE", help_heading = HEADING_NETWORK)] pub fee: u32, /// Output the cost execution to stderr - #[arg(long = "cost", help_heading = HEADING_RPC)] + #[arg(long = "cost", help_heading = HEADING_NETWORK)] pub cost: bool, /// Number of instructions to simulate - #[arg(long, help_heading = HEADING_RPC)] + #[arg(long, help_heading = HEADING_NETWORK)] pub instructions: Option, /// Build the transaction only write the base64 xdr to stdout - #[arg(long, help_heading = HEADING_RPC)] + #[arg(long, help_heading = HEADING_NETWORK)] pub build_only: bool, /// Simulation the transaction only write the base64 xdr to stdout - #[arg(long, help_heading = HEADING_RPC, conflicts_with = "build_only")] + #[arg(long, help_heading = HEADING_NETWORK, conflicts_with = "build_only")] pub sim_only: bool, }