Zcash 底层存储基石LevelDB 键值数据库解析与 zcashd 集成实践【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash导读Zcashzcashd节点使用内嵌在src/leveldb/目录下的 Google LevelDB 作为唯一持久化键值存储引擎承载链上状态UTXO、屏蔽池锚点、区块索引等全部本地数据。本文以仓库内 src/leveldb/README.md 为主体系统讲解 LevelDB 的核心特性、使用限制、构建方式、性能特征与公共 API 结构并结合 Zcash 仓库内的封装层dbwrapper、txdb源码揭示 zcashd 是如何基于这一存储引擎构建chainstate、blocks/index等真实数据库的。读完本文你将掌握 LevelDB 的数据模型、-dbcache等关键配置的底层含义以及如何在 Zcash 代码库中定位和阅读存储层实现。LevelDB 是什么LevelDB 是 Google 开发的一个快速键值存储库它提供从字符串键到字符串值的有序映射。其核心定位由 README 开篇一句话概括LevelDB is a fast key-value storage library written at Google that provides an ordered mapping from string keys to string values.它由 Sanjay Ghemawat 与 Jeff Dean 编写是开源社区中最经典的 LSMLog-Structured Merge树存储实现之一。在 Zcash 项目中它是zcashd钱包与链状态持久化的基础组件所有节点本地数据库都构建于其上。核心特性为什么 Zcash 选择它README 明确列出了 LevelDB 的核心特性这些特性正是 Zcash 存储层所依赖的能力任意字节数组作为键和值键值不需要是字符串Zcash 直接用序列化后的uint256、std::pairchar, uint256等复合类型作为键详见下文 Zcash 的封装与使用数据按键排序存储有序性使得范围扫描range scan高效Zcash 的区块索引加载LoadBlockIndexGuts正是依赖按键顺序迭代完成的可自定义比较函数调用方可以覆盖默认的字节序排序规则三个基本操作Put(key, value)、Get(key)、Delete(key)原子批处理多个变更可以在一个原子批次WriteBatch中一次性提交Zcash 的CDBBatch正是对这一能力的封装瞬时快照Snapshot可以获得数据的一致只读视图支持正反两个方向的迭代自动压缩数据自动使用 Snappy 压缩库压缩环境抽象Env文件系统操作等外部活动通过虚拟接口转发用户可以定制操作系统交互Zcash 正是利用这一点在测试场景中把数据放到内存里NewMemEnv。使用限制README 同时明确指出了 LevelDB 的三条边界理解这些限制对使用 Zcash 节点很有帮助它不是 SQL 数据库没有关系数据模型、不支持 SQL 查询、没有索引机制同一时刻只有一个进程可以打开一个数据库zcashd 与 zcash-cli 的读写模型正基于此——只有zcashd进程直接持有数据库文件锁库本身不内置客户端-服务器支持需要此类能力的应用必须自己包装服务器Zcash 通过 RPC 层见src/rpc/对外提供访问能力。构建 LevelDBPOSIX 平台Linux / macOSLevelDB 原生支持 CMake。在src/leveldb/目录下快速构建mkdir -p build cd build cmake -DCMAKE_BUILD_TYPERelease .. cmake --build .更高级的用法请参考 CMake 文档与src/leveldb/CMakeLists.txt。Windows 平台先生成 Visual Studio 2017 工程文件默认构建 x86mkdir build cd build cmake -G Visual Studio 15 ..如需 64 位构建cmake -G Visual Studio 15 Win64 ..命令行编译devenv /build Debug leveldb.sln也可以直接用 Visual Studio 打开leveldb.sln在 IDE 内构建。在 Zcash 中随项目整体编译值得说明的是Zcash 并没有让 LevelDB 作为外部依赖单独构建而是通过 src/Makefile.leveldb.include 把整个src/leveldb/树直接编进libleveldb.a与libmemenv.a两个静态库随zcashd一起链接。该文件还展示了 Zcash 对 LevelDB 的编译期定制LEVELDB_CPPFLAGS_INT -D__STDC_LIMIT_MACROS LEVELDB_CPPFLAGS_INT -DHAVE_SNAPPY0 -DHAVE_CRC32C1 LEVELDB_CPPFLAGS_INT -DHAVE_FDATASYNCHAVE_FDATASYNC LEVELDB_CPPFLAGS_INT -DHAVE_FULLFSYNCHAVE_FULLFSYNC LEVELDB_CPPFLAGS_INT -DHAVE_O_CLOEXECHAVE_O_CLOEXEC其中-DHAVE_SNAPPY0表明 Zcash 构建中禁用了 Snappy 压缩对应下文 dbwrapper 中kNoCompression的设置-DHAVE_CRC32C1表示使用仓库内src/crc32c/自带的 CRC32C 实现平台宏LEVELDB_PLATFORM_POSIX/LEVELDB_PLATFORM_WINDOWS则由TARGET_WINDOWS条件控制。这意味着开发者阅读 src/leveldb/README.md 中独立构建的流程即可理解 LevelDB 本身而 Zcash 的实际构建入口在 src/Makefile.am其 include 了Makefile.leveldb.include。Zcash 的封装与使用从 leveldb::DB 到 CDBWrapperZcash 没有直接使用leveldb::DB而是在 src/dbwrapper.h 和 src/dbwrapper.cpp 中封装了CDBWrapper/CDBBatch/CDBIterator三个类把 C 对象直接序列化为 LevelDB 的键值。这一层的设计完全对应 README 中仓库内容一节对公共头文件的划分。打开数据库与选项配置CDBWrapper 构造函数 展示了 LevelDB 各选项在 Zcash 中的实际取值对应 README 提到的options.h、cache.h、filter_policy.h、env.hstatic leveldb::Options GetOptions(size_t nCacheSize) { leveldb::Options options; options.block_cache leveldb::NewLRUCache(nCacheSize / 2); options.write_buffer_size nCacheSize / 4; // up to two write buffers may be held in memory simultaneously options.filter_policy leveldb::NewBloomFilterPolicy(10); options.compression leveldb::kNoCompression; options.max_open_files 64; ... options.max_file_size std::max(options.max_file_size, DBWRAPPER_MAX_FILE_SIZE); return options; }这些配置逐条对应 README 中Performance一节Cache块缓存block_cache NewLRUCache(nCacheSize / 2)把-dbcache配置的一半内存用作 LRU 缓存写缓冲write_buffer_size nCacheSize / 4README 指出 leveldb 内存中可能同时持有两个写缓冲过滤器filter_policy NewBloomFilterPolicy(10)即每键 10 bit 的布隆过滤器README 说明这能把随机Get()的不必要磁盘读减少约 100 倍压缩compression kNoCompression——Zcash 出于安全/性能考量显式关闭了 LevelDB 自身的块压缩README 提示只有在基准测试显示收益时才应这样做Zcash 的取舍可结合 doc/developer-notes.md 中关于数据库的说明理解同步与校验readoptions.verify_checksums true、iteroptions.verify_checksums true对应 README Checksums一节的ReadOptions::verify_checksums而options.paranoid_checks trueLevelDB 1.16则对应Options::paranoid_checks会在检测到内部损坏时立即报错这正是 zcashd 在磁盘数据损坏时抛出dbwrapper_error(Database corrupted)的机制来源。构造函数还展示了 README Opening A Database 的两种变化options.create_if_missing true数据库不存在时自动创建对应 README 基础示例fWipe时调用leveldb::DestroyDB(path, options)清空旧数据fMemory时penv leveldb::NewMemEnv(leveldb::Env::Default())即使用 README Environment一节提到的自定义Env机制把数据库整体放进内存——这是 Zcash 单元测试test_*与回归测试快速启动的基石。原子批处理README 强调WriteBatch可以原子地应用一组更新并可用于加速批量写入。Zcash 的 CDBBatch 封装了leveldb::WriteBatch而 txdb.cpp 的 BatchWrite 展示了它的极致运用一次提交中同时写入/删除 UTXODB_COINS、Sprout/Sapling/Orchard 三个屏蔽池的锚点与空化器nullifier、链历史 MMR 节点与子树数据最后统一db.WriteBatch(batch)保证区块状态更新的原子性。错误处理README 提到多数函数返回leveldb::Status并可用s.ToString()打印错误。dbwrapper_private::HandleError 正是该模式的工程化封装把IsCorruption()、IsIOError()、IsNotFound()分别映射为带明确消息的dbwrapper_error异常由上层如init捕获并决定是中止启动还是触发重建。zcashd 的数据库布局LevelDB 在区块链状态中的应用在 src/txdb.cpp 中可以看到 Zcash 在 LevelDB 之上构建了三个逻辑数据库chainstateCCoinsViewDB的默认数据库GetDataDir() / chainstate保存 UTXO、三池锚点/空化器、链历史 MMR、Sapling/Orchard 子树等链上状态blocks/indexCBlockTreeDBGetDataDir() / blocks / index保存区块索引CBlockIndex磁盘形式CDiskBlockIndex、区块文件信息、交易索引与 insightexplorer 相关索引还有钱包数据库等同样基于CDBWrapper。复合键与前缀布局README 的Key Layout一节建议把一起访问的键放得彼此靠近并可用不同前缀分隔不同数据类别。Zcash 正是这样做的——txdb.cpp 开头 定义了大量单字符键前缀static const char DB_SPROUT_ANCHOR A; static const char DB_SAPLING_ANCHOR Z; static const char DB_ORCHARD_ANCHOR Y; static const char DB_NULLIFIER s; static const char DB_SAPLING_NULLIFIER S; static const char DB_ORCHARD_NULLIFIER O; static const char DB_COINS c; static const char DB_BLOCK_FILES f; static const char DB_TXINDEX t; static const char DB_BLOCK_INDEX b; static const char DB_BEST_BLOCK B; ...例如 UTXO 的键是std::pairchar, uint256(DB_COINS, txid)区块索引的键是std::pairchar, uint256(DB_BLOCK_INDEX, blockhash)。由于 LevelDB 按键字典序存储同前缀的记录在磁盘与缓存中天然聚集配合Seek/Next迭代即可高效扫描整类数据——LoadBlockIndexGuts正是通过pcursor-Seek(make_pair(DB_BLOCK_INDEX, uint256()))后顺序迭代来重建内存区块索引的见 txdb.cpp。-dbcache 参数init.cpp 中定义了-dbcachen参数默认值见nDefaultDbCache帮助文本明确为Set database cache size in megabytes随后int64_t nTotalCache (GetArg(-dbcache, nDefaultDbCache) 20);将其换算为字节最终作为nCacheSize传入各CDBWrapper。结合上文GetOptions可以得出-dbcache的一半成为 LevelDB 块缓存四分之一成为写缓冲——这是调优 zcashd 内存与 IO 行为的直接入口。LevelDB 的存储模型与文件布局README 指向 doc/impl.md 了解实现概览。为了理解 zcashd 数据目录里出现哪些文件这里提炼其要点日志文件*.log追加记录最近的更新达到约 4MB 阈值后转为排序表同时内存中维护一份 memtable 副本供读操作实时反映未落盘的更新排序表*.ldb / sstable按键排序的条目序列每个条目是一个键的值或删除标记表按层级组织0 层young level允许键重叠其余层键范围互不重叠层 L 的合并规模阈值约为 10^L MB1 层 10MB、2 层 100MB……MANIFEST 文件记录每层包含哪些排序表、对应键范围等元数据每次重新打开数据库都会新建一个带编号的 MANIFEST其变更以日志形式追加CURRENT 文件一个纯文本文件指明最新 MANIFEST 的名字信息日志LOG与LOG.old其他文件LOCK进程锁、*.dbtmp等临时文件。后台压缩compaction负责把新数据从 0 层逐步合并迁移到更大的层从而改善读性能DeleteObsoleteFiles()在每次压缩和恢复结束时清理不再被引用的文件。这份level 0 → 逐层下沉 → 压缩回收的 LSM 生命周期是理解 zcashd 数据目录增长与-dbcache调优的背景知识。性能特征README 的基准数据解读README 的 Performance 一节给出了上游 leveldb 附带db_bench程序在 2011 年某 4 核 Q6600 机器上的基准结果可用于估算数量级注意这是 LevelDB 上游数据非 Zcash 仓库实测且硬件已过时测试设置100 万条记录每条 16 字节键 100 字节值压缩后约 50 字节原始大小约 110.6 MB文件约 62.9 MB。写性能每次 op 为写一条键值对基准结果fillseq顺序填充1.765 micros/op62.7 MB/sfillsync每次同步落盘268.409 micros/op0.4 MB/s10000 opsfillrandom随机填充2.460 micros/op45.0 MB/soverwrite随机覆写2.380 micros/op46.5 MB/s读性能工作集在内存中基准结果readrandom16.677 micros/op约 6 万次读/秒readseq0.476 micros/op232.3 MB/sreadreverse0.724 micros/op152.9 MB/s后台压缩完成后压缩通常自动触发读性能进一步改善readrandom降至 11.602 micros/op约 8.5 万次读/秒顺序读 261.8 MB/s。给足块缓存、让未压缩块驻留内存后readrandom在压缩前后分别可达 9.775 与 5.215 micros/op约 10 万 / 19 万次读/秒。README 特别指出读开销很大一部分来自对从磁盘读入块的反复解压这正是 Zcash 选择kNoCompression并配置block_cache的动机之一而异步写通常比同步写快上千倍代价是机器崩溃可能丢失最后几次更新。公共 API 指南从哪些头文件入手README 的 Repository contents 一节明确指出公共接口在include/leveldb/*.h调用方不应包含或依赖包内其他头文件的细节内部 API 可能随时变更。在 Zcash 仓库中这些文件位于 src/leveldb/include/leveldb/按 README 的指引db.h数据库的主接口从它开始读options.h控制整个数据库以及单次读写的行为Zcash 的GetOptionsdbwrapper.cpp就是它的实战用法comparator.h用户自定义比较函数的抽象默认按键的字节序比较需要自定义排序如处理不同字符编码时可自行实现——注意 README 强调比较器Name()会写入数据库并在每次打开时校验改名会导致DB::Open失败iterator.h数据迭代接口可从 DB 对象获得支持SeekToFirst/Seek/Next/Prev/SeekToLastwrite_batch.h原子应用多笔更新的接口对应 Zcash 的CDBBatchslice.h维护指向外部字节数组的指针与长度的轻量模块——由于 LevelDB 键值允许含\0字节它不返回以 NUL 结尾的 C 风格字符串使用时必须保证底层数组在 Slice 生命周期内存活status.h多数公共接口返回的状态类型用于报告成功与各类错误env.hOS 环境抽象POSIX 实现在util/env_posix.ccZcash 测试用的内存实现NewMemEnv见 helpers/memenv/memenv.htable.h 与 table_builder.h更底层的模块大多数客户端不会直接使用。结合源码的动手实践建议阅读入口从 src/dbwrapper.h 的CDBWrapper开始对照 src/leveldb/include/leveldb/db.h 与 options.h 逐项核对选项语义观察实际数据库运行 zcashd先按 INSTALL 构建后在数据目录下可以看到chainstate/与blocks/index/两个 LevelDB 目录内部即是上文所述的*.log、*.ldb、MANIFEST-*、CURRENT、LOCK、LOG文件使用-dbcachen调整缓存大小并观察debug.log中Opening LevelDB in ...等日志见 dbwrapper.cpp定位测试佐证仓库内 gtest 用例如 src/gtest/test_mempool.cpp 等大量以fMemorytrue方式构造CDBWrapper可从中看到内存 Env 在测试场景中的真实用法深度文档继续阅读 doc/impl.md实现概览、doc/table_format.md不可变 Table 文件格式与 doc/log_format.md日志文件格式可完整掌握 LevelDB 的磁盘格式细节。【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考