Skip to content
Draft
Changes from 2 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
22 changes: 20 additions & 2 deletions cardano-api/src/Cardano/Api/Experimental/Tx/Internal/Fee.hs
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,13 @@ instance Error FeeCalculationError where
-- computed minimum fee and the function recurses.
--
-- A maximum iteration limit (currently 50) guards against non-termination.
-- In practice convergence occurs within 2–3 iterations.
-- In practice convergence occurs within 2–3 iterations. If the limit is
-- reached, the fee currently in the transaction body is checked: if it is
-- at least the ledger-computed minimum fee (@txBodyFee >= minFee@) the
-- current transaction is returned – the fee is considered /reasonable/
-- even though strict convergence was not confirmed. Only when the fee
-- falls below the ledger minimum is 'FeeCalculationDidNotConverge'
-- returned.
calcMinFeeRecursive
:: forall era
. IsEra era
Expand Down Expand Up @@ -790,7 +796,19 @@ calcMinFeeRecursive changeAddr unsignedTx utxo pparams poolids stakeDelegDeposit
-> Map (Ledger.Credential Ledger.DRepRole) L.Coin
-> Int
-> Either FeeCalculationError (UnsignedTx (LedgerEra era))
go 0 _ _ _ _ _ _ _ = Left FeeCalculationDidNotConverge
-- When the iteration limit is reached, accept the current transaction if its
-- fee is at least the ledger-computed minimum (i.e. the fee is "reasonable"):
-- the result is still useful even though strict convergence was not confirmed.
-- Only fail when the fee would be below the ledger minimum.
go 0 unSignTx@(UnsignedTx ledgerTx) utxo' pparams' _ _ _ nExtraWitnesses'
| txBodyFee >= minFee = do

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.

@copilot figure out an upper bound for the fee too!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7a77450. The full "reasonable fee" bounds are now minFee <= txBodyFee <= 2 * minFee:

  • Lower bound (>= minFee): ensures the ledger will accept the fee.
  • Upper bound (<= 2 * minFee): rules out a fee so far above the minimum that it likely indicates a calculation error rather than ordinary oscillation.

In practice, at the iteration limit the fee was set to a prior ledger-computed minimum and will be within a tiny fraction of the current minimum (oscillation stems from a few bytes of CBOR encoding difference between iterations), so both bounds will be satisfied.

let outs = toList $ ledgerTx ^. L.bodyTxL . L.outputsTxBodyL
mapM_ (checkOutputMinUTxO pparams') outs
return unSignTx
| otherwise = Left FeeCalculationDidNotConverge
where
minFee = obtainCommonConstraints (useEra @era) $ L.calcMinFeeTx utxo' pparams' ledgerTx nExtraWitnesses'
txBodyFee = ledgerTx ^. L.bodyTxL . L.feeTxBodyL
go n unSignTx@(UnsignedTx ledgerTx) utxo' pparams' poolids' stakeDelegDeposits' drepDelegDeposits' nExtraWitnesses'
| minFee == txBodyFee && L.isZero txBalanceValue = do
-- Case 1
Expand Down