09-BTC-分叉

聲明:本文是要點筆記,介紹和系列筆記均收錄在專題:區塊鏈技術與應用

區塊鏈由一條鏈變爲兩條鏈就叫分叉。分叉可能是多種原因造成的,比如挖礦的時候,兩個節點差不多同一個時候挖到了礦,就會出現一個臨時性的分叉,我們把這個分叉叫作狀態分叉(state fork),是由於對比特幣區塊鏈當前的狀態有意見分歧而導致的分叉。

前面還講過分叉攻擊(forking attack),它也屬於 state fork,也是屬於對比特幣這個區塊鏈當前的狀態產生的意見分歧,只不過這個意見分歧是故意造成的,人爲造成的,所以我們又叫它 deliberate fork。

除了這種 state fork 之外,還有一種產生分叉的情況是,比特幣的協議發生了改變,要修改比特幣系統需要軟件升級。在一個去中心化的系統裏,升級軟件的時候沒有辦法保證所有的節點同時都升級軟件。

假設大部分節點升級了軟件,少數節點因爲種種原因可能沒有升級,有可能是還沒來得及升級,也可能是不同意對這個協議的修改。如你想把協議改成某個樣子,社區中可能是有人不支持的,這個時候也會出現分叉,這種分叉叫協議分叉(protocol fork)。因爲對比特幣協議產生了分歧,用不同版本的協議造成的分叉,我們稱作 protocol fork。

根據對協議修改的內容的不同,我們又可以進一步分成硬分叉軟分叉。出現硬分叉的情況:如果對比特幣協議增加一些新的特性,擴展一些新的功能,這些時候那些沒有升級軟件的這些舊的節點,它是不認可這些新特性的,認爲這些特性是非法的,這就屬於對比特幣協議內容產生了意見分歧,所以會導致分叉。

硬分叉

硬分叉的一個例子就是比特幣中的區塊大小限制(block size limit)。比特幣系統規定每個區塊最多是 1M 字節,有些人認爲 1M 的限制太小了,也增加了交易的延遲。可以計算一下:1M=1百萬個字節,一個交易大概認爲是 250 個字節,1百萬/250=4000, 一個區塊大概是 4000 個交易。平均 10 分鐘出現一個區塊 4000/(60×10)=7 大概每秒鐘產生 7 筆交易,即 7tx/sec。 這個傳輸速度是非常低的。

有人發佈一個軟件更新,把 block size limit 從 1M 增加到 4M。假設大多數節點更新這個軟件,把 block size limit 更新到 4M,少數節點沒有更新。這裏的大多數節點和少數節點不是按照賬戶數目來算的,而是按照算力,即系統中擁有大多數哈希算力的節點都更新了軟件。新節點認爲區塊大小限制是 4M,舊節點認爲是 1M。

上圖是硬分叉的示例圖,這時運行系統,會有什麼結果?假如一個新節點挖出一個區塊,這個區塊比較大,但舊節點不認可,它忽略大區塊的存在會繼續沿着它的前一個小區塊接着挖。而舊節點如果挖出了區塊新節點是認可的,因爲 4M 的限制,指不能超過 4M,比 4M 小是可以的。

那爲什麼會產生分叉呢?大區塊挖出之後,因爲大多數區塊是更新了的,是認可新的大區塊的,所以會沿着它繼續挖。只有少數舊節點會接着下面鏈往下挖,這時新節點認爲上下兩條鏈都是合法的,但上面那條是最長合法鏈,所以會沿着上面一條挖。而且算力足夠大會使上面那條鏈越來越長。而舊節點認爲上面的鏈無論多長都是非法的,它們只會沿着下面的鏈挖。當然上面的鏈也可能出現小區塊,因爲新節點也可能挖出大小不到 1M 的區塊,雖然這種是新舊節點都認可的,但這是沒有用的,因爲這條鏈上它們認爲有非法的區塊。所以這種分叉是永久性的,只要舊節點不更新軟件,分叉就不會消失,所以才叫它硬結點。

比特幣社區當中有些人是比較保守的,提高 block size limit 有些人就是不同意。而且區塊的大小也不是越大越好,比特幣底層系統是個 P2P overlay network,它的傳播主要採用 flooding 的方式,所以對帶寬的消耗是很大的,帶寬是瓶頸。

那麼舊節點挖出的小的區塊還有沒有出塊獎勵呢?出現 hard fork 後出現了兩條平行運行的鏈,平行運行鏈彼此之間有各自的加密貨幣。下面鏈的出塊獎勵在下面鏈裏是認的。而分叉之前的幣按道理應該是上下兩條鏈都認可,所以會拆成兩部分。

曾經出現過這樣的問題:分叉前有 A→B 的交易,分叉後在上面鏈出現了 B→C,下面鏈也出現了 B→C,因爲賬戶,私鑰都是一樣的。既然如此,就會有人利用這個特性,想收到上下兩條鏈的轉賬。但如果沒有人轉賬給他怎麼辦?

可以這樣做:比如說 B 去購物,花一筆錢,給了 C。後來 B 要退貨,要取消這筆交易,C 又把錢交給 B。然後 B 又在下面一條鏈進行回放,就賺了一筆錢。那麼在開始 B 轉給 C 的交易在下面鏈會不會回放呢?所以這樣做也是有風險的。爲了解決這個問題,就讓這兩條鏈各帶一個 chain ID,所以現在以太坊的分叉已經沒有問題了,就是兩條獨立運行的鏈了。

軟分叉

軟分叉出現的情況是什麼?如果對比特幣協議加一些限制,加入限制之後原來合法的交易或區塊在新的協議當中有可能變的不是合法了,這就引起軟分叉。

假設有人發佈一個軟件更新,把這個區塊大小變小了。調整區塊大小不止是改變一個參數那麼簡單。一個去中心化的系統,改變一個參數,就可能導致分叉,而且取決於這個參數是怎麼改的。有可能是硬分叉,有可能是軟分叉。這裏把區塊大小變小,只是爲了解釋軟分叉這個概念,實際中是不會這麼做的。

假設新節點把區塊大小改爲 0.5M,舊節點依然以 1M 爲準,這時候會出現什麼情況?假如一個區塊鏈開始分叉,新節點挖出小區塊,這種區塊舊節點也是認的。而舊節點挖出的大區塊,新節點是不認的。這樣下去,舊節點看到上面鏈更長,而且是合法的之後,就會轉去挖上面鏈。

所以,爲什麼稱這種分叉是軟分叉?因爲這種分叉是臨時性的。所以舊節點如果不更新軟件,它們挖的區塊可能就白挖了。舊節點轉向上面鏈挖的話,問題可能會出現:它們可能又挖出了大區塊。而新節點不認這個,新節點會繼續沿着大區塊前面一個小區塊挖,如上圖所示。

實際中可能出現軟分叉的情況:給某些目前協議中沒有規定的域增加一些新的含義,賦予它們一些新的規則,典型的例子就是 coinbase 域。前面講過每一個發佈的區塊裏可以有一個鑄幣交易(coinbase transaction),coinbase transaction 裏有一個域叫 coinbase 域,這個域用來幹什麼是沒人規定,也沒人檢查的。

前面講過 coinbase 域的一個用途:可以把它作爲 extra nonce。挖礦的時候要不斷調整 block header 裏的 nonce,但 block header 裏的 nonce 只有四個字節,最多隻有 2 的 32 次方個可能性,所以實際中可以把 coinbase 前八個字節用來做 extra nonce。兩個合在一起就成了 2 的 96 次方,對於目前的挖礦難度,這個域已經是足夠了。但 coinbase 域不止是八個字節,後面還有很多,剩下的字節有人就提議做 UTXO 集合的根哈希值。

目前這個集合只是每個全節點自己在內存中維護的,主要是爲了快速查找、判斷該交易是不是屬於雙花(double spending),但這個集合的內容並沒有寫到區塊鏈裏,這跟前面講到的 merkle proof 是不太一樣的。

merkle proof 能證明什麼?證明某個交易是不是在給定的區塊裏。比如一個輕節點,沒有維護整個區塊的內容,只知道 block header。輕節點問一個全節點:該交易是不是在這個區塊裏?全節點返回一個 merkle proof 作爲證明,輕節點就可以驗證是否屬實。但如果是另外一種情況,想要證明某個賬戶上有多少錢,這個目前在比特幣系統中是證不出來的。如果是全節點,還可以算一下,方法如下:想要知道 A 賬戶有多少錢,就看一下 A 在 UTXO 裏對應的輸出總共收到多少個幣,就是該賬戶上有多少錢。

對於全節點是可以算出來的,但如果是區塊鏈錢包、有的手機上的 APP,它不可能在手機上維護一個完整的區塊鏈,它實際上是個輕節點,它想要知道賬戶的餘額需要詢問全節點。全節點返回一個結果,怎麼知道這個結果是否屬實呢?現在是證不出來的。如果你自己不維護一個 UTXO 集合,就沒法用 merkle proof 證出來。

有人提議把 UTXO 集合當中的內容也組織成一顆 merkle tree,這個 merkle tree 有一個根哈希值,根哈希值寫在 coinbase 域裏面。因爲 block header 沒法再改了,改 block header 動靜就太大了,coinbase 域正好是沒人用的,所以就寫入 UTXO 的根哈希值。coinbase 域當中的內容,最終往上傳遞的時候會傳遞到 block header 裏的根哈希值裏。所以改 coinbase 域的內容,根哈希值會跟着改。因此這個提案,就是說把 UTXO 集合的內容組織成 merkle tree,算出一個根哈希值來,寫入 coinbase 域裏某個位置。coinbase 域的內容本身也會算哈希,算到 block header 裏的根哈希值,這樣就可以用 merkle proof 證出來了。

假設有人發佈一個軟件更新,規定 coinbase 域要按照這個要求來填寫,大多數節點都升級了軟件,少數節點沒有更新,這屬於軟分叉,因爲新節點發布的區塊舊節點認爲是合法的,因爲舊節點不管新節點寫什麼內容。但舊節點發布的區塊,新節點可能是不認的,因爲如果 coinbase 域不按要求寫,它是不認的,所以屬於軟分叉。

比特幣歷史上比較著名的軟分叉的例子是 pay to script hash。P2SH 這個功能在最初的比特幣版本里是沒有的,它是後來通過軟分叉的功能給加進去的。這是什麼意思呢?你支付的時候,不是通過給一個 public key 的哈希,而是通過給一個贖回腳本的哈希。花錢的時候要把這個交易的輸入腳本跟前面幣的來源的交易的輸出腳本拼接在一起執行。執行的時候驗證分爲兩步,第一步是要驗證輸入腳本中給出的 redeem script 跟前面那個輸出腳本給出的 script 的哈希值是對得上的,證明輸入腳本里提供的 script 是正確的。第二步再執行 redeem script,來驗證輸入腳本里給出的簽名是合法的。

對於舊節點來說,它不知道 P2SH 的特性,只會做第一階段的驗證,即驗證 redeem script 是否正確。新節點纔會做第二階段的驗證,所以舊節點認爲合法的交易,新節點可能認爲是非法的(如果第二階段的驗證通不過的話)。而新節點認爲合法的交易,舊節點肯定認爲也是合法的,因爲舊節點只驗證第一階段。

總結:soft fork 是什麼?只要系統中擁有半數以上算力的節點更新了軟件,那麼系統就不會出現永久性的分叉,只可能有一些臨時性的分叉。hard fork 特點是什麼?必須是所有的節點都要更新軟件,系統纔不會出現永久性的分叉,如果有小部分節點不願意更新,那麼系統就會分成兩條鏈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章