0.3.22, Devnet 3 (7 October 2026): the package names the network its node joins (igneum-app.json node_devnet_suffix and node_peers; Runtime::from_env_with reads them when the environment is silent, node_dir devnet-N beside devnet-v4), no override file on a suffixed devnet from 3 (the node refuses it; the manifest's consensus override ignored with one log line), the version strings; app gate on build-2 258 + 32 + 8

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 16:49:21 +00:00
parent 19441689c3
commit ddb3ef96af
9 changed files with 63 additions and 14 deletions

View file

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

View file

@ -1,6 +1,6 @@
[package]
name = "igneum-app"
version = "0.3.21"
version = "0.3.22"
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"

View file

@ -6,8 +6,8 @@
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,3,21,0
PRODUCTVERSION 0,3,21,0
FILEVERSION 0,3,22,0
PRODUCTVERSION 0,3,22,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.21"
VALUE "FileVersion", "0.3.22"
VALUE "InternalName", "igneum-app"
VALUE "LegalCopyright", "Igneum contributors"
VALUE "OriginalFilename", "igneum-app.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.3.21"
VALUE "ProductVersion", "0.3.22"
END
END
BLOCK "VarFileInfo"

View file

@ -278,6 +278,14 @@ pub struct Packaged {
/// height, 4 October 2026: `{"difficulty_v2_activation_daa": N}`). Absent or empty = no override file.
#[serde(default)]
pub node_override_params: Option<serde_json::Value>,
/// The network the package's node joins (7 October 2026, Devnet 3): a suffixed devnet (`--devnet-suffix=N`), its own
/// genesis and p2p port, its own node datadir devnet-N beside the shared devnet's devnet-v4 (kept for the way back).
/// Absent = the shared devnet. IGNEUM_APP_DEVNET_SUFFIX in the environment wins over it.
#[serde(default)]
pub node_devnet_suffix: Option<u32>,
/// The package's default peers for that network (host:port); absent = the shared devnet's list.
#[serde(default)]
pub node_peers: Option<Vec<String>>,
/// Where the key and the manifest came from, for the log header: "packaged", "file <name>" or "none". Never
/// serialised (the packaged file does not carry them; nothing sends this struct to the UI).
#[serde(skip)]
@ -403,6 +411,11 @@ pub struct Runtime {
impl Runtime {
pub fn from_env() -> Runtime {
Self::from_env_with(None, None)
}
/// `packaged_suffix` and `packaged_peers` are the package's network (config.rs Packaged); the environment wins.
pub fn from_env_with(packaged_suffix: Option<u32>, packaged_peers: Option<&[String]>) -> Runtime {
let env = |k: &str| std::env::var(k).ok().filter(|v| !v.is_empty());
let network = env("IGNEUM_APP_NETWORK").unwrap_or_else(|| "devnet".into());
let rpc_port = env("IGNEUM_APP_RPC_PORT").and_then(|v| v.parse().ok()).unwrap_or(26610);
@ -411,11 +424,12 @@ impl Runtime {
Ok(v) => v.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect(),
// the public seed node first, then the build box's hand nodes (main's decision (b), 6 October 2026, 19:1x UTC:
// the hands move off the project lead's Mac to igneum-build-1), then the project lead's Mac on the house LAN (devnet only)
Err(_) if packaged_peers.map(|p| !p.is_empty()).unwrap_or(false) => packaged_peers.unwrap().to_vec(),
Err(_) if network == "devnet" => vec!["188.245.5.161:26611".to_string(), "188.40.146.49:26611".to_string(), "192.168.68.64:26611".to_string()],
Err(_) => vec![],
};
let unsynced_mining = env("IGNEUM_APP_UNSYNCED").map(|v| v == "1").unwrap_or(false);
let devnet_suffix = env("IGNEUM_APP_DEVNET_SUFFIX").and_then(|v| v.parse().ok());
let devnet_suffix = env("IGNEUM_APP_DEVNET_SUFFIX").and_then(|v| v.parse().ok()).or(packaged_suffix);
let root = crate::platform::data_root();
let node_dir = match env("IGNEUM_APP_NODE_DIR") {
Some(d) => PathBuf::from(d),
@ -596,4 +610,19 @@ mod fixture_tests {
Err(e) => panic!("the crate refuses the file: {e}"),
}
}
#[test]
fn the_package_names_the_network_when_the_environment_is_silent() {
use super::Runtime;
// the test never sets IGNEUM_APP_DEVNET_SUFFIX or IGNEUM_APP_PEERS, so the package's values win
let peers = vec!["188.40.146.49:26631".to_string(), "188.40.146.49:26671".to_string()];
let r = Runtime::from_env_with(Some(3), Some(&peers));
assert_eq!(r.devnet_suffix, Some(3));
assert_eq!(r.peers, peers);
assert!(r.node_dir.ends_with("devnet-3"), "{}", r.node_dir.display());
// no package network: the shared devnet as before
let r = Runtime::from_env_with(None, None);
assert_eq!(r.devnet_suffix, None);
assert!(r.node_dir.ends_with("devnet-v4"));
}
}

View file

@ -802,6 +802,8 @@ pub struct Engine {
fault_reports: Vec<Instant>,
/// MF-6: the job that took the miners hold, when, and its cap in seconds
job_hold_owner: Option<String>,
/// the one log line about the ignored override on a suffixed devnet (node_override_file)
override_ignored_logged: std::sync::atomic::AtomicBool,
/// the choices that give the under-12 GB cards' miners back when the prover stops (settings.prove_instead)
prove_hold_restore: Option<Vec<CardChoice>>,
job_hold_since: Option<Instant>,
@ -928,6 +930,7 @@ impl Engine {
node_settled: false,
fault_reports: Vec::new(),
job_hold_owner: None,
override_ignored_logged: std::sync::atomic::AtomicBool::new(false),
prove_hold_restore: None,
job_hold_since: None,
job_hold_cap_s: 3600.0,
@ -2041,6 +2044,15 @@ impl Engine {
if self.node_override_unusable {
return None;
}
// Devnet 3 and every suffixed devnet from 3 (7 October 2026): every upgrade is on from block zero in the network
// object and the node refuses --override-params-file there, so no file is written whatever the manifest or the
// package carries; the manifest's consensus.override is for the shared devnet and is ignored here, once in the log
if matches!(self.shared.runtime.devnet_suffix, Some(n) if n >= 3) {
if !self.override_ignored_logged.swap(true, std::sync::atomic::Ordering::Relaxed) {
self.shared.log(&format!("node: igneum-devnet-{} carries every parameter in its network object; the manifest's consensus override is ignored here", self.shared.runtime.devnet_suffix.unwrap_or(0)));
}
return None;
}
// the signed OTA manifest's consensus override wins over the packager's pin (src/ota.rs write_override)
if let Some(p) = self.ota.override_path() {
return Some(p);

View file

@ -114,6 +114,14 @@ fn main() {
}
}
let packaged = config::Packaged::load(&candidates).with_env_overrides();
// Devnet 3 (7 October 2026): the package names the network its node joins; the runtime read above knew only the
// environment, so it is read again with the package's suffix and peers (the environment still wins inside)
let runtime = if packaged.node_devnet_suffix.is_some() || packaged.node_peers.is_some() {
config::Runtime::from_env_with(packaged.node_devnet_suffix, packaged.node_peers.as_deref())
} else {
runtime
};
let _ = std::fs::create_dir_all(&runtime.app_dir);
let settings = config::Settings::load(&runtime.app_dir.join("settings.json"));
// a measurement engine runs with the installed app's choices and its own switches (config.rs for_measurement)
let settings = if sweep { settings.for_measurement() } else { settings };

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.
#ifndef IGNEUM_HOST_VERSION_H
#define IGNEUM_HOST_VERSION_H
#define IGNEUM_HOST_VERSION_STR "0.3.21"
#define IGNEUM_HOST_VERSION_RC 0,3,21,0
#define IGNEUM_HOST_VERSION_STR "0.3.22"
#define IGNEUM_HOST_VERSION_RC 0,3,22,0
#endif

View file

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

View file

@ -9,7 +9,7 @@
#define ArtDir "..\..\brand\icons"
#endif
#ifndef AppVersion
#define AppVersion "0.3.21"
#define AppVersion "0.3.22"
#endif
#define AppName "Igneum Miner"
#define Publisher "Igneum"
@ -85,7 +85,7 @@ Name: "{autodesktop}\Igneum Miner"; Filename: "{app}\igneum-app.exe"; Parameters
; Started as the signed-in user, not as administrator (the data lands in that user's %LOCALAPPDATA%).
Filename: "{app}\igneum-app.exe"; Parameters: "--launch"; Description: "Start Igneum Miner now"; Flags: postinstall nowait skipifsilent runasoriginaluser
; The over-the-air updater (packaging/ota, src/ota.rs) runs this installer /VERYSILENT /IGNOTA=1 and the app must come back by itself.
; /IGNOTA=2 (helpers from 0.3.21 on, MF-11): the helper starts the app itself, polls the new engine and restores the kept
; /IGNOTA=2 (helpers from 0.3.22 on, MF-11): the helper starts the app itself, polls the new engine and restores the kept
; exe set when nothing answers; this entry stays silent so the two never race for the single-instance window.
Filename: "{app}\igneum-app.exe"; Parameters: "--launch"; Flags: nowait runasoriginaluser; Check: OtaRelaunch
@ -97,8 +97,8 @@ Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -Fil
Type: filesandordirs; Name: "{app}"
[Code]
// /IGNOTA=1: the app's own updater (before 0.3.21) started this install; relaunch the app when the files are in.
// /IGNOTA=2: the 0.3.21 helper started it and relaunches the app itself (src/ota.rs WIN_HELPER): no relaunch here.
// /IGNOTA=1: the app's own updater (before 0.3.22) started this install; relaunch the app when the files are in.
// /IGNOTA=2: the 0.3.22 helper started it and relaunches the app itself (src/ota.rs WIN_HELPER): no relaunch here.
function OtaRelaunch: Boolean;
begin
Result := ExpandConstant('{param:IGNOTA|0}') = '1';