The previous section learned the basic use of the BOLTDB database based on the Go language, which boltdb the data persistence of the blockchain.
Storage mode
Blockchain data is mainly concentrated on each block, so the data persistence of the blockchain can be transformed into the storage of each chunk. Boltdb is a kv storage method, so here we can use the hash value of the chunk as key, and the chunk as value.
In addition, we need to store the hash value of the latest chunk. In this way, you can find the latest chunk, then find the last chunk by the last chunk hash stored in the chunk, and so on, you can find all the chunks on the block chain.
Chunk serialization
We know that the data type of the key-value pair stored by Boltdb is a byte array. Therefore, the chunk needs to be serialized before the chunk is stored, and of course it needs to be deserialized when it reads chunks.
There are no difficult points, all of which are realized by means of system. Talk less about the code.
Serialization of
//区块序列化func (block *Block) Serialize() []byte { var result bytes.Buffer encoder := gob.NewEncoder(&result) err := encoder.Encode(block) if err != nil{ log.Panic(err) } return result.Bytes()}
Deserialization
//区块反序列化func DeSerializeBlock(blockBytes []byte) *Block { var block *Block dencoder := gob.NewDecoder(bytes.NewReader(blockBytes)) err := dencoder.Decode(&block) if err != nil{ log.Panic(err) } return block}
Block Chain class
Block chain structure
The previously defined blockchain structure is this:
type Blockchain struct { //有序区块的数组 Blocks [] *Block}
But such a structure, every time the program block array is created from scratch, and does not implement the blockchain data persistence. The array property here is changed to a chunk database of type BOLTDB, and there must be a property that stores the current chunk hash of the blockchain.
type Blockchain struct { //最新区块的Hash Tip []byte //存储区块的数据库 DB *bolt.DB}
Related Database constants
//相关数据库属性const dbName = "chaorsBlockchain.db"const blockTableName = "chaorsBlocks"const newestBlockKey = "chNewestBlockKey"
Create a chunk chain
1. Create a blockchain with Genesis block Func Createblockchainwithgensisblock () *blockchain {var Blockchain *blockchain//Determine if the database exists if is Dbexists (dbName) {db, err: = Bolt. Open (DbName, 0600, nil) if err! = Nil {log. Fatal (ERR)} err = db. View (func (TX *bolt). TX) Error {b: = tx. Bucket ([]byte (Blocktablename)) if b! = Nil {hash: = B.get ([]byte (Newestblockkey)) Blockchain = &blockchain{hash, db}//fmt. Printf ("%x", hash)} return nil}) if err! = Nil {log. Panic (Err)}//blockchain. Printchain ()//os. Exit (1) return blockchain}//Create and open database db, err: = Bolt. Open (DbName, 0600, nil) if err! = Nil {log. Fatal (ERR)} err = db. Update (func (TX *bolt). TX) Error {b: = tx. The Bucket ([]byte (Blocktablename))//blocktablename does not exist again to create the table if b = = Nil {b, err = TX. Createbucket ([]byte (BlocktablENAME)) If err! = Nil {log. Panic (ERR)}} if B! = Nil {//Genesis block Gensisblock: = Creategenesisblock ("Gens Is Block ... ")//Deposit Database Err: = B.put (Gensisblock.hash, Gensisblock.serialize ()) If err! = N Il {log. Panic (ERR)}//Store latest chunk Hash err = B.put ([]byte (Newestblockkey), Gensisblock.hash) If err! = Nil {log. Panic (ERR)} blockchain = &blockchain{gensisblock.hash, db}} return nil}) Update database failed if err! = Nil {log. Fatal (ERR)} return blockchain}
New Block
The method we wrote earlier is:
func (blc *Blockchain) AddBlockToBlockchain(data string, height int64, prevHash []byte) {
Look carefully found that the parameters of a lot of huge cumbersome. Is there any argument that is not necessary to pass?
Since we use the database to achieve the data persistence of the blockchain, here height can be based on the height of the previous block, Prevhash can also be taken from the database to get the last chunk. Therefore, starting today, this method omits these two parameters.
//2.新增一个区块到区块链func (blc *Blockchain) AddBlockToBlockchain(data string) { err := blc.DB.Update(func(tx *bolt.Tx) error { //1.取表 b := tx.Bucket([]byte(blockTableName)) if b != nil { //2.height,prevHash都可以从数据库中取到 当前最新区块即添加后的上一个区块 blockBytes := b.Get(blc.Tip) block := DeSerializeBlock(blockBytes) //3.创建新区快 newBlock := NewBlock(data, block.Height+1, block.Hash) //4.区块序列化入库 err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { log.Fatal(err) } //5.更新数据库里最新区块 err = b.Put([]byte(newestBlockKey), newBlock.Hash) if err != nil { log.Fatal(err) } //6.更新区块链最新区块 blc.Tip = newBlock.Hash } return nil }) if err != nil { log.Fatal(err) }}
Block chain traversal
3. Iterate through the output of all chunk information-and later generally use the optimized iterator method (see 3. X) func (BLC *blockchain) Printchain () {var block *block//current traversed chunk hash var curhash []byte = BLC. Tip for {err: = BLC. Db. View (func (TX *bolt). TX) Error {b: = tx. Bucket ([]byte (Blocktablename)) if b! = Nil {blockbytes: = B.get (curhash) block = Deserializeblock (blockbytes)/** Timestamp formatting the year in format must be fixed!!! This seems to be the time when Go was born. Unix (block. Timestamp, 0). Format ("2006-01-02 15:04:05") "2006-01-02 15:04:05" fixed, change other may also error */FMT. Printf ("\n#####\nheight:%d\nprevhash:%x\nhash:%x\ndata:%s\ntime:%s\nnonce:%d\n#####\n", block. Height, block. Prevblockhash, block. Hash, block. Data, time. Unix (block. Timestamp, 0). Format ("2006-01-02 15:04:05"), block. Nonce)} return nil}) if err! = Nil {log. Fatal (Err)} var hashint big. Int hasHint.setbytes (block. Prevblockhash)//traversal into the Genesis block, jump out of the loop Genesis block hash to 0 if big. Newint (0). CMP (&hashint) = = 0 {break} Curhash = block. Prevblockhash}}
Attention:
Time. Unix (block. Timestamp, 0). Format ("2006-01-02 15:04:05") Golang It's a wonderful place ... Timestamp format can only write "2006-01-02 15:04:05", a number can not be written wrong, otherwise you will be "crossed"!!! It is said that this date is the date of birth of the go language, is really proud of ah, for fear that we do not know???
Determine if the Blockchain database exists
//判断数据库是否存在func IsDBExists(dbName string) bool { //if _, err := os.Stat(dbName); os.IsNotExist(err) { // // return false //} _, err := os.Stat(dbName) if err == nil { return true } if os.IsNotExist(err) { return false } return true}
Block Chain iterator
The traversal of blockchain chunks has been implemented, but can also be optimized. It is not difficult to find that the chunk traversal of a blockchain is similar to the traversal of a one-way list, so can we create an iterator like the next property of the list, as long as we are able to iterate through all the chunks by constantly accessing next?
The answer, of course, is Kendang.
Blockchainiterator
//区块链迭代器type BlockchainIterator struct { //当前遍历hash CurrHash []byte //区块链数据库 DB *bolt.DB}
Next Iteration method
func (blcIterator *BlockchainIterator) Next() *Block { var block *Block //数据库查询 err := blcIterator.DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockTableName)) if b != nil { //获取当前迭代器对应的区块 currBlockBytes := b.Get(blcIterator.CurrHash) block = DeSerializeBlock(currBlockBytes) //更新迭代器 blcIterator.CurrHash = block.PrevBlockHash } return nil }) if err != nil { log.Fatal(err) } return block}
How to use it?
1. Add a new method to generate an iterator for the current blockchain in the Blockchain class
//生成当前区块链迭代器的方法func (blc *Blockchain) Iterator() *BlockchainIterator { return &BlockchainIterator{blc.Tip, blc.DB}}
2. Modify the previous Printchain method
//3.X 优化区块链遍历方法func (blc *Blockchain) Printchain() { //迭代器 blcIterator := blc.Iterator() for { block := blcIterator.Next() fmt.Printf("\n#####\nHeight:%d\nPrevHash:%x\nHash:%x\nData:%s\nTime:%s\nNonce:%d\n#####\n", block.Height, block.PrevBlockHash, block.Hash, block.Data, time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05"),block.Nonce) var hashInt big.Int hashInt.SetBytes(block.PrevBlockHash) if big.NewInt(0).Cmp(&hashInt) == 0 { break } }}
is not found to traverse the block code is relatively concise, here the database access and block iteration of the code into the Blockchainiterator, but also in line with the program design of the single principle of responsibility.
Main function test
package mainimport ( "chaors.com/LearnGo/publicChaorsChain/part4-DataPersistence-Prototype/BLC")func main() { blockchain := BLC.CreateBlockchainWithGensisBlock() defer blockchain.DB.Close() //添加一个新区快 blockchain.AddBlockToBlockchain("first Block") blockchain.AddBlockToBlockchain("second Block") blockchain.AddBlockToBlockchain("third Block") blockchain.Printchain()}
1. First run (no database exists at this time)
Maintest_1
2. Comment out three sentences Addblocktoblockchain code, run again
Maintest_2
We did not add chunks this time, so there is no mining process in the print area. But the printed chunks were added last Addblocktoblockchain, indicating that the chunk storage was successful.
2. Modify the Addblocktoblockchain segment code to run again
blockchain.AddBlockToBlockchain("4th Block")blockchain.AddBlockToBlockchain("5th Block")
Maintest_3
We see that the newly dug blocks were successfully added to the blockchain database in the same situation as the original block information. This demonstrates the success of our blockchain data persistence implementation.
.
.
.
.
The internet is disrupting the world and the blockchain is disrupting the internet!
---------------------------------------------20180624 20:16