Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5ad0fd0
Take Mithril snapshots every 40*k slots with no offset
jasagredo Aug 7, 2026
7d5dfe0
Fix excessive allocation in mempool snapshotting
jasagredo Aug 10, 2026
ff53574
Make OneEraGenTxId Eq/Ord allocation-free (#2003)
dnadales Jul 31, 2026
cee6920
Add txid Eq/Ord tests
dnadales Jul 31, 2026
e20b472
Add txid Eq/Ord micro-benchmark
dnadales Jul 31, 2026
ebb37ec
Release ouroboros-consensus 4.1.0.0
jasagredo Aug 11, 2026
c85b71d
Release consensus-4.1 (#2189)
jasagredo Aug 11, 2026
0d12014
Decouple LedgerDB garbage collection from snapshotting
jasagredo Aug 21, 2026
4ff6969
Ensure a single snapshot is queued
jasagredo Aug 21, 2026
d85a76f
Serve LedgerDB snapshot requests in a ChainDB background thread
jasagredo Aug 26, 2026
91e6521
Decouple LedgerDB garbage collection from snapshotting (#2227)
jasagredo Aug 27, 2026
6016e15
Release ouroboros-consensus 4.2.0.0
jasagredo Aug 27, 2026
418baa1
Release ouroboros-consensus 4.2.0.0 (#2242)
jasagredo Aug 27, 2026
4496b8f
Disable IOWait accounting on the LSM backend
jasagredo Aug 28, 2026
76b91ca
Release ouroboros-consensus 4.2.0.1
jasagredo Aug 28, 2026
27396eb
Disable IOWait accounting on the LSM backend and release patch consen…
jasagredo Aug 28, 2026
1338120
Add a ShelleyGenesis codec without sgExtraConfig
dnadales Aug 28, 2026
ccc5938
Serve GetGenesisConfig without sgExtraConfig
dnadales Aug 28, 2026
d76873b
Ignore the artefacts written by the CDDL tests
dnadales Aug 28, 2026
24abaf6
Add a changelog entry for the GetGenesisConfig fix
dnadales Aug 28, 2026
6f011ad
Decode ShelleyGenesis from 15 fields only
dnadales Aug 31, 2026
500772d
Release ouroboros-consensus 4.2.1.0
dnadales Sep 1, 2026
82ecba3
Serve GetGenesisConfig without sgExtraConfig and release consensus 4.…
dnadales Sep 1, 2026
f0c9161
Backport Byron FixedSizedCodec stuff
ch1bo Aug 6, 2026
a1b6d60
Updates for crypton/memory/ram changes
erikd Aug 12, 2026
aed5521
cabal.project: Update index-states
erikd Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ cabal.project.consensus
*/.tasty-rerun-log
ouroboros-consensus-cardano/test/tools-test/disk/chaindb/

# artefacts written by the CDDL tests
/failing_cddl_tests/
/ntnblock.cddl
/ntnheader.cddl
/ntntx.cddl
/ntntxid.cddl

# https://github.com/github/gitignore/blob/main/Haskell.gitignore
dist
dist-*
Expand Down
79 changes: 79 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,85 @@ sections.

# Changelog entries

<a id='changelog-4.2.1.0'></a>
## 4.2.1.0 -- 2026-09-01

### Non-Breaking

- Added `encodeShelleyGenesisNoExtraConfig` and `decodeShelleyGenesisNoExtraConfig`
to `Ouroboros.Consensus.Shelley.Ledger.Query.LegacyShelleyGenesis`. They encode
and decode `ShelleyGenesis` with 15 fields.

### Patch

- Fixed the `GetGenesisConfig` node-to-client query. `cardano-ledger-shelley`
1.19.0.0 added the `sgExtraConfig` field to `ShelleyGenesis` and grew the CBOR
record from 15 fields to 16, under a fixed node-to-client version. The node now
encodes and decodes 15 fields again. `compactGenesis`
erases `sgExtraConfig`, so the reply carries no less data than before.

<a id='changelog-4.2.0.1'></a>
## 4.2.0.1 -- 2026-08-28

### Patch

- Disable IOWait time accounting, see [well-typed/blockio-uring#55](github.com/well-typed/blockio-uring/issues/55).

<a id='changelog-4.2.0.0'></a>
## 4.2.0.0 -- 2026-08-27

### Breaking

- `LedgerDB.tryTakeSnapshot` no longer writes any snapshot itself: it only
enqueues a `SnapshotRequest` on the new `LedgerDB.snapshotRequestQueue`, which
the ChainDB serves in a dedicated background thread. Accordingly, it lost its
"copy blocks" and "random delay" arguments, which are now supplied by that
thread.
- `ChainDB.Internal.intTryTakeSnapshot` lost its arguments for the same reason;
it now enqueues a request and serves it synchronously, without copying blocks
to the ImmutableDB or delaying.

### Patch

- Decouple LedgerDB garbage collection from (randomly delayed) snapshotting.

<a id='changelog-4.1.0.0'></a>
## 4.1.0.0 -- 2026-08-11

### Breaking

- Added `hardForkEqGenTxId` and `hardForkCompareGenTxId` to the `CanHardFork`
class. The `Eq` and `Ord` instances for `OneEraGenTxId` (and hence for
`TxId (GenTx (HardForkBlock xs))`) now delegate to them, so each hard fork
chooses how to compare its transaction ids. The methods have no default, so
existing `CanHardFork` instances must supply them; the exported `rawHashNS`
is the raw-hash implementation the non-optimizing instances reuse.
- The Mithril snapshot policy (which is also the default policy) now takes a
snapshot every `40 * k` slots with no offset, instead of every 432,000 slots
with an offset of 388,800. On mainnet this is one snapshot a day, one of every
five landing on a Shelley epoch boundary.
- `sfaInterval` is now a `SnapshotInterval`, which is either
`DefaultSnapshotInterval` (`40 * k` slots, resolved via
`resolveSnapshotInterval` once the `SecurityParam` is known) or an explicit
`RequestedSnapshotInterval`.
- `defaultSnapshotPolicy` and `sanityCheckSnapshotPolicyArgs` now take a
`SecurityParam`.

### Non-Breaking

- Added `snapshotFromIS`, which builds a `MempoolSnapshot` from the mempool's
internal state in constant time by reusing the transaction sequence
(`isTxs`) and the cached transaction ids (`isTxIds`) it already maintains.

### Patch

- Made those `Eq`/`Ord` comparisons allocation-free for the Cardano eras, on
every comparison rather than only same-era ones. The Cardano instance reads
the transaction id hash as four machine words and compares them in registers,
instead of serialising both ids to their raw hash. The ordering is unchanged.
- `getSnapshot` and the fast path of `getSnapshotFor` no longer rebuild the
mempool contents on every call. This was quadratic in the size of the mempool.

<a id='changelog-4.0.0.0'></a>
## 4.0.0.0 -- 2026-07-30

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Ouroboros Consensus

[![consensus](https://img.shields.io/badge/ouroboros--consensus-4.0.0.0-blue)](https://chap.intersectmbo.org/package/ouroboros-consensus-4.0.0.0/) [![docs](https://img.shields.io/badge/Documentation-yellow)][webpage]
[![consensus](https://img.shields.io/badge/ouroboros--consensus-4.2.1.0-blue)](https://chap.intersectmbo.org/package/ouroboros-consensus-4.2.1.0/) [![docs](https://img.shields.io/badge/Documentation-yellow)][webpage]

Implementation of the Ouroboros family of consensus algorithms.

Expand Down
35 changes: 33 additions & 2 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,29 @@ repository cardano-haskell-packages
-- update either of these.
index-state:
-- Bump this if you need newer packages from Hackage
, hackage.haskell.org 2026-07-30T07:30:12Z
, hackage.haskell.org 2026-09-01T20:29:07Z
-- Bump this if you need newer packages from CHaP
, cardano-haskell-packages 2026-07-30T09:29:13Z
, cardano-haskell-packages 2026-08-31T18:21:54Z

packages: .

-- TEMPORARY: none of these packages touch `memory`/`crypton` directly, they
-- just haven't widened their cardano-crypto-class upper bound past <2.6 yet,
-- so relaxing the bound against their already-published code is safe (unlike
-- the cardano-ledger-* packages, which would drag in an unreleased, version-
-- skewed slice of that monorepo if done via a source-repository-package
-- instead). Remove alongside the source-repository-package at the bottom
-- of this file once upstream catches up.
allow-newer:
cardano-ledger-mary:cardano-crypto-class,
cardano-ledger-shelley:cardano-crypto-class,
cardano-ledger-binary:cardano-crypto-class,
cardano-ledger-core:cardano-crypto-class,
cardano-protocol:cardano-crypto-class,
cardano-protocol-tpraos:cardano-crypto-class,
kes-agent:cardano-crypto-class,
kes-agent-crypto:cardano-crypto-class,

-- We want to always build the test-suites and benchmarks
tests: true
benchmarks: true
Expand All @@ -42,3 +59,17 @@ if os (windows)

constraints:
tasty <1.5.4,

-- TEMPORARY: pulls in cardano-ledger PR #5999 (erikd/ram, not yet merged)
-- https://github.com/IntersectMBO/cardano-ledger/pull/5999
-- which switches cardano-crypto-wrapper from `memory` to `ram` (crypton
-- >=1.1's replacement for `memory`), so it builds against crypton >=1.1.
-- This is the only package in the chain that needs an actual source
-- change; the allow-newer entries above cover everything else, which just
-- has an overly conservative cardano-crypto-class upper bound. Remove once
-- this PR (or an equivalent fix) is released.
source-repository-package
type: git
location: https://github.com/IntersectMBO/cardano-ledger
tag: 6def84b5ae0033053feda72f32b1dac41c812e0e
subdir: eras/byron/crypto
23 changes: 15 additions & 8 deletions docs/website/contents/references/consensus_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,30 +230,37 @@ The LedgerDB periodically writes a **snapshot** of the ledger state to disk so
that a restarting node can resume from a recent state instead of replaying the
whole chain. Only states that are older than `k` blocks (i.e. immutable) are
snapshotted. The policy is configured by `SnapshotPolicyArgs`
(`srnSnapshotPolicyArgs`); every field can be left as `UseDefault` or
overridden. Defaults from `defaultSnapshotPolicy` in
(`srnSnapshotPolicyArgs`). Defaults from `defaultSnapshotPolicyArgs` in
`Ouroboros.Consensus.Storage.LedgerDB.Snapshots`:

| Knob | Default | Meaning / implication |
|---|---|---|
| `spaNum` (snapshots kept on disk) | **2** | When a new snapshot is written, the oldest is deleted, always leaving one intact snapshot in case the write is interrupted (snapshot files are not `fsync`ed). `1` is dangerous for that reason; `0` would delete the snapshot right after writing it. |
| `sfaInterval` | **`2·k` slots** (43,200 on mainnet ≈ 72 min of slots) | Snapshots are taken for the most recent immutable state before each slot in `offset, offset + interval, offset + 2·interval, …`. Nodes with the same interval/offset therefore snapshot *the same slots*, which matters for tools like Mithril that compare snapshots across nodes. Smaller interval = less replay on restart, more snapshot I/O. |
| `sfaInterval` | **`DefaultSnapshotInterval` = `40·k` slots** (86,400 on mainnet ≈ one day of slots) | Snapshots are taken for the most recent immutable state before each slot in `offset, offset + interval, offset + 2·interval, …`. Nodes with the same interval/offset therefore snapshot *the same slots*, which matters for tools like Mithril that compare snapshots across nodes. Smaller interval = less replay on restart, more snapshot I/O. A `RequestedSnapshotInterval` gives an explicit number of slots instead; either way the interval is turned into slots by `resolveSnapshotInterval`, using the `SecurityParam` of the LedgerDB configuration. |
| `sfaOffset` | **0** | Shifts the grid of snapshot slots, see above. |
| `sfaRateLimit` | **10 minutes** | Skip a snapshot if less than this much time passed since the previous one finished. Mainly relevant while syncing, when eligible slots stream past quickly. Non-positive values disable the limit. Should be well below the wall-clock duration of the interval, or snapshots get skipped even when caught up. |
| `sfaDelaySnapshotRange` | **5–10 minutes** | Once a snapshot is due, the node waits a random delay drawn from this range before writing it, so that the network's nodes don't all hit the disk (and slow down) simultaneously. |

Additional points:

- `spaFrequency = DisableSnapshots` turns snapshotting off entirely.
- `mithrilSnapshotPolicyArgs` is a ready-made policy for Mithril: interval
**432,000** (one Shelley epoch) and offset **388,800**, chosen so that
snapshots land on Shelley epoch boundaries even while still syncing Byron.
- `mithrilSnapshotPolicyArgs` is a ready-made policy for Mithril, and is what
`defaultSnapshotPolicyArgs` is defined to be: interval `40·k` and offset
**0**. On mainnet that is 86,400 slots, one fifth of the 432,000-slot Shelley
epoch, so one snapshot a day and one of every five landing exactly on an
epoch boundary (86,400 divides both 432,000 and the 4,492,800-slot start of
Shelley). The epoch boundary itself is not made busier by this: only
immutable states are snapshotted, so the write happens once the state is `k`
blocks deep, and `sfaDelaySnapshotRange` defers it by a further 5–10 minutes.
- Snapshots whose directory name carries a suffix (e.g. `4492799_last_Byron`)
are **never deleted** by the retention policy — useful for pinning a state.
- `sanityCheckSnapshotPolicyArgs` runs at startup and traces a warning for
suspicious overrides: 0 snapshots on disk, a negative or inverted delay
suspicious configurations: 0 snapshots on disk, a negative or inverted delay
range, a disabled or very large rate limit, or an interval incompatible with
Mithril (not dividing the 432,000-slot epoch).
Mithril (not dividing the 432,000-slot epoch). The interval check is applied
to the *resolved* interval, so it depends on `k`: `40·k` divides 432,000 for
mainnet's `k = 2160` and for the usual testnet values, but a network whose
`k` does not divide 10,800 will be warned about even with the default.

## Mempool

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
-- The NFData instance for 'OneEraGenTxId' below is a bench-local orphan.
{-# OPTIONS_GHC -Wno-orphans #-}

module Main (main) where

import Cardano.Protocol.Crypto (StandardCrypto)
import Control.DeepSeq (NFData (..))
import Data.ByteString.Short (ShortByteString)
import qualified Data.ByteString.Short as SBS
import Data.SOP (All, Proxy (..), lengthSList)
import Data.Word (Word8)
import Ouroboros.Consensus.Cardano.Block (CardanoEras)
import Ouroboros.Consensus.Cardano.Node ()
import Ouroboros.Consensus.HardFork.Combinator.AcrossEras (OneEraGenTxId)
import Ouroboros.Consensus.Shelley.HFEras ()
import Ouroboros.Consensus.Shelley.Ledger.SupportsProtocol ()
import Test.Consensus.Cardano.GenTxIdBuilders (BuildGenTxId, oneEraGenTxIds)
import Test.Tasty.Bench (Benchmark, bench, bgroup, defaultMain, env, whnf)

-- | 'OneEraGenTxId' has no 'NFData' instance, and a real one would need the
-- raw-hash walk (or a copy of it) here, which we want to avoid. A deep force is
-- not needed regardless: operands are built once and shared, and 'compare' and
-- '==' re-walk each operand on every call, so their construction never enters
-- the per-iteration figure (confirmed in the generated Core). WHNF suffices.
instance NFData (OneEraGenTxId xs) where
rnf x = x `seq` ()

main :: IO ()
main =
defaultMain
[ -- 'env' builds the benchmark tree before running 'mkOperands', so the
-- operands are not available yet at that point. The irrefutable pattern
-- (@~@) avoids forcing the tuple while the tree is built; the operands are
-- touched only inside the 'bench' bodies, which run after 'mkOperands'.
env (mkOperands @(CardanoEras StandardCrypto)) $ \ ~(lhs, equalRhs, unequalRhs) ->
bgroup
"txid-eq-ord"
[ operationGroup "compare" compare lhs equalRhs unequalRhs
, operationGroup "==" (==) lhs equalRhs unequalRhs
]
]

-- | One operation (@compare@ or @==@), laid out as @equal@ + @unequal@ cells
-- over the era grid.
operationGroup ::
String ->
(a -> a -> b) ->
[a] ->
[a] ->
[a] ->
Benchmark
operationGroup name op lhs equalRhs unequalRhs =
bgroup
name
[ bgroup
"equal"
[ bench (show i) $ whnf (uncurry op) (lhs !! i, equalRhs !! i)
| i <- eras
]
, bgroup
"unequal"
[ bench (show i ++ "-vs-" ++ show j) $ whnf (uncurry op) (lhs !! i, unequalRhs !! j)
| i <- eras
, j <- eras
]
]

-- | Number of eras to benchmark, read from the era list type. It must come from
-- the type, not the operand lists: the benchmark tree shape cannot depend on the
-- runtime operands produced by 'env'.
numEras :: Int
numEras = lengthSList (Proxy @(CardanoEras StandardCrypto))

eras :: [Int]
eras = [0 .. numEras - 1]

-- | A 32-byte hash, zero except for the given first byte. Two such hashes are
-- equal iff that first byte matches.
hashWithFirstByte :: Word8 -> ShortByteString
hashWithFirstByte b = SBS.pack (b : replicate 31 0)

-- | The three operand lists. @lhs@ and @equalRhs@ share a hash (first byte 0),
-- so they compare equal; @unequalRhs@ has first byte 1, so it never does.
mkOperands ::
All BuildGenTxId xs =>
IO ([OneEraGenTxId xs], [OneEraGenTxId xs], [OneEraGenTxId xs])
mkOperands = pure (operandsFor 0, operandsFor 0, operandsFor 1)

-- lhs equalRhs unequalRhs

-- | One operand per era (list index = era position) built from the given first
-- byte.
operandsFor :: All BuildGenTxId xs => Word8 -> [OneEraGenTxId xs]
operandsFor b = oneEraGenTxIds (hashWithFirstByte b)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module Ouroboros.Consensus.Byron.Crypto.DSIGN
, VerKeyDSIGN (..)
) where

import Cardano.Binary.FixedSizeCodec (FixedSizeCodec (..))
import qualified Cardano.Chain.Block as CC.Block
import qualified Cardano.Chain.UTxO as CC.UTxO
import Cardano.Crypto
Expand Down Expand Up @@ -64,9 +65,6 @@ data ByronDSIGN

instance DSIGNAlgorithm ByronDSIGN where
type SeedSizeDSIGN ByronDSIGN = 32
type VerKeySizeDSIGN ByronDSIGN = 64
type SignKeySizeDSIGN ByronDSIGN = 128
type SigSizeDSIGN ByronDSIGN = 64

algorithmNameDSIGN _ = "ByronDSIGN"

Expand Down Expand Up @@ -114,19 +112,19 @@ instance FixedSizeCodec (VerKeyDSIGN ByronDSIGN) where
type FixedSize (VerKeyDSIGN ByronDSIGN) = 64
rawEncodeFixedSized (VerKeyByronDSIGN (VerificationKey vk)) = CC.unXPub vk
rawDecodeFixedSized bs =
either fail (pure . VerKeyByronDSIGN . VerificationKey) (CC.xpub bs)
VerKeyByronDSIGN . VerificationKey <$> either fail pure (CC.xpub bs)

instance FixedSizeCodec (SignKeyDSIGN ByronDSIGN) where
type FixedSize (SignKeyDSIGN ByronDSIGN) = 128
rawEncodeFixedSized (SignKeyByronDSIGN (SigningKey sk)) = CC.unXPrv sk
rawDecodeFixedSized bs =
either fail (pure . SignKeyByronDSIGN . SigningKey) (CC.xprv bs)
SignKeyByronDSIGN . SigningKey <$> either fail pure (CC.xprv bs)

instance FixedSizeCodec (SigDSIGN ByronDSIGN) where
type FixedSize (SigDSIGN ByronDSIGN) = 64
rawEncodeFixedSized (SigByronDSIGN (Signature sig)) = CC.unXSignature sig
rawDecodeFixedSized bs =
either fail (pure . SigByronDSIGN . Signature) (CC.xsignature bs)
SigByronDSIGN . Signature <$> either fail pure (CC.xsignature bs)

instance Condense (SigDSIGN ByronDSIGN) where
condense (SigByronDSIGN s) = show s
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ import Ouroboros.Consensus.Byron.ByronHFC ()
import Ouroboros.Consensus.Byron.Ledger
import Ouroboros.Consensus.Byron.Node ()
import Ouroboros.Consensus.Cardano.Block
import Ouroboros.Consensus.Cardano.CanHardFork.OptimizedTxIdComparison
( compareCardanoGenTxId
)
import Ouroboros.Consensus.Forecast
import Ouroboros.Consensus.HardFork.Combinator
import Ouroboros.Consensus.HardFork.Combinator.State.Types
Expand Down Expand Up @@ -274,6 +277,16 @@ instance CardanoHardForkConstraints c => CanHardFork (CardanoEras c) where
fromTrivial :: TrivialTxMeasurePhase2 -> RefScriptSize
fromTrivial TrivialTxMeasurePhase2 = mempty

-- Both ids are ordered by their txid hash, ignoring the era. Equality reuses
-- 'compare' rather than a separate path: 'hardForkEqGenTxId' is
-- 'compareCardanoGenTxId' returning 'EQ'. This allocates no heap: all three
-- 'Ordering' constructors take zero arguments, so they are never allocated on
-- the dynamic heap; every 'Ordering' pointer is to one of three statically
-- allocated addresses. 'compareCardanoGenTxId' keeps the comparison itself
-- allocation-free.
hardForkCompareGenTxId = compareCardanoGenTxId
hardForkEqGenTxId l r = compareCardanoGenTxId l r == EQ

class TiebreakerView (BlockProtocol blk) ~ PraosTiebreakerView c => HasPraosTiebreakerView c blk
instance TiebreakerView (BlockProtocol blk) ~ PraosTiebreakerView c => HasPraosTiebreakerView c blk

Expand Down
Loading