diff --git a/Cargo.lock b/Cargo.lock index 4cc4220dd9..c202f710a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,7 +1943,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap 2.1.0", "slab", "tokio", @@ -2040,6 +2040,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -2047,7 +2058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.11", "pin-project-lite", ] @@ -2074,7 +2085,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.11", "http-body", "httparse", "httpdate", @@ -2094,7 +2105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.11", "hyper", "log", "rustls", @@ -3050,7 +3061,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.11", "http-body", "hyper", "hyper-rustls", @@ -3562,7 +3573,7 @@ dependencies = [ "gix", "heck", "hex", - "http", + "http 0.2.11", "hyper", "hyper-tls", "itertools 0.10.5", @@ -3584,6 +3595,7 @@ dependencies = [ "shlex", "soroban-env-host", "soroban-ledger-snapshot", + "soroban-rpc", "soroban-sdk", "soroban-spec", "soroban-spec-json", @@ -3691,6 +3703,38 @@ dependencies = [ "thiserror", ] +[[package]] +name = "soroban-rpc" +version = "20.2.0" +dependencies = [ + "base64 0.21.5", + "clap", + "ed25519-dalek 2.0.0", + "ethnum", + "hex", + "http 1.0.0", + "itertools 0.10.5", + "jsonrpsee-core", + "jsonrpsee-http-client", + "serde", + "serde-aux", + "serde_json", + "sha2 0.10.8", + "soroban-env-host", + "soroban-sdk", + "soroban-spec", + "soroban-spec-tools", + "stellar-strkey 0.0.7", + "stellar-xdr", + "termcolor", + "termcolor_output", + "thiserror", + "tokio", + "tracing", + "wasmparser 0.90.0", + "which", +] + [[package]] name = "soroban-sdk" version = "20.1.0" diff --git a/Cargo.toml b/Cargo.toml index df5780093f..11a74c9ef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,10 @@ rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" version = "20.2.0" path = "cmd/soroban-cli" +[workspace.dependencies.soroban-rpc] +version = "20.2.0" +path = "cmd/crates/soroban-rpc" + [workspace.dependencies.stellar-xdr] version = "=20.0.2" default-features = true diff --git a/cmd/crates/soroban-rpc/Cargo.toml b/cmd/crates/soroban-rpc/Cargo.toml new file mode 100644 index 0000000000..38b5ce3a47 --- /dev/null +++ b/cmd/crates/soroban-rpc/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "soroban-rpc" +description = "Soroban RPC client for rust" +homepage = "https://github.com/stellar/soroban-tools" +repository = "https://github.com/stellar/soroban-tools" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +readme = "README.md" +version.workspace = true +edition = "2021" +rust-version = "1.70" +autobins = false + + +[lib] +crate-type = ["rlib"] + + +[dependencies] +soroban-sdk = { workspace = true } +soroban-spec-tools = { workspace = true } + +soroban-env-host = { workspace = true } +stellar-strkey = { workspace = true } +stellar-xdr = { workspace = true, features = ["curr", "std", "serde"] } +soroban-spec = { workspace = true } + + +termcolor = { workspace = true } +termcolor_output = { workspace = true } +clap = { workspace = true } + +serde_json = { workspace = true } +serde-aux = { workspace = true } +itertools = { workspace = true } +ethnum = { workspace = true } +hex = { workspace = true } +wasmparser = { workspace = true } +base64 = { workspace = true } +thiserror = { workspace = true } +serde = { workspace = true } +tokio = { workspace = true } +sha2 = { workspace = true } +ed25519-dalek = { workspace = true } +tracing = { workspace = true } + + +# networking +jsonrpsee-http-client = { workspace = true } +jsonrpsee-core = { workspace = true } +http = { workspace = true } + +# soroban-ledger-snapshot = { workspace = true } +# soroban-sdk = { workspace = true } +# sep5 = { workspace = true } + + +[dev-dependencies] +which = { workspace = true } diff --git a/cmd/crates/soroban-rpc/README.md b/cmd/crates/soroban-rpc/README.md new file mode 100644 index 0000000000..9185b7fd05 --- /dev/null +++ b/cmd/crates/soroban-rpc/README.md @@ -0,0 +1,3 @@ +# soroban-rpc + +Tools and utilities for soroban rpc. diff --git a/cmd/soroban-cli/src/rpc/fixtures/event_response.json b/cmd/crates/soroban-rpc/src/fixtures/event_response.json similarity index 100% rename from cmd/soroban-cli/src/rpc/fixtures/event_response.json rename to cmd/crates/soroban-rpc/src/fixtures/event_response.json diff --git a/cmd/soroban-cli/src/rpc/mod.rs b/cmd/crates/soroban-rpc/src/lib.rs similarity index 97% rename from cmd/soroban-cli/src/rpc/mod.rs rename to cmd/crates/soroban-rpc/src/lib.rs index 9a3974083d..9dac0247d5 100644 --- a/cmd/soroban-cli/src/rpc/mod.rs +++ b/cmd/crates/soroban-rpc/src/lib.rs @@ -31,9 +31,8 @@ mod txn; pub use txn::*; -use soroban_spec_tools::contract::Spec as Contract; +use soroban_spec_tools::contract; -use crate::utils::contract_spec as contract; const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); pub type LogEvents = fn( @@ -179,6 +178,8 @@ impl TryInto for GetTransactionResponseRaw { } impl GetTransactionResponse { + /// + /// # Errors pub fn return_value(&self) -> Result { if let Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 { soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }), @@ -191,6 +192,8 @@ impl GetTransactionResponse { } } + /// + /// # Errors pub fn events(&self) -> Result, Error> { if let Some(meta) = self.result_meta.as_ref() { Ok(extract_events(meta)) @@ -199,6 +202,8 @@ impl GetTransactionResponse { } } + /// + /// # Errors pub fn contract_events(&self) -> Result, Error> { Ok(self .events()? @@ -311,6 +316,8 @@ pub struct SimulateTransactionResponse { } impl SimulateTransactionResponse { + /// + /// # Errors pub fn results(&self) -> Result, Error> { self.results .iter() @@ -332,6 +339,8 @@ impl SimulateTransactionResponse { .collect() } + /// + /// # Errors pub fn events(&self) -> Result, Error> { self.events .iter() @@ -339,6 +348,8 @@ impl SimulateTransactionResponse { .collect() } + /// + /// # Errors pub fn transaction_data(&self) -> Result { Ok(SorobanTransactionData::from_xdr_base64( &self.transaction_data, @@ -435,10 +446,13 @@ impl Display for Event { } impl Event { + /// + /// # Errors pub fn parse_cursor(&self) -> Result<(u64, i32), Error> { parse_cursor(&self.id) } - + /// + /// # Errors pub fn pretty_print(&self) -> Result<(), Box> { let mut stdout = StandardStream::stdout(ColorChoice::Auto); if !stdout.supports_color() { @@ -540,6 +554,8 @@ pub struct Client { } impl Client { + /// + /// # Errors pub fn new(base_url: &str) -> Result { // Add the port to the base URL if there is no port explicitly included // in the URL and the scheme allows us to infer a default port. @@ -570,6 +586,8 @@ impl Client { }) } + /// + /// # Errors fn client(&self) -> Result { let url = self.base_url.clone(); let mut headers = HeaderMap::new(); @@ -581,6 +599,8 @@ impl Client { .build(url)?) } + /// + /// # Errors pub async fn friendbot_url(&self) -> Result { let network = self.get_network().await?; tracing::trace!("{network:#?}"); @@ -591,7 +611,8 @@ impl Client { ) }) } - + /// + /// # Errors pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result { let server = self.get_network().await?.passphrase; if let Some(expected) = expected { @@ -605,11 +626,15 @@ impl Client { Ok(server) } + /// + /// # Errors pub async fn get_network(&self) -> Result { tracing::trace!("Getting network"); Ok(self.client()?.request("getNetwork", rpc_params![]).await?) } + /// + /// # Errors pub async fn get_latest_ledger(&self) -> Result { tracing::trace!("Getting latest ledger"); Ok(self @@ -618,6 +643,8 @@ impl Client { .await?) } + /// + /// # Errors pub async fn get_account(&self, address: &str) -> Result { tracing::trace!("Getting address {}", address); let key = LedgerKey::Account(LedgerKeyAccount { @@ -649,6 +676,8 @@ soroban config identity fund {address} --helper-url "# } } + /// + /// # Errors pub async fn send_transaction( &self, tx: &TransactionEnvelope, @@ -719,6 +748,8 @@ soroban config identity fund {address} --helper-url "# } } + /// + /// # Errors pub async fn simulate_transaction( &self, tx: &TransactionEnvelope, @@ -741,6 +772,8 @@ soroban config identity fund {address} --helper-url "# } } + /// + /// # Errors pub async fn send_assembled_transaction( &self, txn: txn::Assembled, @@ -762,6 +795,8 @@ soroban config identity fund {address} --helper-url "# self.send_transaction(&tx).await } + /// + /// # Errors pub async fn prepare_and_send_transaction( &self, tx_without_preflight: &Transaction, @@ -783,6 +818,8 @@ soroban config identity fund {address} --helper-url "# .await } + /// + /// # Errors pub async fn create_assembled_transaction( &self, txn: &Transaction, @@ -790,6 +827,8 @@ soroban config identity fund {address} --helper-url "# txn::Assembled::new(txn, self).await } + /// + /// # Errors pub async fn get_transaction(&self, tx_id: &str) -> Result { Ok(self .client()? @@ -797,6 +836,8 @@ soroban config identity fund {address} --helper-url "# .await?) } + /// + /// # Errors pub async fn get_ledger_entries( &self, keys: &[LedgerKey], @@ -807,7 +848,7 @@ soroban config identity fund {address} --helper-url "# if base64_result.is_err() { return Err(Error::Xdr(XdrError::Invalid)); } - base64_keys.push(k.to_xdr_base64(Limits::none()).unwrap()); + base64_keys.push(k.to_xdr_base64(Limits::none())?); } Ok(self .client()? @@ -815,6 +856,8 @@ soroban config identity fund {address} --helper-url "# .await?) } + /// + /// # Errors pub async fn get_full_ledger_entries( &self, ledger_keys: &[LedgerKey], @@ -855,7 +898,8 @@ soroban config identity fund {address} --helper-url "# latest_ledger, }) } - + /// + /// # Errors pub async fn get_events( &self, start: EventStart, @@ -895,6 +939,8 @@ soroban config identity fund {address} --helper-url "# Ok(self.client()?.request("getEvents", oparams).await?) } + /// + /// # Errors pub async fn get_contract_data( &self, contract_id: &[u8; 32], @@ -918,6 +964,8 @@ soroban config identity fund {address} --helper-url "# } } + /// + /// # Errors pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result, Error> { match self.get_contract_data(contract_id).await? { xdr::ContractDataEntry { @@ -932,6 +980,8 @@ soroban config identity fund {address} --helper-url "# } } + /// + /// # Errors pub async fn get_remote_wasm_from_hash(&self, hash: xdr::Hash) -> Result, Error> { let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); let contract_data = self.get_ledger_entries(&[code_key]).await?; @@ -948,7 +998,8 @@ soroban config identity fund {address} --helper-url "# scval => Err(Error::UnexpectedContractCodeDataType(scval)), } } - + /// + /// # Errors pub async fn get_remote_contract_spec( &self, contract_id: &[u8; 32], @@ -958,9 +1009,11 @@ soroban config identity fund {address} --helper-url "# xdr::ScVal::ContractInstance(xdr::ScContractInstance { executable: xdr::ContractExecutable::Wasm(hash), .. - }) => Ok(Contract::new(&self.get_remote_wasm_from_hash(hash).await?) - .map_err(Error::CouldNotParseContractSpec)? - .spec), + }) => Ok( + contract::Spec::new(&self.get_remote_wasm_from_hash(hash).await?) + .map_err(Error::CouldNotParseContractSpec)? + .spec, + ), xdr::ScVal::ContractInstance(xdr::ScContractInstance { executable: xdr::ContractExecutable::StellarAsset, .. @@ -997,7 +1050,7 @@ fn extract_events(tx_meta: &TransactionMeta) -> Vec { } } -pub fn parse_cursor(c: &str) -> Result<(u64, i32), Error> { +pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> { let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?; let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?; let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?; diff --git a/cmd/soroban-cli/src/rpc/log.rs b/cmd/crates/soroban-rpc/src/log.rs similarity index 100% rename from cmd/soroban-cli/src/rpc/log.rs rename to cmd/crates/soroban-rpc/src/log.rs diff --git a/cmd/soroban-cli/src/rpc/log/diagnostic_events.rs b/cmd/crates/soroban-rpc/src/log/diagnostic_events.rs similarity index 100% rename from cmd/soroban-cli/src/rpc/log/diagnostic_events.rs rename to cmd/crates/soroban-rpc/src/log/diagnostic_events.rs diff --git a/cmd/soroban-cli/src/rpc/txn.rs b/cmd/crates/soroban-rpc/src/txn.rs similarity index 92% rename from cmd/soroban-cli/src/rpc/txn.rs rename to cmd/crates/soroban-rpc/src/txn.rs index b79755bc2d..1d3d482855 100644 --- a/cmd/soroban-cli/src/rpc/txn.rs +++ b/cmd/crates/soroban-rpc/src/txn.rs @@ -20,13 +20,35 @@ pub struct Assembled { sim_res: SimulateTransactionResponse, } +/// Represents an assembled transaction ready to be signed and submitted to the network. impl Assembled { + /// + /// Creates a new `Assembled` transaction. + /// + /// # Arguments + /// + /// * `txn` - The original transaction. + /// * `client` - The client used for simulation and submission. + /// + /// # Errors + /// + /// Returns an error if simulation fails or if assembling the transaction fails. pub async fn new(txn: &Transaction, client: &Client) -> Result { let sim_res = Self::simulate(txn, client).await?; let txn = assemble(txn, &sim_res)?; Ok(Self { txn, sim_res }) } + /// + /// Calculates the hash of the assembled transaction. + /// + /// # Arguments + /// + /// * `network_passphrase` - The network passphrase. + /// + /// # Errors + /// + /// Returns an error if generating the hash fails. pub fn hash(&self, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { let signature_payload = TransactionSignaturePayload { network_id: Hash(Sha256::digest(network_passphrase).into()), @@ -35,6 +57,17 @@ impl Assembled { Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) } + /// + /// Signs the assembled transaction. + /// + /// # Arguments + /// + /// * `key` - The signing key. + /// * `network_passphrase` - The network passphrase. + /// + /// # Errors + /// + /// Returns an error if signing the transaction fails. pub fn sign( self, key: &ed25519_dalek::SigningKey, @@ -55,6 +88,17 @@ impl Assembled { })) } + /// + /// Simulates the assembled transaction. + /// + /// # Arguments + /// + /// * `tx` - The original transaction. + /// * `client` - The client used for simulation. + /// + /// # Errors + /// + /// Returns an error if simulation fails. pub async fn simulate( tx: &Transaction, client: &Client, @@ -67,6 +111,18 @@ impl Assembled { .await } + /// + /// Handles the restore process for the assembled transaction. + /// + /// # Arguments + /// + /// * `client` - The client used for submission. + /// * `source_key` - The signing key of the source account. + /// * `network_passphrase` - The network passphrase. + /// + /// # Errors + /// + /// Returns an error if the restore process fails. pub async fn handle_restore( self, client: &Client, @@ -88,14 +144,18 @@ impl Assembled { } } + /// Returns a reference to the original transaction. pub fn txn(&self) -> &Transaction { &self.txn } + /// Returns a reference to the simulation response. pub fn sim_res(&self) -> &SimulateTransactionResponse { &self.sim_res } + /// + /// # Errors pub async fn authorize( self, client: &Client, @@ -123,6 +183,8 @@ impl Assembled { self } + /// + /// # Errors pub fn auth(&self) -> VecM { self.txn .operations @@ -138,6 +200,8 @@ impl Assembled { .unwrap_or_default() } + /// + /// # Errors pub fn log( &self, log_events: Option, @@ -199,6 +263,8 @@ impl Assembled { // Apply the result of a simulateTransaction onto a transaction envelope, preparing it for // submission to the network. +/// +/// # Errors pub fn assemble( raw: &Transaction, simulation: &SimulateTransactionResponse, @@ -422,6 +488,8 @@ fn sign_soroban_authorization_entry( Ok(auth) } +/// +/// # Errors pub fn restore(parent: &Transaction, restore: &RestorePreamble) -> Result { let transaction_data = SorobanTransactionData::from_xdr_base64(&restore.transaction_data, Limits::none())?; @@ -442,8 +510,7 @@ pub fn restore(parent: &Transaction, restore: &RestorePreamble) -> Result(s: &str) -> Result