Files
e7b9c0c8bf feat(runtime): spec 17 — los pesos se cobran como en la Pi 5, medidos por Laurha en el banco de referencia
Los 23 weights de spec 16, certificados por Laurha en el santuario de la rp1 (origin-banco):
- Núcleo 3 aislado, governor performance a 2,4 GHz, CI parada, 45-49 °C.
- frame-omni-bencher 0.15.0 sobre el wasm 40cb5a1d de 3cd1068.
- 5 procesos por pallet; se cobra el MÁXIMO con scripts/bench/max-de-n.py, porque procesos
  idénticos se separan hasta un 35 %.
- Evidencia: ~/laurha-work/banco/spec16-20261024-1631 en la rp1 (SHA256SUMS d930c5e1).
- Su perfil va en la cabecera de cada weights.rs.

Antes de cobrarlos:
- Mismos accesos a storage: el esqueleto de los 5 ficheros y sus reads/writes son idénticos a
  los de main.
- Lo que Laurha pidió mirar (cast_vote, finalize_proposal y veto_proposal, mucho más altos que
  en RV12) es esperable. Contra los interinos del Mac todos caen en la banda Pi/Mac de 2,5-3,9×:
  es trabajo nuevo de RV15/RV16 en una CPU unas 3 veces más lenta.
- Destacan los tres que verifican Ed25519 en WASM (attest_asset, register_agent_did,
  rotate_agent_key), con unas 6 veces: la criptografía pura sufre más en la Cortex-A76.
- Lo cobrado sube menos que la ejecución, entre +4 % (set_tier_profile) y +56 %
  (rotate_agent_key), porque las lecturas y escrituras de storage no cambian.
- spec_version 16 → 17, porque cambian las fees. transaction_version no cambia: ninguna llamada
  cambia su codificación.

Ensayado en la rp1 (aarch64) sobre un nodo efímero aparte, antes de la testnet de la casa, con
scripts/e2e/upgrade_en_vivo.py (nuevo):
- sudo(set_code) → CodeUpdated + Sudid Ok;
- spec 17 y LastRuntimeUpgrade sube;
- una firma con la spec vieja → «bad signature»; con la nueva, ExtrinsicSuccess;
- la finalidad pasa del bloque del upgrade.
Es la primera vez que el ejecutor ARM compila y corre un runtime nuevo.

Revisión adversarial (nivel 1 de la directiva), antes de tocar la red, con orden de tumbarlo:
no pudo. Los seis puntos aguantan:
- ningún storage ni codificación cambia, y transaction_version no debe subir;
- el más pesado (veto_proposal) cobra 1,9 ms, 344 veces por debajo del máximo por extrinsic;
- los weights.rs tienen la misma estructura y el impl () coincide;
- ninguna fee, subsidio ni test depende del valor de forma que se rompa.

scripts/rp1 (operación del nodo, medido en 23 h con los valores por defecto):
- 430 MB de WAL de RocksDB retenidos para 15 MB de datos, y el RSS subiendo de 200 a 340 MB:
  --db-cache 128. Al reiniciar, 449 → 21 MB.
- El log, de 7 líneas por bloque a ~2 (txpool, sc_basic_authorship y aura en warn; ojo: las
  líneas de autoría usan el target del módulo, no «basic-authorship»), de 16 MB/día a unos 3.
- Modo append, y medir.sh lo rota al pasar de 32 MB.

Co-Authored-By: Laurha <laurha@origin>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:33:38 -05:00

119 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""Aplica un runtime nuevo a una cadena VIVA por sudo(system.set_code), firmando como un wallet,
y comprueba que la cadena sigue sana. Es el upgrade de verdad, no el simulacro de
simulacro_set_code.py (ese usa un runtime marcado y la misma spec como control negativo).
ORIGIN_RPC=http://127.0.0.1:9944 python3 scripts/e2e/upgrade_en_vivo.py <runtime.wasm> <spec_esperada>
Comprueba, en este orden:
1. el sudo(set_code) entra y su bloque trae CodeUpdated + Sudid Ok;
2. el RPC anuncia la spec esperada y LastRuntimeUpgrade sube;
3. un remark firmado con la spec NUEVA entra (ExtrinsicSuccess) y uno con la VIEJA es «bad
signature»;
4. la finalidad avanza más allá del bloque del upgrade.
Se detiene en el primer fallo, con código de salida 1.
"""
import json, os, subprocess, sys, time, urllib.request
RPC = os.environ.get("ORIGIN_RPC", "http://127.0.0.1:9944")
RAIZ = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
TOOL = os.environ.get("ORIGIN_FIRMADOR", os.path.join(RAIZ, "target/release/examples/firmar_como_wallet"))
ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
EVENTS = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
LAST_UPGRADE = "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8"
def rpc(m, p=None):
req = urllib.request.Request(RPC, json.dumps({"id": 1, "jsonrpc": "2.0", "method": m, "params": p or []}).encode(),
{"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=120) as r:
return json.load(r)
def best():
return int(rpc("chain_getHeader")["result"]["number"], 16)
def finalizado():
return int(rpc("chain_getHeader", [rpc("chain_getFinalizedHead")["result"]])["result"]["number"], 16)
def firmar(llamada, nonce, genesis, v):
return subprocess.run([TOOL, "firmar", llamada, str(nonce), genesis, str(v["specVersion"]), str(v["transactionVersion"])],
capture_output=True, text=True, check=True).stdout.strip()
def eventos(n):
h = rpc("chain_getBlockHash", [n])["result"]
ev = rpc("state_getStorage", [EVENTS, h])["result"]
return subprocess.run([TOOL, "eventos", ev], capture_output=True, text=True).stdout if ev else ""
def buscar(n0, texto, espera=60):
t0 = time.time()
while time.time() - t0 < espera:
for n in range(n0 + 1, best() + 1):
e = eventos(n)
if texto in e:
return n, e
time.sleep(2)
return None, ""
def falla(msg):
print("❌", msg)
sys.exit(1)
def main(wasm, spec_esperada):
genesis = rpc("chain_getBlockHash", [0])["result"]
v = rpc("state_getRuntimeVersion")["result"]
print(f"antes: spec {v['specVersion']} · best #{best()} · finalizado #{finalizado()} · LastRuntimeUpgrade {rpc('state_getStorage', [LAST_UPGRADE])['result']}")
if v["specVersion"] >= spec_esperada:
falla(f"la cadena ya va en spec {v['specVersion']}")
n0 = best()
nonce = rpc("system_accountNextIndex", [ALICE])["result"]
r = rpc("author_submitExtrinsic", [firmar(f"set_code:{wasm}", nonce, genesis, v)])
if "error" in r:
falla(f"set_code rechazado: {r['error']}")
n, e = buscar(n0, "CodeUpdated")
if not n or "Sudid { sudo_result: Ok" not in e:
falla("no apareció CodeUpdated + Sudid Ok\n" + e)
print(f"1 ✅ bloque #{n}: CodeUpdated + Sudid Ok")
while best() < n + 1:
time.sleep(1)
v2 = rpc("state_getRuntimeVersion")["result"]
lru = rpc("state_getStorage", [LAST_UPGRADE])["result"]
if v2["specVersion"] != spec_esperada:
falla(f"el RPC anuncia spec {v2['specVersion']}, no {spec_esperada}")
print(f"2 ✅ spec {v2['specVersion']} · LastRuntimeUpgrade {lru}")
nonce = rpc("system_accountNextIndex", [ALICE])["result"]
viejo = rpc("author_submitExtrinsic", [firmar("remark", nonce, genesis, v)])
if "bad signature" not in json.dumps(viejo):
falla(f"una firma con la spec vieja NO fue rechazada: {viejo}")
m0 = best()
nuevo = rpc("author_submitExtrinsic", [firmar("remark", nonce, genesis, v2)])
if "error" in nuevo:
falla(f"la firma con la spec nueva fue rechazada: {nuevo['error']}")
m, e = buscar(m0, "ExtrinsicSuccess { dispatch_info: DispatchEventInfo { weight: Weight { ref_time", 60)
exitos = [l for l in e.splitlines() if "ExtrinsicSuccess" in l and "Normal" in l]
if not m or not exitos:
falla("el remark con la spec nueva no se incluyó con éxito")
print(f"3 ✅ spec vieja → bad signature · spec nueva → incluido en #{m}: {exitos[0][:110]}")
t0 = time.time()
while finalizado() <= n and time.time() - t0 < 120:
time.sleep(2)
if finalizado() <= n:
falla("la finalidad no pasó del bloque del upgrade en 2 min")
print(f"4 ✅ finalizado #{finalizado()} > bloque del upgrade #{n} · best #{best()}")
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit(__doc__)
main(os.path.abspath(sys.argv[1]), int(sys.argv[2]))