深入理解比特币数据存储

本文由冉小龙和张勇合作完成,致谢:张勇,转载请注明出处

当我们打开 bitcoincash 目录时,我们会看到如下的文件目录,这些文件究竟是什么,具体存储了哪些内容呢?下面我们将一一揭开其神秘面纱。

bitcoincash 文件夹:

bitcoincash.png

blocks 文件夹:

blocks.png

index 文件夹:

深入理解比特币数据存储_第1张图片
index.png

chainstate文件夹:


深入理解比特币数据存储_第2张图片
chainstate.png

通过观察 bitcoincash 目录,我们可以发现, 比特币总共存储了以下内容:

文件名称 文件描述 存储形式
chainstate 存储 utxo 相关的数据 leveldb数据库
blocks/index 存储 blocks 的元数据信息 leveldb数据库
blocks/blk?????.dat 存储 blocks 相关的数据信息,主要包括 block header 和 txs 磁盘文件
blocks/rev?????.dat 存储 blocks undo 的数据,主要包括每笔交易所花费的 out 的信息。 磁盘文件

其中 block 的数据和 block 的 undo 数据是直接存储到disk上面的,block 的 index 数据和 utxo 的数据是写到 leveldb 数据库中。

leveldb

为了方便理解 leveldb 的目录存储结构,下面简述一下 leveldb 的原理。

深入理解比特币数据存储_第3张图片
leveldb.png

leveldb 使用的是 LSMTree 的存储结构,其存储的逻辑大致如上图所示,具体步骤如下:

  • 当往 leveldb 中写入一条数据的时候,首先会将数据写入 log 文件,log 文件完成之后,再将数据写入内存(memtable)中。
  • 当 memtable 中的数据写满之后,.log 文件会被锁定,同时生成 Immutable table 文件,该文件只支持读操作,不支持写和删除,这个时候,会重新生成 .log文 件和memtable 文件,新写入一条数据的时候,会重新写入空的 .log 文件和 memtable 中。
  • LevelDb 后台调度会将 Immutable Memtable 的数据导出到磁盘,形成一个新的SSTable 文件。SSTable 就是由内存中的数据不断导出并进行 Compaction 操作后形成的,而且 SSTable 的所有文件是一种层级结构,第一层为 Level 0,第二层为 Level 1,依次类推,层级逐渐增高,这也是为何称之为LevelDb的原因。

各个文件的含义:

Current文件:

Current 文件是干什么的呢?这个文件的内容只有一个信息,就是记载当前的 manifest 文件名。因为在 LevleDb 的运行过程中,随着 Compaction 的进行,SSTable 文件会发生变化,会有新的文件产生,老的文件被废弃,Manifest 也会跟着反映这种变化,此时往往会新生成Manifest 文件来记载这种变化,而 Current 则用来指出哪个 Manifest 文件才是我们关心的那个 Manifest 文件。

Manifest文件

Manifest 文件存储的是 xxx.ldb 文件的元数据信息,因为,我们只有 xxx.ldb 文件,我们并不知道它具体属于哪一个 level。这也是 Manifest 文件的作用,每次打开 DB 的时候,leveldb 都会去创建这样一个文件并在其尾部追加后缀标识。该文件是以 append 的方式写入 disk 的。

LOG文件

leveldb 运行时的日志文件,方便用户查看。

LOCK文件

它是使用文件实现的一个 DB 锁,告知用户,一个 leveldb 的实例在一个进程范围内只允许被打开一次。

xxx.ldb文件

这个文件是记录 leveldb 的数据文件(区别与元数据文件),按照 KV 有序的形式写入数据库中。

level-0 的文件大小就是 memtable 文件做 compaction 之后的大小,level-1 10MB、level-2 100MB、level-3 1000MB 以此类推。

xxx.log 文件

我们上面说过,为了保证数据不丢失,在写数据之前会先写入 .log 文件,.log 文件存储的是一系列最近的更新,每个更新以 append 的方式追加到当前的 log 文件中,当 log 文件达到 4MB时会转化为一个有序的文件,并创建新的 log 文件来记录最近的更新。这个 log 文件中与上文中提到的 memtable 文件是互相映射的,当 memtable 文件被写入 level-0 后,对应的 log 文件会被删除,新的 log 文件会重新创建,对应新的 memtable,以此类推。

综上所述,我们可以看出,leveldb 是存储模型中一个典型的数据与元数据分离存储的数据库。

chainstate 文件夹

chainstate 是一个leveldb的数据库,主要存储一些 utxo 和 tx 的元数据信息。存储 chainstate 的数据主要是用来去验证新进来的 blocks 和 tx 是否是合法的。如果没有这个操作,就意味着对于每一个被花费的 out 你都需要去进行全表扫描来验证。

chainstate_file.png

如上图所示,utxo的数据主要存储于chainstate这个文件目录,由于要存储到leveldb中,所以肯定是按照 key、value 的格式将数据准备好。

coin

深入理解比特币数据存储_第4张图片
coin.png
深入理解比特币数据存储_第5张图片
coin_db_key.png

如上所示:key总共包含三部分内容,1 字节的大写 C , 32 字节的 hash,4 字节的序列号。

value 是 coin 被序列化之后的值,具体如下:

深入理解比特币数据存储_第6张图片
image.png

coin 又包含了 txout 结构,具体如下:

深入理解比特币数据存储_第7张图片
image.png

对 nValue 和 scriptPubKey 采用了不同的压缩方式来进行序列化,如下:

深入理解比特币数据存储_第8张图片
image.png

best block

image.png

比特币还往 chainstate 中记录了另一部分信息,首先去判断当前 block 的 hash 是否为 null,不为 null 的话,以 1 字节的大写 B 为 key,32 字节的 block hash 为value,写入 coin 数据库中。

总结:utxo 写入 disk 的数据库为:chainstate,写入数据分为两部分,第一部分:key是outpoin, 由+组成,其中txid是32字节,tx out index 是用var int的编码方式序列化value 为 coin 序列化之后的大小。第二部分:写入的 key 为 1 字节的DB_BEST_BLOCK 标识,value 为 32 字节的 block hash。

深入理解比特币数据存储_第9张图片
image.png

在 bitcoin core 0.17 的时候, chainstate 目录做了改动,多写了一部分数据进去,图示如下:

深入理解比特币数据存储_第10张图片
image.png

Note:

在0.17的结构中,第一部分并不会存在很长时间,它只会在触发BatchWrite第一步写入,在整个coinsmap写完之后将这部分删除。

index 文件夹:

image.png

index 文件夹下记录的主要是 blocks 的 index 信息,block index 是block的元数据信息,其中包含和block header信息,高度,以及chain的信息;按照 utxo 存储的思路,我们再去寻找 blocks 中 index 的 key 和 value。

reindex

深入理解比特币数据存储_第11张图片
image.png

index 中写的第一部分数据:key 是 1 字节的 DB_REINDEX_FLAG,value 是 1 字节的布尔值。用来标识是否需要进行 reindex 操作。

txindex

深入理解比特币数据存储_第12张图片
image.png

index 中写的第二部分数据:key 是 1 字节的 DB_TXINDEX 加 32 字节的 hash,value 是序列化之后的 CDiskTxPos,它只有一个成员是,int 类型的 nTxOffset。这些是可选的,只有当'txindex' 被启用时才存在。 每个记录存储:

  • 交易存储在哪个块文件号码中。
  • 哪个文件中的交易所属的块被抵消存储在。
  • 从该块的开始到该交易本身被存储的位置的偏移量。

blockfileinfo

深入理解比特币数据存储_第13张图片
image.png

index 中写的第三部分数据:这部分数据是比较重要的,

fileinfo

首先写入 fileinfo 数据,key 是 1 字节的 DB_BLOCK_FILES 加上 4 字节的文件编号,value 是 CBlockFileInfo 序列化后的数据。

lastFile

其次写入 lastFile 信息,key 是 1 字节的 DB_LAST_BLOCK,value 是 4 字节的 nLastFile。

blockindex

最后写入 blockindex 的信息,key 是 1 字节的 DB_BLOCK_INDEX 加上 32 字节的 blockhash value是CDiskBlockIndex序列化之后的数据。

flag
image.png

index 中写的第四部分数据:key 是 1 字节的 DB_FLAG 加上 flag 的名字,value 是 1 字节的布尔值(1 为 true,0 为 false),可以打开或关闭各种类型的标志,目前定义的比如:TxIndex(是否启动交易索引)。

深入理解比特币数据存储_第14张图片
image.png

block 文件夹

block 文件夹下主要存在两种文件,一种是 blk???.dat,用于存储 block,另一种是 rev???.dat,用于存储 undo block。 主要存储格式如下:

blk?????.dat

存储 block 序列化的数据。

深入理解比特币数据存储_第15张图片
image.png

存储格式如下(按照先后顺序):

image.png
MessageStart

MessageMagic 在启动程序时定义,并且在不同网络中定义不同,MessageMagic 分为 netMagic 和 diskMagic :

Mainnet:
深入理解比特币数据存储_第16张图片
image.png
TestNet:
深入理解比特币数据存储_第17张图片
image.png
RegTestNet:
深入理解比特币数据存储_第18张图片
image.png

MessageMagic 是一个 4 byte 的数组,在写入数据的时候调用 FLATDATA 这个宏定义,具体如下:

image.png

FLATDATA 会将vector或者map这种数据结构中的元素按照数组的原始序列dump到disk上。

image.png

write() 函数的第一个参数代表要写入的数据的起始位置,第二个参数代表要写入数据的大小,pbegin 指向 vector 的起始位置,pend指向末尾元素 +1 的位置,所以在这里先写入了 4 byte 的 messageStart。

image.png

BlockSize

BlockSize主要描述 Block 被序列化后的长度,为 4 byte。

image.png

Block 序列化

block 序列化主要序列化两部分,一部分是 BlockHeader 结构,一部分为 transaction 的一个共享指针 vtx:

深入理解比特币数据存储_第19张图片
image.png

第一部分是 BlockHeader:

深入理解比特币数据存储_第20张图片
image.png

第二部分是 vtx:

image.png

CTransaction 主要序列化以下内容:

深入理解比特币数据存储_第21张图片
image.png

总结:

blk????.dat 文件首先写入 4 byte 的 messageMagic,其次写入 4 byte 的 block size,最后写入 block 被序列化之后的数据。

深入理解比特币数据存储_第22张图片
image.png

rev?????.dat

存储 undoblock 序列化的数据。

image.png

MessageStart 和 UndoBlockSize 与 Block 中的相同。

BlockUndo 序列化

BlockUndo 序列化只有 vtxundo 一个对象,vtxundo 是 CTxUndo 的一个 vector ,对其进行序列化操作如下:

深入理解比特币数据存储_第23张图片
image.png

CTxUndo 的序列化操作如下,其中 prevout 是一个 Coin 的 vector:

深入理解比特币数据存储_第24张图片
image.png

Coin的序列化操作如下:

深入理解比特币数据存储_第25张图片
image.png

Coin包含两部分内容,代码如下:

深入理解比特币数据存储_第26张图片
image.png

其中对 TxOut 的序列化如下,对 nValue 和 scriptPubKey 采用了不同的压缩方式来进行序列化:

深入理解比特币数据存储_第27张图片
image.png
BlockUndoCheckSum

具体代码如下:

深入理解比特币数据存储_第28张图片
image.png

将 hashBlock 和 blockundo 的数据写入 CHashWriter 的接口中,获取 CHashWriter 的 hash ,并将 32 字节的 hash 值写入 undofile 文件中。

总结

深入理解比特币数据存储_第29张图片
image.png

blk????.dat 和 rev????.dat 的区别:

blk???.dat 和 rev????.dat 所存储的数据是不一样的,block 存储的是 block header 和 txs 序列化后的数据,undo block 存储的是 txout 被序列化后的数据。

关于文件大小的一些问题:

blk.dat 的默认初始化大小是16M,最大为 128M, rev.dat 的默认初始化大小为 1M。

深入理解比特币数据存储_第30张图片
image.png

在导入 block 时,会去检查磁盘空间,必须大于 50M,否则就会 Disk space is low

image.png

关于在 prune 时, 磁盘要求必须大于 550M:

深入理解比特币数据存储_第31张图片
image.png

bitcoin 要求必须保留 288 个 block, 按每个 block 1M 大小进行计算, 需要 288M, 还需要额外的 15% 的空间去存储 UNDO 的数据, 再加上以 20% 的孤块率, 大约需要 397M 的空间, 这是最低限度, 但我们还需要加上同步块的数据 blk.dat, 需要128M, 再加上约为 15% 的 undo data, 约为147M。 所以整个需要 147M + 397M=544M, 所以设置限度为 550M。

你可能感兴趣的:(深入理解比特币数据存储)