go公鏈實戰0x03資料持久化

來源:互聯網
上載者:User

上一節學習了基於go語言的資料庫boltDB的基本使用,這一節用boltDB實現區塊鏈的資料持久化。

儲存方式

區塊鏈的資料主要集中在各個區塊上,所以區塊鏈的資料持久化即可轉化為對每一個區塊的儲存。boltDB是KV儲存方式,因此這裡我們可以以區塊的雜湊值為Key,區塊為Value。

此外,我們還需要儲存最新區塊的雜湊值。這樣,就可以找到最新的區塊,然後按照區Block Storage的上個區塊雜湊值找到上個區塊,以此類推便可以找到區塊鏈上所有的區塊。

區塊序列化

我們知道,boltDB儲存的索引值對的資料類型都是位元組數組。所以在儲存區塊前需要對區塊進行序列化,當然讀取區塊的時候就需要做還原序列化處理。

沒什麼痛點,都是藉助系統方法實現。廢話少說上代碼。

序列化
//區塊序列化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()}
還原序列化
//區塊還原序列化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}

區塊鏈類

區塊鏈結構

之前定義的區塊鏈結構是這樣的:

type Blockchain struct {    //有序區塊的數組    Blocks [] *Block}

但是這樣的結構,每次運行程式區塊數組都是從零開始建立,並不能實現區塊鏈的資料持久化。這裡的數組屬性要改為boltDB類型的區塊資料庫,同時還必須有一個儲存當前區塊鏈最新區塊雜湊的屬性。

type Blockchain struct {    //最新區塊的Hash    Tip []byte    //儲存區塊的資料庫    DB *bolt.DB}
相關資料庫常量
//相關資料庫屬性const dbName = "chaorsBlockchain.db"const blockTableName = "chaorsBlocks"const newestBlockKey = "chNewestBlockKey"
建立區塊鏈
//1.建立帶有創世區塊的區塊鏈func CreateBlockchainWithGensisBlock() *Blockchain {    var blockchain *Blockchain    //判斷資料庫是否存在    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    }    //建立並開啟資料庫    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不存在再去建立表        if b == nil {            b, err = tx.CreateBucket([]byte(blockTableName))            if err != nil {                log.Panic(err)            }        }        if b != nil {            //創世區塊            gensisBlock := CreateGenesisBlock("Gensis Block...")            //存入資料庫            err := b.Put(gensisBlock.Hash, gensisBlock.Serialize())            if err != nil {                log.Panic(err)            }            //儲存最新區塊hash            err = b.Put([]byte(newestBlockKey), gensisBlock.Hash)            if err != nil {                log.Panic(err)            }            blockchain = &Blockchain{gensisBlock.Hash, db}        }        return nil    })    //更新資料庫失敗    if err != nil {        log.Fatal(err)    }    return blockchain}
新增區塊

前面我們寫的這個方法為:

func (blc *Blockchain) AddBlockToBlockchain(data string, height int64, prevHash []byte)  {

仔細看發現,參數好多顯得巨繁瑣。那是否有些參數是沒必要傳遞的呢?

我們既然用資料庫實現了區塊鏈的資料持久化,這裡的高度height可以根據上個區塊高度自增,prevHash也可以從資料庫中取出上個區塊而得到。因此,從今天開始,該方法省去這兩個參數。

//2.新增一個區塊到區塊鏈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都可以從資料庫中取到 當前最新區塊即添加後的上一個區塊            blockBytes := b.Get(blc.Tip)            block := DeSerializeBlock(blockBytes)            //3.建立新區快            newBlock := NewBlock(data, block.Height+1, block.Hash)            //4.區塊序列化入庫            err := b.Put(newBlock.Hash, newBlock.Serialize())            if err != nil {                log.Fatal(err)            }            //5.更新資料庫裡最新區塊            err = b.Put([]byte(newestBlockKey), newBlock.Hash)            if err != nil {                log.Fatal(err)            }            //6.更新區塊鏈最新區塊            blc.Tip = newBlock.Hash        }        return nil    })    if err != nil {        log.Fatal(err)    }}
區塊鏈遍曆
//3.遍曆輸出所有區塊資訊  --> 以後一般使用最佳化後的迭代器方法(見3.X)func (blc *Blockchain) Printchain() {    var block *Block    //當前遍曆的區塊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)                /**時間戳記格式化 Format裡的年份必須是固定的!!!                這個好像是go誕生的時間                time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05")                "2006-01-02 15:04:05"格式固定,改變其他也可能會出錯                */                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)        //遍曆到創世區塊,跳出迴圈  創世區塊雜湊為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這裡真是奇葩啊……時間戳記格式化只能寫"2006-01-02 15:04:05",一個數丟不能寫錯,不然你會”被穿越“的!!!據說這個日期是go語言的誕生日期,還真是傲嬌啊,生怕大家不知道嗎???
判斷區塊鏈資料庫是否存在
//判斷資料庫是否存在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}

區塊鏈迭代器

對區塊鏈區塊的遍曆上面已經實現,但是還可以最佳化。我們不難發現區塊鏈的區塊遍曆類似於單向鏈表的遍曆,那麼我們能不能製造一個像鏈表的Next屬性似的迭代器,只要通過不斷地訪問Next就能遍曆所有的區塊?

話都說到這份上了,答案當然是肯當的。

BlockchainIterator
//區塊鏈迭代器type BlockchainIterator struct {    //當前遍曆hash    CurrHash []byte    //區塊鏈資料庫    DB *bolt.DB}
Next迭代方法
func (blcIterator *BlockchainIterator) Next() *Block {    var block *Block    //資料庫查詢    err := blcIterator.DB.View(func(tx *bolt.Tx) error {        b := tx.Bucket([]byte(blockTableName))        if b != nil {            //擷取當前迭代器對應的區塊            currBlockBytes := b.Get(blcIterator.CurrHash)            block = DeSerializeBlock(currBlockBytes)            //更新迭代器            blcIterator.CurrHash = block.PrevBlockHash        }        return nil    })    if err != nil {        log.Fatal(err)    }    return block}
怎麼用?
1.在Blockchain類新增一個產生當前區塊鏈的迭代器的方法
//產生當前區塊鏈迭代器的方法func (blc *Blockchain) Iterator() *BlockchainIterator {    return &BlockchainIterator{blc.Tip, blc.DB}}
2.修改之前的Printchain方法
//3.X 最佳化區塊鏈遍曆方法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        }    }}

是不是發現遍曆區塊的代碼相對簡潔了,這裡把資料庫訪問和區塊迭代的代碼分離到了BlockchainIterator裡實現,也符合程式設計的單一職責原則。

main函數測試

package mainimport (    "chaors.com/LearnGo/publicChaorsChain/part4-DataPersistence-Prototype/BLC")func main() {    blockchain := BLC.CreateBlockchainWithGensisBlock()    defer blockchain.DB.Close()    //添加一個新區快    blockchain.AddBlockToBlockchain("first Block")    blockchain.AddBlockToBlockchain("second Block")    blockchain.AddBlockToBlockchain("third Block")    blockchain.Printchain()}
1.首次運行(這時不存在資料庫)
mainTest_1
2.注釋掉三句AddBlockToBlockchain代碼,再次運行
mainTest_2

這次我們並沒有添加區塊,所以列印區沒有挖礦的過程。但是列印的區塊是上次AddBlockToBlockchain添加的,說明區Block Storage成功了。

2.修改AddBlockToBlockchain段代碼,再次運行
blockchain.AddBlockToBlockchain("4th Block")blockchain.AddBlockToBlockchain("5th Block")
mainTest_3

我們看到,在原有區塊資訊不變的情況,新挖出的區塊成功添加到了區塊鏈資料庫中。說明我們的區塊鏈資料持久化實現成功了。

.
.
.
.

互連網顛覆世界,區塊鏈顛覆互連網!

---------------------------------------------20180624 20:16
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.