0.3.18 (app only): the clock sample asks igneum_getExecStatus first and takes no block from a follower without a record (ledger N7)

A node before 0.3.18's exec RPC fix dies on eth_getBlockByNumber when its exec follower holds no record (rpc.rs indexes records[0] on an empty vector; the panic hook exits the process), and this sample ran every 9 s once blocks arrived, so a fresh install's IBD and PC 1's restart crash-looped. The sample now waits for executedTipHash. No node change; the pin stays at 5899f603. Version 0.3.18 on the 0.3.17 app tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 08:23:44 +00:00
parent a7a9e5c231
commit 0f6b4650b6
7 changed files with 46 additions and 10 deletions

View file

@ -219,7 +219,7 @@ dependencies = [
[[package]] [[package]]
name = "igneum-app" name = "igneum-app"
version = "0.3.17" version = "0.3.18"
dependencies = [ dependencies = [
"ed25519-dalek", "ed25519-dalek",
"getrandom", "getrandom",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "igneum-app" name = "igneum-app"
version = "0.3.17" version = "0.3.18"
edition = "2021" edition = "2021"
description = "Igneum Miner engine: supervises the node, the miner and the GPU workers, and serves the dashboard on 127.0.0.1" description = "Igneum Miner engine: supervises the node, the miner and the GPU workers, and serves the dashboard on 127.0.0.1"
license = "MIT" license = "MIT"

View file

@ -6,8 +6,8 @@
1 ICON "igneum.ico" 1 ICON "igneum.ico"
1 VERSIONINFO 1 VERSIONINFO
FILEVERSION 0,3,17,0 FILEVERSION 0,3,18,0
PRODUCTVERSION 0,3,17,0 PRODUCTVERSION 0,3,18,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32 FILEOS VOS_NT_WINDOWS32
@ -20,12 +20,12 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Igneum" VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner engine" VALUE "FileDescription", "Igneum Miner engine"
VALUE "FileVersion", "0.3.17" VALUE "FileVersion", "0.3.18"
VALUE "InternalName", "igneum-app" VALUE "InternalName", "igneum-app"
VALUE "LegalCopyright", "Igneum contributors" VALUE "LegalCopyright", "Igneum contributors"
VALUE "OriginalFilename", "igneum-app.exe" VALUE "OriginalFilename", "igneum-app.exe"
VALUE "ProductName", "Igneum Miner" VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.3.17" VALUE "ProductVersion", "0.3.18"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -40,9 +40,36 @@ fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
era * 146_097 + doe - 719_468 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::<serde_json::Value>(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 /// 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. /// consensus block times). The first clock source: local time against what the peers produced.
pub fn latest_block_time(evm_port: u16) -> Option<f64> { pub fn latest_block_time(evm_port: u16) -> Option<f64> {
// 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 body = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getBlockByNumber\",\"params\":[\"latest\",false]}";
let out = crate::detect::run_timeout( 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]), 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<f64> {
#[cfg(test)] #[cfg(test)]
mod tests { 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] #[test]
fn http_date() { fn http_date() {
assert_eq!(super::parse_http_date("Sun, 04 Oct 2026 11:17:47 GMT"), Some(1_791_112_667.0)); assert_eq!(super::parse_http_date("Sun, 04 Oct 2026 11:17:47 GMT"), Some(1_791_112_667.0));

View file

@ -3,6 +3,6 @@
// packaging/windows/Igneum-Miner.iss when the app version moves. Include guards, not #pragma once: rc.exe reads it too. // 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 #ifndef IGNEUM_HOST_VERSION_H
#define IGNEUM_HOST_VERSION_H #define IGNEUM_HOST_VERSION_H
#define IGNEUM_HOST_VERSION_STR "0.3.17" #define IGNEUM_HOST_VERSION_STR "0.3.18"
#define IGNEUM_HOST_VERSION_RC 0,3,17,0 #define IGNEUM_HOST_VERSION_RC 0,3,18,0
#endif #endif

View file

@ -11,7 +11,7 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>VERSION_STAMP</string> <string>VERSION_STAMP</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.3.17</string> <string>0.3.18</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>

View file

@ -9,7 +9,7 @@
#define ArtDir "..\..\brand\icons" #define ArtDir "..\..\brand\icons"
#endif #endif
#ifndef AppVersion #ifndef AppVersion
#define AppVersion "0.3.17" #define AppVersion "0.3.18"
#endif #endif
#define AppName "Igneum Miner" #define AppName "Igneum Miner"
#define Publisher "Igneum" #define Publisher "Igneum"