feat(rp1): la cadena corre en la Pi 5 de la casa — binario aarch64, despliegue sin root y medida continua
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 "<frase>" [//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 <noreply@anthropic.com>
This commit is contained in:
1 parent
3cd1068893
commit
22bab19932
8 files changed
+284
-1
No files matched your search
Executable
+99
@@ -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()
|
||||
Reference in new issue
Block a user