Skip to content

Conversation

@agustinmista
Copy link
Contributor

@agustinmista agustinmista commented Nov 24, 2025

This PR introduces a sequence of changes to progressively improve the chances of observing possible-but-currently-uncommon Peras events in the ChainDB state machine tests. Concretely, we are interested in observing the chain selection algorithm switching from a longer to a shorter chain. For this, we rely on the following changes:

  1. Tweaking the Cmd generation frequencies to produce more AddBlock and AddPerasCerts commands.
  2. Enhancing the Cmd generator to "remember" previously generated blocks so that generated gaps can actually be filled sometimes (this previously relied entirely on randomness).
  3. Generating a geometrically-distributed security parameter (k) on the fly, while first addressing Allow for k>2 in ChainDB state machine tests #1745. This includes a rationale for why we have chosen to sample k using a geometric distribution with p=0.5.

Please check the commit messages to find the empirical effect of these changes between commits. In short, we improve the chances of observing TagSwitchedToShorterChain from ~0.03% to about ~1.8%.

Closes #1745

@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch from 6973759 to e56667a Compare November 24, 2025 12:17
@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch 3 times, most recently from 7ad8dca to 3420b36 Compare November 24, 2025 12:58
@agustinmista agustinmista changed the title [Peras #6] Improve generation of Peras events in ChainDB state machine tests [Peras 6] Improve generation of Peras events in ChainDB state machine tests Nov 24, 2025
Copy link
Contributor

@tbagrel1 tbagrel1 left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Contributor

@nbacquey nbacquey left a comment

Choose a reason for hiding this comment

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

LGTM

Comment on lines -1840 to +1980
tabulate "TraceEvents" (map traceEventName trace) $
res === Ok
.&&. prop_trace testCfg (dbModel model) trace
.&&. counterexample
"ImmutableDB is leaking file handles"
(Mock.numOpenHandles (nodeDBsImm fses) === 0)
.&&. counterexample
"VolatileDB is leaking file handles"
(Mock.numOpenHandles (nodeDBsVol fses) === 0)
.&&. counterexample
"LedgerDB is leaking file handles"
(Mock.numOpenHandles (nodeDBsLgr fses) === 0)
.&&. counterexample
"There were registered clean-up actions"
(remainingCleanups === 0)
tabulate "Security Parameter (k)" [show secParam] $
tabulate "Chain length >= k" [show (Chain.length modelChain >= fromIntegral secParam)] $
tabulate "TraceEvents" (map traceEventName trace) $
res
=== Ok
.&&. prop_trace testCfg (dbModel model) trace
.&&. counterexample
"ImmutableDB is leaking file handles"
(Mock.numOpenHandles (nodeDBsImm fses) === 0)
.&&. counterexample
"VolatileDB is leaking file handles"
(Mock.numOpenHandles (nodeDBsVol fses) === 0)
.&&. counterexample
"LedgerDB is leaking file handles"
(Mock.numOpenHandles (nodeDBsLgr fses) === 0)
.&&. counterexample
"There were registered clean-up actions"
(remainingCleanups === 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

NOTE: this hunk only contains formatting changes.

The formatter in my editor is very annoyed by this block not being properly formatted, so I need to remember to manually bypass format-on-save every time I touch this file.

I would appreciate if we can merge this hunk, but I can also revert it back if nobody agrees 🥲

@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch from 801c16e to 3523934 Compare November 24, 2025 15:49
@agustinmista agustinmista marked this pull request as ready for review November 24, 2025 16:55
Copy link
Contributor

@geo2a geo2a left a comment

Choose a reason for hiding this comment

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

I've asked for some clarifications on how the new generator state is used.

Also, does this PR actually depend on #1679 or could it target main directly? I think we would prefer to merge directly to main to keep things independent.

-- under the hood.
data GenState blk
= GenState
{ seenBlocks :: Map (HeaderHash blk) blk
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not understand where exactly these are used and what for. The only place I've found is in genBlk, where we check if the seen blocks are empty or not:

  savedGapBlocks = seenBlocks genState
  noSavedGapBlocks = Map.null savedGapBlocks

could you describe the use if this state in more detail? Preferably in its haddock in Model:

-- | Execution model
data Model blk m r = Model
  { dbModel :: DBModel blk
  , knownIters :: KnownIters blk m r
  , knownFollowers :: KnownFollowers blk m r
  , modelConfig :: Opaque (TopLevelConfig blk)
  , genState :: GenState blk
  -- ^ TODO: briefly describe how it's used
  }

Copy link
Contributor Author

@agustinmista agustinmista Nov 25, 2025

Choose a reason for hiding this comment

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

The main idea here is to keep intermediate blocks generated by genBlk around, so that we no longer need to pray for them to be (re)generated from scratch in subsequent AddBlock and AddPerasCert invocations.

To illustrate, suppose the test has so far built the following chain:

A -> B -> C

Without this change, one of the options genBlk has is to generate a dangling block via genBlockAfterGap, by first building a random fork and then returning its tip. For instance, let's assume that genBlockAfterGap branches from B, and generates a block F, that fits on top of B via some (hypothetical) intermediate blocks D and E:

A -> B -> C
     |
     + -> (D) -> (E) -> F

From here, the state machine runs AddBlock F, leaving the gap between B and F to be potentially filled by other random invocations to AddBlock later on.

Now, the problem with this approach is that generating blocks compatible with D and E to fill the gap created by F completely at random is very unlikely. And since genBlk can only do it by adding new blocks on top of existing ones, it's very likely that any potential fork created this way will diverge and never reach F, rendering F rather useless IMO.

With this commit, whenever we genBlockAfterGap, we also save the hypothetical gap blocks (D and E) for later in the model state, so that genBlk can sample from this set later on (see the new genGapBlock subgenerator). This allows the hypothetical fork created by genBlockAfterGap to be recreated without having to redo all the original hard work leading to F.

Note that in the future we could also be more clever and store collections of gap blocks indexed by their tip (e.g. { F :-> {D, E} }) instead of individual ones:

seenBlocks :: 
  Map 
    (HeaderHash blk) -- tip of the fork
    ( Map 
      (HeaderHash blk) -- gap blocks
      blk
    )

(Using Map (HeaderHash blk) instead of Set just to satisfy Ord.) From there, we could first sample a fork, and then sample one of its gap blocks without replacement (i.e. removing the sampled value from the collection). This would avoid degrading the chances of filling gaps after a while by only remembering the gap blocks that haven't been sampled from yet.


Would adding something like this somewhere help justifying the changes?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see, genGapBlock was the missing piece for me to understand how the gap blocks were actually used. Thanks for the explanation @agustinmista! I think it would be great to have this comment incorporated into the test file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a similar explanation (that doesn't focus on the before vs. after but rather on why we want this) in a6d4a00

@agustinmista
Copy link
Contributor Author

Thanks for the review @geo2a, I left a comment trying to explain the rationale a bit better. As for:

Also, does this PR actually depend on #1679 or could it target main directly? I think we would prefer to merge directly to main to keep things independent.

This PR makes some trivial changes to ouroboros-consensus/test/consensus-test/Test/Consensus/MiniProtocol/ObjectDiffusion/PerasCert/Smoke.hs, which doesn't exist in main yet, so it kinda depends on #1679. However, we can rebase this one onto main and drop those changes. We just need to remember to reapply them to whichever of the two PRs ends up getting merged last.

Up to you 😸

@geo2a
Copy link
Contributor

geo2a commented Nov 25, 2025

Thanks @agustinmista! If it's not too much work, please separate this PR into a standalone one targeting main.

@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch 2 times, most recently from cf6d81f to 38fbb8c Compare November 25, 2025 14:55
@agustinmista agustinmista changed the base branch from main-pr/object-diffusion to main November 25, 2025 14:56
@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch from 38fbb8c to 1827392 Compare November 25, 2025 14:59
@dnadales dnadales moved this to 👀 In review in Consensus Team Backlog Nov 25, 2025
@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch 2 times, most recently from 68f5043 to d5b1cea Compare November 25, 2025 15:48
Copy link
Member

@dnadales dnadales left a comment

Choose a reason for hiding this comment

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

Looks good to me. I left some comments where I think the code would benefit from additional explanations.

@agustinmista
Copy link
Contributor Author

Thanks @dnadales for the review! I added some extra documention to the code (derived mainly from the existing commit messages) in d521432.

Let me know if you're happy with these changes and I will squash them into their corresponding commits.

@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch 2 times, most recently from e3cf626 to cc3212c Compare November 27, 2025 15:02
agustinmista and others added 6 commits November 27, 2025 18:41
Since the Peras boost per certificate will likely become a protocol
parameter, we proactively avoid exposing the current hardcoded value,
replacing it with an instantiation of the (currently trivial) PerasCfg
record.

In the special cases where it's interesting to vary the boost
dynamically (ChainDB q-s-m), validated Peras certs now contain randomly
generated boost weights.

Co-authored-by: Agustin Mista <[email protected]>
Co-authored-by: Alexander Esgen <[email protected]>
Co-authored-by: Georgy Lukyanov <[email protected]>
Co-authored-by: Thomas BAGREL <[email protected]>
Co-authored-by: Nicolas BACQUEY <[email protected]>
This commit addresses
#1745
by selectively disabling EBB generation when the security parameter (k)
is larger than 2.

This is needed to allow generating k>2 on the fly, which is necessary
for testing that certain Peras-related events are properly handled by
weight-based chain selection algorithm.

Co-authored-by: Agustin Mista <[email protected]>
Co-authored-by: Alexander Esgen <[email protected]>
Co-authored-by: Georgy Lukyanov <[email protected]>
Co-authored-by: Thomas BAGREL <[email protected]>
Co-authored-by: Nicolas BACQUEY <[email protected]>
This commit increases the generation frequencies of both the
'genAddBlock' and 'genAddPerasCert' constructions to help producing
denser chains of blocks. This way, some of the events that were
harder to trigger (especially TagSwitchedToShorterChain) are much
more common now:

* Before:
    Tags (5867 in total):
    39.36% TagGetIsValidJust
    29.76% TagGetIsValidNothing
    28.50% TagChainSelReprocessKeptSelection
     2.35% TagChainSelReprocessChangedSelection
     0.03% TagSwitchedToShorterChain

* After:
    Tags (5202 in total):
    37.57% TagGetIsValidJust
    28.24% TagGetIsValidNothing
    27.25% TagChainSelReprocessKeptSelection
     6.41% TagChainSelReprocessChangedSelection
     0.53% TagSwitchedToShorterChain

Co-authored-by: Agustin Mista <[email protected]>
Co-authored-by: Alexander Esgen <[email protected]>
Co-authored-by: Georgy Lukyanov <[email protected]>
Co-authored-by: Thomas BAGREL <[email protected]>
Co-authored-by: Nicolas BACQUEY <[email protected]>
Extends the ChainDB model with generator state to support carrying gap
blocks in state machine tests. This increases the chances of generating
and adding (possibly out-of-order) branching sequences of blocks. This,
in turn increases the chances of observing the event where the
chain selection logic switches from a longer to a shorter (but heavier)
chain containing a boosted block.

Before:
    Tags (5202 in total):
    37.57% TagGetIsValidJust
    28.24% TagGetIsValidNothing
    27.25% TagChainSelReprocessKeptSelection
     6.41% TagChainSelReprocessChangedSelection
     0.53% TagSwitchedToShorterChain

After:
    Tags (5176 in total):
    39.10% TagGetIsValidJust
    27.94% TagChainSelReprocessKeptSelection
    26.49% TagGetIsValidNothing
     5.37% TagChainSelReprocessChangedSelection
     1.10% TagSwitchedToShorterChain

Co-authored-by: Agustin Mista <[email protected]>
Co-authored-by: Alexander Esgen <[email protected]>
Co-authored-by: Georgy Lukyanov <[email protected]>
Co-authored-by: Thomas BAGREL <[email protected]>
Co-authored-by: Nicolas BACQUEY <[email protected]>
After analysing the effect of varying the security parameter (`k`) of
the ChainDB state machine tests (currently hardcoded with 2), we have
observed a tension between:

1) generating enough tests exercising the new Peras behavior where the
   chain selection mechanism switches to a shorter but heavier chain
   (cert boost is derived from k and must be large enough to overcome
   the weight of a longer chain), and

2) generating enough tests exercising the ImmutableDB logic (the chain
   must have at least k blocks)

Here are some empirical results:

k   -> P(switch to shorter chain), P(generate a chain with >= k blocks)
k=2 -> ~1.3%, ~40%
k=3 -> ~1.9%, ~20%
k=4 -> ~2.4%, ~9%
k=5 -> ~2.5%, ~3%
k=10 -> ~3%,  ~0.05%

We believe that the sweet spot between both desiderata appears to be
around `k=2` and `k=4`.

This commit introduces a random generator for `k` using a geometric
distribution to bias the randomly generated `k`s to be relatively small,
while still allowing larger ones to appear from time to time.

Under the current parameters, roughly 87.5% of the tests use `k<=4`;

```
Security Parameter (k) (10000 in total):
  50.82% 2
  23.83% 3
  12.62% 4
   6.69% 5
   3.08% 6
   1.54% 7
   0.74% 8
   0.37% 9
   0.16% 10
   0.06% 11
   0.05% 12
   0.02% 13
   0.01% 14
   0.01% 17
```

Yielding the following distributions for 1) and 2), respectively:

```
Tags (5398 in total):
38.70% TagGetIsValidJust
29.23% TagChainSelReprocessKeptSelection
26.25% TagGetIsValidNothing
 3.98% TagChainSelReprocessChangedSelection
 1.83% TagSwitchedToShorterChain  <- HERE
```

```
Chain length >= k (10000 in total):
  73.25% False
  26.75% True  <- HERE
```

Co-authored-by: Agustin Mista <[email protected]>
Co-authored-by: Alexander Esgen <[email protected]>
Co-authored-by: Georgy Lukyanov <[email protected]>
Co-authored-by: Thomas BAGREL <[email protected]>
Co-authored-by: Nicolas BACQUEY <[email protected]>
@agustinmista agustinmista force-pushed the peras/main-pr/improve-chaindb-qsm-tests branch from cc3212c to ee92a0b Compare November 27, 2025 17:42
@agustinmista agustinmista requested a review from geo2a November 27, 2025 17:43
@agustinmista
Copy link
Contributor Author

agustinmista commented Nov 27, 2025

Rebased and reapplied the patch that depended on #1679.

This should be ready to go on our end at soon as it passes CI 🥳

@agustinmista agustinmista added this pull request to the merge queue Nov 28, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to no response for status checks Nov 28, 2025
@agustinmista agustinmista added this pull request to the merge queue Nov 28, 2025
Merged via the queue into main with commit 3647aa0 Nov 28, 2025
49 of 76 checks passed
@agustinmista agustinmista deleted the peras/main-pr/improve-chaindb-qsm-tests branch November 28, 2025 18:03
@github-project-automation github-project-automation bot moved this from 👀 In review to ✅ Done in Consensus Team Backlog Nov 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

Allow for k>2 in ChainDB state machine tests

6 participants