比特币技术探寻二:数据存储

科技 11 2017-10-09 17:03
比特币技术探寻二:数据存储-微网络

前言

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

区块数据

比特币程序将数据存在了4个地方。

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

比特币程序从网络中接受数据后,会将数据以.dat的形式转储到磁盘上。

一个块文件大约为128MB。每个块文件会有一个对应的撤销文件,比如文件blocks/blk1234.datblocks/recv1234.dat对应。

描述

每个块都包含一些近期的交易记录,和对之前块的引用。同时它还包含了一个难以解决的数学难题的答案,每个块的答案唯一。如果没有正确的答案,新的块将不能提交给网络--“挖矿”的过程本质是竞争的过程,最先找到答案的人将获得挖矿的奖励。每个块中的数学问题是非常难以解决的,但一旦找到有效的解决方案,网络中的其他部分就很容易确认方案的正确性。对任何给定的块,会有多个有效的解决方案--找到一个方案即可以宣称解决了该难题。

比特币网络在设计上每小时出6个块,因此需要自动调整数学问题的难度。每隔2016个块(大概历时两周),所有的比特币客户端会将挖出的块数量和目标数量进行比较,并对目标进行修改。网络会达成共识,并自动调整生成块的难度。

每个块都包含对先前块的引用,所以现有块的集合可以形成区块链。然而,该链可能会存在分叉,比如在两个矿工同时到达同一区块的两个不同的有效解决方案时。点对点的网络会在短时间内解决这些分叉,使链中只有一个分支存活。

比特币客户端会接受“最长”的链作为有效链。链的长度是指具有最大组合难度的链,而不是具有最多块的链。

块结构

比特币数据块的结构如下:

大小(字节) 名称 数据类型 描述
4 magic_number uint32 总是0xD9B4BEF9,作为区块之间的分隔符
4 block_size uint32 后面数据到块结束的字节数
80 block_header char[] block header
varies transaction_cnt uint 交易数量
varies transaction char[] 交易详情

从原始数据中读取的流程大概如下

  1. 读取4个字节,比对magic_number
  2. 一旦匹配,读取后4个字节,得到块的大小m
  3. 读取后面m个字节,得到区块的数据
  4. 返回第一步,读取下一个区块

Block Header

block header固定80字节大小,结构如下

大小(字节) 名称 数据类型 描述
4 version int32_t 版本号
32 previous_block_hash char[32] 前一个block的hash值
32 merkle_root_hash char[32] 区块内所有交易的merkle hash值
4 time uint32 unix时间戳,矿工挖矿的时间
4 nBits uint32 该块的标题hash必须小于的值。难度
4 nonce uint32 随机值,用于产生满足难度的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,如果低于目标,则“挖矿”成功;如果没有,则递增随机数,再次验证。能否挖到矿,除了取决矿工手里的算力,还要加上一点运气。

交易

交易的结构如下

大小(字节) 名称 数据类型 描述
4 version uint32 交易版本号
varint tx_in_count uint 交易输入数量
varies tx_in tx_in 交易输入
varint tx_out_count uint 交易输出数量
varies tx_out tx_out 交易输出
4 lock_time uint32 锁定时间

从数据中解析流程大致如下:

  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 && <=252 1 uint8_t
>=253 && <=0xffff 3 后2个字节uint16_t
>=0x10000 && <=0xffffffff 5 后4个字节uint32_t
>=0x100000000 && <=0xffffffffffffffff 9 后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_ = '<H'
    elif size == 254:
        format_ = '<I'
    elif size == 255:
        format_ = '<Q'
    else:
        assert 0, 'unknown format_ for size: {size}'.format(size=size)
    size = struct.calcsize(format_)
    return struct.unpack(format_, data[1:size+1])[0], size + 1

交易输入

每个非coinbase的交易输入都是之前某个交易的交易输出。

交易输入的结构如下

大小(字节) 名称 数据类型 描述
32 previous_output_hash outpoint 前置交易hash
4 previous_output_index uint32 前置交易index
varint script_bytes uint 解锁脚本长度
varies signature_script char[] 解锁脚本
4 sequence uint32 序列号

交易输出

交易输出的结构如下

大小(字节) 名称 数据类型 描述
8 value int64 花费的数量,单位是聪
1+ pk_script_size uint pubkey脚本中的字节数量
varies pk_script char[] 花费这笔输出需要满足的条件

解析块文件的程序可以参考下这里。

文章评论