Ensayo en vivo el 2026-10-12, nodo --dev con WASM real, todo firmado por RPC como un wallet:
- A, control negativo: set_code con la MISMA spec 16 → Sudid Err(System, 1) =
SpecVersionNeedsToIncrease.
- B, el upgrade: sudo(set_code) con spec 17 → CodeUpdated + Sudid Ok.
- El RPC anuncia 17 y LastRuntimeUpgrade pasa de {16,"origin-chain"} a {17,"origin-chain"}.
- La migración del simulacro dejó su marca (0x11000000): on_runtime_upgrade corrió.
- C, un wallet tras el upgrade: firmado con spec 16 → «bad signature»; con spec 17 →
ExtrinsicSuccess.
- La finalidad siguió: el bloque del upgrade (#5) quedó finalizado.
Contrafactual, nodo sin type Version (el runtime de antes de 11e532e):
- LastRuntimeUpgrade = {0, ""}.
- Firmando como wallet → «bad signature».
- Firmando con spec 0 para poder llegar → Sudid Err(System, 0) = InvalidSpecName.
- Ningún upgrade y ninguna marca.
O sea: hasta el 7-oct la cadena no podía cambiar su runtime sin un fork.
Herramientas, para repetirlo antes de la testnet:
- scripts/e2e/simulacro_set_code.py: los pasos A, B y C contra un nodo vivo. Lo corrí tal cual
y reproduce lo de arriba.
- scripts/e2e/parche_simulacro.py: aplica de forma temporal la spec N+1 con la migración
marcada, y al restaurar comprueba por hash que lib.rs queda idéntico.
- firmar_como_wallet:
- modo set_code:<ruta> (sudo de //Alice);
- spec y tx opcionales: tras un upgrade, el wallet firma con la versión que anuncia el RPC;
- hex completo: HexDisplay recortaba con «...» pasados 1024 bytes.
Las builds son reproducibles: los dos WASM salieron con el mismo sha en dos builds separados
(9b933ff1 spec 16, 51663085 spec 17).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
4.9 KiB
Python
106 lines
4.9 KiB
Python
#!/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]))
|