Commit Graph
4 Commits
Author SHA1 Message Date
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
CharonandClaude Opus 5.5 d37a278ba6 test(governance): el hueco del peso de voto ya tiene donde vivir — test en rojo con control positivo (RV15)
Palace lo midio el 2026-09-06 y escribio que no era explotable porque el nodo
no arrancaba. Desde 63918f3 el nodo arranca. Confirmado hoy: cast_vote y
override_council_veto suman el stake_weight que DECLARA el votante sin
contrastarlo con nada. El diseño dice «1 OGC = 1 voto» (lib.rs:403); el codigo
suma lo que le digan.

Impacto medido, no supuesto: hoy NO drena la tesoreria, porque
treasury_disbursement exige ademas ensure_root. Si permite aprobar o bloquear
cualquier propuesta con u128::MAX y revertir un veto del council. El dia que se
retire el sudo (L-7) y la gobernanza ejecute (L-8), si la drena.

El test va con ignore y no lo esconde: con --ignored sale en rojo hoy («un voto
de peso 10^22+1 con saldo 10^22 fue ACEPTADO»). Lleva control positivo: el
saldo entero si vota y cuenta entero, para que una «cura» que rechace TODO voto
no pueda cerrarlo. Quitar el ignore es el criterio de cierre de RV15: peso
derivado del saldo + un freeze para que el mismo OGC no vote dos veces pasando
por una transferencia.

cargo test -p origin-chain-governance: 51 passed, 1 ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:17:55 -05:00
CharonandClaude Opus 5.5 63918f365b feat(node): Origin Chain produce su primer bloque — el bloqueo arquitectural era un diagnostico falso
El node nunca habia producido un bloque: service.rs y rpc.rs estaban fuera del
build desde v0.0.49 por un «bloqueo arquitectural» que afirmaba que las crates
publicadas sc-consensus-aura 0.52.0 y sp-consensus-aura 0.45.0 no tenian
standalone::slot_duration_at ni from_timestamp_and_slot_duration. Las dos estan
ahi, en crates.io. La nota guio la decision durante meses.

Causa real, medida con cargo check (362 errores) y cargo tree: el workspace
mezclaba dos releases de polkadot-sdk. Las sc-* del cliente arrastraban
sp-runtime 43 y las frame-* del runtime sp-runtime 44: 36 crates del nucleo
duplicadas y errores de la forma «Block no satisface Block». Cura: alinear diez
versiones del Cargo.toml (sc-cli 0.55, sc-service 0.54, sc-network 0.53,
sc-consensus 0.52, sc-consensus-aura 0.53, sc-consensus-grandpa 0.38,
sc-offchain 48, sp-blockchain 42, substrate-frame-rpc-system 47,
sc-telemetry 30.0.1). El lock solo PIERDE la generacion vieja (1210 -> 1123
entradas); ninguna crate que use el runtime cambia de version.

rpc.rs: el client pasa a ser el FullClient concreto. Nuestro node trabaja sobre
el Block completo del runtime, que lleva dentro ChainAccountId, una proyeccion
(<<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId) que rustc
no normaliza dentro de un where-clause generico: 6 errores de «un bound
declarado en la firma no se satisface». Con el generico fallaba, con el
concreto compila.

main.rs: subcomando run (sc_cli::RunCmd), que arranca new_full con litep2p o
libp2p. chain_spec.rs: node_chain_spec construye un GenericChainSpec con el
WASM real del runtime (build-spec sigue con su JSON). run --chain mainnet se
RECHAZA: sus validadores son placeholder sin llave (gate L-3) y un node con ese
genesis no produciria nunca, en silencio. Test con contrafactual: sin el brazo
mainnet el id cae en la rama de ruta y falla como «fichero no encontrado»; el
test exige la razon, y en rojo lo dijo asi.

Medido (build con --features wasm-build, run --dev --tmp):
- bloque #1 importado 2026-09-23 20:04:30, uno cada ~6 s (Aura);
- GRANDPA finaliza: #7 finalizado con best #9 a los 60 s;
- RPC: chain_getHeader, chain_getFinalizedHead, system_accountNextIndex
  (Alice = 0) y system_properties (OGC, 18 decimales, ss58 42) responden;
- «Essential task txpool-background failed» aparece SOLO al SIGINT
  (0 ocurrencias a los 50 s con RPC servido; 1 al segundo del SIGINT).
- cargo test --workspace: 318 passed, 0 failed (igual que RV14) + 1 nuevo.

macOS 27: el build necesita DEVELOPER_DIR=/Library/Developer/CommandLineTools
mientras la licencia de Xcode 27 no este aceptada (exit 69 en cc y git).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:09:25 -05:00
CharonandClaude Opus 5 defd3fa580 feat(identity): RV14 — una identidad sobrevive a perder su llave, y la revision encontro que el storage salia gratis
QUE NACE. rotate_agent_key (call_index 1 de agent-identity) y la runtime API
AgentKeyApi::current_key. Hasta hoy un DID quedaba atado de hecho a su llave de
nacimiento: no se re-derivaba, pero tampoco habia forma de pasar la autoridad a
otra llave. Perder o filtrar la llave obligaba a registrarse otra vez con OTRO
DID y perder credenciales, creditos e historial. Ahora el DID es de por vida y lo
que rota es que llave manda sobre el. Es la dependencia de dos hilos abiertos: el
DID del niño (Marihan) y los creditos de referido indexados por DID (Codex).

Diseño: CurrentKey[did] (ausente = manda la de nacimiento, que ya vivia en
creator; sin migracion), KeyToDid[llave] (una llave manda sobre un solo DID en
toda la vida de la cadena; las retiradas quedan quemadas) y RotationNonce[did].
Dos firmas sobre DOMAIN || genesis || did || vigente || nueva || nonce || who: la
vigente autoriza, la nueva prueba control.

LO QUE LA REVISION ADVERSARIAL ENCONTRO ANTES DE PUBLICAR. Cinco lentes, 18
hallazgos, 17 confirmados. Todos arreglados aqui y cada arreglo con su
contrafactual visto en ROJO:

1. DbWeight = () EN TODO EL RUNTIME. frame_system heredaba el valor por defecto
   de SolochainDefaultConfig sin declararlo. Cada reads(n)/writes(n) de TODOS los
   weights.rs valia cero: la E/S de storage no se cobraba en fees ni llenaba el
   bloque. Ahora RocksDbWeight, con un test que reprueba si vuelve a cero.
   ESTO CORRIGE LO QUE YO DIJE EN RV13: los reads/writes que medi alli no tenian
   efecto en el runtime. Lo que RV13 si arreglo es el proof_size y el ref_time de
   ejecucion, que no pasan por DbWeight.

2. LLAVES DE ORDEN PEQUEÑO. El verificador del host (ed25519-zebra, ZIP215) las
   acepta. Con el punto identidad como llave y la firma trivial R=identidad s=0,
   la «prueba de control» se superaba sin llave privada, en rotacion Y en
   registro: el DID quedaba bajo una llave con la que cualquiera firma en su
   nombre. Para un DID de un niño en Ethos eso es suplantacion. Ahora
   is_strong_ed25519_key descomprime el punto, exige codificacion canonica y
   rechaza orden pequeño (curve25519-dalek, que ya iba dentro del WASM).

3. EL REGISTRO ESCRIBIA KeyToDid PERO NO LO CONSULTABA. Una llave a la que otro
   DID ya habia rotado fundaba un DID nuevo y pisaba el indice. Hallado cinco
   veces por separado. Va detras de la colision de DID para no cambiar el error
   documentado de scenario_12.

4. REPLAY ENTRE CADENAS: el mensaje no llevaba genesis. Ahora si, mismo patron
   que provenance.

Y menores: el evento nuevo va al final del enum (insertarlo en medio corria el
indice SCALE de AgentUpdated); la insercion redundante de la llave vieja ya no
pisa entradas; la doc dice que perder la CUENTA tambien bloquea la rotacion, no
solo perder la llave; el comentario de creator decia «la cuenta que registro» y
el codigo guarda la llave de nacimiento.

DOS TESTS MIOS PASABAN POR LA RAZON EQUIVOCADA, y lo digo. El de «llave retirada
quemada» volvia a la de nacimiento, que atrapa otra clausula. El del ladron
firmaba con la llave vigente nueva, algo que un ladron real no tiene; con el
pallet roto seguia verde en su mitad de ataque. Reescritos, y ahora el rojo sale
de la mitad correcta (verificado por linea del panic).

LO QUE QUEDA ABIERTO Y NO ES DE ESTE COMMIT: tras rotar, derive(llave_vigente) ya
no es el DID. OCF y Palace hoy DERIVAN el identificador; para un usuario que
roto tienen que RESOLVERLO contra KeyToDid. Es una enmienda al DC de unificacion
con sus firmantes. La recuperacion por guardianes va aparte. Y KeyToDid crece
sin deposito; se reevalua ahora que las escrituras cuestan de verdad.

Medido sobre el WASM real (bencher 0.15.0, Mac, no canonico): rotate 97 us, 11
lecturas, 5 escrituras, proof 6100. 318 tests verde. RV13 -> RV14.
TRANSACTION_VERSION intacto: call nueva en indice nuevo.

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