tempo_alloy/fillers/
nonce.rs

1use crate::rpc::TempoTransactionRequest;
2use alloy_network::{Network, TransactionBuilder};
3use alloy_primitives::U256;
4use alloy_provider::{
5    SendableTx,
6    fillers::{FillerControlFlow, TxFiller},
7};
8use alloy_transport::TransportResult;
9use tempo_primitives::subblock::has_sub_block_nonce_key_prefix;
10
11/// A [`TxFiller`] that populates the [`TempoTransaction`](`tempo_primitives::TempoTransaction`) transaction with a random `nonce_key`, and `nonce` set to `0`.
12///
13/// This filler can be used to avoid nonce gaps by having a random 2D nonce key that doesn't conflict with any other transactions.
14#[derive(Clone, Copy, Debug, Default)]
15pub struct Random2DNonceFiller;
16
17impl Random2DNonceFiller {
18    /// Returns `true` if either the nonce or nonce key is already filled.
19    fn is_filled(tx: &TempoTransactionRequest) -> bool {
20        tx.nonce().is_some() || tx.nonce_key.is_some()
21    }
22}
23
24impl<N: Network<TransactionRequest = TempoTransactionRequest>> TxFiller<N> for Random2DNonceFiller {
25    type Fillable = ();
26
27    fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
28        if Self::is_filled(tx) {
29            return FillerControlFlow::Finished;
30        }
31        FillerControlFlow::Ready
32    }
33
34    fn fill_sync(&self, tx: &mut SendableTx<N>) {
35        if let Some(builder) = tx.as_mut_builder()
36            && !Self::is_filled(builder)
37        {
38            let nonce_key = loop {
39                let key = U256::random();
40                // We need to ensure that it doesn't use the subblock nonce key prefix
41                if !has_sub_block_nonce_key_prefix(&key) {
42                    break key;
43                }
44            };
45            builder.set_nonce_key(nonce_key);
46            builder.set_nonce(0);
47        }
48    }
49
50    async fn prepare<P>(
51        &self,
52        _provider: &P,
53        _tx: &N::TransactionRequest,
54    ) -> TransportResult<Self::Fillable>
55    where
56        P: alloy_provider::Provider<N>,
57    {
58        Ok(())
59    }
60
61    async fn fill(
62        &self,
63        _fillable: Self::Fillable,
64        tx: SendableTx<N>,
65    ) -> TransportResult<SendableTx<N>> {
66        Ok(tx)
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use crate::{TempoNetwork, fillers::Random2DNonceFiller, rpc::TempoTransactionRequest};
73    use alloy_network::TransactionBuilder;
74    use alloy_primitives::ruint::aliases::U256;
75    use alloy_provider::{ProviderBuilder, mock::Asserter};
76    use eyre;
77
78    #[tokio::test]
79    async fn test_random_2d_nonce_filler() -> eyre::Result<()> {
80        let provider = ProviderBuilder::<_, _, TempoNetwork>::default()
81            .filler(Random2DNonceFiller)
82            .connect_mocked_client(Asserter::default());
83
84        // No nonce key, no nonce => nonce key and nonce are filled
85        let filled_request = provider
86            .fill(TempoTransactionRequest::default())
87            .await?
88            .try_into_request()?;
89        assert!(filled_request.nonce_key.is_some());
90        assert_eq!(filled_request.nonce(), Some(0));
91
92        // Has nonce => nothing is filled
93        let filled_request = provider
94            .fill(TempoTransactionRequest::default().with_nonce(1))
95            .await?
96            .try_into_request()?;
97        assert!(filled_request.nonce_key.is_none());
98        assert_eq!(filled_request.nonce(), Some(1));
99
100        // Has nonce key => nothing is filled
101        let filled_request = provider
102            .fill(TempoTransactionRequest::default().with_nonce_key(U256::ONE))
103            .await?
104            .try_into_request()?;
105        assert_eq!(filled_request.nonce_key, Some(U256::ONE));
106        assert!(filled_request.nonce().is_none());
107
108        Ok(())
109    }
110}