tempo_alloy/fillers/
nonce.rs1use 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 dashmap::DashMap;
10use std::{
11 sync::Arc,
12 time::{SystemTime, UNIX_EPOCH},
13};
14use tempo_contracts::precompiles::{INonce, NONCE_PRECOMPILE_ADDRESS};
15use tempo_primitives::{
16 subblock::has_sub_block_nonce_key_prefix, transaction::TEMPO_EXPIRING_NONCE_KEY,
17};
18
19#[derive(Clone, Copy, Debug, Default)]
23pub struct Random2DNonceFiller;
24
25impl Random2DNonceFiller {
26 fn is_filled(tx: &TempoTransactionRequest) -> bool {
28 tx.nonce().is_some() || tx.nonce_key.is_some()
29 }
30}
31
32impl<N: Network<TransactionRequest = TempoTransactionRequest>> TxFiller<N> for Random2DNonceFiller {
33 type Fillable = ();
34
35 fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
36 if Self::is_filled(tx) {
37 return FillerControlFlow::Finished;
38 }
39 FillerControlFlow::Ready
40 }
41
42 fn fill_sync(&self, tx: &mut SendableTx<N>) {
43 if let Some(builder) = tx.as_mut_builder()
44 && !Self::is_filled(builder)
45 {
46 let nonce_key = loop {
47 let key = U256::random();
48 if !has_sub_block_nonce_key_prefix(&key) {
50 break key;
51 }
52 };
53 builder.set_nonce_key(nonce_key);
54 builder.set_nonce(0);
55 }
56 }
57
58 async fn prepare<P>(
59 &self,
60 _provider: &P,
61 _tx: &N::TransactionRequest,
62 ) -> TransportResult<Self::Fillable>
63 where
64 P: alloy_provider::Provider<N>,
65 {
66 Ok(())
67 }
68
69 async fn fill(
70 &self,
71 _fillable: Self::Fillable,
72 tx: SendableTx<N>,
73 ) -> TransportResult<SendableTx<N>> {
74 Ok(tx)
75 }
76}
77
78#[derive(Clone, Copy, Debug)]
85pub struct ExpiringNonceFiller {
86 expiry_secs: u64,
88}
89
90impl Default for ExpiringNonceFiller {
91 fn default() -> Self {
92 Self {
93 expiry_secs: Self::DEFAULT_EXPIRY_SECS,
94 }
95 }
96}
97
98impl ExpiringNonceFiller {
99 pub const DEFAULT_EXPIRY_SECS: u64 = 25;
103
104 pub fn with_expiry_secs(expiry_secs: u64) -> Self {
109 Self { expiry_secs }
110 }
111
112 fn is_filled(tx: &TempoTransactionRequest) -> bool {
117 tx.nonce_key == Some(TEMPO_EXPIRING_NONCE_KEY)
118 && tx.nonce() == Some(0)
119 && tx.valid_before.is_some()
120 }
121
122 fn current_timestamp() -> u64 {
125 SystemTime::now()
126 .duration_since(UNIX_EPOCH)
127 .map(|d| d.as_secs())
128 .unwrap_or_else(|_| {
129 tracing::warn!("system clock before UNIX_EPOCH, using 0");
130 0
131 })
132 }
133}
134
135impl<N: Network<TransactionRequest = TempoTransactionRequest>> TxFiller<N> for ExpiringNonceFiller {
136 type Fillable = ();
137
138 fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
139 if Self::is_filled(tx) {
140 return FillerControlFlow::Finished;
141 }
142 FillerControlFlow::Ready
143 }
144
145 fn fill_sync(&self, tx: &mut SendableTx<N>) {
146 if let Some(builder) = tx.as_mut_builder()
147 && !Self::is_filled(builder)
148 {
149 builder.set_nonce_key(TEMPO_EXPIRING_NONCE_KEY);
151 builder.set_nonce(0);
153 builder.set_valid_before(Self::current_timestamp() + self.expiry_secs);
155 }
156 }
157
158 async fn prepare<P>(
159 &self,
160 _provider: &P,
161 _tx: &N::TransactionRequest,
162 ) -> TransportResult<Self::Fillable>
163 where
164 P: alloy_provider::Provider<N>,
165 {
166 Ok(())
167 }
168
169 async fn fill(
170 &self,
171 _fillable: Self::Fillable,
172 tx: SendableTx<N>,
173 ) -> TransportResult<SendableTx<N>> {
174 Ok(tx)
175 }
176}
177
178#[derive(Clone, Debug, Default)]
190pub struct NonceKeyFiller {
191 #[allow(clippy::type_complexity)]
192 nonces: Arc<DashMap<(Address, U256), Arc<futures::lock::Mutex<u64>>>>,
193}
194
195const NONCE_NOT_FETCHED: u64 = u64::MAX;
197
198impl<N: Network<TransactionRequest = TempoTransactionRequest>> TxFiller<N> for NonceKeyFiller {
199 type Fillable = u64;
200
201 fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
202 if tx.nonce().is_some() {
203 return FillerControlFlow::Finished;
204 }
205 if tx.nonce_key.is_none() {
206 return FillerControlFlow::missing("NonceKeyFiller", vec!["nonce_key"]);
207 }
208 if TransactionBuilder::from(tx).is_none() {
209 return FillerControlFlow::missing("NonceKeyFiller", vec!["from"]);
210 }
211 FillerControlFlow::Ready
212 }
213
214 fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
215
216 async fn prepare<P>(
217 &self,
218 provider: &P,
219 tx: &N::TransactionRequest,
220 ) -> TransportResult<Self::Fillable>
221 where
222 P: Provider<N>,
223 {
224 let from = TransactionBuilder::from(tx)
225 .ok_or_else(|| TransportErrorKind::custom_str("missing `from` address"))?;
226 let nonce_key = tx
227 .nonce_key
228 .ok_or_else(|| TransportErrorKind::custom_str("missing `nonce_key`"))?;
229
230 if nonce_key == TEMPO_EXPIRING_NONCE_KEY {
232 return Ok(0);
233 }
234
235 let key = (from, nonce_key);
236 let mutex = self
237 .nonces
238 .entry(key)
239 .or_insert_with(|| Arc::new(futures::lock::Mutex::new(NONCE_NOT_FETCHED)))
240 .clone();
241
242 let mut nonce = mutex.lock().await;
243
244 if *nonce == NONCE_NOT_FETCHED {
245 *nonce = if nonce_key.is_zero() {
246 provider.get_transaction_count(from).await?
247 } else {
248 let contract = INonce::new(NONCE_PRECOMPILE_ADDRESS, provider);
249 contract
250 .getNonce(from, nonce_key)
251 .call()
252 .await
253 .map_err(|e| TransportErrorKind::custom_str(&e.to_string()))?
254 };
255 } else {
256 *nonce += 1;
257 }
258
259 Ok(*nonce)
260 }
261
262 async fn fill(
263 &self,
264 fillable: Self::Fillable,
265 mut tx: SendableTx<N>,
266 ) -> TransportResult<SendableTx<N>> {
267 if let Some(builder) = tx.as_mut_builder() {
268 builder.set_nonce(fillable);
269 }
270 Ok(tx)
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::{TempoNetwork, fillers::Random2DNonceFiller, rpc::TempoTransactionRequest};
277 use alloy_network::TransactionBuilder;
278 use alloy_primitives::ruint::aliases::U256;
279 use alloy_provider::{ProviderBuilder, mock::Asserter};
280 use eyre;
281
282 #[tokio::test]
283 async fn test_random_2d_nonce_filler() -> eyre::Result<()> {
284 let provider = ProviderBuilder::<_, _, TempoNetwork>::default()
285 .filler(Random2DNonceFiller)
286 .connect_mocked_client(Asserter::default());
287
288 let filled_request = provider
290 .fill(TempoTransactionRequest::default())
291 .await?
292 .try_into_request()?;
293 assert!(filled_request.nonce_key.is_some());
294 assert_eq!(filled_request.nonce(), Some(0));
295
296 let filled_request = provider
298 .fill(TempoTransactionRequest::default().with_nonce(1))
299 .await?
300 .try_into_request()?;
301 assert!(filled_request.nonce_key.is_none());
302 assert_eq!(filled_request.nonce(), Some(1));
303
304 let filled_request = provider
306 .fill(TempoTransactionRequest::default().with_nonce_key(U256::ONE))
307 .await?
308 .try_into_request()?;
309 assert_eq!(filled_request.nonce_key, Some(U256::ONE));
310 assert!(filled_request.nonce().is_none());
311
312 Ok(())
313 }
314}