diff --git a/Cargo.toml b/Cargo.toml index a0e3335..f8699f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,13 +128,5 @@ serde = "1.0" serde_json = "1.0" [patch.crates-io] -alloy = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } -alloy-serde = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", branch = "dani/rm-T-transport" } - -foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", branch = "dani/alloy-0.10" } +# alloy = { git = "https://github.com/alloy-rs/alloy", rev = "65dfbe" } +# foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "d113d6e" } diff --git a/examples/advanced/Cargo.toml b/examples/advanced/Cargo.toml index cd76e73..7563a1d 100644 --- a/examples/advanced/Cargo.toml +++ b/examples/advanced/Cargo.toml @@ -19,11 +19,11 @@ revm-primitives.workspace = true revm.workspace = true # reth -# reth-db = { git = "https://github.com/paradigmxyz/reth", package = "reth-db", rev = "c0a8a7b" } -# reth-provider = { git = "https://github.com/paradigmxyz/reth", package = "reth-provider", rev = "c0a8a7b" } -# reth-node-types = { git = "https://github.com/paradigmxyz/reth", package = "reth-node-types", rev = "c0a8a7b" } -# reth-chainspec = { git = "https://github.com/paradigmxyz/reth", package = "reth-chainspec", rev = "c0a8a7b" } -# reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", package = "reth-node-ethereum", rev = "c0a8a7b" } +reth-db = { git = "https://github.com/paradigmxyz/reth", package = "reth-db", rev = "c0a8a7b" } +reth-provider = { git = "https://github.com/paradigmxyz/reth", package = "reth-provider", rev = "c0a8a7b" } +reth-node-types = { git = "https://github.com/paradigmxyz/reth", package = "reth-node-types", rev = "c0a8a7b" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", package = "reth-chainspec", rev = "c0a8a7b" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", package = "reth-node-ethereum", rev = "c0a8a7b" } eyre.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/examples/advanced/examples/reth_db_layer.rs b/examples/advanced/examples/reth_db_layer.rs new file mode 100644 index 0000000..9945d26 --- /dev/null +++ b/examples/advanced/examples/reth_db_layer.rs @@ -0,0 +1,24 @@ +//! `RethDbLayer` implementation to be used with `RethDbProvider` to wrap the Provider trait over +//! reth-db. + +#![allow(dead_code)] +use std::path::PathBuf; + +/// We use the tower-like layering functionality that has been baked into the alloy-provider to +/// intercept the requests and redirect to the `RethDbProvider`. +pub(crate) struct RethDbLayer { + db_path: PathBuf, +} + +/// Initialize the `RethDBLayer` with the path to the reth datadir. +impl RethDbLayer { + pub(crate) const fn new(db_path: PathBuf) -> Self { + Self { db_path } + } + + pub(crate) const fn db_path(&self) -> &PathBuf { + &self.db_path + } +} + +const fn main() {} diff --git a/examples/advanced/examples/reth_db_provider.rs b/examples/advanced/examples/reth_db_provider.rs new file mode 100644 index 0000000..2b315cc --- /dev/null +++ b/examples/advanced/examples/reth_db_provider.rs @@ -0,0 +1,227 @@ +//! In this example, we demonstrate how we wrap the `Provider` trait over reth-db by +//! leveraging `ProviderCall`. +//! +//! `ProviderCall` enables the alloy-provider to fetch results of a rpc request from arbitrary +//! sources. These arbitray sources could be a RPC call over the network, a local database, or even +//! a synchronous function call. +//! +//! `ProviderCall` is the final future in the flow of an rpc request and is used by the +//! `RpcWithBlock` and `EthCall` types under the hood to give flexibility to the user to use +//! their own implementation of the `Provider` trait and fetch results from any source. +//! +//! Learn more about `ProviderCall` [here](https://github.com/alloy-rs/alloy/pull/788). + +use std::{marker::PhantomData, path::PathBuf, sync::Arc}; + +use alloy::{ + eips::{BlockId, BlockNumberOrTag}, + node_bindings::{utils::run_with_tempdir, Reth}, + primitives::{address, Address, U64}, + providers::{ + Provider, ProviderBuilder, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock, + }, + rpc::client::NoParams, + transports::{Transport, TransportErrorKind}, +}; +use eyre::Result; + +use reth_chainspec::ChainSpecBuilder; +use reth_db::{open_db_read_only, DatabaseEnv}; +use reth_node_ethereum::EthereumNode; +use reth_node_types::NodeTypesWithDBAdapter; +use reth_provider::{ + providers::StaticFileProvider, BlockNumReader, DatabaseProviderFactory, ProviderError, + ProviderFactory, StateProvider, TryIntoHistoricalStateProvider, +}; +mod reth_db_layer; +use reth_db_layer::RethDbLayer; + +#[tokio::main] +async fn main() -> Result<()> { + run_with_tempdir("provider-call-reth-db", |data_dir| async move { + // Initializing reth with a tmp data directory. + // We use a tmp directory for the purposes of this example. + // This would actually use an existing reth datadir specified by `--datadir` when starting + // your reth node. + let reth = Reth::new() + .dev() + .disable_discovery() + .block_time("1s") + .data_dir(data_dir.clone()) + .spawn(); + + let db_path = data_dir.join("db"); + + // Initialize the provider with the reth-db layer. The reth-db layer intercepts the rpc + // requests and returns the results from the reth-db database. + // Any RPC method that is not implemented in the RethDbProvider gracefully falls back to the + // RPC provider specified in the `on_http` method. + let provider = + ProviderBuilder::new().layer(RethDbLayer::new(db_path)).on_http(reth.endpoint_url()); + + // Initialize the RPC provider to compare the time taken to fetch the results. + let rpc_provider = ProviderBuilder::new().on_http(reth.endpoint_url()); + + println!("--------get_block_number---------"); + + let start_t = std::time::Instant::now(); + let latest_block_db = provider.get_block_number().await.unwrap(); + println!("via reth-db: {:?}", start_t.elapsed()); + + let start_t = std::time::Instant::now(); + let latest_block_rpc = rpc_provider.get_block_number().await.unwrap(); + println!("via rpc: {:?}\n", start_t.elapsed()); + + assert_eq!(latest_block_db, latest_block_rpc); + + println!("------get_transaction_count------"); + + let alice = address!("14dC79964da2C08b23698B3D3cc7Ca32193d9955"); + + let start_t = std::time::Instant::now(); + let nonce_db = + provider.get_transaction_count(alice).block_id(BlockId::latest()).await.unwrap(); + println!("via reth-db: {:?}", start_t.elapsed()); + + let start_t = std::time::Instant::now(); + let nonce_rpc = + rpc_provider.get_transaction_count(alice).block_id(BlockId::latest()).await.unwrap(); + println!("via rpc: {:?}\n", start_t.elapsed()); + + assert_eq!(nonce_db, nonce_rpc); + }) + .await; + + Ok(()) +} + +/// Implement the `ProviderLayer` trait for the `RethDBLayer` struct. +impl ProviderLayer for RethDbLayer +where + P: Provider, + T: Transport + Clone, +{ + type Provider = RethDbProvider; + + fn layer(&self, inner: P) -> Self::Provider { + RethDbProvider::new(inner, self.db_path().clone()) + } +} + +/// A provider that overrides the vanilla `Provider` trait to get results from the reth-db. +/// +/// It holds the `reth_provider::ProviderFactory` that enables read-only access to the database +/// tables and static files. +#[derive(Clone, Debug)] +pub struct RethDbProvider { + inner: P, + db_path: PathBuf, + provider_factory: DbAccessor, + _pd: PhantomData, +} + +impl RethDbProvider { + /// Create a new `RethDbProvider` instance. + pub fn new(inner: P, db_path: PathBuf) -> Self { + let db = open_db_read_only(&db_path, Default::default()).unwrap(); + let chain_spec = ChainSpecBuilder::mainnet().build(); + let static_file_provider = + StaticFileProvider::read_only(db_path.join("static_files"), false).unwrap(); + + let provider_factory = + ProviderFactory::new(db.into(), chain_spec.into(), static_file_provider); + + let db_accessor: DbAccessor< + ProviderFactory>>, + > = DbAccessor::new(provider_factory); + Self { inner, db_path, provider_factory: db_accessor, _pd: PhantomData } + } + + const fn factory(&self) -> &DbAccessor { + &self.provider_factory + } + + /// Get the DB Path + pub fn db_path(&self) -> PathBuf { + self.db_path.clone() + } +} + +/// Implement the `Provider` trait for the `RethDbProvider` struct. +/// +/// This is where we override specific RPC methods to fetch from the reth-db. +impl Provider for RethDbProvider +where + P: Provider, + T: Transport + Clone, +{ + fn root(&self) -> &RootProvider { + self.inner.root() + } + + /// Override the `get_block_number` method to fetch the latest block number from the reth-db. + fn get_block_number(&self) -> ProviderCall { + let provider = self.factory().provider().map_err(TransportErrorKind::custom).unwrap(); + + let best = provider.best_block_number().map_err(TransportErrorKind::custom); + + ProviderCall::ready(best) + } + + /// Override the `get_transaction_count` method to fetch the transaction count of an address. + /// + /// `RpcWithBlock` uses `ProviderCall` under the hood. + fn get_transaction_count(&self, address: Address) -> RpcWithBlock { + let this = self.factory().clone(); + RpcWithBlock::new_provider(move |block_id| { + let provider = this.provider_at(block_id).map_err(TransportErrorKind::custom).unwrap(); + + let maybe_acc = + provider.basic_account(&address).map_err(TransportErrorKind::custom).unwrap(); + + let nonce = maybe_acc.map(|acc| acc.nonce).unwrap_or_default(); + + ProviderCall::ready(Ok(nonce)) + }) + } +} + +/// A helper type to get the appropriate DB provider. +#[derive(Debug, Clone)] +struct DbAccessor>>> +where + DB: DatabaseProviderFactory, +{ + inner: DB, +} + +impl DbAccessor +where + DB: DatabaseProviderFactory, +{ + const fn new(inner: DB) -> Self { + Self { inner } + } + + fn provider(&self) -> Result { + self.inner.database_provider_ro() + } + + fn provider_at(&self, block_id: BlockId) -> Result, ProviderError> { + let provider = self.inner.database_provider_ro()?; + + let block_number = match block_id { + BlockId::Hash(hash) => { + if let Some(num) = provider.block_number(hash.into())? { + num + } else { + return Err(ProviderError::BlockHashNotFound(hash.into())); + } + } + BlockId::Number(BlockNumberOrTag::Number(num)) => num, + _ => provider.best_block_number()?, + }; + + provider.try_into_history_at_block(block_number) + } +}