|
| 1 | +# Tutorial: Force Inclusion End-to-End Test |
| 2 | + |
| 3 | +`force-inclusion` demonstrates Arbitrum's **censorship resistance** mechanism end-to-end. It deploys a fresh Orbit rollup with a short force inclusion delay (90 seconds), deposits ETH via the delayed inbox, waits for the delay window to pass, and then force includes the deposit — all without a running sequencer. |
| 4 | + |
| 5 | +## What is force inclusion? |
| 6 | + |
| 7 | +Arbitrum's [Sequencer](https://docs.arbitrum.io/how-arbitrum-works/sequencer) normally orders transactions. But what if the sequencer goes offline or starts censoring? Arbitrum guarantees that any message sent to the **delayed inbox** on the parent chain can be **force included** into the chain's inbox after a time delay, bypassing the sequencer entirely. |
| 8 | + |
| 9 | +This tutorial runs the full cycle in a single command: |
| 10 | + |
| 11 | +1. **Deploy** a new Orbit rollup with `maxTimeVariation.delaySeconds = 90` (instead of the default 24 hours) |
| 12 | +2. **Deposit** ETH via `Inbox.depositEth()` on the parent chain (goes into the delayed inbox) |
| 13 | +3. **Force include** the deposit by calling `SequencerInbox.forceInclusion()` after the delay window passes |
| 14 | +4. _(Optional)_ **Start a fullnode** (no sequencer) to verify the deposit appears on the child chain |
| 15 | + |
| 16 | +## Prerequisites |
| 17 | + |
| 18 | +- Node.js 18+ |
| 19 | +- A deployer account with ETH on the parent chain (Arbitrum Sepolia or a custom parent chain) |
| 20 | +- Docker (only needed for the `--with-node` option) |
| 21 | + |
| 22 | +## Set environment variables |
| 23 | + |
| 24 | +Copy the sample env file and fill in your values: |
| 25 | + |
| 26 | +```bash |
| 27 | +cp .env-sample .env |
| 28 | +``` |
| 29 | + |
| 30 | +Required variables: |
| 31 | + |
| 32 | +| Variable | Description | |
| 33 | +| ---------------------- | --------------------------------------------------------------- | |
| 34 | +| `DEPLOYER_PRIVATE_KEY` | Private key of the deployer (must have ETH on the parent chain) | |
| 35 | +| `PARENT_CHAIN_RPC` | RPC URL of the parent chain | |
| 36 | + |
| 37 | +Optional variables: |
| 38 | + |
| 39 | +| Variable | Description | |
| 40 | +| -------------------------- | ----------------------------------------------------- | |
| 41 | +| `PARENT_CHAIN_ID` | Parent chain ID (defaults to Arbitrum Sepolia 421614) | |
| 42 | +| `BATCH_POSTER_PRIVATE_KEY` | Batch poster key (auto-generated if not set) | |
| 43 | +| `VALIDATOR_PRIVATE_KEY` | Validator key (auto-generated if not set) | |
| 44 | + |
| 45 | +## Run |
| 46 | + |
| 47 | +Run steps 1–3 (deploy, deposit, force include): |
| 48 | + |
| 49 | +```bash |
| 50 | +yarn test |
| 51 | +``` |
| 52 | + |
| 53 | +Run all 4 steps including fullnode verification (requires Docker): |
| 54 | + |
| 55 | +```bash |
| 56 | +yarn test:withNode |
| 57 | +``` |
| 58 | + |
| 59 | +## How it works |
| 60 | + |
| 61 | +### Rollup deployment |
| 62 | + |
| 63 | +The script uses `@arbitrum/chain-sdk` (Orbit SDK) to deploy a new rollup. The key configuration is `sequencerInboxMaxTimeVariation`, which controls how long a delayed message must wait before it can be force included: |
| 64 | + |
| 65 | +```js |
| 66 | +sequencerInboxMaxTimeVariation: { |
| 67 | + delayBlocks: 6n, |
| 68 | + futureBlocks: 12n, |
| 69 | + delaySeconds: 90n, |
| 70 | + futureSeconds: 3600n, |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +### Delayed inbox deposit |
| 75 | + |
| 76 | +ETH is deposited using `@arbitrum/sdk`'s `EthBridger.deposit()`. Under the hood, this calls `Inbox.depositEth()` on the parent chain, which routes through `bridge.enqueueDelayedMessage()`. Without a running sequencer, this message stays in the delayed inbox. |
| 77 | + |
| 78 | +### Force inclusion |
| 79 | + |
| 80 | +After the delay window passes (90 seconds + 6 blocks), `InboxTools.forceInclude()` calls `SequencerInbox.forceInclusion()` on the parent chain. This emits the same `SequencerBatchDelivered` event as a normal sequencer batch, making the deposit part of the canonical chain. |
| 81 | + |
| 82 | +### Fullnode verification (--with-node) |
| 83 | + |
| 84 | +When `--with-node` is passed, the script starts a Nitro fullnode via Docker with the sequencer disabled (`node.sequencer = false`, `execution.forwarding-target = "null"`). The fullnode reads from the parent chain and processes the force-included batch, allowing you to verify that the deposited ETH appears on the child chain. |
| 85 | + |
| 86 | +## Related tutorials |
| 87 | + |
| 88 | +- [Send a signed transaction from the parent chain](../delayedInbox-l2msg/) — demonstrates sending transactions via the delayed inbox with a running sequencer |
| 89 | + |
| 90 | +<p align="left"> |
| 91 | + <img width="350" height="150" src= "../../assets/logo.svg" /> |
| 92 | +</p> |
0 commit comments