Reth Storage 存储层设计
1. 概述
Reth 存储层是一个高性能、模块化的以太坊数据存储解决方案。它采用分层架构设计,实现了数据访问的抽象和具体存储的分离,同时提供了丰富的功能和优秀的性能。
在接下来的内容中,我们将依次介绍各个核心层次的设计理念、主要接口和它们之间的协作关系,帮助你系统理解 Reth 存储系统的整体架构。
1.1 核心特点
- 分层架构:清晰的层次划分,职责分明
- 高性能:优化的数据访问和存储机制
- 可扩展:模块化设计,易于扩展
- 可靠性:完善的事务和并发控制
- 可维护:清晰的代码组织和文档
1.2 整体架构
flowchart LR
App[应用层] --> API[Storage API]
API --> DB[Database API]
DB --> Store[(存储层)]
style App fill:#f9f9f9,stroke:#333,stroke-width:2px
style API fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style DB fill:#fff3e0,stroke:#e65100,stroke-width:2px
style Store fill:#e1f5fe,stroke:#01579b,stroke-width:2px
2. 核心组件
下面结合模块架构图,依次介绍各核心层次的设计与接口。
在整体架构的基础上,Reth 存储系统将核心功能分为四大层次。下面我们将依次展开介绍。
2.1 存储 API 层 (storage-api)
flowchart LR
API[Storage API]
subgraph State[State Provider]
direction TB
S1[account_balance]
S2[account_code]
S3[storage]
S4[bytecode_by_hash]
end
subgraph Block[Block Provider]
direction TB
B1[block_by_hash]
B2[block_by_number]
B3[block_transactions]
end
subgraph Tx[Transaction Provider]
direction TB
T1[transaction_by_hash]
T2[receipt_by_hash]
T3[transaction_by_block]
end
subgraph Chain[Chain Provider]
direction TB
C1[chain_info]
C2[block_number]
C3[block_hash]
end
API --> State
API --> Block
API --> Tx
API --> Chain
style API fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style State fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Block fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Tx fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Chain fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
首先,最上层的 Storage API 层为上层模块(如 RPC、同步、执行等)提供了统一、类型安全的数据访问接口。它屏蔽了底层实现细节,使开发者可以专注于业务逻辑。
-
状态管理
StateProvider: 状态数据访问StateWriter: 状态数据写入StateReader: 状态数据读取
-
区块管理
BlockProvider: 区块数据访问BlockWriter: 区块数据写入HeaderProvider: 区块头管理
-
交易管理
TransactionsProvider: 交易数据访问ReceiptsProvider: 收据数据访问WithdrawalsProvider: 提款数据访问
-
链管理
ChainProvider: 链数据访问ChainInfo: 链信息管理HistoryProvider: 历史数据访问
2.1.1 storage-api 主要 trait 与接口
Reth 的 storage-api 层定义了以太坊节点存储访问的核心 trait 和类型,是上层与底层存储实现解耦的关键。其主要特性和接口如下:
1. 统一抽象与分层设计
- 通过 trait 抽象区块、状态、交易、链、收据、历史、哈希、trie 等多种数据访问与写入接口。
- 支持只读/读写、历史/最新/裁剪等多种访问模式。
- 便于 provider 层、db-api 层和具体存储实现的解耦与组合。
2. 主要 trait 及功能
StateProvider/StateProviderFactory:统一的状态访问与工厂接口,支持最新、历史、pending 状态的获取。BlockReader/BlockWriter:区块数据的读取与写入,支持多种来源(数据库、pending、canonical)。HeaderProvider:区块头访问,支持 hash/number/tag 多种定位方式。TransactionsProvider/ReceiptProvider:交易与收据的高效访问。AccountReader/StorageReader:账户与存储槽的访问,支持批量与变更集。TrieWriter/StateRootProvider/StorageRootProvider:trie 相关操作与状态根计算。HistoryWriter/HashingWriter:历史索引与哈希表的维护。FullRpcProvider:组合所有核心 trait,便于 RPC 层依赖。
3. 类型安全与可扩展性
- 所有 trait 强类型约束,接口清晰,便于静态检查和 IDE 自动补全。
- 支持 auto_impl,便于 Arc/Box/引用等多种包装。
- trait 组合灵活,便于扩展和 mock 测试。
4. 典型用法
#![allow(unused)] fn main() { // 获取最新状态 provider let state_provider = StateProviderFactory::latest()?; // 查询账户余额 let balance = state_provider.account_balance(&address)?; // 查询区块 let block = block_reader.block_by_number(1000)?; // 写入区块 block_writer.insert_block(block, StorageLocation::Database)?; // 计算状态根 let state_root = state_provider.state_root(hashed_state)?; }
5. 设计亮点
- 统一 trait 抽象,便于多后端实现和切换
- 支持历史/裁剪/最新等多种状态视图
- 类型安全、易于测试和扩展
- 与 provider/db-api 层协作紧密,支撑高性能和灵活的以太坊节点存储
结论:storage-api 层是 Reth 存储系统的"接口规范层",通过 trait 组合和类型系统,极大提升了系统的灵活性、可维护性和性能,是理解 Reth 存储架构的基础。
2.2 Provider 层核心接口与功能
flowchart LR
State[State Provider]
subgraph Reader[State Reader]
direction TB
R1[account_balance]
R2[account_code]
R3[storage]
R4[bytecode_by_hash]
end
subgraph Writer[State Writer]
direction TB
W1[insert_account]
W2[insert_storage]
W3[insert_code]
W4[delete_account]
end
subgraph Cache[State Cache]
direction TB
C1[get_account]
C2[get_storage]
C3[get_code]
end
State --> Reader
State --> Writer
State --> Cache
style State fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Reader fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Writer fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Cache fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
紧接着,Provider 层作为"服务接口层",在 storage-api trait 的基础上,聚合了多种数据源(数据库、静态文件、内存 overlay、裁剪/历史等),为上层提供一致的数据视图。
1. ProviderFactory
- 管理数据库、静态文件、链配置等,生成只读/读写 provider。
- 支持多后端、裁剪、metrics、可插拔。
2. DatabaseProvider
- 封装事务,统一实现区块、交易、账户、状态等多种数据访问和写入接口。
- 支持只读/读写、历史/最新、裁剪/非裁剪等多种模式。
3. BlockchainProvider
- 区块链全局视图入口,聚合数据库、内存 overlay、forkchoice 状态等。
- 实现所有核心数据访问 trait,支持一致性视图和多种状态访问。
4. StateProvider/LatestStateProviderRef/HistoricalStateProviderRef
- 状态访问统一 trait,支持"最新状态"和"历史状态"两种模式。
- 历史 provider 支持裁剪感知,能优雅处理被裁剪的历史区块。
5. FullProvider trait
- 组合所有 provider trait,便于上层依赖"全功能 provider"。
6. 典型用法
#![allow(unused)] fn main() { // 获取 provider let provider = provider_factory.provider()?; // 区块头、区块、交易、账户访问 let header = provider.header_by_number(1000)?; let block = provider.block(BlockHashOrNumber::Number(1000))?; let tx = provider.transaction_by_hash(tx_hash)?; let account = provider.basic_account(&address)?; // 状态访问 let state_provider = provider_factory.latest()?; let historical_provider = provider_factory.history_by_block_number(9000000)?; // 写入与回滚 let mut provider_rw = provider_factory.provider_rw()?; provider_rw.insert_block(block, StorageLocation::Database)?; provider_rw.remove_state_above(block_number, StorageLocation::Database)?; }
7. 设计优势
- 统一接口、多后端支持、裁剪与历史感知、类型安全、可扩展性、高性能。
结论:Provider 层是 Reth 存储系统的"服务接口层",通过 trait 组合和泛型,极大提升了系统的灵活性、可维护性和性能,是理解 Reth 存储架构的关键一环。
2.2 数据库抽象层 (db-api)
flowchart LR
DB[Database API]
subgraph Table[Table]
direction TB
T1[NAME: &str]
T2[DUPSORT: bool]
T3[Key: Key]
T4[Value: Value]
end
subgraph Tx[Transaction]
direction TB
X1[tx]
X2[tx_mut]
X3[commit]
X4[abort]
end
subgraph Lock[Lock Manager]
direction TB
L1[lock]
L2[unlock]
L3[try_lock]
end
DB --> Table
DB --> Tx
DB --> Lock
style DB fill:#fff3e0,stroke:#e65100,stroke-width:2px
style Table fill:#fff3e0,stroke:#e65100,stroke-width:2px
style Tx fill:#fff3e0,stroke:#e65100,stroke-width:2px
style Lock fill:#fff3e0,stroke:#e65100,stroke-width:2px
在 Provider 层之下,db-api 层承担着"数据库抽象层"的角色。它屏蔽了具体数据库实现(如 MDBX),为上层提供类型安全、事务化的表操作接口。
2.2.1 db-api 主要 trait 与接口
Reth 的 db-api 层定义了底层数据库的抽象接口,是存储实现与上层 provider/storage-api 解耦的关键。其主要特性和接口如下:
1. 事务与表抽象
Databasetrait:数据库主接口,支持只读/读写事务(tx/tx_mut),并提供事务生命周期管理(view/update)。DbTx/DbTxMut:只读/读写事务抽象,支持表的读写、游标遍历、提交/回滚等。Tabletrait:表结构抽象,定义表名、是否 DUPSORT、Key/Value 类型。DupSorttrait:支持一对多(重复 key)表。
2. 游标与遍历
DbCursorRO/DbCursorRW:表的只读/读写游标,支持 seek/next/prev/first/last 等操作。Walker/RangeWalker/ReverseWalker/DupWalker:基于游标的高效遍历与批量操作。
3. 序列化与压缩
Encode/Decodetrait:Key 的序列化与反序列化。Compress/Decompresstrait:Value 的压缩与解压缩,支持多种编码格式(如 Compact、Scale、RoaringBitmap)。- 支持自定义表模型(如 ShardedKey、StorageShardedKey、IntegerList 等)。
4. 类型安全与可扩展性
- 所有 trait 强类型约束,接口清晰,便于静态检查和 IDE 自动补全。
- 支持 mock 数据库(
DatabaseMock),便于测试。 - trait 组合灵活,便于扩展和多后端实现。
5. 典型用法
#![allow(unused)] fn main() { // 打开数据库并开启只读事务 let db: Arc<dyn Database> = ...; let tx = db.tx()?; // 读取表数据 let value = tx.get::<MyTable>(key)?; // 使用游标遍历表 let mut cursor = tx.cursor_read::<MyTable>()?; while let Some((k, v)) = cursor.next()? { // 处理 k, v } // 写入事务 let mut tx_mut = db.tx_mut()?; tx_mut.put::<MyTable>(key, value)?; tx_mut.commit()?; }
6. 设计亮点
- 事务安全、强一致性,所有操作都在事务内完成
- 支持高效批量遍历与范围操作
- 多种表模型和压缩格式,兼容以太坊多样数据结构
- 类型安全、易于测试和扩展
- 与 storage-api/provider 层协作紧密,支撑高性能和灵活的以太坊节点存储
结论:db-api 层是 Reth 存储系统的"数据库抽象层",通过事务、表、游标、序列化等 trait 组合,极大提升了系统的灵活性、可维护性和性能,是理解 Reth 存储架构的核心基础。
2.3 存储实现层 (db)
flowchart LR
Store[存储层]
subgraph MDBX[MDBX]
direction TB
M1[open]
M2[create_table]
M3[drop_table]
end
subgraph Cache[Cache]
direction TB
C1[get]
C2[insert]
C3[remove]
C4[clear]
end
subgraph Static[Static File]
direction TB
S1[read]
S2[write]
S3[append]
end
Store --> MDBX
Store --> Cache
Store --> Static
style Store fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style MDBX fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style Cache fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style Static fill:#e1f5fe,stroke:#01579b,stroke-width:2px
最后,存储实现层提供了具体的存储后端实现(如 MDBX、静态文件、缓存等),支撑了高性能和多样化的存储需求。它实现了 db-api trait,供上层调用。
3. 关键特性
在上述分层架构的基础上,Reth 存储系统还具备如下关键特性:
3.1 性能优化
-
数据压缩
- ZSTD 压缩支持
- 自定义压缩算法
- 压缩级别可配置
-
缓存机制
- 多级缓存
- LRU 缓存策略
- 预加载机制
-
批量操作
- 批量读取
- 批量写入
- 事务批处理
3.2 可靠性保证
-
事务支持
- ACID 特性
- 事务隔离
- 原子操作
-
并发控制
- 文件锁机制
- 读写锁
- 死锁预防
-
错误处理
- 统一错误类型
- 错误传播链
- 错误恢复机制
3.3 可扩展性
-
模块化设计
- 接口抽象
- 实现分离
- 插件化架构
-
存储引擎
- MDBX 支持
- 静态文件支持
- 可扩展接口
-
数据模型
- 灵活的表结构
- 自定义编码
- 版本兼容
4. 使用示例
通过前面的分层讲解,我们可以看到各层 trait 和接口如何协作。下面给出一些典型的使用场景代码片段,帮助理解实际开发中的调用方式。
4.1 基本操作
#![allow(unused)] fn main() { // 创建状态提供者 let state_provider = StateProviderFactory::latest()?; // 查询账户信息 let balance = state_provider.account_balance(&address)?; let code = state_provider.account_code(&address)?; // 查询存储数据 let storage = state_provider.storage(address, storage_key)?; }
4.2 历史数据访问
#![allow(unused)] fn main() { // 获取历史状态 let historical_state = state_provider.history_by_block_number(block_number)?; // 获取待处理状态 let pending_state = state_provider.pending()?; }
5. 最佳实践
结合实际开发经验,推荐如下最佳实践以充分发挥 Reth 存储系统的性能和可靠性:
5.1 状态访问
- 优先使用批量操作
- 合理使用缓存
- 注意错误处理
5.2 数据写入
- 使用事务保证原子性
- 注意并发安全
- 合理使用批量写入
5.3 性能优化
- 使用适当的索引
- 避免重复查询
- 合理使用缓存
6. 总结
综上所述,Reth 存储层设计展示了以下特点:
- 高度模块化:清晰的层次结构和职责划分
- 类型安全:强类型系统和编译时检查
- 性能优化:多级缓存和压缩机制
- 可扩展性:灵活的接口和实现分离
- 可靠性:完善的事务和并发控制
- 可维护性:清晰的代码组织和文档
这些设计使得 Reth 能够高效地处理以太坊节点所需的各种数据存储需求,同时保证了系统的可靠性和可维护性。存储 API 的设计特别注重模块化和可扩展性,使得系统能够轻松适应不同的存储需求和性能要求。