BoltDB有如下特性:
小而简约
使用Go实现
不需要单独部署
const dbName = "blockchain.db"
const bkName = "blocks"
type Blockchain struct {
tip [] byte //存储最新区块的哈希值
Db *bolt.DB //数据库
}
我们定义了两个常量,dbName用来存储数据库名称,bkName用来存储桶/表的名称,桶用来存储区块信息。
Blockchain 结构我们添加了 tip用来存储最新区块的哈希值。
数据序列化处理
使用BlotDB的前提就是,它的K-V都只能存储byte数组,所以我们要对Block结构进行序列化,然后读取到区块的时候我们还需反序列化。
序列化
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 \n",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 \n",err)
}
return &block
}
持久化
写入DB
实现持久化,理所当然我们得从区块链中的创世区块开始,之前的方法(NewBlockchain)只实现了把创世区块添加到链中。
现在需要:
1.创建DB文件,并打开
2.创建桶
3.创世区块链序列化
4.把创世区块的Hash添加到DB中
5.把创世区块添加到链中
设定 block的k-v :
‘l’ -> 4-byte 文件编号: 最后一块文件的编号
func Blockchain_GenesisBlokc() *Blockchain {
db, err := bolt.Open(dbName, 0600, nil)
if err != nil {
log.Panicf("open the Dbfailed! %v\n", 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\n", 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\n", err)
}
//存储最新区块链哈希
err = b.Put([]byte("l"), genesisBlock.Hash)
if err != nil {
log.Panicf("put the hash of latest block to Dbfailed! %v\n", err)
}
tip = genesisBlock.Hash
}
return nil
})
if err != nil {
log.Panicf("update the data of genesis block failed! %v\n", err)
}
return &Blockchain{tip, db}
}
改完链中创世区块后,然后我接着对链中区块的添加AddBlock进行改造。之前我们只是简单的把新的区块添加到链中,现在它需要做到:
1.打开桶
2.取出桶中最后一个区块的Hash
3.对Hash进行反序列化,得到最后一个区块信息
4.根据取出的信息,创建新的区块
5.把新的区块序列化存储到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\n", err)
}
err = b.Put([]byte("l"), newBlock.Hash)
if nil != err {
log.Panicf("put the hash of the newest block into Dbfailed! %v\n", err)
}
bc.tip = newBlock.Hash
}
return nil
})
if nil != err {
log.Panicf("update the Dbof block failed! %v\n", 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\n", curBlock.Index)
fmt.Printf("\tTimeStamp : %d\n", curBlock.TimeStamp)
fmt.Printf("\tPrevBlockHash : %x\n", curBlock.PrevBlockHash)
fmt.Printf("\tHash : %x\n", curBlock.Hash)
fmt.Printf("\tData : %s\n", string(curBlock.Data))
fmt.Printf("\tNonce : %d\n", 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,对数据进行持久化。但是细心的小伙伴可能觉得缺少了什么?是的你想的没错,我们并没有判断区块是否存在。下章我们将进行完善,并实现区块链的迭代和命令行接口。