Saltar al contenido
TrautsLablab

Fixture determinista

Prueba de trabajo y reajuste de dificultad

Ver por qué la prueba de trabajo cuesta en promedio 2^bits intentos y cómo el reajuste de dificultad devuelve el tiempo por bloque al objetivo cuando cambia el poder de cómputo.

Duración
20–30 min
Nivel
Introductorio blockchain
Pasos
2
Versión
3.24.0-alpha.1

Modelo mental

La dificultad no se fija: se reajusta por altura de bloque según cuánto tardó la ventana anterior, con un límite por paso para que un salto brusco no desestabilice la cadena.

  1. 1Minar
  2. 2Comparar con 2^bits
  3. 3Simular reajuste
  4. 4Verificar límite
  5. 5Reporte

Antes de empezar

  • Node.js 20 o superior.
  • Haber leído el problema del doble gasto.

Pasos

  1. Paso 1Minar con 8, 12 y 16 bits

    Medir cuántos intentos cuesta cada objetivo y compararlo con la expectativa 2^bits.

    Minería
    node tools/labs/proof-of-work-difficulty/mine.mjs

    Comprueba

    • — Cada hash empieza con los ceros exigidos.
    • — La razón intentos / 2^bits es del orden de 1 (distribución geométrica).
    • — Cada bit adicional duplica el trabajo esperado.

    Evidencia esperada

    JSON con bits, intentos esperados, intentos reales y prefijo del hash.

  2. Paso 2Simular el reajuste de dificultad

    Ver cómo un salto de 8x en el poder de cómputo se absorbe en pocas ventanas sin superar el límite de 4x por paso.

    Reajuste
    node tools/labs/proof-of-work-difficulty/retarget.mjs

    Comprueba

    • — El reajuste ocurre cada 144 bloques por altura, no por el valor de la dificultad.
    • — Ningún paso multiplica la dificultad por más de 4.
    • — El tiempo medio por bloque vuelve cerca de 600 s.

    Evidencia esperada

    JSON por ventana con tiempo medio por bloque y dificultad.

El código del laboratorio

Estos son los archivos que ejecutan los pasos, tal como están en el repositorio .

tools/labs/proof-of-work-difficulty/runbook.md
# Proof of work and difficulty retargeting

Run `node tools/labs/proof-of-work-difficulty/mine.mjs` and `node tools/labs/proof-of-work-difficulty/retarget.mjs`.

`mine.mjs` searches a nonce whose SHA-256 has the required leading zero bits and compares the attempts with the 2^bits expectation. `retarget.mjs` simulates block times with a seeded random source, retargets every 144 blocks by height, clamps each change to 4x, and checks that the block time returns to the target after an 8x hashrate jump. Both are deterministic fixtures: no network, no wallet, no real chain.
tools/labs/proof-of-work-difficulty/mine.mjs
// Proof of work: find a nonce whose SHA-256 has at least `bits` leading zero bits.
// Deterministic for a fixed block payload, so the attempts can be checked.
import { createHash } from 'node:crypto';

function leadingZeroBits(buffer) {
  let bits = 0;
  for (const byte of buffer) {
    if (byte === 0) { bits += 8; continue; }
    return bits + Math.clz32(byte) - 24;
  }
  return bits;
}

export function mine(payload, bits) {
  for (let nonce = 0; ; nonce += 1) {
    const hash = createHash('sha256').update(`${payload}|${nonce}`).digest();
    if (leadingZeroBits(hash) >= bits) return { nonce, attempts: nonce + 1, hash: hash.toString('hex') };
  }
}

const payload = 'prev=00ab|txs=3|height=42';
const report = [8, 12, 16].map((bits) => {
  const result = mine(payload, bits);
  const expected = 2 ** bits;
  return { bits, expectedAttempts: expected, attempts: result.attempts, ratio: Number((result.attempts / expected).toFixed(2)), hash: result.hash.slice(0, 16) };
});
console.log(JSON.stringify({ lab: 'proof-of-work-difficulty', step: 'mine', report }, null, 2));
// Every hash must meet its target; attempts stay within 20x the expectation (geometric distribution).
const ok = report.every((row) => row.ratio <= 20 && /^0+/.test(row.hash.slice(0, Math.floor(row.bits / 4))));
if (!ok) process.exit(1);
tools/labs/proof-of-work-difficulty/retarget.mjs
// Difficulty retargeting: adjust every INTERVAL blocks *by height* (not by the difficulty
// value), proportionally to how long the window took, clamped to a 4x change.
const TARGET_SECONDS = 600;
const INTERVAL = 144;

// Seeded PRNG so the simulation is reproducible.
let seed = 42;
const random = () => ((seed = (seed * 1664525 + 1013904223) % 2 ** 32) / 2 ** 32);

export function retarget(difficulty, windowSeconds) {
  const expected = TARGET_SECONDS * INTERVAL;
  const proposed = difficulty * (expected / windowSeconds);
  return Math.min(difficulty * 4, Math.max(difficulty / 4, proposed));
}

let difficulty = 1;
let hashrate = 1; // difficulty units per TARGET_SECONDS
let windowSeconds = 0;
const windows = [];
for (let height = 1; height <= INTERVAL * 12; height += 1) {
  if (height === INTERVAL * 4 + 1) hashrate = 8; // hashrate jumps 8x after window 4
  const mean = TARGET_SECONDS * (difficulty / hashrate);
  windowSeconds += -Math.log(1 - random()) * mean; // exponential block time
  if (height % INTERVAL === 0) {
    windows.push({ window: height / INTERVAL, meanBlockSeconds: Math.round(windowSeconds / INTERVAL), difficulty: Number(difficulty.toFixed(2)) });
    difficulty = retarget(difficulty, windowSeconds);
    windowSeconds = 0;
  }
}
console.log(JSON.stringify({ lab: 'proof-of-work-difficulty', step: 'retarget', windows }, null, 2));
const last = windows.at(-1).meanBlockSeconds;
const clampHeld = windows.every((w, i) => i === 0 || w.difficulty <= windows[i - 1].difficulty * 4 + 1e-9);
// After the jump the block time returns near the target, and no step exceeded the clamp.
if (!(Math.abs(last - TARGET_SECONDS) < TARGET_SECONDS * 0.25 && clampHeld)) process.exit(1);

Criterios de aceptación

  • Ambos scripts terminan con código 0.
  • Puedes explicar por qué el reajuste se decide por altura de bloque.
  • Puedes explicar qué protege el límite de 4x y qué retrasa.

Limpieza

Limpiar: No se genera estado persistente.

Si algo falla

Síntoma, causa probable y cómo corregirlo
SíntomaCausaQué hacer
retarget.mjs termina con código 1El tiempo final se alejó más de 25% del objetivo o un paso superó 4x.Revisa la función retarget: el límite se aplica sobre la dificultad anterior.
La minería tarda muchoSe subieron los bits por encima de 20.Cada bit duplica el trabajo; vuelve a 8, 12 y 16 para el laboratorio.