# ArbReth > A modular, Rust-native execution client for Arbitrum export const DocLink = ({ href, name }) => ( {name} ) ## API Reference Full Rust API documentation generated by `cargo doc`. Each crate is documented with its public types, traits, and functions. ### Core Crates * - Core ArbOS state machine, pricing models, retryable tickets * - Block executor, custom opcode handlers, EVM configuration * - Arbitrum system contracts (0x64+) * - Stylus WASM runtime, ink metering, host I/O ### Primitives * - Transaction types, receipts, gas accounting * - Storage-backed types (uint64, address, bytes, queues) * - Chain spec, ArbOS version constants ### Node Infrastructure * - Node builder plugin for reth * - Custom JSON-RPC methods * - Payload building primitives * - Transaction pool validation ## ArbOS State Machine ArbOS is the operating system layer that runs on top of the EVM. It manages L1/L2 pricing, retryable tickets, cross-chain messaging, and protocol-level state - all stored in the state trie under a dedicated system address. ### State Layout All ArbOS state lives at `ARBOS_STATE_ADDRESS` (`0xa4b05...`), organized into subspaces: | Subspace | ID | Purpose | | ------------- | -- | -------------------------------------------------- | | L2 Pricing | 0 | Base fee, gas backlog, per-block gas limit | | L1 Pricing | 1 | L1 data costs, batch poster rewards, equilibration | | Retryables | 2 | Retryable ticket storage and timeout queue | | Address Table | 3 | Calldata compression address lookup table | | Send Merkle | 5 | L2-to-L1 message accumulator | | Blockhashes | 6 | L1 block number and hash cache | | Programs | 8 | Stylus program metadata and parameters | Each subspace uses a key derivation scheme: `hash(parent_key || offset[0..31]) || offset[31]`, which produces unique non-colliding storage slots. ### Versioning ArbOS evolves through version upgrades stored in the block header's `mix_hash` field. Each version maps to an EVM spec: | ArbOS Version | EVM Spec | Key Features | | ------------- | -------- | -------------------------------------------- | | v11 | Shanghai | PUSH0 opcode | | v20 | Cancun | Transient storage, blob base fee | | v30 | Cancun | Stylus WASM support | | v40 | Prague | Prague EVM rules | | v50 | Prague | Dia upgrade | | v60 | Prague | Multi-gas constraints, transaction filtering | The `mix_hash` header field encodes three values: * Bytes 0-7: `send_count` (cumulative L2-to-L1 messages) * Bytes 8-15: `l1_block_number` (L1 block at time of production) * Bytes 16-23: `arbos_format_version` ### Gas Pricing Arbitrum uses a two-layer gas model: L1 data posting costs and L2 execution costs. #### L1 Pricing L1 costs cover data availability - the cost of posting transaction data to Ethereum L1. * Transaction calldata is measured using **Brotli compression** to approximate actual batch size * Units are priced at `l1_price_per_unit` (wei per compressed byte) * An **equilibration algorithm** adjusts the L1 price to balance collected fees against actual batch posting costs * Poster fees are deducted from the transaction's gas budget before EVM execution #### L2 Pricing L2 costs cover computation on the Arbitrum chain. * Base fee uses an **exponential backlog model**: fees increase when demand exceeds the per-block gas limit * The exponential function uses a degree-4 Horner's method polynomial approximation * Three pricing models exist across versions: * **Legacy** (pre-v50): Simple gas accounting * **SingleGas** (v50): Unified gas constraint * **MultiGas** (v60): Eight resource dimensions tracked independently #### Fee Distribution After each transaction, collected fees are split: | Recipient | Share | Purpose | | -------------------------- | ------------ | --------------------- | | Network fee account | Configurable | Protocol revenue | | Infrastructure fee account | Configurable | Infrastructure costs | | Poster (coinbase) | L1 data cost | Reimburses L1 posting | ### Block Processing Each block follows this sequence: 1. **StartBlock** internal transaction - updates L1 pricing, reaps expired retryable tickets, caches L1 block hashes, computes per-block gas limit 2. **Batch posting report** (if present) - records L1 batch costs for the pricing model 3. **User transactions** - each goes through `start_tx` (gas charging, poster fee) → EVM execution → `end_tx` (fee distribution, refunds) 4. **Header derivation** - `mix_hash`, `extra_data` (send root), and gas fields are computed from final state ### Custom EVM Behavior ArbReth modifies several EVM opcodes from their standard Ethereum behavior: | Opcode | Standard | Arbitrum | | ------------- | ----------------- | ------------------------------- | | `NUMBER` | L2 block number | **L1 block number** | | `BLOCKHASH` | L2 block hashes | L1 block hashes (from cache) | | `BLOBBASEFEE` | Blob base fee | Always reverts (no blobs on L2) | | `PREVRANDAO` | Beacon randomness | Constant `0x...0001` | | `COINBASE` | Block proposer | L1 batch poster address | | `DIFFICULTY` | Mining difficulty | Constant `1` | ## Architecture ArbReth is organized as a Cargo workspace of focused, independently consumable crates. The architecture follows a clear layering: node infrastructure at the top, execution in the middle, and core primitives at the bottom. ### Crate Map ``` crates/ ├── arb-node/ Node builder - wires all components into a runnable reth node ├── arb-rpc/ JSON-RPC - eth_, arb_, and nitroexecution_ namespaces ├── arb-payload/ Payload building primitives for the Engine API ├── arb-txpool/ Transaction pool validation (rejects blobs, enforces fee caps) │ ├── arb-evm/ Block executor, custom opcode handlers, EVM configuration ├── arbos/ Core ArbOS state machine, pricing, retryables, block processing ├── arb-precompiles/ System contracts at addresses 0x64+ (gas info, retryables, owner, etc.) ├── arb-stylus/ Stylus WASM runtime - compilation, caching, ink metering, host I/O │ ├── arb-primitives/ Transaction types, receipts, gas accounting ├── arb-storage/ Storage-backed types (uint64, address, bytes, queues, vectors) └── arb-chainspec/ Chain spec, ArbOS version constants, hardfork timestamps bin/ ├── arb-reth/ Node binary (CLI entry point) └── gen-genesis/ Genesis state generator ``` ### Dependency Layers The crates form three layers with one-way dependencies flowing downward: #### Node Layer `arb-node`, `arb-rpc`, `arb-payload`, `arb-txpool` - infrastructure that integrates with reth's node builder framework. These crates wire the execution layer into a runnable node with RPC endpoints and transaction pool management. #### Execution Layer `arb-evm`, `arbos`, `arb-precompiles`, `arb-stylus` - the core state transition logic. `arb-evm` implements reth's `BlockExecutor` trait and delegates to `arbos` for protocol-level state changes. Precompiles and Stylus are called during EVM execution. #### Primitives Layer `arb-primitives`, `arb-storage`, `arb-chainspec` - foundational types shared across all crates. Transaction types, storage abstractions, and version constants. ### Key Traits ArbReth integrates with reth through its standard trait system: | reth Trait | ArbReth Implementation | Purpose | | ---------------- | ---------------------- | -------------------------------------------- | | `BlockExecutor` | `ArbBlockExecutor` | Executes transactions within a block | | `EvmConfig` | `ArbEvmConfig` | Configures the EVM environment per block | | `StateProvider` | reth's built-in | Reads/writes state from the database | | `NodePrimitives` | `ArbPrimitives` | Defines transaction and receipt types | | `Consensus` | `ArbConsensus` | Validates blocks (trust-the-sequencer model) | ### Block Production Flow When the Nitro consensus node sends a message via `nitroexecution_digestMessage`: 1. **Message parsing** - L1-to-L2 message is decoded into transaction segments 2. **Internal transactions** - `StartBlock` initializes ArbOS state for the new block 3. **User transactions** - each transaction runs through the EVM with ArbOS hooks for gas charging, poster fee deduction, and fee distribution 4. **End-of-block** - state is committed, header fields are derived (mix\_hash encodes ArbOS version, send\_count, L1 block number), and the block is persisted ### External Dependencies ArbReth builds on several foundational Rust projects: * [**reth**](https://github.com/paradigmxyz/reth) - node framework, database, networking, and RPC infrastructure * [**revm**](https://github.com/bluealloy/revm) - EVM interpreter for transaction execution * [**alloy**](https://github.com/alloy-rs/alloy) - Ethereum types and primitives * [**wasmer**](https://github.com/wasmerio/wasmer) - WASM runtime for Stylus program execution ## Configuration ### Environment Variables When running with Docker Compose, configuration is done through the `.env` file. Copy `.env.example` and edit: | Variable | Required | Default | Description | | ------------------------- | -------- | ---------------------------------------------- | ------------------------------------------------------ | | `PARENT_CHAIN_RPC_URL` | Yes | - | Ethereum L1 RPC endpoint (Sepolia for testnet) | | `PARENT_CHAIN_BEACON_URL` | Yes | - | Ethereum Beacon API endpoint | | `CHAIN_ID` | No | `421614` | Arbitrum chain ID (421614 = Sepolia) | | `NITRO_IMAGE` | No | `offchainlabs/nitro-node:v3.10.0-rc.2-746bda2` | Consensus node Docker image | | `FEED_URL` | No | `wss://sepolia-rollup.arbitrum.io/feed` | Sequencer feed for faster sync | | `LOG_LEVEL` | No | `WARN` | Consensus node log level (TRACE/DEBUG/INFO/WARN/ERROR) | | `RUST_LOG` | No | `warn` | Execution client log level | | `DATA_DIR` | No | Docker volume | Host path for execution data | | `NITRO_DATA_DIR` | No | Docker volume | Host path for consensus data | ### CLI Options ArbReth inherits all of reth's CLI options. The most commonly used: #### Node ```bash arb-reth node [OPTIONS] ``` | Flag | Description | | ---------------------------- | --------------------------------------------------- | | `--chain ` | Path to chain genesis JSON | | `--datadir ` | Database directory | | `--http` | Enable HTTP JSON-RPC server | | `--http.addr ` | HTTP listen address (default: 127.0.0.1) | | `--http.port ` | HTTP port (default: 8545) | | `--http.api ` | Comma-separated API namespaces (eth,web3,net,debug) | | `--authrpc.addr ` | Engine API listen address | | `--authrpc.port ` | Engine API port (default: 8551) | | `--authrpc.jwtsecret ` | Path to JWT secret file | | `--disable-discovery` | Disable P2P discovery (recommended for Arbitrum) | | `--rollup.sequencer` | Enable sequencer mode | #### Database | Flag | Description | | ------------------------- | ---------------------------------------- | | `--db.exclusive=true` | Exclusive database access (recommended) | | `--db.growth-step ` | MDBX growth step (e.g., 4GB) | | `--db.log-level ` | Database log level | | `--db.sync-mode ` | Sync mode (safe-no-sync for performance) | #### Logging | Flag | Description | | ------------------------------ | ----------------------------------------------- | | `--log.stdout.filter ` | Log filter for stdout (e.g., warn, info, debug) | ### Genesis Files ArbReth ships with genesis configurations in the `genesis/` directory: * `genesis/arbitrum-sepolia.json` - Arbitrum Sepolia testnet The genesis file contains the initial chain configuration, alloc entries for precompile contracts, and the ArbOS state initialization at the ArbOS state address (`0xa4b05...`). #### Key Genesis Parameters | Field | Value | Meaning | | --------------- | ----------------- | ----------------------------------------- | | `chainId` | 421614 | Arbitrum Sepolia | | `difficulty` | 1 | Constant (proof-of-authority) | | `baseFeePerGas` | 100 Gwei | Initial L2 base fee | | `gasLimit` | Very large | Arbitrum manages gas limits through ArbOS | | `mixHash` | Encodes ArbOS v10 | Initial ArbOS version embedded in header | ## Contributing ### Getting Started ```bash git clone https://github.com/0xBloctopus/arbreth.git cd arbreth cargo check cargo test ``` **Requirements:** Rust 1.93+, clang, cmake ### Development Workflow 1. Fork the repository and create a feature branch 2. Make your changes 3. Run all checks: ```bash cargo +nightly fmt --all # format cargo clippy --workspace \ --all-targets -- -D warnings # lint cargo test --workspace # test cargo doc --workspace --no-deps # docs ``` 4. Open a pull request against `master` ### Code Standards * Use `alloy-primitives` types (`Address`, `B256`, `U256`) - not raw `[u8; 32]` * Use `thiserror` for error types * Use `#[derive(Debug, Clone)]` on structs * No `unwrap()` in library code - use `?` and proper error types * Document public APIs with doc comments * Follow existing reth patterns for crate and module structure ### Pull Requests * Open an issue before starting work on larger changes * Keep PRs focused - one logical change per PR * Include a clear description of what changed and why * Ensure CI passes before requesting review ### Reporting Issues * **Bugs**: Open a [GitHub issue](https://github.com/0xBloctopus/arbreth/issues) with reproduction steps * **Security vulnerabilities**: See [SECURITY.md](https://github.com/0xBloctopus/arbreth/blob/master/SECURITY.md) - do not file a public issue ### License By contributing, you agree that your contributions will be licensed under the [Business Source License 1.1](https://github.com/0xBloctopus/arbreth/blob/master/LICENSE.md). ## Installation ### Docker (recommended) The simplest way to run ArbReth is with Docker Compose, which starts both the execution client and the Nitro consensus node. ```bash git clone https://github.com/0xBloctopus/arbreth.git cd arbreth cp .env.example .env ``` Edit `.env` to set your L1 RPC endpoints: ```bash PARENT_CHAIN_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY PARENT_CHAIN_BEACON_URL=https://eth-sepoliabeacon.g.alchemy.com/v2/YOUR_KEY ``` Start the stack: ```bash docker compose up -d ``` ArbReth will start syncing Arbitrum Sepolia. The Nitro consensus node waits for ArbReth to pass its healthcheck before starting. #### Verify sync progress ```bash curl -s -X POST http://localhost:8545 \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' ``` #### View logs ```bash docker compose logs -f arbreth # execution client docker compose logs -f nitro # consensus node ``` #### Stop ```bash docker compose down ``` Data is persisted in Docker named volumes (`arbreth-data`, `nitro-data`) and survives restarts. ### Build from Source #### Requirements * **Rust** 1.93 or later * **clang** and **libclang-dev** (for C dependencies) * **cmake** (for cryptographic library builds) #### Build ```bash git clone https://github.com/0xBloctopus/arbreth.git cd arbreth cargo build --release -p arb-reth ``` The binary is at `./target/release/arb-reth`. #### Run ArbReth requires a JWT secret for the Engine API connection with the Nitro consensus node: ```bash openssl rand -hex 32 > /path/to/jwt.hex ``` Start the node: ```bash ./target/release/arb-reth node \ --chain=genesis/arbitrum-sepolia.json \ --datadir=/path/to/data \ --http \ --http.addr=0.0.0.0 \ --http.api=eth,web3,net,debug \ --authrpc.addr=0.0.0.0 \ --authrpc.jwtsecret=/path/to/jwt.hex ``` #### Ports | Port | Service | Auth | | ---- | --------------- | ---- | | 8545 | JSON-RPC (HTTP) | None | | 8551 | Engine API | JWT | The Nitro consensus node connects to port 8551 using the shared JWT secret. ### Running the Consensus Node ArbReth is an execution client only. It needs the Nitro consensus node to receive L1 messages and drive block production. You can run Nitro separately: ```bash docker run offchainlabs/nitro-node:v3.10.0-rc.2-746bda2 \ --init.empty=true \ --init.validate-genesis-assertion=false \ --parent-chain.connection.url=YOUR_L1_RPC \ --parent-chain.blob-client.beacon-url=YOUR_BEACON_URL \ --chain.id=421614 \ --node.execution-rpc-client.url=http://HOST:8551 \ --node.execution-rpc-client.jwtsecret=/path/to/jwt.hex \ --node.sequencer=false \ --node.feed.input.url=wss://sepolia-rollup.arbitrum.io/feed ``` The `docker-compose.yml` in the repository handles this setup automatically. ## Introduction ArbReth is a ground-up Rust implementation of [Arbitrum Nitro](https://github.com/OffchainLabs/nitro)'s execution layer. It replaces Nitro's embedded Geth fork with [reth](https://github.com/paradigmxyz/reth), delivering the same state-transition logic through a modular crate architecture. Each component - ArbOS state management, L1/L2 pricing, precompiles, Stylus WASM execution - lives in its own crate and builds on reth's trait system (`BlockExecutor`, `StateProvider`, `EvmConfig`). ### Why ArbReth? * **Native Rust performance** - no CGo boundary, no garbage collector pauses * **Modular by design** - each subsystem is an independent crate, consumable as a library * **reth ecosystem** - inherits reth's database layer, networking, RPC framework, and CLI * **SDK-ready** - build custom Arbitrum tooling, indexers, or alternative node configurations using individual crates ### Supported Networks | Network | Chain ID | Status | | ---------------- | -------- | --------- | | Arbitrum Sepolia | 421614 | Supported | | Arbitrum One | 42161 | Planned | ### How It Works ArbReth runs as the **execution layer** in a two-component architecture: 1. **Nitro consensus node** - handles L1 interaction, sequencer feed, and message ordering 2. **ArbReth execution node** - receives ordered messages via the `nitroexecution_` RPC namespace, produces blocks, and manages state The consensus node drives ArbReth through the Engine API (port 8551), sending L1-to-L2 messages that ArbReth processes into blocks. This is the same split architecture used by Ethereum's beacon chain and execution client separation. ### License ArbReth is licensed under the [Business Source License 1.1](https://github.com/0xBloctopus/arbreth/blob/master/LICENSE.md), consistent with the Arbitrum Nitro license. It converts to Apache 2.0 on December 31, 2030. ## JSON-RPC API ArbReth serves three RPC namespaces: the standard `eth_` namespace (with Arbitrum extensions), the `arb_` namespace for Arbitrum-specific queries, and the `nitroexecution_` namespace for consensus-layer communication. ### eth\_ Namespace ArbReth supports the full standard `eth_` namespace inherited from reth. The key difference is gas estimation: #### Gas Estimation `eth_estimateGas` includes L1 posting costs on top of L2 execution gas. The estimation: 1. Runs the standard binary search for compute gas 2. Calculates L1 posting gas: `calldata_units * l1_price_per_unit / l2_base_fee` 3. Applies 110% padding to the L1 component 4. Returns `compute_gas + l1_posting_gas` This ensures transactions include enough gas to cover both execution and data availability costs. ### arb\_ Namespace Arbitrum-specific informational methods. #### `arb_getBlockInfo` Returns Arbitrum-specific block metadata. ```json // Request {"jsonrpc":"2.0","method":"arb_getBlockInfo","params":[12345],"id":1} // Response { "l1BlockNumber": 19000000, "arbosFormatVersion": 20, "sendCount": 5432, "sendRoot": "0xabcd..." } ``` | Field | Description | | -------------------- | ------------------------------------------------------ | | `l1BlockNumber` | L1 block number at the time this L2 block was produced | | `arbosFormatVersion` | ArbOS version active for this block | | `sendCount` | Cumulative L2-to-L1 message count | | `sendRoot` | Merkle root of the L2-to-L1 send tree | #### `arb_maintenanceStatus` Returns the current maintenance status of the node. #### `arb_checkPublisherHealth` Health check endpoint (returns null on success). ### nitroexecution\_ Namespace The interface between the Nitro consensus node and ArbReth. These methods are called by the consensus layer to drive block production and are exposed on the authenticated Engine API port (8551). #### `nitroexecution_digestMessage` The primary block production method. The consensus node sends an ordered L1-to-L2 message, and ArbReth produces a block from it. ``` digestMessage(msgIdx, message, messageForPrefetch?) -> MessageResult ``` **Parameters:** * `msgIdx` - sequential message index (starts at 1; message 0 is the genesis init message) * `message` - the L1 message with metadata (header, delayed flag, batch data) * `messageForPrefetch` - optional next message for state prefetching **Returns:** `{ blockHash, sendRoot }` for the produced block. #### `nitroexecution_headMessageIndex` Returns the index of the latest processed message. #### `nitroexecution_resultAtMessageIndex` Returns the block result for a previously processed message index. #### `nitroexecution_setFinalityData` Updates finality markers (safe, finalized, validated block numbers) from the consensus layer. #### `nitroexecution_setConsensusSyncData` Updates consensus sync state (synced status, max message count). #### `nitroexecution_reorg` Handles chain reorganizations by rolling back to a message index and replaying new messages. #### `nitroexecution_arbOSVersionForMessageIndex` Returns the ArbOS version active at a given message index. #### `nitroexecution_triggerMaintenance` Triggers database maintenance operations (compaction, pruning). ## Precompiles Arbitrum provides system contracts at fixed addresses that expose ArbOS functionality to smart contracts. These precompiles are callable from Solidity like any other contract. ### Address Map | Address | Name | Since | Purpose | | ------- | --------------------- | ------- | ------------------------------------------------ | | `0x64` | ArbSys | Genesis | L2-to-L1 messaging, block info, address aliasing | | `0x65` | ArbInfo | Genesis | Account balance and code queries | | `0x66` | ArbAddressTable | Genesis | Calldata compression via address lookup | | `0x67` | ArbBLS | Genesis | BLS signature registration (deprecated) | | `0x68` | ArbFunctionTable | Genesis | Function table registration (deprecated) | | `0x6B` | ArbOwnerPublic | Genesis | Read-only chain parameter queries | | `0x6C` | ArbGasInfo | Genesis | Gas price and fee telemetry | | `0x6D` | ArbAggregator | Genesis | Preferred aggregator settings (deprecated) | | `0x6E` | ArbRetryableTx | Genesis | Retryable ticket lifecycle management | | `0x6F` | ArbStatistics | Genesis | Chain statistics | | `0x70` | ArbOwner | Genesis | Chain owner configuration (permissioned) | | `0x71` | ArbWasm | v30 | Stylus program activation and queries | | `0x72` | ArbWasmCache | v30 | Stylus cache management | | `0x73` | ArbNativeTokenManager | v41 | Custom gas token support | | `0x74` | ArbFilteredTxManager | v60 | Transaction filtering configuration | | `0xC8` | NodeInterface | Genesis | Gas estimation and node queries | | `0xFF` | ArbDebug | Genesis | Debug utilities (intentionally stubbed) | ### Key Precompiles #### ArbSys (0x64) The most commonly used precompile. Provides: * **`arbBlockNumber()`** - returns the L2 block number (since `NUMBER` opcode returns L1 block number) * **`arbBlockHash(blockNumber)`** - returns L2 block hashes * **`sendTxToL1(destination, data)`** - sends an L2-to-L1 message (emits `L2ToL1Tx` event) * **`isTopLevelCall()`** - returns true if the current call is not a sub-call * **`wasMyCallersAddressAliased()`** - checks if the caller's address was L1 aliased * **`arbChainID()`** - returns the Arbitrum chain ID #### ArbGasInfo (0x6C) Exposes the full gas pricing state: * **`getPricesInWei()`** - returns L2 gas price, L1 calldata price per byte, storage price, and more * **`getPricesInArbGas()`** - same prices denominated in L2 gas units * **`getCurrentTxL1GasFees()`** - L1 gas fees for the current transaction * **`getGasAccountingParams()`** - speed limit, pool size, and per-block gas limit * **`getL1BaseFeeEstimate()`** - current L1 base fee as seen by ArbOS #### ArbRetryableTx (0x6E) Manages the retryable ticket lifecycle: * **`redeem(ticketId)`** - manually redeem an expired auto-redeem * **`getTimeout(ticketId)`** - returns the ticket's expiry timestamp * **`getLifetime()`** - returns the default ticket lifetime (7 days) * **`keepalive(ticketId)`** - extends the ticket's expiry by one lifetime period * **`cancel(ticketId)`** - cancels a ticket and refunds remaining deposit * **`getBeneficiary(ticketId)`** - returns the refund beneficiary address #### ArbOwner (0x70) Permissioned configuration methods (only callable by chain owners): * **`setL2BaseFee(priceInWei)`** - set L2 base fee * **`setSpeedLimit(limit)`** - set the per-second gas speed limit * **`setNetworkFeeAccount(account)`** - set the network fee recipient * **`setInfraFeeAccount(account)`** - set the infrastructure fee recipient * **`addChainOwner(owner)`** / **`removeChainOwner(owner)`** - manage chain owners * **`scheduleArbOSUpgrade(version, timestamp)`** - schedule an ArbOS version upgrade #### NodeInterface (0xC8) Virtual precompile for node-level queries (not accessible on-chain, only via `eth_call`): * **`estimateRetryableTicket(...)`** - estimates gas for a retryable ticket submission * **`gasEstimateComponents(to, contractCreation, data)`** - breaks down gas into L1 and L2 components * **`gasEstimateL1Component(to, contractCreation, data)`** - estimates only the L1 gas component * **`nitroGenesisBlock()`** - returns the genesis block number ### Version Gating Precompiles are version-gated at two levels: 1. **Precompile-level** - the entire precompile is inactive before its introduction version (calls return empty bytes) 2. **Method-level** - individual methods within a precompile can have their own version gates (calls revert if the version is out of range) ## Stylus WASM Runtime Stylus allows smart contracts written in Rust, C, and C++ to run on Arbitrum as compiled WASM programs alongside standard EVM contracts. ArbReth implements the full Stylus execution pipeline. ### How Stylus Works Stylus programs are identified by a 3-byte prefix in their deployed bytecode: `[0xEF, 0xF0, 0x00]`. When the EVM encounters a call to an address with this prefix, execution is dispatched to the WASM runtime instead of the EVM interpreter. #### Execution Flow 1. **Detection** - during an EVM CALL, the target's bytecode is checked for the Stylus discriminant 2. **Cache lookup** - the compiled WASM module is loaded from cache (or compiled on-the-fly) 3. **Ink conversion** - EVM gas is converted to "ink" (Stylus's internal gas unit) at the configured `ink_price` rate 4. **Execution** - the WASM program runs in a wasmer sandbox with host I/O functions for EVM state access 5. **Gas settlement** - remaining ink is converted back to EVM gas and returned to the caller ### Ink Metering Stylus uses **ink** as its internal gas unit, separate from EVM gas. The conversion rate is configurable: ``` Gas * ink_price = Ink ``` Ink is deducted for: * WASM opcode execution (via compiler-injected metering middleware) * Host I/O calls (storage reads/writes, calls, memory operations) * Memory page allocation #### Upfront Gas Costs Before a Stylus program starts executing, gas is deducted for: * **Memory initialization** - based on the program's memory footprint * **Program startup** - `init_gas` (first activation) or `cached_gas` (subsequent calls) ### Program Lifecycle #### Activation Programs must be activated before they can be called. Activation: 1. Validates the WASM module structure 2. Instruments it with ink metering middleware 3. Compiles to native code via Cranelift 4. Stores metadata (version, init cost, cached cost, footprint) in a packed 32-byte storage word 5. Charges a data pricing fee based on an exponential demand curve #### Caching Compiled WASM modules are cached in a two-tier system: * **Long-term cache** - pinned programs that are always available * **LRU cache** - recently-used programs evicted under memory pressure Cache management is exposed through the `ArbWasmCache` precompile (0x72). ### Host I/O Stylus programs interact with EVM state through host functions: | Host Function | Purpose | Ink Cost | | ---------------------------------- | ------------------------------- | ------------------------------ | | `storage_load_bytes32` | Read a storage slot | Cold SLOAD + access cost | | `storage_cache_bytes32` | Write to storage cache | SSTORE sentry + access cost | | `storage_flush_cache` | Flush cached writes | Per-slot SSTORE cost | | `call_contract` | CALL to another contract | EVM call cost + gas forwarding | | `delegate_call_contract` | DELEGATECALL | Same as call | | `static_call_contract` | STATICCALL | Same as call | | `create1` / `create2` | Deploy a contract | CREATE/CREATE2 gas | | `emit_log` | Emit an event | LOG base + topic + data costs | | `account_balance` | Read account balance | Account access cost | | `account_codehash` | Read code hash | Account access cost | | `tx_origin` / `msg_sender` | Transaction context | Base hostio cost | | `block_timestamp` / `block_number` | Block context | Base hostio cost | | `read_return_data` | Read return data from last call | Base hostio cost | | `msg_value` | Callvalue | Base hostio cost | ### Sub-Calls Stylus programs can call other contracts (both EVM and Stylus): * **EVM contracts** - ink is converted back to gas, a standard EVM frame is created * **Stylus contracts** - gas is converted to ink in the callee's context * **Precompiles** - dispatched through the standard precompile routing The 63/64ths gas forwarding rule applies, matching EVM semantics. Call depth is tracked and reentrancy into the same Stylus program is detected per-address. ### Precompiles | Address | Name | Purpose | | ------- | ------------ | ----------------------------------------------------- | | `0x71` | ArbWasm | Activate programs, query version/gas/footprint/expiry | | `0x72` | ArbWasmCache | Pin/unpin programs in cache, query cache status | Both precompiles are available from ArbOS v30 onward. ## Transaction Types Arbitrum extends the EIP-2718 typed transaction envelope with six custom transaction types. These are used for L1-to-L2 messaging, internal protocol operations, and retryable tickets. ### Type Bytes | Type | Byte | Name | Origin | | --------------- | ------ | --------------------------- | ------------------- | | Deposit | `0x64` | `ArbitrumDepositTx` | L1 bridge | | Unsigned | `0x65` | `ArbitrumUnsignedTx` | L1 (no signature) | | Contract | `0x66` | `ArbitrumContractTx` | L1 contract call | | Retry | `0x68` | `ArbitrumRetryTx` | Auto/manual redeem | | SubmitRetryable | `0x69` | `ArbitrumSubmitRetryableTx` | L1 retryable ticket | | Internal | `0x6A` | `ArbitrumInternalTx` | ArbOS protocol | Standard Ethereum transaction types (Legacy, EIP-2930, EIP-1559) are also supported for regular L2 transactions. ### Deposit Transactions (0x64) Deposits move ETH from L1 to L2. They are included in blocks when the bridge contract emits a deposit event on L1. * **Sender**: Set to the L1 depositor (with address aliasing for contracts) * **No signature**: Authenticated by L1 inclusion proof * **Always succeeds**: Value is minted on L2 regardless of execution outcome * **No gas cost**: Gas is paid on L1 ### Unsigned Transactions (0x65) L1-originated transactions without an ECDSA signature. The sender is determined by the L1 message rather than signature recovery. ### Contract Transactions (0x66) Similar to unsigned transactions but specifically for contract-to-contract L1-to-L2 calls. The sender address is aliased (offset by `0x1111000000000000000000000000000000001111`) to prevent address collisions between L1 and L2. ### Retryable Tickets (0x69) Retryable tickets are the primary mechanism for L1-to-L2 message passing with guaranteed delivery. #### Lifecycle 1. **Submit** - L1 transaction creates a retryable ticket on L2 with a deposit covering fees and call value 2. **Auto-redeem** - ArbOS automatically attempts to execute the ticket immediately 3. **Manual redeem** - if auto-redeem fails, anyone can redeem the ticket later via `ArbRetryableTx.redeem()` 4. **Expiry** - unredeemed tickets expire after 7 days (configurable) and deposits are refunded to the beneficiary #### Fee Structure * **Submission fee**: `(1400 + 6 * calldata_length) * l1_base_fee` * **Call value**: Held in escrow at a derived address (`keccak256("retryable escrow" || ticket_id)`) * **Excess deposit**: Refunded to the designated refund address ### Retry Transactions (0x68) Generated when a retryable ticket is redeemed (either automatically or manually). These carry the original call parameters from the retryable submission. ### Internal Transactions (0x6A) Protocol-level transactions generated by ArbOS itself, not by any external account. Two primary types: * **StartBlock** (`0x6bf6a42d`) - initializes ArbOS state at the beginning of each block (pricing updates, retryable reaping, L1 block number caching) * **BatchPostingReport** - records L1 batch posting costs for the L1 pricing model Internal transactions are always the first transactions in a block and have no gas cost. ### Receipt Extensions Arbitrum receipts include additional fields beyond standard Ethereum receipts: | Field | Description | | -------------- | ----------------------------------------- | | `gasUsedForL1` | Gas attributable to L1 data posting costs | Receipt type bytes mirror the transaction type bytes (`0x64`-`0x6A`), each with its own RLP encoding.