上一節(jié)學(xué)習(xí)了基于go語(yǔ)言的數(shù)據(jù)庫(kù)boltDB的基本使用镊辕,這一節(jié)用boltDB實(shí)現(xiàn)區(qū)塊鏈的數(shù)據(jù)持久化官紫。
存儲(chǔ)方式
區(qū)塊鏈的數(shù)據(jù)主要集中在各個(gè)區(qū)塊上萎战,所以區(qū)塊鏈的數(shù)據(jù)持久化即可轉(zhuǎn)化為對(duì)每一個(gè)區(qū)塊的存儲(chǔ)该酗。boltDB是KV存儲(chǔ)方式碍现,因此這里我們可以以區(qū)塊的哈希值為Key悠砚,區(qū)塊為Value晓勇。
此外,我們還需要存儲(chǔ)最新區(qū)塊的哈希值灌旧。這樣绑咱,就可以找到最新的區(qū)塊,然后按照區(qū)塊存儲(chǔ)的上個(gè)區(qū)塊哈希值找到上個(gè)區(qū)塊枢泰,以此類推便可以找到區(qū)塊鏈上所有的區(qū)塊描融。
區(qū)塊序列化
我們知道,boltDB存儲(chǔ)的鍵值對(duì)的數(shù)據(jù)類型都是字節(jié)數(shù)組衡蚂。所以在存儲(chǔ)區(qū)塊前需要對(duì)區(qū)塊進(jìn)行序列化窿克,當(dāng)然讀取區(qū)塊的時(shí)候就需要做反序列化處理。
沒什么難點(diǎn)毛甲,都是借助系統(tǒng)方法實(shí)現(xiàn)年叮。廢話少說(shuō)上代碼。
序列化
//區(qū)塊序列化
func (block *Block) Serialize() []byte {
var result bytes.Buffer
encoder := gob.NewEncoder(&result)
err := encoder.Encode(block)
if err != nil{
log.Panic(err)
}
return result.Bytes()
}
反序列化
//區(qū)塊反序列化
func DeSerializeBlock(blockBytes []byte) *Block {
var block *Block
dencoder := gob.NewDecoder(bytes.NewReader(blockBytes))
err := dencoder.Decode(&block)
if err != nil{
log.Panic(err)
}
return block
}
區(qū)塊鏈類
區(qū)塊鏈結(jié)構(gòu)
之前定義的區(qū)塊鏈結(jié)構(gòu)是這樣的:
type Blockchain struct {
//有序區(qū)塊的數(shù)組
Blocks [] *Block
}
但是這樣的結(jié)構(gòu)玻募,每次運(yùn)行程序區(qū)塊數(shù)組都是從零開始創(chuàng)建只损,并不能實(shí)現(xiàn)區(qū)塊鏈的數(shù)據(jù)持久化。這里的數(shù)組屬性要改為boltDB類型的區(qū)塊數(shù)據(jù)庫(kù)七咧,同時(shí)還必須有一個(gè)存儲(chǔ)當(dāng)前區(qū)塊鏈最新區(qū)塊哈希的屬性跃惫。
type Blockchain struct {
//最新區(qū)塊的Hash
Tip []byte
//存儲(chǔ)區(qū)塊的數(shù)據(jù)庫(kù)
DB *bolt.DB
}
相關(guān)數(shù)據(jù)庫(kù)常量
//相關(guān)數(shù)據(jù)庫(kù)屬性
const dbName = "chaorsBlockchain.db"
const blockTableName = "chaorsBlocks"
const newestBlockKey = "chNewestBlockKey"
創(chuàng)建區(qū)塊鏈
//1.創(chuàng)建帶有創(chuàng)世區(qū)塊的區(qū)塊鏈
func CreateBlockchainWithGensisBlock() *Blockchain {
var blockchain *Blockchain
//判斷數(shù)據(jù)庫(kù)是否存在
if IsDBExists(dbName) {
db, err := bolt.Open(dbName, 0600, nil)
if err != nil {
log.Fatal(err)
}
err = db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
if b != nil {
hash := b.Get([]byte(newestBlockKey))
blockchain = &Blockchain{hash, db}
//fmt.Printf("%x", hash)
}
return nil
})
if err != nil {
log.Panic(err)
}
//blockchain.Printchain()
//os.Exit(1)
return blockchain
}
//創(chuàng)建并打開數(shù)據(jù)庫(kù)
db, err := bolt.Open(dbName, 0600, nil)
if err != nil {
log.Fatal(err)
}
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
//blockTableName不存在再去創(chuàng)建表
if b == nil {
b, err = tx.CreateBucket([]byte(blockTableName))
if err != nil {
log.Panic(err)
}
}
if b != nil {
//創(chuàng)世區(qū)塊
gensisBlock := CreateGenesisBlock("Gensis Block...")
//存入數(shù)據(jù)庫(kù)
err := b.Put(gensisBlock.Hash, gensisBlock.Serialize())
if err != nil {
log.Panic(err)
}
//存儲(chǔ)最新區(qū)塊hash
err = b.Put([]byte(newestBlockKey), gensisBlock.Hash)
if err != nil {
log.Panic(err)
}
blockchain = &Blockchain{gensisBlock.Hash, db}
}
return nil
})
//更新數(shù)據(jù)庫(kù)失敗
if err != nil {
log.Fatal(err)
}
return blockchain
}
新增區(qū)塊
前面我們寫的這個(gè)方法為:
func (blc *Blockchain) AddBlockToBlockchain(data string, height int64, prevHash []byte) {
仔細(xì)看發(fā)現(xiàn)啸蜜,參數(shù)好多顯得巨繁瑣。那是否有些參數(shù)是沒必要傳遞的呢辈挂?
我們既然用數(shù)據(jù)庫(kù)實(shí)現(xiàn)了區(qū)塊鏈的數(shù)據(jù)持久化,這里的高度height可以根據(jù)上個(gè)區(qū)塊高度自增裹粤,prevHash也可以從數(shù)據(jù)庫(kù)中取出上個(gè)區(qū)塊而得到终蒂。因此,從今天開始遥诉,該方法省去這兩個(gè)參數(shù)拇泣。
//2.新增一個(gè)區(qū)塊到區(qū)塊鏈
func (blc *Blockchain) AddBlockToBlockchain(data string) {
err := blc.DB.Update(func(tx *bolt.Tx) error {
//1.取表
b := tx.Bucket([]byte(blockTableName))
if b != nil {
//2.height,prevHash都可以從數(shù)據(jù)庫(kù)中取到 當(dāng)前最新區(qū)塊即添加后的上一個(gè)區(qū)塊
blockBytes := b.Get(blc.Tip)
block := DeSerializeBlock(blockBytes)
//3.創(chuàng)建新區(qū)快
newBlock := NewBlock(data, block.Height+1, block.Hash)
//4.區(qū)塊序列化入庫(kù)
err := b.Put(newBlock.Hash, newBlock.Serialize())
if err != nil {
log.Fatal(err)
}
//5.更新數(shù)據(jù)庫(kù)里最新區(qū)塊
err = b.Put([]byte(newestBlockKey), newBlock.Hash)
if err != nil {
log.Fatal(err)
}
//6.更新區(qū)塊鏈最新區(qū)塊
blc.Tip = newBlock.Hash
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
區(qū)塊鏈遍歷
//3.遍歷輸出所有區(qū)塊信息 --> 以后一般使用優(yōu)化后的迭代器方法(見3.X)
func (blc *Blockchain) Printchain() {
var block *Block
//當(dāng)前遍歷的區(qū)塊hash
var curHash []byte = blc.Tip
for {
err := blc.DB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
if b != nil {
blockBytes := b.Get(curHash)
block = DeSerializeBlock(blockBytes)
/**時(shí)間戳格式化 Format里的年份必須是固定的!0狻霉翔!
這個(gè)好像是go誕生的時(shí)間
time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05")
"2006-01-02 15:04:05"格式固定,改變其他也可能會(huì)出錯(cuò)
*/
fmt.Printf("\n#####\nHeight:%d\nPrevHash:%x\nHash:%x\nData:%s\nTime:%s\nNonce:%d\n#####\n",
block.Height, block.PrevBlockHash, block.Hash, block.Data, time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05"), block.Nonce)
}
return nil
})
if err != nil {
log.Fatal(err)
}
var hashInt big.Int
hashInt.SetBytes(block.PrevBlockHash)
//遍歷到創(chuàng)世區(qū)塊苞笨,跳出循環(huán) 創(chuàng)世區(qū)塊哈希為0
if big.NewInt(0).Cmp(&hashInt) == 0 {
break
}
curHash = block.PrevBlockHash
}
}
注意:
time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05") goLang這里真是奇葩啊……時(shí)間戳格式化只能寫"2006-01-02 15:04:05"债朵,一個(gè)數(shù)丟不能寫錯(cuò),不然你會(huì)”被穿越“的F倌P蚵!據(jù)說(shuō)這個(gè)日期是go語(yǔ)言的誕生日期粤咪,還真是傲嬌啊谚中,生怕大家不知道嗎?寥枝?宪塔?
判斷區(qū)塊鏈數(shù)據(jù)庫(kù)是否存在
//判斷數(shù)據(jù)庫(kù)是否存在
func IsDBExists(dbName string) bool {
//if _, err := os.Stat(dbName); os.IsNotExist(err) {
//
// return false
//}
_, err := os.Stat(dbName)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return true
}
區(qū)塊鏈迭代器
對(duì)區(qū)塊鏈區(qū)塊的遍歷上面已經(jīng)實(shí)現(xiàn),但是還可以優(yōu)化囊拜。我們不難發(fā)現(xiàn)區(qū)塊鏈的區(qū)塊遍歷類似于單向鏈表的遍歷某筐,那么我們能不能制造一個(gè)像鏈表的Next屬性似的迭代器,只要通過不斷地訪問Next就能遍歷所有的區(qū)塊冠跷?
話都說(shuō)到這份上了来吩,答案當(dāng)然是肯當(dāng)?shù)摹?/p>
BlockchainIterator
//區(qū)塊鏈迭代器
type BlockchainIterator struct {
//當(dāng)前遍歷hash
CurrHash []byte
//區(qū)塊鏈數(shù)據(jù)庫(kù)
DB *bolt.DB
}
Next迭代方法
func (blcIterator *BlockchainIterator) Next() *Block {
var block *Block
//數(shù)據(jù)庫(kù)查詢
err := blcIterator.DB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
if b != nil {
//獲取當(dāng)前迭代器對(duì)應(yīng)的區(qū)塊
currBlockBytes := b.Get(blcIterator.CurrHash)
block = DeSerializeBlock(currBlockBytes)
//更新迭代器
blcIterator.CurrHash = block.PrevBlockHash
}
return nil
})
if err != nil {
log.Fatal(err)
}
return block
}
怎么用?
1.在Blockchain類新增一個(gè)生成當(dāng)前區(qū)塊鏈的迭代器的方法
//生成當(dāng)前區(qū)塊鏈迭代器的方法
func (blc *Blockchain) Iterator() *BlockchainIterator {
return &BlockchainIterator{blc.Tip, blc.DB}
}
2.修改之前的Printchain方法
//3.X 優(yōu)化區(qū)塊鏈遍歷方法
func (blc *Blockchain) Printchain() {
//迭代器
blcIterator := blc.Iterator()
for {
block := blcIterator.Next()
fmt.Printf("\n#####\nHeight:%d\nPrevHash:%x\nHash:%x\nData:%s\nTime:%s\nNonce:%d\n#####\n",
block.Height, block.PrevBlockHash, block.Hash, block.Data, time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05"),block.Nonce)
var hashInt big.Int
hashInt.SetBytes(block.PrevBlockHash)
if big.NewInt(0).Cmp(&hashInt) == 0 {
break
}
}
}
是不是發(fā)現(xiàn)遍歷區(qū)塊的代碼相對(duì)簡(jiǎn)潔了蔽莱,這里把數(shù)據(jù)庫(kù)訪問和區(qū)塊迭代的代碼分離到了BlockchainIterator里實(shí)現(xiàn)弟疆,也符合程序設(shè)計(jì)的單一職責(zé)原則。
main函數(shù)測(cè)試
package main
import (
"chaors.com/LearnGo/publicChaorsChain/part4-DataPersistence-Prototype/BLC"
)
func main() {
blockchain := BLC.CreateBlockchainWithGensisBlock()
defer blockchain.DB.Close()
//添加一個(gè)新區(qū)快
blockchain.AddBlockToBlockchain("first Block")
blockchain.AddBlockToBlockchain("second Block")
blockchain.AddBlockToBlockchain("third Block")
blockchain.Printchain()
}
1.首次運(yùn)行(這時(shí)不存在數(shù)據(jù)庫(kù))
2.注釋掉三句AddBlockToBlockchain代碼盗冷,再次運(yùn)行
這次我們并沒有添加區(qū)塊怠苔,所以打印區(qū)沒有挖礦的過程。但是打印的區(qū)塊是上次AddBlockToBlockchain添加的仪糖,說(shuō)明區(qū)塊存儲(chǔ)成功了柑司。
3.修改AddBlockToBlockchain段代碼迫肖,再次運(yùn)行
blockchain.AddBlockToBlockchain("4th Block")
blockchain.AddBlockToBlockchain("5th Block")
我們看到,在原有區(qū)塊信息不變的情況攒驰,新挖出的區(qū)塊成功添加到了區(qū)塊鏈數(shù)據(jù)庫(kù)中蟆湖。說(shuō)明我們的區(qū)塊鏈數(shù)據(jù)持久化實(shí)現(xiàn)成功了。
源代碼在這,喜歡的朋友記得給個(gè)小star玻粪,或者fork.也歡迎大家一起探討區(qū)塊鏈相關(guān)知識(shí)隅津,一起進(jìn)步!
更多原創(chuàng)區(qū)塊鏈技術(shù)文章請(qǐng)?jiān)L問chaors
.
.
.
.
互聯(lián)網(wǎng)顛覆世界劲室,區(qū)塊鏈顛覆互聯(lián)網(wǎng)!
---------------------------------------------20180624 20:16