go區(qū)塊鏈公鏈實(shí)戰(zhàn)0x03數(shù)據(jù)持久化

上一節(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ù))
mainTest_1
2.注釋掉三句AddBlockToBlockchain代碼盗冷,再次運(yùn)行
mainTest_2

這次我們并沒有添加區(qū)塊怠苔,所以打印區(qū)沒有挖礦的過程。但是打印的區(qū)塊是上次AddBlockToBlockchain添加的仪糖,說(shuō)明區(qū)塊存儲(chǔ)成功了柑司。

3.修改AddBlockToBlockchain段代碼迫肖,再次運(yùn)行
blockchain.AddBlockToBlockchain("4th Block")
blockchain.AddBlockToBlockchain("5th Block")
mainTest_3

我們看到,在原有區(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伦仍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子很洋,更是在濱河造成了極大的恐慌充蓝,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喉磁,死亡現(xiàn)場(chǎng)離奇詭異谓苟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)协怒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門娜谊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人斤讥,你說(shuō)我怎么就攤上這事纱皆。” “怎么了芭商?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵派草,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我铛楣,道長(zhǎng)近迁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任簸州,我火速辦了婚禮鉴竭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岸浑。我一直安慰自己搏存,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布矢洲。 她就那樣靜靜地躺著璧眠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上责静,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天淘菩,我揣著相機(jī)與錄音菩佑,去河邊找鬼锚赤。 笑死宽涌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腰鬼。 我是一名探鬼主播嵌赠,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垃喊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起袜炕,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤本谜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后偎窘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乌助,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年陌知,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了他托。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仆葡,死狀恐怖赏参,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沿盅,我是刑警寧澤把篓,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站腰涧,受9級(jí)特大地震影響韧掩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窖铡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一疗锐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧费彼,春花似錦滑臊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春聋庵,著一層夾襖步出監(jiān)牢的瞬間膘融,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工祭玉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氧映,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓脱货,卻偏偏與公主長(zhǎng)得像岛都,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子振峻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 本篇是"用Go構(gòu)建區(qū)塊鏈"系列的第三篇臼疫,主要對(duì)原文進(jìn)行翻譯。對(duì)應(yīng)原文如下: 1扣孟、介紹 到目前為止烫堤,我們已經(jīng)構(gòu)建了一...
    良__閱讀 794評(píng)論 1 3
  • 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitH...
    liuchengxu閱讀 1,926評(píng)論 6 14
  • 看完《后來(lái)的我們》 那句后來(lái)我們什么都有了 卻沒有了我們 不是我們的寫照 只是我和你不再是我們 最近夜里多夢(mèng)常常醒...
    Thepeat閱讀 272評(píng)論 0 2
  • 爸媽凤价,我想你們了鸽斟。 我爸是保安,一輩子做保安利诺。當(dāng)年單位沒黃的時(shí)候是保衛(wèi)科科長(zhǎng)富蓄,經(jīng)濟(jì)警察,可以拿槍的那種÷猓現(xiàn)在是...
    吃瓜群眾翟小羊閱讀 294評(píng)論 0 0