打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
我的区块链技术学习笔记(六):数据库和序列化概述

继续上一篇文章的话题, 在 BoltDB 中,值只能是 byte 类型,但是我们想要存储 Block 结构。所以,我们需要使用 encoding/gob 来对这些结构进行序列化。

让我们来实现 Block 的 Serialize 方法(为了简洁起见,此处略去了错误处理):

func (b *Block) Serialize byte { var result bytes.Buffer encoder := gob.NewEncoder(&result) err := encoder.Encode(b) return result.Bytes}

这个部分比较直观:

首先,我们定义一个 buffer 存储序列化之后的数据。然后,我们初始化一个 gob encoder 并对 block 进行编码,结果作为一个字节数组返回。

接下来,我们需要一个解序列化的函数,它会接受一个字节数组作为输入,并返回一个 Block. 它不是一个方法(method),而是一个单独的函数(function)

func DeserializeBlock(d []byte) *Block { var block Block decoder := gob.NewDecoder(bytes.NewReader(d)) err := decoder.Decode(&block) return &block}

说完序列化,再来说说数据库,让我们从 NewBlockchain 函数开始。 在之前的实现中,NewBlockchain 会创建一个新的 Blockchain实例,并向其中加入创世块。而现在,我们希望它做的事情有:

打开一个数据库文件

检查文件里面是否已经存储了一个区块链

如果已经存储了一个区块链:

创建一个新的 Blockchain 实例

设置 Blockchain 实例的 tip 为数据库中存储的最后一个块的哈希

如果没有区块链:

创建创世块

存储到数据库

将创世块哈希保存为最后一个块的哈希

创建一个新的 Blockchain 实例,初始时 tip 指向创世块(tip 有尾部,尖端的意思,在这里 tip 存储的是最后一个块的哈希)

代码大概是这样:

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 中,数据库操作通过一个事务(transaction)进行操作。有两种类型的事务:只读(read-only)读写(read-write)。这里,打开的是一个读写事务(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:如果存在,就从中读取 l 键;如果不存在,就生成创世块,创建 bucket,并将区块保存到里面,然后更新 l 键以存储链中最后一个块的哈希。

另外,注意创建 Blockchain 一个新的方式:

bc := Blockchain{tip, db}

这次,我们不在里面存储所有的区块了,而是仅存储区块链的 tip。另外,我们存储了一个数据库连接。因为我们想要一旦打开它的话,就让它一直运行,直到程序运行结束。因此,Blockchain 的结构现在看起来是这样:

type Blockchain struct { tip byte db *bolt.DB}

接下来我们想要更新的是 AddBlock 方法:现在向链中加入区块,就不是像之前向一个数组中加入一个元素那么简单了。从现在开始,我们会将区块存储在数据库里面:

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

这是 BoltDB 事务的另一个类型(只读)。在这里,我们会从数据库中获取最后一个块的哈希,然后用它来挖出一个新的块的哈希:

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

检查区块链

现在,产生的所有块都会被保存到一个数据库里面,所以我们可以重新打开一个链,然后向里面加入新块。但是在实现这一点后,我们失去了之前一个非常好的特性:再也无法打印区块链的区块了,因为现在不是将区块存储在一个数组,而是放到了数据库里面。让我们来解决这个问题!

BoltDB 允许对一个 bucket 里面的所有 key 进行迭代,但是所有的 key 都以字节序进行存储,而且我们想要以区块能够进入区块链中的顺序进行打印。此外,因为我们不想将所有的块都加载到内存中(因为我们的区块链数据库可能很大!或者现在可以假装它可能很大),我们将会一个一个地读取它们。故而,我们需要一个区块链迭代器(BlockchainIterator):

type BlockchainIterator struct { currentHash byte db *bolt.DB}

每当要对链中的块进行迭代时,我们就会创建一个迭代器,里面存储了当前迭代的块哈希(currentHash)和数据库的连接(db)。通过 db,迭代器逻辑上被附属到一个区块链上(这里的区块链指的是存储了一个数据库连接的 Blockchain 实例),并且通过 Blockchain 方法进行创建:

func (bc *Blockchain) Iterator *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} return bci}

注意,迭代器的初始状态为链中的 tip,因此区块将从头到尾,也就是从最新的到最旧的进行获取。实际上, 选择一个 tip 就是意味着给一条链“投票”。一条链可能有多个分支,最长的那条链会被认为是主分支。在获得一个 tip (可以是链中的任意一个块)之后,我们就可以重新构造整条链,找到它的长度和需要构建它的工作。这同样也意味着,一个 tip 也就是区块链的一种标识符。

BlockchainIterator 只会做一件事情:返回链中的下一个块。

func (i *BlockchainIterator) Next *Block { var block *Block err := i.db.View(func(tx *bolt.Tx) error { b := tx.Bucket(byte(blocksBucket)) encodedBlock := b.Get(i.currentHash) block = DeserializeBlock(encodedBlock) return nil }) i.currentHash = block.PrevBlockHash return block}

这就是数据库部分的内容了!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
200行Go代码实现自己的区块链——区块生成与网络通信
区块链学习(三)区块链实例
区块链入门学习(1)
简单区块链编写之javascript
区块链实战(二)PoW工作量证明的实现
搭建自己的区块链,人人能写区块链
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服