我们要把生成的区块在磁盘上做一个持久化的存储,而且要自由添加区块
type BlockChain struct {
db *bolt.DB //用于存储数据
tail []byte //最后一个区块的哈希值
}
const genesisInfo = "The first block"
const blockchainDBFile = "blockchain.db"
const bucketBlock = "bucketBlock" //装block的桶
const lastBlockHashKey = "lastBlockHashKey" //用于访问bolt数据库,得到最后一个区块的哈希值
此方法只会执行一次
//创建区块,从无到有:这个函数仅执行一次
func CreateBlockChain() error {
// 1. 区块链不存在,创建
db, err := bolt.Open(blockchainDBFile, 0600, nil)
if err != nil {
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
}
因为bolt数据库的key和value都是[]byte类型,所以我们要为区块定义编解码的方法
//绑定Serialize方法, gob编码
func (b *Block) Serialize() []byte {
var buffer bytes.Buffer
//编码器
encoder := gob.NewEncoder(&buffer)
//编码
err := encoder.Encode(b)
if err != nil {
fmt.Printf("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.Printf("decode err:", err)
return nil
}
return &block
}
//获取区块链实例,用于后续操作, 每一次有业务时都会调用
func GetBlockChainInstance() (*BlockChain, error) {
var lastHash []byte //内存中最后一个区块的哈希值
//两个功能:
// 1. 如果区块链不存在,则创建,同时返回blockchain的示例
db, err := bolt.Open(blockchainDBFile, 0400, nil) //rwx 0100 => 4
if err != nil {
return nil, err
}
//不要db.Close,后续要使用这个句柄
// 2. 如果区块链存在,则直接返回blockchain示例
db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(bucketBlock))
//如果bucket为空,说明不存在
if bucket == nil {
return errors.New("bucket不应为nil")
} else {
//直接读取特定的key,得到最后一个区块的哈希值
lastHash = bucket.Get([]byte(lastBlockHashKey))
}
return nil
})
//5. 拼成BlockChain然后返回
bc := BlockChain{db, lastHash}
return &bc, nil
}
//提供一个向区块链中添加区块的方法
func (bc *BlockChain) AddBlock(data string) error {
lashBlockHash := bc.tail //区块链中最后一个区块的哈希值
//1. 创建区块
newBlock := NewBlock(data, lashBlockHash)
//2. 写入数据库
err := bc.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(bucketBlock))
if bucket == nil {
return errors.New("AddBlock时Bucket不应为空")
}
//key是新区块的哈希值, value是这个区块的字节流
bucket.Put(newBlock.Hash, newBlock.Serialize())
bucket.Put([]byte(lastBlockHashKey), newBlock.Hash)
//更新bc的tail,这样后续的AddBlock才会基于我们newBlock追加
bc.tail = newBlock.Hash
return nil
})
return err
}
因为blot数据库默认排序是按照ASCII大小进行排序的,所以我们要实现一个能根据每个区块中的前区块哈希(PrevHash
)进行迭代查询的迭代器
//定义迭代器
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.Printf("iterator next err:", err)
return nil
}
return
}
在main中调用时,应该做出如下修改
//调用迭代器,输出blockChain
it := bc.NewIterator()
for {
//调用Next方法,获取区块,游标左移
block := it.Next()
fmt.Printf("\n++++++++++++++++++++++\n")
fmt.Printf("Version : %d\n", block.Version)
fmt.Printf("PrevHash : %x\n", block.PrevHash)
fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
fmt.Printf("Bits : %d\n", block.Bits)
fmt.Printf("Nonce : %d\n", block.Nonce)
fmt.Printf("Hash : %x\n", block.Hash)
fmt.Printf("Data : %s\n", block.Data)
pow := NewProofOfWork(block)
fmt.Printf("IsValid: %v\n", pow.IsValid())
//退出条件
if block.PrevHash == nil {
fmt.Println("区块链遍历结束!")
break
}
简化main函数
//处理用户输入命令,完成具体函数的调用
//cli : command line 命令行
type CLI struct {
//不需要字段
}
//使用说明,帮助用户正确使用
const Usage = `
Usage :
./blockchain create "创建区块链"
./blockchain addBlock <需要写入的的数据> "添加区块"
./blockchain print "打印区块链"
`
//负责解析命令的方法
func (cli *CLI) Run() {
cmds := os.Args
//用户至少输入两个参数
if len(cmds) < 2 {
fmt.Println("输入参数无效,请检查!")
fmt.Println(Usage)
return
}
switch cmds[1] {
case "create":
fmt.Println("创建区块被调用!")
cli.createBlockChain()
case "addBlock":
if len(cmd) != 3 {
fmt.Println("输入参数无效,请检查!")
fmt.Println(Usage)
return
}
fmt.Println("添加区块被调用!")
cli.addBlock(cmds[2])
case "print":
fmt.Println("打印区块被调用!")
cli.print()
default:
fmt.Println("输入参数无效,请检查!")
fmt.Println(Usage)
}
}
具体实现方法的调用
func(cli *CLI)createBlockChain(){
err:=CreateBlockChain()
if err!=nil{
fmt.Println("CreateBlockChain failed:", err)
return
}
fmt.Println("创建区块链成功!")
}
func(cli *CLI)addBlock(data string){
bc,err:=GetBlockChainInstance()
if err!=nil{
fmt.Println("GetBlockChainInstance failed:", err)
return
}
err=bc.AddBlock(data)
if err!=nil{
fmt.Println("AddBlock failed:", err)
return
}
fmt.Println("添加区块成功!")
}
func(cli *CLI)print(){
bc,err:=GetBlockChainInstance()
if err!=nil{
fmt.Println("GetBlockChainInstance failed:", err)
return
}
it:=bc.NewIterator()
for{
block,err:=it.Next()
if err!=nil{
return
}
fmt.Println("\n+++++++++++++++++++++++++++++++++++++++++++++")
fmt.Println("版本",block.Version)
fmt.Printf("交易的根哈希%x\n",block.Data)
fmt.Println("事件戳",block.TimeStamp)
fmt.Println("难度值",block.Bits)
fmt.Println("Nonce",block.Nonce)
fmt.Printf("前哈希%x\n",block.PrevHash)
fmt.Printf("哈希%x\n",block.Hash)
fmt.Printf("数据%s\n",block.Data)
miner:=NewProofOfWork(block)
fmt.Printf("ISValid:%v\n",miner.IsValid())
if block.PrevHash==nil{
break
}
}
}
func main() {
cli:=CLI{}
cli.Run()
}