实际的区块链在出块上要复杂的多,例如比特币采用的工作量证明POW机制。其采用了基于现金哈希的哈希算法,把前一个块的时间戳、nonce、自己要输入的DATA数据等合起来计算其HASH,达到设定的条件(前若干位是0,现金哈希原生要求是20,前一段时间比特币的要求是18,后续会逐渐增多)则成功,否则变更nonce的值重新计算。以下以POW为例设计代码。
package main
import (
"fmt"
"time"
"crypto/sha256"
"bytes"
"math/big"
"math"
"strconv"
)
const targetBits = 24
const maxNonce int = math.MaxInt64
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}
type Blockchain struct {
blocks []*Block
}
type ProofOfWork struct {
block *Block
target *big.Int
}
func NewProofOfWork(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits))
pow := &ProofOfWork{b, target}
return pow
}
func (pow *ProofOfWork) prepareData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.Data,
//IntToHex(pow.block.Timestamp),
//IntToHex(int64(targetBits)),
//IntToHex(int64(nonce)),
[]byte(strconv.FormatInt((pow.block.Timestamp),16)),
[]byte(strconv.FormatInt((int64(targetBits)),16)),
[]byte(strconv.FormatInt((int64(nonce)),16)),
},
[]byte{},
)
return data
}
func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
for nonce < maxNonce {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
fmt.Printf("\r%x", hash)
break
} else {
nonce++
}
}
fmt.Print("\n\n")
return nonce, hash[:]
}
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Hash = hash[:]
block.Nonce = nonce
return block
}
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.blocks = append(bc.blocks, newBlock)
}
func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int
data := pow.prepareData(pow.block.Nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
isValid := hashInt.Cmp(pow.target) == -1
return isValid
}
func main() {
bc := NewBlockchain()
bc.AddBlock("Send 1 BTC to Ivan")
bc.AddBlock("Send 2 more BTC to Ivan")
for _, block := range bc.blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Timestamp: %x\n", block.Timestamp)
fmt.Printf("Hash: %x\n", block.Hash)
pow := NewProofOfWork(block)
fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
fmt.Println()
}
}
执行go run pow.go可以看到结果
Mining the block containing "Genesis Block"
00000058fb085e863d52480356a2a3f0672e3f05b71f1636eb4787ef6f136942
Mining the block containing "Send 1 BTC to Ivan"
000000ca03afb7aab594372670c5b0daf5c92092d11884a0d934590ac00bf18f
Mining the block containing "Send 2 more BTC to Ivan"
000000622e11eb48cb7ea0b9c76fceadeec0733bfb820915fedef6ba7c19fe94
Prev. hash:
Data: Genesis Block
Timestamp: 5a658fd4
Hash: 00000058fb085e863d52480356a2a3f0672e3f05b71f1636eb4787ef6f136942
PoW: true
Prev. hash: 00000058fb085e863d52480356a2a3f0672e3f05b71f1636eb4787ef6f136942
Data: Send 1 BTC to Ivan
Timestamp: 5a658fd8
Hash: 000000ca03afb7aab594372670c5b0daf5c92092d11884a0d934590ac00bf18f
PoW: true
Prev. hash: 000000ca03afb7aab594372670c5b0daf5c92092d11884a0d934590ac00bf18f
Data: Send 2 more BTC to Ivan
Timestamp: 5a658fed
Hash: 000000622e11eb48cb7ea0b9c76fceadeec0733bfb820915fedef6ba7c19fe94
PoW: true
在这个进阶的代码中,新增了一个结构ProofOfWork,在上一节使用setHash方法哈希后就直接出块了,在这里则通过ProofOfWork来通过算法的运算增加出块的难度(也就是共识机制,其本质是通过消耗系统外的熵来减少系统本身的熵);
ProofOfWork这个数据结构有三个方法NewProofOfWork、prepareData、Run,其中Run方法就是实现了挖矿的逻辑(在比特币中消耗现实中的电量等要素转化为比特币系统的熵减)。NewProofOfWork方法中先根据预先设定的targetBits计算出一个目标哈希值,prepareData准备每一轮要给哈希提供的弹药数据,最后run哈希prepareData的数据后与NewProofOfWork中的进行比较,小于则成功大于则nonce改变下一轮计算。
最后的Validate方法对整个业务做了一次校验。