arb_node/
genesis.rs

1//! ArbOS genesis state initialization.
2//!
3//! Initializes the ArbOS system state in the database when the chain boots.
4//! Runs when the first message (Kind=11, Initialize) is received from the
5//! consensus sidecar.
6
7use alloy_primitives::{Address, B256, Bytes, U256, address};
8use arb_storage::{
9    ARBOS_STATE_ADDRESS, Storage, StorageBackedBigUint, StorageBackedBytes,
10    layout::{
11        ADDRESS_TABLE_SUBSPACE, BLOCKHASHES_SUBSPACE, CHAIN_CONFIG_SUBSPACE, CHAIN_OWNER_SUBSPACE,
12        FEATURES_SUBSPACE, L1_PRICING_SUBSPACE, L2_PRICING_SUBSPACE, RETRYABLES_SUBSPACE,
13        SEND_MERKLE_SUBSPACE,
14    },
15    set_account_code, set_account_nonce,
16};
17use arbos::{
18    arbos_state::ArbosState, burn::SystemBurner, l1_pricing, l2_pricing, types::ParsedInitMessage,
19};
20use revm::{Database, database::State};
21use tracing::info;
22
23use crate::error::GenesisError;
24
25/// Precompile addresses that exist at genesis (version 0).
26/// Only these get the `[0xFE]` invalid code marker at init time.
27/// Later precompiles (ArbWasm, ArbWasmCache, etc.) get code when their
28/// ArbOS version is reached during the upgrade path.
29const GENESIS_PRECOMPILE_ADDRESSES: [Address; 14] = [
30    address!("0000000000000000000000000000000000000064"), // ArbSys
31    address!("0000000000000000000000000000000000000065"), // ArbInfo
32    address!("0000000000000000000000000000000000000066"), // ArbAddressTable
33    address!("0000000000000000000000000000000000000067"), // ArbBLS
34    address!("0000000000000000000000000000000000000068"), // ArbFunctionTable
35    address!("0000000000000000000000000000000000000069"), // ArbosTest
36    address!("000000000000000000000000000000000000006b"), // ArbOwnerPublic
37    address!("000000000000000000000000000000000000006c"), // ArbGasInfo
38    address!("000000000000000000000000000000000000006d"), // ArbAggregator
39    address!("000000000000000000000000000000000000006e"), // ArbRetryableTx
40    address!("000000000000000000000000000000000000006f"), // ArbStatistics
41    address!("0000000000000000000000000000000000000070"), // ArbOwner
42    address!("00000000000000000000000000000000000000ff"), // ArbDebug
43    address!("00000000000000000000000000000000000a4b05"), // ArbosActs
44];
45
46/// The initial ArbOS version for Arbitrum Sepolia genesis.
47/// The upgrade_arbos_version path handles stepping through all intermediate versions.
48pub const INITIAL_ARBOS_VERSION: u64 = 10;
49
50/// Default chain owner for Arbitrum Sepolia.
51pub const DEFAULT_CHAIN_OWNER: Address = address!("0000000000000000000000000000000000000000");
52
53/// Initialize ArbOS state in a freshly created database.
54///
55/// This sets up:
56/// - ArbOS version (set to 1, then upgrade to target version)
57/// - All precompile accounts with `[0xFE]` invalid code marker
58/// - L1 pricing state (initial base fee, batch poster table)
59/// - L2 pricing state (base fee, gas pool, speed limit)
60/// - Retryable state, address table, merkle accumulator, blockhashes
61/// - Chain owner and chain config
62///
63/// The `init_msg` comes from parsing the L1 Initialize message (Kind=11).
64#[derive(Debug, Clone, Copy, Default)]
65pub struct ArbOSInit {
66    pub native_token_supply_management_enabled: bool,
67    pub transaction_filtering_enabled: bool,
68}
69
70pub fn initialize_arbos_state<D: Database>(
71    state: &mut State<D>,
72    init_msg: &ParsedInitMessage,
73    chain_id: u64,
74    target_arbos_version: u64,
75    chain_owner: Address,
76    arbos_init: ArbOSInit,
77) -> Result<(), GenesisError> {
78    let backing = Storage::new(state, B256::ZERO);
79    if backing.get_uint64_by_uint64(0).unwrap_or(0) != 0 {
80        return Err(GenesisError::AlreadyInitialized);
81    }
82
83    info!(
84        target: "arb::genesis",
85        chain_id,
86        target_arbos_version,
87        initial_l1_base_fee = %init_msg.initial_l1_base_fee,
88        "Initializing ArbOS state"
89    );
90
91    // SAFETY: genesis runs single-threaded; no two state_mut borrows are live
92    // concurrently. `backing` is the only live Storage handle.
93    set_account_nonce(unsafe { backing.state_mut() }, ARBOS_STATE_ADDRESS, 1);
94
95    // 1. Set version to 1 (base version before upgrades).
96    backing
97        .set_by_uint64(0, B256::from(U256::from(1u64)))
98        .map_err(|source| GenesisError::StorageWrite {
99            what: "initial version",
100            source,
101        })?;
102
103    // 2. Set chain ID.
104    // SAFETY: see initial state_mut() comment; no overlapping Storage handles.
105    StorageBackedBigUint::new(B256::ZERO, 4)
106        .set(unsafe { backing.state_mut() }, U256::from(chain_id))
107        .map_err(|source| GenesisError::StorageWrite {
108            what: "chain id",
109            source,
110        })?;
111
112    // 3. Install precompile code markers for version-0 precompiles only.
113    for addr in &GENESIS_PRECOMPILE_ADDRESSES {
114        // SAFETY: see initial state_mut() comment.
115        set_account_code(
116            unsafe { backing.state_mut() },
117            *addr,
118            Bytes::from_static(&[0xFE]),
119        );
120    }
121
122    // 3b. Set network fee account (chain owner for version >= 2).
123    if target_arbos_version >= 2 {
124        let mut hash = B256::ZERO;
125        hash[12..32].copy_from_slice(chain_owner.as_slice());
126        backing
127            .set_by_uint64(3, hash)
128            .map_err(|source| GenesisError::StorageWrite {
129                what: "network fee account",
130                source,
131            })?;
132    }
133
134    // 3c. Store serialized chain config.
135    if !init_msg.serialized_chain_config.is_empty() {
136        let cc_sto = backing.open_sub_storage(CHAIN_CONFIG_SUBSPACE);
137        let cc_bytes = StorageBackedBytes::new(cc_sto.base_key());
138        // SAFETY: see initial state_mut() comment.
139        cc_bytes
140            .set(
141                unsafe { backing.state_mut() },
142                &init_msg.serialized_chain_config,
143            )
144            .map_err(|source| GenesisError::StorageWrite {
145                what: "chain config",
146                source,
147            })?;
148    }
149
150    // 4. Initialize L1 pricing state.
151    let l1_sto = backing.open_sub_storage(L1_PRICING_SUBSPACE);
152    let rewards_recipient = if target_arbos_version >= 2 {
153        chain_owner
154    } else {
155        Address::ZERO
156    };
157    // SAFETY: see initial state_mut() comment.
158    l1_pricing::L1PricingState::initialize(
159        &l1_sto,
160        unsafe { backing.state_mut() },
161        rewards_recipient,
162        init_msg.initial_l1_base_fee,
163    )
164    .map_err(|e| GenesisError::InitSubsystem {
165        subsystem: "L1 pricing",
166        source: e.into(),
167    })?;
168
169    // 5. Initialize L2 pricing state.
170    let l2_sto = backing.open_sub_storage(L2_PRICING_SUBSPACE);
171    // SAFETY: see initial state_mut() comment.
172    l2_pricing::L2PricingState::initialize(&l2_sto, unsafe { backing.state_mut() }).map_err(
173        |e| GenesisError::InitSubsystem {
174            subsystem: "L2 pricing",
175            source: e.into(),
176        },
177    )?;
178
179    // 6. Initialize retryable state.
180    let ret_sto = backing.open_sub_storage(RETRYABLES_SUBSPACE);
181    arbos::retryables::RetryableState::initialize(&ret_sto).map_err(|e| {
182        GenesisError::InitSubsystem {
183            subsystem: "retryables",
184            source: e.into(),
185        }
186    })?;
187
188    // 7. Initialize address table (no-op but call for consistency).
189    let at_sto = backing.open_sub_storage(ADDRESS_TABLE_SUBSPACE);
190    arbos::address_table::initialize_address_table(&at_sto);
191
192    // 8. Initialize chain owners.
193    let co_sto = backing.open_sub_storage(CHAIN_OWNER_SUBSPACE);
194    arbos::address_set::initialize_address_set(&co_sto).map_err(|e| {
195        GenesisError::InitSubsystem {
196            subsystem: "chain owners",
197            source: e.into(),
198        }
199    })?;
200
201    // 9. Initialize merkle accumulator.
202    let ma_sto = backing.open_sub_storage(SEND_MERKLE_SUBSPACE);
203    arbos::merkle_accumulator::initialize_merkle_accumulator(&ma_sto);
204
205    // 10. Initialize blockhashes.
206    let bh_sto = backing.open_sub_storage(BLOCKHASHES_SUBSPACE);
207    arbos::blockhash::initialize_blockhashes(&bh_sto);
208
209    // 11. Initialize features.
210    let _feat_sto = backing.open_sub_storage(FEATURES_SUBSPACE);
211
212    // Open after persisting `version = 1` above. A failure here means the
213    // freshly written version word is unreadable, which is unrecoverable
214    // during genesis bring-up.
215    // SAFETY: see initial state_mut() comment.
216    let mut arb_state = ArbosState::open(
217        unsafe { backing.state_mut() },
218        SystemBurner::new(None, false),
219    )
220    .expect("open ArbOS state after genesis initial setup");
221
222    // SAFETY: see initial state_mut() comment.
223    arb_state
224        .chain_owners
225        .add(unsafe { backing.state_mut() }, chain_owner)
226        .map_err(|e| GenesisError::InitSubsystem {
227            subsystem: "chain owner",
228            source: e.into(),
229        })?;
230
231    if arbos_init.native_token_supply_management_enabled {
232        // SAFETY: see initial state_mut() comment.
233        arb_state
234            .set_native_token_management_from_time(unsafe { backing.state_mut() }, 1)
235            .map_err(|source| GenesisError::InitSubsystem {
236                subsystem: "native token management",
237                source,
238            })?;
239    }
240    if arbos_init.transaction_filtering_enabled {
241        // SAFETY: see initial state_mut() comment.
242        arb_state
243            .set_transaction_filtering_from_time(unsafe { backing.state_mut() }, 1)
244            .map_err(|source| GenesisError::InitSubsystem {
245                subsystem: "transaction filtering",
246                source,
247            })?;
248    }
249
250    if target_arbos_version > 1 {
251        // SAFETY: see initial state_mut() comment.
252        arb_state
253            .upgrade_arbos_version(unsafe { backing.state_mut() }, target_arbos_version, true)
254            .map_err(|source| GenesisError::Upgrade {
255                target: target_arbos_version,
256                source,
257            })?;
258    }
259
260    info!(
261        target: "arb::genesis",
262        final_version = arb_state.arbos_version(),
263        "ArbOS state initialized"
264    );
265
266    Ok(())
267}
268
269/// Check if ArbOS state is already initialized in the given state database.
270pub fn is_arbos_initialized<D: Database>(state: &mut State<D>) -> bool {
271    let backing = Storage::new(state, B256::ZERO);
272    backing.get_uint64_by_uint64(0).unwrap_or(0) != 0
273}