Windows node package: igneumd cross-compiled for the PC, START-NODE and BUILD-NODE launchers
proto-cuda/windows-node/: START-NODE.bat and start-node.ps1 (igneumd --devnet with --addpeer to the Mac, status line every 30 s through igneum-miner watch, keep-awake, random-delay restart, Ctrl+C), BUILD-NODE.bat and build-node.ps1 (builds on the PC from the src.zip snapshot; winget for Rustup, LLVM and protoc; MSVC default toolset), ALLOW-FIREWALL.bat and allow-firewall.ps1, README.txt, cross-build.sh (the mingw-w64 recipe that built igneumd.exe in 8 min 25 s; the exe ships with the three mingw runtime DLLs) and make-package.sh. WINDOWS-MINER.md gains "Run your own node"; bench-log records the cross-compile and the two-peer sync test on the Mac (ports 27000 and 27010, live node untouched). The mining launcher's NODE_HOST=auto edit stays uncommitted for the agent that owns start-mining.ps1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
31ccd2752b
commit
3e1ecab245
11 changed files with 842 additions and 0 deletions
|
|
@ -233,3 +233,10 @@ Metal GPU worker (`proto-metal/igneum-bench --serve`, runtime-compiled `igneum_h
|
|||
Overnight devnet (started 20:07 BST): genesis bits 0x1d100000 = 2^28 expected hashes per block, sized for RTX 5090 229 + M5 Max 45 + gfx1036 4 = 278 MH/s (1.04 blocks/s); genesis hash edc4fa84...fb07; epoch 0 program 136 loads/hash compiled in 69.5 ms on Metal; node 1 alone (0.0.0.0:26610/26611, `caffeinate -dims`, logs /tmp/igneum-devnet/node1.log) with the Metal worker (`/tmp/igneum-devnet/metal-worker.log`): 31 blocks in 273 s = 0.11 blocks/s at 31.1 MH/s, as expected for 2^28 until the PC joins.
|
||||
Worker implementations, all bit-exact with `igneum-pow hash-bound` for a 96-nonce job across the 2^32 lane boundary (nonces 4294967264 to 4294967359, prehash ab x 32, devnet seeds): Metal (M5 Max GPU), OpenCL (`proto-opencl/host.c --serve`, Apple OpenCL 1.2 on the M5 Max, local-memory exchange, `--vendor` device filter added), CUDA (`proto-cuda/host.cu --serve` with the pack's `kernel_bound.cu`, through the clang emulation shim: bench PASS on the Rust-exported devnet pack, serve job bit-exact, seed-mismatch error exercised). CUDA and OpenCL are built ahead of time per pack; the Windows launcher re-exports and rebuilds at each epoch or day change (miner exit 42). `igneum-miner.exe` cross-compiled for x86_64-pc-windows-gnu with Homebrew mingw-w64 (9.4 MB; no rocksdb in the miner's closure).
|
||||
Not done: no NVIDIA or AMD run of the bound kernels yet (the Windows package `~/Desktop/igneum-mine-test.zip` is the next step); NVRTC and runtime OpenCL rebuilds inside the workers; the block level for pruning proofs on a lane-only pow value; 8 vs 18 decimals; the VDF seeds and finality.
|
||||
|
||||
## 3 October 2026, Windows node package: igneumd cross-compiled for x86_64-pc-windows-gnu, two-peer sync test (consensus-engineer)
|
||||
|
||||
Machine: Apple M5 Max, load average 60 to 110 (three other agents building), rustc 1.99.0, Homebrew mingw-w64 (gcc 16.2.0), Homebrew llvm (libclang). Worktree `vendor/igneum-node-win`, branch `windows-node` at `352495f6` (the rename commit `d62708a8` plus one miner change: `watch` prints `blue=` and `synced=` after `sink=`).
|
||||
Cross-compile: `cargo build --release -j 6 -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu` at `nice -n 19`, 8 min 25 s from a clean target directory (recipe in `proto-cuda/windows-node/cross-build.sh`). `librocksdb-sys` (bindgen plus the bundled rocksdb and snappy C++) built with `LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib`, `BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=<mingw sysroot> -I<mingw include>"` and `CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++`; zstd, lz4, bzip2, zlib, secp256k1 and mimalloc built with the mingw gcc. `igneumd.exe` 43,172,352 bytes, `igneum-miner.exe` 9,391,104 bytes. The exe imports `libstdc++-6.dll`: `-static-libstdc++` is ignored by the gcc driver rustc links with, and `-C link-arg=-static` (second full build, 8 min) does not cover a library rustc passes as `-Bdynamic`; the package ships `libstdc++-6.dll`, `libgcc_s_seh-1.dll` and `libwinpthread-1.dll` next to the exe (fix for next time: empty `CXXSTDLIB_x86_64_pc_windows_gnu` plus `-Wl,-Bstatic -lstdc++`). Not run on Windows yet.
|
||||
Sync test (native build of the same worktree, 21:44 to 21:46 BST, live node on 26610/26611 untouched): node A on 27000/27001 with START-NODE.bat's flags (`--addpeer=192.168.68.64:26611 --listen=0.0.0.0:27001 --nodnsseed --disable-upnp --nologfiles`) connected and started IBD within 20 ms, had 5,144 headers and 1,386 blocks at 7 s, and from 17 s on matched the live node's block count, DAA score, blue score and sink at every 10-s sample over 60 s (5,157 to 5,175 blocks, sink identical 5 of 6 samples, `synced=true`); every header checked by `igneum-lottery-v1-bound`. Live node `peers` 1 -> 2 -> 1. Node B on 27010/27011 dialled A's listener, synced to the same sink within 25 s and learnt the live node from A (outbound 2): a node with `--addpeer` plus `--listen` accepts inbound connections (`--connect` would set the inbound limit to 0, `kaspad/src/daemon.rs`). SIGINT stopped both cleanly.
|
||||
Package: `~/Desktop/igneum-node-windows.zip` from `proto-cuda/windows-node/make-package.sh` (exe, miner exe, src.zip snapshot of the worktree HEAD plus `igneum-pow/`, scripts, README). Build on the PC (BUILD-NODE.bat) estimated at 15 to 25 minutes on the 9800X3D, approximate, untested.
|
||||
|
|
|
|||
|
|
@ -129,3 +129,120 @@ start-mining.ps1 (no PowerShell on this Mac), the dashboard drawing (cursor posi
|
|||
console font, the window-height fallback), and running the .exe files on Windows. The 3 October 2026 dashboard version
|
||||
was checked only with a bracket and quote balance pass and a read-through; the first Windows run is the real test, and
|
||||
`PLAIN=1` is the way back if the window misbehaves.
|
||||
|
||||
## Run your own node (igneum-node-windows.zip, 3 October 2026, evening)
|
||||
|
||||
The PC runs its own `igneumd` and the miners mine against localhost; the PC and the Mac are two peers of the same
|
||||
devnet. Package built by `windows-node/make-package.sh` from `windows-node/` (START-NODE.bat, start-node.ps1,
|
||||
BUILD-NODE.bat, build-node.ps1, ALLOW-FIREWALL.bat, allow-firewall.ps1, README.txt), `windows-miner/upload-log.bat`,
|
||||
this file, a cross-compiled `igneumd.exe` and `igneum-miner.exe`, and `src.zip`: a `git archive` of the node worktree's
|
||||
HEAD (branch `windows-node`, commit `352495f6`, the rename commit plus one miner change) under `vendor/igneum-node/`
|
||||
with `igneum-pow/` from this repository's HEAD next to it, the layout the fork's relative path dependency needs. No
|
||||
GitHub access from the PC; the repository stays private.
|
||||
|
||||
Steps on the PC: extract to a short path (C:\igneum-node), double-click START-NODE.bat, click "Allow access" on the
|
||||
firewall prompt (tick Private networks), then START-MINING.bat as before. BUILD-NODE.bat is only for the case where
|
||||
igneumd.exe is missing or has to be rebuilt from the snapshot.
|
||||
|
||||
### The exe: cross-compiled on the Mac (the path that worked)
|
||||
|
||||
`windows-node/cross-build.sh`: `cargo build --release -p kaspad -p igneum-miner --features igneum-pow --target
|
||||
x86_64-pc-windows-gnu` with Homebrew mingw-w64 (gcc 16.2.0) and Homebrew llvm's libclang. The feared blocker,
|
||||
`librocksdb-sys`, built: bindgen parsed `rocksdb/c.h` once `LIBCLANG_PATH` pointed at `/opt/homebrew/opt/llvm/lib` and
|
||||
`BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu` gave clang the mingw target and sysroot
|
||||
(`--target=x86_64-w64-mingw32 --sysroot=<mingw>/x86_64-w64-mingw32 -I<mingw>/x86_64-w64-mingw32/include`); the bundled
|
||||
rocksdb and snappy C++ sources compiled with `CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++` through the cc crate
|
||||
(the crate's own `x86_64-pc-windows-gnu` branch defines `_POSIX_C_SOURCE` and `_WIN32_WINNT_VISTA`); zstd, lz4, bzip2,
|
||||
zlib, secp256k1 and mimalloc (no malloc override on Windows, the crate builds without it) compiled with the mingw gcc.
|
||||
First build 8 min 25 s with `-j 6` at `nice -n 19` on the loaded M5 Max; `igneumd.exe` 43.2 MB (thin LTO, stripped).
|
||||
|
||||
One real problem: the exe imports `libstdc++-6.dll`, which Windows does not have. `-C link-arg=-static-libstdc++` means
|
||||
nothing to the gcc (C) driver rustc links with, and `-C link-arg=-static` does not reach it either: rustc passes the cc
|
||||
crate's `-lstdc++` as `-Wl,-Bdynamic -lstdc++`, so ld takes the import library whatever comes later (both tried, two
|
||||
full rebuilds, the second 8 min). The package therefore ships the three mingw runtime DLLs next to the exe
|
||||
(`libstdc++-6.dll`, which itself needs `libgcc_s_seh-1.dll` and `libwinpthread-1.dll`, from the Homebrew toolchain;
|
||||
GCC runtime library exception). The clean fix for next time is `CXXSTDLIB_x86_64_pc_windows_gnu=` (empty, so the cc
|
||||
crate emits no `-lstdc++`) plus `-C link-arg=-Wl,-Bstatic -C link-arg=-lstdc++` so the archive is taken; not done
|
||||
tonight because each flag change is a full rebuild and the project lead wanted the package. The other imports are Windows' own
|
||||
(kernel32, ws2_32, bcrypt, the `api-ms-win-crt-*` UCRT forwarders the miner already imports, rpcrt4 and shlwapi for
|
||||
rocksdb, pdh, powrprof, psapi and iphlpapi for sysinfo).
|
||||
|
||||
Not tested here: running igneumd.exe on Windows (the miner exe from the same toolchain ran on the PC on 3 October). If
|
||||
the exe fails to start, BUILD-NODE.bat is the fallback; the PC build is upstream's own release path (rusty-kaspa ships
|
||||
Windows binaries built with MSVC).
|
||||
|
||||
### BUILD-NODE.bat (the fallback, builds on the PC)
|
||||
|
||||
`build-node.ps1` unpacks `src.zip` to `src\`, checks the tools and installs the missing ones with winget, silent:
|
||||
`Rustlang.Rustup` (stable, MSVC host), `LLVM.LLVM` (libclang.dll for the rocksdb bindings; `LIBCLANG_PATH` is set to
|
||||
`C:\Program Files\LLVM\bin`), `Google.Protobuf` for protoc (the gRPC and p2p crates run protoc from prost-build, no
|
||||
vendored copy; if winget has no such package the script downloads `protoc-33.1-win64.zip` from the protobuf releases
|
||||
into `tools\protoc`), and the MSVC toolset through `vcvarsall.bat x64` with its default version (14.5x is fine for Rust
|
||||
and the cc crate; the CUDA 14.30 trick is not used). Then `cargo build --release -p kaspad --features igneum-pow` (the
|
||||
crate keeps its upstream name kaspad, the binary is igneumd.exe; without the feature the node would check blocks with
|
||||
the kHeavyHash stub and reject every devnet block) with a progress line every minute (elapsed, crates compiled, the
|
||||
crate being compiled; cargo's output in `build-<stamp>.cargo.log`), then copies `igneumd.exe` next to the bat. No CMake:
|
||||
every native dependency builds through the cc crate. Estimate on the 9800X3D: 15 to 25 minutes for the first build
|
||||
(approximate; about 500 crates plus the rocksdb C++ sources, thin LTO at the end, Defender's scan of `target\`), under a
|
||||
minute to rebuild with nothing changed. Checked only by read-through and the bracket and quote balance pass (no
|
||||
PowerShell on this Mac).
|
||||
|
||||
### START-NODE.bat
|
||||
|
||||
Settings at the top: `MAC_NODE` (192.168.68.64:26611), `RPC_LISTEN` (127.0.0.1:26610, so the miner launcher's default
|
||||
port is the same on either host), `P2P_LISTEN` (0.0.0.0:26611), `APPDIR` (`%LOCALAPPDATA%\igneum\devnet`),
|
||||
`UNSYNCED_MINING` (0: the node hands out no template until synced, `rpc/service/src/service.rs` refuses
|
||||
`get_block_template` without `--enable-unsynced-mining`; the Mac node runs with 1 because it mines alone),
|
||||
`STATUS_SECS`, `EXTRA_ARGS`. The node starts as `igneumd --devnet --appdir=... --rpclisten=... --listen=...
|
||||
--addpeer=<MAC_NODE> --nodnsseed --disable-upnp --nologfiles`. It is `--addpeer`, not `--connect`:
|
||||
`kaspad/src/daemon.rs` sets the inbound limit to 0 and the outbound target to 0 when `--connect` is given, so the Mac
|
||||
could never dial the PC. `--addpeer` is a permanent connection request retried with backoff up to 8 minutes
|
||||
(`components/connectionmanager/src/lib.rs`), and the listener stays open.
|
||||
|
||||
The launcher (`start-node.ps1`): refuses to start when 26610 or 26611 is already in use (names the process), prints the
|
||||
node version, tests the Mac's p2p port once, keeps the PC awake (SetThreadExecutionState), logs the node's output to
|
||||
`node-<stamp>.log` next to the bat (a new segment per start) and its own lines to `node-launcher-<stamp>.log`
|
||||
(uploaded every 5 min with upload-log.bat under `node-<PC>` and `nodelog-<PC>`), prints a status line every 30 s read
|
||||
with `igneum-miner.exe watch 1 grpc://127.0.0.1:26610` in the background (blocks, headers, peers, blue score, DAA, tips,
|
||||
difficulty, synced yes or no; the miner's `watch` line gained `blue=` and `synced=` after `sink=` in commit `352495f6`,
|
||||
appended last so the mining launcher's regex still matches), restarts the node after a crash with a random 5 to 60 s
|
||||
delay (exit code and the last stderr lines logged), and stops it with taskkill on Ctrl+C (rocksdb recovers from its WAL;
|
||||
no graceful signal reaches a console child while the launcher owns Ctrl+C). The "extract the zip first" check in the
|
||||
bats escapes its brackets (`^(` and `^)`), as cmd ends a parenthesised block at a bare `)`.
|
||||
|
||||
Firewall: the first time igneumd.exe listens on 0.0.0.0:26611, Windows Defender Firewall shows "Windows Defender
|
||||
Firewall has blocked some features of this app". Tick "Private networks", click "Allow access". That rule is what lets
|
||||
the Mac dial the PC; the PC dials the Mac anyway, so the chain syncs even without it. "Cancel" creates a block rule:
|
||||
ALLOW-FIREWALL.bat removes it and adds an inbound allow rule for the exe (private and domain profiles, elevated once).
|
||||
The gRPC port is loopback only and never prompts.
|
||||
|
||||
### The mining launcher: NODE_HOST=auto
|
||||
|
||||
`START-MINING.bat` now sets `NODE_HOST=auto` and `start-mining.ps1` resolves it before anything else: a TCP connect to
|
||||
127.0.0.1:NODE_PORT with a 1.5 s timeout picks the node on the PC, otherwise the Mac at 192.168.68.64; the choice is on
|
||||
the launcher log's first line ("NODE_HOST=auto: using the node on this PC" or "nothing listens on 127.0.0.1:26610, using
|
||||
the Mac node"). An explicit NODE_HOST still wins. Nothing else in the launcher changed (the hot-swap work is another
|
||||
agent's).
|
||||
|
||||
### Sync test on the Mac
|
||||
|
||||
The Windows scripts cannot run here, so the flags and the sync were tested with the worktree's native `igneumd`
|
||||
(`target/release`, built from the same commit) as a stand-in for the PC, 3 October 2026, 21:44 to 21:46 BST, against
|
||||
the live node (gRPC 26610, p2p 26611, which stayed up throughout): node A `--devnet --appdir=/tmp/igneum-win-test/nodeA
|
||||
--rpclisten=127.0.0.1:27000 --listen=0.0.0.0:27001 --addpeer=192.168.68.64:26611 --nodnsseed --disable-upnp
|
||||
--nologfiles`, exactly START-NODE.bat's flags with the test ports. Banner `igneumd/2.1.0-2873fed`; "P2P Connected to
|
||||
outgoing peer 192.168.68.64:26611", "IBD started" within 20 ms of the start; every header checked by
|
||||
`igneum-lottery-v1-bound` ("PoW accepted ... daa 0, 1, 2 ..."). `igneum-miner watch 60` on A and the live node: at
|
||||
the first sample (7 s in) A had 5,144 headers and 1,386 blocks, `synced=false`; from the second sample on (17 s) A and
|
||||
the live node reported the same block count, DAA score, blue score and sink at every reading (5,157 to 5,175 blocks,
|
||||
blue 5,126 to 5,143, `synced=true`), sink identical at 5 of 6 samples (the first was mid-IBD). The live node showed
|
||||
`peers=2` while A ran and `peers=1` after it stopped. Inbound: a third node B on 27010/27011 with `--addpeer=127.0.0.1:27001`
|
||||
connected to A's listener (A `peers=2`), synced to the same sink within 25 s (B also picked up the live node from A's
|
||||
address exchange, "outbound: 2"), so a node started with `--addpeer` plus `--listen` accepts connections, which is what
|
||||
the Mac needs when it dials the PC. SIGINT stopped B and A cleanly ("P2P Server stopped", "GRPC Server stopped",
|
||||
"igneumd has stopped"); `/tmp/igneum-win-test` removed afterwards. The `blue=` and `synced=` fields on the `watch` line
|
||||
are from the new miner build and parsed as the launcher does.
|
||||
|
||||
Not tested here: everything Windows (winget, vcvarsall, MSVC, the firewall prompt, Get-NetTCPConnection, taskkill,
|
||||
Ctrl+C, the random restart delay, upload-log.bat from the node launcher, running the cross-compiled exe). The scripts
|
||||
passed the bracket and quote balance pass (`windows-node/` and the two edited lines of `start-mining.ps1`).
|
||||
|
|
|
|||
12
proto-cuda/windows-node/ALLOW-FIREWALL.bat
Normal file
12
proto-cuda/windows-node/ALLOW-FIREWALL.bat
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
@echo off
|
||||
rem Adds a Windows Firewall rule so the Mac node can connect to igneumd.exe on this PC (inbound, private and domain
|
||||
rem networks). Only needed if you clicked Cancel on the firewall prompt the first time START-NODE.bat ran, or if the
|
||||
rem status line keeps saying peers 0 while the Mac node is up. Asks for administrator rights once.
|
||||
cd /d "%~dp0"
|
||||
if not exist "%~dp0igneumd.exe" (
|
||||
echo igneumd.exe is not in this folder yet ^(run BUILD-NODE.bat first^); the rule needs the exe path.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0allow-firewall.ps1"
|
||||
pause
|
||||
28
proto-cuda/windows-node/BUILD-NODE.bat
Normal file
28
proto-cuda/windows-node/BUILD-NODE.bat
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@echo off
|
||||
rem Builds igneumd.exe (the Igneum node) on this PC from the source snapshot in src.zip. Double-click this file.
|
||||
rem Only needed when igneumd.exe is not already in this folder. It installs Rust, LLVM and protoc with winget when they
|
||||
rem are missing, uses the Visual Studio C++ toolset already on the PC, and runs cargo build --release -p kaspad
|
||||
rem --features igneum-pow (about 15 to 25 minutes the first time on a 9800X3D, approximate). A progress line prints
|
||||
rem every minute; cargo's own output goes to build-<stamp>.cargo.log next to this file.
|
||||
|
||||
rem ---- settings -----------------------------------------------------------------------------------
|
||||
rem Visual Studio: only the path is needed; the default toolset of that installation is used.
|
||||
set "VCVARS=C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat"
|
||||
rem -------------------------------------------------------------------------------------------------
|
||||
|
||||
cd /d "%~dp0"
|
||||
if not exist "%~dp0build-node.ps1" (
|
||||
echo Please extract the whole zip first ^(right-click, Extract All^), then run BUILD-NODE.bat from the extracted folder.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
where powershell >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo PowerShell was not found. Windows 10 and 11 ship it; nothing else can be done here.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0build-node.ps1"
|
||||
echo.
|
||||
echo The build has finished ^(see the lines above^). If igneumd.exe is now in this folder, run START-NODE.bat.
|
||||
pause
|
||||
9
proto-cuda/windows-node/README.txt
Normal file
9
proto-cuda/windows-node/README.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Igneum devnet node for Windows: your own igneumd on the PC, so the miners mine against localhost and the PC peers with the Mac node (192.168.68.64:26611).
|
||||
Extract the whole zip to a short path such as C:\igneum-node first (the scripts refuse to run from inside the zip).
|
||||
1. BUILD-NODE.bat, once, only if igneumd.exe is NOT in this folder (the package ships one cross-compiled on the Mac; if that exe will not start, run this). It unpacks src.zip, installs Rust, LLVM and protoc with winget when missing, builds with cargo and the Visual Studio C++ toolset (any version), and copies igneumd.exe here. About 15 to 25 minutes the first time (approximate), a progress line every minute.
|
||||
2. START-NODE.bat. Starts igneumd --devnet with the database under %LOCALAPPDATA%\igneum\devnet, RPC on 127.0.0.1:26610, p2p on 0.0.0.0:26611, peered to the Mac. One status line every 30 s (blocks, headers, peers, blue score, DAA, synced yes or no); the node's output is in node-<stamp>.log next to the bat; the PC stays awake; a crash restarts the node after 5 to 60 s; Ctrl+C stops it.
|
||||
Firewall: the first time, Windows Defender Firewall asks about igneumd.exe. Tick Private networks and click Allow access (that lets the Mac dial this PC). If you clicked Cancel, run ALLOW-FIREWALL.bat once.
|
||||
3. In the mining folder, START-MINING.bat: NODE_HOST=auto (the new default) picks 127.0.0.1:26610 when this node is running and falls back to the Mac; with an older copy of the mining package set NODE_HOST=127.0.0.1 at the top of the bat. Restart START-MINING.bat. The miners wait ("waiting") until this node is synced, usually under a minute.
|
||||
Settings at the top of START-NODE.bat: MAC_NODE, RPC_LISTEN, P2P_LISTEN, APPDIR, UNSYNCED_MINING (1 = templates before sync), STATUS_SECS, EXTRA_ARGS. The node runs with --addpeer (not --connect: --connect would stop the Mac connecting in).
|
||||
If the node exits at once with a database error (an older build's database), delete %LOCALAPPDATA%\igneum\devnet and start again; it resyncs from the Mac in seconds.
|
||||
Files: START-NODE.bat and start-node.ps1, BUILD-NODE.bat and build-node.ps1, ALLOW-FIREWALL.bat and allow-firewall.ps1, igneumd.exe with libstdc++-6.dll, libgcc_s_seh-1.dll and libwinpthread-1.dll (its C++ runtime, keep them next to it), igneum-miner.exe (status reader), src.zip (source snapshot), upload-log.bat, WINDOWS-MINER.md (full write-up, section "Run your own node").
|
||||
45
proto-cuda/windows-node/START-NODE.bat
Normal file
45
proto-cuda/windows-node/START-NODE.bat
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
@echo off
|
||||
rem Igneum devnet node for Windows. Double-click this file.
|
||||
rem Runs igneumd.exe (the Igneum node) on this PC: the miners mine against it on localhost and it peers with the Mac
|
||||
rem node. The window shows one status line every 30 seconds; the node's own output goes to node-<stamp>.log next to
|
||||
rem this file. Stop with Ctrl+C; the window stays open at the end.
|
||||
|
||||
rem ---- settings: edit these lines only ----------------------------------------------------------
|
||||
rem The Mac node (its p2p port). This node dials it at start and keeps retrying; the Mac can dial back as well.
|
||||
set "MAC_NODE=192.168.68.64:26611"
|
||||
rem gRPC for the miners on this PC (START-MINING.bat with NODE_HOST=auto looks here first).
|
||||
set "RPC_LISTEN=127.0.0.1:26610"
|
||||
rem p2p on all interfaces, so the Mac node can connect in. Windows Firewall asks once: tick Private networks, Allow access.
|
||||
set "P2P_LISTEN=0.0.0.0:26611"
|
||||
rem Where the chain database lives.
|
||||
set "APPDIR=%LOCALAPPDATA%\igneum\devnet"
|
||||
rem 1 = hand out block templates before this node is synced (the Mac node runs like this because it mines alone).
|
||||
rem 0 = the miners wait until this node has caught up with the Mac.
|
||||
set "UNSYNCED_MINING=0"
|
||||
rem Seconds between status lines.
|
||||
set "STATUS_SECS=30"
|
||||
rem Anything else to pass to igneumd (for example --loglevel=debug).
|
||||
set "EXTRA_ARGS="
|
||||
rem -------------------------------------------------------------------------------------------------
|
||||
|
||||
cd /d "%~dp0"
|
||||
if not exist "%~dp0start-node.ps1" (
|
||||
echo Please extract the whole zip first ^(right-click, Extract All^), then run START-NODE.bat from the extracted folder.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%~dp0igneumd.exe" (
|
||||
echo igneumd.exe is not in this folder yet. Run BUILD-NODE.bat first ^(it builds the node from the source in src.zip^).
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
where powershell >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo PowerShell was not found. Windows 10 and 11 ship it; nothing else can be done here.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0start-node.ps1"
|
||||
echo.
|
||||
echo The node has stopped. The logs are next to this file.
|
||||
pause
|
||||
26
proto-cuda/windows-node/allow-firewall.ps1
Normal file
26
proto-cuda/windows-node/allow-firewall.ps1
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Adds the inbound Windows Firewall rule for igneumd.exe (started by ALLOW-FIREWALL.bat). Runs itself again elevated,
|
||||
# removes any block rule Windows made for the exe (the "Cancel" button on the prompt creates one), then adds an allow
|
||||
# rule for the exe on the private and domain profiles. 3 October 2026.
|
||||
param([switch]$Elevated)
|
||||
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$exe = Join-Path $root 'igneumd.exe'
|
||||
$rule = 'Igneum node (igneumd)'
|
||||
if (-not $Elevated) {
|
||||
Write-Host "asking for administrator rights to add the firewall rule for $exe"
|
||||
$psArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ('"' + $PSCommandPath + '"'), '-Elevated')
|
||||
try { Start-Process -FilePath powershell.exe -Verb RunAs -Wait -ArgumentList $psArgs } catch { Write-Host "not elevated ($_); nothing changed"; exit 1 }
|
||||
Write-Host 'done. Start the node again with START-NODE.bat.'
|
||||
exit 0
|
||||
}
|
||||
try {
|
||||
$blocked = @(Get-NetFirewallApplicationFilter -ErrorAction SilentlyContinue | Where-Object { $_.Program -and ($_.Program -ieq $exe) } | Get-NetFirewallRule | Where-Object { $_.Action -eq 'Block' })
|
||||
foreach ($b in $blocked) { Write-Host "removing block rule '$($b.DisplayName)'"; $b | Remove-NetFirewallRule }
|
||||
Get-NetFirewallRule -DisplayName $rule -ErrorAction SilentlyContinue | Remove-NetFirewallRule
|
||||
New-NetFirewallRule -DisplayName $rule -Direction Inbound -Program $exe -Action Allow -Profile Private,Domain -Protocol TCP | Out-Null
|
||||
Write-Host "added inbound allow rule '$rule' for $exe (private and domain networks)"
|
||||
} catch {
|
||||
Write-Host "could not change the firewall: $_"
|
||||
Start-Sleep -Seconds 5
|
||||
exit 1
|
||||
}
|
||||
Start-Sleep -Seconds 2
|
||||
212
proto-cuda/windows-node/build-node.ps1
Normal file
212
proto-cuda/windows-node/build-node.ps1
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# Builds igneumd.exe (the Igneum node) on this PC from the source snapshot in src.zip. Started by BUILD-NODE.bat.
|
||||
# 3 October 2026.
|
||||
#
|
||||
# Steps:
|
||||
# 1. Unpacks src.zip into src\ (src\vendor\igneum-node is the node fork, src\igneum-pow the lottery-hash crate the fork
|
||||
# depends on by relative path; the layout must stay as it is). Nothing is cloned: the snapshot is a `git archive`
|
||||
# of the fork's HEAD made on the Mac, so no GitHub access is needed and the repository stays private.
|
||||
# 2. Makes sure the tools are present, installing the missing ones with winget (silent):
|
||||
# Rust Rustlang.Rustup (stable, MSVC host). cargo lands in %USERPROFILE%\.cargo\bin.
|
||||
# LLVM LLVM.LLVM, for libclang.dll: the rocksdb crate generates its bindings with bindgen (LIBCLANG_PATH).
|
||||
# protoc Google.Protobuf, or a direct download of the protoc release zip: the gRPC and p2p crates run protoc
|
||||
# from their build scripts (prost-build, no vendored protoc).
|
||||
# MSVC the toolset of the installed Visual Studio, through vcvarsall x64 (the default 14.5x is fine for
|
||||
# Rust and for the C and C++ crates: rocksdb, zstd, lz4, bzip2, zlib, secp256k1, mimalloc). The CUDA
|
||||
# 14.30 trick of the mining launcher is not used here.
|
||||
# 3. Runs `cargo build --release -p kaspad --features igneum-pow` in src\vendor\igneum-node (the package keeps its
|
||||
# upstream name kaspad; the binary is igneumd.exe; the feature selects the real lottery-hash engine, without it the
|
||||
# node would check blocks with the kHeavyHash stub and reject every devnet block). A progress line prints every
|
||||
# minute: elapsed time, crates compiled, the crate being compiled.
|
||||
# 4. Copies target\release\igneumd.exe next to BUILD-NODE.bat (and igneum-miner.exe if it was missing).
|
||||
#
|
||||
# Time: approximate 15 to 25 minutes on a Ryzen 7 9800X3D for the first build (the rocksdb C++ sources and about 500
|
||||
# Rust crates; thin LTO at the end), under a minute for a rebuild with nothing changed. Windows Defender's real-time
|
||||
# scan of target\ can add a few minutes. Extract the zip to a short path (C:\igneum-node) so no build path reaches the
|
||||
# 260-character limit.
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $root
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$buildLog = Join-Path $root "build-$stamp.log"
|
||||
$started = Get-Date
|
||||
$vcvars = if ($env:VCVARS) { $env:VCVARS } else { 'C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat' }
|
||||
|
||||
function Log([string]$msg) {
|
||||
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg"
|
||||
Write-Host $line
|
||||
Add-Content -Path $buildLog -Value $line
|
||||
}
|
||||
|
||||
function Refresh-Path {
|
||||
$m = [Environment]::GetEnvironmentVariable('PATH', 'Machine')
|
||||
$u = [Environment]::GetEnvironmentVariable('PATH', 'User')
|
||||
$env:PATH = "$env:PATH;$m;$u"
|
||||
}
|
||||
|
||||
function Invoke-Winget([string]$id) {
|
||||
if (-not (Get-Command winget.exe -ErrorAction SilentlyContinue)) { Log "winget is not available, cannot install $id by itself"; return }
|
||||
Log "installing $id with winget (silent)"
|
||||
$out = & winget.exe install --id $id -e --silent --accept-package-agreements --accept-source-agreements 2>&1
|
||||
$out | ForEach-Object { Add-Content -Path $buildLog -Value " winget: $_" }
|
||||
Refresh-Path
|
||||
}
|
||||
|
||||
Log "igneum node build on $env:COMPUTERNAME (run $stamp), folder $root"
|
||||
if ($root.Length -gt 80) { Log "WARNING: this folder path is $($root.Length) characters long; cargo's build paths may pass the 260-character limit. C:\igneum-node is a safer place." }
|
||||
|
||||
# ---- 1. the source -----------------------------------------------------------------------------------------------------
|
||||
|
||||
$src = Join-Path $root 'src'
|
||||
$nodeDir = Join-Path $src 'vendor\igneum-node'
|
||||
$srcZip = Join-Path $root 'src.zip'
|
||||
if (-not (Test-Path (Join-Path $nodeDir 'Cargo.toml'))) {
|
||||
if (-not (Test-Path $srcZip)) { Log 'neither src\vendor\igneum-node nor src.zip is here. Extract the whole package again.'; exit 1 }
|
||||
Log "unpacking src.zip into $src"
|
||||
try { Expand-Archive -Path $srcZip -DestinationPath $src -Force } catch { Log "could not unpack src.zip: $_"; exit 1 }
|
||||
}
|
||||
if (-not (Test-Path (Join-Path $src 'igneum-pow\Cargo.toml'))) { Log 'src\igneum-pow is missing (the fork depends on it by relative path); the snapshot is incomplete.'; exit 1 }
|
||||
$snapshotNote = Join-Path $src 'SNAPSHOT.txt'
|
||||
if (Test-Path $snapshotNote) { Get-Content $snapshotNote | ForEach-Object { Log "source: $_" } }
|
||||
|
||||
# ---- 2. tools -------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Rust
|
||||
$cargoBin = Join-Path $env:USERPROFILE '.cargo\bin'
|
||||
if (Test-Path $cargoBin) { $env:PATH = "$cargoBin;$env:PATH" }
|
||||
if (-not (Get-Command cargo.exe -ErrorAction SilentlyContinue)) {
|
||||
Invoke-Winget 'Rustlang.Rustup'
|
||||
if (Test-Path $cargoBin) { $env:PATH = "$cargoBin;$env:PATH" }
|
||||
}
|
||||
if (-not (Get-Command cargo.exe -ErrorAction SilentlyContinue)) { Log 'cargo is still not found. Install Rust from https://rustup.rs (default options) and run BUILD-NODE.bat again.'; exit 1 }
|
||||
$toolchains = @(& rustup.exe toolchain list 2>&1)
|
||||
if (-not ($toolchains | Where-Object { $_ -match '^stable' })) {
|
||||
Log 'no stable toolchain yet: rustup toolchain install stable'
|
||||
& rustup.exe toolchain install stable --profile minimal 2>&1 | ForEach-Object { Add-Content -Path $buildLog -Value " rustup: $_" }
|
||||
& rustup.exe default stable 2>&1 | ForEach-Object { Add-Content -Path $buildLog -Value " rustup: $_" }
|
||||
}
|
||||
$active = (& rustup.exe show active-toolchain 2>&1 | Select-Object -First 1)
|
||||
if ($active -notmatch 'msvc') { Log "the active toolchain is '$active'; the MSVC host toolchain is the tested one (rustup default stable-msvc)" }
|
||||
Log "rust: $(& rustc.exe --version 2>&1) / $(& cargo.exe --version 2>&1) ($active)"
|
||||
|
||||
# LLVM (libclang for bindgen)
|
||||
$libclang = $null
|
||||
foreach ($d in @($env:LIBCLANG_PATH, 'C:\Program Files\LLVM\bin', 'C:\Program Files\LLVM\lib')) {
|
||||
if ($d -and (Test-Path (Join-Path $d 'libclang.dll'))) { $libclang = $d; break }
|
||||
}
|
||||
if (-not $libclang) {
|
||||
Invoke-Winget 'LLVM.LLVM'
|
||||
foreach ($d in @('C:\Program Files\LLVM\bin', 'C:\Program Files\LLVM\lib')) { if (Test-Path (Join-Path $d 'libclang.dll')) { $libclang = $d; break } }
|
||||
}
|
||||
if (-not $libclang) { Log 'libclang.dll not found (LLVM). Install LLVM from https://releases.llvm.org (the Windows installer) and run BUILD-NODE.bat again.'; exit 1 }
|
||||
$env:LIBCLANG_PATH = $libclang
|
||||
Log "libclang: $libclang"
|
||||
|
||||
# protoc
|
||||
$protoc = $null
|
||||
$cmd = Get-Command protoc.exe -ErrorAction SilentlyContinue
|
||||
if ($cmd) { $protoc = $cmd.Source }
|
||||
$localProtoc = Join-Path $root 'tools\protoc\bin\protoc.exe'
|
||||
if (-not $protoc -and (Test-Path $localProtoc)) { $protoc = $localProtoc }
|
||||
if (-not $protoc) {
|
||||
Invoke-Winget 'Google.Protobuf'
|
||||
$cmd = Get-Command protoc.exe -ErrorAction SilentlyContinue
|
||||
if ($cmd) { $protoc = $cmd.Source }
|
||||
}
|
||||
if (-not $protoc) {
|
||||
# Fallback: the release zip from the protobuf project (public), unpacked under tools\protoc.
|
||||
$ver = '33.1'
|
||||
$url = "https://github.com/protocolbuffers/protobuf/releases/download/v$ver/protoc-$ver-win64.zip"
|
||||
$zip = Join-Path $root 'tools\protoc.zip'
|
||||
Log "protoc not found: downloading $url"
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $root 'tools') | Out-Null
|
||||
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
|
||||
Expand-Archive -Path $zip -DestinationPath (Join-Path $root 'tools\protoc') -Force
|
||||
} catch { Log "download failed: $_" }
|
||||
if (Test-Path $localProtoc) { $protoc = $localProtoc }
|
||||
}
|
||||
if (-not $protoc) { Log 'protoc is still missing. Install it (winget install Google.Protobuf, or the protoc-*-win64.zip from the protobuf releases page, unpacked to tools\protoc) and run BUILD-NODE.bat again.'; exit 1 }
|
||||
$env:PROTOC = $protoc
|
||||
Log "protoc: $protoc ($(& $protoc --version 2>&1))"
|
||||
|
||||
# MSVC: import the toolset environment (default version). rustc and the cc crate find MSVC through vswhere by themselves,
|
||||
# so a missing vcvarsall is a warning, not a stop.
|
||||
function Find-VcVarsAll {
|
||||
if ($vcvars -and (Test-Path $vcvars)) { return $vcvars }
|
||||
foreach ($pf in @('C:\Program Files', 'C:\Program Files (x86)')) {
|
||||
$hits = Get-ChildItem -Path (Join-Path $pf 'Microsoft Visual Studio') -Recurse -Filter 'vcvarsall.bat' -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -match '\\VC\\Auxiliary\\Build\\vcvarsall\.bat$' } | Sort-Object FullName -Descending
|
||||
if ($hits) { return $hits[0].FullName }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
$bat = Find-VcVarsAll
|
||||
if ($bat) {
|
||||
Log "importing the MSVC environment: `"$bat`" x64"
|
||||
$out = cmd /s /c "`"$bat`" x64 >nul 2>&1 && set" 2>$null
|
||||
foreach ($l in @($out)) {
|
||||
$i = $l.IndexOf('=')
|
||||
if ($i -gt 0) { [Environment]::SetEnvironmentVariable($l.Substring(0, $i), $l.Substring($i + 1), 'Process') }
|
||||
}
|
||||
}
|
||||
$cl = Get-Command cl.exe -ErrorAction SilentlyContinue
|
||||
if ($cl) { Log "MSVC: $($cl.Source)" } else { Log 'cl.exe is not on PATH (no vcvarsall found); cargo will look for MSVC by itself. If the build stops with "linker link.exe not found", install the "Desktop development with C++" workload in the Visual Studio Installer.' }
|
||||
|
||||
# ---- 3. the build ------------------------------------------------------------------------------------------------------
|
||||
|
||||
$needMiner = -not (Test-Path (Join-Path $root 'igneum-miner.exe'))
|
||||
$cargoArgs = @('build', '--release', '-p', 'kaspad', '--features', 'igneum-pow')
|
||||
if ($needMiner) { $cargoArgs += @('-p', 'igneum-miner') }
|
||||
$cargoOut = Join-Path $root "build-$stamp.cargo.log"
|
||||
Log "cargo $($cargoArgs -join ' ') in $nodeDir (cargo's own output: $cargoOut)"
|
||||
Log 'this takes about 15 to 25 minutes the first time; a line prints every minute'
|
||||
$t0 = Get-Date
|
||||
$proc = Start-Process -FilePath (Get-Command cargo.exe).Source -ArgumentList $cargoArgs -WorkingDirectory $nodeDir -RedirectStandardOutput ($cargoOut + '.out') -RedirectStandardError $cargoOut -NoNewWindow -PassThru
|
||||
if ($proc) { $null = $proc.Handle }
|
||||
|
||||
function Get-CargoProgress {
|
||||
# cargo prints " Compiling name vX.Y.Z" lines to stderr; count them and show the last one.
|
||||
try {
|
||||
$fs = New-Object IO.FileStream($cargoOut, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
||||
try { $sr = New-Object IO.StreamReader($fs); $text = $sr.ReadToEnd(); $sr.Close() } finally { $fs.Close() }
|
||||
} catch { return 'waiting for cargo' }
|
||||
$lines = @($text -split "`n" | ForEach-Object { $_.TrimEnd("`r") })
|
||||
$compiling = @($lines | Where-Object { $_ -match '^\s*Compiling ' })
|
||||
$last = if ($compiling.Count -gt 0) { ($compiling[-1] -replace '^\s*Compiling ', '') -replace ' \(.*\)$', '' } else { 'resolving and downloading crates' }
|
||||
$errors = @($lines | Where-Object { $_ -match '^error' }).Count
|
||||
$txt = "$($compiling.Count) crates compiled, now $last"
|
||||
if ($errors -gt 0) { $txt += ", $errors error line(s) so far" }
|
||||
return $txt
|
||||
}
|
||||
|
||||
$lastLine = Get-Date
|
||||
while (-not $proc.HasExited) {
|
||||
Start-Sleep -Seconds 5
|
||||
if (((Get-Date) - $lastLine).TotalSeconds -ge 60) {
|
||||
$lastLine = Get-Date
|
||||
Log (" {0:N0} min: {1}" -f ((Get-Date) - $t0).TotalMinutes, (Get-CargoProgress))
|
||||
}
|
||||
}
|
||||
$took = (Get-Date) - $t0
|
||||
$rc = $proc.ExitCode
|
||||
Log ("cargo finished with exit code {0} after {1} ({2})" -f $rc, $took.ToString('hh\:mm\:ss'), (Get-CargoProgress))
|
||||
|
||||
# ---- 4. the result ----------------------------------------------------------------------------------------------------------
|
||||
|
||||
$built = Join-Path $nodeDir 'target\release\igneumd.exe'
|
||||
if ($rc -ne 0 -or -not (Test-Path $built)) {
|
||||
Log "the build FAILED. The last error lines from $cargoOut :"
|
||||
try { Get-Content $cargoOut -ErrorAction SilentlyContinue | Where-Object { $_ -match '^(error|warning: unused| = note: )|LNK|link\.exe|protoc|libclang|bindgen' } | Select-Object -Last 15 | ForEach-Object { Log " $_" } } catch { }
|
||||
Log 'Common causes: no MSVC (install the Desktop development with C++ workload), protoc or libclang not found (see the lines above), a path over 260 characters (move the folder to C:\igneum-node), Defender holding a file (run it again).'
|
||||
exit 1
|
||||
}
|
||||
Copy-Item $built (Join-Path $root 'igneumd.exe') -Force
|
||||
$size = (Get-Item (Join-Path $root 'igneumd.exe')).Length
|
||||
Log ("igneumd.exe copied next to BUILD-NODE.bat ({0:N1} MB)" -f ($size / 1MB))
|
||||
if ($needMiner) {
|
||||
$m = Join-Path $nodeDir 'target\release\igneum-miner.exe'
|
||||
if (Test-Path $m) { Copy-Item $m (Join-Path $root 'igneum-miner.exe') -Force; Log 'igneum-miner.exe copied too (the status reader of START-NODE.bat)' }
|
||||
}
|
||||
Log ("done in {0}. Now run START-NODE.bat." -f ((Get-Date) - $started).ToString('hh\:mm\:ss'))
|
||||
36
proto-cuda/windows-node/cross-build.sh
Executable file
36
proto-cuda/windows-node/cross-build.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
# Cross-compiles igneumd.exe and igneum-miner.exe for x86_64-pc-windows-gnu on the Mac (Homebrew mingw-w64 and llvm).
|
||||
# Worked on 3 October 2026 (8 min 25 s with -j 6 at nice 19 on a loaded M5 Max); see WINDOWS-MINER.md, "Run your own node".
|
||||
# What each variable is for:
|
||||
# CC/CXX/AR_x86_64_pc_windows_gnu the cc crate builds rocksdb (C++), snappy, zstd, lz4, bzip2, zlib, secp256k1 and
|
||||
# mimalloc with the mingw compilers
|
||||
# LIBCLANG_PATH librocksdb-sys runs bindgen on rocksdb/c.h; Homebrew llvm's libclang
|
||||
# BINDGEN_EXTRA_CLANG_ARGS_... bindgen's clang must parse the mingw headers for the Windows target, not the Mac's
|
||||
# CARGO_TARGET_..._RUSTFLAGS -static: ld takes libstdc++.a, libgcc.a and libwinpthread.a instead of the import
|
||||
# libraries, so the exe does not need libstdc++-6.dll, libgcc_s_seh-1.dll or
|
||||
# libwinpthread-1.dll on the PC (the first build linked libstdc++-6.dll because
|
||||
# -static-libstdc++ means nothing to the gcc driver rustc links with)
|
||||
# Usage: windows-node/cross-build.sh [node worktree, default vendor/igneum-node-win] [cargo jobs, default 6]
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$HERE/../.." && pwd)"
|
||||
NODE="${1:-$ROOT/vendor/igneum-node-win}"
|
||||
JOBS="${2:-6}"
|
||||
MINGW="/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32"
|
||||
[ -d "$MINGW" ] || { echo "no mingw-w64 at $MINGW (brew install mingw-w64)" >&2; exit 1; }
|
||||
[ -f /opt/homebrew/opt/llvm/lib/libclang.dylib ] || { echo "no libclang at /opt/homebrew/opt/llvm/lib (brew install llvm)" >&2; exit 1; }
|
||||
export PATH="$HOME/.cargo/bin:/opt/homebrew/bin:$PATH"
|
||||
rustup target list --installed | grep -q x86_64-pc-windows-gnu || rustup target add x86_64-pc-windows-gnu
|
||||
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target}"
|
||||
export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc
|
||||
export CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++
|
||||
export AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar
|
||||
export LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib
|
||||
export BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=$MINGW -I$MINGW/include"
|
||||
export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-C link-arg=-static -C link-arg=-static-libgcc"
|
||||
cd "$NODE"
|
||||
nice -n 19 cargo build --release -j "$JOBS" -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu
|
||||
for exe in igneumd igneum-miner; do
|
||||
f="$CARGO_TARGET_DIR/x86_64-pc-windows-gnu/release/$exe.exe"
|
||||
echo "$f: $(stat -f %z "$f") bytes; DLLs: $(x86_64-w64-mingw32-objdump -p "$f" | grep 'DLL Name' | awk '{print $3}' | sort -u | tr '\n' ' ')"
|
||||
done
|
||||
57
proto-cuda/windows-node/make-package.sh
Executable file
57
proto-cuda/windows-node/make-package.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env bash
|
||||
# Builds igneum-node-windows.zip (run your own Igneum node on a Windows PC) on the Mac.
|
||||
# Contents: START-NODE.bat, start-node.ps1, BUILD-NODE.bat, build-node.ps1, ALLOW-FIREWALL.bat, allow-firewall.ps1,
|
||||
# README.txt, upload-log.bat (from windows-miner/), WINDOWS-MINER.md, igneum-miner.exe (cross-compiled, the status
|
||||
# reader), igneumd.exe when a cross-compiled one exists, and src.zip: a `git archive` of the node worktree's HEAD under
|
||||
# vendor/igneum-node/ plus igneum-pow/ from this repository's HEAD (the fork depends on it by relative path), so the PC
|
||||
# can build the node with BUILD-NODE.bat without any GitHub access.
|
||||
# Usage: windows-node/make-package.sh [node worktree, default vendor/igneum-node-win] [output zip, default ~/Desktop/igneum-node-windows.zip]
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$HERE/../.." && pwd)"
|
||||
NODE="${1:-$ROOT/vendor/igneum-node-win}"
|
||||
OUT="${2:-$HOME/Desktop/igneum-node-windows.zip}"
|
||||
[ -f "$NODE/Cargo.toml" ] || { echo "no node worktree at $NODE" >&2; exit 1; }
|
||||
EXE="$NODE/target/x86_64-pc-windows-gnu/release/igneumd.exe"
|
||||
MINER="$NODE/target/x86_64-pc-windows-gnu/release/igneum-miner.exe"
|
||||
STAGE="$(mktemp -d)/igneum-node-windows"
|
||||
mkdir -p "$STAGE/srcstage"
|
||||
cp "$HERE/START-NODE.bat" "$HERE/start-node.ps1" "$HERE/BUILD-NODE.bat" "$HERE/build-node.ps1" \
|
||||
"$HERE/ALLOW-FIREWALL.bat" "$HERE/allow-firewall.ps1" "$HERE/README.txt" "$STAGE/"
|
||||
cp "$ROOT/proto-cuda/windows-miner/upload-log.bat" "$STAGE/"
|
||||
cp "$ROOT/proto-cuda/WINDOWS-MINER.md" "$STAGE/"
|
||||
MINGW=/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32
|
||||
if [ -f "$EXE" ]; then
|
||||
cp "$EXE" "$STAGE/igneumd.exe"; echo "igneumd.exe: $(ls -la "$EXE" | awk '{print $5}') bytes (cross-compiled)"
|
||||
# The exe imports libstdc++-6.dll (rustc passes the cc crate's -lstdc++ as -Bdynamic, so -static does not cover it);
|
||||
# that DLL needs libgcc_s_seh-1.dll and libwinpthread-1.dll. Ship the three next to the exe (GCC runtime exception).
|
||||
IMPORTS="$(x86_64-w64-mingw32-objdump -p "$EXE" 2>/dev/null | grep 'DLL Name: lib' || true)" # no grep -q: pipefail would turn objdump's SIGPIPE into a miss
|
||||
if [ -n "$IMPORTS" ]; then
|
||||
for dll in lib/libstdc++-6.dll lib/libgcc_s_seh-1.dll bin/libwinpthread-1.dll; do
|
||||
[ -f "$MINGW/$dll" ] || { echo "missing $MINGW/$dll" >&2; exit 1; }
|
||||
cp "$MINGW/$dll" "$STAGE/"
|
||||
x86_64-w64-mingw32-strip "$STAGE/$(basename "$dll")" 2>/dev/null || true # the toolchain's libstdc++-6.dll carries 27 MB of debug symbols
|
||||
done
|
||||
echo "mingw runtime DLLs added next to the exe: libstdc++-6.dll libgcc_s_seh-1.dll libwinpthread-1.dll"
|
||||
fi
|
||||
else echo "no cross-compiled igneumd.exe at $EXE; the package builds on the PC"; fi
|
||||
if [ -f "$MINER" ]; then cp "$MINER" "$STAGE/igneum-miner.exe"; else echo "warning: no cross-compiled igneum-miner.exe at $MINER (BUILD-NODE.bat builds it too)"; fi
|
||||
# the source snapshot
|
||||
NODE_HEAD="$(git -C "$NODE" rev-parse HEAD)"
|
||||
ROOT_HEAD="$(git -C "$ROOT" rev-parse HEAD)"
|
||||
git -C "$NODE" archive --format=tar --prefix=vendor/igneum-node/ HEAD | tar -x -C "$STAGE/srcstage"
|
||||
git -C "$ROOT" archive --format=tar HEAD igneum-pow | tar -x -C "$STAGE/srcstage"
|
||||
{
|
||||
echo "igneum node source snapshot, made $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
echo "vendor/igneum-node: branch $(git -C "$NODE" rev-parse --abbrev-ref HEAD) commit $NODE_HEAD ($(git -C "$NODE" log -1 --format=%s))"
|
||||
echo "igneum-pow: igneum commit $ROOT_HEAD"
|
||||
echo "build: cd vendor/igneum-node && cargo build --release -p kaspad --features igneum-pow (binary: target/release/igneumd)"
|
||||
} > "$STAGE/srcstage/SNAPSHOT.txt"
|
||||
(cd "$STAGE/srcstage" && zip -qr "../src.zip" .)
|
||||
rm -rf "$STAGE/srcstage"
|
||||
# CRLF for the files Windows tools read as text
|
||||
for f in "$STAGE"/*.bat "$STAGE/README.txt"; do perl -pi -e 's/\r?\n/\r\n/' "$f"; done
|
||||
rm -f "$OUT"
|
||||
(cd "$(dirname "$STAGE")" && zip -qr "$OUT" "$(basename "$STAGE")")
|
||||
ls -la "$OUT"
|
||||
unzip -l "$OUT" | tail -n +4 | awk '{print $1, $4}' | sed '/^ *$/d'
|
||||
293
proto-cuda/windows-node/start-node.ps1
Normal file
293
proto-cuda/windows-node/start-node.ps1
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
# Igneum devnet node, Windows launcher. Started by START-NODE.bat, which sets the settings as environment variables
|
||||
# (MAC_NODE, RPC_LISTEN, P2P_LISTEN, APPDIR, UNSYNCED_MINING, STATUS_SECS, EXTRA_ARGS). 3 October 2026.
|
||||
#
|
||||
# What it does, in order:
|
||||
# 1. Checks igneumd.exe and the ports, prints the node version, tests whether the Mac node answers on its p2p port.
|
||||
# 2. Starts igneumd.exe --devnet with --appdir, --rpclisten, --listen, --addpeer=<Mac>, --nodnsseed, --disable-upnp and
|
||||
# --nologfiles; the node's own output goes to node-<stamp>.log next to the bat (a new segment per start, because
|
||||
# Start-Process truncates its redirect target). The launcher's own lines go to node-launcher-<stamp>.log.
|
||||
# 3. Keeps the PC awake (SetThreadExecutionState, no power plan change).
|
||||
# 4. Prints one status line every STATUS_SECS: blocks, headers, peers, blue score, DAA score, tips, synced yes or no.
|
||||
# The reading comes from `igneum-miner.exe watch 1 grpc://<RPC_LISTEN>` run in the background, the same tool and
|
||||
# line the mining launcher reads (vendor/igneum-node/igneum/miner/src/main.rs, watch):
|
||||
# <unix> node1 blocks=N headers=N daa=N tips=N peers=N difficulty=X sink=<12 hex> blue=N synced=true|false
|
||||
# 5. Restarts the node after a crash with a random delay of 5 to 60 s; uploads the launcher log every 5 minutes with
|
||||
# upload-log.bat when it is present; Ctrl+C stops the node and prints a summary.
|
||||
#
|
||||
# Why --addpeer and not --connect: in igneumd (kaspad/src/daemon.rs) --connect sets the inbound limit to 0 and skips
|
||||
# outbound peers, so the Mac could never dial this PC. --addpeer keeps the Mac as a permanent peer (retried with a
|
||||
# backoff of up to 8 minutes, components/connectionmanager/src/lib.rs) and leaves the listener open for the Mac to
|
||||
# connect in. The Mac node's inbound limit is the default 128.
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $root
|
||||
|
||||
$macNode = if ($env:MAC_NODE) { $env:MAC_NODE } else { '192.168.68.64:26611' }
|
||||
$rpcListen = if ($env:RPC_LISTEN) { $env:RPC_LISTEN } else { '127.0.0.1:26610' }
|
||||
$p2pListen = if ($env:P2P_LISTEN) { $env:P2P_LISTEN } else { '0.0.0.0:26611' }
|
||||
$appDir = if ($env:APPDIR) { $env:APPDIR } else { Join-Path $env:LOCALAPPDATA 'igneum\devnet' }
|
||||
$unsyncedMining = ($env:UNSYNCED_MINING -eq '1')
|
||||
$statusSecs = 30
|
||||
if ($env:STATUS_SECS -and [int]$env:STATUS_SECS -ge 5) { $statusSecs = [int]$env:STATUS_SECS }
|
||||
$extraArgs = $env:EXTRA_ARGS
|
||||
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$machine = $env:COMPUTERNAME
|
||||
$launcherLog = Join-Path $root "node-launcher-$stamp.log"
|
||||
$uploader = Join-Path $root 'upload-log.bat'
|
||||
$nodeExe = Join-Path $root 'igneumd.exe'
|
||||
$miner = Join-Path $root 'igneum-miner.exe'
|
||||
$started = Get-Date
|
||||
|
||||
# The RPC address the status reader dials: the listen address, with an unspecified interface mapped to loopback.
|
||||
$rpcHost = ($rpcListen -split ':')[0]
|
||||
$rpcPort = ($rpcListen -split ':')[-1]
|
||||
if ($rpcHost -eq '0.0.0.0' -or $rpcHost -eq '') { $rpcHost = '127.0.0.1' }
|
||||
$rpcUrl = "grpc://${rpcHost}:${rpcPort}"
|
||||
$p2pPort = ($p2pListen -split ':')[-1]
|
||||
$macHost = ($macNode -split ':')[0]
|
||||
$macPort = ($macNode -split ':')[-1]
|
||||
|
||||
function Log([string]$msg) {
|
||||
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg"
|
||||
Write-Host $line
|
||||
Add-Content -Path $launcherLog -Value $line
|
||||
}
|
||||
|
||||
# The last lines of a file another process is still writing (FileShare ReadWrite).
|
||||
function Get-TailLines([string]$path, [int]$count) {
|
||||
if (-not $path -or -not (Test-Path $path)) { return @() }
|
||||
try {
|
||||
$fs = New-Object IO.FileStream($path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
||||
try {
|
||||
$len = $fs.Length; $n = [Math]::Min($len, 16384); [void]$fs.Seek(-$n, [IO.SeekOrigin]::End)
|
||||
$b = New-Object byte[] $n; [void]$fs.Read($b, 0, $n)
|
||||
} finally { $fs.Close() }
|
||||
$lines = @(([Text.Encoding]::UTF8.GetString($b) -split "`n") | ForEach-Object { $_.TrimEnd("`r") } | Where-Object { $_ -ne '' })
|
||||
if ($lines.Count -le $count) { return $lines }
|
||||
return @($lines[($lines.Count - $count)..($lines.Count - 1)])
|
||||
} catch { return @() }
|
||||
}
|
||||
|
||||
Log "igneum node launcher on $machine (run $stamp)"
|
||||
Log "settings: Mac node $macNode, RPC $rpcListen, p2p $p2pListen, appdir $appDir, unsynced mining $(if ($unsyncedMining) { 'on' } else { 'off' }), status every $statusSecs s"
|
||||
|
||||
# ---- 1. checks ------------------------------------------------------------------------------------------------------
|
||||
|
||||
if (-not (Test-Path $nodeExe)) { Log 'igneumd.exe is missing. Run BUILD-NODE.bat first.'; exit 1 }
|
||||
$ver = ''
|
||||
try { $ver = (& $nodeExe --version 2>&1 | Select-Object -First 1) } catch { $ver = "could not run igneumd.exe: $_" }
|
||||
Log "node binary: $nodeExe ($ver)"
|
||||
if (-not (Test-Path $miner)) { Log 'igneum-miner.exe is not in this folder: the status line will only say whether the node process is alive' }
|
||||
|
||||
# Another node on the same ports (a second START-NODE window, or a stale process)
|
||||
foreach ($port in @([int]$rpcPort, [int]$p2pPort)) {
|
||||
try {
|
||||
$busy = @(Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue)
|
||||
if ($busy.Count -gt 0) {
|
||||
$owner = ''
|
||||
try { $owner = (Get-Process -Id $busy[0].OwningProcess -ErrorAction SilentlyContinue).ProcessName } catch { }
|
||||
Log "port $port is already in use by $owner (pid $($busy[0].OwningProcess)). Is a node already running? Close it first."
|
||||
exit 1
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
try { $macUp = Test-NetConnection -ComputerName $macHost -Port ([int]$macPort) -WarningAction SilentlyContinue -InformationLevel Quiet } catch { $macUp = $false }
|
||||
if ($macUp) { Log "the Mac node answers on $macNode" } else { Log "the Mac node at $macNode does not answer right now; igneumd will keep retrying (backoff up to 8 minutes)" }
|
||||
Log "If Windows Firewall asks about igneumd.exe: tick Private networks and click Allow access (the Mac node dials this PC on port $p2pPort). If you clicked Cancel once, run ALLOW-FIREWALL.bat."
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $appDir | Out-Null
|
||||
|
||||
# ---- 2. the node ---------------------------------------------------------------------------------------------------
|
||||
|
||||
# Start-Process joins the argument list with spaces and does not quote, so values that may contain spaces are quoted here.
|
||||
$nodeArgs = @('--devnet', ('"--appdir=' + $appDir + '"'), "--rpclisten=$rpcListen", "--listen=$p2pListen", "--addpeer=$macNode", '--nodnsseed', '--disable-upnp', '--nologfiles')
|
||||
if ($unsyncedMining) { $nodeArgs += '--enable-unsynced-mining' }
|
||||
if ($extraArgs) { $nodeArgs += ($extraArgs -split '\s+' | Where-Object { $_ -ne '' }) }
|
||||
|
||||
$script:node = @{ proc = $null; log = $null; err = $null; starts = 0; startedAt = $null; restartAt = $null; restarts = 0; exitCode = $null }
|
||||
|
||||
function Start-Node {
|
||||
$n = $script:node
|
||||
$n.starts += 1
|
||||
$seg = if ($n.starts -gt 1) { "-r$($n.starts)" } else { '' }
|
||||
$n.log = Join-Path $root "node-$stamp$seg.log"
|
||||
$n.err = $n.log + '.err'
|
||||
$n.restartAt = $null
|
||||
$n.proc = Start-Process -FilePath $nodeExe -ArgumentList $nodeArgs -WorkingDirectory $root -RedirectStandardOutput $n.log -RedirectStandardError $n.err -NoNewWindow -PassThru
|
||||
if ($n.proc) { $null = $n.proc.Handle } # without touching Handle, ExitCode comes back empty after the process ends
|
||||
$n.startedAt = Get-Date
|
||||
Log "igneumd started (pid $($n.proc.Id)): `"$nodeExe`" $($nodeArgs -join ' ') -> $($n.log)"
|
||||
}
|
||||
|
||||
function Stop-Node {
|
||||
$n = $script:node
|
||||
if ($n.proc -and -not $n.proc.HasExited) { & taskkill.exe /T /F /PID $n.proc.Id 2>$null | Out-Null }
|
||||
}
|
||||
|
||||
# ---- 4. status, read with igneum-miner watch in the background ----------------------------------------------------------
|
||||
|
||||
$script:watch = @{ proc = $null; log = (Join-Path $root "status-$stamp.tmp"); err = (Join-Path $root "status-$stamp.tmp.err"); lastStart = $null; fails = 0; last = $null }
|
||||
$script:statusCount = 0
|
||||
|
||||
function Start-Watch {
|
||||
if (-not (Test-Path $miner)) { return }
|
||||
$w = $script:watch
|
||||
if ($w.proc -and -not $w.proc.HasExited) {
|
||||
if ($w.lastStart -and ((Get-Date) - $w.lastStart).TotalSeconds -gt 90) { & taskkill.exe /T /F /PID $w.proc.Id 2>$null | Out-Null; $w.proc = $null; $w.fails += 1 }
|
||||
else { return }
|
||||
}
|
||||
try {
|
||||
$w.proc = Start-Process -FilePath $miner -ArgumentList @('watch', '1', $rpcUrl) -WorkingDirectory $root -RedirectStandardOutput $w.log -RedirectStandardError $w.err -NoNewWindow -PassThru
|
||||
if ($w.proc) { $null = $w.proc.Handle }
|
||||
$w.lastStart = Get-Date
|
||||
} catch { $w.proc = $null; $w.fails += 1 }
|
||||
}
|
||||
|
||||
# Returns the parsed reading when the watch process has finished, else $null. Sets $script:watch.last.
|
||||
function Read-Watch {
|
||||
$w = $script:watch
|
||||
if (-not $w.proc -or -not $w.proc.HasExited) { return $null }
|
||||
$w.proc = $null
|
||||
$line = $null
|
||||
try {
|
||||
$text = ''
|
||||
$fs = New-Object IO.FileStream($w.log, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
||||
try { $sr = New-Object IO.StreamReader($fs); $text = $sr.ReadToEnd(); $sr.Close() } finally { $fs.Close() }
|
||||
foreach ($l in ($text -split "`n")) { if ($l -match ' node1 blocks=') { $line = $l; break } }
|
||||
} catch { }
|
||||
if ($line -and $line -match 'blocks=([0-9]+) headers=([0-9]+) daa=([0-9]+) tips=([0-9]+) peers=([0-9]+) difficulty=([0-9.]+) sink=([0-9a-f]+)(?: blue=([0-9]+))?(?: synced=(\w+))?') {
|
||||
$r = @{ at = (Get-Date); blocks = [long]$Matches[1]; headers = [long]$Matches[2]; daa = [long]$Matches[3]; tips = [int]$Matches[4]; peers = [int]$Matches[5];
|
||||
difficulty = [double]$Matches[6]; sink = $Matches[7]; blue = $(if ($Matches[8]) { [long]$Matches[8] } else { -1 }); synced = $(if ($Matches[9]) { ($Matches[9] -eq 'true') } else { $null }) }
|
||||
$w.fails = 0
|
||||
$w.last = $r
|
||||
return $r
|
||||
}
|
||||
$w.fails += 1
|
||||
return $null
|
||||
}
|
||||
|
||||
function Print-Status($r) {
|
||||
$script:statusCount += 1
|
||||
$n = $script:node
|
||||
$up = ((Get-Date) - $started).ToString('hh\:mm\:ss')
|
||||
$alive = ($n.proc -and -not $n.proc.HasExited)
|
||||
$tail = "up $up, restarts $($n.restarts)"
|
||||
if (-not $alive) { Log "status: the node process is not running ($tail)"; return }
|
||||
if ($r) {
|
||||
$syncTxt = if ($r.synced -eq $null) { 'n/a' } elseif ($r.synced) { 'yes' } else { 'no' }
|
||||
$blueTxt = if ($r.blue -ge 0) { [string]$r.blue } else { 'n/a' }
|
||||
$peerTxt = if ($r.peers -eq 0) { '0 (the Mac node is not connected yet)' } else { [string]$r.peers }
|
||||
Log ("status: blocks {0}, headers {1}, peers {2}, blue score {3}, DAA {4}, tips {5}, difficulty {6:N0}, synced {7} ({8})" -f $r.blocks, $r.headers, $peerTxt, $blueTxt, $r.daa, $r.tips, $r.difficulty, $syncTxt, $tail)
|
||||
} elseif (-not (Test-Path $miner)) {
|
||||
Log "status: igneumd is running (pid $($n.proc.Id)), no reader for the chain state without igneum-miner.exe ($tail)"
|
||||
} else {
|
||||
$age = [int]((Get-Date) - $n.startedAt).TotalSeconds
|
||||
$why = if ($age -lt 60) { 'it is still starting, opening the database' } else { "no answer from the RPC at $rpcUrl for $($script:watch.fails) readings; see $($n.log)" }
|
||||
Log "status: igneumd is running (pid $($n.proc.Id)) but gave no reading yet: $why ($tail)"
|
||||
}
|
||||
}
|
||||
|
||||
# ---- upload (optional) --------------------------------------------------------------------------------------------------
|
||||
|
||||
function Upload-Logs {
|
||||
if (-not (Test-Path $uploader)) { return }
|
||||
$o = cmd /s /c "`"$uploader`" `"$launcherLog`" node-$machine node-$machine-$stamp" 2>&1
|
||||
Add-Content -Path $launcherLog -Value (" upload launcher log: " + ($o -join ' '))
|
||||
$n = $script:node
|
||||
if ($n.log -and (Test-Path $n.log)) {
|
||||
$o = cmd /s /c "`"$uploader`" `"$($n.log)`" nodelog-$machine nodelog-$machine-$stamp" 2>&1
|
||||
Add-Content -Path $launcherLog -Value (" upload node log: " + ($o -join ' '))
|
||||
}
|
||||
}
|
||||
|
||||
# ---- 3. keep awake ---------------------------------------------------------------------------------------------------
|
||||
|
||||
$keepAwake = $null
|
||||
try {
|
||||
$keepAwake = Add-Type -Name 'KeepAwake' -Namespace 'Igneum' -PassThru -MemberDefinition @'
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint SetThreadExecutionState(uint esFlags);
|
||||
'@
|
||||
[void]$keepAwake::SetThreadExecutionState([uint32]2147483648 -bor [uint32]0x00000001) # ES_CONTINUOUS | ES_SYSTEM_REQUIRED
|
||||
Log 'keeping the PC awake while the node runs'
|
||||
} catch { Log "could not set the keep-awake state: $_ (set Sleep to Never for the night)"; $keepAwake = $null }
|
||||
$lastAwake = Get-Date
|
||||
|
||||
# ---- 5. loop ----------------------------------------------------------------------------------------------------------
|
||||
|
||||
Start-Node
|
||||
Start-Sleep -Seconds 3
|
||||
if ($script:node.proc.HasExited) {
|
||||
Log "igneumd exited at once with code $($script:node.proc.ExitCode). Last lines:"
|
||||
foreach ($l in (Get-TailLines $script:node.err 8)) { Log " stderr: $l" }
|
||||
foreach ($l in (Get-TailLines $script:node.log 8)) { Log " stdout: $l" }
|
||||
Log "Nothing to restart. Fix the cause above (a wrong --appdir, a port in use, a database from an older build that needs deleting: $appDir) and start again."
|
||||
if ($keepAwake) { [void]$keepAwake::SetThreadExecutionState([uint32]2147483648) }
|
||||
exit 1
|
||||
}
|
||||
foreach ($l in (Get-TailLines $script:node.log 3)) { Log " node: $l" }
|
||||
|
||||
[Console]::TreatControlCAsInput = $true
|
||||
Log "running. Press Ctrl+C to stop. Status every $statusSecs s; the node's full output is in $($script:node.log)"
|
||||
$lastStatus = (Get-Date).AddSeconds($statusSecs - 10) # first reading soon after the start
|
||||
$lastUpload = Get-Date
|
||||
$watchPending = $false
|
||||
$stop = $false
|
||||
try {
|
||||
while (-not $stop) {
|
||||
Start-Sleep -Milliseconds 500
|
||||
if ($keepAwake -and ((Get-Date) - $lastAwake).TotalSeconds -ge 60) {
|
||||
[void]$keepAwake::SetThreadExecutionState([uint32]2147483648 -bor [uint32]0x00000001)
|
||||
$lastAwake = Get-Date
|
||||
}
|
||||
while ([Console]::KeyAvailable) {
|
||||
$k = [Console]::ReadKey($true)
|
||||
if ($k.Key -eq 'C' -and ($k.Modifiers -band [ConsoleModifiers]::Control)) { $stop = $true }
|
||||
}
|
||||
if ($stop) { break }
|
||||
|
||||
$n = $script:node
|
||||
if ($n.proc -and $n.proc.HasExited -and -not $n.restartAt) {
|
||||
$n.restarts += 1
|
||||
$n.exitCode = $null
|
||||
try { $n.exitCode = $n.proc.ExitCode } catch { }
|
||||
$delay = Get-Random -Minimum 5 -Maximum 61
|
||||
$n.restartAt = (Get-Date).AddSeconds($delay)
|
||||
Log "igneumd exited with code $($n.exitCode) after $([int]((Get-Date) - $n.startedAt).TotalSeconds) s; restarting in $delay s (restart $($n.restarts))"
|
||||
foreach ($l in (Get-TailLines $n.err 5)) { Log " stderr: $l" }
|
||||
foreach ($l in (Get-TailLines $n.log 5)) { Log " stdout: $l" }
|
||||
}
|
||||
if ($n.restartAt -and (Get-Date) -ge $n.restartAt) { Start-Node }
|
||||
|
||||
# one reading per status period: start the reader, print when it has finished
|
||||
if (((Get-Date) - $lastStatus).TotalSeconds -ge $statusSecs) {
|
||||
$lastStatus = Get-Date
|
||||
if ($n.proc -and -not $n.proc.HasExited -and (Test-Path $miner)) { Start-Watch; $watchPending = $true }
|
||||
else { Print-Status $null }
|
||||
}
|
||||
if ($watchPending) {
|
||||
$w = $script:watch
|
||||
if (-not $w.proc) { Print-Status $null; $watchPending = $false }
|
||||
elseif ($w.proc.HasExited) { $r = Read-Watch; Print-Status $r; $watchPending = $false }
|
||||
elseif ($w.lastStart -and ((Get-Date) - $w.lastStart).TotalSeconds -gt 60) { Start-Watch } # Start-Watch kills a reader older than 90 s
|
||||
}
|
||||
if (((Get-Date) - $lastUpload).TotalSeconds -ge 300) { Upload-Logs; $lastUpload = Get-Date }
|
||||
}
|
||||
} finally {
|
||||
[Console]::TreatControlCAsInput = $false
|
||||
if ($keepAwake) { [void]$keepAwake::SetThreadExecutionState([uint32]2147483648); Log 'released the keep-awake state' } # ES_CONTINUOUS alone clears it
|
||||
Log 'stopping the node'
|
||||
Stop-Node
|
||||
if ($script:watch.proc -and -not $script:watch.proc.HasExited) { & taskkill.exe /T /F /PID $script:watch.proc.Id 2>$null | Out-Null }
|
||||
Start-Sleep -Seconds 2
|
||||
$last = $script:watch.last
|
||||
$lastTxt = if ($last) { "last reading at $($last.at.ToString('HH:mm:ss')): blocks $($last.blocks), headers $($last.headers), peers $($last.peers), DAA $($last.daa)" } else { 'no reading from the node in this run' }
|
||||
Log ('SUMMARY after ' + ((Get-Date) - $started).ToString('hh\:mm\:ss') + ": node started $($script:node.starts) time(s), restarts $($script:node.restarts), $($script:statusCount) status lines; $lastTxt")
|
||||
Remove-Item -Path $script:watch.log, $script:watch.err -ErrorAction SilentlyContinue
|
||||
Upload-Logs
|
||||
Log "the node's output is in $($script:node.log); the database stays in $appDir"
|
||||
}
|
||||
Loading…
Reference in a new issue