From 22bab199320039ee7cdd364ac8e07c3b7e0c865c Mon Sep 17 00:00:00 2001 From: Charon Date: Sat, 24 Oct 2026 13:19:52 -0500 Subject: [PATCH] =?UTF-8?q?feat(rp1):=20la=20cadena=20corre=20en=20la=20Pi?= =?UTF-8?q?=205=20de=20la=20casa=20=E2=80=94=20binario=20aarch64,=20despli?= =?UTF-8?q?egue=20sin=20root=20y=20medida=20continua?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit David, 24-oct: «lo que podamos hacer en la rp1 hagamoslo no esperemos la VPS porfavor». Y ese mismo día, por Ergon: la rp1 aloja el servidor de arranque de la red. scripts/rp1/: - construir-aarch64.sh + Dockerfile.aarch64: origin-chain-node release, con el WASM real, para linux/aarch64 en el Mac. - Imagen bookworm (glibc 2.36; la rp1 tiene 2.41). - Target en un volumen de Docker: la VM no ve el disco externo de compilación si se montó después de arrancar Docker, y reiniciarla tiraría los contenedores de otras hermanas. - Copia el binario con docker cp y avisa de cómo borrar el volumen. - desplegar.sh: copia el binario con su sha256 (y deja el anterior para volver atrás), copia arrancar.sh y medir.sh, e instala tres líneas en el crontab del usuario: @reboot, vigilante cada 5 min y medida cada 5 min. Sin root y sin systemd. - arrancar.sh: idempotente. - taskset -c 0-2: el nodo nunca toca el núcleo 3, el santuario de origin-banco de Laurha. - nice 10 + ionice 7: la CI de la casa va primero. - RPC sólo en 127.0.0.1; P2P en el 30333. - Cadena dev con Alice: es la testnet de la casa, con llaves públicas de desarrollo; ningún valor real. - medir.sh: una fila CSV cada 5 min con best, finalizado, pares, RSS, CPU, MB de datos, load, temperatura y estado de la CI. scripts/bench/max-de-n.py: funde N weights.rs del mismo pallet cobrando el MÁXIMO de cada coeficiente. - Por qué: Laurha midió el 24-oct en su santuario que 8 procesos idénticos de frame-omni-bencher separan el base weight hasta un 66 %. - Se niega si los esqueletos difieren (otra lista de extrinsics o de storage) o si los conteos de reads/writes no coinciden. Avisa si N < 5. - Controles: tres copias idénticas salen idénticas byte a byte; el máximo cae sólo donde se subió; un conteo de reads distinto se rechaza; un extrinsic renombrado se rechaza. firmar_como_wallet: modo cuenta "" [//derivación], que da la llave sr25519 y la SS58 que verá un wallet Substrate. Son los vectores para la migración de la wallet Origin! (Irhis). fix(node): build-spec emite el chain spec REAL cuando el binario lleva el WASM. - Antes, aunque el binario llevara el WASM real, emitía siempre el de mayo, con :code = «0x» simulado, y --raw no sacaba el génesis en crudo. - Ahora usa el mismo spec con el que arranca run (node_chain_spec), y --raw lleva el génesis entero. - Sin WASM sigue el simulado, pero avisa de que no sirve para unirse a una red. Mainnet sigue cerrada por la compuerta L-3. - Por qué importa, medido hoy: el WASM del runtime NO es reproducible entre máquinas. El mismo commit compilado en el Mac y en Linux da génesis distintos (0xe80c… frente a 0x0926…), y el nodo del Mac baneó al de la rp1 con «Genesis mismatch». El génesis tiene que fijarlo un chain spec que se reparte, no quien compila. - arrancar.sh usa ~/origin-chain/casa.json cuando existe (exportado en la propia rp1). Así un binario nuevo nunca crea otro génesis sobre los mismos datos. Medido en vivo el 24-oct, todo por RPC firmado como un wallet: - Nodo en la rp1 (Pi 5, Debian 13): bloques cada 6 s y finalidad a 2-3 bloques. RSS ~200 MB y afinidad 0-2 con nice 10. - El vigilante de cron lo resucita con el mismo peer id (12D3KooWDWm5p3Z8Y5DddRki6M6nM1A2xUVQzPqNU1DW53uB6BVi) y sin regenerar la llave. - E2E en la rp1: remark → ExtrinsicSuccess; finalize_override → ProposalNotFound; uphold0 → ZeroWeightWindowVote. - casa.json exportado en la rp1 = estado del bloque 0 de la cadena viva: 46 de 46 claves idénticas. El nodo reinició sobre él con el mismo génesis y siguió desde el #155. - Red de dos máquinas por la LAN: el nodo del Mac, con casa.json y la rp1 como bootnode, sincronizó (mismo best, mismo finalizado). Una transacción enviada al nodo del Mac la incluyó la rp1 en el bloque #167, con el mismo hash blake2-256 que devolvió author_submitExtrinsic. Defectos encontrados en mis propios scripts antes de dejarlos publicados: - desplegar.sh instalaba un crontab VACÍO: con set -e, el grep -v sobre un crontab vacío abortaba la subshell antes de los echo. - medir.sh dejaba best/finalizado en NA: set -u con $2 sin definir, y stderr se iba con cron. - max-de-n.py reescribía 16416 como 16_416 (la prueba de identidad lo cazó). Co-Authored-By: Claude Opus 5.5 --- crates/origin-chain-node/src/main.rs | 23 +++++ .../examples/firmar_como_wallet.rs | 12 ++- scripts/bench/max-de-n.py | 99 +++++++++++++++++++ scripts/rp1/Dockerfile.aarch64 | 7 ++ scripts/rp1/arrancar.sh | 46 +++++++++ scripts/rp1/construir-aarch64.sh | 38 +++++++ scripts/rp1/desplegar.sh | 32 ++++++ scripts/rp1/medir.sh | 28 ++++++ 8 files changed, 284 insertions(+), 1 deletion(-) create mode 100755 scripts/bench/max-de-n.py create mode 100644 scripts/rp1/Dockerfile.aarch64 create mode 100755 scripts/rp1/arrancar.sh create mode 100755 scripts/rp1/construir-aarch64.sh create mode 100755 scripts/rp1/desplegar.sh create mode 100755 scripts/rp1/medir.sh diff --git a/crates/origin-chain-node/src/main.rs b/crates/origin-chain-node/src/main.rs index 8bb62a0..eeb3312 100644 --- a/crates/origin-chain-node/src/main.rs +++ b/crates/origin-chain-node/src/main.rs @@ -243,6 +243,29 @@ fn print_preset(id: &str) { /// JSON canonical via [`chain_spec::serialize_chain_spec`]. Exit code canonical 1 /// si chain id canonical unknown o JSON serialization canonical fails. fn build_spec(chain: &str, raw: bool) { + // Con el WASM real, el chain spec que se emite es el MISMO con el que arranca `run` + // (`node_chain_spec`), y `--raw` lleva el génesis entero, `:code` incluido. Es lo que se + // reparte para que otro nodo se una a la red: el WASM no es reproducible entre máquinas + // (Mac y Linux dan génesis distintos con el mismo commit, medido el 2026-10-24), así que + // el génesis lo fija el chain spec, no quien compila. + if origin_chain_runtime::WASM_BINARY.is_some() + && !matches!(chain, "mainnet" | "mainnet_genesis") + { + match chain_spec::node_chain_spec(chain).and_then(|spec| spec.as_json(raw)) { + Ok(json) => println!("{json}"), + Err(e) => { + eprintln!("❌ Chain spec no resuelto: {e}"); + std::process::exit(1); + } + } + return; + } + if origin_chain_runtime::WASM_BINARY.is_none() { + eprintln!( + "⚠ binario sin el WASM del runtime: este chain spec lleva un WASM SIMULADO (\"0x\") \ + y no sirve para arrancar ni para unirse a una red (--features wasm-build)." + ); + } match chain_spec::load_spec(chain) { Ok(spec) => match chain_spec::serialize_chain_spec(&spec, raw) { Ok(json) => println!("{}", json), diff --git a/crates/origin-chain-runtime/examples/firmar_como_wallet.rs b/crates/origin-chain-runtime/examples/firmar_como_wallet.rs index 8a2884a..14ad01f 100644 --- a/crates/origin-chain-runtime/examples/firmar_como_wallet.rs +++ b/crates/origin-chain-runtime/examples/firmar_como_wallet.rs @@ -17,6 +17,8 @@ //! compilado. Tras un `set_code` hay que pasarlos: el wallet firma con la versión //! nueva que anuncia el nodo, no con la que se compiló aquí. //! eventos decodifica los eventos de un bloque +//! cuenta "" [//derivación] llave sr25519 y dirección SS58 (42) que verá un wallet +//! Substrate (substrate-bip39 → mini-secret → sr25519): vectores para la wallet Origin! //! verificar pasa la firma por el check del runtime nativo //! sobre el preset development (sin nodo) //! ``` @@ -124,6 +126,14 @@ fn main() { println!("{:?}", e.event); } } + "cuenta" => { + use sp_core::crypto::Ss58Codec; + let uri = format!("{}{}", args[2], args.get(3).map(String::as_str).unwrap_or("")); + let par = sr25519::Pair::from_string(&uri, None).expect("frase o derivación inválida"); + let publica = par.public(); + println!("sr25519 publica 0x{}", sp_core::hexdisplay::HexDisplay::from(&publica.0)); + println!("ss58 (42) {}", publica.to_ss58check()); + } "verificar" => { use sp_runtime::traits::Checkable; let bytes = hex(&args[2]); @@ -145,6 +155,6 @@ fn main() { println!("check del runtime: {:?}", r.map(|_| "firma aceptada")); }); } - _ => panic!("modo: firmar · eventos · verificar (ver la cabecera del fichero)"), + _ => panic!("modo: firmar · eventos · verificar · cuenta (ver la cabecera del fichero)"), } } diff --git a/scripts/bench/max-de-n.py b/scripts/bench/max-de-n.py new file mode 100755 index 0000000..6941fb2 --- /dev/null +++ b/scripts/bench/max-de-n.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Funde N weights.rs del MISMO pallet en uno que cobra el MÁXIMO de los N procesos. + + python3 scripts/bench/max-de-n.py salida.rs proceso1.rs proceso2.rs ... (N >= 2) + +Por qué: Laurha midió el 2026-10-24 en la Pi 5, dentro del santuario (núcleo aislado, +governor performance, CI parada), que 8 procesos idénticos de frame-omni-bencher dan +base weights que se separan hasta un 66 % (revoke_validator: 18,8 a 31,3 µs), mientras el +mínimo apenas se mueve. Un weight cobra el caso malo, y el caso malo existe: se cobra el +máximo de N >= 5 procesos, no la media ni la mediana. + +Qué hace: +- Exige que los N ficheros tengan el MISMO esqueleto: todo el texto salvo los números del + cuerpo, desde `pub trait WeightInfo`. Si un fichero trae otra lista de extrinsics, otro + acceso a storage u otra plantilla, se niega. Mezclar pallets o plantillas no es un máximo. +- `Weight::from_parts(a, b)`: toma el máximo de cada coeficiente. +- `reads(n)` y `writes(n)`: deben ser IGUALES en los N (cuentan accesos, no tiempo). Si no lo + son, se niega. +- Los comentarios numéricos del cuerpo: «Medido» y «Estimado» toman el máximo; «Tiempo + mínimo» toma el mínimo. +- Cabecera: la del primer fichero, más una línea que declara N y el sha256 de cada entrada. +""" +import hashlib, re, sys + +NUM = re.compile(r"\d(?:[\d_]*\d)?") # «7_u64» → 7; nunca se come el guion bajo final +INICIO_CUERPO = "pub trait WeightInfo" + + +def partir(texto): + i = texto.index(INICIO_CUERPO) + return texto[:i], texto[i:] + + +def esqueleto(cuerpo): + return NUM.sub("#", cuerpo) + + +def numeros(cuerpo): + return [(m.start(), m.end(), int(m.group().replace("_", ""))) for m in NUM.finditer(cuerpo)] + + +def contexto(cuerpo, inicio): + """La línea donde cae un número, para decidir la regla.""" + a = cuerpo.rfind("\n", 0, inicio) + 1 + b = cuerpo.find("\n", inicio) + return cuerpo[a:b if b != -1 else len(cuerpo)] + + +def con_guiones(n): + s = str(n) + partes = [] + while s: + partes.insert(0, s[-3:]) + s = s[:-3] + return "_".join(partes) + + +def main(): + if len(sys.argv) < 4: + sys.exit(__doc__) + salida, entradas = sys.argv[1], sys.argv[2:] + if len(entradas) < 5: + print(f"AVISO: N = {len(entradas)} < 5. La regla de la casa (Laurha, 24-oct) cobra el máximo de N >= 5 procesos.", file=sys.stderr) + textos = [open(p).read() for p in entradas] + cabecera, cuerpo0 = partir(textos[0]) + cuerpos = [partir(t)[1] for t in textos] + sk = esqueleto(cuerpo0) + for p, c in zip(entradas, cuerpos): + if esqueleto(c) != sk: + sys.exit(f"RECHAZO: {p} no tiene el mismo esqueleto que {entradas[0]} (otra lista de extrinsics, storage o plantilla)") + listas = [numeros(c) for c in cuerpos] + out, cursor = [], 0 + for k, (ini, fin, _) in enumerate(listas[0]): + valores = [lst[k][2] for lst in listas] + linea = contexto(cuerpo0, ini) + if "reads(" in linea or "writes(" in linea: + if len(set(valores)) != 1: + sys.exit(f"RECHAZO: los conteos de storage difieren entre procesos en «{linea.strip()}»: {valores}") + v = valores[0] + elif "minimo" in linea or "mínimo" in linea: + v = min(valores) + else: + v = max(valores) + crudo = cuerpo0[ini:fin] + nuevo = con_guiones(v) if "_" in crudo else str(v) # conserva el formato del literal + out.append(cuerpo0[cursor:ini]) + out.append(nuevo) + cursor = fin + out.append(cuerpo0[cursor:]) + shas = " ".join(hashlib.sha256(t.encode()).hexdigest()[:12] for t in textos) + nota = (f"// MAXIMO DE N = {len(entradas)} PROCESOS (scripts/bench/max-de-n.py): cada coeficiente\n" + f"// de tiempo y de proof es el maximo de los N; reads/writes iguales en los N.\n" + f"// Entradas (sha256[:12]): {shas}\n\n") + with open(salida, "w") as f: + f.write(cabecera + nota + "".join(out)) + print(f"{salida}: máximo de {len(entradas)} procesos, {len(listas[0])} números revisados") + + +main() diff --git a/scripts/rp1/Dockerfile.aarch64 b/scripts/rp1/Dockerfile.aarch64 new file mode 100644 index 0000000..b89c309 --- /dev/null +++ b/scripts/rp1/Dockerfile.aarch64 @@ -0,0 +1,7 @@ +# Compila origin-chain-node para la Raspberry Pi 5 (linux/aarch64) en un Mac Apple Silicon. +# Bookworm (glibc 2.36): el binario corre en cualquier Debian con glibc >= 2.36 (la rp1 tiene 2.41). +FROM rust:1.88-bookworm +RUN apt-get update \ + && apt-get install -y --no-install-recommends clang libclang-dev llvm protobuf-compiler cmake \ + && rm -rf /var/lib/apt/lists/* +RUN rustup target add wasm32v1-none wasm32-unknown-unknown && rustup component add rust-src diff --git a/scripts/rp1/arrancar.sh b/scripts/rp1/arrancar.sh new file mode 100755 index 0000000..fccbb78 --- /dev/null +++ b/scripts/rp1/arrancar.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Arranca el nodo de Origin Chain en la rp1 si no está corriendo. Idempotente: lo llaman +# cron @reboot y el vigilante cada 5 min. Sin root. +# +# Convivencia con la casa (la rp1 es también el runner de CI y el banco de Laurha): +# - taskset -c 0-2: el nodo NUNCA toca el núcleo 3, que es el santuario de origin-banco. +# - nice 10 + ionice best-effort 7: la CI tiene prioridad sobre el nodo. +# - RPC sólo en 127.0.0.1: se entra por túnel SSH. P2P en 30333 para la LAN. +# +# Cadena: `dev` (Alice, llaves de desarrollo PÚBLICAS). Es la testnet de la casa, no la +# testnet pública: ningún valor real vive aquí. +set -euo pipefail +BASE="$HOME/origin-chain" +BIN="$BASE/bin/origin-chain-node" +PIDF="$BASE/nodo.pid" +mkdir -p "$BASE/data" "$BASE/logs" + +if [ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null; then + exit 0 +fi + +LOG="$BASE/logs/nodo-$(date +%Y%m%d-%H%M%S).log" +# Un validador fuera de --dev exige una llave de red estable, y este CLI no trae `key`. Se +# genera UNA vez (la vía 4 del propio nodo) y queda en el base-path; en los arranques +# siguientes la bandera no se pasa, así que el peer id del bootnode no cambia nunca. +LLAVE_RED="$BASE/data/chains/origin_chain_dev/network/secret_ed25519" +EXTRA=() +[ -f "$LLAVE_RED" ] || EXTRA=(--unsafe-force-node-key-generation) +# El génesis lo fija casa.json (chain spec raw con el :code dentro), no el binario: el WASM no +# es reproducible entre máquinas, y con `--chain dev` un binario nuevo crearía OTRO génesis +# sobre los mismos datos. Sin casa.json (el primer arranque) se usa el `dev` embebido; después +# se exporta con `bin/origin-chain-node build-spec --chain dev --raw > casa.json`. +CADENA=dev +[ -f "$BASE/casa.json" ] && CADENA="$BASE/casa.json" +nohup taskset -c 0-2 nice -n 10 ionice -c2 -n7 "$BIN" run \ + --chain "$CADENA" \ + --validator --alice \ + --name rp1-origin-casa \ + --base-path "$BASE/data" \ + --port 30333 \ + --rpc-port 9944 --rpc-methods safe \ + --no-telemetry --no-prometheus "${EXTRA[@]}" \ + >"$LOG" 2>&1 & +echo $! >"$PIDF" +ln -sfn "$LOG" "$BASE/logs/actual.log" +echo "$(date -Is) arrancado pid $(cat "$PIDF") log $LOG" >>"$BASE/logs/arranques.log" diff --git a/scripts/rp1/construir-aarch64.sh b/scripts/rp1/construir-aarch64.sh new file mode 100755 index 0000000..efe87ea --- /dev/null +++ b/scripts/rp1/construir-aarch64.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Compila origin-chain-node (release, con el WASM real) para la rp1, Raspberry Pi 5 linux/aarch64, +# en un Mac Apple Silicon con Docker (linux/arm64 corre nativo, sin emulación). +# +# scripts/rp1/construir-aarch64.sh +# +# Por qué así (medido el 2026-10-24): +# - La rp1 no compila Substrate: es el runner de CI de la casa y tiene 8 GB. +# - Imagen bookworm (glibc 2.36): el binario corre en la rp1 (Debian 13, glibc 2.41). +# - El target vive en un VOLUMEN de Docker y no en el disco externo de compilación: la VM de +# Docker Desktop no ve /Volumes/MacOSBukup si el disco se montó después de arrancar Docker +# («not a directory»), y reiniciar Docker tiraría los contenedores de otras hermanas. +# Al acabar se copia el binario fuera y se borra el volumen. +# - 4 jobs: la VM tiene 8 GB compartidos con las bases de datos de otras hermanas. +# - WASM_BUILD_WORKSPACE_HINT=/src: substrate-wasm-builder busca Cargo.lock subiendo desde el +# target; con el target fuera del workspace no lo encuentra, resuelve dependencias nuevas +# (que piden rustc 1.90) y el build del runtime falla. +set -euo pipefail +DESTINO="${1:?carpeta destino}" +RAIZ="$(cd "$(dirname "$0")/../.." && pwd)" +IMG=origin-chain-builder:1.88-bookworm +docker image inspect "$IMG" >/dev/null 2>&1 || docker build -q -t "$IMG" -f "$RAIZ/scripts/rp1/Dockerfile.aarch64" "$RAIZ/scripts/rp1" +docker volume create charon-arm64-target >/dev/null +docker volume create charon-arm64-registry >/dev/null +docker run --rm --name charon-aarch64 \ + -v "$RAIZ":/src:ro -v charon-arm64-target:/target -v charon-arm64-registry:/usr/local/cargo/registry \ + -e CARGO_TARGET_DIR=/target -e WASM_BUILD_WORKSPACE_HINT=/src -e WASM_BUILD_STD=0 -e CARGO_BUILD_JOBS=4 \ + -e CC_wasm32v1_none=clang -e AR_wasm32v1_none=llvm-ar \ + -e CC_wasm32_unknown_unknown=clang -e AR_wasm32_unknown_unknown=llvm-ar \ + -w /src "$IMG" cargo build --release -p origin-chain-node --features wasm-build +mkdir -p "$DESTINO" +# docker cp escribe desde el host: funciona aunque la VM no vea el destino (p. ej. /Volumes/…). +C=$(docker create -v charon-arm64-target:/target "$IMG" true) +docker cp "$C:/target/release/origin-chain-node" "$DESTINO/origin-chain-node" +docker rm "$C" >/dev/null +file "$DESTINO/origin-chain-node" +shasum -a 256 "$DESTINO/origin-chain-node" +echo "Para liberar el disco de Docker: docker volume rm charon-arm64-target charon-arm64-registry" diff --git a/scripts/rp1/desplegar.sh b/scripts/rp1/desplegar.sh new file mode 100755 index 0000000..725c7b2 --- /dev/null +++ b/scripts/rp1/desplegar.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Despliega el nodo de Origin Chain en la rp1 desde el Mac (Charon, 2026-10-24). +# +# scripts/rp1/desplegar.sh +# +# - Copia el binario a ~/origin-chain/bin y deja el anterior como .anterior (volver atrás: +# mv .anterior encima y matar el pid; el vigilante lo rearranca). +# - Copia arrancar.sh y medir.sh, e instala en el crontab del usuario (sin root): +# @reboot y cada 5 min arrancar.sh (vigilante); cada 5 min medir.sh. +# - No toca nada fuera de ~/origin-chain ni el crontab de otras entradas. +set -euo pipefail +BIN="${1:?ruta al binario aarch64}" +HOST="${ORIGIN_RP1:-rp1}" +DIR="$(cd "$(dirname "$0")" && pwd)" +file "$BIN" | grep -q "ARM aarch64" || { echo "no es un binario aarch64: $BIN"; exit 1; } +SHA=$(shasum -a 256 "$BIN" | cut -d' ' -f1) + +ssh "$HOST" 'mkdir -p ~/origin-chain/bin ~/origin-chain/logs && { [ -f ~/origin-chain/bin/origin-chain-node ] && cp -p ~/origin-chain/bin/origin-chain-node ~/origin-chain/bin/origin-chain-node.anterior || true; }' +scp -q "$BIN" "$HOST:origin-chain/bin/origin-chain-node.nuevo" +scp -q "$DIR/arrancar.sh" "$DIR/medir.sh" "$HOST:origin-chain/" +ssh "$HOST" "set -e; cd ~/origin-chain + echo '$SHA bin/origin-chain-node.nuevo' | sha256sum -c --quiet + mv bin/origin-chain-node.nuevo bin/origin-chain-node; chmod +x bin/origin-chain-node arrancar.sh medir.sh + echo '$SHA' > bin/origin-chain-node.sha256 + # || true: con set -e, el grep -v sobre un crontab vacío sale 1 y abortaba la subshell + # ANTES de los echo, así que se instalaba un crontab VACÍO sin ningún error (medido el 24-oct). + ( { crontab -l 2>/dev/null | grep -v 'origin-chain/' || true; } ; \ + echo '@reboot \$HOME/origin-chain/arrancar.sh'; \ + echo '*/5 * * * * \$HOME/origin-chain/arrancar.sh'; \ + echo '*/5 * * * * \$HOME/origin-chain/medir.sh' ) | crontab - + bin/origin-chain-node version | head -3" +echo "desplegado en $HOST: sha256 $SHA" diff --git a/scripts/rp1/medir.sh b/scripts/rp1/medir.sh new file mode 100755 index 0000000..8615904 --- /dev/null +++ b/scripts/rp1/medir.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Una fila de medida del nodo de la rp1 cada vez que se llama (cron cada 5 min). +# CSV: fecha, best, finalizado, pares, rss_mb, cpu_pct, datos_mb, load1, temp_c, ci_activo +set -uo pipefail +BASE="$HOME/origin-chain" +CSV="$BASE/logs/medidas.csv" +[ -f "$CSV" ] || echo "fecha,best,finalizado,pares,rss_mb,cpu_pct,datos_mb,load1,temp_c,ci_activo" >"$CSV" + +rpc() { + curl -s -m 5 -H 'Content-Type: application/json' \ + -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"$1\",\"params\":[${2:-}]}" http://127.0.0.1:9944 +} +num() { python3 -c "import json,sys; r=json.load(sys.stdin)['result']; print(int(r['number'],16) if isinstance(r,dict) else r)" 2>/dev/null || echo NA; } + +best=$(rpc chain_getHeader | num) +fh=$(rpc chain_getFinalizedHead | python3 -c "import json,sys;print(json.load(sys.stdin)['result'])" 2>/dev/null || echo "") +fin=NA; [ -n "$fh" ] && fin=$(rpc chain_getHeader "\"$fh\"" | num) +pares=$(rpc system_health | python3 -c "import json,sys;print(json.load(sys.stdin)['result']['peers'])" 2>/dev/null || echo NA) +pid=$(cat "$BASE/nodo.pid" 2>/dev/null || echo 0) +rss=NA; cpu=NA +if kill -0 "$pid" 2>/dev/null; then + read -r rss cpu < <(ps -o rss=,pcpu= -p "$pid" | awk '{printf "%.0f %s\n", $1/1024, $2}') +fi +datos=$(du -sm "$BASE/data" 2>/dev/null | cut -f1) +load1=$(cut -d' ' -f1 /proc/loadavg) +temp=$(awk '{printf "%.1f", $1/1000}' /sys/class/thermal/thermal_zone0/temp) +ci=$(systemctl is-active gitea-runner 2>/dev/null || echo NA) +echo "$(date -Is),$best,$fin,$pares,$rss,$cpu,$datos,$load1,$temp,$ci" >>"$CSV"