git / moonbite-network / deploy/miner/moonbite-miner.sh · raw
#!/usr/bin/env bash# Run a MoonBite full node and mine on it.## The seed nodes cannot do this themselves. RandomX asks for roughly 3.3 GB of# address space; the DigitalOcean seed has 458 MB (plus 2 GB of swap, which is# not enough - it was still OOM-killed) and the Railway container is smaller.# So mining has to live on a machine with real memory, and that machine talks# to the seeds over ordinary P2P.## ./moonbite-miner.sh start bring the node up and connect to the seeds# ./moonbite-miner.sh mine [N] mine N blocks (default: forever)# ./moonbite-miner.sh status height, peers, wallet balance# ./moonbite-miner.sh stop stop the node## Env:# MOONBITE_SRC directory holding litecoind/litecoin-cli (built binaries)# MOONBITE_DATADIR chain data + wallet (default ~/.moonbite)# MOONBITE_SEEDS comma-separated host:port (default: the two live seeds)set -euo pipefailSRC="${MOONBITE_SRC:-/root/bigcoin-core/src}"DATADIR="${MOONBITE_DATADIR:-/root/.moonbite}"CONF="$DATADIR/moonbite.conf"SEEDS="${MOONBITE_SEEDS:-67.205.154.64:9444}"WALLET=minerDAEMON="$SRC/litecoind"CLIBIN="$SRC/litecoin-cli"cli() { "$CLIBIN" -datadir="$DATADIR" -conf="$CONF" "$@"; }# Physical core count (not logical/hyperthreads). A RandomX fast-mode VM wants# ~2 MB of L3 cache; running one VM per logical thread makes sibling# hyperthreads fight over the same core's L3 and is *slower*, not faster.# Measured on a 6c/12t i7-8700 (12 MiB L3): 6 threads = 1045 H/s vs 12 threads# = 906 H/s, and the node's own default (all logical threads) hashed one-at-a-# time at 656 H/s -- so pinning to physical cores is ~1.6x. Falls back to nproc# if the topology can't be read.nproc_physical() { local n # Unique physical core ids from lscpu's parseable output (column 2 = Core). # Portable across util-linux versions; the `-p=Core,Socket` field form is # not supported on older lscpu and silently yields nothing. n=$(lscpu -p 2>/dev/null | grep -v '^#' | cut -d, -f2 | sort -un | wc -l) if ! [ "${n:-0}" -ge 1 ] 2>/dev/null || [ "${n:-0}" -eq 0 ]; then # Fallback: cores-per-socket * sockets; last resort nproc (logical). n=$(lscpu 2>/dev/null | awk -F: '/^Core\(s\) per socket/{c=$2} /^Socket\(s\)/{s=$2} END{gsub(/ /,"",c); gsub(/ /,"",s); if (c>0 && s>0) print c*s}') fi [ "${n:-0}" -ge 1 ] 2>/dev/null && { echo "$n"; return; } nproc 2>/dev/null || echo 1}write_conf() { mkdir -p "$DATADIR" # Preserve the RPC password across restarts; regenerating it would orphan any # running process still holding the old one. local pw old_umask # First run has no conf, so sed fails; under `set -e` an unguarded # assignment from a failing substitution kills the script silently. pw=$(sed -n 's/^rpcpassword=//p' "$CONF" 2>/dev/null | head -1) || true [ -z "$pw" ] && pw=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') # Create the file 0600 from birth. A bare `> "$CONF"` makes it at the # default umask (often 0644), leaving a window where the RPC password is # world-readable before the chmod below runs. Tighten umask around the # write only, then restore it. old_umask=$(umask) umask 077 { echo "server=1" echo "listen=1" echo "txindex=1" echo "dbcache=1024" # One RandomX mining VM per physical core (see nproc_physical above). # Unset, the node would spawn one per logical thread and run ~1.6x slower # on hyperthreaded/cache-limited CPUs. Needs the fast-mode miner binary # (v0.21.5.5 with the -miningthreads arg); older binaries ignore this line. echo "miningthreads=$(nproc_physical)" echo "port=9444" echo "rpcport=9445" echo "rpcuser=moonminer" echo "rpcpassword=$pw" # This host is typically behind NAT, so it dials the seeds rather than # waiting to be dialled. local s for s in ${SEEDS//,/ }; do echo "addnode=$s"; done } > "$CONF" umask "$old_umask" chmod 600 "$CONF"}# Find the daemon by its command line, not its process name. Core renames its# main thread once shutdown is requested ("b-shutoff"), so `pgrep -x litecoind`# stops matching a daemon that is still very much alive - and every kill aimed# that way misses silently while the process keeps running.node_pid() { pgrep -f "litecoind -datadir=$DATADIR" | head -1; }wait_rpc() { local i for i in $(seq 1 60); do cli getblockcount >/dev/null 2>&1 && return 0 sleep 2 done echo "node did not answer RPC within 120s" >&2 return 1}ensure_wallet() { cli createwallet "$WALLET" >/dev/null 2>&1 || cli loadwallet "$WALLET" >/dev/null 2>&1 || true cli -rpcwallet="$WALLET" getwalletinfo >/dev/null 2>&1}mining_address() { # A stable address, so every block in a run pays the same place and the # coinbase history is one auditable line rather than a scatter of addresses. local f="$DATADIR/mining-address.txt" if [ -s "$f" ]; then cat "$f"; return; fi local a; a=$(cli -rpcwallet="$WALLET" getnewaddress "mining") echo "$a" > "$f" echo "$a"}case "${1:-status}" in start) [ -x "$DAEMON" ] || { echo "no daemon at $DAEMON (set MOONBITE_SRC)" >&2; exit 1; } if cli getblockcount >/dev/null 2>&1; then echo "already running"; else write_conf "$DAEMON" -datadir="$DATADIR" -conf="$CONF" -daemon >/dev/null wait_rpc fi ensure_wallet echo "node up : height $(cli getblockcount), peers $(cli getconnectioncount)" echo "mining to : $(mining_address)" ;; mine) wait_rpc ensure_wallet ADDR=$(mining_address) TARGET="${2:-0}" # 0 = keep going echo "mining to $ADDR (target: ${TARGET:-forever})" N=0 while :; do H0=$(cli getblockcount) # One block per call, with a BOUNDED try budget. generatetoaddress holds # cs_main for the whole call, so a large budget does not "mine harder" - # it makes one call monopolise the node for hours: RPC stops answering, # net processing starves, and the node starts timing out its own peers. # Keep each call short and let the loop supply the persistence. A call # that finds nothing is normal, not an error. # # Measured on this host: ~496 H/s across 11 cores. A block at the # minimum difficulty (0.000244) needs ~1,048,576 hashes, so expect # roughly 35 minutes per block. 100k tries is ~3.5 minutes per call # with about a 1-in-10 hit rate, which keeps RPC responsive between # attempts instead of locking the node for the whole search. cli generatetoaddress 1 "$ADDR" "${MAXTRIES:-100000}" >/dev/null 2>&1 || true H1=$(cli getblockcount) if [ "$H1" -gt "$H0" ]; then N=$((N + 1)) echo " height $H1 (mined $N this run) peers $(cli getconnectioncount)" fi [ "$TARGET" != "0" ] && [ "$N" -ge "$TARGET" ] && { echo "done: $N block(s)"; break; } sleep 1 done ;; status) if ! cli getblockcount >/dev/null 2>&1; then echo "node not running"; exit 1; fi echo "height : $(cli getblockcount)" echo "tip : $(cli getbestblockhash)" echo "peers : $(cli getconnectioncount)" ensure_wallet echo "address: $(mining_address)" echo "balance: $(cli -rpcwallet="$WALLET" getbalances | tr -d '\n ' | head -c 200)" ;; stop) cli stop 2>/dev/null || true for _ in $(seq 1 20); do [ -z "$(node_pid)" ] && break; sleep 2; done # A long-running generatetoaddress holds cs_main and will not answer the # stop RPC, so fall back to the PID. PID=$(node_pid) if [ -n "$PID" ]; then echo "not responding to stop; killing $PID"; kill -9 "$PID"; sleep 3; fi rm -f "$DATADIR/.lock" echo "stopped" ;; *) sed -n '3,20p' "$0"; exit 1 ;;esac