【Golang區塊鏈開發003】區塊序列化存儲
目錄
區塊序列化
BoltDB資料庫使用
通過BoltDB存儲區塊
區塊鏈基於BoltDB存儲區塊
遍歷區塊鏈區塊信息
通過迭代器遍歷區塊信息
一.區塊序列化
本章節討論如何將區塊對象序列化,以便存儲至資料庫中。1.序列化概念互聯通訊的雙方需要採用約定的協議,序列化和反序列化屬於通訊協議的一部分。通訊協議往往採用分層模型,不同模型每層的功能定義以及顆粒度不同,例如:TCP/IP協議是一個四層協議,而OSI模型卻是七層協議模型。在OSI七層協議模型中展現層(Presentation Layer)的主要功能是把應用層的對象轉換成一段連續的二進位串,或者反過來,把二進位串轉換成應用層的對象--這兩個功能就是序列化和反序列化。一般而言,TCP/IP協議的應用層對應與OSI七層協議模型的應用層,展示層和會話層,所以序列化協議屬於TCP/IP協議應用層的一部分。本文對序列化協議的講解主要基於OSI七層協議模型。序列化: 將數據結構或對象轉換成二進位串的過程。反序列化:將在序列化過程中所生成的二進位串轉換成數據結構或者對象的過程。2.對象轉換這個概念是從Java序列化中抽取而來,同樣適用於本文的理解不同的計算機語言中,數據結構,對象以及二進位串的表示方式並不相同。數據結構和對象:對於類似Java這種完全面向對象的語言,工程師所操作的一切都是對象(Object),來自於類的實例化。在Java語言中最接近數據結構的概念,就是POJO(Plain Old Java Object)或者Javabean--那些只有setter/getter方法的類。而在C二進位串:序列化所生成的二進位串指的是存儲在內存中的一塊數據。C語言的字元串可以直接被傳輸層使用,因為其本質上就是以"0"結尾的存儲在內存中的二進位串。在Java語言裡面,二進位串的概念容易和String混淆。實際上String 是Java的一等公民,是一種特殊對象(Object)。對於跨語言間的通訊,序列化後的數據當然不能是某種語言的特殊數據類型。二進位串在Java裡面所指的是byte[],byte是Java的8中原生數據類型之一(Primitive data types)。在Golang中,我們同樣是將區塊對象轉換成位元組數組([]byte)進行序列化並進行數據存儲。3. Gob編碼工具gob是Golang包自帶的一個數據結構序列化的編碼/解碼工具。編碼使用Encoder,解碼使用Decoder。一種典型的應用場景就是RPC(remote procedure calls)。gob由發送端使用Encoder對數據結構進行編碼。在接收端收到消息之後,接收端使用Decoder將序列化的數據變化成本地變數。gob包是golang提供的「私有」的編解碼方式,官方文檔中也提及其它的效率會比json,xml等更高。因此在兩個Go 服務之間的相互通信建議不要再使用json傳遞了,完全可以直接使用gob來進行數據傳遞。
import(
"encoding/gob"
)
4.序列化實現
4.1 區塊定義
//定義區塊
typeBlockstruct{
//1.區塊高度,也就是區塊的編號,第幾個區塊
Heightint64
//2.上一個區塊的Hash值
PreBlockHash[]byte
//3.交易數據(最終都屬於transaction 事務)
Data[]byte
//4.創建時間的時間戳
TimeStampint64
//5.當前區塊的Hash值
Hash[]byte
//6.Nonce 隨機數,用於驗證工作量證明
Nonceint64
}4.2 序列化區塊
// 定義Block的方法Serialize(),將區塊序列化成位元組數組
func(block*Block)Serialize() []byte{
//1.定義result的位元組buffer,用於存儲序列化後的區塊
varresultbytes.Buffer
//2.初始化序列化對象encoder
encoder:=gob.NewEncoder(&result)
//3.通過Encode()方法對區塊進行序列化
err:=encoder.Encode(block)
iferr!=nil{
log.Panic(err)
}
//4.返回result的位元組數組
returnresult.Bytes()
}4.3 反序列化位元組數組
//定義函數DeserializeBlock(),傳入參數為位元組數組,返回值為Block
funcDeserializeBlock(blockBytes[]byte)*Block{
//1.定義一個Block指針對象
varblockBlock
//2.初始化反序列化對象decoder
decoder:=gob.NewDecoder(bytes.NewReader(blockBytes))
//3.通過Decode()進行反序列化
err:=decoder.Decode(&block)
iferr!=nil{
log.Panic(err)
}
//4.返回block對象
return&block
}
二.BoltDB資料庫使用
1.BoltDB簡介Bolt是一個純粹Key/Value模型的程序。該項目的目標是為不需要完整資料庫伺服器(如Postgres或MySQL)的項目提供一個簡單,快速,可靠的資料庫。BoltDB只需要將其鏈接到你的應用程序代碼中即可使用BoltDB提供的API來高效的存取數據。而且BoltDB支持完全可序列化的ACID事務,讓應用程序可以更簡單的處理複雜操作。其源碼地址為:https://github.com/boltdb/bolt2.BoltDB特性BoltDB設計源於LMDB,具有以下特點:
使用Go語言編寫
不需要伺服器即可運行
支持數據結構
直接使用API存取數據,沒有查詢語句;
支持完全可序列化的ACID事務,這個特性比LevelDB強;
數據保存在內存映射的文件里。沒有wal、線程壓縮和垃圾回收;
通過COW技術,可實現無鎖的讀寫並發,但是無法實現無鎖的寫寫並發,這就註定了讀性能超高,但寫性能一般,適合與讀多寫少的場景。
BoltDB是一個Key/Value(鍵/值)存儲,這意味著沒有像SQL RDBMS(MySQL,PostgreSQL等)中的表,沒有行,沒有列。相反,數據作為鍵值對存儲(如在Golang Maps中)。鍵值對存儲在Buckets中,它們旨在對相似的對進行分組(這與RDBMS中的表類似)。因此,為了獲得Value(值),需要知道該Value所在的桶和鑰匙。
3.BoltDB簡單使用
//通過go get下載並import
import"github.com/boltdb/bolt"3.1 打開或創建資料庫
db,err:=bolt.Open("my.db",0600,nil)
iferr!=nil{
log.Fatal(err)
}
deferdb.Close()
執行注意點
如果通過goland程序運行創建的my.db會保存在$GOPATH /src/Project目錄下如果通過go build main.go ; ./main 執行生成的my.db,會保存在當前目錄$GOPATH /src/Project/package下3.2 資料庫操作3.2.1 創建資料庫表與數據寫入操作
//1. 調用Update方法進行數據的寫入
err=db.Update(func(tx*bolt.Tx)error{
//2.通過CreateBucket()方法創建BlockBucket(表),初次使用創建
b,err:=tx.CreateBucket([]byte("BlockBucket"))
iferr!=nil{
returnfmt.Errorf("Create bucket :%s",err)
}
//3.通過Put()方法往表裡面存儲一條數據(key,value),注意類型必須為[]byte
ifb!=nil{
err:=b.Put([]byte("l"), []byte("Send $100 TO Bruce"))
iferr!=nil{
log.Panic("數據存儲失敗..")
}
}
returnnil
})
//數據Update失敗,退出程序
iferr!=nil{
log.Panic(err)
}3.2.2 數據寫入
//1.打開資料庫
db,err:=bolt.Open("my.db",0600,nil)
iferr!=nil{
log.Fatal(err)
}
deferdb.Close()
err=db.Update(func(tx*bolt.Tx)error{
//2.通過Bucket()方法打開BlockBucket表
b:=tx.Bucket([]byte("BlockBucket"))
//3.通過Put()方法往表裡面存儲數據
ifb!=nil{
err:=b.Put([]byte("l"), []byte("Send $200 TO Fengyingcong"))
err=b.Put([]byte("ll"), []byte("Send $100 TO Bruce"))
iferr!=nil{
log.Panic("數據存儲失敗..")
}
}
returnnil
})
//更新失敗
iferr!=nil{
log.Panic(err)
}3.2.3 數據讀取
//1.打開資料庫
db,err:=bolt.Open("my.db",0600,nil)
iferr!=nil{
log.Fatal(err)
}
deferdb.Close()
//2.通過View方法獲取數據
err=db.View(func(tx*bolt.Tx)error{
//3.打開BlockBucket表,獲取表對象
b:=tx.Bucket([]byte("BlockBucket"))
//4.Get()方法通過key讀取value
ifb!=nil{
data:=b.Get([]byte("l"))
fmt.Printf("%s
",data)
data=b.Get([]byte("ll"))
fmt.Printf("%s
",data)
}
returnnil
})
iferr!=nil{
log.Panic(err)
}
三.通過BoltDB存儲區塊
該代碼包含對BoltDB的資料庫創建,表創建,區塊添加,區塊查詢操作
//1.創建一個區塊對象block
block:=BLC.NewBlock("Send $500 to Tom",1, []byte{,,,,,,,,,,,,,,,})
//2. 列印區塊對象相關信息
fmt.Printf("區塊的Hash信息為: %x
",block.Hash)
fmt.Printf("區塊的數據信息為: %v
",string(block.Data))
fmt.Printf("區塊的隨機數為: %d
",block.Nonce)
//3. 打開資料庫
db,err:=bolt.Open("my.db",0600,nil)
iferr!=nil{
log.Fatal(err)
}
deferdb.Close()
//4. 更新數據
err=db.Update(func(tx*bolt.Tx)error{
//4.1 打開BlockBucket表對象
b:=tx.Bucket([]byte("blocks"))
//4.2 如果表對象不存在,創建表對象
ifb==nil{
b,err=tx.CreateBucket([]byte("blocks"))
iferr!=nil{
log.Panic("Block Table Create Failed")
}
}
//4.3 往表裡面存儲一條數據(key,value)
err=b.Put([]byte("l"),block.Serialize())
iferr!=nil{
log.Panic("數據存儲失敗..")
}
returnnil
})
//更新失敗,返回錯誤
iferr!=nil{
log.Panic("數據更新失敗")
}
//5. 查看數據
err=db.View(func(tx*bolt.Tx)error{
//5.1打開BlockBucket表對象
b:=tx.Bucket([]byte("blocks"))
ifb!=nil{
//5.2 取出key=「l」對應的value
blockData:=b.Get([]byte("l"))
//5.3反序列化
block:=BLC.DeserializeBlock(blockData)
//6. 列印區塊對象相關信息
fmt.Printf("區塊的Hash信息為: %x
",block.Hash)
fmt.Printf("區塊的數據信息為: %v
",string(block.Data))
fmt.Printf("區塊的隨機數為: %d
",block.Nonce)
}
returnnil
})
//數據查看失敗
iferr!=nil{
log.Panic("數據更新失敗")
}
四.區塊鏈基於BoltDB存儲區塊
1. 定義區塊屬性與方法 Block.go1.1 導入相關庫
import(
"time"
"bytes"
"encoding/gob"//編碼解碼庫
"log"
)1.2 定義區塊屬性
//定義區塊屬性
typeBlockstruct{
//1.區塊高度,也就是區塊的編號,第幾個區塊
Heightint64
//2.上一個區塊的Hash值
PreBlockHash[]byte
//3.交易數據(最終都屬於transaction 事務)
Data[]byte
//4.創建時間的時間戳
TimeStampint64
//5.當前區塊的Hash值
Hash[]byte
//6.Nonce 隨機數,用於驗證工作量證明
Nonceint64
}1.3 序列化區塊
// 序列化區塊(Block類對象轉成位元組數組)
func(block*Block)Serialize() []byte{
varresultbytes.Buffer
encoder:=gob.NewEncoder(&result)
err:=encoder.Encode(block)
iferr!=nil{
log.Panic(err)
}
returnresult.Bytes()
}1.4 反序列化
//反序列化
funcDeserializeBlock(blockBytes[]byte)*Block{
varblockBlock
decoder:=gob.NewDecoder(bytes.NewReader(blockBytes))
err:=decoder.Decode(&block)
iferr!=nil{
log.Panic(err)
}
return&block
}1.5 創建區塊
//傳入參數data,height,PreBlockHash,返回值*Block
funcNewBlock(datastring,heightint64,PreBlockHash[]byte)*Block{
//根據傳入參數創建區塊
block:=&Block{
height,
PreBlockHash,
[]byte(data),
time.Now().Unix(),
nil,
,
}
//調用工作量證明的方法,並且返回有效的Hash和Nonce值
//創建pow對象
pow:=NewProofOfWork(block)
//通過Run()方法進行挖礦驗證
hash,nonce:=pow.Run(height)
//將Nonce,Hash賦值給類對象屬性
block.Hash=hash[:]
block.Nonce=nonce
returnblock
}1.6 創建創世區塊
//單獨為創世區塊定義的函數
funcCreateGenesisBlock(datastring)*Block{
returnNewBlock(data,1, []byte{,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,})
}1.7 代碼整合
packageBLC
import(
"time"
"bytes"
"encoding/gob"
"log"
)
//定義區塊
typeBlockstruct{
//1.區塊高度,也就是區塊的編號,第幾個區塊
Heightint64
//2.上一個區塊的Hash值
PreBlockHash[]byte
//3.交易數據(最終都屬於transaction 事務)
Data[]byte
//4.創建時間的時間戳
TimeStampint64
//5.當前區塊的Hash值
Hash[]byte
//6.Nonce 隨機數,用於驗證工作量證明
Nonceint64
}
// 序列化區塊(Block類對象轉成位元組數組)
func(block*Block)Serialize() []byte{
varresultbytes.Buffer
encoder:=gob.NewEncoder(&result)
err:=encoder.Encode(block)
iferr!=nil{
log.Panic(err)
}
returnresult.Bytes()
}
//反序列化,位元組數組==>區塊
funcDeserializeBlock(blockBytes[]byte)*Block{
varblockBlock
decoder:=gob.NewDecoder(bytes.NewReader(blockBytes))
err:=decoder.Decode(&block)
iferr!=nil{
log.Panic(err)
}
return&block
}
//1. 創建新的區塊
funcNewBlock(datastring,heightint64,PreBlockHash[]byte)*Block{
//創建區塊
block:=&Block{
height,
PreBlockHash,
[]byte(data),
time.Now().Unix(),
nil,
,
}
//創建pow對象
pow:=NewProofOfWork(block)
//調用工作量證明的方法,並且返回有效的Hash和Nonce值
hash,nonce:=pow.Run(height)
block.Hash=hash[:]
block.Nonce=nonce
returnblock
}
//2.生成創世區塊
funcCreateGenesisBlock(datastring)*Block{
returnNewBlock(data,1, []byte{,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,})
}
2.定義POW屬性與方法ProofOfWork.go
2.1 導入相關庫
import(
"math/big"//導入big包
"bytes"
"crypto/sha256"//導入sha256密碼庫
"fmt"
"time"
)
關於big包的說明
使用Go語言中的float64類型進行浮點運算,返回結果將精確到15位,足以滿足大多數的任務。當對超出int64或者uint64 類型這樣的大數進行計算時,如果對精度沒有要求,float32 或者 float64 可以勝任,但如果對精度有嚴格要求的時候,我們不能使用浮點數,在內存中它們只能被近似的表示。對於整數的高精度計算 Go 語言中提供了 big 包。其中包含了 math 包:有用來表示大整數的 big.Int 和表示大有理數的 big.Rat 類型(可以表示為 2/5 或 3.1416 這樣的分數,而不是無理數或 π)。這些類型可以實現任意位類型的數字,只要內存足夠大。缺點是更大的內存和處理開銷使它們使用起來要比內置的數字類型慢很多。大的整型數字是通過 big.NewInt(n) 來構造的,其中 n 為 int64 類型整數。而大有理數是用過 big.NewRat(N,D) 方法構造。N(分子)和 D(分母)都是 int64 型整數。因為 Go 語言不支持運算符重載,所以所有大數字類型都有像是 Add() 和 Mul() 這樣的方法。它們作用於作為 receiver 的整數和有理數,大多數情況下它們修改 receiver 並以 receiver 作為返回結果。因為沒有必要創建 big.Int 類型的臨時變數來存放中間結果,所以這樣的運算可通過內存鏈式存儲。2.2 定義POW結構體屬性
typeProofOfWorkstruct{
Block*Block//當前要驗證的區塊
target*big.Int//大數存儲,區塊難度
}2.3 定義常量 TargetBit
//256位Hash裡面至少要有16個零0000 0000 0000 0000
constTargetBit=162.4 拼接區塊屬性
func(pow*ProofOfWork)prePareData(nonceint) []byte{
data:=bytes.Join(
[][]byte{
pow.Block.PreBlockHash,
pow.Block.Data,
IntToHex(pow.Block.TimeStamp),
IntToHex(int64(TargetBit)),
IntToHex(int64(nonce)),
IntToHex(int64(pow.Block.Height)),
},
[]byte{},
)
returndata
}2.5 創建工作量證明對象
funcNewProofOfWork(block*Block)*ProofOfWork{
//1.創建初始值為1的target
target:=big.NewInt(1)
//2.左移256-TargetBit
target=target.Lsh(target,256-TargetBit)
return&ProofOfWork{block,target}
}2.6 通過POW類對象Run方法挖礦
func(proofOfWork*ProofOfWork)Run(numint64) ([]byte,int64) {
//初始化隨機數nonce為0
nonce:=
//存儲新生成的hash值
varhashIntbig.Int
//存儲hash值
varhash[32]byte
for{
//1. 將Block的屬性拼接成位元組數組,注意,參數為Nonce
databytes:=proofOfWork.prePareData(nonce)
//2.將拼接後的位元組數組生成Hash
hash=sha256.Sum256(databytes)
//3. 將hash存儲至hashInt
hashInt.SetBytes(hash[:])
//4.判斷hashInt是否小於Block裡面的Target
// Cmp compares x and y and returns:
//
// -1 if x
// 0 if x == y
// +1 if x > y
//此處需要滿足hashInt(y)小於設置的target(x)即 x > y,則挖礦成功
ifproofOfWork.target.Cmp(&hashInt)==1{
fmt.Printf("第%d個區塊,挖礦成功:%x
",num,hash)
fmt.Println(time.Now())
time.Sleep(time.Second*2)
//挖礦成功,退出循環
break
}
nonce++
}
returnhash[:],int64(nonce)
}2.7 代碼整合
packageBLC
import(
"math/big"
"bytes"
"crypto/sha256"
"fmt"
"time"
)
typeProofOfWorkstruct{
Block*Block//當前要驗證的區塊
target*big.Int//大數存儲,區塊難度
}
//數據拼接,返回位元組數組
func(pow*ProofOfWork)prePareData(nonceint) []byte{
data:=bytes.Join(
[][]byte{
pow.Block.PreBlockHash,
pow.Block.Data,
IntToHex(pow.Block.TimeStamp),
IntToHex(int64(TargetBit)),
IntToHex(int64(nonce)),
IntToHex(int64(pow.Block.Height)),
},
[]byte{},
)
returndata
}
//256位Hash裡面至少要有16個零0000 0000 0000 0000
constTargetBit=16
//判斷挖礦得到的區塊是否有效
func(proofOfWork*ProofOfWork)IsValid()bool{
//1.proofOfWork.Block.Hash
//2.proofOfWork.Target
varhashIntbig.Int
hashInt.SetBytes(proofOfWork.Block.Hash)
ifproofOfWork.target.Cmp(&hashInt)==1{
returntrue
}
returnfalse
}
//創建新的工作量證明對象
funcNewProofOfWork(block*Block)*ProofOfWork{
/*1.創建初始值為1的target
0000 0001
8 - 2
*/
target:=big.NewInt(1)
//2.左移256-targetBit
target=target.Lsh(target,256-TargetBit)
return&ProofOfWork{block,target}
}
func(proofOfWork*ProofOfWork)Run(numint64) ([]byte,int64) {
nonce:=
varhashIntbig.Int//存儲新生成的hash值
varhash[32]byte
for{
//1. 將Block的屬性拼接成位元組數組
databytes:=proofOfWork.prePareData(nonce)
//2.生成Hash
hash=sha256.Sum256(databytes)
//fmt.Printf("挖礦中..%x
", hash)
//3. 將hash存儲至hashInt
hashInt.SetBytes(hash[:])
//4.判斷hashInt是否小於Block裡面的target
// Cmp compares x and y and returns:
//
// -1 if x
// 0 if x == y
// +1 if x > y
//需要hashInt(y)小於設置的target(x)
ifproofOfWork.target.Cmp(&hashInt)==1{
//fmt.Println("挖礦成功", hashInt)
fmt.Printf("第%d個區塊,挖礦成功:%x
",num,hash)
fmt.Println(time.Now())
time.Sleep(time.Second*2)
break
}
nonce++
}
returnhash[:],int64(nonce)
}
3.區塊鏈屬性與方法 BlockChain.go
3.1 導入相關庫
import(
"github.com/boltdb/bolt"//導入bolt庫,用於操作DB
"log"
)3.2 創建常量
constdbName="blockchain.db"//資料庫名
constblockTableName="blocks"//表名3.3 創建區塊鏈屬性
typeBlockChainstruct{
Tip[]byte//區塊鏈裡面最後一個區塊的Hash
DB*bolt.DB//資料庫
}3.4 創建帶有創世區塊的區塊鏈
funcCreateBlockChainWithGenesisBlock()*BlockChain{
//1. 創建或者打開資料庫
db,err:=bolt.Open(dbName,0600,nil)
iferr!=nil{
log.Fatal(err)
}
//2.創建存儲
varblockHash[]byte
//3.更新數據
err=db.Update(func(tx*bolt.Tx)error{
//獲取表
b:=tx.Bucket([]byte(blockTableName))
ifb==nil{//如果不存在block表,則進行創建
b,err=tx.CreateBucket([]byte(blockTableName))
iferr!=nil{
log.Panic(err)
}
}
//4.調用Block.go中的函數CreateGenesisBlock創建創世區塊
genesisBlock:=CreateGenesisBlock("Genesis Data..")
//5.將創世區塊存儲至表中(key=當前區塊的Hash值,value=當前區塊的序列化位元組數組)
err:=b.Put(genesisBlock.Hash,genesisBlock.Serialize())
iferr!=nil{
log.Panic(err)
}
//6.存儲最新的區塊鏈的hash(key="l",value=當前區塊的Hash值)
err=b.Put([]byte("l"),genesisBlock.Hash)
iferr!=nil{
log.Panic(err)
}
//7.定義該區塊鏈的Tip值為最新區塊的Hash值
blockHash=genesisBlock.Hash
returnnil
})
//返回區塊鏈對象(Tip:blockHash,DB:進行Update操作的db對象)
return&BlockChain{blockHash,db}
}3.5 添加新區塊至區塊鏈中
func(blc*BlockChain)AddBlockChain(datastring) {
//獲取db對象blc.DB
err:=blc.DB.Update(func(tx*bolt.Tx)error{
//1.獲取表
b:=tx.Bucket([]byte(blockTableName))
//2.創建新區塊
ifb!=nil{
//通過Key:blc.Tip獲取Value(區塊序列化位元組數組)
byteBytes:=b.Get(blc.Tip)
//反序列化出最新區塊(上一個區塊)對象
block:=DeserializeBlock(byteBytes)
//3.通過NewBlock進行挖礦生成新區塊newBlock
newBlock:=NewBlock(data,block.Height+1,block.Hash)
//4.將最新區塊序列化並且存儲到資料庫中(key=新區塊的Hash值,value=新區塊序列化)
err:=b.Put(newBlock.Hash,newBlock.Serialize())
iferr!=nil{
log.Panic(err)
}
/*5.更新資料庫中"l"對應的Hash為新區塊的Hash值
用途:便於通過該Hash值找到對應的Block序列化,從而找到上一個Block對象,為生成新區塊函數NewBlock提供高度Height與上一個區塊的Hash值PreBlockHash
*/
err=b.Put([]byte("l"),newBlock.Hash)
iferr!=nil{
log.Panic(err)
}
//6. 更新Tip值為新區塊的Hash值
blc.Tip=newBlock.Hash
}
returnnil
})
iferr!=nil{
log.Panic(err)
}
}3.6 代碼整合
packageBLC
import(
"github.com/boltdb/bolt"
"log"
)
constdbName="blockchain.db"//資料庫名
constblockTableName="blocks"//表名
typeBlockChainstruct{
Tip[]byte//區塊鏈裡面最後一個區塊的Hash
DB*bolt.DB//資料庫
}
//1.創建帶有創世區塊的區塊鏈
funcCreateBlockChainWithGenesisBlock()*BlockChain{
//創建或者打開資料庫
db,err:=bolt.Open(dbName,0600,nil)
iferr!=nil{
log.Fatal(err)
}
varblockHash[]byte
err=db.Update(func(tx*bolt.Tx)error{
//獲取表
b:=tx.Bucket([]byte(blockTableName))
ifb==nil{
b,err=tx.CreateBucket([]byte(blockTableName))
iferr!=nil{
log.Panic(err)
}
}
//創建創世區塊
genesisBlock:=CreateGenesisBlock("Genesis Data..")
//將創世區塊存儲至表中
err:=b.Put(genesisBlock.Hash,genesisBlock.Serialize())
iferr!=nil{
log.Panic(err)
}
//存儲最新的區塊鏈的hash
err=b.Put([]byte("l"),genesisBlock.Hash)
iferr!=nil{
log.Panic(err)
}
blockHash=genesisBlock.Hash
returnnil
})
//返回區塊鏈對象
return&BlockChain{blockHash,db}
}
func(blc*BlockChain)AddBlockChain(datastring) {
err:=blc.DB.Update(func(tx*bolt.Tx)error{
//1.獲取表
b:=tx.Bucket([]byte(blockTableName))
//2.創建新區塊
ifb!=nil{
//獲取最新區塊
byteBytes:=b.Get(blc.Tip)
//反序列化
block:=DeserializeBlock(byteBytes)
//3. 將區塊序列化並且存儲到資料庫中
newBlock:=NewBlock(data,block.Height+1,block.Hash)
err:=b.Put(newBlock.Hash,newBlock.Serialize())
iferr!=nil{
log.Panic(err)
}
//4.更新資料庫中"l"對應的Hash
err=b.Put([]byte("l"),newBlock.Hash)
iferr!=nil{
log.Panic(err)
}
//5. 更新blockchain的Tip
blc.Tip=newBlock.Hash
}
returnnil
})
iferr!=nil{
log.Panic(err)
}
}
4.自定義幫助庫:help.go
packageBLC
import(
"bytes"
"encoding/binary"
"log"
)
//將int64轉換為位元組數組
funcIntToHex(numint64) []byte{
buff:=new(bytes.Buffer)
err:=binary.Write(buff,binary.BigEndian,num)
iferr!=nil{
log.Panic(err)
}
returnbuff.Bytes()
}
5. 測試代碼 main.go
packagemain
import(
"publicChain/BLC"//引用BLC包
"fmt"
)
funcmain() {
fmt.Println("開始挖礦")
//創建創世區塊
blockChain:=BLC.CreateBlockChainWithGenesisBlock()
deferblockChain.DB.Close()
//創建新的區塊
blockChain.AddBlockChain("Send $100 to Bruce")
blockChain.AddBlockChain("Send $200 to Apple")
blockChain.AddBlockChain("Send $300 to Alice")
blockChain.AddBlockChain("Send $400 to Bob")
}
挖礦成功,數據已經存入blockchain.db中
五.遍歷區塊鏈區塊信息
1.定義遍歷函數上述操作已經將區塊挖出並且通過序列化存儲至基於BoltDB的區塊鏈中,本節將通過區塊鏈對象中Tip的值逐步取出所有區塊的相關信息,該方法PrintChain()集成在BlockChain.go中
func(blc*BlockChain)PrintChain() {
//1.定義區塊對象block
varblock*Block
//2.定義當前區塊Hash值對象currentHash
varcurrentHash=blc.Tip
//3.從資料庫對象blc.DB中循環取值
for{
err:=blc.DB.View(func(tx*bolt.Tx)error{
//4. 打開表 blockTableName
b:=tx.Bucket([]byte(blockTableName))
ifb!=nil{
//5.通過獲取Key:currentHash獲得對應的Value:當前區塊的序列化位元組數組
blockBytes:=b.Get(currentHash)
//6.反序列化位元組數組,獲取最新區塊對象
block=DeserializeBlock(blockBytes)
//7. 列印獲取到的最新區塊對象的相關屬性
fmt.Printf("Height:%d
",block.Height)
fmt.Printf("PreBlockHash:%x
",block.PreBlockHash)
fmt.Printf("Data:%s
",block.Data)
fmt.Printf("TimeStamp:%s
",time.Unix(block.TimeStamp,).Format("2006-01-02 03:04:05 PM"))
fmt.Printf("Hash:%x
",block.Hash)
fmt.Printf("Nonce:%d
",block.Nonce)
}
returnnil
})
iferr!=nil{
log.Panic("資料庫查詢失敗",err)
}
//8.定義存儲最新區塊對應的上一個區塊Hash值
varhashIntbig.Int
hashInt.SetBytes(block.PreBlockHash)
//9.判斷該區塊是否為創世區塊,如果是,則退出循環遍歷
ifbig.NewInt().Cmp(&hashInt)=={
fmt.Println("該區塊為創世區塊,退出程序")
break
}
/*10.如果該區塊不是創世區塊,則將用於表數據查詢的key的值currentHash的值用上一個區塊Hash賦值,繼續進行取值循環,直至找到創世區塊
*/
currentHash=block.PreBlockHash
fmt.Println()
}
}2.測試代碼main.go
新增blockChain.PrintChain()
packagemain
import(
"publicChain/part17-數據迭代/BLC"
"fmt"
)
funcmain() {
fmt.Println("開始挖礦")
//創建創世區塊
blockChain:=BLC.CreateBlockChainWithGenesisBlock()
deferblockChain.DB.Close()
////創建新的區塊
blockChain.AddBlockChain("Send $100 to Bruce")
blockChain.AddBlockChain("Send $200 to Apple")
blockChain.AddBlockChain("Send $300 to Alice")
blockChain.AddBlockChain("Send $400 to Bob")
blockChain.PrintChain()
}
通過遍歷函數,列印出從最後一個區塊至創世區塊的所有信息。
六.通過迭代器遍歷區塊信息
1.迭代器屬性與方法 BlockChainIterator.go1.1 導入相關庫
import(
"github.com/boltdb/bolt"//導入BoltDB庫
"log"
)1.2 定義屬性
typeBlockChainIteratorstruct{
CurrentHash[]byte// 保存當前的區塊Hash值
DB*bolt.DB//DB對象
}1.3 迭代方法
func(blockchainIterator*BlockChainIterator)Next()*Block{
//1.定義Block對象block
varblock*Block
//2.操作DB對象blockchainIterator.DB
err:=blockchainIterator.DB.View(func(tx*bolt.Tx)error{
//3.打開表對象blockTableName
b:=tx.Bucket([]byte(blockTableName))
ifb!=nil{
//Get()方法通過Key:當前區塊的Hash值獲取當前區塊的序列化信息
currentBlockBytes:=b.Get(blockchainIterator.CurrentHash)
//反序列化出當前的區塊
block=DeserializeBlock(currentBlockBytes)
//更新迭代器裡面的CurrentHash
blockchainIterator.CurrentHash=block.PreBlockHash
}
returnnil
})
iferr!=nil{
log.Panic(err)
}
returnblock
}
2.修改遍歷方法 BlockChain.go
2.1 添加 Iterator() 方法
func(blockchain*BlockChain)Iterator()*BlockChainIterator{
return&BlockChainIterator{blockchain.Tip,blockchain.DB}
}2.2 修改PrintChain()方法
func(blc*BlockChain)PrintChain() {
//創建迭代類對象blockchainIterator
blockchainIterator:=blc.Iterator()
for{
//通過Next()方法獲取當前區塊,並更新區塊鏈對象保存的Hash值為上一個區塊的Hash值
block:=blockchainIterator.Next()
fmt.Printf("Height:%d
",block.Height)
fmt.Printf("PreBlockHash:%x
",block.PreBlockHash)
fmt.Printf("Data:%s
",block.Data)
fmt.Printf("TimeStamp:%s
",time.Unix(block.TimeStamp,).Format("2006-01-02 03:04:05 PM"))
fmt.Printf("Hash:%x
",block.Hash)
fmt.Printf("Nonce:%d
",block.Nonce)
varhashIntbig.Int
hashInt.SetBytes(block.PreBlockHash)
ifbig.NewInt().Cmp(&hashInt)=={
break
}
}
}通過迭代器的方式其實與直接遍歷區塊信息的結果沒有任何差別,只是對數據操作的代碼進行了封裝,也能從一定程度上對程序進行了簡單的解耦合設計,推薦使用。
TAG:區塊鏈學習聯盟 |