Repository: gakonst/ethers-rs
Pull request #2424 (2023-05-18 20:21:06)

add subscribe_full_pending_txs()
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
geth recently added [SubscribeFullPendingTransactions](https://github.com/ethereum/go-ethereum/blob/73697529994e14996b7740730481e926d5ec3e40/ethclient/gethclient/gethclient.go#L196)

this PR just adds a new method called ```subscribe_full_pending_txs()```, which instead of streaming hashes, streams
whole tx bodies. the only edit was adding ```true``` as a additional parameter, so ```subscribe([utils::serialize(&"newPendingTransactions"), utils::serialize(&true)])```.
 
<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

-   [ ] Added Tests
-   [x] Added Documentation
-   [ ] Breaking changes



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 28505a8a..424b3dc7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -768,6 +768,12 @@ dependencies = [
  "parking_lot_core",
 ]
 
+[[package]]
+name = "data-encoding"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
+
 [[package]]
 name = "dbl"
 version = "0.3.2"
@@ -3106,7 +3112,7 @@ dependencies = [
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
- "webpki-roots",
+ "webpki-roots 0.22.6",
  "winreg",
 ]
 
@@ -4046,20 +4052,19 @@ dependencies = [
 
 [[package]]
 name = "tokio-tungstenite"
-version = "0.18.0"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd"
+checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c"
 dependencies = [
  "futures-util",
  "log",
  "native-tls",
- "rustls 0.20.8",
+ "rustls 0.21.1",
  "tokio",
  "tokio-native-tls",
- "tokio-rustls 0.23.4",
+ "tokio-rustls 0.24.0",
  "tungstenite",
- "webpki",
- "webpki-roots",
+ "webpki-roots 0.23.0",
 ]
 
 [[package]]
@@ -4234,19 +4239,19 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
 
 [[package]]
 name = "tungstenite"
-version = "0.18.0"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788"
+checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
 dependencies = [
- "base64 0.13.1",
  "byteorder",
  "bytes",
+ "data-encoding",
  "http",
  "httparse",
  "log",
  "native-tls",
  "rand",
- "rustls 0.20.8",
+ "rustls 0.21.1",
  "sha1",
  "thiserror",
  "url",
@@ -4506,6 +4511,15 @@ dependencies = [
  "webpki",
 ]
 
+[[package]]
+name = "webpki-roots"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125"
+dependencies = [
+ "rustls-webpki",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 98e00e7f..8845dda3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -79,7 +79,7 @@ ethers-contract-derive = { version = "2.0.4", path = "ethers-contract/ethers-con
 
 # async / async utils
 tokio = "1.28"
-tokio-tungstenite = { version = "0.18.0", default-features = false }
+tokio-tungstenite = { version = "0.19", default-features = false }
 futures = { version = "0.3.28", default-features = false, features = ["std"] }
 futures-core = "0.3.28"
 futures-util = "0.3.28"
diff --git a/book/providers/advanced_usage.md b/book/providers/advanced_usage.md
index 47a7b1dc..a648ee92 100644
--- a/book/providers/advanced_usage.md
+++ b/book/providers/advanced_usage.md
@@ -8,7 +8,7 @@ Lets take a quick look at how to use the `CallBuilder`.
 
 ```rust
 use ethers::{
-    providers::{ Http, Provider},
+    providers::{Http, Provider},
     types::{TransactionRequest, H160},
     utils::parse_ether,
 };
@@ -75,7 +75,7 @@ Let's look at how to use the state override set. In short, the state override se
 ```rust
 use ethers::{
     providers::{
-        call_raw::{spoof::State, RawCall},
+        call_raw::RawCall,
         Http, Provider,
     },
     types::{TransactionRequest, H160, U256, U64},
@@ -98,7 +98,7 @@ async fn main() -> eyre::Result<()> {
         .value(val)
         .into();
 
-    let mut state = State::default();
+    let mut state = spoof::State::default();
 
     // Set the account balance to max u256
     state.account(from_adr).balance(U256::MAX);
diff --git a/deny.toml b/deny.toml
index eef64229..0977fe81 100644
--- a/deny.toml
+++ b/deny.toml
@@ -77,6 +77,11 @@ name = "webpki"
 expression = "LicenseRef-webpki"
 license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
 
+[[licenses.clarify]]
+name = "rustls-webpki"
+expression = "LicenseRef-webpki"
+license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
+
 # This section is considered when running `cargo deny check sources`.
 # More documentation about the 'sources' section can be found here:
 # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
diff --git a/ethers-core/src/types/trace/geth.rs b/ethers-core/src/types/trace/geth.rs
index 358dd9dc..1aa89dac 100644
--- a/ethers-core/src/types/trace/geth.rs
+++ b/ethers-core/src/types/trace/geth.rs
@@ -9,7 +9,9 @@ pub use self::{
     noop::NoopFrame,
     pre_state::{AccountState, DiffMode, PreStateConfig, PreStateFrame, PreStateMode},
 };
-use crate::types::{serde_helpers::deserialize_stringified_numeric, Bytes, H256, U256};
+use crate::types::{
+    serde_helpers::deserialize_stringified_numeric, Address, Bytes, H256, U256, U64,
+};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use std::collections::BTreeMap;
@@ -205,5 +207,265 @@ pub struct GethDebugTracingOptions {
 pub struct GethDebugTracingCallOptions {
     #[serde(flatten)]
     pub tracing_options: GethDebugTracingOptions,
-    // TODO: Add stateoverrides and blockoverrides options
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub state_overrides: Option<spoof::State>,
+    // TODO: Add blockoverrides options
+}
+
+/// Provides types and methods for constructing an `eth_call`
+/// [state override set](https://geth.ethereum.org/docs/rpc/ns-eth#3-object---state-override-set)
+pub mod spoof {
+    use super::*;
+    use std::collections::HashMap;
+
+    /// The state elements to override for a particular account.
+    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+    pub struct Account {
+        /// Account nonce
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub nonce: Option<U64>,
+        /// Account balance
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub balance: Option<U256>,
+        /// Account code
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub code: Option<Bytes>,
+        /// Account storage
+        #[serde(flatten, skip_serializing_if = "Option::is_none")]
+        pub storage: Option<Storage>,
+    }
+
+    impl Account {
+        /// Override the account nonce
+        pub fn nonce(&mut self, nonce: U64) -> &mut Self {
+            self.nonce = Some(nonce);
+            self
+        }
+        /// Override the account balance
+        pub fn balance(&mut self, bal: U256) -> &mut Self {
+            self.balance = Some(bal);
+            self
+        }
+        /// Override the code at the account
+        pub fn code(&mut self, code: Bytes) -> &mut Self {
+            self.code = Some(code);
+            self
+        }
+        /// Override the value of the account storage at the given storage `key`
+        pub fn store(&mut self, key: H256, val: H256) -> &mut Self {
+            self.storage.get_or_insert_with(Default::default).insert(key, val);
+            self
+        }
+    }
+
+    /// Wraps a map from storage slot to the overriden value.
+    ///
+    /// Storage overrides can either replace the existing state of an account or they can be treated
+    /// as a diff on the existing state.
+    #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+    pub enum Storage {
+        /// State Diff
+        #[serde(rename = "stateDiff")]
+        Diff(HashMap<H256, H256>),
+        /// State override
+        #[serde(rename = "state")]
+        Replace(HashMap<H256, H256>),
+    }
+
+    /// The default storage override is a diff on the existing state of the account.
+    impl Default for Storage {
+        fn default() -> Self {
+            Self::Diff(Default::default())
+        }
+    }
+    impl std::ops::Deref for Storage {
+        type Target = HashMap<H256, H256>;
+        fn deref(&self) -> &Self::Target {
+            match self {
+                Self::Diff(map) => map,
+                Self::Replace(map) => map,
+            }
+        }
+    }
+    impl std::ops::DerefMut for Storage {
+        fn deref_mut(&mut self) -> &mut Self::Target {
+            match self {
+                Self::Diff(map) => map,
+                Self::Replace(map) => map,
+            }
+        }
+    }
+
+    /// A wrapper type that holds a complete state override set.
+    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+    #[serde(transparent)]
+    pub struct State(#[serde(skip_serializing_if = "HashMap::is_empty")] HashMap<Address, Account>);
+
+    impl State {
+        /// Returns a mutable reference to the [`Account`] in the map.
+        pub fn account(&mut self, adr: Address) -> &mut Account {
+            self.0.entry(adr).or_default()
+        }
+    }
+
+    /// Returns an empty state override set.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256, spoof},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::RawCall};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
+    /// let key = H256::from_low_u64_be(1);
+    /// let val = H256::from_low_u64_be(17);
+    ///
+    /// let tx = TransactionRequest::default().to(adr2).from(adr1).into();
+    ///
+    /// // override the storage at `adr2`
+    /// let mut state = spoof::state();
+    /// state.account(adr2).store(key, val);
+    ///
+    /// // override the nonce at `adr1`
+    /// state.account(adr1).nonce(2.into());
+    ///
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn state() -> State {
+        Default::default()
+    }
+
+    /// Returns a state override set with a single element setting the balance of the address.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
+    /// let pay_amt = parse_ether(1u64)?;
+    ///
+    /// // Not enough ether to pay for the transaction
+    /// let tx = TransactionRequest::pay(adr2, pay_amt).from(adr1).into();
+    ///
+    /// // override the sender's balance for the call
+    /// let state = spoof::balance(adr1, pay_amt * 2);
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn balance(adr: Address, bal: U256) -> State {
+        let mut state = State::default();
+        state.account(adr).balance(bal);
+        state
+    }
+
+    /// Returns a state override set with a single element setting the nonce of the address.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let pay_amt = parse_ether(1u64)?;
+    ///
+    /// let tx = TransactionRequest::default().from(adr).into();
+    ///
+    /// // override the sender's nonce for the call
+    /// let state = spoof::nonce(adr, 72.into());
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn nonce(adr: Address, nonce: U64) -> State {
+        let mut state = State::default();
+        state.account(adr).nonce(nonce);
+        state
+    }
+
+    /// Returns a state override set with a single element setting the code at the address.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let pay_amt = parse_ether(1u64)?;
+    ///
+    /// let tx = TransactionRequest::default().to(adr).into();
+    ///
+    /// // override the code at the target address
+    /// let state = spoof::code(adr, "0x00".parse()?);
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn code(adr: Address, code: Bytes) -> State {
+        let mut state = State::default();
+        state.account(adr).code(code);
+        state
+    }
+
+    /// Returns a state override set with a single element setting the storage at the given address
+    /// and key.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let key = H256::from_low_u64_be(1);
+    /// let val = H256::from_low_u64_be(17);
+    ///
+    /// let tx = TransactionRequest::default().to(adr).into();
+    ///
+    /// // override the storage slot `key` at `adr`
+    /// let state = spoof::storage(adr, key, val);
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn storage(adr: Address, key: H256, val: H256) -> State {
+        let mut state = State::default();
+        state.account(adr).store(key, val);
+        state
+    }
 }
diff --git a/ethers-etherscan/src/blocks.rs b/ethers-etherscan/src/blocks.rs
new file mode 100644
index 00000000..f009e32b
--- /dev/null
+++ b/ethers-etherscan/src/blocks.rs
@@ -0,0 +1,47 @@
+use crate::{Client, EtherscanError, Response, Result};
+use ethers_core::types::BlockNumber;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct BlockNumberByTimestamp {
+    pub timestamp: u64,
+    pub block_number: BlockNumber,
+}
+
+impl Client {
+    /// Returns either (1) the oldest block since a particular timestamp occurred or (2) the newest
+    /// block that occurred prior to that timestamp
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
+    /// // The newest block that occurred prior to 1 January 2020
+    /// let block_number_before = client.get_block_by_timestamp(1577836800, "before");
+    /// // The oldest block that occurred after 1 January 2020
+    /// let block_number_after = client.get_block_by_timestamp(1577836800, "after");
+    /// # Ok(()) }
+    /// ```
+    pub async fn get_block_by_timestamp(
+        &self,
+        timestamp: u64,
+        closest: &str,
+    ) -> Result<BlockNumberByTimestamp> {
+        let query = self.create_query(
+            "block",
+            "getblocknobytime",
+            HashMap::from([("timestamp", timestamp.to_string()), ("closest", closest.to_string())]),
+        );
+        let response: Response<String> = self.get_json(&query).await?;
+
+        match response.status.as_str() {
+            "0" => Err(EtherscanError::BlockNumberByTimestampFailed),
+            "1" => Ok(BlockNumberByTimestamp {
+                timestamp,
+                block_number: response.result.parse::<BlockNumber>().unwrap(),
+            }),
+            err => Err(EtherscanError::BadStatusCode(err.to_string())),
+        }
+    }
+}
diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs
index 2942afa6..0cfff5cd 100644
--- a/ethers-etherscan/src/contract.rs
+++ b/ethers-etherscan/src/contract.rs
@@ -331,17 +331,31 @@ impl Client {
         }
 
         let query = self.create_query("contract", "getabi", HashMap::from([("address", address)]));
-        let resp: Response<String> = self.get_json(&query).await?;
-        if resp.result.starts_with("Max rate limit reached") {
+        let resp: Response<Option<String>> = self.get_json(&query).await?;
+
+        let result = match resp.result {
+            Some(result) => result,
+            None => {
+                return Err(EtherscanError::EmptyResult {
+                    message: resp.message,
+                    status: resp.status,
+                })
+            }
+        };
+
+        if result.starts_with("Max rate limit reached") {
             return Err(EtherscanError::RateLimitExceeded)
         }
-        if resp.result.starts_with("Contract source code not verified") {
+
+        if result.starts_with("Contract source code not verified") ||
+            resp.message.starts_with("Contract source code not verified")
+        {
             if let Some(ref cache) = self.cache {
                 cache.set_abi(address, None);
             }
             return Err(EtherscanError::ContractCodeNotVerified(address))
         }
-        let abi = serde_json::from_str(&resp.result)?;
+        let abi = serde_json::from_str(&result)?;
 
         if let Some(ref cache) = self.cache {
             cache.set_abi(address, Some(&abi));
diff --git a/ethers-etherscan/src/errors.rs b/ethers-etherscan/src/errors.rs
index 5f71f52d..794d62e5 100644
--- a/ethers-etherscan/src/errors.rs
+++ b/ethers-etherscan/src/errors.rs
@@ -9,6 +9,8 @@ pub enum EtherscanError {
     ExecutionFailed(String),
     #[error("Balance failed")]
     BalanceFailed,
+    #[error("Block by timestamp failed")]
+    BlockNumberByTimestampFailed,
     #[error("Transaction receipt failed")]
     TransactionReceiptFailed,
     #[error("Gas estimation failed")]
@@ -23,6 +25,8 @@ pub enum EtherscanError {
     Serde(#[from] serde_json::Error),
     #[error("Contract source code not verified: {0}")]
     ContractCodeNotVerified(Address),
+    #[error("Response result is unexpectedly empty: status={status}, message={message}")]
+    EmptyResult { status: String, message: String },
     #[error("Rate limit exceeded")]
     RateLimitExceeded,
     #[error(transparent)]
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index c1ea855e..12827955 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -20,6 +20,7 @@ use std::{
 use tracing::{error, trace};
 
 pub mod account;
+pub mod blocks;
 pub mod contract;
 pub mod errors;
 pub mod gas;
diff --git a/ethers-etherscan/src/verify.rs b/ethers-etherscan/src/verify.rs
index e76b4eca..ab3fb3ba 100644
--- a/ethers-etherscan/src/verify.rs
+++ b/ethers-etherscan/src/verify.rs
@@ -24,8 +24,13 @@ pub struct VerifyContract {
     /// applicable when codeformat=solidity-single-file
     #[serde(skip_serializing_if = "Option::is_none")]
     pub runs: Option<String>,
-    /// NOTE: there is a typo in the etherscan API `constructorArguements`
-    #[serde(rename = "constructorArguements", skip_serializing_if = "Option::is_none")]
+    /// NOTE: the alias with a typo is due to an old typo in the etherscan API
+    /// `constructorArguements`, but blockscout requires that it's spelled correctly.
+    #[serde(
+        rename = "constructorArguments",
+        alias = "constructorArguements",
+        skip_serializing_if = "Option::is_none"
+    )]
     pub constructor_arguments: Option<String>,
     /// applicable when codeformat=solidity-single-file
     #[serde(rename = "evmversion", skip_serializing_if = "Option::is_none")]
diff --git a/ethers-middleware/src/timelag/mod.rs b/ethers-middleware/src/timelag/mod.rs
index 258fc15a..2735887c 100644
--- a/ethers-middleware/src/timelag/mod.rs
+++ b/ethers-middleware/src/timelag/mod.rs
@@ -380,6 +380,15 @@ where
         Err(TimeLagError::Unsupported)
     }
 
+    async fn subscribe_full_pending_txs(
+        &self,
+    ) -> Result<ethers_providers::SubscriptionStream<'_, Self::Provider, Transaction>, Self::Error>
+    where
+        Self::Provider: ethers_providers::PubsubClient,
+    {
+        Err(TimeLagError::Unsupported)
+    }
+
     async fn subscribe_logs<'a>(
         &'a self,
         _filter: &ethers_core::types::Filter,
diff --git a/ethers-providers/src/middleware.rs b/ethers-providers/src/middleware.rs
index 436c8c3b..a13de6b3 100644
--- a/ethers-providers/src/middleware.rs
+++ b/ethers-providers/src/middleware.rs
@@ -905,7 +905,7 @@ pub trait Middleware: Sync + Send + Debug {
         self.inner().subscribe_blocks().await.map_err(MiddlewareError::from_err)
     }
 
-    /// Subscribe to a stream of pending transactions.
+    /// Subscribe to a stream of pending transaction hashes.
     ///
     /// This function is only available on pubsub clients, such as Websockets
     /// or IPC. For a polling alternative available over HTTP, use
@@ -920,6 +920,23 @@ pub trait Middleware: Sync + Send + Debug {
         self.inner().subscribe_pending_txs().await.map_err(MiddlewareError::from_err)
     }
 
+    /// Subscribe to a stream of pending transaction bodies.
+    ///
+    /// This function is only available on pubsub clients, such as Websockets
+    /// or IPC. For a polling alternative available over HTTP, use
+    /// [`Middleware::watch_pending_transactions`]. However, be aware that
+    /// polling increases RPC usage drastically.
+    ///
+    /// Note: This endpoint is compatible only with Geth client version 1.11.0 or later.
+    async fn subscribe_full_pending_txs(
+        &self,
+    ) -> Result<SubscriptionStream<'_, Self::Provider, Transaction>, Self::Error>
+    where
+        <Self as Middleware>::Provider: PubsubClient,
+    {
+        self.inner().subscribe_full_pending_txs().await.map_err(MiddlewareError::from_err)
+    }
+
     /// Subscribe to a stream of event logs matchin the provided [`Filter`].
     ///
     /// This function is only available on pubsub clients, such as Websockets
diff --git a/ethers-providers/src/rpc/provider.rs b/ethers-providers/src/rpc/provider.rs
index 522073a1..1845f664 100644
--- a/ethers-providers/src/rpc/provider.rs
+++ b/ethers-providers/src/rpc/provider.rs
@@ -205,20 +205,21 @@ impl<P: JsonRpcClient> Provider<P> {
     /// Analogous to [`Middleware::call`], but returns a [`CallBuilder`] that can either be
     /// `.await`d or used to override the parameters sent to `eth_call`.
     ///
-    /// See the [`call_raw::spoof`] for functions to construct state override parameters.
+    /// See the [`ethers_core::types::spoof`] for functions to construct state override
+    /// parameters.
     ///
     /// Note: this method _does not_ send a transaction from your account
     ///
-    /// [`call_raw::spoof`]: crate::call_raw::spoof
+    /// [`ethers_core::types::spoof`]: ethers_core::types::spoof
     ///
     /// # Example
     ///
     /// ```no_run
     /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
+    /// #     types::{Address, TransactionRequest, H256, spoof},
     /// #     utils::{parse_ether, Geth},
     /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::RawCall};
     /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
     /// let geth = Geth::new().spawn();
     /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
@@ -1049,6 +1050,15 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.subscribe(["newPendingTransactions"]).await
     }
 
+    async fn subscribe_full_pending_txs(
+        &self,
+    ) -> Result<SubscriptionStream<'_, P, Transaction>, ProviderError>
+    where
+        P: PubsubClient,
+    {
+        self.subscribe([utils::serialize(&"newPendingTransactions"), utils::serialize(&true)]).await
+    }
+
     async fn subscribe_logs<'a>(
         &'a self,
         filter: &Filter,
diff --git a/ethers-providers/src/rpc/transports/ws/backend.rs b/ethers-providers/src/rpc/transports/ws/backend.rs
index 6ae3391e..e29cfa16 100644
--- a/ethers-providers/src/rpc/transports/ws/backend.rs
+++ b/ethers-providers/src/rpc/transports/ws/backend.rs
@@ -73,8 +73,9 @@ impl WsBackend {
     pub async fn connect_with_config(
         details: ConnectionDetails,
         config: WebSocketConfig,
+        disable_nagle: bool,
     ) -> Result<(Self, BackendDriver), WsClientError> {
-        let ws = connect_async_with_config(details, Some(config)).await?.0.fuse();
+        let ws = connect_async_with_config(details, Some(config), disable_nagle).await?.0.fuse();
         Ok(Self::new(ws))
     }
 
diff --git a/ethers-providers/src/rpc/transports/ws/manager.rs b/ethers-providers/src/rpc/transports/ws/manager.rs
index 5ab3720e..c685da4c 100644
--- a/ethers-providers/src/rpc/transports/ws/manager.rs
+++ b/ethers-providers/src/rpc/transports/ws/manager.rs
@@ -316,7 +316,7 @@ impl RequestManager {
     #[cfg(not(target_arch = "wasm32"))]
     async fn reconnect_backend(&mut self) -> Result<(WsBackend, BackendDriver), WsClientError> {
         if let Some(config) = self.config {
-            WsBackend::connect_with_config(self.conn.clone(), config).await
+            WsBackend::connect_with_config(self.conn.clone(), config, false).await
         } else {
             WsBackend::connect(self.conn.clone()).await
         }
@@ -351,25 +351,29 @@ impl RequestManager {
         old_backend.shutdown();
 
         tracing::debug!(count = self.subs.count(), "Re-starting active subscriptions");
+        let req_cnt = self.reqs.len();
 
-        // reissue subscriptionps
+        // reissue subscriptions
         for (id, sub) in self.subs.to_reissue() {
-            self.backend
-                .dispatcher
-                .unbounded_send(sub.serialize_raw(*id)?)
-                .map_err(|_| WsClientError::DeadChannel)?;
+            let (tx, _rx) = oneshot::channel();
+            let in_flight = InFlight {
+                method: "eth_subscribe".to_string(),
+                params: sub.params.clone(),
+                channel: tx,
+            };
+            // Need an entry in reqs to ensure response with new server sub ID is processed
+            self.reqs.insert(*id, in_flight);
         }
 
-        tracing::debug!(count = self.reqs.len(), "Re-issuing pending requests");
-        // reissue requests. We filter these to prevent in-flight requests for
-        // subscriptions to be re-issued twice (once in above loop, once in this loop).
-        for (id, req) in self.reqs.iter().filter(|(id, _)| !self.subs.has(**id)) {
+        tracing::debug!(count = req_cnt, "Re-issuing pending requests");
+        // reissue requests, including the re-subscription requests we just added above
+        for (id, req) in self.reqs.iter() {
             self.backend
                 .dispatcher
                 .unbounded_send(req.serialize_raw(*id)?)
                 .map_err(|_| WsClientError::DeadChannel)?;
         }
-        tracing::info!(subs = self.subs.count(), reqs = self.reqs.len(), "Re-connection complete");
+        tracing::info!(subs = self.subs.count(), reqs = req_cnt, "Re-connection complete");
 
         Ok(())
     }
diff --git a/ethers-providers/src/toolbox/call_raw.rs b/ethers-providers/src/toolbox/call_raw.rs
index 55d783c1..293f5760 100644
--- a/ethers-providers/src/toolbox/call_raw.rs
+++ b/ethers-providers/src/toolbox/call_raw.rs
@@ -2,14 +2,11 @@
 
 use crate::{utils::PinBoxFut, JsonRpcClient, Provider, ProviderError};
 use ethers_core::{
-    types::{
-        transaction::eip2718::TypedTransaction, Address, BlockId, BlockNumber, Bytes, H256, U256,
-        U64,
-    },
+    types::{transaction::eip2718::TypedTransaction, BlockId, BlockNumber, Bytes},
     utils,
 };
 use pin_project::pin_project;
-use serde::{ser::SerializeTuple, Deserialize, Serialize};
+use serde::{ser::SerializeTuple, Serialize};
 use std::{
     fmt,
     future::Future,
@@ -17,7 +14,7 @@ use std::{
     task::{Context, Poll},
 };
 
-pub use spoof::{balance, code, nonce, state, storage};
+pub use ethers_core::types::spoof;
 
 /// Provides methods for overriding parameters to the `eth_call` rpc method
 pub trait RawCall<'a> {
@@ -230,272 +227,15 @@ where
     }
 }
 
-/// Provides types and methods for constructing an `eth_call`
-/// [state override set](https://geth.ethereum.org/docs/rpc/ns-eth#3-object---state-override-set)
-pub mod spoof {
-    use super::*;
-    use std::collections::HashMap;
-
-    /// The state elements to override for a particular account.
-    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
-    pub struct Account {
-        /// Account nonce
-        #[serde(skip_serializing_if = "Option::is_none")]
-        pub nonce: Option<U64>,
-        /// Account balance
-        #[serde(skip_serializing_if = "Option::is_none")]
-        pub balance: Option<U256>,
-        /// Account code
-        #[serde(skip_serializing_if = "Option::is_none")]
-        pub code: Option<Bytes>,
-        /// Account storage
-        #[serde(flatten, skip_serializing_if = "Option::is_none")]
-        pub storage: Option<Storage>,
-    }
-
-    impl Account {
-        /// Override the account nonce
-        pub fn nonce(&mut self, nonce: U64) -> &mut Self {
-            self.nonce = Some(nonce);
-            self
-        }
-        /// Override the account balance
-        pub fn balance(&mut self, bal: U256) -> &mut Self {
-            self.balance = Some(bal);
-            self
-        }
-        /// Override the code at the account
-        pub fn code(&mut self, code: Bytes) -> &mut Self {
-            self.code = Some(code);
-            self
-        }
-        /// Override the value of the account storage at the given storage `key`
-        pub fn store(&mut self, key: H256, val: H256) -> &mut Self {
-            self.storage.get_or_insert_with(Default::default).insert(key, val);
-            self
-        }
-    }
-
-    /// Wraps a map from storage slot to the overriden value.
-    ///
-    /// Storage overrides can either replace the existing state of an account or they can be treated
-    /// as a diff on the existing state.
-    #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-    pub enum Storage {
-        /// State Diff
-        #[serde(rename = "stateDiff")]
-        Diff(HashMap<H256, H256>),
-        /// State override
-        #[serde(rename = "state")]
-        Replace(HashMap<H256, H256>),
-    }
-
-    /// The default storage override is a diff on the existing state of the account.
-    impl Default for Storage {
-        fn default() -> Self {
-            Self::Diff(Default::default())
-        }
-    }
-    impl std::ops::Deref for Storage {
-        type Target = HashMap<H256, H256>;
-        fn deref(&self) -> &Self::Target {
-            match self {
-                Self::Diff(map) => map,
-                Self::Replace(map) => map,
-            }
-        }
-    }
-    impl std::ops::DerefMut for Storage {
-        fn deref_mut(&mut self) -> &mut Self::Target {
-            match self {
-                Self::Diff(map) => map,
-                Self::Replace(map) => map,
-            }
-        }
-    }
-
-    /// A wrapper type that holds a complete state override set.
-    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
-    #[serde(transparent)]
-    pub struct State(#[serde(skip_serializing_if = "HashMap::is_empty")] HashMap<Address, Account>);
-
-    impl State {
-        /// Returns a mutable reference to the [`Account`] in the map.
-        pub fn account(&mut self, adr: Address) -> &mut Account {
-            self.0.entry(adr).or_default()
-        }
-    }
-
-    /// Returns an empty state override set.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{spoof, RawCall}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
-    /// let key = H256::from_low_u64_be(1);
-    /// let val = H256::from_low_u64_be(17);
-    ///
-    /// let tx = TransactionRequest::default().to(adr2).from(adr1).into();
-    ///
-    /// // override the storage at `adr2`
-    /// let mut state = spoof::state();
-    /// state.account(adr2).store(key, val);
-    ///
-    /// // override the nonce at `adr1`
-    /// state.account(adr1).nonce(2.into());
-    ///
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn state() -> State {
-        Default::default()
-    }
-
-    /// Returns a state override set with a single element setting the balance of the address.
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
-    /// let pay_amt = parse_ether(1u64)?;
-    ///
-    /// // Not enough ether to pay for the transaction
-    /// let tx = TransactionRequest::pay(adr2, pay_amt).from(adr1).into();
-    ///
-    /// // override the sender's balance for the call
-    /// let state = spoof::balance(adr1, pay_amt * 2);
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn balance(adr: Address, bal: U256) -> State {
-        let mut state = State::default();
-        state.account(adr).balance(bal);
-        state
-    }
-
-    /// Returns a state override set with a single element setting the nonce of the address.
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let pay_amt = parse_ether(1u64)?;
-    ///
-    /// let tx = TransactionRequest::default().from(adr).into();
-    ///
-    /// // override the sender's nonce for the call
-    /// let state = spoof::nonce(adr, 72.into());
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn nonce(adr: Address, nonce: U64) -> State {
-        let mut state = State::default();
-        state.account(adr).nonce(nonce);
-        state
-    }
-
-    /// Returns a state override set with a single element setting the code at the address.
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let pay_amt = parse_ether(1u64)?;
-    ///
-    /// let tx = TransactionRequest::default().to(adr).into();
-    ///
-    /// // override the code at the target address
-    /// let state = spoof::code(adr, "0x00".parse()?);
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn code(adr: Address, code: Bytes) -> State {
-        let mut state = State::default();
-        state.account(adr).code(code);
-        state
-    }
-
-    /// Returns a state override set with a single element setting the storage at the given address
-    /// and key.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let key = H256::from_low_u64_be(1);
-    /// let val = H256::from_low_u64_be(17);
-    ///
-    /// let tx = TransactionRequest::default().to(adr).into();
-    ///
-    /// // override the storage slot `key` at `adr`
-    /// let state = spoof::storage(adr, key, val);
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn storage(adr: Address, key: H256, val: H256) -> State {
-        let mut state = State::default();
-        state.account(adr).store(key, val);
-        state
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::{Http, Provider};
     use ethers_core::{
-        types::TransactionRequest,
+        types::{Address, TransactionRequest, H256, U256},
         utils::{get_contract_address, keccak256, parse_ether, Geth},
     };
+    use serde::Deserialize;
     use std::convert::TryFrom;
 
     // Deserializes eth_call parameters as owned data for testing serialization
diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index f70511d2..4d973c78 100644
--- a/ethers-solc/src/compile/mod.rs
+++ b/ethers-solc/src/compile/mod.rs
@@ -311,7 +311,7 @@ impl Solc {
     /// # Ok(())
     /// # }
     /// ```
-    #[cfg(all(not(target_arch = "wasm32"), all(feature = "svm-solc")))]
+    #[cfg(all(not(target_arch = "wasm32"), feature = "svm-solc"))]
     pub fn find_or_install_svm_version(version: impl AsRef<str>) -> Result<Self> {
         let version = version.as_ref();
         if let Some(solc) = Solc::find_svm_installed_version(version)? {
diff --git a/examples/anvil/Cargo.toml b/examples/anvil/Cargo.toml
index 3d8b0971..26120922 100644
--- a/examples/anvil/Cargo.toml
+++ b/examples/anvil/Cargo.toml
@@ -10,5 +10,5 @@ edition.workspace = true
 
 [dev-dependencies]
 ethers.workspace = true
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 eyre.workspace = true
diff --git a/examples/contracts/Cargo.toml b/examples/contracts/Cargo.toml
index 6db33211..4246eb7e 100644
--- a/examples/contracts/Cargo.toml
+++ b/examples/contracts/Cargo.toml
@@ -15,7 +15,7 @@ legacy = ["ethers/legacy"]
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "rustls", "ws", "ethers-solc"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml
index 8dbaea5a..6c3fb842 100644
--- a/examples/events/Cargo.toml
+++ b/examples/events/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/geth/Cargo.toml b/examples/geth/Cargo.toml
index eef9e3a1..c552a413 100644
--- a/examples/geth/Cargo.toml
+++ b/examples/geth/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers.workspace = true
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/middleware/Cargo.toml b/examples/middleware/Cargo.toml
index 924a0162..6ca79c64 100644
--- a/examples/middleware/Cargo.toml
+++ b/examples/middleware/Cargo.toml
@@ -12,7 +12,7 @@ edition.workspace = true
 ethers = { workspace = true, features = ["rustls"] }
 serde.workspace = true
 serde_json.workspace = true
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 async-trait.workspace = true
 thiserror.workspace = true
 eyre.workspace = true
diff --git a/examples/providers/Cargo.toml b/examples/providers/Cargo.toml
index 3a5b37cb..2f65de88 100644
--- a/examples/providers/Cargo.toml
+++ b/examples/providers/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ipc", "rustls", "ws"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 async-trait.workspace = true
 reqwest.workspace = true
 url.workspace = true
diff --git a/examples/queries/Cargo.toml b/examples/queries/Cargo.toml
index 315de0e9..ff678531 100644
--- a/examples/queries/Cargo.toml
+++ b/examples/queries/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml
index 0d6d276a..a6a51cf7 100644
--- a/examples/subscriptions/Cargo.toml
+++ b/examples/subscriptions/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/transactions/Cargo.toml b/examples/transactions/Cargo.toml
index ad065baf..cb7f78ee 100644
--- a/examples/transactions/Cargo.toml
+++ b/examples/transactions/Cargo.toml
@@ -9,9 +9,9 @@ rust-version.workspace = true
 edition.workspace = true
 
 [dev-dependencies]
-ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
+ethers = { workspace = true, features = ["abigen", "ws", "rustls", "ethers-solc"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/transactions/examples/call_override.rs b/examples/transactions/examples/call_override.rs
index 4a124fce..70c6466b 100644
--- a/examples/transactions/examples/call_override.rs
+++ b/examples/transactions/examples/call_override.rs
@@ -1,13 +1,10 @@
 use ethers::{
     contract::abigen,
     core::{
-        types::{Address, TransactionRequest, H256},
+        types::{spoof, Address, TransactionRequest, H256},
         utils::{parse_ether, Geth},
     },
-    providers::{
-        call_raw::{self, RawCall},
-        Http, Provider,
-    },
+    providers::{call_raw::RawCall, Http, Provider},
 };
 use eyre::Result;
 use std::{
@@ -42,7 +39,7 @@ async fn main() -> Result<()> {
     // Override the sender's balance for the call
     let pay_amt = parse_ether(1u64)?;
     let tx = TransactionRequest::pay(target, pay_amt).from(from);
-    let state = call_raw::balance(from, pay_amt * 2);
+    let state = spoof::balance(from, pay_amt * 2);
 
     // The call succeeds as if the sender had sufficient balance
     client.call_raw(&tx.into()).state(&state).await.expect("balance override");
@@ -57,7 +54,7 @@ async fn main() -> Result<()> {
 
     // Override the target account's code, simulating a call to Greeter.greet()
     // as if the Greeter contract was deployed at the target address
-    let state = call_raw::code(target, runtime_bytecode.clone());
+    let state = spoof::code(target, runtime_bytecode.clone());
     let res = greeter.greet().call_raw().state(&state).await?;
 
     // greet() returns the empty string, because the target account's storage is empty
@@ -69,7 +66,7 @@ async fn main() -> Result<()> {
     let greet_val = encode_string_for_storage(greeting);
 
     // Override the target account's code and storage
-    let mut state = call_raw::state();
+    let mut state = spoof::state();
     state.account(target).code(runtime_bytecode.clone()).store(greet_slot, greet_val);
     let res = greeter.greet().call_raw().state(&state).await?;
 
diff --git a/examples/transactions/examples/trace_call.rs b/examples/transactions/examples/trace_call.rs
index 67551d6e..849f564b 100644
--- a/examples/transactions/examples/trace_call.rs
+++ b/examples/transactions/examples/trace_call.rs
@@ -26,6 +26,7 @@ async fn main() -> Result<()> {
                 )),
                 ..Default::default()
             },
+            state_overrides: None,
         };
         let traces = client.debug_trace_call(tx, Some(block), options).await?;
         println!("{traces:?}");
diff --git a/examples/wallets/Cargo.toml b/examples/wallets/Cargo.toml
index 8c82e345..7667e840 100644
--- a/examples/wallets/Cargo.toml
+++ b/examples/wallets/Cargo.toml
@@ -17,7 +17,7 @@ yubi = ["ethers/yubi"]
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true


Test patch:
diff --git a/ethers-etherscan/tests/it/blocks.rs b/ethers-etherscan/tests/it/blocks.rs
new file mode 100644
index 00000000..9c7ee6a2
--- /dev/null
+++ b/ethers-etherscan/tests/it/blocks.rs
@@ -0,0 +1,28 @@
+use crate::*;
+use ethers_core::types::BlockNumber;
+use serial_test::serial;
+
+#[tokio::test]
+#[serial]
+async fn check_get_block_by_timestamp_before() {
+    run_with_client(Chain::Mainnet, |client| async move {
+        let block_no = client.get_block_by_timestamp(1577836800, "before").await;
+        assert!(block_no.is_ok());
+
+        let block_no = block_no.unwrap().block_number;
+        assert_eq!(block_no, "9193265".parse::<BlockNumber>().unwrap());
+    })
+    .await
+}
+
+#[tokio::test]
+#[serial]
+async fn check_get_block_by_timestamp_after() {
+    run_with_client(Chain::Mainnet, |client| async move {
+        let block_no = client.get_block_by_timestamp(1577836800, "after").await;
+
+        let block_no = block_no.unwrap().block_number;
+        assert_eq!(block_no, "9193266".parse::<BlockNumber>().unwrap());
+    })
+    .await
+}
diff --git a/ethers-etherscan/tests/it/main.rs b/ethers-etherscan/tests/it/main.rs
index e0548705..0eac9aa1 100644
--- a/ethers-etherscan/tests/it/main.rs
+++ b/ethers-etherscan/tests/it/main.rs
@@ -10,6 +10,7 @@ use std::{
 };
 
 mod account;
+mod blocks;
 mod contract;
 mod gas;
 mod transaction;
diff --git a/ethers-providers/tests/it/ws_errors.rs b/ethers-providers/tests/it/ws_errors.rs
index f4eced17..168b24e9 100644
--- a/ethers-providers/tests/it/ws_errors.rs
+++ b/ethers-providers/tests/it/ws_errors.rs
@@ -1,4 +1,4 @@
-use ethers_core::types::Filter;
+use ethers_core::{types::Filter, utils::Anvil};
 use ethers_providers::{Middleware, Provider, StreamExt};
 use futures_util::SinkExt;
 use std::time::Duration;
@@ -64,3 +64,26 @@ async fn graceful_disconnect_on_ws_errors() {
 
     assert!(stream.next().await.is_none());
 }
+
+#[tokio::test]
+async fn resubscribe_on_ws_reconnect() {
+    let anvil = Anvil::new().block_time(1u64).spawn();
+    let port = anvil.port();
+    let provider = Provider::connect_with_reconnects(anvil.ws_endpoint(), 1).await.unwrap();
+
+    // Attempt to ensure a different server-side subscription id after reconnect by making
+    // the subscription we care about be the second one after initial startup, but the first
+    // (and only) one after reconnection.
+    let ignored_sub = provider.subscribe_blocks().await.unwrap();
+    let mut blocks = provider.subscribe_blocks().await.unwrap();
+    ignored_sub.unsubscribe().await.expect("unsubscribe failed");
+
+    blocks.next().await.expect("no block notice before reconnect");
+
+    // Kill & restart using the same port so we end up with the same endpoint url:
+    drop(anvil);
+    let _anvil = Anvil::new().port(port).block_time(1u64).spawn();
+
+    // Wait for the next block on existing subscription. Will fail w/o resubscription:
+    blocks.next().await.expect("no block notice after reconnect");
+}