Author: NIC Lin, person in charge of Taipei Ethereum Meetup
Original title: "Introduction to Rollup's Force Inclusion Mechanism"
Just yesterday something happened that shocked countless people: Linea, the second layer of Ethereum launched by Consensys, the parent company of Metamask, proactively shut down. Officials said that the purpose of this was to reduce the impact of the Velocore hacking incident. And this can’t help but remind people of the previous shutdown of BSC Chain (BNB Chain) under official coordination in order to reduce the losses from hacker attacks. Whenever people talk about this kind of thing, they will doubt the decentralized value advocated by Web3.
Of course,the above The core reason for the incident lies more in the imperfection of the infrastructure itself, that is, insufficient decentralization: If a chain is sufficiently decentralized, then it should not stop at the drop of a hat. Due to the unique structure of the second layer of Ethereum, most Layer 2 relies on the centralized Sequencer. Although there have been more and more arguments for decentralized sequencers in recent years, considering the existence of the second layer In view of its purpose and structure, we can safely assume that Layer2’s sequencer will most likely not be very decentralized, and in the end it may not be as decentralized as the BSC chain. If this is true, then what should we do?
In fact, for the second layer,the most direct harm caused by the non-decentralization of the sorter lies in its censorship resistance and activity. If there are very few entities (Sequencers) that process transactions, then it has absolute power over whether to serve you: it can reject you if it wants to, and you may have no way. How to solve the anti-censorship problem of Layer 2 is obviously an important topic.
In the past few years, major Ethereum second-layers have proposed various solutions to the anti-censorship problem, such as Loopring and Degate, StarkEx’s forced withdrawal and escape cabin functions, and Arbitrum and other Force Inclusion functions of OP Rollup. These methods can check and balance the Sequencer under certain conditions to prevent it from unreasonably rejecting any user's transaction request.
In today’s article, NIC Lin from the Taipei Ethereum Association gives his own account. He personally experiments with the censorship-resistant transaction functions of four mainstream Rollups, and provides in-depth analysis from aspects such as workflow and operation methods. With the design of the Force Inclusion mechanism, this is especially valuable for the Ethereum community and large investors holding huge amounts of assets.
Transaction review and Force Inclusion
Transaction censorship resistance (Censorship Resistance) is very important for a blockchain, if the blockchain can arbitrarily review and reject transactions initiated by users , which is no different from a Web2 server. Ethereum’s current transaction resistance to censorship comes from its large number of Validators. If someone wants to review Bob’s transactions and prevent his transactions from being uploaded to the chain, they must either try to bribe most of the Validators in the network, or spam the entire network. Continuously send junk transactions with higher handling fees than Bob to seize block space. Either way, the cost will be very high.
Note: In Ethereum’s current PBS structure, the cost of reviewing transactions will be reduced a lot. You can refer to the proportion of blocks that cooperate with OFAC to review Tornado Cash transactions. Current censorship resistance relies on independent verifiers and relays outside OFAC and government jurisdiction.
But what about Rollup? Rollup does not require a large number of validators to ensure security. Even if Rollup has only one centralized role (Sequencer) to generate blocks, it is as secure as L1. But security and censorship resistance are two different things. Even if a Rollup is as secure as Ethereum, with only one centralized Sequencer, any user’s transactions can be censored.
Sequencer can refuse to process the user's transaction, resulting in the user's 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 use the anti-censorship ability of L1:
Original Sequencers The purpose is to package the transaction data and send it to the Rollup contract of L1. It is better to add a design to the contract to allow users to insert transactions into the Rollup contract by themselves. This mechanism is called "Force Inclusion". As long as Sequencer cannot censor users at the L1 level, it cannot prevent users from forcibly inserting transactions at L1. In this way,Rollup can inherit the censorship resistance of L1.
Sequencer cannot review the user's L1 transactions without paying a high cost
How should forced transactions take effect?
If transactions are allowed to be written directly into the Rollup contract through Force Inclusion (that is, effective immediately), the status of the Rollup will change immediately. For example, Bob inserts a transaction through the Force Inclusion mechanism. For a transaction of "transfer 1000 DAI to Carol", if 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 effect immediately, the status will change immediately
If the Sequencer is also collecting transactions off-chain at this time and sends the next batch of transactions to the Rollup contract, it may be affected by the transactions that Bob forcibly inserts and take effect immediately. This kind of problem must be avoided as much as possible, soRollup generally does not make the Force Inclusion transaction take effect immediately. Instead, it first allows the user to insert the transaction into the waiting queue on L1 and enter the "preparing" state.
When Sequencer packages off-chain transactions and sends them to the Rollup contract, it chooses whether to insert the aforementioned transactions into the transaction sequence. If Sequencer keeps ignoring these transactions in the "preparing" state Transactions, after the window period ends, users can forcefully insert these transactions into the Rollup contract.
Sequencer can decide when to "incidentally receive" transactions in the waiting queue
Sequencer can still refuse to process transactions in the waiting queue
If Sequencer refuses for a long time, anyone can after a period of time Forcibly insert transactions into the Rollup contract through the Force Inclusion function
Next, we will introduce in order the Force Inclusion of four more famous Rollup, including Optimism, Arbitrum, StarkNet and zkSync. Mechanism is implemented.
Optimism’s Force Inclusion Mechanism
First introduce the Deposit process of Optimism. This Deposit not only refers to depositing money into Optimism, but also includes "Send the information sent by the user to L2" into L2. After receiving the newly deposited message, the L2 node will convert the message into an L2 transaction for execution and send it to the designated recipient of the message.
User's message from L1 Deposit to L2
L1CrossDomainMessenger Contract
When a user wants to deposit ETH or ERC-20 tokens into Optimism, he will interact with the L1StandardBridge contract on L1 through the front-end web page, specifying the amount to deposit and which L2 address. Receive these assets.
The L1StandardBridge contract will pass the message to the next layer's L1CrossDomainMessenger contract. This contract is mainly used as a component for mutual communication between L1 and L2, L1StandardBridge This universal communication component communicates with the L2StandardBridge on L2 to determine who can mint tokens in L2 or who can unlock tokens from L1.
If a developer needs to develop a contract that communicates and synchronizes status between L1 and L2, then he can build it on the L1CrossDomainMessenger contract.
The user's message is passed from L1 to L2 through the CrossDomainMessenger contract Note: In some of the pictures in this article, CrossDomainMessager is written as CrossChainMessager
OptimismPortal contract
The L1CrossDomainMessenger contract will then The message is sent to the bottom-level OptimismPortal contract. After the OptimismPortal contract is processed, an event named TransactionDeposited will be thrown. The parameters include "the person who sent the message" and "the person who received the message". "person", and related execution parameters.
Then L2's Optimism node will listen to the Transaction Deposited event thrown by the OptimismPortal contract and convert the parameters in the event into an L2 transaction. The initiator of this transaction will be The "person who sent the message" specified in the Transaction Deposited event parameters and the transaction receiver are the "person who received the message" 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 user deposited 0.01ETH through the L1StandardBridge contract. This message and the ETH were transmitted all the way to the OptimismPortal contract (the address is 0xbEb5...06Ed), and then converted into an L2 transaction a few minutes later:
The initiator of the message is the L1CrossDomainMessenger contract; the receiver is the L2CrossDomainMessenger contract on L2; the message content is that L1StandardBridge received BoB's 0.01ETH deposit. After this, some processes will be triggered, such as issuing an additional 0.01 ETH to L2StandardBridge, which will then be transferred to Bob.
How to trigger it specifically
When you want to force a transaction into Optimism’s Rollup contract, the effect you want to achieve is to make a transaction "from "The transaction initiated and executed by your L2 address on L2" can be executed smoothly. At this timeyou should use your L2 address to submit the message directly to the OptimismPortal contract (note that the OptimismPortal contract is actually on L1. However, the OP's address format is the same as the L1 address format. You can directly call the above contract using the 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. At this time, the transaction format is the same as a normal L2 transaction.
In the L2 transaction converted from the Transaction Deposited event, the initiator will be Bob himself; the recipient is the Uniswap contract; and it will be accompanied by the specified ETH, so Just like Bob initiated the L2 transaction himself
p>
If you want to call the Force Inclusion function of Optimism, you have to directly call the depositTransaction function of the OptimismPortal contract and add the Fill in the parameters of the transaction executed on L2
I did a simple Force Inclusion experiment.This transaction wants to achieve one thing: on L2 Use my address to self-transfer (0xeDc1…6909) with a “force inclusion” text message.
This is the L1 transaction in which I execute the depositTransaction function through the OptimismPortal contract. You can see that in the Transaction Deposited event it throws, both from and to are myself
The value in the remaining opaque Data column is encoded with "call "How much ETH does the deposit Transaction function bring", "How much ETH does the L2 transaction initiator want to send to the recipient", "L2 transaction GasLimit" and "Data to the L2 recipient" and other information.
After decoding the above information, you will get:
"How much ETH was attached by the person who called deposit Transaction": 0 , because I am not depositing ETH from L1 to L2;
"How much ETH does the L2 transaction initiator want to send to the recipient": 5566 (wei)
< p>
"GasLimit for L2 transaction": 50000
"Data for L2 recipient":0x666f72636520696e636c7573696f6e, which is the word "force inclusion" The hexadecimal encoding of the string
Not long after, the converted L2 transaction appeared: an L2 transaction in which I transferred money to myself.The amount was 5566 wei, and the Data was " force inclusion" string. And you can notice that the TxnType (transaction type) in Other Attributes in the second to last line in the picture shows system transaction 126 (System), which means that this transaction was not initiated by me in L2, but was Deposited by L1 transaction. Events are transformed.
Converted L2 transaction
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 into the previous deposit Transaction function. Just remember, Use the same L1 address as your L2 account to call the deposit Transaction function, so that when the Deposited Event is converted into an L2 transaction, the initiator is your L2 account.
SequencerWindow
The Optimism L2 node mentioned earlier converts the Transaction Deposited event into an L2 transaction. In fact, this Optimism node refers to Sequencer. After all, this relationship to transaction ordering, soonly the Sequencer can decide when to convert the aforementioned event into an L2 transaction.
When listening to the TransactionDeposited event, the Sequencer does not necessarily convert the event into an L2 transaction immediately. There can be a delay. The maximum value of this period is called the SequencerWindow.
The current Sequencer Window on the Optimism mainnet is 24 hours. That is, when a user deposits a sum of money from L1 or Force Includes a transaction, the worst case scenario is that it will be included in the L2 transaction history after 24 hours. .
Arbitrum’s Force Inclusion mechanism
The Deposit operation of L1 in Optimism will throw a Transaction Deposited event, and the rest is to wait for the Sequencer to include the above operation. ;But in Arbitrum, operations that occur on L1 (depositing money or sending messages to L2, etc.) 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 into the L2 transaction history. If the Sequencer does nothing when the time is up, anyone can complete it for the Sequencer.
Arbitrum will maintain a Queue in the L1 contract. If the Sequencer does not actively process the transactions in the Queue, when the time is up, anyone can force the transactions in the Queue to be included in the L2 transaction history
Arbitrum In the design, operations such as deposits that occur on L1 must go through the Delayed Inbox contract. As the name suggests, the operations here will be delayed to take effect; the other contract is the Sequencer Inbox, which is the direct place where the Sequencer uploads L2 transactions to L1. Every time the Sequencer uploads an L2 transaction, it can take out some pending transactions from the Delayed Inbox and write them into the transaction history.
When Sequencer writes a new transaction, you can take out the transaction from DelayedInbox and write it togetherComplex design and numerous reference materials
If readers directly refer to Arbitrum’s official chapter on Sequencer and Force Inclusion, they will see that Force is mentioned in it How Inclusion works roughly, as well as some parameter names and function names:
The user first goes to the DelayedInbox contract to call the sendUnsignedTransaction function. If Sequencer If it is not included within about 24 hours, the user can call the forceInclusion function of the SequencerInbox contract. Then Arbitrum official did not attach the link of the function to the official website document, so you can only look at the corresponding function in the contract code yourself.
When you find the sendUnsignedTransaction function, you find that you have to fill in the nonce value and maxFeePerGas value yourself. Which address is the nonce? On which network is maxFeePerGas? What's the best way to fill it out? There is no file reference, not even Natpsec. Then you will also find a bunch of similar functions in the Arbitrum contract:
sendL1FundedUnsignedTransaction, sendUnsignedTransactionToFork, sendContractTransaction, sendL1FundedContractTransaction. There is also no document telling you the difference between these functions, how to use them, and how to fill in the parameters. , not even Natpsec.
You try to fill in the parameters and send the transaction with a tentative mentality. You want to use trial and error to see if you can find the correct usage, but you find that these functions will all use your L1 address. AddressAliasing results in the Sender finally having a different address when initiating a transaction on L2, so your L2 address remains motionless.
sendL2Message
Later I accidentally clicked on Google search and found out that Arbitrum has its own Tutorial library, which contains scripts to demonstrate how to send L2 transactions from L1 (That is, the meaning of Force Inclusion), and then the function it lists is not any of the ones mentioned above, but a function called sendL2Message, and the message parameter to be brought in turns out to be an L2 account A signed deal?
Who would have known that the "message sent to L2 through Force Inclusion" would actually be a "signed L2 transaction"? And there is no documentation or Natspec explaining when and how to use this function.
Conclusion: It is troublesome to manually generate an Arbitrum forced transaction. It is recommended to follow the official Tutorial and run the Arbitrum SDK. Unlike other Rollups, Arbitrum has clear developer documentation and code notes. The purpose and parameters of many functions lack explanation, causing developers to spend more time than expected to access and use it. I also asked the people at Arbitrum on the Arbitrum Discord but didn't get a satisfactory answer.
When I asked on Discord, the other party would only ask me to look at sendL2Message. They did not want to explain the functions of other functions (even sendUnsignedTransaction mentioned in the Force Inclusion document), what they are used for, how to use them, and what they are. time to use.
StarkNet’s ForceInclusion mechanism
Unfortunately, StarkNet does not yet have a ForceInclusion mechanism. There are only two articles discussing Censorship and ForceInclusion on the official forum.
Unable to prove a failed transaction
The above reason is actually because StarkNet’s zero-knowledge proof system cannot prove a failed transaction, so it cannot be allowed Force Inclusion. Because If someone maliciously (or unintentionally) Force Includes a failed transaction that cannot be proven, StarkNet will be stuck directly: because after the transaction is forced to be included, Prover must prove that the transaction failed deal, but it has no way of proving it.
StartNet expects to introduce the function of proving failed transactions in version v0.15.0, and then it should be possible to further implement the Force Inclusion mechanism.
zkSync’s ForceInclusion mechanism
zkSync’s L1->L2 message transmission and Force Inclusion mechanism are all through the requestL2Transaction of the MailBox contract When the function is executed, the user specifies the L2 address, calldata, additional ETH amount, L2GasLimit value, etc. requestL2Transaction will combine these parameters into an L2 transaction and then put it into the priority queue (PriorityQueue). The Sequencer will package the transaction. When uploading to L1 (through the commitBatches function), indicate how many transactions should be taken out of the priority queue and included in the L2 transaction record.
zkSync is very similar to Optimism in the form of Force Inclusion. Both use the initiator's L2 address (consistent with the L1 address) to call related functions and fill in the data (received by caller, calldata, etc.), instead of filling in a signed L2 transaction like Arbitrum; but in design, it is the same as Arbitrum, maintaining a queue Queue in L1, and the Sequencer takes it from the Queue Output the pending transactions submitted directly by the user and write them into the transaction history.
If you Go to Deposit ETH through the official bridge of zkSync. For this transaction, it calls the requestL2Transaction function of the MailBox contract. It will put the L2 transaction of 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 difficult to read. If you 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 yourself), so After a while, when this L2 transaction is taken out of the priority queue by Sequencer and included in the transaction history, it will be converted into a transaction transferred to itself on L2, and the amount of the transfer is the Deposit of the transaction initiator in L1 The amount of ETH carried in the ETH transaction.
In the L1Deposit transaction, the transaction initiator and receiver are both 0xeDc1...6909, the amount is 0.03ETH, and the calldata is empty
< p style="text-align: center;">
< span style="font-size: 14px;">There will be a transaction of 0xeDc1...6909 transferred to itself on L2. The transaction type (TxnType) is 255, which is a system transaction
Then I directly called the requestL2Transaction function of zkSync and sent a self-transfer as I did before when I experimented with the OP's forced transaction function: without any ETH, the calldata brought in the HEX encoding of the "force inclusion" string.
Then it is converted into L2’s previous transaction to itself. The calldata contains the hexadecimal string of “force inclusion”: 0x666f72636520696e636c7573696f6e.
When the Sequencer takes the transaction out of the PriorityQueue and writes it into the transaction history, it will be converted into the corresponding L2 transaction on L2 p>
Through the requestL2Transaction function, users can use the L1 account with the same address as the L2 address, submit information in L1, and specify the L2 recipient, the accompanying ETH amount, and calldata. If the user wants to call other contracts with different Data, the same is done by filling in the parameters into the requestL2Transaction function one by one.
There is no function that allows users to force inclusion
Although after the L2 transaction is placed in the priority queue, it will be calculated by the Sequencer. There is a waiting period for inclusion, but there is currently no Force Inclusion function that users can enforce in the zkSync design, which is equivalent to only half a set. That is to say, although there is a "waiting period for inclusion", it is actually "seeing whether the Sequencer wants to receive revenue": the Sequencer can wait until the expiration date before receiving the transaction, or it can never receive any transactions in the priority queue.
In the future, zkSync should add related functions so that users can force the transaction to be included in the L2 transaction history when the income validity period has passed but has not been included by Sequencer. This is the truly effective Force. Inclusion mechanism.
Summary
L1 relies on a large number of verifiers to ensure the "security" and "censorship resistance" of the network. Rollup is If a few or even a single Sequencer writes transactions, the censorship resistance is even weaker. Therefore, Rollup needs a Force Inclusion mechanism to allow users to bypass Sequencer and write transactions into history, to avoid being reviewed by Sequencer and unable to use or withdraw funds from the Rollup.
Force Inclusion allows users to force transactions to be written into the history, but in the design, they need to make a choice on "whether the transaction can be immediately inserted into the history and take effect immediately." If transactions are allowed to take effect immediately, it will have a negative impact on the Sequencer, because transactions waiting to be received on L2 may be affected by transactions forcibly received by L1.
ThereforeThe current Force Inclusion mechanism of Rollup will first put the transactions inserted on L1 into a waiting state, and allow the Sequencer a period of time to react and choose whether to receive these waits. transactions in.
Both zkSync and Arbitrum maintain a queue Queue in L1 to manage L2 transactions sent by users from L1 or messages to L2. Arbitrum is called DelayedInbox; zkSync is called PriorityQueue
But the way zkSync sends L2 transactions is more similar to Optimism, both use L2 addresses to send messages to L1, which converts them to L2 After the transaction, the initiator will be the L2 address. Optimism's function to send L2 transactions is called depositTransaction; zkSync is called requestL2Transaction. Arbitrum generates and signs a complete L2 transaction, and then sends it 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 is like a half set of Force Inclusion, with a PriorityQueue and each L2 transaction in the Queue has an expiration date. , but this validity period is currently just for decoration. In fact, Sequencer can choose not to include any L2 transactions in the PriorityQueue at all