Rust 實現一個簡單的區塊鏈


一、背景

近期用 Rust 實現了 Jeiwan/blockchain_go,與原項目相比沒有加入新的功能,只是換了一個編程語言實現了一遍,源碼放在 Github 上。

開發這個項目,花費了好幾個周末,比較低效,需要反思。中途差點爛尾,被情緒影響,不知道做這件事的意義在哪里,有什么收益,還好堅持了下來。我很佩服原項目的作者,能夠持之以恆將一個項目做得那么好,還有完整的文檔講解。循序漸進,代碼配合文檔,非常清晰易懂。換句話說,作者為了寫一篇技術科普文章介紹區塊鏈,捎帶用代碼寫了一個演示案例 ————————— 代碼是文檔的注釋

過去一年里,在學習攝影過程中,了解到美術的一個概念:

  • 臨摹:將別人的作品惟妙惟肖地畫出來,這祌方法就是臨摹。
  • 寫生:直接面對實物進行描繪就是寫生。
  • 創作:是對現實生活通過觀察、體驗、研究、分析、選擇、加工和提煉后,塑造藝術形象的創造性勞動。

(圖:https://www.laihuihua.com/K/article-1481.html

臨摹是學習、研究和掌握前人繪畫經驗的一個橋梁,可以使畫者獲得一定的繪畫技巧,為寫生奠定基礎。寫生是不斷提高畫者對客觀事物的感受能力以及繪畫技藝的唯嚙徑,為創作積累生動的素材,是創作的必經之路。創作則集中體現了畫家的繪畫功底和綜合藝術修養。臨摹、寫生和創作三者相互依存,互相促進,缺一不可。

我覺得這個概念同樣適合於技術領域,甚至任何行業,以技術領域舉例:

  • 臨摹:學習框架、開源項目的源碼,臨摹其中的優秀設計,將其用於實際項目。
  • 寫生:發現一個需求,根據過往的經驗,輸出技術方案,完成程序開發工作。
  • 創作:推動技術創新,改變世界,比如:Google 大數據的三大論文。

好啦,前面啰嗦了那么多,究竟想干啥。其實就是臨摹這個項目,同時提高學習內容留存率:

  • 通過做實踐項目,提高 Rust 編程能力。
  • 學習區塊鏈基本概念,理解 BTC 內部實現原理。

二、區塊鏈概念

注:本文不是區塊鏈科普文章,想看完整的介紹可以看 原項目。(中文翻譯

下面簡要摘錄下里面使用的名詞術語:

  • 區塊

    在區塊鏈中,真正存儲有效信息的是區塊(block)。而在比特幣中,真正有價值的信息就是交易(transaction)。實際上,交易信息是所有加密貨幣的價值所在。除此以外,區塊還包含了一些技術實現的相關信息,比如版本,當前時間戳和前一個區塊的哈希。

  • 區塊鏈

    本質上,區塊鏈就是一個有着特定結構的數據庫,是一個有序,每一個塊都連接到前一個塊的鏈表。也就是說,區塊按照插入的順序進行存儲,每個塊都與前一個塊相連。

  • 工作量證明

    區塊鏈的一個關鍵點就是,一個人必須經過一系列困難的工作,才能將數據放入到區塊鏈中。正是由於這種困難的工作,才保證了區塊鏈的安全和一致。此外,完成這個工作的人,也會獲得相應獎勵(這也就是通過挖礦獲得幣)。
    這個機制與生活現象非常類似:一個人必須通過努力工作,才能夠獲得回報或者獎勵,用以支撐他們的生活。在區塊鏈中,是通過網絡中的參與者(礦工)不斷的工作來支撐起了整個網絡。礦工不斷地向區塊鏈中加入新塊,然后獲得相應的獎勵。在這種機制的作用下,新生成的區塊能夠被安全地加入到區塊鏈中,它維護了整個區塊鏈數據庫的穩定性。值得注意的是,完成了這個工作的人必須要證明這一點,即他必須要證明他的確完成了這些工作。

    整個 “努力工作並進行證明” 的機制,就叫做工作量證明(proof-of-work)。要想完成工作非常地不容易,因為這需要大量的計算能力:即便是高性能計算機,也無法在短時間內快速完成。另外,這個工作的困難度會隨着時間不斷增長,以保持每 10 分鍾出 1 個新塊的速度。在比特幣中,這個工作就是找到一個塊的哈希,同時這個哈希滿足了一些必要條件。這個哈希,也就充當了證明的角色。因此,尋求證明(尋找有效哈希),就是礦工實際要做的事情。

  • 交易

    交易(transaction)是比特幣的核心所在,而區塊鏈唯一的目的,也正是為了能夠安全可靠地存儲交易。在區塊鏈中,交易一旦被創建,就沒有任何人能夠再去修改或是刪除它。

    由於比特幣采用的是 UTXO 模型,並非賬戶模型,並不直接存在“余額”這個概念,余額需要通過遍歷整個交易歷史得來。一筆交易由一些輸入(input)和輸出(output)組合而來。

  • 獎勵

    挖礦獎勵,實際上就是一筆 coinbase 交易。當一個挖礦節點開始挖出一個新塊時,它會將交易從隊列中取出,並在前面附加一筆coinbase交易。coinbase 交易只有一個輸出,里面包含了礦工的公鑰哈希。

  • UTXO 集

    Bitcoin Core 存儲設計,是將區塊存儲在 blocks 數據庫,將交易輸出存儲在 chainstate 數據庫。其中,chainstate 也就是未花費交易輸出的集合,可以理解為 blocks 數據庫的未花費交易輸出的索引,稱之為 UTXO 集。

  • P2PKH

    在比特幣中有一個 腳本(Script) 編程語言,它用於鎖定交易輸出;交易輸入提供了解鎖輸出的數據。它所做的事情就是向一個公鑰哈希支付,也就是說,用某一個公鑰鎖定一些幣。這是比特幣支付的核心:沒有賬戶,沒有資金轉移;只有一個腳本檢查提供的簽名和公鑰是否正確。

    有了一個這樣的腳本語言,實際上也可以讓比特幣成為一個智能合約平台:除了將一個單一的公鑰轉移資金,這個語言還使得一些其他的支付方案成為可能。

  • 錢包地址

    比特幣地址是完全公開的,如果你想要給某個人發送幣,只需要知道他的地址就可以了。但是,地址並不是用來證明你是一個“錢包”所有者的信物。實際上,所謂的地址,只不過是將公鑰表示成人類可讀的形式而已,因為原生的公鑰人類很難閱讀。在比特幣中,你的身份(identity)就是一對(或者多對)保存在你的電腦(或者你能夠獲取到的地方)上的公鑰(public key)和私鑰(private key)。比特幣基於一些加密算法的組合來創建這些密鑰,並且保證了在這個世界上沒有其他人能夠取走你的幣,除非拿到你的密鑰。

  • 數字簽名

    在數學和密碼學中,有一個數字簽名(digital signature)的概念,算法可以保證:

    1. 當數據從發送方傳送到接收方時,數據不會被修改;
    2. 數據由某一確定的發送方創建;
    3. 發送方無法否認發送過數據這一事實。

    通過在數據上應用簽名算法(也就是對數據進行簽名),你就可以得到一個簽名,這個簽名晚些時候會被驗證。生成數字簽名需要一個私鑰,而驗證簽名需要一個公鑰。簽名有點類似於印章,比方說我做了一幅畫,完了用印章一蓋,就說明了這幅畫是我的作品。給數據生成簽名,就是給數據蓋了章。

  • 區塊鏈網絡

    區塊鏈特性可以認為是規則(rule),區塊鏈網絡就是一個程序社區,里面的每個程序都遵循同樣的規則,正是由於遵循着同一個規則,才使得網絡能夠長存。

    區塊鏈網絡是去中心化的,這意味着沒有服務器,客戶端也不需要依賴服務器來獲取或處理數據。在區塊鏈網絡中,有的是節點,每個節點是網絡的一個完全(full-fledged)成員。節點就是一切:它既是一個客戶端,也是一個服務器。這一點需要牢記於心,因為這與傳統的網頁應用非常不同。

    區塊鏈網絡是一個 P2P(Peer-to-Peer,端到端)的網絡,即節點直接連接到其他節點。它的拓撲是扁平的,因為在節點的世界中沒有層級之分。

  • 節點角色

    盡管節點具有完備成熟的屬性,但是它們也可以在網絡中扮演不同角色。比如:

    1. 礦工 這樣的節點運行於強大或專用的硬件(比如 ASIC)之上,它們唯一的目標是,盡可能快地挖出新塊。礦工是區塊鏈中唯一可能會用到工作量證明的角色,因為挖礦實際上意味着解決 PoW 難題。在權益證明 PoS 的區塊鏈中,沒有挖礦。
    2. 全節點 這些節點驗證礦工挖出來的塊的有效性,並對交易進行確認。為此,他們必須擁有區塊鏈的完整拷貝。同時,全節點執行路由操作,幫助其他節點發現彼此。對於網絡來說,非常重要的一段就是要有足夠多的全節點。因為正是這些節點執行了決策功能:他們決定了一個塊或一筆交易的有效性。
    3. SPV SPV 表示 Simplified Payment Verification,簡單支付驗證。這些節點並不存儲整個區塊鏈副本,但是仍然能夠對交易進行驗證(不過不是驗證全部交易,而是一個交易子集,比如,發送到某個指定地址的交易)。一個 SPV 節點依賴一個全節點來獲取數據,可能有多個 SPV 節點連接到一個全節點。SPV 使得錢包應用成為可能:一個人不需要下載整個區塊鏈,但是仍能夠驗證他的交易。

三、節點網絡事件

注:下面是一個簡化的網絡模型,用於模擬多節點網絡。筆者沒有研究過 BTC 的P2P網絡,不了解真實的P2P網絡通訊過程。

在簡化后的網絡模型中,節點之間采用異步消息通信機制。將每個消息稱為網絡事件,梳理的事件機制如下:

四、項目介紹

源碼地址:https://github.com/ZuoFuhong/blockchain_rust

前面提到,Rust 實現的版本,僅僅是編程語言不同於原項目,其它的邏輯均是一致的。如果您閱讀過源碼,還是會發現一丟丟的不同。

  • 單元測試:Rust 編碼有個很爽的點,就是可以在同一個源碼文件中寫單元測試,運行單元測試。不用像 Golang 那樣需要在不同文件中切換。所以可以看到,項目源碼中有着非常多的單元測試。
  • 命令行程序:由於語言上的差異,命令行的實現差異較大,可以看到 Rust 的版本使用因語言特性,使用屬性宏整體更加簡潔。
  • 區塊鏈 db / 錢包文件:區塊數據和錢包數據會持久化到磁盤上,相比原項目,Rust 的版本沒有給文件打上端口號,減少源碼閱讀干擾。

五、開始游戲

我很樂於將 BTC 稱之為游戲,每個節點有自己的身份角色,也可以切換角色。通過網絡,大家遵守着同樣的游戲規則。玩家穿行於虛擬和現實,在現實世界中賺取游戲中的虛擬貨幣,用游戲中的虛擬貨幣進行現實消費。Wow! That's totally awesome!

好啦,現在開始一場游戲吧 !

1.在同一台機器上,使用三個不同的端口,模擬三個節點:

其中:

  • node1: 中心節點
  • node2: 錢包節點
  • node3: 礦工節點

2.在 node1 中心節點下創建一個錢包和一個新的區塊鏈

生成一個僅包含創世塊的區塊鏈,並在其他節點使用。創世塊承擔了一條鏈標識符的角色(在 Bitcoin Core 中,創世塊是硬編碼的)

其中:

  • 錢包地址:1QAjnwyGZL3woxUvzhdVePyjThtxQYpogL
  • 創世塊 hash:00e2b7601305ffe6ac39a7272324d42d3de2cf6f68129c9f555127c8ccb94b7f

3.接下來,在 node2 錢包節點生成一些錢包地址,我們稱這些地址叫做:

  • WALLET_11DB3GnPvCXEeLyzL9FxZHNzUz6C2eQFxvN
  • WALLET_219g2ZZBiVCafgTQjUrEmJnwGgoc16nk1Yi
  • WALLET_314A4DXBji2pnZRmXL97dVGVDe2ckuVcGGh

4.在 node1 中心節點,向錢包地址發一些幣:

其中:

# 創世塊礦工地址,向 WALLET_1 發 10 個幣
$ blockchain_rust send ${CENTREAL_NODE} ${WALLET_1} 10 1
# 創世塊礦工地址,向 WALLET_2 發 10 個幣
$ blockchain_rust send ${CENTREAL_NODE} ${WALLET_2} 10 1

5.在 node1 中心節點,運行節點

$ blockchain_rust startnode

6.在 node2 錢包節點,運行節點

$ blockchain_rust startnode

它會從中心節點(node1)下載所有區塊。為了檢查一切正常,暫停節點運行並檢查余額:

$ blockchain_rust getbalance ${WALLET_1}
Balance of 'WALLET_1': 10

$ blockchain_rust getbalance ${WALLET_2}
Balance of 'WALLET_2': 10

7.在 node3 礦工節點中生成一個錢包地址。初始化區塊鏈:

# 將錢包地址,作為礦工錢包
$ blockchain_rust startnode ${MINER_WALLET}

其中:

  • 錢包地址:1EwpTmQ941b1B7VMxzbVXvc8CrXR89dNXM

8.在 node2 錢包節點,發送一些幣:

# WALLET_1 地址,向 WALLET_3 發 2 個幣
$ blockchain_rust send ${WALLET_1} ${WALLET_3} 2 0
# WALLET_2 地址,向 WALLET_3 發 3 個幣
$ blockchain_rust send ${WALLET_2} ${WALLET_3} 3 0

迅速切換到礦工節點(node3),你會看到挖出了一個新塊!同時,檢查中心節點(node1) 的輸出。

9.切換到 node2 錢包節點並啟動

它會下載最近挖出來的塊!

暫停節點並檢查余額:

Done !


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM