Skip to content

Add HasCallStack constraints to functions containing error calls#1175

Merged
Jimbo4350 merged 7 commits intomasterfrom
add-has-callstack-constraints
Apr 20, 2026
Merged

Add HasCallStack constraints to functions containing error calls#1175
Jimbo4350 merged 7 commits intomasterfrom
add-has-callstack-constraints

Conversation

@Jimbo4350
Copy link
Copy Markdown
Contributor

@Jimbo4350 Jimbo4350 commented Apr 2, 2026

Changelog

- description: |
    Add HasCallStack constraints to standalone functions that call error
    directly or indirectly, and propagate to their callers. Dijkstra-era
    placeholder error messages are also improved.
  type:
   - compatible
  projects:
   - cardano-api

Context

Closes #809

Phase 1: HasCallStack at error sites

Each placement is justified by a real error call in the function body. We add HasCallStack rather than replacing error with proper error handling for the reasons noted:

Function File Error source Why HasCallStack rather than replacing error
generateInsecureSigningKey Key/Internal/Class.hs Direct error on deserialization failure Genuinely impossible — random bytes that don't deserialize as a valid signing key indicate a broken crypto implementation, not a recoverable condition
foldBlocks LedgerState.hs error "Impossible! Missing Ledger state" in nested chain sync client Return type is constrained by the ouroboros-network protocol client callback interface — can't change to Either without rearchitecting the protocol client
chainSyncClientWithLedgerState LedgerState.hs error "Impossible! History should always be non-empty" Same — deep inside a protocol handler whose type is dictated by ouroboros-network
chainSyncClientPipelinedWithLedgerState LedgerState.hs 2× same as above Pipelined variant, same constraint
foldEpochState LedgerState.hs error "Impossible! Missing Ledger state" in nested function Same — protocol callback interface
fromShelleyNetwork Network/Internal/NetworkId.hs error on wrong mainnet network magic Could return Either, but it's called from fromShelleyGenesis and other pure conversion functions — changing the return type would ripple through the genesis/network conversion chain
toConsensusQueryShelleyBased Query/Internal/Type/QueryInMode.hs 4× direct error for wrong-era queries Could return Either, but feeds into the consensus query pipeline which expects a pure value — would require reworking the query infrastructure
getTxIdByron Tx/Internal/Body.hs fromMaybe impossible (hash size mismatch) Genuinely impossible — hash size is fixed by the crypto algorithm
makeShelleySignature Tx/Internal/Sign.hs fromMaybe impossible (signature size mismatch) Genuinely impossible — signature size is fixed by the crypto algorithm

Phase 2: Propagation to callers

After adding HasCallStack to the error sites, it was propagated up the call chain so stack traces show the full path rather than stopping at the immediate error site. Each line below is a call chain — read as "calls". The rightmost function is where the error lives; each function to its left gets HasCallStack so its frame appears in the stack trace.

Propagation from Phase 1 error sites

Tx signing (all paths end at makeShelleySignature):

makeShelleyKeyWitness       → makeShelleyKeyWitness'            → makeShelleySignature
makeShelleyBootstrapWitness → makeShelleyBasedBootstrapWitness  → makeShelleySignature
makeKeyWitness              → makeShelleySignature
issueOperationalCertificate → makeShelleySignature

Query:

toConsensusQuery → toConsensusQueryShelleyBased

Genesis:

fromShelleyGenesis → fromShelleyNetwork

The remaining Phase 1 error sites (generateInsecureSigningKey, foldBlocks, chainSyncClientWithLedgerState, chainSyncClientPipelinedWithLedgerState, foldEpochState, getTxIdByron) are top-level entry points with no internal callers in this repo to propagate to.

Propagation from pre-existing master constraints

These functions call downstream functions that already carried HasCallStack on master but were themselves missing the constraint:

Tx body construction & output (callees pre-existing on master: mkCommonTxBody, toShelleyTxOut, toShelleyTxOutAny, createTransactionBody):

createAndValidateTransactionBody → makeShelleyTransactionBody → (pre-existing constrained callees)
fromLegacyTxOut                                               → (pre-existing constrained callees)
toShelleyUTxO                                                 → (pre-existing constrained callees)

Fee calculation (callees pre-existing on master: balanceTxOuts, makeTransactionBodyAutoBalance, estimateBalancedTxBody, calculateMinimumUTxO):

calcMinFeeRecursive               → (pre-existing constrained callees)
estimateOrCalculateBalancedTxBody → (pre-existing constrained callees)
constructBalancedTx               → (pre-existing constrained callees)

Error message improvements (no HasCallStack)

Dijkstra-era placeholder error calls had unhelpful messages (error "dijkstra", error ""). These are replaced with descriptive messages naming the function and the reason, e.g. error "getPlutusDatum: Dijkstra era not supported".

What was excluded and why

Dijkstra-era placeholders (~20 calls across Era/Internal/Case.hs, Eon/*.hs, etc.): All temporary error calls that will be replaced with real implementations when Dijkstra support lands. Adding HasCallStack would create churn across many signatures that will be rewritten anyway.

Class method instances (castVerificationKey, deterministicSigningKey): The failing cases guard impossible code paths (e.g. ed25519-bip32 extended key byte length can never mismatch ed25519). HasCallStack on instance methods via InstanceSigs doesn't propagate the caller's stack through class dispatch, and putting it on the class method would force the constraint on all instances including those that can never fail.

Certificate validation functions (toShelleyPoolParams, toShelleyDnsName, toShelleyUrl, fromShelleyPoolMetadata): These have real error calls for validation failures, but they carry TODO: proper validation comments — they should be converted to proper error handling rather than patched with HasCallStack.

How to trust this PR

  • Every HasCallStack placement is justified by a real error call in the function body — see table above
  • Propagation chains are visualized above so the "caller → callee" relationship is auditable
  • Only standalone functions are constrained; class methods are left alone
  • Dijkstra placeholder messages are improved but not constrained
  • Builds clean with -Wredundant-constraints — every constraint is actually used

Checklist

  • Commit sequence broadly makes sense and commits have useful messages
  • New tests are added if needed and existing tests are updated
  • Self-reviewed the diff

@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch from 4e83260 to 77d1ec0 Compare April 7, 2026 17:54
@Jimbo4350 Jimbo4350 changed the title Add HasCallStack constraints to error call sites Add HasCallStack constraints to functions containing error calls Apr 7, 2026
@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch 2 times, most recently from 5b44c18 to 3082fdd Compare April 7, 2026 18:09
@Jimbo4350 Jimbo4350 marked this pull request as ready for review April 7, 2026 18:53
Copilot AI review requested due to automatic review settings April 7, 2026 18:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves runtime debuggability by adding HasCallStack constraints to selected standalone functions that can reach error, and by replacing placeholder “Dijkstra”/empty error messages with more descriptive ones across a few era-bridging/conversion helpers.

Changes:

  • Add HasCallStack constraints to a small set of functions so call stacks include their callers when error is triggered.
  • Improve several Dijkstra-era placeholder error messages to include the function name and a clearer reason.
  • Add GHC.Stack (HasCallStack) imports where newly required.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
cardano-api/src/Cardano/Api/Tx/Internal/Sign.hs Adds HasCallStack to makeShelleySignature and imports HasCallStack.
cardano-api/src/Cardano/Api/Tx/Internal/Body.hs Adds HasCallStack constraint to getTxIdByron for better stacks on the “impossible” hash-size path.
cardano-api/src/Cardano/Api/Query/Internal/Type/QueryInMode.hs Adds HasCallStack to toConsensusQueryShelleyBased to improve stacks for wrong-era query errors.
cardano-api/src/Cardano/Api/Plutus/Internal/Script.hs Replaces empty/placeholder error messages with descriptive “Dijkstra era not supported” messages.
cardano-api/src/Cardano/Api/Network/Internal/NetworkId.hs Adds HasCallStack to fromShelleyNetwork and imports HasCallStack.
cardano-api/src/Cardano/Api/LedgerState.hs Adds HasCallStack to chain-sync/ledger-state folding entrypoints and imports HasCallStack.
cardano-api/src/Cardano/Api/Key/Internal/Class.hs Adds HasCallStack to generateInsecureSigningKey and imports HasCallStack.
cardano-api/src/Cardano/Api/Experimental/Tx/Internal/AnyWitness.hs Improves a Dijkstra placeholder error message in getPlutusDatum.
cardano-api/src/Cardano/Api/Certificate/Internal.hs Improves Dijkstra placeholder error messages in getAnchorDataFromCertificate.

@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch from 3082fdd to ac36214 Compare April 9, 2026 12:25
@carbolymer
Copy link
Copy Markdown
Contributor

Closes #1175

Wrong link

Copy link
Copy Markdown
Contributor

@carbolymer carbolymer left a comment

Choose a reason for hiding this comment

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

That's a good direction, but I think we can do better.

  1. It would be nice to enhance all the callers of the modified functions here with HasCallstack for complete call stacks.

  2. Currently all errors for dijkstra are not following any convention. It would be better to update all of them to follow the same pattern so they're easier to find. Here's what exists across ~26 Dijkstra placeholder errors:

    Three patterns in use

    1. Descriptive (majority, ~22 instances) -- already follows "functionName: Dijkstra era not supported":
    error "caseByronOrShelleyBasedEra: DijkstraEra is not supported"     -- Era/Internal/Case.hs
    error "shelleyBasedEraConstraints: Dijkstra is not yet supported"    -- Eon/ShelleyBasedEra.hs
    error "makeShelleyTransactionBody: Dijkstra is not  supported"       -- Tx/Internal/Body.hs (note: double space typo)
    error "estimateBalancedTxBody: DijkstraEra is not supported for fee estimation"  -- Fee.hs
    1. Bare placeholders (4 instances) -- the ones the PR fixes:
    error "dijkstra"   -- 3 places
    error ""           -- 1 place (fromShelleyMultiSig)
    1. TODO-prefixed error (1 instance):
    error "TODO: makeStakeAddressDelegationCertificate DijkstraEra"  -- Certificate/Compatible.hs:74

Copy link
Copy Markdown
Contributor

@palas palas left a comment

Choose a reason for hiding this comment

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

Nothing controversial 👍

@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch 3 times, most recently from 8f67488 to 6d531d5 Compare April 16, 2026 15:59
@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch 2 times, most recently from 264534d to 53c7673 Compare April 17, 2026 14:09
kind:
- compatible
description: |
Add HasCallStack constraints to standalone functions that call error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

line breaks will be carried over to changelog. You should tell your Claude that's 2026 and we're not using 80 character line length limit anymore.

Idk why mine was insisting on doing that too...

Copy link
Copy Markdown
Contributor

@carbolymer carbolymer left a comment

Choose a reason for hiding this comment

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

LGTM. Do you think we could address the other remaining error stubs (in other cardano-api code) for Dijkstra era in a follow up PR?

kind:
- compatible
description: |
Add HasCallStack constraints to standalone functions that call error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
Add HasCallStack constraints to standalone functions that call error
Add `HasCallStack` constraints to standalone functions that call error

@Jimbo4350
Copy link
Copy Markdown
Contributor Author

LGTM. Do you think we could address the other remaining error stubs (in other cardano-api code) for Dijkstra era in a follow up PR?

Can you elaborate? Address in what sense?

@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch from 53c7673 to db74111 Compare April 20, 2026 12:10
Add HasCallStack constraints to standalone functions that call error
directly or indirectly. Improve bare Dijkstra-era placeholder error
messages to follow the convention: functionName: Dijkstra era not supported.
Add HasCallStack constraints to callers of makeShelleySignature so that
stack traces show the full signing call chain:
makeShelleyKeyWitness, makeShelleyKeyWitness', makeShelleyBootstrapWitness,
makeShelleyBasedBootstrapWitness, makeKeyWitness, issueOperationalCertificate.
Add HasCallStack to toConsensusQuery so it propagates the stack trace
from toConsensusQueryShelleyBased's error calls for wrong-era queries.
Add HasCallStack to fromShelleyGenesis so it propagates the stack trace
from fromShelleyNetwork's error call on wrong mainnet network magic.
Add HasCallStack to makeShelleyTransactionBody, createAndValidateTransactionBody,
fromLegacyTxOut, and toShelleyUTxO, which call functions like mkCommonTxBody,
toShelleyTxOutAny, toShelleyTxOut, and createTransactionBody that already
carry the constraint.
Add HasCallStack to calcMinFeeRecursive, estimateOrCalculateBalancedTxBody,
and constructBalancedTx, which call balanceTxOuts, makeTransactionBodyAutoBalance,
estimateBalancedTxBody, and calculateMinimumUTxO that already carry the constraint.
@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch from db74111 to f50ea7e Compare April 20, 2026 12:22
@carbolymer
Copy link
Copy Markdown
Contributor

@Jimbo4350 There are different error messages in error stubs, which are harder to find. Making them uniform would make they more greppable:

Pattern A - error "Dijkstra era is not active yet" (12 hits, identical message):

  • OrphanInstances/Shelley.hs:482,487,492,497,502,507 - 6x ToObject instances
  • Tracing/Era/Shelley.hs:1224,1229,1234,1239,1244,1249 - 6x LogFormatting instances

Pattern B - -- TODO dijkstra: (1 hit):

  • Blockfrost.hs:283 - comment about missing blockfrost params

Pattern C - -- TODO: with dijkstra mentioned in the text (1 hit):

  • Shutdown.hs:133 - comment about cardano-cli genesis create

The main problem: patterns A, B, and C use three different conventions. grep 'TODO.*dijkstra' misses all 12 error stubs. grep 'Dijkstra era is not active' misses the two TODO comments. No single grep
catches all 14.

Unifying them all under e.g. TODO Dijkstra in either the error string or an adjacent comment would make grep -r 'TODO Dijkstra' a one-stop search.

@Jimbo4350 Jimbo4350 force-pushed the add-has-callstack-constraints branch from f50ea7e to 9f7b1df Compare April 20, 2026 12:54
@Jimbo4350 Jimbo4350 enabled auto-merge April 20, 2026 13:39
@Jimbo4350
Copy link
Copy Markdown
Contributor Author

@Jimbo4350 There are different error messages in error stubs, which are harder to find. Making them uniform would make they more greppable:

Pattern A - error "Dijkstra era is not active yet" (12 hits, identical message):

* OrphanInstances/Shelley.hs:482,487,492,497,502,507 - 6x ToObject instances

* Tracing/Era/Shelley.hs:1224,1229,1234,1239,1244,1249 - 6x LogFormatting instances

Pattern B - -- TODO dijkstra: (1 hit):

* Blockfrost.hs:283 - comment about missing blockfrost params

Pattern C - -- TODO: with dijkstra mentioned in the text (1 hit):

* Shutdown.hs:133 - comment about cardano-cli genesis create

The main problem: patterns A, B, and C use three different conventions. grep 'TODO.*dijkstra' misses all 12 error stubs. grep 'Dijkstra era is not active' misses the two TODO comments. No single grep catches all 14.

Unifying them all under e.g. TODO Dijkstra in either the error string or an adjacent comment would make grep -r 'TODO Dijkstra' a one-stop search.

Will open a follow up PR

@Jimbo4350 Jimbo4350 added this pull request to the merge queue Apr 20, 2026
Merged via the queue into master with commit b545f50 Apr 20, 2026
31 of 33 checks passed
@Jimbo4350 Jimbo4350 deleted the add-has-callstack-constraints branch April 20, 2026 14:02
@Jimbo4350
Copy link
Copy Markdown
Contributor Author

Thanks @carbolymer — good catch. The four files you cite are in cardano-node, not cardano-api, so I'll track them over there separately.

For cardano-api itself I audited the same thing and found 27 error stubs mentioning Dijkstra across 5+ phrasings plus 4 inconsistent -- TODO comments — none shared a single greppable token. #1187 unifies 30 of them under TODO Dijkstra:. After that PR, grep -r 'TODO Dijkstra' cardano-api/src returns every Dijkstra-era placeholder in one shot.

Two sites were intentionally excluded from the cardano-api unification, both documented in #1187's description:

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.

[FR] - Add 'HasCallStack' constraints

4 participants