From 396408f3dbede3beb303404d9ca084622a551dc6 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 8 Mar 2023 11:25:23 +0000 Subject: [PATCH] Remove 'soroban serve' subcommand --- Cargo.lock | 263 +-------------- cmd/soroban-cli/Cargo.toml | 1 - cmd/soroban-cli/src/jsonrpc.rs | 68 ---- cmd/soroban-cli/src/main.rs | 7 - cmd/soroban-cli/src/rpc/mod.rs | 5 - cmd/soroban-cli/src/serve.rs | 568 --------------------------------- 6 files changed, 1 insertion(+), 911 deletions(-) delete mode 100644 cmd/soroban-cli/src/jsonrpc.rs delete mode 100644 cmd/soroban-cli/src/serve.rs diff --git a/Cargo.lock b/Cargo.lock index 3dcf2a14c..058cc5f22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,16 +198,6 @@ dependencies = [ "serde", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "bumpalo" version = "3.11.1" @@ -639,15 +629,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - [[package]] name = "futures-channel" version = "0.3.25" @@ -687,7 +668,6 @@ dependencies = [ "futures-task", "pin-project-lite", "pin-utils", - "slab", ] [[package]] @@ -780,31 +760,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "headers" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" -dependencies = [ - "base64 0.13.1", - "bitflags", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - [[package]] name = "heck" version = "0.4.0" @@ -961,16 +916,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "ignore" version = "0.4.19" @@ -1181,22 +1126,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1224,24 +1153,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand 0.8.5", - "safemem", - "tempfile", - "twoway", -] - [[package]] name = "nom" version = "7.1.1" @@ -1402,12 +1313,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - [[package]] name = "pest" version = "2.5.0" @@ -1572,12 +1477,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "1.0.23" @@ -1798,20 +1697,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.2", + "rustls-pemfile", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" -dependencies = [ - "base64 0.13.1", -] - [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -1833,12 +1723,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "same-file" version = "1.0.6" @@ -1857,12 +1741,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.1.0" @@ -1962,18 +1840,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa 1.0.5", - "ryu", - "serde", -] - [[package]] name = "serde_with" version = "2.2.0" @@ -2002,17 +1868,6 @@ dependencies = [ "syn", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.6", -] - [[package]] name = "sha1" version = "0.10.5" @@ -2140,7 +1995,6 @@ dependencies = [ "thiserror", "tokio", "toml", - "warp", "wasm-opt", "wasmparser 0.90.0", ] @@ -2619,29 +2473,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "tokio-stream" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - [[package]] name = "tokio-util" version = "0.7.4" @@ -2678,7 +2509,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2720,34 +2550,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" -[[package]] -name = "tungstenite" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand 0.8.5", - "sha-1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] - [[package]] name = "typenum" version = "1.16.0" @@ -2760,21 +2562,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - [[package]] name = "unicode-ident" version = "1.0.6" @@ -2808,23 +2595,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "version_check" version = "0.9.4" @@ -2861,37 +2631,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "warp" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "headers", - "http", - "hyper", - "log", - "mime", - "mime_guess", - "multipart", - "percent-encoding", - "pin-project", - "rustls-pemfile 0.2.1", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tokio-util", - "tower-service", - "tracing", -] - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 9449cb58a..3adeba1c5 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -34,7 +34,6 @@ num-bigint = "0.4" tokio = { version = "1", features = ["full"] } termcolor = "1.1.3" termcolor_output = "1.0.1" -warp = "0.3" clap_complete = "3.2.3" rand = "0.8.5" wasmparser = "0.90.0" diff --git a/cmd/soroban-cli/src/jsonrpc.rs b/cmd/soroban-cli/src/jsonrpc.rs deleted file mode 100644 index 2174e8c46..000000000 --- a/cmd/soroban-cli/src/jsonrpc.rs +++ /dev/null @@ -1,68 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize, PartialOrd, Ord)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Id { - /// Null - Null, - /// Numeric id - Number(u64), - /// String id - Str(String), -} - -/// JSON-RPC request object as defined in the [spec](https://www.jsonrpc.org/specification#request-object). -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct Request { - /// JSON-RPC version. - pub jsonrpc: String, - /// Request ID - pub id: Option, - /// Name of the method to be invoked. - pub method: String, - /// Parameter values of the request. - pub params: Option, -} - -/// JSON-RPC Response object as defined in the [spec](https://www.jsonrpc.org/specification#request-object). -/// TODO: Figure out a cleaner way to do this. -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Response { - Ok(ResultResponse), - Err(ErrorResponse), -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct ResultResponse { - pub jsonrpc: String, - pub id: Id, - pub result: T, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct ErrorResponse { - pub jsonrpc: String, - pub id: Id, - pub error: ErrorResponseError, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct ErrorResponseError { - pub code: i64, - pub message: String, - pub data: Option, -} diff --git a/cmd/soroban-cli/src/main.rs b/cmd/soroban-cli/src/main.rs index 62ef6acba..050db2e2f 100644 --- a/cmd/soroban-cli/src/main.rs +++ b/cmd/soroban-cli/src/main.rs @@ -4,11 +4,9 @@ mod completion; mod config; mod contract; mod events; -mod jsonrpc; mod lab; mod network; mod rpc; -mod serve; mod strval; mod toid; mod utils; @@ -38,8 +36,6 @@ enum Cmd { /// Read and update config #[clap(subcommand)] Config(config::Cmd), - /// Run a local webserver for web app development and testing - Serve(serve::Cmd), /// Watch the network for contract events Events(events::Cmd), /// Experiment with early features and expert tools @@ -60,8 +56,6 @@ enum CmdError { #[error(transparent)] Events(#[from] events::Error), #[error(transparent)] - Serve(#[from] serve::Error), - #[error(transparent)] Lab(#[from] lab::Error), #[error(transparent)] Config(#[from] config::Error), @@ -71,7 +65,6 @@ async fn run(cmd: Cmd) -> Result<(), CmdError> { match cmd { Cmd::Contract(contract) => contract.run().await?, Cmd::Events(events) => events.run().await?, - Cmd::Serve(serve) => serve.run().await?, Cmd::Lab(lab) => lab.run().await?, Cmd::Version(version) => version.run(), Cmd::Completion(completion) => completion.run(), diff --git a/cmd/soroban-cli/src/rpc/mod.rs b/cmd/soroban-cli/src/rpc/mod.rs index 544f03ff8..d673bccdf 100644 --- a/cmd/soroban-cli/src/rpc/mod.rs +++ b/cmd/soroban-cli/src/rpc/mod.rs @@ -37,7 +37,6 @@ pub enum Error { MissingResult, } -// TODO: this should also be used by serve #[derive(serde::Deserialize, serde::Serialize, Debug)] pub struct SendTransactionResponse { pub hash: String, @@ -60,7 +59,6 @@ pub struct SendTransactionResponse { pub latest_ledger_close_time: u32, } -// TODO: this should also be used by serve #[derive(serde::Deserialize, serde::Serialize, Debug)] pub struct GetTransactionResponse { pub status: String, @@ -81,14 +79,12 @@ pub struct GetTransactionResponse { // TODO: add ledger info and application order } -// TODO: this should also be used by serve #[derive(serde::Deserialize, serde::Serialize, Debug)] pub struct GetLedgerEntryResponse { pub xdr: String, // TODO: add lastModifiedLedgerSeq and latestLedger } -// TODO: this should also be used by serve #[derive(serde::Deserialize, serde::Serialize, Debug)] pub struct Cost { #[serde(rename = "cpuInsns")] @@ -184,7 +180,6 @@ impl Client { headers.insert("X-Client-Name", "soroban-cli".parse().unwrap()); let version = VERSION.unwrap_or("devel"); headers.insert("X-Client-Version", version.parse().unwrap()); - // TODO: We should consider migrating the server subcommand to jsonrpsee Ok(HttpClientBuilder::default() .set_headers(headers) .build(url)?) diff --git a/cmd/soroban-cli/src/serve.rs b/cmd/soroban-cli/src/serve.rs deleted file mode 100644 index 34c95a19f..000000000 --- a/cmd/soroban-cli/src/serve.rs +++ /dev/null @@ -1,568 +0,0 @@ -use std::collections::HashMap; -use std::{convert::Infallible, fmt::Debug, io, net::SocketAddr, path::PathBuf, rc::Rc, sync::Arc}; - -use clap::Parser; -use hex::FromHexError; -use serde_json::{json, Value}; -use sha2::{Digest, Sha256}; -use soroban_env_host::{ - budget::Budget, - storage::Storage, - xdr::{ - self, AccountId, AddressWithNonce, ContractAuth, Error as XdrError, - FeeBumpTransactionInnerTx, HostFunction, LedgerKey, MuxedAccount, Operation, OperationBody, - PublicKey, ReadXdr, ScHostStorageErrorCode, ScObject, ScStatus, ScVal, ScVec, - TransactionEnvelope, WriteXdr, - }, - Host, HostError, -}; -use tokio::sync::Mutex; -use warp::{http::Response, Filter}; - -use crate::network::SANDBOX_NETWORK_PASSPHRASE; -use crate::strval; -use crate::utils::{self, create_ledger_footprint}; -use crate::{jsonrpc, HEADING_SANDBOX}; - -#[derive(Parser, Debug)] -pub struct Cmd { - /// Port to listen for requests on. - #[clap(long, default_value("8000"))] - port: u16, - - /// File to persist ledger state - #[clap( - long, - parse(from_os_str), - default_value(".soroban/ledger.json"), - env = "SOROBAN_LEDGER_FILE", - help_heading = HEADING_SANDBOX, - )] - ledger_file: PathBuf, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("io")] - Io(#[from] io::Error), - #[error("strval")] - StrVal(#[from] strval::Error), - #[error("xdr")] - Xdr(#[from] XdrError), - #[error("host")] - Host(#[from] HostError), - #[error("snapshot")] - Snapshot(#[from] soroban_ledger_snapshot::Error), - #[error("serde")] - Serde(#[from] serde_json::Error), - #[error("unsupported transaction: {message}")] - UnsupportedTransaction { message: String }, - #[error("hex")] - FromHex(#[from] FromHexError), - #[error("unknownmethod")] - UnknownMethod, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -#[serde(untagged)] -enum Requests { - NoArg(), - StringArg(Box<[String]>), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let ledger_file = Arc::new(self.ledger_file.clone()); - let with_ledger_file = warp::any().map(move || ledger_file.clone()); - - // Just track in-flight transactions in-memory for sandbox for now. Simple. - let transaction_status_map: Arc>> = - Arc::new(Mutex::new(HashMap::new())); - let with_transaction_status_map = warp::any().map(move || transaction_status_map.clone()); - - let jsonrpc_route = warp::post() - .and(warp::path!("soroban" / "rpc")) - .and(warp::body::json()) - .and(with_ledger_file) - .and(with_transaction_status_map) - .and_then(handler); - - // Allow access from all remote sites when we are in local sandbox mode. (Always for now) - let cors = warp::cors() - .allow_any_origin() - .allow_credentials(true) - .allow_headers(vec![ - "Accept", - "Access-Control-Request-Headers", - "Access-Control-Request-Method", - "Content-Length", - "Content-Type", - "Encoding", - "Origin", - "Referer", - "Sec-Fetch-Mode", - "User-Agent", - "X-Client-Name", - "X-Client-Version", - ]) - .allow_methods(vec!["GET", "POST"]); - let routes = jsonrpc_route.with(cors); - - let addr: SocketAddr = ([127, 0, 0, 1], self.port).into(); - println!("Listening on: {addr}/soroban/rpc"); - warp::serve(routes).run(addr).await; - Ok(()) - } -} - -async fn handler( - request: jsonrpc::Request, - ledger_file: Arc, - transaction_status_map: Arc>>, -) -> Result { - let resp = Response::builder() - .status(200) - .header("content-type", "application/json; charset=utf-8"); - if request.jsonrpc != "2.0" { - return Ok(resp.body( - json!({ - "jsonrpc": "2.0", - "id": &request.id, - "error": { - "code":-32600, - "message": "Invalid jsonrpc value in request", - }, - }) - .to_string(), - )); - } - let result = match (request.method.as_str(), request.params) { - ("getHealth", Some(Requests::NoArg()) | None) => Ok(get_health()), - ("getLedgerEntry", Some(Requests::StringArg(key))) => get_ledger_entry(key, &ledger_file), - ("getNetwork", Some(Requests::NoArg())) => Ok(get_network()), - ("getTransactionStatus", Some(Requests::StringArg(b))) => { - get_transaction_status(&transaction_status_map, b).await - } - ("simulateTransaction", Some(Requests::StringArg(b))) => { - simulate_transaction(&ledger_file, b) - } - ("sendTransaction", Some(Requests::StringArg(b))) => { - send_transaction(&ledger_file, &transaction_status_map, b).await - } - _ => Err(Error::UnknownMethod), - }; - let r = reply(&request.id, result); - Ok(resp.body(serde_json::to_string(&r).unwrap_or_else(|_| { - json!({ - "jsonrpc": "2.0", - "id": &request.id, - "error": { - "code":-32603, - "message": "Internal server error", - }, - }) - .to_string() - }))) -} - -fn reply( - id: &Option, - result: Result, -) -> jsonrpc::Response { - match result { - Ok(res) => jsonrpc::Response::Ok(jsonrpc::ResultResponse { - jsonrpc: "2.0".to_string(), - id: id.as_ref().unwrap_or(&jsonrpc::Id::Null).clone(), - result: res, - }), - Err(err) => { - eprintln!("err: {err:?}"); - jsonrpc::Response::Err(jsonrpc::ErrorResponse { - jsonrpc: "2.0".to_string(), - id: id.as_ref().unwrap_or(&jsonrpc::Id::Null).clone(), - error: jsonrpc::ErrorResponseError { - code: match err { - Error::Serde(_) => -32700, - Error::UnknownMethod => -32601, - _ => -32603, - }, - message: err.to_string(), - data: None, - }, - }) - } - } -} - -fn get_ledger_entry(k: Box<[String]>, ledger_file: &PathBuf) -> Result { - if let Some(key_xdr) = k.into_vec().first() { - // Initialize storage and host - let state = utils::ledger_snapshot_read_or_default(ledger_file).map_err(Error::Snapshot)?; - let key = LedgerKey::from_xdr_base64(key_xdr)?; - - let snap = Rc::new(state); - let mut storage = Storage::with_recording_footprint(snap); - let ledger_entry = storage.get(&key, &soroban_env_host::budget::Budget::default())?; - - Ok(json!({ - "xdr": ledger_entry.data.to_xdr_base64()?, - "lastModifiedLedgerSeq": ledger_entry.last_modified_ledger_seq, - // TODO: Find "real" ledger seq number here - "latestLedger": 1, - })) - } else { - Err(Error::Xdr(XdrError::Invalid)) - } -} - -fn get_network() -> Value { - json!({ - "passphrase": SANDBOX_NETWORK_PASSPHRASE, - "protocolVersion": 20, - }) -} - -fn parse_transaction( - txn_xdr: &str, - passphrase: &str, -) -> Result<(AccountId, [u8; 32], Vec), Error> { - // Parse and validate the txn - let transaction = TransactionEnvelope::from_xdr_base64(txn_xdr)?; - let hash = hash_transaction_in_envelope(&transaction, passphrase)?; - let ops = match &transaction { - TransactionEnvelope::TxV0(envelope) => &envelope.tx.operations, - TransactionEnvelope::Tx(envelope) => &envelope.tx.operations, - TransactionEnvelope::TxFeeBump(envelope) => { - let FeeBumpTransactionInnerTx::Tx(tx_envelope) = &envelope.tx.inner_tx; - &tx_envelope.tx.operations - } - }; - if ops.len() != 1 { - return Err(Error::UnsupportedTransaction { - message: "Must only have one operation".to_string(), - }); - } - let op = ops.first().ok_or(Error::Xdr(XdrError::Invalid))?; - let source_account = parse_op_source_account(&transaction, op); - let OperationBody::InvokeHostFunction(body) = &op.body else { - return Err(Error::UnsupportedTransaction { - message: "Operation must be invokeHostFunction".to_string(), - }); - }; - - // TODO: Support creating contracts and token wrappers here as well. - let HostFunction::InvokeContract(parameters) = &body.function else { - return Err(Error::UnsupportedTransaction { - message: "Function must be invokeContract".to_string(), - }); - }; - - if parameters.len() < 2 { - return Err(Error::UnsupportedTransaction { - message: "Function must have at least 2 parameters".to_string(), - }); - }; - - let contract_xdr = parameters.get(0).ok_or(Error::UnsupportedTransaction { - message: "First parameter must be the contract id".to_string(), - })?; - let method_xdr = parameters.get(1).ok_or(Error::UnsupportedTransaction { - message: "Second parameter must be the contract method".to_string(), - })?; - let (_, params) = parameters.split_at(2); - - let contract_id: [u8; 32] = if let ScVal::Object(Some(ScObject::Bytes(bytes))) = contract_xdr { - bytes - .as_slice() - .try_into() - .map_err(|_| Error::UnsupportedTransaction { - message: "Could not parse contract id".to_string(), - })? - } else { - return Err(Error::UnsupportedTransaction { - message: "Could not parse contract id".to_string(), - }); - }; - - // TODO: Figure out and enforce the expected type here. For now, handle both a symbol and a - // binary. The cap says binary, but other implementations use symbol. - let method: String = if let ScVal::Object(Some(ScObject::Bytes(bytes))) = method_xdr { - bytes - .try_into() - .map_err(|_| Error::UnsupportedTransaction { - message: "Could not parse contract method".to_string(), - })? - } else if let ScVal::Symbol(bytes) = method_xdr { - bytes - .try_into() - .map_err(|_| Error::UnsupportedTransaction { - message: "Could not parse contract method".to_string(), - })? - } else { - return Err(Error::UnsupportedTransaction { - message: "Could not parse contract method".to_string(), - }); - }; - - let mut complete_args = vec![ - ScVal::Object(Some(ScObject::Bytes(contract_id.try_into()?))), - ScVal::Symbol(method.try_into()?), - ]; - complete_args.extend_from_slice(params); - - Ok((source_account, hash, complete_args)) -} - -fn execute_transaction( - source_account: AccountId, - args: &Vec, - ledger_file: &PathBuf, - commit: bool, -) -> Result { - // Initialize storage and host - let mut state = utils::ledger_snapshot_read_or_default(ledger_file).map_err(Error::Snapshot)?; - - let snap = Rc::new(state.clone()); - let storage = Storage::with_recording_footprint(snap); - let h = Host::with_storage_and_budget(storage, Budget::default()); - - h.switch_to_recording_auth(); - h.set_source_account(source_account); - - let mut ledger_info = state.ledger_info(); - ledger_info.sequence_number += 1; - ledger_info.timestamp += 5; - let latest_sequence = ledger_info.sequence_number; - h.set_ledger_info(ledger_info); - - // TODO: Check the parameters match the contract spec, or return a helpful error message - - // TODO: Handle installing code and creating contracts here as well - let res = h.invoke_function(HostFunction::InvokeContract(args.try_into()?))?; - - state.update(&h); - - let contract_auth: Vec = h - .get_recorded_auth_payloads()? - .into_iter() - .map(|payload| { - let address_with_nonce = match (payload.address, payload.nonce) { - (Some(address), Some(nonce)) => Some(AddressWithNonce { address, nonce }), - _ => None, - }; - ContractAuth { - address_with_nonce, - root_invocation: payload.invocation, - signature_args: ScVec::default(), - } - .to_xdr_base64() - }) - .collect::, _>>()?; - - let (storage, budget, _) = h.try_finish().map_err(|_h| { - HostError::from(ScStatus::HostStorageError( - ScHostStorageErrorCode::UnknownError, - )) - })?; - - // Calculate the budget usage - let mut cost = serde_json::Map::new(); - cost.insert( - "cpuInsns".to_string(), - Value::String(budget.get_cpu_insns_count().to_string()), - ); - cost.insert( - "memBytes".to_string(), - Value::String(budget.get_mem_bytes_count().to_string()), - ); - // TODO: Include these extra costs. Figure out the rust type conversions. - // for cost_type in CostType::variants() { - // m.insert(cost_type, b.get_input(*cost_type)); - // } - - // Calculate the storage footprint - let footprint = create_ledger_footprint(&storage.footprint); - - if commit { - state.write_file(ledger_file).map_err(Error::Snapshot)?; - } - - Ok(json!({ - "cost": cost, - "results": [{ - "auth": contract_auth, - "footprint": footprint.to_xdr_base64()?, - "xdr": res.to_xdr_base64()?, - }], - "latestLedger": latest_sequence, - })) -} - -fn hash_transaction_in_envelope( - envelope: &TransactionEnvelope, - passphrase: &str, -) -> Result<[u8; 32], Error> { - let tagged_transaction = match envelope { - TransactionEnvelope::TxV0(envelope) => { - xdr::TransactionSignaturePayloadTaggedTransaction::Tx(xdr::Transaction { - source_account: xdr::MuxedAccount::Ed25519( - envelope.tx.source_account_ed25519.clone(), - ), - fee: envelope.tx.fee, - seq_num: envelope.tx.seq_num.clone(), - cond: match envelope.tx.time_bounds.clone() { - None => xdr::Preconditions::None, - Some(time_bounds) => xdr::Preconditions::Time(time_bounds), - }, - memo: envelope.tx.memo.clone(), - operations: envelope.tx.operations.clone(), - ext: xdr::TransactionExt::V0, - }) - } - TransactionEnvelope::Tx(envelope) => { - xdr::TransactionSignaturePayloadTaggedTransaction::Tx(envelope.tx.clone()) - } - TransactionEnvelope::TxFeeBump(envelope) => { - xdr::TransactionSignaturePayloadTaggedTransaction::TxFeeBump(envelope.tx.clone()) - } - }; - - // trim spaces from passphrase - // Check if network passpharse is empty - - let network_id = xdr::Hash(hash_bytes(passphrase.as_bytes().to_vec())); - let payload = xdr::TransactionSignaturePayload { - network_id, - tagged_transaction, - }; - let tx_bytes = payload.to_xdr()?; - - // hash it - Ok(hash_bytes(tx_bytes)) -} - -fn hash_bytes(b: Vec) -> [u8; 32] { - let mut output: [u8; 32] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ]; - let mut hasher = Sha256::new(); - hasher.update(b); - output.copy_from_slice(&hasher.finalize()); - output -} - -fn get_health() -> Value { - json!({ - "status": "healthy", - }) -} - -async fn get_transaction_status( - transaction_status_map: &Mutex>, - b: Box<[String]>, -) -> Result { - if let Some(hash) = b.into_vec().first() { - let m = transaction_status_map.lock().await; - let status = m.get(hash); - Ok(match status { - Some(status) => status.clone(), - None => json!({ - "error": { - "code":404, - "message": "Transaction not found", - }, - }), - }) - } else { - Err(Error::Xdr(XdrError::Invalid)) - } -} - -fn simulate_transaction(ledger_file: &PathBuf, b: Box<[String]>) -> Result { - if let Some(txn_xdr) = b.into_vec().first() { - parse_transaction(txn_xdr, SANDBOX_NETWORK_PASSPHRASE) - // Execute and do NOT commit - .and_then(|(source_account, _, args)| { - execute_transaction(source_account, &args, ledger_file, false) - }) - } else { - Err(Error::Xdr(XdrError::Invalid)) - } -} - -async fn send_transaction( - ledger_file: &PathBuf, - transaction_status_map: &Mutex>, - b: Box<[String]>, -) -> Result { - if let Some(txn_xdr) = b.into_vec().first() { - // TODO: Format error object output if txn is invalid - let mut m = transaction_status_map.lock().await; - parse_transaction(txn_xdr, SANDBOX_NETWORK_PASSPHRASE).map( - |(source_account, hash, args)| { - let id = hex::encode(hash); - // Execute and commit - let result = execute_transaction(source_account, &args, ledger_file, true); - // Add it to our status tracker - m.insert( - id.clone(), - match result { - Ok(result) => { - if let Value::Object(mut o) = result { - o.insert("id".to_string(), Value::String(id.to_string())); - o.insert( - "status".to_string(), - Value::String("success".to_string()), - ); - Value::Object(o) - } else { - panic!("Expected object"); - } - } - Err(err) => { - eprintln!("error: {err:?}"); - json!({ - "id": id, - "status": "error", - "error": { - "code":-32603, - "message": "Internal server error", - }, - }) - } - }, - ); - // Return the hash - json!({ "id": id, "status": "pending" }) - }, - ) - } else { - Err(Error::Xdr(XdrError::Invalid)) - } -} - -fn parse_op_source_account(transaction: &TransactionEnvelope, op: &Operation) -> AccountId { - if let Some(source_account) = &op.source_account { - parse_muxed_account(source_account) - } else { - match transaction { - TransactionEnvelope::TxV0(envelope) => AccountId(PublicKey::PublicKeyTypeEd25519( - envelope.tx.source_account_ed25519.clone(), - )), - TransactionEnvelope::Tx(envelope) => parse_muxed_account(&envelope.tx.source_account), - TransactionEnvelope::TxFeeBump(envelope) => { - let FeeBumpTransactionInnerTx::Tx(tx_envelope) = &envelope.tx.inner_tx; - parse_muxed_account(&tx_envelope.tx.source_account) - } - } - } -} - -fn parse_muxed_account(muxed_account: &MuxedAccount) -> AccountId { - AccountId(PublicKey::PublicKeyTypeEd25519(match muxed_account { - xdr::MuxedAccount::Ed25519(a) => a.clone(), - xdr::MuxedAccount::MuxedEd25519(a) => a.ed25519.clone(), - })) -}