比特币的数据结构分析

转自:比特币的数据结构分析

前言

本文将会介绍比特币区块原始数据的存储格式,以及各个字段所代表的含义。

区块数据

比特币程序将数据存在了4个地方。
  • blocks/blk*.dat的文件中存储了实际的块数据,这些数据以网络格式存储。它们仅用于重新扫描钱包中丢失的交易,将这些交易重新组织到链的不同部分,并将数据块提供给其他正在同步数据的节点。
  • blocks/index/*是一个levelDB数据库,存储着目前已知块的元数据,这些元数据记录所有已知的块以及它们存储在磁盘上的位置。没有这些文件,查找一个块将是非常慢的。
  • chainstate/*是一个levelDB数据库,以紧凑的形式存储所有当前未花费的交易以及它们的元数据。这里的数据对于验证新传入的块和交易是必要的。在理论上,这些数据可以从块数据中重建,但是这需要很长时间。没有这些数据也可以对数据进行验证,但是需要现有块数据进行扫面,这无疑是非常慢的。
  • blocks/rev*.dat中包含了“撤销”数据,可以将区块视为链的“补丁”(它们消耗一些未花费的输出并生成新的输出),那么这些撤销数据将是反向补丁。如果需要回滚链,这些数据将是必须的。
比特币程序从网络中接受数据后,会将数据以.dat的形式转储到磁盘上。
一个块文件大约为128MB。每个块文件会有一个对应的撤销文件,比如文件blocks/blk1234.datblocks/recv1234.dat对应。

描述

每个块都包含一些近期的交易记录,和对之前块的引用。同时它还包含了一个难以解决的数学难题的答案,每个块的答案唯一。如果没有正确的答案,新的块将不能提交给网络--“挖矿”的过程本质是竞争的过程,最先找到答案的人将获得挖矿的奖励。每个块中的数学问题是非常难以解决的,但一旦找到有效的解决方案,网络中的其他部分就很容易确认方案的正确性。对任何给定的块,会有多个有效的解决方案--找到一个方案即可以宣称解决了该难题。
比特币网络在设计上每小时出6个块,因此需要自动调整数学问题的难度。每隔2016个块(大概历时两周),所有的比特币客户端会将挖出的块数量和目标数量进行比较,并对目标进行修改。网络会达成共识,并自动调整生成块的难度。
每个块都包含对先前块的引用,所以现有块的集合可以形成区块链。然而,该链可能会存在分叉,比如在两个矿工同时到达同一区块的两个不同的有效解决方案时。点对点的网络会在短时间内解决这些分叉,使链中只有一个分支存活。
比特币客户端会接受“最长”的链作为有效链。链的长度是指具有最大组合难度的链,而不是具有最多块的链。

块结构

比特币数据块的结构如下:
大小(字节)名称数据类型描述
4magic_numberuint32总是0xD9B4BEF9,作为区块之间的分隔符
4block_sizeuint32后面数据到块结束的字节数
80block_headerchar[]block header
variestransaction_cntuint交易数量
variestransactionchar[]交易详情
从原始数据中读取的流程大概如下
  1. 读取4个字节,比对magic_number
  2. 一旦匹配,读取后4个字节,得到块的大小m
  3. 读取后面m个字节,得到区块的数据
  4. 返回第一步,读取下一个区块

Block Header

block header固定80字节大小,结构如下
大小(字节)名称数据类型描述
4versionint32_t版本号
32previous_block_hashchar[32]前一个block的hash值
32merkle_root_hashchar[32]区块内所有交易的merkle hash值
4timeuint32unix时间戳,矿工挖矿的时间
4nBitsuint32该块的标题hash必须小于的值。难度
4nonceuint32随机值,用于产生满足难度的hash值
hash字段使内部字节顺序存储;其他的值以小端序存储。
其中,内部字节顺序需要以字节为单位逆序读取,如下面的python代码:

def format_hash(data):
    # data为读取的32字节的二进制数据
    return str(hexlify(data[::-1]).decode('utf-8'))


下面是一个header例子

02000000 ........................... Block version: 2
 
b6ff0b1b1680a2862a30ca44d346d9e8
910d334beb48ca0c0000000000000000 ... Hash of previous block's header
9d10aa52ee949386ca9385695f04ede2
70dda20810decd12bc9b048aaab31471 ... Merkle root
 
24d95a54 ........................... Unix time: 1415239972
30c31b18 ........................... Target: 0x1bc330 * 256**(0x18-3)
fe9f0864 ........................... Nonce

对header进行两次hash,可以得到区块的hash值,示例代码如下:

def double_sha256(data):
    return hashlib.sha256(hashlib.sha256(data).digest()).digest()

Merkle Root

Merkle树,或者叫hash树,是每个叶子节点用数据块标记的树,并且每个非叶子节点用其子节点的值加密hash后进行标记。这种数据结构允许高效和安全地验证大型数据结构的内容。
在比特币区块中,Merkle root由交易列表生成,如下图。
Merkle树提供了一种验证区块中交易的方式。

Target nBits

Target值是256位无符号整数。header的SHA-256散列值必须低于或等于网络接收的块的当前目标。这个值越小,生成块的难度越高。
前面提到的数学难题,即是找到一个随机数(这个数介于0~2的256次方),使得对header的hash值满足条件。
矿工们每次取一个随机值,计算header的hash,如果低于目标,则“挖矿”成功;如果没有,则递增随机数,再次验证。能否挖到矿,除了取决矿工手里的算力,还要加上一点运气。

交易

交易的结构如下
大小(字节)名称数据类型描述
4versionuint32交易版本号
varinttx_in_countuint交易输入数量
variestx_intx_in交易输入
varinttx_out_countuint交易输出数量
variestx_outtx_out交易输出
4lock_timeuint32锁定时间
从数据中解析流程大致如下:
  1. 读取4个字节版本号
  2. 解析varint,得到输入数量n
  3. 执行1~n次循环,解析交易输入
  4. 解析varint,得到输出数量m
  5. 执行1~m次循环,解析交易输出
一个示例交易数据如下:


01000000 ................................... Version
 
01 ......................................... Number of inputs
|
| 7b1eabe0209b1fe794124575ef807057
| c77ada2138ae4fa8d6c4de0398a14f3f ......... Outpoint TXID
| 00000000 ................................. Outpoint index number
|
| 49 ....................................... Bytes in sig. script: 73
| | 48 ..................................... Push 72 bytes as data
| | | 30450221008949f0cb400094ad2b5eb3
| | | 99d59d01c14d73d8fe6e96df1a7150de
| | | b388ab8935022079656090d7f6bac4c9
| | | a94e0aad311a4268e082a725f8aeae05
| | | 73fb12ff866a5f01 ..................... Secp256k1 signature
|
| ffffffff ................................. Sequence number: UINT32_MAX
01 ......................................... Number of outputs
| f0ca052a01000000 ......................... Satoshis (49.99990000 BTC)
|
| 19 ....................................... Bytes in pubkey script: 25
| | 76 ..................................... OP_DUP
| | a9 ..................................... OP_HASH160
| | 14 ..................................... Push 20 bytes as data
| | | cbc20a7664f2f69e5355aa427045bc15
| | | e7c6c772 ............................. PubKey hash
| | 88 ..................................... OP_EQUALVERIFY
| | ac ..................................... OP_CHECKSIG
 
00000000 ................................... locktime: 0 (a block height)


Varint

交易中使用可变长度整数来表示下一条数据中的字节数。对于不同的数值,存储的空间不一样。
对于0~252的值,只占用一个字节;对于其他小于0xffffffffffffffff的值,第一个字节将成为长度标识位。值和存储空间的关系如下表:
存储空间(字节)数据类型
>=0 && <=2521uint8_t
>=253 && <=0xffff3后2个字节uint16_t
>=0x10000 && <=0xffffffff5后4个字节uint32_t
>=0x100000000 && <=0xffffffffffffffff9后8个字节uint64_t
解析的示例代码如下:

def decode_varint(data):
    assert(len(data) > 0)
    size = to_int(data[0])
    assert(size <= 255)
 
    if size < 253:
        return size, 1
 
    format_ = None
    if size == 253:
        format_ = '

交易输入

每个非coinbase的交易输入都是之前某个交易的交易输出。
交易输入的结构如下
大小(字节)名称数据类型描述
32previous_output_hashoutpoint前置交易hash
4previous_output_indexuint32前置交易index
varintscript_bytesuint解锁脚本长度
variessignature_scriptchar[]解锁脚本
4sequenceuint32序列号

交易输出

交易输出的结构如下
大小(字节)名称数据类型描述
8valueint64花费的数量,单位是聪
1+pk_script_sizeuintpubkey脚本中的字节数量
variespk_scriptchar[]花费这笔输出需要满足的条件
解析块文件的程序可以参考下这里