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

CIP-0033 does not fully utilize eUTxO #417

Closed
fallen-icarus opened this issue Dec 16, 2022 · 3 comments
Closed

CIP-0033 does not fully utilize eUTxO #417

fallen-icarus opened this issue Dec 16, 2022 · 3 comments

Comments

@fallen-icarus
Copy link

The Problem

With the way CIP-33 is currently implemented, and all cardano spending scripts for that matter, the script must be executed for every utxo that comes from the corresponding script address even if validation is based on the entire transaction (as opposed to individual inputs). Here is an example transaction that illustrates this:

cardano-cli transaction build \
  --tx-in c1d01ea50fd233f9fbaef3a295ba607a72c736e58c9c9df588abf4e5009ad4fe#0 \
  --tx-in 622034715b64318e9e2176b7ad9bb22c3432f360293e9258729ce23c1999b9d8#2 \
  --spending-tx-in-reference 622034715b64318e9e2176b7ad9bb22c3432f360293e9258729ce23c1999b9d8#0 \
  --spending-plutus-script-v2 \
  --spending-reference-tx-in-inline-datum-present \
  --spending-reference-tx-in-redeemer-file $swapRedeemerFile \
  --tx-in 766555130db8ff7b50fc548cbff3caa0d0557ce5af804da3b993cd090f1a8c3a#1 \
  --spending-tx-in-reference 622034715b64318e9e2176b7ad9bb22c3432f360293e9258729ce23c1999b9d8#0 \
  --spending-plutus-script-v2 \
  --spending-reference-tx-in-inline-datum-present \
  --spending-reference-tx-in-redeemer-file $swapRedeemerFile \
  --tx-out "$(cat ../assets/wallets/01.addr) 2000000 lovelace + 300 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.54657374546f6b656e0a + 0 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.4f74686572546f6b656e0a" \
  --tx-out "$(cat ${swapScriptAddrFile}) + 4000000 lovelace + 0 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.54657374546f6b656e0a + 250 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.4f74686572546f6b656e0a" \
  --tx-out-inline-datum-file $swapDatumFile \
  --tx-in-collateral bc54229f0755611ba14a2679774a7c7d394b0a476e59c609035e06244e1572bb#0 \
  --change-address $(cat ../assets/wallets/01.addr) \
  --protocol-params-file "${tmpDir}protocol.json" \
  --testnet-magic 1 \
  --out-file "${tmpDir}tx.body"

The second and third utxo inputs are from the same script address. The script I am using above actually passes or fails based on the entire transaction, not a single utxo. This design does not take full advantage of the eUTxO model where a script can see the entire transaction during validation.

The issue with the current implementation is that it is causing redundant computation checks which result in a quadratic scaling of fees. Being that my script checks all the inputs and outputs to the transaction and decides based on that, below is the fee estimation with the current CIP-33 implementation:

tx fee = # ref scripts executed * ( 0.3 ADA + 0.02 ADA * ( # input utxos + # output utxos ) )

The transaction fee increases linearly for every utxo (inputs + outputs) in the transaction, then quadratically if more than one reference script needs to be executed. The reason for this is that the script traverses all the inputs and outputs of the transaction to determine successful validation; the validation is not based on an individual utxo. So since my above transaction is spending two utxos from the script address, the reference script must be executed twice even though the second execution is completely redundant.

The Proposed Change

It would be better in this context if scripts could (but don't have to) work more like public key signatures: only one signature is required no matter how many utxos come from that user. The cardano-node is already capable of detecting if there is a utxo without the necessary accompanying script.

I admit I am ignorant about certain lower-level implementations so all I can do is ask: why can there not be a transaction level script where the datum and redeemer are attached as usual? Here is how I am proposing my above transaction would look instead:

cardano-cli transaction build \
  --tx-in c1d01ea50fd233f9fbaef3a295ba607a72c736e58c9c9df588abf4e5009ad4fe#0 \
  --tx-in 622034715b64318e9e2176b7ad9bb22c3432f360293e9258729ce23c1999b9d8#2 \
  --spending-plutus-script-v2 \
  --spending-reference-tx-in-inline-datum-present \
  --spending-reference-tx-in-redeemer-file $swapRedeemerFile \
  --tx-in 766555130db8ff7b50fc548cbff3caa0d0557ce5af804da3b993cd090f1a8c3a#1 \
  --spending-plutus-script-v2 \
  --spending-reference-tx-in-inline-datum-present \
  --spending-reference-tx-in-redeemer-file $swapRedeemerFile \
  --tx-out "$(cat ../assets/wallets/01.addr) 2000000 lovelace + 300 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.54657374546f6b656e0a + 0 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.4f74686572546f6b656e0a" \
  --tx-out "$(cat ${swapScriptAddrFile}) + 4000000 lovelace + 0 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.54657374546f6b656e0a + 250 c0f8644a01a6bf5db02f4afe30d604975e63dd274f1098a1738e561d.4f74686572546f6b656e0a" \
  --tx-out-inline-datum-file $swapDatumFile \
  --tx-in-collateral bc54229f0755611ba14a2679774a7c7d394b0a476e59c609035e06244e1572bb#0 \
  --change-address $(cat ../assets/wallets/01.addr) \
  --tx-level-spending-tx-in-reference 622034715b64318e9e2176b7ad9bb22c3432f360293e9258729ce23c1999b9d8#0 \
  --protocol-params-file "${tmpDir}protocol.json" \
  --testnet-magic 1 \
  --out-file "${tmpDir}tx.body"

I removed the spending-tx-in-reference lines and added a tx-level-spending-tx-in-reference after change-address. If validation needs to be done on a per utxo basis, the original spending-tx-in-reference lines can still be used.

Minting scripts can already be used in a similar manner:

cardano-cli transaction build \
  --tx-in fadae52f0323d7178c8116aa6adce31aba3ad6c561cbe5b31009251f742aa1bb#1 \
  --tx-out "$(cat ../../assets/wallets/01.addr) 2000000 lovelace + 1000 ${alwaysSucceedSymbol}.${tokenName}" \
  --tx-out "$(cat ../../assets/wallets/02.addr) 2000000 lovelace + 50 ${alwaysSucceedSymbol}.${tokenName}" \
  --mint "1050 ${alwaysSucceedSymbol}.${tokenName}" \
  --mint-script-file alwaysSucceedsMintingPolicy.plutus \
  --mint-redeemer-file unit.json \
  --tx-in-collateral af3b8901a464f53cb69e6e240a506947154b1fedbe89ab7ff9263ed2263f5cf5#0 \
  --change-address $(cat ../../assets/wallets/01.addr) \
  --protocol-params-file "${tmpDir}protocol.json" \
  --testnet-magic 1 \
  --out-file "${tmpDir}tx.body"

Here the minting script is only executed once despite the minting occurring in two different outputs.

How would this work?

Two cases need to be considered:

  1. A transaction level script is used where a utxo level script should be used.
  2. A utxo level script is used where a transaction level script should be used.

The current way a spending script is written (in Haskell) is like this:

-- | validator function before being compiled to plutus
mkValidator :: Datum -> Redeemer -> ScriptContext -> Bool
mkValidator d r ctx = ...

Meanwhile minting scripts are written like this:

-- | minting policy before being compiled to plutus
mkMintPolicy :: Redeemer -> ScriptContext -> Bool
mkMintPolicy r ctx = ...

So taking inspiration from the fact that minting policies don't deal with a datum, I propose the following change to how spending scripts are written:

-- | new validator function before being compiled to plutus
mkValidator :: Maybe Datum -> Redeemer -> ScriptContext -> Bool
mkValidator Nothing r ctx = {- the case where the script can be used at a transaction level -}
mkValidator (Just d) r ctx = {- the case where the script can be used at the utxo level -}

When the script is used with the tx-level-reference-script option, Nothing is passed in for the datum. On the other hand, when spending-tx-in-reference is used like usual, the datum will be parsed and passed with the Just. This way the code explicitly handles both of the above cases.

Using this technique, it would also be theoretically possible to spend a utxo at a script address even if it doesn't have a datum attached. This assumes the proper accompanying logic.

What if a malicious entity forcibly passes in the wrong version of the datum (Nothing when it should be Just d or vice versa)?

The script logic can be written to account for this so I argue it is up to the developer to defend against this kind of attack. A simple error message when the wrong version is used could suffice for most use cases. For my use case, the code would be:

mkValidator :: Maybe Datum -> Redeemer -> ScriptContext -> Bool
mkValidator (Just _) _ _ = traceError "Not meant to be used at the utxo level"  -- ^ I don't want the script used in this case
mkValidator Nothing r ctx = {- do what I want -}

Can multiple transaction level scripts be used in one transaction?

I don't see why not. The node is capable of detecting if all relevant scripts are present in the transaction. The transaction would only be valid if all necessary scripts succeed.

Does this require a hardfork?

I do not know enough to say.

Extra Comment

I wasn't sure if this should be its own CIP or if it was related enough to CIP-33 to be opened as an Issue. Being that it is extending the behavior of CIP-33, I chose the latter.

@rphair
Copy link
Collaborator

rphair commented Dec 16, 2022

I wasn't sure if this should be its own CIP or if it was related enough to CIP-33 to be opened as an Issue

@fallen-icarus please also consider the option of writing a Cardano Problem Statement (CPS) about what you've reported, especially given the uncertainty about implementation: https://github.com/cardano-foundation/CIPs/tree/master/CIP-9999

It would be interesting also to get @michaelpj to comment on this if he has time. I know he definitely would respond if this were submitted as a CPS: but enquiring before then could at least avoid some duplication of effort if this had already been considered somehow. (Note I am more of a Cardano generalist so this is above my own level.)

@fallen-icarus
Copy link
Author

@rphair Thanks for pointing out that option. I hadn't considered it but you are probably right that a CPS is more appropriate for this. I will look to create one next week.

@fallen-icarus
Copy link
Author

I opened a pull request here so I am closing this issue.

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

No branches or pull requests

2 participants