Skip to main content

arbiter_core/middleware/
nonce_middleware.rs

1//! The `nonce_middleware` module provides a middleware implementation for
2//! managing nonces for Ethereum-like virtual machines. A nonce is a number that
3//! is used only once in a cryptographic communication. In this case, it is used
4//! to ensure that each transaction sent from the address associated with the
5//! middleware is unique and cannot be replayed.
6//!
7//! Main components:
8//! - [`NonceManagerMiddleware`]: The core middleware implementation.
9//! - [`NonceManagerError`]: Error type for the middleware.
10use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
11
12use ethers::providers::MiddlewareError;
13use thiserror::Error;
14
15use super::*;
16
17#[derive(Debug)]
18/// Middleware used for calculating nonces locally, useful for signing multiple
19/// consecutive transactions without waiting for them to hit the mempool
20pub struct NonceManagerMiddleware<M> {
21    inner: M,
22    init_guard: futures_locks::Mutex<()>,
23    initialized: AtomicBool,
24    nonce: AtomicU64,
25    address: eAddress,
26}
27
28impl<M> NonceManagerMiddleware<M>
29where
30    M: Middleware,
31{
32    /// Instantiates the nonce manager with a 0 nonce. The `address` should be
33    /// the address which you'll be sending transactions from
34    pub fn new(inner: M, address: eAddress) -> Self {
35        Self {
36            inner,
37            init_guard: Default::default(),
38            initialized: Default::default(),
39            nonce: Default::default(),
40            address,
41        }
42    }
43
44    /// Returns the next nonce to be used
45    pub fn next(&self) -> eU256 {
46        let nonce = self.nonce.fetch_add(1, Ordering::SeqCst);
47        nonce.into()
48    }
49    /// Initializes the nonce for the address associated with this middleware.
50    ///
51    /// This function initializes the nonce for the address associated with this
52    /// middleware. If the nonce has already been initialized, this function
53    /// returns the current nonce. Otherwise, it initializes the nonce by
54    /// querying the blockchain for the current transaction count for the
55    /// address. The nonce is used to ensure that each transaction sent from the
56    /// address is unique and cannot be replayed.
57    ///
58    /// # Arguments
59    ///
60    /// * `block` - An optional block ID to use when querying the blockchain for
61    ///   the current transaction count. If `None`, the latest block will be
62    ///   used.
63    ///
64    /// # Errors
65    ///
66    /// This function returns an error if there is an error querying the
67    /// blockchain for the current transaction count.
68
69    pub async fn initialize_nonce(
70        &self,
71        block: Option<BlockId>,
72    ) -> Result<eU256, NonceManagerError<M>> {
73        if self.initialized.load(Ordering::SeqCst) {
74            // return current nonce
75            return Ok(self.nonce.load(Ordering::SeqCst).into());
76        }
77
78        let _guard = self.init_guard.lock().await;
79
80        // do this again in case multiple tasks enter this codepath
81        if self.initialized.load(Ordering::SeqCst) {
82            // return current nonce
83            return Ok(self.nonce.load(Ordering::SeqCst).into());
84        }
85
86        // Note: Need to implement get_transaction_count for the middleware
87        // initialize the nonce the first time the manager is called
88        let nonce = self
89            .inner
90            .get_transaction_count(self.address, block)
91            .await
92            .map_err(MiddlewareError::from_err)?;
93        self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
94        self.initialized.store(true, Ordering::SeqCst);
95        trace!("Nonce initialized for address: {:?}", self.address);
96        Ok(nonce)
97    } // guard dropped here
98
99    async fn get_transaction_count_with_manager(
100        &self,
101        block: Option<BlockId>,
102    ) -> Result<eU256, NonceManagerError<M>> {
103        // initialize the nonce the first time the manager is called
104        if !self.initialized.load(Ordering::SeqCst) {
105            let nonce = self
106                .inner
107                .get_transaction_count(self.address, block)
108                .await
109                .map_err(MiddlewareError::from_err)?;
110            self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
111            self.initialized.store(true, Ordering::SeqCst);
112        }
113
114        Ok(self.next())
115    }
116}
117
118#[derive(Error, Debug)]
119/// Thrown when an error happens at the Nonce Manager
120pub enum NonceManagerError<M: Middleware> {
121    /// Thrown when the internal middleware errors
122    #[error(transparent)]
123    MiddlewareError(M::Error),
124}
125
126impl<M: Middleware> MiddlewareError for NonceManagerError<M> {
127    type Inner = M::Error;
128
129    fn from_err(src: M::Error) -> Self {
130        NonceManagerError::MiddlewareError(src)
131    }
132
133    fn as_inner(&self) -> Option<&Self::Inner> {
134        match self {
135            NonceManagerError::MiddlewareError(e) => Some(e),
136        }
137    }
138}
139
140#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
141#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
142impl<M> Middleware for NonceManagerMiddleware<M>
143where
144    M: Middleware,
145{
146    type Error = NonceManagerError<M>;
147    type Provider = M::Provider;
148    type Inner = M;
149
150    fn inner(&self) -> &M {
151        &self.inner
152    }
153
154    async fn fill_transaction(
155        &self,
156        tx: &mut TypedTransaction,
157        block: Option<BlockId>,
158    ) -> Result<(), Self::Error> {
159        if tx.nonce().is_none() {
160            tx.set_nonce(self.get_transaction_count_with_manager(block).await?);
161        }
162
163        Ok(self
164            .inner()
165            .fill_transaction(tx, block)
166            .await
167            .map_err(MiddlewareError::from_err)?)
168    }
169
170    /// Signs and broadcasts the transaction. The optional parameter `block` can
171    /// be passed so that gas cost and nonce calculations take it into
172    /// account. For simple transactions this can be left to `None`.
173    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
174        &self,
175        tx: T,
176        block: Option<BlockId>,
177    ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
178        let mut tx = tx.into();
179
180        if tx.nonce().is_none() {
181            tx.set_nonce(self.get_transaction_count_with_manager(block).await?);
182        }
183
184        match self.inner.send_transaction(tx.clone(), block).await {
185            Ok(tx_hash) => Ok(tx_hash),
186            Err(err) => {
187                let nonce = self.get_transaction_count(self.address, block).await?;
188                if nonce != self.nonce.load(Ordering::SeqCst).into() {
189                    // try re-submitting the transaction with the correct nonce if there
190                    // was a nonce mismatch
191                    self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
192                    tx.set_nonce(nonce);
193                    trace!("Nonce incremented for address: {:?}", self.address);
194                    self.inner
195                        .send_transaction(tx, block)
196                        .await
197                        .map_err(MiddlewareError::from_err)
198                } else {
199                    // propagate the error otherwise
200                    Err(MiddlewareError::from_err(err))
201                }
202            }
203        }
204    }
205}