Skip to main content

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