Skip to content

Alternative to #2377: pass the mempool snapshot to forgeBlock - #2397

Draft
dnadales wants to merge 6 commits into
ch1bo/leios-header-validationfrom
dnadales/eb-forge-snapshot
Draft

dnadales wants to merge 6 commits into
ch1bo/leios-header-validationfrom
dnadales/eb-forge-snapshot

Conversation

@dnadales

@dnadales dnadales commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Alternative to #2377, for comparison only.
Do not merge.

forgeBlock gets the whole mempool snapshot (fbMempoolSnapshot) and returns the transactions that it selected (ForgedBlock).
Each era selects its own transactions.
The Dijkstra forge partitions the snapshot with both capacities, builds an endorser block, and traces it.
The hard fork combinator projects the snapshot to the tip era with three new CanHardFork methods.

Based on #2354.

Comparison with main and #2377: input-output-hk/ouroboros-leios#1132.

The second part of 'snapshotPartition' is the longest prefix of the
transactions that remain after the first part. The docs name the two
parts the ranking-block part and the endorser-block part, and describe
both as a longest prefix. They say that a part fits a capacity, and that
each part comes with its total measure. The DijkstraEbMeasure docs say
"sequence" in place of "run" for mempool transactions. The property
that tests the ranking-block part says "longest prefix" too.
Add hardForkProjTxMeasurePhase1, hardForkProjTxMeasurePhase2 and
hardForkProjTxEbMeasure. Projecting the injection of a measure gives
back that measure. Each one returns a measure at every era position,
and the caller picks one era with projectNP.

The hard fork combinator needs these methods to convert the combined
measures of a mempool snapshot to the measures of the era it forges in.

A property in cardano-test checks that, at every Cardano era position,
the projection of the injection of a measure gives back that measure.
forgeBlock returns a ForgedBlock. It holds the forged block and the
transactions that forgeBlock selected for it. The forge loop removes
these transactions from the mempool when the ChainDB finds the block
invalid. It traces them when the ChainDB adopts the block.

ForgedBlock lets an era decide which transactions its block holds. No
era changes how it builds its block, and every era returns fbTxs as
forgedTxs. So forged blocks and traces stay the same.

The hard fork combinator injects the era's transactions back into
combined transactions. A unit test in consensus-diffusion-test checks
that it returns the transactions that the era returns, in the same
order.
ForgeBlockArgs carries the mempool snapshot in fbMempoolSnapshot, in
place of the list fbTxs. Each era's forgeBlock selects its own
transactions from the snapshot. The snapshot lets an era select from
the whole mempool, not only from the prefix that fits a block.

Every era calls selectBlockTxs. It selects the ranking-block part of
the snapshot, for the block capacity of the ticked ledger state. The
forge loop made the same selection before. ForgedBlock gains
forgedTxsMeasure, the total measure of forgedTxs. The forge loop traces
it in TraceForgedBlock. The loop forces the snapshot size before it
calls forgeBlock, so the mempool revalidation still runs before the
forge and outside the KES state lock.

forgeRegularBlock takes the block config, block number, slot number,
ticked ledger state, transactions and leader proof as separate
arguments. The dual Byron forge passes the Byron part of its own
selection, and the Byron example block passes one transaction. Neither
has a Byron mempool snapshot. The db-synthesizer puts its generated
transactions in a snapshot with zero ticket measures, so the block
holds all of them.

The hard fork combinator projects the combined snapshot to the era at
the tip and injects the result back. So for a hard fork block,
TraceForgedBlock gets the era's measure, injected into the combined
measure. In Cardano before Dijkstra, the traced endorser-block measure
has a zero txReferencesSize. The unary embedding converts the snapshot
in both directions. Two unit tests in consensus-diffusion-test check
the combinator. A forge in the first era returns the era's selection
and measure. A forge in the first era can also select the
endorser-block part, and gets that part and its measure.
mkLeiosEb builds the endorser block that references the given
Shelley-based transactions, in the order of the list. It gives Nothing
for an empty list. Each reference holds the Blake2b-256 hash and the
length of the bytes that toCBOR writes for the ledger transaction. The
ToCBOR instance of GenTx wraps the same bytes with wrapCBORinCBOR.
encodeNodeToNode uses that instance, so these are the transaction bytes
that peers exchange.

Nothing calls it yet. The Dijkstra forge needs it to build an endorser
block from the endorser-block part of the mempool snapshot.

mkLeiosEb builds the vector inside LeiosEb with V.fromList, so the
cardano library depends on vector. The shelley-test suite depends on
cardano-crypto-class and the cardano-ledger-shelley testlib, for the
expected hash and the example ledger state in the new tests. The docs
of the module and of prop_referenceSizeConsistent say that a
transaction takes bytes of a capacity, in place of "charges".
The Dijkstra forge partitions the mempool snapshot once, with the block
capacity and the endorser-block capacity of the ticked ledger state. It
puts the ranking-block part in the block, as the Praos forge does. So
the block holds the same transactions as before this commit. If the
endorser-block part is not empty, the forge builds the endorser block
with mkLeiosEb. It traces that endorser block with TraceForgedLeiosEb.
The header does not announce the endorser block, and nothing stores it.

leiosSharedBlockForging takes a tracer of TraceLeiosForge events as its
first argument. The block-forging function that protocolInfoCardano
returns takes it as a second tracer, after the KES agent client tracer.
ThreadNet and db-synthesizer pass nullTracer.

forgeShelleyBlockWithTxs forges a block with the given transactions, so
the Dijkstra forge does not select the ranking-block part twice.
forgeShelleyBlock calls it with the selection of selectBlockTxs.

Two unit tests in shelley-test forge a Dijkstra block. With a non-zero
endorser-block byte capacity, the forge traces one endorser block, built
from the endorser-block part. With a zero byte capacity, it traces
nothing. In both, forgedTxs and the block hold the ranking-block part.

This branch has not been deployed

No deployments
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.

1 participant