diff --git a/docs/bench-log.md b/docs/bench-log.md index dd7c501cb..ab0465f6b 100644 --- a/docs/bench-log.md +++ b/docs/bench-log.md @@ -630,3 +630,19 @@ The self-test times are dominated by reading the 256 MiB cache back and hashing 2^26 words); the emulation's dataset build is 256 host threads over 2^24 items and says nothing about a GPU. Nothing NVIDIA was measured: the first NVRTC compile, cubin load and hash rate on the RTX 5090 are owed from the PC run (`proto-cuda/windows-app/TEST.md` lists the lines to copy here). + +## 4 October 2026, first hourly program swap on the live devnet: compile-ahead, no pause, two cards + +Live devnet v4, epoch boundary at DAA 3,600 (11:05:07 BST). The node announced the next seed once the sink was 150 DAA +past the seed score (`next_epoch_seed`, confirm = lead/4); each miner sent `prepare` to its worker and the worker built the +next program in the background while the current one mined. + +| Machine | Prepare sent | Compile | Swap at 3,600 | Restart | Rate before / after | Rejected | +|---|---|---|---|---|---|---| +| Mac M5 Max (Metal, `prepare 1`) | 449 DAA before the boundary | 82 ms | 0.01 ms, resident 2 programs | none | 26.7 / 26.7 MH/s | 0 | +| RTX 5090 (CUDA, nvcc in the background) | same template | 1,285 ms (nvcc 1,129, cache 4, dataset 23) | 0.00 ms, resident 2 programs 2 datasets | none | 121.8 / 123.4 MH/s | 0 | +| AMD Radeon integrated (OpenCL) | same | prepared | no restart | none | 2.74 / 2.74 MH/s | 0 | + +Reading: the compile-ahead rule (3 October 2026 decision: never restart every miner at once) holds on real values with +three vendors; the hash rate is unbroken through the boundary and the launcher's exit-42 rebuild path was not used +(`rebuilds 0`). The next boundary is DAA 7,200, which is also the first finality lock (weight window 7,200). diff --git a/packaging/mac/app/igneum-miner.sh b/packaging/mac/app/igneum-miner.sh deleted file mode 100755 index 31a24a2ae..000000000 --- a/packaging/mac/app/igneum-miner.sh +++ /dev/null @@ -1,491 +0,0 @@ -#!/bin/bash -# Igneum Miner for Apple silicon Macs: the node and the Metal miner in one Terminal window. The devnet is the only -# network for now. Lives in "Igneum Miner.app/Contents/Resources" next to bin/ (igneumd, igneum-miner, igneum-bench). -# The app's launcher opens this script in Terminal. 3 October 2026 (was the devnet launcher, renamed the same day); -# 0.2.0 for devnet v4 on 4 October 2026 (fresh data folder devnet-v4, EVM payout address, one miner process with -# MINERS identities, finality voting on, hot swap through the worker's prepare command). -# -# What it does, in order: -# 1. Checks the Mac (Apple silicon), the binaries, the ports, and that no other copy is running. -# 2. Starts igneumd --devnet with the database under ~/Library/Application Support/Igneum/devnet-v4, -# RPC on 127.0.0.1:26610, p2p on 0.0.0.0:26611, peered to SEED_PEERS (--addpeer, never --connect, -# so the seed node can dial back in). The node's output goes to ~/Library/Logs/Igneum/node-.log. -# 3. Waits until the node is synced ("syncing, N blocks" every 5 s meanwhile). The reading is -# `igneum-miner watch`: the node's own synced flag when the line carries one, else peers > 0, blocks -# caught up with headers, and the block count moving. -# 4. Starts ONE igneum-miner with the Metal worker (igneum-bench --serve) and MINERS identities inside it -# (--identities; vote keys and payout labels mac--1..N, or mac- alone for 1). One -# worker owns the whole GPU. Block rewards go to PAYOUT_EVM, or to an address derived from the hostname. -# 5. Prints a plain status line every 30 s, uploads the logs to the intake every 60 s, keeps the Mac -# awake with caffeinate, restarts the node (5 to 60 s later) or the miner after a crash, restarts the -# miner at once when it exits with code 42 (the hourly program change on a worker without prepare). -# 6. Ctrl+C, closing the window (SIGHUP) or `igneum-miner.sh --stop` stops the miner, its worker, -# then the node, then uploads the logs one last time. -# -# Nothing is written outside the app except the data directory and the log directory below. - -# ---- settings --------------------------------------------------------------------------------------------- -VERSION="0.2.0" -NETWORK="devnet" -CHAIN="devnet v4" -# The seed node(s) to peer with: the public seed node first, then the project lead's Mac on the house LAN. -# A comma list is allowed: "1.2.3.4:26611,5.6.7.8:26611". -SEED_PEERS="${SEED_PEERS:-188.245.5.161:26611,192.168.68.64:26611}" -# Miner identities on this Mac, all inside one miner process. 1 is right for one GPU; 0 runs the node alone. -MINERS="${MINERS:-1}" -# EVM payout address (0x + 40 hex) for the block rewards of every identity. Empty = derived from the hostname -# (the same on every run of this Mac; printed at start). A block without an address pays nobody. -PAYOUT_EVM="${PAYOUT_EVM:-}" -# Finality voting: 1 = every identity signs every checkpoint (the v4 default); 0 = mine without voting. -VOTE="${VOTE:-1}" -STATUS_SECS="${STATUS_SECS:-30}" -UPLOAD_SECS="${UPLOAD_SECS:-60}" -RPC_PORT="${IGNEUM_RPC_PORT:-26610}" -P2P_PORT="${IGNEUM_P2P_PORT:-26611}" -# devnet-v4 is a fresh folder: v4 is a new chain from genesis (4 Oct 2026); the v3 folder (devnet) is left alone. -DATA="${IGNEUM_DATA:-$HOME/Library/Application Support/Igneum/devnet-v4}" -LOGS="${IGNEUM_LOGS:-$HOME/Library/Logs/Igneum}" -# The log intake (site/api/log.mjs). The key only authorises log uploads and is meant to ship here. -LOG_URL="https://igneum-six.vercel.app/api/log" -LOG_KEY="***INTAKE-KEY-REMOVED***" -LIVE_PAGE="igneum.network/live" - -# ---- paths and names -------------------------------------------------------------------------------------- -RES="$(cd "$(dirname "$0")" && pwd)" -BIN="$RES/bin" -NODE_EXE="$BIN/igneumd" -MINER_EXE="$BIN/igneum-miner" -WORKER_EXE="$BIN/igneum-bench" -RPC_URL="grpc://127.0.0.1:$RPC_PORT" -PIDFILE="$DATA/igneum-miner.pid" -RUNFILE="$DATA/igneum-miner.run" -STAMP="$(date +%Y%m%d-%H%M%S)" -HOST="$(scutil --get LocalHostName 2>/dev/null || hostname -s)" -HOST="$(printf '%s' "$HOST" | tr -c 'A-Za-z0-9-' '-' | sed -e 's/^-*//' -e 's/-*$//')" -[ -n "$HOST" ] || HOST="mac" -LABEL_BASE="mac-$HOST" -RUN_ID="$LABEL_BASE-$STAMP" -LAUNCHER_LOG="$LOGS/igneum-$STAMP.log" -NODE_LOG="" -START_AT=$SECONDS - -log() { - local line - line="$(date '+%Y-%m-%d %H:%M:%S') $*" - printf '%s\n' "$line" 2>/dev/null || true - [ -d "$LOGS" ] && printf '%s\n' "$line" >> "$LAUNCHER_LOG" 2>/dev/null -} - -alive() { [ -n "$1" ] && kill -0 "$1" 2>/dev/null; } - -uptime_txt() { - local s=$(( SECONDS - START_AT )) - printf '%02d:%02d:%02d' $(( s / 3600 )) $(( s % 3600 / 60 )) $(( s % 60 )) -} - -# The Terminal window and tab title: the product name, never the script path (OSC 0 sets both in Terminal.app). -[ -t 1 ] && printf '\033]0;Igneum Miner\007' - -# ---- --stop: stop a running instance (also used by "Stop Igneum Miner.command") ---------------------------- -stop_mode() { - local pid="" waited=0 left - [ -f "$PIDFILE" ] && pid="$(cat "$PIDFILE" 2>/dev/null)" - if alive "$pid"; then - echo "stopping Igneum Miner (launcher pid $pid): miners first, then the node" - kill -TERM "$pid" 2>/dev/null - while alive "$pid" && [ $waited -lt 60 ]; do sleep 1; waited=$(( waited + 1 )); done - if alive "$pid"; then echo "the launcher did not stop in 60 s; forcing"; kill -KILL "$pid" 2>/dev/null; fi - else - echo "no running launcher found (no live pid in $PIDFILE)" - fi - # Anything left from this app (a launcher killed with SIGKILL leaves its children behind) - left="$(pgrep -f 'Igneum Miner.app/Contents/Resources/bin/' 2>/dev/null)" - if [ -n "$left" ]; then - echo "stopping leftover processes: $(echo $left | tr '\n' ' ')" - pkill -TERM -f 'Igneum Miner.app/Contents/Resources/bin/' 2>/dev/null - sleep 5 - pkill -KILL -f 'Igneum Miner.app/Contents/Resources/bin/' 2>/dev/null - fi - rm -f "$PIDFILE" "$RUNFILE" - echo "Igneum Miner is stopped." - exit 0 -} - -case "$1" in - --stop) stop_mode ;; - --help|-h) sed -n '2,22p' "$0"; echo; echo "usage: igneum-miner.sh [--stop] (settings: SEED_PEERS, MINERS, PAYOUT_EVM, VOTE, STATUS_SECS, UPLOAD_SECS at the top)"; exit 0 ;; -esac - -# ---- 1. checks -------------------------------------------------------------------------------------------- -# sysctl rather than uname -m: a Terminal running under Rosetta reports x86_64 from uname on an Apple silicon Mac. -if [ "$(sysctl -n hw.optional.arm64 2>/dev/null)" != "1" ]; then - echo "Igneum Miner needs an Apple silicon Mac (M1 or later). This Mac reports $(uname -m) and no arm64 support. Nothing was started." - exit 1 -fi -for f in "$NODE_EXE" "$MINER_EXE" "$WORKER_EXE"; do - if [ ! -x "$f" ]; then echo "missing or not executable: $f. The app is incomplete; copy it again from the DMG."; exit 1; fi -done -mkdir -p "$DATA" "$LOGS" || { echo "cannot create $DATA or $LOGS"; exit 1; } -if [ -f "$PIDFILE" ] && alive "$(cat "$PIDFILE" 2>/dev/null)"; then - echo "Igneum Miner is already running in another window (launcher pid $(cat "$PIDFILE")). Press Ctrl+C there, or open Stop Igneum Miner." - exit 1 -fi -for port in "$RPC_PORT" "$P2P_PORT"; do - busy="$(lsof -nP -iTCP:"$port" -sTCP:LISTEN -t 2>/dev/null | head -1)" - if [ -n "$busy" ]; then - echo "port $port is already in use by pid $busy ($(ps -p "$busy" -o comm= 2>/dev/null)). Is a node already running? Open Stop Igneum Miner first." - exit 1 - fi -done -case "$MINERS" in ''|*[!0-9]*) echo "MINERS must be a number (got '$MINERS')"; exit 1 ;; esac -case "$VOTE" in 0|1) ;; *) echo "VOTE must be 0 or 1 (got '$VOTE')"; exit 1 ;; esac -# The EVM payout address: PAYOUT_EVM, or 20 bytes of SHA-256 over a fixed tag and the hostname label. -if [ -n "$PAYOUT_EVM" ]; then - case "$PAYOUT_EVM" in 0x[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) ;; - *) echo "PAYOUT_EVM must be 0x followed by 40 hex characters (got '$PAYOUT_EVM')"; exit 1 ;; esac - EVM_ADDRESS="$(printf '%s' "$PAYOUT_EVM" | tr 'A-F' 'a-f')"; EVM_SOURCE="PAYOUT_EVM" -else - EVM_ADDRESS="0x$(printf 'igneum-evm-payout-v0:%s' "$LABEL_BASE" | shasum -a 256 | cut -c1-40)"; EVM_SOURCE="derived from the hostname; set PAYOUT_EVM to pay elsewhere" -fi - -echo "$$" > "$PIDFILE" -printf 'pid=%s\nscript=%s\nrpc=%s\nrun=%s\n' "$$" "$RES/igneum-miner.sh" "$RPC_URL" "$RUN_ID" > "$RUNFILE" - -log "Igneum Miner $VERSION on $HOST, $CHAIN (run $RUN_ID)" -log "Nobody from Igneum will ever ask for your seed. Nothing is bought or sold on this network." -log "node: RPC 127.0.0.1:$RPC_PORT, p2p 0.0.0.0:$P2P_PORT, data $DATA" -log "seed peers: $SEED_PEERS; identities: $MINERS; voting: $(if [ "$VOTE" = 1 ]; then echo on; else echo off; fi); logs: $LOGS" -log "EVM payout address for the block rewards: $EVM_ADDRESS ($EVM_SOURCE)" -log "node $("$NODE_EXE" --version 2>&1 | head -1); worker $(basename "$WORKER_EXE") (Metal)" - -# ---- keep awake ------------------------------------------------------------------------------------------- -caffeinate -dims -w $$ >/dev/null 2>&1 & -CAFFEINATE_PID=$! -log "keeping the Mac awake while this window runs (caffeinate)" - -# ---- the node --------------------------------------------------------------------------------------------- -NODE_PID=""; NODE_STARTS=0; NODE_RESTARTS=0; NODE_RESTART_AT=""; NODE_STARTED_AT=0 -NODE_SYNCED=0; NODE_SYNCED_AT=""; SYNC_PREV=""; SYNC_STABLE_SINCE=""; NOPEER_WARNED=0 -NODE_BLOCKS=0; NODE_HEADERS=0; NODE_PEERS=0; NODE_DAA=0; NODE_READ_OK=0; NODE_FLAG="" - -start_node() { - local args p seg - NODE_STARTS=$(( NODE_STARTS + 1 )) - seg=""; [ $NODE_STARTS -gt 1 ] && seg="-r$NODE_STARTS" - NODE_LOG="$LOGS/node-$STAMP$seg.log" - # --yes answers the database-version question so the node never waits on a prompt nobody sees - args=("--$NETWORK" "--appdir=$DATA" "--rpclisten=127.0.0.1:$RPC_PORT" "--listen=0.0.0.0:$P2P_PORT" --nodnsseed --disable-upnp --nologfiles --yes) - for p in $(printf '%s' "$SEED_PEERS" | tr ',' ' '); do args+=("--addpeer=$p"); done - # Children ignore SIGHUP (the window closing) so the launcher can stop them in order; bash already makes - # background children ignore SIGINT (Ctrl+C) in a script. - ( trap '' HUP; exec "$NODE_EXE" "${args[@]}" ) > "$NODE_LOG" 2>&1 & - NODE_PID=$! - NODE_STARTED_AT=$SECONDS - NODE_RESTART_AT="" - NODE_SYNCED=0; SYNC_PREV=""; SYNC_STABLE_SINCE=""; NODE_READ_OK=0 - log "igneumd started (pid $NODE_PID): ${args[*]}" - log "node output: $NODE_LOG" -} - -stop_node() { - local waited=0 - alive "$NODE_PID" || return 0 - kill -TERM "$NODE_PID" 2>/dev/null - while alive "$NODE_PID" && [ $waited -lt 30 ]; do sleep 1; waited=$(( waited + 1 )); done - if alive "$NODE_PID"; then log "the node did not stop in 30 s; forcing"; kill -KILL "$NODE_PID" 2>/dev/null; fi - wait "$NODE_PID" 2>/dev/null -} - -# One reading from `igneum-miner watch 1 `: the first "node1 blocks=..." line. The watch command sleeps -# 10 s after its sample, so it runs in the background and is stopped once the line is there. -read_node() { - local tmp="$LOGS/.watch-$$.tmp" pid n=0 line - NODE_READ_OK=0 - alive "$NODE_PID" || return 1 - "$MINER_EXE" watch 1 "$RPC_URL" > "$tmp" 2>/dev/null & - pid=$! - while [ $n -lt 32 ]; do - if grep -q ' node1 blocks=' "$tmp" 2>/dev/null; then break; fi - alive "$pid" || break - sleep 0.25; n=$(( n + 1 )) - done - kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null - line="$(grep ' node1 blocks=' "$tmp" 2>/dev/null | head -1)" - rm -f "$tmp" - [ -n "$line" ] || return 1 - set -- $(printf '%s' "$line" | sed -E 's/.* blocks=([0-9]+) headers=([0-9]+) daa=([0-9]+) tips=([0-9]+) peers=([0-9]+).*/\1 \2 \3 \4 \5/') - [ "$#" -eq 5 ] || return 1 - NODE_BLOCKS=$1; NODE_HEADERS=$2; NODE_DAA=$3; NODE_PEERS=$5; NODE_READ_OK=1 - # the v4 watch line carries the node's own flag: synced=true|false (empty on an older miner) - NODE_FLAG="$(printf '%s' "$line" | sed -n -E 's/.* synced=(true|false).*/\1/p')" - return 0 -} - -# Synced: a peer, blocks caught up with headers, more than genesis, and the count moving between two readings -# (or stable for 60 s with a peer, for a devnet that is not producing blocks right now). -check_sync() { - read_node || return 1 - if [ "$NODE_FLAG" = "true" ] && [ "$NODE_PEERS" -gt 0 ]; then SYNC_PREV=$NODE_BLOCKS; return 0; fi - if [ "$NODE_FLAG" = "false" ]; then SYNC_PREV=$NODE_BLOCKS; return 1; fi - if [ "$NODE_PEERS" -gt 0 ] && [ "$NODE_BLOCKS" -ge "$NODE_HEADERS" ] && [ "$NODE_BLOCKS" -gt 1 ]; then - [ -n "$SYNC_STABLE_SINCE" ] || SYNC_STABLE_SINCE=$SECONDS - if { [ -n "$SYNC_PREV" ] && [ "$NODE_BLOCKS" -gt "$SYNC_PREV" ]; } || [ $(( SECONDS - SYNC_STABLE_SINCE )) -ge 60 ]; then - SYNC_PREV=$NODE_BLOCKS - return 0 - fi - else - SYNC_STABLE_SINCE="" - fi - SYNC_PREV=$NODE_BLOCKS - return 1 -} - -# ---- the miners ------------------------------------------------------------------------------------------- -# One miner process (slot 1) carries every identity (--identities MINERS): one worker, one dataset and one -# program on the GPU. The slot arrays stay (label, pid, worker pid, log, restart bookkeeping, accepted blocks -# of earlier runs) so the loop below is unchanged; PROCS is the number of processes, 1 or 0. -MINER_LABEL=(); MINER_PID=(); MINER_WORKER=(); MINER_LOG=(); MINER_STARTS=(); MINER_RESTARTS=(); MINER_RESTART_AT=(); MINER_ACC_BASE=(); MINER_ID=() -PROCS=0; [ "$MINERS" -gt 0 ] && PROCS=1 -i=1 -while [ $i -le "$PROCS" ]; do - MINER_LABEL[$i]="$LABEL_BASE" - MINER_PID[$i]=""; MINER_WORKER[$i]=""; MINER_LOG[$i]=""; MINER_STARTS[$i]=0; MINER_RESTARTS[$i]=0; MINER_RESTART_AT[$i]=""; MINER_ACC_BASE[$i]=0; MINER_ID[$i]="" - i=$(( i + 1 )) -done -MINERS_UP=0 -MINER_ARGS=(--worker "$WORKER_EXE" --status-secs "$STATUS_SECS" --exit-on-seed-change --evm-address "$EVM_ADDRESS") -[ "$MINERS" -gt 1 ] && MINER_ARGS+=(--identities "$MINERS") -[ "$VOTE" = 1 ] || MINER_ARGS+=(--no-vote) - -start_miner() { - local i=$1 label seg - label="${MINER_LABEL[$i]}" - MINER_STARTS[$i]=$(( ${MINER_STARTS[$i]} + 1 )) - seg=""; [ "${MINER_STARTS[$i]}" -gt 1 ] && seg="-r${MINER_STARTS[$i]}" - MINER_LOG[$i]="$LOGS/miner-$label-$STAMP$seg.log" - MINER_RESTART_AT[$i]="" - ( trap '' HUP; exec "$MINER_EXE" mine "$RPC_URL" 1 100000000 "$label" "${MINER_ARGS[@]}" --payout-label "$label" ) > "${MINER_LOG[$i]}" 2>&1 & - MINER_PID[$i]=$! - MINER_WORKER[$i]="" - log "miner $label started (pid ${MINER_PID[$i]}): igneum-miner mine $RPC_URL 1 100000000 $label ${MINER_ARGS[*]} --payout-label $label" - log "miner output: ${MINER_LOG[$i]}" -} - -# The worker is the miner's child; remembered so it can be stopped if the miner dies without closing it. -note_worker() { - local i=$1 - [ -z "${MINER_WORKER[$i]}" ] && alive "${MINER_PID[$i]}" && MINER_WORKER[$i]="$(pgrep -P "${MINER_PID[$i]}" 2>/dev/null | head -1)" -} - -accepted_in() { [ -f "$1" ] && grep -c ' ACCEPTED block' "$1" 2>/dev/null || echo 0; } - -stop_miner() { - local i=$1 waited=0 - note_worker "$i" - alive "${MINER_PID[$i]}" && kill -TERM "${MINER_PID[$i]}" 2>/dev/null - alive "${MINER_WORKER[$i]}" && kill -TERM "${MINER_WORKER[$i]}" 2>/dev/null - while { alive "${MINER_PID[$i]}" || alive "${MINER_WORKER[$i]}"; } && [ $waited -lt 8 ]; do sleep 1; waited=$(( waited + 1 )); done - alive "${MINER_PID[$i]}" && kill -KILL "${MINER_PID[$i]}" 2>/dev/null - alive "${MINER_WORKER[$i]}" && kill -KILL "${MINER_WORKER[$i]}" 2>/dev/null - wait "${MINER_PID[$i]}" 2>/dev/null - MINER_ACC_BASE[$i]=$(( ${MINER_ACC_BASE[$i]} + $(accepted_in "${MINER_LOG[$i]}") )) - MINER_PID[$i]=""; MINER_WORKER[$i]="" -} - -start_all_miners() { - local i=1 - while [ $i -le "$PROCS" ]; do start_miner $i; i=$(( i + 1 )); done - MINERS_UP=1 -} - -stop_all_miners() { - local i=1 - while [ $i -le "$PROCS" ]; do stop_miner $i; i=$(( i + 1 )); done - MINERS_UP=0 -} - -# Miner bookkeeping every second: the live id once it is known, exits and restarts. -tend_miners() { - local i=1 rc delay - while [ $i -le "$PROCS" ]; do - if alive "${MINER_PID[$i]}"; then - note_worker "$i" - if [ -z "${MINER_ID[$i]}" ]; then - MINER_ID[$i]="$(sed -n 's/.*vote_key_hash=\([0-9a-f]\{8\}\).*/\1/p' "${MINER_LOG[$i]}" 2>/dev/null | head -1)" - [ -n "${MINER_ID[$i]}" ] && log "miner ${MINER_LABEL[$i]} shows on $LIVE_PAGE as ${MINER_ID[$i]} once it finds a block" - fi - elif [ -n "${MINER_PID[$i]}" ]; then - wait "${MINER_PID[$i]}" 2>/dev/null; rc=$? - alive "${MINER_WORKER[$i]}" && kill -TERM "${MINER_WORKER[$i]}" 2>/dev/null - MINER_ACC_BASE[$i]=$(( ${MINER_ACC_BASE[$i]} + $(accepted_in "${MINER_LOG[$i]}") )) - MINER_PID[$i]=""; MINER_WORKER[$i]="" - if [ "$rc" -eq 42 ]; then - log "miner ${MINER_LABEL[$i]}: the hourly program changed (exit 42); restarting it now" - MINER_RESTART_AT[$i]=$SECONDS - else - delay=$(( RANDOM % 56 + 5 )) - MINER_RESTARTS[$i]=$(( ${MINER_RESTARTS[$i]} + 1 )) - MINER_RESTART_AT[$i]=$(( SECONDS + delay )) - log "miner ${MINER_LABEL[$i]} exited with code $rc; restarting in $delay s (restart ${MINER_RESTARTS[$i]}). Last lines:" - tail -3 "${MINER_LOG[$i]}" 2>/dev/null | while IFS= read -r l; do log " miner: $l"; done - fi - elif [ -n "${MINER_RESTART_AT[$i]}" ] && [ "$SECONDS" -ge "${MINER_RESTART_AT[$i]}" ] && [ "$NODE_SYNCED" -eq 1 ]; then - start_miner $i - fi - i=$(( i + 1 )) - done -} - -# ---- status ----------------------------------------------------------------------------------------------- -STATUS_COUNT=0 -print_status() { - local i=1 acc=0 a st mh age node_txt miner_txt sync_txt - STATUS_COUNT=$(( STATUS_COUNT + 1 )) - if alive "$NODE_PID"; then - if read_node; then - if [ "$NODE_SYNCED" -eq 1 ]; then sync_txt="synced"; else sync_txt="syncing"; fi - node_txt="node $NODE_BLOCKS blocks, $NODE_PEERS peers, $sync_txt" - else - node_txt="node running (pid $NODE_PID), no reading from the RPC yet" - fi - elif [ -n "$NODE_RESTART_AT" ]; then - node_txt="node restarting in $(( NODE_RESTART_AT - SECONDS )) s" - else - node_txt="node not running" - fi - if [ "$MINERS" -eq 0 ]; then - miner_txt="no miner (MINERS=0)" - else - mh="n/a"; age="n/a" - while [ $i -le "$PROCS" ]; do - acc=$(( acc + ${MINER_ACC_BASE[$i]} + $(accepted_in "${MINER_LOG[$i]}") )) - if alive "${MINER_PID[$i]}"; then - st="$(grep ' STATUS ' "${MINER_LOG[$i]}" 2>/dev/null | tail -1)" - if [ -n "$st" ]; then - a="$(printf '%s' "$st" | sed -E 's/.* hash=([0-9.]+) MH\/s wall.* template_age=([0-9.]+)s.*/\1 \2/')" - set -- $a; mh="${1:-n/a}"; age="${2:-n/a}" - fi - fi - i=$(( i + 1 )) - done - if [ "$MINERS_UP" -eq 1 ]; then - miner_txt="accepted $acc blocks, $mh MH/s, template $age s old" - else - miner_txt="accepted $acc blocks, miner waiting for the node" - fi - fi - log "status: $miner_txt | $node_txt | up $(uptime_txt)" -} - -# ---- uploads ---------------------------------------------------------------------------------------------- -# JSON {label, machine, run_id, lines} with the last 256 KB of the file, as site/api/log.mjs expects. -upload_one() { - local file=$1 label=$2 json="$LOGS/.upload-$$-$RANDOM.json" code - [ -s "$file" ] || return 0 - { - printf '{"label":"%s","machine":"%s","run_id":"%s","lines":"' "$label" "$HOST" "$RUN_ID" - tail -c 262144 "$file" | tr -d '\000-\010\013\014\016-\037' | awk 'BEGIN{ORS=""} { gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); gsub(/\t/,"\\t"); printf "%s\\n", $0 }' - printf '"}' - } > "$json" - code="$(curl -sS --max-time 60 -X POST "$LOG_URL" -H "Content-Type: application/json" -H "x-igneum-key: $LOG_KEY" --data-binary "@$json" -o /dev/null -w '%{http_code}' 2>&1)" - rm -f "$json" - printf '%s' "$code" -} - -upload_logs() { - local i=1 r - r="$(upload_one "$LAUNCHER_LOG" "$LABEL_BASE")"; [ "$r" = "200" ] || log "upload of the launcher log failed: $r" - r="$(upload_one "$NODE_LOG" "nodelog-$LABEL_BASE")"; [ "$r" = "200" ] || [ -z "$r" ] || log "upload of the node log failed: $r" - while [ $i -le "$PROCS" ]; do - r="$(upload_one "${MINER_LOG[$i]}" "miner-${MINER_LABEL[$i]}")"; [ "$r" = "200" ] || [ -z "$r" ] || log "upload of the miner log failed: $r" - i=$(( i + 1 )) - done -} - -UPLOAD_PID="" -upload_logs_bg() { - alive "$UPLOAD_PID" && return 0 # the previous upload is still running - upload_logs & - UPLOAD_PID=$! -} - -# ---- stop ------------------------------------------------------------------------------------------------- -STOPPING=0; STOP_REASON="" -on_signal() { STOPPING=1; STOP_REASON="$1"; } -trap 'on_signal "Ctrl+C"' INT -trap 'on_signal "stop requested"' TERM -trap 'on_signal "window closed"' HUP - -cleanup() { - trap '' INT TERM HUP - local acc=0 i=1 - log "$STOP_REASON: stopping the miners, then the node" - [ "$MINERS" -gt 0 ] && stop_all_miners - stop_node - alive "$CAFFEINATE_PID" && kill "$CAFFEINATE_PID" 2>/dev/null - alive "$UPLOAD_PID" && wait "$UPLOAD_PID" 2>/dev/null - while [ $i -le "$PROCS" ]; do acc=$(( acc + ${MINER_ACC_BASE[$i]} )); i=$(( i + 1 )); done - log "SUMMARY after $(uptime_txt): node started $NODE_STARTS time(s), restarts $NODE_RESTARTS, $STATUS_COUNT status lines, $acc accepted blocks; data stays in $DATA" - upload_logs - rm -f "$PIDFILE" "$RUNFILE" - log "stopped. This window can be closed." -} - -# ---- run -------------------------------------------------------------------------------------------------- -start_node -sleep 2 -if ! alive "$NODE_PID"; then - log "igneumd exited at once. Last lines of $NODE_LOG:" - tail -8 "$NODE_LOG" 2>/dev/null | while IFS= read -r l; do log " node: $l"; done - log "Nothing to restart. If it mentions the database, delete \"$DATA\" and start again (the chain resyncs from the seed in seconds)." - alive "$CAFFEINATE_PID" && kill "$CAFFEINATE_PID" 2>/dev/null - rm -f "$PIDFILE" "$RUNFILE" - exit 1 -fi -log "running. Press Ctrl+C to stop. Status every $STATUS_SECS s, logs uploaded every $UPLOAD_SECS s." -log "node: syncing, waiting for the first peer ($SEED_PEERS)" - -LAST_SYNC_CHECK=$SECONDS -LAST_STATUS=$SECONDS -LAST_UPLOAD=$SECONDS -while [ "$STOPPING" -eq 0 ]; do - sleep 1 - [ "$STOPPING" -eq 0 ] || break - - # the node: crash, restart - if [ -n "$NODE_PID" ] && ! alive "$NODE_PID" && [ -z "$NODE_RESTART_AT" ]; then - wait "$NODE_PID" 2>/dev/null; rc=$? - NODE_RESTARTS=$(( NODE_RESTARTS + 1 )) - delay=$(( RANDOM % 56 + 5 )) - NODE_RESTART_AT=$(( SECONDS + delay )) - NODE_SYNCED=0 - log "igneumd exited with code $rc after $(( SECONDS - NODE_STARTED_AT )) s; restarting in $delay s (restart $NODE_RESTARTS). Last lines:" - tail -5 "$NODE_LOG" 2>/dev/null | while IFS= read -r l; do log " node: $l"; done - if [ "$MINERS_UP" -eq 1 ]; then log "stopping the miners until the node is back and synced (their connection died with it)"; stop_all_miners; fi - fi - if [ -n "$NODE_RESTART_AT" ] && [ "$SECONDS" -ge "$NODE_RESTART_AT" ]; then start_node; fi - - # sync, then the miners - if alive "$NODE_PID" && [ "$NODE_SYNCED" -eq 0 ] && [ $(( SECONDS - LAST_SYNC_CHECK )) -ge 5 ]; then - LAST_SYNC_CHECK=$SECONDS - if check_sync; then - NODE_SYNCED=1; NODE_SYNCED_AT=$SECONDS - log "node synced: $NODE_BLOCKS blocks, $NODE_PEERS peer(s), $(( SECONDS - NODE_STARTED_AT )) s after the start" - if [ "$MINERS" -gt 0 ] && [ "$MINERS_UP" -eq 0 ]; then start_all_miners; fi - elif [ "$NODE_READ_OK" -eq 1 ]; then - log "node: syncing, $NODE_BLOCKS blocks ($NODE_HEADERS headers, $NODE_PEERS peers)" - if [ "$NOPEER_WARNED" -eq 0 ] && [ "$NODE_PEERS" -eq 0 ] && [ $(( SECONDS - NODE_STARTED_AT )) -ge 90 ]; then - NOPEER_WARNED=1 - log "no peer after 90 s: is the seed node reachable at $SEED_PEERS? The node keeps retrying (backoff up to 8 minutes)." - fi - elif [ $(( SECONDS - NODE_STARTED_AT )) -ge 60 ]; then - log "node: no answer from the RPC at $RPC_URL yet (still opening its database?); see $NODE_LOG" - fi - fi - [ "$MINERS" -gt 0 ] && tend_miners - - if [ $(( SECONDS - LAST_STATUS )) -ge "$STATUS_SECS" ]; then LAST_STATUS=$SECONDS; print_status; fi - if [ $(( SECONDS - LAST_UPLOAD )) -ge "$UPLOAD_SECS" ]; then LAST_UPLOAD=$SECONDS; upload_logs_bg; fi -done -cleanup -exit 0 diff --git a/packaging/mac/app/launcher.sh b/packaging/mac/app/launcher.sh deleted file mode 100755 index bf1a3cd39..000000000 --- a/packaging/mac/app/launcher.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# Bundle executable of "Igneum Miner.app" (Contents/MacOS/Igneum Miner). Finder runs this with no terminal, so -# it opens Resources/igneum-miner.sh in Terminal and exits. A shell script, not an AppleScript applet: Terminal -# opening a file it was handed by `open -a Terminal` needs no Automation consent, an AppleScript `tell Terminal -# to do script` would prompt "Igneum Miner wants to control Terminal" on the first run. -# Before that it removes the quarantine flag from the bundle's own files: once the user has let the app through -# Gatekeeper, the binaries inside would still be checked one by one on their first exec and refused as -# unidentified. The flag can only be removed on a writable volume, so the app must be run from Applications -# (or any folder), not straight from the DMG. -CONTENTS="$(cd "$(dirname "$0")/.." && pwd)" -RES="$CONTENTS/Resources" -if ! xattr -dr com.apple.quarantine "$RES" 2>/dev/null; then - if [ -n "$(xattr -p com.apple.quarantine "$RES/igneum-miner.sh" 2>/dev/null)" ]; then - osascript -e 'display dialog "Drag Igneum Miner into Applications first, then open it from there. It cannot start from the disk image." buttons {"OK"} default button 1 with title "Igneum Miner"' >/dev/null 2>&1 - exit 1 - fi -fi -chmod +x "$RES/igneum-miner.sh" "$RES/bin/"* 2>/dev/null -exec /usr/bin/open -a Terminal "$RES/igneum-miner.sh" diff --git a/site/bench.html b/site/bench.html index 21e7c7609..e846b0037 100644 --- a/site/bench.html +++ b/site/bench.html @@ -68,7 +68,7 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

Engineering log

Every measurement the project has made, newest at the bottom, written by the people and agents who ran it, with the commands and hardware. Prototype numbers are not mining numbers and say so.

- +

Igneum bench log

Append-only. Every number here was measured on the machine named, on the date given.

2026-10-03 proto-metal / igneum-bench, first run

@@ -209,7 +209,15 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c
StageRTX 5090Apple M5 Max CPU (3 October, loaded)
native re-execution0.0003 s, state root MATCHES the fixture0.0004 s, matches
setup (one per program id)23.23 s20.55 s on the PC's CPU; Mac not timed separately
execute626,876 cycles, prover gas 844,704, 0.19 s, 14 cycles per EVM gas626,246 cycles, 0.15 s on the PC's CPU
core proofprove 1.4 s, 7,317,217 bytes, verify 0.221 s, VERIFIEDprove 22.0 s, 7.3 MB, verify 0.16 s
compressed proofprove 2.7 s, 1,272,769 bytes, verify 0.038 s, VERIFIEDprove 55.7 s, 1.27 MB, verify 0.03 s

Reading: 15.7x on core and 20.6x on compressed against a loaded laptop CPU. The block is far below one SP1 shard, so these are the fixed per-proof overheads of the proof system on this card; the throughput number needs the larger fixtures (proving e2e standard). Post-state root and receipts root identical to the node's on every stage. Two defects, neither in the proof: the host aborted (exit 134) AFTER writing and uploading the results, in sp1-cuda's destructor outside a Tokio runtime; and the host was silent for ten minutes between the core and compressed stages with the card idle. Ledger P20. Setup on the PC needed three package fixes found only by running it on Windows: protobuf-compiler in the apt list, the WSL distro check (UTF-16 output), the elevated window closing; and WSL2 itself needed bcdedit /set hypervisorlaunchtype auto on a PC whose BIOS already had SVM on.

4 October 2026, devnet v4 cut-over: generator v2, 2/3 floor, three nodes and a seed on a fresh chain

-

Sequence (BST): seed re-staged from 6457ca95 (build 55 min on the 2-vCPU VM, 08:55 to 09:54); node 1 stopped and the v3 database moved aside at 10:05:30, igneumd v4 up at 10:05:31 with the execution layer; observer peer and observer.mjs restarted on fresh appdirs at 10:06:10; seed switched with switch-v4.sh at 10:06:28 and synced at 10:07:38 (blocks=2, peers=1); Mac Metal miner (generator v2 port, prepare 1) first accepted block at 10:07:35; the PC joined at 10:10:55 (protocol version 12) and its first blocks followed within the minute. Block rate went from about 1 per second (Mac alone, difficulty easing 9.1% a block from the 134M genesis value) to about 2 per second with the 5090; the live page followed from block 0 with 9 identities after five minutes. First NVIDIA card to mine a generator v2 program; CPU re-check passed on every Mac share. One launcher defect found only on Windows: "$machine:$vendorName" in igneum-common.ps1 is a drive-qualified variable to PowerShell, so the file failed to parse (fixed, braces). The first finality lock is due at DAA 7,200, two hours after the v4 genesis; the first hourly swap at DAA 3,600.

+

Sequence (local time, UTC+1): seed re-staged from 6457ca95 (build 55 min on the 2-vCPU VM, 08:55 to 09:54); node 1 stopped and the v3 database moved aside at 10:05:30, igneumd v4 up at 10:05:31 with the execution layer; observer peer and observer.mjs restarted on fresh appdirs at 10:06:10; seed switched with switch-v4.sh at 10:06:28 and synced at 10:07:38 (blocks=2, peers=1); Mac Metal miner (generator v2 port, prepare 1) first accepted block at 10:07:35; the PC joined at 10:10:55 (protocol version 12) and its first blocks followed within the minute. Block rate went from about 1 per second (Mac alone, difficulty easing 9.1% a block from the 134M genesis value) to about 2 per second with the 5090; the live page followed from block 0 with 9 identities after five minutes. First NVIDIA card to mine a generator v2 program; CPU re-check passed on every Mac share. One launcher defect found only on Windows: "$machine:$vendorName" in igneum-common.ps1 is a drive-qualified variable to PowerShell, so the file failed to parse (fixed, braces). The first finality lock is due at DAA 7,200, two hours after the v4 genesis; the first hourly swap at DAA 3,600.

+

4 October 2026, one-click Windows workers: what the Mac could measure (no NVIDIA GPU here)

+

Package 0.3.0 replaces the build-on-the-PC workers with two prebuilt exes (proto-cuda/nvrtc/worker.cpp: driver API + NVRTC loaded at run time; proto-opencl/host.c --pack: OpenCL.dll loaded at run time, pack read at run time). Both cross-compile with Homebrew mingw-w64 16.2 and import only KERNEL32 and the Universal CRT (1,117,184 and 78,336 bytes stripped). The NVRTC DLLs (12.8.93) add 93 MB to the zip. Checks run on the M5 Max, same protocol script for both (proto-cuda/nvrtc/emu/serve-check.sh: jobs on pack A, a background prepare of a second pack, the swap, a job after the swap, 15 to 17 found hashes against igneum-pow hash-bound):

+
WorkerHow it ran hereFirst pack (cache / dataset / self-test)Prepare of pack B (reported)Verdict
igneum-worker-cuda (emulation backend: pack kernels on host threads, NVRTC stand-in)nvrtc/emu/test.sh157 / 4,758 / 1,702 ms8,413 ms (cache 132, dataset 4,759, check 1,671)PASS, 17 of 17 sampled hashes, source check PASS for 4 files
igneum-bench-cl --pack (Apple OpenCL 1.2, built against a different placeholder pack)proto-opencl/test-generic.sh53 / 289 / 1,588 ms2,083 ms (build 212, cache 25, dataset 197, check 1,387)PASS, 15 of 15 sampled hashes
+

The self-test times are dominated by reading the 256 MiB cache back and hashing it on one CPU thread (FNV-1a 64 over 2^26 words); the emulation's dataset build is 256 host threads over 2^24 items and says nothing about a GPU. Nothing NVIDIA was measured: the first NVRTC compile, cubin load and hash rate on the RTX 5090 are owed from the PC run (proto-cuda/windows-app/TEST.md lists the lines to copy here).

+

4 October 2026, first hourly program swap on the live devnet: compile-ahead, no pause, two cards

+

Live devnet v4, epoch boundary at DAA 3,600 (11:05:07 BST). The node announced the next seed once the sink was 150 DAA past the seed score (next_epoch_seed, confirm = lead/4); each miner sent prepare to its worker and the worker built the next program in the background while the current one mined.

+
MachinePrepare sentCompileSwap at 3,600RestartRate before / afterRejected
Mac M5 Max (Metal, prepare 1)449 DAA before the boundary82 ms0.01 ms, resident 2 programsnone26.7 / 26.7 MH/s0
RTX 5090 (CUDA, nvcc in the background)same template1,285 ms (nvcc 1,129, cache 4, dataset 23)0.00 ms, resident 2 programs 2 datasetsnone121.8 / 123.4 MH/s0
AMD Radeon integrated (OpenCL)samepreparedno restartnone2.74 / 2.74 MH/s0
+

Reading: the compile-ahead rule (3 October 2026 decision: never restart every miner at once) holds on real values with three vendors; the hash rate is unbroken through the boundary and the launcher's exit-42 rebuild path was not used (rebuilds 0). The next boundary is DAA 7,200, which is also the first finality lock (weight window 7,200).

diff --git a/site/build.mjs b/site/build.mjs index 5b1bf6a89..31eed6316 100644 --- a/site/build.mjs +++ b/site/build.mjs @@ -171,6 +171,7 @@ if (existsSync(join(docs, 'bench-log.md'))) { ['First prototype of the random-program', 'First prototype of the lottery hash on Apple silicon'], ['Igneum named', 'Igneum named. Litepaper, brand and wallet designs published'], ['Finality floor', 'Finality floor raised to two thirds of all weight'], + ['First hourly program swap on the live devnet', 'First live hourly swap: no pause on Mac, NVIDIA or AMD'], ['Devnet v4 cut-over', 'Devnet v4 live: generator v2, two-thirds floor, fresh chain'], ]; function shortTitle(t) { diff --git a/site/journey.json b/site/journey.json index 5f5fa752e..40f40b7f6 100644 --- a/site/journey.json +++ b/site/journey.json @@ -50,6 +50,16 @@ } ], "log": [ + { + "date": "2026-10-04", + "text": "One-click Windows workers: what the Mac could measure", + "short": "One-click Windows workers" + }, + { + "date": "2026-10-04", + "text": "First hourly program swap on the live devnet: compile-ahead, no pause, two cards", + "short": "First live hourly swap: no pause on Mac, NVIDIA or AMD" + }, { "date": "2026-10-04", "text": "Proving v0 on the RTX 5090: first GPU proof of an Igneum block",