简易区块链实现V2(golang)


前言

这个版本主要在上一个版本V1的基础上增加了POW(工作量证明)
整体还是很简单,主要还是一个对区块链的理解和go语言的练习

代码和分析

这里主要新增了pow.go,创建了工作证明的结构体,通过设定难度值来计算得到nonce值

const targitBits  = 24

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

首先是设定难度值,这里设定的24表示,256位的2进制哈希值中前24位是0,这里因为是简易版,所以难度值是设为了定值

工作量证明结构体,有两个成员一个是区块结构体一个是难度值,这里难度值我们是需要将前24位为0的二进制数具体出来方便后面进行比较判断是否找到了正确的nonce值

func NewPow(block *Block) *Pow{
	var IntTarget = big.NewInt(1)
	IntTarget.Lsh(IntTarget,(256-targitBits)) //这里确定难度值,因为是bigint左移要用自带的Lsh方法
	MyPow := Pow{block, IntTarget}
	return &MyPow
}

上面的方法用于创建一个工作量证明对象,这里主要是要将设定的难度值转化为具体数值,这里我们采用左移的方法,左移位数等于256减去难度值,这里要注意的是大数左移必须用它特定的方法,不能像普通的int类型用<<

下面是工作量证明结构体的组合方法首先我们需要一个方法像V1里一样来准备数据

func (MyPow Pow)PrepareData(nonce int64)[]byte{
	newblock := MyPow.block
	tmp := [][]byte{
		Inttobyte(newblock.Version),
		newblock.PrevHash,
		newblock.MerkleRoot,
		Inttobyte(newblock.TimeStamp),
		Inttobyte(targitBits),
		Inttobyte(nonce),
		newblock.Data,
	}

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

主要是后面计算sha值需要使用[]byte类型,这里也是主要从V1中将前面的方法拷贝过来稍做修改

然后我们就是设计一个方法计算Nonce值

func (MyPow Pow)CalcPow(){
	var nonce int64
	var HashInt big.Int
	for nonce < math.MaxInt64{
		data := MyPow.PrepareData(nonce)
		hash := sha256.Sum256(data)
		HashInt.SetBytes(hash[:])
		if HashInt.Cmp(MyPow.target) == -1{
			fmt.Printf("find hash : %x\n", hash[:])
			MyPow.block.Nonce = nonce
			MyPow.block.Hash = hash[:]
			break
		}else{
			nonce++
		}
	}
}

这里我们主要就是不断递增nonce值,直到找到一个nonce计算得到的哈希值小于我们前面计算得到的目标难度,这里要注意的就是大数比较也有它特定的方法,有把切片转big.int的方法。这里又感觉到脚本语言还是很方便的,python感觉处理这样的情况类型转换需要我们考虑的就相对少很多

最后再设计一个,校验的方法,当别人的区块上链后我们要有能力计算这个块的hash是否满足难度值要求

func (MyPow Pow)Isvalid()bool{
	var HashInt big.Int
	data := MyPow.PrepareData(MyPow.block.Nonce)
	hash := sha256.Sum256(data)
	HashInt.SetBytes(hash[:])
	if HashInt.Cmp(MyPow.target) == -1{
		return true
	}else {
		return false
	}
}

这里就是计算一下工作量证明中块的hash来和块中的难度值进行一下比较

其它的函数基本还是维持V1版本,仅仅稍做改动
运行就可以得到下面的结果
简易区块链实现V2(golang)-LMLPHP
可以看到这里的时间戳已经不再和V1中全都是一样的路

代码

pow.go

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"math"
	"math/big"
)

const targitBits  = 24

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

func NewPow(block *Block) *Pow{
	var IntTarget = big.NewInt(1)
	IntTarget.Lsh(IntTarget,(256-targitBits)) //这里确定难度值,因为是bigint左移要用自带的Lsh方法
	MyPow := Pow{block, IntTarget}
	return &MyPow
}

func (MyPow Pow)CalcPow(){
	var nonce int64
	var HashInt big.Int
	for nonce < math.MaxInt64{
		data := MyPow.PrepareData(nonce)
		hash := sha256.Sum256(data)
		HashInt.SetBytes(hash[:])
		if HashInt.Cmp(MyPow.target) == -1{
			fmt.Printf("find hash : %x\n", hash[:])
			MyPow.block.Nonce = nonce
			MyPow.block.Hash = hash[:]
			break
		}else{
			nonce++
		}
	}
}

func (MyPow Pow)PrepareData(nonce int64)[]byte{
	newblock := MyPow.block
	tmp := [][]byte{
		Inttobyte(newblock.Version),
		newblock.PrevHash,
		newblock.MerkleRoot,
		Inttobyte(newblock.TimeStamp),
		Inttobyte(targitBits),
		Inttobyte(nonce),
		newblock.Data,
	}

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

func (MyPow Pow)Isvalid()bool{
	var HashInt big.Int
	data := MyPow.PrepareData(MyPow.block.Nonce)
	hash := sha256.Sum256(data)
	HashInt.SetBytes(hash[:])
	if HashInt.Cmp(MyPow.target) == -1{
		return true
	}else {
		return false
	}
}

block.go

package main

import (
	"time"
)

type Block struct {
	Version int64
	PrevHash []byte
	Hash []byte
	MerkleRoot []byte
	TimeStamp int64
	Difficulty int64
	Nonce int64
	Data []byte
}

func Newblock(data string, prevhash []byte) *Block{
	var newblock Block
	newblock.Version = 1
	newblock.PrevHash = prevhash
	newblock.MerkleRoot = []byte{}
	newblock.TimeStamp = time.Now().Unix()
	newblock.Difficulty = targitBits
	newblock.Nonce = 5
	newblock.Data = []byte(data)

	mypow := NewPow(&newblock)
	mypow.CalcPow()
	return &newblock
}

blockchain.go

package main

import "os"

type BlockChain struct {
	Blocks []*Block
}

func NewBlockchain(data string) *BlockChain{
	return &BlockChain{[]*Block{Newblock(data,[]byte{})}}
}

func (bc *BlockChain)AddBlock(data string){
	if len(bc.Blocks) <= 0{
		os.Exit(1)
	}
	prehash := bc.Blocks[len(bc.Blocks)-1].Hash
	newblock := Newblock(data, prehash)
	bc.Blocks = append(bc.Blocks, newblock)
}

utils.go

package main

import "encoding/binary"

func Inttobyte(num int64)[]byte{
	var buf = make([]byte, 8)
	binary.BigEndian.PutUint64(buf, uint64(num))
	return buf
}

main.go

package main

import "fmt"

func main(){
	bc := NewBlockchain("first block")
	bc.AddBlock("root to kid 2")
	bc.AddBlock("root to he 1")
	for i,block := range bc.Blocks{
		fmt.Printf("第%d个区块\n",i)
		fmt.Printf("区块版本:%d\n",block.Version)
		fmt.Printf("前区块Hash:%x\n",block.PrevHash)
		fmt.Printf("区块Hash:%x\n",block.Hash)
		fmt.Printf("区块MerkleRoot:%x\n",block.MerkleRoot)
		fmt.Printf("区块时间戳:%d\n",block.TimeStamp)
		fmt.Printf("区块难度:%d\n",block.Difficulty)
		fmt.Printf("区块Nonce:%d\n",block.Nonce)
		fmt.Printf("区块内容:%s\n",string(block.Data))
		pow := NewPow(block)
		fmt.Printf("isvalid : %v\n", pow.Isvalid())
		fmt.Println("==========================================")
	}
}

总结

这个版本主要在前面的基础上增加了工作量证明,整体还是比较简单
主要还是go语言不是很熟悉,写的时候思路也不是很清晰也写了1,2个小时…
但还是提高自己对区块链的进一步认识,写V1的时候就一直不是很理解nonce是拿来干嘛的…
后面的版本应该是增加本地化存储的功能等,目前的区块链还是存储在内存中已关闭就没了,ok就这亚子吧

07-05 14:19