简介
上一次已经实现了区块链的基本结构和功能,但只能运行在内存中,一关闭程序就会失去所有的数据,作为一个数字货币这显然是不行的。事实上区块链本来就是一个分布式的数据库,当作为数字货币时便用来存储交易信息。由于时间限制,这次就先将我们的区块链在本地的数据库中持久化,以达到”记账“的目的。同时,由于我们所需要的功能只是简单的K/V存储,所以并不选用MYSQL等常见的多功能的数据库,而采用同样基于go编写的较为简单的boltDB。
先来改变一下chain的结构,由于是存储在数据库中,我们弃用原本的array结构,在数据库中额外指定一个"last_hash"键用来标记最新Block的Hash,从而方便地连接新的Block或是遍历查询历史Block。
type BlockChain struct {
//BlockChain []*Block
LastHash []byte
DB *bolt.DB
}
由于是存储在数据库中,Block需要添加相应的序列化和反序列化方法
func (block *Block) SerializeBlock() []byte {
var result bytes.Buffer
encoder := gob.NewEncoder(&result)
err := encoder.Encode(block)
if err != nil {
log.Panic(err)
}
return result.Bytes()
}
func DeserializeBlock(blockBytes []byte) *Block{
var block Block
var reader = bytes.NewReader(blockBytes)
decoder := gob.NewDecoder(reader)
err := decoder.Decode(&block)
if err != nil {
log.Panic(err)
}
return &block
}
创建区块链的方法也相应改写
func CreateBlockChain(data string) *BlockChain{
if isDBExist() {
fmt.Println("[#] Database already exists......")
db, err := bolt.Open(DBNAME, 0600, nil)
if err != nil {
log.Fatal(err)
}
var blockChain *BlockChain
err = db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(BLOCKTABLENAME))
if b != nil {
hash := b.Get([]byte("last_hash"))
blockChain = &BlockChain{hash, db}
}
return nil
})
if err != nil {
log.Fatal(err)
}
return blockChain
}
fmt.Println("[#] Creating a database ......")
genesisBlock := CreateGenesisBlock(data)
db, err := bolt.Open(DBNAME, 0600, nil)
if err != nil {
log.Fatal(err)
}
err = db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte(BLOCKTABLENAME))
if err != nil {
log.Panic(err)
}
if b != nil {
err = b.Put(genesisBlock.Hash, genesisBlock.SerializeBlock())
if err != nil {
log.Panic("[#] Failed to add the block ......")
}
b.Put([]byte("last_hash"), genesisBlock.Hash)
}
return nil
})
if err != nil {
log.Panic(err)
}
return &BlockChain{genesisBlock.Hash, db}
}
添加新的区块只需通过"last_hash"键拿到最近的Block,再把本区块的prevHash设为它的hash,将height递增即可。
//添加新区块
func (blockChain *BlockChain) AddNewBlock(data string) {
err := blockChain.DB.Update(func(tx *bolt.Tx) error {
table := tx.Bucket([]byte(BLOCKTABLENAME))
if table != nil {
blockBytes := table.Get(blockChain.LastHash)
lastBlock := DeserializeBlock(blockBytes)
newBlock := NewBlock(data, lastBlock.Hash, lastBlock.Height + 1)
err := table.Put(newBlock.Hash, newBlock.SerializeBlock())
if err != nil {
log.Panic(err)
}
table.Put([]byte("last_hash"), newBlock.Hash)
blockChain.LastHash = newBlock.Hash
}
return nil
})
if err != nil {
log.Panic(err)
}
}
针对区块链的遍历,另外设计一个迭代器结构
type Iterator struct {
CurrentHash [] byte //当前区块的hash
DB *bolt.DB
}
func GetInterator(chain *BlockChain) *Iterator{
return &Iterator{
CurrentHash: chain.LastHash,
DB: chain.DB,
}
}
通过Next方法便可以不断地得到前一个Block,可类比链表的遍历。
func (bcIterator *Iterator) Next() *Block {
block := &Block{}
err := bcIterator.DB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(BLOCKTABLENAME))
if b != nil{
blockBytes := b.Get(bcIterator.CurrentHash)
block = DeserializeBlock(blockBytes)
bcIterator.CurrentHash = block.PrevHash
}
return nil
})
if err != nil{
log.Panic(err)
}
return block
}
最后放到main里跑了几次
func main() {
blockChain := BlockChain.CreateBlockChain("[#] genesis block created......")
fmt.Println("[#] genesis block created......")
blockChain.AddNewBlock("[#] first block created......")
fmt.Println("[#] first block created......")
iterator := BlockChain.GetInterator(blockChain)
block := iterator.Next()
for big.NewInt(0).Cmp(new(big.Int).SetBytes(block.PrevHash)) != 0 {
fmt.Println("[#] Height : ", block.Height)
fmt.Println("[#] Nonce : ", block.Nonce)
fmt.Println("[#] Time stamp : ", block.TimeStamp)
fmt.Printf("[#] Previous hash : %x\n", block.PrevHash)
fmt.Printf("[#] Hash : %x\n", block.Hash)
fmt.Println("[#] Data : ", block.Data)
block = iterator.Next()
}
defer blockChain.DB.Close()
//db, err := bolt.Open("block_chain.db", 0600, nil)
//if err != nil {
// log.Fatal(err)
//}
//defer db.Close()
}
结果如下,说明我们的持久化和遍历都已经具备正常功能。
那么持久化到这里就结束了。下一步将会考虑将”交易(Transaction)“引入,以及为这个数字货币系统提供相对友好的交互界面。