1use alloy_primitives::{Address, B256, U256};
2
3use arb_chainspec::arbos_version as arb_ver;
4
5use crate::{header::ArbHeaderInfo, internal_tx::L1Info, l2_pricing::GETH_BLOCK_GAS_LIMIT};
6
7const TX_GAS: u64 = 21_000;
9
10#[derive(Debug, Clone, Default)]
16pub struct ConditionalOptions {
17 pub known_accounts: Vec<(Address, Option<B256>)>,
18 pub block_number_min: Option<u64>,
19 pub block_number_max: Option<u64>,
20 pub timestamp_min: Option<u64>,
21 pub timestamp_max: Option<u64>,
22}
23
24pub trait SequencingHooks {
30 fn next_tx_to_sequence(&mut self) -> Option<Vec<u8>>;
32
33 fn pre_tx_filter(&self, tx: &[u8]) -> Result<(), String>;
35
36 fn post_tx_filter(&self, tx: &[u8], result: &[u8]) -> Result<(), String>;
38
39 fn discard_invalid_txs_early(&self) -> bool;
41
42 fn block_filter(
44 &self,
45 _header: &NewHeaderResult,
46 _txs: &[Vec<u8>],
47 _receipts: &[Vec<u8>],
48 ) -> Result<(), String> {
49 Ok(())
50 }
51
52 fn insert_last_tx_error(&mut self, _err: String) {}
54}
55
56pub struct NoopSequencingHooks;
58
59impl SequencingHooks for NoopSequencingHooks {
60 fn next_tx_to_sequence(&mut self) -> Option<Vec<u8>> {
61 None
62 }
63
64 fn pre_tx_filter(&self, _tx: &[u8]) -> Result<(), String> {
65 Ok(())
66 }
67
68 fn post_tx_filter(&self, _tx: &[u8], _result: &[u8]) -> Result<(), String> {
69 Ok(())
70 }
71
72 fn discard_invalid_txs_early(&self) -> bool {
73 false
74 }
75}
76
77#[derive(Debug, Clone)]
83pub struct BlockProductionResult {
84 pub l1_info: L1Info,
85 pub num_txs: usize,
86 pub gas_used: u64,
87}
88
89#[derive(Debug, Clone)]
91pub struct NewHeaderParams {
92 pub parent_hash: B256,
93 pub parent_number: u64,
94 pub parent_timestamp: u64,
95 pub parent_extra_data: Vec<u8>,
96 pub parent_mix_hash: B256,
97 pub coinbase: Address,
98 pub timestamp: u64,
99 pub base_fee: U256,
100}
101
102#[derive(Debug, Clone)]
104pub struct NewHeaderResult {
105 pub parent_hash: B256,
106 pub coinbase: Address,
107 pub number: u64,
108 pub gas_limit: u64,
109 pub timestamp: u64,
110 pub extra_data: Vec<u8>,
111 pub mix_hash: B256,
112 pub base_fee: U256,
113 pub difficulty: U256,
114}
115
116pub fn create_new_header(
125 l1_info: Option<&L1Info>,
126 prev_hash: B256,
127 prev_number: u64,
128 prev_timestamp: u64,
129 prev_extra: &[u8],
130 prev_mix_hash: B256,
131 base_fee: U256,
132) -> NewHeaderResult {
133 let mut timestamp = 0u64;
134 let mut coinbase = Address::ZERO;
135
136 if let Some(info) = l1_info {
137 timestamp = info.l1_timestamp;
138 coinbase = info.poster;
139 }
140
141 if timestamp < prev_timestamp {
142 timestamp = prev_timestamp;
143 }
144
145 let mut extra_data = vec![0u8; 32];
146 let copy_len = prev_extra.len().min(32);
147 extra_data[..copy_len].copy_from_slice(&prev_extra[..copy_len]);
148
149 NewHeaderResult {
150 parent_hash: prev_hash,
151 coinbase,
152 number: prev_number + 1,
153 gas_limit: GETH_BLOCK_GAS_LIMIT,
154 timestamp,
155 extra_data,
156 mix_hash: prev_mix_hash,
157 base_fee,
158 difficulty: U256::from(1),
159 }
160}
161
162pub fn finalize_block_header_info(
167 send_root: B256,
168 send_count: u64,
169 l1_block_number: u64,
170 arbos_version: u64,
171) -> ArbHeaderInfo {
172 ArbHeaderInfo {
173 send_root,
174 send_count,
175 l1_block_number,
176 arbos_format_version: arbos_version,
177 }
178}
179
180#[derive(Debug)]
186pub enum TxOutcome {
187 Success(TxResult),
189 Invalid(String),
191}
192
193#[derive(Debug, Clone)]
195pub struct TxResult {
196 pub gas_used: u64,
198 pub data_gas: u64,
200 pub evm_success: bool,
202 pub scheduled_txs: Vec<Vec<u8>>,
204 pub evm_error: Option<String>,
206}
207
208#[derive(Debug)]
210pub enum TxAction {
211 ExecuteStartBlock,
213 ExecuteRedeem(Vec<u8>),
215 ExecuteUserTx(Vec<u8>),
217 Done,
219}
220
221#[derive(Debug)]
228pub struct BlockProductionState {
229 pub block_gas_left: u64,
231 redeems: Vec<Vec<u8>>,
233 start_block_produced: bool,
235 user_txs_processed: usize,
237 pub expected_balance_delta: i128,
239 arbos_version: u64,
241 pub timestamp: u64,
243 pub base_fee: U256,
245}
246
247impl BlockProductionState {
248 pub fn new(
250 per_block_gas_limit: u64,
251 arbos_version: u64,
252 timestamp: u64,
253 base_fee: U256,
254 ) -> Self {
255 Self {
256 block_gas_left: per_block_gas_limit,
257 redeems: Vec::new(),
258 start_block_produced: false,
259 user_txs_processed: 0,
260 expected_balance_delta: 0,
261 arbos_version,
262 timestamp,
263 base_fee,
264 }
265 }
266
267 pub fn next_tx_action<H: SequencingHooks>(&mut self, hooks: &mut H) -> TxAction {
269 if !self.start_block_produced {
270 self.start_block_produced = true;
271 return TxAction::ExecuteStartBlock;
272 }
273
274 if !self.redeems.is_empty() {
276 let redeem = self.redeems.remove(0);
277 return TxAction::ExecuteRedeem(redeem);
278 }
279
280 match hooks.next_tx_to_sequence() {
282 Some(tx_bytes) => {
283 if self.block_gas_left < TX_GAS {
285 hooks.insert_last_tx_error("block gas limit reached".to_string());
286 return TxAction::Done;
287 }
288 TxAction::ExecuteUserTx(tx_bytes)
289 }
290 None => TxAction::Done,
291 }
292 }
293
294 pub fn should_reject_for_block_gas(&self, compute_gas: u64, is_user_tx: bool) -> bool {
300 self.arbos_version < arb_ver::ARBOS_VERSION_50
301 && compute_gas > self.block_gas_left
302 && is_user_tx
303 && self.user_txs_processed > 0
304 }
305
306 pub fn compute_data_gas(poster_cost: U256, base_fee: U256, tx_gas: u64) -> u64 {
308 if base_fee.is_zero() {
309 return 0;
310 }
311
312 let poster_cost_in_l2_gas = poster_cost / base_fee;
313 let data_gas: u64 = poster_cost_in_l2_gas.try_into().unwrap_or(u64::MAX);
314
315 data_gas.min(tx_gas)
317 }
318
319 pub fn record_tx_outcome(
323 &mut self,
324 action: &TxAction,
325 outcome: TxOutcome,
326 ) -> Result<(), String> {
327 match outcome {
328 TxOutcome::Invalid(err) => {
329 match action {
331 TxAction::ExecuteUserTx(_) => {
332 self.block_gas_left = self.block_gas_left.saturating_sub(TX_GAS);
333 self.user_txs_processed += 1;
334 }
335 _ => {
336 self.block_gas_left = self.block_gas_left.saturating_sub(TX_GAS);
337 }
338 }
339 tracing::debug!(err, "tx invalid, skipped");
340 Ok(())
341 }
342 TxOutcome::Success(result) => {
343 if matches!(action, TxAction::ExecuteStartBlock) {
345 if let Some(ref err) = result.evm_error {
346 return Err(format!("internal tx failed: {err}"));
347 }
348 }
349
350 let tx_gas_used = result.gas_used;
351 let data_gas = result.data_gas;
352
353 if self.arbos_version >= arb_ver::ARBOS_VERSION_3 {
355 for scheduled in &result.scheduled_txs {
356 let _ = scheduled; }
361 }
362
363 self.redeems.extend(result.scheduled_txs);
365
366 let compute_used = if tx_gas_used >= data_gas {
368 let c = tx_gas_used - data_gas;
369 if c < TX_GAS {
370 TX_GAS
371 } else {
372 c
373 }
374 } else {
375 tracing::error!(tx_gas_used, data_gas, "tx used less gas than expected");
376 TX_GAS
377 };
378
379 self.block_gas_left = self.block_gas_left.saturating_sub(compute_used);
380
381 if matches!(action, TxAction::ExecuteUserTx(_)) {
382 self.user_txs_processed += 1;
383 }
384
385 Ok(())
386 }
387 }
388 }
389
390 pub fn track_deposit(&mut self, value: U256) {
392 let value_i128: i128 = value.try_into().unwrap_or(i128::MAX);
393 self.expected_balance_delta = self.expected_balance_delta.saturating_add(value_i128);
394 }
395
396 pub fn track_withdrawal(&mut self, value: U256) {
398 let value_i128: i128 = value.try_into().unwrap_or(i128::MAX);
399 self.expected_balance_delta = self.expected_balance_delta.saturating_sub(value_i128);
400 }
401
402 pub fn set_arbos_version(&mut self, version: u64) {
404 self.arbos_version = version;
405 }
406
407 pub fn verify_balance_delta(
409 &self,
410 actual_balance_delta: i128,
411 debug_mode: bool,
412 ) -> Result<(), String> {
413 if actual_balance_delta == self.expected_balance_delta {
414 return Ok(());
415 }
416
417 if actual_balance_delta > self.expected_balance_delta || debug_mode {
418 return Err(format!(
419 "unexpected balance delta {} (expected {})",
420 actual_balance_delta, self.expected_balance_delta,
421 ));
422 }
423
424 tracing::error!(
426 actual = actual_balance_delta,
427 expected = self.expected_balance_delta,
428 "unexpected balance delta (funds burnt)"
429 );
430 Ok(())
431 }
432
433 pub fn user_txs_processed(&self) -> usize {
435 self.user_txs_processed
436 }
437
438 pub fn arbos_version(&self) -> u64 {
440 self.arbos_version
441 }
442}