用GO语言构建区块链——第三部分:持久性和CLI

 前言:

 到目前为止,我们已经建立了一个工作证明系统的区块链,这使得开采一个区块链成为可能。我们越来越接近实现功能齐全的区块链,但它仍然缺乏一些重要的功能。今天将开始在数据库中存储区块链,之后我们将创建一个简单的命令行界面来执行区块链操作。从本质上讲,区块链是一个分布式数据库。我们暂时将省略“分布式”部分,并专注于“数据库”部分。

数据库选择

目前,我们的实施中没有数据库; 相反,我们每次运行程序时都会创建块并将它们存储在内存中。我们无法重复使用区块链,我们无法与其他人共享,因此我们需要将其存储在磁盘上。

我们需要哪个数据库?实际上,他们中的任何一个。在最初的比特币文件中,没有任何关于使用某个数据库的说法,因此要由开发人员使用DB。比特币核心,最初由Satoshi Nakamoto发布,目前是比特币的参考实现,使用LevelDB(尽管它仅在2012年被引入客户端)。我们会用……

BoltDB

因为:

它简单而简约。

它在Go中实现。

它不需要运行服务器。

它允许构建我们想要的数据结构。

来自BoltDB的Github上的README:

Bolt是一个纯粹的Go / key / store商店,受到Howard Chu的LMDB项目的启发。该项目的目标是为不需要完整数据库服务器(如Postgres或MySQL)的项目提供简单,快速,可靠的数据库。

由于Bolt旨在用作这种低级功能,因此简单性是关键。API很小,只关注获取值和设置值而已。

听起来非常适合我们的需求!让我们花点时间回顾一下。

BoltDB是一个键/值存储,这意味着没有像SQL RDBMS(MySQL,PostgreSQL等)中的表,没有行,没有列。相反,数据存储为键值对(如Golang地图中)。键值对存储在存储桶中,存储桶用于对类似的对进行分组(这类似于RDBMS中的表)。因此,为了获得一个值,您需要知道一个桶和一个密钥。

BoltDB的一个重要特点是没有数据类型:键和值是字节数组。由于我们将Go结构(特别是Block)存储在其中,我们需要对它们进行序列化,即实现将Go结构转换为字节数组并从字节数组中恢复的机制。我们将为此使用编码/gob,也可以用JSON,XML,Protocol Buffers等。我们使用encoding/gob因为它很简单,是标准Go库的一部分。

数据库结构

在开始实现持久性逻辑之前,我们首先需要决定如何在数据库中存储数据。为此,我们将参考比特币核心的方式。

简单来说,比特币核心使用两个“存储桶”来存储数据:

 1. blocks 存储描述链中所有块的元数据。

 2. chainstate 存储链的状态,这是当前未使用的事务输出和一些元数据。

     此外,块在磁盘上作为单独的文件存储。这样做是出于性能目的考虑:读取单个块不需要将所有(或部分)块加载到内存中。我们不会实现这一点。  

用GO语言构建区块链——第三部分:持久性和CLI

由于我们还没有交易,我们只有blocks存货桶。另外,如上所述,我们将整个DB存储为单个文件,而不将块存储在单独的文件中。所以我们不需要任何与文件编号相关的内容。所以这些是key -> value我们将使用的对:

 32-byte block-hash -> Block structure (serialized)

  ‘l’ -> the hash of the last block in a chain

这就是我们开始实现持久性机制时需要知道的全部内容。

序列化

如前所述,在BoltDB中,值只能是[]byte类型,我们希望将Block结构存储在DB中。我们将使用encoding / gob来序列化结构。

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

      用GO语言构建区块链——第三部分:持久性和CLI

这篇文章很简单:首先,我们声明一个存储序列化数据的缓冲区; 然后我们初始化一个gob编码器并对块进行编码; 结果以字节数组的形式返回。

接下来,我们需要一个反序列化函数,它将接收一个字节数组作为输入并返回一个Block。这不是一种方法,而是一种独立的功能:

   用GO语言构建区块链——第三部分:持久性和CLI

这就是序列化!

坚持

让我们从这个NewBlockchain功能开始吧。目前,它创建了一个新实例,Blockchain并为其添加了genesis块。我们希望它做的是:

用GO语言构建区块链——第三部分:持久性和CLI

在代码中,它看起来像这样:

用GO语言构建区块链——第三部分:持久性和CLI

让我们一块一块地回顾一下。

用GO语言构建区块链——第三部分:持久性和CLI

这是打开BoltDB文件的标准方法。请注意,如果没有这样的文件,它将不会返回错误。

用GO语言构建区块链——第三部分:持久性和CLI

在BoltDB中,使用数据库的操作在事务中运行。并且有两种类型的事务:只读和读写。在这里,我们打开一个读写事务(db.Update(…)),因为我们希望将genesis块放在DB中。

用GO语言构建区块链——第三部分:持久性和CLI

这是该功能的核心。在这里,我们获取存储块的存储桶:如果存在,我们l从中读取密钥; 如果它不存在,我们生成创世块,创建存储桶,将块保存到其中,并更新存储链的最后一个块哈希的密钥。

另外,请注意创建的新方法Blockchain:

用GO语言构建区块链——第三部分:持久性和CLI

我们不再存储其中的所有块,而是仅存储链的尖端。此外,我们存储数据库连接,因为我们想要打开它一次并在程序运行时保持打开状态。因此,Blockchain结构现在看起来像这样:

用GO语言构建区块链——第三部分:持久性和CLI

我们要更新的下一件事是AddBlock方法:现在向链添加块并不像向数组添加元素那么容易。从现在开始,我们将在数据库中存储块:

用GO语言构建区块链——第三部分:持久性和CLI

让我们逐一地回顾一下:

用GO语言构建区块链——第三部分:持久性和CLI

这是BoltDB事务的另一种(只读)类型。在这里,我们从DB获取最后一个块哈希,以使用它来挖掘新的块哈希。

用GO语言构建区块链——第三部分:持久性和CLI

在挖掘新块之后,我们将其序列化表示保存到DB中并更新l密钥,该密钥现在存储新块的哈希。

完成!这不难,是吗?

检查区块链

所有新块现在都保存在数据库中,因此我们可以重新打开区块链并向其添加新块。但是在实现之后,我们失去了一个很好的功能:我们不能再打印出区块链块了,因为我们不再将块存储在数组中。让我们解决这个缺陷吧!

BoltDB允许迭代桶中的所有键,但键按字节排序顺序存储,我们希望块按照它们在区块链中的顺序打印。另外,因为我们不想将所有块加载到内存中(我们的区块链数据库可能很大!或者只是假装它可以),我们将逐一阅读它们。为此,我们需要一个区块链迭代器:

用GO语言构建区块链——第三部分:持久性和CLI

每次我们想要迭代区块链中的块时,都会创建一个迭代器,它将存储当前迭代的块哈希和与DB的连接。由于后者,迭代器在逻辑上附加到区块链(它Blockchain是存储数据库连接的实例),因此,在Blockchain方法中创建:

用GO语言构建区块链——第三部分:持久性和CLI

请注意,迭代器最初指向区块链的顶端,因此将从上到下(从最新到最旧)获得块。实际上,选择提示意味着对区块链进行“投票”。区块链可以有多个分支,并且它们是最长的被认为是主要分支。在获得提示后(它可以是区块链中的任何块),我们可以重建整个区块链并找到其长度和构建它所需的工作。这个事实也意味着小费是一种区块链的标识符。

BlockchainIterator 将只做一件事:它将从区块链返回下一个区块。

用GO语言构建区块链——第三部分:持久性和CLI

这就是数据库部分!

CLI

到现在为止我们的实现并没有提供任何接口与程序交互:我们只是执行NewBlockchain,bc.AddBlock在main函数。是时候改善了!我们想要这些命令:

用GO语言构建区块链——第三部分:持久性和CLI

所有与命令行相关的操作都将由CLIstruct 处理:

用GO语言构建区块链——第三部分:持久性和CLI

它的“入口点”是Run功能:

用GO语言构建区块链——第三部分:持久性和CLI

我们使用标准标志包来解析命令行参数。

用GO语言构建区块链——第三部分:持久性和CLI

首先,我们创建了两个子命令,addblock并且printchain,我们再加入-data标志前者。printchain不会有任何旗帜。

用GO语言构建区块链——第三部分:持久性和CLI

接下来,我们检查用户提供的命令并解析相关的flag子命令。

用GO语言构建区块链——第三部分:持久性和CLI

接下来,我们检查哪些子命令被解析并运行相关的函数。

用GO语言构建区块链——第三部分:持久性和CLI

这件作品与我们之前的作品非常相似。唯一的区别是我们现在使用a BlockchainIterator来迭代区块链中的块。

另外,我们不要忘记相应地修改main函数:

用GO语言构建区块链——第三部分:持久性和CLI

请注意,Blockchain无论提供什么命令行参数,都会创建new 。

就是这样!让我们检查一切是否按预期工作:

用GO语言构建区块链——第三部分:持久性和CLI

(啤酒的声音可以打开)

编辑:却原来

本文系转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们。