Skip to content

Conversation

dnadales
Copy link
Member

This PR reorganizes the Consensus documentation, integrating information from various sources such as the Consensus report, Haddock comments, and existing documentation. The documentation is structured using the Diátaxis framework. Implementation-specific concepts will be moved to the Cardano Blueprints.

dnadales added 3 commits May 30, 2025 15:36
- Update the README to reflect the new structure.
- Update the frontpage to reflect the contents of the documentation.
- Delete all the content. It'll be gradually reintroduced and
  redistributed when appropriate.
@dnadales dnadales marked this pull request as draft May 30, 2025 16:57
@@ -55,7 +55,7 @@ Custom implementations of the Cardano node client are free to bypass this check

Our code does not use the negotiated [`NodeToClientVersion`][n2c] directly, but translates them first to a [`CardanoNodeToClientVersion`][cardano-n2c] and then to [`ShelleyNodeToClientVersion`][shelley-n2c].

- The [`querySupportedVersion`][query-supported-version] function assigns a [`ShelleyNodeToClientVersion`][shelley-n2c] to each Shelley-based query.
- The [`querySupportedVersion`][query-supported-version] function assigns a [`ShelleyNodeToClientVersion`][shelley-n2c] to each Shelley-based query, indicating the minimum *Shelley* version that supports the query.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nfrisby
Copy link
Contributor

nfrisby commented Jul 1, 2025

The added text looks good and nice to me.


The [`blockForgingController`](https://github.com/intersectmbo/ouroboros-consensus/blob/6c1f0e293b50b898e69116df0355595004432077/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/NodeKernel.hs#L375) function uses [`BlockChainTime`](https://github.com/intersectmbo/ouroboros-consensus/blob/6c1f0e293b50b898e69116df0355595004432077/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/BlockchainTime/API.hs#L23) to monitor the current slot.

Block forging invokes `forecastFor` on the `LedgerView`. If this operation takes too long, it can delay block production. Note that if it’s not possible to forecast to the current slot, then forging a block is not possible.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Block forging invokes `forecastFor` on the `LedgerView`. If this operation takes too long, it can delay block production. Note that if it’s not possible to forecast to the current slot, then forging a block is not possible.
Block forging invokes `forecastFor` for the current slot on the `LedgerView`. If this operation takes too long, it can delay block production. Note that if it’s not possible to forecast to the current slot, then forging a block is not possible.


Block forging invokes `forecastFor` on the `LedgerView`. If this operation takes too long, it can delay block production. Note that if it’s not possible to forecast to the current slot, then forging a block is not possible.

If `checkLeader` confirms the node is a leader, a block is forged, extending the current [selected chain](#chain-selection). Transactions are selected from a [mempool snapshot](TODO-ref!), which is a sequence of valid transactions consistent with the ledger state upon which the block will be built. For the purposes of block forging and the consensus protocol, these transactions are irrelevant as long as they are _valid_.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If `checkLeader` confirms the node is a leader, a block is forged, extending the current [selected chain](#chain-selection). Transactions are selected from a [mempool snapshot](TODO-ref!), which is a sequence of valid transactions consistent with the ledger state upon which the block will be built. For the purposes of block forging and the consensus protocol, these transactions are irrelevant as long as they are _valid_.
If `checkLeader` confirms the node is a leader, a block is forged, extending the current [selected chain](#chain-selection). Transactions are selected from a [mempool snapshot](TODO-ref!), which is a sequence of valid transactions consistent with the ledger state upon which the block will be built. For the purposes of block forging and the consensus protocol, these transactions are irrelevant as long as they are _valid from the Ledger point of view_.


The `k` parameter, often referred to as the security parameter, is a fundamental constant in the Ouroboros consensus protocols. On Cardano mainnet, `k` is set to 2160 blocks. This parameter underpins various aspects of a Cardano node's operation, from chain selection and storage to security guarantees and performance.

For Ouroboros Praos, used in Shelley-based eras, theoretical proofs guarantee that within `3k/f` slots, the chain will grow by at least `k` blocks.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention or link the Chain Growth Property


### Chain Selection and Rollbacks

The `ouroboros-consensus` implementation enforces a strict rule: a node will never switch to a chain that forks more than `k` blocks deep from its current tip.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention again the CP property


When a node is [caught up](TODO-ref), any candidate chains requiring a rollback exceeding `k` blocks are excluded from consideration. This constraint limits the number of ledger states that need to be retained to evaluate forks. Additionally, it prevents arbitrary-length rollbacks thus protecting against potential [attacks](#network-security-and-performance) that rely on this.

When a node is [syncing](TODO-ref) with the chain, an adversary may present it with an alternative chain that forks more than `k` blocks from the current tip. However, the [Genesis](TODO-ref) syncing protocol ensures that a node joining the network does not switch to an adversarial chain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When a node is [syncing](TODO-ref) with the chain, an adversary may present it with an alternative chain that forks more than `k` blocks from the current tip. However, the [Genesis](TODO-ref) syncing protocol ensures that a node joining the network does not switch to an adversarial chain.
When a node is [syncing](TODO-ref) with the chain, an adversary may present it with an alternative chain that forks more than `k` blocks from the server's current tip, which would be rejected by a caught up node. However, the [Genesis](TODO-ref) syncing protocol ensures that a node joining the network does not switch to an adversarial chain.

or something similar

Copy link
Contributor

@fraser-iohk fraser-iohk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the documentation changes look good (modulo typos), I've also left a comment on ChainOrder but that's more of an aside and changing that probably shouldn't be considered as part of this PR

However, Ouroboros involves short forks and potentially invalid blocks.
On the other hand, even for a single chain, the Consensus Layer would forecast across the same slots multiple times: it does it each time the wall clock enters a new slot as part of the leadership check, and it does so each time any peer sends it a header.

Ideally all ticks would be inexpensive, but the occasional spike is managable, since short forks are short lived.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"manageable"

## Bounding Ticking and Forecasting Computations

On a single valid chain, the Consensus Layer will never tick across the same slots multiple times.
However, Ouroboros involves short forks and potentially invalid blocks.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, Ouroboros necessarily involves both short forks and potentially invalid blocks, and even for a single chain, [...]

However, Ouroboros involves short forks and potentially invalid blocks.
On the other hand, even for a single chain, the Consensus Layer would forecast across the same slots multiple times: it does it each time the wall clock enters a new slot as part of the leadership check, and it does so each time any peer sends it a header.

Ideally all ticks would be inexpensive, but the occasional spike is managable, since short forks are short lived.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comma after "ideally"

Comment on lines +100 to +101
It could plausibly cause problems within the ledger rules if an entire epoch has no blocks, for example, so there's definitely no guarantee that the resulting ledger state would actually be useful.
But again: the Consensus Layer will never request such a tick.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

can these lines be moved into a [!note] block?

## Query versioning

Queries are versioned to ensure compatibility between nodes and clients running different software versions.
As the ledger gets more features and new use cases are explored, teams will add new queries that allow to get the necessary data.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"that allow getting the necessary data" or (probably better) "allow the client to get the necessary data"

@@ -0,0 +1,37 @@
# How to version a new query

1. Determine whether the query is supposed to be experimental.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"should be experimental" or "is intended to be experimental"

Comment on lines +220 to +226

-- | The 'ChainOrder' type class's 'preferCandidate' function
-- consumes both the 'SelectView's of competing chain tips and the
-- 'ChainOrderConfig' to make the final decision on which chain is
-- strictly preferred. This design allows for flexible and
-- protocol-specific tie-breaking rules that go beyond simple chain
-- length comparisons.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this extra comment is good but it's made me unsure about whether the design of the ChainOrder class makes sense -- should it instead have projectBlockNo and tiebreakCandidates functions, and preferCandidates could be removed from the class and separately enforce the "longer chains are always preferred" property?
(this is a bit unrelated to the actual documentation changes, so it might be better discussed somewhere else)

Copy link
Member

@amesgen amesgen Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this has come up in the past; I think everyone agreed that this would be neat (I don't remember why we didn't follow through with it; probably because it was relatively minor and SelectView containing the BlockNo has been the case forever). Eg concretely

data SelectView proto = SelectView {
    svBlockNo :: BlockNo
  , svTiebreaker :: TiebreakerView proto
  }

where TiebreakerView is an associated type family of ConsensusProtocol with ChainOrder (TiebreakerView proto) as a superclass constraint, and instances

  • Ord (TiebreakerView proto) => Ord (SelectView proto).
  • ChainOrder (TiebreakerView proto) => ChainOrder (SelectView proto).

I was actually considering doing sth like this in the context of Peras, as doing weighted comparisons between chains is naturally modeled by adding the additional weight to the BlockNo component, and it is a bit weird that it currently isn't directly accessible.


EDIT: #1591

Ensuring the thorough testability of the consensus layer is a critical design goal.
As a core component of the Cardano Node, which manages the cryptocurrency, the consensus layer must adhere to strict correctness standards.
Currently, we extensively employ property-based testing.
Whenever possible, we should abstract over IO, enabling simulations of various failures (such as disk or network errors) to verify system recovery capabilities.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably worth mentioning IOSim and io-classes here specifically

github-merge-queue bot pushed a commit that referenced this pull request Jul 18, 2025
This PR does not change any behavior; most of the diff is purely
mechanical.

Previously, the `SelectView`s of all `ConsensusProtocol`s contained the
`BlockNo` as the most important criterion for comparing chains, with
only tiebreaking behavior (among chains of equal length) being different
across different protocols.

This PR makes this explicit: `SelectView` is now longer an associated
type family of `ConsensusProtocol`, but rather an ordinary data type
```haskell
data SelectView p = SelectView
  { svBlockNo :: !BlockNo
  , svTiebreakerView :: !(TiebreakerView p)
  }
```
where `TiebreakerView` is now an associated type family of
`ConsensusProtocol`.

This PR also removes the `WithBlockNo` type that the HFC was already
using to always attach a `BlockNo` to its `HardForkSelectView`. The code
can be simpler now that `SelectView`s always automatically contain an
explicit `BlockNo`.

Also see
#1542 (comment)

## Primary motivation

The main motivation for this is to enable weighted chain selection in
Peras (tweag/cardano-peras#62), which can now
be easily modeled by adding the weight of a chain to the block number
component, at least morally; we probably want to eventually have more
type safety here to make it hard to "forget" to account for the weight,
such as defining
```haskell
data WeightedSelectView p = WeightedSelectView
  { wsvWeight :: !PerasWeight
  , wsvTiebreakerView :: !(TiebreakerView p)
  }
```

If the only goal were to minimize the diff, then it would also be
possible to add a function `(BlockNo -> BlockNo) -> SelectView p ->
SelectView p`, but it seems more honest to "properly" extract out the
block number.
dnadales added 3 commits July 21, 2025 12:12
- The Lightweight Checkpointing Component
- The Genesis State Machine Component
- The Limit on Eagerness Component
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants