Search
K

Using the Espresso Sequencer

This section describes the recommended architecture for an optimistic rollup integrated with the Espresso Sequencer. Additions, deletions, and changes are highlighted in comparison with the typical optimistic rollup architecture.

Components

Generalized optimistic rollup architecture, modified to use Espresso Sequencer
The Espresso Sequencer brings two new components into the picture:
  • The Espresso Sequencer replaces the rollup-specific sequencer. It provides a similar functionality of batching transactions into blocks and committing to an ordering, but the ordering is now determined by the HotShot consensus protocol running on thousands of nodes, so the sequencer is decentralized, and the immutability of its ordering is guaranteed by a large, slashable economic stake.
    The Espresso Sequencer also provides an additional feature that most rollup sequencers do not. The Tiramisu DA Protocol provides data availability for all blocks output by the sequencer. This availability is guaranteed by the same economic stake that guarantees finality, so it is reliable, and rollups have the option of using Tiramisu to replace Ethereum as their data availability layer. Tiramisu is designed to provide higher throughput and lower fees than Ethereum's data availability layer.
  • The Sequencer Contract runs a HotShot client on the layer 1 blockchain, verifying the finality of each sequenced block and storing a binding commitment to the order of blocks decided by consensus. As described in the following sections, rollup contracts on the same layer 1 can use this contract to relate their state transitions to the committed block order.

Transaction Flow

The transaction flow with the Espresso Sequencer is very similar to the transaction flow with a rollup-specific sequencer. The main difference is in how sequenced blocks get disseminated. With the rollup-specific sequencer, rollup nodes are free to choose from where they fetch sequenced blocks: from the rollup contract on layer 1, or directly from the sequencer. The Espresso Sequencer, however, does not send sequenced blocks to each rollup contract: doing so would be expensive, and is not even necessary for all rollups. Instead, the Espresso Sequencer only sends sequenced blocks to one place — the sequencer contract — and it only sends a commitment to each block, at that: since the Espresso Sequencer provides its own data availability solution, the default is not to store entire blocks on the layer 1.
With this new flow, rollup nodes may choose how they get notified of new blocks: either by streaming them directly from the sequencer or streaming events from the sequencer contract. But in either case, they must download the block contents from the sequencer's data availability API. Furthermore, rollups that still wish to use the layer 1 for data availability are responsible for sending the necessary data to the layer 1 once it has been sequenced.
It is important to note that while the transaction flow changes in terms of how transactions get sequenced and how rollups consume that sequence, there is no change in what a rollup does with a block after it is sequenced. This means that the execution layer, which makes up the bulk of the complexity of many rollups, can be used completely unmodified with the Espresso Sequencer.
The following sections describe how the components of the rollup must be modified to support the updated transaction flow and retain security.

Fraud Proofs in a Shared-Sequencer World

The bulk of the changes relate to the fraud proofs, requiring additions to the proposer, the challenger, and the rollup contract's challenge mechanism. While the details of challenge mechanisms vary widely between optimistic rollup instantiations, the general concept is universal: a proposer has sent a state root to the contract, and a challenger has claimed that the correct state root is something different. The challenger must then provide evidence that the proposed state root is incorrect, and the proposer may be given a chance to produce evidence defending their proposal.
There are two ways a state root can be incorrect:
  1. 1.
    The proposer executed the wrong transactions.
  2. 2.
    The proposer executed the right transactions incorrectly.
Therefore, any challenge mechanism must first check that the proposer and challenger both agree on the correct transactions to execute, according to the ordering determined by the sequencer. If they do not, then whichever one has executed the right transactions trivially wins the challenge. Once the proposer and challenger have agreed on the sequence of transactions being executed, the challenger must prove that the proposed state root is not the correct result of applying the state transition function to those transactions.
The first part, agreeing on the sequence of transactions, is generally a fairly straightforward check against the committed sequence. The second part, checking the proposer's computation of the state root, is where most of the complexity lies, since it involves proofs or interactive challenge games that encompass the low-level semantics of a complex VM. Luckily, this part of the challenge remains completely unchanged when integrating with the Espresso Sequencer, and only the mechanism for agreeing on the sequence of transactions needs to change.
With a rollup-specific sequencer, reaching this agreement may only consist of a simple lookup, since the sequencer is sending blocks (or block commitments) directly to the rollup contract, and those blocks contain only transactions that are relevant to this particular rollup. Therefore the contract simply reads the last sequenced block directly from its own storage and compares it with the blocks claimed by the proposer and the challenger (or compares commitments, in some rollups). If the proposer's claimed block does not match the expected block, the contract can immediately consider the challenge successful. If the challenger's claimed block does not match, the contract can immediately reject the challenge.
Things are a bit more complicated when the sequencer is
  1. 1.
    decentralized, and
  2. 2.
    shared among multiple rollups
as the Espresso Sequencer is. Blocks produced by the Espresso Sequencer may contain transactions meant for other rollups, which must be filtered out, and the process of checking that a block has been sequenced is more complicated than simply reading from the rollup contract.

Handling Multi-Rollup Blocks

Let's tackle the first problem first. The rollup contract must be able to check that the lists of transactions executed by the proposer and challenger match a certain block produced by the sequencer. However, these transactions may not be the entirety of any block, since the Espresso Sequencer will produce blocks that combine transactions for multiple rollups. Thus, the contract needs a way to check that the proposed list of transactions contains all transactions from this rollup in a particular block ("completeness"), only those transactions ("inclusion"), and respects the order dictated by the sequencer. It must do this without necessarily having access to the whole sequencer block.
Instead of the whole sequencer block, the contract will work with short, binding commitments to sequencer blocks. The proposer and challenger can each provide a commitment C to the block they have executed along with a proof of inclusion and completness relative to C for the list of transactions they executed. The contract then simply verifies that proof and checks that the commitment C was actually sequenced at the required position in the chain (see the next section).
To permit an efficient proof of inclusion and completeness, C is computed using a novel commitment scheme based on KZG polynomial commitments. This commitment scheme is still under active development, and more details will be published soon. The Espresso Sequencer SDK will include predefined functions for working with these commitments and doing proofs about them, so the complexity will be abstracted away from rollup integrations.
There is a subtle point regarding ordering. The inclusion and completeness proofs provided by the block commitment scheme enforce an ordering, which prevents an attack where a malicious proposer executes the right transactions in the wrong order, arriving at the wrong state root which was nonetheless correctly computed, and cannot be challenged. However, some rollups may want to allow the proposer to execute transactions in a different order than the sequencer dictated. Such rollups can have the contract check the ordering proof, and then apply a deterministic reordering function before proceeding to the execution phase of the challenge protocol, thus ensuring that the transactions are executed in an order which respects VM-specific constraints. It may be beneficial to do so, because the Espresso Sequencer is agnostic to the semantics of any particular rollup and does not enforce ordering constraints at the sequencer level.
However, most rollups can probably consider this step optional. In practice, sequencer nodes will not build blocks themselves but will work with third-party builders, who are aware of rollup-specific semantics. These builders will seek to profit from the blocks they build, either by requiring users to pay fees or via arbitrage, and thus they will have an economic incentive to fill their blocks with valid transactions. In most cases, then, it will be sufficient for rollups to reject transactions that are "out of order" (by executing them as no-ops), which can be somewhat simpler than unconditionally sorting or reordering a whole block.

Checking That a Block Has Been Sequenced

The last unsolved problem is how the rollup contract can check that a block commitment C, which has been proven to correspond to a certain list of rollup transactions, has actually been sequenced at a given position in the chain.
With a centralized sequencer, this check is usually straightforward. The sequencer may have a known public key, for which it can produce a signature on any given block. Or, the sequencer may own the only Ethereum account authorized to call the function on the rollup contract which stores a new block on the layer 1. In most cases this smart contract is used as the single source of truth, even if multiple parties are allowed to sequence transactions via escape hatches.
With a decentralized sequencer, things are a bit more complicated. The Espresso Sequencer is operated by a consensus protocol, where thousands of nodes act as peers, and no one node has the privilege of unilaterally sequencing a block. A block is considered finalized if this network of peers reaches consensus on the decision to sequence that block at the next available position in the chain. Luckily, the process of reaching consensus produces artifacts which can be independently verified by non-participants of consensus, including smart contracts. These artifacts are called quorum certificates, or QCs.
A quorum certificate shows that a vote took place to sequence a certain block in a certain view, and that consensus nodes controlling a sufficient fraction of the total stake voted yes. The certificate contains an aggregated signature from those that voted. Due to the nature of distributed consensus, it actually requires three consecutive rounds of voting to finalize a block, so a chain of three consecutive valid QCs is definitive evidence that a block (and all previous blocks) has been finalized. Thus, any client — such as a rollup state transition verifier — wishing to verify that a block has been sequenced must obtain and validate a chain of three consecutive QCs.
Luckily, the work required to verify finality for each sequenced block is the same across all rollups using the Espresso Sequencer, and can be shared. This is where the Sequencer Contract comes in. It is a single contract that receives commitments to sequenced blocks along with QCs proving the finality of those commitments, verifies the QCs, and stores a commitment to the finalized order of blocks. Anyone, including operators of the sequencer itself and operators of any rollup, can append a commitment to a newly sequenced block to the contract, simply by providing a valid chain of QCs, which can be obtained from any honest consensus node.
Once the sequencer contract has done the hard work of checking QCs to verify that a block is finalized, anyone else can check finality simply by checking that the block is included in the sequence committed to by the contract. The contract uses a Merkle tree to commit to the sequence of finalized blocks, storing the root of this tree, so this check is usually done by Merkle proof.
In a typical optimistic rollup, there are several parties who need to verify that a certain block has been sequenced:
  • The proposer and challenger must verify that a block has been sequenced before using it to compute a new state root. They can do this easily by waiting for the sequencer contract to verify the block and emit an event confirming that the block has been finalized. However, they may opt to confirm the block faster than the sequencer contract (thus providing preconfirmations to users) by downloading and verifying the QCs themselves. There are two options for verifying QCs:
    • Use the Espresso Sequencer SDK to run the same QC verification algorithm that HotShot consensus uses.
    • Participate in consensus as a sequencer node. The sequencer node interface exposes a stream of verified blocks that the proposer and challenger can then consume.
  • The rollup contract can read the sequence commitment directly from the sequencer contract. It can then require as part of the challenge protocol that the proposer and challenger provide Merkle proofs showing that the block commitment C from their inclusion/completeness proofs exists at a certain position in the Merkle tree.

Transaction Format

From the sequencer's perspective, a transaction is just an array of bytes with an integer attached to identify the rollup that the transaction belongs to. Therefore, rollups using the Espresso Sequencer can keep their existing transaction format. The only change required is that, if the rollup provides a service like JSON-RPC that accepts transaction submissions, it must be modified to attach the rollup identifier when forwarding the transaction to the Espresso Sequencer.
The rollup identifier works much like an EVM chain ID. Each rollup is completely free to choose whatever identifier they want. However, it is strongly recommended to choose an identifier that no other rollup is using, because the rollup identifier determines which transactions are included in the completeness proof when filtering a multi-rollup block. Therefore, if you choose a rollup identifier which is already in use, your rollup will be forced to execute not only its own transactions but also all of those intended for the rollup with the same ID.

Downloading Data

Once a block has been sequenced, various rollup participants will need to download it or a subset of it from the Tiramisu data availability layer. We consider three main use cases:
  • A node wants to get notified when a new block is sequenced
  • An end user wants a proof that a particular transaction has been included in a block, but they don't want to download the entire block. This is a way of obtaining fast finality, because once a transaction is included in a sequencer block, it is guaranteed that the rollup will eventually execute it. (This follows from completeness proofs.)
  • A proposer or challenger wants to download just the subset of a block pertaining to the relevant rollup, with a proof that the server has provided the correct transactions in the correct order.
All of these use cases can make use of the availability API. Any HotShot node or client can provide this API by plugging in the modular HotShot query service, and Espresso Systems will operate at least one node which serves this API at a public URL.

New Block Notifications

The availability API provides several streaming endpoints, which a client can connect to using a WebSockets client. These endpoints allow clients to receive information when a new block is sequenced or becomes available in the DA layer, without excessive polling. The streaming endpoints are:
  • /availability/stream/leaves/:height
    Stream blocks as soon as they are sequenced, starting from :height (use 0 to start from genesis). The stream yields leaves, which contain metadata about finalized blocks such as the identity of the node that proposed them, the signature from nodes that voted for the block, and so on. This is the fastest way to learn of new blocks being sequenced, but because Tiramisu disseminates data asynchronously, the actual contents of the block may or may not be included in this stream.
  • /availability/stream/blocks/:height
    This endpoint is similar to the leaves stream, but it waits until a block is fully available for download from Tiramisu before notifying the client. Each entry in the stream is a block with its full contents.
  • /availability/stream/headers/:height
    This endpoint is similar to the blocks stream, in that it will not notify the client of a new block until the full contents of that block are ready for download. However, it will not send the full contents of the block. It will only send the block header, which contains metadata like the block height and timestamp. This is a good way to wait until a block is sequenced, at which time you can use some of the finer-grained endpoints discussed below to download only a subset of the block contents, saving bandwidth.
In the following sub-sections, it is assumed that clients of the availability API will use one of these streams to wait for more blocks to be sequenced before querying for the specific data they are interested in.

Single-Transaction Finality

The typical flow for this use case is
  1. 1.
    A user builds a transaction using rollup-specific client software.
  2. 2.
    The user saves the hash of their transaction and then submits it.
  3. 3.
    The user queries the availability API for proof that a transaction with the same hash has been included in a block.
  4. 4.
    The user checks that the resulting block has in fact been finalized.
  5. 5.
    The user verifies the proof, at which point it is guaranteed that the rollup will eventually execute the transaction.
The query for a proof uses the endpoint GET /availability/transaction/hash/:hash, replacing :hash with the tagged base 64 encoding of their transaction hash. If the requested transaction has in fact been sequenced, the response is a JSON object with a key proof, containing a proof of inclusion in a block, as well as metadata about the block, such as height and block_hash. It does not include the full block contents, so the bandwidth usage is minimal.
The user can check that block_hash has been sequenced as described above: either by checking for the corresponding event from the sequencer contract, or by downloading the relevant QCs and verifying them manually. The QCs can be obtained using the endpoint GET /availability/leaf/:height, and the Espresso Sequencer SDK will include functionality for verifying them.
Once the user has confirmed that the block is finalized, the only thing left to do is to verify the proof that the transaction of interest was included in that block. This is a namespace KZG inclusion proof just like the ones used in the state transition proofs, and the SDK will include functionality for verifying it.

Rollup's Subset of a Block

Rollup proposers and challengers must download transactions relevant to their rollup in order to compute new state roots, but it would be wasteful to download entire blocks, which may contain many transactions from other rollups. However, they do not generally want to trust the availability service to provide the correct subset of transactions. The desired flow is:
  1. 1.
    A proposer or challenger queries for the relevant subset of the next block.
  2. 2.
    The availability service responds with the desired transactions and a proof of completeness and inclusion.
  3. 3.
    The node verifies that the block has been sequenced, verifies the completeness/inclusion proof, and then executes the transactions.
The node's query has the form GET /availability/block/:height/namespace/:rollup-id. On success, this returns a JSON object with two keys:
  • block, a commitment to the desired block
  • proof, which includes within it a list of transactions and proves their inclusion and completeness in block.
The node checks that block has been sequenced at :height as described above: either by checking for the corresponding event from the sequencer contract, or by downloading the relevant QCs and verifying them manually. The QCs can be obtained using the endpoint GET /availability/leaf/:height, and the Espresso Sequencer SDK will include functionality for verifying them.
proof is a KZG namespace proof just like the ones used in the state transition proofs, and the SDK will include functionality for verifying it. After verifying the proof, the node is assured that block :height includes all the returned transactions, in the correct order, and no other transactions with ID :rollup-id. It can then execute the transactions to compute the next rollup state.

Data Availability

The Espresso Sequencer provides Tiramisu, a scalable and secure data availability solution which ensures that all sequenced blocks will be available for rollup participants to download and execute. This ensures that any participant can reconstruct the state of the rollup.
While Tiramisu with ETH restaking can be just as secure as Ethereum DA, using it as the only source of data availability technically makes a rollup into a validium. Some rollups in the Ethereum ecosystem place a high value on persisting all of their data to Ethereum. The Espresso Sequencer supports both approaches. Any rollup may continue to use Ethereum for DA in addition to Tiramisu simply by having a rollup node send each block produced by the Espresso Sequencer to a layer 1 contract. If your existing rollup already uses Ethereum DA, this is actually one less change you have to make!