diff --git a/app/igneum-app/Cargo.lock b/app/igneum-app/Cargo.lock index 373848fd..c1f16da5 100644 --- a/app/igneum-app/Cargo.lock +++ b/app/igneum-app/Cargo.lock @@ -219,7 +219,7 @@ dependencies = [ [[package]] name = "igneum-app" -version = "0.3.17" +version = "0.3.18" dependencies = [ "ed25519-dalek", "getrandom", diff --git a/app/igneum-app/Cargo.toml b/app/igneum-app/Cargo.toml index 97ebc53a..f9ea86a8 100644 --- a/app/igneum-app/Cargo.toml +++ b/app/igneum-app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "igneum-app" -version = "0.3.17" +version = "0.3.18" edition = "2021" description = "Igneum Miner engine: supervises the node, the miner and the GPU workers, and serves the dashboard on 127.0.0.1" license = "MIT" diff --git a/app/igneum-app/resources/igneum-app.rc b/app/igneum-app/resources/igneum-app.rc index f94d8b68..ed47e205 100644 --- a/app/igneum-app/resources/igneum-app.rc +++ b/app/igneum-app/resources/igneum-app.rc @@ -6,8 +6,8 @@ 1 ICON "igneum.ico" 1 VERSIONINFO -FILEVERSION 0,3,17,0 -PRODUCTVERSION 0,3,17,0 +FILEVERSION 0,3,18,0 +PRODUCTVERSION 0,3,18,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS VOS_NT_WINDOWS32 @@ -20,12 +20,12 @@ BEGIN BEGIN VALUE "CompanyName", "Igneum" VALUE "FileDescription", "Igneum Miner engine" - VALUE "FileVersion", "0.3.17" + VALUE "FileVersion", "0.3.18" VALUE "InternalName", "igneum-app" VALUE "LegalCopyright", "Igneum contributors" VALUE "OriginalFilename", "igneum-app.exe" VALUE "ProductName", "Igneum Miner" - VALUE "ProductVersion", "0.3.17" + VALUE "ProductVersion", "0.3.18" END END BLOCK "VarFileInfo" diff --git a/app/igneum-app/src/update.rs b/app/igneum-app/src/update.rs index 16fdb0f5..ef2ce46e 100644 --- a/app/igneum-app/src/update.rs +++ b/app/igneum-app/src/update.rs @@ -40,9 +40,36 @@ fn days_from_civil(y: i64, m: i64, d: i64) -> i64 { era * 146_097 + doe - 719_468 } +/// 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. +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) +} + +/// 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) +} + /// 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]), @@ -56,6 +83,15 @@ pub fn latest_block_time(evm_port: u16) -> Option { #[cfg(test)] mod tests { + /// Ledger N7: the clock sample must not ask eth_getBlockByNumber of a follower with no record + #[test] + fn exec_status_gate() { + assert!(super::exec_status_has_record(r#"{"jsonrpc":"2.0","id":1,"result":{"executedTip":"0x38a5","executedTipHash":"0xa3ae37ec31b9227c3855a1b25ba50ee9f6815bf00b0befcc8836b430baf04343"}}"#)); + assert!(!super::exec_status_has_record(r#"{"jsonrpc":"2.0","id":1,"result":{"executedTip":"0x0","executedTipHash":null}}"#)); + assert!(!super::exec_status_has_record(r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"method not found"}}"#)); + assert!(!super::exec_status_has_record("")); + assert!(!super::exec_status_has_record("not json")); + } #[test] fn http_date() { assert_eq!(super::parse_http_date("Sun, 04 Oct 2026 11:17:47 GMT"), Some(1_791_112_667.0)); diff --git a/app/windows/version.h b/app/windows/version.h index a1ef426e..44191cd9 100644 --- a/app/windows/version.h +++ b/app/windows/version.h @@ -3,6 +3,6 @@ // packaging/windows/Igneum-Miner.iss when the app version moves. Include guards, not #pragma once: rc.exe reads it too. #ifndef IGNEUM_HOST_VERSION_H #define IGNEUM_HOST_VERSION_H -#define IGNEUM_HOST_VERSION_STR "0.3.17" -#define IGNEUM_HOST_VERSION_RC 0,3,17,0 +#define IGNEUM_HOST_VERSION_STR "0.3.18" +#define IGNEUM_HOST_VERSION_RC 0,3,18,0 #endif diff --git a/packaging/mac/app/Info.plist b/packaging/mac/app/Info.plist index e8cc562f..04b07315 100644 --- a/packaging/mac/app/Info.plist +++ b/packaging/mac/app/Info.plist @@ -11,7 +11,7 @@ CFBundleVersion VERSION_STAMP CFBundleShortVersionString - 0.3.17 + 0.3.18 CFBundlePackageType APPL CFBundleExecutable diff --git a/packaging/windows/Igneum-Miner.iss b/packaging/windows/Igneum-Miner.iss index effc9c8b..1bcc37d4 100644 --- a/packaging/windows/Igneum-Miner.iss +++ b/packaging/windows/Igneum-Miner.iss @@ -9,7 +9,7 @@ #define ArtDir "..\..\brand\icons" #endif #ifndef AppVersion - #define AppVersion "0.3.17" + #define AppVersion "0.3.18" #endif #define AppName "Igneum Miner" #define Publisher "Igneum"