use reth_chainspec::ChainSpecBuilder; use reth_primitives::{Address, B256}; use reth_provider::{ providers::StaticFileProvider, 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. let db_path = std::env::var("RETH_DB_PATH")?; let db_path = Path::new(&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_with_database_path( db_path, spec.into(), Default::default(), StaticFileProvider::read_only(db_path.join("static_files"))?, )?; // 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 queries 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(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!(!td.is_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(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(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( 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 = B256::random(); // TODO: Make it clearer how to choose between event_signature(topic0) (event name) and the // other 3 indexed topics. This API is a bit clunky and not obvious to use at the moment. let filter = Filter::new().address(addr).event_signature(topic); let filter_params = FilteredParams::new(Some(filter)); let address_filter = FilteredParams::address_filter(&addr.into()); let topics_filter = FilteredParams::topics_filter(&[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.address) && filter_params.filter_topics(log.topics()) { // Do something with the log e.g. decode it. println!("Matching log found! {log:?}") } } } Ok(()) } fn state_provider_example(provider: T) -> eyre::Result<()> { let address = Address::random(); let storage_key = B256::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(()) }