diff --git a/app/igneum-app/src/extnode.rs b/app/igneum-app/src/extnode.rs index e4d512f11..dd6681d26 100644 --- a/app/igneum-app/src/extnode.rs +++ b/app/igneum-app/src/extnode.rs @@ -1,7 +1,7 @@ //! Another node on this machine (publish 2's read-back, 6 October 2026): the hand node on the Mac held 26610/26611, //! the app's default RPC and p2p ports, the app read its state as its own and the digest field read empty. Now the //! collision is loud on every surface and checked: the other node's chain id (eth_chainId on the EVM port) and its -//! consensus overrides (`igneum_getNodeInfo`, the 0.3.17 node lane; "method not found" on an older node) against the +//! consensus overrides (`igneum_getNodeInfo`, the node lane, 7 October 2026; "method not found" on an older node) against the //! override file the app would start its own node with. A match: "Another node holds port N on this machine; using //! it". A mismatch: "Node not started: port N is taken" and the app does not read that node. Unknown (an older node //! that cannot answer): used, and said so. The same RPC gives the digest, so api/state carries it for an external node @@ -69,7 +69,7 @@ pub fn decide_on(port: u16, ours: Option<&Value>, our_chain: Option, our_ne if checked { Decision { use_it: true, verdict: "match", line: format!("Another node holds port {port} on this machine; using it (same network and rules; it is not stopped by this app)") } } else { - Decision { use_it: true, verdict: "unknown", line: format!("Another node holds port {port} on this machine; using it. Its rules could not be checked (it predates 0.3.17), so the app reads it as it is") } + Decision { use_it: true, verdict: "unknown", line: format!("Another node holds port {port} on this machine; using it. Its rules could not be checked (an older node that lacks igneum_getNodeInfo), so the app reads it as it is") } } } @@ -117,7 +117,7 @@ pub fn rpc(evm_port: u16, method: &str, params: Value, limit: Duration) -> Optio v.get("result").cloned().filter(|r| !r.is_null()) } -/// The other node's answers, with what each method gives: eth_chainId (every node), igneum_getNodeInfo (0.3.17). +/// The other node's answers, with what each method gives: eth_chainId (every node), igneum_getNodeInfo (newer nodes). pub fn probe(evm_port: u16) -> Check { let mut c = Check::default(); if let Some(Value::String(h)) = rpc(evm_port, "eth_chainId", json!([]), Duration::from_secs(4)) { diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js index e250f158b..2ecb21056 100644 --- a/app/igneum-app/ui/app.js +++ b/app/igneum-app/ui/app.js @@ -345,7 +345,7 @@ var View = (function () { function effOf(cd) { if (cd.eff_mhw > 0) return cd.eff_mhw; return cd.power_w > 0 && cd.hash_now > 0 ? cd.hash_now / cd.power_w : 0; } function effText(e) { return e > 0 ? (e >= 10 ? e.toFixed(1) : e.toFixed(2)) + ' MH/W' : ''; } // the watts drawn now by the cards that are mining or tuning (an idle card's standby draw is not the fleet's cost; - // the 0.3.17 engine's mining.watts_total counts the same way) + // the engine's mining.watts_total counts the same way) function wattsTotal(cards) { return (cards || []).filter(function (c) { return present(c) && (c.state === 'mining' || c.state === 'tuning'); }).reduce(function (a, c) { return a + (c.power_w > 0 ? c.power_w : 0); }, 0); } // why a row has no power or temperature cell: one sentence, once, instead of three n/a function noReading(cd) { @@ -755,7 +755,7 @@ var View = (function () { if (src === 'site' || src === 'observer') return 'From Igneum’s observer. Your blocks are ringed.'; return ''; } - // finality (ember-tune 1357d280, folded in for 0.3.17): f = state.finality. While the network's finality is paused + // finality (ember-tune 1357d280, folded in at the rebase onto a9eb58f1): f = state.finality. While the network's finality is paused // (under two thirds of the weight signing) the node line's sub-text and the lock row say so, and no surface calls a // lock "final" (main, Q83/Q84). {paused, age, note} function finalityWords(f) { @@ -768,7 +768,7 @@ var View = (function () { function nodeSource(n) { n = n || {}; if (n.source === 'app') return { text: 'this app’s own node', sub: n.mode_reason || n.port_note || '' }; - if (n.source === 'external') return { text: 'another node on this machine', sub: n.rules_check === 'match' ? 'same network and rules, checked' : n.rules_check === 'unknown' ? 'its rules could not be checked (it predates 0.3.17)' : (n.port_note || '') }; + if (n.source === 'external') return { text: 'another node on this machine', sub: n.rules_check === 'match' ? 'same network and rules, checked' : n.rules_check === 'unknown' ? 'its rules could not be checked (an older node)' : (n.port_note || '') }; if (n.source === 'none') return { text: 'no node', sub: n.port_note || n.mode_reason || 'the ports are taken by a node on other rules' }; return { text: '', sub: '' }; } diff --git a/app/igneum-app/ui/view.test.mjs b/app/igneum-app/ui/view.test.mjs index 873b05598..3749e932a 100644 --- a/app/igneum-app/ui/view.test.mjs +++ b/app/igneum-app/ui/view.test.mjs @@ -454,7 +454,7 @@ test('finality paused (ember-tune 1357d280): the words, no lock called final whi test('whose node (publish 2): the app’s own, another node on this machine checked or unchecked, or none when the ports are taken by other rules', () => { assert.deepEqual(V.nodeSource({ source: 'app' }), { text: 'this app’s own node', sub: '' }); assert.deepEqual(V.nodeSource({ source: 'external', rules_check: 'match' }), { text: 'another node on this machine', sub: 'same network and rules, checked' }); - assert.deepEqual(V.nodeSource({ source: 'external', rules_check: 'unknown' }), { text: 'another node on this machine', sub: 'its rules could not be checked (it predates 0.3.17)' }); + assert.deepEqual(V.nodeSource({ source: 'external', rules_check: 'unknown' }), { text: 'another node on this machine', sub: 'its rules could not be checked (an older node)' }); assert.deepEqual(V.nodeSource({ source: 'none', port_note: 'Node not started: port 26611 is taken by a node on another network (chain id 7778, ours 7777)' }), { text: 'no node', sub: 'Node not started: port 26611 is taken by a node on another network (chain id 7778, ours 7777)' }); assert.deepEqual(V.nodeSource({}), { text: '', sub: '' }); // 7 October 2026: the mode is re-decided; after the other node leaves for 60 s the app runs its own and says why diff --git a/docs/plans/miner-ui-4.md b/docs/plans/miner-ui-4.md index 8fbb0d441..cc5bfcfb8 100644 --- a/docs/plans/miner-ui-4.md +++ b/docs/plans/miner-ui-4.md @@ -93,14 +93,14 @@ file is superseded and its `source` word is "site" too, so nothing in the UI cha Tests: UI 39; the app crate on the box 152 passed (the N4 pair, the shot path, the four live tests among them). -## 9. 0.3.17 fold (7 October 2026, 23:20 UK) +## 9. The rebased fold (7 October 2026, 23:20 UK; renumbered: tonight's 0.3.17 is a node-only hotfix on the 0.3.16 app tree, and this branch at afc331bd ships as 0.3.18 on release-0.3.18) - Rebased onto a9eb58f1 (origin/release-0.3.15). ui/ comes wholly from this branch; Ember's finality-paused hunk (ember-tune 1357d280) is folded in as `View.finalityWords(f)`: while finality is paused the node sub-line and the lock row say "paused" and nothing calls a lock final. Test in view.test.mjs (41 UI tests). - src/extnode.rs reads the node lane's `igneum_getNodeInfo` shape: `params` (every resolved consensus field, compared value by value against the manifest's keys), `network` (must be equal), `powEngine` ("stub" = refused on the port check, and a fault on the app's own node: node state `stub`, pill "Node fault", one error event). 168 box tests. - src/live.rs is Ember's merged module at the merge (nothing of this branch's lives there any more). - External node re-decided (the Mac, 7 October 2026 00:18 UK: the hand node left and the app sat on "node stopped" all night). While the app reads another node, or refused one, it checks the port every 5 s; gone for 60 s (`extnode::TAKEOVER_WAIT_S`) it starts its own node on the freed ports and says so in Activity; a node back inside the wait is kept. api/state node.mode own | external | none with node.mode_reason, shown under the Node details. Test `an_external_node_that_goes_away_hands_the_ports_to_the_app_after_the_wait` (169 box tests). -## 10. 0.3.18 lane (7 October 2026, 03:20 UK): the merge check +## 10. The merge check (7 October 2026, 03:20 UK; 74c12665, on the 0.3.18 list after afc331bd) - Design note from the node lane's 30-s propagation reading: a node behind a slow link keeps mining its own chain and the N4 "synced" test cannot see it. New `src/merge.rs` (pure): `not_merging(MergeCheck)` is true when every sink the network reports is more than the merge depth ahead of ours (the node's `params.merge_depth` via igneum_getNodeInfo, else 3,600) and none of our blocks has appeared in the network's view for 120 s, after at least 120 s of mining. Known-failed test first (`a_node_whose_blocks_never_merge_reads_behind_and_holds_the_miner`). - Engine: every 30 s while mining, the observer's /api/live (600-s window, this machine's lanes marked) gives the network's sink and the newest block of ours it shows (`merge::view_of`); the watch line's decision runs after sync_decision_v2; the state reads "behind" with sync_cause "not merging" and the message "behind: your blocks are not merging into the network"; the miner is held through the existing synced gate; one error event with the gap in blocks. Source owed: peers' own sinks (igneum_getNodeInfo over the peer list) when the node exposes them; the observer is the first view.