1use crate::rpc::TempoTransactionRequest;
2use alloy_network::{Network, TransactionBuilder};
3use alloy_primitives::{Address, U256};
4use alloy_provider::{
5 Provider, SendableTx,
6 fillers::{FillerControlFlow, TxFiller},
7};
8use alloy_transport::{TransportErrorKind, TransportResult};
9use core::num::NonZeroU64;
10use dashmap::DashMap;
11use std::{
12 sync::Arc,
13 time::{SystemTime, UNIX_EPOCH},
14};
15use tempo_contracts::precompiles::{INonce, NONCE_PRECOMPILE_ADDRESS};
16use tempo_primitives::{
17 subblock::has_sub_block_nonce_key_prefix, transaction::TEMPO_EXPIRING_NONCE_KEY,
18};
19
20#[derive(Clone, Copy, Debug, Default)]
24pub struct Random2DNonceFiller;
25
26impl Random2DNonceFiller {
27 fn is_filled(tx: &TempoTransactionRequest) -> bool {
29 tx.nonce().is_some() || tx.nonce_key.is_some()
30 }
31}
32
33impl<N: Network<TransactionRequest = TempoTransactionRequest>> TxFiller<N> for Random2DNonceFiller {
34 type Fillable = ();
35
36 fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
37 if Self::is_filled(tx) {
38 return FillerControlFlow::Finished;
39 }
40 FillerControlFlow::Ready
41 }
42
43 fn fill_sync(&self, tx: &mut SendableTx<N>) {
44 if let Some(builder) = tx.as_mut_builder()
45 && !Self::is_filled(builder)
46 {
47 let nonce_key = loop {
48 let key = U256::random();
49 if !has_sub_block_nonce_key_prefix(&key) {
51 break key;
52 }
53 };
54 builder.set_nonce_key(nonce_key);
55 builder.set_nonce(0);
56 }
57 }
58
59 async fn prepare<P>(
60 &self,
61 _provider: &P,
62 _tx: &N::TransactionRequest,
63 ) -> TransportResult<Self::Fillable>
64 where
65 P: alloy_provider::Provider<N>,
66 {
67 Ok(())
68 }
69
70 async fn fill(
71 &self,
72 _fillable: Self::Fillable,
73 tx: SendableTx<N>,
74 ) -> TransportResult<SendableTx<N>> {
75 Ok(tx)
76 }
77}
78
79#[derive(Clone, Copy, Debug)]
86pub struct ExpiringNonceFiller {
87 expiry_secs: u64,
89}
90
91impl Default for ExpiringNonceFiller {
92 fn default() -> Self {
93 Self {
94 expiry_secs: Self::DEFAULT_EXPIRY_SECS,
95 }
96 }
97}
98
99impl ExpiringNonceFiller {
100 pub const DEFAULT_EXPIRY_SECS: u64 = 25;
104
105 pub fn with_expiry_secs(expiry_secs: u64) -> Self {
110 Self { expiry_secs }
111 }
112
113 fn is_filled(tx: &TempoTransactionRequest) -> bool {
118 tx.nonce_key == Some(TEMPO_EXPIRING_NONCE_KEY)
119 && tx.nonce() == Some(0)
120 && tx.valid_before.is_some()
121 }
122
123 fn current_timestamp() -> u64 {
126 SystemTime::now()
127 .duration_since(UNIX_EPOCH)
128 .map(|d| d.as_secs())
129 .unwrap_or_else(|_| {
130 tracing::warn!("system clock before UNIX_EPOCH, using 0");
131 0
132 })
133 }
134}
135
136impl<N: Network<TransactionRequest = TempoTransactionRequest>> TxFiller<N> for ExpiringNonceFiller {
137 type Fillable = ();
138
139 fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
140 if Self::is_filled(tx) {
141 return FillerControlFlow::Finished;
142 }
143 FillerControlFlow::Ready
144 }
145
146 fn fill_sync(&self, tx: &mut SendableTx<N>) {
147 if let Some(builder) = tx.as_mut_builder()
148 && !Self::is_filled(builder)
149 {
150 builder.set_nonce_key(TEMPO_EXPIRING_NONCE_KEY);
152 builder.set_nonce(0);
154 builder.set_valid_before(
156 NonZeroU64::new(Self::current_timestamp() + self.expiry_secs)
157 .expect("expiring nonce filler requires a non-zero valid_before"),
158 );
159 }
160 }
161
162 async fn prepare<P>(
163 &self,
164 _provider: &P,
165 _tx: &N::TransactionRequest,
166 ) -> TransportResult<Self::Fillable>
167 where
168 P: alloy_provider::Provider<N>,
169 {
170 Ok(())
171 }
172
173 async fn fill(
174 &self,
175 _fillable: Self::Fillable,
176 tx: SendableTx<N>,
177 ) -> TransportResult<SendableTx<N>> {
178 Ok(tx)
179 }
180}
181
182#[derive(Clone, Debug)]
195pub struct NonceKeyFiller {
196 cache_enabled: bool,
197 #[allow(clippy::type_complexity)]
198 nonces: Arc<DashMap<(Address, U256), Arc<futures::lock::Mutex<u64>>>>,
199}
200
201const NONCE_NOT_FETCHED: u64 = u64::MAX;
203
204impl Default for NonceKeyFiller {
205 fn default() -> Self {
206 Self {
207 cache_enabled: true,
208 nonces: Arc::default(),
209 }
210 }
211}
212
213impl NonceKeyFiller {
214 pub const fn with_caching_enabled(mut self, cache_enabled: bool) -> Self {
216 self.cache_enabled = cache_enabled;
217 self
218 }
219
220 pub fn set_caching_enabled(&mut self, cache_enabled: bool) {
222 self.cache_enabled = cache_enabled;
223 }
224
225 pub fn disable_caching(&mut self) {
227 self.set_caching_enabled(false);
228 }
229
230 pub fn clear(&self) {
234 self.nonces.clear();
235 }
236}
237
238impl<N: Network<TransactionRequest = TempoTransactionRequest>> TxFiller<N> for NonceKeyFiller {
239 type Fillable = u64;
240
241 fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
242 if tx.nonce().is_some() {
243 return FillerControlFlow::Finished;
244 }
245 if tx.nonce_key.is_none() {
246 return FillerControlFlow::missing("NonceKeyFiller", vec!["nonce_key"]);
247 }
248 if TransactionBuilder::from(tx).is_none() {
249 return FillerControlFlow::missing("NonceKeyFiller", vec!["from"]);
250 }
251 FillerControlFlow::Ready
252 }
253
254 fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
255
256 async fn prepare<P>(
257 &self,
258 provider: &P,
259 tx: &N::TransactionRequest,
260 ) -> TransportResult<Self::Fillable>
261 where
262 P: Provider<N>,
263 {
264 let from = TransactionBuilder::from(tx)
265 .ok_or_else(|| TransportErrorKind::custom_str("missing `from` address"))?;
266 let nonce_key = tx
267 .nonce_key
268 .ok_or_else(|| TransportErrorKind::custom_str("missing `nonce_key`"))?;
269
270 if nonce_key == TEMPO_EXPIRING_NONCE_KEY {
272 return Ok(0);
273 }
274
275 let key = (from, nonce_key);
276 let mutex = self
277 .nonces
278 .entry(key)
279 .or_insert_with(|| Arc::new(futures::lock::Mutex::new(NONCE_NOT_FETCHED)))
280 .clone();
281
282 let mut nonce = mutex.lock().await;
283
284 if *nonce == NONCE_NOT_FETCHED || !self.cache_enabled {
285 *nonce = if nonce_key.is_zero() {
286 provider.get_transaction_count(from).await?
287 } else {
288 let contract = INonce::new(NONCE_PRECOMPILE_ADDRESS, provider);
289 contract
290 .getNonce(from, nonce_key)
291 .call()
292 .await
293 .map_err(|e| TransportErrorKind::custom_str(&e.to_string()))?
294 };
295 } else {
296 *nonce += 1;
297 }
298
299 Ok(*nonce)
300 }
301
302 async fn fill(
303 &self,
304 fillable: Self::Fillable,
305 mut tx: SendableTx<N>,
306 ) -> TransportResult<SendableTx<N>> {
307 if let Some(builder) = tx.as_mut_builder() {
308 builder.set_nonce(fillable);
309 }
310 Ok(tx)
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::{TempoNetwork, fillers::Random2DNonceFiller, rpc::TempoTransactionRequest};
318 use alloy::sol_types::SolCall;
319 use alloy_network::TransactionBuilder;
320 use alloy_primitives::{Bytes, ruint::aliases::U256};
321 use alloy_provider::{ProviderBuilder, mock::Asserter};
322 use eyre;
323
324 #[tokio::test]
325 async fn test_random_2d_nonce_filler() -> eyre::Result<()> {
326 let provider = ProviderBuilder::<_, _, TempoNetwork>::default()
327 .filler(Random2DNonceFiller)
328 .connect_mocked_client(Asserter::default());
329
330 let filled_request = provider
332 .fill(TempoTransactionRequest::default())
333 .await?
334 .try_into_request()?;
335 assert!(filled_request.nonce_key.is_some());
336 assert_eq!(filled_request.nonce(), Some(0));
337
338 let filled_request = provider
340 .fill(TempoTransactionRequest::default().with_nonce(1))
341 .await?
342 .try_into_request()?;
343 assert!(filled_request.nonce_key.is_none());
344 assert_eq!(filled_request.nonce(), Some(1));
345
346 let filled_request = provider
348 .fill(TempoTransactionRequest::default().with_nonce_key(U256::ONE))
349 .await?
350 .try_into_request()?;
351 assert_eq!(filled_request.nonce_key, Some(U256::ONE));
352 assert!(filled_request.nonce().is_none());
353
354 Ok(())
355 }
356
357 #[tokio::test]
358 async fn test_nonce_key_filler_clear_refetches_chain_nonce() -> eyre::Result<()> {
359 let asserter = Asserter::new();
360 let provider = ProviderBuilder::<_, _, TempoNetwork>::default()
361 .connect_mocked_client(asserter.clone());
362 let filler = NonceKeyFiller::default();
363 let account = Address::repeat_byte(0x11);
364 let nonce_key = U256::from(7_u64);
365 let mut tx = TempoTransactionRequest::default().with_nonce_key(nonce_key);
366 tx.set_from(account);
367
368 asserter.push_success(&Bytes::from(INonce::getNonceCall::abi_encode_returns(
369 &10_u64,
370 )));
371
372 let first = TxFiller::<TempoNetwork>::prepare(&filler, &provider, &tx).await?;
373 let second = TxFiller::<TempoNetwork>::prepare(&filler, &provider, &tx).await?;
374
375 assert_eq!(first, 10);
376 assert_eq!(second, 11);
377
378 filler.clear();
379
380 asserter.push_success(&Bytes::from(INonce::getNonceCall::abi_encode_returns(
381 &42_u64,
382 )));
383
384 let reset = TxFiller::<TempoNetwork>::prepare(&filler, &provider, &tx).await?;
385
386 assert_eq!(reset, 42);
387
388 Ok(())
389 }
390
391 #[tokio::test]
392 async fn test_nonce_key_filler_can_disable_caching() -> eyre::Result<()> {
393 let asserter = Asserter::new();
394 let provider = ProviderBuilder::<_, _, TempoNetwork>::default()
395 .connect_mocked_client(asserter.clone());
396 let mut filler = NonceKeyFiller::default();
397 filler.disable_caching();
398 let account = Address::repeat_byte(0x22);
399 let nonce_key = U256::from(7_u64);
400 let mut tx = TempoTransactionRequest::default().with_nonce_key(nonce_key);
401 tx.set_from(account);
402
403 asserter.push_success(&Bytes::from(INonce::getNonceCall::abi_encode_returns(
404 &10_u64,
405 )));
406 asserter.push_success(&Bytes::from(INonce::getNonceCall::abi_encode_returns(
407 &42_u64,
408 )));
409
410 let first = TxFiller::<TempoNetwork>::prepare(&filler, &provider, &tx).await?;
411 let second = TxFiller::<TempoNetwork>::prepare(&filler, &provider, &tx).await?;
412
413 assert_eq!(first, 10);
414 assert_eq!(second, 42);
415
416 Ok(())
417 }
418}