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

Updates with more info #1118

Merged
Merged
Show file tree
Hide file tree
Changes from 11 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
134 changes: 104 additions & 30 deletions docs/architecture/transaction.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,130 @@
# Transaction

`Transaction`s are a mechanism by which the state of the chain is updated. That is, all changes in account and note states result from executing `Transaction`s. In Miden, `Transaction`s can originate either from the users or from the network itself.
A `Transaction` in Miden is the state transition of a single account. A `Transaction` takes as input a single [account](accounts.md) and zero or more [notes](notes.md), and outputs the same account with an updated state, together with zero or more notes. `Transaction`s in Miden are Miden VM programs, their execution resulting in the generation of a zero-knowledge proof.

Miden aims for the following characteristics in `Transaction`s:
Miden's `Transaction` model aims for the following:

- **Parallel transaction execution**: Because a transaction is always performed against a single account, Miden obtains asynchronicity.
- **Private transaction execution**: Local execution of transactions enables preservation of sensitive data.
- **Parallel transaction execution**: Accounts can update their state independently from each other and in parallel.
- **Private transaction execution**: Client-side `Transaction` proving allows the network to verify `Transaction`s validity with zero knowledge.

## What is the purpose of a transaction?
![Transaction diagram](../img/architecture/transaction/transaction-diagram.png)

In Miden, a `Transaction` represents the state transition of a single account. A `Transaction` takes a single [account](accounts.md) and zero or more [notes](notes.md), and none or one script (piece of code executed after all notes have been executed) as input, and outputs the same account with a potentially updated state, together with some potential newly created notes.
Compared to most blockchains, where a `Transaction` typically involves more than one account (e.g., sender and receiver), a `Transaction` in Miden involves a single account. To illustrate, Alice sends 5 ETH to Bob. In Miden, sending 5 ETH from Alice to Bob takes two `Transaction`s, one in which Alice creates a note containing 5 ETH and one in which Bob consumes that note and receives the 5 ETH. This model removes the need for a global lock on the blockchain's state, enabling Miden to process `Transaction`s in parallel.

![Transaction diagram](../img/architecture/transaction/transaction-diagram.png)
## Transaction execution
Every `Transaction` describes the process of an account changing its state. This process is described as a Miden VM program, resulting in the generation of a zero-knowledge proof. `Transaction`s are being executed in a well-defined sequence, in which several note and transaction scripts can call the account interface to interact with it.

![Transaction execution flow](../img/architecture/transaction/transaction-program.png)

### Transaction inputs
A `Transaction` requires several inputs:

- **Account**: `Transaction` are always executed against a single account. The executor must have complete knowledge of the account's state including its code, nonce, vault and storage. Private account data must be known at runtime.
- **Notes**: A `Transaction` can consume up to `1K` notes. The executor must have complete knowledge of the note data, including note inputs, before consumption. The note script must be known for the parts that are being executed. For private notes, the data cannot be fetched from the blockchain and must be received otherwise, e.g., via P2P file transfer.
- **Blockchain state**: The current reference block and information about the notes database used to authenticate input notes must be retrieved from the Miden operator before execution. Usually, notes to be consumed in a `Transaction` must have been created before the reference block.
- **Transaction script (optional)**: `Transaction` scripts are defined by the executor. And like note scripts, they can invoke account methods, e.g., sign a transaction.
- **Transaction arguments (optional)**: For every note, the executor can inject transaction arguments that are present at runtime. If the note script - and therefore the note creator - allows, the note script can read those arguments to allow dynamic execution. See below for an example.
- **Foreign account data (optional)**: Any foreign account data accessed during a `Transaction`, whether private or public, must be available beforehand. There is no need to know the full account storage, but the data necessary for the `Transaction`, e.g., the key/value pair that is read and the corresponding storage root.

> **Info**
> - Usually, notes that are consumed in a `Transaction` must be recorded on-chain in order for the `Transaction` to succeed. However, in Miden there is the concept of **ephemeral notes** that can be consumed in a `Transaction` before being registered on-chain. This allows the executor to consume notes before they reach the blockchain. One can build a sub-second order book by allowing its traders to build faster transactions that depend on each other and are being validated or nullified in batches.
> - There is no nullifier check during a `Transaction`. Nullifiers are checked by the Miden operator during `Transaction` verification. So at the `Transaction` level, there is "double spending." If a note was already spent, i.e. there exists a nullifier for that note, the whole `Transaction` will fail when submitted to the network.

### Transaction flow

The transaction **prologue** executes at the beginning of a transaction. It validates on-chain commitments against the provided data. This is to ensure that the transaction executes against a valid on-chain account and note state. Notes to be consumed must be registered on-chain - except for [ephemeral notes](note.md) which can be consumed without block inclusion.

After the prologue, **note processing** starts. Notes are executed sequentially against the account, following a sequence defined by the executor. To execute a note means processing the note script that calls methods exposed on the account interface. Notes must be consumed fully, which means all assets must be transferred into the account or onto other notes.

After all notes are being consumed, the **`Transaction` script processing** starts. `Transaction` scripts are optional code to interact with account methods without the need of notes. `Transaction` scripts are used to sign the `Transaction` or to mint tokens from a faucet.

Finally, the **Epilogue** completes the execution, resulting in an updated account state and a generated zero-knowledge proof. The validity of the resulting state change is being checked. The account's `Nonce` must have been incremented, to allow transaction authentication. Also the net sum of all involved assets mus be `0` - if the account is not a faucet.

The proof together with the corresponding data needed for verification and updates on the global state can then be submitted and processed by the network.

### Difference between note and transaction scripts

[Note scripts](note.md/script) can invoke the account interface during execution. They can push assets into the account's vault, create new notes, set a transaction expiration, and read from or write to the account’s storage. Any method they call must be explicitly exposed by the account interface. Note scripts can also invoke methods of foreign accounts to read their state.

`Transaction` scripts execute after all note scripts and are optional. Unlike note scripts, which are defined by the note creator, `Transaction` scripts are defined by the executor. For example, they allow the executor to authenticate the transaction via the account’s authentication component. However, the executor can invoke any exposed method. This enables actions such as creating notes, modifying account storage, or, if the account is a faucet, minting new tokens. `Transaction` scripts can also invoke methods of foreign accounts to read their state.

## Basic examples - consuming and creating a P2ID note

## Transaction core components
To illustrate the `Transaction` protocol, we provide two examples for a basic `Transaction`. We will reference to the existing Miden `Transaction` kernel - the reference implementation of the protocol - and to the methods in Miden Assembly.

WIP
### Creating a P2ID note

<Paul Henry, can you try to give an example of how to create a P2ID note?>

### Consuming a P2ID note

Let's assume account A wants to consume a P2ID note to receive the assets contained in that note. P2ID notes are pay-to-id notes that can only consumed by a specified target account Id. Note creators can provide the target account Id using the [note inputs](note.md).

In this example, account A uses the basic wallet and the authentication component provided by `miden-lib`. The basic wallet component defines methods to send `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` and to receive assets - `wallets::basic::receive_asset`. The authentication component provides the ability to sign a transaction exposing `auth::basic::auth_tx_rpo_falcon512`. Some account methods like `account::get_id` are always exposed.

To start the transaction process, the executor fetches and prepares all the input data to the `Transaction`. First, it retrieves blockchain data, like global inputs and block data of the last recent block. This information is needed to authenticate the native account's state and that the P2ID note exists on-chain. Then it loads the full account and note data, to start the `Transaction` execution.

In the transaction's prologue the data is being authenticated by re-hashing the provided values and comparing them to the blockchain's data.

Then the P2ID note script is being executed. The script starts by reading the note inputs `note::get_inputs` - in our case the account Id of the intended target account. It checks if the provided target account Id equals to the account Id of the executing account. This is the first time, the note invokes a method exposed by the account `account::get_id`.

If the check passes, the note script pushes the assets it holds into the account's vault. For every asset the note contains, the script calls the `wallets::basic::receive_asset` method exposed but the account's wallet component. For security reasons, `wallets::basic::receive_asset` calls `account::add_asset`, which can not be called within the note context itself.

After the assets are stored in the account's vault, the transaction script is being executed. The script calls `auth::basic::auth_tx_rpo_falcon512` which is explicitly exposed in the account interface. The method is used to verify a provided signature against a public key stored in the account's storage and a commitment to this specific transaction. If the signature can be verified, the method increments the nonce.

The epilogue finalizes the transaction by computing the final account hash, asserting the nonce increment and checking that no assets were created or destroyed in the transaction - that means the net sum of all assets must stay the same.

## Transaction types

There are two types of transactions in Miden: **local transactions** and **network transactions**.
There are two types of `Transaction`s in Miden: **local transactions** and **network transactions** [not yet implemented].

![Local vs network transactions](../img/architecture/transaction/local-vs-network-transaction.png)
### Local transaction

Users transition their account's state locally using the Miden VM and generate a `Transaction` proof that can be verified by the network, which we call **client-side proving**. The network then only has to verify the proof and to change the global parts of the state to apply the state transition.

### Local transactions
They are useful, because:

This is where clients executing the transactions also generate the proofs of their correct execution. So, no additional work needs to be performed by the network.
1. They enable privacy as neither the account state nor account code are needed to verify the zero-knowledge proof. Public inputs are only commitments and block information that are stored on-chain.
2. They are cheaper (i.e., lower in fees) as the execution of the state transition and the generation of the zero-knowledge proof are already made by the users. Hence **privacy is the cheaper option on Miden**.
3. They allow arbitrarily complex computation to be done. The proof size doesn't grow linearly with the complexity of the computation. Hence there is no gas limit for client-side proving.

Local transactions are useful for several reasons:
Client-side proving or local transactions on low-power devices can be slow, but Miden offers a pragmatic alternative: **delegated proving**. Instead of waiting for complex computations to finish on your device, you can hand off proof generation to a service, ensuring a consistent 1-2 second proving time, even on mobile.

1. They are cheaper (i.e., lower fees) as zk-proofs are already generated by the clients.
2. They allow fairly complex computations because the proof size doesn't grow linearly with the complexity of the computation.
3. They enable privacy as neither the account state nor account code are needed to verify the zk-proof.
### Network transaction

### Network transactions
The Miden operator executes the `Transaction` and generates the proof. Miden uses network `Transaction`s for smart contracts with public shared state. This type of `Transaction` is quite similar to the ones in traditional blockchains (e.g., Ethereum).

This is where the operator executes the transaction and generates the proofs.
They are useful, because:

Network transactions are useful for two reasons:
1. For public shared state of smart contracts. Network `Transaction`s allow orchestrated state changes of public smart contracts without race conditions.
2. Smart contracts should be able to be executed autonomously, ensuring liveness. Local `Transaction`s require a user to execute and prove, but in some cases a smart contract should be able to execute when certain conditions are met.
3. Clients may not have sufficient resources to generate zero-knowledge proofs.

1. Clients may not have sufficient resources to generate zk-proofs.
2. Executing many transactions against the same public account by different clients is challenging, as the account state changes after every transaction. Due to this, the Miden node/operator acts as a "synchronizer" to execute transactions sequentially by feeding the output of the previous transaction into the input of the next.
![Local vs network transactions](../img/architecture/transaction/local-vs-network-transaction.png)

## Transaction lifecycle
The ability to facilitate both, local and network `Transaction`s, **is one of the differentiating factors of Miden** compared to other blockchains. Local `Transaction` execution and proving can happen in parallel as for most `Transaction`s there is no need for public state changes. This increases the network's throughput tremendously and provides privacy. Network `Transaction`s on the other hand enable autonomous smart contracts and public shared state.

In Miden, every `Transaction` is executed within the Miden VM. Throughout its lifetime, a `Transaction` progresses through various phases:
### Transaction limits

1. **Compilation:** All `Transaction` inputs (account, notes, script) are compiled into an executable Miden program.
2. **Execution:** The `Transaction` program is executed within the Miden VM, which produces outputs (updated account, notes).
3. **Proving:** The executed `Transaction` is proven by the Miden prover.
Currently the protocol limits the number of notes that can be consumed or produced in a transaction to `1K` each. That means on the other hand, in one transaction, an application can serve up to 2000 different user requests like deposits or withdrawals into a pool.

![Transaction execution process](../img/architecture/transaction/transaction-execution-process.png)
A simple transaction currently takes about 1-2 seconds on a MacBook Pro. It takes around `90K` cycles to create the proof, whereas the signature verification step is the dominant cost.

> **Info**
> - One of the main reasons for separating out the execution and proving steps is to allow _stateless provers_; i.e., the executed transaction has all the data it needs to re-execute and prove a transaction without database access. This supports easier proof-generation distribution.

---

> **More info**
> - One of the main reasons for separating execution and proving steps is to allow _stateless provers_; i.e., the executed `Transaction` has all the data it needs to re-execute and prove a `Transaction` without database access. This supports easier proof-generation distribution.
>
> - Not all `Transaction`s require notes. For example, the owner of a faucet can mint new tokens using only a `Transaction` script, without interacting with external notes.
>
> - It is possible to set `Transaction` expiration heights and in doing so, to define a block height until a `Transaction` should be included into a block. If the `Transaction` is expired, the resulting account state change is not valid and the `Transaction` cannot be verified anymore.
>
> - Note and `Transaction` scripts can read the state of foreign accounts during execution. This is called foreign procedure invocation. For example, the price of an asset for the **Swap** script might depend on a certain value stored in the oracle account.
>
> - An example of the right usage of `Transaction` arguments is the consumption of a **Swap** note. Those notes allow asset exchange based on predefined conditions. Example:
- The note's consumption condition is defined as "anyone can consume this note to take X units of asset A if they simultaneously create a note sending Y units of asset B back to the creator."
- If an executor wants to buy only a fraction `(X-m)` of asset A, they provide this amount via transaction arguments. The executor would provide the value `m`.
- The note script then enforces the correct transfer:
- A new note is created returning `Y-((m*Y)/X)` of asset B to the sender.
- A second note is created, holding the remaining `(X-m)` of asset A for future consumption.
Binary file modified docs/img/architecture/transaction/transaction-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading