区块链持久化--golang实现区块链系列之三
2018-10-11 本文已影响2人
若与
本篇使用boltdb的简易数据库将区块链的数据持久化到一个文件中。boltdb是kv形式保存的。
下面代码:
package main
import (
"github.com/boltdb/bolt"
"os"
)
const dbfile = "blockchain.db"
const blockBucket = "block_demo"
const lastHashKey = "genesis"
type BlockChian struct {
//blocks []*Block
db *bolt.DB
lastHash []byte
}
func NewBlockChain() *BlockChian {
//return &Blo ckChian{[]*Block{NewGenesisBlock()}}
//func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// var db = &DB{opened: true}
db, err := bolt.Open(dbfile, 7777, nil)
CheckErr(err)
var lasthash []byte
//db.View()
db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blockBucket))
if bucket != nil {
//读取lasthash
lasthash = bucket.Get([]byte(lastHashKey))
} else {
// 1.创建bucket
// 2. 创世区块
genesis := NewGenesisBlock()
bucket, err := tx.CreateBucket([]byte(blockBucket))
CheckErr(err)
err = bucket.Put(genesis.Hash, genesis.Serialize())
CheckErr(err)
lasthash = genesis.Hash
err = bucket.Put([]byte(lastHashKey), genesis.Hash)
CheckErr(err)
}
return nil
})
return &BlockChian{db:db, lastHash:lasthash}
}
func (bc *BlockChian)AddBlock(data string) {
//var prevBlockHash []byte
/*err := bc.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blockBucket))
lasthash := bucket.Get([]byte(lastHashKey))
prevBlockHash = lasthash
return nil
})*/
block := NewBlock(data, bc.lastHash)
err := bc.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blockBucket))
err := bucket.Put(block.Hash, block.Serialize())
CheckErr(err)
err = bucket.Put([]byte(lastHashKey), block.Hash)
CheckErr(err)
return nil
})
CheckErr(err)
}
type BlockChainIterator struct {
db *bolt.DB
currentHash []byte
}
func (bc *BlockChian)Iterator() *BlockChainIterator {
return &BlockChainIterator{bc.db, bc.lastHash}
}
func (it *BlockChainIterator)Next() *Block {
var block *Block
err := it.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blockBucket))
if bucket == nil {
os.Exit(1)
}
blockTmp := bucket.Get(it.currentHash)
block = Deserialize(blockTmp)
it.currentHash = block.PrevBlockHash
return nil
})
CheckErr(err)
return block
}
boltdb是key-value形式的数据库, 所以,区块链保存hash当做key, 区块链的内容当做value进行保存。
上面有一个特殊不变的key保存最后一个区块的hash。
先获取最后区块的hash,就可以获取到区块的数据,获取数据就可以获取前一个区块的hash,以此递推,可以获取所有区块的数据。
命令行工具:
package main
import (
"flag"
"fmt"
"os"
)
const Usage = `
addBlock --data DATA "add a block to block chain"
printChain "print all blocks"
`
type CLI struct {
bc *BlockChian
}
func (cli *CLI)Run() {
if len(os.Args) < 2 {
fmt.Println("too few parameters!\n", Usage)
os.Exit(1)
}
addBlockCmd := flag.NewFlagSet("addBlock", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printChain", flag.ExitOnError)
addBlockCmdPara := addBlockCmd.String("data", "", "block info")
switch os.Args[1] {
case "addBlock":
err := addBlockCmd.Parse(os.Args[2:])
CheckErr(err)
if addBlockCmd.Parsed() {
if *addBlockCmdPara == "" {
fmt.Println("data is empty")
os.Exit(1)
}
cli.AddBlock(*addBlockCmdPara)
}
case "printChain":
err := printChainCmd.Parse(os.Args[2:])
CheckErr(err)
if printChainCmd.Parsed() {
cli.PrintChain()
}
default:
fmt.Println("invalid cmd\n", Usage)
os.Exit(1)
}
}
package main
import "fmt"
func (cli *CLI)AddBlock(data string) {
cli.bc.AddBlock(data)
}
func (cli *CLI)PrintChain() {
bc := cli.bc
it := bc.Iterator()
for {
// 取回当前hash指定的block,并且将当前的hash指向上一个区块的hash
block := it.Next()
fmt.Println("data:", string(block.Data))
fmt.Println("Version:", block.Version)
fmt.Printf("Hash:%x\n", block.Hash)
fmt.Printf("TimeStamp:%d\n", block.TimeStamp)
fmt.Printf("MerkeRoot:%x\n", block.MerkeRoot)
fmt.Printf("Nonce:%d\n", block.Nonce)
fmt.Printf("preblock Hash:%x\n", block.PrevBlockHash)
println(" ")
pow := NewProofOfWork(block)
fmt.Println("Vaild:", pow.IsVaild())
if len(block.PrevBlockHash) == 0 {
break
}
}
}
终端执行效果: