Author:Token Analyst
Author: NIC Lin, Head of Taipei Ethereum Meetup
Title of the original text: "Introduction to the Force Inclusion Mechanism of Rollup"
Just yesterday, an event that shocked countless people occurred: the Ethereum Layer 2 Linea launched by the parent company of Metamak, Consys, took the initiative to shut down. The official stated that the purpose of doing so was to reduce the impact of the Velocere hacking incident. And this inevitably reminds people of the previous incident where the BSC Chain (BNB Chain) was shut down under official coordination in order to reduce the losses of hacker attacks. Whenever people talk about such things, they doubt the decentralized value advocated by Web3.

Of course, the core reason for the above-mentioned events is more due to the imperfect infrastructure itself, which is not decentralized enough: if a chain is decentralized enough, then it should not be said to stop and stop. Due to the unique construction of Ethereum's second layer, most Layer2 relies on centralized sequencers. Although there have been more and more discussions on decentralized sorters in recent years, considering the purpose and structure of the existence of the second layer, we can generally assume that Layer2's sorters are unlikely to have much decentralization and may not even be as decentralized as the BSC chain. If that's really the case, what should we do?
In fact, for the second layer, the most direct harm caused by the lack of decentralization of the sorter lies in its anti censorship and activity. If there are very few entities (Sequencers) handling transactions, then they have absolute power over whether or not they serve you: if they want to reject you, they may reject you, and you may have no choice. How to solve the anti censorship problem of Layer2 is clearly an important topic.
In the past few years, various solutions have been proposed by major Ethereum Layer 2 to combat censorship issues, such as Loopering and Gate, StarkEx's mandatory withdrawal and escape pod functions, Arbitrum, and other OP Rollup's Force Inclusion functions. These methods can all create checks and balances on Sequencers under certain conditions to prevent them from unreasonably rejecting any user's transaction requests.
In today's article, NIC Lin from the Taipei Ethereum Association personally experimented with the anti censorship transaction function of four mainstream Rollups, and analyzed the mechanism design of Force Inclusion in depth from the aspects of workflow and operation methods. This is particularly valuable for the Ethereum community and large holders of huge assets.
Transaction Review and Force Inclusion
Censorship Resistance is crucial for a blockchain. If a blockchain can arbitrarily review and reject transactions initiated by users, it is no different from a Web2 server. Ethereum's current transaction resistance to censorship comes from its numerous validators. If someone wants to review Bob's transactions and prevent them from being linked, they can either try to buy most of the validators on the network or spam the entire network, constantly sending out junk transactions with higher transaction fees than Bob to seize block space. Regardless of the method, the cost will be very high.
Note: In Ethereum's current PBS architecture, the cost of reviewing transactions will be significantly reduced. You can refer to cooperating with OFAC to review the block ratio of Tornado Cash transactions. The current ability to resist censorship relies on independent validators and relays outside of OFAC and government jurisdiction.
But what about Rollup? Rollup does not require a bunch of validators to ensure security, even if it only has a centralized role (Sequencer) to produce blocks, it is as secure as L1. But security and anti censorship are two different things. Even if a Rollup is as secure as Ethereum, with only one centralized sequencer, any user's transaction can be reviewed.

Sequencer can refuse to process user transactions, resulting in user funds being withheld and unable to leave the Rollup
Force Inclusion mechanism
Instead of requiring Rollup to have a large number of decentralized sequencers, it is better to directly utilize L1's anti censorship ability:
Originally, the Sequencer was supposed to package transaction data and send it to the L1 Rollup contract. It would be better to include a design in the contract that allows users to insert transactions into the Rollup contract on their own. This mechanism is called "Force Inclusion". As long as the Sequencer cannot review users at the L1 level, it cannot prevent users from forcibly inserting transactions in L1. In this way, Rollup can inherit L1's ability to resist censorship.

Sequencer cannot review L1 transactions of users unless they incur high costs
How should mandatory transactions take effect?
If the transaction is allowed to be directly written into the Rollup contract through Force Inclusion (i.e. immediately effective), the status of Rollup will immediately change. For example, if Bob inserts a transaction that "transfers 1000 DAI to Carol" through the Force Inclusion mechanism, and the transaction takes effect immediately, Bob's balance in the latest status will be 1000 DAI less and Carol will be 1000 DAI more.

If Force Inclusion can directly write the transaction into the Rollup contract and take immediate effect, the status will immediately change
If the Sequencer is also collecting transactions off the chain at this time and sending the next batch of transactions to the Rollup contract, it may be affected by Bob's forced insertion and immediately effective transactions. This kind of problem needs to be avoided as much as possible, so Rollup generally does not make Force Inclusion transactions take effect immediately. Instead, it first asks the user to insert the transaction into the waiting queue on L1 and enter the "ready" state.
When the Sequencer packages off chain transactions and sends them to the Rollup contract, it chooses whether to include the aforementioned transactions in the transaction sequence. If the Sequencer continues to ignore these transactions that are in a "ready" state, after the window period ends, users can forcibly insert these transactions into the Rollup contract.

The Sequencer can decide when to "incidentally receive" transactions waiting in the queue

The Sequencer can still refuse to process transactions in the waiting queue

If the Sequencer refuses for a long time, anyone can use the Force Inclusion function to forcibly insert the transaction into the Rollup contract after a period of time
Next, we will sequentially introduce the Force Inclusion mechanism implementations of four well-known Rollups, including Optimism, Arbitrum, StarkNet, and zkSync.
Optimism's Force Inclusion Mechanism
Firstly, introduce the Deposit process of Optimism. This Deposit not only refers to depositing money into Optimism, but also includes sending information sent by users to L2. After receiving a newly stored message, the L2 node will convert the message into an L2 transaction for execution and deliver it to the recipient specified in the message.

Message from L1 Deposit to L2 by User
L1CrossDomainMessenger contract
When a user wants to deposit ETH or ERC-20 tokens into Optimism, they will interact with the L1 StandardBridge contract on the front-end webpage, specifying how much amount to deposit and which L2 address to receive these assets from.
The L1StandardBridge contract will pass messages to the next layer's L1CrossDomainMessenger contract, which mainly serves as a component for communication between L1 and L2. Through this universal communication component, L1StandardBridge communicates with L2StandardBridge on L2 to determine who can mint tokens in L2 or who can unlock tokens from L1.
If developers need to develop a contract that communicates and synchronizes status between L1 and L2, they can build it on top of the L1CrossDomainMessenger contract.

The user's message is transmitted from L1 to L2 through the CrossDomainMessenger contract. Note: In some of the images in this article, CrossDomainMessage is written as CrossChainMessar
Optimism Portal Contract
The L1CrossDomainMessenger contract will then send messages to the lowest level OptimismPortal contract. After processing, the OptimismPortal contract will throw an event called TransactionDeposited, with parameters including "sender", "receiver", and related execution parameters.
Next, the Optimism node in L2 will listen to the Transaction Deposited event thrown by the Optimism Portal contract and convert the parameters in the event into an L2 transaction. The initiator of this transaction will be the "sender" specified in the Transaction Deposited event parameters, and the transaction receiver will be the "receiver" specified in the event parameters. Other transaction parameters are also derived from the parameters in the above events.

The L2 node will convert the Transaction Deposited event parameter of OptimismPortalemit into an L2 transaction
For example, this is a transaction where a user deposits 0.01 ETH through an L1StandardBridge contract. This message and ETH are transmitted all the way to the Optimism Portal contract (address 0xbEb5... 06Ed), and a few minutes later it is converted into an L2 transaction:
The initiator of the message is the L1CrossDomainMessenger contract; The recipient is the L2CrossDomainMessenger contract on L2; The message is that L1StandardBridge has received a deposit of 0.01 ETH from BoB. Afterwards, some processes will be triggered, such as issuing 0.01 ETH to L2StandardBridge, which will then be transferred to Bob.
How to trigger it specifically
When you want to force a transaction to be included in the Optimism Rollup contract, the effect you need to achieve is to ensure that a transaction initiated and executed from your L2 address on L2 can be executed smoothly. At this time, you should directly submit the message to the Optimism Portal contract using your L2 address (note that the Optimism Portal contract is actually on L1, but the OP address format is the same as the L1 address format. You can directly call the above contract using an L1 account with the same address as the L2 account).
The initiator of the L2 transaction converted by the Transaction Deposited event thrown by the contract will be your L2 account, and the transaction format will be consistent with normal L2 transactions.

In the L2 transaction converted from the Transaction Deposited event, the initiator will be Bob himself; The recipient is a Uniswap contract; And it will be accompanied by a specified ETH, just like Bob initiating an L2 transaction himself

If you want to call the Force Inclusion function of Optimism, you need to directly call the deployTransaction function of the Optimism Portal contract and fill in the parameters of the transaction you want to execute in L2
I conducted a simple Force Inclusion experiment, and this transaction aims to achieve the following: self transfer (0xeDc1... 6909) using my address on L2, accompanied by a text message of "Force Inclusion".
This is the L1 transaction where I executed the deployTransaction function through the Optimism Portal contract. I can see that in the Transaction Deposited event it throws, both from and to are myself

The remaining values in the Opaque Data column are encoded with information such as "how much ETH does the person calling the deposit Transaction function carry", "how much ETH should the L2 transaction initiator send to the receiver", "L2 transaction GasLimit", and "Data to the L2 receiver".
After decoding the above information, we will obtain:
"How much ETH did the person calling deposit Transaction attach": 0, because I did not store ETH from L1 to L2;
"How much ETH should the initiator of L2 transactions send to the receiver": 5566 (wei)
GasLimit for L2 transactions: 50000
"Data for L2 receiver": 0x666f72636520696e636c7573696f6e, which is the hexadecimal encoding of the string "force inclusion"
Not long after, a converted L2 transaction appeared: an L2 transaction in which I transferred money to myself, with an amount of 5566 wei and a Data string of "force inclusion". Moreover, it can be noted that in the second to last row of the graph, the TxnType (transaction type) in Other Attributes shows system transaction 126 (System), indicating that this transaction was not initiated by myself in L2, but was converted from the Deposited event of L1 transaction.

L2 transaction converted from
If you want to call the L2 contract and send different data through Force Inclusion, it is nothing more than filling in the parameters one by one in the previous deposit transaction function. However, remember to use the same L1 address as your own L2 account to call the deposit transaction function, so that when the Deposited Event is converted to an L2 transaction, the initiator is your L2 account.
SequencerWindow
The Optimism L2 node mentioned earlier converts a Transaction Deposited event into an L2 transaction. In fact, this Optimism node refers to the Sequencer, as it is related to transaction sorting. Therefore, only the Sequencer can decide when to convert the aforementioned event into an L2 transaction.
When listening to a TransactionDeposited event, the Sequencer may not immediately convert the event into an L2 transaction, but there may be a delay, and the maximum value during this period is called the SequencerWindow.
At present, the Sequencer Window on the Optimism main network is 24 hours, which means that when a user deposits a money or Force Inclusion transaction from L1, the worst case scenario is that it is only 24 hours later before it is included in the L2 transaction history.
The Force Inclusion Mechanism of Arbitrum
In Optimism, the Deposit operation of L1 will throw a Transaction Deposited event, and the rest is waiting for the Sequencer to include the above operation; But in Arbitrum, operations that occur in L1 (such as saving money or sending messages to L2) will be stored in a queue on L1 instead of simply throwing an event.
The Sequencer will be given a period of time to include the transactions in the above queue in the L2 transaction history. If the Sequencer does not take any action at the end of the time, anyone can go and complete it on behalf of the Sequencer.

Arbitrum will maintain a queue in the L1 contract. If the Sequencer does not actively process transactions in the queue, anyone can force the transactions in the queue to be included in the L2 transaction history when the time is up
In the design of Arbitrum, operations such as deposits that occur on L1 must go through a Delayed Inbox contract, and as the name suggests, these operations will be delayed and take effect; The other contract is the Sequencer Inbox, which is the direct location where the Sequencer uploads L2 transactions to L1. Every time the Sequencer uploads L2 transactions, they can also retrieve some pending transactions from the Delayed Inbox and write them down in the transaction history.

When the Sequencer writes a new transaction, it can also take out the transaction from DelayedInbox and write it together with complex designs and reference materials that can be used for analysis
If readers directly refer to the official chapter of Arbitrum on Sequencer and Force Inclusion, they will see that it mentions how Force Inclusion operates roughly, as well as some parameter and function names:
The user first goes to the DelayedInbox contract to call the sendUnsignedTransaction function. If the Sequencer has not been indexed within about 24 hours, the user can call the ForceInclusion function of the SequencerInbox contract. Then, the official website of Arbitrum did not attach the link to the function in the documentation, so they had to look at the corresponding function in the contract code themselves.
When you find the sendUnsignedTransaction function, you realize that you have to fill in the nonce value and maxFeePerGas value yourself. Which address is the nonce from? Which network is maxFeePerGas on? How to fill it out better? There is no file reference, not even Natpsec. Then you will also find a bunch of functions that look similar in the Arbitrum contract:
sendL1FundedUnsignedTransaction、sendUnsignedTransactionToFork、sendContractTransaction、sendL1FundedContractTransaction, There is no file to tell you the differences between these functions, how to use them, or how to fill in the parameters, not even Natpsec.
You try to fill in the parameters and send out the transaction with a trial and error mentality, hoping to find the correct usage through trial and error. However, you find that all these functions will use your L1 address as AddressAliasing, resulting in a completely different sender address when initiating the transaction on L2. As a result, your L2 address remains motionless.
sendL2Message
Later, when I accidentally clicked on Google Search, I found out that Arbitrum had its own Tutorial library, which had scripts demonstrating how to send L2 transactions from L1 (meaning Force Inclusion). However, the functions it listed were not any of the ones mentioned above, but rather a function called sendL2Message, and the message parameter was actually a transaction signed with an L2 account?
Who would have known that the message sent to L2 through Force Inclusion would actually be a signed L2 transaction? And there are no files or Natspec explanations on when and how to use this function.
Conclusion: It is inconvenient to manually generate a mandatory transaction for Arbitrum. It is recommended to follow the official Tutorial to run the Arbitrum SDK. Unlike other Rollups, Arbitrum does not have clear developer documentation and code annotations, and many functions lack explanations for their purpose and parameters, resulting in developers spending more time than expected to access and use them. I also asked the people at Arbitrum Discord, but did not get a satisfactory answer.
When asked on Discord, the other party only asked me to check the sendL2Message and did not want to explain the functionality of other functions (even the sendUnsignedTransaction mentioned in the Force Inclusion document), its purpose, how to use it, and when to use it.
The ForceInclusion Mechanism of StarkNet
Unfortunately, StarkNet currently does not have a ForceInclusion mechanism. There are only two articles discussing Censorship and ForceInclusion on official forums.
Unable to prove failed transactions
The above reason is actually because StarkNet's zero knowledge proof system cannot prove a failed transaction, so it cannot allow Force Inclusion. Because if someone maliciously (or unintentionally) Force Includes a failed and unproven transaction, StarkNet will get stuck directly: because after the transaction is forcibly received, the Prover must prove the failed transaction, but it cannot prove it.
StarkNet is expected to introduce the function of proving failed transactions in v0.15.0, and should be able to further implement the Force Inclusion mechanism thereafter.
ForceInclusion mechanism of zkSync
The L1->L2 message transmission and Force Inclusion mechanism of zkSync are both carried out through the requestL2Transaction function of the MailBox contract. The user specifies the L2 address, calldata, number of additional ETHs, L2GasLimit value, etc. The requestL2Transaction combines these parameters into an L2 transaction, which is then placed in the PriorityQueue. When the transaction is packaged and uploaded to L1 (through the commitBatches function), the Sequencer will indicate how many transactions need to be taken out of the priority queue and included in the L2 transaction record.
ZkSync is similar in form to Optimism in Force Inclusion, as it calls related functions using the initiator's L2 address (consistent with L1 address) and fills in information (called by, calldata, etc.), rather than filling in a signed L2 transaction like Arbitrum; But in terms of design, it is similar to Arbitrum, which maintains a queue in L1, and the Sequencer takes out pending transactions directly submitted by users from the queue and writes them into the transaction history.

If you use the official bridge of zkSync to Deposit ETH, such as this transaction, it is to call the requestL2Transaction function of the MailBox contract, which will put the L2 transaction of this Deposit ETH into the priority queue and throw a NewPriorityRequest event. Because the contract encodes the L2 transaction data into a string of bytes, it is not easy to read. If you change to look at the parameters of this L1 transaction, you will see that the recipient of L2 in the parameters is also the initiator of the transaction (because it is deposited to oneself). Therefore, after a while, when this L2 transaction is taken out of the priority queue by the Sequencer and included in the transaction history, it will be converted into a transaction transferred to oneself on L2, and the transferred amount is the ETH amount carried by the initiator in the Deposit ETH transaction of L1.

In L1Deposit transactions, both the initiator and receiver are 0xeDc1... 6909, the amount is 0.03ETH, and calldata is empty

On L2, there will be a transaction with 0xeDc1... 6909 that I transferred to myself, and the transaction type (TxnType) is 255, which is the system transaction
Next, I directly called the requestL2Transaction function of zkSync, just like the forced transaction function in the previous experiment of OP, and sent a self transfer: without any ETH, the call data carried the HEX code of the "force inclusion" string.
Next, it is converted into the previous self converted transaction on L2, with the hexadecimal string "force inclusion" in calldata: 0x666f72636520696e636c7573696f6e.

When the Sequencer takes the transaction out of PriorityQueue and writes it into the transaction history, it will be converted into the corresponding L2 transaction on L2
Through the requestL2Transaction function, users can submit data in L1 using the same L1 account as the L2 address, specifying the L2 recipient, accompanying ETH amount, and calldata. If the user wants to call other contracts with different data, the same is to fill in the requestL2Transaction function with each parameter one by one.
There is no mandatory inclusion feature for users yet
Although the waiting period for the L2 transaction to be indexed by the Sequencer will be calculated when it is placed in the priority queue, there is currently no Force Inclusion function in zkSync design that allows users to enforce it, which is equivalent to only doing half of the set. That is to say, although there is a "waiting period for inclusion", in reality, it is still "to see if the sequencer should be included": the sequencer can wait until it expires to be included, or it can never be included in any transactions in the priority queue.
In the future, zkSync should incorporate relevant functions so that users can force transactions to be included in the L2 transaction history when the revenue validity period has expired but has not yet been indexed by the Sequencer. This is the truly effective Force Inclusion mechanism.
summary
L1 relies on a large number of validators to ensure the "security" and "anti censorship ability" of the network, while Rollup has weaker anti censorship ability because it is written into transactions by a few or even a single sequencer. Therefore, Rollup requires a Force Inclusion mechanism to allow users to bypass the Sequencer, write transactions into history, and avoid being reviewed by the Sequencer, making it impossible to use or withdraw funds from the Rollup.
Force Inclusion allows users to force transactions to be written into history, but the design requires a choice on whether transactions can be immediately inserted into history and take effect. If transactions are allowed to take effect immediately, it will have a negative impact on the Sequencer, as transactions waiting to be received on L2 may be affected by exchanges forced to receive revenue on L1.
Therefore, currently the Force Inclusion mechanism of Rollup will first put the inserted transactions on L1 into a waiting state, and allow the Sequencer a period of time window to react and choose whether to include these waiting transactions.
ZkSync and Arbitrum both maintain a queue in L1 to manage L2 transactions sent by users from L1 or messages sent to L2. Arbitrum is called DelayedInbox; ZkSync is called PriorityQueue
But the way zkSync sends L2 transactions is similar to Optimism, both of which send messages to L1 using the L2 address. After converting to an L2 transaction, the initiator will be the L2 address. The function that Optimism sends L2 transactions is called deployTransaction; ZkSync is called requestL2Transaction. On the other hand, Arbitrum generates a complete L2 transaction and signs it, and then sends it out through the sendL2Message function. Arbitrum will restore the signer through the signature on L2 as the initiator of the L2 transaction.
StarkNet currently does not have a Force Inclusion mechanism; ZkSync, on the other hand, is like a Force Inclusion with half a set - it has a PriorityQueue and each L2 transaction in the queue has a validity period for inclusion, but this validity period is currently only for decorative purposes. In fact, the Sequencer can choose not to include any L2 transactions in the PriorityQueue at all














No Comments