Files
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

98 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Ensambla un weights.rs desde varios certificados del banco de referencia (Laurha, la Pi 5).
python3 scripts/bench/ensamblar-pesos.py <weights.rs> <cert-antiguo.rs> ... <cert-vigente.rs>
python3 scripts/bench/ensamblar-pesos.py --comprobar <weights.rs> <certs...> # 1 si no coincide
Por qué existe: «El máximo de 5 de un día no es el de otro» (Laurha, 29-oct). Entre dos
certificados del mismo código, el base weight de un mismo extrinsic se separa hasta un 22 %. Si se
cobra sólo el último certificado, se cobra de menos lo que otro día midió más alto sobre el MISMO
código. Si se mezclan certificados de código distinto, se cobra lo que no corre.
La regla, función a función (en los dos impl, `SubstrateWeight<T>` y `()`):
- El ÚLTIMO certificado de la lista es el del código que corre. Su bloque es la referencia: sus
lecturas, escrituras, proof y lista de storage son las que se cobran.
- Entran todos los certificados cuyo bloque tiene el MISMO esqueleto que la referencia: el cuerpo
sin números ni comentarios numéricos. Un certificado de código anterior que lea otra cosa no entra.
- Cada `Weight::from_parts(a, b)` cobra el máximo de los que entran, coeficiente a coeficiente.
- Se niega si el esqueleto de la referencia no es el que ya tiene el weights.rs (el código cambió y
no hay certificado de él) o si una función del weights.rs no está en la referencia.
Sólo toca los bloques de las funciones. La cabecera, el trait y la guarda de proof_size se quedan
como están: la cabecera la escribe quien ensambla, y debe nombrar los certificados y sus shas.
"""
import hashlib, os, re, sys
IMPLS = ("impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {", "impl WeightInfo for () {")
FN = re.compile(r"((?:\t//[^\n]*\n)*)\tfn (\w+)\(\) -> Weight \{\n.*?\n\t\}\n", re.S)
PARTES = re.compile(r"from_parts\(([\d_]+), ([\d_]+)\)")
def impl(texto, cabeza):
a = texto.index(cabeza)
b = texto.index("\n}\n", a) + 1
return a, b
def bloques(texto, cabeza):
a, b = impl(texto, cabeza)
return {m.group(2): m for m in FN.finditer(texto, a, b)}
def esqueleto(bloque):
cuerpo = bloque.split("\tfn ", 1)[1]
cuerpo = re.sub(r"\t\t// [^\n]*\n", "", cuerpo)
return PARTES.sub("from_parts(N, N)", cuerpo)
def numeros(bloque):
return [(int(a.replace("_", "")), int(b.replace("_", ""))) for a, b in PARTES.findall(bloque)]
def ensamblar(destino, certs):
texto = open(destino).read()
leidos = [(os.path.basename(c).rsplit(".", 1)[0], open(c).read()) for c in certs]
for cabeza in IMPLS:
actuales = bloques(texto, cabeza)
ref = bloques(leidos[-1][1], cabeza)
otros = [(et, bloques(t, cabeza)) for et, t in leidos]
nuevos = {}
for nombre, m in actuales.items():
if nombre not in ref:
sys.exit(f"{nombre}: no está en el certificado vigente ({leidos[-1][0]})")
r = ref[nombre].group(0)
if esqueleto(r) != esqueleto(m.group(0)):
sys.exit(f"{nombre}: el certificado vigente no mide el código del weights.rs")
entran = [(et, b[nombre].group(0)) for et, b in otros
if nombre in b and esqueleto(b[nombre].group(0)) == esqueleto(r)]
maximos = [tuple(max(v) for v in zip(*col)) for col in zip(*[numeros(b) for _, b in entran])]
it = iter(maximos)
bloque = PARTES.sub(lambda _m: "from_parts({:_}, {:_})".format(*next(it)), r)
etiquetas = ", ".join(et for et, _ in entran)
bloque = re.sub(r"\t\t// Tiempo minimo de ejecucion:[^\n]*\n",
f"\t\t// Máximo por coeficiente de: {etiquetas}.\n", bloque)
nuevos[nombre] = bloque
a, b = impl(texto, cabeza)
seccion = FN.sub(lambda mm: nuevos[mm.group(2)], texto[a:b])
texto = texto[:a] + seccion + texto[b:]
return texto
if __name__ == "__main__":
args = sys.argv[1:]
comprobar = bool(args) and args[0] == "--comprobar"
if comprobar:
args = args[1:]
if len(args) < 2:
sys.exit(__doc__)
destino, certs = args[0], args[1:]
nuevo = ensamblar(destino, certs)
for c in certs:
print(f" {hashlib.sha256(open(c, 'rb').read()).hexdigest()[:16]} {c}")
if comprobar:
igual = nuevo == open(destino).read()
print("weights.rs coincide con el ensamblado" if igual else "weights.rs NO coincide con el ensamblado")
sys.exit(0 if igual else 1)
open(destino, "w").write(nuevo)
print(f"ensamblado {destino} desde {len(certs)} certificados")