上一次我们为自己的区块链引入了工作量证明,这个区块链目前还缺乏一系列重要的特性。我们知道,本质上区块链是一个分布式数据库,接下来我们要为这个区块链引入数据库的特性,并实现一个 CLI (命令行接口) 来操作区块链。
数据库
选择哪种数据库?
截止目前,我们的区块链都是在运行时存储在内存中的,不能与其他人共享,也不能重用,因此我们需要一个数据库来存储区块链。
选择哪种数据库其实取决于开发者意愿,你对哪种熟悉就可以用哪种。比特币最初的选择是 LevelDB,这里我们选择 Go 的自带的 K/V 嵌入式数据库 BoltDB,它有这么几个特点:
简单
基于Go
不用服务端
支持完全可序列化
没有查询语句
BoltDB 在 github 上如是说,Bolt是基于纯 Go 语言开发的 KV 存储,灵感来自于 Howard Chu 的 LMDB 项目。该项目目标是开发一个简单、快速、可靠的无服务端的数据库。API 非常小巧和简洁,仅仅关注如何获取或设置数据,这就是全部。
BoltDB 没有数据类,存储时的 key 和 value 都是字符串类型,而我们的区块都是自定义结构体,因此在存取数据时要对我们的数据进行序列化和反序列化。当然有很多种方式序列化了,比如 Json、XML 之类的,我们这里采用 Go 的标准库 encoding/gob 来实现。
数据库结构
我们在设计存储和操作数据的逻辑之前,我们先要明确数据在数据库中的结构是什么样的,参考比特币的做法。
设计两个存储桶:
blocks 用于存储描述所有区块的元数据信息
chainstate 用于存储区块链状态,包含交易元数据和部分元数据
同时,出于性能考虑,每个 block 都是单独的一个 KV 文件。这里我们为了简化都存储在一个文件中。
目前,还没有设计交易的特性,因此我们只需要关心 blocks 存储桶即可,正如上面所说我们我们简单的先把所有区块信息存在一个文件中,那我们的数据库结构就很简单了,这里我用 key : value 表示一下:
32 字节的 A 区块 hash : 序列化的 A 区块
32 字节的 B 区块 hash : 序列化的 B 区块
...
last : 最后一个区块的 hash
以上就是我们即将实现的所有持久化信息。
序列化与持久化
ok,现在我们数据结构出来了,我们可以开始一步步改造我们的区块链,让它拥有数据库特性。
序列化
因为存储的是字符串数据,首先我们要做的就是把区块数据序列化和反序列化。
基于 encoding/gob 的方式可以很轻易的实现一个序列化方法:
首先声明一个 buffer,然后初始化一个 gob 编码 block ,最后返回编码后的字节数组。
然后我们实现一个反序列化方法将字节数组解码成一个区块:
持久化
我们现在要改造之前生成区块等一系列操作区块链的工作从操作内存改为操作数据库。
首先来改造创建区块链的工作,原先创建区块链的工作流程就是创建一个创世块并添加到链中,我们现在把流程丰富一下并结合我们的数据库:
打开数据库文件
检查是否有 blockchain
如果有 blockchain
创建一个 blockchain 实例
设置这个实例的的 tip 值为最新的区块 hash
如果没有 blockchain
创建创世块
存储到数据库
将创世块的 hash 值设为 tip
按照上述步骤,我们改造一下 BlockChain 的结构:
然后改造创建区块链的方法:
至此,我们不再把区块存在内存中了,而是改为持久化的存储到数据库中。
接下来我们改造创建区块的方法:
创建区块不再像之前一个个往区块列表中添加元素了,而是改为往数据库中写入,首先读取数据中最后区块的 hash,创建新区块,然后往数据中插入如下键值对:
新区块的hash : 区块序列化值
并更新 last 的值为最新区块的 hash。
至此整个区块链的持久化工作结束了。
获取区块链
之前我们把区块链存在内存中能够很轻松的打印区块链,获取区块链中的每一个区块。现在我们把区块链的所有区块持久化以后,该怎么获取到整个区块链,并且按照区块生成的顺序获取呢?
首先我们不能够一次性把所有的区块都从数据库中读出来,因为我们的区块链数据库很可能会非常大,我们最好的方式是从一个区块开始往前一个个读,如果我们从最后一个区块开始往前读就可以获取到整个区块链。
当然我们可以从区块链中任意一个区块的 hash 开始,实际上,选择一个开始的区块 hash 在区块链中意味着投票,因为区块链很有可能很多分支,最长的那条链就是主分支。我们可以从任意一个区块开始,指定长度把它从数据库中读出来构建成一条新链。换句话来说,一个区块链的 tip 值(最后那个区块的 hash)就是区块链的一个标识。
好了,首先我们定义区块链迭代器的结构和获取迭代器的方法:
迭代器结构
获取迭代器,注意到这里的迭代器为了方便我们选择从区块链的 tip 值开始,也就是最后一个区块
然后我们定义一个 next() 方法来往后迭代获取上一个区块:
ok,到这里整个区块链与数据库打交道的地方就都已经完成了。
CLI
做为一名脚手架爱好者,我们当然不想每次都运行 来简单的运行几个示例数据,我们现在可以设计几个简单的交互命令来管理我们的区块链(因为我们的区块链已经持久化了,妈妈再也不用担心只能运行一次的问题了)。命令设计如下:
往区块链中添加一个区块
打印整个区块链
好,我们首先定义一个 CLI 的结构体,所有的脚手架操作都通过它来操作:
定义脚手架入口函数,功能很简单:
校验命令行参数
解析命令行参数
根据参数决定执行的方法
如果是添加区块,再判断一下数据是不是空
如果是打印区块链,直接打印整个区块链
再把相应的功能函数补充完整:
校验参数
使用 demo
添加区块
打印区块链
最后我们再 文件中调用上述的入口函数,简单的 CLI 到这里就做完了。
运行结果
能坚持到这里的小伙伴一定都是真爱,让我们来看看 CLI 运行的效果吧。
build 完第一次运行会是酱的,因为我们此时没有进行任何参数的设置:
辣我们现在试着往区块链中添加一个区块:
再试试打印整个区块链吧:
小结
完美,我们现在的区块链已经有了持久化和简单的 CLI 了,是不是看上去稍微完整了一点了呢。
路漫漫其修远兮,吾将上下而求索
MalukChain 的代码地址:
https://git.coding.net/MrNullPoint/MalukChain.git
领取专属 10元无门槛券
私享最新 技术干货