用go编写区块链系列之2---PoW

前一篇文章中,我们构建了一个最简单的额区块链,本文中我们将改进这个区块链,加入PoW机制,使得这个区块链变成一个可以挖矿的区块链。原文见https://jeiwan.cc/posts/building-blockchain-in-go-part-2/

1 Proof-of-Work(PoW)

区块链的PoW机制的思想就是,一个节点必须做艰难的工作才能够推送数据到区块链中。这个艰难工作保证了区块链的安全和一致性。同时,区块链也会为这个工作提供奖励。这有点像我们的人生,一个人必须努力工作才能获得他想要的额生活。

2 Hash算法

Hash算法就是为特定的数据计算其Hash值的过程。一个hash函数可以输入任意长度的数据,但是其输出结果总是固定长度的hash值。Hash算法的特征有:

  • 不能根据Hash值推导原始数据,所以Hash算法并不是一种加密算法。
  • 特定数据只会对应一个唯一特定的hash值。
  • 输入数据即使只改变一个数据位,hash结果也会产生很大的变化。

Hash算法被广泛的用于数据的一致性检查。在区块链中,hash算法被用于验证区块的一致性。hash算法的输入数据包含上一个区块的hash值,这使得修改区块链数据成为不可能的任务:一个人必须计算这个区块的hash,以及其后所有区块的hash。

3 Hashcash

比特币使用Hashcash,一种最早设计用来拦截垃圾邮件的算法。Hashcash算法包含以下几步:

(1)准备一些已知的公共数据data,比如区块头。

(2)给这些数据额外添加一个计数器counter,counter从0开始递增。

(3)计算data+counter拼接后字符串的Hash值

扫描二维码关注公众号,回复: 4004111 查看本文章

(4)检查计算出的Hash值是否满足要求,如果满足,则工作完成;如果不满足,则counter增1,重新执行(3)、(4)步骤。

在最开始的Hashcash算法中,对Hash值的检查要求是Hash值的最开始的20位全为0。在比特币中,这个要求会随时调整以使得计算难度满足没10分钟生产一个区块。

为了验证这个算法,以字符串“I like donuts”为例,要找到一个counter使得I like donuts N的Hash值前面3个字节都为0,

找到的counter将是ca07ca,换算成10进制是13240266。

4 PoW算法实现

首先我们定义挖矿难度系数为:

const targetBits = 24

哈希函数SHA256()计算出的hash值是256位,我们希望前面24位都是0。

然后我们定义一个挖矿结构体:

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
}

在ProofOfWork结构体中我们定义了一个区块指针block和整型指针target,block中保存了要挖出区块的数据,target中保存了跟挖矿难度相对应的目标值。

NewProofOfWork 函数中,我们先将target赋值为1,然后再左移256-targetBits位。如果一个Hash值小于这个target,则说明它的前面24位都是0。比如我们现在构造的target等于:

0x10000000000000000000000000000000000000000000000000000000000

它在内存中占用了29个字节,对于下面3个数:

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一个数大于target,它不满足要求;第3个数小于target,我们可以看到它前面24位都为0。

现在我们需要构造给Hash函数计算的数据:

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{},
	)

	return data
}

在这里我们准备计算Hash值的数据为block.PrevBlockHash+block.Data+block.Timestamp+target+nonce拼接而成的数据,其中nonce就是上面的计数器counter。

所有数据准备完毕,现在开始实现PoW算法:

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)
		fmt.Printf("\r%x", hash)
		hashInt.SetBytes(hash[:])

		if hashInt.Cmp(pow.target) == -1 {
			break
		} else {
			nonce++
		}
	}
	fmt.Print("\n\n")

	return nonce, hash[:]
}

我们在for循环里面做的事情:

(1)准备数据

(2)计算Hash值

(3)将Hash值转成一个整数

(4)比较整数和目标值的大小

(5)结束或递增Nonce

为了配合挖矿算法,我们需要更改块结构:

type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
	Nonce         int
}
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
}

最后贴上完整代码:


package main

import (
"strconv"
"bytes"
"crypto/sha256"
"time"
"fmt"
	"math/big"
	"encoding/binary"
	"log"
	"math"
)

const targetBits = 24
var maxNonce = math.MaxInt64

type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
	Nonce         int
}

type ProofOfWork struct {
	block  *Block
	target *big.Int
}

func (b *Block) SetHash() {
	timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
	headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
	hash := sha256.Sum256(headers)

	b.Hash = hash[:]
}

func NewProofOfWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-targetBits))

	pow := &ProofOfWork{b, target}

	return pow
}

func IntToHex(num int64) []byte {
	buff := new(bytes.Buffer)
	err := binary.Write(buff, binary.BigEndian, num)
	if err != nil {
		log.Panic(err)
	}

	return buff.Bytes()
}

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{},
	)

	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)
		fmt.Printf("\r%x", hash)
		hashInt.SetBytes(hash[:])

		if hashInt.Cmp(pow.target) == -1 {
			break
		} else {
			nonce++
		}
	}
	fmt.Println()
	fmt.Println(nonce)

	return nonce, hash[:]
}

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 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
}

type Blockchain struct {
	blocks []*Block
}
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 NewGenesisBlock() *Block {
	return NewBlock("Genesis Block", []byte{})
}

func NewBlockchain() *Blockchain {
	return &Blockchain{[]*Block{NewGenesisBlock()}}
}

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("Hash: %x\n", block.Hash)
		fmt.Println()
	}
}


运行结果:

Mining the block containing "Genesis Block"
00000040831b867e4a7e75460eb76f9a3b32c5be4043467d11d09240522d25d9
7470848
Mining the block containing "Send 1 BTC to Ivan"
000000f49ad3dbc76bce70f317082dbbf1eac00f044e8807865bba35a30d2fba
32065795
Mining the block containing "Send 2 more BTC to Ivan"
0000002250ba1c3254ad765b4a0a47af2e5452c9bb27102cf5e0fe3f5de10070
29669735
Prev. hash: 
Data: Genesis Block
Hash: 00000040831b867e4a7e75460eb76f9a3b32c5be4043467d11d09240522d25d9

Prev. hash: 00000040831b867e4a7e75460eb76f9a3b32c5be4043467d11d09240522d25d9
Data: Send 1 BTC to Ivan
Hash: 000000f49ad3dbc76bce70f317082dbbf1eac00f044e8807865bba35a30d2fba

Prev. hash: 000000f49ad3dbc76bce70f317082dbbf1eac00f044e8807865bba35a30d2fba
Data: Send 2 more BTC to Ivan
Hash: 0000002250ba1c3254ad765b4a0a47af2e5452c9bb27102cf5e0fe3f5de10070


Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/liuzhijun301/article/details/82706619