1. 程式人生 > >一步步教小白使用C++構建區塊鏈

一步步教小白使用C++構建區塊鏈

        最近你可能聽說過很多關於區塊鏈的訊息,想知道大家都在談論什麼。 區塊鏈是一種分類賬本,它的實現原理使得很難改變它所包含的資料;有人說區塊鏈是不可改變的,它的目的是保持其正確的,不變性意味著永久性;但是硬碟上沒有任何東西 永遠可以被認為是永久性不變的;我們把這些哲學辯論留給非技術人員。而我現在要做的是向您展示如何使用C ++編寫區塊鏈。

免責宣告

  • 本教程是參考Savjee使用NodeJS編寫一篇文章;
  • 有一段時間沒有編寫任何C ++程式碼,程式碼可能會有點生疏。(譯者注:執行該C++程式,可以生成區塊並組成區塊鏈,也具備工作量證明POW功能

首先要做的是開啟一個新的C++專案,我使用JetBrains中的CLion,但任何C++ IDE甚至文字編輯器都可以(譯者注:譯者使用的是VIM)。 我們將建立TestChain專案,繼續下一步建立專案。
如果您使用CLion,您會看到main.cpp檔案已經建立並開啟; 我們暫時不使用該主函式檔案。
在主專案資料夾中建立一個名為Block.h的檔案,您應該可以通過右鍵單擊專案工具視窗中的TestChain目錄並選擇:New>C/C ++ Header File來新建。
在檔案內部,新增下面的程式碼(如果您使用CLion將所有程式碼放在#define TESTCHAIN_BLOCK_H和#endif的行之間)(譯者注:防止標頭檔案被重複呼叫)

  1. #include<cstdint>
  2. #include<iostream>

上面語句的意思是告訴編譯器包含cstdint和iostream庫。在它下面新增這一行:

  1. usingnamespace std;

上面語句是為std名稱空間建立了一個快捷方式。其作用是不需要通過其使用全名引用std名稱空間中的宣告,例如,如果有上面語句則書寫string就可以使用字串,否則需要使用全名訪問字串,如std::string。

到現在為止感覺還挺好; 讓我們進一步完善充實。

區塊鏈由一系列包含資料的區塊組成,每個區塊都包含前一區塊的加密HASH,這就表示很難改變某一個區塊的內容,而後面的區塊內容也同樣發生改變(譯者注:如果改變中間某個區塊的內容,則這個區塊之後的所有區塊的內容都需要被改變才有效

); 因此區塊鏈被賦予了不變的特性。

區塊頭結構

接下來我們建立我們的塊類,將以下幾行新增到Block.h標頭檔案中:

  1. classBlock{
  2. public:
  3. string sPrevHash;
  4. Block(uint32_t nIndexIn,const string &sDataIn);
  5. string GetHash();
  6. voidMineBlock(uint32_t nDifficulty);
  7. private:
  8. uint32_t _nIndex;
  9. int64_t _nNonce;
  10. string _sData;
  11. string _sHash;
  12. time_t _tTime;
  13. string
    _CalculateHash()const;
  14. };

不出所料,第1行 類Block第2行 公有修飾符 public第3行 公有變數sPrevHash,儲存區塊的前一個區塊第5行 建構函式,需要的引數為nIndexIn和sDataInconst和&一起使用,表示引數為引用傳遞,其值不可被修改,其目的是提高效率並且節省記憶體第7行 GetHash()函式,獲得HASH值第9行 MineBlock()函式,挖礦,其引數nDifficulty表示指定的難度值第11行 私有修飾符private,表示後面的變數為私有變數,不能被其他類訪問第12-16行 私有變數

  • _nIndex 區塊索引值,第幾個區塊,從0開始計算,
  • _nNonce 區塊隨機數,
  • _sData 區塊描述字元
  • _sHash 區塊HASH值
  • _tTime 區塊生成時間

第18行 _CalculateHash(),計算HASH值。使用const關鍵字,是為了確保函式的輸出不能被修改,這在處理區塊鏈時非常有用。

現在是在主專案資料夾中建立Blockchain.h標頭檔案的時候了。讓我們開始新增這些行(如果您使用CLion將所有程式碼#define TESTCHAIN_BLOCKCHAIN_H 和 #endif之間)

  1. #include<cstdint>
  2. #include<vector>
  3. #include"Block.h"
  4. usingnamespace std;

上面語句告知編譯器包含cstdint和vector向量庫,以及呼叫我們剛建立的Block.h標頭檔案,並建立std namespace快捷方式。

區塊鏈頭結構

現在讓我們建立區塊鏈類,將以下程式碼行新增到Blockchain.h標頭檔案中:

  1. classBlockchain{
  2. public:
  3. Blockchain();
  4. voidAddBlock(Block bNew);
  5. private:
  6. uint32_t _nDifficulty;
  7. vector<Block> _vChain;
  8. Block_GetLastBlock()const;
  9. };

第1行 建立區塊鏈類第2行 public修飾符第3行 預設建構函式第5行 AddBlock() 增加區塊函式,引數為我們前面建立的Block類的物件bNew第7行 private修飾符,後面的變數和方法是私有的第8-9行 私有變數

  • _nDifficulty難度值
  • _vChain儲存區塊的變數第11行 _GetLastBlock()獲取最新的區塊,由const關鍵字,表示輸出的內容不可更改

因為區塊鏈使用密碼學,所以我們區塊鏈需要一些加密函式。 我們將使用SHA256雜湊來建立塊的雜湊值,(我們可以編寫自己的SHA256函式,但是沒必要重複造輪子),為了讓編碼更加簡單,引用了來自Zedwood的C++ sha256函式,該連結中有sha256.h,sha256.cpp和LICENSE.txt檔案,我們將它們儲存在專案資料夾中。(譯者注:可以使用譯者提供的centos系統可以變編譯執行的程式碼

好的,讓我們繼續

接下來我們的區塊建立一個原始檔Block.cpp並儲存到主專案資料夾中首先新增這些行,告訴編譯器包含我們之前新增的Block.h和sha256.h檔案。

  1. #include"Block.h"
  2. #include"sha256.h"

下面實現區塊建構函式:

  1. Block::Block(uint32_t nIndexIn,const string &sDataIn): _nIndex(nIndexIn), _sData(sDataIn){
  2. _nNonce =-1;
  3. _tTime = time(nullptr);
  4. }

第1行 將引數的內容賦值給變數_nIndex和_sData中。第2行 _nNonce變數設定為-1。第3行 _tTime變數設定為當前時間。

實現訪問區塊的HASH函式:

  1. string Block::GetHash(){
  2. return _sHash;
  3. }

第2行 返回私有變數_sHash的值

您可能已經清楚,比特幣數字貨幣中設計區塊鏈技術非常受歡迎,使得分類賬不能被修改或者保密;這意味著,當一個使用者將比特幣傳送給另一個使用者時,比特幣網路上的節點將轉賬交易寫到區塊鏈中的一個區塊中。另一臺計算機作為一個節點執行比特幣軟體(因為比特幣網路是P2P,所以該節點可以是世界上任何一個人執行);這個過程被稱為“挖礦”,每當他們在區塊鏈上成功建立一個有效的區塊時,該節點將獲得相應的比特幣獎勵。

如果想成功建立一個有效的區塊並因此獲得獎勵,礦工必須建立一個有效的區塊加密HASH新增到區塊鏈中。 有效區塊HASH是通過計算HASH開頭零的數量來實現的;如果零的數量等於或大於網路設定的難度,則表示有效,否則將增加隨機數後重新建立雜湊;直到生成一個有效的雜湊。 這個過程稱為工作量證明(Proof of Work,PoW)。

挖礦過程-工作量證明

接下來,我們實現MineBlock方法來複現上述過程

  1. voidBlock::MineBlock(uint32_t nDifficulty){
  2. char cstr[nDifficulty +1];
  3. for(uint32_t i =0; i < nDifficulty;++i){
  4. cstr[i]='0';
  5. }
  6. cstr[nDifficulty]='\0';
  7. string str(cstr);
  8. do{
  9. _nNonce++;
  10. _sHash =_CalculateHash();
  11. }while(_sHash.substr(0, nDifficulty)!= str);
  12. cout <<"Block mined: "<< _sHash << endl;
  13. }

第1行 MineBlock函式在block.h檔案中宣告,引數nDifficulty為難度第2行 建立一個難度大於nDifficulty的值的字元資料第3-6行 用0填充陣列,最後一個字元填充為終止符\0第8行 將字元陣列轉換為string字串第10-13行 迴圈增加隨機數,並且計算HASH值儲存到_sHash中。迴圈結束的條件是比較生成的HASH值和剛剛建立的零串,一旦匹配則跳出迴圈,否則繼續生成HASH第15行 一旦找到匹配,就會向輸出傳送該區塊的HASH值,表示該塊已成功開採。

區塊HASH計算

我們前面已經看到它好幾次,所以接下來新增_CalculateHash方法:

  1. inline string Block::_CalculateHash()const{
  2. stringstream ss;
  3. ss << _nIndex << _tTime << _sData << _nNonce << sPrevHash;
  4. return sha256(ss.str());
  5. }

第1行 該函式在Block.h標頭檔案中宣告,inline關鍵字修飾,表示編譯器編譯時將該函式程式碼嵌入到其他函式中,減少了函式的呼叫開銷(譯者注:inline通常用在迴圈體內)
第2行 建立一個字串流ss。
第3行 將_nIndex,_tTime,_sData,_nNonce和sPrevHash的值附加到流字串流ss中。
第5行 將字串流ss的sha256值輸出

增加區塊鏈檔案Blockchain.cpp到主專案資料夾中。

下面的程式碼編譯器包含新增的Blockchain.h檔案。

  1. #include"Blockchain.h"

創世塊的產生

接下來,我們實現區塊鏈建構函式

  1. Blockchain::Blockchain(){
  2. _vChain.emplace_back(Block(0,"Genesis Block"));
  3. _nDifficulty =3;
  4. }

第1行 區塊鏈建構函式,生成“創世塊”。
因為將區塊新增到區塊鏈中時,需要新增前一個區塊HASH值,所以區塊鏈必須從某處開始,我們稱最開始的那個區塊為“創世塊”。
第2行 將生成的區塊放在向量_vChain中。
emplace_back()函式,在容器_vChain尾部新增一個元素,這個元素原地構造,不需要觸發拷貝構造和轉移構造。
第3行 設定工作量POW的難度值(即區塊HASH前面有多少個零)

新增區塊

現在,我們實現區塊增加函式

  1. voidBlockchain::AddBlock(Block bNew){
  2. bNew.sPrevHash =_GetLastBlock().GetHash();
  3. bNew.MineBlock(_nDifficulty);
  4. _vChain.push_back(bNew);
  5. }

第1行 該函式是在Blockchain.h檔案中申明,引數為區塊物件第2行 將上一區塊的hash值(_vChain的最後一個區塊HASH值)儲存到sPrevHash中第3行 挖礦,引數為_nDifficulty難度值第4行 將新挖的區塊儲存到_vChain向量的末尾

讓我們實現區塊鏈的最後一個方法

  1. BlockBlockchain::_GetLastBlock()const