简易区块链实现V3(golang)


前言

这个版本主要在上一个版本V2的基础上增加了本地化存储和命令行功能
比起前面两个版本相对要难写一些,主要用到了bolt模块和flag模块都需要取查看文档 但整体思路不难,要实现的功能都很明确很好理解,与区块链本身其实关系不大,主要是功能的完善


代码和分析

首先来实现本地化存储,这里用到了go一个特有的bolt数据库,一种轻量级键值对数据库。

这里我们要实现本地话存储就要对前面的blockchain进行修改,使其实现数据库存储,这里重新设计BlockChain结构体,首先要存储在数据库所以有一个数据库成员,这里我是觉得只存储一个数据库成员就足够了,我看的教程是再添加了最后一个区块的哈希值,这样应该是更方便

type BlockChain struct {
	Db *bolt.DB
	//LastHash []byte
}

这里本地话用到了bolt数据库,不会查资料,文档就好了
参考:https://blog.csdn.net/yang731227/article/details/82974575

首先我们希望来创建一个BlockChain结构体在创建的同时检测其有没有已有区块(这里特指创建创世块),如果没有我们就创建一个区块,并把它写入数据库中做为创世块。
数据库我们创建一个名为bucket的桶然后将数据已键值对的方式存储进桶中,键值我们就用区块的哈希值,值我们就用区块的结构体。同时多创建一个lasthash的键,值用于存储最新的一块区块的哈希值
有了思路我们就可以开始实现了

func NewBlockchain() *BlockChain{
	//打开数据库文件
	db, err := bolt.Open("block.db",0600, nil)
	if err != nil{
		fmt.Println(err)
		os.Exit(1)
	}
	 err = db.Update(func(tx *bolt.Tx) error{
	 	//获取bucket
		bucket, err := tx.CreateBucketIfNotExists([]byte(bucket))
		if err != nil{
			return err
		}
		lasthash := bucket.Get([]byte("lasthash"))
		if lasthash == nil{
			//插入创世块
			firstblock := FirstBlock()
			err = bucket.Put(firstblock.Hash, firstblock.Serialize())
			if err != nil{
				return err
			}
			err = bucket.Put([]byte("lasthash"),firstblock.Hash)
			if err != nil{
				return err
			}
			return nil

		}else{
			return nil
		}
	})
	 if err != nil{
	 	fmt.Println(err)
	 	os.Exit(1)
	 }
	return &BlockChain{db}
}

上面的firstblock用于创建创世块,提供data,调用我们前面写好的Newblock函数,传入创世块信息

还有这里要做序列化和反序列化操作,传入数据的结构体需要序列化成[]byte,后面从数据中取出数据又需要将[]byte转为结构体
可以参考下序列化和反序列化的语法:https://studygolang.com/articles/14884

func (b *Block)Serialize()[]byte{
	var result bytes.Buffer
	encoder := gob.NewEncoder(&result)
	encoder.Encode(b)
	return result.Bytes()
}

func Deserialize(block []byte)*Block{
	var deblock Block
	decoder := gob.NewDecoder(bytes.NewReader(block))
	decoder.Decode(&deblock)
	return &deblock
}

然后我们给结构体添加一个组合方法用于新增区块,这就是向数据库中写数据,和上面创建创建创世块那里有挺多代码相同,可以把相同的部分提出来重新写个函数,这里都是要向数据库中写入值

func (bc *BlockChain)AddBlock(data string){
	err := bc.Db.Update(func(tx *bolt.Tx) error{
		bucket, err := tx.CreateBucketIfNotExists([]byte(bucket))
		if err != nil{
			return err
		}
		lasthash := bucket.Get([]byte("lasthash"))
		newblock := Newblock(data,lasthash)
		err = bucket.Put(newblock.Hash,newblock.Serialize())
		if err != nil{
			return err
		}
		err = bucket.Put([]byte("lasthash"),newblock.Hash)
		if err != nil{
			return err
		}
		return nil
	})
	if err != nil{
		fmt.Println(err)
		os.Exit(1)
	}
}

最后我们来添加一个打印功能,这里我们就取出先取出lasthash值,然后以它为键找出区块,然后又从这个区块中取出里面的前一块区块的哈希值,这样进行迭代我们就能从后往前遍历整个区块链了

func (bc *BlockChain)PrintAll(){
	err := bc.Db.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists([]byte(bucket))
		if err != nil{
			return err
		}
		lasthash := bucket.Get([]byte("lasthash"))
		for{
			if lasthash != nil{
				block := bucket.Get(lasthash)
				Myprint(Deserialize(block))
				lasthash = Deserialize(block).PrevHash
			}else {
				break
			}
		}
		return nil
	})
	if err != nil{
		fmt.Println(err)
		os.Exit(1)
	}
}

后面再使用自带的flag模块来将其改为命令行操作形式,也是找文档来看用法,思路上其实没什么难点了

参考:https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter13/13.1.html

下面是运行的结果,这里还有点小问题就是data后面跟的值有空格会被截断只取第一个…
简易区块链实现V3(golang)-LMLPHP


总结

其实这个版本主要是增加了本地存储和命令行功能,主要是对bolt和flag两个模块的应用学习啊,本质其实和前面还是没多大区别的,下一个版本要实现交易功能对区块内容等都要有较大改动相对来说就比较复杂了


参考

  1. Golang实现区块链(三)—数据持久化(1)使用BoltDB
  2. BlotDB简单使用
  3. golang的序列化和反序列化
  4. flag - 命令行参数解析
  5. Golang实现区块链(三)—数据持久化(2)添加CLI交互接口
  6. package bolt
07-05 23:07