diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 684821f5e..8bdc350cc 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -19,4 +19,4 @@ jobs: port: ${{ secrets.SSH_PROD_PORT }} command_timeout: 60m script: | - curl -X PUT http://localhost:8000/monitor/build-hyperdrive + ~/hosting_backend/smd/backend/production/smd_scripts/call-build-hyperdrive.sh diff --git a/.github/workflows/release_candidate.yml b/.github/workflows/release_candidate.yml index bd66c8815..5936cecd4 100644 --- a/.github/workflows/release_candidate.yml +++ b/.github/workflows/release_candidate.yml @@ -19,4 +19,4 @@ jobs: port: ${{ secrets.SSH_PORT }} command_timeout: 60m script: | - curl -X PUT http://localhost:8000/monitor/build-hyperdrive + ~/hosting_backend/smd/backend/staging/smd_scripts/call-build-hyperdrive.sh diff --git a/Cargo.toml b/Cargo.toml index 96dac9a66..b75022824 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,11 @@ members = [ "hyperdrive/packages/hypermap-cacher/hypermap-cacher", "hyperdrive/packages/hypermap-cacher/reset-cache", "hyperdrive/packages/hypermap-cacher/set-nodes", "hyperdrive/packages/hypermap-cacher/start-providing", "hyperdrive/packages/hypermap-cacher/stop-providing", "hyperdrive/packages/sign/sign", - "hyperdrive/packages/terminal/terminal", - "hyperdrive/packages/terminal/alias", "hyperdrive/packages/terminal/cat", "hyperdrive/packages/terminal/echo", + "hyperdrive/packages/terminal/terminal", "hyperdrive/packages/terminal/add-node-provider", "hyperdrive/packages/terminal/add-rpcurl-provider", + "hyperdrive/packages/terminal/alias", "hyperdrive/packages/terminal/cat", "hyperdrive/packages/terminal/echo", "hyperdrive/packages/terminal/get-providers", "hyperdrive/packages/terminal/help", "hyperdrive/packages/terminal/hfetch", "hyperdrive/packages/terminal/hi", "hyperdrive/packages/terminal/kill", "hyperdrive/packages/terminal/m", "hyperdrive/packages/terminal/top", - "hyperdrive/packages/terminal/net-diagnostics", "hyperdrive/packages/terminal/peer", "hyperdrive/packages/terminal/peers", + "hyperdrive/packages/terminal/net-diagnostics", "hyperdrive/packages/terminal/peer", "hyperdrive/packages/terminal/peers", "hyperdrive/packages/terminal/remove-provider", "hyperdrive/packages/tester/tester", "scripts/build-packages", ] diff --git a/hyperdrive/packages/terminal/Cargo.lock b/hyperdrive/packages/terminal/Cargo.lock index 4b12f80f2..538883c0c 100644 --- a/hyperdrive/packages/terminal/Cargo.lock +++ b/hyperdrive/packages/terminal/Cargo.lock @@ -2,6 +2,28 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "add-node-provider" +version = "0.1.0" +dependencies = [ + "hyperware_process_lib", + "rmp-serde", + "serde", + "serde_json", + "wit-bindgen", +] + +[[package]] +name = "add-rpcurl-provider" +version = "0.1.0" +dependencies = [ + "hyperware_process_lib", + "rmp-serde", + "serde", + "serde_json", + "wit-bindgen", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -1431,6 +1453,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "get-providers" +version = "0.1.0" +dependencies = [ + "hyperware_process_lib", + "rmp-serde", + "serde", + "serde_json", + "wit-bindgen", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -2541,6 +2574,17 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "remove-provider" +version = "0.1.0" +dependencies = [ + "hyperware_process_lib", + "rmp-serde", + "serde", + "serde_json", + "wit-bindgen", +] + [[package]] name = "reqwest" version = "0.12.9" @@ -2851,9 +2895,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", diff --git a/hyperdrive/packages/terminal/Cargo.toml b/hyperdrive/packages/terminal/Cargo.toml index c181a15b3..7a6d7f5c5 100644 --- a/hyperdrive/packages/terminal/Cargo.toml +++ b/hyperdrive/packages/terminal/Cargo.toml @@ -1,9 +1,12 @@ [workspace] resolver = "2" members = [ + "add-node-provider", + "add-rpcurl-provider", "alias", "cat", "echo", + "get-providers", "help", "hfetch", "hi", @@ -12,6 +15,7 @@ members = [ "net-diagnostics", "peer", "peers", + "remove-provider", "terminal", "top", ] diff --git a/hyperdrive/packages/terminal/add-node-provider/Cargo.toml b/hyperdrive/packages/terminal/add-node-provider/Cargo.toml new file mode 100644 index 000000000..2fa86f0a2 --- /dev/null +++ b/hyperdrive/packages/terminal/add-node-provider/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "add-node-provider" +version = "0.1.0" +edition = "2021" + +[features] +simulation-mode = [] + +[dependencies] +hyperware_process_lib = "2.0.0" +rmp-serde = "1.1.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.140" +wit-bindgen = "0.42.1" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "hyperware:process" \ No newline at end of file diff --git a/hyperdrive/packages/terminal/add-node-provider/src/lib.rs b/hyperdrive/packages/terminal/add-node-provider/src/lib.rs new file mode 100644 index 000000000..02049dd16 --- /dev/null +++ b/hyperdrive/packages/terminal/add-node-provider/src/lib.rs @@ -0,0 +1,115 @@ +// add-node-provider/lib.rs +use hyperware_process_lib::{script, Address, Message, Request}; +use serde_json::{json, Value}; +use std::collections::HashMap; + +wit_bindgen::generate!({ + path: "../target/wit", + world: "process-v1", +}); + +script!(init); +fn init(_our: Address, args: String) -> String { + // Parse arguments: [--trusted ] + let parts: Vec<&str> = args.trim().split_whitespace().collect(); + + if parts.len() < 5 { + return "Usage: add-node-provider [--trusted ]\n Examples:\n add-node-provider 8453 other-node.hypr abc123pubkey 192.168.1.1 9000 (defaults to trusted=false)\n add-node-provider 1 other-node.hypr abc123pubkey 192.168.1.1 9000 --trusted true".to_string(); + } + + let chain_id = match parts[0].parse::() { + Ok(id) => id, + Err(_) => return format!("Invalid chain ID: {}. Must be a number.", parts[0]), + }; + + let node_name = parts[1]; + let public_key = parts[2]; + let ip_address = parts[3]; + let ws_port = match parts[4].parse::() { + Ok(port) => port, + Err(_) => return format!("Invalid WebSocket port: {}. Must be a number.", parts[4]), + }; + + // Parse trusted flag (default to false for node providers) + let trusted = parse_flag_bool(&parts[5..], "--trusted", false); + + // Create ports map with WebSocket port + let mut ports = HashMap::new(); + ports.insert("ws".to_string(), ws_port); + + // Create the HNS update object + let hns_update = json!({ + "name": node_name, + "public_key": public_key, + "ips": [ip_address], + "ports": ports, + "routers": [] + }); + + // Create the provider configuration + let provider_config = json!({ + "chain_id": chain_id, + "provider": { + "Node": { + "hns_update": hns_update, + "use_as_provider": true + } + }, + "trusted": trusted + }); + + // Create AddProvider request + let request_body = json!({ + "AddProvider": provider_config + }); + + let Ok(Ok(Message::Response { body, .. })) = Request::to(("our", "eth", "distro", "sys")) + .body(serde_json::to_vec(&request_body).unwrap()) + .send_and_await_response(60) + else { + return "Failed to communicate with eth module".to_string(); + }; + + // Parse the response + if let Ok(json_value) = serde_json::from_slice::(&body) { + if let Some(response) = json_value.as_str() { + match response { + "Ok" => { + format!( + "Successfully added node provider: {} ({}:{}) on chain {} with trusted={}", + node_name, ip_address, ws_port, chain_id, trusted + ) + } + "PermissionDenied" => "Permission denied: insufficient privileges".to_string(), + other => format!("Error: {}", other), + } + } else { + // Handle any other response types with better formatting + format!( + "Unexpected response: {}", + serde_json::to_string_pretty(&json_value) + .unwrap_or_else(|_| "Failed to format response".to_string()) + ) + } + } else { + format!( + "Failed to parse response as JSON\nRaw response: {}", + String::from_utf8_lossy(&body) + ) + } +} +fn parse_flag_bool(args: &[&str], flag: &str, default: bool) -> bool { + let mut i = 0; + while i < args.len() { + if args[i] == flag { + if i + 1 < args.len() { + if let Ok(value) = args[i + 1].parse::() { + return value; + } + } + break; + } + i += 1; + } + default +} diff --git a/hyperdrive/packages/terminal/add-rpcurl-provider/Cargo.toml b/hyperdrive/packages/terminal/add-rpcurl-provider/Cargo.toml new file mode 100644 index 000000000..c05778fff --- /dev/null +++ b/hyperdrive/packages/terminal/add-rpcurl-provider/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "add-rpcurl-provider" +version = "0.1.0" +edition = "2021" + +[features] +simulation-mode = [] + +[dependencies] +hyperware_process_lib = "2.0.0" +rmp-serde = "1.1.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.140" +wit-bindgen = "0.42.1" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "hyperware:process" \ No newline at end of file diff --git a/hyperdrive/packages/terminal/add-rpcurl-provider/src/lib.rs b/hyperdrive/packages/terminal/add-rpcurl-provider/src/lib.rs new file mode 100644 index 000000000..4883cae67 --- /dev/null +++ b/hyperdrive/packages/terminal/add-rpcurl-provider/src/lib.rs @@ -0,0 +1,188 @@ +use hyperware_process_lib::{script, Address, Message, Request}; +use serde_json::{json, Value}; + +wit_bindgen::generate!({ + path: "../target/wit", + world: "process-v1", +}); + +script!(init); +fn init(_our: Address, args: String) -> String { + // Parse arguments: [--chain-id ] [--trusted ] [--auth-type --auth-value ] + let parts: Vec<&str> = args.trim().split_whitespace().collect(); + + if parts.is_empty() { + return "Usage: add-rpcurl-provider [--chain-id ] [--trusted ] [--auth-type --auth-value ]\n Examples:\n add-rpcurl-provider wss://base-mainnet.infura.io/v3/your-key (defaults to chain-id=8453, trusted=true)\n add-rpcurl-provider wss://mainnet.infura.io/v3/your-key --chain-id 1\n add-rpcurl-provider wss://base-mainnet.infura.io/ws/v3/your-key --trusted false\n add-rpcurl-provider wss://base-mainnet.infura.io/ws/v3/your-key --auth-type bearer --auth-value your-token\n add-rpcurl-provider wss://rpc.example.com --chain-id 1 --trusted true --auth-type basic --auth-value username:password".to_string(); + } + + let provider_str = parts[0]; + + // Validate URL format + if !provider_str.starts_with("http://") + && !provider_str.starts_with("https://") + && !provider_str.starts_with("wss://") + && !provider_str.starts_with("ws://") + { + return "Error: URL must start with http://, https://, ws://, or wss://".to_string(); + } + + // Parse optional flags + let chain_id = parse_flag_value(&parts[1..], "--chain-id", 8453); + let trusted = parse_flag_bool(&parts[1..], "--trusted", true); + let auth = parse_auth_options(&parts[1..]); + + match auth { + Ok(auth_option) => { + // Check if auth is configured before consuming the value + let has_auth = auth_option.is_some(); + + // This is an RPC URL + let mut rpc_config = json!({ + "url": provider_str + }); + + // Only include auth field if authentication is provided + if let Some(auth_value) = auth_option { + rpc_config + .as_object_mut() + .unwrap() + .insert("auth".to_string(), auth_value); + } + + let provider_config = json!({ + "chain_id": chain_id, + "provider": { + "RpcUrl": rpc_config + }, + "trusted": trusted + }); + + // Create AddProvider request + let request_body = json!({ + "AddProvider": provider_config + }); + + let Ok(Ok(Message::Response { body, .. })) = + Request::to(("our", "eth", "distro", "sys")) + .body(serde_json::to_vec(&request_body).unwrap()) + .send_and_await_response(60) + else { + return "Failed to communicate with eth module".to_string(); + }; + + // Parse the response + if let Ok(json_value) = serde_json::from_slice::(&body) { + if let Some(response) = json_value.as_str() { + match response { + "Ok" => { + let auth_info = if has_auth { " with authentication" } else { "" }; + format!( + "Successfully added RPC URL provider: {} on chain {}{}", + provider_str, chain_id, auth_info + ) + } + "PermissionDenied" => { + "Permission denied: insufficient privileges".to_string() + } + other => format!("Error: {}", other), + } + } else { + // Handle any other response types with better formatting + format!( + "Unexpected response: {}", + serde_json::to_string_pretty(&json_value) + .unwrap_or_else(|_| "Failed to format response".to_string()) + ) + } + } else { + format!( + "Failed to parse response as JSON\nRaw response: {}", + String::from_utf8_lossy(&body) + ) + } + } + Err(err_msg) => err_msg, + } +} + +fn parse_flag_value(args: &[&str], flag: &str, default: T) -> T { + let mut i = 0; + while i < args.len() { + if args[i] == flag { + if i + 1 < args.len() { + if let Ok(value) = args[i + 1].parse::() { + return value; + } + } + break; + } + i += 1; + } + default +} + +fn parse_flag_bool(args: &[&str], flag: &str, default: bool) -> bool { + let mut i = 0; + while i < args.len() { + if args[i] == flag { + if i + 1 < args.len() { + if let Ok(value) = args[i + 1].parse::() { + return value; + } + } + break; + } + i += 1; + } + default +} + +fn parse_auth_options(args: &[&str]) -> Result, String> { + let mut i = 0; + let mut auth_type: Option<&str> = None; + let mut auth_value: Option<&str> = None; + + while i < args.len() { + match args[i] { + "--auth-type" => { + if i + 1 >= args.len() { + return Err("Missing value for --auth-type".to_string()); + } + auth_type = Some(args[i + 1]); + i += 2; + } + "--auth-value" => { + if i + 1 >= args.len() { + return Err("Missing value for --auth-value".to_string()); + } + auth_value = Some(args[i + 1]); + i += 2; + } + "--chain-id" | "--trusted" => { + // Skip these flags and their values as they're handled separately + i += 2; + } + _ => { + // Skip unknown arguments (the URL is handled separately) + i += 1; + } + } + } + + match (auth_type, auth_value) { + (Some(auth_type), Some(auth_value)) => { + let auth_json = match auth_type.to_lowercase().as_str() { + "basic" => json!({"Basic": auth_value}), + "bearer" => json!({"Bearer": auth_value}), + "raw" => json!({"Raw": auth_value}), + _ => { + return Err("Invalid auth type. Must be 'basic', 'bearer', or 'raw'".to_string()) + } + }; + Ok(Some(auth_json)) + } + (Some(_), None) => Err("--auth-type specified but --auth-value is missing".to_string()), + (None, Some(_)) => Err("--auth-value specified but --auth-type is missing".to_string()), + (None, None) => Ok(None), + } +} diff --git a/hyperdrive/packages/terminal/get-providers/Cargo.toml b/hyperdrive/packages/terminal/get-providers/Cargo.toml new file mode 100644 index 000000000..9e5f3c893 --- /dev/null +++ b/hyperdrive/packages/terminal/get-providers/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "get-providers" +version = "0.1.0" +edition = "2021" + +[features] +simulation-mode = [] + +[dependencies] +hyperware_process_lib = "2.0.0" +rmp-serde = "1.1.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.140" +wit-bindgen = "0.42.1" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "hyperware:process" \ No newline at end of file diff --git a/hyperdrive/packages/terminal/get-providers/src/lib.rs b/hyperdrive/packages/terminal/get-providers/src/lib.rs new file mode 100644 index 000000000..4c92cac85 --- /dev/null +++ b/hyperdrive/packages/terminal/get-providers/src/lib.rs @@ -0,0 +1,87 @@ +use hyperware_process_lib::{script, Address, Message, Request}; +use serde_json::Value; + +wit_bindgen::generate!({ + path: "../target/wit", + world: "process-v1", +}); + +script!(init); +fn init(_our: Address, _args: String) -> String { + // Manually construct the JSON for GetProviders since it's a simple enum variant + let request_body = r#""GetProviders""#.as_bytes().to_vec(); + + let Ok(Ok(Message::Response { body, .. })) = Request::to(("our", "eth", "distro", "sys")) + .body(request_body) + .send_and_await_response(60) + else { + return "Failed to get providers from eth module".to_string(); + }; + if let Ok(json_value) = serde_json::from_slice::(&body) { + // Check if it looks like a Providers response + if let Some(obj) = json_value.as_object() { + if let Some(providers_value) = obj.get("Providers") { + if let Some(providers_array) = providers_value.as_array() { + // We have a Providers response with an array + if providers_array.is_empty() { + return "No providers configured.".to_string(); + } else { + let mut output = format!("Found {} provider(s):\n", providers_array.len()); + for (i, provider) in providers_array.iter().enumerate() { + output.push_str(&format!("\n{}. ", i + 1)); + + // Extract basic info + if let Some(chain_id) = provider.get("chain_id") { + output.push_str(&format!("Chain ID: {}\n", chain_id)); + } + if let Some(trusted) = provider.get("trusted") { + output.push_str(&format!(" Trusted: {}\n", trusted)); + } + + // Handle provider type + if let Some(provider_info) = provider.get("provider") { + if let Some(rpc_url) = provider_info.get("RpcUrl") { + output.push_str(" Type: RPC URL\n"); + if let Some(url) = rpc_url.get("url") { + output.push_str(&format!(" URL: {}\n", url)); + } + if let Some(auth) = rpc_url.get("auth") { + if !auth.is_null() { + output.push_str(" Auth: Configured\n"); + } + } + } else if let Some(node_info) = provider_info.get("Node") { + output.push_str(" Type: Node Provider\n"); + if let Some(hns_update) = node_info.get("hns_update") { + if let Some(name) = hns_update.get("name") { + output.push_str(&format!(" Node: {}\n", name)); + } + } + if let Some(use_as_provider) = node_info.get("use_as_provider") + { + output.push_str(&format!( + " Use as Provider: {}\n", + use_as_provider + )); + } + } + } + } + return output; + } + } + } + } + // If it's not a Providers response, show the JSON structure + format!( + "Response (not Providers): {}", + serde_json::to_string_pretty(&json_value) + .unwrap_or_else(|_| "Could not format JSON".to_string()) + ) + } else { + format!( + "Failed to parse as JSON\nRaw response: {}", + String::from_utf8_lossy(&body) + ) + } +} diff --git a/hyperdrive/packages/terminal/help/src/lib.rs b/hyperdrive/packages/terminal/help/src/lib.rs index ec8f3e2f8..999e32d93 100644 --- a/hyperdrive/packages/terminal/help/src/lib.rs +++ b/hyperdrive/packages/terminal/help/src/lib.rs @@ -5,10 +5,13 @@ wit_bindgen::generate!({ world: "process-v1", }); -const HELP_MESSAGES: [[&str; 2]; 11] = [ +const HELP_MESSAGES: [[&str; 2]; 15] = [ + ["add-node-provider", "\n\x1b[1madd-node-provider\x1b[0m [--trusted ]: add a node provider to the providers configuration.\n - Examples:\n \x1b[1madd-node-provider 8453 other-node.hypr abc123pubkey 192.168.1.1 9000\x1b[0m (defaults to trusted=false)\n \x1b[1madd-node-provider 1 other-node.hypr abc123pubkey 192.168.1.1 9000 --trusted true\x1b[0m"], + ["add-rpcurl-provider", "\n\x1b[1madd-rpcurl-provider\x1b[0m [--chain-id ] [--trusted ] [--auth-type --auth-value ]: add an RPC URL provider to the providers configuration.\n - Examples:\n \x1b[1madd-rpcurl-provider wss://base-mainnet.infura.io/v3/your-key\x1b[0m (defaults to chain-id=8453, trusted=true)\n \x1b[1madd-rpcurl-provider wss://mainnet.infura.io/v3/your-key --chain-id 1\x1b[0m\n \x1b[1madd-rpcurl-provider wss://base-mainnet.infura.io/ws/v3/your-key --trusted false\x1b[0m\n \x1b[1madd-rpcurl-provider wss://rpc.example.com --auth-type bearer --auth-value your-token\x1b[0m"], ["alias", "\n\x1b[1malias\x1b[0m : create an alias for a script.\n - Example: \x1b[1malias get-block get-block:hns-indexer:sys\x1b[0m\n - note: all of these listed commands are just default aliases for terminal scripts."], ["cat", "\n\x1b[1mcat\x1b[0m : print the contents of a file in the terminal.\n - Example: \x1b[1mcat /terminal:sys/pkg/scripts.json\x1b[0m"], ["echo", "\n\x1b[1mecho\x1b[0m : print text to the terminal.\n - Example: \x1b[1mecho foo\x1b[0m"], + ["get-providers", "\n\x1b[1mget-providers\x1b[0m: display the providers configuration."], ["hi", "\n\x1b[1mhi\x1b[0m : send a text message to another node's command line.\n - Example: \x1b[1mhi mothu.hypr hello world\x1b[0m"], ["kfetch", "\n\x1b[1mkfetch\x1b[0m: print system information a la neofetch. No arguments."], ["kill", "\n\x1b[1mkill\x1b[0m : terminate a running process. This will bypass any restart behavior; use judiciously.\n - Example: \x1b[1mkill chess:chess:sys\x1b[0m"], @@ -16,6 +19,7 @@ const HELP_MESSAGES: [[&str; 2]; 11] = [ ["net-diagnostics", "\n\x1b[1mnet-diagnostics\x1b[0m: print some useful networking diagnostic data."], ["peer", "\n\x1b[1mpeer\x1b[0m : print the peer's PKI info, if it exists."], ["peers", "\n\x1b[1mpeers\x1b[0m: print the peers the node currently hold connections with."], + ["remove-provider", "\n\x1b[1mremove-provider\x1b[0m : remove a provider from the providers configuration.\n - Example: \x1b[1mremove-provider 8453 wss://base-mainnet.infura.io/ws/v3/your-key\x1b[0m"], ["top", "\n\x1b[1mtop\x1b[0m : display kernel debugging info about a process. Leave the process ID blank to display info about all processes and get the total number of running processes.\n - Example: \x1b[1mtop net:distro:sys\x1b[0m\n - Example: \x1b[1mtop\x1b[0m"], ]; diff --git a/hyperdrive/packages/terminal/pkg/scripts.json b/hyperdrive/packages/terminal/pkg/scripts.json index 427658e72..1b2c17825 100644 --- a/hyperdrive/packages/terminal/pkg/scripts.json +++ b/hyperdrive/packages/terminal/pkg/scripts.json @@ -1,4 +1,40 @@ { + "add-node-provider.wasm": { + "root": true, + "public": false, + "request_networking": false, + "request_capabilities": [ + "eth:distro:sys", + { + "process": "eth:distro:sys", + "params": { + "root": true + } + } + ], + "grant_capabilities": [ + "eth:distro:sys" + ], + "wit_version": 1 + }, + "add-rpcurl-provider.wasm": { + "root": true, + "public": false, + "request_networking": false, + "request_capabilities": [ + "eth:distro:sys", + { + "process": "eth:distro:sys", + "params": { + "root": true + } + } + ], + "grant_capabilities": [ + "eth:distro:sys" + ], + "wit_version": 1 + }, "alias.wasm": { "root": false, "public": false, @@ -33,6 +69,24 @@ "grant_capabilities": [], "wit_version": 1 }, + "get-providers.wasm": { + "root": true, + "public": false, + "request_networking": false, + "request_capabilities": [ + "eth:distro:sys", + { + "process": "eth:distro:sys", + "params": { + "root": true + } + } + ], + "grant_capabilities": [ + "eth:distro:sys" + ], + "wit_version": 1 + }, "help.wasm": { "root": false, "public": false, @@ -124,6 +178,24 @@ ], "wit_version": 1 }, + "remove-provider.wasm": { + "root": true, + "public": false, + "request_networking": false, + "request_capabilities": [ + "eth:distro:sys", + { + "process": "eth:distro:sys", + "params": { + "root": true + } + } + ], + "grant_capabilities": [ + "eth:distro:sys" + ], + "wit_version": 1 + }, "top.wasm": { "root": true, "public": false, diff --git a/hyperdrive/packages/terminal/remove-provider/Cargo.toml b/hyperdrive/packages/terminal/remove-provider/Cargo.toml new file mode 100644 index 000000000..bdc5d563f --- /dev/null +++ b/hyperdrive/packages/terminal/remove-provider/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "remove-provider" +version = "0.1.0" +edition = "2021" + +[features] +simulation-mode = [] + +[dependencies] +hyperware_process_lib = "2.0.0" +rmp-serde = "1.1.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.140" +wit-bindgen = "0.42.1" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "hyperware:process" \ No newline at end of file diff --git a/hyperdrive/packages/terminal/remove-provider/src/lib.rs b/hyperdrive/packages/terminal/remove-provider/src/lib.rs new file mode 100644 index 000000000..eb81cf61e --- /dev/null +++ b/hyperdrive/packages/terminal/remove-provider/src/lib.rs @@ -0,0 +1,72 @@ +use hyperware_process_lib::{script, Address, Message, Request}; +use serde_json::Value; + +wit_bindgen::generate!({ + path: "../target/wit", + world: "process-v1", +}); + +script!(init); +fn init(_our: Address, args: String) -> String { + if args.is_empty() { + return "Usage: remove-provider ".to_string(); + } + + let parts: Vec<&str> = args.trim().split_whitespace().collect(); + if parts.len() != 2 { + return "Usage: remove-provider ".to_string(); + } + + let Ok(chain_id) = parts[0].parse::() else { + return format!("Invalid chain_id: '{}'. Must be a number.", parts[0]); + }; + + let provider_identifier = parts[1].to_string(); + + // Manually construct the JSON for RemoveProvider + let request_json = format!( + r#"{{"RemoveProvider": [{}, "{}"]}}"#, + chain_id, provider_identifier + ); + + let Ok(Ok(Message::Response { body, .. })) = Request::to(("our", "eth", "distro", "sys")) + .body(request_json.as_bytes().to_vec()) + .send_and_await_response(60) + else { + return "Failed to remove provider from eth module".to_string(); + }; + + // Parse the response and handle different variants + if let Ok(json_value) = serde_json::from_slice::(&body) { + match json_value.as_str() { + Some("Ok") => { + format!( + "Successfully removed provider '{}' from chain {}", + provider_identifier, chain_id + ) + } + Some("ProviderNotFound") => { + format!( + "Provider '{}' not found on chain {} (may have already been removed)", + provider_identifier, chain_id + ) + } + Some("PermissionDenied") => { + "Permission denied: you don't have root capability for eth module".to_string() + } + _ => { + // Handle any other response types + format!( + "Unexpected response: {}", + serde_json::to_string_pretty(&json_value) + .unwrap_or_else(|_| "Failed to format response".to_string()) + ) + } + } + } else { + format!( + "Failed to parse response as JSON\nRaw response: {}", + String::from_utf8_lossy(&body) + ) + } +} diff --git a/hyperdrive/packages/terminal/terminal/src/lib.rs b/hyperdrive/packages/terminal/terminal/src/lib.rs index ef0583687..babd24ceb 100644 --- a/hyperdrive/packages/terminal/terminal/src/lib.rs +++ b/hyperdrive/packages/terminal/terminal/src/lib.rs @@ -60,6 +60,14 @@ impl VersionedState { Self::V1(TerminalStateV1 { our, aliases: HashMap::from([ + ( + "add-node-provider".to_string(), + ProcessId::new(Some("add-node-provider"), "terminal", "sys"), + ), + ( + "add-rpcurl-provider".to_string(), + ProcessId::new(Some("add-rpcurl-provider"), "terminal", "sys"), + ), ( "alias".to_string(), ProcessId::new(Some("alias"), "terminal", "sys"), @@ -72,6 +80,10 @@ impl VersionedState { "echo".to_string(), ProcessId::new(Some("echo"), "terminal", "sys"), ), + ( + "get-providers".to_string(), + ProcessId::new(Some("get-providers"), "terminal", "sys"), + ), ( "help".to_string(), ProcessId::new(Some("help"), "terminal", "sys"), @@ -104,6 +116,10 @@ impl VersionedState { "peers".to_string(), ProcessId::new(Some("peers"), "terminal", "sys"), ), + ( + "remove-provider".to_string(), + ProcessId::new(Some("remove-provider"), "terminal", "sys"), + ), ( "top".to_string(), ProcessId::new(Some("top"), "terminal", "sys"), diff --git a/hyperdrive/src/eth/mod.rs b/hyperdrive/src/eth/mod.rs index 5aa07b11d..af96d9326 100644 --- a/hyperdrive/src/eth/mod.rs +++ b/hyperdrive/src/eth/mod.rs @@ -64,39 +64,47 @@ struct NodeProvider { impl ActiveProviders { fn add_provider_config(&mut self, new: ProviderConfig) { - match new.provider { - NodeOrRpcUrl::Node { - hns_update, - use_as_provider, - } => { - self.remove_provider(&hns_update.name); - self.nodes.insert( - 0, - NodeProvider { - trusted: new.trusted, - usable: use_as_provider, - hns_update, - }, - ); - } + match &new.provider { NodeOrRpcUrl::RpcUrl { url, auth } => { - self.remove_provider(&url); - self.urls.insert( - 0, - UrlProvider { - trusted: new.trusted, - url, - pubsub: vec![], - auth, - }, - ); + // Remove any existing URL provider with this URL + self.urls + .retain(|existing_provider| existing_provider.url != *url); + + // Create and add new URL provider + let url_provider = UrlProvider { + trusted: new.trusted, + url: url.clone(), + pubsub: vec![], + auth: auth.clone(), + }; + self.urls.insert(0, url_provider); + } + NodeOrRpcUrl::Node { hns_update, .. } => { + // Remove any existing node provider with this node name + self.nodes.retain(|existing_provider| { + existing_provider.hns_update.name != hns_update.name + }); + + // Create and add new node provider + let node_provider = NodeProvider { + trusted: new.trusted, + usable: true, // Default to usable + hns_update: hns_update.clone(), + }; + self.nodes.insert(0, node_provider); } } } - fn remove_provider(&mut self, remove: &str) { + fn remove_provider(&mut self, remove: &str) -> bool { + let urls_len_before = self.urls.len(); + let nodes_len_before = self.nodes.len(); + self.urls.retain(|x| x.url != remove); self.nodes.retain(|x| x.hns_update.name != remove); + + // Return true if anything was actually removed + self.urls.len() < urls_len_before || self.nodes.len() < nodes_len_before } } @@ -973,6 +981,7 @@ async fn handle_eth_config_action( let mut save_settings = false; let mut save_providers = false; + let mut provider_not_found = false; // modify our providers and access settings based on config action match eth_config_action { @@ -989,8 +998,13 @@ async fn handle_eth_config_action( } EthConfigAction::RemoveProvider((chain_id, remove)) => { if let Some(mut aps) = state.providers.get_mut(&chain_id) { - aps.remove_provider(&remove); - save_providers = true; + if aps.remove_provider(&remove) { + save_providers = true; + } else { + provider_not_found = true; + } + } else { + provider_not_found = true; } } EthConfigAction::SetPublic => { @@ -1085,5 +1099,9 @@ async fn handle_eth_config_action( verbose_print(&state.print_tx, "eth: saved new provider settings").await; }; } - EthConfigResponse::Ok + if provider_not_found { + EthConfigResponse::ProviderNotFound + } else { + EthConfigResponse::Ok + } } diff --git a/hyperdrive/src/main.rs b/hyperdrive/src/main.rs index c440d5d7f..e9bb037f4 100644 --- a/hyperdrive/src/main.rs +++ b/hyperdrive/src/main.rs @@ -135,16 +135,7 @@ async fn main() { serde_json::from_str(DEFAULT_ETH_PROVIDERS).unwrap() }; - // TODO: Remove debug logging before merging - before modification - eprintln!( - "[DEBUG-AUTH] eth_provider_config before --rpc/--rpc-config processing: {:#?}", - eth_provider_config - ); - if let Some(rpc) = rpc { - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Processing --rpc flag with URL: {}", rpc); - let new_provider = lib::eth::ProviderConfig { chain_id: CHAIN_ID, trusted: true, @@ -156,92 +147,34 @@ async fn main() { add_provider_to_config(&mut eth_provider_config, new_provider); is_eth_provider_config_updated = true; - - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Added --rpc provider (with deduplication)"); } if let Some(rpc_config) = rpc_config { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] Processing --rpc-config flag with file: {}", - rpc_config.display() - ); + if let Ok(contents) = std::fs::read_to_string(&rpc_config) { + if let Ok(rpc_configs) = + serde_json::from_str::>(&contents) + { + // Process in reverse order so the first entry in the file becomes highest priority + for (_reverse_index, rpc_url_config) in rpc_configs.into_iter().rev().enumerate() { + let new_provider = lib::eth::ProviderConfig { + chain_id: CHAIN_ID, + trusted: true, + provider: lib::eth::NodeOrRpcUrl::RpcUrl { + url: rpc_url_config.url, + auth: rpc_url_config.auth, + }, + }; - match std::fs::read_to_string(&rpc_config) { - Ok(contents) => { - match serde_json::from_str::>(&contents) { - Ok(rpc_configs) => { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] Loaded {} providers from config file", - rpc_configs.len() - ); - - // Store the length before consuming the vector - let total_configs = rpc_configs.len(); - - // Process in reverse order so the first entry in the file becomes highest priority - for (reverse_index, rpc_url_config) in - rpc_configs.into_iter().rev().enumerate() - { - let original_index = total_configs - 1 - reverse_index; - - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Processing config provider {} (original position {}): {}", - reverse_index + 1, original_index + 1, rpc_url_config.url); - if let Some(ref auth) = rpc_url_config.auth { - match auth { - lib::eth::Authorization::Basic(creds) => { - eprintln!("[DEBUG-AUTH] Config provider has Basic auth (length: {})", creds.len()); - } - lib::eth::Authorization::Bearer(token) => { - eprintln!("[DEBUG-AUTH] Config provider has Bearer auth (length: {})", token.len()); - } - lib::eth::Authorization::Raw(raw) => { - eprintln!("[DEBUG-AUTH] Config provider has Raw auth (length: {})", raw.len()); - } - } - } else { - eprintln!("[DEBUG-AUTH] Config provider has no auth"); - } - - let new_provider = lib::eth::ProviderConfig { - chain_id: CHAIN_ID, - trusted: true, - provider: lib::eth::NodeOrRpcUrl::RpcUrl { - url: rpc_url_config.url, - auth: rpc_url_config.auth, - }, - }; - - add_provider_to_config(&mut eth_provider_config, new_provider); - } - is_eth_provider_config_updated = true; - - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Added all --rpc-config providers (with deduplication, order preserved)"); - } - Err(e) => { - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Failed to parse RPC config file: {}", e); - eprintln!("Failed to parse RPC config file: {e}"); - } + add_provider_to_config(&mut eth_provider_config, new_provider); } + is_eth_provider_config_updated = true; + } else { + eprintln!("Failed to parse RPC config file"); } - Err(e) => { - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Failed to read RPC config file: {}", e); - eprintln!("Failed to read RPC config file: {e}"); - } + } else { + eprintln!("Failed to read RPC config file"); } } - // TODO: Remove debug logging before merging - after modification - eprintln!( - "[DEBUG-AUTH] eth_provider_config after --rpc/--rpc-config processing: {:#?}", - eth_provider_config - ); - if is_eth_provider_config_updated { // save the new provider config tokio::fs::write( diff --git a/hyperdrive/src/register.rs b/hyperdrive/src/register.rs index db7b504b6..a0fa844b8 100644 --- a/hyperdrive/src/register.rs +++ b/hyperdrive/src/register.rs @@ -251,103 +251,33 @@ pub async fn connect_to_provider_from_config( ) -> RootProvider { let saved_configs = ð_provider_config.0; - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Number of configured providers (including Node providers which will not be used): {}", saved_configs.len()); - // Try each configured provider first - for (index, provider_config) in saved_configs.iter().enumerate() { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] Trying configured provider {}/{}", - index + 1, - saved_configs.len() - ); - + for (_index, provider_config) in saved_configs.iter().enumerate() { match &provider_config.provider { lib::eth::NodeOrRpcUrl::RpcUrl { url, auth } => { - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] RPC URL provider: {}", url); - eprintln!("[DEBUG-AUTH] Auth present: {}", auth.is_some()); - if let Some(auth_ref) = auth { - match auth_ref { - lib::eth::Authorization::Basic(creds) => { - eprintln!( - "[DEBUG-AUTH] Auth type: Basic (credentials length: {})", - creds.len() - ); - eprintln!( - "[DEBUG-AUTH] Basic auth format valid (contains ':'): {}", - creds.contains(':') - ); - } - lib::eth::Authorization::Bearer(token) => { - eprintln!( - "[DEBUG-AUTH] Auth type: Bearer (token length: {})", - token.len() - ); - } - lib::eth::Authorization::Raw(raw) => { - eprintln!("[DEBUG-AUTH] Auth type: Raw (value length: {})", raw.len()); - } - } - } - let ws_connect = WsConnect { url: url.clone(), auth: auth.clone().map(|a| a.into()), config: None, }; - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] Attempting connection to provider: {}", url); - if let Ok(client) = ProviderBuilder::new().on_ws(ws_connect).await { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] ✅ Successfully connected to configured provider: {}", - url - ); println!("Connected to configured provider: {url}\r"); return client; } else { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] ❌ Failed to connect to configured provider: {}", - url - ); println!("Failed to connect to provider: {url}\r"); } } - lib::eth::NodeOrRpcUrl::Node { - hns_update, - use_as_provider, - } => { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] Node provider: {} (use_as_provider: {})", - hns_update.name, use_as_provider - ); - eprintln!("[DEBUG-AUTH] Skipping node provider (need RPC URL)"); - continue; + lib::eth::NodeOrRpcUrl::Node { .. } => { + // Node providers are not supported in registration, skip to next } } } - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] All configured providers failed, falling back to defaults"); - // Fall back to default providers if configured ones fail let default_rpc_urls = ["wss://base.llamarpc.com", "wss://base-rpc.publicnode.com"]; - for (index, rpc_url) in default_rpc_urls.iter().enumerate() { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] Trying fallback provider {}/{}: {}", - index + 1, - default_rpc_urls.len(), - rpc_url - ); - + for (_index, rpc_url) in default_rpc_urls.iter().enumerate() { let ws_connect = WsConnect { url: rpc_url.to_string(), auth: None, @@ -355,29 +285,16 @@ pub async fn connect_to_provider_from_config( }; if let Ok(client) = ProviderBuilder::new().on_ws(ws_connect).await { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] ✅ Successfully connected to fallback provider: {}", - rpc_url - ); println!("Connected to fallback provider: {rpc_url}\r"); return client; - } else { - // TODO: Remove debug logging before merging - eprintln!( - "[DEBUG-AUTH] ❌ Failed to connect to fallback provider: {}", - rpc_url - ); } } - // TODO: Remove debug logging before merging - eprintln!("[DEBUG-AUTH] ❌ All providers (configured + fallback) failed!"); - panic!( - "Error: runtime could not connect to any ETH RPC providers\n\ + "Error: runtime could not connect to configured or fallback Base ETH RPC providers\n\ This is necessary in order to verify node identity onchain.\n\ - Please check your configured providers and internet connection." + Please make sure you are using a valid WebSockets URL if using \ + the --rpc or --rpc-config flag, and you are connected to the internet." ); } diff --git a/hyperdrive/src/terminal/mod.rs b/hyperdrive/src/terminal/mod.rs index 8ceb92456..8eb7054f8 100644 --- a/hyperdrive/src/terminal/mod.rs +++ b/hyperdrive/src/terminal/mod.rs @@ -990,7 +990,9 @@ async fn handle_key_event( } else { current_line.line_col -= 1; let c = current_line.delete_char(); - current_line.cursor_col = current_line.cursor_col.saturating_sub(utils::display_width(&c) as u16); + current_line.cursor_col = current_line + .cursor_col + .saturating_sub(utils::display_width(&c) as u16); } } // diff --git a/lib/src/eth.rs b/lib/src/eth.rs index 46c9df404..fc00d1fd9 100644 --- a/lib/src/eth.rs +++ b/lib/src/eth.rs @@ -171,6 +171,8 @@ pub enum EthConfigResponse { active_subscriptions: HashMap>>, // None if local, Some(node_provider_name) if remote outstanding_requests: HashSet, }, + /// Provider was not found + ProviderNotFound, } /// Settings for our ETH provider @@ -223,10 +225,11 @@ pub struct ProviderConfig { pub enum NodeOrRpcUrl { Node { hns_update: crate::core::HnsUpdate, - use_as_provider: bool, // false for just-routers inside saved config + use_as_provider: bool, }, RpcUrl { url: String, + #[serde(skip_serializing_if = "Option::is_none")] auth: Option, }, }