Refactor: organize ethers-providers (#2159)

* feature: bubble up jsonrpc error response via trait

* refactor: ClientError to TransportError

* refactor: FromErr to MiddlewareError

* tests: fix test with middlewareerror

* fix: doctest

* fix: fix custom middleware example

* feature: as_serde_error

* docs: for error traits

* fix: custom example and unnecessary ref

* refactor: in progress organization

* refactor: continue cleaning up

* refactor: finish changing crate layout

* refactor: fix test imports

* refactor: move convenience impl into toolbox

* chore: changelog

* docs: make them suck less

* fix: remove deprecation

* fix: DaniPopes's nits
This commit is contained in:
James Prestwich 2023-02-20 15:55:36 -08:00 committed by GitHub
parent 33ed94851c
commit ef6e7f41a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2126 additions and 1446 deletions

View File

@ -238,6 +238,7 @@
### Unreleased
- Re-organize the crate. #[2150](https://github.com/gakonst/ethers-rs/pull/2159)
- Convert provider errors to arbitrary middleware errors
[#1920](https://github.com/gakonst/ethers-rs/pull/1920)
- Add a subset of the `admin` namespace

View File

@ -13,7 +13,9 @@ mod eth_tests {
utils::{keccak256, Anvil},
};
use ethers_derive_eip712::*;
use ethers_providers::{Http, Middleware, PendingTransaction, Provider, StreamExt};
use ethers_providers::{
Http, Middleware, MiddlewareError, PendingTransaction, Provider, StreamExt,
};
use ethers_signers::{LocalWallet, Signer};
use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
@ -24,13 +26,20 @@ mod eth_tests {
#[derive(Debug)]
pub struct MwErr<M: Middleware>(M::Error);
impl<M> ethers_providers::FromErr<M::Error> for MwErr<M>
impl<M> MiddlewareError for MwErr<M>
where
M: Middleware,
{
fn from(src: M::Error) -> Self {
type Inner = M::Error;
fn from_err(src: M::Error) -> Self {
Self(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
Some(&self.0)
}
}
impl<M: Middleware> std::fmt::Display for MwErr<M> {

View File

@ -7,7 +7,7 @@ pub use linear::LinearGasPrice;
use async_trait::async_trait;
use ethers_core::types::{BlockId, TransactionRequest, TxHash, U256};
use ethers_providers::{interval, FromErr, Middleware, PendingTransaction, StreamExt};
use ethers_providers::{interval, Middleware, MiddlewareError, PendingTransaction, StreamExt};
use futures_util::lock::Mutex;
use instant::Instant;
use std::{pin::Pin, sync::Arc};
@ -238,10 +238,19 @@ where
}
// Boilerplate
impl<M: Middleware> FromErr<M::Error> for GasEscalatorError<M> {
fn from(src: M::Error) -> GasEscalatorError<M> {
impl<M: Middleware> MiddlewareError for GasEscalatorError<M> {
type Inner = M::Error;
fn from_err(src: M::Error) -> GasEscalatorError<M> {
GasEscalatorError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
GasEscalatorError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
#[derive(Error, Debug)]

View File

@ -1,7 +1,7 @@
use super::{GasOracle, GasOracleError};
use async_trait::async_trait;
use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
use ethers_providers::{FromErr, Middleware, PendingTransaction};
use ethers_providers::{Middleware, MiddlewareError as METrait, PendingTransaction};
use thiserror::Error;
/// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`.
@ -33,10 +33,19 @@ pub enum MiddlewareError<M: Middleware> {
UnsupportedTxType,
}
impl<M: Middleware> FromErr<M::Error> for MiddlewareError<M> {
fn from(src: M::Error) -> MiddlewareError<M> {
impl<M: Middleware> METrait for MiddlewareError<M> {
type Inner = M::Error;
fn from_err(src: M::Error) -> MiddlewareError<M> {
MiddlewareError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
MiddlewareError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@ -86,7 +95,7 @@ where
}
};
self.inner().fill_transaction(tx, block).await.map_err(FromErr::from)
self.inner().fill_transaction(tx, block).await.map_err(METrait::from_err)
}
async fn get_gas_price(&self) -> Result<U256, Self::Error> {

View File

@ -1,6 +1,6 @@
use async_trait::async_trait;
use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
use ethers_providers::{FromErr, Middleware, PendingTransaction};
use ethers_providers::{Middleware, MiddlewareError, PendingTransaction};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use thiserror::Error;
@ -40,7 +40,7 @@ where
.inner
.get_transaction_count(self.address, block)
.await
.map_err(FromErr::from)?;
.map_err(MiddlewareError::from_err)?;
self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
self.initialized.store(true, Ordering::SeqCst);
Ok(nonce)
@ -60,7 +60,7 @@ where
.inner
.get_transaction_count(self.address, block)
.await
.map_err(FromErr::from)?;
.map_err(MiddlewareError::from_err)?;
self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
self.initialized.store(true, Ordering::SeqCst);
}
@ -77,10 +77,18 @@ pub enum NonceManagerError<M: Middleware> {
MiddlewareError(M::Error),
}
impl<M: Middleware> FromErr<M::Error> for NonceManagerError<M> {
fn from(src: M::Error) -> Self {
impl<M: Middleware> MiddlewareError for NonceManagerError<M> {
type Inner = M::Error;
fn from_err(src: M::Error) -> Self {
NonceManagerError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
NonceManagerError::MiddlewareError(e) => Some(e),
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@ -106,7 +114,7 @@ where
tx.set_nonce(self.get_transaction_count_with_manager(block).await?);
}
Ok(self.inner().fill_transaction(tx, block).await.map_err(FromErr::from)?)
Ok(self.inner().fill_transaction(tx, block).await.map_err(MiddlewareError::from_err)?)
}
/// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that
@ -132,10 +140,10 @@ where
// was a nonce mismatch
self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
tx.set_nonce(nonce);
self.inner.send_transaction(tx, block).await.map_err(FromErr::from)
self.inner.send_transaction(tx, block).await.map_err(MiddlewareError::from_err)
} else {
// propagate the error otherwise
Err(FromErr::from(err))
Err(MiddlewareError::from_err(err))
}
}
}

View File

@ -1,5 +1,5 @@
use ethers_core::types::{transaction::eip2718::TypedTransaction, BlockId};
use ethers_providers::{FromErr, Middleware, PendingTransaction};
use ethers_providers::{Middleware, MiddlewareError, PendingTransaction};
use async_trait::async_trait;
use std::fmt::Debug;
@ -52,12 +52,6 @@ pub struct PolicyMiddleware<M, P> {
pub(crate) policy: P,
}
impl<M: Middleware, P: Policy> FromErr<M::Error> for PolicyMiddlewareError<M, P> {
fn from(src: M::Error) -> PolicyMiddlewareError<M, P> {
PolicyMiddlewareError::MiddlewareError(src)
}
}
impl<M, P> PolicyMiddleware<M, P>
where
M: Middleware,
@ -80,6 +74,21 @@ pub enum PolicyMiddlewareError<M: Middleware, P: Policy> {
MiddlewareError(M::Error),
}
impl<M: Middleware, P: Policy> MiddlewareError for PolicyMiddlewareError<M, P> {
type Inner = M::Error;
fn from_err(src: M::Error) -> Self {
PolicyMiddlewareError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
PolicyMiddlewareError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M, P> Middleware for PolicyMiddleware<M, P>

View File

@ -2,7 +2,7 @@ use ethers_core::types::{
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
Address, BlockId, Bytes, Chain, Signature, TransactionRequest, U256,
};
use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction};
use ethers_providers::{maybe, Middleware, MiddlewareError, PendingTransaction};
use ethers_signers::Signer;
use std::convert::TryFrom;
@ -67,12 +67,6 @@ pub struct SignerMiddleware<M, S> {
pub(crate) address: Address,
}
impl<M: Middleware, S: Signer> FromErr<M::Error> for SignerMiddlewareError<M, S> {
fn from(src: M::Error) -> SignerMiddlewareError<M, S> {
SignerMiddlewareError::MiddlewareError(src)
}
}
#[derive(Error, Debug)]
/// Error thrown when the client interacts with the blockchain
pub enum SignerMiddlewareError<M: Middleware, S: Signer> {
@ -101,6 +95,21 @@ pub enum SignerMiddlewareError<M: Middleware, S: Signer> {
DifferentChainID,
}
impl<M: Middleware, S: Signer> MiddlewareError for SignerMiddlewareError<M, S> {
type Inner = M::Error;
fn from_err(src: M::Error) -> Self {
SignerMiddlewareError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
SignerMiddlewareError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
// Helper functions for locally signing transactions
impl<M, S> SignerMiddleware<M, S>
where

View File

@ -6,7 +6,7 @@ use ethers_core::types::{
use std::sync::Arc;
use thiserror::Error;
use ethers_providers::{FromErr, Middleware};
use ethers_providers::{Middleware, MiddlewareError};
type TimeLagResult<T, M> = Result<T, TimeLagError<M>>;
@ -25,12 +25,20 @@ where
}
// Boilerplate
impl<M: Middleware> FromErr<M::Error> for TimeLagError<M> {
fn from(src: M::Error) -> TimeLagError<M> {
impl<M: Middleware> MiddlewareError for TimeLagError<M> {
type Inner = M::Error;
fn from_err(src: M::Error) -> Self {
TimeLagError::MiddlewareError(src)
}
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
TimeLagError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
/// TimeLag Provider
#[derive(Debug)]
pub struct TimeLag<M> {
@ -115,7 +123,7 @@ where
.get_block_number()
.await
.map(|num| num - self.lag)
.map_err(ethers_providers::FromErr::from)
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
@ -123,7 +131,10 @@ where
tx: T,
block: Option<BlockId>,
) -> Result<ethers_providers::PendingTransaction<'_, Self::Provider>, Self::Error> {
self.inner().send_transaction(tx, block).await.map_err(ethers_providers::FromErr::from)
self.inner()
.send_transaction(tx, block)
.await
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_block<T: Into<BlockId> + Send + Sync>(
@ -135,7 +146,10 @@ where
.await?
.expect("Cannot return None if Some is passed in");
self.inner().get_block(block_hash_or_number).await.map_err(ethers_providers::FromErr::from)
self.inner()
.get_block(block_hash_or_number)
.await
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
@ -150,7 +164,7 @@ where
self.inner()
.get_block_with_txs(block_hash_or_number)
.await
.map_err(ethers_providers::FromErr::from)
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
@ -165,7 +179,7 @@ where
self.inner()
.get_uncle_count(block_hash_or_number)
.await
.map_err(ethers_providers::FromErr::from)
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_uncle<T: Into<BlockId> + Send + Sync>(
@ -181,7 +195,7 @@ where
self.inner()
.get_uncle(block_hash_or_number, idx)
.await
.map_err(ethers_providers::FromErr::from)
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
@ -194,7 +208,7 @@ where
self.inner()
.get_transaction_count(from, block)
.await
.map_err(ethers_providers::FromErr::from)
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn call(
@ -204,7 +218,7 @@ where
) -> Result<Bytes, Self::Error> {
let block = self.normalize_block_id(block).await?;
self.inner().call(tx, block).await.map_err(ethers_providers::FromErr::from)
self.inner().call(tx, block).await.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
@ -213,7 +227,10 @@ where
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
let block = self.normalize_block_id(block).await?;
self.inner().get_balance(from, block).await.map_err(ethers_providers::FromErr::from)
self.inner()
.get_balance(from, block)
.await
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
@ -224,7 +241,7 @@ where
.inner()
.get_transaction_receipt(transaction_hash)
.await
.map_err(ethers_providers::FromErr::from)?;
.map_err(ethers_providers::MiddlewareError::from_err)?;
if receipt.is_none() {
return Ok(None)
@ -251,7 +268,7 @@ where
) -> Result<Bytes, Self::Error> {
let block = self.normalize_block_id(block).await?;
self.inner().get_code(at, block).await.map_err(ethers_providers::FromErr::from)
self.inner().get_code(at, block).await.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
@ -264,7 +281,7 @@ where
self.inner()
.get_storage_at(from, location, block)
.await
.map_err(ethers_providers::FromErr::from)
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn fill_transaction(
@ -272,7 +289,10 @@ where
tx: &mut TypedTransaction,
block: Option<BlockId>,
) -> Result<(), Self::Error> {
self.inner().fill_transaction(tx, block).await.map_err(ethers_providers::FromErr::from)
self.inner()
.fill_transaction(tx, block)
.await
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
@ -285,7 +305,10 @@ where
.await?
.expect("Cannot return None if Some is passed in");
self.inner().get_block_receipts(block).await.map_err(ethers_providers::FromErr::from)
self.inner()
.get_block_receipts(block)
.await
.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn get_logs(
@ -295,7 +318,7 @@ where
let mut filter = filter.clone();
filter.block_option = self.normalize_filter_range(filter.block_option).await?;
self.inner().get_logs(&filter).await.map_err(ethers_providers::FromErr::from)
self.inner().get_logs(&filter).await.map_err(ethers_providers::MiddlewareError::from_err)
}
async fn new_filter(

View File

@ -1,7 +1,7 @@
use super::{Transformer, TransformerError};
use async_trait::async_trait;
use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
use ethers_providers::{FromErr, Middleware, PendingTransaction};
use ethers_providers::{Middleware, MiddlewareError, PendingTransaction};
use thiserror::Error;
#[derive(Debug)]
@ -33,10 +33,19 @@ pub enum TransformerMiddlewareError<M: Middleware> {
MiddlewareError(M::Error),
}
impl<M: Middleware> FromErr<M::Error> for TransformerMiddlewareError<M> {
fn from(src: M::Error) -> TransformerMiddlewareError<M> {
impl<M: Middleware> MiddlewareError for TransformerMiddlewareError<M> {
type Inner = M::Error;
fn from_err(src: M::Error) -> Self {
TransformerMiddlewareError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
TransformerMiddlewareError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]

View File

@ -0,0 +1,219 @@
use std::{error::Error, fmt::Debug};
use thiserror::Error;
use crate::JsonRpcError;
/// An `RpcError` is an abstraction over error types returned by a
/// [`crate::JsonRpcClient`].
///
/// All clients can return [`JsonRpcError`] responses, as
/// well as serde deserialization errors. However, because client errors are
/// typically type-erased via the [`ProviderError`], the error info can be
/// difficult to access. This trait provides convenient access to the
/// underlying error types.
///
/// This trait deals only with behavior that is common to all clients.
/// Client-specific errorvariants cannot be accessed via this trait.
pub trait RpcError: Error + Debug + Send + Sync {
/// Access an underlying JSON-RPC error (if any)
///
/// Attempts to access an underlying [`JsonRpcError`]. If the underlying
/// error is not a JSON-RPC error response, this function will return
/// `None`.
fn as_error_response(&self) -> Option<&JsonRpcError>;
/// Returns `true` if the underlying error is a JSON-RPC error response
fn is_error_response(&self) -> bool {
self.as_error_response().is_some()
}
/// Access an underlying `serde_json` error (if any)
///
/// Attempts to access an underlying [`serde_json::Error`]. If the
/// underlying error is not a serde_json error, this function will return
/// `None`.
///
/// ### Implementor's Note
///
/// When writing a stacked [`crate::JsonRpcClient`] abstraction (e.g. a quorum
/// provider or retrying provider), be sure to account for `serde_json`
/// errors at your layer, as well as at lower layers.
fn as_serde_error(&self) -> Option<&serde_json::Error>;
/// Returns `true` if the underlying error is a serde_json (de)serialization
/// error. This method can be used to identify
fn is_serde_error(&self) -> bool {
self.as_serde_error().is_some()
}
}
/// [`MiddlewareError`] is a companion trait to [`crate::Middleware`]. It
/// describes error behavior that is common to all Middleware errors.
///
/// Like [`crate::Middleware`], it allows moving down through layered errors.
///
/// Like [`RpcError`] it exposes convenient accessors to useful underlying
/// error information.
///
///
/// ## Not to Devs:
/// While this trait includes the same methods as [`RpcError`], it is not a
/// supertrait. This is so that 3rd party developers do not need to learn and
/// implement both traits. We provide default methods that delegate to inner
/// middleware errors on the assumption that it will eventually reach a
/// [`ProviderError`], which has correct behavior. This allows Middleware devs
/// to ignore the methods' presence if they want. Middleware are already plenty
/// complicated and we don't need to make it worse :)
pub trait MiddlewareError: Error + Sized + Send + Sync {
/// The `Inner` type is the next lower middleware layer's error type.
type Inner: MiddlewareError;
/// Convert the next lower middleware layer's error to this layer's error
fn from_err(e: Self::Inner) -> Self;
/// Attempt to convert this error to the next lower middleware's error.
/// Conversion fails if the error is not from an inner layer (i.e. the
/// error originates at this middleware layer)
fn as_inner(&self) -> Option<&Self::Inner>;
/// Returns `true` if the underlying error stems from a lower middleware
/// layer
fn is_inner(&self) -> bool {
self.as_inner().is_some()
}
/// Access an underlying `serde_json` error (if any)
///
/// Attempts to access an underlying [`serde_json::Error`]. If the
/// underlying error is not a serde_json error, this function will return
/// `None`.
///
/// ### Implementor's Note:
///
/// When writing a custom middleware, if your middleware uses `serde_json`
/// we recommend a custom implementation of this method. It should first
/// check your Middleware's error for local `serde_json` errors, and then
/// delegate to inner if none is found. Failing to implement this method may
/// result in missed `serde_json` errors.
fn as_serde_error(&self) -> Option<&serde_json::Error> {
self.as_inner()?.as_serde_error()
}
/// Returns `true` if the underlying error is a serde_json (de)serialization
/// error. This method can be used to identify
fn is_serde_error(&self) -> bool {
self.as_serde_error().is_some()
}
/// Attempts to access an underlying [`ProviderError`], usually by
/// traversing the entire middleware stack. Access fails if the underlying
/// error is not a [`ProviderError`]
fn as_provider_error(&self) -> Option<&ProviderError> {
self.as_inner()?.as_provider_error()
}
/// Convert a [`ProviderError`] to this type, by successively wrapping it
/// in the error types of all lower middleware
fn from_provider_err(p: ProviderError) -> Self {
Self::from_err(Self::Inner::from_provider_err(p))
}
/// Access an underlying JSON-RPC error (if any)
///
/// Attempts to access an underlying [`JsonRpcError`]. If the underlying
/// error is not a JSON-RPC error response, this function will return
/// `None`.
fn as_error_response(&self) -> Option<&JsonRpcError> {
self.as_inner()?.as_error_response()
}
/// Returns `true` if the underlying error is a JSON-RPC error response
fn is_error_response(&self) -> bool {
self.as_error_response().is_some()
}
}
#[derive(Debug, Error)]
/// An error thrown when making a call to the provider
pub enum ProviderError {
/// An internal error in the JSON RPC Client
#[error("{0}")]
JsonRpcClientError(Box<dyn crate::RpcError + Send + Sync>),
/// An error during ENS name resolution
#[error("ens name not found: {0}")]
EnsError(String),
/// Invalid reverse ENS name
#[error("reverse ens name not pointing to itself: {0}")]
EnsNotOwned(String),
/// Error in underlying lib `serde_json`
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
/// Error in underlying lib `hex`
#[error(transparent)]
HexError(#[from] hex::FromHexError),
/// Error in underlying lib `reqwest`
#[error(transparent)]
HTTPError(#[from] reqwest::Error),
/// Custom error from unknown source
#[error("custom error: {0}")]
CustomError(String),
/// RPC method is not supported by this provider
#[error("unsupported RPC")]
UnsupportedRPC,
/// Node is not supported by this provider
#[error("unsupported node client")]
UnsupportedNodeClient,
/// Signer is not available to this provider.
#[error("Attempted to sign a transaction with no available signer. Hint: did you mean to use a SignerMiddleware?")]
SignerUnavailable,
}
impl RpcError for ProviderError {
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
if let ProviderError::JsonRpcClientError(err) = self {
err.as_error_response()
} else {
None
}
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
ProviderError::JsonRpcClientError(e) => e.as_serde_error(),
ProviderError::SerdeJson(e) => Some(e),
_ => None,
}
}
}
// Do not change these implementations, they are critical to proper middleware
// error stack behavior.
impl MiddlewareError for ProviderError {
type Inner = Self;
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
RpcError::as_error_response(self)
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
RpcError::as_serde_error(self)
}
fn from_err(e: Self::Inner) -> Self {
e
}
fn as_inner(&self) -> Option<&Self::Inner> {
// prevents infinite loops
None
}
}

View File

@ -1,6 +1,8 @@
use crate::{H256, U256};
use enr::{k256::ecdsa::SigningKey, Enr};
use ethers_core::utils::{from_int_or_hex, ChainConfig};
use ethers_core::{
types::{H256, U256},
utils::{from_int_or_hex, ChainConfig},
};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, SocketAddr};
@ -49,9 +51,11 @@ pub struct Ports {
/// This contains protocol information reported by the connected RPC node.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ProtocolInfo {
/// Details about the node's supported eth protocol. `None` if unsupported
#[serde(default, skip_serializing_if = "Option::is_none")]
pub eth: Option<EthProtocolInfo>,
/// Details about the node's supported snap protocol. `None` if unsupported
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snap: Option<SnapProtocolInfo>,
}
@ -94,9 +98,11 @@ pub struct SnapProtocolInfo {}
/// connected RPC node.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PeerProtocolInfo {
/// Details about the peer's supported eth protocol. `None` if unsupported
#[serde(default, skip_serializing_if = "Option::is_none")]
pub eth: Option<EthPeerInfo>,
/// Details about the peer's supported snap protocol. `None` if unsupported
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snap: Option<SnapPeerInfo>,
}

View File

@ -0,0 +1,193 @@
//! A middleware supporting development-specific JSON RPC methods
//!
//! # Example
//!
//!```
//! use ethers_providers::{Provider, Http, Middleware, DevRpcMiddleware};
//! use ethers_core::types::TransactionRequest;
//! use ethers_core::utils::Anvil;
//! use std::convert::TryFrom;
//!
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let anvil = Anvil::new().spawn();
//! let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
//! let client = DevRpcMiddleware::new(provider);
//!
//! // snapshot the initial state
//! let block0 = client.get_block_number().await.unwrap();
//! let snap_id = client.snapshot().await.unwrap();
//!
//! // send a transaction
//! let accounts = client.get_accounts().await?;
//! let from = accounts[0];
//! let to = accounts[1];
//! let balance_before = client.get_balance(to, None).await?;
//! let tx = TransactionRequest::new().to(to).value(1000).from(from);
//! client.send_transaction(tx, None).await?.await?;
//! let balance_after = client.get_balance(to, None).await?;
//! assert_eq!(balance_after, balance_before + 1000);
//!
//! // revert to snapshot
//! client.revert_to_snapshot(snap_id).await.unwrap();
//! let balance_after_revert = client.get_balance(to, None).await?;
//! assert_eq!(balance_after_revert, balance_before);
//! # Ok(())
//! # }
//! ```
use crate::{Middleware, MiddlewareError, ProviderError};
use async_trait::async_trait;
use ethers_core::types::U256;
use thiserror::Error;
use std::fmt::Debug;
/// `DevRpcMiddleware`
#[derive(Clone, Debug)]
pub struct DevRpcMiddleware<M>(M);
/// DevRpcMiddleware Errors
#[derive(Error, Debug)]
pub enum DevRpcMiddlewareError<M: Middleware> {
/// Internal Middleware error
#[error("{0}")]
MiddlewareError(M::Error),
/// Internal Provider error
#[error("{0}")]
ProviderError(ProviderError),
/// Attempted to revert to unavailable snapshot
#[error("Could not revert to snapshot")]
NoSnapshot,
}
#[async_trait]
impl<M: Middleware> Middleware for DevRpcMiddleware<M> {
type Error = DevRpcMiddlewareError<M>;
type Provider = M::Provider;
type Inner = M;
fn inner(&self) -> &M {
&self.0
}
}
impl<M: Middleware> MiddlewareError for DevRpcMiddlewareError<M> {
type Inner = M::Error;
fn from_err(src: M::Error) -> DevRpcMiddlewareError<M> {
DevRpcMiddlewareError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
DevRpcMiddlewareError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
impl<M> From<ProviderError> for DevRpcMiddlewareError<M>
where
M: Middleware,
{
fn from(src: ProviderError) -> Self {
Self::ProviderError(src)
}
}
impl<M: Middleware> DevRpcMiddleware<M> {
/// Instantiate a new `DevRpcMiddleware`
pub fn new(inner: M) -> Self {
Self(inner)
}
/// Create a new snapshot on the DevRpc node. Return the Snapshot ID
///
/// ### Note
///
/// Ganache, Hardhat and Anvil increment snapshot ID even if no state has changed
pub async fn snapshot(&self) -> Result<U256, DevRpcMiddlewareError<M>> {
self.provider().request::<(), U256>("evm_snapshot", ()).await.map_err(From::from)
}
/// Revert the state of the DevRpc node to the Snapshot, specified by its ID
pub async fn revert_to_snapshot(&self, id: U256) -> Result<(), DevRpcMiddlewareError<M>> {
let ok = self
.provider()
.request::<[U256; 1], bool>("evm_revert", [id])
.await
.map_err(DevRpcMiddlewareError::ProviderError)?;
if ok {
Ok(())
} else {
Err(DevRpcMiddlewareError::NoSnapshot)
}
}
}
#[cfg(test)]
// Celo blocks can not get parsed when used with Ganache
#[cfg(not(feature = "celo"))]
mod tests {
use super::*;
use crate::{Http, Provider};
use ethers_core::utils::Anvil;
use std::convert::TryFrom;
#[tokio::test]
async fn test_snapshot() {
let anvil = Anvil::new().spawn();
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
let client = DevRpcMiddleware::new(provider);
// snapshot initial state
let block0 = client.get_block_number().await.unwrap();
let time0 = client.get_block(block0).await.unwrap().unwrap().timestamp;
let snap_id0 = client.snapshot().await.unwrap();
// mine a new block
client.provider().mine(1).await.unwrap();
// snapshot state
let block1 = client.get_block_number().await.unwrap();
let time1 = client.get_block(block1).await.unwrap().unwrap().timestamp;
let snap_id1 = client.snapshot().await.unwrap();
// mine some blocks
client.provider().mine(5).await.unwrap();
// snapshot state
let block2 = client.get_block_number().await.unwrap();
let time2 = client.get_block(block2).await.unwrap().unwrap().timestamp;
let snap_id2 = client.snapshot().await.unwrap();
// mine some blocks
client.provider().mine(5).await.unwrap();
// revert_to_snapshot should reset state to snap id
client.revert_to_snapshot(snap_id2).await.unwrap();
let block = client.get_block_number().await.unwrap();
let time = client.get_block(block).await.unwrap().unwrap().timestamp;
assert_eq!(block, block2);
assert_eq!(time, time2);
client.revert_to_snapshot(snap_id1).await.unwrap();
let block = client.get_block_number().await.unwrap();
let time = client.get_block(block).await.unwrap().unwrap().timestamp;
assert_eq!(block, block1);
assert_eq!(time, time1);
// revert_to_snapshot should throw given non-existent or
// previously used snapshot
let result = client.revert_to_snapshot(snap_id1).await;
assert!(result.is_err());
client.revert_to_snapshot(snap_id0).await.unwrap();
let block = client.get_block_number().await.unwrap();
let time = client.get_block(block).await.unwrap().unwrap().timestamp;
assert_eq!(block, block0);
assert_eq!(time, time0);
}
}

View File

@ -15,8 +15,11 @@ const IPFS_GATEWAY: &str = "https://ipfs.io/ipfs/";
/// An ERC 721 or 1155 token
pub struct ERCNFT {
/// Type of the NFT
pub type_: ERCNFTType,
/// Address of the NFT contract
pub contract: Address,
/// NFT ID in that contract
pub id: [u8; 32],
}
@ -56,7 +59,9 @@ impl FromStr for ERCNFT {
/// Supported ERCs
#[derive(PartialEq, Eq)]
pub enum ERCNFTType {
/// ERC721
ERC721,
/// ERC1155
ERC1155,
}
@ -72,7 +77,8 @@ impl FromStr for ERCNFTType {
}
impl ERCNFTType {
pub fn resolution_selector(&self) -> Selector {
/// Get the method selector
pub const fn resolution_selector(&self) -> Selector {
match self {
// tokenURI(uint256)
ERCNFTType::ERC721 => [0xc8, 0x7b, 0x56, 0xdd],
@ -85,6 +91,7 @@ impl ERCNFTType {
/// ERC-1155 and ERC-721 metadata document.
#[derive(Deserialize)]
pub struct Metadata {
/// The URL of the image for the NFT
pub image: String,
}

View File

@ -0,0 +1,13 @@
/// Types for the admin api
pub mod admin;
pub use admin::{NodeInfo, PeerInfo};
pub mod ens;
pub use ens::*;
pub mod erc;
#[cfg(feature = "dev-rpc")]
pub mod dev_rpc;
#[cfg(feature = "dev-rpc")]
pub use dev_rpc::{DevRpcMiddleware, DevRpcMiddlewareError};

View File

@ -3,772 +3,40 @@
#![deny(rustdoc::broken_intra_doc_links)]
#![allow(clippy::type_complexity)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
mod transports;
pub use transports::*;
mod ext;
pub use ext::*;
mod provider;
pub use provider::{is_local_endpoint, FilterKind, Provider, ProviderError, ProviderExt};
mod rpc;
pub use rpc::*;
// types for the admin api
pub mod admin;
pub use admin::{NodeInfo, PeerInfo};
mod toolbox;
pub use toolbox::*;
// ENS support
pub mod ens;
/// Crate utilities and type aliases
mod utils;
pub use utils::{interval, maybe, EscalationPolicy};
mod pending_transaction;
pub use pending_transaction::PendingTransaction;
mod pending_escalator;
pub use pending_escalator::EscalatingPending;
mod log_query;
pub use log_query::{LogQuery, LogQueryError};
/// Errors
mod errors;
pub use errors::{MiddlewareError, ProviderError, RpcError};
mod stream;
pub use futures_util::StreamExt;
pub use stream::{
interval, FilterWatcher, TransactionStream, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL,
tx_stream::TransactionStream, FilterWatcher, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL,
};
mod pubsub;
pub use pubsub::{PubsubClient, SubscriptionStream};
pub mod call_raw;
pub mod erc;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers_core::types::{
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
*,
};
use futures_util::future::join_all;
use serde::{de::DeserializeOwned, Serialize};
use std::{error::Error, fmt::Debug, future::Future, pin::Pin};
use url::Url;
// feature-enabled support for dev-rpc methods
#[cfg(feature = "dev-rpc")]
pub use provider::dev_rpc::DevRpcMiddleware;
/// A simple gas escalation policy
pub type EscalationPolicy = Box<dyn Fn(U256, usize) -> U256 + Send + Sync>;
// Helper type alias
#[cfg(target_arch = "wasm32")]
pub(crate) type PinBoxFut<'a, T> = Pin<Box<dyn Future<Output = Result<T, ProviderError>> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) type PinBoxFut<'a, T> =
Pin<Box<dyn Future<Output = Result<T, ProviderError>> + Send + 'a>>;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&, Box, Arc)]
/// Trait which must be implemented by data transports to be used with the Ethereum
/// JSON-RPC provider.
pub trait JsonRpcClient: Debug + Send + Sync {
/// A JSON-RPC Error
type Error: Error + Into<ProviderError>;
/// Sends a request with the provided JSON-RPC and parameters serialized as JSON
async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
where
T: Debug + Serialize + Send + Sync,
R: DeserializeOwned + Send;
}
pub trait FromErr<T> {
fn from(src: T) -> Self;
}
/// Calls the future if `item` is None, otherwise returns a `futures::ok`
pub async fn maybe<F, T, E>(item: Option<T>, f: F) -> Result<T, E>
where
F: Future<Output = Result<T, E>>,
{
if let Some(item) = item {
futures_util::future::ok(item).await
} else {
f.await
}
}
/// A middleware allows customizing requests send and received from an ethereum node.
///
/// Writing a middleware is as simple as:
/// 1. implementing the [`inner`](crate::Middleware::inner) method to point to the next layer in the
/// "middleware onion", 2. implementing the [`FromErr`](crate::FromErr) trait on your middleware's
/// error type 3. implementing any of the methods you want to override
///
/// ```rust
/// use ethers_providers::{Middleware, FromErr};
/// use ethers_core::types::{U64, TransactionRequest, U256, transaction::eip2718::TypedTransaction, BlockId};
/// use thiserror::Error;
/// use async_trait::async_trait;
///
/// #[derive(Debug)]
/// struct MyMiddleware<M>(M);
///
/// #[derive(Error, Debug)]
/// pub enum MyError<M: Middleware> {
/// #[error("{0}")]
/// MiddlewareError(M::Error),
///
/// // Add your middleware's specific errors here
/// }
///
/// impl<M: Middleware> FromErr<M::Error> for MyError<M> {
/// fn from(src: M::Error) -> MyError<M> {
/// MyError::MiddlewareError(src)
/// }
/// }
///
/// #[async_trait]
/// impl<M> Middleware for MyMiddleware<M>
/// where
/// M: Middleware,
/// {
/// type Error = MyError<M>;
/// type Provider = M::Provider;
/// type Inner = M;
///
/// fn inner(&self) -> &M {
/// &self.0
/// }
///
/// /// Overrides the default `get_block_number` method to always return 0
/// async fn get_block_number(&self) -> Result<U64, Self::Error> {
/// Ok(U64::zero())
/// }
///
/// /// Overrides the default `estimate_gas` method to log that it was called,
/// /// before forwarding the call to the next layer.
/// async fn estimate_gas(&self, tx: &TypedTransaction, block: Option<BlockId>) -> Result<U256, Self::Error> {
/// println!("Estimating gas...");
/// self.inner().estimate_gas(tx, block).await.map_err(FromErr::from)
/// }
/// }
/// ```
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&, Box, Arc)]
pub trait Middleware: Sync + Send + Debug {
type Error: Sync + Send + Error + FromErr<<Self::Inner as Middleware>::Error>;
type Provider: JsonRpcClient;
type Inner: Middleware<Provider = Self::Provider>;
/// The next middleware in the stack
fn inner(&self) -> &Self::Inner;
/// Convert a provider error into the associated error type by successively
/// converting it to every intermediate middleware error
fn convert_err(p: ProviderError) -> Self::Error {
let e = <Self as Middleware>::Inner::convert_err(p);
FromErr::from(e)
}
/// The HTTP or Websocket provider.
fn provider(&self) -> &Provider<Self::Provider> {
self.inner().provider()
}
fn default_sender(&self) -> Option<Address> {
self.inner().default_sender()
}
async fn client_version(&self) -> Result<String, Self::Error> {
self.inner().client_version().await.map_err(FromErr::from)
}
/// Fill necessary details of a transaction for dispatch
///
/// This function is defined on providers to behave as follows:
/// 1. populate the `from` field with the default sender
/// 2. resolve any ENS names in the tx `to` field
/// 3. Estimate gas usage
/// 4. Poll and set legacy or 1559 gas prices
/// 5. Set the chain_id with the provider's, if not already set
///
/// It does NOT set the nonce by default.
///
/// Middleware are encouraged to override any values _before_ delegating
/// to the inner implementation AND/OR modify the values provided by the
/// default implementation _after_ delegating.
///
/// E.g. a middleware wanting to double gas prices should consider doing so
/// _after_ delegating and allowing the default implementation to poll gas.
async fn fill_transaction(
&self,
tx: &mut TypedTransaction,
block: Option<BlockId>,
) -> Result<(), Self::Error> {
self.inner().fill_transaction(tx, block).await.map_err(FromErr::from)
}
async fn get_block_number(&self) -> Result<U64, Self::Error> {
self.inner().get_block_number().await.map_err(FromErr::from)
}
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
self.inner().send_transaction(tx, block).await.map_err(FromErr::from)
}
/// Send a transaction with a simple escalation policy.
///
/// `policy` should be a boxed function that maps `original_gas_price`
/// and `number_of_previous_escalations` -> `new_gas_price`.
///
/// e.g. `Box::new(|start, escalation_index| start * 1250.pow(escalations) /
/// 1000.pow(escalations))`
async fn send_escalating<'a>(
&'a self,
tx: &TypedTransaction,
escalations: usize,
policy: EscalationPolicy,
) -> Result<EscalatingPending<'a, Self::Provider>, Self::Error> {
let mut original = tx.clone();
self.fill_transaction(&mut original, None).await?;
// set the nonce, if no nonce is found
if original.nonce().is_none() {
let nonce =
self.get_transaction_count(tx.from().copied().unwrap_or_default(), None).await?;
original.set_nonce(nonce);
}
let gas_price = original.gas_price().expect("filled");
let sign_futs: Vec<_> = (0..escalations)
.map(|i| {
let new_price = policy(gas_price, i);
let mut r = original.clone();
r.set_gas_price(new_price);
r
})
.map(|req| async move {
self.sign_transaction(&req, self.default_sender().unwrap_or_default())
.await
.map(|sig| req.rlp_signed(&sig))
})
.collect();
// we reverse for convenience. Ensuring that we can always just
// `pop()` the next tx off the back later
let mut signed = join_all(sign_futs).await.into_iter().collect::<Result<Vec<_>, _>>()?;
signed.reverse();
Ok(EscalatingPending::new(self.provider(), signed))
}
async fn resolve_name(&self, ens_name: &str) -> Result<Address, Self::Error> {
self.inner().resolve_name(ens_name).await.map_err(FromErr::from)
}
async fn lookup_address(&self, address: Address) -> Result<String, Self::Error> {
self.inner().lookup_address(address).await.map_err(FromErr::from)
}
async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, Self::Error> {
self.inner().resolve_avatar(ens_name).await.map_err(FromErr::from)
}
async fn resolve_nft(&self, token: erc::ERCNFT) -> Result<Url, Self::Error> {
self.inner().resolve_nft(token).await.map_err(FromErr::from)
}
async fn resolve_field(&self, ens_name: &str, field: &str) -> Result<String, Self::Error> {
self.inner().resolve_field(ens_name, field).await.map_err(FromErr::from)
}
async fn get_block<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
) -> Result<Option<Block<TxHash>>, Self::Error> {
self.inner().get_block(block_hash_or_number).await.map_err(FromErr::from)
}
async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
) -> Result<Option<Block<Transaction>>, Self::Error> {
self.inner().get_block_with_txs(block_hash_or_number).await.map_err(FromErr::from)
}
async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
) -> Result<U256, Self::Error> {
self.inner().get_uncle_count(block_hash_or_number).await.map_err(FromErr::from)
}
async fn get_uncle<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
idx: U64,
) -> Result<Option<Block<H256>>, Self::Error> {
self.inner().get_uncle(block_hash_or_number, idx).await.map_err(FromErr::from)
}
async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner().get_transaction_count(from, block).await.map_err(FromErr::from)
}
async fn estimate_gas(
&self,
tx: &TypedTransaction,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner().estimate_gas(tx, block).await.map_err(FromErr::from)
}
async fn call(
&self,
tx: &TypedTransaction,
block: Option<BlockId>,
) -> Result<Bytes, Self::Error> {
self.inner().call(tx, block).await.map_err(FromErr::from)
}
async fn syncing(&self) -> Result<SyncingStatus, Self::Error> {
self.inner().syncing().await.map_err(FromErr::from)
}
async fn get_chainid(&self) -> Result<U256, Self::Error> {
self.inner().get_chainid().await.map_err(FromErr::from)
}
async fn get_net_version(&self) -> Result<String, Self::Error> {
self.inner().get_net_version().await.map_err(FromErr::from)
}
async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner().get_balance(from, block).await.map_err(FromErr::from)
}
async fn get_transaction<T: Send + Sync + Into<TxHash>>(
&self,
transaction_hash: T,
) -> Result<Option<Transaction>, Self::Error> {
self.inner().get_transaction(transaction_hash).await.map_err(FromErr::from)
}
async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
&self,
transaction_hash: T,
) -> Result<Option<TransactionReceipt>, Self::Error> {
self.inner().get_transaction_receipt(transaction_hash).await.map_err(FromErr::from)
}
async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
&self,
block: T,
) -> Result<Vec<TransactionReceipt>, Self::Error> {
self.inner().get_block_receipts(block).await.map_err(FromErr::from)
}
async fn get_gas_price(&self) -> Result<U256, Self::Error> {
self.inner().get_gas_price().await.map_err(FromErr::from)
}
async fn estimate_eip1559_fees(
&self,
estimator: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>,
) -> Result<(U256, U256), Self::Error> {
self.inner().estimate_eip1559_fees(estimator).await.map_err(FromErr::from)
}
async fn get_accounts(&self) -> Result<Vec<Address>, Self::Error> {
self.inner().get_accounts().await.map_err(FromErr::from)
}
async fn send_raw_transaction<'a>(
&'a self,
tx: Bytes,
) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
self.inner().send_raw_transaction(tx).await.map_err(FromErr::from)
}
/// This returns true if either the middleware stack contains a `SignerMiddleware`, or the
/// JSON-RPC provider has an unlocked key that can sign using the `eth_sign` call. If none of
/// the above conditions are met, then the middleware stack is not capable of signing data.
async fn is_signer(&self) -> bool {
self.inner().is_signer().await
}
async fn sign<T: Into<Bytes> + Send + Sync>(
&self,
data: T,
from: &Address,
) -> Result<Signature, Self::Error> {
self.inner().sign(data, from).await.map_err(FromErr::from)
}
/// Sign a transaction via RPC call
async fn sign_transaction(
&self,
tx: &TypedTransaction,
from: Address,
) -> Result<Signature, Self::Error> {
self.inner().sign_transaction(tx, from).await.map_err(FromErr::from)
}
////// Contract state
async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, Self::Error> {
self.inner().get_logs(filter).await.map_err(FromErr::from)
}
/// Returns a stream of logs are loaded in pages of given page size
fn get_logs_paginated<'a>(
&'a self,
filter: &Filter,
page_size: u64,
) -> LogQuery<'a, Self::Provider> {
self.inner().get_logs_paginated(filter, page_size)
}
async fn new_filter(&self, filter: FilterKind<'_>) -> Result<U256, Self::Error> {
self.inner().new_filter(filter).await.map_err(FromErr::from)
}
async fn uninstall_filter<T: Into<U256> + Send + Sync>(
&self,
id: T,
) -> Result<bool, Self::Error> {
self.inner().uninstall_filter(id).await.map_err(FromErr::from)
}
async fn watch<'a>(
&'a self,
filter: &Filter,
) -> Result<FilterWatcher<'a, Self::Provider, Log>, Self::Error> {
self.inner().watch(filter).await.map_err(FromErr::from)
}
async fn watch_pending_transactions(
&self,
) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
self.inner().watch_pending_transactions().await.map_err(FromErr::from)
}
async fn get_filter_changes<T, R>(&self, id: T) -> Result<Vec<R>, Self::Error>
where
T: Into<U256> + Send + Sync,
R: Serialize + DeserializeOwned + Send + Sync + Debug,
{
self.inner().get_filter_changes(id).await.map_err(FromErr::from)
}
async fn watch_blocks(&self) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
self.inner().watch_blocks().await.map_err(FromErr::from)
}
async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
&self,
at: T,
block: Option<BlockId>,
) -> Result<Bytes, Self::Error> {
self.inner().get_code(at, block).await.map_err(FromErr::from)
}
async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
location: H256,
block: Option<BlockId>,
) -> Result<H256, Self::Error> {
self.inner().get_storage_at(from, location, block).await.map_err(FromErr::from)
}
async fn get_proof<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
locations: Vec<H256>,
block: Option<BlockId>,
) -> Result<EIP1186ProofResponse, Self::Error> {
self.inner().get_proof(from, locations, block).await.map_err(FromErr::from)
}
/// Returns an indication if this node is currently mining.
async fn mining(&self) -> Result<bool, Self::Error> {
self.inner().mining().await.map_err(FromErr::from)
}
// Personal namespace
async fn import_raw_key(
&self,
private_key: Bytes,
passphrase: String,
) -> Result<Address, Self::Error> {
self.inner().import_raw_key(private_key, passphrase).await.map_err(FromErr::from)
}
async fn unlock_account<T: Into<Address> + Send + Sync>(
&self,
account: T,
passphrase: String,
duration: Option<u64>,
) -> Result<bool, Self::Error> {
self.inner().unlock_account(account, passphrase, duration).await.map_err(FromErr::from)
}
// Admin namespace
async fn add_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().add_peer(enode_url).await.map_err(FromErr::from)
}
async fn add_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().add_trusted_peer(enode_url).await.map_err(FromErr::from)
}
async fn node_info(&self) -> Result<NodeInfo, Self::Error> {
self.inner().node_info().await.map_err(FromErr::from)
}
async fn peers(&self) -> Result<Vec<PeerInfo>, Self::Error> {
self.inner().peers().await.map_err(FromErr::from)
}
async fn remove_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().remove_peer(enode_url).await.map_err(FromErr::from)
}
async fn remove_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().remove_trusted_peer(enode_url).await.map_err(FromErr::from)
}
// Miner namespace
/// Starts the miner with the given number of threads. If threads is nil, the number of workers
/// started is equal to the number of logical CPUs that are usable by this process. If mining
/// is already running, this method adjust the number of threads allowed to use and updates the
/// minimum price required by the transaction pool.
async fn start_mining(&self, threads: Option<usize>) -> Result<(), Self::Error> {
self.inner().start_mining(threads).await.map_err(FromErr::from)
}
/// Stop terminates the miner, both at the consensus engine level as well as at
/// the block creation level.
async fn stop_mining(&self) -> Result<(), Self::Error> {
self.inner().stop_mining().await.map_err(FromErr::from)
}
// Mempool inspection for Geth's API
async fn txpool_content(&self) -> Result<TxpoolContent, Self::Error> {
self.inner().txpool_content().await.map_err(FromErr::from)
}
async fn txpool_inspect(&self) -> Result<TxpoolInspect, Self::Error> {
self.inner().txpool_inspect().await.map_err(FromErr::from)
}
async fn txpool_status(&self) -> Result<TxpoolStatus, Self::Error> {
self.inner().txpool_status().await.map_err(FromErr::from)
}
// Geth `trace` support
/// After replaying any previous transactions in the same block,
/// Replays a transaction, returning the traces configured with passed options
async fn debug_trace_transaction(
&self,
tx_hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> Result<GethTrace, Self::Error> {
self.inner().debug_trace_transaction(tx_hash, trace_options).await.map_err(FromErr::from)
}
/// Executes the given call and returns a number of possible traces for it
async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: T,
block: Option<BlockId>,
trace_options: GethDebugTracingCallOptions,
) -> Result<GethTrace, Self::Error> {
self.inner().debug_trace_call(req, block, trace_options).await.map_err(FromErr::from)
}
// Parity `trace` support
/// Executes the given call and returns a number of possible traces for it
async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: T,
trace_type: Vec<TraceType>,
block: Option<BlockNumber>,
) -> Result<BlockTrace, Self::Error> {
self.inner().trace_call(req, trace_type, block).await.map_err(FromErr::from)
}
async fn trace_call_many<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: Vec<(T, Vec<TraceType>)>,
block: Option<BlockNumber>,
) -> Result<Vec<BlockTrace>, Self::Error> {
self.inner().trace_call_many(req, block).await.map_err(FromErr::from)
}
/// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces
async fn trace_raw_transaction(
&self,
data: Bytes,
trace_type: Vec<TraceType>,
) -> Result<BlockTrace, Self::Error> {
self.inner().trace_raw_transaction(data, trace_type).await.map_err(FromErr::from)
}
/// Replays a transaction, returning the traces
async fn trace_replay_transaction(
&self,
hash: H256,
trace_type: Vec<TraceType>,
) -> Result<BlockTrace, Self::Error> {
self.inner().trace_replay_transaction(hash, trace_type).await.map_err(FromErr::from)
}
/// Replays all transactions in a block returning the requested traces for each transaction
async fn trace_replay_block_transactions(
&self,
block: BlockNumber,
trace_type: Vec<TraceType>,
) -> Result<Vec<BlockTrace>, Self::Error> {
self.inner().trace_replay_block_transactions(block, trace_type).await.map_err(FromErr::from)
}
/// Returns traces created at given block
async fn trace_block(&self, block: BlockNumber) -> Result<Vec<Trace>, Self::Error> {
self.inner().trace_block(block).await.map_err(FromErr::from)
}
/// Return traces matching the given filter
async fn trace_filter(&self, filter: TraceFilter) -> Result<Vec<Trace>, Self::Error> {
self.inner().trace_filter(filter).await.map_err(FromErr::from)
}
/// Returns trace at the given position
async fn trace_get<T: Into<U64> + Send + Sync>(
&self,
hash: H256,
index: Vec<T>,
) -> Result<Trace, Self::Error> {
self.inner().trace_get(hash, index).await.map_err(FromErr::from)
}
/// Returns all traces of a given transaction
async fn trace_transaction(&self, hash: H256) -> Result<Vec<Trace>, Self::Error> {
self.inner().trace_transaction(hash).await.map_err(FromErr::from)
}
// Parity namespace
/// Returns all receipts for that block. Must be done on a parity node.
async fn parity_block_receipts<T: Into<BlockNumber> + Send + Sync>(
&self,
block: T,
) -> Result<Vec<TransactionReceipt>, Self::Error> {
self.inner().parity_block_receipts(block).await.map_err(FromErr::from)
}
async fn subscribe<T, R>(
&self,
params: T,
) -> Result<SubscriptionStream<'_, Self::Provider, R>, Self::Error>
where
T: Debug + Serialize + Send + Sync,
R: DeserializeOwned + Send + Sync,
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe(params).await.map_err(FromErr::from)
}
async fn unsubscribe<T>(&self, id: T) -> Result<bool, Self::Error>
where
T: Into<U256> + Send + Sync,
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().unsubscribe(id).await.map_err(FromErr::from)
}
async fn subscribe_blocks(
&self,
) -> Result<SubscriptionStream<'_, Self::Provider, Block<TxHash>>, Self::Error>
where
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe_blocks().await.map_err(FromErr::from)
}
async fn subscribe_pending_txs(
&self,
) -> Result<SubscriptionStream<'_, Self::Provider, TxHash>, Self::Error>
where
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe_pending_txs().await.map_err(FromErr::from)
}
async fn subscribe_logs<'a>(
&'a self,
filter: &Filter,
) -> Result<SubscriptionStream<'a, Self::Provider, Log>, Self::Error>
where
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe_logs(filter).await.map_err(FromErr::from)
}
async fn fee_history<T: Into<U256> + serde::Serialize + Send + Sync>(
&self,
block_count: T,
last_block: BlockNumber,
reward_percentiles: &[f64],
) -> Result<FeeHistory, Self::Error> {
self.inner()
.fee_history(block_count, last_block, reward_percentiles)
.await
.map_err(FromErr::from)
}
async fn create_access_list(
&self,
tx: &TypedTransaction,
block: Option<BlockId>,
) -> Result<AccessListWithGasUsed, Self::Error> {
self.inner().create_access_list(tx, block).await.map_err(FromErr::from)
}
}
mod middleware;
#[cfg(feature = "celo")]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait CeloMiddleware: Middleware {
async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
&self,
block_id: T,
) -> Result<Vec<String>, ProviderError> {
self.provider().get_validators_bls_public_keys(block_id).await.map_err(FromErr::from)
}
}
pub use middleware::CeloMiddleware;
pub use middleware::Middleware;
#[allow(deprecated)]
pub use test_provider::{GOERLI, MAINNET, ROPSTEN, SEPOLIA};
#[allow(missing_docs)]
/// Pre-instantiated Infura HTTP clients which rotate through multiple API keys
/// to prevent rate limits
pub mod test_provider {

View File

@ -0,0 +1,962 @@
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers_core::types::{
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
*,
};
use futures_util::future::join_all;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use url::Url;
use crate::{
erc, EscalatingPending, EscalationPolicy, FilterKind, FilterWatcher, JsonRpcClient, LogQuery,
MiddlewareError, NodeInfo, PeerInfo, PendingTransaction, Provider, ProviderError, PubsubClient,
SubscriptionStream,
};
/// A middleware allows customizing requests send and received from an ethereum node.
///
/// Writing a middleware is as simple as:
/// 1. implementing the [`inner`](crate::Middleware::inner) method to point to the next layer in the
/// "middleware onion", 2. implementing the
/// [`MiddlewareError`](crate::MiddlewareError) trait on your middleware's
/// error type 3. implementing any of the methods you want to override
///
/// ```
/// use ethers_providers::{Middleware, MiddlewareError};
/// use ethers_core::types::{U64, TransactionRequest, U256, transaction::eip2718::TypedTransaction, BlockId};
/// use thiserror::Error;
/// use async_trait::async_trait;
///
/// #[derive(Debug)]
/// struct MyMiddleware<M>(M);
///
/// #[derive(Error, Debug)]
/// pub enum MyError<M: Middleware> {
/// #[error("{0}")]
/// MiddlewareError(M::Error),
///
/// // Add your middleware's specific errors here
/// }
///
/// impl<M: Middleware> MiddlewareError for MyError<M> {
/// type Inner = M::Error;
///
/// fn from_err(src: M::Error) -> MyError<M> {
/// MyError::MiddlewareError(src)
/// }
///
/// fn as_inner(&self) -> Option<&Self::Inner> {
/// match self {
/// MyError::MiddlewareError(e) => Some(e),
/// _ => None,
/// }
/// }
/// }
///
/// #[async_trait]
/// impl<M> Middleware for MyMiddleware<M>
/// where
/// M: Middleware,
/// {
/// type Error = MyError<M>;
/// type Provider = M::Provider;
/// type Inner = M;
///
/// fn inner(&self) -> &M {
/// &self.0
/// }
///
/// /// Overrides the default `get_block_number` method to always return 0
/// async fn get_block_number(&self) -> Result<U64, Self::Error> {
/// Ok(U64::zero())
/// }
///
/// /// Overrides the default `estimate_gas` method to log that it was called,
/// /// before forwarding the call to the next layer.
/// async fn estimate_gas(&self, tx: &TypedTransaction, block: Option<BlockId>) -> Result<U256, Self::Error> {
/// println!("Estimating gas...");
/// self.inner().estimate_gas(tx, block).await.map_err(MiddlewareError::from_err)
/// }
/// }
/// ```
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&, Box, Arc)]
pub trait Middleware: Sync + Send + Debug {
/// Error type returned by most operations
type Error: MiddlewareError<Inner = <<Self as Middleware>::Inner as Middleware>::Error>;
/// The JSON-RPC client type at the bottom of the stack
type Provider: JsonRpcClient;
/// The next-lower middleware in the middleware stack
type Inner: Middleware<Provider = Self::Provider>;
/// Get a reference to the next-lower middleware in the middleware stack
fn inner(&self) -> &Self::Inner;
/// Convert a provider error into the associated error type by successively
/// converting it to every intermediate middleware error
fn convert_err(p: ProviderError) -> Self::Error {
Self::Error::from_provider_err(p)
}
/// The HTTP or Websocket provider.
fn provider(&self) -> &Provider<Self::Provider> {
self.inner().provider()
}
/// Return the default sender (if any). This will typically be the
/// connected node's first address, or the address of a Signer in a lower
/// middleware stack
fn default_sender(&self) -> Option<Address> {
self.inner().default_sender()
}
/// Returns the current client version using the `web3_clientVersion` RPC.
async fn client_version(&self) -> Result<String, Self::Error> {
self.inner().client_version().await.map_err(MiddlewareError::from_err)
}
/// Fill necessary details of a transaction for dispatch
///
/// This function is defined on providers to behave as follows:
/// 1. populate the `from` field with the default sender
/// 2. resolve any ENS names in the tx `to` field
/// 3. Estimate gas usage
/// 4. Poll and set legacy or 1559 gas prices
/// 5. Set the chain_id with the provider's, if not already set
///
/// It does NOT set the nonce by default.
///
/// Middleware are encouraged to override any values _before_ delegating
/// to the inner implementation AND/OR modify the values provided by the
/// default implementation _after_ delegating.
///
/// E.g. a middleware wanting to double gas prices should consider doing so
/// _after_ delegating and allowing the default implementation to poll gas.
async fn fill_transaction(
&self,
tx: &mut TypedTransaction,
block: Option<BlockId>,
) -> Result<(), Self::Error> {
self.inner().fill_transaction(tx, block).await.map_err(MiddlewareError::from_err)
}
/// Get the block number
async fn get_block_number(&self) -> Result<U64, Self::Error> {
self.inner().get_block_number().await.map_err(MiddlewareError::from_err)
}
/// Sends the transaction to the entire Ethereum network and returns the
/// transaction's hash. This will consume gas from the account that signed
/// the transaction. This call will fail if no signer is available, and the
/// RPC node does not have an unlocked accounts
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
self.inner().send_transaction(tx, block).await.map_err(MiddlewareError::from_err)
}
/// Send a transaction with a simple escalation policy.
///
/// `policy` should be a boxed function that maps `original_gas_price`
/// and `number_of_previous_escalations` -> `new_gas_price`.
///
/// e.g. `Box::new(|start, escalation_index| start * 1250.pow(escalations) /
/// 1000.pow(escalations))`
async fn send_escalating<'a>(
&'a self,
tx: &TypedTransaction,
escalations: usize,
policy: EscalationPolicy,
) -> Result<EscalatingPending<'a, Self::Provider>, Self::Error> {
let mut original = tx.clone();
self.fill_transaction(&mut original, None).await?;
// set the nonce, if no nonce is found
if original.nonce().is_none() {
let nonce =
self.get_transaction_count(tx.from().copied().unwrap_or_default(), None).await?;
original.set_nonce(nonce);
}
let gas_price = original.gas_price().expect("filled");
let sign_futs: Vec<_> = (0..escalations)
.map(|i| {
let new_price = policy(gas_price, i);
let mut r = original.clone();
r.set_gas_price(new_price);
r
})
.map(|req| async move {
self.sign_transaction(&req, self.default_sender().unwrap_or_default())
.await
.map(|sig| req.rlp_signed(&sig))
})
.collect();
// we reverse for convenience. Ensuring that we can always just
// `pop()` the next tx off the back later
let mut signed = join_all(sign_futs).await.into_iter().collect::<Result<Vec<_>, _>>()?;
signed.reverse();
Ok(EscalatingPending::new(self.provider(), signed))
}
////// Ethereum Naming Service
// The Ethereum Naming Service (ENS) allows easy to remember and use names to
// be assigned to Ethereum addresses. Any provider operation which takes an address
// may also take an ENS name.
//
// ENS also provides the ability for a reverse lookup, which determines the name for an address
// if it has been configured.
/// Returns the address that the `ens_name` resolves to (or None if not configured).
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// an address. This should theoretically never happen.
async fn resolve_name(&self, ens_name: &str) -> Result<Address, Self::Error> {
self.inner().resolve_name(ens_name).await.map_err(MiddlewareError::from_err)
}
/// Returns the ENS name the `address` resolves to (or None if not configured).
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn lookup_address(&self, address: Address) -> Result<String, Self::Error> {
self.inner().lookup_address(address).await.map_err(MiddlewareError::from_err)
}
/// Returns the avatar HTTP link of the avatar that the `ens_name` resolves to (or None
/// if not configured)
///
/// # Example
/// ```no_run
/// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
/// # use std::convert::TryFrom;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
/// let avatar = provider.resolve_avatar("parishilton.eth").await.unwrap();
/// assert_eq!(avatar.to_string(), "https://i.imgur.com/YW3Hzph.jpg");
/// # }
/// ```
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, Self::Error> {
self.inner().resolve_avatar(ens_name).await.map_err(MiddlewareError::from_err)
}
/// Returns the URL (not necesserily HTTP) of the image behind a token.
///
/// # Example
/// ```no_run
/// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
/// # use std::{str::FromStr, convert::TryFrom};
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
/// let token = ethers_providers::erc::ERCNFT::from_str("erc721:0xc92ceddfb8dd984a89fb494c376f9a48b999aafc/9018").unwrap();
/// let token_image = provider.resolve_nft(token).await.unwrap();
/// assert_eq!(token_image.to_string(), "https://creature.mypinata.cloud/ipfs/QmNwj3aUzXfG4twV3no7hJRYxLLAWNPk6RrfQaqJ6nVJFa/9018.jpg");
/// # }
/// ```
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn resolve_nft(&self, token: erc::ERCNFT) -> Result<Url, Self::Error> {
self.inner().resolve_nft(token).await.map_err(MiddlewareError::from_err)
}
/// Fetch a field for the `ens_name` (no None if not configured).
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn resolve_field(&self, ens_name: &str, field: &str) -> Result<String, Self::Error> {
self.inner().resolve_field(ens_name, field).await.map_err(MiddlewareError::from_err)
}
/// Gets the block at `block_hash_or_number` (transaction hashes only)
async fn get_block<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
) -> Result<Option<Block<TxHash>>, Self::Error> {
self.inner().get_block(block_hash_or_number).await.map_err(MiddlewareError::from_err)
}
/// Gets the block at `block_hash_or_number` (full transactions included)
async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
) -> Result<Option<Block<Transaction>>, Self::Error> {
self.inner()
.get_block_with_txs(block_hash_or_number)
.await
.map_err(MiddlewareError::from_err)
}
/// Gets the block uncle count at `block_hash_or_number`
async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
) -> Result<U256, Self::Error> {
self.inner().get_uncle_count(block_hash_or_number).await.map_err(MiddlewareError::from_err)
}
/// Gets the block uncle at `block_hash_or_number` and `idx`
async fn get_uncle<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
idx: U64,
) -> Result<Option<Block<H256>>, Self::Error> {
self.inner().get_uncle(block_hash_or_number, idx).await.map_err(MiddlewareError::from_err)
}
/// Returns the nonce of the address
async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner().get_transaction_count(from, block).await.map_err(MiddlewareError::from_err)
}
/// Sends a transaction to a single Ethereum node and return the estimated amount of gas
/// required (as a U256) to send it This is free, but only an estimate. Providing too little
/// gas will result in a transaction being rejected (while still consuming all provided
/// gas).
async fn estimate_gas(
&self,
tx: &TypedTransaction,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner().estimate_gas(tx, block).await.map_err(MiddlewareError::from_err)
}
/// Sends the read-only (constant) transaction to a single Ethereum node and return the result
/// (as bytes) of executing it. This is free, since it does not change any state on the
/// blockchain.
async fn call(
&self,
tx: &TypedTransaction,
block: Option<BlockId>,
) -> Result<Bytes, Self::Error> {
self.inner().call(tx, block).await.map_err(MiddlewareError::from_err)
}
/// Return current client syncing status. If IsFalse sync is over.
async fn syncing(&self) -> Result<SyncingStatus, Self::Error> {
self.inner().syncing().await.map_err(MiddlewareError::from_err)
}
/// Returns the currently configured chain id, a value used in replay-protected
/// transaction signing as introduced by EIP-155.
async fn get_chainid(&self) -> Result<U256, Self::Error> {
self.inner().get_chainid().await.map_err(MiddlewareError::from_err)
}
/// Returns the network version.
async fn get_net_version(&self) -> Result<String, Self::Error> {
self.inner().get_net_version().await.map_err(MiddlewareError::from_err)
}
/// Returns the account's balance
async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner().get_balance(from, block).await.map_err(MiddlewareError::from_err)
}
/// Gets the transaction with `transaction_hash`
async fn get_transaction<T: Send + Sync + Into<TxHash>>(
&self,
transaction_hash: T,
) -> Result<Option<Transaction>, Self::Error> {
self.inner().get_transaction(transaction_hash).await.map_err(MiddlewareError::from_err)
}
/// Gets the transaction receipt with `transaction_hash`
async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
&self,
transaction_hash: T,
) -> Result<Option<TransactionReceipt>, Self::Error> {
self.inner()
.get_transaction_receipt(transaction_hash)
.await
.map_err(MiddlewareError::from_err)
}
/// Returns all receipts for a block.
///
/// Note that this uses the `eth_getBlockReceipts` RPC, which is
/// non-standard and currently supported by Erigon.
async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
&self,
block: T,
) -> Result<Vec<TransactionReceipt>, Self::Error> {
self.inner().get_block_receipts(block).await.map_err(MiddlewareError::from_err)
}
/// Gets the current gas price as estimated by the node
async fn get_gas_price(&self) -> Result<U256, Self::Error> {
self.inner().get_gas_price().await.map_err(MiddlewareError::from_err)
}
/// Gets a heuristic recommendation of max fee per gas and max priority fee per gas for
/// EIP-1559 compatible transactions.
async fn estimate_eip1559_fees(
&self,
estimator: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>,
) -> Result<(U256, U256), Self::Error> {
self.inner().estimate_eip1559_fees(estimator).await.map_err(MiddlewareError::from_err)
}
/// Gets the accounts on the node
async fn get_accounts(&self) -> Result<Vec<Address>, Self::Error> {
self.inner().get_accounts().await.map_err(MiddlewareError::from_err)
}
/// Send the raw RLP encoded transaction to the entire Ethereum network and returns the
/// transaction's hash This will consume gas from the account that signed the transaction.
async fn send_raw_transaction<'a>(
&'a self,
tx: Bytes,
) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
self.inner().send_raw_transaction(tx).await.map_err(MiddlewareError::from_err)
}
/// This returns true if either the middleware stack contains a `SignerMiddleware`, or the
/// JSON-RPC provider has an unlocked key that can sign using the `eth_sign` call. If none of
/// the above conditions are met, then the middleware stack is not capable of signing data.
async fn is_signer(&self) -> bool {
self.inner().is_signer().await
}
/// Signs data using a specific account. This account needs to be unlocked,
/// or the middleware stack must contain a `SignerMiddleware`
async fn sign<T: Into<Bytes> + Send + Sync>(
&self,
data: T,
from: &Address,
) -> Result<Signature, Self::Error> {
self.inner().sign(data, from).await.map_err(MiddlewareError::from_err)
}
/// Sign a transaction via RPC call
async fn sign_transaction(
&self,
tx: &TypedTransaction,
from: Address,
) -> Result<Signature, Self::Error> {
self.inner().sign_transaction(tx, from).await.map_err(MiddlewareError::from_err)
}
////// Contract state
/// Returns an array (possibly empty) of logs that match the filter
async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, Self::Error> {
self.inner().get_logs(filter).await.map_err(MiddlewareError::from_err)
}
/// Returns a stream of logs are loaded in pages of given page size
fn get_logs_paginated<'a>(
&'a self,
filter: &Filter,
page_size: u64,
) -> LogQuery<'a, Self::Provider> {
self.inner().get_logs_paginated(filter, page_size)
}
/// Install a new filter on the node.
///
/// This method is hidden because filter lifecycle should be managed by
/// the [`FilterWatcher`]
#[doc(hidden)]
async fn new_filter(&self, filter: FilterKind<'_>) -> Result<U256, Self::Error> {
self.inner().new_filter(filter).await.map_err(MiddlewareError::from_err)
}
/// Uninstalls a filter.
///
/// This method is hidden because filter lifecycle should be managed by
/// the [`FilterWatcher`]
#[doc(hidden)]
async fn uninstall_filter<T: Into<U256> + Send + Sync>(
&self,
id: T,
) -> Result<bool, Self::Error> {
self.inner().uninstall_filter(id).await.map_err(MiddlewareError::from_err)
}
/// Streams event logs matching the filter.
///
/// This function streams via a polling system, by repeatedly dispatching
/// RPC requests. If possible, prefer using a WS or IPC connection and the
/// `stream` interface
async fn watch<'a>(
&'a self,
filter: &Filter,
) -> Result<FilterWatcher<'a, Self::Provider, Log>, Self::Error> {
self.inner().watch(filter).await.map_err(MiddlewareError::from_err)
}
/// Streams pending transactions.
///
/// This function streams via a polling system, by repeatedly dispatching
/// RPC requests. If possible, prefer using a WS or IPC connection and the
/// `stream` interface
async fn watch_pending_transactions(
&self,
) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
self.inner().watch_pending_transactions().await.map_err(MiddlewareError::from_err)
}
/// Polling method for a filter, which returns an array of logs which occurred since last poll.
///
/// This method must be called with one of the following return types, depending on the filter
/// type:
/// - `eth_newBlockFilter`: [`H256`], returns block hashes
/// - `eth_newPendingTransactionFilter`: [`H256`], returns transaction hashes
/// - `eth_newFilter`: [`Log`], returns raw logs
///
/// If one of these types is not used, decoding will fail and the method will
/// return an error.
///
/// [`H256`]: ethers_core::types::H256
/// [`Log`]: ethers_core::types::Log
///
/// This method is hidden because filter lifecycle should be managed by
/// the [`FilterWatcher`]
#[doc(hidden)]
async fn get_filter_changes<T, R>(&self, id: T) -> Result<Vec<R>, Self::Error>
where
T: Into<U256> + Send + Sync,
R: Serialize + DeserializeOwned + Send + Sync + Debug,
{
self.inner().get_filter_changes(id).await.map_err(MiddlewareError::from_err)
}
/// Streams new block hashes
///
/// This function streams via a polling system, by repeatedly dispatching
/// RPC requests. If possible, prefer using a WS or IPC connection and the
/// `stream` interface
async fn watch_blocks(&self) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
self.inner().watch_blocks().await.map_err(MiddlewareError::from_err)
}
/// Returns the deployed code at a given address
async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
&self,
at: T,
block: Option<BlockId>,
) -> Result<Bytes, Self::Error> {
self.inner().get_code(at, block).await.map_err(MiddlewareError::from_err)
}
/// Get the storage of an address for a particular slot location
async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
location: H256,
block: Option<BlockId>,
) -> Result<H256, Self::Error> {
self.inner().get_storage_at(from, location, block).await.map_err(MiddlewareError::from_err)
}
/// Returns the EIP-1186 proof response
/// <https://github.com/ethereum/EIPs/issues/1186>
async fn get_proof<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
locations: Vec<H256>,
block: Option<BlockId>,
) -> Result<EIP1186ProofResponse, Self::Error> {
self.inner().get_proof(from, locations, block).await.map_err(MiddlewareError::from_err)
}
/// Returns an indication if this node is currently mining.
async fn mining(&self) -> Result<bool, Self::Error> {
self.inner().mining().await.map_err(MiddlewareError::from_err)
}
// Personal namespace
// NOTE: This will eventually need to be enabled by users explicitly because the personal
// namespace is being deprecated:
// Issue: https://github.com/ethereum/go-ethereum/issues/25948
// PR: https://github.com/ethereum/go-ethereum/pull/26390
/// Sends the given key to the node to be encrypted with the provided
/// passphrase and stored.
///
/// The key represents a secp256k1 private key and should be 32 bytes.
async fn import_raw_key(
&self,
private_key: Bytes,
passphrase: String,
) -> Result<Address, Self::Error> {
self.inner()
.import_raw_key(private_key, passphrase)
.await
.map_err(MiddlewareError::from_err)
}
/// Prompts the node to decrypt the given account from its keystore.
///
/// If the duration provided is `None`, then the account will be unlocked
/// indefinitely. Otherwise, the account will be unlocked for the provided
/// number of seconds.
async fn unlock_account<T: Into<Address> + Send + Sync>(
&self,
account: T,
passphrase: String,
duration: Option<u64>,
) -> Result<bool, Self::Error> {
self.inner()
.unlock_account(account, passphrase, duration)
.await
.map_err(MiddlewareError::from_err)
}
// Admin namespace
/// Requests adding the given peer, returning a boolean representing
/// whether or not the peer was accepted for tracking.
async fn add_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().add_peer(enode_url).await.map_err(MiddlewareError::from_err)
}
/// Requests adding the given peer as a trusted peer, which the node will
/// always connect to even when its peer slots are full.
async fn add_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().add_trusted_peer(enode_url).await.map_err(MiddlewareError::from_err)
}
/// Returns general information about the node as well as information about the running p2p
/// protocols (e.g. `eth`, `snap`).
async fn node_info(&self) -> Result<NodeInfo, Self::Error> {
self.inner().node_info().await.map_err(MiddlewareError::from_err)
}
/// Returns the list of peers currently connected to the node.
async fn peers(&self) -> Result<Vec<PeerInfo>, Self::Error> {
self.inner().peers().await.map_err(MiddlewareError::from_err)
}
/// Requests to remove the given peer, returning true if the enode was successfully parsed and
/// the peer was removed.
async fn remove_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().remove_peer(enode_url).await.map_err(MiddlewareError::from_err)
}
/// Requests to remove the given peer, returning a boolean representing whether or not the
/// enode url passed was validated. A return value of `true` does not necessarily mean that the
/// peer was disconnected.
async fn remove_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
self.inner().remove_trusted_peer(enode_url).await.map_err(MiddlewareError::from_err)
}
// Miner namespace
/// Starts the miner with the given number of threads. If threads is nil, the number of workers
/// started is equal to the number of logical CPUs that are usable by this process. If mining
/// is already running, this method adjust the number of threads allowed to use and updates the
/// minimum price required by the transaction pool.
async fn start_mining(&self, threads: Option<usize>) -> Result<(), Self::Error> {
self.inner().start_mining(threads).await.map_err(MiddlewareError::from_err)
}
/// Stop terminates the miner, both at the consensus engine level as well as at
/// the block creation level.
async fn stop_mining(&self) -> Result<(), Self::Error> {
self.inner().stop_mining().await.map_err(MiddlewareError::from_err)
}
// Mempool inspection for Geth's API
/// Returns the details of all transactions currently pending for inclusion in the next
/// block(s), as well as the ones that are being scheduled for future execution only.
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content)
async fn txpool_content(&self) -> Result<TxpoolContent, Self::Error> {
self.inner().txpool_content().await.map_err(MiddlewareError::from_err)
}
/// Returns a summary of all the transactions currently pending for inclusion in the next
/// block(s), as well as the ones that are being scheduled for future execution only.
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect)
async fn txpool_inspect(&self) -> Result<TxpoolInspect, Self::Error> {
self.inner().txpool_inspect().await.map_err(MiddlewareError::from_err)
}
/// Returns the number of transactions currently pending for inclusion in the next block(s), as
/// well as the ones that are being scheduled for future execution only.
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status)
async fn txpool_status(&self) -> Result<TxpoolStatus, Self::Error> {
self.inner().txpool_status().await.map_err(MiddlewareError::from_err)
}
// Geth `trace` support
/// After replaying any previous transactions in the same block,
/// Replays a transaction, returning the traces configured with passed options
async fn debug_trace_transaction(
&self,
tx_hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> Result<GethTrace, Self::Error> {
self.inner()
.debug_trace_transaction(tx_hash, trace_options)
.await
.map_err(MiddlewareError::from_err)
}
/// Executes the given call and returns a number of possible traces for it
async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: T,
block: Option<BlockId>,
trace_options: GethDebugTracingCallOptions,
) -> Result<GethTrace, Self::Error> {
self.inner()
.debug_trace_call(req, block, trace_options)
.await
.map_err(MiddlewareError::from_err)
}
// Parity `trace` support
/// Executes the given call and returns a number of possible traces for it
async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: T,
trace_type: Vec<TraceType>,
block: Option<BlockNumber>,
) -> Result<BlockTrace, Self::Error> {
self.inner().trace_call(req, trace_type, block).await.map_err(MiddlewareError::from_err)
}
/// Executes given calls and returns a number of possible traces for each
/// call
async fn trace_call_many<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: Vec<(T, Vec<TraceType>)>,
block: Option<BlockNumber>,
) -> Result<Vec<BlockTrace>, Self::Error> {
self.inner().trace_call_many(req, block).await.map_err(MiddlewareError::from_err)
}
/// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces
async fn trace_raw_transaction(
&self,
data: Bytes,
trace_type: Vec<TraceType>,
) -> Result<BlockTrace, Self::Error> {
self.inner()
.trace_raw_transaction(data, trace_type)
.await
.map_err(MiddlewareError::from_err)
}
/// Replays a transaction, returning the traces
async fn trace_replay_transaction(
&self,
hash: H256,
trace_type: Vec<TraceType>,
) -> Result<BlockTrace, Self::Error> {
self.inner()
.trace_replay_transaction(hash, trace_type)
.await
.map_err(MiddlewareError::from_err)
}
/// Replays all transactions in a block returning the requested traces for each transaction
async fn trace_replay_block_transactions(
&self,
block: BlockNumber,
trace_type: Vec<TraceType>,
) -> Result<Vec<BlockTrace>, Self::Error> {
self.inner()
.trace_replay_block_transactions(block, trace_type)
.await
.map_err(MiddlewareError::from_err)
}
/// Returns traces created at given block
async fn trace_block(&self, block: BlockNumber) -> Result<Vec<Trace>, Self::Error> {
self.inner().trace_block(block).await.map_err(MiddlewareError::from_err)
}
/// Return traces matching the given filter
async fn trace_filter(&self, filter: TraceFilter) -> Result<Vec<Trace>, Self::Error> {
self.inner().trace_filter(filter).await.map_err(MiddlewareError::from_err)
}
/// Returns trace at the given position
async fn trace_get<T: Into<U64> + Send + Sync>(
&self,
hash: H256,
index: Vec<T>,
) -> Result<Trace, Self::Error> {
self.inner().trace_get(hash, index).await.map_err(MiddlewareError::from_err)
}
/// Returns all traces of a given transaction
async fn trace_transaction(&self, hash: H256) -> Result<Vec<Trace>, Self::Error> {
self.inner().trace_transaction(hash).await.map_err(MiddlewareError::from_err)
}
// Parity namespace
/// Returns all receipts for that block. Must be done on a parity node.
async fn parity_block_receipts<T: Into<BlockNumber> + Send + Sync>(
&self,
block: T,
) -> Result<Vec<TransactionReceipt>, Self::Error> {
self.inner().parity_block_receipts(block).await.map_err(MiddlewareError::from_err)
}
/// Create a new subscription
///
/// This method is hidden as subscription lifecycles are intended to be
/// handled by a [`SubscriptionStream`] object.
#[doc(hidden)]
async fn subscribe<T, R>(
&self,
params: T,
) -> Result<SubscriptionStream<'_, Self::Provider, R>, Self::Error>
where
T: Debug + Serialize + Send + Sync,
R: DeserializeOwned + Send + Sync,
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe(params).await.map_err(MiddlewareError::from_err)
}
/// Instruct the RPC to cancel a subscription by its ID
///
/// This method is hidden as subscription lifecycles are intended to be
/// handled by a [`SubscriptionStream`] object
#[doc(hidden)]
async fn unsubscribe<T>(&self, id: T) -> Result<bool, Self::Error>
where
T: Into<U256> + Send + Sync,
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().unsubscribe(id).await.map_err(MiddlewareError::from_err)
}
/// Subscribe to a stream of incoming blocks.
///
/// This function is only available on pubsub clients, such as Websockets
/// or IPC. For a polling alternative available over HTTP, use
/// [`Middleware::watch_blocks`]. However, be aware that polling increases
/// RPC usage drastically.
async fn subscribe_blocks(
&self,
) -> Result<SubscriptionStream<'_, Self::Provider, Block<TxHash>>, Self::Error>
where
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe_blocks().await.map_err(MiddlewareError::from_err)
}
/// Subscribe to a stream of pending transactions.
///
/// 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.
async fn subscribe_pending_txs(
&self,
) -> Result<SubscriptionStream<'_, Self::Provider, TxHash>, Self::Error>
where
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe_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
/// or IPC. For a polling alternative available over HTTP, use
/// [`Middleware::watch`]. However, be aware that polling increases
/// RPC usage drastically.
async fn subscribe_logs<'a>(
&'a self,
filter: &Filter,
) -> Result<SubscriptionStream<'a, Self::Provider, Log>, Self::Error>
where
<Self as Middleware>::Provider: PubsubClient,
{
self.inner().subscribe_logs(filter).await.map_err(MiddlewareError::from_err)
}
/// Query the node for a [`FeeHistory`] object. This objct contains
/// information about the EIP-1559 base fee in past blocks, as well as gas
/// utilization within those blocks.
///
/// See the
/// [EIP-1559 documentation](https://eips.ethereum.org/EIPS/eip-1559) for
/// details
async fn fee_history<T: Into<U256> + serde::Serialize + Send + Sync>(
&self,
block_count: T,
last_block: BlockNumber,
reward_percentiles: &[f64],
) -> Result<FeeHistory, Self::Error> {
self.inner()
.fee_history(block_count, last_block, reward_percentiles)
.await
.map_err(MiddlewareError::from_err)
}
/// Querty the node for an EIP-2930 Access List.
///
/// See the
/// [EIP-2930 documentation](https://eips.ethereum.org/EIPS/eip-2930) for
/// details
async fn create_access_list(
&self,
tx: &TypedTransaction,
block: Option<BlockId>,
) -> Result<AccessListWithGasUsed, Self::Error> {
self.inner().create_access_list(tx, block).await.map_err(MiddlewareError::from_err)
}
}
#[cfg(feature = "celo")]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
/// Celo-specific extension trait
pub trait CeloMiddleware: Middleware {
/// Get validator BLS public keys
async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
&self,
block_id: T,
) -> Result<Vec<String>, ProviderError> {
self.provider()
.get_validators_bls_public_keys(block_id)
.await
.map_err(MiddlewareError::from_err)
}
}
#[cfg(feature = "celo")]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T> CeloMiddleware for T where T: Middleware {}

View File

@ -0,0 +1,37 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers_core::types::U256;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::value::RawValue;
use crate::{ProviderError, RpcError};
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&, Box, Arc)]
/// Trait which must be implemented by data transports to be used with the Ethereum
/// JSON-RPC provider.
pub trait JsonRpcClient: Debug + Send + Sync {
/// A JSON-RPC Error
type Error: Into<ProviderError> + RpcError;
/// Sends a request with the provided JSON-RPC and parameters serialized as JSON
async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
where
T: Debug + Serialize + Send + Sync,
R: DeserializeOwned + Send;
}
/// A transport implementation supporting pub sub subscriptions.
pub trait PubsubClient: JsonRpcClient {
/// The type of stream this transport returns
type NotificationStream: futures_core::Stream<Item = Box<RawValue>> + Send + Unpin;
/// Add a subscription to this transport
fn subscribe<T: Into<U256>>(&self, id: T) -> Result<Self::NotificationStream, Self::Error>;
/// Remove a subscription from this transport
fn unsubscribe<T: Into<U256>>(&self, id: T) -> Result<(), Self::Error>;
}

View File

@ -0,0 +1,11 @@
mod provider;
pub use provider::*;
mod transports;
pub use transports::*;
mod connections;
pub use connections::*;
mod pubsub;
pub use pubsub::{PubsubClient, SubscriptionStream};

View File

@ -1,20 +1,25 @@
use ethers_core::types::SyncingStatus;
use crate::{
call_raw::CallBuilder,
ens, erc, maybe,
pubsub::{PubsubClient, SubscriptionStream},
errors::ProviderError,
ext::{ens, erc},
rpc::pubsub::{PubsubClient, SubscriptionStream},
stream::{FilterWatcher, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL},
FromErr, Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, LogQuery, MockProvider,
NodeInfo, PeerInfo, PendingTransaction, QuorumProvider, RwClient, SyncingStatus,
utils::maybe,
Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, LogQuery, MiddlewareError,
MockProvider, NodeInfo, PeerInfo, PendingTransaction, QuorumProvider, RwClient,
};
#[cfg(all(not(target_arch = "wasm32"), feature = "ws"))]
use crate::transports::Authorization;
use crate::Authorization;
#[cfg(not(target_arch = "wasm32"))]
use crate::transports::{HttpRateLimitRetryPolicy, RetryClient};
use crate::{HttpRateLimitRetryPolicy, RetryClient};
#[cfg(feature = "celo")]
use crate::CeloMiddleware;
use crate::Middleware;
pub use crate::CeloMiddleware;
pub use crate::Middleware;
use async_trait::async_trait;
use ethers_core::{
@ -35,17 +40,22 @@ use serde::{de::DeserializeOwned, Serialize};
use std::{
collections::VecDeque, convert::TryFrom, fmt::Debug, str::FromStr, sync::Arc, time::Duration,
};
use thiserror::Error;
use tracing::trace;
use tracing_futures::Instrument;
use url::{ParseError, Url};
/// Node Clients
#[derive(Copy, Clone)]
pub enum NodeClient {
/// Geth
Geth,
/// Erigon
Erigon,
/// OpenEthereum
OpenEthereum,
/// Nethermind
Nethermind,
/// Besu
Besu,
}
@ -103,49 +113,6 @@ impl<P> AsRef<P> for Provider<P> {
}
}
impl FromErr<ProviderError> for ProviderError {
fn from(src: ProviderError) -> Self {
src
}
}
#[derive(Debug, Error)]
/// An error thrown when making a call to the provider
pub enum ProviderError {
/// An internal error in the JSON RPC Client
#[error(transparent)]
JsonRpcClientError(#[from] Box<dyn std::error::Error + Send + Sync>),
/// An error during ENS name resolution
#[error("ens name not found: {0}")]
EnsError(String),
/// Invalid reverse ENS name
#[error("reverse ens name not pointing to itself: {0}")]
EnsNotOwned(String),
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
#[error(transparent)]
HexError(#[from] hex::FromHexError),
#[error(transparent)]
HTTPError(#[from] reqwest::Error),
#[error("custom error: {0}")]
CustomError(String),
#[error("unsupported RPC")]
UnsupportedRPC,
#[error("unsupported node client")]
UnsupportedNodeClient,
#[error("Attempted to sign a transaction with no available signer. Hint: did you mean to use a SignerMiddleware?")]
SignerUnavailable,
}
/// Types of filters supported by the JSON-RPC.
#[derive(Clone, Debug)]
pub enum FilterKind<'a> {
@ -191,11 +158,13 @@ impl<P: JsonRpcClient> Provider<P> {
}
#[must_use]
/// Set the default sender on the provider
pub fn with_sender(mut self, address: impl Into<Address>) -> Self {
self.from = Some(address.into());
self
}
/// Make an RPC request via the internal connection, and return the result.
pub async fn request<T, R>(&self, method: &str, params: T) -> Result<R, ProviderError>
where
T: Debug + Serialize + Send + Sync,
@ -275,19 +244,6 @@ impl<P: JsonRpcClient> Provider<P> {
}
}
#[cfg(feature = "celo")]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<P: JsonRpcClient> CeloMiddleware for Provider<P> {
async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
&self,
block_id: T,
) -> Result<Vec<String>, ProviderError> {
let block_id = utils::serialize(&block_id.into());
self.request("istanbul_getValidatorsBLSPublicKeys", [block_id]).await
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<P: JsonRpcClient> Middleware for Provider<P> {
@ -312,11 +268,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.from
}
////// Blockchain Status
//
// Functions for querying the state of the blockchain
/// Returns the current client version using the `web3_clientVersion` RPC.
async fn client_version(&self) -> Result<String, Self::Error> {
self.request("web3_clientVersion", ()).await
}
@ -377,12 +328,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(())
}
/// Gets the latest block number via the `eth_BlockNumber` API
async fn get_block_number(&self) -> Result<U64, ProviderError> {
self.request("eth_blockNumber", ()).await
}
/// Gets the block at `block_hash_or_number` (transaction hashes only)
async fn get_block<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
@ -390,7 +339,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.get_block_gen(block_hash_or_number.into(), false).await
}
/// Gets the block at `block_hash_or_number` (full transactions included)
async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
@ -398,7 +346,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.get_block_gen(block_hash_or_number.into(), true).await
}
/// Gets the block uncle count at `block_hash_or_number`
async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
@ -416,7 +363,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
})
}
/// Gets the block uncle at `block_hash_or_number` and `idx`
async fn get_uncle<T: Into<BlockId> + Send + Sync>(
&self,
block_hash_or_number: T,
@ -436,7 +382,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
})
}
/// Gets the transaction with `transaction_hash`
async fn get_transaction<T: Send + Sync + Into<TxHash>>(
&self,
transaction_hash: T,
@ -445,7 +390,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_getTransactionByHash", [hash]).await
}
/// Gets the transaction receipt with `transaction_hash`
async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
&self,
transaction_hash: T,
@ -454,10 +398,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_getTransactionReceipt", [hash]).await
}
/// Returns all receipts for a block.
///
/// Note that this uses the `eth_getBlockReceipts` RPC, which is
/// non-standard and currently supported by Erigon.
async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
&self,
block: T,
@ -465,7 +405,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_getBlockReceipts", [block.into()]).await
}
/// Returns all receipts for that block. Must be done on a parity node.
async fn parity_block_receipts<T: Into<BlockNumber> + Send + Sync>(
&self,
block: T,
@ -473,13 +412,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("parity_getBlockReceipts", vec![block.into()]).await
}
/// Gets the current gas price as estimated by the node
async fn get_gas_price(&self) -> Result<U256, ProviderError> {
self.request("eth_gasPrice", ()).await
}
/// Gets a heuristic recommendation of max fee per gas and max priority fee per gas for
/// EIP-1559 compatible transactions.
async fn estimate_eip1559_fees(
&self,
estimator: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>,
@ -509,12 +445,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok((max_fee_per_gas, max_priority_fee_per_gas))
}
/// Gets the accounts on the node
async fn get_accounts(&self) -> Result<Vec<Address>, ProviderError> {
self.request("eth_accounts", ()).await
}
/// Returns the nonce of the address
async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
@ -530,7 +464,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_getTransactionCount", [from, block]).await
}
/// Returns the account's balance
async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
@ -546,29 +479,18 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_getBalance", [from, block]).await
}
/// Returns the currently configured chain id, a value used in replay-protected
/// transaction signing as introduced by EIP-155.
async fn get_chainid(&self) -> Result<U256, ProviderError> {
self.request("eth_chainId", ()).await
}
/// Return current client syncing status. If IsFalse sync is over.
async fn syncing(&self) -> Result<SyncingStatus, Self::Error> {
self.request("eth_syncing", ()).await
}
/// Returns the network version.
async fn get_net_version(&self) -> Result<String, ProviderError> {
self.request("net_version", ()).await
}
////// Contract Execution
//
// These are relatively low-level calls. The Contracts API should usually be used instead.
/// Sends the read-only (constant) transaction to a single Ethereum node and return the result
/// (as bytes) of executing it. This is free, since it does not change any state on the
/// blockchain.
async fn call(
&self,
tx: &TypedTransaction,
@ -579,10 +501,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_call", [tx, block]).await
}
/// Sends a transaction to a single Ethereum node and return the estimated amount of gas
/// required (as a U256) to send it This is free, but only an estimate. Providing too little
/// gas will result in a transaction being rejected (while still consuming all provided
/// gas).
async fn estimate_gas(
&self,
tx: &TypedTransaction,
@ -609,8 +527,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_createAccessList", [tx, block]).await
}
/// Sends the transaction to the entire Ethereum network and returns the transaction's hash
/// This will consume gas from the account that signed the transaction.
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
@ -623,8 +539,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(PendingTransaction::new(tx_hash, self))
}
/// Send the raw RLP encoded transaction to the entire Ethereum network and returns the
/// transaction's hash This will consume gas from the account that signed the transaction.
async fn send_raw_transaction<'a>(
&'a self,
tx: Bytes,
@ -634,8 +548,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(PendingTransaction::new(tx_hash, self))
}
/// The JSON-RPC provider is at the bottom-most position in the middleware stack. Here we check
/// if it has the key for the sender address unlocked, as well as supports the `eth_sign` call.
async fn is_signer(&self) -> bool {
match self.from {
Some(sender) => self.sign(vec![], &sender).await.is_ok(),
@ -643,7 +555,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
}
}
/// Signs data using a specific account. This account needs to be unlocked.
async fn sign<T: Into<Bytes> + Send + Sync>(
&self,
data: T,
@ -668,12 +579,11 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
_tx: &TypedTransaction,
_from: Address,
) -> Result<Signature, Self::Error> {
Err(ProviderError::SignerUnavailable).map_err(FromErr::from)
Err(ProviderError::SignerUnavailable).map_err(MiddlewareError::from_err)
}
////// Contract state
/// Returns an array (possibly empty) of logs that match the filter
async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, ProviderError> {
self.request("eth_getLogs", [filter]).await
}
@ -682,7 +592,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
LogQuery::new(self, filter).with_page_size(page_size)
}
/// Streams matching filter logs
async fn watch<'a>(
&'a self,
filter: &Filter,
@ -692,7 +601,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(filter)
}
/// Streams new block hashes
async fn watch_blocks(&self) -> Result<FilterWatcher<'_, P, H256>, ProviderError> {
let id = self.new_filter(FilterKind::NewBlocks).await?;
let filter = FilterWatcher::new(id, self).interval(self.get_interval());
@ -708,8 +616,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(filter)
}
/// Creates a filter object, based on filter options, to notify when the state changes (logs).
/// To check if the state has changed, call `get_filter_changes` with the filter id.
async fn new_filter(&self, filter: FilterKind<'_>) -> Result<U256, ProviderError> {
let (method, args) = match filter {
FilterKind::NewBlocks => ("eth_newBlockFilter", vec![]),
@ -720,7 +626,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request(method, args).await
}
/// Uninstalls a filter
async fn uninstall_filter<T: Into<U256> + Send + Sync>(
&self,
id: T,
@ -729,19 +634,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_uninstallFilter", [id]).await
}
/// Polling method for a filter, which returns an array of logs which occurred since last poll.
///
/// This method must be called with one of the following return types, depending on the filter
/// type:
/// - `eth_newBlockFilter`: [`H256`], returns block hashes
/// - `eth_newPendingTransactionFilter`: [`H256`], returns transaction hashes
/// - `eth_newFilter`: [`Log`], returns raw logs
///
/// If one of these types is not used, decoding will fail and the method will
/// return an error.
///
/// [`H256`]: ethers_core::types::H256
/// [`Log`]: ethers_core::types::Log
async fn get_filter_changes<T, R>(&self, id: T) -> Result<Vec<R>, ProviderError>
where
T: Into<U256> + Send + Sync,
@ -751,7 +643,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_getFilterChanges", [id]).await
}
/// Get the storage of an address for a particular slot location
async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
@ -777,7 +668,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(H256::from_slice(&Vec::from_hex(value)?))
}
/// Returns the deployed code at a given address
async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
&self,
at: T,
@ -793,8 +683,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_getCode", [at, block]).await
}
/// Returns the EIP-1186 proof response
/// <https://github.com/ethereum/EIPs/issues/1186>
async fn get_proof<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
@ -818,15 +706,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("eth_mining", ()).await
}
// Personal namespace
// NOTE: This will eventually need to be enabled by users explicitly because the personal
// namespace is being deprecated:
// Issue: https://github.com/ethereum/go-ethereum/issues/25948
// PR: https://github.com/ethereum/go-ethereum/pull/26390
/// Sends the given key to the node to be encrypted with the provided passphrase and stored.
///
/// The key represents a secp256k1 private key and should be 32 bytes.
async fn import_raw_key(
&self,
private_key: Bytes,
@ -842,10 +721,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("personal_importRawKey", [private_key, passphrase]).await
}
/// Prompts the node to decrypt the given account from its keystore.
///
/// If the duration provided is `None`, then the account will be unlocked indefinitely.
/// Otherwise, the account will be unlocked for the provided number of seconds.
async fn unlock_account<T: Into<Address> + Send + Sync>(
&self,
account: T,
@ -858,88 +733,47 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("personal_unlockAccount", [account, passphrase, duration]).await
}
// Admin namespace
/// Requests adding the given peer, returning a boolean representing whether or not the peer
/// was accepted for tracking.
async fn add_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
let enode_url = utils::serialize(&enode_url);
self.request("admin_addPeer", [enode_url]).await
}
/// Requests adding the given peer as a trusted peer, which the node will always connect to
/// even when its peer slots are full.
async fn add_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
let enode_url = utils::serialize(&enode_url);
self.request("admin_addTrustedPeer", [enode_url]).await
}
/// Returns general information about the node as well as information about the running p2p
/// protocols (e.g. `eth`, `snap`).
async fn node_info(&self) -> Result<NodeInfo, Self::Error> {
self.request("admin_nodeInfo", ()).await
}
/// Returns the list of peers currently connected to the node.
async fn peers(&self) -> Result<Vec<PeerInfo>, Self::Error> {
self.request("admin_peers", ()).await
}
/// Requests to remove the given peer, returning true if the enode was successfully parsed and
/// the peer was removed.
async fn remove_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
let enode_url = utils::serialize(&enode_url);
self.request("admin_removePeer", [enode_url]).await
}
/// Requests to remove the given peer, returning a boolean representing whether or not the
/// enode url passed was validated. A return value of `true` does not necessarily mean that the
/// peer was disconnected.
async fn remove_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
let enode_url = utils::serialize(&enode_url);
self.request("admin_removeTrustedPeer", [enode_url]).await
}
// Miner namespace
/// Starts the miner with the given number of threads. If threads is nil, the number of workers
/// started is equal to the number of logical CPUs that are usable by this process. If mining
/// is already running, this method adjust the number of threads allowed to use and updates the
/// minimum price required by the transaction pool.
async fn start_mining(&self, threads: Option<usize>) -> Result<(), Self::Error> {
let threads = utils::serialize(&threads);
self.request("miner_start", [threads]).await
}
/// Stop terminates the miner, both at the consensus engine level as well as at the block
/// creation level.
async fn stop_mining(&self) -> Result<(), Self::Error> {
self.request("miner_stop", ()).await
}
////// Ethereum Naming Service
// The Ethereum Naming Service (ENS) allows easy to remember and use names to
// be assigned to Ethereum addresses. Any provider operation which takes an address
// may also take an ENS name.
//
// ENS also provides the ability for a reverse lookup, which determines the name for an address
// if it has been configured.
/// Returns the address that the `ens_name` resolves to (or None if not configured).
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// an address. This should theoretically never happen.
async fn resolve_name(&self, ens_name: &str) -> Result<Address, ProviderError> {
self.query_resolver(ParamType::Address, ens_name, ens::ADDR_SELECTOR).await
}
/// Returns the ENS name the `address` resolves to (or None if not configured).
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn lookup_address(&self, address: Address) -> Result<String, ProviderError> {
let ens_name = ens::reverse_address(address);
let domain: String =
@ -952,25 +786,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
}
}
/// Returns the avatar HTTP link of the avatar that the `ens_name` resolves to (or None
/// if not configured)
///
/// # Example
/// ```no_run
/// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
/// # use std::convert::TryFrom;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
/// let avatar = provider.resolve_avatar("parishilton.eth").await.unwrap();
/// assert_eq!(avatar.to_string(), "https://i.imgur.com/YW3Hzph.jpg");
/// # }
/// ```
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, ProviderError> {
let (field, owner) =
try_join!(self.resolve_field(ens_name, "avatar"), self.resolve_name(ens_name))?;
@ -1030,25 +845,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
}
}
/// Returns the URL (not necesserily HTTP) of the image behind a token.
///
/// # Example
/// ```no_run
/// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
/// # use std::{str::FromStr, convert::TryFrom};
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
/// let token = ethers_providers::erc::ERCNFT::from_str("erc721:0xc92ceddfb8dd984a89fb494c376f9a48b999aafc/9018").unwrap();
/// let token_image = provider.resolve_nft(token).await.unwrap();
/// assert_eq!(token_image.to_string(), "https://creature.mypinata.cloud/ipfs/QmNwj3aUzXfG4twV3no7hJRYxLLAWNPk6RrfQaqJ6nVJFa/9018.jpg");
/// # }
/// ```
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn resolve_nft(&self, token: erc::ERCNFT) -> Result<Url, ProviderError> {
let selector = token.type_.resolution_selector();
let tx = TransactionRequest {
@ -1070,12 +866,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Url::parse(&metadata.image).map_err(|e| ProviderError::CustomError(e.to_string()))
}
/// Fetch a field for the `ens_name` (no None if not configured).
///
/// # Panics
///
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
/// a string. This should theoretically never happen.
async fn resolve_field(&self, ens_name: &str, field: &str) -> Result<String, ProviderError> {
let field: String = self
.query_resolver_parameters(
@ -1088,28 +878,18 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(field)
}
/// Returns the details of all transactions currently pending for inclusion in the next
/// block(s), as well as the ones that are being scheduled for future execution only.
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content)
async fn txpool_content(&self) -> Result<TxpoolContent, ProviderError> {
self.request("txpool_content", ()).await
}
/// Returns a summary of all the transactions currently pending for inclusion in the next
/// block(s), as well as the ones that are being scheduled for future execution only.
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect)
async fn txpool_inspect(&self) -> Result<TxpoolInspect, ProviderError> {
self.request("txpool_inspect", ()).await
}
/// Returns the number of transactions currently pending for inclusion in the next block(s), as
/// well as the ones that are being scheduled for future execution only.
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status)
async fn txpool_status(&self) -> Result<TxpoolStatus, ProviderError> {
self.request("txpool_status", ()).await
}
/// Executes the given call and returns a number of possible traces for it
async fn debug_trace_transaction(
&self,
tx_hash: TxHash,
@ -1120,7 +900,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("debug_traceTransaction", [tx_hash, trace_options]).await
}
/// Executes the given call and returns a number of possible traces for it
async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: T,
@ -1134,7 +913,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("debug_traceCall", [req, block, trace_options]).await
}
/// Executes the given call and returns a number of possible traces for it
async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: T,
@ -1148,7 +926,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("trace_call", [req, trace_type, block]).await
}
/// Executes given calls and returns a number of possible traces for each call
async fn trace_call_many<T: Into<TypedTransaction> + Send + Sync>(
&self,
req: Vec<(T, Vec<TraceType>)>,
@ -1161,7 +938,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("trace_callMany", [req, block]).await
}
/// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces
async fn trace_raw_transaction(
&self,
data: Bytes,
@ -1172,7 +948,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("trace_rawTransaction", [data, trace_type]).await
}
/// Replays a transaction, returning the traces
async fn trace_replay_transaction(
&self,
hash: H256,
@ -1183,7 +958,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("trace_replayTransaction", [hash, trace_type]).await
}
/// Replays all transactions in a block returning the requested traces for each transaction
async fn trace_replay_block_transactions(
&self,
block: BlockNumber,
@ -1194,19 +968,16 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("trace_replayBlockTransactions", [block, trace_type]).await
}
/// Returns traces created at given block
async fn trace_block(&self, block: BlockNumber) -> Result<Vec<Trace>, ProviderError> {
let block = utils::serialize(&block);
self.request("trace_block", [block]).await
}
/// Return traces matching the given filter
async fn trace_filter(&self, filter: TraceFilter) -> Result<Vec<Trace>, ProviderError> {
let filter = utils::serialize(&filter);
self.request("trace_filter", vec![filter]).await
}
/// Returns trace at the given position
async fn trace_get<T: Into<U64> + Send + Sync>(
&self,
hash: H256,
@ -1218,7 +989,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self.request("trace_get", vec![hash, index]).await
}
/// Returns all traces of a given transaction
async fn trace_transaction(&self, hash: H256) -> Result<Vec<Trace>, ProviderError> {
let hash = utils::serialize(&hash);
self.request("trace_transaction", vec![hash]).await
@ -1458,6 +1228,14 @@ impl Provider<crate::Ws> {
Ok(Self::new(ws))
}
/// Direct connection to a websocket endpoint
#[cfg(target_arch = "wasm32")]
pub async fn connect(url: &str) -> Result<Self, ProviderError> {
let ws = crate::Ws::connect(url).await?;
Ok(Self::new(ws))
}
/// Connect to a WS RPC provider with authentication details
#[cfg(not(target_arch = "wasm32"))]
pub async fn connect_with_auth(
url: impl tokio_tungstenite::tungstenite::client::IntoClientRequest + Unpin,
@ -1466,13 +1244,6 @@ impl Provider<crate::Ws> {
let ws = crate::Ws::connect_with_auth(url, auth).await?;
Ok(Self::new(ws))
}
/// Direct connection to a websocket endpoint
#[cfg(target_arch = "wasm32")]
pub async fn connect(url: &str) -> Result<Self, ProviderError> {
let ws = crate::Ws::connect(url).await?;
Ok(Self::new(ws))
}
}
#[cfg(all(feature = "ipc", any(unix, windows)))]
@ -1587,6 +1358,8 @@ impl<'a> TryFrom<&'a String> for Provider<HttpProvider> {
#[cfg(not(target_arch = "wasm32"))]
impl Provider<RetryClient<HttpProvider>> {
/// Create a new [`RetryClient`] by connecting to the provided URL. Errors
/// if `src` is not a valid URL
pub fn new_client(src: &str, max_retry: u32, initial_backoff: u64) -> Result<Self, ParseError> {
Ok(Provider::new(RetryClient::new(
HttpProvider::new(Url::parse(src)?),
@ -1711,182 +1484,6 @@ pub fn is_local_endpoint(url: &str) -> bool {
url.contains("127.0.0.1") || url.contains("localhost")
}
/// A middleware supporting development-specific JSON RPC methods
///
/// # Example
///
///```
/// use ethers_providers::{Provider, Http, Middleware, DevRpcMiddleware};
/// use ethers_core::types::TransactionRequest;
/// use ethers_core::utils::Anvil;
/// use std::convert::TryFrom;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let anvil = Anvil::new().spawn();
/// let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
/// let client = DevRpcMiddleware::new(provider);
///
/// // snapshot the initial state
/// let block0 = client.get_block_number().await.unwrap();
/// let snap_id = client.snapshot().await.unwrap();
///
/// // send a transaction
/// let accounts = client.get_accounts().await?;
/// let from = accounts[0];
/// let to = accounts[1];
/// let balance_before = client.get_balance(to, None).await?;
/// let tx = TransactionRequest::new().to(to).value(1000).from(from);
/// client.send_transaction(tx, None).await?.await?;
/// let balance_after = client.get_balance(to, None).await?;
/// assert_eq!(balance_after, balance_before + 1000);
///
/// // revert to snapshot
/// client.revert_to_snapshot(snap_id).await.unwrap();
/// let balance_after_revert = client.get_balance(to, None).await?;
/// assert_eq!(balance_after_revert, balance_before);
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "dev-rpc")]
pub mod dev_rpc {
use crate::{FromErr, Middleware, ProviderError};
use async_trait::async_trait;
use ethers_core::types::U256;
use thiserror::Error;
use std::fmt::Debug;
#[derive(Clone, Debug)]
pub struct DevRpcMiddleware<M>(M);
#[derive(Error, Debug)]
pub enum DevRpcMiddlewareError<M: Middleware> {
#[error("{0}")]
MiddlewareError(M::Error),
#[error("{0}")]
ProviderError(ProviderError),
#[error("Could not revert to snapshot")]
NoSnapshot,
}
#[async_trait]
impl<M: Middleware> Middleware for DevRpcMiddleware<M> {
type Error = DevRpcMiddlewareError<M>;
type Provider = M::Provider;
type Inner = M;
fn inner(&self) -> &M {
&self.0
}
}
impl<M: Middleware> FromErr<M::Error> for DevRpcMiddlewareError<M> {
fn from(src: M::Error) -> DevRpcMiddlewareError<M> {
DevRpcMiddlewareError::MiddlewareError(src)
}
}
impl<M> From<ProviderError> for DevRpcMiddlewareError<M>
where
M: Middleware,
{
fn from(src: ProviderError) -> Self {
Self::ProviderError(src)
}
}
impl<M: Middleware> DevRpcMiddleware<M> {
pub fn new(inner: M) -> Self {
Self(inner)
}
// Ganache, Hardhat and Anvil increment snapshot ID even if no state has changed
pub async fn snapshot(&self) -> Result<U256, DevRpcMiddlewareError<M>> {
self.provider().request::<(), U256>("evm_snapshot", ()).await.map_err(From::from)
}
pub async fn revert_to_snapshot(&self, id: U256) -> Result<(), DevRpcMiddlewareError<M>> {
let ok = self
.provider()
.request::<[U256; 1], bool>("evm_revert", [id])
.await
.map_err(DevRpcMiddlewareError::ProviderError)?;
if ok {
Ok(())
} else {
Err(DevRpcMiddlewareError::NoSnapshot)
}
}
}
#[cfg(test)]
// Celo blocks can not get parsed when used with Ganache
#[cfg(not(feature = "celo"))]
mod tests {
use super::*;
use crate::{Http, Provider};
use ethers_core::utils::Anvil;
use std::convert::TryFrom;
#[tokio::test]
async fn test_snapshot() {
let anvil = Anvil::new().spawn();
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
let client = DevRpcMiddleware::new(provider);
// snapshot initial state
let block0 = client.get_block_number().await.unwrap();
let time0 = client.get_block(block0).await.unwrap().unwrap().timestamp;
let snap_id0 = client.snapshot().await.unwrap();
// mine a new block
client.provider().mine(1).await.unwrap();
// snapshot state
let block1 = client.get_block_number().await.unwrap();
let time1 = client.get_block(block1).await.unwrap().unwrap().timestamp;
let snap_id1 = client.snapshot().await.unwrap();
// mine some blocks
client.provider().mine(5).await.unwrap();
// snapshot state
let block2 = client.get_block_number().await.unwrap();
let time2 = client.get_block(block2).await.unwrap().unwrap().timestamp;
let snap_id2 = client.snapshot().await.unwrap();
// mine some blocks
client.provider().mine(5).await.unwrap();
// revert_to_snapshot should reset state to snap id
client.revert_to_snapshot(snap_id2).await.unwrap();
let block = client.get_block_number().await.unwrap();
let time = client.get_block(block).await.unwrap().unwrap().timestamp;
assert_eq!(block, block2);
assert_eq!(time, time2);
client.revert_to_snapshot(snap_id1).await.unwrap();
let block = client.get_block_number().await.unwrap();
let time = client.get_block(block).await.unwrap().unwrap().timestamp;
assert_eq!(block, block1);
assert_eq!(time, time1);
// revert_to_snapshot should throw given non-existent or
// previously used snapshot
let result = client.revert_to_snapshot(snap_id1).await;
assert!(result.is_err());
client.revert_to_snapshot(snap_id0).await.unwrap();
let block = client.get_block_number().await.unwrap();
let time = client.get_block(block).await.unwrap().unwrap().timestamp;
assert_eq!(block, block0);
assert_eq!(time, time0);
}
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {

View File

@ -1,6 +1,6 @@
use crate::{JsonRpcClient, Middleware, Provider, TransactionStream};
use crate::{JsonRpcClient, Middleware, Provider};
use ethers_core::types::{TxHash, U256};
use ethers_core::types::U256;
use futures_util::stream::Stream;
use pin_project::{pin_project, pinned_drop};
@ -35,7 +35,7 @@ pub struct SubscriptionStream<'a, P: PubsubClient, R: DeserializeOwned> {
loaded_elements: VecDeque<R>,
provider: &'a Provider<P>,
pub(crate) provider: &'a Provider<P>,
#[pin]
rx: P::NotificationStream,
@ -66,6 +66,13 @@ where
self.provider.unsubscribe(self.id).await
}
/// Set the loaded elements buffer. This buffer contains logs waiting for
/// the consumer to read. Setting the buffer can be used to add logs
/// without receiving them from the RPC node
///
/// ### Warning
///
/// Setting the buffer will drop any logs in the current buffer.
pub fn set_loaded_elements(&mut self, loaded_elements: VecDeque<R>) {
self.loaded_elements = loaded_elements;
}
@ -114,18 +121,3 @@ where
let _ = (*self.provider).as_ref().unsubscribe(self.id);
}
}
impl<'a, P> SubscriptionStream<'a, P, TxHash>
where
P: PubsubClient,
{
/// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
/// yields.
///
/// This internally calls `Provider::get_transaction` with every new transaction.
/// No more than n futures will be buffered at any point in time, and less than n may also be
/// buffered depending on the state of each future.
pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
TransactionStream::new(self.provider, self, n)
}
}

View File

@ -186,11 +186,14 @@ impl<'de: 'a, 'a> Deserialize<'de> for Response<'a> {
/// Use to inject username and password or an auth token into requests
#[derive(Clone, Debug)]
pub enum Authorization {
/// HTTP Basic Auth
Basic(String),
/// Bearer Auth
Bearer(String),
}
impl Authorization {
/// Make a new basic auth
pub fn basic(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
let username = username.as_ref();
let password = password.as_ref();
@ -198,6 +201,7 @@ impl Authorization {
Self::Basic(auth_secret)
}
/// Make a new bearer auth
pub fn bearer(token: impl Into<String>) -> Self {
Self::Bearer(token.into())
}

View File

@ -1,7 +1,7 @@
// Code adapted from: https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc
use super::common::{Authorization, JsonRpcError, Request, Response};
use crate::{provider::ProviderError, JsonRpcClient};
use crate::{errors::ProviderError, JsonRpcClient};
use async_trait::async_trait;
use reqwest::{header::HeaderValue, Client, Error as ReqwestError};
use serde::{de::DeserializeOwned, Serialize};
@ -46,7 +46,12 @@ pub enum ClientError {
#[error("Deserialization Error: {err}. Response: {text}")]
/// Serde JSON Error
SerdeJson { err: serde_json::Error, text: String },
SerdeJson {
/// Underlying error
err: serde_json::Error,
/// The contents of the HTTP response that could not be deserialized
text: String,
},
}
impl From<ClientError> for ProviderError {
@ -58,13 +63,28 @@ impl From<ClientError> for ProviderError {
}
}
impl crate::RpcError for ClientError {
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
if let ClientError::JsonRpcError(err) = self {
Some(err)
} else {
None
}
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
ClientError::SerdeJson { err, .. } => Some(err),
_ => None,
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl JsonRpcClient for Provider {
type Error = ClientError;
/// Sends a POST request with the provided method and the params serialized as JSON
/// over HTTP
async fn request<T: Serialize + Send + Sync, R: DeserializeOwned>(
&self,
method: &str,

View File

@ -1,9 +1,4 @@
use super::common::Params;
use crate::{
provider::ProviderError,
transports::common::{JsonRpcError, Request, Response},
JsonRpcClient, PubsubClient,
};
use async_trait::async_trait;
use bytes::{Buf, BytesMut};
use ethers_core::types::U256;
@ -31,6 +26,9 @@ use tokio::{
sync::oneshot::{self, error::RecvError},
};
use super::common::{JsonRpcError, Request, Response};
use crate::{errors::ProviderError, JsonRpcClient, PubsubClient};
type FxHashMap<K, V> = std::collections::HashMap<K, V, BuildHasherDefault<FxHasher64>>;
type Pending = oneshot::Sender<Result<Box<RawValue>, JsonRpcError>>;
@ -471,16 +469,19 @@ pub enum IpcError {
#[error(transparent)]
IoError(#[from] io::Error),
/// Server responded to the request with a valid JSON-RPC error response
#[error(transparent)]
/// Thrown if the response could not be parsed
JsonRpcError(#[from] JsonRpcError),
/// Internal channel failed
#[error("{0}")]
ChannelError(String),
/// Listener for request result is gone
#[error(transparent)]
RequestCancelled(#[from] RecvError),
/// IPC server exited
#[error("The IPC server has exited")]
ServerExit,
}
@ -491,6 +492,23 @@ impl From<IpcError> for ProviderError {
}
}
impl crate::RpcError for IpcError {
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
if let IpcError::JsonRpcError(err) = self {
Some(err)
} else {
None
}
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
IpcError::JsonError(err) => Some(err),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -97,16 +97,32 @@ impl MockProvider {
#[derive(Error, Debug)]
/// Errors for the `MockProvider`
pub enum MockError {
/// (De)Serialization error
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
#[error("empty responses array, please push some requests")]
/// Empty requests array
#[error("empty requests array, please push some requests")]
EmptyRequests,
/// Empty responses array
#[error("empty responses array, please push some responses")]
EmptyResponses,
}
impl crate::RpcError for MockError {
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
None
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
MockError::SerdeJson(e) => Some(e),
_ => None,
}
}
}
impl From<MockError> for ProviderError {
fn from(src: MockError) -> Self {
ProviderError::JsonRpcClientError(Box::new(src))

View File

@ -1,5 +1,5 @@
mod common;
pub use common::Authorization;
pub(crate) mod common;
pub use common::{Authorization, JsonRpcError};
mod http;
pub use self::http::{ClientError as HttpClientError, Provider as Http};

View File

@ -1,4 +1,4 @@
use crate::{provider::ProviderError, JsonRpcClient, PubsubClient};
use crate::{errors::ProviderError, JsonRpcClient, PubsubClient};
use async_trait::async_trait;
use ethers_core::types::{U256, U64};
use futures_core::Stream;
@ -100,10 +100,13 @@ impl<T> QuorumProvider<T> {
QuorumProviderBuilder::default()
}
/// Instantiate a new `QuorumProvider` from a [`Quorum`] and a set of
/// providers
pub fn new(quorum: Quorum, providers: impl IntoIterator<Item = WeightedProvider<T>>) -> Self {
Self::builder().add_providers(providers).quorum(quorum).build()
}
/// Return a reference to the weighted providers
pub fn providers(&self) -> &[WeightedProvider<T>] {
&self.providers
}
@ -113,6 +116,7 @@ impl<T> QuorumProvider<T> {
self.quorum_weight
}
/// Add a provider to the set
pub fn add_provider(&mut self, provider: WeightedProvider<T>) {
self.providers.push(provider);
self.quorum_weight = self.quorum.weight(&self.providers)
@ -342,6 +346,7 @@ impl<T> WeightedProvider<T> {
Self::with_weight(inner, 1)
}
/// Instantiate a `WeightedProvider` with a set weight
pub fn with_weight(inner: T, weight: u64) -> Self {
assert!(weight > 0);
Self { inner, weight }
@ -352,7 +357,23 @@ impl<T> WeightedProvider<T> {
/// Error thrown when sending an HTTP request
pub enum QuorumError {
#[error("No Quorum reached.")]
NoQuorumReached { values: Vec<Value>, errors: Vec<ProviderError> },
/// NoQuorumReached
NoQuorumReached {
/// Returned responses
values: Vec<Value>,
/// Returned errors
errors: Vec<ProviderError>,
},
}
impl crate::RpcError for QuorumError {
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
None
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
None
}
}
impl From<QuorumError> for ProviderError {
@ -361,9 +382,12 @@ impl From<QuorumError> for ProviderError {
}
}
/// Wrapper trait for [`crate::JsonRpcClient`] that erases generics and is
/// object-safe. This trait is not intended for outside implementation
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait JsonRpcClientWrapper: Send + Sync + Debug {
/// Make a request, as [`crate::JsonRpcClient`]
async fn request(&self, method: &str, params: QuorumParams) -> Result<Value, ProviderError>;
}
type NotificationStream =

View File

@ -2,7 +2,7 @@
//! with an exponential backoff.
use super::{common::JsonRpcError, http::ClientError};
use crate::{provider::ProviderError, JsonRpcClient};
use crate::{errors::ProviderError, JsonRpcClient};
use async_trait::async_trait;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
@ -50,7 +50,7 @@ pub trait RetryPolicy<E>: Send + Sync + Debug {
pub struct RetryClient<T>
where
T: JsonRpcClient,
T::Error: Sync + Send + 'static,
T::Error: crate::RpcError + Sync + Send + 'static,
{
inner: T,
requests_enqueued: AtomicU32,
@ -114,6 +114,7 @@ where
}
}
/// Builder for a [`RetryClient`]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RetryClientBuilder {
/// How many connection `TimedOut` should be retried.
@ -201,14 +202,36 @@ impl Default for RetryClientBuilder {
/// 3. Request timed out i.e. max retries were already made.
#[derive(Error, Debug)]
pub enum RetryClientError {
/// Internal provider error
#[error(transparent)]
ProviderError(ProviderError),
/// Timeout while making requests
TimeoutError,
/// (De)Serialization error
#[error(transparent)]
SerdeJson(serde_json::Error),
/// TimerError (wasm only)
TimerError,
}
impl crate::RpcError for RetryClientError {
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
if let RetryClientError::ProviderError(err) = self {
err.as_error_response()
} else {
None
}
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
RetryClientError::ProviderError(e) => e.as_serde_error(),
RetryClientError::SerdeJson(e) => Some(e),
_ => None,
}
}
}
impl std::fmt::Display for RetryClientError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")

View File

@ -1,7 +1,7 @@
//! A [JsonRpcClient] implementation that serves as a wrapper around two different [JsonRpcClient]
//! and uses a dedicated client for read and the other for write operations
use crate::{provider::ProviderError, JsonRpcClient};
use crate::{errors::ProviderError, JsonRpcClient};
use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
@ -68,9 +68,9 @@ impl<Read, Write> RwClient<Read, Write> {
pub enum RwClientError<Read, Write>
where
Read: JsonRpcClient,
<Read as JsonRpcClient>::Error: Sync + Send + 'static,
<Read as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
Write: JsonRpcClient,
<Write as JsonRpcClient>::Error: Sync + Send + 'static,
<Write as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
{
/// Thrown if the _read_ request failed
#[error(transparent)]
@ -80,6 +80,28 @@ where
Write(Write::Error),
}
impl<Read, Write> crate::RpcError for RwClientError<Read, Write>
where
Read: JsonRpcClient,
<Read as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
Write: JsonRpcClient,
<Write as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
{
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
match self {
RwClientError::Read(e) => e.as_error_response(),
RwClientError::Write(e) => e.as_error_response(),
}
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
RwClientError::Read(e) => e.as_serde_error(),
RwClientError::Write(e) => e.as_serde_error(),
}
}
}
impl<Read, Write> From<RwClientError<Read, Write>> for ProviderError
where
Read: JsonRpcClient + 'static,

View File

@ -1,9 +1,9 @@
use super::common::{Params, Response};
use crate::{
provider::ProviderError,
transports::common::{JsonRpcError, Request},
errors::ProviderError,
rpc::transports::common::{JsonRpcError, Params, Request, Response},
JsonRpcClient, PubsubClient,
};
use async_trait::async_trait;
use ethers_core::types::U256;
use futures_channel::{mpsc, oneshot};
@ -461,9 +461,11 @@ pub enum ClientError {
TungsteniteError(#[from] WsError),
#[error("{0}")]
/// Error in internal mpsc channel
ChannelError(String),
#[error(transparent)]
#[error("{0}")]
/// Error in internal oneshot channel
Canceled(#[from] oneshot::Canceled),
/// Remote server sent a Close message
@ -472,7 +474,7 @@ pub enum ClientError {
WsClosed(CloseFrame<'static>),
/// Remote server sent a Close message
#[error("Websocket closed with info")]
#[error("Websocket closed")]
#[cfg(target_arch = "wasm32")]
WsClosed,
@ -496,6 +498,23 @@ pub enum ClientError {
RequestError(#[from] http::Error),
}
impl crate::RpcError for ClientError {
fn as_error_response(&self) -> Option<&super::JsonRpcError> {
if let ClientError::JsonRpcError(err) = self {
Some(err)
} else {
None
}
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
ClientError::JsonError(err) => Some(err),
_ => None,
}
}
}
impl From<ClientError> for ProviderError {
fn from(src: ClientError) -> Self {
ProviderError::JsonRpcClientError(Box::new(src))

View File

@ -0,0 +1,5 @@
pub mod tx_stream;
pub use tx_stream::*;
pub mod watcher;
pub use watcher::*;

View File

@ -1,143 +1,22 @@
#![allow(clippy::return_self_not_must_use)]
use crate::{JsonRpcClient, Middleware, PinBoxFut, Provider, ProviderError};
use ethers_core::types::{Transaction, TxHash, U256};
use futures_core::{stream::Stream, Future};
use futures_util::{stream, stream::FuturesUnordered, FutureExt, StreamExt};
use pin_project::pin_project;
use serde::{de::DeserializeOwned, Serialize};
use std::{
collections::VecDeque,
fmt::Debug,
pin::Pin,
task::{Context, Poll},
time::Duration,
vec::IntoIter,
};
#[cfg(not(target_arch = "wasm32"))]
use futures_timer::Delay;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Delay;
// https://github.com/tomusdrw/rust-web3/blob/befcb2fb8f3ca0a43e3081f68886fa327e64c8e6/src/api/eth_filter.rs#L20
pub fn interval(duration: Duration) -> impl Stream<Item = ()> + Send + Unpin {
stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
}
/// The default polling interval for filters and pending transactions
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(7000);
/// The polling interval to use for local endpoints, See [`crate::is_local_endpoint()`]
pub const DEFAULT_LOCAL_POLL_INTERVAL: Duration = Duration::from_millis(100);
enum FilterWatcherState<'a, R> {
WaitForInterval,
GetFilterChanges(PinBoxFut<'a, Vec<R>>),
NextItem(IntoIter<R>),
}
#[must_use = "filters do nothing unless you stream them"]
/// Streams data from an installed filter via `eth_getFilterChanges`
#[pin_project]
pub struct FilterWatcher<'a, P, R> {
/// The filter's installed id on the ethereum node
pub id: U256,
provider: &'a Provider<P>,
// The polling interval
interval: Box<dyn Stream<Item = ()> + Send + Unpin>,
/// statemachine driven by the Stream impl
state: FilterWatcherState<'a, R>,
}
impl<'a, P, R> FilterWatcher<'a, P, R>
where
P: JsonRpcClient,
R: Send + Sync + DeserializeOwned,
{
/// Creates a new watcher with the provided factory and filter id.
pub fn new<T: Into<U256>>(id: T, provider: &'a Provider<P>) -> Self {
Self {
id: id.into(),
interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
state: FilterWatcherState::WaitForInterval,
provider,
}
}
/// Sets the stream's polling interval
pub fn interval(mut self, duration: Duration) -> Self {
self.interval = Box::new(interval(duration));
self
}
/// Alias for Box::pin, must be called in order to pin the stream and be able
/// to call `next` on it.
pub fn stream(self) -> Pin<Box<Self>> {
Box::pin(self)
}
}
// Advances the filter's state machine
impl<'a, P, R> Stream for FilterWatcher<'a, P, R>
where
P: JsonRpcClient,
R: Serialize + Send + Sync + DeserializeOwned + Debug + 'a,
{
type Item = R;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
let mut this = self.project();
let id = *this.id;
loop {
*this.state = match &mut this.state {
FilterWatcherState::WaitForInterval => {
// Wait the polling period
let _ready = futures_util::ready!(this.interval.poll_next_unpin(cx));
let fut = Box::pin(this.provider.get_filter_changes(id));
FilterWatcherState::GetFilterChanges(fut)
}
FilterWatcherState::GetFilterChanges(fut) => {
// NOTE: If the provider returns an error, this will return an empty
// vector. Should we make this return a Result instead? Ideally if we're
// in a streamed loop we wouldn't want the loop to terminate if an error
// is encountered (since it might be a temporary error).
let items: Vec<R> =
futures_util::ready!(fut.as_mut().poll(cx)).unwrap_or_default();
FilterWatcherState::NextItem(items.into_iter())
}
// Consume 1 element from the vector. If more elements are in the vector,
// the next call will immediately go to this branch instead of trying to get
// filter changes again. Once the whole vector is consumed, it will poll again
// for new logs
FilterWatcherState::NextItem(iter) => {
if let item @ Some(_) = iter.next() {
return Poll::Ready(item)
}
FilterWatcherState::WaitForInterval
}
use futures_core::{stream::Stream, Future};
use futures_util::{
self,
stream::{FuturesUnordered, StreamExt},
FutureExt,
};
}
}
}
impl<'a, P> FilterWatcher<'a, P, TxHash>
where
P: JsonRpcClient,
{
/// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
/// yields.
///
/// This internally calls `Provider::get_transaction` with every new transaction.
/// No more than n futures will be buffered at any point in time, and less than n may also be
/// buffered depending on the state of each future.
pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
TransactionStream::new(self.provider, self, n)
}
}
use ethers_core::types::{Transaction, TxHash};
use crate::{
FilterWatcher, JsonRpcClient, Middleware, Provider, ProviderError, PubsubClient,
SubscriptionStream,
};
/// Errors `TransactionStream` can throw
#[derive(Debug, thiserror::Error)]
@ -159,25 +38,26 @@ impl From<GetTransactionError> for ProviderError {
}
#[cfg(not(target_arch = "wasm32"))]
type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + 'a>>;
pub(crate) type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + Send + 'a>>;
type TransactionResult = Result<Transaction, GetTransactionError>;
#[cfg(target_arch = "wasm32")]
pub(crate) type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + 'a>>;
pub(crate) type TransactionResult = Result<Transaction, GetTransactionError>;
/// Drains a stream of transaction hashes and yields entire `Transaction`.
#[must_use = "streams do nothing unless polled"]
pub struct TransactionStream<'a, P, St> {
/// Currently running futures pending completion.
pending: FuturesUnordered<TransactionFut<'a>>,
pub(crate) pending: FuturesUnordered<TransactionFut<'a>>,
/// Temporary buffered transaction that get started as soon as another future finishes.
buffered: VecDeque<TxHash>,
pub(crate) buffered: VecDeque<TxHash>,
/// The provider that gets the transaction
provider: &'a Provider<P>,
pub(crate) provider: &'a Provider<P>,
/// A stream of transaction hashes.
stream: St,
pub(crate) stream: St,
/// max allowed futures to execute at once.
max_concurrent: usize,
pub(crate) max_concurrent: usize,
}
impl<'a, P: JsonRpcClient, St> TransactionStream<'a, P, St> {
@ -193,7 +73,7 @@ impl<'a, P: JsonRpcClient, St> TransactionStream<'a, P, St> {
}
/// Push a future into the set
fn push_tx(&mut self, tx: TxHash) {
pub(crate) fn push_tx(&mut self, tx: TxHash) {
let fut = self.provider.get_transaction(tx).then(move |res| match res {
Ok(Some(tx)) => futures_util::future::ok(tx),
Ok(None) => futures_util::future::err(GetTransactionError::NotFound(tx)),
@ -254,17 +134,47 @@ where
}
}
impl<'a, P> FilterWatcher<'a, P, TxHash>
where
P: JsonRpcClient,
{
/// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
/// yields.
///
/// This internally calls `Provider::get_transaction` with every new transaction.
/// No more than n futures will be buffered at any point in time, and less than n may also be
/// buffered depending on the state of each future.
pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
TransactionStream::new(self.provider, self, n)
}
}
impl<'a, P> SubscriptionStream<'a, P, TxHash>
where
P: PubsubClient,
{
/// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
/// yields.
///
/// This internally calls `Provider::get_transaction` with every new transaction.
/// No more than n futures will be buffered at any point in time, and less than n may also be
/// buffered depending on the state of each future.
pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
TransactionStream::new(self.provider, self, n)
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use crate::{Http, Ws};
use crate::{stream::tx_stream, Http, Ws};
use ethers_core::{
types::{TransactionReceipt, TransactionRequest},
types::{Transaction, TransactionReceipt, TransactionRequest},
utils::Anvil,
};
use futures_util::{FutureExt, StreamExt};
use std::{collections::HashSet, convert::TryFrom};
use std::{collections::HashSet, convert::TryFrom, time::Duration};
#[tokio::test]
async fn can_stream_pending_transactions() {
@ -342,9 +252,9 @@ mod tests {
}))
.await;
let stream = TransactionStream::new(
let stream = tx_stream::TransactionStream::new(
&provider,
stream::iter(txs.iter().cloned().map(|tx| tx.unwrap().transaction_hash)),
futures_util::stream::iter(txs.iter().cloned().map(|tx| tx.unwrap().transaction_hash)),
10,
);
let res =

View File

@ -0,0 +1,115 @@
use crate::{
utils::{interval, PinBoxFut},
JsonRpcClient, Middleware, Provider,
};
use ethers_core::types::U256;
use futures_core::stream::Stream;
use futures_util::StreamExt;
use pin_project::pin_project;
use serde::{de::DeserializeOwned, Serialize};
use std::{
fmt::Debug,
pin::Pin,
task::{Context, Poll},
time::Duration,
vec::IntoIter,
};
/// The default polling interval for filters and pending transactions
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(7000);
/// The polling interval to use for local endpoints, See [`crate::is_local_endpoint()`]
pub const DEFAULT_LOCAL_POLL_INTERVAL: Duration = Duration::from_millis(100);
enum FilterWatcherState<'a, R> {
WaitForInterval,
GetFilterChanges(PinBoxFut<'a, Vec<R>>),
NextItem(IntoIter<R>),
}
#[must_use = "filters do nothing unless you stream them"]
/// Streams data from an installed filter via `eth_getFilterChanges`
#[pin_project]
pub struct FilterWatcher<'a, P, R> {
/// The filter's installed id on the ethereum node
pub id: U256,
pub(crate) provider: &'a Provider<P>,
// The polling interval
interval: Box<dyn Stream<Item = ()> + Send + Unpin>,
/// statemachine driven by the Stream impl
state: FilterWatcherState<'a, R>,
}
impl<'a, P, R> FilterWatcher<'a, P, R>
where
P: JsonRpcClient,
R: Send + Sync + DeserializeOwned,
{
/// Creates a new watcher with the provided factory and filter id.
pub fn new<T: Into<U256>>(id: T, provider: &'a Provider<P>) -> Self {
Self {
id: id.into(),
interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
state: FilterWatcherState::WaitForInterval,
provider,
}
}
/// Sets the stream's polling interval
pub fn interval(mut self, duration: Duration) -> Self {
self.interval = Box::new(interval(duration));
self
}
/// Alias for Box::pin, must be called in order to pin the stream and be able
/// to call `next` on it.
pub fn stream(self) -> Pin<Box<Self>> {
Box::pin(self)
}
}
// Advances the filter's state machine
impl<'a, P, R> Stream for FilterWatcher<'a, P, R>
where
P: JsonRpcClient,
R: Serialize + Send + Sync + DeserializeOwned + Debug + 'a,
{
type Item = R;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
let mut this = self.project();
let id = *this.id;
loop {
*this.state = match &mut this.state {
FilterWatcherState::WaitForInterval => {
// Wait the polling period
let _ready = futures_util::ready!(this.interval.poll_next_unpin(cx));
let fut = Box::pin(this.provider.get_filter_changes(id));
FilterWatcherState::GetFilterChanges(fut)
}
FilterWatcherState::GetFilterChanges(fut) => {
// NOTE: If the provider returns an error, this will return an empty
// vector. Should we make this return a Result instead? Ideally if we're
// in a streamed loop we wouldn't want the loop to terminate if an error
// is encountered (since it might be a temporary error).
let items: Vec<R> =
futures_util::ready!(fut.as_mut().poll(cx)).unwrap_or_default();
FilterWatcherState::NextItem(items.into_iter())
}
// Consume 1 element from the vector. If more elements are in the vector,
// the next call will immediately go to this branch instead of trying to get
// filter changes again. Once the whole vector is consumed, it will poll again
// for new logs
FilterWatcherState::NextItem(iter) => {
if let item @ Some(_) = iter.next() {
return Poll::Ready(item)
}
FilterWatcherState::WaitForInterval
}
};
}
}
}

View File

@ -1,6 +1,6 @@
//! Overrides for the `eth_call` rpc method
use crate::{JsonRpcClient, PinBoxFut, Provider, ProviderError};
use crate::{utils::PinBoxFut, JsonRpcClient, Provider, ProviderError};
use ethers_core::{
types::{
transaction::eip2718::TypedTransaction, Address, BlockId, BlockNumber, Bytes, H256, U256,
@ -59,6 +59,7 @@ impl<P: fmt::Debug> fmt::Debug for CallBuilder<'_, P> {
}
impl<'a, P> CallBuilder<'a, P> {
/// Instantiate a new call builder based on `tx`
pub fn new(provider: &'a Provider<P>, tx: &'a TypedTransaction) -> Self {
Self::Build(Caller::new(provider, tx))
}
@ -125,6 +126,7 @@ pub struct Caller<'a, P> {
}
impl<'a, P> Caller<'a, P> {
/// Instantiate a new `Caller` based on `tx`
pub fn new(provider: &'a Provider<P>, tx: &'a TypedTransaction) -> Self {
Self { provider, input: CallInput::new(tx) }
}
@ -192,6 +194,7 @@ impl<T: fmt::Debug, F> fmt::Debug for Map<T, F> {
}
impl<T, F> Map<T, F> {
/// Instantiate a new map
pub fn new(inner: T, f: F) -> Self {
Self { inner, f }
}
@ -236,12 +239,16 @@ pub mod spoof {
/// 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>,
}
@ -275,8 +282,10 @@ pub mod spoof {
/// 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>),
}

View File

@ -1,4 +1,4 @@
use super::{JsonRpcClient, Middleware, PinBoxFut, Provider, ProviderError};
use crate::{utils::PinBoxFut, JsonRpcClient, Middleware, Provider, ProviderError};
use ethers_core::types::{Filter, Log, U64};
use futures_core::stream::Stream;
use std::{
@ -8,6 +8,9 @@ use std::{
};
use thiserror::Error;
/// A log query provides streaming access to historical logs via a paginated
/// request. For streaming access to future logs, use [`Middleware::watch`] or
/// [`Middleware::subscribe_logs`]
pub struct LogQuery<'a, P> {
provider: &'a Provider<P>,
filter: Filter,
@ -29,6 +32,7 @@ impl<'a, P> LogQuery<'a, P>
where
P: JsonRpcClient,
{
/// Instantiate a new `LogQuery`
pub fn new(provider: &'a Provider<P>, filter: &Filter) -> Self {
Self {
provider,
@ -56,10 +60,13 @@ macro_rules! rewake_with_new_state {
};
}
/// Errors while querying for logs
#[derive(Error, Debug)]
pub enum LogQueryError<E> {
/// Error loading latest block
#[error(transparent)]
LoadLastBlockError(E),
/// Error loading logs from block range
#[error(transparent)]
LoadLogsError(E),
}

View File

@ -0,0 +1,11 @@
mod pending_transaction;
pub use pending_transaction::PendingTransaction;
mod pending_escalator;
pub use pending_escalator::EscalatingPending;
mod log_query;
pub use log_query::{LogQuery, LogQueryError};
pub mod call_raw;
pub use call_raw::*;

View File

@ -15,7 +15,9 @@ use futures_timer::Delay;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Delay;
use crate::{JsonRpcClient, Middleware, PendingTransaction, PinBoxFut, Provider, ProviderError};
use crate::{
utils::PinBoxFut, JsonRpcClient, Middleware, PendingTransaction, Provider, ProviderError,
};
/// States for the EscalatingPending future
enum EscalatorStates<'a, P> {

View File

@ -1,4 +1,7 @@
use crate::{stream::interval, JsonRpcClient, Middleware, PinBoxFut, Provider, ProviderError};
use crate::{
utils::{interval, PinBoxFut},
JsonRpcClient, Middleware, Provider, ProviderError,
};
use ethers_core::types::{Transaction, TransactionReceipt, TxHash, U64};
use futures_core::stream::Stream;
use futures_util::stream::StreamExt;

View File

@ -0,0 +1,40 @@
use ethers_core::types::U256;
use futures_util::{stream, FutureExt, StreamExt};
use std::{future::Future, pin::Pin};
#[cfg(not(target_arch = "wasm32"))]
use futures_timer::Delay;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Delay;
use crate::ProviderError;
/// A simple gas escalation policy
pub type EscalationPolicy = Box<dyn Fn(U256, usize) -> U256 + Send + Sync>;
// Helper type alias
#[cfg(target_arch = "wasm32")]
pub(crate) type PinBoxFut<'a, T> = Pin<Box<dyn Future<Output = Result<T, ProviderError>> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) type PinBoxFut<'a, T> =
Pin<Box<dyn Future<Output = Result<T, ProviderError>> + Send + 'a>>;
/// Calls the future if `item` is None, otherwise returns a `futures::ok`
pub async fn maybe<F, T, E>(item: Option<T>, f: F) -> Result<T, E>
where
F: Future<Output = Result<T, E>>,
{
if let Some(item) = item {
futures_util::future::ok(item).await
} else {
f.await
}
}
// https://github.com/tomusdrw/rust-web3/blob/befcb2fb8f3ca0a43e3081f68886fa327e64c8e6/src/api/eth_filter.rs#L20
/// Create a stream that emits items at a fixed interval. Used for rate control
pub fn interval(
duration: std::time::Duration,
) -> impl futures_core::stream::Stream<Item = ()> + Send + Unpin {
stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
}

View File

@ -25,7 +25,7 @@ async fn graceful_disconnect_on_ws_errors() {
spawn_ws_server().await;
// Connect to the fake server
let (ws, _) = connect_async(format!("ws://{}", WS_ENDPOINT)).await.unwrap();
let (ws, _) = connect_async(format!("ws://{WS_ENDPOINT}")).await.unwrap();
let provider = Provider::new(Ws::new(ws));
let filter = Filter::new().event("Transfer(address,address,uint256)");
let mut stream = provider.subscribe_logs(&filter).await.unwrap();

View File

@ -5,7 +5,7 @@ use ethers::{
utils::{parse_units, Anvil},
},
middleware::MiddlewareBuilder,
providers::{FromErr, Http, Middleware, PendingTransaction, Provider},
providers::{Http, Middleware, MiddlewareError, PendingTransaction, Provider},
signers::{LocalWallet, Signer},
};
use thiserror::Error;
@ -98,7 +98,7 @@ where
println!("Raised transaction gas: {raised_gas:?} wei");
// Dispatch the call to the inner layer
self.inner().send_transaction(tx, block).await.map_err(FromErr::from)
self.inner().send_transaction(tx, block).await.map_err(MiddlewareError::from_err)
}
}
@ -122,10 +122,19 @@ pub enum GasMiddlewareError<M: Middleware> {
NoGasSetForTransaction,
}
impl<M: Middleware> FromErr<M::Error> for GasMiddlewareError<M> {
fn from(src: M::Error) -> Self {
impl<M: Middleware> MiddlewareError for GasMiddlewareError<M> {
type Inner = M::Error;
fn from_err(src: M::Error) -> Self {
GasMiddlewareError::MiddlewareError(src)
}
fn as_inner(&self) -> Option<&Self::Inner> {
match self {
GasMiddlewareError::MiddlewareError(e) => Some(e),
_ => None,
}
}
}
#[tokio::main]

View File

@ -7,12 +7,14 @@ use std::fmt::Debug;
use thiserror::Error;
use url::Url;
/// First we must create an error type, and implement [`From`] for [`ProviderError`].
/// First we must create an error type, and implement [`From`] for
/// [`ProviderError`].
///
/// Here we are using [`thiserror`](https://docs.rs/thiserror) to wrap [`WsClientError`]
/// and [`IpcError`].
/// This also provides a conversion implementation ([`From`]) for both, so we can use
/// the [question mark operator](https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html)
/// Here we are using [`thiserror`](https://docs.rs/thiserror) to wrap
/// [`WsClientError`] and [`IpcError`].
///
/// This also provides a conversion implementation ([`From`]) for both, so we
/// can use the [question mark operator](https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html)
/// later on in our implementations.
#[derive(Debug, Error)]
pub enum WsOrIpcError {
@ -23,6 +25,36 @@ pub enum WsOrIpcError {
Ipc(#[from] IpcError),
}
/// In order to use our `WsOrIpcError` in the RPC client, we have to implement
/// this trait.
///
/// [`RpcError`] helps other parts off the stack get access to common provider
/// error cases. For example, any RPC connection may have a `serde_json` error,
/// so we want to make those easily accessible, so we implement
/// `as_serde_error()`
///
/// In addition, RPC requests may return JSON errors from the node, describing
/// why the request failed. In order to make these accessible, we implement
/// `as_error_response()`.
impl RpcError for WsOrIpcError {
fn as_error_response(&self) -> Option<&ethers::providers::JsonRpcError> {
match self {
WsOrIpcError::Ws(e) => e.as_error_response(),
WsOrIpcError::Ipc(e) => e.as_error_response(),
}
}
fn as_serde_error(&self) -> Option<&serde_json::Error> {
match self {
WsOrIpcError::Ws(WsClientError::JsonError(e)) => Some(e),
WsOrIpcError::Ipc(IpcError::JsonError(e)) => Some(e),
_ => None,
}
}
}
/// This implementation helps us convert our Error to the library's
/// [`ProviderError`] so that we can use the `?` operator
impl From<WsOrIpcError> for ProviderError {
fn from(value: WsOrIpcError) -> Self {
Self::JsonRpcClientError(Box::new(value))