概述

小白入门:https://github.com/dukedaily/solidity-expert ,欢迎star转发,文末加V入群。

职场进阶: https://dukeweb3.com

1. 当前版本问题

  • 随机数和难度值是随便填写的

  • 区块的哈希值是无规则的

2. V2版本思路

  • 定义一个工作量证明的结构ProofOfWork
  • 提供创建POW的函数

  • 提供计算不断计算hash的哈数

  • 提供一个校验函数

一、工作量证明详细介绍

1. POW详细流程

image-20221108132337319

2. 定义工作量证明结构

创建proofOfWork.go文件,添加如下代码:

//- 定一个工作量证明的结构ProofOfWork
//- block
//- 目标值

type ProofOfWork struct {

    //区块数据
    block Block

    //目标值,先写成固定的值,后面再进行推到演算。
    target big.Int
}

3. 添加创建POW的函数

//- 提供一个创建POW的方法
//NewProofOfWork(参数)

func NewProofOfWork(block Block)  *ProofOfWork{
    pow := ProofOfWork{
        block:block,
    }

    //自定义的难度值,先写成固定值
    //0000100000000000000000000000000000000000000000000000000000000000
    targetString := "0000100000000000000000000000000000000000000000000000000000000000"

    //不要连着写
    bigIntTmp := big.Int{}
    //bigIntTmp.SetBytes([]byte(targetString))
    bigIntTmp.SetString(targetString, 16)

    pow.target = bigIntTmp

    return &pow
}

4.添加挖矿的函数

//- 提供一个计算哈希值的方法
//Run()
func (pow *ProofOfWork)Run() ([]byte, uint64) {

    //var data = "helloworld"
    ////10s中左右
    //for i := 0; i < 1000000; i++ {
    //    hash := sha256.Sum256([]byte(data + string(i)))
    //    fmt.Printf("hash : %x, %d\n", string(hash[:]), i)
    //}
    var Nonce uint64
    var hash [32]byte

    fmt.Printf("target : %x\n", pow.target)
    for ;; {
        //[Size]byte
        hash = sha256.Sum256(pow.prepareData(Nonce))

        //比较:当前的哈希值[32]byte,目标值:big.Int

        //目标是:把当前的哈希值转换成big.Int
        tTmp := big.Int{}
        tTmp.SetBytes(hash[:])

        // Cmp compares x and y and returns:
        //
        //   -1 if x <  y
        //    0 if x == y
        //   +1 if x >  y
        //
        //func (x *Int) Cmp(y *Int) (r int) {
        //当前哈希.Cmp(目标值) == -1,说明当前的哈希小于目标值
        if tTmp.Cmp(&pow.target)  == -1 {

        //if tTmp < pow.target {
            //找到了,退出
            fmt.Printf("found hash : %x, %d\n", hash, Nonce)
            break
        } else {
            //继续循环
            Nonce++
        }
    }

    return hash[:], Nonce
}

5.实现辅助函数prepareData

这个函数类似于v1中的setHash函数的功能,代码如下:

func (pow *ProofOfWork) prepareData(num uint64) []byte {
    block := pow.block
    tmp := [][]byte{
        uintToByte(block.Version),
        block.PrevBlockHash,
        //block.Hash,
        block.MerkleRoot,
        uintToByte(block.TimeStamp),
        uintToByte(block.Difficuty),
        uintToByte(num),
        block.Data}

    data := bytes.Join(tmp, []byte{})
    return data
}

二、调用POW

pow在block.go文件中使用,即创建完区块之后引入pow。将setHash()注释掉,使用ProofOfWork替换之。

代码如下:

    //block.setHash()
    pow := NewProofOfWork(block)
    hash, nonce := pow.Run()
    block.Hash = hash
    block.Nonce = nonce

具体位置如下:

image-20221108132616306

三、第一次测试

1. main函数

主函数基于v1版本,无需修改:

package main

import (
    "fmt"
)

func main() {

    bc := NewBlockChain()
    bc.AddBlock("HelloWorld!")
    bc.AddBlock("Hello Itcast!")
    bc.AddBlock("Hello i11111!")
    //block的数组
    for index, block := range bc.blocks {
        fmt.Println(" ============== current block index :", index)
        fmt.Printf("Version : %d\n", block.Version)
        fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
        fmt.Printf("Hash : %x\n", block.Hash)
        fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
        fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
        fmt.Printf("Difficuty : %d\n", block.Difficuty)
        fmt.Printf("Nonce : %d\n", block.Nonce)
        fmt.Printf("Data : %s\n", block.Data)
    }
}

2. 编译执行

go build -o block *.go
./block

3.执行结果

duke ~/Desktop/code/v2$  ./block 
target : f00000000000000000000000000000000000000000000000000000000000
found hash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49, 77832
target : f00000000000000000000000000000000000000000000000000000000000
found hash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec, 77018
target : f00000000000000000000000000000000000000000000000000000000000
found hash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563, 80369
target : f00000000000000000000000000000000000000000000000000000000000
found hash : 00005acbe17bfbd374fbabe94ba6b64ba8cdfdedf828f02b21f14a4517dcffc3, 64729
 ============== current block index : 0
Version : 0
PrevBlockHash : 
Hash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49
MerkleRoot : 
TimeStamp : 1536370652
Difficuty : 10
Nonce : 77832
Data : Genesis Block!
 ============== current block index : 1
Version : 0
PrevBlockHash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49
Hash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec
MerkleRoot : 
TimeStamp : 1536370652
Difficuty : 10
Nonce : 77018
Data : HelloWorld!
 ============== current block index : 2
Version : 0
PrevBlockHash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec
Hash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563
MerkleRoot : 
TimeStamp : 1536370652
Difficuty : 10
Nonce : 80369
Data : Hello Itcast!
 ============== current block index : 3
Version : 0
PrevBlockHash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563
Hash : 00005acbe17bfbd374fbabe94ba6b64ba8cdfdedf828f02b21f14a4517dcffc3
MerkleRoot : 
TimeStamp : 1536370652
Difficuty : 10
Nonce : 64729
Data : Hello i11111!
 duke ~/Desktop/code/v2$

图示:

image-20221108132725590

四、添加校验函数

校验即对求出来的哈希和随机数进行验证,只需要对求出来的值进行反向的计算比较即可。

在ProofOfWork.go文件中,添加如下代码:

//- 提供一个校验函数
//IsValid()

func (pow *ProofOfWork)IsValid()  bool{
    hash := sha256.Sum256(pow.prepareData(pow.block.Nonce))
    fmt.Printf("is valid hash : %x, %d\n", hash[:], pow.block.Nonce)

    tTmp := big.Int{}
    tTmp.SetBytes(hash[:])
    if tTmp.Cmp(&pow.target)  == -1 {
        return true
    }

    return false

    //return tTmp.Cmp(&pow.target)  == -1
}

在main.go文件中调用校验函数IsValid,添加如下代码:

pow := NewProofOfWork(*block)
fmt.Printf("IsValid : %v\n", pow.IsValid())

位置如下:

image-20221108132744530

五、第二次测试

重新编译执行,结果如下:

image-20221108132807179

优化时间打印:

timeFormat := time.Unix(int64(block.TimeStamp), 0).Format("2006-01-02 15:04:05")
fmt.Printf("时间戳: %s\n", timeFormat)

六、推导难度值

比特币系统并不是直接给定一个哈希值,而是给定一个难度数字,通过这个数字计算出一个哈希值,我们上面为了简化所以直接给定了一个哈希。现在,我们要模拟比特币模式,给定一个数字,然后推导出目标哈希值。

func NewProofOfWork(block *Block) *ProofOfWork {
    pow := ProofOfWork{
        block: block,
    }

    //hashString := "0001000000000000000000000000000000000000000000000000000000000000"
    //tmp := big.Int{}
    //tmp.SetString(hashString, 16)

    //pow.target = &tmp

    //目标值:
    // 0001000000000000000000000000000000000000000000000000000000000000
    //初始值
    // 0000000000000000000000000000000000000000000000000000000000000001 
    //左移256位
    //10000000000000000000000000000000000000000000000000000000000000000
    //右移4位(16进制位数)
    // 0001000000000000000000000000000000000000000000000000000000000000

    targetLocal := big.NewInt(1)
    //targetLocal.Lsh(targetLocal, 256)
    //targetLocal.Rsh(targetLocal, difficulty)
    targetLocal.Lsh(targetLocal, 256-difficulty)
    pow.target = targetLocal

    return &pow
}

七、总结

这个版本我们详细讲解了pow工作证明的原理即实现方法,相信大家对挖矿的概念有了更深刻的了解。

由于我们没有讲到交易,所以目前没有实现奖励机制,后面会逐步引入,请继续学习后续课程。

八、下节预告

持久化 + 命令行