From dd4b2869d3a9a6b5f4745471441656d52120abbd Mon Sep 17 00:00:00 2001 From: Sergey Melnychuk <8093171+sergey-melnychuk@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:36:30 +0200 Subject: [PATCH] docs(example): extract full contract state from db (#17601) Co-authored-by: sergey-melnychuk Co-authored-by: Matthias Seitz --- Cargo.lock | 8 ++ Cargo.toml | 1 + examples/full-contract-state/Cargo.toml | 16 ++++ examples/full-contract-state/README.md | 69 +++++++++++++++++ examples/full-contract-state/src/main.rs | 94 ++++++++++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 examples/full-contract-state/Cargo.toml create mode 100644 examples/full-contract-state/README.md create mode 100644 examples/full-contract-state/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 4ad82c6d4f..9eb746d309 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3544,6 +3544,14 @@ dependencies = [ "tokio", ] +[[package]] +name = "example-full-contract-state" +version = "1.6.0" +dependencies = [ + "eyre", + "reth-ethereum", +] + [[package]] name = "example-manual-p2p" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index d647f60462..5976f8d46e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,6 +155,7 @@ members = [ "examples/exex-hello-world", "examples/exex-subscription", "examples/exex-test", + "examples/full-contract-state", "examples/manual-p2p/", "examples/network-txpool/", "examples/network/", diff --git a/examples/full-contract-state/Cargo.toml b/examples/full-contract-state/Cargo.toml new file mode 100644 index 0000000000..f4f61244a2 --- /dev/null +++ b/examples/full-contract-state/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example-full-contract-state" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +reth-ethereum = { workspace = true, features = ["node"] } +eyre.workspace = true + +[lints] +workspace = true diff --git a/examples/full-contract-state/README.md b/examples/full-contract-state/README.md new file mode 100644 index 0000000000..c0cd4a0def --- /dev/null +++ b/examples/full-contract-state/README.md @@ -0,0 +1,69 @@ +# Full Contract State Example + +This example demonstrates how to extract the complete state of a specific contract from the reth database. + +## What it does + +The example shows how to: + +1. **Connect to a reth database** - Uses the recommended builder pattern to create a read-only provider +2. **Get basic account information** - Retrieves balance, nonce, and code hash for the contract address +3. **Get contract bytecode** - Fetches the actual contract bytecode if the account is a contract +4. **Iterate through all storage slots** - Uses database cursors to efficiently retrieve all storage key-value pairs + +## Prerequisites + +- A reth database with some data (you can sync a node or use a pre-synced database) +- Set the `RETH_DATADIR` environment variable to point to your reth data directory +- Set the `CONTRACT_ADDRESS` environment variable to provide target contract address + +## Usage + +```bash +# Set your reth data directory +export RETH_DATADIR="/path/to/your/reth/datadir" +# Set target contract address +export CONTRACT_ADDRESS="0x0..." + +# Run the example +cargo run --example full-contract-state +``` + +## Code Structure + +The example consists of: + +- **`ContractState` struct** - Holds all contract state information +- **`extract_contract_state` function** - Main function that extracts contract state +- **`main` function** - Sets up the provider and demonstrates usage + +## Key Concepts + +### Provider Pattern +The example uses reth's provider pattern: +- `ProviderFactory` - Creates database connections +- `DatabaseProvider` - Provides low-level database access +- `StateProvider` - Provides high-level state access + +### Database Cursors +For efficient storage iteration, the example uses database cursors: +- `cursor_dup_read` - Creates a cursor for duplicate key tables +- `seek_exact` - Positions cursor at specific key +- `next_dup` - Iterates through duplicate entries + +## Output + +The example will print: +- Contract address +- Account balance +- Account nonce +- Code hash +- Number of storage slots +- All storage key-value pairs + +## Error Handling + +The example includes proper error handling: +- Returns `None` if the contract doesn't exist +- Uses `ProviderResult` for database operation errors +- Gracefully handles missing bytecode or storage diff --git a/examples/full-contract-state/src/main.rs b/examples/full-contract-state/src/main.rs new file mode 100644 index 0000000000..d68705480e --- /dev/null +++ b/examples/full-contract-state/src/main.rs @@ -0,0 +1,94 @@ +//! Example demonstrating how to extract the full state of a specific contract from the reth +//! database. +//! +//! This example shows how to: +//! 1. Connect to a reth database +//! 2. Get basic account information (balance, nonce, code hash) +//! 3. Get contract bytecode +//! 4. Iterate through all storage slots for the contract + +use reth_ethereum::{ + chainspec::ChainSpecBuilder, + evm::revm::primitives::{Address, B256, U256}, + node::EthereumNode, + primitives::{Account, Bytecode}, + provider::{ + db::{ + cursor::{DbCursorRO, DbDupCursorRO}, + tables, + transaction::DbTx, + }, + providers::ReadOnlyConfig, + ProviderResult, + }, + storage::{DBProvider, StateProvider}, +}; +use std::{collections::HashMap, str::FromStr}; + +/// Represents the complete state of a contract including account info, bytecode, and storage +#[derive(Debug, Clone)] +pub struct ContractState { + /// The address of the contract + pub address: Address, + /// Basic account information (balance, nonce, code hash) + pub account: Account, + /// Contract bytecode (None if not a contract or doesn't exist) + pub bytecode: Option, + /// All storage slots for the contract + pub storage: HashMap, +} + +/// Extract the full state of a specific contract +pub fn extract_contract_state( + provider: &P, + state_provider: &dyn StateProvider, + contract_address: Address, +) -> ProviderResult> { + let account = state_provider.basic_account(&contract_address)?; + let Some(account) = account else { + return Ok(None); + }; + + let bytecode = state_provider.account_code(&contract_address)?; + + let mut storage_cursor = provider.tx_ref().cursor_dup_read::()?; + let mut storage = HashMap::new(); + + if let Some((_, first_entry)) = storage_cursor.seek_exact(contract_address)? { + storage.insert(first_entry.key, first_entry.value); + + while let Some((_, entry)) = storage_cursor.next_dup()? { + storage.insert(entry.key, entry.value); + } + } + + Ok(Some(ContractState { address: contract_address, account, bytecode, storage })) +} + +fn main() -> eyre::Result<()> { + let address = std::env::var("CONTRACT_ADDRESS")?; + let contract_address = Address::from_str(&address)?; + + let datadir = std::env::var("RETH_DATADIR")?; + let spec = ChainSpecBuilder::mainnet().build(); + let factory = EthereumNode::provider_factory_builder() + .open_read_only(spec.into(), ReadOnlyConfig::from_datadir(datadir))?; + + let provider = factory.provider()?; + let state_provider = factory.latest()?; + let contract_state = + extract_contract_state(&provider, state_provider.as_ref(), contract_address)?; + + if let Some(state) = contract_state { + println!("Contract: {}", state.address); + println!("Balance: {}", state.account.balance); + println!("Nonce: {}", state.account.nonce); + println!("Code hash: {:?}", state.account.bytecode_hash); + println!("Storage slots: {}", state.storage.len()); + for (key, value) in &state.storage { + println!("\t{}: {}", key, value); + } + } + + Ok(()) +}