Transaction Rollups

High-frequency transactions are hard to achieve on a blockchain that is decentralized and open. For this reason, many blockchains offer the possibility to define “layer-2” solutions that relax some constraints in terms of consensus to increase transaction throughput. The layer-1 (the main blockchain) acts as a gatekeeper for several layer-2 (secondary blockchains), and provides economic incentives to prevent attacks.

Introduction to Optimistic Rollups

Optimistic rollups are a popular layer-2 solution, e.g., on the Ethereum blockchain (Boba, Arbitrum, Optimism, etc.). When it comes to an optimistic rollup, the layer-2 operates using a logic similar to the layer-1, but it is updated off-chain, potentially much faster, and its changes are regularly committed to layer-1.

The layer-1 implements a decentralized ledger (called the layer-1 context thereafter) that participants of the network can update thanks to authenticated layer-1 operations. These operations are grouped together in layer-1 blocks (hence the name “blockchain”).

Similarly, the layer-2 implements a ledger (called the layer-2 context), that participants can update by sending messages stored in the layer-1 context in an inbox, with precise semantics for the interpretation of messages on top of a layer-2 context, resulting in the production of a new layer-2 context.

More precisely, an optimistic rollup works as follows:

  1. Certain layer-1 operations will append messages to the inbox. The inbox is analogous to the layer-1 blocks. As such, the consensus of the layer-1 decides which messages the layer-2 has to consume, and in which order.

  2. The layer-2 context is updated off-chain by a rollup node, using the semantics of the messages.

  3. A layer-1 operation allows the rollup node to include the hash of the layer-2 context after the execution of the layer-2 operations in the layer-1 context.

  4. The layer-1 implements a procedure to reject erroneous hashes of the layer-2 context (e.g., submitted by an attacker).

  5. After a period of time specific to each rollup implementation, and in the absence of a dispute, the hashes of the layer-2 context become final, that is, they cannot be rejected. We call layer-2 finality period the time necessary for the hash of a layer-2 context to become final. In the meantime, new layer-1 operations may have filled the inbox with new messages that call for pursuing the same workflow.

The layer-2 context is encoded in a Merkle tree, an ubiquitous data structure in the blockchain universe with many interesting properties. One of these properties is significant in the context of optimistic rollups: it is possible to prove the presence of a value in the tree without having to share the whole tree, by means of Merkle proofs. This property ensures that the procedure to reject a hash does not require to compute the whole layer-2 context.

The rollup node is a daemon responsible for interpreting the messages (as stored in the inbox) onto the layer-2 context, and for posting the resulting hashes in the layer-1. In “optimistic rollup”, the word “optimistic” refers to the assumption that at least one honest transaction rollup node will always be active to reject erroneous hash. The presence of a single honest node is sufficient to guarantee the correct application of the layer-2 operations in the rollup. In its absence, nothing prevents a rogue node to post a maliciously tampered layer-2 context.

Introduction to Transaction Rollups

Transaction Rollups are an implementation of optimistic rollups in Tezos, characterized by the following principles:

  1. The semantics of the messages consists of the transfer of assets represented as Michelson tickets.

  2. The procedure to reject erroneous hashes allows for a finality period of 40,000 blocks.

  3. They are implemented as part of the economic protocol of Tezos directly, not as smart contracts.

The latter design choice, made possible by the amendment feature of Tezos, allows for a specialized, gas- and storage-efficient implementation of optimistic rollups.

Note that it is possible to create any number of transaction rollups on Tezos. They are identified with transaction rollup addresses, assigned by the layer-1 at their respective creation (called origination in Tezos to mimic the terminology used for smart contract). They are prefixed by txr1 when encoded in a base58 alphabet (see also the kinds of address prefixes in Tezos).

Workflow Overview

Transaction rollups allow for exchanging financial assets, encoded as Michelson tickets, at a higher throughput than what is possible on Tezos natively.

Analogous to layer-1 addresses, layer-2 addresses identify assets holders in the layer-2 ledger, meaning layer-2 addresses own and exchange Michelson tickets. They are prefixed by tz4 when encoded in a base58 alphabet.

The expected workflow proceeds as follows.

  1. Layer-1 smart contracts can deposit tickets to a transaction rollup for the benefit of a layer-2 address.

  2. A layer-2 address is associated with a cryptographic public key, and the owner of the companion secret key (called “the owner of the layer-2 address” afterwards) can sign transfer orders. These orders are for the benefit of either another layer-2 address (meaning the transfer order only concerns the layer-2) or a layer-1 address (making the transfer order a withdrawal of their asset outside of the transaction rollup).

To be interpreted by the transaction rollup, transfer orders have to be signed by (1) the owner of a layer-2 address, and (2) the owner of a layer-1 address. This is because they are wrapped in a dedicated layer-1 operation.

While owners of layer-2 addresses who also own a layer-1 address can submit their transfer and withdraw orders themselves, the expected workflow is that they delegate this to a trusted transaction rollup node, which can batch together several layer-2 operations signed by several owners of layer-2 addresses and submit only one layer-1 operation.

Implementation Overview

Here we examine the specific implementation of transaction rollups in Tezos.

Origination

Anyone can originate a transaction rollup on Tezos, as the result of the layer-1 operation Tx_rollup_origination. In a similar manner as contracts, transaction rollups are assigned an address, prefixed by txr1 when encoded with a base58 alphabet.

Exchanging Tickets

The main objective of a transaction rollup is to allow Michelson tickets to be exchanged between layer-2 addresses. Before diving into more details on how these exchanges happen, it is necessary to discuss how layer-2 addresses and tickets are identified in the layer-2.

First, a layer-2 address is primarily identified by a Blake2B, 20-bytes long hash of a BLS public key (prefixed by tz4 when encoded with a base58 encoding). Besides, the layer-2 assigns an integer to each layer-2 address, which can be used in place of the hash of the BLS public key. This design choice allows for reducing the size of the layer-1 operations responsible for appending messages to the inbox of a transaction rollup, which in turn allows for publishing more of these layer-1 operations in a layer-1 block. This is an essential property to give transaction rollup a high throughput.

Secondly, a similar mechanism is implemented for ticket identifiers. By default, tickets are identified by 32-byte hashes computed by the economic protocol. However, the layer-2 also assigns integers to ticket hashes, to save up block space.

Ticket Deposit

Initially, the layer-2 ledger of the newly created transaction rollup is empty. This ledger needs to be provisioned with tickets, which are deposited into layer-2 by layer-1 smart contracts. They do so by emitting layer-1 transactions to the transaction rollup address, targeting more specifically the deposit entrypoint, whose argument is a pair consisting of:

  1. a ticket (of any type), and

  2. a layer-2 address (of type tx_rollup_l2_address in Michelson), which can either be a natural number or a base58 encoded public key hash.

Only smart contracts can send tickets to rollups.

Here is a minimal example of a smart contract depositing unit tickets to a Transaction Rollup:

parameter (pair address tx_rollup_l2_address);
storage (unit);
code {
       # cast the address to contract type
       CAR;
       UNPAIR;
       CONTRACT %deposit (pair (ticket unit) tx_rollup_l2_address);

       IF_SOME {
                 SWAP;

                 # amount for transferring
                 PUSH mutez 0;
                 SWAP;

                 # create a ticket
                 PUSH nat 10;
                 PUSH unit Unit;
                 TICKET;

                 PAIR ;

                 # deposit
                 TRANSFER_TOKENS;

                 DIP { NIL operation };
                 CONS;

                 DIP { PUSH unit Unit };
                 PAIR;
               }
               { FAIL ; }
     }

When its default entrypoint is called, this smart contract emits an internal transaction targeting a transaction rollup in order to deposit 10 unit tickets for the benefit of a given layer-2 address.

Transfers

Once a layer-2 address has been provisioned with a ticket, the owner of this address can transfer it to other layer-2 addresses.

Transfer orders are divided into two parts: a header, which identifies the emitter, and one or more payloads, which specify as many transfer orders.

More precisely, the header consists in:

  1. The layer-2 account authoring the operation, also called its signer

  2. The counter associated to this layer-2 address.

Counters are an anti-replay measure commonly used in blockchains. For instance, Tezos uses a similar mechanism. See the documentation for more information.

Then, the payload allows the signer to transfer the ownership of a given ticket in a given quantity for the benefit of a given address. More precisely, the payload consists in

  1. A destination address. It can either be a layer-1 address, that is a tz1, or a layer-2 address, that is a tz4 or the integer associated with this address by the layer-2.

  2. A ticket hash identifying the asset to exchange, or the integer associated with this ticket hash by the layer-2.

  3. The quantity of the ticket being exchanged, encoded as an int64 value.

The mapping between the layer-2 addresses and their associated integers is maintained by the transaction rollup node.

The interpretation of a transfer order will fail in the following cases:

  1. If the signer of the operation does not own the required quantity of the ticket.

  2. If the new balance of the beneficiary of the transfer after the application of the operation overflows. The quantity of the ticket a layer-2 address owns is encoded using a int64 value. This is a known limitation of the transaction rollups, made necessary to bound the size of the rejection payload so that it can fit in a layer-1 operation.

Transfers can be grouped inside a transaction. A transaction is atomic: if any transfer of the transaction fails, then the whole transaction fails and leaves the balances of the related addresses unchanged. This can be useful to implement trades. For instance, two parties can agree upon exchanging two tickets without having to trust each other for the emission of the counter-part transfer. For a transaction to be valid, it needs to be signed by the authors of the transfers it encompasses.

If a transaction fails (because a transfer within that transaction fails), the transfers are ignored, but the counters of their signers are updated nonetheless. This means the transaction will need to be submitted again, with updated counters, if the error is involuntary.

Transactions are submitted in batches to the layer-1, via the Tx_rollup_submit_batch layer-1 operation. A batch of transactions contains the following pieces of information:

  1. The list of transactions that are batched together.

  2. A BLS signature that aggregates together all the signatures of all the transactions contained by the batch.

A batch of transactions is invalid if the aggregated BLS signature is incorrect (e.g., if one of the included transactions is invalid). Such an invalid batch is discarded by the transaction rollup node, and the counters of the signers are not incremented. This means they can be submitted again in a batch with a valid signature.

Numbering layer-2 inboxes

A rollup level is analogous to a block of layer-1. It is identified by a natural number, starting from zero. For a given rollup, a rollup level is assigned for each layer-1 block in which there is at least one message in that rollup. Each rollup maintains its own set of levels. So, layer-1 block 24601 might correspond to rollup level 0 for rollup A, rollup level 3 for rollup B, and no rollup level at all for rollup C. We often speak of inboxes and rollup levels interchangeably, as each rollup level corresponds to one inbox.

A batch is one sort of message. The other sort is a deposit. Deposits are created by L1 operations which transfer tickets to the rollup.

Proofs

Merkle proofs allow a computation to be proven, and then verified. The roles in the proof game are:

  1. The prover performs a computation, producing a Merkle proof.

  2. The verifier is given the proof and re-runs the computation, producing a boolean: either the proof is correct, or the proof is incorrect.

The details of how Merkle proofs work are too complicated to explain in this document. The important thing to know is that the data storage of the computation is encoded in a Merkle tree, and that the proof includes the root node of this tree (in addition to possibly some other nodes).

Commitments and rejections

In order to ensure that layer-2 transfers and ticket withdrawals are correctly computed, rollup nodes issue commitments. A commitment describes (using Merkle tree hashes) the result of applying all the messages of an inbox to the layer-2 state. For each message of the inbox, the commitment includes the hash of the state, and the hash of any ticket withdrawals generated by the message.

In this section, we describe commitments primarily from the perspective of layer-1. Rollup nodes are responsible for executing layer-2 operations and issuing commitments (and finalization, deletions, and rejections if necessary), but their internal deliberations are not described in this section. The incentive design of optimistic rollups ensures that rollup nodes will behave correctly.

The usual lifecycle of a rollup level is: uncommitted, then committed, then finalized, then deleted.

  1. Uncommitted: At the uncommitted stage, there is no commitment. When a commitment for an inbox is submitted using a layer-1 operation, the level moves to the committed stage. A commitment for an inbox can be issued in any layer-1 block after the block containing that inbox.

  2. Committed: During this stage, commitments can be rejected, moving the inbox back to the uncommitted stage. An inbox remains in this stage until its commitment has been finalized by a finalize operation. Finalize operations are only accepted for commitments that have survived for more than 40,000 blocks (the finality period, defined in tx_rollup_finality_period) without being rejected. The “finalize” operation removes the inbox from the context.

  3. Finalized: During this stage, any dispatch tickets operations from this rollup level can occur. Commitments can no longer be rejected.

  4. Deleted: Finally, after the withdrawal period (tx_rollup_withdraw_period = 40,000 blocks), the commitment can be removed from the context by another layer-1 operation. Dispatch tickets operations from this rollup level can no longer occur (since the commitment has been removed).

A commitment also includes the predecessor commitment’s hash (except in the case of the first commitment for a rollup) and the rollup level of the block that it is committing to, as well as the level’s inbox hash (in case of reorganizations). For each message of the inbox, the commitment has a hash of two hashes: the layer-2 context hash, and the withdrawal list’s hash. This saves operation size by storing only a single hash, at the cost of more complex rejection and withdrawal operations. There is exactly one valid commitment possible for a given block, because the computation of the Merkle proof of the layer-2 operations is deterministic.

At most one commitment is stored per level. If a commitment operation is attempted for a level that already has a non-rejected commitment, it will fail. Commitments are stored in a compact fashion: their message hash lists are themselves Merkelized.

Finalization is implemented as a layer-1 operation. This allows finalization to be carbonated. It operates on the oldest unfinalized commitment for a rollup. The finalization period needs to be long enough so that an attempt by 33% of bakers to steal from a rollup by censoring rejections can be noticed, and avoided by forking the chain.

After finalization, a commitment sticks around for the withdrawal period, and then can be deleted by a layer-1 remove commitment operation. For the most recent commitment deleted, the context keeps the commitment’s hash and last message around in case we need to examine them to reject its successor.

As discussed above, the inbox for a level is deleted during commitment finalization. If no commitments are made, it is possible for inboxes to pile up, which violates our gas assumptions that inboxes are temporary. To prevent this, if there are more than tx_rollup_max_unfinalized_levels = 40,100 inbox levels with messages but without finalized commitments, no further messages are accepted on the rollup until a commitment is finalized.

In order to issue a commitment, a bond is required: Tez tokens are temporarily frozen, and are subject to slashing in the event of a rejection. The bond is treated just like frozen balances for the purpose of delegation. The bond is expensive enough (tx_rollup_commitment_bond = 10,000 Tez) to discourage bad commitments. One bond can support any number of commitments from the same source on a single rollup. The bond is collected at the time that a given contract creates its first commitment on a rollup. It may be refunded by another layer-1 operation, once the last commitment on the rollup from its creator has been removed from the context (that is, after the finality and withdrawal period).

If a commitment is wrong (that is, its Merkle proof does not correspond to the correct execution trace of the layer-2 apply function), it may be rejected. A rejection operation for a commitment names one of the messages of the commitment, and includes a Merkle proof of the computation. It also includes the disputed commitment message, and the Merkle proof that the commitment exists in the compact commitment’s Merkle tree. A layer-1 node can then replay just the transfers of a single message to determine whether the rejection is valid. Because of the compact structure of commitments, a rejection also must include the predecessor message’s layer-2 context hash, as a starting point to verify the proof. And the withdrawal list must be included, so that the layer-2 context hash can be verified against the disputed message’s predecessor. A rejection must be included in a block within the finality period of the commitment.

It might be possible to create a message whose proof is too long to fit into a rejection operation. The limits we have imposed are intended to be long enough to avoid this, but it is possible that our limits are wrong. To handle this possibility, we impose a hard limit on proof size, tx_rollup_rejection_max_proof_size, which is less than the operation size limit. If a proof turns out to be greater than tx_rollup_rejection_max_proof_size, the entire message is treated as a no-op. To reject such a commitment, the rejection operation will contain a truncated proof. If the commitment committed to anything other than the state prior to applying the message, the rejection succeeds. So such messages are always treated as no-ops.

In the case of a valid rejection, half of the commitment bond goes to the rejector; the rest is burned.

Ticket withdrawals

Withdrawing a ticket from a rollup back to layer-1 is a three-phase operation.

First, a layer-2 operation is submitted requesting the withdrawal. This is actually implemented as a transfer with a destination that is a layer-1 address. As with any other transfer, it is included in a batch, which is included in an inbox, which gets a commitment. After that commitment is finalized, the next phase can begin.

Next, because commitments are stored Merkelized, a dispatch tickets layer-1 operation must be sent. This includes:

  1. The rollup and level that the withdrawals are in

  2. The message index in the inbox that contains the withdrawals.

  3. A Merkle proof that the hash of these withdrawals is included in the specified Merkelized commitment.

  4. The ticket contents and amounts for every ticket withdrawn in that message.

Because all of the ticket contents for every withdrawal in a message must fit into a single layer-1 operation, there are hard limits on the size and count of tickets. The limit on ticket size, tx_rollup_max_ticket_payload_size, is enforced at deposit time. The limit on withdraw count is tx_rollup_max_withdrawals_per_batch, and it is enforced by the layer-2 apply function. Withdrawals beyond this limit are treated as no-ops.

The “dispatch tickets” operation updates the table of tickets to transfer the tickets from the rollup to a layer-1 implicit account. It also stores a record in the layer-1 context indicating that the tickets for a message have been dispatched, so that the same dispatch cannot happen again. If a dispatch tickets operation is not sent before the tx_rollup_withdraw_period, the tickets withdrawn in the corresponding inbox are destroyed irrecoverably.

After the dispatch tickets operation, a transfer ticket layer-1 operation can be sent, which will transfer the tickets to their final smart contract. There is no deadline for this.

Getting Started

Note

We use shell variable to generalize command invocations. For instance, we write:

echo ${tx_rollup_address}

to emphasis that users are expected to provide the address of their transaction rollup.

Originating a Transaction Rollup

The tezos-client has a dedicated command that any implicit account holder can use to originate a transaction rollup.

tezos-client originate tx rollup ${tx_rollup_allias} from ${implicit_account_alias}

where tx is an abbreviation for transaction.

The origination of a transaction rollup burns ꜩ1.

A transaction rollup address is attributed to the new transaction rollup. This address is derived from the hash of the Tezos operation with the origination operation similarly to the smart contract origination. It is always prefixed by txr1. For instance,:

txr1YNMEtkj5Vkqsbdmt7xaxBTMRZjzS96UAi

is a valid transaction rollup address.

When using the tezos-client to originate a transaction rollup, it outputs the newly created address.

Starting a Rollup Node

Octez does not provide an official transaction rollup node. That being said, an experimental transaction rollup node is under development for testing and demonstration purposes.

To get the experimental transaction rollup node, one can build it from source. Following the official procedure is expected to be enough: the binaries will be available at the root of the repository after make.

Another approach is to use the Docker images provided by Octez, for instance the master image tag (see the Docker Hub). Note that we do not provide a specialized entrypoint to interact with the binaries. One needs to log into a container built on top of the image to use them.

For instance,

docker run -it --entrypoint=/bin/sh tezos/tezos:master_4435f908_20220706144700

provides a shell with the rollup node and client available in the PATH.

Note

Similarly to other Octez binaries like the baker, there exists a rollup node and client for each version of the Tezos protocol. You can use the 014-PtKathma binaries on testnets like Kathmandunet <https://jakartanet.tzkt.io/>_ (--network kathmandunet). Note that, compared to testnets like Mondaynet or Dailynet, Kathmandunet is using the same protocol constants as Mainnet, meaning the finality period is 40,000 blocks (about 2 weeks).

The first step towards being able to use the experimental transaction rollup node is to prepare its configuration file.

tezos-tx-rollup-node-014-PtKathma init ${mode} config \
      for ${tx_rollup_address_or_name} \
      --data-dir ${data_dir} \
      --rpc-addr ${rpc_address} \
      [additional options to decide which keys to use to sign which L1 operations]

(where ${rpc_address} will be the address of the RPC server provided by the rollup node)

The main decision to make here is to choose a mode for the rollup node, that is the set of actions a rollup node will perform.

At its core, the rollup node is a software component responsible for making a given transaction rollup progress by means of a set of dedicated Tezos layer-1 operations.

Finally, the rollup node comes with a concept of “modes” that decide the set of actions the rollup node is expected to perform (and, as a consequence, requires to provides certain keys).

For instance, the observer mode makes the rollup node passive. An observer node computes the state of the ledger of a transaction rollup, but does not make it progress on the layer-1. An observer mode can be useful to get a trustworthy source of truth w.r.t. the layer-2 context. The accuser mode is similar to the observer mode, with the caveat that if a erroneous commitment is posted on the layer-1, it will compute and publish a rejection operation. For the security of a transaction rollup to be enforced, it is therefore necessary that at least one honest accuser node is operating at all time. The batcher mode will enable a RPC that third-parties can use to submit layer-2 operations; the rollup node will then batch them together and post them on the layer-1 chain. The operator mode will enable all the features of the rollup node, including the injection of the maintenance operations of a transaction rollup (publishing, finalizing, removing commitments, dispatching tickets). Finally, the custom mode will allow advanced users to decide which set of features to enable.

Each mode will then send a specific set of layer-1 operations. These operations have to be signed by the public key of an implicit account that owns enough XTZ to pay for the inclusion fees.

These operations notably includes:

  • Submitting batches of layer-2 operations (enabled by providing the --batch-signer command-line argument)

  • Submitting commitments (enabled by providing the --operator command-line argument)

  • Finalizing (--finalize-commitment-signer) and removing (--remove-commitment-signer) commitments

  • Dispatching tickets whose withdrawals are authorized by a finalized commitments (--dispatch-withdrawals-signer)

  • Posting rejections (--rejection-signer)

These various command-line arguments accept the Tezos implicit account aliases registered in the ~/.tezos-client directory.

Note that the same keys can be used to sign several kind of layer-1 operations.

Note

For the rollup node to work better, it is a good practice to (1) to reveal the keys associated to these aliases before starting the rollup node, and (2) to use a dedicated key for batching and submitting layer-2 operations (--batcher-signer).

For instance,

tezos-tx-rollup-node-014-PtKathma init batcher config for ${rollup}  \
      --data-dir /tmp/tx-node \
      --rpc-addr ${rollup_node_rpc_server_address} \
      --batch-signer ${batcher}

will create a configuration file in /tmp/tx-node, for the transaction rollup identified by the variable ${rollup}.

The rollup node will expose a RPC server at the address provided with the rpc-addr argument. If omitted, the default value used by the rollup node is localhost:9999.

Once the configuration is ready, starting the rollup node is as simple as

tezos-tx-rollup-node-014-PtKathma --endpoint ${tezos_node_address} \
      run ${mode} for ${rollup_address_or_name} \
      --data-dir ${data_dir} \
      [--allow-deposit]

The --allow-deposit argument is required in case you want to make the rollup node post commitment to the layer-1. This is an additional layer of security (in addition to having to providing a signer key for the commitment operation thanks to the --operator argument), in order to reduce as much as possible the risk for users to have 10,000ꜩ frozen for a long period by accident.

The rollup node works by tracking the head of the layer-1 chain, using a Tezos node whose addressed is provided with the --endpoint argument. It is not possible to run a rollup node without a Tezos node available.

Warning

The rollup node has been developed with the assumption that it is executed on the same machine as the Tezos node it tracks. It may not work properly in another set-up (e.g., when --endpoint is the address of a remote, public Tezos node).

Depositing Assets on a Rollup

As discussed in this document, transaction rollups allow their users to exchange assets encoded as Michelson tickets.

An example of a smart contract that deposit tickets to a transaction rollup can be found in the integration tests of the feature.:

parameter (pair string nat tx_rollup_l2_address address);
storage unit;
code {
       CAR;
       UNPAIR 4;
       TICKET;
       PAIR;
       SWAP;
       CONTRACT %deposit (pair (ticket string) tx_rollup_l2_address);
       ASSERT_SOME;
       SWAP;
       PUSH mutez 0;
       SWAP;
       TRANSFER_TOKENS;
       UNIT;
       NIL operation;
       DIG 2;
       CONS;
       PAIR;
     }

The %default entrypoint of this contract takes four arguments: (1) the contents and (2) the quantity of a ticket string to mint, (3) the beneficiary of the minted ticket in the layer-2, that is a tz4 address, and (4) the transaction rollup address that is the target of the deposit.

tezos-client originate contract ${contract_alias} \
             transferring 0 \
             from ${alias} \
             running ${path_to_contract} \
             --burn-cap 0.401 --force

Once the contract is originated, it becomes possible to deposit arbitrary tickets to the layer-2.

Note that the tezos-client comes with facilities to generate and manipulate BLS keys (that are used to authenticate users on the layer-2, and to generate layer-2 addresses).

# generate a pair of public and secret keys
tezos-client bls gen keys ${alias}
# display the keys (including the ``tz4`` hash)
tezos-client bls show address ${alias}

So, to mint and deposit a ticket string whose contents is foobar, one can make the following contract call.

tezos-client transfer 0 from user to deposit_contract \
         --arg '(Pair "foobar" ${qty} "${tz4_address}" "${tx_rollup_address}")' \
         --burn-cap 0.068

Of course, more realistic use cases will require a more business-oriented logic for their deposit contracts. For instance, they will require the caller to provide XTZ tokens in exchange to mint tickets.

To deposit a ticket to a transaction rollup, a smart contract emits an internal transaction. The ticket is assigned a hash, that the user can retrieve in the operation metadata. This hash is used in the layer-2 operations to identify the tickets.

For instance, here is the metadata of the internal operation responsible for the deposit of our foobar ticket.:

Internal operations:
Internal Transaction:
  Amount: ꜩ0
  From: KT1D9WLMFRndmrYthHruFudcXKvQkFhmW2KM
  To: txr1aDUUQK7mT31PQbTPFKXFMZgsd7qNq3YkV
  Entrypoint: deposit
  Parameter: { Pair (Pair 0x01321183f2d68b3ce59d2b89e40609079d132969ed00 (Pair "foobar" 100))
                    0xf4bdd875da3fc06d56f9b1f5ad9d603d82683834 ;
               string }
  This transaction was successfully applied
  Consumed gas: 2525.576
  Ticket hash: expruTnG5XeYjtLaCBwcEku1Xj4Po8caDWcQdTDhaaXMo3FHRfzCb7
  Consumed gas: 2525.576

The line of interest is the penultimate line, starting with Ticket hash:.

Exchanging Assets inside a Rollup

In addition to an experimental transaction rollup node, we also provide an experimental client to interact with said node.

In particular, the preferred way to post layer-2 operations to the layer-1 chain is to use a node configured to batch and submit layer-2 operations.

We list the main commands that are of interest when it comes to demonstsrate how rollup works.

To inspect the balance of a layer-2 address for a given ticket hash, the get balance command can be used.

tezos-tx-rollup-client-014-PtKathma get balance for alice of ${ticket_hash}

Note

There is no command to gather the list of ticket hashes a given layer-2 address owns.

To transfer a ticket to another address on the layer-2, the transfer command can be used.

tezos-tx-rollup-client-014-PtKathma transfer ${qty} \
      of ${ticket_hash} \
      from ${src} to ${dst} \
      [--endpoint ${tx_rollup_node_address}]

The --endpoint argument can be omitted if the rollup node is using the default RPC server address, that is localhost:9999.

Here, the source has to be an alias identifying a BLS pair of keys generated with tezos-client bls gen key, while the destination can either be an alias, or a tz4 address directly.

Similarly to Tezos, a transaction rollup also has a notion of L2 blocks “levels” starting from 0. To get the content of a rollup L2 block, one can use the following command.

tezos-tx-rollup-client-014-PtKathma get block ${block_id}

where block_id can either be head (the latest rollup block), a level (0, 1, etc.), or a Tezos block hash.

Finally, it is also possible to “withdraw” a ticket from a transaction rollup, and to inject it back in the layer-1.

tezos-tx-rollup-client-014-PtKathma withdraw ${qty} of ${ticket_hash} from ${l2_src} to ${l1_dst}

Similarly to the transfer command, l2_src has to be an alias to a BLS pair of keys, while l1_dst can either be an alias or an implicit account address directly.

If a rollup node is run using the mode operator, then once the commitment related to a withdraw order is finalized, the rollup node will “dispatch” the ticket to the l1_dst address. Once this is done, the owner of l1_dst can use a dedicated tezos-client command to transfer their tickets to a smart contract.

To do so, it is necessary to provide the layer-1 with the actual contents of the ticket, not just the ticket hash.

tezos-client transfer ${qty} tickets \
      from ${src} to ${sc_dst} \
      with entrypoint ${ep} \
      and contents ${micheline_contents} \
      and type ${ty} and ticketer ${ticketer}

For instance, to transfer the ownership of our foobar ticket, micheline_contents='"foobar"' (the quotes are necessary for it to be interpreted as a micheline string), and ty=string.

To retrieve the values of micheline_contents, ty and ticketer, the following command can be used.

tezos-tx-rollup-client-014-PtKathma rpc get "/context/head/tickets/${ticket_hash}"

Besides, the entrypoint ep of sc_dst needs to expect a ticket ty as its input.