最新比特币价格-比特币今日行情价格_比特币交易网

深入理解比特币数据存储

佚名

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

bitcoincash 文件夹:

bitcoincash.png

blocks 文件夹:

blocks.png

index 文件夹:

index.png

chainstate文件夹:

chainstate.png

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

文件名称文件描述存储形式

chainstate

存储 utxo 相关的数据

leveldb数据库

blocks/index

存储 blocks 的元数据信息

leveldb数据库

存储 blocks 相关的数据信息,主要包括 block header 和 txs

磁盘文件

存储 blocks undo 的数据,主要包括每笔交易所花费的 out 的信息。

磁盘文件

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

leveldb

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

leveldb.png

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

各个文件的含义: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

coin.png

coin_db_key.png

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

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

image.png

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

image.png

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

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。

image.png

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

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

image.png

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

txindex

image.png

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

blockfileinfo

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(是否启动交易索引)。

image.png

block 文件夹

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

存储 block 序列化的数据。

image.png

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

image.png

MessageStart

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

Mainnet:

image.png

TestNet:

image.png

RegTestNet:

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:

image.png

第一部分是 BlockHeader:

image.png

第二部分是 vtx:

image.png

CTransaction 主要序列化以下内容:

image.png

总结:

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

image.png

存储 undoblock 序列化的数据。

image.png

MessageStart 和 UndoBlockSize 与 Block 中的相同。

BlockUndo 序列化

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

image.png

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

image.png

Coin的序列化操作如下:

image.png

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

image.png

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

image.png

BlockUndoCheckSum

具体代码如下:

image.png

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

总结

image.png

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

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

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

image.png

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

image.png

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

image.png

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