最小可行性区块链设计系列:Day 4 挖矿机制

前言

《最小可行性区块链设计系列》的第三讲(http://www.jianshu.com/p/82e7a6de1f02) 实现了区块的构造。

本文的代码地址:https://github.com/qikh/mini-block-chain/commit/90c141eb3098f2f7f91e218cc47ecc988cfac12c (开发语言为Kotlin,更简洁的Java)

正文

Alice向Bob发起一笔100元的转账,这笔转账记录被发布到区块链网络上等待区块链客户端登账,如果没有人来完成登账的动作,转账就没有完成。为了激励大家来登记账本,完成登账的客户端可以获得2元的奖励(Incentive)。Charlie和David都运行着区块链的客户端,希望通过登账来获得2元/区块(Block)的奖励。我们把Charlie和David的登账行为称之为“挖矿”,Charlie和David则被成为“矿工”。

上一讲我们看到新区块(Block)的作用,就是把交易数据(Transaction)包含进区块,并且更新发送方和接收方的账户状态(余额),逻辑十分简单。构造新区快既然有了奖励,就需要增加竞争机制来决定谁的新区块最终完成登账,算力证明(Proof of Work)是区块链领域的主要竞争算法之一。

我们参考比特币的协议来设计我们的POW算法:

  1. 所有的区块链客户端统一一个目标数字(target),例如:0x00000FFFF0000000000000000000000000000000000000000000000000000000
  2. 不断计算区块的哈希值,希望得到一个小于目标数字的哈希值,例如:0x00006853cc0fc1161a7c12da76866fd79095556aed267f883872df4be859f204。
  3. 第一个算出小于目标数字的客户端写入区块,并向区块链网络广播消息,其他客户端放弃本次挖矿,开始计算下一个区块。

区块的哈希算法并不是简单地对区块内容进行哈希运算,而是对Block Header进行哈希运算,Block Header包括:
version:协议版本号
parentHash:上一个区块的哈希值
merkleRoot:交易数据(Transaction)的Merkle Root Hash,代表了交易数据的唯一性,Merkle Tree算法的其他优点我们随后还会讨论。
time:时间戳
difficulty:挖矿难度值,可以推算出目标数字
nonce:工作量证明随机数

/**
 * 区块(Block)类,包含了区块高度(height),上一个区块哈希值(parentHash),旷工账户地址(minerAddress),交易列表(transactions)和时间戳(time)。
 */
class Block(val height: Long, val parentHash: ByteArray, val minerAddress: String, val transactions: List<Transaction>,
            val time: DateTime) {

  val version: Int = 1

  val merkleRoot: ByteArray
    get() = CryptoUtil.merkleRoot(transactions)

  var difficulty: Int = 0

  var nonce: Int = 0

  /**
   * 区块(Block)的哈希值(KECCAK-256)
   */
  val hash: ByteArray
    get() = CryptoUtil.hashBlock(this)

}

其中nonce的初始值为1,每次哈希运算如果没有获得小于target的数字,nonce就加1,然后再次运算哈希值,直到获得一个小于target的数字。

  /**
   * 挖矿,返回nonce值和target值。目前采用阻塞模型,后期修改为更合理的异步模型。
   */
  fun mine(block: Block): MineResult {
    val ver = block.version
    val parentHash = block.parentHash
    val merkleRoot = block.merkleRoot
    val time = (block.time.millis/1000).toInt() // Current timestamp as seconds since 1970-01-01T00:00 UTC
    val difficulty = currentDifficulty // difficulty

    // 挖矿难度的算法:https://en.bitcoin.it/wiki/Difficulty
    val exp = difficulty shr 24
    val mant = difficulty and 0xffffff
    val target = BigInteger.valueOf(mant.toLong()).multiply(BigInteger.valueOf(2).pow(8 * (exp - 3)))
    val targetStr = "%064x".format(target)

    var nonce = 0
    while (nonce < 0x100000000) {

      val headerBuffer = ByteBuffer.allocate(4 + 32 + 32 + 4 + 4 + 4)
      headerBuffer.put(ByteBuffer.allocate(4).putInt(ver).array()) // version
      headerBuffer.put(parentHash) // parentHash
      headerBuffer.put(merkleRoot) // merkleRoot
      headerBuffer.put(ByteBuffer.allocate(4).putInt(time).array()) // time
      headerBuffer.put(ByteBuffer.allocate(4).putInt(difficulty).array()) // difficulty(current difficulty)
      headerBuffer.put(ByteBuffer.allocate(4).putInt(nonce).array()) // nonce

      val header = headerBuffer.array()
      val hit = Hex.toHexString(CryptoUtil.sha256(CryptoUtil.sha256(header)))

      if (hit < targetStr) {
        break
      }
      nonce += 1
    }

    val result = MineResult(currentDifficulty, nonce)
    return result
  }

挖矿的难度difficulty和算出的nonce会写入区块的Header,其他客户端可以再次计算区块Header的哈希值来确定这次运算的有效性。

我们POW所采用的哈希算法与比特币相同,为两次SHA-256运算。这种POW设计的缺点是哈希算法很容易用ASIC集成电路来实现硬件加速,也就是所谓的矿机,CPU挖矿与ASIC挖矿相比性能差别百万倍。为了解决这个问题,以太坊的POW采用了Dagger Hashimoto算法,结合了Memory Hard的Dagger算法和IO-bound的Hashimoto算法,来阻止ASIC芯片硬件加速。Dagger Hashimoto算法的实现相对复杂,后期我们会作为POW算法的一个选项来实现。

下一讲我们将会讨论在分布式环境下如何取得网络共识(Consensus)。

    原文作者:大鱼Whale
    原文地址: https://www.jianshu.com/p/608cfb46ff49
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞