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-0131? | Transaction swaps #880

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

lehins
Copy link
Contributor

@lehins lehins commented Aug 9, 2024

This CIP is a solution to Intents for Cardano | CPS-15

After reading Transaction pieces - CIP-0130 and Validation Zones I realized there are too many downsides in both of those proposals fro my liking. Almost all of the downsides come out of introduction these strange new relationships between transactions. In order to retain sanity for all of the ledger team developers including myself, I would like to reduce the complexity and propose a different approach that hopefully satisfies all of the needs that other competing proposals would solve.

This is an initial attempt. Before I spend more time on polishing it up, I would like to get some feedback from the community.


(rendered proposal)

@lehins
Copy link
Contributor Author

lehins commented Aug 9, 2024

CC @polinavino, @fallen-icarus and @WhatisRT

@rphair
Copy link
Collaborator

rphair commented Aug 9, 2024

thanks; a timely submission @lehins... So we can link to a readable draft, please rename your document to README.md.

@rphair rphair added the Category: Ledger Proposals belonging to the 'Ledger' category. label Aug 9, 2024
@lehins
Copy link
Contributor Author

lehins commented Aug 9, 2024

thanks; a timely submission

There was a lot of discussion on the mentioned CIPs and it was easier for me to write a CIP of my own that describes of how I would solve this problem, rather participate in lengthy discussion on existing CIPs trying to explain my point of view.

So we can link to a readable draft, please rename your document to README.md.

Done. Sorry, my first CIP here 😁

@rphair
Copy link
Collaborator

rphair commented Aug 9, 2024

Since this is not marked as a draft on GitHub, and because it's being referenced in other actively reviewed CIP pull requests, I think it's appropriate to "triage" it at the next CIP meeting even though you arewere still referring to it as a "draft" - so @lehins I've put it on the agenda (https://hackmd.io/@cip-editors/95) to help bring it up to review speed with other contemporary proposals of similar scope.

@lehins
Copy link
Contributor Author

lehins commented Aug 9, 2024

@rphair I've changed the wording to "attempt", which actually better reflects my intent than a "draft"

@fallen-icarus
Copy link

I like this approach. It seems simple and straightforward. AFAICT this doesn't support transaction batching, but I think it covers enough use cases to still be worth it.

@polinavino
Copy link

polinavino commented Aug 9, 2024

Interesting idea! I would be very curious to understand the changes to the UTXO rule that this approach requires.

Also, the updated approach to Validation Zones does not require changes to the ledger state because there are no more requests and fulfills. There are also no changes to the transaction structure at all, so it is compatible with existing Plutus contracts (no new version needed). The main change is moving the consumed = produced check to the ZONE rule, allowing unbalanced transactions to perform swaps (with implicit value flow).

https://github.com/cardano-foundation/CIPs/blob/1fd1d6dc56fadca8b07009e795b3ec4eedc298cd/CIP-0118/README-implicit.md

CIP-xxxx/README.md Outdated Show resolved Hide resolved
@lehins
Copy link
Contributor Author

lehins commented Aug 9, 2024

AFAICT this doesn't support transaction batching

We'd first need to define what transaction batching would actually mean for Cardano. What does it mean in the context of plutus scripts, etc.

This approach allows you to batch multiple swaps, which themselves can be thought of miniture transactions with limited functionality. So in some sense it is batching of transactions.
For example I can create a swap that spends 100ADA and outputs 101ADA. That is a basic transaction, which has 1ADA being reserved for fees.

I would be very curious to understand the changes to the UTXO rule that this approach requires.

We would need to validate each individual swap (eg. verify signatures, ensure outputs satisfy minutxo requirement, etc.) We would be able to reuse various portions of existing rules to create this rule for swap validation. We would then need to enforce that that there are no duplicate inputs. Then for the purpose of balancing consumed and produced in the UTXO rule we could pretend that all pieces of swaps are defined in the transaction itself, while enforcing there are no duplicate inputs.

updated approach to Validation Zones does not require changes to the ledger state

I am not to worried about changing the ledger state, wed do it all the time.

it is compatible with existing Plutus contracts (no new version needed).

This is not possible. Any existing script that for example expects a fee to never be 0 could now fail, where it wouldn't before. So I would advise against advertising that PlutusV1-PlutusV3 would be able to work with this feature.

@polinavino
Copy link

We would need to validate each individual swap (eg. verify signatures, ensure outputs satisfy minutxo requirement, etc.) We would be able to reuse various portions of existing rules to create this rule for swap validation. We would then need to enforce that that there are no duplicate inputs. Then for the purpose of balancing consumed and produced in the UTXO rule we could pretend that all pieces of swaps are defined in the transaction itself, while enforcing there are no duplicate inputs.

As far a rule changes, that is significantly more complicated than the (implicit/updated) validation zones proposal, but could possibly work. I still do not understand if this is built on top of the zones concept or transactions can be validated individually (without the need for zones)?

This is not possible. Any existing script that for example expects a fee to never be 0 could now fail, where it wouldn't before. So I would advise against advertising that PlutusV1-PlutusV3 would be able to work with this feature.

Fair, a better approach would be - add a new field of type Value to the transaction that is computed at the time of deserialisation - the extra or missing value would be in it, and only let old version scripts run on a transaction if that field is 0.

@fallen-icarus
Copy link

fallen-icarus commented Aug 9, 2024

We'd first need to define what transaction batching would actually mean for Cardano.

With the way I am thinking about it, and the way @WhatisRT described it, perhaps transaction "batching" is not appropriate. Transaction "chaining" may be more accurate.

Ergo defines transaction chaining as: the sequential use of outputs from off-chain transactions.

I personally don't have a use case for it, but a CIP was opened a while ago about it. I like the simplicity of this CIP so unless there is also a simple extension to enable chaining, I think I'd rather leave it as future work. IMO babel fees and off-chain trading are more critical than chaining.

@polinavino
Copy link

polinavino commented Aug 9, 2024

A couple of other thoughts -

  1. without swaps providing collateral, it could be the case that the work a transaction builder does to turn a swap into a transaction (which includes validating possibly very large scripts) is not compensated when scripts phase-2 fail. This is not the case for (implicit) Validation Zones - any phase-1 partially valid transaction can pay collateral

  2. if a swap includes, for example, a role token, a transaction builder can do whatever they want with it (e.g. interact with another contract that requires the same role token), and the swap-builder, if they want to engage with a contract (e.g. have the transaction builder sponsor the dApp fees for its execution), has no control over this. They dont even sign the final transaction in which their role token will be used. This is, again, not the case for (implicit) Validation Zones. What your transaction does is under your control. One transaction author is not surrendering the use of all their tokens for an unknown purpose (as one does within a swap) to another user because these tokens can only be used within a transaction signed by them. While this could be potentially architected around in new-version Plutus contracts, it seems like it would introduce a lot of additional cases to consider and difficultly in formal verification.

another example of the sort of problem I am getting at in (2) : you can mint an NFT whose script requires the signature of a swap-builder, and the spending of some input in the swap, without them actually signing off on this minting.

Comment on lines 36 to 45
swap_body = { 0 : oset<transaction_input> ;; Regular inputs
, 1 : [+ transaction_output] ;; Outputs, that will have to be satisfied
, ? 3 : slot_no ;; Validity interval start slot number
, ? 5 : withdrawals ;; Regular withdrawals
, ? 8 : slot_no ;; Validity interval end slot number
, ? 9 : mint ;; Mint field
, ? 14 : required_signers ;; Required signers
, ? 15 : network_id ;; Network Id
, ? 22 : positive_coin ;; Treasury donation
}

Choose a reason for hiding this comment

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

I think this still needs something like the original script_data_hash to guarantee the proper plutus_data is used as the redeemer for each smart contract. Otherwise, there is a possible man-in-the-middle attack where a malicious party can change the supplied redeemer to one that benefits them. Wouldn't the following work?

swap_redeemer = [ tag: swap_redeemer_tag, index: uint, data: plutus_data ] ; no ex_units
swap_redeemer_tag =
    0 ; inputTag "Spend"
  / 1 ; mintTag  "Mint"
  / 3 ; wdrlTag  "Reward"

swap_script_data_hash = $hash32
; The derivation is the same as script_data_hash except it uses 
; swap_redeemer instead of the original redeemer so that the execution units
; can be given later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this still needs something like the original script_data_hash to guarantee the proper plutus_data is used as the redeemer for each smart contract.

The transaction swap is signed, so no-one can mess with the datum that will be passed to the swap scripts. Transaction builder will not have the capability to supply plutus datum's, it will only be the execution units that are supplied:

https://github.com/lehins/CIPs/blob/4fb4ba9d2b4a1164742607036aaa5e8566315819/CIP-xxxx/README.md?plain=1#L119-L130

Copy link

@fallen-icarus fallen-icarus Aug 10, 2024

Choose a reason for hiding this comment

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

Datums and redeemers are separate things. Datums are part of the UTxO set so the input itself has everything needed to verify the proper datum is being used. The same is not true for redeemers. The above swap_body does not contain the plutus_data (or hash of it) for the redeemers anywhere. AFAIU that was the point of the script_data_hash being in the transaction_body.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Datums and redeemers are separate things.

Datums was a terrible terminology. The way we use it internally in Ledger (not sure of that is the same outside) is there are spending datums and datums that are supplied in the redeemers. Both are just arguments that are supplied to plutus scripts through plutus context.

You are right. We'll need to move swap_redeemers into swap_body, so it can be signed. That is because there is no scriptIntegrityHash in the swap body.

swap_redeemers = { + [ tag   : swap_redeemer_tag
                     , index : uint .size 4
                     ] => plutus_data
                 }

Copy link

@fallen-icarus fallen-icarus Aug 10, 2024

Choose a reason for hiding this comment

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

Datums was a terrible terminology. The way we use it internally in Ledger (not sure of that is the same outside) is there are spending datums and datums that are supplied in the redeemers.

It seems everyone uses the terms differently. The redeemer for a smart contract developer doesn't contain execution units, it is just the plutus data. The fact that the same term is used with a different definition by the cddl is really confusing... The terms should be standardized.

We'll need to move swap_redeemers into swap_body, so it can be signed.

I don't think this is enough. If we have a list of signed redeemers, how do we know they are being paired up with the right scripts? I think we really need a script integrity check for the transaction swaps; this version just wouldn't care about the execution units. The swap_redeemers can remain outside the swap_body while the swap_script_data_hash encapsulates the required script+datum+redeemer pairing.

Choose a reason for hiding this comment

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

The swap_redeemer_tag is also relevant for the integrity check since some scripts can be executed as spending, minting, or withdrawals. How do we know the script is being executed as the right type without the script integrity check?

Copy link
Contributor Author

@lehins lehins Aug 10, 2024

Choose a reason for hiding this comment

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

If we have a list of signed redeemers, how do we know they are being paired up with the right scripts?

Ledger will take of this part in the same way we take care of it right now.

The point of script integrity hash is that it takes pieces outside of the transaction body and through a cryptographic hash it places it into the body, so it can be signed. In other words it is like placing redeemers, datums and current costmodels directly into the transaction body.

So, if we place redeemers (without execution units) into the swap body, then the user by signing the body prevents anyone messing with the arguments of all of the plutus scripts in the swap. Same applies to the purpose (redeemer tag), since that is the key in the redeemer map.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could of course employ the same process and include script integrity hash in the swap transactions. Maybe that would be even better for consistency with regular transactions, albeit at the cost of slightly higher complexity

Copy link
Contributor

Choose a reason for hiding this comment

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

No please, calculating and working with script integrity hash is the worst part of offchain library development on Cardano. Frankly redeemers should have just been directly in the body originally.

@lehins
Copy link
Contributor Author

lehins commented Aug 10, 2024

@polinavino

without swaps providing collateral, it could be the case that the work a transaction builder does to turn a swap into a transaction (which includes validating possibly very large scripts) is not compensated when scripts phase-2 fail. This is not the case for (implicit) Validation Zones - any phase-1 partially valid transaction can pay collateral

The work of a transaction builder happens off chain, which is the work that no-one has to pay for. Even the largest and the most expensive of scripts are really cheap to run locally on a single computer. We only need to worry about cases like that for cardano-node that does validations of transaction. Maybe I misunderstood your concern, so if you can elaborate on it that will be great.

if a swap includes, for example, a role token, a transaction builder can do whatever they want with it (e.g. interact with another contract that requires the same role token),

No they can't. This is not a problem for swap transactions at all. When you have a script that locks something in a swap and that swap also contains a role token. That script will only succeed if that role token is present in that exact swap. Tokens from swaps are not placed in some pool that makes all of them equivalent. So, the fact that scripts from other swaps can see such tokens in other swaps, does not mean anything for the safety of the swap. It is up to the script writer to enforce where the role token is expected to be for the script to succeed. So, using such script that expects a token in the same swap will make it fail if it is missing, regardless if that token is present in other swaps in the transaction. You gotta remember that scripts will see the full transaction and they can make any decision the please on that transaction.

and the swap-builder, if they want to engage with a contract (e.g. have the transaction builder sponsor the dApp fees for its execution), has no control over this. They dont even sign the final transaction in which their role token will be used.

There is no expected trust in this model between swap builders and the transaction builder. swap builders have full control of their portion of the transaction through signing the swaps and providing all the necessary data for execution of their scripts. So they DO NOT NEED to sign the transaction in which their token is used. In the matter of fact this is a feature that Validation Zones does not have: a script writer can look into other swaps and see who provided such tokens or make their own decision on such information. Hell, they even could even make the transaction to fail if it contains swaps with undesired tokens.

What your transaction does is under your control. One transaction author is not surrendering the use of all their tokens for an unknown purpose (as one does within a swap) to another user because these tokens can only be used within a transaction signed by them.

So, this is totally incorrect. No one is surrendering anything. In the matter of fact they are receiving more powers when comparing to Validation Zones: the see the origin of funds and everything that took place for those funds to balance the whole transaction. And they can make any decision they like on that information

While this could be potentially architected around in new-version Plutus contracts, it seems like it would introduce a lot of additional cases to consider and difficultly in formal verification.

Plutus contracts will have to change, since the context will change. I would like to bring an important argument to this point. Formal verification is just a tool that we use. It should never get in a way of new features. If some feature requires more work on the formal methods side we will just have to add more people to solve it. Vast majority of work always happens on the implementation side and I don't see any blockers here.

@polinavino
Copy link

polinavino commented Aug 10, 2024

The work of a transaction builder happens off chain, which is the work that no-one has to pay for. Even the largest and the most expensive of scripts are really cheap to run locally on a single computer. We only need to worry about cases like that for cardano-node that does validations of transaction. Maybe I misunderstood your concern, so if you can elaborate on it that will be great.

The point of the two-phase validation mechanism is to make sure that when a node executes a script from an incoming (phase-1 valid) transaction, and the script fails (this is still off-chain/in the mempool), the node is still able to put that transaction in a block for the purpose of collecting its collateral, thereby getting paid for the work of running of the scripts in that transaction. The scripts are later also run as part of block validation, to make sure that they indeed fail, and collateral should be collected. This feature is a way to prevent DDoS attacks. Because swaps are not transactions, they force the node to run their scripts to determine if they can go into blocks, but do not provide any collateral. The two-phase validation guarantee that nodes will be compensated for running mempools is then void.

Part (2) of my earlier comment is not so much an issue to solve, but rather I am just pointing out that script logic with swaps will have to be significantly more complicated, with more cases/attacks to consider.

@lehins
Copy link
Contributor Author

lehins commented Aug 10, 2024

@polinavino I know exactly how phase1/2 validation works and how, why and when collateral is collected. You are just missing my point. Transaction builder has to supply the collateral for all of the scripts in all of the swaps.

The person or application building the final transaction that contains all the swaps is ultimately responsible for its validity. They know ahead of time before submitting transaction will it result in a phase2 validationfailure or not. We are not dealing with Etherium here. It is the same as with ValidationZones, the last transaction must provide collateral for all prior transactions in the zone.

Comment on lines 153 to 155
#### Plutus Context

This proposal has one huge difference from the Validation Zones proposal, namely all of the scripts in a transaction, uncluding the ones in the swaps will see all of the transaction swaps in their context, because they get access to the full transaction. This comes with a benefit of allowing plutus scripts to make decisions on all of the individually unbalanced pieces. That being said it would come at a higher cost for scripts, unless we would also implement [cardano-ledger#3124](https://github.com/IntersectMBO/cardano-ledger/issues/3124), which we have plans on doing anyways. The biggest cost is extra complexity for script writers, since now inputs and withdrawals and minting scritps could now appear in two different places: in regular transactions and in swaps.

Choose a reason for hiding this comment

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

I think the way the plutus context would work with this CIP needs to be explicitly stated in the specification section. I'm imagining the TxInfo would have a new field:

data TxInfo = TxInfo
  { ...
  , swapTransactions :: [SwapTxInfo]
  }

All of the information related to the transaction swaps would only appear in their respective SwapTxInfo (eg, the signatures for a swap transaction are only found in that swap transaction's SwapTxInfo).

Smart contracts also need a quick way to know which context is theirs. The ScriptPurpose would need to specify which SwapTxInfo this execution is for in addition to the rest of the information in the purpose. An index into the [SwapTxInfo] would likely be fine.


#### Dependencies of transactions and collateral

Another major difference is that swaps are constructed completely independently and it is only the top level transaction that combines them all together. This allows for an unlimited number of swaps to be constructed concurrently, while Validation zones have inherent dependency in their design: every transaction depends on all of the preceding transactions in the zone. From my understanding this dependency comes from the design of how colateral is specified in the zones.
Copy link

@polinavino polinavino Aug 11, 2024

Choose a reason for hiding this comment

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

With the new update to Validation Zones (implicit) , this dependency only exists for transactions that specify requiredTxs. That is, transactions that are performing exchanges without caring who the counterparty is are now not required to provide collateral for any other transactions, only themselves.

For requiredTxs, any DAG of dependencies can be formed. As soon as a failing script is encountered, only the failing transaction + the ones it depends on is entered into the block/zone. For this reason, each transaction is phase-1 checked to cover the size-based fee of transactions it depends on so that they can be included in the zone, and shown to Plutus scripts when needed. Note that this is actually not the collateral for running scripts, but only the size-based fee portion. This is cheaper than having to supply collateral for all scripts.


The decision of who pays for the collateral in Validation Zones comes with a natural benefit of deterring users from constructing transactions with phase2 validation, since the first transaction that fails phase2 validation is the one that pays for all scripts in all of the preceding transactions in the zone.

In case of swaps it is up to the transaction builder to figure out which swaps together make up a phase2 valid transaction, because ultimately they will be paying for the collateral if any of the scripts do not succeed. In my personal opinion it is totally reasonable to put this responsibilty on the transaction builder, since ultimately that is the entity that will be making the money in this process.
Copy link

@polinavino polinavino Aug 11, 2024

Choose a reason for hiding this comment

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

After some consideration, it became clear to me that in Validation Zones implicit, transactions do not need to provide collateral for preceding ones (except in requiredTx cases discussed above). So, everyone provides their own collateral and it still functions for its intended purpose - to prevent DDoS attacks on nodes by forcing them to run arbitrary amounts of failing scripts.

In VZ design, is possible to allow for the special cases where some nodes would be willing to cover collateral of other users by signing users transactions that include the use of their collateral UTxOs, taking on the risk. This should be the exception and not the rule, however.


#### Full transaction vs a subset of features

Validation Zones allow for full blown transactions that allow usage of features that are not relevant for the goal of solving unbalanced transactions and the feature of Babel Fees, for example voting, proposing, certificates etc. It makes no sense to include them in the swaps, since that would unnecessarily complicate the logic, while in Validation zones it would not make sense to exclude any of them for the same reason. This could be viewed as a benefit or a drawback, depending on one's point of view. One imporant thing to remember when concidering this point is that both of the approaches respet the same transaction size limit.
Copy link

@polinavino polinavino Aug 11, 2024

Choose a reason for hiding this comment

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

I guess my overall comparison would be something like :

VZ and Swaps do :

  • reduce duplication of witnesses
  • support exchanges with counterparty irrelevance
  • allow scripts to see other Txs
  • allow others to pay your fees
  • require some sophisticated-ish off-chain infrastructure

VZ negatives :

  • need to construct zones
  • mempool needs to deal with zones
  • more fiddly

VZ positives :

  • does not impose transaction dependencies unless specified by the user
  • any DAG dependency graph is allowed rather than *-dependency , which is more versatile
  • collateral mechanism still works for DDoS
  • collateral required for only your own scripts (unless dependencies specified)
  • individual collateral slightly cheaper because only the scripts in one transaction with failing scripts ever need to be run/covered by collateral
  • Plutus scripts will be slightly cheaper to run, because other transactions' data is only shown to them when required by user

CIP-xxxx/README.md Outdated Show resolved Hide resolved
@rphair rphair changed the title CIP-???? | Transaction swaps CIP-0131? | Transaction swaps Aug 21, 2024
Copy link
Collaborator

@rphair rphair left a comment

Choose a reason for hiding this comment

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

Thanks @lehins for the groundbreaking work so far on this proposal and vital review on other proposals within the same scope. CIP meeting today acknowledged this & was keen to assign a number so the community can start referring to the proposal more concretely.

Please also change your directory in this branch to CIP-0131 and update the link to (rendered proposal) in your OP. 🎉

CIP-xxxx/README.md Outdated Show resolved Hide resolved
@Quantumplation
Copy link
Contributor

@lehins it would be quite nice to mesh this with CIP-112, and provide a "required observers" field on each transaction swap, which specifies observer scripts that must be executed on the full transaction. That would allow me, for example, to add additional requirements that must be true of the whole transaction in order for the piece I'm signing to be valid.

@rphair rphair added the State: Confirmed Candiate with CIP number (new PR) or update under review. label Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Ledger Proposals belonging to the 'Ledger' category. State: Confirmed Candiate with CIP number (new PR) or update under review.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants