Files
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

96 lines
4.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# regen-weights.sh — REGENERAR LOS WEIGHTS DE LOS 5 PALLETS CUSTOM
#
# POR QUE EXISTE
# ──────────────
# Durante meses los weights fueron "INTERIM hand-derived" y el fichero prometia
# que regenerarlos "es mecanico". No lo era: no habia comando. Cuando por fin se
# midio (2026-09-09) los 19 extrinsics estaban SUBDECLARADOS — los 19 — y el
# proof_size valia 0 en todos, que es el mismo agujero que los interinos decian
# haber cerrado. La promesa costo lo que cuestan las promesas sin codigo.
#
# QUE HACE
# ────────
# 1. Construye el runtime WASM con `runtime-benchmarks`.
# 2. Corre `frame-omni-bencher` sobre ESE blob. OJO: NO es el que se despliega.
# Lleva `runtime-benchmarks` (expone la runtime API Benchmark: superficie de DoS
# en los nodos RPC). Lo que tiene que coincidir con lo desplegado es el COMMIT,
# no el blob. Por eso se construye en su propio target (`target-bench/`) y nunca
# en `target/release/wbuild`, que es de donde sale el blob de un set_code.
# (Corregido en RV15: la revisión adversaria encontró el blob de bench en la
# ruta de despliegue, justo después de regenerar pesos.)
# 3. Reescribe los 5 `weights.rs` con la plantilla de la casa, que emite
# trait + SubstrateWeight<T> + () + un guard generado que reprueba cualquier
# proof_size = 0.
#
# LA VERSION DEL BENCHER NO ES OPCIONAL
# ─────────────────────────────────────
# `frame-omni-bencher 0.15.0`. La ultima (0.23) trae `sp-storage 25` contra el
# `sp-storage 22` de este runtime, y `TrackedStorageKey` no decodifica al cruzar
# la frontera FFI. Falla con "unexpected first byte decoding Option", que no se
# parece en nada a "version equivocada".
# cargo install frame-omni-bencher --version 0.15.0 --locked
#
# DONDE CORRERLO
# ──────────────
# Para NUMEROS CANONICOS: en el banco de referencia (Pi 5), con el governor
# fijado a `performance` y el core aislado. `ondemand` arranca a 1600 MHz y sube
# a 2400: las primeras muestras salen ~50% lentas y el weight sale de la rampa,
# no del silicio. Sin `isolcpus` el aislamiento es una moneda al aire (medido).
# En cualquier otra maquina el resultado es un SMOKE del pipeline, util para ver
# la forma (reads/writes/proof_size, que NO dependen del hardware) e inservible
# como precio.
set -euo pipefail
cd "$(dirname "$0")/../.."
ROOT="$PWD"
STEPS="${STEPS:-50}"
REPEAT="${REPEAT:-20}"
BENCHER="${BENCHER:-frame-omni-bencher}"
# DENTRO del repo, no en /tmp: substrate-wasm-builder busca el Cargo.lock subiendo
# desde el target. Fuera del workspace no lo encuentra, resuelve dependencias desde
# cero y trae crates que exigen otro rustc (medido en RV15).
BENCH_TARGET="${BENCH_TARGET:-$ROOT/target-bench}"
WASM="${WASM:-$BENCH_TARGET/release/wbuild/origin-chain-runtime/origin_chain_runtime.compact.compressed.wasm}"
# SKIP_BUILD=1 mide un blob de bench YA construido. Es el modo del banco de
# referencia: el Pi 5 no compila Substrate, se le lleva el blob de bench del MISMO
# COMMIT que se va a desplegar. La condicion de Laurha (medir lo que se despliega)
# se cumple por commit: el blob desplegado es el mismo codigo SIN runtime-benchmarks.
command -v "$BENCHER" >/dev/null || { echo "falta $BENCHER — ver cabecera"; exit 1; }
"$BENCHER" --version | grep -q "0\.15\.0" || echo "⚠ el bencher NO es 0.15.0; si falla al decodificar TrackedStorageKey, es esto"
if [ "${SKIP_BUILD:-0}" = "1" ]; then
echo "═══ 1/2 SALTADO — midiendo blob ya construido ═══"
else
echo "═══ 1/2 construyendo el WASM con runtime-benchmarks ═══"
CARGO_TARGET_DIR="$BENCH_TARGET" WASM_BUILD_STD=0 \
cargo build --release -p origin-chain-runtime --features wasm-build,runtime-benchmarks
fi
echo "blob: $( (shasum -a 256 "$WASM" 2>/dev/null || sha256sum "$WASM") | cut -d' ' -f1)"
echo "═══ 2/2 midiendo (steps=$STEPS repeat=$REPEAT) ═══"
PARES="
origin_chain_provenance:crates/origin-chain-provenance/src/weights.rs
origin_chain_agent_identity:crates/origin-chain-agent-identity/src/weights.rs
origin_chain_fees:crates/origin-chain-fees/src/weights.rs
origin_chain_emission:crates/origin-chain-emission/src/weights.rs
origin_chain_governance:crates/origin-chain-governance/src/weights.rs
"
# Lista plana y no array asociativo a proposito: el bash por defecto de macOS
# es el 3.2, que no tiene `declare -A`, y con `set -u` falla con un
# «unbound variable» que no se parece en nada a la causa.
echo "$PARES" | while IFS=: read -r p dest; do
[ -n "$p" ] || continue
echo "── $p"
"$BENCHER" v1 benchmark pallet \
--runtime "$WASM" --genesis-builder-preset development \
--pallet "$p" --extrinsic "" \
--steps "$STEPS" --repeat "$REPEAT" \
--template "$ROOT/scripts/bench/origin-weights-template.hbs" \
--header "$ROOT/scripts/bench/HEADER.txt" \
--output "$ROOT/$dest"
done
echo "═══ listo — ahora: cargo test --workspace ═══"