列旭松,唯品會資深工程師,曾任職於YY語音,熟識PHP、C語言和Go語言。10年PHP開發經驗,對PHP底層實現原理有較深理解。熱衷於開源事業,開源過多個PHP相關的擴充套件,流行的PHP原始碼加密擴充套件(PHP-Beast)作者。另外,本人對分散式快取系統(如Redis、Memcached)有較大的興趣,喜歡鑽研底層實現原理,《 PHP 核心技術與最佳實踐》一書的作者。
什麼是區塊鏈?官方的解釋是:區塊鏈是一個分散式記賬系統,是藉用密碼學串接並保護其內容的串連交易記錄(又稱區塊)。每一個區塊包含了前一個區塊的加密雜湊、對應的時間戳記以及交易資料(通常用默克爾樹演演算法計算的雜湊值表示),這樣的設計使得區塊內容具有難以被竄改的特性。用區塊鏈所串接的分散式賬本能讓兩方有效率地紀錄交易,且此交易可永久被查驗。
但這個解釋對於初學者來說太抽象了,所以接下來我們將會使用PHP來實現一個簡易的區塊鏈來加深對區塊鏈的理解。
大家應該玩過成語接龍,規則是這樣:我先說一個成語“人上人海”,下一個玩家需要使用我說的成語的最後一個字作為下一個成語的開頭,就是說需要使用“海”這個字作為新成語的開頭,這時就可以接一個“海闊天空”。
而區塊鏈的形式有點像成語接龍,就是下一個區塊必須使用上一個區塊的Hash值作為憑據來生成下一個區塊。如下圖:
這樣做的好處是:從任意一個區塊開始都可以透過前一個區塊的Hash值可以不斷的追溯整條區塊鏈,直到創世區塊(也就是區塊鏈的第一個區塊)。如果有人惡意攻擊,也必須更改整條區塊鏈的資料。但是計算Hash值是一個耗時的操作,所以要更改整條區塊鏈的資料基本是不可能達到,這就保證了區塊鏈的安全性。
下麵我們使用PHP程式碼來定義區塊:
class Block {
public $prevHash;
public $hash;
public $timeStamp;
public $data;
}
欄位 | 解釋 |
prevHash |
前一個區塊的Hash值 |
hash | 當前區塊的Hash值 |
timeStamp | 區塊生成的時間戳 |
data | 區塊儲存的資料 |
prevHash、hash和timeStamp這幾個欄位在區塊鏈中被稱為區塊頭,區塊的Hash值使用SHA-256演演算法計算。計算方法如下:
class Block {
...
public function setBlockHash() {
$data = serialize($this);
$this->hash = hash('sha256', $data);
}
}
首先我們使用serialize()函式把整個區塊序列化,然後使用hash()函式計算區塊的Hash值,並賦值給hash欄位。
區塊物件的建構式如下:
class Block {
...
public function __construct($prevHash, $data) {
$this->prevHash = $prevHash;
$this->timeStamp = time();
$this->data = $data;
$this->setBlockHash();
}
}
另外我們提供一個獲取區塊Hash值的方法:
class Block
{
...
public function getBlockHash()
{
return $this->hash;
}
}
前面說了,區塊鏈就是按照一定的規則連線起來的區塊,連線的規則就是下一個區塊的區塊頭中必須包含前一個區塊的Hash值。我們編寫一個區塊鏈物件來儲存整條區塊鏈,程式碼如下:
include('block.php');
class Blockchain
{
public $blocks = [];
}
區塊鏈物件內部使用了一個陣列來儲存所有的區塊,現階段我們還沒有使用到資料庫來儲存區塊鏈,所以現在只需要把區塊鏈儲存在記憶體即可。
向區塊鏈新增一個新的區塊程式碼如下:
include('block.php');
class Blockchain
{
...
public function addBlock($data)
{
$prevBlock = $this->blocks[count($this->blocks)-1];
$this->blocks[] = new Block($prevBlock->getBlockHash(), $data);
}
}
因為生成新區塊必須包含前一個區塊的Hash值,所以在新增新區塊時需要獲取區塊鏈中最後一個區塊作為新區塊的前一個區塊,然後把前一個區塊的Hash包含到新區塊的區塊頭中。
可能聰明的讀者會發現,在區塊鏈剛建立時並沒有任何區塊,那麼新增新區塊時拿哪個區塊作為前一個區塊呢?答案就是創世區塊。創世區塊不用包含前一個區塊的Hash值,而且隨著區塊鏈的建立被建立,程式碼如下:
include('block.php');
class Blockchain
{
...
public function __construct()
{
$this->blocks[] = new Block('', 'Genesis Block');
}
}
創世區塊並不需要包含前一個區塊的Hash值,所以在建立創世區塊時把前一個區塊的Hash值設定為空。
OK!我們的簡易區塊鏈已經完成了,現在來測試一下我們的程式碼吧:
include('blockchain.php');
$bc = new Blockchain();
$bc->addBlock('This is block1');
$bc->addBlock('This is block2');
foreach ($bc->blocks as $block) {
printf("PrevHash: %s\n", $block->prevHash);
printf("Hash: %s\n", $block->hash);
printf("Data: %s\n", $block->data);
printf("\n");
}
我們來執行一下測試程式碼,執行結果如下:
很好,結果符合我們的預期。
本文只是實現了一個最簡易的區塊鏈,離完整的區塊鏈還有非常遠的距離。在我們現在的實現中存在很多不足,如:新增一個區塊的成本很低,沒有實現分散式,不能儲存到本地磁碟(重啟機器資料就會丟失)等,接下來的教程將會不斷完善這些問題。
相關閱讀:
重磅!美圖技術團隊釋出開源 Ethereum DPoS 實現
特別推薦:
比特幣、以太坊、ERC20、PoW、PoS、智慧合約、閃電網路……
想深入瞭解及討論這些話題?高可用架構在知識星球(小密圈)建立了區塊鏈學習小組,共同學習區塊鏈包括數字貨幣前沿技術,歡迎點選連結加入。
高可用架構
改變網際網路的構建方式
長按二維碼 關註「高可用架構」公眾號