Skip to content

Commit d927907

Browse files
authored
fix(fortuna): Reset nonce manager if receipt is not available (#1774)
* Reset nonce manager if receipt is not available
1 parent 80a7c05 commit d927907

File tree

6 files changed

+210
-3
lines changed

6 files changed

+210
-3
lines changed

apps/fortuna/Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/fortuna/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fortuna"
3-
version = "6.4.1"
3+
version = "6.4.2"
44
edition = "2021"
55

66
[dependencies]
@@ -37,6 +37,7 @@ url = "2.5.0"
3737
chrono = { version = "0.4.38", features = ["clock", "std"] , default-features = false}
3838
backoff = { version = "0.4.0", features = ["futures", "tokio"] }
3939
thiserror = "1.0.61"
40+
futures-locks = "0.7.1"
4041

4142

4243
[dev-dependencies]

apps/fortuna/src/chain.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub(crate) mod eth_gas_oracle;
22
pub(crate) mod ethereum;
3+
mod nonce_manager;
34
pub(crate) mod reader;
45
pub(crate) mod traced_client;

apps/fortuna/src/chain/ethereum.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use {
33
api::ChainId,
44
chain::{
55
eth_gas_oracle::EthProviderOracle,
6+
nonce_manager::NonceManagerMiddleware,
67
reader::{
78
self,
89
BlockNumber,
@@ -33,7 +34,6 @@ use {
3334
middleware::{
3435
gas_oracle::GasOracleMiddleware,
3536
MiddlewareError,
36-
NonceManagerMiddleware,
3737
SignerMiddleware,
3838
},
3939
prelude::{
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// This is a copy of the NonceManagerMiddleware from ethers-rs, with an additional reset method.
2+
// Copied from: https://github.com/gakonst/ethers-rs/blob/34ed9e372e66235aed7074bc3f5c14922b139242/ethers-middleware/src/nonce_manager.rs
3+
4+
use {
5+
axum::async_trait,
6+
ethers::{
7+
providers::{
8+
Middleware,
9+
MiddlewareError,
10+
PendingTransaction,
11+
},
12+
types::{
13+
transaction::eip2718::TypedTransaction,
14+
*,
15+
},
16+
},
17+
std::sync::atomic::{
18+
AtomicBool,
19+
AtomicU64,
20+
Ordering,
21+
},
22+
thiserror::Error,
23+
};
24+
25+
#[derive(Debug)]
26+
/// Middleware used for calculating nonces locally, useful for signing multiple
27+
/// consecutive transactions without waiting for them to hit the mempool
28+
pub struct NonceManagerMiddleware<M> {
29+
inner: M,
30+
init_guard: futures_locks::Mutex<()>,
31+
initialized: AtomicBool,
32+
nonce: AtomicU64,
33+
address: Address,
34+
}
35+
36+
impl<M> NonceManagerMiddleware<M>
37+
where
38+
M: Middleware,
39+
{
40+
/// Instantiates the nonce manager with a 0 nonce. The `address` should be the
41+
/// address which you'll be sending transactions from
42+
pub fn new(inner: M, address: Address) -> Self {
43+
Self {
44+
inner,
45+
init_guard: Default::default(),
46+
initialized: Default::default(),
47+
nonce: Default::default(),
48+
address,
49+
}
50+
}
51+
52+
/// Returns the next nonce to be used
53+
pub fn next(&self) -> U256 {
54+
let nonce = self.nonce.fetch_add(1, Ordering::SeqCst);
55+
nonce.into()
56+
}
57+
58+
pub async fn initialize_nonce(
59+
&self,
60+
block: Option<BlockId>,
61+
) -> Result<U256, NonceManagerError<M>> {
62+
if self.initialized.load(Ordering::SeqCst) {
63+
// return current nonce
64+
return Ok(self.nonce.load(Ordering::SeqCst).into());
65+
}
66+
67+
let _guard = self.init_guard.lock().await;
68+
69+
// do this again in case multiple tasks enter this codepath
70+
if self.initialized.load(Ordering::SeqCst) {
71+
// return current nonce
72+
return Ok(self.nonce.load(Ordering::SeqCst).into());
73+
}
74+
75+
// initialize the nonce the first time the manager is called
76+
let nonce = self
77+
.inner
78+
.get_transaction_count(self.address, block)
79+
.await
80+
.map_err(MiddlewareError::from_err)?;
81+
self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
82+
self.initialized.store(true, Ordering::SeqCst);
83+
Ok(nonce)
84+
} // guard dropped here
85+
86+
/// Resets the initialized flag so the next usage of the manager will reinitialize the nonce
87+
/// based on the chain state.
88+
/// This is useful when the RPC does not return an error if the transaction is submitted with
89+
/// an incorrect nonce.
90+
/// This is the only new method compared to the original NonceManagerMiddleware.
91+
pub fn reset(&self) {
92+
self.initialized.store(false, Ordering::SeqCst);
93+
}
94+
95+
async fn get_transaction_count_with_manager(
96+
&self,
97+
block: Option<BlockId>,
98+
) -> Result<U256, NonceManagerError<M>> {
99+
// initialize the nonce the first time the manager is called
100+
if !self.initialized.load(Ordering::SeqCst) {
101+
let nonce = self
102+
.inner
103+
.get_transaction_count(self.address, block)
104+
.await
105+
.map_err(MiddlewareError::from_err)?;
106+
self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
107+
self.initialized.store(true, Ordering::SeqCst);
108+
}
109+
110+
Ok(self.next())
111+
}
112+
}
113+
114+
#[derive(Error, Debug)]
115+
/// Thrown when an error happens at the Nonce Manager
116+
pub enum NonceManagerError<M: Middleware> {
117+
/// Thrown when the internal middleware errors
118+
#[error("{0}")]
119+
MiddlewareError(M::Error),
120+
}
121+
122+
impl<M: Middleware> MiddlewareError for NonceManagerError<M> {
123+
type Inner = M::Error;
124+
125+
fn from_err(src: M::Error) -> Self {
126+
NonceManagerError::MiddlewareError(src)
127+
}
128+
129+
fn as_inner(&self) -> Option<&Self::Inner> {
130+
match self {
131+
NonceManagerError::MiddlewareError(e) => Some(e),
132+
}
133+
}
134+
}
135+
136+
#[async_trait]
137+
impl<M> Middleware for NonceManagerMiddleware<M>
138+
where
139+
M: Middleware,
140+
{
141+
type Error = NonceManagerError<M>;
142+
type Provider = M::Provider;
143+
type Inner = M;
144+
145+
fn inner(&self) -> &M {
146+
&self.inner
147+
}
148+
149+
async fn fill_transaction(
150+
&self,
151+
tx: &mut TypedTransaction,
152+
block: Option<BlockId>,
153+
) -> Result<(), Self::Error> {
154+
if tx.nonce().is_none() {
155+
tx.set_nonce(self.get_transaction_count_with_manager(block).await?);
156+
}
157+
158+
Ok(self
159+
.inner()
160+
.fill_transaction(tx, block)
161+
.await
162+
.map_err(MiddlewareError::from_err)?)
163+
}
164+
165+
/// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that
166+
/// gas cost and nonce calculations take it into account. For simple transactions this can be
167+
/// left to `None`.
168+
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
169+
&self,
170+
tx: T,
171+
block: Option<BlockId>,
172+
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
173+
let mut tx = tx.into();
174+
175+
if tx.nonce().is_none() {
176+
tx.set_nonce(self.get_transaction_count_with_manager(block).await?);
177+
}
178+
179+
match self.inner.send_transaction(tx.clone(), block).await {
180+
Ok(tx_hash) => Ok(tx_hash),
181+
Err(err) => {
182+
let nonce = self.get_transaction_count(self.address, block).await?;
183+
if nonce != self.nonce.load(Ordering::SeqCst).into() {
184+
// try re-submitting the transaction with the correct nonce if there
185+
// was a nonce mismatch
186+
self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
187+
tx.set_nonce(nonce);
188+
self.inner
189+
.send_transaction(tx, block)
190+
.await
191+
.map_err(MiddlewareError::from_err)
192+
} else {
193+
// propagate the error otherwise
194+
Err(MiddlewareError::from_err(err))
195+
}
196+
}
197+
}
198+
}
199+
}

apps/fortuna/src/keeper.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,10 @@ pub async fn process_event(
503503
))
504504
})?
505505
.ok_or_else(|| {
506+
// RPC may not return an error on tx submission if the nonce is too high.
507+
// But we will never get a receipt. So we reset the nonce manager to get the correct nonce.
508+
let nonce_manager = contract.client_ref().inner().inner();
509+
nonce_manager.reset();
506510
backoff::Error::transient(anyhow!(
507511
"Can't verify the reveal, probably dropped from mempool Tx:{:?}",
508512
transaction

0 commit comments

Comments
 (0)