Article directory
Link: Blockchain project github address
Current progress of the project:
![![insert here](https://img-blog.csdnimg.cn/37d6242263154bde9e4aceb88a576bb8.png)
Bolt database installation
Bolt database introduction:
The bolt database is a lightweight dataset provided for the go language. It is a simple and fast relational database, in which data is
stored in []byte. The indexing method is the key/value method, that is, the data is indexed by keywords.
Bolt project address: link
Installation steps:
1. Run in the terminal under the project address
go get github.com/boltdb/bolt/...
2. Add statement to the .mod file of the current project
require (
github.com/boltdb/bolt v1.3.1 // indirect
golang.org/x/sys v0.3.0 // indirect
)
3. After modifying the mod file, you need to run
go mod vendor
update
Use bolts for persistent storage
Compared with the previous blockchain program, using persistent storage means storing the blockchain in the database for preservation and persistence. Compared with the previous unpersisted program, the modifications are as follows:
1. Persist the genesis block
2. Persist the new block
Basic steps for bolt persistence
1. Create or open a database, dbName is the name of the database, 0600 indicates the authority of the database (the binary code of 0600 is 111,000,000, indicating that the administrator has read and write authority, but no execution authority), nil indicates the default operation
db, err := bolt.Open(dbName, 0600, nil)
2. Perform the operation of creating a data table on the database, where bucket means the meaning of the data table
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
if b == nil {
//数据表不存在
b, err := tx.CreateBucket([]byte(blockTableName))
if err != nil {
log.Panicf("create backet [%s] failed %v\n", blockTableName, err)
}
}
return nil
})
3. Add and insert data operations, b.get means to index the value through the key, and the obtained value is in the format of []byte, b.put means to store the value, where the data must be stored in the form of key-value pairs.
bc.DB.Update(func(tx *bolt.Tx) error {
//获取数据库桶
b := tx.Bucket([]byte(blockTableName))
if b != nil {
blockTypes := b.Get(bc.Tip)
err = b.Put([]byte("l"), newBlock.Hash)
if err != nil {
log.Panicf("save the latest block hash failed %v\n", err)
}
}
return nil
})
Genesis Block Persistence
Original way:
func CreateBlockChainWithGenesisBlock() *BlockChain {
//生成创世区块
block := CreateGenesisBlock([]byte("the first block"))
return &BlockChain{
[]*Block{
block}}
}
// 生成创世区块
func CreateGenesisBlock(data []byte) *Block {
return NewBlock(nil, 1, data)
}
Persistence method:
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
if b == nil {
//数据表不存在
b, err := tx.CreateBucket([]byte(blockTableName))
if err != nil {
log.Panicf("create backet [%s] failed %v\n", blockTableName, err)
}
//生成创世区块
genesisBlock := CreateGenesisBlock([]byte("the first block"))
//键为区块的哈希,值为区块的序列化
err = b.Put(genesisBlock.Hash, genesisBlock.Serialize())
if err != nil {
log.Panicf("insert the genesis block failed %v\n", err)
}
blockHash = genesisBlock.Hash
//数据库中也存储最新区块的哈希
err = b.Put([]byte("l"), genesisBlock.Hash)
if err != nil {
log.Panicf("save the latest hash of genesis block failed %v\n", err)
}
} else {
//数据表已存在
blockHash = b.Get([]byte("l"))
}
return nil
})
if err != nil {
log.Panicf("create db [%s] failed %v\n", dbName, err)
}
Persistence of new blocks
Original way:
func (bc *BlockChain) AddBlock(prevBlockHash []byte, height int64, data []byte) {
var newBlock *Block
newBlock = NewBlock(prevBlockHash, height, data)
bc.Blocks = append(bc.Blocks, newBlock)
}
Persistence method:
bc.DB.Update(func(tx *bolt.Tx) error {
//获取数据库桶
b := tx.Bucket([]byte(blockTableName))
if b != nil {
//获取对应区块
blockTypes := b.Get(bc.Tip)
//区块结构反序列化
latestBlock := DeserializeBlock(blockTypes)
//新建区块,传入参数:prevBlockHash []byte, height int64, data []byte
newBlock := NewBlock(latestBlock.Hash, latestBlock.Height+1, data)
//存入数据库
bc.Tip = newBlock.Hash
err := b.Put(newBlock.Hash, newBlock.Serialize())
if err != nil {
log.Panicf("insert the new block failed %v\n", err)
}
err = b.Put([]byte("l"), newBlock.Hash)
if err != nil {
log.Panicf("save the latest block hash failed %v\n", err)
}
}
return nil
})
Improve the basic functions of blockchain
Since the blockchain does not support deletion and modification, the functions of the current project blockchain include creating a genesis block, adding a block, and traversing the blockchain. In the future, it is planned to improve multiple functions such as transaction information and mining. . Compared with the previous functions, the improvements include: 1. Persistence, 2. New traversal function, 3. New iterator
Genesis block creation
By serializing the structure, the block can be stored in the database by means of (block hash –> block information), which facilitates the indexing of the previous block after obtaining the hash of the previous block later .
// 初始化区块链
func CreateBlockChainWithGenesisBlock() *BlockChain {
//存储最新区块链哈希
var blockHash []byte
//1.创建或打开一个数据库
db, err := bolt.Open(dbName, 0600, nil)
//2.创建一个桶,将创世区块存入数据库中
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
if b == nil {
//数据表不存在
b, err := tx.CreateBucket([]byte(blockTableName))
if err != nil {
log.Panicf("create backet [%s] failed %v\n", blockTableName, err)
}
//生成创世区块
genesisBlock := CreateGenesisBlock([]byte("the first block"))
//键为区块的哈希,值为区块的序列化
err = b.Put(genesisBlock.Hash, genesisBlock.Serialize())
if err != nil {
log.Panicf("insert the genesis block failed %v\n", err)
}
blockHash = genesisBlock.Hash
//数据库中也存储最新区块的哈希
err = b.Put([]byte("l"), genesisBlock.Hash)
if err != nil {
log.Panicf("save the latest hash of genesis block failed %v\n", err)
}
}
return nil
})
if err != nil {
log.Panicf("create db [%s] failed %v\n", dbName, err)
}
return &BlockChain{
DB: db, Tip: blockHash}
}
Serialization code of Block:
// 区块结构序列化,结构体->字节数组
func (block *Block) Serialize() []byte {
var buffer bytes.Buffer
//序列化结构体
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(block)
if err != nil {
log.Panicf("serialize the block to byte[] failed %v", err)
}
return buffer.Bytes()
}
Block deserialization code
// 区块结构反序列化,字节数组->结构体
func DeserializeBlock(blockByte []byte) *Block {
var block Block
decoder := gob.NewDecoder(bytes.NewReader(blockByte))
err := decoder.Decode(&block)
if err != nil {
log.Panicf("deserialize the byte[] to block failed %v", err)
}
return &block
}
add blocks
func (bc *BlockChain) AddBlock(data []byte) {
//更新区块数据(insert)
bc.DB.Update(func(tx *bolt.Tx) error {
//获取数据库桶
b := tx.Bucket([]byte(blockTableName))
if b != nil {
//获取对应区块
blockTypes := b.Get(bc.Tip)
//区块结构反序列化
latestBlock := DeserializeBlock(blockTypes)
//新建区块,传入参数:prevBlockHash []byte, height int64, data []byte
newBlock := NewBlock(latestBlock.Hash, latestBlock.Height+1, data)
//存入数据库
bc.Tip = newBlock.Hash
err := b.Put(newBlock.Hash, newBlock.Serialize())
if err != nil {
log.Panicf("insert the new block failed %v\n", err)
}
err = b.Put([]byte("l"), newBlock.Hash)
if err != nil {
log.Panicf("save the latest block hash failed %v\n", err)
}
}
return nil
})
}
traverse the blockchain
Here, an iterator is used to optimize the blockchain and improve code readability.
func (bc *BlockChain) PrintBlockChain() {
var curBlock *Block
var iter *BlockChainIterator = bc.Iterator()
fmt.Printf("打印区块链完整信息。。。\n")
//循环读取
for {
curBlock = iter.Next()
fmt.Printf("-------------------------------------------\n")
fmt.Printf("\tHash : %x\n", curBlock.Hash)
fmt.Printf("\tPrevBlockHash : %x\n", curBlock.PrevBlockHash)
fmt.Printf("\tHeight : %x\n", curBlock.Height)
fmt.Printf("\tData : %v\n", curBlock.Data)
fmt.Printf("\tNonce : %x\n", curBlock.Nonce)
fmt.Printf("\tTimeStamp : %x\n", curBlock.TimeStamp)
//退出条件,由于哈希值的大数特性,需要使用函数进行比较
var hashInt big.Int
hashInt.SetBytes(iter.curHash)
if big.NewInt(0).Cmp(&hashInt) == 0 {
//遍历到了创世区块
break
}
}
The iterator is implemented as follows:
// 实现迭代函数next()
func (iter *BlockChainIterator) Next() *Block {
var curBlock *Block
err := iter.DB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
if b != nil {
Block := b.Get(iter.curHash)
curBlock = DeserializeBlock(Block)
//更新迭代器当前指向结点哈希
iter.curHash = curBlock.PrevBlockHash
}
return nil
})
if err != nil {
log.Panicf("Iterator the db failed%v\n", err)
} else {
return curBlock
}
return nil
}