Bench log: first hourly swap on the live devnet, no pause on three vendors
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e5d41910f1
commit
3ecaeb637b
6 changed files with 37 additions and 512 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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-<stamp>.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-<hostname>-1..N, or mac-<hostname> 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 <rpc>`: 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
|
||||
|
|
@ -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"
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue