1use alloy_primitives::{Address, B256, U256, keccak256};
2use arb_storage::{
3 Queue, Storage, StorageBackedAddress, StorageBackedAddressOrNil, StorageBackedBigUint,
4 StorageBackedBytes, StorageBackedUint64, StorageBackend, SystemStateBackend, initialize_queue,
5 open_queue,
6};
7use revm::Database;
8
9use crate::util::BalanceError;
10
11mod error;
12pub use error::RetryableError;
13
14pub const RETRYABLE_LIFETIME_SECONDS: u64 = 7 * 24 * 60 * 60; pub const RETRYABLE_REAP_PRICE: u64 = 58000;
16
17const WINDOWS_LEFT_SLOAD_GAS: u64 = 800;
18
19pub const TIMEOUT_QUEUE_KEY: &[u8] = &[0];
20pub const CALLDATA_KEY: &[u8] = &[1];
21
22pub const NUM_TRIES_OFFSET: u64 = 0;
24pub const FROM_OFFSET: u64 = 1;
25pub const TO_OFFSET: u64 = 2;
26pub const CALLVALUE_OFFSET: u64 = 3;
27pub const BENEFICIARY_OFFSET: u64 = 4;
28pub const TIMEOUT_OFFSET: u64 = 5;
29pub const TIMEOUT_WINDOWS_LEFT_OFFSET: u64 = 6;
30
31pub struct RetryableState<'a, D> {
33 retryables: Storage<'a, D>,
34 pub timeout_queue: Queue,
35 pub arbos_version: u64,
36}
37
38pub enum LookupOutcome<T> {
40 Found(T),
41 NoTicket,
42}
43
44pub struct RetryableLookup<T> {
47 pub outcome: LookupOutcome<T>,
48 pub extra_gas: u64,
49}
50
51pub enum CancelOutcome {
54 Cleared {
55 calldata_size: u64,
56 beneficiary: Address,
57 },
58 NoTicket,
59 NotBeneficiary,
60}
61
62pub struct CancelLookup {
64 pub outcome: CancelOutcome,
65 pub extra_gas: u64,
66}
67
68pub struct Retryable<'a, D> {
70 pub id: B256,
71 backing_storage: Storage<'a, D>,
72 num_tries: StorageBackedUint64,
73 from: StorageBackedAddress,
74 to: StorageBackedAddressOrNil,
75 callvalue: StorageBackedBigUint,
76 beneficiary: StorageBackedAddress,
77 calldata: StorageBackedBytes,
78 timeout: StorageBackedUint64,
79 timeout_windows_left: StorageBackedUint64,
80}
81
82pub fn initialize_retryable_state<D: Database>(sto: &Storage<'_, D>) -> Result<(), RetryableError> {
83 Ok(initialize_queue(&sto.open_sub_storage(TIMEOUT_QUEUE_KEY))?)
84}
85
86pub fn open_retryable_state<D>(sto: Storage<'_, D>, arbos_version: u64) -> RetryableState<'_, D> {
87 let queue_sto = sto.open_sub_storage(TIMEOUT_QUEUE_KEY);
88 RetryableState {
89 timeout_queue: open_queue(queue_sto),
90 retryables: sto,
91 arbos_version,
92 }
93}
94
95impl<'a, D> RetryableState<'a, D> {
96 pub fn open(sto: Storage<'a, D>, arbos_version: u64) -> Self {
97 open_retryable_state(sto, arbos_version)
98 }
99
100 pub fn create_retryable<B: StorageBackend>(
102 &self,
103 backend: &mut B,
104 id: B256,
105 timeout: u64,
106 from: Address,
107 to: Option<Address>,
108 callvalue: U256,
109 beneficiary: Address,
110 calldata: &[u8],
111 ) -> Result<Retryable<'a, D>, RetryableError> {
112 let ret = self.internal_open(id);
113 ret.num_tries.set(backend, 0)?;
114 ret.from.set(backend, from)?;
115 ret.to.set(backend, to)?;
116 ret.callvalue.set(backend, callvalue)?;
117 ret.beneficiary.set(backend, beneficiary)?;
118 ret.calldata.set(backend, calldata)?;
119 ret.timeout.set(backend, timeout)?;
120 ret.timeout_windows_left.set(backend, 0)?;
121 self.timeout_queue.put(backend, id)?;
122 Ok(ret)
123 }
124
125 pub fn open_retryable<B: SystemStateBackend>(
130 &self,
131 backend: &mut B,
132 id: B256,
133 current_timestamp: u64,
134 ) -> Result<Option<Retryable<'a, D>>, RetryableError> {
135 let (retryable, _extra_gas) =
136 self.open_retryable_metered(backend, id, current_timestamp)?;
137 Ok(retryable)
138 }
139
140 pub fn open_retryable_metered<B: SystemStateBackend>(
144 &self,
145 backend: &mut B,
146 id: B256,
147 current_timestamp: u64,
148 ) -> Result<(Option<Retryable<'a, D>>, u64), RetryableError> {
149 let sto = self.retryables.open_sub_storage(id.as_slice());
150 let base_key = sto.base_key();
151 let timeout = StorageBackedUint64::new(base_key, TIMEOUT_OFFSET).get(backend)?;
152 if timeout == 0 {
153 return Ok((None, 0));
154 }
155 let mut extra_gas = 0;
156 if timeout < current_timestamp {
157 let mut effective_timeout = timeout;
158 if self.arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_60 {
159 let windows_left =
160 StorageBackedUint64::new(base_key, TIMEOUT_WINDOWS_LEFT_OFFSET).get(backend)?;
161 extra_gas = WINDOWS_LEFT_SLOAD_GAS;
162 effective_timeout =
163 timeout.saturating_add(windows_left.saturating_mul(RETRYABLE_LIFETIME_SECONDS));
164 }
165 if effective_timeout < current_timestamp {
166 return Ok((None, extra_gas));
167 }
168 }
169 Ok((Some(self.internal_open(id)), extra_gas))
170 }
171
172 pub fn retryable_size_bytes<B: SystemStateBackend>(
174 &self,
175 backend: &mut B,
176 id: B256,
177 current_time: u64,
178 ) -> Result<u64, RetryableError> {
179 let retryable = self.open_retryable(backend, id, current_time)?;
180 match retryable {
181 None => Ok(0),
182 Some(ret) => {
183 let size = ret.calldata_size(backend)?;
184 let calldata_slots = 32 + 32 * words_for_bytes(size);
185 Ok(6 * 32 + calldata_slots)
186 }
187 }
188 }
189
190 pub fn delete_retryable<F, G, B>(
193 &self,
194 backend: &mut B,
195 id: B256,
196 mut transfer_fn: F,
197 mut balance_of: G,
198 ) -> Result<bool, RetryableError>
199 where
200 F: FnMut(Address, Address, U256) -> Result<(), BalanceError>,
201 G: FnMut(Address) -> U256,
202 B: StorageBackend,
203 {
204 let ret = self.internal_open(id);
205 let timeout = ret.timeout.get(backend)?;
206 if timeout == 0 {
207 return Ok(false);
208 }
209
210 let beneficiary_address = ret.beneficiary.get(backend)?;
211 let escrow_address = retryable_escrow_address(id);
212 let amount = balance_of(escrow_address);
213 transfer_fn(escrow_address, beneficiary_address, amount)?;
214
215 clear_ticket_fields(backend, &ret)?;
216 Ok(true)
217 }
218
219 pub fn get_timeout<B: SystemStateBackend>(
223 &self,
224 backend: &mut B,
225 ticket_id: B256,
226 current_timestamp: u64,
227 ) -> Result<RetryableLookup<u64>, RetryableError> {
228 let (opened, extra_gas) =
229 self.open_retryable_metered(backend, ticket_id, current_timestamp)?;
230 let outcome = match opened {
231 Some(ret) => LookupOutcome::Found(ret.calculate_timeout(backend)?),
232 None => LookupOutcome::NoTicket,
233 };
234 Ok(RetryableLookup { outcome, extra_gas })
235 }
236
237 pub fn get_beneficiary<B: SystemStateBackend>(
241 &self,
242 backend: &mut B,
243 ticket_id: B256,
244 current_timestamp: u64,
245 ) -> Result<RetryableLookup<Address>, RetryableError> {
246 let (opened, extra_gas) =
247 self.open_retryable_metered(backend, ticket_id, current_timestamp)?;
248 let outcome = match opened {
249 Some(ret) => LookupOutcome::Found(ret.beneficiary(backend)?),
250 None => LookupOutcome::NoTicket,
251 };
252 Ok(RetryableLookup { outcome, extra_gas })
253 }
254
255 pub fn calldata_size_for<B: SystemStateBackend>(
260 &self,
261 backend: &mut B,
262 ticket_id: B256,
263 current_timestamp: u64,
264 ) -> Result<(u64, u64), RetryableError> {
265 let (opened, extra_gas) =
266 self.open_retryable_metered(backend, ticket_id, current_timestamp)?;
267 let size = match opened {
268 Some(ret) => ret.calldata_size(backend)?,
269 None => 0,
270 };
271 Ok((size, extra_gas))
272 }
273
274 pub fn increment_num_tries_for<B: StorageBackend>(
279 &self,
280 backend: &mut B,
281 ticket_id: B256,
282 current_timestamp: u64,
283 ) -> Result<RetryableLookup<u64>, RetryableError> {
284 let (opened, extra_gas) =
285 self.open_retryable_metered(backend, ticket_id, current_timestamp)?;
286 let outcome = match opened {
287 Some(ret) => LookupOutcome::Found(ret.increment_num_tries(backend)? - 1),
288 None => LookupOutcome::NoTicket,
289 };
290 Ok(RetryableLookup { outcome, extra_gas })
291 }
292
293 pub fn keepalive<B: StorageBackend>(
295 &self,
296 backend: &mut B,
297 ticket_id: B256,
298 current_timestamp: u64,
299 limit_before_add: u64,
300 _time_to_add: u64,
301 ) -> Result<RetryableLookup<u64>, RetryableError> {
302 let (opened, extra_gas) =
303 self.open_retryable_metered(backend, ticket_id, current_timestamp)?;
304 let retryable = match opened {
305 Some(ret) => ret,
306 None => {
307 return Ok(RetryableLookup {
308 outcome: LookupOutcome::NoTicket,
309 extra_gas,
310 });
311 }
312 };
313 let timeout = retryable.calculate_timeout(backend)?;
314 if timeout > limit_before_add {
315 return Err(RetryableError::TimeoutTooFarFuture);
316 }
317 self.timeout_queue.put(backend, retryable.id)?;
318 retryable.increment_timeout_windows(backend)?;
319 Ok(RetryableLookup {
320 outcome: LookupOutcome::Found(timeout + RETRYABLE_LIFETIME_SECONDS),
321 extra_gas,
322 })
323 }
324
325 pub fn cancel<B: StorageBackend>(
332 &self,
333 backend: &mut B,
334 ticket_id: B256,
335 caller: Address,
336 current_timestamp: u64,
337 ) -> Result<CancelLookup, RetryableError> {
338 let (opened, extra_gas) =
339 self.open_retryable_metered(backend, ticket_id, current_timestamp)?;
340 let retryable = match opened {
341 Some(ret) => ret,
342 None => {
343 return Ok(CancelLookup {
344 outcome: CancelOutcome::NoTicket,
345 extra_gas,
346 });
347 }
348 };
349 let beneficiary = retryable.beneficiary(backend)?;
350 if caller != beneficiary {
351 return Ok(CancelLookup {
352 outcome: CancelOutcome::NotBeneficiary,
353 extra_gas,
354 });
355 }
356 let calldata_size = retryable.calldata_size(backend)?;
357 clear_ticket_fields(backend, &retryable)?;
358 Ok(CancelLookup {
359 outcome: CancelOutcome::Cleared {
360 calldata_size,
361 beneficiary,
362 },
363 extra_gas,
364 })
365 }
366
367 pub fn try_to_reap_one_retryable<F, G, B>(
369 &self,
370 backend: &mut B,
371 current_timestamp: u64,
372 mut transfer_fn: F,
373 mut balance_of: G,
374 ) -> Result<(), RetryableError>
375 where
376 F: FnMut(Address, Address, U256) -> Result<(), BalanceError>,
377 G: FnMut(Address) -> U256,
378 B: StorageBackend,
379 {
380 let id = self.timeout_queue.peek(backend)?;
381 let id = match id {
382 None => return Ok(()),
383 Some(id) => id,
384 };
385
386 let ret_storage = self.retryables.open_sub_storage(id.as_slice());
387 let timeout_storage = StorageBackedUint64::new(ret_storage.base_key(), TIMEOUT_OFFSET);
388 let timeout = timeout_storage.get(backend)?;
389
390 if timeout == 0 {
391 self.timeout_queue.get(backend)?;
392 return Ok(());
393 }
394
395 let windows_left_storage =
396 StorageBackedUint64::new(ret_storage.base_key(), TIMEOUT_WINDOWS_LEFT_OFFSET);
397 let windows_left = windows_left_storage.get(backend)?;
398
399 if timeout >= current_timestamp {
400 return Ok(());
401 }
402
403 self.timeout_queue.get(backend)?;
404
405 if windows_left == 0 {
406 self.delete_retryable(backend, id, &mut transfer_fn, &mut balance_of)?;
407 return Ok(());
408 }
409
410 timeout_storage.set(backend, timeout + RETRYABLE_LIFETIME_SECONDS)?;
411 windows_left_storage.set(backend, windows_left - 1)?;
412 Ok(())
413 }
414
415 pub fn queue_size<B: SystemStateBackend>(
417 &self,
418 backend: &mut B,
419 ) -> Result<u64, RetryableError> {
420 Ok(self.timeout_queue.size(backend)?)
421 }
422
423 pub fn snapshot_queue<B: SystemStateBackend>(
426 &self,
427 backend: &mut B,
428 current_time: u64,
429 max_entries: usize,
430 ) -> Result<Vec<(B256, u64)>, RetryableError> {
431 let ids: Vec<B256> = {
432 let mut collected = Vec::new();
433 self.timeout_queue
434 .for_each(backend, |id| -> Result<(), RetryableError> {
435 collected.push(id);
436 Ok(())
437 })?;
438 collected
439 };
440 let mut out = Vec::new();
441 for id in ids {
442 if out.len() >= max_entries {
443 break;
444 }
445 if let Some(retryable) = self.open_retryable(backend, id, current_time)? {
446 let timeout = retryable.calculate_timeout(backend)?;
447 out.push((id, timeout));
448 }
449 }
450 Ok(out)
451 }
452
453 fn internal_open(&self, id: B256) -> Retryable<'a, D> {
454 let sto = self.retryables.open_sub_storage(id.as_slice());
455 let base_key = sto.base_key();
456 let calldata_key = sto.open_sub_storage(CALLDATA_KEY).base_key();
457 Retryable {
458 id,
459 num_tries: StorageBackedUint64::new(base_key, NUM_TRIES_OFFSET),
460 from: StorageBackedAddress::new(base_key, FROM_OFFSET),
461 to: StorageBackedAddressOrNil::new(base_key, TO_OFFSET),
462 callvalue: StorageBackedBigUint::new(base_key, CALLVALUE_OFFSET),
463 beneficiary: StorageBackedAddress::new(base_key, BENEFICIARY_OFFSET),
464 calldata: StorageBackedBytes::new(calldata_key),
465 timeout: StorageBackedUint64::new(base_key, TIMEOUT_OFFSET),
466 timeout_windows_left: StorageBackedUint64::new(base_key, TIMEOUT_WINDOWS_LEFT_OFFSET),
467 backing_storage: sto,
468 }
469 }
470}
471
472impl<D: Database> RetryableState<'_, D> {
473 pub fn initialize(sto: &Storage<'_, D>) -> Result<(), RetryableError> {
474 initialize_retryable_state(sto)
475 }
476}
477
478fn clear_ticket_fields<D, B: StorageBackend>(
479 backend: &mut B,
480 ret: &Retryable<'_, D>,
481) -> Result<(), RetryableError> {
482 use arb_storage::ARBOS_STATE_ADDRESS;
483 let base_key = ret.backing_storage.base_key();
484 let key_slice: &[u8] = if base_key == B256::ZERO {
485 &[]
486 } else {
487 base_key.as_slice()
488 };
489 for offset in [
490 NUM_TRIES_OFFSET,
491 FROM_OFFSET,
492 TO_OFFSET,
493 CALLVALUE_OFFSET,
494 BENEFICIARY_OFFSET,
495 TIMEOUT_OFFSET,
496 TIMEOUT_WINDOWS_LEFT_OFFSET,
497 ] {
498 let slot = arb_storage::storage_key_map(key_slice, offset);
499 backend
500 .sstore(ARBOS_STATE_ADDRESS, slot, U256::ZERO)
501 .map_err(Into::into)?;
502 }
503 ret.calldata.clear(backend)?;
504 Ok(())
505}
506
507impl<D> Retryable<'_, D> {
508 pub fn num_tries<B: SystemStateBackend>(&self, backend: &mut B) -> Result<u64, RetryableError> {
509 Ok(self.num_tries.get(backend)?)
510 }
511
512 pub fn increment_num_tries<B: StorageBackend>(
513 &self,
514 backend: &mut B,
515 ) -> Result<u64, RetryableError> {
516 let current = self.num_tries.get(backend)?;
517 let new_val = current + 1;
518 self.num_tries.set(backend, new_val)?;
519 Ok(new_val)
520 }
521
522 pub fn beneficiary<B: SystemStateBackend>(
523 &self,
524 backend: &mut B,
525 ) -> Result<Address, RetryableError> {
526 Ok(self.beneficiary.get(backend)?)
527 }
528
529 pub fn calculate_timeout<B: SystemStateBackend>(
530 &self,
531 backend: &mut B,
532 ) -> Result<u64, RetryableError> {
533 let timeout = self.timeout.get(backend)?;
534 let windows = self.timeout_windows_left.get(backend)?;
535 Ok(timeout + windows * RETRYABLE_LIFETIME_SECONDS)
536 }
537
538 pub fn set_timeout<B: StorageBackend>(
539 &self,
540 backend: &mut B,
541 val: u64,
542 ) -> Result<(), RetryableError> {
543 Ok(self.timeout.set(backend, val)?)
544 }
545
546 pub fn timeout_windows_left<B: SystemStateBackend>(
547 &self,
548 backend: &mut B,
549 ) -> Result<u64, RetryableError> {
550 Ok(self.timeout_windows_left.get(backend)?)
551 }
552
553 fn increment_timeout_windows<B: StorageBackend>(
554 &self,
555 backend: &mut B,
556 ) -> Result<u64, RetryableError> {
557 let current = self.timeout_windows_left.get(backend)?;
558 let new_val = current + 1;
559 self.timeout_windows_left.set(backend, new_val)?;
560 Ok(new_val)
561 }
562
563 pub fn from<B: SystemStateBackend>(&self, backend: &mut B) -> Result<Address, RetryableError> {
564 Ok(self.from.get(backend)?)
565 }
566
567 pub fn to<B: SystemStateBackend>(
568 &self,
569 backend: &mut B,
570 ) -> Result<Option<Address>, RetryableError> {
571 Ok(self.to.get(backend)?)
572 }
573
574 pub fn callvalue<B: SystemStateBackend>(
575 &self,
576 backend: &mut B,
577 ) -> Result<U256, RetryableError> {
578 Ok(self.callvalue.get(backend)?)
579 }
580
581 pub fn calldata<B: SystemStateBackend>(
582 &self,
583 backend: &mut B,
584 ) -> Result<Vec<u8>, RetryableError> {
585 Ok(self.calldata.get(backend)?)
586 }
587
588 pub fn calldata_size<B: SystemStateBackend>(
589 &self,
590 backend: &mut B,
591 ) -> Result<u64, RetryableError> {
592 Ok(self.calldata.size(backend)?)
593 }
594
595 pub fn make_tx<B: SystemStateBackend>(
598 &self,
599 backend: &mut B,
600 chain_id: U256,
601 nonce: u64,
602 gas_fee_cap: U256,
603 gas: u64,
604 ticket_id: B256,
605 refund_to: Address,
606 max_refund: U256,
607 submission_fee_refund: U256,
608 ) -> Result<arb_alloy_consensus::tx::ArbRetryTx, RetryableError> {
609 Ok(arb_alloy_consensus::tx::ArbRetryTx {
610 chain_id,
611 nonce,
612 from: self.from(backend)?,
613 gas_fee_cap,
614 gas,
615 to: self.to(backend)?,
616 value: self.callvalue(backend)?,
617 data: self.calldata(backend)?.into(),
618 ticket_id,
619 refund_to,
620 max_refund,
621 submission_fee_refund,
622 })
623 }
624}
625
626pub fn retryable_escrow_address(ticket_id: B256) -> Address {
628 let mut data = Vec::with_capacity(16 + 32);
629 data.extend_from_slice(b"retryable escrow");
630 data.extend_from_slice(ticket_id.as_slice());
631 let hash = keccak256(&data);
632 Address::from_slice(&hash[12..])
633}
634
635pub fn retryable_submission_fee(calldata_length: usize, l1_base_fee: U256) -> U256 {
638 let factor = U256::from(1400u64)
639 .saturating_add(U256::from(6u64).saturating_mul(U256::from(calldata_length as u128)));
640 l1_base_fee.saturating_mul(factor)
641}
642
643fn words_for_bytes(bytes: u64) -> u64 {
645 bytes.div_ceil(32)
646}