簡易區塊鏈實現V2(golang)

簡易區塊鏈實現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版本,僅僅稍做改動
運行就可以得到下面的結果
1
可以看到這裏的時間戳已經不再和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就這亞子吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章