1use std::{
4 collections::HashMap,
5 sync::{
6 Arc, OnceLock,
7 atomic::{AtomicBool, AtomicU64, AtomicUsize},
8 },
9};
10
11use alloy_primitives::{Address, B256, U256};
12use arb_primitives::multigas::MultiGas;
13use arb_storage::{Detached, SystemStateBackend};
14use arbos::{
15 arbos_state::{ArbosState, ArbosStateError, arbos_from_input_system},
16 burn::SystemBurner,
17};
18use parking_lot::Mutex;
19
20#[derive(Debug, Default)]
22pub struct RecentWasms {
23 entries: Vec<B256>,
24 capacity: usize,
25}
26
27impl RecentWasms {
28 pub fn new(capacity: usize) -> Self {
29 Self {
30 entries: Vec::new(),
31 capacity,
32 }
33 }
34
35 pub fn reset(&mut self, capacity: usize) {
36 self.entries.clear();
37 self.capacity = capacity;
38 }
39
40 pub fn insert(&mut self, hash: B256) -> bool {
42 let was_present = if let Some(pos) = self.entries.iter().position(|h| *h == hash) {
43 self.entries.remove(pos);
44 true
45 } else {
46 false
47 };
48 self.entries.push(hash);
49 if self.capacity > 0 && self.entries.len() > self.capacity {
50 self.entries.remove(0);
51 }
52 was_present
53 }
54}
55
56#[derive(Debug, Default)]
58pub struct ChainCaches {
59 pub l1_block_numbers: Mutex<HashMap<u64, u64>>,
60 pub l2_block_hashes: Mutex<HashMap<u64, B256>>,
61}
62
63pub struct BlockCtx {
65 pub arbos_version: u64,
66 pub block_timestamp: u64,
67 pub l1_block_number_for_evm: u64,
70 pub l2_block_number: u64,
71 pub allow_debug_precompiles: bool,
72 pub current_gas_backlog: AtomicU64,
74 pub state_params_dirty: AtomicBool,
78 pub chain_caches: Arc<ChainCaches>,
79 pub recent_wasms: Mutex<RecentWasms>,
80 arbos_state: OnceLock<Result<ArbosState<'static, Detached, SystemBurner>, ArbosStateError>>,
82}
83
84impl Default for BlockCtx {
85 fn default() -> Self {
86 Self::new_with_caches(0, 0, 0, 0, false, Arc::new(ChainCaches::default()))
87 }
88}
89
90impl std::fmt::Debug for BlockCtx {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("BlockCtx")
93 .field("arbos_version", &self.arbos_version)
94 .field("block_timestamp", &self.block_timestamp)
95 .field("l1_block_number_for_evm", &self.l1_block_number_for_evm)
96 .field("l2_block_number", &self.l2_block_number)
97 .field("allow_debug_precompiles", &self.allow_debug_precompiles)
98 .field("current_gas_backlog", &self.current_gas_backlog)
99 .field("chain_caches", &self.chain_caches)
100 .field("recent_wasms", &self.recent_wasms)
101 .field("arbos_state_cached", &self.arbos_state.get().is_some())
102 .finish()
103 }
104}
105
106impl BlockCtx {
107 pub fn new(
108 arbos_version: u64,
109 block_timestamp: u64,
110 l1_block_number_for_evm: u64,
111 l2_block_number: u64,
112 allow_debug_precompiles: bool,
113 ) -> Self {
114 Self::new_with_caches(
115 arbos_version,
116 block_timestamp,
117 l1_block_number_for_evm,
118 l2_block_number,
119 allow_debug_precompiles,
120 Arc::new(ChainCaches::default()),
121 )
122 }
123
124 pub fn new_with_caches(
125 arbos_version: u64,
126 block_timestamp: u64,
127 l1_block_number_for_evm: u64,
128 l2_block_number: u64,
129 allow_debug_precompiles: bool,
130 chain_caches: Arc<ChainCaches>,
131 ) -> Self {
132 Self {
133 arbos_version,
134 block_timestamp,
135 l1_block_number_for_evm,
136 l2_block_number,
137 allow_debug_precompiles,
138 current_gas_backlog: AtomicU64::new(0),
139 state_params_dirty: AtomicBool::new(false),
140 chain_caches,
141 recent_wasms: Mutex::new(RecentWasms::default()),
142 arbos_state: OnceLock::new(),
143 }
144 }
145
146 pub fn arbos_state<S: SystemStateBackend>(
151 &self,
152 backend: &mut S,
153 ) -> Result<&ArbosState<'static, Detached, SystemBurner>, ArbosStateError> {
154 self.arbos_state
155 .get_or_init(|| arbos_from_input_system(backend, SystemBurner::new(None, false)))
156 .as_ref()
157 .map_err(Clone::clone)
158 }
159
160 pub fn current_gas_backlog(&self) -> u64 {
161 self.current_gas_backlog
162 .load(std::sync::atomic::Ordering::Relaxed)
163 }
164
165 pub fn set_current_gas_backlog(&self, value: u64) {
166 self.current_gas_backlog
167 .store(value, std::sync::atomic::Ordering::Relaxed);
168 }
169
170 pub fn cache_l1_block_number(&self, l2_block: u64, l1_block: u64) {
173 let mut map = self.chain_caches.l1_block_numbers.lock();
174 map.insert(l2_block, l1_block);
175 if l2_block > 100 {
176 map.retain(|&k, _| k >= l2_block - 100);
177 }
178 }
179
180 pub fn cached_l1_block_number(&self, l2_block: u64) -> Option<u64> {
181 self.chain_caches
182 .l1_block_numbers
183 .lock()
184 .get(&l2_block)
185 .copied()
186 }
187
188 pub fn cache_l2_block_hash(&self, l2_block: u64, hash: B256) {
189 let mut map = self.chain_caches.l2_block_hashes.lock();
190 map.insert(l2_block, hash);
191 if l2_block > 256 {
193 map.retain(|&k, _| k >= l2_block - 256);
194 }
195 }
196
197 pub fn cached_l2_block_hash(&self, l2_block: u64) -> Option<B256> {
198 self.chain_caches
199 .l2_block_hashes
200 .lock()
201 .get(&l2_block)
202 .copied()
203 }
204
205 pub fn reset_recent_wasms(&self, capacity: usize) {
206 self.recent_wasms.lock().reset(capacity);
207 }
208
209 pub fn insert_recent_wasm(&self, hash: B256) -> bool {
210 self.recent_wasms.lock().insert(hash)
211 }
212}
213
214#[derive(Debug, Default, Clone)]
216pub struct TxCtx {
217 pub sender: Address,
218 pub effective_gas_price: u128,
219 pub poster_fee: u128,
220 pub poster_balance_correction: u128,
221 pub retryable_id: B256,
222 pub redeemer: Address,
223 pub tx_is_aliased: bool,
224 pub stylus_activation_addr: Option<Address>,
225 pub stylus_keepalive_hash: Option<B256>,
226 pub stylus_activation_data_fee: U256,
227 pub stylus_call_value: U256,
228 pub stylus_program_counts: HashMap<Address, u32>,
229 pub stylus_pages_open: u16,
230 pub stylus_pages_ever: u16,
231 pub stylus_multi_gas: MultiGas,
232 pub precompile_multi_gas: MultiGas,
233 pub stylus_upfront_oog_gas: u64,
234 pub cancel_escrow_sweep: Option<(Address, Address)>,
235}
236
237impl TxCtx {
238 pub fn redeemer_word(&self) -> U256 {
240 U256::from_be_bytes(B256::left_padding_from(self.redeemer.as_slice()).0)
241 }
242}
243
244#[derive(Debug, Default, Clone)]
246pub struct ArbPrecompileCtx {
247 pub block: Arc<BlockCtx>,
248 pub tx: Arc<Mutex<TxCtx>>,
249 pub evm_depth: Arc<AtomicUsize>,
252 pub caller_stack: Arc<Mutex<Vec<Address>>>,
256 pub stylus_frame_depth: Arc<AtomicUsize>,
259}
260
261impl ArbPrecompileCtx {
262 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub fn with_block(block: Arc<BlockCtx>) -> Self {
267 Self {
268 block,
269 tx: Arc::new(Mutex::new(TxCtx::default())),
270 evm_depth: Arc::new(AtomicUsize::new(0)),
271 caller_stack: Arc::new(Mutex::new(Vec::new())),
272 stylus_frame_depth: Arc::new(AtomicUsize::new(0)),
273 }
274 }
275
276 pub fn tx_snapshot(&self) -> TxCtx {
278 self.tx.lock().clone()
279 }
280
281 pub fn reset_tx(&self) {
283 *self.tx.lock() = TxCtx::default();
284 }
285
286 pub fn set_sender(&self, sender: Address) {
287 self.tx.lock().sender = sender;
288 }
289
290 pub fn set_effective_gas_price(&self, price: u128) {
291 self.tx.lock().effective_gas_price = price;
292 }
293
294 pub fn set_poster_fee(&self, fee: u128) {
295 self.tx.lock().poster_fee = fee;
296 }
297
298 pub fn set_poster_balance_correction(&self, correction: u128) {
299 self.tx.lock().poster_balance_correction = correction;
300 }
301
302 pub fn set_retryable_id(&self, id: B256) {
303 self.tx.lock().retryable_id = id;
304 }
305
306 pub fn set_redeemer(&self, redeemer: Address) {
307 self.tx.lock().redeemer = redeemer;
308 }
309
310 pub fn set_tx_is_aliased(&self, aliased: bool) {
311 self.tx.lock().tx_is_aliased = aliased;
312 }
313
314 pub fn tx_is_aliased(&self) -> bool {
315 self.tx.lock().tx_is_aliased
316 }
317
318 pub fn set_evm_depth(&self, depth: usize) {
319 self.evm_depth
320 .store(depth, std::sync::atomic::Ordering::Relaxed);
321 }
322
323 pub fn evm_depth(&self) -> usize {
324 self.evm_depth.load(std::sync::atomic::Ordering::Relaxed)
325 }
326
327 pub fn enter_stylus_frame(&self) -> usize {
330 self.stylus_frame_depth
331 .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
332 + 1
333 }
334
335 pub fn exit_stylus_frame(&self) {
337 self.stylus_frame_depth
338 .fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
339 }
340
341 pub fn push_caller(&self, caller: Address) {
342 self.caller_stack.lock().push(caller);
343 }
344
345 pub fn pop_caller(&self) {
346 self.caller_stack.lock().pop();
347 }
348
349 pub fn reset_caller_stack(&self) {
350 self.caller_stack.lock().clear();
351 }
352
353 pub fn caller_at_depth(&self, depth: usize) -> Option<Address> {
356 if depth == 0 {
357 return None;
358 }
359 self.caller_stack.lock().get(depth - 1).copied()
360 }
361
362 pub fn set_stylus_activation_addr(&self, addr: Option<Address>) {
363 self.tx.lock().stylus_activation_addr = addr;
364 }
365
366 pub fn take_stylus_activation_addr(&self) -> Option<Address> {
367 self.tx.lock().stylus_activation_addr.take()
368 }
369
370 pub fn set_cancel_escrow_sweep(&self, escrow: Address, beneficiary: Address) {
371 self.tx.lock().cancel_escrow_sweep = Some((escrow, beneficiary));
372 }
373
374 pub fn take_cancel_escrow_sweep(&self) -> Option<(Address, Address)> {
375 self.tx.lock().cancel_escrow_sweep.take()
376 }
377
378 pub fn set_stylus_keepalive_hash(&self, hash: Option<B256>) {
379 self.tx.lock().stylus_keepalive_hash = hash;
380 }
381
382 pub fn take_stylus_keepalive_hash(&self) -> Option<B256> {
383 self.tx.lock().stylus_keepalive_hash.take()
384 }
385
386 pub fn set_stylus_activation_data_fee(&self, fee: U256) {
387 self.tx.lock().stylus_activation_data_fee = fee;
388 }
389
390 pub fn take_stylus_activation_data_fee(&self) -> U256 {
391 std::mem::replace(&mut self.tx.lock().stylus_activation_data_fee, U256::ZERO)
392 }
393
394 pub fn set_stylus_call_value(&self, value: U256) {
395 self.tx.lock().stylus_call_value = value;
396 }
397
398 pub fn stylus_call_value(&self) -> U256 {
399 self.tx.lock().stylus_call_value
400 }
401
402 pub fn add_stylus_multi_gas(&self, gas: MultiGas) {
403 let mut tx = self.tx.lock();
404 tx.stylus_multi_gas = tx.stylus_multi_gas.saturating_add(gas);
405 }
406
407 pub fn stylus_multi_gas(&self) -> MultiGas {
408 self.tx.lock().stylus_multi_gas
409 }
410
411 pub fn add_stylus_upfront_oog_gas(&self, gas: u64) {
412 let mut tx = self.tx.lock();
413 tx.stylus_upfront_oog_gas = tx.stylus_upfront_oog_gas.saturating_add(gas);
414 }
415
416 pub fn stylus_upfront_oog_gas(&self) -> u64 {
417 self.tx.lock().stylus_upfront_oog_gas
418 }
419
420 pub fn add_precompile_multi_gas(
424 &self,
425 kind: arb_primitives::multigas::ResourceKind,
426 amount: u64,
427 ) {
428 let mut tx = self.tx.lock();
429 tx.precompile_multi_gas
430 .saturating_increment_into(kind, amount);
431 }
432
433 pub fn precompile_multi_gas(&self) -> MultiGas {
434 self.tx.lock().precompile_multi_gas
435 }
436
437 pub fn snapshot_precompile_multi_gas(&self) -> MultiGas {
443 self.tx.lock().precompile_multi_gas
444 }
445
446 pub fn restore_precompile_multi_gas(&self, snapshot: MultiGas) {
448 self.tx.lock().precompile_multi_gas = snapshot;
449 }
450
451 pub fn push_stylus_program(&self, addr: Address) -> bool {
454 let mut tx = self.tx.lock();
455 let count = tx.stylus_program_counts.entry(addr).or_insert(0);
456 *count += 1;
457 *count > 1
458 }
459
460 pub fn stylus_program_count(&self, addr: Address) -> u32 {
461 self.tx
462 .lock()
463 .stylus_program_counts
464 .get(&addr)
465 .copied()
466 .unwrap_or(0)
467 }
468
469 pub fn pop_stylus_program(&self, addr: Address) {
470 let mut tx = self.tx.lock();
471 if let Some(count) = tx.stylus_program_counts.get_mut(&addr) {
472 *count = count.saturating_sub(1);
473 if *count == 0 {
474 tx.stylus_program_counts.remove(&addr);
475 }
476 }
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483
484 fn ctx_sharing(caches: &Arc<ChainCaches>) -> BlockCtx {
485 BlockCtx::new_with_caches(60, 0, 0, 0, false, caches.clone())
486 }
487
488 #[test]
489 fn chain_caches_share_l2_block_hashes_across_block_ctxs() {
490 let caches = Arc::new(ChainCaches::default());
491 let block_a = ctx_sharing(&caches);
492 let block_b = ctx_sharing(&caches);
493
494 let hash = B256::repeat_byte(0x42);
495 block_a.cache_l2_block_hash(100, hash);
496 assert_eq!(block_b.cached_l2_block_hash(100), Some(hash));
497 }
498
499 #[test]
500 fn cache_l2_block_hash_evicts_entries_outside_window() {
501 let block = BlockCtx::new_with_caches(60, 0, 0, 0, false, Arc::new(ChainCaches::default()));
502 for n in 0..=512u64 {
503 block.cache_l2_block_hash(n, B256::from(U256::from(n)));
504 }
505 assert_eq!(
506 block.cached_l2_block_hash(256),
507 Some(B256::from(U256::from(256)))
508 );
509 assert_eq!(
510 block.cached_l2_block_hash(512),
511 Some(B256::from(U256::from(512)))
512 );
513 assert_eq!(block.cached_l2_block_hash(255), None);
514 assert_eq!(block.cached_l2_block_hash(0), None);
515 }
516
517 #[test]
518 fn block_ctx_new_creates_isolated_caches() {
519 let a = BlockCtx::new(60, 0, 0, 0, false);
520 let b = BlockCtx::new(60, 0, 0, 0, false);
521 a.cache_l2_block_hash(1, B256::repeat_byte(0x01));
522 assert!(b.cached_l2_block_hash(1).is_none());
523 }
524}