From 6bc82fb50820d0b0db47ae33c176b1cf2042db42 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 20 Aug 2020 23:20:40 +0300 Subject: [PATCH 01/11] feat: first stab at a NonceManager --- ethers-signers/Cargo.toml | 1 + ethers-signers/src/client.rs | 61 +++++++++++++++++++++++++++++++--- ethers-signers/src/nonces.rs | 0 ethers-signers/src/wallet.rs | 2 ++ ethers-signers/tests/signer.rs | 32 ++++++++++++++++++ 5 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 ethers-signers/src/nonces.rs diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml index 6d67aae66..6218d24cd 100644 --- a/ethers-signers/Cargo.toml +++ b/ethers-signers/Cargo.toml @@ -19,6 +19,7 @@ ethers-providers = { version = "0.1.3", path = "../ethers-providers" } thiserror = { version = "1.0.15", default-features = false } futures-util = { version = "0.3.5", default-features = false } serde = "1.0.112" +tokio = "0.2.21" [dev-dependencies] ethers = { version = "0.1.3", path = "../ethers" } diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 15818166b..29b0f51cf 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -1,7 +1,7 @@ use crate::Signer; use ethers_core::types::{ - Address, BlockNumber, Bytes, NameOrAddress, Signature, TransactionRequest, TxHash, + Address, BlockNumber, Bytes, NameOrAddress, Signature, TransactionRequest, TxHash, U256, }; use ethers_providers::{ gas_oracle::{GasOracle, GasOracleError}, @@ -74,8 +74,35 @@ pub struct Client { pub(crate) signer: Option, pub(crate) address: Address, pub(crate) gas_oracle: Option>, + pub(crate) nonce_manager: RwLock, } +use tokio::sync::RwLock; + +#[derive(Clone, Debug)] +pub(crate) struct NonceManager { + initialized: bool, + nonce: U256, +} + +impl NonceManager { + pub fn new() -> Self { + NonceManager { + initialized: false, + nonce: 0.into(), + } + } + + /// Returns the next nonce to be used + fn next(&mut self) -> U256 { + let nonce = self.nonce; + self.nonce += ONE; + nonce + } +} + +const ONE: U256 = U256([1, 0, 0, 0]); + #[derive(Debug, Error)] /// Error thrown when the client interacts with the blockchain pub enum ClientError { @@ -110,6 +137,7 @@ where signer: Some(signer), address, gas_oracle: None, + nonce_manager: RwLock::new(NonceManager::new()), } } @@ -171,10 +199,7 @@ where let (gas_price, gas, nonce) = join!( maybe(tx.gas_price, self.provider.get_gas_price()), maybe(tx.gas, self.provider.estimate_gas(&tx)), - maybe( - tx.nonce, - self.provider.get_transaction_count(self.address(), block) - ), + maybe(tx.nonce, self.get_transaction_count(block)), ); tx.gas_price = Some(gas_price?); tx.gas = Some(gas?); @@ -183,6 +208,31 @@ where Ok(()) } + pub async fn get_transaction_count( + &self, + block: Option, + ) -> Result { + Ok( + if block.unwrap_or(BlockNumber::Pending) == BlockNumber::Pending { + let mut nonce_manager = self.nonce_manager.write().await; + // the first time we make this call we'll have to set the nonce to the correct value + if !nonce_manager.initialized { + nonce_manager.nonce = self + .provider + .get_transaction_count(self.address(), block) + .await?; + nonce_manager.initialized = true; + } + + nonce_manager.next() + } else { + self.provider + .get_transaction_count(self.address(), block) + .await? + }, + ) + } + /// Returns the client's address pub fn address(&self) -> Address { self.address @@ -282,6 +332,7 @@ impl From> for Client { signer: None, address: Address::zero(), gas_oracle: None, + nonce_manager: RwLock::new(NonceManager::new()), } } } diff --git a/ethers-signers/src/nonces.rs b/ethers-signers/src/nonces.rs new file mode 100644 index 000000000..e69de29bb diff --git a/ethers-signers/src/wallet.rs b/ethers-signers/src/wallet.rs index 4377e3c80..938d3a59b 100644 --- a/ethers-signers/src/wallet.rs +++ b/ethers-signers/src/wallet.rs @@ -1,3 +1,4 @@ +use super::client::NonceManager; use crate::{Client, ClientError, Signer}; use ethers_providers::{JsonRpcClient, Provider}; @@ -122,6 +123,7 @@ impl Wallet { signer: Some(self), provider, gas_oracle: None, + nonce_manager: tokio::sync::RwLock::new(NonceManager::new()), } } diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs index 9b67a9f1c..c85257360 100644 --- a/ethers-signers/tests/signer.rs +++ b/ethers-signers/tests/signer.rs @@ -75,6 +75,38 @@ mod eth_tests { assert!(balance_before > balance_after); } + #[tokio::test] + async fn nonce_manager() { + let ganache = Ganache::new().block_time(5u64).spawn(); + + // this private key belongs to the above mnemonic + let wallet: Wallet = ganache.keys()[0].clone().into(); + let wallet2: Wallet = ganache.keys()[1].clone().into(); + + // connect to the network + let provider = Provider::::try_from(ganache.endpoint()) + .unwrap() + .interval(Duration::from_millis(10u64)); + + // connect the wallet to the provider + let client = wallet.connect(provider); + + let mut futs = Vec::new(); + for _ in 0..10 { + futs.push( + client.send_transaction(TransactionRequest::pay(wallet2.address(), 100u64), None), + ); + } + let result = futures_util::future::join_all(futs).await; + let mut nonces = Vec::new(); + for res in result { + let tx_hash = res.unwrap(); + nonces.push(client.get_transaction(tx_hash).await.unwrap().nonce.as_u64()); + } + + assert_eq!(nonces, (0..10).collect::>()) + } + #[tokio::test] async fn using_gas_oracle() { let ganache = Ganache::new().spawn(); From a2d79085edcbfed04f2ddb2bf0a4b124f9888bdf Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Fri, 21 Aug 2020 00:45:38 +0300 Subject: [PATCH 02/11] test: adjust the test --- ethers-signers/tests/signer.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs index c85257360..10ef9aa23 100644 --- a/ethers-signers/tests/signer.rs +++ b/ethers-signers/tests/signer.rs @@ -91,16 +91,14 @@ mod eth_tests { // connect the wallet to the provider let client = wallet.connect(provider); - let mut futs = Vec::new(); + let mut tx_hashes = Vec::new(); for _ in 0..10 { - futs.push( - client.send_transaction(TransactionRequest::pay(wallet2.address(), 100u64), None), - ); + let tx = client.send_transaction(TransactionRequest::pay(wallet2.address(), 100u64), None).await.unwrap(); + tx_hashes.push(tx); } - let result = futures_util::future::join_all(futs).await; + let mut nonces = Vec::new(); - for res in result { - let tx_hash = res.unwrap(); + for tx_hash in tx_hashes { nonces.push(client.get_transaction(tx_hash).await.unwrap().nonce.as_u64()); } From b3d0e0ea6223abe2621a83d1439b0821b69f4bcb Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Fri, 21 Aug 2020 15:34:51 +0300 Subject: [PATCH 03/11] fix: reset nonce if nonce manager errors --- ethers-signers/src/client.rs | 25 +++++++++++++++++++++++++ ethers-signers/tests/signer.rs | 14 ++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 29b0f51cf..53932caf0 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -170,6 +170,31 @@ where self.fill_transaction(&mut tx, block).await?; // sign the transaction and broadcast it + let mut tx_clone = tx.clone(); + let result = self.submit_transaction(tx).await; + + // if we got a nonce error, get the account's latest nonce and re-submit + let tx_hash = if result.is_err() { + let mut nonce_manager = self.nonce_manager.write().await; + let nonce = self + .provider + .get_transaction_count(self.address(), block) + .await?; + if nonce != nonce_manager.nonce { + nonce_manager.nonce = nonce; + tx_clone.nonce = Some(nonce); + self.submit_transaction(tx_clone).await? + } else { + result? + } + } else { + result? + }; + + Ok(tx_hash) + } + + async fn submit_transaction(&self, tx: TransactionRequest) -> Result { Ok(if let Some(ref signer) = self.signer { let signed_tx = signer.sign_transaction(tx).map_err(Into::into)?; self.provider.send_raw_transaction(&signed_tx).await? diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs index 10ef9aa23..cf83bbbb0 100644 --- a/ethers-signers/tests/signer.rs +++ b/ethers-signers/tests/signer.rs @@ -93,13 +93,23 @@ mod eth_tests { let mut tx_hashes = Vec::new(); for _ in 0..10 { - let tx = client.send_transaction(TransactionRequest::pay(wallet2.address(), 100u64), None).await.unwrap(); + let tx = client + .send_transaction(TransactionRequest::pay(wallet2.address(), 100u64), None) + .await + .unwrap(); tx_hashes.push(tx); } let mut nonces = Vec::new(); for tx_hash in tx_hashes { - nonces.push(client.get_transaction(tx_hash).await.unwrap().nonce.as_u64()); + nonces.push( + client + .get_transaction(tx_hash) + .await + .unwrap() + .nonce + .as_u64(), + ); } assert_eq!(nonces, (0..10).collect::>()) From 32df7dc106369336239ca4923f0a08ca62274088 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 16:04:57 +0300 Subject: [PATCH 04/11] feat: make nonce manager opt in --- ethers-signers/src/client.rs | 84 +++++++++++++++++++--------------- ethers-signers/src/wallet.rs | 3 +- ethers-signers/tests/signer.rs | 2 +- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 53932caf0..7bf4e2b60 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -74,7 +74,7 @@ pub struct Client { pub(crate) signer: Option, pub(crate) address: Address, pub(crate) gas_oracle: Option>, - pub(crate) nonce_manager: RwLock, + pub(crate) nonce_manager: Option>, } use tokio::sync::RwLock; @@ -137,7 +137,7 @@ where signer: Some(signer), address, gas_oracle: None, - nonce_manager: RwLock::new(NonceManager::new()), + nonce_manager: None, } } @@ -169,26 +169,30 @@ where // fill any missing fields self.fill_transaction(&mut tx, block).await?; - // sign the transaction and broadcast it - let mut tx_clone = tx.clone(); - let result = self.submit_transaction(tx).await; - - // if we got a nonce error, get the account's latest nonce and re-submit - let tx_hash = if result.is_err() { - let mut nonce_manager = self.nonce_manager.write().await; - let nonce = self - .provider - .get_transaction_count(self.address(), block) - .await?; - if nonce != nonce_manager.nonce { - nonce_manager.nonce = nonce; - tx_clone.nonce = Some(nonce); - self.submit_transaction(tx_clone).await? + // if we have a nonce manager set, we should try handling the result in + // case there was a nonce mismatch + let tx_hash = if let Some(ref nonce_manager) = self.nonce_manager { + let mut tx_clone = tx.clone(); + let result = self.submit_transaction(tx).await; + // if we got a nonce error, get the account's latest nonce and re-submit + if result.is_err() { + let mut nonce_manager = nonce_manager.write().await; + let nonce = self + .provider + .get_transaction_count(self.address(), block) + .await?; + if nonce != nonce_manager.nonce { + nonce_manager.nonce = nonce; + tx_clone.nonce = Some(nonce); + self.submit_transaction(tx_clone).await? + } else { + result? + } } else { result? } } else { - result? + self.submit_transaction(tx).await? }; Ok(tx_hash) @@ -237,25 +241,26 @@ where &self, block: Option, ) -> Result { - Ok( - if block.unwrap_or(BlockNumber::Pending) == BlockNumber::Pending { - let mut nonce_manager = self.nonce_manager.write().await; - // the first time we make this call we'll have to set the nonce to the correct value - if !nonce_manager.initialized { - nonce_manager.nonce = self - .provider - .get_transaction_count(self.address(), block) - .await?; - nonce_manager.initialized = true; - } - - nonce_manager.next() - } else { - self.provider + // If there's a nonce manager set, short circuit by just returning the next nonce + if let Some(ref nonce_manager) = self.nonce_manager { + let mut nonce_manager = nonce_manager.write().await; + + // initialize the nonce the first time the manager is called + if !nonce_manager.initialized { + nonce_manager.nonce = self + .provider .get_transaction_count(self.address(), block) - .await? - }, - ) + .await?; + nonce_manager.initialized = true; + } + + return Ok(nonce_manager.next()); + } + + Ok(self + .provider + .get_transaction_count(self.address(), block) + .await?) } /// Returns the client's address @@ -325,6 +330,11 @@ where self.gas_oracle = Some(gas_oracle); self } + + pub fn with_nonce_manager(mut self) -> Self { + self.nonce_manager = Some(RwLock::new(NonceManager::new())); + self + } } /// Calls the future if `item` is None, otherwise returns a `futures::ok` @@ -357,7 +367,7 @@ impl From> for Client { signer: None, address: Address::zero(), gas_oracle: None, - nonce_manager: RwLock::new(NonceManager::new()), + nonce_manager: None, } } } diff --git a/ethers-signers/src/wallet.rs b/ethers-signers/src/wallet.rs index 938d3a59b..1b239312d 100644 --- a/ethers-signers/src/wallet.rs +++ b/ethers-signers/src/wallet.rs @@ -1,4 +1,3 @@ -use super::client::NonceManager; use crate::{Client, ClientError, Signer}; use ethers_providers::{JsonRpcClient, Provider}; @@ -123,7 +122,7 @@ impl Wallet { signer: Some(self), provider, gas_oracle: None, - nonce_manager: tokio::sync::RwLock::new(NonceManager::new()), + nonce_manager: None, } } diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs index cf83bbbb0..445dcf216 100644 --- a/ethers-signers/tests/signer.rs +++ b/ethers-signers/tests/signer.rs @@ -89,7 +89,7 @@ mod eth_tests { .interval(Duration::from_millis(10u64)); // connect the wallet to the provider - let client = wallet.connect(provider); + let client = wallet.connect(provider).with_nonce_manager(); let mut tx_hashes = Vec::new(); for _ in 0..10 { From 2522df51c18f8286be2945e97182aedc68e01284 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 17:06:33 +0300 Subject: [PATCH 05/11] fix: add read-only nonce call --- ethers-signers/src/client.rs | 16 ++++++++++------ ethers-signers/tests/signer.rs | 31 +++++++++++++++++++------------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 7bf4e2b60..51203db54 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -177,10 +177,7 @@ where // if we got a nonce error, get the account's latest nonce and re-submit if result.is_err() { let mut nonce_manager = nonce_manager.write().await; - let nonce = self - .provider - .get_transaction_count(self.address(), block) - .await?; + let nonce = self.get_transaction_count(block).await?; if nonce != nonce_manager.nonce { nonce_manager.nonce = nonce; tx_clone.nonce = Some(nonce); @@ -228,7 +225,7 @@ where let (gas_price, gas, nonce) = join!( maybe(tx.gas_price, self.provider.get_gas_price()), maybe(tx.gas, self.provider.estimate_gas(&tx)), - maybe(tx.nonce, self.get_transaction_count(block)), + maybe(tx.nonce, self.get_transaction_count_with_manager(block)), ); tx.gas_price = Some(gas_price?); tx.gas = Some(gas?); @@ -237,7 +234,7 @@ where Ok(()) } - pub async fn get_transaction_count( + async fn get_transaction_count_with_manager( &self, block: Option, ) -> Result { @@ -257,6 +254,13 @@ where return Ok(nonce_manager.next()); } + self.get_transaction_count(block).await + } + + pub async fn get_transaction_count( + &self, + block: Option, + ) -> Result { Ok(self .provider .get_transaction_count(self.address(), block) diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs index 445dcf216..b5182fa17 100644 --- a/ethers-signers/tests/signer.rs +++ b/ethers-signers/tests/signer.rs @@ -77,24 +77,31 @@ mod eth_tests { #[tokio::test] async fn nonce_manager() { - let ganache = Ganache::new().block_time(5u64).spawn(); - - // this private key belongs to the above mnemonic - let wallet: Wallet = ganache.keys()[0].clone().into(); - let wallet2: Wallet = ganache.keys()[1].clone().into(); + let provider = Provider::::try_from( + "https://rinkeby.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778", + ) + .unwrap() + .interval(Duration::from_millis(2000u64)); - // connect to the network - let provider = Provider::::try_from(ganache.endpoint()) + let client = "FF7F80C6E9941865266ED1F481263D780169F1D98269C51167D20C630A5FDC8A" + .parse::() .unwrap() - .interval(Duration::from_millis(10u64)); + .connect(provider) + .with_nonce_manager(); - // connect the wallet to the provider - let client = wallet.connect(provider).with_nonce_manager(); + let nonce = client + .get_transaction_count(Some(BlockNumber::Pending)) + .await + .unwrap() + .as_u64(); let mut tx_hashes = Vec::new(); for _ in 0..10 { let tx = client - .send_transaction(TransactionRequest::pay(wallet2.address(), 100u64), None) + .send_transaction( + TransactionRequest::pay(client.address(), 100u64), + Some(BlockNumber::Pending), + ) .await .unwrap(); tx_hashes.push(tx); @@ -112,7 +119,7 @@ mod eth_tests { ); } - assert_eq!(nonces, (0..10).collect::>()) + assert_eq!(nonces, (nonce..nonce + 10).collect::>()) } #[tokio::test] From 3dbdd7b9e3e50db554bfbca7d0e7e369e9cd7fd1 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 17:07:03 +0300 Subject: [PATCH 06/11] feat: improve http provider errors --- ethers-providers/src/transports/http.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ethers-providers/src/transports/http.rs b/ethers-providers/src/transports/http.rs index cea58f0b1..304a0c8f9 100644 --- a/ethers-providers/src/transports/http.rs +++ b/ethers-providers/src/transports/http.rs @@ -43,6 +43,13 @@ pub enum ClientError { #[error(transparent)] /// Thrown if the response could not be parsed JsonRpcError(#[from] JsonRpcError), + + #[error("Deserialization Error: {err}. Response: {text}")] + /// Serde JSON Error + SerdeJson { + err: serde_json::Error, + text: String, + }, } impl From for ProviderError { @@ -73,7 +80,9 @@ impl JsonRpcClient for Provider { .json(&payload) .send() .await?; - let res = res.json::>().await?; + let text = res.text().await?; + let res: Response = + serde_json::from_str(&text).map_err(|err| ClientError::SerdeJson { err, text })?; Ok(res.data.into_result()?) } From dcf2cfa07cb12821754df2d801fcec5d2b3e3897 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 17:19:14 +0300 Subject: [PATCH 07/11] feat: convert to Atomic datatypes --- ethers-signers/src/client.rs | 41 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 51203db54..8a8de27a1 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -9,7 +9,11 @@ use ethers_providers::{ }; use futures_util::{future::ok, join}; -use std::{future::Future, ops::Deref, time::Duration}; +use std::{ + future::Future, ops::Deref, time::Duration, + sync::atomic::{AtomicBool, AtomicU64, Ordering}, +}; + use thiserror::Error; #[derive(Debug)] @@ -74,30 +78,27 @@ pub struct Client { pub(crate) signer: Option, pub(crate) address: Address, pub(crate) gas_oracle: Option>, - pub(crate) nonce_manager: Option>, + pub(crate) nonce_manager: Option, } -use tokio::sync::RwLock; - -#[derive(Clone, Debug)] +#[derive(Debug)] pub(crate) struct NonceManager { - initialized: bool, - nonce: U256, + initialized: AtomicBool, + nonce: AtomicU64, } impl NonceManager { pub fn new() -> Self { NonceManager { - initialized: false, + initialized: false.into(), nonce: 0.into(), } } /// Returns the next nonce to be used - fn next(&mut self) -> U256 { - let nonce = self.nonce; - self.nonce += ONE; - nonce + fn next(&self) -> U256 { + let nonce = self.nonce.fetch_add(1, Ordering::SeqCst); + nonce.into() } } @@ -176,10 +177,9 @@ where let result = self.submit_transaction(tx).await; // if we got a nonce error, get the account's latest nonce and re-submit if result.is_err() { - let mut nonce_manager = nonce_manager.write().await; let nonce = self.get_transaction_count(block).await?; - if nonce != nonce_manager.nonce { - nonce_manager.nonce = nonce; + if nonce != nonce_manager.nonce.load(Ordering::SeqCst).into() { + nonce_manager.nonce.store(nonce.as_u64(), Ordering::SeqCst); tx_clone.nonce = Some(nonce); self.submit_transaction(tx_clone).await? } else { @@ -240,15 +240,14 @@ where ) -> Result { // If there's a nonce manager set, short circuit by just returning the next nonce if let Some(ref nonce_manager) = self.nonce_manager { - let mut nonce_manager = nonce_manager.write().await; - // initialize the nonce the first time the manager is called - if !nonce_manager.initialized { - nonce_manager.nonce = self + if !nonce_manager.initialized.load(Ordering::SeqCst) { + let nonce = self .provider .get_transaction_count(self.address(), block) .await?; - nonce_manager.initialized = true; + nonce_manager.nonce.store(nonce.as_u64(), Ordering::SeqCst); + nonce_manager.initialized.store(true, Ordering::SeqCst); } return Ok(nonce_manager.next()); @@ -336,7 +335,7 @@ where } pub fn with_nonce_manager(mut self) -> Self { - self.nonce_manager = Some(RwLock::new(NonceManager::new())); + self.nonce_manager = Some(NonceManager::new()); self } } From 1253c1b0c4eee6b2cbdc92d8815b32f2909b73b3 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 17:21:34 +0300 Subject: [PATCH 08/11] refactor: move to own file --- ethers-signers/src/client.rs | 30 ++--------------------------- ethers-signers/src/lib.rs | 3 +++ ethers-signers/src/nonce_manager.rs | 24 +++++++++++++++++++++++ ethers-signers/src/nonces.rs | 0 4 files changed, 29 insertions(+), 28 deletions(-) create mode 100644 ethers-signers/src/nonce_manager.rs delete mode 100644 ethers-signers/src/nonces.rs diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 8a8de27a1..b3dd0c3cc 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -1,4 +1,4 @@ -use crate::Signer; +use crate::{NonceManager, Signer}; use ethers_core::types::{ Address, BlockNumber, Bytes, NameOrAddress, Signature, TransactionRequest, TxHash, U256, @@ -9,10 +9,7 @@ use ethers_providers::{ }; use futures_util::{future::ok, join}; -use std::{ - future::Future, ops::Deref, time::Duration, - sync::atomic::{AtomicBool, AtomicU64, Ordering}, -}; +use std::{future::Future, ops::Deref, sync::atomic::Ordering, time::Duration}; use thiserror::Error; @@ -81,29 +78,6 @@ pub struct Client { pub(crate) nonce_manager: Option, } -#[derive(Debug)] -pub(crate) struct NonceManager { - initialized: AtomicBool, - nonce: AtomicU64, -} - -impl NonceManager { - pub fn new() -> Self { - NonceManager { - initialized: false.into(), - nonce: 0.into(), - } - } - - /// Returns the next nonce to be used - fn next(&self) -> U256 { - let nonce = self.nonce.fetch_add(1, Ordering::SeqCst); - nonce.into() - } -} - -const ONE: U256 = U256([1, 0, 0, 0]); - #[derive(Debug, Error)] /// Error thrown when the client interacts with the blockchain pub enum ClientError { diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs index 186e85330..10bf4e416 100644 --- a/ethers-signers/src/lib.rs +++ b/ethers-signers/src/lib.rs @@ -40,6 +40,9 @@ mod wallet; pub use wallet::Wallet; +mod nonce_manager; +pub(crate) use nonce_manager::NonceManager; + mod client; pub use client::{Client, ClientError}; diff --git a/ethers-signers/src/nonce_manager.rs b/ethers-signers/src/nonce_manager.rs new file mode 100644 index 000000000..1f0056407 --- /dev/null +++ b/ethers-signers/src/nonce_manager.rs @@ -0,0 +1,24 @@ +use ethers_core::types::U256; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; + +#[derive(Debug)] +pub(crate) struct NonceManager { + pub initialized: AtomicBool, + pub nonce: AtomicU64, +} + +impl NonceManager { + /// Instantiates the nonce manager with a 0 nonce. + pub fn new() -> Self { + NonceManager { + initialized: false.into(), + nonce: 0.into(), + } + } + + /// Returns the next nonce to be used + pub fn next(&self) -> U256 { + let nonce = self.nonce.fetch_add(1, Ordering::SeqCst); + nonce.into() + } +} diff --git a/ethers-signers/src/nonces.rs b/ethers-signers/src/nonces.rs deleted file mode 100644 index e69de29bb..000000000 From d72c410a953932be120f04ebaed32196afc0fc0f Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 17:22:37 +0300 Subject: [PATCH 09/11] chore: remove tokio dep --- ethers-signers/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml index 6218d24cd..6d67aae66 100644 --- a/ethers-signers/Cargo.toml +++ b/ethers-signers/Cargo.toml @@ -19,7 +19,6 @@ ethers-providers = { version = "0.1.3", path = "../ethers-providers" } thiserror = { version = "1.0.15", default-features = false } futures-util = { version = "0.3.5", default-features = false } serde = "1.0.112" -tokio = "0.2.21" [dev-dependencies] ethers = { version = "0.1.3", path = "../ethers" } From 07b37b4aeb17c8bc7de6fa1dbaa9166d9e5e28ee Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 17:27:10 +0300 Subject: [PATCH 10/11] fix: improve nonce retry logic readability --- ethers-signers/src/client.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index b3dd0c3cc..098a2dac1 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -148,19 +148,21 @@ where // case there was a nonce mismatch let tx_hash = if let Some(ref nonce_manager) = self.nonce_manager { let mut tx_clone = tx.clone(); - let result = self.submit_transaction(tx).await; - // if we got a nonce error, get the account's latest nonce and re-submit - if result.is_err() { - let nonce = self.get_transaction_count(block).await?; - if nonce != nonce_manager.nonce.load(Ordering::SeqCst).into() { - nonce_manager.nonce.store(nonce.as_u64(), Ordering::SeqCst); - tx_clone.nonce = Some(nonce); - self.submit_transaction(tx_clone).await? - } else { - result? + match self.submit_transaction(tx).await { + Ok(tx_hash) => tx_hash, + Err(err) => { + let nonce = self.get_transaction_count(block).await?; + if nonce != nonce_manager.nonce.load(Ordering::SeqCst).into() { + // try re-submitting the transaction with the correct nonce if there + // was a nonce mismatch + nonce_manager.nonce.store(nonce.as_u64(), Ordering::SeqCst); + tx_clone.nonce = Some(nonce); + self.submit_transaction(tx_clone).await? + } else { + // propagate the error otherwise + return Err(err); + } } - } else { - result? } } else { self.submit_transaction(tx).await? From d5dfe55aa7d02accea3ddd6e209b687c741ab19d Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 3 Sep 2020 17:39:08 +0300 Subject: [PATCH 11/11] fix: use other privkey to avoid nonce races with other tests --- ethers-signers/tests/signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs index b5182fa17..10978e865 100644 --- a/ethers-signers/tests/signer.rs +++ b/ethers-signers/tests/signer.rs @@ -83,7 +83,7 @@ mod eth_tests { .unwrap() .interval(Duration::from_millis(2000u64)); - let client = "FF7F80C6E9941865266ED1F481263D780169F1D98269C51167D20C630A5FDC8A" + let client = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4" .parse::() .unwrap() .connect(provider)