用Go中构建区块链——第七部分:网络

前言

到目前为止,我们已经构建了一个具有所有关键功能的区块链:匿名,安全和随机生成的地址; 区块链数据存储; 工作证明制度; 存储交易的可靠方式。虽然这些功能至关重要,但这还不够。是什么让这些功能真正发挥作用,以及使加密货币成为可能的因素是网络。在单台计算机上运行这种区块链实现有什么用?当只有一个用户时,那些基于加密的功能有什么用?它的网络使所有这些机制都很有用。

您可以将区块链功能视为规则,类似于人们在希望生活和共同生活时建立的规则,一种社会安排。区块链网络是遵循相同规则的程序社区,它遵循使网络活跃的规则。同样,当人们分享相同的想法时,他们会变得更强大,可以共同创造更好的生活。如果有人遵循不同的规则,他们将生活在一个单独的区域。同样,如果区块链节点遵循不同的规则,它们将形成一个单独的网络。

这非常重要:如果没有网络,并且没有大多数节点共享相同的规则,这些规则就没用了!

免责声明:不幸的是,我没有足够的时间来实现真正的P2P网络原型。在本文中,我将演示一个最常见的场景,它涉及不同类型的节点。改善这种情况并使其成为P2P网络对您来说是一个很好的挑战和实践!此外,我不能保证除了本文中实现的方案之外的其他方案也可以。抱歉!

这部分介绍了重要的代码更改,因此在这里解释所有这些都没有意义。请参阅此页面以查看自上一篇文章以来的所有更改。

区块链网络

区块链网络是分散的,这意味着没有服务器可以使用服务器来获取或处理数据。在区块链网络中有节点,每个节点都是网络的成熟成员。节点就是一切:它既是客户端又是服务器。记住这一点非常重要,因为它与通常的Web应用程序非常不同。

区块链网络是P2P(点对点)网络,这意味着节点彼此直接连接。它的拓扑结构是扁平的,因为节点角色中没有层次结构。这里的示意图如下:

用Go中构建区块链——第七部分:网络

(商业矢量由Dooder创建 – Freepik.com)

这种网络中的节点更难实现,因为它们必须执行大量操作。每个节点必须与多个其他节点交互,它必须请求其他节点的状态,将其与自己的状态进行比较,并在其过时时更新其状态。

节点角色

尽管是完全成熟的,但区块链节点可以在网络中扮演不同的角色。

1、矿工。
     这些节点在强大的或专用的硬件(如ASIC)上运行,其唯一目标是尽可能快地挖掘新块。矿工只能在使用工作证明的区块链中使用,因为挖掘实际上意味着解决PoW难题。例如,在Pro-of-Stake区块链中,没有采矿。

2、完整节点。
     这些节点验证矿工开采的块并验证交易。要做到这一点,他们必须拥有区块链的全部副本。此外,这样的节点执行这样的路由操作,例如帮助其他节点发现彼此。
     对于网络而言,拥有许多完整节点非常重要,因为正是这些节点做出了决策:他们决定一个块或事务是否有效。

3、SPV。

 SPV代表简化付款验证。这些节点不存储区块链的完整副本,但它们仍然能够验证事务(不是所有事务,而是一个子集,例如,发送到特定地址的子集)。SPV节点依赖于完整节点来获取数据,并且可能有许多SPV节点连接到一个完整节点。SPV使钱包应用成为可能:一个不需要下载完整的区块链,但仍然可以验证他们的交易。

4、网络简化

要在我们的区块链中实现网络,我们必须简化一些事情。问题是我们没有很多计算机来模拟具有多个节点的网络。我们可以使用虚拟机或Docker来解决这个问题,但它可能会使一切变得更加困难:你必须解决可能的虚拟机或Docker问题,而我的目标只是集中在区块链实现上。因此,我们希望在一台机器上运行多个区块链节点,同时我们希望它们具有不同的地址。为实现此目的,我们将端口用作节点标识符,而不是IP地址。例如,会出现与地址的节点:127.0.0.1:3000127.0.0.1:3001127.0.0.1:3002等,我们会打电话给港口节点ID和使用NODE_ID环境变量来设置它们。因此,您可以打开多个终端窗口,设置不同的NODE_IDs并运行不同的节点。

这种方法还需要具有不同的区块链和钱包文件。现在,他们必须依靠节点ID和命名一样blockchain_3000.dbblockchain_30001.db并且wallet_3000.dbwallet_30001.db等等。

实现

那么,当您下载比特币核心并首次运行它时会发生什么?它必须连接到某个节点才能下载区块链的最新状态。那么你清楚什么是节点呢?

在比特币核心中对节点地址进行硬编码将是一个错误:节点可能受到攻击或关闭,这可能导致新节点无法加入网络。相反,在比特币核心中,有DNS种子硬编码。这些不是节点,而是知道某些节点地址的DNS服务器。当你启动一个干净的比特币核心时,它将连接到其中一个种子并获得一个完整节点列表,然后它将从中下载区块链。

在我们的实施中,将会集中化。我们将有三个节点:

1.   1、中心节点。这是所有其他节点将连接到的节点,这是将在其他节点之间发送数据的节点。

2.   2、矿工节点。此节点将在mempool中存储新事务,并且当有足够的事务时,它将挖掘新块。

3.   3、钱包节点。此节点将用于在钱包之间发送硬币。与SPV节点不同,它会存储区块链的完整副本。

场景

本文的目标是实现以下场景:

1. 中心节点创建区块链。

2. 其他(钱包)节点连接到它并下载区块链。

3. 另一个(矿工)节点连接到中央节点并下载区块链。

4. 钱包节点创建一个事务。

5. 矿工节点接收事务并将其保留在其内存池中。

6. 当内存池中有足够的事务时,矿工开始挖掘新块。

7. 当开采新块时,它将发送到中央节点。

8. 钱包节点与中心节点同步。

9. 钱包节点的用户检查他们的付款是否成功。

这就是比特币的样子。即使我们不打算建立一个真正的P2P网络,我们也将实现一个真实的,比特币的主要和最重要的用例。

版本

节点通过消息进行通信。当一个新节点运行时,它从DNS种子中获取几个节点,并向它们发送version消息,在我们的实现中将看起来像这样:

用Go中构建区块链——第七部分:网络

我们只有一个区块链版本,因此该Version字段不会保留任何重要信息。BestHeight存储节点区块链的长度。AddFrom存储发件人的地址。

接收version消息的节点应该做什么?它会回复自己的version消息。这是一种握手:没有事先互相问候,就不可能有其他互动。但这不仅仅是礼貌:version用于寻找更长的区块链。当节点收到version消息时,它会检查节点的区块链是否长于值BestHeight。如果不是,节点将请求并下载丢失的块。

为了接收消息,我们需要一个服务器:

用Go中构建区块链——第七部分:网络

首先,我们对中央节点的地址进行硬编码:每个节点必须知道最初连接到哪里。minerAddressargument指定接收挖掘奖励的地址。这件作品:

用Go中构建区块链——第七部分:网络

意味着如果当前节点不是中心节点,它必须向version中央节点发送消息以查明其区块链是否过时。

用Go中构建区块链——第七部分:网络

我们在较低级别的消息是字节序列。前12个字节指定命令名(在本例中为“version”),后面的字节将包含gob-encoded消息结构。commandToBytes看起来像这样:

用Go中构建区块链——第七部分:网络

它创建一个12字节的缓冲区并使用命令名填充它,将rest字节留空。有一个相反的功能:

用Go中构建区块链——第七部分:网络

当节点收到命令时,它运行bytesToCommand以提取命令名称并使用正确的处理程序处理命令体:

用Go中构建区块链——第七部分:网络

好的,这就是version命令处理程序的样子:

用Go中构建区块链——第七部分:网络

首先,我们需要解码请求并提取有效负载。这与所有处理程序类似,因此我将在以后的代码片段中省略这一部分。

然后,节点将其BestHeight与消息中的节点进行比较。如果节点的区块链较长,它将回复version消息; 否则,它会发送getblocks消息。

Getblocks

用Go中构建区块链——第七部分:网络

getblocks意思是“告诉我你有什么块”(在比特币中,它更复杂)。注意,它没有说“给我所有的块”,而是它请求块哈希列表。这样做是为了减少网络负载,因为可以从不同的节点下载块,我们不想从一个节点下载几十GB。

处理命令非常简单:

用Go中构建区块链——第七部分:网络

在我们的简化实现中,它将返回所有块哈希。

INV

用Go中构建区块链——第七部分:网络

比特币用于inv向其他节点显示当前节点具有哪些块或事务。同样,它不包含整个块和事务,只包含它们的哈希值。该Type字段表示这些是块还是交易。

处理inv更加困难:

用Go中构建区块链——第七部分:网络

如果传输了块哈希,我们希望将它们保存在blocksInTransit变量中以跟踪下载的块。这允许我们从不同节点下载块。在将块放入传输状态之后,我们将getdata命令发送给inv消息的发送者并进行更新blocksInTransit。在真正的P2P网络中,我们希望从不同节点传输块。

在我们的实现中,我们永远不会发送inv多个哈希值。这就是为什么payload.Type== “tx”只采用第一个哈希。然后我们检查我们的mempool中是否已经有哈希值,如果没有,发送getdata消息。

GetData

用Go中构建区块链——第七部分:网络

getdata 是对某个块或事务的请求,它只能包含一个块/事务ID。

用Go中构建区块链——第七部分:网络

处理程序很简单:如果它们请求块,则返回块; 如果他们请求交易,则返回交易。请注意,我们不会检查我们是否确实拥有此块或事务。这是一个缺陷。

block和tx

用Go中构建区块链——第七部分:网络

这些消息实际上是传输数据的。

处理block邮件很简单:

用Go中构建区块链——第七部分:网络

当我们收到一个新区块时,我们将其放入区块链中。如果要下载更多块,我们从下载前一个块的同一节点请求它们。当我们最终下载了所有块时,重新索引UTXO集。

TODO:我们应该在将其添加到区块链之前验证每个传入的块,而不是无条件地信任。

TODO:不应运行UTXOSet.Reindex(),而应使用UTXOSet.Update(块),因为如果区块链很大,则需要花费大量时间来重新索引整个UTXO集。

处理tx消息是最困难的部分:

用Go中构建区块链——第七部分:网络

首先要做的是将新事务放入mempool(同样,事务必须在放入mempool之前进行验证)。下一件:

用Go中构建区块链——第七部分:网络

检查当前节点是否为中心节点。在我们的实现中,中心节点不会挖掘块。相反,它会将新事务转发到网络中的其他节点。

下一个重要部分仅适用于矿工节点。让我们将它分成更小的部分:

用Go中构建区块链——第七部分:网络

miningAddress仅在矿工节点上设置。当当前(矿工)节点的mempool中有2个或更多事务时,开始挖掘。

用Go中构建区块链——第七部分:网络

首先,验证mempool中的所有事务。忽略无效事务,如果没有有效事务,则中断挖掘。

用Go中构建区块链——第七部分:网络

已验证的交易被放入一个区块,以及带有奖励的coinbase交易。挖掘块后,重新索引UTXO集。

TODO:同样,应该使用UTXOSet.Update而不是UTXOSet.Reindex

用Go中构建区块链——第七部分:网络

交易完成后,它将从mempool中删除。当前节点知道的每个其他节点,inv使用新块的散列接收消息。他们可以在处理消息后请求块。

结果

让我们播放我们之前定义的场景。

首先,在第一个终端窗口中设置NODE_ID为3000(exportNODE_ID=3000)。我会在下一段之前NODE 3000NODE3001之前使用徽章,以便了解要对其执行操作的节点。

NODE 3000
     创建一个钱包和一个新的区块链:

用Go中构建区块链——第七部分:网络

(为了清晰和简洁,我将使用虚假地址)

之后,区块链将包含单一的成因块。我们需要保存块并在其他节点中使用它。Genesis块用作区块链的标识符(在比特币核心中,创世块是硬编码的)。

用Go中构建区块链——第七部分:网络

NODE 3001
     接下来,打开一个新的终端窗口并将节点ID设置为3001.这将是一个钱包节点。生成一些地址blockchain_gocreatewallet,我们会打电话给这些地址WALLET_1WALLET_2WALLET_3

NODE 3000
     将一些硬币发送到钱包地址:

用Go中构建区块链——第七部分:网络

-mineflag表示该块将立即被同一节点挖掘。我们必须拥有此标志,因为最初网络中没有矿工节点。
      启动节点:

用Go中构建区块链——第七部分:网络

节点必须运行直到方案结束。

NODE 3001
     使用上面保存的genesis块启动节点的区块链:

用Go中构建区块链——第七部分:网络

运行节点:

用Go中构建区块链——第七部分:网络

它将从中央节点下载所有块。要检查一切正常,请停止节点并检查余额:

用Go中构建区块链——第七部分:网络

此外,您可以检查CENTRAL_NODE地址的余额,因为节点3001现在具有其区块链:

用Go中构建区块链——第七部分:网络

NODE 3002
     打开一个新的终端窗口并将其ID设置为3002,然后生成一个钱包。这将是一个矿工节点。初始化区块链:

用Go中构建区块链——第七部分:网络

并启动节点:

用Go中构建区块链——第七部分:网络

NODE 3001
     发送一些硬币:

用Go中构建区块链——第七部分:网络

NODE 3002
     很快!切换到矿工节点并看到它挖掘一个新块!另外,检查中央节点的输出。

NODE 3001
     切换到钱包节点并启动它:

用Go中构建区块链——第七部分:网络

它将下载新开采的块!

停止并检查余额:

用Go中构建区块链——第七部分:网络

就是这么多了!

结论

这是该系列的最后一部分。我可以发布一些实现P2P网络真实原型的帖子,但我没有时间做这件事。我希望这篇文章回答你关于比特币技术的一些问题并提出新的问题,你可以自己找到答案。比特币技术中隐藏着更多有趣的东西!祝你好运!

后记:您可以通过实施addr消息开始改进网络,如比特币网络协议(链接如下)所述。这是一个非常重要的消息,因为它允许节点相互发现。我开始实现它,但还没有完成!

链接:

1、Source codes:https://github.com/Jeiwan/blockchain_go/tree/part_7

2、Bitcoin protocol documentation:https://en.bitcoin.it/wiki/Protocol_documentationBitcoin

 3、network:https://en.bitcoin.it/wiki/Network

编辑:却原来

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