mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
examples: add examples of using reth-provider and instantiating an RPC on top of the DB (#3533)
This commit is contained in:
committed by
GitHub
parent
0aeffe96b0
commit
766f520c17
35
examples/Cargo.toml
Normal file
35
examples/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
reth-primitives = { workspace = true }
|
||||
|
||||
reth-db = { workspace = true }
|
||||
reth-provider = { workspace = true }
|
||||
|
||||
reth-rpc-builder = { workspace = true }
|
||||
reth-rpc-types = { workspace = true }
|
||||
|
||||
reth-revm = { workspace = true }
|
||||
reth-blockchain-tree = { workspace = true }
|
||||
reth-beacon-consensus = { workspace = true }
|
||||
reth-network-api = { workspace = true, features = ["test-utils"] }
|
||||
reth-transaction-pool = { workspace = true, features = ["test-utils"] }
|
||||
reth-tasks = { workspace = true }
|
||||
|
||||
|
||||
eyre = "0.6.8"
|
||||
futures = "0.3.0"
|
||||
tokio = { workspace = true }
|
||||
|
||||
[[example]]
|
||||
name = "rpc-db"
|
||||
path = "rpc-db.rs"
|
||||
|
||||
[[example]]
|
||||
name = "db-access"
|
||||
path = "db-access.rs"
|
||||
17
examples/README.md
Normal file
17
examples/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
## Examples of how to use the Reth SDK
|
||||
|
||||
This directory contains a number of examples showcasing various capabilities of
|
||||
the `reth-*` crates.
|
||||
|
||||
All examples can be executed with:
|
||||
|
||||
```
|
||||
cargo run --example $name
|
||||
```
|
||||
|
||||
A good starting point for the examples would be [`db-access`](db-access.rs)
|
||||
and [`rpc-db`](rpc-db.rs).
|
||||
|
||||
If you've got an example you'd like to see here, please feel free to open an
|
||||
issue. Otherwise if you've got an example you'd like to add, please feel free
|
||||
to make a PR!
|
||||
222
examples/db-access.rs
Normal file
222
examples/db-access.rs
Normal file
@ -0,0 +1,222 @@
|
||||
use reth_db::open_db_read_only;
|
||||
use reth_primitives::{Address, ChainSpecBuilder, H256, U256};
|
||||
use reth_provider::{
|
||||
AccountReader, BlockReader, BlockSource, HeaderProvider, ProviderFactory, ReceiptProvider,
|
||||
StateProvider, TransactionsProvider,
|
||||
};
|
||||
use reth_rpc_types::{Filter, FilteredParams};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
// Providers are zero cost abstractions on top of an opened MDBX Transaction
|
||||
// exposing a familiar API to query the chain's information without requiring knowledge
|
||||
// of the inner tables.
|
||||
//
|
||||
// These abstractions do not include any caching and the user is responsible for doing that.
|
||||
// Other parts of the code which include caching are parts of the `EthApi` abstraction.
|
||||
fn main() -> eyre::Result<()> {
|
||||
// Opens a RO handle to the database file.
|
||||
// TODO: Should be able to do `ProviderFactory::new_with_db_path_ro(...)` instead of
|
||||
// doing in 2 steps.
|
||||
let db = open_db_read_only(&Path::new(&std::env::var("RETH_DB_PATH")?))?;
|
||||
|
||||
// Instantiate a provider factory for Ethereum mainnet using the provided DB.
|
||||
// TODO: Should the DB version include the spec so that you do not need to specify it here?
|
||||
let spec = ChainSpecBuilder::mainnet().build();
|
||||
let factory = ProviderFactory::new(db, spec.into());
|
||||
|
||||
// This call opens a RO transaction on the database. To write to the DB you'd need to call
|
||||
// the `provider_rw` function and look for the `Writer` variants of the traits.
|
||||
let provider = factory.provider()?;
|
||||
|
||||
// Run basic queryies against the DB
|
||||
let block_num = 100;
|
||||
header_provider_example(&provider, block_num)?;
|
||||
block_provider_example(&provider, block_num)?;
|
||||
txs_provider_example(&provider)?;
|
||||
receipts_provider_example(&provider)?;
|
||||
|
||||
// Closes the RO transaction opened in the `factory.provider()` call. This is optional and
|
||||
// would happen anyway at the end of the function scope.
|
||||
drop(provider);
|
||||
|
||||
// Run the example against latest state
|
||||
state_provider_example(factory.latest()?)?;
|
||||
|
||||
// Run it with historical state
|
||||
state_provider_example(factory.history_by_block_number(block_num)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The `HeaderProvider` allows querying the headers-related tables.
|
||||
fn header_provider_example<T: HeaderProvider>(provider: T, number: u64) -> eyre::Result<()> {
|
||||
// Can query the header by number
|
||||
let header = provider.header_by_number(number)?.ok_or(eyre::eyre!("header not found"))?;
|
||||
|
||||
// We can convert a header to a sealed header which contains the hash w/o needing to re-compute
|
||||
// it every time.
|
||||
let sealed_header = header.seal_slow();
|
||||
|
||||
// Can also query the header by hash!
|
||||
let header_by_hash =
|
||||
provider.header(&sealed_header.hash)?.ok_or(eyre::eyre!("header by hash not found"))?;
|
||||
assert_eq!(sealed_header.header, header_by_hash);
|
||||
|
||||
// The header's total difficulty is stored in a separate table, so we have a separate call for
|
||||
// it. This is not needed for post PoS transition chains.
|
||||
let td = provider.header_td_by_number(number)?.ok_or(eyre::eyre!("header td not found"))?;
|
||||
assert_ne!(td, U256::ZERO);
|
||||
|
||||
// Can query headers by range as well, already sealed!
|
||||
let headers = provider.sealed_headers_range(100..200)?;
|
||||
assert_eq!(headers.len(), 100);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The `TransactionsProvider` allows querying transaction-related information
|
||||
fn txs_provider_example<T: TransactionsProvider>(provider: T) -> eyre::Result<()> {
|
||||
// Try the 5th tx
|
||||
let txid = 5;
|
||||
|
||||
// Query a transaction by its primary ordered key in the db
|
||||
let tx = provider.transaction_by_id(txid)?.ok_or(eyre::eyre!("transaction not found"))?;
|
||||
|
||||
// Can query the tx by hash
|
||||
let tx_by_hash =
|
||||
provider.transaction_by_hash(tx.hash)?.ok_or(eyre::eyre!("txhash not found"))?;
|
||||
assert_eq!(tx, tx_by_hash);
|
||||
|
||||
// Can query the tx by hash with info about the block it was included in
|
||||
let (tx, meta) =
|
||||
provider.transaction_by_hash_with_meta(tx.hash)?.ok_or(eyre::eyre!("txhash not found"))?;
|
||||
assert_eq!(tx.hash, meta.tx_hash);
|
||||
|
||||
// Can reverse lookup the key too
|
||||
let id = provider.transaction_id(tx.hash)?.ok_or(eyre::eyre!("txhash not found"))?;
|
||||
assert_eq!(id, txid);
|
||||
|
||||
// Can find the block of a transaction given its key
|
||||
let _block = provider.transaction_block(txid)?;
|
||||
|
||||
// Can query the txs in the range [100, 200)
|
||||
let _txs_by_tx_range = provider.transactions_by_tx_range(100..200)?;
|
||||
// Can query the txs in the _block_ range [100, 200)]
|
||||
let _txs_by_block_range = provider.transactions_by_block_range(100..200)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The `BlockReader` allows querying the headers-related tables.
|
||||
fn block_provider_example<T: BlockReader>(provider: T, number: u64) -> eyre::Result<()> {
|
||||
// Can query a block by number
|
||||
let block = provider.block(number.into())?.ok_or(eyre::eyre!("block num not found"))?;
|
||||
assert_eq!(block.number, number);
|
||||
|
||||
// Can query a block with its senders, this is useful when you'd want to execute a block and do
|
||||
// not want to manually recover the senders for each transaction (as each transaction is
|
||||
// stored on disk with its v,r,s but not its `from` field.).
|
||||
let block = provider.block(number.into())?.ok_or(eyre::eyre!("block num not found"))?;
|
||||
|
||||
// Can seal the block to cache the hash, like the Header above.
|
||||
let sealed_block = block.clone().seal_slow();
|
||||
|
||||
// Can also query the block by hash directly
|
||||
let block_by_hash =
|
||||
provider.block_by_hash(sealed_block.hash)?.ok_or(eyre::eyre!("block by hash not found"))?;
|
||||
assert_eq!(block, block_by_hash);
|
||||
|
||||
// Or by relying in the internal conversion
|
||||
let block_by_hash2 =
|
||||
provider.block(sealed_block.hash.into())?.ok_or(eyre::eyre!("block by hash not found"))?;
|
||||
assert_eq!(block, block_by_hash2);
|
||||
|
||||
// Or you can also specify the datasource. For this provider this always return `None`, but
|
||||
// the blockchain tree is also able to access pending state not available in the db yet.
|
||||
let block_by_hash3 = provider
|
||||
.find_block_by_hash(sealed_block.hash, BlockSource::Any)?
|
||||
.ok_or(eyre::eyre!("block hash not found"))?;
|
||||
assert_eq!(block, block_by_hash3);
|
||||
|
||||
// Can query the block's ommers/uncles
|
||||
let _ommers = provider.ommers(number.into())?;
|
||||
|
||||
// Can query the block's withdrawals (via the `WithdrawalsProvider`)
|
||||
let _withdrawals =
|
||||
provider.withdrawals_by_block(sealed_block.hash.into(), sealed_block.timestamp)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The `ReceiptProvider` allows querying the receipts tables.
|
||||
fn receipts_provider_example<T: ReceiptProvider + TransactionsProvider + HeaderProvider>(
|
||||
provider: T,
|
||||
) -> eyre::Result<()> {
|
||||
let txid = 5;
|
||||
let header_num = 100;
|
||||
|
||||
// Query a receipt by txid
|
||||
let receipt = provider.receipt(txid)?.ok_or(eyre::eyre!("tx receipt not found"))?;
|
||||
|
||||
// Can query receipt by txhash too
|
||||
let tx = provider.transaction_by_id(txid)?.unwrap();
|
||||
let receipt_by_hash =
|
||||
provider.receipt_by_hash(tx.hash)?.ok_or(eyre::eyre!("tx receipt by hash not found"))?;
|
||||
assert_eq!(receipt, receipt_by_hash);
|
||||
|
||||
// Can query all the receipts in a block
|
||||
let _receipts = provider
|
||||
.receipts_by_block(100.into())?
|
||||
.ok_or(eyre::eyre!("no receipts found for block"))?;
|
||||
|
||||
// Can check if a address/topic filter is present in a header, if it is we query the block and
|
||||
// receipts and do something with the data
|
||||
// 1. get the bloom from the header
|
||||
let header = provider.header_by_number(header_num)?.unwrap();
|
||||
let bloom = header.logs_bloom;
|
||||
|
||||
// 2. Construct the address/topics filters
|
||||
// For a hypothetical address, we'll want to filter down for a specific indexed topic (e.g.
|
||||
// `from`).
|
||||
let addr = Address::random();
|
||||
let topic = H256::random();
|
||||
|
||||
// TODO: Make it clearer how to choose between topic0 (event name) and the other 3 indexed
|
||||
// topics. This API is a bit clunky and not obvious to use at the moemnt.
|
||||
let filter = Filter::new().address(addr).topic0(topic);
|
||||
let filter_params = FilteredParams::new(Some(filter));
|
||||
let address_filter = FilteredParams::address_filter(&Some(addr.into()));
|
||||
let topics_filter = FilteredParams::topics_filter(&Some(vec![topic.into()]));
|
||||
|
||||
// 3. If the address & topics filters match do something. We use the outer check against the
|
||||
// bloom filter stored in the header to avoid having to query the receipts table when there
|
||||
// is no instance of any event that matches the filter in the header.
|
||||
if FilteredParams::matches_address(bloom, &address_filter) &&
|
||||
FilteredParams::matches_topics(bloom, &topics_filter)
|
||||
{
|
||||
let receipts = provider.receipt(header_num)?.ok_or(eyre::eyre!("receipt not found"))?;
|
||||
for log in &receipts.logs {
|
||||
if filter_params.filter_address(log) && filter_params.filter_topics(&log) {
|
||||
// Do something with the log e.g. decode it.
|
||||
println!("Matching log found! {log:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state_provider_example<T: StateProvider + AccountReader>(provider: T) -> eyre::Result<()> {
|
||||
let address = Address::random();
|
||||
let storage_key = H256::random();
|
||||
|
||||
// Can get account / storage state with simple point queries
|
||||
let _account = provider.basic_account(address)?;
|
||||
let _code = provider.account_code(address)?;
|
||||
let _storage = provider.storage(address, storage_key)?;
|
||||
// TODO: unimplemented.
|
||||
// let _proof = provider.proof(address, &[])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
78
examples/rpc-db.rs
Normal file
78
examples/rpc-db.rs
Normal file
@ -0,0 +1,78 @@
|
||||
// Talking to the DB
|
||||
use reth_db::open_db_read_only;
|
||||
use reth_primitives::ChainSpecBuilder;
|
||||
use reth_provider::{providers::BlockchainProvider, ProviderFactory};
|
||||
|
||||
// Bringing up the RPC
|
||||
use reth_rpc_builder::{
|
||||
RethRpcModule, RpcModuleBuilder, RpcServerConfig, TransportRpcModuleConfig,
|
||||
};
|
||||
|
||||
// Code which we'd ideally like to not need to import if you're only spinning up
|
||||
// read-only parts of the API and do not require access to pending state or to
|
||||
// EVM sims
|
||||
use reth_beacon_consensus::BeaconConsensus;
|
||||
use reth_blockchain_tree::{
|
||||
BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, TreeExternals,
|
||||
};
|
||||
use reth_revm::Factory as ExecutionFactory;
|
||||
// Configuring the network parts, ideally also wouldn't ned to think about this.
|
||||
use reth_network_api::test_utils::NoopNetwork;
|
||||
use reth_provider::test_utils::TestCanonStateSubscriptions;
|
||||
use reth_tasks::TokioTaskExecutor;
|
||||
use reth_transaction_pool::test_utils::testing_pool;
|
||||
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
// Example illustrating how to run the ETH JSON RPC API as standalone over a DB file.
|
||||
// TODO: Add example showing how to spin up your own custom RPC namespace alongside
|
||||
// the other default name spaces.
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
// 1. Setup the DB
|
||||
let db = Arc::new(open_db_read_only(&Path::new(&std::env::var("RETH_DB_PATH")?))?);
|
||||
let spec = Arc::new(ChainSpecBuilder::mainnet().build());
|
||||
let factory = ProviderFactory::new(db.clone(), spec.clone());
|
||||
|
||||
// 2. Setup blcokchain tree to be able to receive live notifs
|
||||
// TODO: Make this easier to configure
|
||||
let provider = {
|
||||
let consensus = Arc::new(BeaconConsensus::new(spec.clone()));
|
||||
let exec_factory = ExecutionFactory::new(spec.clone());
|
||||
|
||||
let externals = TreeExternals::new(db.clone(), consensus, exec_factory, spec.clone());
|
||||
let tree_config = BlockchainTreeConfig::default();
|
||||
let (canon_state_notification_sender, _receiver) =
|
||||
tokio::sync::broadcast::channel(tree_config.max_reorg_depth() as usize * 2);
|
||||
|
||||
let tree = ShareableBlockchainTree::new(BlockchainTree::new(
|
||||
externals,
|
||||
canon_state_notification_sender.clone(),
|
||||
tree_config,
|
||||
)?);
|
||||
|
||||
BlockchainProvider::new(factory, tree)?
|
||||
};
|
||||
|
||||
let noop_pool = testing_pool();
|
||||
let rpc_builder = RpcModuleBuilder::default()
|
||||
.with_provider(provider)
|
||||
// Rest is just defaults
|
||||
// TODO: How do we make this easier to configure?
|
||||
.with_pool(noop_pool)
|
||||
.with_network(NoopNetwork)
|
||||
.with_executor(TokioTaskExecutor::default())
|
||||
.with_events(TestCanonStateSubscriptions::default());
|
||||
|
||||
// Pick which namespaces to expose.
|
||||
let config = TransportRpcModuleConfig::default().with_http([RethRpcModule::Eth]);
|
||||
let server = rpc_builder.build(config);
|
||||
|
||||
// Start the server & keep it alive
|
||||
let server_args =
|
||||
RpcServerConfig::http(Default::default()).with_http_address("0.0.0.0:8545".parse()?);
|
||||
let _handle = server_args.start(server).await?;
|
||||
futures::future::pending::<()>().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user