Go language to implement a simple Bitcoin system (3): Persistence

1. There is a problem with the previous version

  • The block is not persisted, but the block chain is stored in memory. Then once the program exits, all the content will disappear, so the blockchain needs to be persisted. Bitcoin uses LevelDB, and we use blot, because bolts are lighter, more concise and can meet our needs.

2. Bolt common operations

Only a brief description of the use of bolt is given here. For in-depth study, you can go to the github website of bolt for more detailed instructions. bolt github link

2.1 Open the database

//函数原型: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 Read and write database

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

2.3 Read the database

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

2.4 Create bucket

The role of the bucket: the key-value pair is stored in the bucket, which is the data we want to store into the database

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

2.5 Value and storage

//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. Endurance

3.1 Modify the blockchain structure

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

After modifying the blockchain storage method, two things must be done each time a block is added:

  1. Add block structure
  2. Update the value of tail

3.2 Modify the method of creating a blockchain

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 Modify the method of adding blocks

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. Added accessibility features

4.1 Iterator traverses the block

4.1.1 Create iterator

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 Using iterators

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 Command line function

4.2.1 Logic processing

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 Function realization

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
		}
	}
}

Source code: https://gitee.com/xiaoshengdada/go_bitcoin/tree/master/v3

If you have any questions, you can come to the WeChat group to communicate, and there are learning materials in the group, you can download it yourself. Learn and progress together.
Insert picture description hereFinally, recommend the official account of a big brother: the blockchain technology stack .

Guess you like

Origin blog.csdn.net/qq_31639829/article/details/115310365