# 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'))