5 Commits
Author SHA1 Message Date
CharonandClaude Opus 5.5 6055faff1b feat(pesos): spec 20, los 17 pesos del gobierno salen de los certificados de Laurha
Ya no queda ningun smoke de Charon. weights.rs se ENSAMBLA desde los tres certificados del banco
de referencia (la Pi 5) con scripts/bench/ensamblar-pesos.py:
- Cada funcion cobra el maximo, coeficiente a coeficiente, de todos los certificados que midieron
  su mismo codigo (mismo esqueleto de lecturas, escrituras y proof que el del ultimo, que es el del
  codigo que corre).
- Con --comprobar dice si el fichero es exactamente ese ensamblado: probado en rojo cambiando a
  mano un peso.
- Los tres certificados quedan en scripts/bench/certificados/ (sha256 18b8fcd6, 1e15d04e y
  6f5cfec1), con su perfil y su evidencia en la cabecera.

Por que el maximo de todos y no solo el ultimo: «El maximo de 5 de un dia no es el de otro»
(Laurha). La revision adversaria de spec 20 encontro que cobrar solo el certificado vigente de
cada funcion dejaba treasury_disbursement en 136 us, cuando el certificado del 2-nov, sobre el
wasm que corre, mide 155 us, y otros siete por debajo de lo medido. Con la regla, ningun peso baja
respecto a spec 19; los que suben, entre el +1 % y el +33 % de ejecucion.

Probado:
- 365 tests verdes.
- Ensayo en la rp1 sobre una cadena con el codigo vivo de spec 19. Contrafactual: con estos pesos,
  el E2E reprueba contra spec 19, porque la cadena cobra los viejos. Tras el upgrade a 20 pasa
  entero, y lo cobrado es weights.rs + extensiones.

Ademas: configs.rs ya no dice «Weights INTERIM hand-derived» en los cinco pallets, que cobran
pesos certificados desde spec 17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:47:00 -05:00
CharonandClaude Opus 5.5 22bab19932 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>
2026-10-24 13:19:52 -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