区块链持久化
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结合
- 将block写入bolt数据库
- 使用block的哈希作为key
- 使用block的字节流作为value
- 最后一个区块哈希自己维护: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包编解码
- gob包是go语言内置编解码包
- 可以支持变长类型的编解码(通用)
- binary.Write() ,高效,短板:编解码数据必须定长string,[]byte
- proto(不可读,高效) > binary(高效)> gob(通用)> json(可读)
编码流程:
-
创建编码器
-
编码
解码流程:
- 创建解码器
- 解码
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