通过我们前边的讲解,我们已经生成了区块链,并可在区块链中增加区块,不过这些区块的只是通过简单的Hash运算获取hash值,这些哈希值太过随意,实际上我们需要在区块中使用的hash是有一定要求,并有生成的难度,这样的过程叫挖矿,在挖矿时,谁先生成符合要求的hash才能生成区块并加入区块链,并且在生成的区块中生成过程的工作还需要能够证明,下面我们来模拟下这个过程
用Visual Studio Code打开blockchain文件夹,找到blockchain/core文件夹,创建proofofwork.go文件,在文件中增加如下代码:
package core
import (
"crypto/sha256"
"math"
"math/big"
)
const difficulty = 4 //生成Hash难度定义,值越大难度越高
//ProofOfWork 定义工作证明结构
type ProofOfWork struct {
block *Block
target *big.Int
}
//NewProofOfWork 新建工作证明
func NewProofOfWork(block *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-difficulty))
pow := &ProofOfWork{block, target}
return pow
}
//GetWrokResult 获取工作结果
func (pow *ProofOfWork) GetWrokResult() (int, []byte) {
var hashInt big.Int
var hash [32]byte
count := math.MaxInt64
nonce := 0
for nonce < count {
data := pow.block.CalculateHash(nonce)
hash = sha256.Sum256(data)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
return nonce, hash[:]
}
//ValidateWork 验证工作结果
func (pow *ProofOfWork) ValidateWork() bool {
var hashInt big.Int
data := pow.block.CalculateHash(pow.block.Nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
isvalid := hashInt.Cmp(pow.target) == -1
return isvalid
}
添加后保存,我们需要修改block.go文件中计算哈希的方法和新建区块方法,不再简单计算哈希,而是交给工作证明方式来进行

如上图,我们对calculateHash方法修改3个地方,修改为Block的内部方法,calculateHash的C字母大写修改为public方法,方法的参数修改为nonce,并在生成Block的hash参数时使用,最后注释生成hash方法,返回和成的data参数,这个方法在ProofOfWork 中的GetWrokResult和ValidateWork都有调用

红色栏标记了,NewBlock方法修改的地方,注释了原哈希获取,创建工作证明,并获取工作证明后的hash值,nonce参数返回的数值为尝试生成符合规则的hash值的次数,修改blockchain.go文件中的Show方法,把区块中的Nonce显示出来

我们再进入终端,go run main.go看下结果

注意下Hash值和Nonce,哈希值的首位都是0,第一个区块的Nonce为22,意思就是尝试的22次才找到符合首位为0的哈希值,这个首位为0是在proofofwork.go文件中的const difficulty = 4参数来控制的,我们修改难度为8后再次尝试下

可以看到结果,难度增加了,Hash值前2位都为0,第一个区块110次尝试才找到,第二个区块859次尝试才找到,我们再次修改难度为12要求生成的hash值必须前3位都是0,并验证这些生成区块中的Hash值是工作得到,并非伪造产生

修个blockchain.go文件中的Show方法,显示验证的结果

在mina方法中再增加2个区块,对比输出结果

我们看到输出的结果上每个区块的Hash值前3位都是0,并且运行验证方法返回的也都是true,说明这些区块都是工作产生的,并非伪造添加,同时输出结果的时间也变长了,每个区块尝试的次数变大了,这样我们的区块就可以工作验证
Go语言知识点
1、go语言的函数可以返回多个参数,在函数方法中 { 前定义返回的参数类型,用(,)号分开多个参数即可在方法中返回,例如proofofwork.go文件中的获取工作结果方法GetWrokResult() (int, []byte) ,在这个方法中要获取到尝试的次数和找到的Hash值2个参数,所以定义了2个返回参数,我们在block.go文件中的NewBlock方法内通过
nonce, hash := pow.GetWrokResult() 的形式直接获取这二个参数使用
Go 从零开发区块链-区块链开发准备
Go 从零开发区块链-1、创建数据区块
Go 从零开发区块链-2、创建区块链