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:
parent
33ed94851c
commit
ef6e7f41a1
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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>,
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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};
|
|
@ -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 {
|
||||
|
|
|
@ -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 {}
|
|
@ -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>;
|
||||
}
|
|
@ -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};
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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,
|
|
@ -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::*;
|
|
@ -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))
|
|
@ -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};
|
|
@ -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 =
|
|
@ -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:?}")
|
|
@ -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,
|
|
@ -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))
|
|
@ -0,0 +1,5 @@
|
|||
pub mod tx_stream;
|
||||
pub use tx_stream::*;
|
||||
|
||||
pub mod watcher;
|
||||
pub use watcher::*;
|
|
@ -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 =
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>),
|
||||
}
|
|
@ -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),
|
||||
}
|
|
@ -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::*;
|
|
@ -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> {
|
|
@ -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;
|
|
@ -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)
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<ðers::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))
|
||||
|
|
Loading…
Reference in New Issue