go语言实现简易比特币系统(三):持久化

1. 上版本存在问题

  • 没有对区块进行持久化,而是将区块链存放在内存当中。那么程序一旦退出,所有的内容都会消失,所以需要对区块链进行持久化。比特币使用的是LevelDB,而我们使用的是blot, 因为bolt更轻便、简洁并且能够满足我们的需求。

2. bolt常用操作

在此只对bolt进行简单说明使用,深入学习可以去bolt的github网站,有更加详细的使用说明。bolt github 链接

2.1 打开数据库

//函数原型:func Open(path string, mode os.FileMode, options *Options) (*DB, error) {}
//参数说明:
//	path :要打开的数据库文件,没有就会创建一个
//	mode :打开数据库文件时的权限,0600为读写
// 	options :;连接数据库的一些设置,可选
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
    
    
		log.Fatal(err)
	}
	defer db.Close()
}

2.2 读写数据库

//函数原型:func (db *DB) Update(fn func(*Tx) error) error {}
//参数说明:
//	函数:作为参数的函数可以看作一个事务,当返回值为nil时,会自动提交事务。当返回值为err时,会回滚事务。
err := db.Update(func(tx *bolt.Tx) error {
    
    
	...
	return nil
})

2.3 读取数据库

//函数原型:func (db *DB) View(fn func(*Tx) error) error {}
//参数说明:
//	在参数函数中,只能在只读事务中检索存储区,检索值并复制数据库
err := db.View(func(tx *bolt.Tx) error {
    
    
	...
	return nil
})

2.4 创建bucket

bucket的作用:bucket里面存放键值对,就是我们要存放进数据库的数据

//函数原型:func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {}
//参数说明:
//	[]byte: 要创建bucket的名称。
b, err := tx.CreateBucket([]byte("MyBucket"))

2.5 取值和存值

//put和get之前一定要有bucket
b := tx.Bucket([]byte("MyBucket"))

//put函数原型:func (b *Bucket) Put(key []byte, value []byte) error {}
err := b.Put([]byte("answer"), []byte("42"))

//get函数原型:func (b *Bucket) Get(key []byte) []byte {}
v := b.Get([]byte("answer"))

3. 持久化

3.1 修改区块链结构

type BlockChain struct {
    
    
	//使用数据库代替数组
	
	//区块链中有两个属性:
	//数据库文件,key是区块的hash值,value为区块的字节流
	db *bolt.DB
	//存储最后一个区块的哈希,方便找到最后一个区块哈希
	tail []byte
}

修改区块链存储方式后,每次添加区块要做两件事:

  1. 添加区块结构
  2. 更新tail的值

3.2 修改创建区块链方法

func NewBlockChain () *BlockChain{
    
    

	//最后一个区块的哈希,从数据库中读出来的
	var lastHash []byte

	//打开数据库
	db, err := bolt.Open(blockChianDb, 0600, nil)
	defer db.Close()
	if err != nil{
    
    
		log.Panic(err)
	}

	//写数据
	db.Update(func(tx *bolt.Tx) error {
    
    
		//找到bucket,(如果没有就创建,没有要找的bucket就代表要对一个新链进行操作,否则就是已有的链,进行追加即可)
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
    
    
			//没有bucket,创建
			bucket, err = tx.CreateBucket([]byte(blockBucket))
			if err != nil {
    
    
				log.Panic("创建bucket(blockBucket)失败")
			}

			//定义创世块
			genesisBlock := GenesisBlock()
			//把创世块加入到数据库文件中
			//block的哈希作为key,block的字节流作为value
			bucket.Put(genesisBlock.Hash, genesisBlock.Serialize())
			//修改最后一个区块的哈希
			bucket.Put([]byte("LastHashKey"), genesisBlock.Hash)
			lastHash = genesisBlock.Hash
		}else {
    
    
			//有数据库文件就直接引用
			lastHash = bucket.Get([]byte("LastHashKey"))
		}
		//return nil代表整个事务操作完成,不需要回滚
		return nil
	})

	//返回刚刚操作的区块链
	return &BlockChain{
    
    
		db:   db,
		tail: lastHash,
	}
}

3.3 修改添加区块方法

func (bc *BlockChain) AddBlock (data string) {
    
    

	//获取区块链
	db := bc.db
	//获取最后一个区块哈希
	lastHash := bc.tail

	db.Update(func(tx *bolt.Tx) error {
    
    

		//完成区块添加
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
    
    
			log.Panic("bucket 不应该为空,请检查!")
		}

		//1. 创建新区块
		block := NewBlock(data, lastHash)

		//2. 添加区块到数据库中
		//hash作为key, block的字节流作为value
		bucket.Put(block.Hash, block.Serialize())
		bucket.Put([]byte("LastHashKey"), block.Hash)

		//3. 更新内存中的区块链
		bc.tail = block.Hash

		return nil
	})
}

4. 新增辅助功能

4.1 迭代器遍历区块

4.1.1 创建迭代器

type BlockChainIterator struct {
    
    
	db *bolt.DB
	//游标
	currentHashPointer []byte
}

func (bc *BlockChain) NewIterator() *BlockChainIterator{
    
    
	return &BlockChainIterator{
    
    
		db:                 bc.db,
		//最初指向区块链的最后一个区块,随着Next的调用,不断变化
		currentHashPointer: bc.tail,
	}
}

func (it *BlockChainIterator) Next() *Block{
    
    
	var block Block

	it.db.View(func(tx *bolt.Tx) error {
    
    
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
    
    
			log.Panic("迭代器遍历时,bucket不应该为空!")
		}

		blockTmp := bucket.Get(it.currentHashPointer)

		//解码动作
		block = Deserialize(blockTmp)
		//游标左移
		it.currentHashPointer = block.PrevHash

		return nil
	})

	return &block
}

4.1.2 使用迭代器

bc := NewBlockChain()
it := bc.NewIterator()
for{
    
    
	//返回区块并左移
	block := it.Next()

	fmt.Println("==========================\n")
	fmt.Printf("前区块哈希: %x\n", block.PrevHash)
	fmt.Printf("区块哈希: %x\n", block.Hash)
	fmt.Printf("区块数据: %s\n", block.Data)

	if len(block.PrevHash) == 0 {
    
    
		fmt.Printf("区块遍历完成!\n")
		break
	}
}

4.2 命令行功能

4.2.1 逻辑处理

type CLI struct {
    
    
	bc *BlockChain
}

const Usage = `
	addBlock --data DATA	"add data to blockchain"
	printChain	"print all blockchain data"
`

func (cli *CLI) Run() {
    
    

	//1. 获取命令
	args := os.Args
	//	校验参数是否准确
	if len(args) < 2 {
    
    
		fmt.Printf(Usage)
		return
	}

	//2. 分析命令
	cmd := args[1]
	switch cmd {
    
    
	case "addBlock":
		//添加区块
		fmt.Printf("添加区块")

		//命令校验,验确保参数为4,并且第三个参数为--data
		if len(args) == 4 && args[2] == "--data"{
    
    
			//获取数据
			data := args[3]
			//添加区块
			cli.AddBlock(data)
		}else {
    
    
			fmt.Printf("添加区块参数使用不当,请检查!")
		}
	case "printChain":
		//打印区块
		fmt.Printf("打印区块\n")
		cli.PrintBlockChain()
	default:
		fmt.Printf("无效命令,请检查!")
		fmt.Printf(Usage)
		
	}
}

4.2.2 功能实现

func (cli *CLI) AddBlock(data string){
    
    
	cli.bc.AddBlock(data)
	fmt.Printf("添加区块成功!\n")
}

func (cli *CLI) PrintBlockChain(){
    
    
	bc := cli.bc
	it := bc.NewIterator()
	for{
    
    
		//返回区块并左移
		block := it.Next()

		fmt.Println("==========================\n")
		fmt.Printf("版本号: %d\n", block.Version)
		fmt.Printf("前区块哈希: %x\n", block.PrevHash)
		fmt.Printf("梅克尔根: %x\n", block.MerkelRoot)
		fmt.Printf("时间戳: %d\n", block.TimeStamp)
		fmt.Printf("难度值: %d\n", block.Difficulty)
		fmt.Printf("随机数: %d\n", block.Nonce)
		fmt.Printf("当前区块哈希: %x\n", block.Hash)
		fmt.Printf("区块数据: %s\n", block.Data)

		if len(block.PrevHash) == 0 {
    
    
			fmt.Printf("区块遍历完成!\n")
			break
		}
	}
}

源码:https://gitee.com/xiaoshengdada/go_bitcoin/tree/master/v3

如果有任何问题可以来微信群交流,另外群里有学习资料,可以自行下载。一起学习进步。
在这里插入图片描述最最后,推荐一位大佬的公众号:区块链技术栈

猜你喜欢

转载自blog.csdn.net/qq_31639829/article/details/115310365