Skip to content

Commit c184890

Browse files
authored
Merge pull request #144 from alloy-rs/yash/reth-db-prov-call
feat(advanced): `Provider` trait over reth-db
2 parents 4773fb9 + e1557cf commit c184890

File tree

6 files changed

+262
-0
lines changed

6 files changed

+262
-0
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ jobs:
6060
-e 'ws_auth' \
6161
-e 'ws' \
6262
-e 'yubi_signer' \
63+
-e 'reth_db_provider' \
6364
| xargs -n1 echo
6465
)"
6566

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ alloy = { version = "0.4.0", features = [
108108
"signer-mnemonic",
109109
"signer-trezor",
110110
"signer-yubihsm",
111+
"eips",
111112
] }
112113

113114
foundry-fork-db = "0.4.0"
@@ -122,3 +123,6 @@ tokio = "1.40"
122123
eyre = "0.6"
123124
serde = "1.0"
124125
serde_json = "1.0"
126+
127+
# [patch.crates-io]
128+
# alloy = { git = "https://github.com/alloy-rs/alloy", rev = "65dfbe" }

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ This repository contains the following examples:
110110
- [x] [Encoding with `dyn_abi`](./examples/advanced/examples/encoding_dyn_abi.rs)
111111
- [x] [Static encoding with `sol!`](./examples/advanced/examples/encoding_sol_static.rs)
112112
- [x] [Using `foundry-fork-db`](./examples/advanced/examples/foundry_fork_db.rs)
113+
- [x] [Wrapping `Provider` trait over reth-db](./examples/advanced/examples/reth_db_provider.rs)
113114

114115
## Contributing
115116

examples/advanced/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ foundry-fork-db.workspace = true
1818
revm-primitives.workspace = true
1919
revm.workspace = true
2020

21+
# reth
22+
reth-db = { git = "https://github.com/paradigmxyz/reth", package = "reth-db", tag = "v1.0.8" }
23+
reth-provider = { git = "https://github.com/paradigmxyz/reth", package = "reth-provider", tag = "v1.0.8" }
24+
reth-node-types = { git = "https://github.com/paradigmxyz/reth", package = "reth-node-types", tag = "v1.0.8" }
25+
reth-chainspec = { git = "https://github.com/paradigmxyz/reth", package = "reth-chainspec", tag = "v1.0.8" }
26+
reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", package = "reth-node-ethereum", tag = "v1.0.8" }
27+
2128
eyre.workspace = true
2229
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
2330
serde = { workspace = true, features = ["derive"] }
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! `RethDbLayer` implementation to be used with `RethDbProvider` to wrap the Provider trait over
2+
//! reth-db.
3+
#![allow(dead_code)]
4+
use std::path::PathBuf;
5+
6+
/// We use the tower-like layering functionality that has been baked into the alloy-provider to
7+
/// intercept the requests and redirect to the `RethDbProvider`.
8+
pub(crate) struct RethDbLayer {
9+
db_path: PathBuf,
10+
}
11+
12+
/// Initialize the `RethDBLayer` with the path to the reth datadir.
13+
impl RethDbLayer {
14+
pub(crate) const fn new(db_path: PathBuf) -> Self {
15+
Self { db_path }
16+
}
17+
18+
pub(crate) const fn db_path(&self) -> &PathBuf {
19+
&self.db_path
20+
}
21+
}
22+
23+
const fn main() {}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
//! In this example, we demonstrate how we wrap the `Provider` trait over reth-db by
2+
//! leveraging `ProviderCall`.
3+
//!
4+
//! `ProviderCall` enables the alloy-provider to fetch results of a rpc request from arbitrary
5+
//! sources. These arbitray sources could be a RPC call over the network, a local database, or even
6+
//! a synchronous function call.
7+
//!
8+
//! `ProviderCall` is the final future in the flow of an rpc request and is used by the
9+
//! `RpcWithBlock` and `EthCall` types under the hood to give flexibility to the user to use
10+
//! their own implementation of the `Provider` trait and fetch results from any source.
11+
//!
12+
//! Learn more about `ProviderCall` [here](https://github.com/alloy-rs/alloy/pull/788).
13+
use std::{marker::PhantomData, path::PathBuf, sync::Arc};
14+
15+
use alloy::{
16+
eips::{BlockId, BlockNumberOrTag},
17+
node_bindings::{utils::run_with_tempdir, Reth},
18+
primitives::{address, Address, U64},
19+
providers::{
20+
Provider, ProviderBuilder, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock,
21+
},
22+
rpc::client::NoParams,
23+
transports::{Transport, TransportErrorKind},
24+
};
25+
use eyre::Result;
26+
27+
use reth_chainspec::ChainSpecBuilder;
28+
use reth_db::{open_db_read_only, DatabaseEnv};
29+
use reth_node_ethereum::EthereumNode;
30+
use reth_node_types::NodeTypesWithDBAdapter;
31+
use reth_provider::{
32+
providers::StaticFileProvider, BlockNumReader, DatabaseProviderFactory, ProviderError,
33+
ProviderFactory, StateProvider, TryIntoHistoricalStateProvider,
34+
};
35+
mod reth_db_layer;
36+
use reth_db_layer::RethDbLayer;
37+
38+
#[tokio::main]
39+
async fn main() -> Result<()> {
40+
run_with_tempdir("provider-call-reth-db", |data_dir| async move {
41+
// Initializing reth with a tmp data directory.
42+
// We use a tmp directory for the purposes of this example.
43+
// This would actually use an existing reth datadir specified by `--datadir` when starting
44+
// your reth node.
45+
let reth = Reth::new()
46+
.dev()
47+
.disable_discovery()
48+
.block_time("1s")
49+
.data_dir(data_dir.clone())
50+
.spawn();
51+
52+
let db_path = data_dir.join("db");
53+
54+
// Initialize the provider with the reth-db layer. The reth-db layer intercepts the rpc
55+
// requests and returns the results from the reth-db database.
56+
// Any RPC method that is not implemented in the RethDbProvider gracefully falls back to the
57+
// RPC provider specified in the `on_http` method.
58+
let provider =
59+
ProviderBuilder::new().layer(RethDbLayer::new(db_path)).on_http(reth.endpoint_url());
60+
61+
// Initialize the RPC provider to compare the time taken to fetch the results.
62+
let rpc_provider = ProviderBuilder::new().on_http(reth.endpoint_url());
63+
64+
println!("--------get_block_number---------");
65+
66+
let start_t = std::time::Instant::now();
67+
let latest_block_db = provider.get_block_number().await.unwrap();
68+
println!("via reth-db: {:?}", start_t.elapsed());
69+
70+
let start_t = std::time::Instant::now();
71+
let latest_block_rpc = rpc_provider.get_block_number().await.unwrap();
72+
println!("via rpc: {:?}\n", start_t.elapsed());
73+
74+
assert_eq!(latest_block_db, latest_block_rpc);
75+
76+
println!("------get_transaction_count------");
77+
78+
let alice = address!("14dC79964da2C08b23698B3D3cc7Ca32193d9955");
79+
80+
let start_t = std::time::Instant::now();
81+
let nonce_db =
82+
provider.get_transaction_count(alice).block_id(BlockId::latest()).await.unwrap();
83+
println!("via reth-db: {:?}", start_t.elapsed());
84+
85+
let start_t = std::time::Instant::now();
86+
let nonce_rpc =
87+
rpc_provider.get_transaction_count(alice).block_id(BlockId::latest()).await.unwrap();
88+
println!("via rpc: {:?}\n", start_t.elapsed());
89+
90+
assert_eq!(nonce_db, nonce_rpc);
91+
})
92+
.await;
93+
94+
Ok(())
95+
}
96+
97+
/// Implement the `ProviderLayer` trait for the `RethDBLayer` struct.
98+
impl<P, T> ProviderLayer<P, T> for RethDbLayer
99+
where
100+
P: Provider<T>,
101+
T: Transport + Clone,
102+
{
103+
type Provider = RethDbProvider<P, T>;
104+
105+
fn layer(&self, inner: P) -> Self::Provider {
106+
RethDbProvider::new(inner, self.db_path().clone())
107+
}
108+
}
109+
110+
/// A provider that overrides the vanilla `Provider` trait to get results from the reth-db.
111+
///
112+
/// It holds the `reth_provider::ProviderFactory` that enables read-only access to the database
113+
/// tables and static files.
114+
#[derive(Clone, Debug)]
115+
pub struct RethDbProvider<P, T> {
116+
inner: P,
117+
db_path: PathBuf,
118+
provider_factory: DbAccessor,
119+
_pd: PhantomData<T>,
120+
}
121+
122+
impl<P, T> RethDbProvider<P, T> {
123+
/// Create a new `RethDbProvider` instance.
124+
pub fn new(inner: P, db_path: PathBuf) -> Self {
125+
let db = open_db_read_only(&db_path, Default::default()).unwrap();
126+
let chain_spec = ChainSpecBuilder::mainnet().build();
127+
let static_file_provider =
128+
StaticFileProvider::read_only(db_path.join("static_files"), false).unwrap();
129+
130+
let provider_factory =
131+
ProviderFactory::new(db.into(), chain_spec.into(), static_file_provider);
132+
133+
let db_accessor: DbAccessor<
134+
ProviderFactory<NodeTypesWithDBAdapter<EthereumNode, Arc<DatabaseEnv>>>,
135+
> = DbAccessor::new(provider_factory);
136+
Self { inner, db_path, provider_factory: db_accessor, _pd: PhantomData }
137+
}
138+
139+
const fn factory(&self) -> &DbAccessor {
140+
&self.provider_factory
141+
}
142+
143+
/// Get the DB Path
144+
pub fn db_path(&self) -> PathBuf {
145+
self.db_path.clone()
146+
}
147+
}
148+
149+
/// Implement the `Provider` trait for the `RethDbProvider` struct.
150+
///
151+
/// This is where we override specific RPC methods to fetch from the reth-db.
152+
impl<P, T> Provider<T> for RethDbProvider<P, T>
153+
where
154+
P: Provider<T>,
155+
T: Transport + Clone,
156+
{
157+
fn root(&self) -> &RootProvider<T> {
158+
self.inner.root()
159+
}
160+
161+
/// Override the `get_block_number` method to fetch the latest block number from the reth-db.
162+
fn get_block_number(&self) -> ProviderCall<T, NoParams, U64, u64> {
163+
let provider = self.factory().provider().map_err(TransportErrorKind::custom).unwrap();
164+
165+
let best = provider.best_block_number().map_err(TransportErrorKind::custom);
166+
167+
ProviderCall::ready(best)
168+
}
169+
170+
/// Override the `get_transaction_count` method to fetch the transaction count of an address.
171+
///
172+
/// `RpcWithBlock` uses `ProviderCall` under the hood.
173+
fn get_transaction_count(&self, address: Address) -> RpcWithBlock<T, Address, U64, u64> {
174+
let this = self.factory().clone();
175+
RpcWithBlock::new_provider(move |block_id| {
176+
let provider = this.provider_at(block_id).map_err(TransportErrorKind::custom).unwrap();
177+
178+
let maybe_acc =
179+
provider.basic_account(address).map_err(TransportErrorKind::custom).unwrap();
180+
181+
let nonce = maybe_acc.map(|acc| acc.nonce).unwrap_or_default();
182+
183+
ProviderCall::ready(Ok(nonce))
184+
})
185+
}
186+
}
187+
188+
/// A helper type to get the appropriate DB provider.
189+
#[derive(Debug, Clone)]
190+
struct DbAccessor<DB = ProviderFactory<NodeTypesWithDBAdapter<EthereumNode, Arc<DatabaseEnv>>>>
191+
where
192+
DB: DatabaseProviderFactory<Provider: TryIntoHistoricalStateProvider + BlockNumReader>,
193+
{
194+
inner: DB,
195+
}
196+
197+
impl<DB> DbAccessor<DB>
198+
where
199+
DB: DatabaseProviderFactory<Provider: TryIntoHistoricalStateProvider + BlockNumReader>,
200+
{
201+
const fn new(inner: DB) -> Self {
202+
Self { inner }
203+
}
204+
205+
fn provider(&self) -> Result<DB::Provider, ProviderError> {
206+
self.inner.database_provider_ro()
207+
}
208+
209+
fn provider_at(&self, block_id: BlockId) -> Result<Box<dyn StateProvider>, ProviderError> {
210+
let provider = self.inner.database_provider_ro()?;
211+
212+
let block_number = match block_id {
213+
BlockId::Hash(hash) => {
214+
if let Some(num) = provider.block_number(hash.into())? {
215+
num
216+
} else {
217+
return Err(ProviderError::BlockHashNotFound(hash.into()));
218+
}
219+
}
220+
BlockId::Number(BlockNumberOrTag::Number(num)) => num,
221+
_ => provider.best_block_number()?,
222+
};
223+
224+
provider.try_into_history_at_block(block_number)
225+
}
226+
}

0 commit comments

Comments
 (0)