diff --git a/core/archipelago/src/api/rpc/bitcoin.rs b/core/archipelago/src/api/rpc/bitcoin.rs index 9a8a812e..8fdd48e9 100644 --- a/core/archipelago/src/api/rpc/bitcoin.rs +++ b/core/archipelago/src/api/rpc/bitcoin.rs @@ -86,7 +86,7 @@ impl RpcHandler { }); let resp = client - .post("http://127.0.0.1:8332/") + .post(crate::constants::BITCOIN_RPC_URL) .basic_auth(&rpc_user, Some(&rpc_pass)) .json(&body) .send() diff --git a/core/archipelago/src/api/rpc/content.rs b/core/archipelago/src/api/rpc/content.rs index e0254702..e0786680 100644 --- a/core/archipelago/src/api/rpc/content.rs +++ b/core/archipelago/src/api/rpc/content.rs @@ -218,7 +218,7 @@ impl RpcHandler { return Err(anyhow::anyhow!("Invalid v3 onion address")); } - let socks_proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050") + let socks_proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY) .context("Failed to create SOCKS proxy")?; let client = reqwest::Client::builder() @@ -281,7 +281,7 @@ impl RpcHandler { } // Connect via Tor SOCKS proxy to the peer's content catalog endpoint - let socks_proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050") + let socks_proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY) .context("Failed to create SOCKS proxy")?; let client = reqwest::Client::builder() diff --git a/core/archipelago/src/api/rpc/federation.rs b/core/archipelago/src/api/rpc/federation.rs index 205154da..a074e938 100644 --- a/core/archipelago/src/api/rpc/federation.rs +++ b/core/archipelago/src/api/rpc/federation.rs @@ -539,7 +539,7 @@ impl RpcHandler { let nodes = federation::load_nodes(&self.config.data_dir).await?; - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050") + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY) .context("Invalid Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) diff --git a/core/archipelago/src/api/rpc/handshake.rs b/core/archipelago/src/api/rpc/handshake.rs index c329ff0a..9ba4d6ec 100644 --- a/core/archipelago/src/api/rpc/handshake.rs +++ b/core/archipelago/src/api/rpc/handshake.rs @@ -74,7 +74,7 @@ impl RpcHandler { &identity_dir, &self.config.nostr_relays, self.config.nostr_tor_proxy.as_deref(), - None, // TODO: track last-seen timestamp to avoid re-processing + None, ) .await?; diff --git a/core/archipelago/src/api/rpc/identity.rs b/core/archipelago/src/api/rpc/identity.rs index eff4010d..b994a2bc 100644 --- a/core/archipelago/src/api/rpc/identity.rs +++ b/core/archipelago/src/api/rpc/identity.rs @@ -521,7 +521,7 @@ impl RpcHandler { let url = format!("http://{}/rpc/", host); // Use SOCKS5 proxy to reach .onion address - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050") + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY) .context("Failed to create Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) diff --git a/core/archipelago/src/api/rpc/lnd.rs b/core/archipelago/src/api/rpc/lnd.rs index 3fb1982f..d07118d5 100644 --- a/core/archipelago/src/api/rpc/lnd.rs +++ b/core/archipelago/src/api/rpc/lnd.rs @@ -34,8 +34,6 @@ struct LndChannelBalanceResponse { #[derive(Debug, Deserialize)] struct LndBalanceResponse { total_balance: Option, - #[allow(dead_code)] - confirmed_balance: Option, } #[derive(Debug, Deserialize)] @@ -93,11 +91,9 @@ impl RpcHandler { { Ok(resp) => resp.json().await.unwrap_or(LndBalanceResponse { total_balance: None, - confirmed_balance: None, }), Err(_) => LndBalanceResponse { total_balance: None, - confirmed_balance: None, }, }; @@ -125,7 +121,7 @@ impl RpcHandler { } /// Helper: create an authenticated LND REST client - async fn lnd_client(&self) -> Result<(reqwest::Client, String)> { + pub(crate) async fn lnd_client(&self) -> Result<(reqwest::Client, String)> { let macaroon_path = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon"; let macaroon_bytes = tokio::fs::read(macaroon_path) diff --git a/core/archipelago/src/api/rpc/marketplace.rs b/core/archipelago/src/api/rpc/marketplace.rs index cdd7688e..e4e1eaa4 100644 --- a/core/archipelago/src/api/rpc/marketplace.rs +++ b/core/archipelago/src/api/rpc/marketplace.rs @@ -179,10 +179,29 @@ impl RpcHandler { .and_then(|v| v.as_str()) .ok_or_else(|| anyhow::anyhow!("Missing r_hash"))?; - // Check invoice status — stub until LND lookup is implemented - // TODO: Add lnd.lookupinvoice RPC endpoint for real payment verification - let paid = false; // Payment verification pending LND integration - let _ = r_hash; // Used when LND lookup is available + // Validate r_hash is hex-encoded (LND payment hashes are 32 bytes = 64 hex chars) + if r_hash.len() != 64 || !r_hash.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(anyhow::anyhow!("Invalid r_hash: must be 64-character hex string")); + } + + let (client, macaroon_hex) = self.lnd_client().await?; + + let url = format!( + "https://127.0.0.1:8080/v1/invoice/{}", + r_hash + ); + let paid = match client + .get(&url) + .header("Grpc-Metadata-macaroon", &macaroon_hex) + .send() + .await + { + Ok(r) if r.status().is_success() => { + let body: serde_json::Value = r.json().await.unwrap_or_default(); + body.get("settled").and_then(|v| v.as_bool()).unwrap_or(false) + } + _ => false, + }; Ok(serde_json::json!({ "r_hash": r_hash, diff --git a/core/archipelago/src/api/rpc/tor.rs b/core/archipelago/src/api/rpc/tor.rs index e16760d7..0beff758 100644 --- a/core/archipelago/src/api/rpc/tor.rs +++ b/core/archipelago/src/api/rpc/tor.rs @@ -770,7 +770,13 @@ async fn notify_federation_peers_address_change( let identity_dir = data_dir.join("identity"); match identity::NodeIdentity::load_or_create(&identity_dir).await { Ok(node_id) => { - let did = node_id.did_key(); + let did = match node_id.did_key() { + Ok(d) => d, + Err(e) => { + tracing::warn!("Failed to derive DID key: {}", e); + return; + } + }; let proxy = tor_proxy.unwrap_or("127.0.0.1:9050"); match federation::load_nodes(data_dir).await { Ok(peers) => { @@ -789,7 +795,7 @@ async fn notify_federation_peers_address_change( let url = format!("http://{}/rpc/v1", &peer.onion); let client = match reqwest::Client::builder() .proxy(match reqwest::Proxy::all(format!("socks5h://{}", proxy)) - .or_else(|_| reqwest::Proxy::all("socks5h://127.0.0.1:9050")) { + .or_else(|_| reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY)) { Ok(p) => p, Err(_) => continue, }) diff --git a/core/archipelago/src/constants.rs b/core/archipelago/src/constants.rs new file mode 100644 index 00000000..b0584d48 --- /dev/null +++ b/core/archipelago/src/constants.rs @@ -0,0 +1,19 @@ +/// Centralized constants for the Archipelago backend. +/// Avoids hardcoded values scattered across the codebase. + +/// Bitcoin Core RPC endpoint (localhost only). +pub const BITCOIN_RPC_URL: &str = "http://127.0.0.1:8332/"; + +/// DWN (Decentralized Web Node) health check endpoint. +pub const DWN_HEALTH_URL: &str = "http://127.0.0.1:3100/health"; + +/// Tor SOCKS5 proxy for outbound onion connections. +pub const TOR_SOCKS_PROXY: &str = "socks5h://127.0.0.1:9050"; + +/// DNS-over-HTTPS providers for secure DNS resolution. +pub const DNS_PROVIDERS: &[&str] = &[ + "https://cloudflare-dns.com/dns-query", + "https://dns.google/dns-query", + "https://dns.quad9.net/dns-query", + "https://dns.mullvad.net/dns-query", +]; diff --git a/core/archipelago/src/container/data_manager.rs b/core/archipelago/src/container/data_manager.rs index 109902b4..67d35676 100644 --- a/core/archipelago/src/container/data_manager.rs +++ b/core/archipelago/src/container/data_manager.rs @@ -64,29 +64,6 @@ impl DevDataManager { // This is a no-op by default, but can be extended Ok(()) } - - /// Get all app data directories - #[allow(dead_code)] - pub async fn list_app_data_dirs(&self) -> Result> { - if !self.dev_data_dir.exists() { - return Ok(vec![]); - } - - let mut entries = fs::read_dir(&self.dev_data_dir) - .await - .with_context(|| format!("Failed to read dev data directory: {:?}", self.dev_data_dir))?; - - let mut app_ids = Vec::new(); - while let Some(entry) = entries.next_entry().await? { - if entry.file_type().await?.is_dir() { - if let Some(name) = entry.file_name().to_str() { - app_ids.push(name.to_string()); - } - } - } - - Ok(app_ids) - } } #[cfg(test)] diff --git a/core/archipelago/src/container/dev_orchestrator.rs b/core/archipelago/src/container/dev_orchestrator.rs index 8fa7f03d..2da2de79 100644 --- a/core/archipelago/src/container/dev_orchestrator.rs +++ b/core/archipelago/src/container/dev_orchestrator.rs @@ -247,16 +247,4 @@ impl DevContainerOrchestrator { archipelago_container::ContainerState::Unknown(_) => Ok("unknown".to_string()), } } - - /// Get port mapping for an app - #[allow(dead_code)] - pub fn get_port_mapping(&self, app_id: &str) -> Option> { - self.port_manager.get_port_mapping(app_id).ok().flatten() - } - - /// Get Bitcoin simulator - #[allow(dead_code)] - pub fn bitcoin_simulator(&self) -> &Arc { - &self.bitcoin_simulator - } } diff --git a/core/archipelago/src/electrs_status.rs b/core/archipelago/src/electrs_status.rs index 69424acb..7daf30b3 100644 --- a/core/archipelago/src/electrs_status.rs +++ b/core/archipelago/src/electrs_status.rs @@ -8,7 +8,6 @@ use tokio::net::TcpStream; const ELECTRUMX_HOST: &str = "127.0.0.1"; const ELECTRUMX_PORT: u16 = 50001; -const BITCOIN_RPC_URL: &str = "http://127.0.0.1:8332/"; const ELECTRUMX_DATA_DIR: &str = "/var/lib/archipelago/electrumx"; // Approximate final index size in bytes for mainnet (~130GB for ElectrumX full index as of 2026) const ESTIMATED_FULL_INDEX_BYTES: f64 = 130_000_000_000.0; @@ -132,7 +131,7 @@ async fn bitcoin_network_height() -> Result { "params": [] }); let resp = client - .post(BITCOIN_RPC_URL) + .post(crate::constants::BITCOIN_RPC_URL) .header("Content-Type", "application/json") .header("Authorization", bitcoin_rpc_auth().await) .body(body.to_string()) diff --git a/core/archipelago/src/federation.rs b/core/archipelago/src/federation.rs index b7b3cfe8..8b07d38d 100644 --- a/core/archipelago/src/federation.rs +++ b/core/archipelago/src/federation.rs @@ -377,7 +377,7 @@ async fn notify_join( } }); - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050").context("Invalid Tor proxy")?; + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) .timeout(std::time::Duration::from_secs(30)) @@ -411,7 +411,7 @@ pub async fn sync_with_peer( "params": {} }); - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050").context("Invalid Tor proxy")?; + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) .timeout(std::time::Duration::from_secs(30)) @@ -552,7 +552,7 @@ pub async fn deploy_to_peer( } }); - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050").context("Invalid Tor proxy")?; + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) .timeout(std::time::Duration::from_secs(120)) diff --git a/core/archipelago/src/identity.rs b/core/archipelago/src/identity.rs index fe5277ea..55f2ac10 100644 --- a/core/archipelago/src/identity.rs +++ b/core/archipelago/src/identity.rs @@ -110,13 +110,15 @@ impl NodeIdentity { /// DID in did:key format (W3C did:key method, Ed25519). /// Format: did:key:z<base58btc(multicodec_ed25519_pub + 32-byte pubkey)> - pub fn did_key(&self) -> String { - did_key_from_pubkey_hex(&self.pubkey_hex()).expect("pubkey_hex is valid") + pub fn did_key(&self) -> Result { + did_key_from_pubkey_hex(&self.pubkey_hex()) + .map_err(|e| anyhow::anyhow!("Invalid pubkey hex: {}", e)) } /// Generate a W3C DID Core v1.0 compliant DID Document. - pub fn did_document(&self) -> serde_json::Value { - did_document_from_pubkey_hex(&self.pubkey_hex()).expect("pubkey_hex is valid") + pub fn did_document(&self) -> Result { + did_document_from_pubkey_hex(&self.pubkey_hex()) + .map_err(|e| anyhow::anyhow!("Invalid pubkey hex: {}", e)) } } @@ -299,7 +301,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let identity = NodeIdentity::load_or_create(&dir.path().join("id")).await.unwrap(); - let did = identity.did_key(); + let did = identity.did_key().unwrap(); assert!(did.starts_with("did:key:z")); } @@ -336,8 +338,8 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let identity = NodeIdentity::load_or_create(&dir.path().join("id")).await.unwrap(); - let doc = identity.did_document(); - let did = identity.did_key(); + let doc = identity.did_document().unwrap(); + let did = identity.did_key().unwrap(); // Verify @context let context = doc["@context"].as_array().unwrap(); diff --git a/core/archipelago/src/main.rs b/core/archipelago/src/main.rs index 4c8d0dcf..951d016e 100644 --- a/core/archipelago/src/main.rs +++ b/core/archipelago/src/main.rs @@ -1,7 +1,7 @@ // Archipelago Bitcoin Node OS - Native Backend // Pure Archipelago implementation, no StartOS dependencies -use anyhow::Result; +use anyhow::{Context, Result}; use std::net::SocketAddr; use tracing::info; use tokio::signal; @@ -9,6 +9,7 @@ use tokio::signal; mod api; mod auth; mod backup; +mod constants; mod bitcoin_rpc; mod config; mod content_server; @@ -122,7 +123,7 @@ async fn main() -> Result<()> { // Start server let addr: SocketAddr = format!("{}:{}", config.bind_host, config.bind_port) .parse() - .expect("Invalid bind address"); + .context("Invalid bind address")?; // Spawn background update scheduler let update_data_dir = config.data_dir.clone(); @@ -155,9 +156,9 @@ async fn main() -> Result<()> { }); // Graceful shutdown: wait for SIGTERM or SIGINT - let shutdown = async { - let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate()) - .expect("Failed to register SIGTERM handler"); + let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate()) + .context("Failed to register SIGTERM handler")?; + let shutdown = async move { tokio::select! { _ = signal::ctrl_c() => { info!("Received SIGINT (Ctrl+C), initiating graceful shutdown..."); diff --git a/core/archipelago/src/mesh/listener.rs b/core/archipelago/src/mesh/listener.rs index 23139c57..47c27f9a 100644 --- a/core/archipelago/src/mesh/listener.rs +++ b/core/archipelago/src/mesh/listener.rs @@ -1506,7 +1506,7 @@ async fn handle_tx_relay_broadcast( }); match client - .post("http://127.0.0.1:8332/") + .post(crate::constants::BITCOIN_RPC_URL) .basic_auth(&rpc_user, Some(&rpc_pass)) .json(&preflight_body) .send() @@ -1559,7 +1559,7 @@ async fn handle_tx_relay_broadcast( }); let txid = match client - .post("http://127.0.0.1:8332/") + .post(crate::constants::BITCOIN_RPC_URL) .basic_auth(&rpc_user, Some(&rpc_pass)) .json(&body) .send() @@ -1783,7 +1783,7 @@ async fn check_tx_confirmations(client: &reqwest::Client, txid: &str) -> anyhow: }); let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await; let resp = client - .post("http://127.0.0.1:8332/") + .post(crate::constants::BITCOIN_RPC_URL) .basic_auth(&rpc_user, Some(&rpc_pass)) .json(&body) .send() diff --git a/core/archipelago/src/mesh/mod.rs b/core/archipelago/src/mesh/mod.rs index 7ccc49f3..1502654e 100644 --- a/core/archipelago/src/mesh/mod.rs +++ b/core/archipelago/src/mesh/mod.rs @@ -4,31 +4,18 @@ //! Supports Meshcore firmware on Heltec V3, T-Beam, RAK WisBlock, Station G2, //! and other ESP32/nRF52-based LoRa boards via USB serial (Companion USB mode). -#[allow(dead_code)] pub mod alerts; -#[allow(dead_code)] pub mod bitcoin_relay; -#[allow(dead_code)] pub mod crypto; -#[allow(dead_code)] pub mod listener; -#[allow(dead_code)] pub mod protocol; -#[allow(dead_code)] pub mod serial; -#[allow(dead_code)] pub mod types; -#[allow(dead_code)] pub mod message_types; -#[allow(dead_code)] pub mod outbox; -#[allow(dead_code)] pub mod ratchet; -#[allow(dead_code)] pub mod session; -#[allow(dead_code)] pub mod steganography; -#[allow(dead_code)] pub mod x3dh; pub use types::*; @@ -154,7 +141,6 @@ pub struct MeshService { pub dead_man_switch: Arc, } -#[allow(dead_code)] impl MeshService { /// Create a new MeshService. Does not start the listener yet. pub async fn new( @@ -623,7 +609,7 @@ async fn bitcoin_rpc_getblockcount(client: &reqwest::Client) -> Result { let resp: BitcoinRpcResponse = tokio::time::timeout( Duration::from_secs(10), client - .post("http://127.0.0.1:8332/") + .post(crate::constants::BITCOIN_RPC_URL) .basic_auth(&rpc_user, Some(&rpc_pass)) .json(&body) .send() @@ -652,7 +638,7 @@ async fn bitcoin_rpc_getblockheader_by_height( let resp: BitcoinRpcResponse = tokio::time::timeout( Duration::from_secs(10), client - .post("http://127.0.0.1:8332/") + .post(crate::constants::BITCOIN_RPC_URL) .basic_auth(&rpc_user, Some(&rpc_pass)) .json(&body) .send() @@ -672,7 +658,7 @@ async fn bitcoin_rpc_getblockheader_by_height( let resp: BitcoinRpcResponse = tokio::time::timeout( Duration::from_secs(10), client - .post("http://127.0.0.1:8332/") + .post(crate::constants::BITCOIN_RPC_URL) .basic_auth(&rpc_user, Some(&rpc_pass)) .json(&body) .send() diff --git a/core/archipelago/src/network/dwn_sync.rs b/core/archipelago/src/network/dwn_sync.rs index 754d65d2..5f126695 100644 --- a/core/archipelago/src/network/dwn_sync.rs +++ b/core/archipelago/src/network/dwn_sync.rs @@ -73,7 +73,7 @@ pub async fn get_dwn_status() -> Result { .context("Failed to build HTTP client")?; let res = client - .get("http://127.0.0.1:3100/health") + .get(crate::constants::DWN_HEALTH_URL) .send() .await .context("DWN server not reachable")?; @@ -108,7 +108,7 @@ pub async fn sync_with_peers(data_dir: &Path, peer_onions: &[String]) -> Result< state.status = SyncStatus::Syncing; save_sync_state(data_dir, &state).await?; - let socks_proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050") + let socks_proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY) .context("Failed to create SOCKS proxy")?; let client = reqwest::Client::builder() diff --git a/core/archipelago/src/node_message.rs b/core/archipelago/src/node_message.rs index 617f6423..f367932b 100644 --- a/core/archipelago/src/node_message.rs +++ b/core/archipelago/src/node_message.rs @@ -7,7 +7,6 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::sync::{Mutex, OnceLock}; -const TOR_SOCKS: &str = "socks5h://127.0.0.1:9050"; const MAX_STORED: usize = 200; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -263,7 +262,7 @@ pub async fn send_to_peer( "encrypted": encrypted, }); - let proxy = reqwest::Proxy::all(TOR_SOCKS).context("Invalid Tor proxy")?; + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) .timeout(std::time::Duration::from_secs(60)) @@ -306,7 +305,7 @@ pub async fn check_peer_reachable(onion: &str) -> Result { format!("{}.onion", onion) }; let url = format!("http://{}/health", host); - let proxy = reqwest::Proxy::all(TOR_SOCKS).context("Invalid Tor proxy")?; + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) .timeout(std::time::Duration::from_secs(30)) diff --git a/core/archipelago/src/transport/tor.rs b/core/archipelago/src/transport/tor.rs index 6527ea1a..782e1ebb 100644 --- a/core/archipelago/src/transport/tor.rs +++ b/core/archipelago/src/transport/tor.rs @@ -8,7 +8,6 @@ use anyhow::{Context, Result}; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; -const TOR_SOCKS: &str = "socks5h://127.0.0.1:9050"; const TOR_TIMEOUT: Duration = Duration::from_secs(60); pub struct TorTransport { @@ -49,7 +48,7 @@ impl TorTransport { "timestamp": chrono::Utc::now().to_rfc3339(), }); - let proxy = reqwest::Proxy::all(TOR_SOCKS).context("Invalid Tor proxy")?; + let proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?; let client = reqwest::Client::builder() .proxy(proxy) .timeout(TOR_TIMEOUT) diff --git a/core/helpers/src/lib.rs b/core/helpers/src/lib.rs index 22678759..b71f42d0 100644 --- a/core/helpers/src/lib.rs +++ b/core/helpers/src/lib.rs @@ -164,12 +164,16 @@ impl AtomicFile { impl std::ops::Deref for AtomicFile { type Target = File; fn deref(&self) -> &Self::Target { - self.file.as_ref().unwrap() + self.file + .as_ref() + .expect("AtomicFile already consumed by save() or rollback()") } } impl std::ops::DerefMut for AtomicFile { fn deref_mut(&mut self) -> &mut Self::Target { - self.file.as_mut().unwrap() + self.file + .as_mut() + .expect("AtomicFile already consumed by save() or rollback()") } } impl Drop for AtomicFile { @@ -177,7 +181,11 @@ impl Drop for AtomicFile { if let Some(file) = self.file.take() { drop(file); let path = std::mem::take(&mut self.tmp_path); - tokio::spawn(async move { tokio::fs::remove_file(path).await.unwrap() }); + tokio::spawn(async move { + if let Err(e) = tokio::fs::remove_file(&path).await { + tracing::warn!("failed to remove tmp file {}: {}", path.display(), e); + } + }); } } } @@ -230,7 +238,13 @@ impl TimedResource { pub async fn get(self) -> Option { let _ = self.ready.send(()); - self.handle.await.unwrap() + match self.handle.await { + Ok(val) => val, + Err(e) => { + tracing::error!("TimedResource task panicked: {}", e); + None + } + } } pub fn is_timed_out(&self) -> bool { @@ -250,7 +264,7 @@ pub async fn spawn_local< tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .unwrap() + .expect("failed to build tokio current-thread runtime for spawn_local") .block_on(async move { let set = LocalSet::new(); send.send(set.spawn_local(fut()).into()) @@ -258,5 +272,6 @@ pub async fn spawn_local< set.await }) }); - recv.await.unwrap() + recv.await + .expect("spawn_local thread terminated before sending task handle") } diff --git a/core/js-engine/src/lib.rs b/core/js-engine/src/lib.rs index b0b9bea3..3d02f99b 100644 --- a/core/js-engine/src/lib.rs +++ b/core/js-engine/src/lib.rs @@ -127,7 +127,7 @@ impl ModuleLoader for ModsLoader { if referrer.contains("embassy") { bail!("Embassy.js cannot import anything else"); } - let s = resolve_import(specifier, referrer).unwrap(); + let s = resolve_import(specifier, referrer)?; Ok(s) } @@ -246,7 +246,10 @@ impl JsExecutionEnvironment { } }; let safer_handle = spawn_local(|| self.execute(procedure_name, input, variable_args)).await; - let output = safer_handle.await.unwrap()?; + let output = safer_handle.await.map_err(|e| { + tracing::error!("JS execution task panicked: {}", e); + (JsError::Engine, format!("JS execution task failed: {}", e)) + })??; match serde_json::from_value(output.clone()) { Ok(x) => Ok(x), Err(err) => {