feat(providers): unify get_block_receipts for eth/parity RPCs (#541)
* feat(providers): unify get_block_receipts for eth/parity RPCs * add besu * impl tryfrom for nodeclient * better node client storage * fix lint * tryfrom<&str> -> fromstr * fix: remove nested Option Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
parent
3b2aa955d0
commit
dc3ac427f4
|
@ -557,14 +557,6 @@ pub trait Middleware: Sync + Send + Debug {
|
||||||
|
|
||||||
// Parity namespace
|
// 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>(
|
async fn subscribe<T, R>(
|
||||||
&self,
|
&self,
|
||||||
params: T,
|
params: T,
|
||||||
|
|
|
@ -25,10 +25,35 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
use std::{convert::TryFrom, fmt::Debug, time::Duration};
|
use futures_util::lock::Mutex;
|
||||||
|
use std::{convert::TryFrom, fmt::Debug, str::FromStr, sync::Arc, time::Duration};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
use tracing_futures::Instrument;
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum NodeClient {
|
||||||
|
Geth,
|
||||||
|
Erigon,
|
||||||
|
OpenEthereum,
|
||||||
|
Nethermind,
|
||||||
|
Besu,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for NodeClient {
|
||||||
|
type Err = ProviderError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.split('/').next().unwrap() {
|
||||||
|
"Geth" => Ok(NodeClient::Geth),
|
||||||
|
"Erigon" => Ok(NodeClient::Erigon),
|
||||||
|
"OpenEthereum" => Ok(NodeClient::OpenEthereum),
|
||||||
|
"Nethermind" => Ok(NodeClient::Nethermind),
|
||||||
|
"besu" => Ok(NodeClient::Besu),
|
||||||
|
_ => Err(ProviderError::UnsupportedNodeClient),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An abstract provider for interacting with the [Ethereum JSON RPC
|
/// An abstract provider for interacting with the [Ethereum JSON RPC
|
||||||
/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC). Must be instantiated
|
/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC). Must be instantiated
|
||||||
/// with a data transport which implements the [`JsonRpcClient`](trait@crate::JsonRpcClient) trait
|
/// with a data transport which implements the [`JsonRpcClient`](trait@crate::JsonRpcClient) trait
|
||||||
|
@ -56,6 +81,10 @@ pub struct Provider<P> {
|
||||||
ens: Option<Address>,
|
ens: Option<Address>,
|
||||||
interval: Option<Duration>,
|
interval: Option<Duration>,
|
||||||
from: Option<Address>,
|
from: Option<Address>,
|
||||||
|
/// Node client hasn't been checked yet = `None`
|
||||||
|
/// Unsupported node client = `Some(None)`
|
||||||
|
/// Supported node client = `Some(Some(NodeClient))`
|
||||||
|
_node_client: Arc<Mutex<Option<NodeClient>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> AsRef<P> for Provider<P> {
|
impl<P> AsRef<P> for Provider<P> {
|
||||||
|
@ -89,6 +118,12 @@ pub enum ProviderError {
|
||||||
|
|
||||||
#[error("custom error: {0}")]
|
#[error("custom error: {0}")]
|
||||||
CustomError(String),
|
CustomError(String),
|
||||||
|
|
||||||
|
#[error("unsupported RPC")]
|
||||||
|
UnsupportedRPC,
|
||||||
|
|
||||||
|
#[error("unsupported node client")]
|
||||||
|
UnsupportedNodeClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Types of filters supported by the JSON-RPC.
|
/// Types of filters supported by the JSON-RPC.
|
||||||
|
@ -108,7 +143,31 @@ pub enum FilterKind<'a> {
|
||||||
impl<P: JsonRpcClient> Provider<P> {
|
impl<P: JsonRpcClient> Provider<P> {
|
||||||
/// Instantiate a new provider with a backend.
|
/// Instantiate a new provider with a backend.
|
||||||
pub fn new(provider: P) -> Self {
|
pub fn new(provider: P) -> Self {
|
||||||
Self { inner: provider, ens: None, interval: None, from: None }
|
Self {
|
||||||
|
inner: provider,
|
||||||
|
ens: None,
|
||||||
|
interval: None,
|
||||||
|
from: None,
|
||||||
|
_node_client: Arc::new(Mutex::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of node we're connected to, while also caching the value for use
|
||||||
|
/// in other node-specific API calls, such as the get_block_receipts call.
|
||||||
|
pub async fn node_client(&self) -> Result<NodeClient, ProviderError> {
|
||||||
|
let mut node_client = self._node_client.lock().await;
|
||||||
|
|
||||||
|
if let Some(node_client) = *node_client {
|
||||||
|
Ok(node_client)
|
||||||
|
} else {
|
||||||
|
let client_version = self.client_version().await?;
|
||||||
|
let client_version = match client_version.parse::<NodeClient>() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => return Err(ProviderError::UnsupportedNodeClient),
|
||||||
|
};
|
||||||
|
*node_client = Some(client_version);
|
||||||
|
Ok(client_version)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_sender(mut self, address: impl Into<Address>) -> Self {
|
pub fn with_sender(mut self, address: impl Into<Address>) -> Self {
|
||||||
|
@ -275,13 +334,19 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
||||||
|
|
||||||
/// Returns all receipts for a block.
|
/// Returns all receipts for a block.
|
||||||
///
|
///
|
||||||
/// Note that this uses the `eth_getBlockReceipts` RPC, which is
|
/// Note that this uses the `eth_getBlockReceipts` or `parity_getBlockReceipts` RPC, which is
|
||||||
/// non-standard and currently supported by Erigon.
|
/// non-standard and currently supported by Erigon, OpenEthereum and Nethermind.
|
||||||
async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
|
async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
|
||||||
&self,
|
&self,
|
||||||
block: T,
|
block: T,
|
||||||
) -> Result<Vec<TransactionReceipt>, Self::Error> {
|
) -> Result<Vec<TransactionReceipt>, Self::Error> {
|
||||||
self.request("eth_getBlockReceipts", [block.into()]).await
|
let method = match self.node_client().await? {
|
||||||
|
NodeClient::Erigon => "eth_getBlockReceipts",
|
||||||
|
NodeClient::OpenEthereum | NodeClient::Nethermind => "parity_getBlockReceipts",
|
||||||
|
_ => return Err(ProviderError::UnsupportedRPC),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.request(method, [block.into()]).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current gas price as estimated by the node
|
/// Gets the current gas price as estimated by the node
|
||||||
|
@ -712,14 +777,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
||||||
self.request("trace_transaction", vec![hash]).await
|
self.request("trace_transaction", vec![hash]).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,
|
|
||||||
) -> Result<Vec<TransactionReceipt>, Self::Error> {
|
|
||||||
self.request("parity_getBlockReceipts", vec![block.into()]).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn subscribe<T, R>(
|
async fn subscribe<T, R>(
|
||||||
&self,
|
&self,
|
||||||
params: T,
|
params: T,
|
||||||
|
@ -1085,7 +1142,7 @@ mod tests {
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let provider = Provider::<Http>::try_from(url.as_str()).unwrap();
|
let provider = Provider::<Http>::try_from(url.as_str()).unwrap();
|
||||||
let receipts = provider.parity_block_receipts(10657200).await.unwrap();
|
let receipts = provider.get_block_receipts(10657200).await.unwrap();
|
||||||
assert!(!receipts.is_empty());
|
assert!(!receipts.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue