用Go语言构建区块链——第6部分:交易2

介绍
    在本系列的第一部分中,表明区块链是一个分布式数据库。那时,我们决定跳过“分布式”部分并专注于“数据库”部分。到目前为止,我们已经实现了几乎所有构成区块链数据库的东西。在这篇文章中,我们将介绍在前面部分中跳过的一些机制,在下一部分中,我们将开始研究区块链的分布式特性。

上一部分:

1. 基本原型 : http://www.qkljw.com/article/2956.html

2. 工作量证明: http://www.qkljw.com/article/2966.html

3. 持久性和CLI:http://www.qkljw.com/article/2985.html

4. 交易1:http://www.qkljw.com/article/3013.html

5. 地址:http://www.qkljw.com/article/3023.html

这部分介绍了重要的代码更改,请参阅此页面以查看自上一篇文章以来的所有更改。

奖励

我们在前一篇文章中跳过的就是采矿奖励,现在让我们实现它。

奖励只是一个coinbase交易。当挖掘节点开始挖掘新块时,它从队列中获取交易并为它们预先设置coinbase交易。coinbase交易的唯一输出包含miner的公钥哈希。

实现奖励就像更新send命令一样简单:

用Go语言构建区块链——第6部分:交易2

      在我们的实现中,创建交易的人挖掘新块,从而获得奖励。

UTXO集

在第3部分:持久性和CLI中,我们研究了比特币核心在数据库中存储块的方式。据说块存储在blocks数据库中,交易输出存储在chainstate数据库中。让我提醒你结构chainstate是什么:

1. ‘c’ + 32-byte transaction hash -> unspent transaction output record for that transaction

2. ‘B’ -> 32-byte block hash: the block hash up to which the database represents the unspent transaction outputs

自那篇文章以来,我们已经实现了交易,但我们还没有使用chainstate来存储它们的输出。所以,这就是我们现在要做的。

chainstate不存储交易,它存储UTXO集即未使用的交易输出集。除此之外,它存储“数据库代表未使用的交易输出的块哈希”,我们现在将省略它们,因为我们没有使用块高度(但我们将在下一篇文章中实现它们)。

那么,为什么我们要设置UTXO呢?

考虑一下我们之前实现的Blockchain.FindUnspentTransactions方法:

用Go语言构建区块链——第6部分:交易2

该函数查找具有未使用输出的交易。由于交易存储在块中,因此它会迭代块链中的每个块并检查其中的每个交易。截至2017年9月18日,比特币有485,860个区块,整个数据库需要140+ Gb的磁盘空间。这意味着必须运行完整节点才能验证交易。此外,验证交易需要迭代许多块。

这个问题的解决方案是有一个只存储未使用输出的索引,这就是UTXO设置的作用:这是一个从所有区块链交易构建的缓存(通过迭代块,是的,但这只做一次),以后用于计算余额和验证新交易。截至2017年9月,UTXO套装约为2.7 Gb。

好吧,让我们考虑一下我们需要改变什么来实现UTXO集。目前,以下方法用于查找交易:

1. Blockchain.FindUnspentTransactions – 查找具有未使用输出的交易的主函数。这是所有块的迭代发生的功能。

2. Blockchain.FindSpendableOutputs – 创建新交易时使用此函数。如果找到足够数量的输出保持所需数量,用途Blockchain.FindUnspentTransactions。

3. Blockchain.FindUTXO – 查找公钥哈希的未使用输出,用于获得平衡。用途Blockchain.FindUnspentTransactions。

4. Blockchain.FindTransaction – 通过其ID在区块链中查找交易。它迭代所有块直到找到它。

如您所见,所有方法都迭代数据库中的块。但是我们现在无法改进所有这些,因为UTXO集不存储所有交易,而只存储那些具有未使用输出的交易

因此,它不能用于Blockchain.FindTransaction。

所以,我们需要以下方法:

1. Blockchain.FindUTXO – 通过迭代块找到所有未使用的输出。

2. UTXOSet.Reindex- 用于FindUTXO查找未使用的输出,并将它们存储在数据库中。这是缓存发生的地方。

3. UTXOSet.FindSpendableOutputs- 模拟Blockchain.FindSpendableOutputs,但使用UTXO设置。

4. UTXOSet.FindUTXO- 模拟Blockchain.FindUTXO,但使用UTXO设置。

5. Blockchain.FindTransaction 保持不变。

因此,两个最常用的函数将从现在开始使用缓存!我们开始编码了。

用Go语言构建区块链——第6部分:交易2

我们将使用单个数据库,但我们会将UTXO集存储在不同的存储桶中。因此,UTXOSet加上Blockchain。

用Go语言构建区块链——第6部分:交易2

     此方法最初创建UTXO集。首先,如果存在,则删除存储桶,然后从区块链中获取所有未使用的输出,最后将输出保存到存储桶。
     Blockchain.FindUTXO几乎相同Blockchain.FindUnspentTransactions,但现在它返回TransactionID → TransactionOutputs一对对的地图。

现在,UTXO集可用于发送硬币:

用Go语言构建区块链——第6部分:交易2

或检查余额:

用Go语言构建区块链——第6部分:交易2

这些是相应Blockchain方法的略微修改版本。Blockchain不再需要那些方法。

设置UTXO意味着我们的数据(交易)现在被分成存储:实际交易存储在区块链中,未使用的输出存储在UTXO集中。这种分离需要牢固的同步机制,因为我们希望始终更新UTXO集并存储最近交易的输出。但是我们不希望每次开采新块时都重新索引,因为我们想要避免这些频繁的区块链扫描。因此,我们需要一种更新UTXO集的机制:

用Go语言构建区块链——第6部分:交易2

该方法看起来很大,但它的作用非常简单。当开采新块时,应更新UTXO集。更新意味着删除已用完的交易并从新挖掘的交易中添加未使用的输出。如果删除了输出的交易,不再包含输出,则也会将其删除。非常简单!

现在让我们在必要时使用UTXO集:

用Go语言构建区块链——第6部分:交易2

在创建新的区块链后立即重新编制索引。现在,这是唯一使用的地方Reindex,即使它在这里看起来过分,因为在区块链的开头只有一个块有一个交易,而且Update可能已经被使用了。但是我们将来可能需要重建索引机制。

用Go语言构建区块链——第6部分:交易2

     并且在挖掘新块之后更新UTXO集。
     让我们检查它是否有效

用Go语言构建区块链——第6部分:交易2

太好了!1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1收到的地址奖励3次:

1. 一次用于挖掘成因块。

2. 一旦开采块0000001f75cb3a5033aeecbf6a8d378e15b25d026fb0a665c7721a5bb0faa21b。

3. 并且一旦开采块000000cc51e665d53c78af5e65774a72fc7b864140a8224bf4e7709d8e0fa433。

Merkle树

我想在这篇文章中讨论另外一种优化机制。

如上所述,完整的比特币数据库(即区块链)需要超过140 Gb的磁盘空间。由于比特币的分散性,网络中的每个节点必须是独立且自给自足的,每个节点必须存储区块链的完整副本。随着许多人开始使用比特币,这条规则变得更难以遵循:每个人都不可能运行完整的节点。此外,由于节点是网络的成熟参与者,因此他们有责任:他们必须验证交易和阻止。此外,与其他节点交互并下载新块需要一定的互联网流量。

在Satoshi Nakamoto发布的原始比特币论文中,有一个解决方案可以解决这个问题:简化付款验证(SPV)。SPV是一个轻型比特币节点,不下载整个区块链,也不验证块和交易。相反,它在块中查找交易(以验证付款)并链接到完整节点以检索必要的数据。此机制允许多个轻型钱包节点仅运行一个完整节点。

为了使SPV成为可能,应该有一种方法来检查一个块是否包含某个交易而不下载整个块。这就是Merkle树发挥作用的地方。

比特币使用Merkle树来获取交易哈希值,然后将其保存在块头中并由工作量证明系统考虑。到目前为止,我们只是在一个块中连接每个交易的哈希值并应用于SHA-256它们。这也是获得块交易的唯一表示的好方法,但它没有Merkle树的好处。

让我们看一下Merkle树:

用Go语言构建区块链——第6部分:交易2

为每个块构建Merkle树,它以叶子(树的底部)开始,其中叶子是交易散列(比特币使用双SHA256散列)。叶子的数量必须是偶数,但不是每个块都包含偶数个交易。如果存在奇数个交易,则最后一个交易是重复的(在Merkle树中,而不是在块中!)。

从底部向上移动,叶子成对分组,它们的哈希串联,并且从连接的哈希中获得新的哈希。新的哈希形成新的树节点。重复此过程,直到只有一个节点,称为树的根。然后将根哈希用作交易的唯一表示,保存在块头中,并用于工作量证明系统。

Merkle树的好处是节点可以在不下载整个块的情况下验证某些交易的成员资格。只需要一个交易哈希,一个Merkle树根哈希和一个Merkle路径。
     最后,让我们编写代码:

用Go语言构建区块链——第6部分:交易2

我们从结构开始。每个人都MerkleNode保存数据和链接到其分支。MerkleTree实际上是链接到下一个节点的根节点,这些节点又链接到其他节点等。
      让我们先创建一个新节点:

用Go语言构建区块链——第6部分:交易2

每个节点都包含一些数据。当节点是叶子时,数据从外部传递(在我们的例子中是序列化交易)。当一个节点链接到其他节点时,它会获取它们的数据并连接并对其进行哈希处理。

用Go语言构建区块链——第6部分:交易2

创建新树时,首先要确保的是有一些偶数叶子。之后,data(这是一系列序列化交易)被转换为树叶,并从这些叶子生长树。
     现在,让我们修改Block.HashTransactions,在工作量证明系统中用于获取交易哈希:

用Go语言构建区块链——第6部分:交易2

     首先,交易被序列化(使用encoding/gob),然后它们用于构建Merkle树。树的根将作为块交易的唯一标识符。


P2PKH

还有一件事我想更详细地讨论。

记住,在比特币中有脚本编程语言,用于锁定交易输出; 和交易输入提供解锁输出的数据。语言很简单,这种语言的代码只是一系列数据和运算符。考虑这个例子:

用Go语言构建区块链——第6部分:交易2

5,2和7用于数据。OP_ADD并且OP_EQUAL是运营商。脚本代码从左到右执行:每个数据都放入堆栈,下一个操作符应用于顶层堆栈元素。脚本的 堆栈只是一个简单的FILO(第一个输入最后输出)内存存储:堆栈中的第一个元素是最后一个元素,每个元素都放在前一个元素上。

让我们将上述脚本的执行分解为以下步骤:

1. 堆栈:空。脚本:5 2 OP_ADD 7 OP_EQUAL。

2. 堆栈:5。脚本:2 OP_ADD 7 OP_EQUAL。

3. 堆栈:5 2。脚本:OP_ADD 7 OP_EQUAL。

4. 堆栈:7。脚本:7 OP_EQUAL。

5. 堆栈:7 7。脚本:OP_EQUAL。

6. 堆栈:true。脚本:空的。

OP_ADD从堆栈中获取两个元素,汇总它们,并将总和推入堆栈。OP_EQUAL从堆栈中获取两个元素并对它们进行比较:如果它们相等则会推true送到堆栈; 否则它会推动false。脚本执行的结果是顶部堆栈元素的值:在我们的例子中,它是true,这意味着脚本成功完成。

现在让我们看看比特币中用于执行付款的脚本:

用Go语言构建区块链——第6部分:交易2

此脚本称为Pay to Public Key Hash(P2PKH),这是比特币中最常用的脚本。它实际上支付公钥哈希,即用某个公钥锁定硬币。这是比特币支付的核心:没有账户,没有资金转移; 只有一个脚本可以检查提供的签名和公钥是否正确。

该脚本实际上存储在两个部分:

1. 第一部分存储在输入ScriptSig字段中。

2. 第二部分OP_DUP OP_HASH160OP_EQUALVERIFY OP_CHECKSIG存储在输出中ScriptPubKey。

因此,它的输出定义了解锁逻辑,它的输入提供数据来解锁输出。让我们执行脚本。

1、堆栈:空

脚本:OP_DUPOP_HASH160OP_EQUALVERIFY OP_CHECKSIG

2.         2、堆栈:

           脚本:OP_DUP OP_HASH160OP_EQUALVERIFY OP_CHECKSIG

3.         3、堆栈:
       脚本:OP_DUP OP_HASH160OP_EQUALVERIFY OP_CHECKSIG

4.         4、 堆栈:
       脚本:OP_HASH160OP_EQUALVERIFYOP_CHECKSIG

5.         5、堆栈:
       脚本:OP_EQUALVERIFY OP_CHECKSIG

6.         6、 堆栈:
       脚本:OP_EQUALVERIFY OP_CHECKSIG

7.         7、堆栈:
       脚本:OP_CHECKSIG

8.         8、堆栈:truefalse。脚本:空的。

OP_DUP复制顶部堆栈元素。OP_HASH160采用顶部堆栈元素并将其哈希RIPEMD160; 结果被推回到堆栈。OP_EQUALVERIFY比较两个顶部堆栈元素,如果它们不相等,则中断脚本。OP_CHECKSIG通过散列交易并使用和来验证交易的签名。后一个运算符非常复杂:它会对交易进行修剪,对其进行哈希处理(因为它是已签名的交易的哈希),并使用提供的和检查签名是否正确。

拥有这样的脚本语言使得比特币也成为智能合约平台:除了转移到单个密钥之外,该语言还可以实现其他支付方案。

结论

就是这样!我们已经实现了基于区块链的加密货币的几乎所有关键功能。我们有区块链,地址,挖矿和交易。但是还有一件事给所有这些机制赋予了生命,并使比特币成为一个全球系统:共识。在下一篇文章中,我们将开始实现区块链的“分散”部分。敬请关注!

编辑:却原来

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