Fixture determinista
Rollup de juguete
Seguir el flujo de un rollup: ejecutar un lote, publicar digest del lote y state root, y que un verificador re-ejecute para aceptar o rechazar la afirmación.
- Duración
- 15–25 min
- Nivel
- Introductorio L2
- Pasos
- 1
- Versión
- 3.29.0-alpha.1
Modelo mental
El rollup ejecuta fuera y publica compromisos. Lo que da seguridad no es el hash en sí, sino que alguien pueda recalcular el estado a partir de los datos publicados y rechazar un state root falso.
- 1Estado inicial
- 2Lote
- 3Compromisos
- 4Verificación
- 5Manipulación
Antes de empezar
- Node.js 20 o superior. No necesita Foundry.
Pasos
Paso 1Ejecutar y verificar el lote
Ver el estado antes y después, los compromisos publicados y el resultado del verificador.
Simulación node tools/labs/ethereum-rollup-mock-lab/simulate.mjsComprueba
- — before es alice 100, bob 25, carol 0 y after es alice 75, bob 35, carol 15; la suma sigue en 125.
- — batchDigest empieza con c9c2fda4 y stateRoot con f26f371b (el resultado es determinista).
- — verified es true: el verificador re-ejecutó el lote y obtuvo el mismo state root.
- — tamperRejected es true: un state root con carol = 1000 se rechaza aunque su mockProof sea consistente.
- — El script termina con código 0.
Evidencia esperada
JSON completo del reporte.
El código del laboratorio
Estos son los archivos que ejecutan los pasos, tal como están en el repositorio .
# Rollup mock lab
Version: `2.0.44-alpha.3`
Run `node tools/labs/ethereum-rollup-mock-lab/simulate.mjs`. Record state before/after, transaction batch digest, state root and proof verification result.
The verifier re-executes the published batch from the previous state and only accepts the claim if the recomputed state root matches (`verified: true`). A claim with a forged state root is rejected (`tamperRejected: true`).
The proof is intentionally a commitment mock. It demonstrates data flow and invariants, not cryptographic soundness. Only Node.js 20+ is required.import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
const transactions = JSON.parse(readFileSync(new URL('./transactions.json', import.meta.url), 'utf8'));
const hash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
const genesis = { alice: 100, bob: 25, carol: 0 };
const domain = 'trautslab-rollup-mock-v1';
function execute(initial, batch) {
const state = new Map(Object.entries(initial));
for (const tx of batch) {
if ((state.get(tx.from) ?? 0) < tx.amount) throw new Error(`insufficient balance: ${tx.from}`);
state.set(tx.from, state.get(tx.from) - tx.amount);
state.set(tx.to, (state.get(tx.to) ?? 0) + tx.amount);
}
return state;
}
const rootOf = (state) => hash([...state.entries()].sort());
const proofOf = (batchDigest, stateRoot) => hash({ batchDigest, stateRoot, domain });
// Sequencer: executes the batch and publishes (batchDigest, stateRoot, mockProof).
const state = execute(genesis, transactions);
const claim = { batchDigest: hash(transactions), stateRoot: rootOf(state) };
claim.mockProof = proofOf(claim.batchDigest, claim.stateRoot);
// Verifier: re-executes the published batch from the previous state and checks the claim.
function verify(prev, batch, candidate) {
const recomputedRoot = rootOf(execute(prev, batch));
return (
candidate.batchDigest === hash(batch) &&
candidate.stateRoot === recomputedRoot &&
candidate.mockProof === proofOf(candidate.batchDigest, candidate.stateRoot)
);
}
const verified = verify(genesis, transactions, claim);
const tamperedRoot = hash([...execute(genesis, transactions).set('carol', 1000).entries()].sort());
const tampered = { ...claim, stateRoot: tamperedRoot, mockProof: proofOf(claim.batchDigest, tamperedRoot) };
const tamperRejected = !verify(genesis, transactions, tampered);
const report = {
before: genesis,
transactions,
after: Object.fromEntries(state),
batchDigest: claim.batchDigest,
stateRoot: claim.stateRoot,
mockProof: claim.mockProof,
verified,
tamperRejected,
warning: 'Mock proof: not a validity or fraud proof.',
};
console.log(JSON.stringify(report, null, 2));
if (!verified || !tamperRejected || [...state.values()].reduce((sum, value) => sum + value, 0) !== 125) process.exit(1);[
{ "from": "alice", "to": "bob", "amount": 20 },
{ "from": "bob", "to": "carol", "amount": 10 },
{ "from": "alice", "to": "carol", "amount": 5 }
]Criterios de aceptación
- El script termina con código 0.
- Puedes explicar por qué mockProof por sí solo no prueba nada.
- Puedes explicar qué necesita el verificador además del state root.
Limpieza
Limpiar: No se genera estado persistente; descarta cambios hechos a transactions.json.
Si algo falla
| Síntoma | Causa | Qué hacer |
|---|---|---|
| Error "insufficient balance" | transactions.json pide gastar más de lo que tiene una cuenta. | Ajusta los montos o el orden del lote. |
| batchDigest o stateRoot distintos a los esperados | Se modificó transactions.json o el estado inicial. | Es correcto si cambiaste el lote; anota los valores nuevos en la evidencia. |