让我们来实现 NewBlockchain函数,该函数创建一个blockchain实例同时添加genesis block,步骤如下:
- 打开数据库文件
- 检查是否有blockchain
- 如果有blockchain
- 创建一个blockchain实例
- 将blockchain的tip指向最新的block
- 如果没有blockchain
- 创建genesis block
- 存储在数据库中
- 将genesis block的hash值保存为最新的block hash值
- 创建一个新的blockchain实例,同时tip指向最新的block
代码如下:
func NewBlockchain() *Blockchain {
var tip []byte
db, err := bolt.Open(dbFile, 0600, nil)
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
if b == nil {
genesis := NewGenesisBlock()
b, err := tx.CreateBucket([]byte(blocksBucket))
err = b.Put(genesis.Hash, genesis.Serialize())
err = b.Put([]byte("l"), genesis.Hash)
tip = genesis.Hash
} else {
tip = b.Get([]byte("l"))
}
return nil
})
bc := Blockchain{tip, db}
return &bc
}
让我们一段一段来看实现。
db, err := bolt.Open(dbFile, 0600, nil)
上面代码是打开BoltDB文件的标准方式,如果文件不存在不会反馈错误。
err = db.Update(func(tx *bolt.Tx) error {
...
})
在BoltDB中,数据库操作都在一个事务中运行。BoltDB有两种事务:只读事务和读写事务。在这里,由于可能会存在添加genesis block的操作,因此我们使用读写事务(db.Update(...))。
b := tx.Bucket([]byte(blocksBucket))
if b == nil {
genesis := NewGenesisBlock()
b, err := tx.CreateBucket([]byte(blocksBucket))
err = b.Put(genesis.Hash, genesis.Serialize())
err = b.Put([]byte("l"), genesis.Hash)
tip = genesis.Hash
} else {
tip = b.Get([]byte("l"))
}
上述代码是核心逻辑。首先,我们尝试获取一个bucket,如果bucket存在,则将tip设置为key(l)的值;如果bucket不存在,则创建genesis block、创建bucket、存储genesis block、将tip设置为genesis block的hash值。
此外,注意创建blockchain的方法有了变化:
bc := Blockchain{tip, db}
现在不再存储任何block,而仅仅存储blockchain的tip,同时为了避免程序运行过程中数据库反复被打开,因此blockchain中会存储已打开的数据库链接,blockchain的结构修改如下:
type Blockchain struct {
tip []byte
db *bolt.DB
}
接下来,修改AddBlock方法:新增一个block会变得复杂一些:
func (bc *Blockchain) AddBlock(data string) {
var lastHash []byte
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("l"))
return nil
})
newBlock := NewBlock(data, lastHash)
err = bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash, newBlock.Serialize())
err = b.Put([]byte("l"), newBlock.Hash)
bc.tip = newBlock.Hash
return nil
})
}
我们来一段一段来看:
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("l"))
return nil
})
首先使用只读事务获取当前数据库中最新block的hash值。
newBlock := NewBlock(data, lastHash)
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash, newBlock.Serialize())
err = b.Put([]byte("l"), newBlock.Hash)
bc.tip = newBlock.Hash
在挖到一个新block后,我们将序列化后的结果存储到数据库文件中同时将key(l)值更新为最新block的hash值。
完成!一切简单明了。