区块链持久化

区块链持久化

1. bolt数据库

1.1 存储结构

其中有桶的概念,在一个数据库下有多个桶bucket,很像一个个抽屉

  • key ==> value

    []byte ==> []byte

  • key是唯一的

  • 存储顺序是按照key的ASCII值,小在前,大在后

1.2 读写方法

例如桶1叫:b1

写: b1.Put([]byte("key1"), []byte("value1"))

读: byte1 := b1.Get([]byte("key1"))

读取不存在的key会返回零值

1.3 demo

const testDb = "test.db"

func main() {
   // 打开数据库
   db, err := bolt.Open(testDb, 0600, nil)
   if err != nil {
      fmt.Println("bolt open err:", err)
      return
   }
   defer db.Close()

   // 创建bucket
   err = db.Update(func(tx *bolt.Tx) error {
      b1 := tx.Bucket([]byte("bucket1"))

      // 没有这个bucket
      if b1 == nil {
         // 创建一个bucket
         b1, err = tx.CreateBucket([]byte("bucket1"))
         if err != nil {
            fmt.Println("tx.CreateBucket error:", err)
            return err
         }

         // 写入数据
         err = b1.Put([]byte("key1"), []byte("hello"))
         if err != nil {
            fmt.Println("b1.Put error:", err)
            return err
         }
         err = b1.Put([]byte("key2"), []byte("world"))
         if err != nil {
            fmt.Println("b1.Put error:", err)
            return err
         }

         // 读取数据
         v1 := b1.Get([]byte("key1"))
         v2 := b1.Get([]byte("key2"))
         v3 := b1.Get([]byte("key3"))

         fmt.Printf("v1:%s\n", v1)
         fmt.Printf("v2:%s\n", v2)
         fmt.Printf("v3:%s\n", v3)
      }
      return nil
   })
}

2. bolt与blockchain结合

  1. 将block写入bolt数据库
  2. 使用block的哈希作为key
  3. 使用block的字节流作为value
  4. 最后一个区块哈希自己维护:key写死为"lastBlockHashKey" ==> 永远存储最后一个区块哈希
  • 每次添加新区块时,一定是两个操作
    • 写入区块
    • 更新lastBlockHashKey的value

3. 改写blockchain

package main

import (
	"errors"
	"fmt"
	"github.com/boltdb/bolt"
)

// 定义区块链结构(使用数组模拟区块链)
type BlockChain struct {
	// Blocks []*Block  // 区块链
	db *bolt.DB   // 用于存储数据
	tail []byte   // 最后一个区块的哈希值
}

// 创世语
const genesisInfo = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
const blockchainDbFile = "blockchain.db"
const bucketBlock = "bucketBlock"   // 装block的桶
const lastBlockHashKey = "lastBlockHashKey"  // 用于访问blot数据库,用于存最后一个哈希

// 创建区块,从无到有:这个函数仅执行一次
func CreateBlockChain() error {

	// 1. 区块不存在,创建
	db, err := bolt.Open(blockchainDbFile, 0600, nil)
	if err != nil {
		fmt.Println("bolt.Open error:", err)
		return err
	}
	// 不要db.Close,后续要使用这个句柄
	defer db.Close()

	// 2. 开始创建
	err = db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketBlock))

		// 如果bucket为空, 说明不存在
		if bucket == nil {
			// 创建bucket
			bucket, err = tx.CreateBucket([]byte(bucketBlock))
			if err != nil {
				return err
			}
			// 写入创世块
			// 创建BlockChain,同时添加一个创世块
			genesisBlock := NewBlock(genesisInfo, nil)
			// key是区块的哈希值,value是block的字节流
			bucket.Put(genesisBlock.Hash, genesisBlock.Serialize())    // 将block序列化
			bucket.Put([]byte(lastBlockHashKey), genesisBlock.Hash)
		}
		return nil
	})
	return err   // 如果成功返回nil
}

// 获取区块链实例,用于后续操作,每一次有业务时都会调用
func GetBlockChainInstance() (*BlockChain, error) {
	var lastHash []byte   // 内存中最后一个区块的哈希值

	// 两个功能
	// 1. 如果区块不存在,则创建,同时返回blockchain的实例
	db, err := bolt.Open(blockchainDbFile, 0600, nil)
	if err != nil {
		fmt.Println("bolt.Open error:", err)
		return nil, err
	}
	// 不要db.Close,后续要使用这个句柄

	// 2. 如果区块链存在,则直接返回blockchain实例
	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketBlock))

		// 如果bucket为空, 说明不存在
		if bucket == nil {
			return errors.New("bucket不应为nil")
		} else {
			lastHash = bucket.Get([]byte(lastBlockHashKey))
		}
		return nil
	})

	// 1. 创建数据库
	// 2. 更新,找到目标bucket
	// 3. 如果bucket不存在则创建,写入创世块
	// 4. 如果存在,则直接返回最后一个区块的哈希

	// 5. 拼接成blockchain然后返回
	// 6. 拆成两个函数
	bc := BlockChain{db, lastHash}
	return &bc, nil
}

// 提供一个向区块链中添加区块的方法
func (bc *BlockChain) AddBlock(data string) error {
	lastBlockHashKey := bc.tail

	// 1. 创建区块
	newBlock := NewBlock(data, lastBlockHashKey)

	// 2. 写入数据库
	err := bc.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketBlock))
		if bucket == nil {
			return errors.New("AddBlock时bucket不应为空")
		}
		bucket.Put(newBlock.Hash, newBlock.Serialize())
		bucket.Put([]byte(lastBlockHashKey), newBlock.Hash)

		// 3. 更新lastBlockHashKey,这样后续的Addblock才会基于我们newBlock追加
		bc.tail = newBlock.Hash

		return nil
	})
	return err
}

4. 改写block,添加serialize方法

package main

import (
   "bytes"
   "encoding/gob"
   "fmt"
   "time"
)

// 定义区块结构
// 第一阶段:先实现基础字段:前区块哈希,哈希,数据
// 第二阶段:补充字段:Version,时间戳,难度值等
type Block struct {
   // 版本号
   Version uint64

   // 前区块哈希
   PrevHash []byte

   // 交易的根哈希值
   MerkleRoot []byte

   // 时间戳
   TimeStamp uint64

   // 难度值,系统提供一个数据,用于计算出一个哈希值
   Bits uint64

   // 随机数,挖矿要求的数值
   Nonce uint64

   // 哈希,为了方便,把当前区块的哈希放入block中
   Hash []byte

   // 数据
   Data []byte
}

// 创建一个区块(提供一个方法)
// 输入:数据,前区块的哈希值
// 输出:区块
func NewBlock(data string, prevHash []byte) *Block {
   b := Block{
      Version:0,
      MerkleRoot:nil,  // 随意写的
      TimeStamp:uint64(time.Now().Unix()),
      Bits:0,    // 随意写的
      Nonce:0,   // 随意写的
      PrevHash:prevHash,
      Hash:nil,
      Data:[]byte(data),
   }
   // 计算哈希值,对区块中的所有信息的拼接计算哈希
   //TODO
   pow := NewProofOfWork(&b)
   hash, nonce := pow.Run()
   b.Hash = hash
   b.Nonce = nonce

   return &b
}

// 绑定Serialize方法
func (b *Block) Serialize() []byte {
   var buffer bytes.Buffer

   // 创建编码器
   encoder := gob.NewEncoder(&buffer)
   // 编码
   err := encoder.Encode(b)
   if err != nil {
      fmt.Println("Encode err:", err)
      return nil
   }

   return buffer.Bytes()
}

// 反序列化,输入[]byte,返回block
func Deserialize(src []byte) *Block {
   var block Block

   // 解码器
   decoder := gob.NewDecoder(bytes.NewReader(src))
   // 解密
   err := decoder.Decode(&block)
   if err != nil {
      fmt.Println("Decode error:", err)
      return nil
   }
   return &block
}

5. 自定义blockchain的迭代器

type Iterator struct {
	db *bolt.DB
    currentHash []byte   // 不断移动的游标
}

向Iterator绑定一个方法Next():

功能:能够不断地获取游标指向的区块,同时向左移动

//定义迭代器
type Iterator struct {
	db          *bolt.DB
	currentHash []byte //不断移动的哈希值,由于访问所有区块
}

//将Iterator绑定到BlockChain
func (bc *BlockChain) NewIterator() *Iterator {
	it := Iterator{
		db:          bc.db,
		currentHash: bc.tail,
	}

	return &it
}

//给Iterator绑定一个方法:Next
//1. 返回当前所指向的区块
//2. 向左移动(指向前一个区块)
func (it *Iterator) Next() (block *Block) {

	//读取Bucket当前哈希block
	err := it.db.View(func(tx *bolt.Tx) error {
		//读取bucket
		bucket := tx.Bucket([]byte(bucketBlock))
		if bucket == nil {
			return errors.New("Iterator Next时bucket不应为nil")
		}

		blockTmpInfo /*block的字节流*/ := bucket.Get(it.currentHash) //一定要注意,是currentHash
		block = Deserialize(blockTmpInfo)
		it.currentHash = block.PrevHash //游标左移

		return nil
	})
	//哈希游标向左移动

	if err != nil {
		fmt.Println("iterator next err:", err)
		return nil
	}
	return
}

6. gob包编解码

  1. gob包是go语言内置编解码包
  2. 可以支持变长类型的编解码(通用)
  • binary.Write() ,高效,短板:编解码数据必须定长string,[]byte
  • proto(不可读,高效) > binary(高效)> gob(通用)> json(可读)

编码流程:

  1. 创建编码器

  2. 编码

解码流程:

  1. 创建解码器
  2. 解码

7. GOROOT和GOPATH

  • GOROOT存放go源码包的目录:go sdk,提供go语言的标准库,fmt,os,error

    • /usr/local/Go
  • GOPATH:用户存放自己的项目的目录

    • 例如:GOPATH=Users/alin/go
    • Src
      • 工程1
      • 工程2
      • 工程3
      • github.com
      • golang.org
      • google.golang.org
    • pkg
    • bin

下载包可以使用:Git clone https://github.com/boltdb/bolt.git,这样下载会下载到书写命令的地方

go get -u github.com/boltdb/bolt 这时会把blot数据库源码下载到GOPATH/src/github.com/bolt

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页