基於Java語言構建區塊鏈(二)—— 工做量證實

最終內容請以原文爲準:https://wangwei.one/posts/7890ab7e.htmlhtml

引言

上一篇文章中,咱們實現了區塊鏈最基本的數據結構模型,添加區塊以及和前一個區塊鏈接在一塊兒。可是,咱們的實現方式很是簡單,而真實的比特幣區塊鏈中,每個區塊的添加都是須要通過大量的計算才能夠完成,這個過程就是咱們熟知的挖礦java

工做量證實機制

區塊鏈最關鍵的一個思想就是,必須進行大量且困難的計算工做才能將交易數據存放到區塊鏈上。這種工做機制才能保證整個區塊鏈數據的安全性和一致性。同時,完成這個計算工做的礦工會獲得相應的Token獎勵。git

這套機制和咱們的現實生活很是類似:咱們必須努力工做來賺取報酬用以維持咱們的生活。在區塊鏈中,網絡中的礦工們努力工做來維持區塊鏈網絡,爲其添加區塊,而且得到必定的Token獎勵。做爲他們工做的成果,一個區塊以安全的方式被組合進了區塊鏈中,這樣就保證了整個區塊鏈數據庫的穩定性。還有一個必需要注意的是,某個礦工完成了計算工做的結果,還必須獲得其餘全部礦工的認同(證實是正確的),這樣纔算完成。github

這一整套的計算和證實機制,就稱爲Proof-of-Work(工做量證實)。計算工做是很是很是困難的,由於它須要消耗大量的計算機算力資源,即便是性能很是高的計算機都不能很是快地計算出正確的結果。此外,隨着時間的推移,這項計算工做的難度也會隨之增長,目的是爲了保證每小時6個新區塊的出塊率。在比特幣中,這種工做的目標是找到知足某個特定要求的區塊Hash(哈希值)。這個區塊哈希值就是工做結果的一個證實。所以,計算工做的目的就是爲了尋找到這個證實值。算法

最後要注意的是,計算出這個特定的Hash(哈希值)是很是困難的,可是別人來驗證這個Hash值是否正確的時候,是很是簡單的,一會兒就能完成。shell

Hashing

Hash:哈希 | 散列數據庫

咱們來討論一下Hashing(哈希),對這一塊很是熟悉的朋友能夠直接跳過這一段內容。安全

哈希是一種計算機算法,該算法可以計算出任意大小數據的哈希值,而且這個哈希值的長度是固定的,256bit。這個被計算出來的哈希值可以做爲這個數據的惟一表明。哈希算法有幾個關鍵的特性:bash

  • 不可逆性。不能根據一個哈希值推導出原始數據。因此,哈希不是加密。
  • 惟一性。每一個數據有且僅有一個惟一的哈希值。
  • 迥異性。原始數據一丁點的變化都將獲得徹底不同的哈希值。

例如:網絡

SHA256("wangwei1") ——> 1e898b7c9adaad86c20139a302ccd5277f81040cab68dc2aecfc684773532652
SHA256("wangwei2") ——> c9cc7417c17318c8aab448cc8ace24c53b6dcf350f5c5fd8e91cbc3b011a179d
複製代碼

哈希算法被普遍用於驗證文件的一致性上。好比軟件提供商一般會在安裝包上附加一個檢驗碼(checksums),當咱們下載完一個軟件安裝包後,能夠用哈希函數計算一下這個軟件安裝包的哈希值,而後再和軟件安裝包的檢驗碼作個對比,就能夠知道下載的安裝包是否完整、是否有數據丟失。

在區塊鏈中,哈希值用於保證區塊的一致性。每個區塊被用於進行哈希計算的數據,都包含前一個區塊鏈的哈希值,所以任何人想要修改區塊的數據幾乎是不可能的,他必需要把整個區塊鏈中從創世區塊到最新的區塊的全部哈希值所有從新計算一遍。

你能夠腦補一下這個工做量有多大,按照目前計算機的算力來看,幾乎不可能

Hashcash

比特幣的工做量證實是使用的是Hashcash算法,一種最初被用於反垃圾郵件的算法,它能夠被拆解爲如下幾步:

  1. 獲取某種公開可知的數據data(在郵件案例中,指的是收件人郵件地址;比特幣案例中,指的是區塊頭)
  2. 添加一個計數器counter,初始值設置爲0;
  3. 計算 data 與 counter拼接字符串的哈希值;
  4. 檢查上一步的哈希值是否知足某個條件,知足則中止計算,不知足則 counter 加1,而後重複第3步和第4步,直到知足這個特定的條件爲止。

這是一種粗暴的算法:你改變計數器,計算一個新的哈希值,檢查它,增長計數器,計算一個新的哈希值,循環往復,這就是爲何它須要花費大量計算機算力資源的緣由所在。

讓咱們來近距離看一下這個特定的條件指的是什麼。在原始的Hashcash算法中,這個特殊的要求指的是計算出來的哈希值的前20bit必須全是零,

在比特幣種,這種要求哈希值前面有多少個零打頭的要求是隨着時間的推移而不斷調整的,這是出於設計的目的,儘管在計算機的算力會不斷的提高和愈來愈多的礦工加入這個網絡中的狀況下,都要保證每10min生產一個區塊。

咱們演示一下這個算法,

# 計算字符串'I like donuts'的哈希值
SHA256("I like donuts") 
——> f80867f6efd4484c23b0e7184e53fe4af6ab49b97f5293fcd50d5b2bfa73a4d0

# 拼接一個計數器值(ca07ca),再次進行Hash計算
SHA256("I like donutsca07ca") 
——> 0000002f7c1fe31cb82acdc082cfec47620b7e4ab94f2bf9e096c436fc8cee06
複製代碼

這裏的ca07ca是計數器值的十六進制,他表示的十進制值爲13240266

即,從0開始,總共計算了13240266次,才計算出I like donuts這個數據的Hash值,知足前6位(3字節)全是零。

代碼實現

思路:

1)每次區塊被添加到區塊鏈以前,先要進行挖礦(Pow)

2)挖礦過程當中,產生的 Hash 值,若是小於難度目標值則添加進區塊,不然繼續挖礦,直到找到正確的Hash爲止

3)最後,驗證區塊Hash是否有效

定義Pow類

/** * 工做量證實 * * @author wangwei * @date 2018/02/04 */
@Data
public class ProofOfWork {

    /** * 難度目標位 */
    public static final int TARGET_BITS = 20;

    /** * 區塊 */
    private Block block;
    /** * 難度目標值 */
    private BigInteger target;

    private ProofOfWork(Block block, BigInteger target) {
        this.block = block;
        this.target = target;
    }
  
    /** * 建立新的工做量證實,設定難度目標值 * <p> * 對1進行移位運算,將1向左移動 (256 - TARGET_BITS) 位,獲得咱們的難度目標值 * * @param block * @return */
    public static ProofOfWork newProofOfWork(Block block) {
        BigInteger targetValue = BigInteger.valueOf(1).shiftLeft((256 - TARGET_BITS));
        return new ProofOfWork(block, targetValue);
    }
}
複製代碼
  • 設定一個難度目標位TARGET_BITS,表示最終挖礦挖出來Hash值,轉化爲二進制後,與256相比,長度少了多少bit,也即二進制前面有多少bit是零.

    • TARGET_BITS 越大,最終targetValue就越小,要求計算出來的Hash愈來愈小,也就是挖礦的難度愈來愈大。
    • 咱們這裏的TARGET_BITS是固定的,可是在真實的比特幣中,難度目標是隨着時間的推推,會動態調整的。詳見:《精通比特幣 (第二版)》第10章
  • 因爲數值比較大,這裏要使用BitInteger類型。

準備數據

/** * 準備數據 * <p> * 注意:在準備區塊數據時,必定要從原始數據類型轉化爲byte[],不能直接從字符串進行轉換 * @param nonce * @return */
private String prepareData(long nonce) {
   byte[] prevBlockHashBytes = {};
   if (StringUtils.isNoneBlank(this.getBlock().getPrevBlockHash())) {
       prevBlockHashBytes = new BigInteger(this.getBlock().getPrevBlockHash(), 16).toByteArray();
   }

   return ByteUtils.merge(
           prevBlockHashBytes,
           this.getBlock().getData().getBytes(),
           ByteUtils.toBytes(this.getBlock().getTimeStamp()),
           ByteUtils.toBytes(TARGET_BITS),
           ByteUtils.toBytes(nonce)
    );
}
複製代碼
  • 參與Hash運算的以下幾個信息:
    • 前一個區塊(父區塊)的Hash值;
    • 區塊中的交易數據;
    • 區塊生成的時間;
    • 難度目標;
    • 用於工做量證實算法的計數器

詳見:《精通比特幣 (第二版)》第09章

Pow算法

/** * 運行工做量證實,開始挖礦,找到小於難度目標值的Hash * * @return */
public PowResult run() {
    long nonce = 0;
    String shaHex = "";
    System.out.printf("Mining the block containing:%s \n", this.getBlock().getData());

    long startTime = System.currentTimeMillis();
    while (nonce < Long.MAX_VALUE) {
        String data = this.prepareData(nonce);
        shaHex = DigestUtils.sha256Hex(data);
        if (new BigInteger(shaHex, 16).compareTo(this.target) == -1) {
            System.out.printf("Elapsed Time: %s seconds \n", (float) (System.currentTimeMillis() - startTime) / 1000);
            System.out.printf("correct hash Hex: %s \n\n", shaHex);
            break;
         } else {
            nonce++;
         }
     }
     return new PowResult(nonce, shaHex);
}
複製代碼
  • 循環體裏面主要如下四步:
    • 準備數據
    • 進行sha256運算
    • 轉化爲BigInter類型
    • 與target進行比較
  • 最後,返回正確的Hash值以及運算計數器nonce

驗證區塊Hash有效性

/** * 驗證區塊是否有效 * * @return */
public boolean validate() {
    String data = this.prepareData(this.getBlock().getNonce());
    return new BigInteger(DigestUtils.sha256Hex(data), 16).compareTo(this.target) == -1;
}
複製代碼

修改區塊添加邏輯

/** * <p> 建立新區塊 </p> * * @param previousHash * @param data * @return */
public static Block newBlock(String previousHash, String data) {
    Block block = new Block("", previousHash, data, Instant.now().getEpochSecond(), 0);
    ProofOfWork pow = ProofOfWork.newProofOfWork(block);
    PowResult powResult = pow.run();
    block.setHash(powResult.getHash());
    block.setNonce(powResult.getNonce());
    return block;
}
複製代碼
  • 建立區塊
  • 建立Pow算法對象
  • 執行Pow算法
  • 保存返回的Hash以及運算計數器

測試運行

/** * 測試 * * @author wangwei * @date 2018/02/05 */
public class BlockchainTest {

    public static void main(String[] args) {

        Blockchain blockchain = Blockchain.newBlockchain();

        blockchain.addBlock("Send 1 BTC to Ivan");
        blockchain.addBlock("Send 2 more BTC to Ivan");

        for (Block block : blockchain.getBlockList()) {
            System.out.println("Prev.hash: " + block.getPrevBlockHash());
            System.out.println("Data: " + block.getData());
            System.out.println("Hash: " + block.getHash());
            System.out.println("Nonce: " + block.getNonce());

            ProofOfWork pow = ProofOfWork.newProofOfWork(block);
            System.out.println("Pow valid: " +  pow.validate() + "\n");
        }
    }
}

/** * 設定TARGET_BITS = 20,獲得以下結果: */
Mining the block containing:Genesis Block 
Elapsed Time: 2.118 seconds 
correct hash Hex: 00000828ee8289ef6381f297585ef8c952fde93fc2b673ff7cc655f699bb2442 

Mining the block containing:Send 1 BTC to Ivan 
Elapsed Time: 1.069 seconds 
correct hash Hex: 00000a38c0d7f2ebbd20773e93770298aa8bc0cc6d85fca8756fe0646ae7fea5 

Mining the block containing:Send 2 more BTC to Ivan 
Elapsed Time: 4.258 seconds 
correct hash Hex: 00000777f93efe91d9aabcba14ab3d8ab8e0255b89818cdb9b93cfa844ad0c7f 

Prev.hash: 
Data: Genesis Block
Hash: 00000828ee8289ef6381f297585ef8c952fde93fc2b673ff7cc655f699bb2442
Nonce: 522163
Pow valid: true

Prev.hash: 00000828ee8289ef6381f297585ef8c952fde93fc2b673ff7cc655f699bb2442
Data: Send 1 BTC to Ivan
Hash: 00000a38c0d7f2ebbd20773e93770298aa8bc0cc6d85fca8756fe0646ae7fea5
Nonce: 474758
Pow valid: true

Prev.hash: 00000a38c0d7f2ebbd20773e93770298aa8bc0cc6d85fca8756fe0646ae7fea5
Data: Send 2 more BTC to Ivan
Hash: 00000777f93efe91d9aabcba14ab3d8ab8e0255b89818cdb9b93cfa844ad0c7f
Nonce: 1853839
Pow valid: true
複製代碼

總結

咱們正在一步一步接近真實的區塊鏈架構,本篇咱們實現了挖礦機制,可是咱們還有不少關鍵性的功能沒有實現:區塊鏈數據庫的持久性、錢包、地址、交易、共識機制,這些咱們後面一步一步來實現

資料

相關文章
相關標籤/搜索