arbos/arbos_state/
initialize.rs

1use alloy_primitives::{Address, B256, U256};
2use revm::Database;
3
4use crate::{
5    burn::Burner,
6    retryables::{self, RetryableState},
7};
8
9use super::ArbosState;
10
11/// Genesis data for a retryable ticket.
12#[derive(Debug, Clone)]
13pub struct InitRetryableData {
14    pub id: B256,
15    pub timeout: u64,
16    pub from: Address,
17    pub to: Option<Address>,
18    pub callvalue: U256,
19    pub beneficiary: Address,
20    pub calldata: Vec<u8>,
21}
22
23/// Genesis data for an account.
24#[derive(Debug, Clone)]
25pub struct AccountInitInfo {
26    pub addr: Address,
27    pub nonce: u64,
28    pub balance: U256,
29    pub contract_info: Option<ContractInitInfo>,
30    pub aggregator_info: Option<AggregatorInitInfo>,
31}
32
33/// Contract info for genesis account initialization.
34#[derive(Debug, Clone)]
35pub struct ContractInitInfo {
36    pub code: Vec<u8>,
37    pub storage: Vec<(U256, U256)>,
38}
39
40/// Aggregator (batch poster) info for genesis account initialization.
41#[derive(Debug, Clone)]
42pub struct AggregatorInitInfo {
43    pub fee_collector: Address,
44}
45
46/// Creates a genesis block header.
47///
48/// Returns the fields needed for the genesis block. The actual block
49/// construction uses reth's block types, so this returns a struct
50/// that the genesis pipeline can consume.
51#[derive(Debug, Clone)]
52pub struct GenesisBlockInfo {
53    pub parent_hash: B256,
54    pub block_number: u64,
55    pub timestamp: u64,
56    pub state_root: B256,
57    pub gas_limit: u64,
58    pub base_fee: u64,
59    pub nonce: u64,
60    pub arbos_format_version: u64,
61}
62
63/// Build genesis block info from chain parameters.
64pub fn make_genesis_block(
65    parent_hash: B256,
66    block_number: u64,
67    timestamp: u64,
68    state_root: B256,
69    initial_arbos_version: u64,
70) -> GenesisBlockInfo {
71    use crate::l2_pricing;
72
73    GenesisBlockInfo {
74        parent_hash,
75        block_number,
76        timestamp,
77        state_root,
78        gas_limit: l2_pricing::GETH_BLOCK_GAS_LIMIT,
79        base_fee: l2_pricing::INITIAL_BASE_FEE_WEI,
80        nonce: 1, // genesis reads the init message
81        arbos_format_version: initial_arbos_version,
82    }
83}
84
85/// Initialize retryable tickets from genesis data.
86///
87/// Expired retryables (timeout <= current_timestamp) are skipped, and their
88/// call value is returned as `(beneficiary, callvalue)` pairs for the caller
89/// to credit balances. Active retryables are sorted by timeout and created.
90///
91/// Returns `(balance_credits, escrow_credits)` where:
92/// - `balance_credits`: expired retryable beneficiaries to credit
93/// - `escrow_credits`: (escrow_address, callvalue) for active retryable escrow funding
94pub fn initialize_retryables<D: Database>(
95    rs: &RetryableState<D>,
96    mut retryables_data: Vec<InitRetryableData>,
97    current_timestamp: u64,
98) -> Result<(Vec<(Address, U256)>, Vec<(Address, U256)>), ()> {
99    let mut balance_credits = Vec::new();
100    let mut active_retryables = Vec::new();
101
102    // Separate expired from active retryables.
103    for r in retryables_data.drain(..) {
104        if r.timeout <= current_timestamp {
105            balance_credits.push((r.beneficiary, r.callvalue));
106            continue;
107        }
108        active_retryables.push(r);
109    }
110
111    // Sort by timeout, then by id for determinism.
112    active_retryables.sort_by(|a, b| a.timeout.cmp(&b.timeout).then_with(|| a.id.cmp(&b.id)));
113
114    let mut escrow_credits = Vec::new();
115
116    for r in &active_retryables {
117        let escrow_addr = retryables::retryable_escrow_address(r.id);
118        escrow_credits.push((escrow_addr, r.callvalue));
119        rs.create_retryable(
120            r.id,
121            r.timeout,
122            r.from,
123            r.to,
124            r.callvalue,
125            r.beneficiary,
126            &r.calldata,
127        )?;
128    }
129
130    Ok((balance_credits, escrow_credits))
131}
132
133/// Initialize an account's ArbOS-specific state during genesis.
134///
135/// If the account has aggregator info and is a known batch poster,
136/// sets the batch poster's pay-to (fee collector) address.
137pub fn initialize_arbos_account<D: Database, B: Burner>(
138    arbos_state: &ArbosState<D, B>,
139    account: &AccountInitInfo,
140) -> Result<(), ()> {
141    if let Some(ref aggregator) = account.aggregator_info {
142        let poster_table = arbos_state.l1_pricing_state.batch_poster_table();
143        let is_poster = poster_table.contains_poster(account.addr)?;
144        if is_poster {
145            let poster = poster_table.open_poster(account.addr, false)?;
146            poster.set_pay_to(aggregator.fee_collector)?;
147        }
148    }
149    Ok(())
150}
151
152/// Full database initialization for ArbOS genesis.
153///
154/// This is the high-level orchestrator that:
155/// 1. Initializes ArbOS state (version upgrades, precompile code)
156/// 2. Adds chain owner
157/// 3. Imports address table entries
158/// 4. Imports retryable tickets
159/// 5. Imports account state (balances, nonces, code, storage, batch poster config)
160///
161/// The caller provides the state database, init data, and handles commits.
162/// Balance credits from expired retryables and escrow funding are returned
163/// for the caller to execute against the state.
164#[derive(Debug)]
165pub struct GenesisInitResult {
166    /// Expired retryable beneficiaries to credit.
167    pub balance_credits: Vec<(Address, U256)>,
168    /// Escrow addresses to fund for active retryables.
169    pub escrow_credits: Vec<(Address, U256)>,
170    /// Accounts to initialize (balances, nonces, code, storage).
171    pub accounts: Vec<AccountInitInfo>,
172}
173
174/// Initialize ArbOS in the database.
175///
176/// Creates the ArbOS state, adds the chain owner, imports address table
177/// entries, retryable tickets, and accounts. Returns a `GenesisInitResult`
178/// containing all balance operations the caller needs to execute.
179pub fn initialize_arbos_in_database<D: Database, B: Burner>(
180    arbos_state: &ArbosState<D, B>,
181    chain_owner: Address,
182    address_table_entries: Vec<Address>,
183    retryable_data: Vec<InitRetryableData>,
184    accounts: Vec<AccountInitInfo>,
185    current_timestamp: u64,
186) -> Result<GenesisInitResult, ()> {
187    // Add chain owner.
188    if chain_owner != Address::ZERO {
189        arbos_state.chain_owners.add(chain_owner)?;
190    }
191
192    // Import address table entries.
193    let table_size = arbos_state.address_table.size()?;
194    if table_size != 0 {
195        return Err(());
196    }
197    for (i, addr) in address_table_entries.iter().enumerate() {
198        let slot = arbos_state.address_table.register(*addr)?;
199        if slot != i as u64 {
200            return Err(());
201        }
202    }
203
204    // Import retryable tickets.
205    let (balance_credits, escrow_credits) = initialize_retryables(
206        &arbos_state.retryable_state,
207        retryable_data,
208        current_timestamp,
209    )?;
210
211    // Initialize per-account ArbOS state (batch poster config).
212    for account in &accounts {
213        initialize_arbos_account(arbos_state, account)?;
214    }
215
216    Ok(GenesisInitResult {
217        balance_credits,
218        escrow_credits,
219        accounts,
220    })
221}