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    /// Get a reference to the nonce manager.
153    pub const fn nonce_manager(&self) -> &M {
154        &self.nonce_manager
155    }
156
157    /// Get a mutable reference to the nonce manager.
158    pub const fn nonce_manager_mut(&mut self) -> &mut M {
159        &mut self.nonce_manager
160    }
161}
162
163impl<M: NonceManager, N: Network> TxFiller<N> for NonceFiller<M> {
164    type Fillable = u64;
165
166    fn status(&self, tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
167        if tx.nonce().is_some() {
168            return FillerControlFlow::Finished;
169        }
170        if tx.from().is_none() {
171            return FillerControlFlow::missing("NonceManager", vec!["from"]);
172        }
173        FillerControlFlow::Ready
174    }
175
176    fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
177
178    async fn prepare<P>(
179        &self,
180        provider: &P,
181        tx: &N::TransactionRequest,
182    ) -> TransportResult<Self::Fillable>
183    where
184        P: Provider<N>,
185    {
186        let from = tx.from().expect("checked by 'ready()'");
187        self.nonce_manager.get_next_nonce(provider, from).await
188    }
189
190    async fn fill(
191        &self,
192        nonce: Self::Fillable,
193        mut tx: SendableTx<N>,
194    ) -> TransportResult<SendableTx<N>> {
195        if let Some(builder) = tx.as_mut_builder() {
196            builder.set_nonce(nonce);
197        }
198        Ok(tx)
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::{ProviderBuilder, WalletProvider};
206    use alloy_consensus::Transaction;
207    use alloy_primitives::{address, U256};
208    use alloy_rpc_types_eth::TransactionRequest;
209
210    async fn check_nonces<P, N, M>(
211        filler: &NonceFiller<M>,
212        provider: &P,
213        address: Address,
214        start: u64,
215    ) where
216        P: Provider<N>,
217        N: Network,
218        M: NonceManager,
219    {
220        for i in start..start + 5 {
221            let nonce = filler.nonce_manager.get_next_nonce(&provider, address).await.unwrap();
222            assert_eq!(nonce, i);
223        }
224    }
225
226    #[tokio::test]
227    async fn smoke_test() {
228        let filler = NonceFiller::<CachedNonceManager>::default();
229        let provider = ProviderBuilder::new().connect_anvil();
230        let address = Address::ZERO;
231        check_nonces(&filler, &provider, address, 0).await;
232
233        #[cfg(feature = "anvil-api")]
234        {
235            use crate::ext::AnvilApi;
236            filler.nonce_manager.nonces.clear();
237            provider.anvil_set_nonce(address, 69).await.unwrap();
238            check_nonces(&filler, &provider, address, 69).await;
239        }
240    }
241
242    #[tokio::test]
243    async fn concurrency() {
244        let filler = Arc::new(NonceFiller::<CachedNonceManager>::default());
245        let provider = Arc::new(ProviderBuilder::new().connect_anvil());
246        let address = Address::ZERO;
247        let tasks = (0..5)
248            .map(|_| {
249                let filler = Arc::clone(&filler);
250                let provider = Arc::clone(&provider);
251                tokio::spawn(async move {
252                    filler.nonce_manager.get_next_nonce(&provider, address).await
253                })
254            })
255            .collect::<Vec<_>>();
256
257        let mut ns = Vec::new();
258        for task in tasks {
259            ns.push(task.await.unwrap().unwrap());
260        }
261        ns.sort_unstable();
262        assert_eq!(ns, (0..5).collect::<Vec<_>>());
263
264        assert_eq!(filler.nonce_manager.nonces.len(), 1);
265        assert_eq!(*filler.nonce_manager.nonces.get(&address).unwrap().value().lock().await, 4);
266    }
267
268    #[tokio::test]
269    async fn no_nonce_if_sender_unset() {
270        let provider = ProviderBuilder::new()
271            .disable_recommended_fillers()
272            .with_cached_nonce_management()
273            .connect_anvil();
274
275        let tx = TransactionRequest {
276            value: Some(U256::from(100)),
277            to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
278            gas_price: Some(20e9 as u128),
279            gas: Some(21000),
280            ..Default::default()
281        };
282
283        // errors because signer layer expects nonce to be set, which it is not
284        assert!(provider.send_transaction(tx).await.is_err());
285    }
286
287    #[tokio::test]
288    async fn increments_nonce() {
289        let provider = ProviderBuilder::new()
290            .disable_recommended_fillers()
291            .with_cached_nonce_management()
292            .connect_anvil_with_wallet();
293
294        let from = provider.default_signer_address();
295        let tx = TransactionRequest {
296            from: Some(from),
297            value: Some(U256::from(100)),
298            to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
299            gas_price: Some(20e9 as u128),
300            gas: Some(21000),
301            ..Default::default()
302        };
303
304        let pending = provider.send_transaction(tx.clone()).await.unwrap();
305        let tx_hash = pending.watch().await.unwrap();
306        let mined_tx = provider
307            .get_transaction_by_hash(tx_hash)
308            .await
309            .expect("failed to fetch tx")
310            .expect("tx not included");
311        assert_eq!(mined_tx.nonce(), 0);
312
313        let pending = provider.send_transaction(tx).await.unwrap();
314        let tx_hash = pending.watch().await.unwrap();
315        let mined_tx = provider
316            .get_transaction_by_hash(tx_hash)
317            .await
318            .expect("fail to fetch tx")
319            .expect("tx didn't finalize");
320        assert_eq!(mined_tx.nonce(), 1);
321    }
322
323    #[tokio::test]
324    async fn cloned_managers() {
325        let cnm1 = CachedNonceManager::default();
326        let cnm2 = cnm1.clone();
327
328        let provider = ProviderBuilder::new().connect_anvil();
329        let address = Address::ZERO;
330
331        assert_eq!(cnm1.get_next_nonce(&provider, address).await.unwrap(), 0);
332        assert_eq!(cnm2.get_next_nonce(&provider, address).await.unwrap(), 1);
333        assert_eq!(cnm1.get_next_nonce(&provider, address).await.unwrap(), 2);
334        assert_eq!(cnm2.get_next_nonce(&provider, address).await.unwrap(), 3);
335    }
336}