Skip to content
Open
Changes from all commits
Commits
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
136 changes: 136 additions & 0 deletions docs/ADR-016-Backwards-and-Forwards-Compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@

# ADR-016: Backwards and Forwards Compatibility

**Status:** Proposed

**Date:** 2025-10-07

## Context

We are working on [a new API](./ADR-004-Support-only-for-mainnet-and-upcoming-eras.md) for `cardano-api` that is centred around the `Era era` type. We would like to maintain backwards compatibility with the "old" api under two conditions:

1) Existing exposed functionality that uses the "old" api which may be in use.
2) Any functionality needed to enable QA's ability to test hardforking from Byron to the current era.

## Proposed Solution

The solution proposed below is specific to certificates but can be expanded to any era depdendent type.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
The solution proposed below is specific to certificates but can be expanded to any era depdendent type.
The solution proposed below is specific to certificates but can be expanded to any era dependent type.


Our "old" Certificate definition is as follows:

```haskell
data Certificate era where
-- Pre-Conway
ShelleyRelatedCertificate
:: Typeable era
=> ShelleyToBabbageEra era
-> Ledger.ShelleyTxCert (ShelleyLedgerEra era)
-> Certificate era
-- Conway onwards
ConwayCertificate
:: Typeable era
=> ConwayEraOnwards era
-> Ledger.ConwayTxCert (ShelleyLedgerEra era)
-> Certificate era
```

`Cardano.Api.Certificate` exposes several helper functions that use this "old" type. E.g `makeStakeAddressDelegationCertificate`, `makeStakeAddressRegistrationCertificate` and `makeStakeAddressUnregistrationCertificate` to name a few. All of these "old" functions will be deprecated in favour of a new API that allows us to expose functionality from any era required without adding significant boilerplate as seen in the above definition of `data Certificate era`. We achieve this by the following:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not link to IntersectMBO/cardano-api#962 as an example. The inline example is much better, but still, being able to easily check the other one could help too.



Replace era-variant GADTs (like Certificate era) with a uniform wrapper (Certificate :: L.TxCert era -> Certificate era) and using open type families to resolve era-specific types (e.g., Delegatee era). Era constraints (IsEra, IsShelleyBasedEra) gate access to functionality.
Copy link
Collaborator

@carbolymer carbolymer Oct 15, 2025

Choose a reason for hiding this comment

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

using open type families to resolve era-specific types (e.g., Delegatee era)

The example below uses a closed type family Delegatee era. What's the final choice, and why?


I will make this clearer with an example below.

**NB**: We will only implement functionality constrained by `IsShelleyBasedEra era` and `IsEra era` that either is currently exposed by the "old" api or specifically required by QA. If we do not need backwards compatibility then we will use`Era era` or `IsEra era` without implementing an additional type synonym family.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
**NB**: We will only implement functionality constrained by `IsShelleyBasedEra era` and `IsEra era` that either is currently exposed by the "old" api or specifically required by QA. If we do not need backwards compatibility then we will use`Era era` or `IsEra era` without implementing an additional type synonym family.
**NB**: We will only implement functionality constrained by `IsShelleyBasedEra era` and `IsEra era` that either is currently exposed by the "old" api or specifically required by QA. If we do not need backwards compatibility then we will use `Era era` or `IsEra era` without implementing an additional type synonym family.

There is no space between "use" and "Era era"

Copy link
Collaborator

@carbolymer carbolymer Oct 15, 2025

Choose a reason for hiding this comment

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

Suggestion: use [!NOTE] admonition instead of NB https://github.com/orgs/community/discussions/16925 for better visual separation.



Let's use `makeStakeAddressDelegationCertificate` as an example.

```haskell
data StakeDelegationRequirements era where
StakeDelegationRequirementsConwayOnwards
:: ConwayEraOnwards era
-> StakeCredential
-> Ledger.Delegatee
-> StakeDelegationRequirements era
StakeDelegationRequirementsPreConway
:: ShelleyToBabbageEra era
-> StakeCredential
-> PoolId
-> StakeDelegationRequirements era

makeStakeAddressDelegationCertificate :: StakeDelegationRequirements era -> Certificate era
makeStakeAddressDelegationCertificate = \case
StakeDelegationRequirementsConwayOnwards cOnwards scred delegatee ->
conwayEraOnwardsConstraints cOnwards $
ConwayCertificate cOnwards $
Ledger.mkDelegTxCert (toShelleyStakeCredential scred) delegatee
StakeDelegationRequirementsPreConway atMostBabbage scred pid ->
shelleyToBabbageEraConstraints atMostBabbage $
ShelleyRelatedCertificate atMostBabbage $
Ledger.mkDelegStakeTxCert (toShelleyStakeCredential scred) (unStakePoolKeyHash pid)

-- The `StakeDelegationRequirements era` GADT will necessitate more data constructors if there are any changes to the requirements of stake delegation in future eras.
-- This is already seen in the transition from Babbage (can only delegate to stake pools) -> Conway (can delegate to stake pools and dreps).
-- This obviously does not scale in the face of possible incoming changes and requires the propagation of an additional data constructor.

-- I propose the following:
Comment on lines +73 to +77
Copy link
Collaborator

@palas palas Oct 9, 2025

Choose a reason for hiding this comment

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

These comments could be unquoted. I think that would make it easier to parse


-- Already exists in Cardano.Api.Experimental.Tx.Internal.Certificate
data Certificate era where
Certificate :: L.EraTxCert era => L.TxCert era -> Certificate era

-- Type family definition capturing the change of possible delegatees. We can now delegate to stake pools or dreps or delegate
-- to dreps and stake pools simultaneously. This is captured in the `Ledger.Delegatee` type.
type family Delegatee era where
Delegatee ConwayEra = Ledger.Delegatee
Delegatee BabbageEra = Api.Hash Api.StakePoolKey
Delegatee AlonzoEra = Api.Hash Api.StakePoolKey
...
...
Delegate ShelleyEra = Api.Hash Api.StakePoolKey
Comment on lines +86 to +91
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Delegatee ConwayEra = Ledger.Delegatee
Delegatee BabbageEra = Api.Hash Api.StakePoolKey
Delegatee AlonzoEra = Api.Hash Api.StakePoolKey
...
...
Delegate ShelleyEra = Api.Hash Api.StakePoolKey
Delegatee L.ConwayEra = Ledger.Delegatee
Delegatee L.BabbageEra = Api.Hash Api.StakePoolKey
Delegatee L.AlonzoEra = Api.Hash Api.StakePoolKey
...
...
Delegate L.ShelleyEra = Api.Hash Api.StakePoolKey

Delegatee is a part of the experimental (new) API so I think we should be using ledger eras.


makeStakeAddressDelegationCertificate
:: forall era
. IsEra era
=> StakeCredential
-> Delegatee era
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
-> Delegatee era
-> Delegatee (LedgerEra era)

-> Certificate (LedgerEra era)
makeStakeAddressDelegationCertificate sCred delegatee =
case useEra @era of
e@ConwayEra ->
obtainCommonConstraints e $
Certificate $
Ledger.mkDelegTxCert (Api.toShelleyStakeCredential sCred) delegatee


--- BELOW IS IN A SEPARATE "COMPATIBLE" MODULE DESIGNED FOR BACKWARDS COMPATIBILITY.
Copy link
Collaborator

@palas palas Oct 9, 2025

Choose a reason for hiding this comment

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

This already implies that the function above is not backwards compatible, but I would say it explicitly, so that the difference is more obvious to the reader. I would even explicitly say that IsEra constrains to the latest two eras while IsShelleyBasedEra doesn't and thus provides backwards compatibility.
Also both versions could be in two separate Haskell code blocks.


makeStakeAddressDelegationCertificate
:: forall era
. IsShelleyBasedEra era
=> StakeCredential
-> Delegatee era
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
-> Delegatee era
-> Delegatee (LedgerEra era)

-> Certificate (LedgerEra era)
makeStakeAddressDelegationCertificate sCred delegatee =
case shelleyBasedEra @era of
...

-- All functions using the old Certificate era GADT will be marked {-# DEPRECATED #-}

```

We remove entirely the need to define a type like `StakeDelegationRequirements era`. The type synonym family is now responsible for resolving the era dependent type.

## Decision

The ADR gets adopted in `cardano-api`.

## Consequences

- An additional type synonym family per era dependent type.
- Removal of all the "old" api's data definitions that capture the notion of backwards compatibility.
- Removal of all helper functions that use these data definitions described in the aforementioned point.
- Implementation of new equivalent helper functions that utilize a type synonym family for the era dependent type.