arbos/arbos_state/
mod.rs

1pub mod initialize;
2
3use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
4use revm::Database;
5use std::sync::OnceLock;
6
7use arb_primitives::arbos_versions::{
8    HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE_ARBITRUM, PRECOMPILE_MIN_ARBOS_VERSIONS,
9};
10use arb_storage::{
11    get_account_balance, set_account_code, set_account_nonce, Storage, StorageBackedAddress,
12    StorageBackedBigUint, StorageBackedBytes, StorageBackedUint64, FILTERED_TX_STATE_ADDRESS,
13};
14
15use crate::{
16    address_set::{self, AddressSet},
17    address_table::{self, AddressTable},
18    blockhash::{self, Blockhashes},
19    burn::Burner,
20    features::{self, Features},
21    filtered_transactions::FilteredTransactionsState,
22    l1_pricing::{self, L1PricingState},
23    l2_pricing::{self, L2PricingState},
24    merkle_accumulator::{self, MerkleAccumulator},
25    programs::Programs,
26    retryables::RetryableState,
27};
28
29// Storage offsets for root-level ArbOS state values.
30const VERSION_OFFSET: u64 = 0;
31const UPGRADE_VERSION_OFFSET: u64 = 1;
32const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
33const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
34const CHAIN_ID_OFFSET: u64 = 4;
35const GENESIS_BLOCK_NUM_OFFSET: u64 = 5;
36const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
37const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
38const NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET: u64 = 8;
39const TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET: u64 = 9;
40const FILTERED_FUNDS_RECIPIENT_OFFSET: u64 = 10;
41const COLLECT_TIPS_OFFSET: u64 = 11;
42
43// Subspace IDs for partitioned storage.
44pub(crate) const L1_PRICING_SUBSPACE: &[u8] = &[0];
45pub(crate) const L2_PRICING_SUBSPACE: &[u8] = &[1];
46pub(crate) const RETRYABLES_SUBSPACE: &[u8] = &[2];
47const ADDRESS_TABLE_SUBSPACE: &[u8] = &[3];
48const CHAIN_OWNER_SUBSPACE: &[u8] = &[4];
49const SEND_MERKLE_SUBSPACE: &[u8] = &[5];
50const BLOCKHASHES_SUBSPACE: &[u8] = &[6];
51const CHAIN_CONFIG_SUBSPACE: &[u8] = &[7];
52pub(crate) const PROGRAMS_SUBSPACE: &[u8] = &[8];
53const FEATURES_SUBSPACE: &[u8] = &[9];
54const NATIVE_TOKEN_OWNER_SUBSPACE: &[u8] = &[10];
55const TRANSACTION_FILTERING_SUBSPACE: &[u8] = &[11];
56
57/// Cached root→subspace derivations: `keccak256(sub_key)` for each static child.
58macro_rules! cached_root_key {
59    ($name:ident, $sub:expr) => {
60        fn $name() -> B256 {
61            static KEY: OnceLock<B256> = OnceLock::new();
62            *KEY.get_or_init(|| keccak256($sub))
63        }
64    };
65}
66
67cached_root_key!(l1_pricing_root_key, L1_PRICING_SUBSPACE);
68cached_root_key!(l2_pricing_root_key, L2_PRICING_SUBSPACE);
69cached_root_key!(retryables_root_key, RETRYABLES_SUBSPACE);
70cached_root_key!(address_table_root_key, ADDRESS_TABLE_SUBSPACE);
71cached_root_key!(chain_owner_root_key, CHAIN_OWNER_SUBSPACE);
72cached_root_key!(send_merkle_root_key, SEND_MERKLE_SUBSPACE);
73cached_root_key!(blockhashes_root_key, BLOCKHASHES_SUBSPACE);
74cached_root_key!(chain_config_root_key, CHAIN_CONFIG_SUBSPACE);
75cached_root_key!(programs_root_key, PROGRAMS_SUBSPACE);
76cached_root_key!(features_root_key, FEATURES_SUBSPACE);
77cached_root_key!(native_token_owner_root_key, NATIVE_TOKEN_OWNER_SUBSPACE);
78cached_root_key!(
79    transaction_filtering_root_key,
80    TRANSACTION_FILTERING_SUBSPACE
81);
82
83/// The maximum ArbOS version supported by this node.
84pub const MAX_ARBOS_VERSION_SUPPORTED: u64 = 60;
85
86/// Central ArbOS state aggregating all subsystem states.
87pub struct ArbosState<D, B: Burner> {
88    pub arbos_version: u64,
89    pub max_arbos_version_supported: u64,
90    pub upgrade_version: StorageBackedUint64<D>,
91    pub upgrade_timestamp: StorageBackedUint64<D>,
92    pub network_fee_account: StorageBackedAddress<D>,
93    pub l1_pricing_state: L1PricingState<D>,
94    pub l2_pricing_state: L2PricingState<D>,
95    pub retryable_state: RetryableState<D>,
96    pub address_table: AddressTable<D>,
97    pub chain_owners: AddressSet<D>,
98    pub send_merkle_accumulator: MerkleAccumulator<D>,
99    pub programs: Programs<D>,
100    pub blockhashes: Blockhashes<D>,
101    pub chain_id: StorageBackedBigUint<D>,
102    pub chain_config: StorageBackedBytes<D>,
103    pub genesis_block_num: StorageBackedUint64<D>,
104    pub infra_fee_account: StorageBackedAddress<D>,
105    pub brotli_compression_level: StorageBackedUint64<D>,
106    pub backing_storage: Storage<D>,
107    pub burner: B,
108    pub native_token_enabled_from_time: StorageBackedUint64<D>,
109    pub native_token_owners: AddressSet<D>,
110    pub transaction_filtering_enabled_from_time: StorageBackedUint64<D>,
111    pub transaction_filterers: AddressSet<D>,
112    pub features: Features<D>,
113    pub filtered_funds_recipient: StorageBackedAddress<D>,
114    pub filtered_transactions: FilteredTransactionsState<D>,
115    pub collect_tips: StorageBackedUint64<D>,
116}
117
118impl<D: Database, B: Burner> ArbosState<D, B> {
119    /// Open existing ArbOS state from storage.
120    pub fn open(state: *mut revm::database::State<D>, burner: B) -> Result<Self, ()> {
121        let backing_storage = Storage::new(state, B256::ZERO);
122
123        let arbos_version = backing_storage.get_uint64_by_uint64(VERSION_OFFSET)?;
124        if arbos_version == 0 {
125            return Err(());
126        }
127
128        let chain_config_sto = backing_storage.open_sub_storage_with_key(chain_config_root_key());
129        let features_sto = backing_storage.open_sub_storage_with_key(features_root_key());
130
131        Ok(Self {
132            arbos_version,
133            max_arbos_version_supported: MAX_ARBOS_VERSION_SUPPORTED,
134            upgrade_version: StorageBackedUint64::new(state, B256::ZERO, UPGRADE_VERSION_OFFSET),
135            upgrade_timestamp: StorageBackedUint64::new(
136                state,
137                B256::ZERO,
138                UPGRADE_TIMESTAMP_OFFSET,
139            ),
140            network_fee_account: StorageBackedAddress::new(
141                state,
142                B256::ZERO,
143                NETWORK_FEE_ACCOUNT_OFFSET,
144            ),
145            l1_pricing_state: L1PricingState::open(
146                backing_storage.open_sub_storage_with_key(l1_pricing_root_key()),
147                arbos_version,
148            ),
149            l2_pricing_state: L2PricingState::open(
150                backing_storage.open_sub_storage_with_key(l2_pricing_root_key()),
151                arbos_version,
152            ),
153            retryable_state: RetryableState::open(
154                backing_storage.open_sub_storage_with_key(retryables_root_key()),
155            ),
156            address_table: address_table::open_address_table(
157                backing_storage.open_sub_storage_with_key(address_table_root_key()),
158            ),
159            chain_owners: address_set::open_address_set(
160                backing_storage.open_sub_storage_with_key(chain_owner_root_key()),
161            ),
162            send_merkle_accumulator: merkle_accumulator::open_merkle_accumulator(
163                backing_storage.open_sub_storage_with_key(send_merkle_root_key()),
164            ),
165            programs: Programs::open(
166                arbos_version,
167                backing_storage.open_sub_storage_with_key(programs_root_key()),
168            ),
169            blockhashes: blockhash::open_blockhashes(
170                backing_storage.open_sub_storage_with_key(blockhashes_root_key()),
171            ),
172            chain_id: StorageBackedBigUint::new(state, B256::ZERO, CHAIN_ID_OFFSET),
173            chain_config: StorageBackedBytes::new(chain_config_sto),
174            genesis_block_num: StorageBackedUint64::new(
175                state,
176                B256::ZERO,
177                GENESIS_BLOCK_NUM_OFFSET,
178            ),
179            infra_fee_account: StorageBackedAddress::new(
180                state,
181                B256::ZERO,
182                INFRA_FEE_ACCOUNT_OFFSET,
183            ),
184            brotli_compression_level: StorageBackedUint64::new(
185                state,
186                B256::ZERO,
187                BROTLI_COMPRESSION_LEVEL_OFFSET,
188            ),
189            native_token_enabled_from_time: StorageBackedUint64::new(
190                state,
191                B256::ZERO,
192                NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET,
193            ),
194            native_token_owners: address_set::open_address_set(
195                backing_storage.open_sub_storage_with_key(native_token_owner_root_key()),
196            ),
197            transaction_filtering_enabled_from_time: StorageBackedUint64::new(
198                state,
199                B256::ZERO,
200                TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET,
201            ),
202            transaction_filterers: address_set::open_address_set(
203                backing_storage.open_sub_storage_with_key(transaction_filtering_root_key()),
204            ),
205            features: features::open_features(state, features_sto.base_key(), 0),
206            filtered_funds_recipient: StorageBackedAddress::new(
207                state,
208                B256::ZERO,
209                FILTERED_FUNDS_RECIPIENT_OFFSET,
210            ),
211            filtered_transactions: FilteredTransactionsState::open(Storage::new_with_account(
212                state,
213                B256::ZERO,
214                FILTERED_TX_STATE_ADDRESS,
215            )),
216            collect_tips: StorageBackedUint64::new(state, B256::ZERO, COLLECT_TIPS_OFFSET),
217            backing_storage,
218            burner,
219        })
220    }
221
222    // --- Accessor methods ---
223
224    pub fn arbos_version(&self) -> u64 {
225        self.arbos_version
226    }
227
228    pub fn backing_storage(&self) -> &Storage<D> {
229        &self.backing_storage
230    }
231
232    pub fn set_format_version(&mut self, version: u64) -> Result<(), ()> {
233        self.arbos_version = version;
234        self.backing_storage
235            .set_by_uint64(VERSION_OFFSET, B256::from(U256::from(version)))
236    }
237
238    pub fn brotli_compression_level(&self) -> Result<u64, ()> {
239        self.brotli_compression_level.get()
240    }
241
242    pub fn set_brotli_compression_level(&self, level: u64) -> Result<(), ()> {
243        self.brotli_compression_level.set(level)
244    }
245
246    /// Whether tip collection is enabled. Always false before ArbOS 60.
247    pub fn collect_tips(&self) -> Result<bool, ()> {
248        if self.arbos_version < 60 {
249            return Ok(false);
250        }
251        Ok(self.collect_tips.get()? != 0)
252    }
253
254    pub fn chain_id(&self) -> Result<U256, ()> {
255        self.chain_id.get()
256    }
257
258    pub fn chain_config(&self) -> Result<Vec<u8>, ()> {
259        self.chain_config.get()
260    }
261
262    pub fn set_chain_config(&self, config: &[u8]) -> Result<(), ()> {
263        self.chain_config.set(config)
264    }
265
266    pub fn genesis_block_num(&self) -> Result<u64, ()> {
267        self.genesis_block_num.get()
268    }
269
270    pub fn network_fee_account(&self) -> Result<Address, ()> {
271        self.network_fee_account.get()
272    }
273
274    pub fn set_network_fee_account(&self, account: Address) -> Result<(), ()> {
275        self.network_fee_account.set(account)
276    }
277
278    pub fn infra_fee_account(&self) -> Result<Address, ()> {
279        self.infra_fee_account.get()
280    }
281
282    pub fn set_infra_fee_account(&self, account: Address) -> Result<(), ()> {
283        self.infra_fee_account.set(account)
284    }
285
286    pub fn filtered_funds_recipient(&self) -> Result<Address, ()> {
287        self.filtered_funds_recipient.get()
288    }
289
290    pub fn filtered_funds_recipient_or_default(&self) -> Result<Address, ()> {
291        let addr = self.filtered_funds_recipient.get()?;
292        if addr == Address::ZERO {
293            self.network_fee_account()
294        } else {
295            Ok(addr)
296        }
297    }
298
299    pub fn set_filtered_funds_recipient(&self, addr: Address) -> Result<(), ()> {
300        self.filtered_funds_recipient.set(addr)
301    }
302
303    pub fn native_token_management_from_time(&self) -> Result<u64, ()> {
304        self.native_token_enabled_from_time.get()
305    }
306
307    pub fn set_native_token_management_from_time(&self, time: u64) -> Result<(), ()> {
308        self.native_token_enabled_from_time.set(time)
309    }
310
311    pub fn transaction_filtering_from_time(&self) -> Result<u64, ()> {
312        self.transaction_filtering_enabled_from_time.get()
313    }
314
315    pub fn set_transaction_filtering_from_time(&self, time: u64) -> Result<(), ()> {
316        self.transaction_filtering_enabled_from_time.set(time)
317    }
318
319    pub fn get_scheduled_upgrade(&self) -> Result<(u64, u64), ()> {
320        let version = self.upgrade_version.get()?;
321        let timestamp = self.upgrade_timestamp.get()?;
322        Ok((version, timestamp))
323    }
324
325    pub fn schedule_arbos_upgrade(&self, version: u64, timestamp: u64) -> Result<(), ()> {
326        self.upgrade_version.set(version)?;
327        self.upgrade_timestamp.set(timestamp)
328    }
329
330    /// Checks and performs a scheduled ArbOS version upgrade if due.
331    pub fn upgrade_arbos_version_if_necessary(&mut self, current_timestamp: u64) -> Result<(), ()> {
332        let scheduled_version = self.upgrade_version.get()?;
333        let scheduled_timestamp = self.upgrade_timestamp.get()?;
334
335        // Check: arbosVersion < upgradeTo && currentTimestamp >= flagday
336        if scheduled_version == 0
337            || self.arbos_version >= scheduled_version
338            || current_timestamp < scheduled_timestamp
339        {
340            return Ok(());
341        }
342
343        if scheduled_version > MAX_ARBOS_VERSION_SUPPORTED {
344            return Err(());
345        }
346
347        let old_version = self.arbos_version;
348        self.upgrade_arbos_version(scheduled_version, false)?;
349
350        // The scheduled upgrade fields are NOT cleared after upgrade.
351        // They remain in storage and are simply ignored because the
352        // arbos_version >= scheduled_version check prevents re-upgrade.
353
354        if old_version != self.arbos_version {
355            tracing::info!(
356                old_version,
357                new_version = self.arbos_version,
358                "ArbOS version upgraded"
359            );
360        }
361
362        Ok(())
363    }
364
365    /// Performs version upgrade steps from current version up to `upgrade_to`.
366    ///
367    /// `first_time` is true during genesis initialization, which affects
368    /// some initial parameter values.
369    pub fn upgrade_arbos_version(&mut self, upgrade_to: u64, first_time: bool) -> Result<(), ()> {
370        while self.arbos_version < upgrade_to {
371            let next = self.arbos_version + 1;
372
373            match next {
374                2 => {
375                    self.l1_pricing_state.set_last_surplus(U256::ZERO, false)?;
376                }
377                3 => {
378                    self.l1_pricing_state.set_per_batch_gas_cost(0)?;
379                    self.l1_pricing_state
380                        .set_amortized_cost_cap_bips(u64::MAX)?;
381                }
382                4..=9 => {
383                    // No state changes needed
384                }
385                10 => {
386                    let state = unsafe { &mut *self.backing_storage.state };
387                    let pool_balance =
388                        get_account_balance(state, l1_pricing::L1_PRICER_FUNDS_POOL_ADDRESS);
389                    self.l1_pricing_state.set_l1_fees_available(pool_balance)?;
390                }
391                11 => {
392                    self.l1_pricing_state
393                        .set_per_batch_gas_cost(l1_pricing::INITIAL_PER_BATCH_GAS_COST_V12)?;
394
395                    // Fix: math.MaxUint64 was incorrectly used for "disabled";
396                    // the correct disable value is 0.
397                    let old_cap = self.l1_pricing_state.amortized_cost_cap_bips()?;
398                    if old_cap == u64::MAX {
399                        self.l1_pricing_state.set_amortized_cost_cap_bips(0)?;
400                    }
401
402                    if !first_time {
403                        self.chain_owners.clear_list()?;
404                    }
405                }
406                // 12..=19: reserved for Orbit chains
407                12..=19 => {}
408                20 => {
409                    self.set_brotli_compression_level(1)?;
410                }
411                // 21..=29: reserved for Orbit chains
412                21..=29 => {}
413                30 => {
414                    Programs::initialize(
415                        next,
416                        &self.backing_storage.open_sub_storage(PROGRAMS_SUBSPACE),
417                    );
418                }
419                31 => {
420                    let mut params = self.programs.params()?;
421                    params.upgrade_to_version(2).map_err(|_| ())?;
422                    params
423                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
424                        .map_err(|_| ())?;
425                }
426                32 => {
427                    // No state changes needed
428                }
429                // 33..=39: reserved for Orbit chains
430                33..=39 => {}
431                40 => {
432                    // EIP-2935: historical block hashes
433                    let state = unsafe { &mut *self.backing_storage.state };
434                    set_account_nonce(state, HISTORY_STORAGE_ADDRESS, 1);
435                    set_account_code(
436                        state,
437                        HISTORY_STORAGE_ADDRESS,
438                        HISTORY_STORAGE_CODE_ARBITRUM.clone(),
439                    );
440                    // Upgrade Stylus params for version 40
441                    let mut params = self.programs.params()?;
442                    params.upgrade_to_arbos_version(next).map_err(|_| ())?;
443                    params
444                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
445                        .map_err(|_| ())?;
446                }
447                41 => {
448                    // No state changes needed
449                }
450                // 42..=49: reserved for Orbit chains
451                42..=49 => {}
452                50 => {
453                    let mut params = self.programs.params()?;
454                    params.upgrade_to_arbos_version(next).map_err(|_| ())?;
455                    params
456                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
457                        .map_err(|_| ())?;
458                    self.l2_pricing_state
459                        .set_max_per_tx_gas_limit(l2_pricing::INITIAL_PER_TX_GAS_LIMIT_V50)?;
460                }
461                51 => {
462                    // No state changes needed
463                }
464                // 52..=59: reserved for Orbit chains
465                52..=59 => {}
466                60 => {
467                    let mut params = self.programs.params()?;
468                    params.upgrade_to_arbos_version(next).map_err(|_| ())?;
469                    params
470                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
471                        .map_err(|_| ())?;
472                    // Initialize transaction filtering address set
473                    crate::address_set::initialize_address_set(
474                        &self
475                            .backing_storage
476                            .open_sub_storage(TRANSACTION_FILTERING_SUBSPACE),
477                    )?;
478                }
479                _ => {
480                    tracing::error!(version = next, "unsupported ArbOS version");
481                    return Err(());
482                }
483            }
484
485            // Install precompile code for newly introduced precompiles
486            for &(addr, version) in PRECOMPILE_MIN_ARBOS_VERSIONS {
487                if version == next {
488                    let state = unsafe { &mut *self.backing_storage.state };
489                    set_account_code(state, addr, Bytes::from_static(&[0xFE])); // INVALID opcode
490                }
491            }
492
493            self.arbos_version = next;
494            self.programs.arbos_version = next;
495            self.l1_pricing_state.arbos_version = next;
496            self.l2_pricing_state.arbos_version = next;
497        }
498
499        // First-time initialization overrides
500        if first_time && upgrade_to >= 6 {
501            if upgrade_to < 11 {
502                self.l1_pricing_state
503                    .set_per_batch_gas_cost(l1_pricing::INITIAL_PER_BATCH_GAS_COST_V6)?;
504            }
505            self.l1_pricing_state
506                .set_equilibration_units(U256::from(l1_pricing::INITIAL_EQUILIBRATION_UNITS_V6))?;
507            self.l2_pricing_state
508                .set_speed_limit_per_second(l2_pricing::INITIAL_SPEED_LIMIT_PER_SECOND_V6)?;
509            self.l2_pricing_state
510                .set_max_per_block_gas_limit(l2_pricing::INITIAL_PER_BLOCK_GAS_LIMIT_V6)?;
511        }
512
513        // Persist the final version
514        self.set_format_version(self.arbos_version)?;
515
516        Ok(())
517    }
518}