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