简易区块链实现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版本,仅仅稍做改动
运行就可以得到下面的结果
可以看到这里的时间戳已经不再和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就这亚子吧