Commit Graph
4 Commits
Author SHA1 Message Date
CharonandClaude Opus 5.5 3cd1068893 feat(e2e): simulacro de set_code 16→17 — la cadena ya se actualiza sin fork, y sin 11e532e no podía
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>
2026-10-12 19:15:49 -05:00
CharonandClaude Opus 5.5 a694cbd9f0 feat(governance): RV15 — el peso de un voto sale del saldo, y la revision adversaria tumbo la primera version
Cierra L-10. cast_vote y override_council_veto sumaban el stake_weight que
DECLARABA el votante (hueco medido por Palace el 2026-09-06; test en rojo
publicado en d37a278). Ahora:

- El peso no puede superar el OGC LIBRE de la cuenta, y ese OGC queda
  congelado (fungible freeze, FreezeReason::GovernanceVote) mientras el voto
  pueda cambiar un resultado: hasta end_block en un voto normal y hasta el
  cierre de la ventana en un override. El mismo OGC puede respaldar votos en
  propuestas distintas (se solapan, como en Polkadot), pero no puede salir de
  la cuenta: se cierra votar, transferir y votar otra vez desde otra cuenta.
- Un respaldo POR VOTO (VoteBacking: propuesta, tipo, importe, hasta), no uno
  por cuenta. El freeze es siempre el maximo de los votos VIVOS; los vencidos
  se descartan solos al votar o desbloquear. MaxActiveVotes = 32 en el runtime.
- unlock_vote_funds(who) lo puede llamar CUALQUIERA, como el unlock de
  conviction-voting: quien vota con todo su saldo no puede pagar la comision de
  su propio desbloqueo. Libera solo lo que ya no respalda un voto vivo.
- El override tiene ventana: 14 dias desde el veto que ALCANZA el umbral
  (antes estaba abierto para siempre). Sin ventana registrada, falla cerrado.
- voting_period_blocks tiene tope: 28 dias (MAX_VOTING_PERIOD_BLOCKS).
- VoteFundsUnlocked va al FINAL del enum de eventos: no desplaza indices SCALE.
- spec_version 14 -> 15; transaction_version sigue en 1.

La revision adversaria (4 lentes independientes sobre la primera version)
encontro tres fallos serios, y los tres eran mios:
1. El lock unico por cuenta (max importe, max bloque) mas un periodo de voto
   sin tope congelaba OGC PARA SIEMPRE: un voto de 1 unidad en una propuesta de
   periodo u64::MAX arrastraba el voto mayor de la cuenta hasta ese bloque, y ni
   el veto lo liberaba. Lo demostraron tres lentes por separado. Cura: respaldo
   por voto + tope de periodo.
2. Un lock vencido y no desbloqueado revivia con el siguiente voto pequeno.
3. Votar con todo el saldo dejaba la cuenta sin poder pagar la comision del
   unlock, y solo ella podia desbloquearse. Cura: unlock por terceros.
Y seis mutaciones sobrevivian a mi suite, entre ellas congelar el peso del
voto actual en vez del maximo (el propio ataque que el test decia cubrir).

Certificacion: 17 contrafactuales, TODOS en rojo con su pieza quitada
(freeze = peso actual, sin exigir saldo, reducible_balance, total_balance,
unlock en until, sin podar vencidos, ventana desde start_block, ventana 0,
peso 0 con thaw, evento released=0, unlock todo o nada, sin tope de periodo,
unlock solo propio, override hasta end_block, veto sin ventana, sin freeze, y
MaxFreezes=0 en el runtime). Cada test lleva control positivo. Test nuevo con
la configuracion REAL del runtime (preset local_testnet): el voto congela en
pallet-balances, lo congelado no sale, y un tercero lo libera al vencer.

Benchmarks en el PEOR caso real (la revision mostro que median la rama
barata): lista de respaldo casi llena, override que alcanza el umbral, veto de
un council de 7 que devuelve el bond, finalize que devuelve el bond, unlock
con la mitad vencida. Pesos regenerados con frame-omni-bencher (Mac; los
canonicos siguen pendientes del Pi 5, L-6). veto_proposal estaba
subdeclarado ~3x: 36M ps 10r/7w -> 112M 21r/10w, proof 6132 -> 16416.

regen-weights.sh: el blob de bench (runtime-benchmarks) se construye en
target-bench/ DENTRO del repo, nunca en target/release/wbuild, que es de donde
sale un set_code. Lo que debe coincidir con lo desplegado es el commit, no el
blob. Dentro del repo porque substrate-wasm-builder busca el Cargo.lock
subiendo desde el target: fuera no lo encuentra y resuelve dependencias
nuevas (medido).

Fuera de RV15, a RV16 (L-11): el override no es un 4/5 real (el bond que el
veto devuelve y el OGC Aye cuentan en el denominador y en el numerador), el
veto sigue siendo posible tras end_block, y un periodo de voto minimo.
Existian antes de RV15; los demostro la misma revision.

cargo test --workspace: 334 passed, 0 failed, 0 ignored.
cargo test -p origin-chain-governance --features runtime-benchmarks: 79 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:03:44 -05:00
davidherranandClaude Opus 5 38e5602d78 feat(weights): RV13 — los 19 estaban subdeclarados, los 19
Primera medicion real de los weights con frame-omni-bencher sobre el
propio blob WASM. El resultado no es un ajuste fino: los 19 extrinsics
de los 5 pallets custom declaraban MENOS de lo que cuestan.

EL PATRON SISTEMATICO. Se costeo el storage del pallet y se olvido el
que cuesta emitir un evento — System::Events, EventCount, ExecutionPhase
y Number son reads y writes reales en cada extrinsic que deposita
evento. Ejemplos medidos: attest_asset declaraba 4 reads / 1 write y
cuesta 7/3; submit_proposal declaraba 12 us y cuesta 62; register_agent_did
declaraba 3/2 y cuesta 6/4.

EL PEOR, Y ES UN ERROR MIO DOCUMENTADO AL REVES. proof_size valia 0 en
los 19. La cabecera de los interinos afirmaba que T::DbWeight "puebla
tanto ref_time como proof_size ... cerrando el hueco". Es falso y se
comprueba en la fuente: sp_weights::RuntimeDbWeight solo tiene los
campos read y write, y reads(r) devuelve from_parts(read * r, 0). Los
interinos arreglaron reads/writes y dejaron el proof_size exactamente
donde estaba. Un proof_size en cero miente al block-builder sobre el
peso PoV: es el vector de DoS que la nota decia haber cerrado. La
afirmacion sobrevivio mas de un mes sin que ninguna lectura la
desmintiera; la desmintio una medicion.

Cada fichero generado lleva ahora un guard GENERADO desde la lista real
de benchmarks que reprueba cualquier proof_size = 0 o ref_time = 0.
Verificado que sabe reprobar: contra los weights anteriores se pone rojo
con "`attest_asset` declara proof_size = 0".

ADVERTENCIA QUE VA EN EL CODIGO, NO SOLO AQUI: son numeros de un
MacBook Pro M3 Max, NO del banco de referencia BM V2. La forma
(reads/writes/proof_size) no depende del hardware y es correcta; el
ref_time si, y en un Pi 5 sera mayor. Un caso lo enseña solo:
attest_asset BAJA de 50 a 44 us, y es el extrinsic dominado por el
verify Ed25519, el mas sensible al silicio. Antes de mainnet hay que
regenerar en el Pi 5 con governor y aislamiento declarados — peticion
abierta a Laurha (request:66f500d54e28a80c) con el blob, el comando y
la version exacta del bencher.

RV12 -> RV13 (weights = consenso). TRANSACTION_VERSION intacto.
295 tests verde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:06:39 -05:00
davidherranandClaude Opus 5 b72650974d feat(bench): regenerar weights deja de ser una promesa y pasa a ser un comando
Durante meses la cabecera de los cinco weights.rs prometia que regenerar
"es mecanico". No lo era: no habia comando, ni plantilla, ni forma de
correrlo. La promesa costo lo que cuestan las promesas sin codigo — al
medir por primera vez, los 19 extrinsics estaban SUBDECLARADOS, los 19.

Tres piezas:

· scripts/bench/regen-weights.sh — construye el WASM con
  runtime-benchmarks y mide los 5 pallets. SKIP_BUILD=1 + WASM=<ruta>
  mide un blob YA construido: es el modo del banco de referencia, donde
  el Pi 5 no compila Substrate sino que recibe el blob que se despliega,
  de modo que el binario medido y el desplegado son el MISMO.

· scripts/bench/origin-weights-template.hbs — emite la forma de la casa
  (trait + SubstrateWeight<T> + impl para ()) y, dentro, un guard
  GENERADO a partir de los benchmarks reales que reprueba cualquier
  proof_size = 0 o ref_time = 0. Generado, no escrito: no puede quedarse
  corto cuando se anada un extrinsic.

· scripts/bench/HEADER.txt — la cabecera que va dentro de cada fichero
  generado, diciendo que un weight es una latencia y que sin su hardware
  y su perfil de potencia no se reproduce.

El script documenta ademas dos cosas medidas hoy que cuestan horas si no
estan escritas: (1) hay que usar frame-omni-bencher 0.15.0, porque la
0.23 trae sp-storage 25 contra el sp-storage 22 de este runtime y
TrackedStorageKey no decodifica al cruzar el FFI — falla con "unexpected
first byte decoding Option", que no se parece a "version equivocada";
(2) el governor ondemand arranca a 1600 MHz y sube a 2400, asi que las
primeras muestras salen ~50% lentas y el weight sale de la rampa.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 10:45:30 -05:00