Execution Engine
The Geth fork, ArbOS's role as a nested OS, the precompile set, retryables, and Nitro's gas/fee model.
Third-party documentation. This is independently authored analysis of the public Arbitrum Nitro codebase — not the official docs, and not reviewed or endorsed by the Arbitrum Nitro team.
Execution Engine
Nitro executes transactions with a fork of go-ethereum, then delegates the non-EVM work to ArbOS, a program running in the state itself. The result is a chain that behaves like Ethereum to applications while extending it in exactly the places an L2 must extend it.
The Geth core
Blocks, the EVM, state trie, receipts, and the JSON-RPC server are stock go-ethereum. Two changes stand out:
- ArbOS runs inside state — Nitro seeds ArbOS's bytecode into the genesis state (at addresses below
0x0000...100, the "system" range), and Geth's block production calls it at the start of each block. - Blocks are produced by the sequencer (or derived from L1 by validators), not by mining/PoS — the Geth "miner" path is replaced by Nitro's block builder.
What ArbOS does
- Message ingress — converts each L1 inbox message into an L2 transaction. For retryables this includes the refund/max-submission bookkeeping (see Bridging).
- L1 data fee accounting — tracks how many bytes each L2 transaction occupies in the current batch and charges an L1 data fee proportional to that. Users pay the "rollup tax" deterministically.
- Gas/token bookkeeping — manages the L2's ETH (the "native token"), fee collection, and the per-transaction fee split between L2 execution gas and L1 data gas.
- Outbox emission — on the L2 side, marks L2-to-L1 messages so the L1 Outbox can later redeem them.
Precompiles
Applications interact with ArbOS through a fixed set of precompile contracts at deterministic addresses:
- ArbSys —
arbBlockNumber(the L2 block number, which diverges from the L1-anchored number),sendTxToL1,withdrawEth,arbBlockHash. - ArbGasInfo —
getPricesInWei,getL1FeesApproximate, so contracts can estimate the L1 component of fees. - ArbToken — the canonical token bridge helpers used by the standard bridge.
- ArbRetryableTx —
createRetryableTicket,redeem,cancel, for L1→L2 messages that can be retried. - ArbOwner — chain-owner administration (fees, sequencer address, L1 pricing params).
Retryables: L1 → L2 with retry
A retryable ticket is Nitro's answer to "the L2 side of my deposit ran out of gas." Instead of failing silently, the L1→L2 message is stored as a ticket that:
- Is funded at submission with a
maxSubmissionCost(L1), plusmaxFeePerGasandgasLimitfor its L2 execution. - Executes automatically when the L2 is ready. If it runs out of gas, the ticket survives and anyone can
redeemit with fresh gas. - Can be
canceled by the sender after a timeout, refunding unused funds.
This makes deposits self-healing — a flaky L2 gas estimate can't permanently strand assets.
Fees: two dimensions
- L2 execution gas — ordinary EVM gas, priced by the L2's own base-fee mechanism (Arbitrum uses a dynamically adjusted base fee).
- L1 data gas — per-byte charge for the transaction's share of posted batch data, priced off current L1 conditions via
ArbGasInfo. The two are summed into the total the user pays.
Edge cases
- Calling a precompile address for a non-Arbitrum chain will hit a plain address with no code — never hardcode precompile addresses across L1 and L2 (they exist only on the L2 side).
arbBlockNumberdiffers fromblock.number; some tools assume the two are the same. Read the block'sl1BlockNumberfield to anchor on L1 instead.- Retryables have a fixed validity window (roughly one week) before they become cancellable; a ticket that is neither redeemed nor canceled after that window refunds to the sender.
- L1 data fees are estimated, not exact — a transaction's actual cost depends on final batch compression and L1 base fee, so quote with a buffer.
Interface
// ArbSys — the core L2 precompile exposing Arbitrum-specific primitives.interface ArbSys {// Absolute L2 block number (resets only at genesis boundaries).function arbBlockNumber() external view returns (uint256);// Bridge transaction to L1. Emits an event the Outbox consumes.function sendTxToL1(address destination,bytes calldata data) external payable returns (uint256);// User-level L2-to-L1 message with arbitrary calldata.function sendTxToL1CustomFee(address destination,bytes calldata data,address feeRefunder) external payable returns (uint256);function withdrawEth(address destination) external payable returns (uint256);}// ArbRetryableTx — L1->L2 messages that can be retried later.interface ArbRetryableTx {function createRetryableTicket(address to, uint256 l2CallValue, uint256 maxSubmissionCost,address excessFeeRefundAddress, address callValueRefundAddress,uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data) external payable returns (uint256 ticketId);}
