Skip to content

Synthetic block generator conjures value (unfunded fees/deposits) — breaks pots invariant across epoch boundaries #1020

Description

@scarmuega

Summary

dolos_testing::synthetic::build_synthetic_blocks produces transactions that are not value-balanced: each tx's output coin equals its input coin while also declaring a fee and registration-deposit certs that are never funded from the inputs. Dolos doesn't phase-1-validate in sync/import (it trusts the chain), so it records the fees/deposits anyway. This conjures value and breaks the pots supply invariant the first time a chain of these blocks crosses an epoch boundary.

This surfaced while building the #1018 reproduction test (tests/boundary_resume.rs, added in #1019): those tests must run --release to compile out the pots.is_consistent debug_assert in crates/cardano/src/estart/reset.rs, because crossing a boundary trips it. The invariant is orthogonal to what those tests exercise (entity snapshot rotation), so release-only is an acceptable status quo — but it means the boundary-resume tests cannot run in debug, and the generator is not boundary-safe for future tests.

Root cause

  • Per-block UTxO accounting: utxo_delta = Σ outputs.coin − Σ resolved_inputs.coin (crates/cardano/src/roll/epochs.rs:143-169), folded into the pots at the boundary (apply_delta, crates/cardano/src/pots.rs:413-414).
  • Pots invariant: reserves + treasury + utxos + rewards + fees + obligations == max_supply (crates/cardano/src/pots.rs:72).
  • In crates/testing/src/synthetic.rs::sample_transaction: input = one seed UTxO of seed_amount (MIN_UTXO_AMOUNT); output coin = lovelace (MIN_UTXO_AMOUNT, equal to input); fee: 7 plus stake/pool/DRep registration certs.
  • Result: utxo_delta == 0, but fees and obligations grow with no offsetting decrease in utxos. The invariant overshoots by exactly fees + obligations (observed: 896,199,386 at the first boundary; produced_utxos: 0 / consumed_utxos: 0 in the boundary PotDelta).

What a fix requires

  1. Balance every tx: input_value = output_value + fee + deposits_charged_by_this_tx, so utxo_delta = −(fee+deposits) and the utxos pot drops by what fees/obligations gain. (Sufficient for is_consistent, which checks pot arithmetic, not the actual UTxO-set total.)
  2. Handle deposit conditionality (the hard part): a deposit is charged only on the first registration of a credential. The generator currently re-includes the same registration certs in every tx, but only the first charges a deposit (observed pool_count == 1 despite a pool cert in every block). So either emit registration certs once and fund that tx, or use distinct fresh credentials per registration and fund each.
  3. (Optional, fidelity only) trace the seed funds back to genesis (reduce genesis reserves / add to the genesis utxos pot) so the actual UTxO-set total also reconciles — not needed to pass the debug_assert.

Risk / scope

build_synthetic_blocks is shared by tests/bootstrap.rs, SyntheticVectors-based assertions, and others that hardcode the current amounts/cert shapes — changing tx values and the cert strategy will likely require re-greening those. A lower-risk alternative is a dedicated, minimal, properly-funded block builder for boundary tests instead of reworking the shared generator.

Acceptance

  • Synthetic-block chains can cross epoch boundaries without tripping pots.is_consistent.
  • tests/boundary_resume.rs runs in debug (drop the require_release() gate).
  • Existing generator-dependent tests stay green.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cardanoCardano ledger / epoch / pots logicbugSomething isn't working

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions