alloy_provider/fillers/
nonce.rs

1use crate::{
2    fillers::{FillerControlFlow, TxFiller},
3    provider::SendableTx,
4    Provider,
5};
6use alloy_network::{Network, TransactionBuilder};
7use alloy_primitives::Address;
8use alloy_transport::TransportResult;
9use async_trait::async_trait;
10use dashmap::DashMap;
11use futures::lock::Mutex;
12use std::sync::Arc;
13
14/// A trait that determines the behavior of filling nonces.
15#[cfg_attr(target_family = "wasm", async_trait(?Send))]
16#[cfg_attr(not(target_family = "wasm"), async_trait)]
17pub trait NonceManager: Clone + Send + Sync + std::fmt::Debug {
18    /// Get the next nonce for the given account.
19    async fn get_next_nonce<P, N>(&self, provider: &P, address: Address) -> TransportResult<u64>
20    where
21        P: Provider<N>,
22        N: Network;
23}
24
25/// This [`NonceManager`] implementation will fetch the transaction count for any new account it
26/// sees.
27///
28/// Unlike [`CachedNonceManager`], this implementation does not store the transaction count locally,
29/// which results in more frequent calls to the provider, but it is more resilient to chain
30/// reorganizations.
31#[derive(Clone, Debug, Default)]
32#[non_exhaustive]
33pub struct SimpleNonceManager;
34
35#[cfg_attr(target_family = "wasm", async_trait(?Send))]
36#[cfg_attr(not(target_family = "wasm"), async_trait)]
37impl NonceManager for SimpleNonceManager {
38    async fn get_next_nonce<P, N>(&self, provider: &P, address: Address) -> TransportResult<u64>
39    where
40        P: Provider<N>,
41        N: Network,
42    {
43        provider.get_transaction_count(address).pending().await
44    }
45}
46
47/// Cached nonce manager
48///
49/// This [`NonceManager`] implementation will fetch the transaction count for any new account it
50/// sees, store it locally and increment the locally stored nonce as transactions are sent via
51/// [`Provider::send_transaction`].
52///
53/// There is also an alternative implementation [`SimpleNonceManager`] that does not store the
54/// transaction count locally.
55#[derive(Clone, Debug, Default)]
56pub struct CachedNonceManager {
57    nonces: Arc<DashMap<Address, Arc<Mutex<u64>>>>,
58}
59
60#[cfg_attr(target_family = "wasm", async_trait(?Send))]
61#[cfg_attr(not(target_family = "wasm"), async_trait)]
62impl NonceManager for CachedNonceManager {
63    async fn get_next_nonce<P, N>(&self, provider: &P, address: Address) -> TransportResult<u64>
64    where
65        P: Provider<N>,
66        N: Network,
67    {
68        // Use `u64::MAX` as a sentinel value to indicate that the nonce has not been fetched yet.
69        const NONE: u64 = u64::MAX;
70
71        // Locks dashmap internally for a short duration to clone the `Arc`.
72        // We also don't want to hold the dashmap lock through the await point below.
73        let nonce = {
74            let rm = self.nonces.entry(address).or_insert_with(|| Arc::new(Mutex::new(NONE)));
75            Arc::clone(rm.value())
76        };
77
78        let mut nonce = nonce.lock().await;
79        let new_nonce = if *nonce == NONE {
80            // Initialize the nonce if we haven't seen this account before.
81            trace!(%address, "fetching nonce");
82            provider.get_transaction_count(address).await?
83        } else {
84            trace!(%address, current_nonce = *nonce, "incrementing nonce");
85            *nonce + 1
86        };
87        *nonce = new_nonce;
88        Ok(new_nonce)
89    }
90}
91
92/// A [`TxFiller`] that fills nonces on transactions. The behavior of filling nonces is determined
93/// by the [`NonceManager`].
94///
95/// # Note
96///
97/// - If the transaction request does not have a sender set, this layer will not fill nonces.
98/// - Using two providers with their own nonce layer can potentially fill invalid nonces if
99///   transactions are sent from the same address, as the next nonce to be used is cached internally
100///   in the layer.
101///
102/// # Example
103///
104/// ```
105/// # use alloy_network::{Ethereum};
106/// # use alloy_rpc_types_eth::TransactionRequest;
107/// # use alloy_provider::{ProviderBuilder, RootProvider, Provider};
108/// # use alloy_signer_local::PrivateKeySigner;
109/// # async fn test(url: url::Url) -> Result<(), Box<dyn std::error::Error>> {
110/// let pk: PrivateKeySigner = "0x...".parse()?;
111/// let provider = ProviderBuilder::<_, _, Ethereum>::default()
112///     .with_simple_nonce_management()
113///     .wallet(pk)
114///     .connect_http(url);
115///
116/// provider.send_transaction(TransactionRequest::default()).await;
117/// # Ok(())
118/// # }
119/// ```
120#[derive(Clone, Debug, Default)]
121pub struct NonceFiller<M: NonceManager = CachedNonceManager> {
122    nonce_manager: M,
123}
124
125impl<M: NonceManager> NonceFiller<M> {
126    /// Creates a new [`NonceFiller`] with the specified [`NonceManager`].
127    ///
128    /// To instantiate with the [`SimpleNonceManager`], use [`NonceFiller::simple()`].
129    ///
130    /// To instantiate with the [`CachedNonceManager`], use [`NonceFiller::cached()`].
131    pub const fn new(nonce_manager: M) -> Self {
132        Self { nonce_manager }
133    }
134
135    /// Creates a new [`NonceFiller`] with the [`SimpleNonceManager`].
136    ///
137    /// [`SimpleNonceManager`] will fetch the transaction count for any new account it sees,
138    /// resulting in frequent RPC calls.
139    pub const fn simple() -> NonceFiller<SimpleNonceManager> {
140        NonceFiller { nonce_manager: SimpleNonceManager }
141    }
142
143    /// Creates a new [`NonceFiller`] with the [`CachedNonceManager`].
144    ///
145    /// [`CachedNonceManager`] will fetch the transaction count for any new account it sees,
146    /// store it locally and increment the locally stored nonce as transactions are sent via
147    /// [`Provider::send_transaction`], reducing the number of RPC calls.
148    pub fn cached() -> NonceFiller<CachedNonceManager> {
149        NonceFiller { nonce_manager: CachedNonceManager::default() }
150    }
151}
152
153impl<M: NonceManager, N: Network> TxFiller<N> for NonceFiller<M> {
154    type Fillable = u64;
155
156    fn status(&self, tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
157        if tx.nonce().is_some() {
158            return FillerControlFlow::Finished;
159        }
160        if tx.from().is_none() {
161            return FillerControlFlow::missing("NonceManager", vec!["from"]);
162        }
163        FillerControlFlow::Ready
164    }
165
166    fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
167
168    async fn prepare<P>(
169        &self,
170        provider: &P,
171        tx: &N::TransactionRequest,
172    ) -> TransportResult<Self::Fillable>
173    where
174        P: Provider<N>,
175    {
176        let from = tx.from().expect("checked by 'ready()'");
177        self.nonce_manager.get_next_nonce(provider, from).await
178    }
179
180    async fn fill(
181        &self,
182        nonce: Self::Fillable,
183        mut tx: SendableTx<N>,
184    ) -> TransportResult<SendableTx<N>> {
185        if let Some(builder) = tx.as_mut_builder() {
186            builder.set_nonce(nonce);
187        }
188        Ok(tx)
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::{ProviderBuilder, WalletProvider};
196    use alloy_consensus::Transaction;
197    use alloy_primitives::{address, U256};
198    use alloy_rpc_types_eth::TransactionRequest;
199
200    async fn check_nonces<P, N, M>(
201        filler: &NonceFiller<M>,
202        provider: &P,
203        address: Address,
204        start: u64,
205    ) where
206        P: Provider<N>,
207        N: Network,
208        M: NonceManager,
209    {
210        for i in start..start + 5 {
211            let nonce = filler.nonce_manager.get_next_nonce(&provider, address).await.unwrap();
212            assert_eq!(nonce, i);
213        }
214    }
215
216    #[tokio::test]
217    async fn smoke_test() {
218        let filler = NonceFiller::<CachedNonceManager>::default();
219        let provider = ProviderBuilder::new().connect_anvil();
220        let address = Address::ZERO;
221        check_nonces(&filler, &provider, address, 0).await;
222
223        #[cfg(feature = "anvil-api")]
224        {
225            use crate::ext::AnvilApi;
226            filler.nonce_manager.nonces.clear();
227            provider.anvil_set_nonce(address, 69).await.unwrap();
228            check_nonces(&filler, &provider, address, 69).await;
229        }
230    }
231
232    #[tokio::test]
233    async fn concurrency() {
234        let filler = Arc::new(NonceFiller::<CachedNonceManager>::default());
235        let provider = Arc::new(ProviderBuilder::new().connect_anvil());
236        let address = Address::ZERO;
237        let tasks = (0..5)
238            .map(|_| {
239                let filler = Arc::clone(&filler);
240                let provider = Arc::clone(&provider);
241                tokio::spawn(async move {
242                    filler.nonce_manager.get_next_nonce(&provider, address).await
243                })
244            })
245            .collect::<Vec<_>>();
246
247        let mut ns = Vec::new();
248        for task in tasks {
249            ns.push(task.await.unwrap().unwrap());
250        }
251        ns.sort_unstable();
252        assert_eq!(ns, (0..5).collect::<Vec<_>>());
253
254        assert_eq!(filler.nonce_manager.nonces.len(), 1);
255        assert_eq!(*filler.nonce_manager.nonces.get(&address).unwrap().value().lock().await, 4);
256    }
257
258    #[tokio::test]
259    async fn no_nonce_if_sender_unset() {
260        let provider = ProviderBuilder::new()
261            .disable_recommended_fillers()
262            .with_cached_nonce_management()
263            .connect_anvil();
264
265        let tx = TransactionRequest {
266            value: Some(U256::from(100)),
267            to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
268            gas_price: Some(20e9 as u128),
269            gas: Some(21000),
270            ..Default::default()
271        };
272
273        // errors because signer layer expects nonce to be set, which it is not
274        assert!(provider.send_transaction(tx).await.is_err());
275    }
276
277    #[tokio::test]
278    async fn increments_nonce() {
279        let provider = ProviderBuilder::new()
280            .disable_recommended_fillers()
281            .with_cached_nonce_management()
282            .connect_anvil_with_wallet();
283
284        let from = provider.default_signer_address();
285        let tx = TransactionRequest {
286            from: Some(from),
287            value: Some(U256::from(100)),
288            to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
289            gas_price: Some(20e9 as u128),
290            gas: Some(21000),
291            ..Default::default()
292        };
293
294        let pending = provider.send_transaction(tx.clone()).await.unwrap();
295        let tx_hash = pending.watch().await.unwrap();
296        let mined_tx = provider
297            .get_transaction_by_hash(tx_hash)
298            .await
299            .expect("failed to fetch tx")
300            .expect("tx not included");
301        assert_eq!(mined_tx.nonce(), 0);
302
303        let pending = provider.send_transaction(tx).await.unwrap();
304        let tx_hash = pending.watch().await.unwrap();
305        let mined_tx = provider
306            .get_transaction_by_hash(tx_hash)
307            .await
308            .expect("fail to fetch tx")
309            .expect("tx didn't finalize");
310        assert_eq!(mined_tx.nonce(), 1);
311    }
312
313    #[tokio::test]
314    async fn cloned_managers() {
315        let cnm1 = CachedNonceManager::default();
316        let cnm2 = cnm1.clone();
317
318        let provider = ProviderBuilder::new().connect_anvil();
319        let address = Address::ZERO;
320
321        assert_eq!(cnm1.get_next_nonce(&provider, address).await.unwrap(), 0);
322        assert_eq!(cnm2.get_next_nonce(&provider, address).await.unwrap(), 1);
323        assert_eq!(cnm1.get_next_nonce(&provider, address).await.unwrap(), 2);
324        assert_eq!(cnm2.get_next_nonce(&provider, address).await.unwrap(), 3);
325    }
326}