1use alloy_primitives::{keccak256, Address, B256, U256};
2use revm::Database;
3
4use arb_storage::{
5 initialize_queue, open_queue, Queue, Storage, StorageBackedAddress, StorageBackedAddressOrNil,
6 StorageBackedBigUint, StorageBackedBytes, StorageBackedUint64,
7};
8
9pub const RETRYABLE_LIFETIME_SECONDS: u64 = 7 * 24 * 60 * 60; pub const RETRYABLE_REAP_PRICE: u64 = 58000;
11
12pub const TIMEOUT_QUEUE_KEY: &[u8] = &[0];
13pub const CALLDATA_KEY: &[u8] = &[1];
14
15pub const NUM_TRIES_OFFSET: u64 = 0;
17pub const FROM_OFFSET: u64 = 1;
18pub const TO_OFFSET: u64 = 2;
19pub const CALLVALUE_OFFSET: u64 = 3;
20pub const BENEFICIARY_OFFSET: u64 = 4;
21pub const TIMEOUT_OFFSET: u64 = 5;
22pub const TIMEOUT_WINDOWS_LEFT_OFFSET: u64 = 6;
23
24pub struct RetryableState<D> {
26 retryables: Storage<D>,
27 pub timeout_queue: Queue<D>,
28}
29
30pub struct Retryable<D> {
32 pub id: B256,
33 #[allow(dead_code)]
34 backing_storage: Storage<D>,
35 num_tries: StorageBackedUint64<D>,
36 from: StorageBackedAddress<D>,
37 to: StorageBackedAddressOrNil<D>,
38 callvalue: StorageBackedBigUint<D>,
39 beneficiary: StorageBackedAddress<D>,
40 calldata: StorageBackedBytes<D>,
41 timeout: StorageBackedUint64<D>,
42 timeout_windows_left: StorageBackedUint64<D>,
43}
44
45pub fn initialize_retryable_state<D: Database>(sto: &Storage<D>) -> Result<(), ()> {
46 initialize_queue(&sto.open_sub_storage(TIMEOUT_QUEUE_KEY))
47}
48
49pub fn open_retryable_state<D: Database>(sto: Storage<D>) -> RetryableState<D> {
50 let queue_sto = sto.open_sub_storage(TIMEOUT_QUEUE_KEY);
51 RetryableState {
52 timeout_queue: open_queue(queue_sto),
53 retryables: sto,
54 }
55}
56
57impl<D: Database> RetryableState<D> {
58 pub fn initialize(sto: &Storage<D>) -> Result<(), ()> {
59 initialize_retryable_state(sto)
60 }
61
62 pub fn open(sto: Storage<D>) -> Self {
63 open_retryable_state(sto)
64 }
65
66 pub fn create_retryable(
68 &self,
69 id: B256,
70 timeout: u64,
71 from: Address,
72 to: Option<Address>,
73 callvalue: U256,
74 beneficiary: Address,
75 calldata: &[u8],
76 ) -> Result<Retryable<D>, ()> {
77 let ret = self.internal_open(id);
78 ret.num_tries.set(0)?;
79 ret.from.set(from)?;
80 ret.to.set(to)?;
81 ret.callvalue.set(callvalue)?;
82 ret.beneficiary.set(beneficiary)?;
83 ret.calldata.set(calldata)?;
84 ret.timeout.set(timeout)?;
85 ret.timeout_windows_left.set(0)?;
86 self.timeout_queue.put(id)?;
87 Ok(ret)
88 }
89
90 pub fn open_retryable(
92 &self,
93 id: B256,
94 current_timestamp: u64,
95 ) -> Result<Option<Retryable<D>>, ()> {
96 let sto = self.retryables.open_sub_storage(id.as_slice());
97 let timeout_storage =
98 StorageBackedUint64::new(sto.state_ptr(), sto.base_key(), TIMEOUT_OFFSET);
99 let timeout = timeout_storage.get()?;
100 if timeout == 0 || timeout < current_timestamp {
101 return Ok(None);
102 }
103 Ok(Some(self.internal_open(id)))
104 }
105
106 pub fn retryable_size_bytes(&self, id: B256, current_time: u64) -> Result<u64, ()> {
108 let retryable = self.open_retryable(id, current_time)?;
109 match retryable {
110 None => Ok(0),
111 Some(ret) => {
112 let size = ret.calldata_size()?;
113 let calldata_slots = 32 + 32 * words_for_bytes(size);
114 Ok(6 * 32 + calldata_slots)
115 }
116 }
117 }
118
119 pub fn delete_retryable<F, G>(
122 &self,
123 id: B256,
124 mut transfer_fn: F,
125 mut balance_of: G,
126 ) -> Result<bool, ()>
127 where
128 F: FnMut(Address, Address, U256) -> Result<(), ()>,
129 G: FnMut(Address) -> U256,
130 {
131 let ret_storage = self.retryables.open_sub_storage(id.as_slice());
132 let timeout_val = ret_storage.get_by_uint64(TIMEOUT_OFFSET)?;
133 if timeout_val == B256::ZERO {
134 return Ok(false);
135 }
136
137 let beneficiary_val = ret_storage.get_by_uint64(BENEFICIARY_OFFSET)?;
139 let escrow_address = retryable_escrow_address(id);
140 let beneficiary_address = Address::from_slice(&beneficiary_val[12..]);
141 let amount = balance_of(escrow_address);
142 transfer_fn(escrow_address, beneficiary_address, amount)?;
143
144 let _ = ret_storage.set_by_uint64(NUM_TRIES_OFFSET, B256::ZERO);
146 let _ = ret_storage.set_by_uint64(FROM_OFFSET, B256::ZERO);
147 let _ = ret_storage.set_by_uint64(TO_OFFSET, B256::ZERO);
148 let _ = ret_storage.set_by_uint64(CALLVALUE_OFFSET, B256::ZERO);
149 let _ = ret_storage.set_by_uint64(BENEFICIARY_OFFSET, B256::ZERO);
150 let _ = ret_storage.set_by_uint64(TIMEOUT_OFFSET, B256::ZERO);
151 let _ = ret_storage.set_by_uint64(TIMEOUT_WINDOWS_LEFT_OFFSET, B256::ZERO);
152 let bytes_storage = StorageBackedBytes::new(ret_storage.open_sub_storage(CALLDATA_KEY));
153 bytes_storage.clear()?;
154 Ok(true)
155 }
156
157 pub fn keepalive(
159 &self,
160 ticket_id: B256,
161 current_timestamp: u64,
162 limit_before_add: u64,
163 _time_to_add: u64,
164 ) -> Result<u64, ()> {
165 let retryable = self.open_retryable(ticket_id, current_timestamp)?;
166 let retryable = retryable.ok_or(())?;
167 let timeout = retryable.calculate_timeout()?;
168 if timeout > limit_before_add {
169 return Err(());
170 }
171 self.timeout_queue.put(retryable.id)?;
172 retryable.increment_timeout_windows()?;
173 let new_timeout = timeout + RETRYABLE_LIFETIME_SECONDS;
174 Ok(new_timeout)
176 }
177
178 pub fn try_to_reap_one_retryable<F, G>(
180 &self,
181 current_timestamp: u64,
182 mut transfer_fn: F,
183 mut balance_of: G,
184 ) -> Result<(), ()>
185 where
186 F: FnMut(Address, Address, U256) -> Result<(), ()>,
187 G: FnMut(Address) -> U256,
188 {
189 let id = self.timeout_queue.peek()?;
190 let id = match id {
191 None => return Ok(()),
192 Some(id) => id,
193 };
194
195 let ret_storage = self.retryables.open_sub_storage(id.as_slice());
196 let timeout_storage = StorageBackedUint64::new(
197 ret_storage.state_ptr(),
198 ret_storage.base_key(),
199 TIMEOUT_OFFSET,
200 );
201 let timeout = timeout_storage.get()?;
202
203 if timeout == 0 {
204 let _ = self.timeout_queue.get()?;
206 return Ok(());
207 }
208
209 let windows_left_storage = StorageBackedUint64::new(
210 ret_storage.state_ptr(),
211 ret_storage.base_key(),
212 TIMEOUT_WINDOWS_LEFT_OFFSET,
213 );
214 let windows_left = windows_left_storage.get()?;
215
216 if timeout >= current_timestamp {
217 return Ok(());
218 }
219
220 let _ = self.timeout_queue.get()?;
222
223 if windows_left == 0 {
224 self.delete_retryable(id, &mut transfer_fn, &mut balance_of)?;
226 return Ok(());
227 }
228
229 timeout_storage.set(timeout + RETRYABLE_LIFETIME_SECONDS)?;
231 windows_left_storage.set(windows_left - 1)?;
232 Ok(())
233 }
234
235 pub fn queue_size(&self) -> Result<u64, ()> {
237 self.timeout_queue.size()
238 }
239
240 pub fn snapshot_queue(
246 &self,
247 current_time: u64,
248 max_entries: usize,
249 ) -> Result<Vec<(B256, u64)>, ()> {
250 let mut out = Vec::new();
251 self.timeout_queue.for_each(|id| {
252 if out.len() >= max_entries {
253 return Ok(());
254 }
255 match self.open_retryable(id, current_time)? {
256 Some(retryable) => {
257 let timeout = retryable.calculate_timeout()?;
258 out.push((id, timeout));
259 }
260 None => {
261 }
263 }
264 Ok(())
265 })?;
266 Ok(out)
267 }
268
269 fn internal_open(&self, id: B256) -> Retryable<D> {
270 let sto = self.retryables.open_sub_storage(id.as_slice());
271 let state = sto.state_ptr();
272 let base_key = sto.base_key();
273 Retryable {
274 id,
275 num_tries: StorageBackedUint64::new(state, base_key, NUM_TRIES_OFFSET),
276 from: StorageBackedAddress::new(state, base_key, FROM_OFFSET),
277 to: StorageBackedAddressOrNil::new(state, base_key, TO_OFFSET),
278 callvalue: StorageBackedBigUint::new(state, base_key, CALLVALUE_OFFSET),
279 beneficiary: StorageBackedAddress::new(state, base_key, BENEFICIARY_OFFSET),
280 calldata: StorageBackedBytes::new(sto.open_sub_storage(CALLDATA_KEY)),
281 timeout: StorageBackedUint64::new(state, base_key, TIMEOUT_OFFSET),
282 timeout_windows_left: StorageBackedUint64::new(
283 state,
284 base_key,
285 TIMEOUT_WINDOWS_LEFT_OFFSET,
286 ),
287 backing_storage: sto,
288 }
289 }
290}
291
292impl<D: Database> Retryable<D> {
293 pub fn num_tries(&self) -> Result<u64, ()> {
294 self.num_tries.get()
295 }
296
297 pub fn increment_num_tries(&self) -> Result<u64, ()> {
298 let current = self.num_tries.get()?;
299 let new_val = current + 1;
300 self.num_tries.set(new_val)?;
301 Ok(new_val)
302 }
303
304 pub fn beneficiary(&self) -> Result<Address, ()> {
305 self.beneficiary.get()
306 }
307
308 pub fn calculate_timeout(&self) -> Result<u64, ()> {
309 let timeout = self.timeout.get()?;
310 let windows = self.timeout_windows_left.get()?;
311 Ok(timeout + windows * RETRYABLE_LIFETIME_SECONDS)
312 }
313
314 pub fn set_timeout(&self, val: u64) -> Result<(), ()> {
315 self.timeout.set(val)
316 }
317
318 pub fn timeout_windows_left(&self) -> Result<u64, ()> {
319 self.timeout_windows_left.get()
320 }
321
322 fn increment_timeout_windows(&self) -> Result<u64, ()> {
323 let current = self.timeout_windows_left.get()?;
324 let new_val = current + 1;
325 self.timeout_windows_left.set(new_val)?;
326 Ok(new_val)
327 }
328
329 pub fn from(&self) -> Result<Address, ()> {
330 self.from.get()
331 }
332
333 pub fn to(&self) -> Result<Option<Address>, ()> {
334 self.to.get()
335 }
336
337 pub fn callvalue(&self) -> Result<U256, ()> {
338 self.callvalue.get()
339 }
340
341 pub fn calldata(&self) -> Result<Vec<u8>, ()> {
342 self.calldata.get()
343 }
344
345 pub fn calldata_size(&self) -> Result<u64, ()> {
346 self.calldata.size()
347 }
348
349 pub fn make_tx(
352 &self,
353 chain_id: U256,
354 nonce: u64,
355 gas_fee_cap: U256,
356 gas: u64,
357 ticket_id: B256,
358 refund_to: Address,
359 max_refund: U256,
360 submission_fee_refund: U256,
361 ) -> Result<arb_alloy_consensus::tx::ArbRetryTx, ()> {
362 Ok(arb_alloy_consensus::tx::ArbRetryTx {
363 chain_id,
364 nonce,
365 from: self.from()?,
366 gas_fee_cap,
367 gas,
368 to: self.to()?,
369 value: self.callvalue()?,
370 data: self.calldata()?.into(),
371 ticket_id,
372 refund_to,
373 max_refund,
374 submission_fee_refund,
375 })
376 }
377}
378
379pub fn retryable_escrow_address(ticket_id: B256) -> Address {
381 let mut data = Vec::with_capacity(16 + 32);
382 data.extend_from_slice(b"retryable escrow");
383 data.extend_from_slice(ticket_id.as_slice());
384 let hash = keccak256(&data);
385 Address::from_slice(&hash[12..])
386}
387
388pub fn retryable_submission_fee(calldata_length: usize, l1_base_fee: U256) -> U256 {
393 let factor = U256::from(1400u64)
394 .saturating_add(U256::from(6u64).saturating_mul(U256::from(calldata_length as u128)));
395 l1_base_fee.saturating_mul(factor)
396}
397
398fn words_for_bytes(bytes: u64) -> u64 {
400 bytes.div_ceil(32)
401}