Force Inclusion on Arbitrum
- Caveats
- Flow
- Gas
- Transaction Replacement
- Reasons Force Inclusion transaction could fail
- Side effects of Force Inclusion transaction
Caveats
- The user is assumed to be an EOA. If the user is using a contract account, the process will be a little different and is not covered in this document.
Flow
Steps to successfully complete a force inclusion on Arbitrum:
- Sign Normal L2 Transaction: Sign the normal L2 transaction as usual. This will be the force included transaction.
- Request: Make the force inclusion request from L1. In Arbitrum's case, it's the
Inbox
contract on L1. - Transaction ID: Get the identifier to the force included L2 transaction in the request. In Arbitrum's case, it's the transaction hash of the normal L2 transaction signed in step 0.
- Track Progress: Tracking the progess of the force included L2 transaction in the request. In Arbitrum's case, query for the transaction's receipt using
getTransactionReceipt
RPC call. - Complete Inclusion: In Arbitrum's case, sequencer has around 24 hours to include the force included L2 transaction. If sequencer fails to do so, anyone can complete the force inclusion via the
SequencerInbox
contract on L1.
Contracts Used
0: Sign Normal L2 Transaction
The users signs a normal L2 transaction as he used to. Or if he already signed and sent his L2 transaction but the transaction never gets included, then he can just use the transaction to make the Force Inclusion request.
1: Request
The user calls the sendL2Message
function on Inbox
contract on L1:
function sendL2Message(bytes calldata messageData)
Important notes:
- The user does not have to be the one sending the L1 transaction to call
sendL2Message
function. He can have a relayer carry his signed normal L2 transaction onchain. - The function has two modifiers:
whenNotPaused
andonlyAllowed
- Rollup owner can
pause
the contract, effectively disabling force inclusion. - Allow list is currently not enabled. If enabled, it will check if the
tx.origin
is on the allow list, effectively disabling force inclusion by anyone except the ones in the allow list. Only Rollup owner can set allow list.
- Rollup owner can
Parameters
To better explain the parameters, we will use an example: suppose Bob wants to force include his transaction which executes an ETH->USDC swap on Uniswap on Arbitrum.
Bob will first sign the L2 transaction which will execute an ETH->USDC swap on Uniswap on Arbitrum. Next he will append 0x04
to the signed L2 transaction payload: 0x04 | Bob's signed transaction
. This will be the messageData
param to the sendL2Message
function. See the sendChildSignedTx
function in Arbitrum SDK as an example.
Important notes:
- The size of
messageData
can not exceed117964
bytes (~116 KB)
2. Transaction ID
Once the request is made successfully, a MessageDelivered
event will be emitted. The event will contain all data necessary to complete the force inclusion in step 4. You can use the getForceIncludableEvent
function in Arbiturm SDK to extract MessageDelivered
logs
. However, that function will exclude any force inclusion transactions that is not eligible yet, i.e., it has not passed the 24 hours Force Inclusion Grace Period. Hence if you wish to extract MessageDelivered
logs before the grace period ends, you will have to copy and modify the function.
Since the user is force including his signed normal L2 transaction, naturally the identifier for the force included L2 transaction is the transaction hash of his signed normal L2 transaction. You can use ``ethers.utils.parseTransaction` to get the L2 transaction hash
3. Track Progess
With the L2 transaction hash, you can query APIs/RPCs to chekc its status or query the explorer. Or if you are using libraries like Viem, you can use `waitForTransactionReceipt. But note that it could take up to 24 hours for the transaction to be included.
4. Complete Inclusion
In Arbitrum's case, currently sequencer has around 24 hours grace period to include user's transaction. If the sequencer did not include it in 24 hours. Anyone can call the forceInclusion
function on SequencerInbox
contract on L1. The grace period on Ethereum is currently set to 5760 L1 blocks (24 hours).
Parameters
The emitted MessageDelivered
event basically contains all data needed to call the forceInclusion
function. The parameters will be used to compute the message hash and check if it matches the one stored in the transaction queue. Message hash is stored in the transaction queue when user called sendL2Message
to make the force inclusion request, i.e., step 1.
Code Walkthrough
Message hash computation in sendL2Message
function:
bytes32 messageHash = Messages.messageHash(
kind,
sender,
blockNumber,
blockTimestamp,
count,
baseFeeL1,
messageDataHash
);
The data used to compute message hash will be emitted in the MessageDelivered
event in sendL2Message
function:
emit MessageDelivered(
count,
prevAcc, // not used by `Messages.messageHash()`
msg.sender, // not used by `Messages.messageHash()`
kind,
sender,
messageDataHash,
baseFeeL1,
blockTimestamp
);
Message hash computation in forceInclusion
function:
function forceInclusion(
uint256 _totalDelayedMessagesRead,
uint8 kind,
uint64[2] calldata l1BlockAndTime,
uint256 baseFeeL1,
address sender,
bytes32 messageDataHash
) external {
...
bytes32 messageHash = Messages.messageHash(
kind, // the `kind` in `MessageDelivered` event
sender, // the `sender` in `MessageDelivered` event
l1BlockAndTime[0], // not emitted by the `MessageDelivered` event but you will get it when filtering for event logs
l1BlockAndTime[1], // the `blockTimestamp` in `MessageDelivered` event
_totalDelayedMessagesRead - 1, // the `count` parameter in `MessageDelivered` event
baseFeeL1, // the `baseFeeL1` in `MessageDelivered` event
messageDataHash // the `messageDataHash` in `MessageDelivered` event
);
...
}
Gas
The gas cost of a force inclusion transaction is not paid on L1, like Optimism. Instead, it is paid on L2, when the force included transaction is finally included and executed.
L2 Gas Limit and L2 Gas Price
User should simulate his L2 transaction, get the estimated gas limit and gas price for his transaction. Perhaps adding a buffer because the force inclusion transaction can take up to 24 hours before inclusion and execution.
Transaction Replacement
Force inclusion transaction is not replaceable.
Reasons Force Inclusion transaction could fail
1. Not enough ETH to transfer
User should make sure he has enough ETH on Arbitrum if he wants to transfer ETH in his force inclusion transaction.
2. Not enough L2 gas limit
User should make sure he sets high enough gas limit so that his force inclusion transaction does not run into "Out of Gas" error.
3. Gas price too low
User should make sure he sets high enough gas price so that his force inclusion transaction does not get rejected due to low gas price.
4. L2 execution failed
If force inclusion transaction is not included and executed in time, the transaction might fail. For example, Bob's force inclusion transaction which swaps ETH to USDC on Uniswap Pool might fail because the price has moved quite a bit since he makes the request on L1.
Side effects of Force Inclusion transaction
EOA's nonce on L2 will be incremented
Nonce of user's address on L2 will be incremented whenever his force inclusion transaction is included and executed, no matter the transaction fail or not. This could result in wallet crafting a transaction with wrong nonce if it was not aware of force inclusion transactions.