
reth 合约全量状态提取实战full-contract-state 示例从 Provider 到数据库游标的完整解析【免费下载链接】rethModular, contributor-friendly and blazing-fast implementation of the Ethereum protocol, in Rust项目地址: https://gitcode.com/GitHub_Trending/re/reth本文以 reth 仓库中的 full-contract-state 示例 为主线讲解如何从 reth 数据库中提取指定合约的完整状态账户信息余额、nonce、code hash、合约字节码以及全部存储槽位。读完后你将掌握 reth 的 Provider 分层模式、MDBX 数据库游标遍历技巧以及PlainStorageState/HashedStorages双存储布局的差异与正确读取方式。示例目标该示例演示如何从 reth 数据库中抽取某个合约的完整状态具体覆盖四个步骤与 README 保持一致连接 reth 数据库—— 使用推荐的 builder 模式创建只读 Provider获取基础账户信息—— 读取合约地址对应的 balance、nonce、code hash获取合约字节码—— 若该账户是合约取出其真实字节码遍历全部存储槽位—— 利用数据库游标高效地取出所有存储键值对。运行前提一个含有数据的 reth 数据库自行同步一个节点或使用已同步好的数据库设置环境变量RETH_DATADIR指向 reth 数据目录设置环境变量CONTRACT_ADDRESS提供目标合约地址。运行方式# Set your reth data directory export RETH_DATADIR/path/to/your/reth/datadir # Set target contract address export CONTRACT_ADDRESS0x0... # Run the example cargo run --example full-contract-state示例的输出内容包括合约地址Contract address账户余额Account balance账户 noncecode hash存储槽位总数Number of storage slots全部存储键值对All storage key-value pairs如果RETH_DATADIR或CONTRACT_ADDRESS未设置main中的std::env::var会直接报错退出若目标地址不存在示例会静默返回Ok(None)而不打印任何内容这符合 README 中合约不存在时返回None的错误处理设计。代码结构总览examples/full-contract-state/src/main.rs 由三部分组成ContractState结构体—— 承载合约的全部状态信息extract_contract_state函数—— 核心提取逻辑main函数—— 构建只读 Provider 并演示调用。依赖方面Cargo.toml 只引入了alloy-primitives、reth-ethereum启用nodefeature和eyre是一个轻量的单文件示例。ContractState结构体#[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 doesnt exist) pub bytecode: OptionBytecode, /// All storage slots. Keyed by keccak256(slot) when /// hashed is true, by the raw slot otherwise. pub storage: B256MapU256, /// Whether storage is keyed by keccak256(slot) rather than the raw slot. pub hashed: bool, }见 main.rs#L29-L44。注意storage的键类型是B256MapU256并且额外带一个hashed标记当节点只保留哈希态hashed-only为省空间不维护PlainStorageState镜像时存储键是keccak256(slot)而非原始槽位号——这一点在输出时会被明确打印提示main.rs#L117-L124避免读者把keccak256(slot)误当作槽位序号。extract_contract_state核心提取流程pub fn extract_contract_stateP: DBProvider StorageSettingsCache( provider: P, state_provider: dyn StateProvider, contract_address: Address, ) - ProviderResultOptionContractState { 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 hashed provider.cached_storage_settings().use_hashed_state(); // ... 根据 hashed 选择 HashedStorages 或 PlainStorageState 游标 }完整实现见 main.rs#L57-L92流程为state_provider.basic_account(contract_address)?—— 通过高层StateProvider读账户账户不存在None时直接返回Ok(None)不视为错误state_provider.account_code(contract_address)?—— 读取字节码。对非合约账户这里会得到None分支选择存储表—— 通过provider.cached_storage_settings().use_hashed_state()判断节点维护的是哪种存储表再创建对应游标遍历。游标遍历两种存储表if hashed { let hashed_address keccak256(contract_address); let mut cursor provider.tx_ref().cursor_dup_read::tables::HashedStorages()?; if let Some((_, first_entry)) cursor.seek_exact(hashed_address)? { storage.insert(first_entry.key, first_entry.value); while let Some((_, entry)) cursor.next_dup()? { storage.insert(entry.key, entry.value); } } } else { let mut cursor provider.tx_ref().cursor_dup_read::tables::PlainStorageState()?; if let Some((_, first_entry)) cursor.seek_exact(contract_address)? { storage.insert(first_entry.key, first_entry.value); while let Some((_, entry)) cursor.next_dup()? { storage.insert(entry.key, entry.value); } } }两种布局的对照模式表主键contract 维度副键slot 维度Plainv1/legacytables::PlainStorageState原始合约地址原始 32 字节槽位Hashedv2--storage.v2tables::HashedStorageskeccak256(合约地址)keccak256(槽位)这两个表都是 dupduplicate key表一个主键下挂多条槽位记录因此使用cursor_dup_read创建的重复键游标配合seek_exact定位主键、next_dup逐条取回同一合约下的所有槽位值这正是 README Database Cursors 一节强调的三个 API。为什么必须先看use_hashed_state()README 的 Hashed State 一节指出根据cached_storage_settings().use_hashed_state()节点只会保持其中一张表是最新的另一张表是空的而非报错。源码印证了这一点StorageSettings::use_hashed_state()直接返回self.storage_v2见 crates/storage/db-api/src/models/metadata.rs#L83-L87即 v2 存储布局--storage.v2CLI 开关下以HashedAccounts/HashedStorages作为规范态表示reth 自身的高层接口StateProvider::storage()也是按同一标志分支的——v2 时走hashed_storage_lookup(keccak256(account), keccak256(key))否则走PlainStorageState游标见 crates/storage/provider/src/providers/state/latest.rs#L285-L304。本示例的分支逻辑正是对这一内部实现的显式复刻因此读错表不会报错只是静默返回零行——写任何直接摸库的工具前先确认 datadir 的存储版本是硬性前提。此外hash 模式下打印的键是keccak256(slot)而非原始槽位号因为哈希态模式不保留 plain 键镜像来反推槽位号省空间正是其代价。main函数构建只读 Providerlet 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 runtime reth_ethereum::tasks::Runtime::test(); let factory EthereumNode::provider_factory_builder().open_read_only( spec.into(), ReadOnlyConfig::from_datadir(datadir), runtime, )?; let provider factory.provider()?; let state_provider factory.latest()?; let contract_state extract_contract_state(provider, state_provider.as_ref(), contract_address)?;见 main.rs#L94-L110。几个要点ChainSpecBuilder::mainnet().build()—— 示例硬编码主网链规格若你的 datadir 是其他链sepolia、holesky 等需换成对应的 builder 调用EthereumNode::provider_factory_builder().open_read_only(...)—— 这是官方推荐的 builder 路径返回ProviderFactoryReadOnlyConfig::from_datadir(datadir)让数据库以只读方式打开不会与正在运行的节点争抢写锁适合做离线数据提取Runtime::test()—— 仅用于满足 API 对任务运行时的要求示例本身不会派发后台任务factory.provider()与factory.latest()分别返回底层数据库 Provider实现DBProvider StorageSettingsCache用于开游标、查存储设置和指向最新区块的StateProvider用于读账户、字节码等高层语义。StorageSettingsCache是 crates/storage/storage-api/src/metadata.rs#L87-L96 定义的 trait其cached_storage_settings()从工厂侧缓存读取该节点写入数据库 metadata 表中的StorageSettingsJSON 序列化存储从而无需每次解析磁盘元数据即可判断存储布局。关键概念Provider 分层模式README 将 reth 的 provider 模式概括为三层示例中对应关系如下概念职责在示例中的体现ProviderFactory创建数据库连接EthereumNode::provider_factory_builder().open_read_only(...)DatabaseProviderDBProvider低层数据库访问provider.tx_ref().cursor_dup_read::...()开游标StateProvider高层状态访问basic_account、account_code这个分层的实用含义是语义查询走StateProvider批量/自定义遍历走DBProvider的游标。extract_contract_state同时接收PP: DBProvider StorageSettingsCache和dyn StateProvider两个参数正是这一分工的直接体现——账户与字节码用高层 API 一行拿完而全量存储遍历则必须落到PlainStorageState/HashedStorages两张 dup 表上手工游标化。错误处理设计与 README 的 Error Handling 一节对应源码中的处理策略是合约不存在basic_account返回None时直接return Ok(None)main中静默跳过打印数据库操作错误全部经由ProviderResultT以?向上冒泡最终在main中转为eyre::Result缺失字节码或无存储均不构成错误——bytecode: OptionBytecode允许None非合约seek_exact未命中时storage保持空 map。小结full-contract-state示例用不到 130 行代码展示了 reth 离线读取数据库的完整套路provider_factory_builder().open_read_only建只读工厂 →StateProvider读账户与字节码 → 依据cached_storage_settings().use_hashed_state()选择HashedStorages或PlainStorageState→cursor_dup_readseek_exactnext_dup遍历存储槽。它同时是理解 reth 存储布局v1 plain / v2 hashed见 metadata.rs 中StorageSettings的v1()/v2()构造与各项*_in_rocksdb标志的一个绝佳切入点。相关核心文件示例入口与实现examples/full-contract-state/src/main.rs示例说明examples/full-contract-state/README.md存储设置与use_hashed_statecrates/storage/db-api/src/models/metadata.rsStorageSettingsCache/MetadataProvidertraitcrates/storage/storage-api/src/metadata.rs官方StateProvider::storage的分支实现crates/storage/provider/src/providers/state/latest.rs【免费下载链接】rethModular, contributor-friendly and blazing-fast implementation of the Ethereum protocol, in Rust项目地址: https://gitcode.com/GitHub_Trending/re/reth创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考