簡易區塊鏈實現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就這亞子吧