Problem
AnyNewEpochState in Cardano.Api.LedgerState carries a ShelleyBasedEra era witness:
data AnyNewEpochState where
AnyNewEpochState
:: ShelleyBasedEra era
-> ShelleyAPI.NewEpochState (ShelleyLedgerEra era)
-> Ledger.LedgerTables ...
-> AnyNewEpochState
Consumers in cardano-testnet that need EraCommonConstraints (via obtainCommonConstraints) must first convert ShelleyBasedEra era to Exp.Era era using sbeToEra. Because Exp.Era only inhabits Conway and Dijkstra, sbeToEra is partial for pre-Conway eras, forcing every call site to use a helper like:
unsafeEraFromSbe :: HasCallStack => ShelleyBasedEra era -> Era era
unsafeEraFromSbe = withFrozenCallStack $
either (error . prettyShow . prettyError) id . sbeToEra
This appears in EpochStateProcessing.hs and is used across multiple modules (Components/Query.hs, Runtime.hs, Process/Cli/SPO.hs). Every pattern match on AnyNewEpochState in testnet code repeats the same partial conversion.
See cardano-node PR #6551 for context and the review discussion that motivated this issue.
Constraint
foldEpochState replays the chain from genesis via a pipelined chain-sync client. The callback receives blocks from all eras (Shelley through Dijkstra), so the existing AnyNewEpochState with ShelleyBasedEra must remain available for consumers that need full-era coverage (e.g. cardano-cli debug log-epoch-state).
Proposal
Add a new existential type in Cardano.Api.Experimental that carries an Era era witness instead of ShelleyBasedEra era:
-- | Like 'AnyNewEpochState' but restricted to experimental (Conway+) eras,
-- so that 'obtainCommonConstraints' is always total.
data AnyNewEpochState where
AnyNewEpochState
:: Era era
-> ShelleyAPI.NewEpochState (ShelleyLedgerEra era)
-> Ledger.LedgerTables ...
-> AnyNewEpochState
Optionally, provide one or both of:
- A conversion function from the legacy type to
Maybe the experimental type (fails for pre-Conway eras).
- A wrapper around
foldEpochState that silently skips pre-Conway blocks and delivers only Exp.AnyNewEpochState to the callback, for consumers that only care about Conway+ governance state.
Migration path
- cardano-testnet: switch to the experimental
AnyNewEpochState. All unsafeEraFromSbe calls and the helper itself can be removed; obtainCommonConstraints works directly on the Era witness.
- cardano-cli (
debug log-epoch-state): continues using the legacy AnyNewEpochState with ShelleyBasedEra, unaffected by this change.
Alternatives considered
- Make
sbeToEra total: not possible without adding pre-Conway constructors to Era, which defeats the purpose of the experimental API.
- Keep the status quo: every new testnet module repeats the same partial conversion boilerplate, and
error calls in test infrastructure are fragile.
Problem
AnyNewEpochStateinCardano.Api.LedgerStatecarries aShelleyBasedEra erawitness:Consumers in
cardano-testnetthat needEraCommonConstraints(viaobtainCommonConstraints) must first convertShelleyBasedEra eratoExp.Era erausingsbeToEra. BecauseExp.Eraonly inhabits Conway and Dijkstra,sbeToErais partial for pre-Conway eras, forcing every call site to use a helper like:This appears in
EpochStateProcessing.hsand is used across multiple modules (Components/Query.hs,Runtime.hs,Process/Cli/SPO.hs). Every pattern match onAnyNewEpochStatein testnet code repeats the same partial conversion.See cardano-node PR #6551 for context and the review discussion that motivated this issue.
Constraint
foldEpochStatereplays the chain from genesis via a pipelined chain-sync client. The callback receives blocks from all eras (Shelley through Dijkstra), so the existingAnyNewEpochStatewithShelleyBasedEramust remain available for consumers that need full-era coverage (e.g.cardano-cli debug log-epoch-state).Proposal
Add a new existential type in
Cardano.Api.Experimentalthat carries anEra erawitness instead ofShelleyBasedEra era:Optionally, provide one or both of:
Maybethe experimental type (fails for pre-Conway eras).foldEpochStatethat silently skips pre-Conway blocks and delivers onlyExp.AnyNewEpochStateto the callback, for consumers that only care about Conway+ governance state.Migration path
AnyNewEpochState. AllunsafeEraFromSbecalls and the helper itself can be removed;obtainCommonConstraintsworks directly on theErawitness.debug log-epoch-state): continues using the legacyAnyNewEpochStatewithShelleyBasedEra, unaffected by this change.Alternatives considered
sbeToEratotal: not possible without adding pre-Conway constructors toEra, which defeats the purpose of the experimental API.errorcalls in test infrastructure are fragile.