From 97ec94c7592574c8bbe58744c221cb70e0517833 Mon Sep 17 00:00:00 2001 From: wind108369 <1305983+wind108369@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:25:18 +0800 Subject: [PATCH 1/3] fix(windows-miner): dedupe rejected header slots --- miners/windows/rustchain_windows_miner.py | 12 ++++++---- ...test_windows_headless_lifecycle_logging.py | 10 ++++++++ tests/test_windows_miner_chain_identity.py | 24 +++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/miners/windows/rustchain_windows_miner.py b/miners/windows/rustchain_windows_miner.py index e904ceeb8..23eba29c0 100644 --- a/miners/windows/rustchain_windows_miner.py +++ b/miners/windows/rustchain_windows_miner.py @@ -328,7 +328,8 @@ def _mine_loop(self, callback): "type": "share", "submitted": self.shares_submitted, "accepted": self.shares_accepted, - "success": success + "success": success, + "error": "" if success else self.last_header_error, }) time.sleep(10) except Exception as e: @@ -725,8 +726,9 @@ def generate_header(self, slot): } def submit_header(self, payload): - """Submit one signed header and remember accepted slots.""" + """Submit one signed header and remember attempted slots.""" slot = payload.get("header", {}).get("slot") + self._last_submitted_slot = slot try: response = requests.post( f"{self.node_url}/headers/ingest_signed", @@ -740,7 +742,6 @@ def submit_header(self, payload): and bool(result.get("ok")) ) if success: - self._last_submitted_slot = slot self.last_header_error = "" else: self.last_header_error = self._response_diagnostic(response) @@ -842,10 +843,13 @@ def _format_headless_event(evt): t = evt.get("type") if t == "share": ok = "OK" if evt.get("success") else "FAIL" - return ( + line = ( f"[share] submitted={evt.get('submitted')} " f"accepted={evt.get('accepted')} {ok}" ) + if not evt.get("success") and evt.get("error"): + line = f"{line} error={evt.get('error')}" + return line if t == "attest": return ( f"[attest] {evt.get('message')} " diff --git a/tests/test_windows_headless_lifecycle_logging.py b/tests/test_windows_headless_lifecycle_logging.py index 32066493e..e50ea952b 100644 --- a/tests/test_windows_headless_lifecycle_logging.py +++ b/tests/test_windows_headless_lifecycle_logging.py @@ -65,6 +65,16 @@ def test_ready_status_and_headless_format_include_lifecycle_details(): "message": "Epoch enrollment succeeded", "miner_id": "windows_abc123", }) == "[enroll] Epoch enrollment succeeded miner_id=windows_abc123" + assert module._format_headless_event({ + "type": "share", + "submitted": 3, + "accepted": 1, + "success": False, + "error": "HTTP 403 error=no pubkey registered for miner", + }) == ( + "[share] submitted=3 accepted=1 FAIL " + "error=HTTP 403 error=no pubkey registered for miner" + ) def test_ensure_ready_surfaces_attestation_diagnostics(monkeypatch): diff --git a/tests/test_windows_miner_chain_identity.py b/tests/test_windows_miner_chain_identity.py index 699a18fd1..0d0e1cbce 100644 --- a/tests/test_windows_miner_chain_identity.py +++ b/tests/test_windows_miner_chain_identity.py @@ -124,3 +124,27 @@ def fake_post(url, **kwargs): "json": payload, "timeout": 15, } + + +def test_submit_deduplicates_rejected_slot_and_records_diagnostic(monkeypatch): + module = load_miner_module() + miner = make_miner(module) + payload = { + "miner_id": "RTCwallet", + "header": {"slot": 43, "miner": "RTCwallet", "timestamp": 1234}, + "message": "00", + "signature": "cd" * 64, + "pubkey": "ab" * 32, + } + + def fake_post(url, **kwargs): + return FakeResponse( + {"ok": False, "error": "no pubkey registered for miner"}, + status_code=403, + ) + + monkeypatch.setattr(module.requests, "post", fake_post) + + assert miner.submit_header(payload) is False + assert miner._last_submitted_slot == 43 + assert miner.last_header_error == "HTTP 403 error=no pubkey registered for miner" From d054f5c05088a151b5c0a5f8a5874e460c811920 Mon Sep 17 00:00:00 2001 From: wind108369 <1305983+wind108369@users.noreply.github.com> Date: Fri, 19 Jun 2026 07:36:29 +0800 Subject: [PATCH 2/3] fix(windows-miner): update pinned miner hash --- miners/windows/rustchain_miner_setup.bat | 2 +- setup_miner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/miners/windows/rustchain_miner_setup.bat b/miners/windows/rustchain_miner_setup.bat index 4f3c50060..a4c04745e 100755 --- a/miners/windows/rustchain_miner_setup.bat +++ b/miners/windows/rustchain_miner_setup.bat @@ -6,7 +6,7 @@ set "PYTHON_URL=https://www.python.org/ftp/python/3.11.5/python-3.11.5-amd64.exe set "PYTHON_INSTALLER=%SCRIPT_DIR%python-3.11.5-amd64.exe" set "MINER_URL=https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/rustchain_windows_miner.py" set "MINER_SCRIPT=%SCRIPT_DIR%rustchain_windows_miner.py" -set "MINER_SHA256=2381b9448c83556fae84e7ddd20a61789f718a4e4a01dc6986731b5064043811" +set "MINER_SHA256=8d5dcca0570b7b6c90654cb0ae11fb82df47f4e9918c9710e692914b8bd1c321" set "CRYPTO_URL=https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/miner_crypto.py" set "CRYPTO_SCRIPT=%SCRIPT_DIR%miner_crypto.py" set "CRYPTO_SHA256=ffe2e4c78fdc3f53c129a2ef820cc84549a5720655140e69a3e0baf1f7f385fa" diff --git a/setup_miner.py b/setup_miner.py index 2cd683558..2e1922b65 100644 --- a/setup_miner.py +++ b/setup_miner.py @@ -28,7 +28,7 @@ }, "Windows": { "url": "https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/rustchain_windows_miner.py", - "sha256": "2381b9448c83556fae84e7ddd20a61789f718a4e4a01dc6986731b5064043811", + "sha256": "8d5dcca0570b7b6c90654cb0ae11fb82df47f4e9918c9710e692914b8bd1c321", }, } From 227256ed0040758ff2c27195e78cb74c48e048b4 Mon Sep 17 00:00:00 2001 From: wind108369 <1305983+wind108369@users.noreply.github.com> Date: Sun, 21 Jun 2026 08:38:43 +0800 Subject: [PATCH 3/3] fix(windows-miner): guard slot tracking and verify hashes --- miners/windows/rustchain_miner_setup.bat | 2 +- miners/windows/rustchain_windows_miner.py | 3 ++- setup_miner.py | 2 +- tests/test_setup_miner_downloads.py | 10 ++++++++++ tests/test_windows_miner_chain_identity.py | 22 ++++++++++++++++++++++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/miners/windows/rustchain_miner_setup.bat b/miners/windows/rustchain_miner_setup.bat index a4c04745e..7a5d59f59 100755 --- a/miners/windows/rustchain_miner_setup.bat +++ b/miners/windows/rustchain_miner_setup.bat @@ -6,7 +6,7 @@ set "PYTHON_URL=https://www.python.org/ftp/python/3.11.5/python-3.11.5-amd64.exe set "PYTHON_INSTALLER=%SCRIPT_DIR%python-3.11.5-amd64.exe" set "MINER_URL=https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/rustchain_windows_miner.py" set "MINER_SCRIPT=%SCRIPT_DIR%rustchain_windows_miner.py" -set "MINER_SHA256=8d5dcca0570b7b6c90654cb0ae11fb82df47f4e9918c9710e692914b8bd1c321" +set "MINER_SHA256=b2abc6bf75acc562297137b20f719c3ef850a4de43377b55157e1d90a043340a" set "CRYPTO_URL=https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/miner_crypto.py" set "CRYPTO_SCRIPT=%SCRIPT_DIR%miner_crypto.py" set "CRYPTO_SHA256=ffe2e4c78fdc3f53c129a2ef820cc84549a5720655140e69a3e0baf1f7f385fa" diff --git a/miners/windows/rustchain_windows_miner.py b/miners/windows/rustchain_windows_miner.py index 23eba29c0..8491ce7c6 100644 --- a/miners/windows/rustchain_windows_miner.py +++ b/miners/windows/rustchain_windows_miner.py @@ -728,7 +728,8 @@ def generate_header(self, slot): def submit_header(self, payload): """Submit one signed header and remember attempted slots.""" slot = payload.get("header", {}).get("slot") - self._last_submitted_slot = slot + if slot is not None: + self._last_submitted_slot = slot try: response = requests.post( f"{self.node_url}/headers/ingest_signed", diff --git a/setup_miner.py b/setup_miner.py index 2e1922b65..5745ec473 100644 --- a/setup_miner.py +++ b/setup_miner.py @@ -28,7 +28,7 @@ }, "Windows": { "url": "https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/rustchain_windows_miner.py", - "sha256": "8d5dcca0570b7b6c90654cb0ae11fb82df47f4e9918c9710e692914b8bd1c321", + "sha256": "b2abc6bf75acc562297137b20f719c3ef850a4de43377b55157e1d90a043340a", }, } diff --git a/tests/test_setup_miner_downloads.py b/tests/test_setup_miner_downloads.py index 89e0ead86..15b3844d9 100644 --- a/tests/test_setup_miner_downloads.py +++ b/tests/test_setup_miner_downloads.py @@ -1,10 +1,12 @@ import hashlib +import re from pathlib import Path import setup_miner ROOT = Path(__file__).resolve().parents[1] +WINDOWS_BOOTSTRAP = ROOT / "miners" / "windows" / "rustchain_miner_setup.bat" def test_setup_miner_pins_current_miner_artifacts(): @@ -19,6 +21,14 @@ def test_setup_miner_pins_current_miner_artifacts(): assert artifact["sha256"] == hashlib.sha256(expected_files[platform].read_bytes()).hexdigest() +def test_windows_bootstrap_pins_current_miner_script(): + content = WINDOWS_BOOTSTRAP.read_text(encoding="utf-8") + match = re.search(r'^set "MINER_SHA256=([0-9a-fA-F]{64})"$', content, re.MULTILINE) + assert match is not None + expected = hashlib.sha256((ROOT / "miners" / "windows" / "rustchain_windows_miner.py").read_bytes()).hexdigest() + assert match.group(1).lower() == expected + + def test_setup_miner_pins_current_macos_artifact(): expected_file = ROOT / "miners" / "macos" / "rustchain_mac_miner_v2.5.py" artifact = setup_miner.MINER_ARTIFACTS["Darwin"] diff --git a/tests/test_windows_miner_chain_identity.py b/tests/test_windows_miner_chain_identity.py index 0d0e1cbce..7321fa183 100644 --- a/tests/test_windows_miner_chain_identity.py +++ b/tests/test_windows_miner_chain_identity.py @@ -148,3 +148,25 @@ def fake_post(url, **kwargs): assert miner.submit_header(payload) is False assert miner._last_submitted_slot == 43 assert miner.last_header_error == "HTTP 403 error=no pubkey registered for miner" + + +def test_submit_without_slot_does_not_clear_last_submitted_slot(monkeypatch): + module = load_miner_module() + miner = make_miner(module) + miner._last_submitted_slot = 41 + payload = { + "miner_id": "RTCwallet", + "header": {"miner": "RTCwallet", "timestamp": 1234}, + "message": "00", + "signature": "cd" * 64, + "pubkey": "ab" * 32, + } + + def fake_post(url, **kwargs): + return FakeResponse({"ok": False, "error": "missing slot"}, status_code=400) + + monkeypatch.setattr(module.requests, "post", fake_post) + + assert miner.submit_header(payload) is False + assert miner._last_submitted_slot == 41 + assert miner.last_header_error == "HTTP 400 error=missing slot"