-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor witnesses indexing functions to have the indexing logic in one place #697
Refactor witnesses indexing functions to have the indexing logic in one place #697
Conversation
| let proposalsList = toList proposalProcedures | ||
, (ix, proposal) <- zip [0 ..] proposalsList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one was incorrect. proposals from mScriptWitnesses
not present in proposalProcedures
were not added to the list. This issue was fixed in: #602, but it looks like this place was ommited by accident.
4d209e2
to
c50555d
Compare
-> [(ScriptWitnessIndex, TxIn, Witness WitCtxTxIn era)] | ||
txInsToIndexed txins = | ||
[ (ScriptWitnessIndexTxIn ix, txIn, witness) | ||
| (ix, (txIn, BuildTxWith witness)) <- zip [0 ..] $ orderTxIns txins |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will now pattern match on key witnesses because we don't pattern match on ScriptWitness
:
scriptWitnessesTxIns txins =
[ (ScriptWitnessIndexTxIn ix, AnyScriptWitness witness)
| -- The tx ins are indexed in the map order by txid
(ix, (_, BuildTxWith (ScriptWitness _ witness))) <-
zip [0 ..] (orderTxIns txins)
We need to be extremely careful with the refactors here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh good catch. However, I've copied the indexing logic from Fees.hs:1464:
[ (txin, BuildTxWith <$> wit')
| -- The tx ins are indexed in the map order by txid
(ix, (txin, BuildTxWith wit)) <- zip [0 ..] (orderTxIns txins)
, let wit' = case wit of
KeyWitness{} -> Right wit
ScriptWitness ctx witness -> ScriptWitness ctx <$> witness'
where
witness' = substituteExecUnits (ScriptWitnessIndexTxIn ix) witness
]
and there indices were generated for both ScriptWitness
and KeyWitness
!
Which is correct then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
substituteExecutionUnits
would strip TxIn
s with KeyWitnes
es from TxBodyContent
if we index only scripts. That sounds wrong.
But giving script indices to key witnesses does not seem right as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep both: calculate indices for scripts only and update only script inputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so the actual issue is we do need all the indexes but they are not all necessarily ScriptWitnessIndex
es. Some may be witnessed by a key and technically shouldn't have a corresponding ScriptWitnessIndex
. However I'm not seeing a simple way to disambiguate this without making the code unnecessarily complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM I would consider renaming the indexing functions however. We need to do a follow up PR to prevent the abuse of ScriptWitnessIndex
.
-> [(ScriptWitnessIndex, TxIn, Witness WitCtxTxIn era)] | ||
txInsToIndexed txins = | ||
[ (ScriptWitnessIndexTxIn ix, txIn, witness) | ||
| (ix, (txIn, BuildTxWith witness)) <- zip [0 ..] $ orderTxIns txins |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so the actual issue is we do need all the indexes but they are not all necessarily ScriptWitnessIndex
es. Some may be witnessed by a key and technically shouldn't have a corresponding ScriptWitnessIndex
. However I'm not seeing a simple way to disambiguate this without making the code unnecessarily complicated.
@@ -1232,6 +1268,20 @@ deriving instance Eq (TxCertificates build era) | |||
|
|||
deriving instance Show (TxCertificates build era) | |||
|
|||
-- | Index certificates with witnesses by the order they appear in the list (in the transaction). If there | |||
-- are multiple witnesses for the credential, the last one is returned. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are multiple witnesses for the credential, the last one is returned
Can you elaborate on this? What does this mean/why is this ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I am not sure. I've preserved the original logic without thinking much about it:
mapScriptWitnessesCertificates
( TxCertificates
supported
certs
(BuildTxWith witnesses)
) =
let mappedScriptWitnesses
:: [(StakeCredential, Either (TxBodyErrorAutoBalance era) (Witness WitCtxStake era))]
mappedScriptWitnesses =
[ (stakecred, ScriptWitness ctx <$> witness')
| -- The certs are indexed in list order
(ix, cert) <- zip [0 ..] certs
, stakecred <- maybeToList (selectStakeCredentialWitness cert)
, ScriptWitness ctx witness <-
maybeToList (List.lookup stakecred witnesses)
, let witness' = substituteExecUnits (ScriptWitnessIndexCertificate ix) witness
]
in TxCertificates supported certs . BuildTxWith
<$> traverse
( \(sCred, eScriptWitness) ->
case eScriptWitness of
Left e -> Left e
Right wit -> Right (sCred, wit)
)
mappedScriptWitnesses
so this bit:
ScriptWitness ctx witness <- maybeToList (List.lookup stakecred witnesses)
returns only the first witness. The comment mentioning the last witness is wrong.
However:
TxCertificates
:: ShelleyBasedEra era
-> [Certificate era]
-> BuildTxWith build [(StakeCredential, Witness WitCtxStake era)]
-- ^ There can be more than one script witness per stake credential
-> TxCertificates build era
So the fact that we only use the first witness for the stake credential, feels wrong here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it should only matter in fee calculation in estimateTransactionKeyWitnessCount
, because we were counting witnesses there. So if we used only first witness for the stake credential, the fee was underestimated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've included all witnesses for the stake now.
List.lookup stakecred witnesses | ||
scriptWitnessesCertificates txc = | ||
[ (ix, AnyScriptWitness witness) | ||
| (ix, _, _, ScriptWitness _ witness) <- txCertificatesToIndexed txc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's clearer here that we are using the indexing functions in two contexts:
- Script witness execution unit substitution
- Retrieval of script witnesses
I would argue having these as separate functions would make this code clearer. However that is not a task for this PR.
In the case of 1. we keep the key witnesses because we are updating the tx body with the set of all things being witnessed. Because of this we abuse ScriptWitnessIndex
and pair it with things that are actually key witnessed.
-> [(ScriptWitnessIndex, L.ProposalProcedure (ShelleyLedgerEra era), ScriptWitness WitCtxStake era)] | ||
txProposalProceduresToIndexed TxProposalProceduresNone = [] | ||
txProposalProceduresToIndexed txpp@(TxProposalProcedures _ (BuildTxWith witnesses)) = do | ||
let allProposalsList = toList $ convProposalProcedures txpp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC you were suggesting a pattern synonym to prevent users from having to call convProposalProcedures
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I was suggesting:
- Not exporting
TxProposalProcedures
constructors and prefix them withUnsafe
- Add the following unidirectional pattern:
pattern TxProposalProcedures :: OSet (L.ProposalProcedure (ShelleyLedgerEra era)) -> TxProposalProcedures build era
pattern TxProposalProcedures oset <- (convProposalProcedures -> oset)
(export of convProposalProcedures
will be unnecessary)
3. Make usage of mkTxProposalProcedures
the only way to construct the value.
That would make API safer to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, feel free to add it in this PR or a separate one. Be sure to make any breaking changes clear in the next release notes.
0d22df3
to
3f14ab0
Compare
3f14ab0
to
c18d177
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! If you have a branch integrated in cardano-cli, I would ask Martin to run tests related to plutus scripts from the branch to be safe.
Backported to 10.1 and tested in: https://github.com/IntersectMBO/cardano-node-tests/actions/runs/12596557732 |
Changelog
Context
It was annoying to look up how witnesses are indexed: the logic was copied into multiple places, away from the datatypes which store them.
This PR moves them into one place, simplifies witness updating code in
Cardano.Api.Fees
and fixes one place which had wrong witness ordering.Checklist