本章,我们会通过levelDB对区块链的数据进行持久化操作。持久化主要涉及的是对levelDB的使用,如果不懂的地方可以看看leveldb的API。此处用到最多的API就两个,put
和get
。
持久化的数据结构
在实现之前,我们首先定义需要持久化数据的结构。此处,我们还是参考比特币的实现。
在比特币中,将两类主要的数据储存在两个bucket中:
1 | 1. blocks: 保存所有区块的信息 |
在blocks bucket中,采用key -> value的形式,保存了如下数据:
1 |
|
chainstate bucket中,保存的数据为:
1 |
|
关于比特币的实现有兴趣的朋友还可以读一读bitcoin core:_Data_Storage)
当前的区块链还没涉及对交易的处理,因此我们暂时不储存chainstate,也不考虑通过索引和分片来优化数据的存取速度,仅仅在数据库中保存blocks的主要信息, 包括:
1 |
|
由于区块链是一个链式结构,因此只要我们通过l
字段拿到最后一个区块的hashID,我们就可以顺藤摸瓜,遍历整个区块链了。
具体实现
由于数据库中只能储存序列化的数据,因此,我们首先要对区块数据进行序列化/反序列化操作。在JS中,最常用的做法,自然是使用stringify:
1 |
|
增加了一个timestamp字段来记录区块的创建时间。
反序列化:
1 |
|
有了序列化的区块数据,我们就可以将他们储存到数据库中。
通过对区块数据进行序列化,我们就可以将它储存到数据库中;对数据库中的数据执行反序列化,我们就可以在内存中实例化区块。将区块信息实例化之后,我们就可以初始化整个区块链。
这里有两种情况需要考虑:已经存在区块链数据以及区块信息为空的情况。下面简单说说解决的步骤:
访问数据库
查看数据库中是否已经储存了区块链数据
若已经储存了区块链数据:
创建一个新的blockchain实例
读取数据库中的区块链信息,并用它来初始化区块链
若还未储存数据:
调用
newGenesisBlock
创建创世区块将区块信息保存到数据库中
将创世区块的HashID保存到
l
字段中用创世区块数据来初始化一个新的区块链
读取数据库并初始化区块链的代码如下:
1 |
|
首先我们调用level(DB)
与数据库建立连接,接着调用db.get('l')
获取最后一个区块的hashID,若存在,则我们初始化区块链,如果不存在,则调用newGenesisBlock
后,使用db.put
将区块信息写入到数据库中,再初始化区块链。
区块链初始化以后,我们添加如下代码,赋予节点向区块链中写入并持久化数据的能力:
1 |
|
在addBlock
方法中,首先通过getLashHash
函数拿到最新的区块ID,接着将它作为下一个区块的parentHashId,创建一个新的区块,最后对新区块执行序列化操作并写入到数据库中。完成这一些操作之后,我们会将数据库中的l
字段更新为刚刚创建的区块的hashID。
通过这几个方法,我们已经可以将区块链储存到本地的数据库中,而不必在每次程序运行时生成一个新的区块链。但此处还有一个问题:
比如当我们需要查找区块链中是否包含某些数据时,我们还并没有一个有效的方法去遍历这个区块链。下我们通过实现一个迭代器来实现这个功能:
1 |
|
现在,通过不断调用迭代器提供的next
方法,我们就可以遍历整个区块链了。
下面,我们编写一段简单的测试代码,看看效果:
1 |
|
连续调用两次该操作后,控制台的输出为:
1 |
|
看来我们成功更新了数据库中的区块链信息,mission complete!
结论
这一章我们实现了对区块链数据的持久化。前面我们提到过,比特币的持久化包含对block
和chainstate
,下一章,我们会实现一个基本的交易utxo模型。在那之后,我们再回过头实现对chainstate
的持久化。