使用GO语言构建区块链 - 第3部分:持久性和CLI

2019/07/27 栏目:行业资讯
原文标题:使用GO语言构建区块链 - 第3部分:持久性和CLI

  前言:

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


数据库选择

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

我们需要哪个数据库? 实际上,其中任何一个。 原始比特币文件 中,没有使用数据库的东西,因此开发人员应该使用DB。 比特币核心 ,最初由Satoshi Nakamoto出版,目前是比特币的参考实现,使用 LevelDB (虽然它仅在2012年被介绍给客户)。 我们会的使用...


[123 ]

BoltDB

因为:

这很简单。

它在Go中实施。

它不需要运行服务器。

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


来自BoltDB Github ] ]自述文件

博尔特是一个纯粹的围棋/钥匙/商店,灵感来自霍华德朱的LMDB项目。 该项目的目标是为不需要完整数据库服务器的项目提供简单,快速,可靠的数据库,如Postgres或MySQL。

因为Bolt被设计用于这种低级功能,所以简单性是关键。 API非常小,仅涉及获得的值和设定值

听起来很棒我们的需求! 让我们花点时间让我们回顾一下。

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

BoltDB的一个重要特征是没有数据类型:键和值是字节数组。 由于我们将Go结构( ,特别是Block )存储在其中,我们需要对它们进行排序A将Go结构转换为字节数组并从字节数组中恢复的机制。 我们将为此目的使用 代码/ gob ,或者我们可以使用] JSON XML Protocol Buffers 等。 我们使用 编码/ gob 因为它非常简单并且是标准Go库的一部分。


数据库结构 ]

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

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

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

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

     此外,该块作为单独的文件存储在磁盘 这样做是出于性能目的:读取单个块不需要将所有(或某些)块加载到内存中。 我们不会这样做。   


[ 123]

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

  32-字节块 - 哈希 - >块结构(serialized)

  'l' - >链中最后一个块的散列

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


序列化

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

让我们实现 Block Se rialize方法 (为简洁起见,省略错误处理): [1]23]       

这篇文章非常简单:首先,我们声明一个存储序列化数据的缓冲区;然后我们初始化

gob 编码器并编码块;  作为字节数组返回。 接下来,我们需要一个反序列化函数,它将一个字节数组作为输入并返回

模块

这不是一种方法,而是一种独立的功能:    

这是序列化!


坚持


让我们从得到这个

NewBlockchain

功能 启动 目前,它创建了一个新的实例, 区块链 并添加了一个创世块。 我们希望它的作用是: [ 在代码中,它看起来像这样:

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

这是打开BoltDB文件的标准方法。

请注意,如果没有此类文件,则不会返回错误。

在BoltDB中,使用数据库的操作在事务中运行。

并且有两种类型的事务:只读和读写。

我们在这里打开读写事务( db.Update(...) )因为我们想要生成创世块把它放在DB中。 这是此功能的核心。

在这里,我们得到了存储块的桶:如果它存在,我们

l 来自 ] 读取 键;  如果它不存在,我们生成一个创建块,创建一个桶,将块保存到它,以及更新 存储链哈希 的最后一个块的 键。 此外,请注意新方法创作

区块链 [1]23]

我们不再存储其中的所有块,而是仅存储链的尖端。

此外,我们存储数据库连接,因为我们想要打开它一次并在程序运行时保持打开状态。 因此, 区块链 结构现在看起来像这样: [ 我们要更新的下一件事是

AddBlock

方法:添加现在块到链不像添加元素到数组那么容易。 从现在开始,我们将把该块存储在数据库中: [ 123] 让我们逐一回顾:


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

挖掘新块后,我们将其序列化表示保存到数据库并更新l 密钥,现在存储新块的哈希值。

完成! 这不难,是吗?

检查区块链


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

让我们解决这个缺陷!

BoltDB允许迭代存储桶中的所有密钥,但密钥按字节顺序存储,我们希望块按顺序跟随其区块链打印。 另外,因为我们不想将所有块加载到内存中(我们的区块链数据库可能非常大!或者只是假装它可以),我们将逐一阅读它们。

为此,我们需要一个区块链迭代器:

每次我们想要迭代时块链中的块,创建一个迭代器,用于存储当前迭代的块哈希和与DB的连接。

由于后者,迭代器逻辑上附加到区块链(

Blockchain 是存储数据库连接 ] 区块链 创建 注意,迭代器最初指向区块链的顶部,因此块从上到下(从最新到最旧)。

事实上,

选择提示意味着“投票”到区块链 区块链可以有多个分支,它们是最长的分支。 在获得提示后(它可以说它是区块链中的任何块,我们可以重建整个区块链并找到它的长度以及构建它所需的工作。事实 [ 123]也意味着小费是一种区块链的标识符。 BlockchainIterator  只做一件事:它将从区块链返回下一个块。

这是数据库的一部分!


CLI


到目前为止我们的实施没有接口是提供与程序交互:我们只执行

NewBlockchain

bc.AddBlock [123 ] 功能。 是时候改进了! 我们希望这些命令: 将处理与命令行相关的所有操作 CLI

struct 

其“切入点”是 运行

功能:

[123 ]

我们使用标准 徽标 包来解决命令行参数。

首先,我们创建了两个子命令, addblock

printchain ,我们将 - 数据 添加到前者。 printchain 将不会有任何旗帜。 接下来,我们检查用户 - 提供命令并解析关联的 标志 子命令。 [1

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

这项工作与我们以前的工作非常相似。

唯一的区别是我们现在使用

BlockchainIterator

来迭代区块链中的区块。

此外,我们不应忘记修改 相应的功能:

请注意 区块链 [无论提供的命令行参数如何,都将创建123]。

新 

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

(啤酒声可以打开)

编辑:但原始