Jun 04, 12-1 PM (53)
Jun 04, 1-2 PM (39)
Jun 04, 2-3 PM (60)
Jun 04, 3-4 PM (12)
Jun 04, 4-5 PM (4)
Jun 04, 5-6 PM (7)
Jun 04, 6-7 PM (46)
Jun 04, 7-8 PM (27)
Jun 04, 8-9 PM (4)
Jun 04, 9-10 PM (2)
Jun 04, 10-11 PM (24)
Jun 04, 11-12 AM (7)
Jun 05, 12-1 AM (6)
Jun 05, 1-2 AM (8)
Jun 05, 2-3 AM (1)
Jun 05, 3-4 AM (1)
Jun 05, 4-5 AM (1)
Jun 05, 5-6 AM (5)
Jun 05, 6-7 AM (9)
Jun 05, 7-8 AM (12)
Jun 05, 8-9 AM (8)
Jun 05, 9-10 AM (11)
Jun 05, 10-11 AM (12)
Jun 05, 11-12 PM (8)
Jun 05, 12-1 PM (52)
Jun 05, 1-2 PM (61)
Jun 05, 2-3 PM (26)
Jun 05, 3-4 PM (24)
Jun 05, 4-5 PM (17)
Jun 05, 5-6 PM (7)
Jun 05, 6-7 PM (14)
Jun 05, 7-8 PM (12)
Jun 05, 8-9 PM (6)
Jun 05, 9-10 PM (2)
Jun 05, 10-11 PM (20)
Jun 05, 11-12 AM (9)
Jun 06, 12-1 AM (6)
Jun 06, 1-2 AM (0)
Jun 06, 2-3 AM (3)
Jun 06, 3-4 AM (4)
Jun 06, 4-5 AM (0)
Jun 06, 5-6 AM (24)
Jun 06, 6-7 AM (1)
Jun 06, 7-8 AM (2)
Jun 06, 8-9 AM (3)
Jun 06, 9-10 AM (0)
Jun 06, 10-11 AM (3)
Jun 06, 11-12 PM (6)
Jun 06, 12-1 PM (2)
Jun 06, 1-2 PM (2)
Jun 06, 2-3 PM (2)
Jun 06, 3-4 PM (18)
Jun 06, 4-5 PM (1)
Jun 06, 5-6 PM (6)
Jun 06, 6-7 PM (0)
Jun 06, 7-8 PM (6)
Jun 06, 8-9 PM (0)
Jun 06, 9-10 PM (1)
Jun 06, 10-11 PM (27)
Jun 06, 11-12 AM (9)
Jun 07, 12-1 AM (14)
Jun 07, 1-2 AM (2)
Jun 07, 2-3 AM (0)
Jun 07, 3-4 AM (0)
Jun 07, 4-5 AM (1)
Jun 07, 5-6 AM (1)
Jun 07, 6-7 AM (3)
Jun 07, 7-8 AM (0)
Jun 07, 8-9 AM (0)
Jun 07, 9-10 AM (1)
Jun 07, 10-11 AM (2)
Jun 07, 11-12 PM (2)
Jun 07, 12-1 PM (5)
Jun 07, 1-2 PM (35)
Jun 07, 2-3 PM (2)
Jun 07, 3-4 PM (4)
Jun 07, 4-5 PM (2)
Jun 07, 5-6 PM (4)
Jun 07, 6-7 PM (0)
Jun 07, 7-8 PM (0)
Jun 07, 8-9 PM (17)
Jun 07, 9-10 PM (1)
Jun 07, 10-11 PM (21)
Jun 07, 11-12 AM (9)
Jun 08, 12-1 AM (9)
Jun 08, 1-2 AM (5)
Jun 08, 2-3 AM (3)
Jun 08, 3-4 AM (4)
Jun 08, 4-5 AM (2)
Jun 08, 5-6 AM (9)
Jun 08, 6-7 AM (5)
Jun 08, 7-8 AM (25)
Jun 08, 8-9 AM (36)
Jun 08, 9-10 AM (40)
Jun 08, 10-11 AM (24)
Jun 08, 11-12 PM (22)
Jun 08, 12-1 PM (40)
Jun 08, 1-2 PM (48)
Jun 08, 2-3 PM (33)
Jun 08, 3-4 PM (27)
Jun 08, 4-5 PM (12)
Jun 08, 5-6 PM (23)
Jun 08, 6-7 PM (14)
Jun 08, 7-8 PM (3)
Jun 08, 8-9 PM (6)
Jun 08, 9-10 PM (19)
Jun 08, 10-11 PM (29)
Jun 08, 11-12 AM (8)
Jun 09, 12-1 AM (5)
Jun 09, 1-2 AM (3)
Jun 09, 2-3 AM (1)
Jun 09, 3-4 AM (3)
Jun 09, 4-5 AM (26)
Jun 09, 5-6 AM (5)
Jun 09, 6-7 AM (23)
Jun 09, 7-8 AM (50)
Jun 09, 8-9 AM (35)
Jun 09, 9-10 AM (45)
Jun 09, 10-11 AM (51)
Jun 09, 11-12 PM (46)
Jun 09, 12-1 PM (86)
Jun 09, 1-2 PM (84)
Jun 09, 2-3 PM (36)
Jun 09, 3-4 PM (38)
Jun 09, 4-5 PM (16)
Jun 09, 5-6 PM (18)
Jun 09, 6-7 PM (18)
Jun 09, 7-8 PM (19)
Jun 09, 8-9 PM (16)
Jun 09, 9-10 PM (16)
Jun 09, 10-11 PM (28)
Jun 09, 11-12 AM (10)
Jun 10, 12-1 AM (11)
Jun 10, 1-2 AM (16)
Jun 10, 2-3 AM (11)
Jun 10, 3-4 AM (19)
Jun 10, 4-5 AM (5)
Jun 10, 5-6 AM (2)
Jun 10, 6-7 AM (46)
Jun 10, 7-8 AM (82)
Jun 10, 8-9 AM (18)
Jun 10, 9-10 AM (59)
Jun 10, 10-11 AM (46)
Jun 10, 11-12 PM (134)
Jun 10, 12-1 PM (48)
Jun 10, 1-2 PM (33)
Jun 10, 2-3 PM (32)
Jun 10, 3-4 PM (28)
Jun 10, 4-5 PM (35)
Jun 10, 5-6 PM (12)
Jun 10, 6-7 PM (12)
Jun 10, 7-8 PM (38)
Jun 10, 8-9 PM (11)
Jun 10, 9-10 PM (9)
Jun 10, 10-11 PM (20)
Jun 10, 11-12 AM (7)
Jun 11, 12-1 AM (10)
Jun 11, 1-2 AM (2)
Jun 11, 2-3 AM (0)
Jun 11, 3-4 AM (2)
Jun 11, 4-5 AM (7)
Jun 11, 5-6 AM (12)
Jun 11, 6-7 AM (34)
Jun 11, 7-8 AM (106)
Jun 11, 8-9 AM (34)
Jun 11, 9-10 AM (20)
Jun 11, 10-11 AM (100)
Jun 11, 11-12 PM (7)
Jun 11, 12-1 PM (0)
3,058 commits this week Jun 04, 2026 - Jun 11, 2026
Discard snapshots if at same slot as the immutable db and is EBB
This tackles a weird corner case in a test run, checkout the commit before this
one and run `ChainDB q-s-m.sequential` with `--quickcheck-replay="(SMGen
15718721082101496336 1793087168948606521,99)"` to observe it.

In short, in the tests we don't copy blocks when we snapshot (which we do on
production, see TODO(geo2a) in ChainDB.StateMachine). Normally we would be
guarded by the fact that the snapshot would have a higher slot than the
immutable db tip but when the tip is exactly at an EBB this is not the case and
we reach the exception case.

With this fix, we instead discard the snapshot in this extremely rare situation.
Fix worst-case ClosedDatum pre-funding: use N not N-1 contesters
  The init tx pre-funds the head output with enough ADA to cover the
  largest possible ClosedDatum (all parties having contested). The
  previous calculation used N-1 contesters, but the datum can grow
  to N entries when every party contests. For a single-party head,
  N-1=0 so the pre-funded amount was computed with an empty contesters
  list, leaving 103,440 lovelace short; the wallet's ensureMinUTxO
  then topped up the contest output, which the strict
  mustPreserveHeadValue (H4) check on-chain correctly rejected.

Signed-off-by: Sasha Bogicevic <[email protected]>
Pre-fund head output with worst-case ClosedDatum min-UTxO at init time
  The geq → == change in mustPreserveHeadValue prevents stuck-head griefing
  but requires the head output ADA to never need a wallet bump at Close or
  Contest time. Since ClosedDatum with N-1 contesters is larger than
  OpenDatum, compute minUTxO(ClosedDatum_{N-1}, V_tokens) at initTx and
  set that as the head output ADA. This ADA is returned to participants at
  fanout and remains stable through Increments (headAdaOverhead = headADA -
  utxoADA stays constant as both sides grow by deposit_ADA).

  Move InitTx out of prepareTxToPost (STM) into postTx (IO) so getPParams
  — a new TinyWallet field — can be called to fetch live protocol params
  for the min-UTxO calculation.

Signed-off-by: Sasha Bogicevic <[email protected]>
Enforce strict value equality in Close/Contest: prevent stuck-head griefing
  mustPreserveHeadValue used geq (≥) rather than == for the Close and
  Contest transitions. This allowed a malicious contester to add extra ADA
  to the head output — a griefing attack where the added value would cause
  the fanout's strict == conservation check to fail, permanently locking
  the head.

  The geq was a defensive measure for datum-growth min-UTxO scenarios, but
  neither closeTx nor contestTx ever changes the head value (both call
  modifyTxOutDatum only), and using geq to "top up" would break the fanout
  anyway. The correct invariant is ==, which matches the spec.

  Add ContestIncreaseHeadValue mutation test to document the property.

Signed-off-by: Sasha Bogicevic <[email protected]>
Add regression test for FinalPartialFanoutTx with pending deposit UTxO
  When snapshotVersion < version (an IncrementTx confirmed on L1 after the
  last snapshot), computeFullFanoutUTxO includes utxoToCommit in the full
  fanout set. After a PartialFanoutTx distributes all other outputs, a
  FinalPartialFanoutTx must distribute the pending deposit UTxO alone.

  Adds genClosedStateWithPendingCommit to the testlib and a property test
  that verifies finalPartialFanout succeeds in this scenario — guarding
  against the StaleChainState failure observed in practice when the
  in-memory deposit UTxO accumulator element diverges from the on-chain
  commitment.

Signed-off-by: Sasha Bogicevic <[email protected]>
Fix genCloseTx head value mismatch in close property tests
  genStOpen inflates the head UTxO by u0Value but leaves headAdaOverhead
  in the open datum unchanged. genCloseTx now adjusts the head UTxO value
  to headAdaOverhead + confirmedUTxOValue + decommitValue before building
  the close tx, so closeTx computes the correct headAdaOverhead' for the
  closed datum (fixes H65).

  forAllClose was discarding the adjusted UTxO returned by genCloseTx and
  recomputing it from the original inflated OpenState, causing the
  evaluator to see a different input value than what the tx was built
  against (fixes H4/HeadValueIsNotPreserved).

Signed-off-by: Sasha Bogicevic <[email protected]>
Cache BLS12-381 accumulator hash as lazy field in HydraAccumulator
  getAccumulatorHash (which runs a BLS12-381 multi-scalar multiplication)
  was called three times per snapshot: once in `sign`, once in
  `mkSeenSnapshot`, and once in `toJSON`. All three operate on the same
  HydraAccumulator value.

  Change HydraAccumulator from a newtype to a data type with a lazy
  _cachedHash field. The thunk is computed at most once per value via
  normal Haskell sharing — subsequent callers return the already-evaluated
  ByteString immediately.

Signed-off-by: Sasha Bogicevic <[email protected]>
Fix genCloseTx head UTxO value for strict mustPreserveHeadValue
  genStOpen inflates the head UTxO by u0Value but genCloseTx was not
  adjusting it before calling unsafeClose. With mustPreserveHeadValue
  now using strict equality (instead of geq), the head value must be
  exactly headAdaOverhead + confirmedUTxO + decommit at close time,
  or the Plutus script fires H65. Adjust the UTxO in genCloseTx the
  same way genClosedStateForFanout and genFanoutTx already do.

Signed-off-by: Sasha Bogicevic <[email protected]>
Add contract tests for CloseAny, CloseInitial mutations, and ContestCommit
  Three coverage gaps in the contract test suite are filled:

  - CloseAny: new test module exercising the NoThing → CloseAny redeemer
    path, including the unique on-chain snapshotNumber > 0 check (21
    mutations; signature/number failures all map to FailedCloseAny because
    that path wraps both checks in a single traceIfFalse).

  - CloseInitial: expanded from 2 to 15 mutations, adding the three
    CloseInitial-specific checks (snapshotNumber == 0, version == 0,
    accumulatorCommitment == G1 generator → FailedCloseInitial) plus the
    ten shared close checks (parties, headId, contestation period, validity
    bounds, minting, contesters, value, required signer).

  - ContestInc: new test module contesting with a commit-type snapshot
    (utxoToCommit = Just depositedUTxO), exercising the ToCommit
    accumulator commitment path that ContestDec (decommit path) left
    uncovered.

Signed-off-by: Sasha Bogicevic <[email protected]>
Fix headAdaOverhead griefing: lock value in OpenDatum at init time
    A malicious participant could set an arbitrary headAdaOverhead in the
    output ClosedDatum at close time. Since mustPreserveHeadAdaOverhead in
    checkContest locks this value permanently after Close, a wrong value
    makes fanout impossible and freezes all head funds.

    Fix: store headAdaOverhead in OpenDatum at init time (where it equals
    worstCaseMinLovelace, the pre-funded overhead with no L2 UTxOs) and
    verify it is unchanged in checkClose, checkIncrement, and checkDecrement.
    The invariant headAdaOverhead = headLovelace - utxoLovelace holds across
    the head lifetime because Increment and Decrement both change headLovelace
    and utxoLovelace by the same deposit/decommit amount.

    Also fix worstCaseClosedDatum used to compute the pre-funded ADA at
    initTx: variable-size integer fields (version, snapshotNumber,
    contestationDeadline, headAdaOverhead) previously used small constants
    (0 or 2 trillion) that produce 1-9 CBOR bytes less than the true
    maximum. Using maxBound @Word64 for all of them ensures the datum size
    estimate is a genuine upper bound, preventing potential ledger min-UTxO
    rejections on close for long-running or high-throughput heads.

Signed-off-by: Sasha Bogicevic <[email protected]>