git / moonbite-network / release/miner/mine.ps1 · raw
# MoonBite solo miner - Windows (PowerShell).## Solo and pool-free: runs a full MoonBite node on your PC and mines straight to# YOUR wallet address. No pool, no account, no middleman.## .\mine.ps1 moon1youraddress mine rewards to your wallet# .\mine.ps1 confirm the saved address (Enter keeps it), or ask for one# .\mine.ps1 address print the reward address in use# .\mine.ps1 stop stop the node# .\mine.ps1 -Cores 50% mine with half your CPU cores (or -Cores 4);# default is every physical core. The same# setting works as $env:MOONBITE_CORES.## Get your address from the MoonBite wallet app (or moonbite.org/wallet):# create a wallet, open Receive, copy the moon1... address.param([string]$arg = "", [string]$Cores = "")$ErrorActionPreference = "Stop"$here = Split-Path -Parent $MyInvocation.MyCommand.Path$daemon = Join-Path $here "moonbited.exe"$clibin = Join-Path $here "moonbite-cli.exe"if (-not (Test-Path $daemon)) { Write-Error "moonbited.exe not found next to this script."; exit 1 }# What this script will never do (docs/ADR-013-miner-distribution-policy.md):# download or execute anything, add a startup entry / scheduled task / service,# request administrator rights, or change Windows Defender or any other# security setting. It runs the node binary that sits next to it, and only# while this window is open.function Show-BinaryHash { # Print the SHA-256 of the exact binaries about to run, so the user can # compare them against the published manifest before mining starts. foreach ($bin in @($daemon, $clibin)) { if (Test-Path $bin) { $h = (Get-FileHash -Algorithm SHA256 -Path $bin).Hash.ToLower() Write-Host (" sha256 {0} {1}" -f $h, (Split-Path -Leaf $bin)) } } Write-Host " Compare against SHA256SUMS.txt from the release page:" Write-Host " https://moonbite.org/mine" Write-Host " (see ANTIVIRUS.md in this folder for signature + attestation checks)"}$datadir = if ($env:MOONBITE_DATADIR) { $env:MOONBITE_DATADIR } else { Join-Path $env:USERPROFILE ".moonbite" }$conf = Join-Path $datadir "moonbite.conf"$rewardFile = Join-Path $datadir "reward-address.txt"function Invoke-Cli { # moonbite-cli is a native exe: a non-zero exit does not raise a PowerShell # error, so probe $LASTEXITCODE and throw ourselves. Without this every # "is the node up?" check silently succeeds and the node is never started. $out = & $clibin "-datadir=$datadir" "-conf=$conf" @args 2>&1 if ($LASTEXITCODE -ne 0) { throw ("moonbite-cli failed: " + ($out | Out-String)) } $out | Where-Object { $_ -isnot [System.Management.Automation.ErrorRecord] }}function Test-Addr([string]$a) { return ($a -match '^moon1[0-9a-z]{20,88}$') }function Get-PhysicalCores { try { $n = (Get-CimInstance Win32_Processor -ErrorAction Stop | Measure-Object -Property NumberOfCores -Sum).Sum; if ($n -ge 1) { return [int]$n } } catch {} return [Environment]::ProcessorCount}function Resolve-Threads([string]$want, [int]$phys) { # How many CPU cores to mine with. Default: every physical core. RandomX gains # nothing from hyperthreads (each VM wants its own 2 MB of L3) and runs slower # on them, so requests above the physical count are capped there. if (-not $want) { $want = $env:MOONBITE_CORES } if (-not $want) { return $phys } $w = "$want".Trim() if ($w -match '^(\d{1,3})%$') { $n = [math]::Round($phys * [int]$Matches[1] / 100) } elseif ($w -match '^\d{1,4}$') { $n = [int]$w } else { throw "-Cores must be a number of cores (e.g. 4) or a share of them (e.g. 50%), not '$want'." } return [int][math]::Max(1, [math]::Min($phys, $n))}$physCores = Get-PhysicalCores$threads = Resolve-Threads $Cores $physCoresfunction Get-P2pPort { if ($env:MOONBITE_P2P_PORT) { [int]$env:MOONBITE_P2P_PORT } else { 9444 } }function Get-RpcPort { if ($env:MOONBITE_RPC_PORT) { [int]$env:MOONBITE_RPC_PORT } else { 9445 } }function Test-PortListening([int]$port) { # Get-NetTCPConnection is missing on very old Windows; fall back to netstat. try { return [bool](Get-NetTCPConnection -State Listen -LocalPort $port -ErrorAction Stop) } catch { return [bool](netstat -ano | Select-String ":$port\s.*LISTENING") }}function Write-Conf { New-Item -ItemType Directory -Force -Path $datadir | Out-Null $pw = $null if (Test-Path $conf) { $pw = (Select-String -Path $conf -Pattern '^rpcpassword=(.*)$').Matches.Groups[1].Value } if (-not $pw) { # Cryptographic RNG for the node RPC password. Get-Random is NOT a CSPRNG # (seedable/predictable), so a local attacker could guess it and drive the # node's wallet over 127.0.0.1 RPC. Use RandomNumberGenerator for 24 bytes # (48 hex chars). $bytes = New-Object byte[] 24 [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) $pw = -join ($bytes | ForEach-Object { '{0:x2}' -f $_ }) } $p2p = if ($env:MOONBITE_P2P_PORT) { "port=$($env:MOONBITE_P2P_PORT)`n" } else { "" } $rpcp = if ($env:MOONBITE_RPC_PORT) { "rpcport=$($env:MOONBITE_RPC_PORT)`n" } else { "" } @"server=1listen=1dbcache=512rpcuser=moonminerrpcpassword=$pwminingthreads=$threads$p2p${rpcp}addnode=67.205.154.64:9444addnode=165.232.106.9:9444"@ | Set-Content -Encoding ascii $conf}function Wait-Rpc { Write-Host " Starting the node..." for ($i=0; $i -lt 90; $i++) { try { Invoke-Cli getblockcount | Out-Null; return } catch {} if ($i -eq 4) { Write-Host " Still starting - the first run builds the RandomX cache, which can take a minute..." } Start-Sleep 2 } throw "The node did not start. Check $datadir\debug.log for the reason."}function Start-Node { # Already have a reachable node (e.g. this script re-run)? Use it. Its core # count was fixed when it started, so say how to apply a different one. $up = $false; try { Invoke-Cli getblockcount | Out-Null; $up = $true } catch {} if ($up) { $cur = $null if (Test-Path $conf) { $hit = Select-String -Path $conf -Pattern '^miningthreads=(\d+)$'; if ($hit) { $cur = [int]$hit.Matches[0].Groups[1].Value } } if ($cur -ne $threads) { Write-Host " Note: the node is already running with a different core setting. To mine with $threads cores, run .\mine.ps1 stop and start again." } return } # A node we can't authenticate to may already own the ports - typically a # second copy of this miner on the same PC. Only one node runs per machine; # launching another just fails to bind and hangs. Say so plainly and stop. if ((Test-PortListening (Get-P2pPort)) -or (Test-PortListening (Get-RpcPort))) { Write-Host "" Write-Host " A MoonBite node is already running on this computer" Write-Host " (network port $(Get-P2pPort) is in use). You are already mining -" Write-Host " only one node runs per machine, so this window isn't needed." Write-Host "" Write-Host " To run a SECOND, separate miner here on purpose, give it its own" Write-Host " ports and data folder first, for example:" Write-Host ' $env:MOONBITE_P2P_PORT=19444; $env:MOONBITE_RPC_PORT=19445' Write-Host ' $env:MOONBITE_DATADIR="$env:USERPROFILE\.moonbite2"' Write-Host " .\mine.ps1" Write-Host "" exit 0 } Write-Conf Start-Process -FilePath $daemon -ArgumentList "-datadir=$datadir","-conf=$conf" -WindowStyle Hidden | Out-Null Wait-Rpc}function Test-AddrValid([string]$a) { # Shape check, then the node's own bech32 checksum check, so a mistyped # address is refused instead of silently paying a stranger. if (-not (Test-Addr $a)) { return $false } try { return ((Invoke-Cli validateaddress $a | Out-String | ConvertFrom-Json).isvalid -eq $true) } catch { return $false }}function Resolve-Reward([string]$cand, [switch]$Confirm) { # Priority: CLI arg, env var, a previously saved address. When mining starts # (-Confirm) with a saved address and a person at the keyboard, show it and # let them press Enter to keep it or paste a new one. With no address at all, # ask for one (Enter skips). Unattended runs never wait: they use the saved # address, or fall back to the node's own built-in wallet so mining still # starts with zero setup. if ($cand) { if (-not (Test-AddrValid $cand)) { throw "'$cand' is not a valid MoonBite address. Check it and try again." } Set-Content -Encoding ascii $rewardFile $cand; return $cand } if ($env:MOONBITE_ADDRESS -and (Test-AddrValid $env:MOONBITE_ADDRESS)) { Set-Content -Encoding ascii $rewardFile $env:MOONBITE_ADDRESS; return $env:MOONBITE_ADDRESS } if (Test-Path $rewardFile) { $a = (Get-Content $rewardFile -Raw).Trim() if (Test-Addr $a) { if ($Confirm -and [Environment]::UserInteractive -and -not [Console]::IsInputRedirected) { Write-Host "" Write-Host " Rewards go to: $a" Write-Host " Press Enter to keep it, or paste a different address." while ($true) { $in = "$(Read-Host ' Address [Enter = keep]')".Trim() if (-not $in) { break } if (Test-AddrValid $in) { Set-Content -Encoding ascii $rewardFile $in; Write-Host " Saved. Rewards now go to $in"; return $in } Write-Host " That isn't a valid MoonBite address (typo?). Paste it again, or press Enter to keep the saved one." } } return $a } } if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) { Write-Host "" Write-Host " Where should your mining rewards go?" Write-Host " Paste your wallet address (MoonBite wallet app > Receive, starts with moon1)," Write-Host " or just press Enter to create a wallet on this computer instead." while ($true) { $in = (Read-Host " Address").Trim() if (-not $in) { break } if (Test-AddrValid $in) { Set-Content -Encoding ascii $rewardFile $in; Write-Host " Saved. Next start just press Enter to keep it."; return $in } Write-Host " That isn't a valid MoonBite address (typo?). Paste it again, or press Enter to skip." } } try { Invoke-Cli createwallet "miner" | Out-Null } catch { try { Invoke-Cli loadwallet "miner" | Out-Null } catch {} } $a = (Invoke-Cli -rpcwallet=miner getnewaddress "mining" | Out-String).Trim() if (-not (Test-Addr $a)) { throw "Could not create a mining wallet on this node." } Set-Content -Encoding ascii $rewardFile $a Write-Host "" Write-Host " Rewards will go to a wallet on THIS computer only: $a" Write-Host " IMPORTANT: it has no backup and no password yet. If this disk dies, the coins are gone." Write-Host " Back it up, then copy the file to a USB stick:" Write-Host " .\moonbite-cli.exe -datadir=`"$datadir`" -conf=`"$conf`" -rpcwallet=miner backupwallet `"$env:USERPROFILE\Desktop\moonbite-miner-wallet.dat`"" Write-Host " Or switch to your wallet app any time: .\mine.ps1 moon1youraddress" Write-Host "" return $a}switch ($arg) { "stop" { Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like "*moonbite-cli*generatetoaddress*" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } try { Invoke-Cli stop } catch {} for ($i=0; $i -lt 20; $i++) { if (-not (Get-Process moonbited -ErrorAction SilentlyContinue)) { break }; Start-Sleep 2 } Get-Process moonbited -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Write-Host "stopped." } "address" { Show-BinaryHash; Start-Node; Write-Host "Rewards go to: $(Resolve-Reward '')" } default { Show-BinaryHash Start-Node $addr = Resolve-Reward $arg -Confirm Write-Host "======================================================================" Write-Host " MoonBite solo miner" Write-Host " Rewards to: $addr" Write-Host " Using $threads of $physCores CPU cores (change with -Cores 50% or -Cores 4)." Write-Host " Ctrl-C to stop. Coins are spendable 100 blocks after being mined." Write-Host "======================================================================" Write-Host " Syncing with the network before mining..." $stable = 0 while ($true) { try { $info = Invoke-Cli getblockchaininfo | Out-String | ConvertFrom-Json if ($info -and -not $info.initialblockdownload -and $info.blocks -ge $info.headers) { break } # A chain whose newest block is over a day old reports "initial download" # until someone mines the next block, so waiting on that flag alone # deadlocks a young or quiet chain. Holding every header our peers # announce, with peers connected, for ~15 s means nothing is left to fetch. $peers = [int]("$(Invoke-Cli getconnectioncount)".Trim()) if ($info -and $peers -ge 1 -and $info.blocks -ge $info.headers) { $stable++ } else { $stable = 0 } if ($stable -ge 5) { Write-Host " Up to date with $peers peer(s); the chain is just quiet."; break } Write-Host " ...$($info.blocks) / $($info.headers) blocks" } catch {} Start-Sleep 3 } # Public launch: the genesis block is stamped 2 h after the announced launch, # and no block may be more than 2 h ahead of the clock, so block 1 becomes # possible exactly at launch. Until then wait here instead of failing calls. try { $gtime = [long]((Invoke-Cli getblockheader "$(Invoke-Cli getblockhash 0)".Trim() | Out-String | ConvertFrom-Json).time) $opens = $gtime - 7200 + 5 if ([int]"$(Invoke-Cli getblockcount)".Trim() -eq 0 -and [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() -lt $opens) { # Announced in Central European time (CET/CEST). $cet = [TimeZoneInfo]::FindSystemTimeZoneById("Central European Standard Time") $local = [TimeZoneInfo]::ConvertTimeFromUtc([DateTimeOffset]::FromUnixTimeSeconds($gtime - 7200).UtcDateTime, $cet) $at = $local.ToString('yyyy-MM-dd HH:mm') + $(if ($cet.IsDaylightSavingTime($local)) { " CEST" } else { " CET" }) Write-Host " MoonBite opens for mining at $at. Nobody can mine before then." Write-Host " Leave this window open: mining starts by itself at launch." while (($left = $opens - [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()) -gt 0) { $s = [TimeSpan]::FromSeconds($left) Write-Host (" launch in {0}d {1:00}h {2:00}m {3:00}s" -f $s.Days, $s.Hours, $s.Minutes, $s.Seconds) Start-Sleep ([Math]::Max(1, [Math]::Min($left, $(if ($left -gt 3600) { 600 } elseif ($left -gt 120) { 60 } else { 10 })))) } Write-Host " Launch! Mining is open." } } catch {} Write-Host " Synced at height $(Invoke-Cli getblockcount). Mining now." $found = 0 # The node hashes one block template per call and does not notice a newer # block from someone else until the call ends, so keep each call to about # $roundSecs of work: size the tries from the speed measured on the last # call that found nothing. Long calls waste work on a stale tip and orphan # blocks. MAXTRIES pins a fixed value instead. $roundSecs = 20 $tries = if ($env:MAXTRIES) { [int]$env:MAXTRIES } else { 5000 } while ($true) { try { $out = "" $sw = [Diagnostics.Stopwatch]::StartNew() try { $out = (Invoke-Cli generatetoaddress 1 $addr $tries | Out-String) } catch {} $secs = $sw.Elapsed.TotalSeconds if ($out -match '[0-9a-f]{64}') { $found++; Write-Host " BLOCK FOUND! height $(Invoke-Cli getblockcount) (found $found this session) peers $(Invoke-Cli getconnectioncount)" } elseif (-not $env:MAXTRIES -and $secs -gt 1) { $tries = [int][Math]::Min(200000, [Math]::Max(2000, $tries / $secs * $roundSecs)) } if ($secs -lt 1) { Start-Sleep 1 } } catch { Write-Host " (rpc hiccup) retrying..."; Start-Sleep 5 } } }}