比特幣學習筆記——————7、區塊鏈

FLy_鵬程萬里發表於2018-06-12


7.1 簡介

區塊鏈是由包含交易資訊的區塊從後向前有序連結起來的資料結構。它可以被儲存為flat file(一種包含沒有相對關係記錄的檔案),或是儲存在一個簡單資料庫中。比特幣核心客戶端使用Google的LevelDB資料庫儲存區塊鏈後設資料。區塊被從後向前有序地連結在這個鏈條裡,每個區塊都指向前一個區塊。區塊鏈經常被視為一個垂直的棧,第一個區塊作為棧底的首區塊,隨後每個區塊都被放置在其他區塊之上。用棧來形象化表示區塊依次堆疊這一概念後,我們便可以使用一些術語,例如:“高度”來表示區塊與首區塊之間的距離;以及“頂部”或“頂端”來表示最新新增的區塊。

對每個區塊頭進行SHA256加密雜湊,可生成一個雜湊值。通過這個雜湊值,可以識別出區塊鏈中的對應區塊。同時,每一個區塊都可以通過其區塊頭的“父區塊雜湊值”欄位引用前一區塊(父區塊)。也就是說,每個區塊頭都包含它的父區塊雜湊值。這樣把每個區塊連結到各自父區塊的雜湊值序列就建立了一條一直可以追溯到第一個區塊(創世區塊)的鏈條。

雖然每個區塊只有一個父區塊,但可以暫時擁有多個子區塊。每個子區塊都將同一區塊作為其父區塊,並且在“父區塊雜湊值”欄位中具有相同的(父區塊)雜湊值。一個區塊出現多個子區塊的情況被稱為“區塊鏈分叉”。區塊鏈分叉只是暫時狀態,只有當多個不同區塊幾乎同時被不同的礦工發現時才會發生(參見“8.10.1 區塊鏈分叉”)。最終,只有一個子區塊會成為區塊鏈的一部分,同時解決了“區塊鏈分叉”的問題。儘管一個區塊可能會有不止一個子區塊,但每一區塊只有一個父區塊,這是因為一個區塊只有一個“父區塊雜湊值”欄位可以指向它的唯一父區塊。

由於區塊頭裡麵包含“父區塊雜湊值”欄位,所以當前區塊的雜湊值因此也受到該欄位的影響。如果父區塊的身份標識發生變化,子區塊的身份標識也會跟著變化。當父區塊有任何改動時,父區塊的雜湊值也發生變化。父區塊的雜湊值發生改變將迫使子區塊的“父區塊雜湊值”欄位發生改變,從而又將導致子區塊的雜湊值發生改變。而子區塊的雜湊值發生改變又將迫使孫區塊的“父區塊雜湊值”欄位發生改變,又因此改變了孫區塊雜湊值,等等以此類推。一旦一個區塊有很多代以後,這種瀑布效應將保證該區塊不會被改變,除非強制重新計算該區塊所有後續的區塊。正是因為這樣的重新計算需要耗費巨大的計算量,所以一個長區塊鏈的存在可以讓區塊鏈的歷史不可改變,這也是比特幣安全性的一個關鍵特徵。

你可以把區塊鏈想象成地質構造中的地質層或者是冰川巖芯樣品。表層可能會隨著季節而變化,甚至在沉積之前就被風吹走了。但是越往深處,地質層就變得越穩定。到了幾百英尺深的地方,你看到的將是儲存了數百萬年但依然保持歷史原狀的岩層。在區塊鏈裡,最近的幾個區塊可能會由於區塊鏈分叉所引發的重新計算而被修改。最新的六個區塊就像幾英寸深的表土層。但是,超過這六塊後,區塊在區塊鏈中的位置越深,被改變的可能性就越小。在100個區塊以後,區塊鏈已經足夠穩定,這時Coinbase交易(包含新挖出的比特幣的交易)可以被支付。幾千個區塊(一個月)後的區塊鏈將變成確定的歷史,永遠不會改變。

7.2 區塊結構

區塊是一種被包含在公開賬簿(區塊鏈)裡的聚合了交易資訊的容器資料結構。它由一個包含後設資料的區塊頭和緊跟其後的構成區塊主體的一長串交易組成。區塊頭是80位元組,而平均每個交易至少是250位元組,而且平均每個區塊至少包含超過500個交易。因此,一個包含所有交易的完整區塊比區塊頭的1000倍還要大。表7-1描述了一個區塊結構。

表7-1 區塊結構

大小欄位描述
4位元組區塊大小用位元組表示的該欄位之後的區塊大小
80位元組區塊頭組成區塊頭的幾個欄位
1-9 (可變整數)交易計數器交易的數量
可變的交易記錄在區塊裡的交易資訊

7.3 區塊頭

區塊頭由三組區塊後設資料組成。首先是一組引用父區塊雜湊值的資料,這組後設資料用於將該區塊與區塊鏈中前一區塊相連線。第二組後設資料,即難度、時間戳和nonce,與挖礦競爭相關,詳見第8章。第三組後設資料是merkle樹根(一種用來有效地總結區塊中所有交易的資料結構)。表7-2描述了區塊頭的資料結構。

表7-2 區塊頭結構

大小欄位描述
4位元組版本版本號,用於跟蹤軟體/協議的更新
32位元組父區塊雜湊值引用區塊鏈中父區塊的雜湊值
32位元組Merkle根該區塊中交易的merkle樹根的雜湊值
4位元組時間戳該區塊產生的近似時間(精確到秒的Unix時間戳)
4位元組難度目標該區塊工作量證明演算法的難度目標
4位元組Nonce用於工作量證明演算法的計數器

Nonce、難度目標和時間戳會用於挖礦過程,更多細節將在第8章討論。

7.4 區塊識別符號:區塊頭雜湊值和區塊高度

區塊主識別符號是它的加密雜湊值,一個通過SHA256演算法對區塊頭進行二次雜湊計算而得到的數字指紋。產生的32位元組雜湊值被稱為區塊雜湊值,但是更準確的名稱是:區塊頭雜湊值,因為只有區塊頭被用於計算。例如:000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f是第一個比特幣區塊的區塊雜湊值。區塊雜湊值可以唯一、明確地標識一個區塊,並且任何節點通過簡單地對區塊頭進行雜湊計算都可以獨立地獲取該區塊雜湊值。

請注意,區塊雜湊值實際上並不包含在區塊的資料結構裡,不管是該區塊在網路上傳輸時,抑或是它作為區塊鏈的一部分被儲存在某節點的永久性儲存裝置上時。相反,區塊雜湊值是當該區塊從網路被接收時由每個節點計算出來的。區塊的雜湊值可能會作為區塊後設資料的一部分被儲存在一個獨立的資料庫表中,以便於索引和更快地從磁碟檢索區塊。

第二種識別區塊的方式是通過該區塊在區塊鏈中的位置,即“區塊高度(block height)”。第一個區塊,其區塊高度為0,和之前雜湊值000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f所引用的區塊為同一個區塊。因此,區塊可以通過兩種方式被識別:區塊雜湊值或者區塊高度。每一個隨後被儲存在第一個區塊之上的區塊在區塊鏈中都比前一區塊“高”出一個位置,就像箱子一個接一個堆疊在其他箱子之上。2014年1月1日的區塊高度大約是278,000,說明已經有278,000個區塊被堆疊在2009年1月建立的第一個區塊之上。

和區塊雜湊值不同的是,區塊高度並不是唯一的識別符號。雖然一個單一的區塊總是會有一個明確的、固定的區塊高度,但反過來卻並不成立,一個區塊高度並不總是識別一個單一的區塊。兩個或兩個以上的區塊可能有相同的區塊高度,在區塊鏈裡爭奪同一位置。這種情況在“8.10.1 區塊鏈分叉”一節中有詳細討論。區塊高度也不是區塊資料結構的一部分,它並不被儲存在區塊裡。當節點接收來自比特幣網路的區塊時,會動態地識別該區塊在區塊鏈裡的位置(區塊高度)。區塊高度也可作為後設資料儲存在一個索引資料庫表中以便快速檢索。

一個區塊的區塊雜湊值總是能唯一地識別出一個特定區塊。一個區塊也總是有特定的區塊高度。但是,一個特定的區塊高度並不一定總是能唯一地識別出一個特定區塊。更確切地說,兩個或者更多數量的區塊也許會為了區塊鏈中的一個位置而競爭。

7.5 創世區塊

區塊鏈裡的第一個區塊建立於2009年,被稱為創世區塊。它是區塊鏈裡面所有區塊的共同祖先,這意味著你從任一區塊,循鏈向後回溯,最終都將到達創世區塊。

因為創世區塊被編入到比特幣客戶端軟體裡,所以每一個節點都始於至少包含一個區塊的區塊鏈,這能確保創世區塊不會被改變。每一個節點都“知道”創世區塊的雜湊值、結構、被建立的時間和裡面的一個交易。因此,每個節點都把該區塊作為區塊鏈的首區塊,從而構建了一個安全的、可信的區塊鏈的根。

chainparams.cpp裡可以看到創世區塊被編入到比特幣核心客戶端裡。

創世區塊的雜湊值為:

0000000000 
19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

你可以在任何區塊瀏覽網站搜尋這個區塊雜湊值,如blockchain.info,你會發現一個用包含這個雜湊值的連結來描述這一區塊內容的頁面:

https://blockchain.info/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

https://blockexplorer.com/block/ 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

在命令列使用比特幣核心客戶端:

$ bitcoind getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
{
 "hash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
 "confirmations":308321,
 "size":285,
 "height":0,
 "version":1,
 "merkleroot":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
 "tx":["4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"],
 "time":1231006505,
 "nonce":2083236893,
 "bits":"1d00ffff",
 "difficulty":1.00000000,
 "nextblockhash":"00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
}

創世區塊包含一個隱藏的資訊。在其Coinbase交易的輸入中包含這樣一句話“The Times 03/Jan/2009 Chancellor on brink of second bailout forbanks.”這句話是泰晤士報當天的頭版文章標題,引用這句話,既是對該區塊產生時間的說明,也可視為半開玩笑地提醒人們一個獨立的貨幣制度的重要性,同時告訴人們隨著比特幣的發展,一場前所未有的世界性貨幣革命將要發生。該訊息是由比特幣的創立者中本聰嵌入創世區塊中。

7.6 區塊的連線

比特幣的完整節點儲存了區塊鏈從創世區塊起的一個本地副本。隨著新的區塊的產生,該區塊鏈的本地副本會不斷地更新用於擴充套件這個鏈條。當一個節點從網路接收傳入的區塊時,它會驗證這些區塊,然後連結到現有的區塊鏈上。為建立一個連線,一個節點將檢查傳入的區塊頭並尋找該區塊的“父區塊雜湊值”。

讓我們假設,例如,一個節點在區塊鏈的本地副本中有277,314個區塊。該節點知道最後一個區塊為第277,314個區塊,這個區塊的區塊頭雜湊值為: 00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249。

然後該比特幣節點從網路上接收到一個新的區塊,該區塊描述如下:

{
 "size":43560,
 "version":2,

 "previousblockhash":"00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249",
 "merkleroot":"5e049f4030e0ab2debb92378f53c0a6e09548aea083f3ab25e1d94ea1155e29d",
 "time":1388185038,
 "difficulty":1180923195.25802612,
 "nonce":4215469401,
 "tx":["257e7497fb8bc68421eb2c7b699dbab234831600e7352f0d9e6522c7cf3f6c77",
  #[...many more transactions omitted...]
  "05cfd38f6ae6aa83674cc99e4d75a1458c165b7ab84725eda41d018a09176634"
 ]
}

對於這一新的區塊,節點會在“父區塊雜湊值”欄位裡找出包含它的父區塊的雜湊值。這是節點已知的雜湊值,也就是第277314塊區塊的雜湊值。故這個區塊是這個鏈條裡的最後一個區塊的子區塊,因此現有的區塊鏈得以擴充套件。節點將新的區塊新增至鏈條的尾端,使區塊鏈變長到一個新的高度277,315。圖7-1顯示了通過“父區塊雜湊值”欄位進行連線三個區塊的鏈。


圖7-1 區塊通過引用父區塊的區塊頭雜湊值的方式,以鏈條的形式進行相連

7.7 Merkle 樹

區塊鏈中的每個區塊都包含了產生於該區塊的所有交易,且以Merkle樹表示。

Merkle樹是一種雜湊二叉樹,它是一種用作快速歸納和校驗大規模資料完整性的資料結構。這種二叉樹包含加密雜湊值。術語“樹”在計算機學科中常被用來描述一種具有分支的資料結構,但是樹常常被倒置顯示,“根”在圖的上部同時“葉子”在圖的下部,你會在後續章節中看到相應的例子。

在比特幣網路中,Merkle樹被用來歸納一個區塊中的所有交易,同時生成整個交易集合的數字指紋,且提供了一種校驗區塊是否存在某交易的高效途徑。生成一棵完整的Merkle樹需要遞迴地對雜湊節點對進行雜湊,並將新生成的雜湊節點插入到Merkle樹中,直到只剩一個雜湊節點,該節點就是Merkle樹的根。在比特幣的Merkle樹中兩次使用到了SHA256演算法,因此其加密雜湊演算法也被稱為double-SHA256。

當N個資料元素經過加密後插入Merkle樹時,你至多計算2*log2(N)次就能檢查出任意某資料元素是否在該樹中,這使得該資料結構非常高效。

Merkle樹是自底向上構建的。在如下的例子中,我們從A、B、C、D四個構成Merkle樹樹葉的交易開始,如圖7-2。起始時所有的交易都還未儲存在Merkle樹中,而是先將資料雜湊化,然後將雜湊值儲存至相應的葉子節點。這些葉子節點分別是HA、HB、HC和HD:

H~A~ = SHA256(SHA256(交易A))

通過串聯相鄰葉子節點的雜湊值然後雜湊之,這對葉子節點隨後被歸納為父節點。 例如,為了建立父節點HAB,子節點A和子節點B的兩個32位元組的雜湊值將被串聯成64位元組的字串。隨後將字串進行兩次雜湊來產生父節點的雜湊值:

H~AB~=SHA256(SHA256(H~A~ + H~B~))

繼續類似的操作直到只剩下頂部的一個節點,即Merkle根。產生的32位元組雜湊值儲存在區塊頭,同時歸納了四個交易的所有資料。


圖7-2 在Merkle樹中計算節點

因為Merkle樹是二叉樹,所以它需要偶數個葉子節點。如果僅有奇數個交易需要歸納,那最後的交易就會被複制一份以構成偶數個葉子節點,這種偶數個葉子節點的樹也被稱為平衡樹。如圖7-3所示,C節點被複制了一份。


圖7-3 複製一份資料節點,使整個樹中資料節點個數是偶數

由四個交易構造Merkle樹的方法同樣適用於從任意交易數量構造Merkle樹。在比特幣中,在單個區塊中有成百上千的交易是非常普遍的,這些交易都會採用同樣的方法歸納起來,產生一個僅僅32位元組的資料作為Merkle根。在圖7-4中,你會看見一個從16個交易形成的樹。需要注意的是,儘管圖中的根看起來比所有葉子節點都大,但實際上它們都是32位元組的相同大小。無論區塊中有一個交易或者有十萬個交易,Merkle根總會把所有交易歸納為32位元組。


圖7-4 一顆囊括了許多資料元素的Merkle樹

為了證明區塊中存在某個特定的交易,一個節點只需要計算log2(N)個32位元組的雜湊值,形成一條從特定交易到樹根的認證路徑或者Merkle路徑即可。隨著交易數量的急劇增加,這樣的計算量就顯得異常重要,因為相對於交易數量的增長,以基底為2的交易數量的對數的增長會緩慢許多。這使得比特幣節點能夠高效地產生一條10或者12個雜湊值(320-384位元組)的路徑,來證明了在一個巨量位元組大小的區塊中上千交易中的某筆交易的存在。

在圖7-5中,一個節點能夠通過生成一條僅有4個32位元組雜湊值長度(總128位元組)的Merkle路徑,來證明區塊中存在一筆交易K。該路徑有4個雜湊值(在圖7-5中由藍色標註)HL、HIJ、HMNOP和HABCDEFGH。由這4個雜湊值產生的認證路徑,再通過計算另外四對雜湊值HKL、HIJKL、HIJKLMNOP和Merkle樹根(在圖中由虛線標註),任何節點都能證明HK(在圖中由綠色標註)包含在Merkle根中。


圖7-5 一條為了證明樹中包含某個資料元素而使用的Merkle路徑

例7-1中的程式碼借用libbitcoin庫中的一些輔助程式,演示了從葉子節點雜湊至根建立整個Merkle樹的過程。

例7-1 構造Merkle樹

#include <bitcoin/bitcoin.hpp>  
bc::hash_digest create_merkle(bc::hash_digest_list& merkle)
{
// Stop if hash list is empty.
 if (merkle.empty())
  return bc::null_hash;
 else if (merkle.size() == 1)
  return merkle[0];
  // While there is more than 1 hash in the list, keep looping…
 while (merkle.size() > 1) 
 {
  // If number of hashes is odd, duplicate last hash in the list.
  if (merkle.size() % 2 != 0)
   merkle.push_back(merkle.back());
  // List size is now even.
  assert(merkle.size() % 2 == 0);

  // New hash list.
  bc::hash_digest_list new_merkle;
  // Loop through hashes 2 at a time.
  for (auto it = merkle.begin(); it != merkle.end(); it += 2) 
  {
   // Join both current hashes together (concatenate).
   bc::data_chunk concat_data(bc::hash_size * 2);
   auto concat = bc::make_serializer(concat_data.begin());
   concat.write_hash(*it);
   concat.write_hash(*(it + 1));
   assert(concat.iterator() == concat_data.end());
   // Hash both of the hashes.
   bc::hash_digest new_root = bc::bitcoin_hash(concat_data); 
   // Add this to the new list.
   new_merkle.push_back(new_root);   
  }
  // This is the new list.
  merkle = new_merkle;

  // DEBUG output -------------------------------------
  std::cout << "Current merkle hash list:" << std::endl;
  for (const auto& hash: merkle)
   std::cout << " " << bc::encode_hex(hash) << std::endl;
  std::cout << std::endl;
  // --------------------------------------------------

 }
 // Finally we end up with a single item.
 return merkle[0]; 
}

int main()
{
 // Replace these hashes with ones from a block to reproduce the same merkle root.
 bc::hash_digest_list tx_hashes{
  {
   bc::decode_hash("0000000000000000000000000000000000000000000000000000000000000000"),
   bc::decode_hash("0000000000000000000000000000000000000000000000000000000000000011"),
   bc::decode_hash("0000000000000000000000000000000000000000000000000000000000000022"),
  }
 };
 const bc::hash_digest merkle_root = create_merkle(tx_hashes);
 std::cout << "Result: " << bc::encode_hex(merkle_root) << std::endl;
 return 0;
}

例7-2展示了編譯以及執行上述程式碼後的結果。

例7-2 編譯以及執行構造Merkle樹程式碼

$ # Compile the merkle.cpp code
$ g++ -o merkle merkle.cpp $(pkg-config --cflags --libs libbitcoin) 
$ # Run the merkle executable
$ ./merkle
Current merkle hash list:
32650049a0418e4380db0af81788635d8b65424d397170b8499cdc28c4d27006
30861db96905c8dc8b99398ca1cd5bd5b84ac3264a4e1b3e65afa1bcee7540c4

Current merkle hash list:
 d47780c084bad3830bcdaf6eace035e4c6cbf646d103795d22104fb105014ba3

Result: d47780c084bad3830bcdaf6eace035e4c6cbf646d103795d22104fb105014ba3

Merkle樹的高效隨著交易規模的增加而變得異常明顯。表7-3展示了為了證明區塊中存在某交易而所需轉化為Merkle路徑的資料量。

表7-3 Merkle樹的效率

交易數量區塊的近似大小路徑大小(雜湊數量)路徑大小(位元組)
16筆交易4KB4個雜湊128位元組
512筆交易128KB9個雜湊288位元組
2048筆交易512KB11個雜湊352位元組
65,535筆交易16MB16個雜湊512位元組

依表可得,當區塊大小由16筆交易(4KB)急劇增加至65,535筆交易(16MB)時,為證明交易存在的Merkle路徑長度增長極其緩慢,僅僅從128位元組到512位元組。有了Merkle樹,一個節點能夠僅下載區塊頭(80位元組/區塊),然後通過從一個滿節點回溯一條小的Merkle路徑就能認證一筆交易的存在,而不需要儲存或者傳輸大量區塊鏈中大多數內容,這些內容可能有幾個G的大小。這種不需要維護一條完整的區塊鏈的節點,又被稱作簡單支付驗證(SPV)節點,它不需要下載整個區塊而通過Merkle路徑去驗證交易的存在。

7.8 Merkle樹和簡單支付驗證(SPV)

Merkle樹被SPV節點廣泛使用。SPV節點不儲存所有交易也不會下載整個區塊,僅僅儲存區塊頭。它們使用認證路徑或者Merkle路徑來驗證交易存在於區塊中,而不必下載區塊中所有交易。

例如,一個SPV節點欲知它錢包中某個比特幣地址即將到達的支付,該節點會在節點間的通訊連結上建立起bloom過濾器,限制只接受含有目標比特幣地址的交易。當節點探測到某交易符合bloom過濾器,它將以Merkleblock訊息的形式傳送該區塊。Merkleblock訊息包含區塊頭和一條連線目標交易與Merkle根的Merkle路徑。SPV節點能夠使用該路徑找到與該交易相關的區塊,進而驗證對應區塊中該交易的有無。SPV節點同時也使用區塊頭去關聯區塊和區塊鏈中的區域區塊。這兩種關聯,交易與區塊、區塊和區塊鏈,證明交易存在於區塊鏈。簡而言之,SPV節點會收到少於1KB的有關區塊頭和Merkle路徑的資料,其資料量比一個完整的區塊(目前大約有1MB)少了一千倍有餘。

相關文章