前言
本文將會介紹比特幣區塊原始數據的存儲格式,以及各個字段所代表的含義。
區塊數據
比特幣程序將數據存在了4個地方。
-
blocks/blk*.dat
的文件中存儲了實際的塊數據,這些數據以網絡格式存儲。它們僅用于重新掃描錢包中丟失的交易,將這些交易重新組織到鏈的不同部分,并將數據塊提供給其他正在同步數據的節點。 -
blocks/index/*
是一個levelDB數據庫,存儲著目前已知塊的元數據,這些元數據記錄所有已知的塊以及它們存儲在磁盤上的位置。沒有這些文件,查找一個塊將是非常慢的。 -
chainstate/*
是一個levelDB數據庫,以緊湊的形式存儲所有當前未花費的交易以及它們的元數據。這里的數據對于驗證新傳入的塊和交易是必要的。在理論上,這些數據可以從塊數據中重建,但是這需要很長時間。沒有這些數據也可以對數據進行驗證,但是需要現有塊數據進行掃面,這無疑是非常慢的。 -
blocks/rev*.dat
中包含了“撤銷”數據,可以將區塊視為鏈的“補丁”(它們消耗一些未花費的輸出并生成新的輸出),那么這些撤銷數據將是反向補丁。如果需要回滾鏈,這些數據將是必須的。
比特幣程序從網絡中接受數據后,會將數據以.dat
的形式轉儲到磁盤上。
一個塊文件大約為128MB。每個塊文件會有一個對應的撤銷文件,比如文件blocks/blk1234.dat
和blocks/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[] | 交易詳情 |
從原始數據中讀取的流程大概如下
- 讀取4個字節,比對magic_number
- 一旦匹配,讀取后4個字節,得到塊的大小m
- 讀取后面m個字節,得到區塊的數據
- 返回第一步,讀取下一個區塊
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 | 鎖定時間 |
從數據中解析流程大致如下:
- 讀取4個字節版本號
- 解析varint,得到輸入數量n
- 執行1~n次循環,解析交易輸入
- 解析varint,得到輸出數量m
- 執行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[] | 花費這筆輸出需要滿足的條件 |
解析塊文件的程序可以參考下這里。