Golangによるブロックチェーンの実装(3)—データ持続化(1)BoltDBの使用


データ持続性
前述の論文では,poW掘削可能なブロック鎖を実現した.しかし、私たちの前のブロック情報はキャッシュに保存されており、実行するたびに創世ブロックから開始する必要があります.これは明らかに重要な欠陥であり、本章では永続化を実現します.ビットコインではLevelDBを用いてデータ永続化を行い,ビットコインシステムとLevelDBはいずれもC++で実現される.私たちのブロックチェーンはGoで実現されているので、Go言語で書かれたBoltDBも探しています.
BoltDBの追加
BoltDBはkey/valueベースのストレージであり、SQLリレーショナル・データベース(MySQL、PG)のようなテーブルも行、列もない.データはKey-value構造にのみ存在する(Golangのmapsに似ている).Key-valueはSQLのテーブル機能とあまり差のないバケツ(buckets)に格納されているので、値を得るには「バケツ」と「key」を知る必要があります
BoltDBには次のような特性があります.
  • 小さくて簡潔な
  • Goによる
  • の実装
  • は、
  • を個別に配置する必要はありません.
  • は、EMCのデータ構造
  • をサポートします.
    BoltDBの紹介と使用については、私の前の文章を参考にすることができますが、本稿では検討していません.
    https://blog.csdn.net/yang731227/article/details/82974575
    const dbName = "blockchain.db"  //   
    const bkName = "blocks" // 
    
    type Blockchain struct {
       tip [] byte  //          
       Db *bolt.DB //   
    }
    

    2つの定数を定義し、dbNameはデータベース名、bkNameはバケツ/テーブル名、バケツはブロック情報を格納します.
    Blockchain構造体最新のブロックのハッシュ値を格納するためにtipを追加した.
    データシーケンス化処理
    BlotDBを使用する前提は、そのK-Vはbyte配列しか記憶できないので、まずBlock[]byte、すなわちデータシーケンス化に変換し、逆にブロックデータを読み取る必要がある場合は[]byteBlock、すなわちデータ逆シーケンス化に復元しなければならない.
    シーケンス化
    データのシーケンス化
    func (b *Block) Serialize() []byte  {
    	var result bytes.Buffer
    	encoder:=gob.NewEncoder(&result)
    	err :=encoder.Encode(b)
    	if err!=nil{
    		log.Panicf("serialize the block to byte failed %v 
    "
    ,err) } return result.Bytes() }

    逆シーケンス化
    再実現シーケンス化方法
    func DeserilizeBlock (blockBytes []byte) *Block{
       var block Block
       decoder:= gob.NewDecoder(bytes.NewReader(blockBytes))
       err:= decoder.Decode(&block)
       if err !=nil{
       	log.Panicf("deserialize the block to byte failed %v 
    "
    ,err) } return &block }

    データ持続性の実現
    ブロックチェーンは創世ブロックによって導かれていることを知っているので、まず創世ブロックを改造しなければならない.前の文章ではNewBlockchain()を使用しています.これは創世ブロックをチェーンに追加したものです.明らかに今、この方法は私たちのニーズを満たしていません.他の方法で置き換える必要があります.ここでは、Blockchain_GenesisBlokc()を再作成します.この関数は実現する必要があります.1.DBファイルを作成し、2を開きます.ブロック情報を格納バケツ3を作成する.創世ブロックの情報をシーケンス化し、DB中4を記憶する.創世ブロックをチェーンに追加
    書き込みDB
    データを書き込む前に、ビットコインに2つのテーブルでデータを格納する方法を明らかにしなければなりません.
  • blocksは、チェーン内のすべてのブロックのメタデータ
  • を格納する.
  • chainstateストレージチェーンのステータス.現在完了していないトランザクション情報およびその他のメタデータが格納されます.

  • blocksでは、k->vペアは、「b」+32-byteブロックのhashコード->ブロックインデックスレコード「f」+4-byteファイル番号->ファイル情報レコード「l」->4-byteファイル番号:最後のブロックの番号「R」->1-byteブール値:タグがインデックス「F」+1-byteタグ名長さ+タグ名->1 byte booleanをリセットしているかどうかトランザクションのhash値->トランザクションのインデックスレコード
    私たちはまだ取引していないので、今はblocksだけを検討しています.chainstate私たちは検討しないことを示しています.また,ブロックをそれぞれ独立したファイルに存在させず,DB全体を1つのファイルとしてBlocksを格納する.ファイルに関連付けられた数字は必要ありません
    次のk->vペアだけが必要です
  • 32-byteこのブロックハッシュ->シーケンス化後ブロック情報
  • 'l->チェーン内の最後のブロックのhash値
  • func Blockchain_GenesisBlokc() *Blockchain {
       db, err := bolt.Open(dbName, 0600, nil)
       if err != nil {
       	log.Panicf("open the Dbfailed! %v
    "
    , err) } //defer db.Close() var tip []byte // err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(bkName)) if b == nil { b, err = tx.CreateBucket([]byte(bkName)) if err != nil { log.Panicf("create the bucket [%s] failed! %v
    "
    , bkName, err) } } if b != nil { genesisBlock := NewGenesisBlock() // err = b.Put(genesisBlock.Hash, genesisBlock.Serialize()) if err != nil { log.Panicf("put the data of genesisBlock to Dbfailed! %v
    "
    , err) } // err = b.Put([]byte("l"), genesisBlock.Hash) if err != nil { log.Panicf("put the hash of latest block to Dbfailed! %v
    "
    , err) } tip = genesisBlock.Hash } return nil }) if err != nil { log.Panicf("update the data of genesis block failed! %v
    "
    , err) } return &Blockchain{tip, db} }

    チェーン内の創世ブロックを変更した後、関数AddBlockを改造しました.以前は、新しいブロックをチェーンに簡単に追加しただけでしたが、次のようにする必要があります.
    1.バケツの最後のブロックのHash 2を取り出す.Hashを逆シーケンス化し、最後のブロック情報3を得る.取り出した情報に基づいて、新しいブロック4を作成する.新しいブロックをDBにシーケンス化して格納する
    func (bc *Blockchain) AddBlock(data string) {
       err := bc.Db.Update(func(tx *bolt.Tx) error {
       	b := tx.Bucket([]byte(bkName))
       	if b != nil {
       		blockBytes := b.Get(bc.tip) 
       		latest_block := DeserilizeBlock(blockBytes)
       		newBlock := NewBlock(latest_block.Index+1, data, latest_block.Hash)
    
       		err := b.Put(newBlock.Hash, newBlock.Serialize())
       		if nil != err {
       			log.Panicf("put the data of new block into Dbfailed! %v
    "
    , err) } err = b.Put([]byte("l"), newBlock.Hash) if nil != err { log.Panicf("put the hash of the newest block into Dbfailed! %v
    "
    , err) } bc.tip = newBlock.Hash } return nil }) if nil != err { log.Panicf("update the Dbof block failed! %v
    "
    , err) } }

    ブロックの読み込み
    書き込みDBは改造されていますので、読み取りを実現します
    func (bc *Blockchain) PrintChain() {
    	fmt.Println("——————————————     ———————————————————————")
    	var curBlock *Block
    	var curHash []byte = bc.tip
    	for {
    		fmt.Println("—————————————————————————————————————————————")
    		bc.Db.View(func(tx *bolt.Tx) error {
    			b := tx.Bucket([]byte(bkName))
    			if b != nil {
    				blockBytes := b.Get(curHash)
    				curBlock = DeserilizeBlock(blockBytes)
    
    				fmt.Printf("\tHeigth : %d
    "
    , curBlock.Index) fmt.Printf("\tTimeStamp : %d
    "
    , curBlock.TimeStamp) fmt.Printf("\tPrevBlockHash : %x
    "
    , curBlock.PrevBlockHash) fmt.Printf("\tHash : %x
    "
    , curBlock.Hash) fmt.Printf("\tData : %s
    "
    , string(curBlock.Data)) fmt.Printf("\tNonce : %d
    "
    , curBlock.Nonce) } return nil }) // var hashInt big.Int hashInt.SetBytes(curBlock.PrevBlockHash) if big.NewInt(0).Cmp(&hashInt) == 0 { break // } curHash = curBlock.PrevBlockHash } }

    うんてん
    func main() {
    	blockChain := BLC.Blockchain_GenesisBlokc()
    	defer blockChain.Db.Close();
    	blockChain.AddBlock("Send 100 btc to Jay")
    	blockChain.AddBlock("Send 50 btc to Clown")
    	blockChain.AddBlock("Send 20 btc to Bob")
    	blockChain.PrintChain()
    }
    

    まとめ
    本論文では,BoltDBを用いてデータを永続化することを実現したが,実行するたびに創世ブロックから始まるのはBUGではないか.はい、あなたの考えは間違いありません.本文はデータベースの存在を判断していません.ブロックデータをクエリーする機能も欠けています.心配しないでください.次の章では、データの永続化について説明し続けます.ブロックチェーンの反復とコマンドラインのインタラクティブインタフェースを追加して、私たちの機能をさらに改善します.