diff --git a/app/igneum-app/src/prover.rs b/app/igneum-app/src/prover.rs index a1ecc6e6..1d2140bb 100644 --- a/app/igneum-app/src/prover.rs +++ b/app/igneum-app/src/prover.rs @@ -135,6 +135,22 @@ struct Tools { /// The node's re-derivation boundary (0.3.14, 6 October 2026): the chain block its EVM restarted at after the /// bodies below the pruning point were gone (`igneum_getExecStatus.restartNumber`; on a 0.3.13 node the /// `startedFrom` text "restart at chain block N"), else 0. The prover never claims work below it (no bodies, no +/// The exporter's failure line. When the node's export carries the account dump (0.3.14) and the exporter's output +/// never mentions it, the exporter at `path` predates 0.3.14: it replays the dump's own segment from the restart +/// state and fails there with "port state root differs" (PC 1, 6 October 2026 18:16Z, block 140,662: the installer +/// had not replaced igneum-prove-export); the line names the stale binary instead of the misleading root error. +fn exporter_failure(export_has_dump: bool, out: &str, path: &Path, block: Option) -> String { + let last = out.lines().rev().find(|l| !l.trim().is_empty()).unwrap_or("failed"); + let what = match block { + Some(b) => format!("exporter, block {b}"), + None => "exporter".to_string(), + }; + if export_has_dump && !out.contains("account dump") { + return format!("{what}: stale igneum-prove-export at {}: the node's export carries the account dump and this exporter does not read it (it predates 0.3.14); install this release's exporter there ({last})", path.display()); + } + format!("{what}: {last}") +} + /// state: unprovable on every node). fn exec_boundary(shared: &Shared) -> u64 { let st = match evm_rpc(shared, "igneum_getExecStatus", json!([]), Duration::from_secs(5)) { @@ -665,7 +681,7 @@ fn loop_forever(shared: Arc, bin_dir: PathBuf) { let (seq_p, fix_p, res_p) = if t.wsl { (wsl_path(&seq), wsl_path(&fixture), wsl_path(&results)) } else { (seq.display().to_string(), fixture.display().to_string(), results.display().to_string()) }; let (ok, out) = run_tool(&shared, t, &t.export, &[seq_p, w.number.to_string(), fix_p.clone()], &[], Duration::from_secs(600), &dir.join(format!("export-{}.log", w.number))); if !ok || !fixture.exists() { - return Err(format!("exporter: {}", out.lines().rev().find(|l| !l.trim().is_empty()).unwrap_or("failed"))); + return Err(exporter_failure(export["preState"].is_array(), &out, &t.export, None)); } set(&shared, |p| p.message = if t.cuda { "proving on the GPU".into() } else { "CPU prover: about five minutes a shard, 30 GB of RAM, paid only when no card proves first".into() }); let prover_env = if t.cuda { "cuda" } else { "cpu" }; @@ -854,7 +870,7 @@ fn prove_segment(shared: &Shared, t: &Tools, seg: &crate::segments::SegmentWork, let fixture = dir.join(format!("block-{b}.json")); let (ok, out) = run_tool(shared, t, &t.export, &[as_host_path(&seq), b.to_string(), as_host_path(&fixture)], &[], Duration::from_secs(600), &dir.join(format!("export-{b}.log"))); if !ok || !fixture.exists() { - return Err(format!("exporter, block {b}: {}", out.lines().rev().find(|l| !l.trim().is_empty()).unwrap_or("failed"))); + return Err(exporter_failure(export["preState"].is_array(), &out, &t.export, Some(b))); } fixtures.push(as_host_path(&fixture)); } @@ -1092,6 +1108,21 @@ pub fn setup(shared: &Shared) -> Result { mod tests { use super::*; + #[test] + fn a_stale_exporter_is_named_when_the_export_carries_the_dump() { + let p = Path::new("C:/x/igneum-prove-export.exe"); + let old_out = "Error: segment 140661: port state root 0xe45c differs from the node's 0xddd7; the port is not the node's executor, stop\n"; + let line = exporter_failure(true, old_out, p, Some(140662)); + assert!(line.contains("stale igneum-prove-export at C:/x/igneum-prove-export.exe"), "{line}"); + assert!(line.starts_with("exporter, block 140662:")); + // the 0.3.14 exporter read the dump and failed later: the real line, not the stale one + let new_out = "account dump: 79 accounts after chain block 140661, state root 0x1 (equals the node's)\nError: segment 140662: port state root 0x2 differs from the node's 0x3\n"; + let line = exporter_failure(true, new_out, p, None); + assert_eq!(line, "exporter: Error: segment 140662: port state root 0x2 differs from the node's 0x3"); + // an export without a dump (an older node): never the stale line + assert_eq!(exporter_failure(false, old_out, p, None), format!("exporter: {}", old_out.trim())); + } + #[test] fn pinned_ids_come_out_of_the_hosts_id_line() { let out = "RESULT id: pinned guests: shard program id 0x2b1a81cb413236cf063077b46ed3111628f6c41036bcf6e23ee4cbbf5679ef7a (2832504 bytes, sha256 0x150f4c05a2951fc5) aggregator id 0x474678f35f7545db28055d5e5bbc308231d84a5a072202087a2a8d5b09123896 (319744 bytes), pinned 2026-10-05T16:20:38Z on Darwin, SP1 5.0 circuit v5\n";