diff --git a/app/igneum-app/src/extnode.rs b/app/igneum-app/src/extnode.rs index 25764ac1..91aee55b 100644 --- a/app/igneum-app/src/extnode.rs +++ b/app/igneum-app/src/extnode.rs @@ -8,7 +8,6 @@ //! too, read every 30 s instead of parsed from a stdout the app does not own. use serde_json::{json, Value}; use std::path::Path; -use std::process::Command; use std::time::Duration; /// What the other node answered: None where it could not answer. @@ -110,14 +109,8 @@ pub fn step(port_open: bool, now_s: f64, gone_since: &mut Option) -> Step { /// One JSON-RPC call to the node's EVM port through curl (the engine carries no HTTP client; update.rs does the same). pub fn rpc(evm_port: u16, method: &str, params: Value, limit: Duration) -> Option { - let body = json!({ "jsonrpc": "2.0", "id": 1, "method": method, "params": params }).to_string(); - let out = crate::detect::run_timeout( - Command::new(crate::platform::tool("curl")).args(["-s", "--max-time", &format!("{}", limit.as_secs().max(1)), "-X", "POST", &format!("http://127.0.0.1:{evm_port}"), "-H", "Content-Type: application/json", "-d", &body]), - None, - limit + Duration::from_secs(2), - )?; - let v: Value = serde_json::from_str(&out).ok()?; - v.get("result").cloned().filter(|r| !r.is_null()) + // one path (ledger N7): execrpc holds a records-indexing method until the node's exec follower has a record + crate::execrpc::call(evm_port, method, params, limit).ok().filter(|r| !r.is_null()) } /// The other node's answers, with what each method gives: eth_chainId (every node), igneum_getNodeInfo (newer nodes). diff --git a/app/igneum-app/src/main.rs b/app/igneum-app/src/main.rs index 0e0a11bb..51aef925 100644 --- a/app/igneum-app/src/main.rs +++ b/app/igneum-app/src/main.rs @@ -26,6 +26,7 @@ mod manifest; mod ota; mod uiota; mod update; +mod execrpc; mod jobs; mod jobrun; mod jobbuild; diff --git a/app/igneum-app/src/prover.rs b/app/igneum-app/src/prover.rs index 47ad1caf..724fc12d 100644 --- a/app/igneum-app/src/prover.rs +++ b/app/igneum-app/src/prover.rs @@ -168,22 +168,8 @@ fn exec_boundary(shared: &Shared) -> u64 { } pub(crate) fn evm_rpc(shared: &Shared, method: &str, params: Value, timeout: Duration) -> Result { - let body = json!({ "jsonrpc": "2.0", "id": 1, "method": method, "params": params }).to_string(); - let tmp = std::env::temp_dir().join(format!("igneum-prover-{}-{}.json", std::process::id(), method)); - std::fs::write(&tmp, body).map_err(|e| e.to_string())?; - let url = format!("http://127.0.0.1:{}", shared.runtime.evm_port()); - let out = crate::detect::run_timeout( - Command::new(crate::platform::tool("curl")).args(["-s", "--max-time", &timeout.as_secs().to_string(), "-X", "POST", &url, "-H", "Content-Type: application/json", "--data-binary", &format!("@{}", tmp.display())]), - None, - timeout + Duration::from_secs(2), - ); - let _ = std::fs::remove_file(&tmp); - let out = out.ok_or_else(|| format!("{method}: the node's RPC did not answer"))?; - let v: Value = serde_json::from_str(&out).map_err(|e| format!("{method}: {e}"))?; - if let Some(err) = v.get("error") { - return Err(format!("{method}: {}", err.get("message").and_then(|m| m.as_str()).unwrap_or("error"))); - } - Ok(v.get("result").cloned().unwrap_or(Value::Null)) + // one path (ledger N7): execrpc holds a records-indexing method until the node's exec follower has a record + crate::execrpc::call(shared.runtime.evm_port(), method, params, timeout) } fn find_tools(bin_dir: &Path) -> Result { diff --git a/app/igneum-app/src/update.rs b/app/igneum-app/src/update.rs index 2978d880..50b06136 100644 --- a/app/igneum-app/src/update.rs +++ b/app/igneum-app/src/update.rs @@ -43,41 +43,20 @@ fn days_from_civil(y: i64, m: i64, d: i64) -> i64 { /// True once the node's exec follower holds a record (igneum_getExecStatus's executedTipHash is set). Any error or an /// unreachable node reads false: the block sample waits rather than asks. pub fn exec_has_record(evm_port: u16) -> bool { - let body = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"igneum_getExecStatus\",\"params\":[]}"; - let out = crate::detect::run_timeout( - Command::new(crate::platform::tool("curl")).args(["-s", "--max-time", "5", "-X", "POST", &format!("http://127.0.0.1:{evm_port}"), "-H", "Content-Type: application/json", "-d", body]), - None, - Duration::from_secs(7), - ); - out.as_deref().map(exec_status_has_record).unwrap_or(false) + crate::execrpc::has_record(evm_port) } /// The reading of an igneum_getExecStatus reply: a record is held when executedTipHash is a non-null string. pub fn exec_status_has_record(reply: &str) -> bool { - serde_json::from_str::(reply) - .ok() - .and_then(|v| v.get("result")?.get("executedTipHash")?.as_str().map(|h| !h.is_empty())) - .unwrap_or(false) + serde_json::from_str::(reply).ok().map(|v| crate::execrpc::status_has_record(v.get("result").unwrap_or(&serde_json::Value::Null))).unwrap_or(false) } /// The latest block's timestamp (unix seconds) from the node's Ethereum JSON-RPC (the execution layer mirrors the /// consensus block times). The first clock source: local time against what the peers produced. pub fn latest_block_time(evm_port: u16) -> Option { - // 0.3.18 (ledger N7, 7 October 2026): a node whose exec follower holds no record yet dies on eth_getBlockByNumber - // (rpc.rs indexes records[0] on an empty vector and the panic hook exits the process). The nodes before 0.3.18's - // fix are live on every machine, and this sample runs every 9 s once blocks arrive, so it asks igneum_getExecStatus - // first (it indexes nothing) and takes no block until the follower reports an executed tip. - if !exec_has_record(evm_port) { - return None; - } - let body = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getBlockByNumber\",\"params\":[\"latest\",false]}"; - let out = crate::detect::run_timeout( - Command::new(crate::platform::tool("curl")).args(["-s", "--max-time", "5", "-X", "POST", &format!("http://127.0.0.1:{evm_port}"), "-H", "Content-Type: application/json", "-d", body]), - None, - Duration::from_secs(7), - )?; - let v: serde_json::Value = serde_json::from_str(&out).ok()?; - let ts = v.get("result")?.get("timestamp")?.as_str()?; + // 0.3.18/0.3.19 (ledger N7): through the one gate, which asks nothing of a follower without a record + let block = crate::execrpc::call(evm_port, "eth_getBlockByNumber", serde_json::json!(["latest", false]), Duration::from_secs(5)).ok()?; + let ts = block.get("timestamp")?.as_str()?; u64::from_str_radix(ts.trim_start_matches("0x"), 16).ok().map(|t| t as f64) }