Skip to content
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

Replace Set TxIn with [TxIn] to fix StateMachine.getInput #594

Merged
5 commits merged into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import GHC.Generics (Generic)
import Ledger (Address (..), TxOut (..), TxOutRef)
import Ledger.Credential (Credential)
import Ledger.TxId (TxId)
import Plutus.ChainIndex.Tx (ChainIndexTx (..), citxData, citxRedeemers, citxScripts, citxTxId, txOutsWithRef)
import Plutus.ChainIndex.Tx (ChainIndexTx (..), citxData, citxScripts, citxTxId, txOutsWithRef, txRedeemersWithHash)
import Plutus.ChainIndex.Types (Diagnostics (..))
import Plutus.V1.Ledger.Ada qualified as Ada
import Plutus.V1.Ledger.Api (Datum, DatumHash, Redeemer, RedeemerHash)
Expand Down Expand Up @@ -139,7 +139,7 @@ fromTx tx =
{ _DataMap = view citxData tx
, _ScriptMap = view citxScripts tx
, _TxMap = Map.singleton (view citxTxId tx) tx
, _RedeemerMap = view citxRedeemers tx
, _RedeemerMap = txRedeemersWithHash tx
, _AddressMap = txCredentialMap tx
, _AssetClassMap = txAssetClassMap tx
}
Expand Down
2 changes: 1 addition & 1 deletion plutus-chain-index-core/src/Plutus/ChainIndex/Handlers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ fromTx :: ChainIndexTx -> Db InsertRows
fromTx tx = mempty
{ datumRows = fromMap citxData
, scriptRows = fromMap citxScripts
, redeemerRows = fromMap citxRedeemers
, redeemerRows = fromPairs (Map.toList . txRedeemersWithHash)
, txRows = InsertRows [toDbValue (_citxTxId tx, tx)]
, addressRows = fromPairs (fmap credential . txOutsWithRef)
, assetClassRows = fromPairs (concatMap assetClasses . txOutsWithRef)
Expand Down
31 changes: 20 additions & 11 deletions plutus-chain-index-core/src/Plutus/ChainIndex/Tx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Plutus.ChainIndex.Tx(
, txOutsWithRef
, txOutRefMap
, txOutRefMapForAddr
, txRedeemersWithHash
-- ** Lenses
, citxTxId
, citxInputs
Expand All @@ -38,13 +39,13 @@ import Data.Set qualified as Set
import Data.Tuple (swap)
import Ledger (Address, OnChainTx (..), SomeCardanoApiTx (SomeTx), Tx (..), TxIn (..), TxInType (..),
TxOut (txOutAddress), TxOutRef (..), onCardanoTx, txId)
import Plutus.ChainIndex.Types
import Plutus.Contract.CardanoAPI (fromCardanoTx, setValidity)
import Plutus.Script.Utils.V1.Scripts (datumHash, mintingPolicyHash, redeemerHash, validatorHash)
import Plutus.V1.Ledger.Scripts (Datum, DatumHash, MintingPolicy (getMintingPolicy),
MintingPolicyHash (MintingPolicyHash), Redeemer (..), RedeemerHash, Script,
ScriptHash (..), Validator (getValidator), ValidatorHash (ValidatorHash))

import Plutus.ChainIndex.Types
MintingPolicyHash (MintingPolicyHash), Redeemer, RedeemerHash, Script, ScriptHash (..),
Validator (getValidator), ValidatorHash (ValidatorHash))
import Plutus.V1.Ledger.Tx (RedeemerPtr (RedeemerPtr), Redeemers, ScriptTag (Spend))

-- | Get tx output references from tx.
txOutRefs :: ChainIndexTx -> [TxOutRef]
Expand Down Expand Up @@ -78,7 +79,7 @@ fromOnChainTx = \case
let (validatorHashes, otherDataHashes, redeemers) = validators txInputs in
ChainIndexTx
{ _citxTxId = txId tx
, _citxInputs = txInputs
, _citxInputs = Set.toList txInputs
Comment on lines -81 to +82
Copy link
Author

Choose a reason for hiding this comment

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

This is incorrect but to fix it we need to fix the Tx from plutus-core. Or create our own Tx with [TxIn] instead of Set TxIn.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a TODO and a story for this? I guess it will need to be done once next-node is merged in main.

Copy link
Contributor

Choose a reason for hiding this comment

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

No need imho, this is good enough.

, _citxOutputs = ValidTx txOutputs
, _citxValidRange = txValidRange
, _citxData = txData <> otherDataHashes
Expand All @@ -94,7 +95,7 @@ fromOnChainTx = \case
let (validatorHashes, otherDataHashes, redeemers) = validators txInputs in
ChainIndexTx
{ _citxTxId = txId tx
, _citxInputs = txCollateral
, _citxInputs = Set.toList txCollateral
, _citxOutputs = InvalidTx
, _citxValidRange = txValidRange
, _citxData = txData <> otherDataHashes
Expand All @@ -117,13 +118,21 @@ mintingPolicies = Map.fromList . fmap withHash . Set.toList
withHash mp = let (MintingPolicyHash mph) = mintingPolicyHash mp
in (ScriptHash mph, getMintingPolicy mp)

validators :: Set TxIn -> (Map ScriptHash Script, Map DatumHash Datum, Map RedeemerHash Redeemer)
validators = foldMap (maybe mempty withHash . txInType) . Set.toList
validators :: Set TxIn -> (Map ScriptHash Script, Map DatumHash Datum, Redeemers)
validators txIns = foldMap (\(ix, txIn) -> maybe mempty (withHash ix) $ txInType txIn) $ zip [0..] (Set.toList txIns)
where
withHash (ConsumeScriptAddress val red dat) =
-- TODO: the index of the txin is probably incorrect as we take it from the set.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this going to have bad consequences?

Copy link
Author

Choose a reason for hiding this comment

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

My understanding is that this PR makes a move from a completely broken state to a semi-broken state. Instead of not getting redeemers at all, we will start choosing them but with a big chance of selecting the incorrect ones.

Copy link
Author

@ghost ghost Jul 14, 2022

Choose a reason for hiding this comment

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

Speaking of fromOnChainTx in particular, I think withHash function was useless already since we use the Tx here, and the inputs have an empty TxInType here as I understand. And we might skip the ConsumeScriptAddress case at all always return mempty.

Copy link
Contributor

Choose a reason for hiding this comment

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

As long as the redeemer pointer points to the right input, I think this is totally fine.

To show that there is a relation between the list and the indexes, I think it's better to do the Set.toList once and pass the list to validators.

Copy link
Author

Choose a reason for hiding this comment

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

As long as the redeemer pointer points to the right input, I think this is totally fine.

There is a problem here that we take a set that could break the order. I looked at usages of the fromOnChainTx and there is no anything that directly relies on that order. But would be nice to fix Tx type.

-- To determine the proper index we have to convert the plutus's `TxIn` to cardano-api `TxIn` and
-- sort them by using the standard `Ord` instance.
withHash ix (ConsumeScriptAddress val red dat) =
let (ValidatorHash vh) = validatorHash val
in ( Map.singleton (ScriptHash vh) (getValidator val)
, Map.singleton (datumHash dat) dat
, Map.singleton (redeemerHash red) red
, Map.singleton (RedeemerPtr Spend ix) red
)
withHash _ = mempty
withHash _ _ = mempty

txRedeemersWithHash :: ChainIndexTx -> Map RedeemerHash Redeemer
txRedeemersWithHash ChainIndexTx{_citxRedeemers} = Map.fromList
$ fmap (\r -> (redeemerHash r, r))
$ Map.elems _citxRedeemers
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fromTx tx =
{ _tobUnspentOutputs = Set.fromList $ fmap snd $ txOutsWithRef tx
, _tobSpentOutputs =
Map.fromSet (const $ view citxTxId tx)
$ Set.mapMonotonic txInRef (view citxInputs tx)
$ Set.fromList $ map txInRef (view citxInputs tx)
}

-- | Whether a 'TxOutRef' is a member of the UTXO set (ie. unspent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fromTx :: ChainIndexTx -> TxUtxoBalance
fromTx tx =
TxUtxoBalance
{ _tubUnspentOutputs = Set.fromList $ fmap snd $ txOutsWithRef tx
, _tubUnmatchedSpentInputs = Set.mapMonotonic txInRef (view citxInputs tx)
, _tubUnmatchedSpentInputs = Set.fromList $ map txInRef (view citxInputs tx)
}

-- | Whether a 'TxOutRef' is a member of the UTXO set (ie. unspent)
Expand Down
11 changes: 7 additions & 4 deletions plutus-chain-index-core/src/Plutus/ChainIndex/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import Ledger.Blockchain qualified as Ledger
import Ledger.Slot (Slot)
import Ledger.TxId (TxId)
import Plutus.V1.Ledger.Scripts (Datum, DatumHash, Redeemer, RedeemerHash, Script, ScriptHash)
import Plutus.V1.Ledger.Tx (Redeemers)
import PlutusTx.Lattice (MeetSemiLattice (..))
import Prettyprinter
import Prettyprinter.Extras (PrettyShow (..))
Expand All @@ -89,18 +90,20 @@ data ChainIndexTxOutputs =

makePrisms ''ChainIndexTxOutputs

newtype RedeemerIx = RedeemerIx { getRedeemerIx :: Integer }

data ChainIndexTx = ChainIndexTx {
_citxTxId :: TxId,
-- ^ The id of this transaction.
_citxInputs :: Set TxIn,
_citxInputs :: [TxIn],
-- ^ The inputs to this transaction.
_citxOutputs :: ChainIndexTxOutputs,
-- ^ The outputs of this transaction, ordered so they can be referenced by index.
_citxValidRange :: !SlotRange,
-- ^ The 'SlotRange' during which this transaction may be validated.
_citxData :: Map DatumHash Datum,
-- ^ Datum objects recorded on this transaction.
_citxRedeemers :: Map RedeemerHash Redeemer,
_citxRedeemers :: Redeemers,
-- ^ Redeemers of the minting scripts.
_citxScripts :: Map ScriptHash Script,
-- ^ The scripts (validator, stake validator or minting) part of cardano tx.
Expand All @@ -116,7 +119,7 @@ makeLenses ''ChainIndexTx
instance Pretty ChainIndexTx where
pretty ChainIndexTx{_citxTxId, _citxInputs, _citxOutputs = ValidTx outputs, _citxValidRange, _citxData, _citxRedeemers, _citxScripts} =
let lines' =
[ hang 2 (vsep ("inputs:" : fmap pretty (Set.toList _citxInputs)))
[ hang 2 (vsep ("inputs:" : fmap pretty _citxInputs))
, hang 2 (vsep ("outputs:" : fmap pretty outputs))
, hang 2 (vsep ("scripts hashes:": fmap (pretty . fst) (Map.toList _citxScripts)))
, "validity range:" <+> viaShow _citxValidRange
Expand All @@ -126,7 +129,7 @@ instance Pretty ChainIndexTx where
in nest 2 $ vsep ["Valid tx" <+> pretty _citxTxId <> colon, braces (vsep lines')]
pretty ChainIndexTx{_citxTxId, _citxInputs, _citxOutputs = InvalidTx, _citxValidRange, _citxData, _citxRedeemers, _citxScripts} =
let lines' =
[ hang 2 (vsep ("inputs:" : fmap pretty (Set.toList _citxInputs)))
[ hang 2 (vsep ("inputs:" : fmap pretty _citxInputs))
, hang 2 (vsep ["no outputs:"])
, hang 2 (vsep ("scripts hashes:": fmap (pretty . fst) (Map.toList _citxScripts)))
, "validity range:" <+> viaShow _citxValidRange
Expand Down
7 changes: 5 additions & 2 deletions plutus-chain-index-core/src/Plutus/Contract/CardanoAPI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module Plutus.Contract.CardanoAPI(

import Cardano.Api qualified as C
import Cardano.Api.Shelley qualified as C
import Data.List (sort)
import Data.Set qualified as Set
import Ledger qualified as P
import Ledger.Tx.CardanoAPI as Export
Expand All @@ -37,7 +38,9 @@ fromCardanoTx eraInMode tx@(C.Tx txBody@(C.TxBody C.TxBodyContent{..}) _) = do
let scriptMap = plutusScriptsFromTxBody txBody
isTxScriptValid = fromTxScriptValidity txScriptValidity
(datums, redeemers) = scriptDataFromCardanoTxBody txBody
inputs =
-- We need to sort the inputs as the order is important
-- to find the corresponding redeemers
inputs = sort $
Comment on lines -40 to +42
Copy link
Author

Choose a reason for hiding this comment

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

Here is the correct order of inputs as we sort cardano-api's TxIn with an indented Ord instance.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch, I would have thought they would already be in the right order!

if isTxScriptValid
then fst <$> txIns
else case txInsCollateral of
Expand All @@ -48,7 +51,7 @@ fromCardanoTx eraInMode tx@(C.Tx txBody@(C.TxBody C.TxBodyContent{..}) _) = do
{ _citxTxId = fromCardanoTxId (C.getTxId txBody)
, _citxValidRange = fromCardanoValidityRange txValidityRange
-- If the transaction is invalid, we use collateral inputs
, _citxInputs = Set.fromList $ fmap ((`P.TxIn` Nothing) . fromCardanoTxIn) inputs
, _citxInputs = fmap ((`P.TxIn` Nothing) . fromCardanoTxIn) inputs
-- No outputs if the one of scripts failed
, _citxOutputs = if isTxScriptValid then ValidTx txOutputs
else InvalidTx
Expand Down
8 changes: 4 additions & 4 deletions plutus-contract/src/Plutus/Contract/StateMachine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import Data.Aeson (FromJSON, ToJSON)
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe (listToMaybe, mapMaybe)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Void (Void, absurd)
Expand All @@ -77,7 +76,7 @@ import Ledger.Constraints.TxConstraints (ScriptInputConstraint (ScriptInputConst
import Ledger.Tx qualified as Tx
import Ledger.Typed.Scripts qualified as Scripts
import Ledger.Value qualified as Value
import Plutus.ChainIndex (ChainIndexTx (_citxInputs))
import Plutus.ChainIndex (ChainIndexTx (_citxInputs, _citxRedeemers))
import Plutus.Contract (AsContractError (_ConstraintResolutionContractError, _ContractError), Contract, ContractError,
Promise, adjustUnbalancedTx, awaitPromise, isSlot, isTime, logWarn, mapError, never,
ownFirstPaymentPubKeyHash, ownUtxos, promiseBind, select, submitTxConfirmed, utxoIsProduced,
Expand Down Expand Up @@ -128,8 +127,9 @@ getInput ::
-> ChainIndexTx
-> Maybe i
getInput outRef tx = do
(_validator, Ledger.Redeemer r, _) <-
listToMaybe $ mapMaybe Tx.inScripts $ filter (\Tx.TxIn{Tx.txInRef} -> outRef == txInRef) $ Set.toList $ _citxInputs tx
-- We retrieve the correspondent redeemer according to the index of txIn in the list
let findRedeemer (ix, _) = Map.lookup (Tx.RedeemerPtr Tx.Spend ix) (_citxRedeemers tx)
Ledger.Redeemer r <- listToMaybe $ mapMaybe findRedeemer $ filter (\(_, Tx.TxIn{Tx.txInRef}) -> outRef == txInRef) $ zip [0..] $ _citxInputs tx
Copy link
Author

Choose a reason for hiding this comment

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

Here is where we use the knowledge of inputs order to get the redeemer. I hope I got the idea correctly that index of TxIn is binded to the redeemer pointer.

Source: IntersectMBO/cardano-node#3389 (comment)

Maybe instead of relying on their order it's better to include the index. Set TxIn ~> Set (TxInIndx, TxIn), that would make it more explicit.

Copy link
Contributor

Choose a reason for hiding this comment

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

The fact that we now have a list instead of a set makes it clear enough that the order is significant in my opinion.

PlutusTx.fromBuiltinData r

getStates
Expand Down
4 changes: 2 additions & 2 deletions plutus-contract/src/Plutus/Trace/Emulator/ContractInstance.hs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ updateTxOutStatus txns = do
-- Check whether the contract instance is waiting for a status change of a
-- transaction output of any of the new transactions. If that is the case,
-- call 'addResponse' to sent the response.
let getSpentOutputs = Set.toList . Set.map txInRef . view citxInputs
let getSpentOutputs = map txInRef . view citxInputs
-- If the tx is invalid, there is not outputs
txWithTxOutStatus tx@ChainIndexTx {_citxTxId, _citxOutputs = InvalidTx} =
fmap (, Committed TxInvalid (Spent _citxTxId)) $ getSpentOutputs tx
Expand Down Expand Up @@ -531,6 +531,6 @@ indexBlock :: [ChainIndexTx] -> IndexedBlock
indexBlock = foldMap indexTx where
indexTx otx =
IndexedBlock
{ ibUtxoSpent = Map.fromSet (const otx) $ Set.map txInRef $ view citxInputs otx
{ ibUtxoSpent = Map.fromSet (const otx) $ Set.fromList $ map txInRef $ view citxInputs otx
, ibUtxoProduced = Map.fromListWith (<>) $ view (citxOutputs . _ValidTx) otx >>= (\TxOut{txOutAddress} -> [(txOutAddress, otx :| [])])
}
1 change: 1 addition & 0 deletions plutus-contract/src/Wallet/Emulator/Wallet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ handleBalance utx' = do
theFee <- calcFee 5 $ Ada.lovelaceValueOf 300000
tx <- handleBalanceTx utxo (utx & U.tx . Ledger.fee .~ theFee)
cTx <- handleError (Right tx) $ fromPlutusTx params cUtxoIndex requiredSigners tx
-- pure $ Tx.CardanoApiTx (Tx.CardanoApiEmulatorEraTx cTx)
pure $ Tx.Both tx (Tx.CardanoApiEmulatorEraTx cTx)
Left txBodyContent -> do
ownPaymentPubKey <- gets ownPaymentPublicKey
Expand Down
27 changes: 19 additions & 8 deletions plutus-ledger/src/Ledger/Tx/CardanoAPI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ toTxScriptValidity False = C.TxScriptValidity C.TxScriptValiditySupportedInAlonz
-- with their hashes.
scriptDataFromCardanoTxBody
:: C.TxBody era
-> (Map P.DatumHash P.Datum, Map P.RedeemerHash P.Redeemer)
-> (Map P.DatumHash P.Datum, P.Redeemers)
scriptDataFromCardanoTxBody C.ByronTxBody {} = (mempty, mempty)
scriptDataFromCardanoTxBody (C.ShelleyTxBody _ _ _ C.TxBodyNoScriptData _ _) =
(mempty, mempty)
Expand All @@ -308,15 +308,26 @@ scriptDataFromCardanoTxBody
)
$ Map.elems dats
redeemers = Map.fromList
$ fmap ( (\r -> (P.redeemerHash r, r))
. P.Redeemer
. fromCardanoScriptData
. C.fromAlonzoData
. fst
)
$ Map.elems reds
$ map (\(ptr, rdmr) ->
( redeemerPtrFromCardanoRdmrPtr ptr
, P.Redeemer
$ fromCardanoScriptData
$ C.fromAlonzoData
$ fst rdmr
)
)
$ Map.toList reds
in (datums, redeemers)

redeemerPtrFromCardanoRdmrPtr :: Alonzo.RdmrPtr -> P.RedeemerPtr
redeemerPtrFromCardanoRdmrPtr (Alonzo.RdmrPtr rdmrTag ptr) = P.RedeemerPtr t (toInteger ptr)
This conversation was marked as resolved.
Show resolved Hide resolved
where
t = case rdmrTag of
Alonzo.Spend -> P.Spend
Alonzo.Mint -> P.Mint
Alonzo.Cert -> P.Cert
Alonzo.Rewrd -> P.Reward

-- | Extract plutus scripts from a Cardano API tx body.
--
-- Note that Plutus scripts are only supported in Alonzo era and onwards.
Expand Down