#!/usr/bin/env python3 """Simulacro de upgrade sin fork (set_code N → N+1) contra un nodo vivo, firmando como un wallet. Prueba lo que un upgrade real necesita, por el canal del consumidor (RPC): A. control negativo: un `set_code` con la MISMA spec se rechaza (SpecVersionNeedsToIncrease). Si sale InvalidSpecName, el runtime no sabe su propia versión (el defecto de 11e532e). B. el upgrade: `sudo(set_code)` con la spec N+1 → CodeUpdated, el RPC anuncia N+1, `LastRuntimeUpgrade` sube y la migración del simulacro deja su marca. C. un wallet después del upgrade: firmado con la spec vieja da «bad signature»; con la nueva entra. Preparación (ver parche_simulacro.py): 1. nodo con la spec N: cargo build -p origin-chain-node --features wasm-build (con WASM_BUILD_STD=0 y CC/AR_wasm32v1_none = clang/llvm-ar de LLVM) y copiar target/debug/wbuild/origin-chain-runtime/origin_chain_runtime.wasm → igual.wasm 2. parche_simulacro.py aplicar · cargo build -p origin-chain-runtime --features wasm-build · copiar el wasm → nuevo.wasm · parche_simulacro.py restaurar 3. SKIP_WASM_BUILD=1 cargo build -p origin-chain-runtime --example firmar_como_wallet 4. target/debug/origin-chain-node run --dev --rpc-port 9955 --rpc-max-request-size 64 5. python3 scripts/e2e/simulacro_set_code.py igual.wasm nuevo.wasm Medido la primera vez el 2026-10-12 (16 → 17): A, B y C en verde. El contrafactual, sin `type Version`, da InvalidSpecName y ningún upgrade. """ import json, os, subprocess, sys, time, urllib.request RPC = os.environ.get("ORIGIN_RPC", "http://127.0.0.1:9955") RAIZ = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) TOOL = os.path.join(RAIZ, "target/debug/examples/firmar_como_wallet") ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" EVENTS = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7" LAST_UPGRADE = "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8" MARCA = "0x" + b":origin:simulacro_set_code".hex() def rpc(method, params=None): body = json.dumps({"id": 1, "jsonrpc": "2.0", "method": method, "params": params or []}).encode() req = urllib.request.Request(RPC, body, {"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 firmar(llamada, nonce, genesis, version): args = [TOOL, "firmar", llamada, str(nonce), genesis, str(version["specVersion"]), str(version["transactionVersion"])] return subprocess.run(args, capture_output=True, text=True, check=True).stdout.strip() def eventos(n0, n1, filtro): for n in range(n0, n1 + 1): h = rpc("chain_getBlockHash", [n])["result"] if len(rpc("chain_getBlock", [h])["result"]["block"]["extrinsics"]) > 1: ev = rpc("state_getStorage", [EVENTS, h])["result"] out = subprocess.run([TOOL, "eventos", ev], capture_output=True, text=True).stdout for linea in out.splitlines(): if any(f in linea for f in filtro): print(f" #{n} {linea}") def esperar(k): b = best() while best() < b + k: time.sleep(1) def enviar(xt): r = rpc("author_submitExtrinsic", [xt]) return r.get("result", r.get("error")) def main(igual, nuevo): genesis = rpc("chain_getBlockHash", [0])["result"] v = rpc("state_getRuntimeVersion")["result"] print(f"antes: {v['specName']} spec {v['specVersion']} tx {v['transactionVersion']} · LastRuntimeUpgrade {rpc('state_getStorage', [LAST_UPGRADE])['result']}") n0 = best() nonce = rpc("system_accountNextIndex", [ALICE])["result"] print("A) set_code con la misma spec →", enviar(firmar(f"set_code:{igual}", nonce, genesis, v))) esperar(2) eventos(n0 + 1, best(), ("Sudo", "Code")) n0 = best() nonce = rpc("system_accountNextIndex", [ALICE])["result"] print("B) set_code con la spec siguiente →", enviar(firmar(f"set_code:{nuevo}", nonce, genesis, v))) esperar(3) eventos(n0 + 1, best(), ("Sudo", "Code")) v2 = rpc("state_getRuntimeVersion")["result"] print(f"después: spec {v2['specVersion']} · LastRuntimeUpgrade {rpc('state_getStorage', [LAST_UPGRADE])['result']} · marca {rpc('state_getStorage', [MARCA])['result']}") nonce = rpc("system_accountNextIndex", [ALICE])["result"] print("C1) remark firmado con la spec vieja →", enviar(firmar("remark", nonce, genesis, v))) n0 = best() print("C2) remark firmado con la spec nueva →", enviar(firmar("remark", nonce, genesis, v2))) esperar(2) eventos(n0 + 1, best(), ("ExtrinsicSuccess", "ExtrinsicFailed")) fin = rpc("chain_getFinalizedHead")["result"] print(f"finalizado #{int(rpc('chain_getHeader', [fin])['result']['number'], 16)} · best #{best()}") if __name__ == "__main__": if len(sys.argv) != 3: sys.exit(__doc__) main(os.path.abspath(sys.argv[1]), os.path.abspath(sys.argv[2]))