go语言模拟区块链step3

持久化存储与命令行的加入

我们要把生成的区块在磁盘上做一个持久化的存储,而且要自由添加区块

持久化存储思路分析

在这里插入图片描述

改变BlockChain结构体成员和方法
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
		}
命令行操作区块链
创建cli.go

简化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)
	}
}
创建commandline.go

具体实现方法的调用

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
		}
	}
}
main函数
func main() {
	cli:=CLI{}
	cli.Run()
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值