澳门新葡亰平台9411矿工为什么能挖空块?

澳门新葡亰平台9411 4

在PoW网络中,矿工通过出块以及在区块链账本中添加有效交易来获利。只要不断有新币产出,矿工的奖励就分为两种:coinbase奖励(区块中产生的新币)以及验证交易的手续费。矿工偶尔也可能挖出空块,即仅包含coinbase奖励,但没有交易的块。但通常情况下,只要内存池中依然有等待确认的交易,矿工都会自愿放弃这笔收入。然而,根据Bitcoin
Unlimited(BU)开发者Andrew
Stone的研究,加密货币挖矿的经济和技术实践可能导致这一问题变得更加复杂。从技术方面来看,要想抢在其他矿工前面挖出一个区块,需要包含几个非常耗时的步骤。其中之一就是必须下载上一个区块的所有交易,并且在内存池副本中删除这些交易。这一步能够防止矿工挖出的新区块中的交易与上一个区块重复,一旦出现重复的状况,这个新区块就可能被视为无效。而挖空块的矿工就有先发优势,他们不需要更新内存池,也不需要下载上一个区块的交易。因此,当区块奖励很高,而交易手续费偏低的时候,矿工更愿意去挖空块。

翻译的系列文章我已经放到了 GitHub
上:blockchain-tutorial,后续如有更新都会在 GitHub
上,可能就不在这里同步了。如果想直接运行代码,也可以 clone GitHub
上的教程仓库,进入 src 目录执行 make 即可。

简介

“挖矿”,挖矿的主要目的不是这个奖励或者产生新币。
挖矿最重要的作用是巩固了去中心化的清算交易机制,通过这种机制,交易得到验证和清算/清除。挖矿是使得比特币与众不同的发明,它实现去中心化的安全机制,是P2P数字货币的基础,

挖矿确保了比特币系统安全,并且在没有中央权力机构的情况下实现了全网络范围的共识。

新币发行和交易费的奖励是将矿工的行动与网络安全保持一致的激励计划,同时实现了货币发行。

提示:挖矿的目的不是创造新的比特币。 这是激励机制。
挖矿是一种机制,这种机制实现了去中心化的安全。

每10分钟就会有一个新的区块被“挖掘”出来,每个区块里包含着从
上一个区块产生到目前这段时间内发生的所有交易,这些交易被依次添加到区块链中。我们把包含在区块内且被添加到
区块链上的交易称为“确认”(confirmed)交易,交易经过“确认”之后,新的拥有者才能够花费他在交易中得到的比特币。

矿工们在挖矿过程中会得到两种类型的奖励:

  • 创建新区块的新币奖励
  • 区块中所含交易的交易费。

为了得到这些奖
励,矿工们争相完成一种基于加密哈希算法的数学难题,这些难题的答案包括在新区块中,作为矿工的计算工作量的证
明,被称为“工作量证明”。该算法的竞争机制以及获胜者有权在区块链上进行交易记录的机制,这二者是比特币安全的基石。

新比特币的生成过程被称为挖矿,是因为它的奖励机制被设计为速度递减模式。

矿工通过创造一个新区块得到的比特币数量大约
每四年(或准确说是每210,000个块)减少一半。

开始时为2009年1月每个区块奖励50个比特币,然后到2012年11月减
半为每个区块奖励25个比特币。之后在2016年7月
再次减半为每个新区块奖励12.5个比特币。基于这个公
式,比特币挖矿奖励以指数方式递减,直到2140年。届时所有的比特币(20,999,999,980)全部发行完毕。换句话说
在2140年之后,不会再有新的比特币产生。

矿工们同时也会获取交易费。每笔交易都可能包含一笔交易费,交易费是每笔交易记录的输入和输出的差额。在挖矿过
程中成功“挖出”新区块的矿工可以得到该区块中包含的所有交易“小费”。目前,这笔费用占矿工收入的0.5%或更少,大
部分收益仍来自挖矿所得的比特币奖励。

然而随着挖矿奖励的递减,以及每个区块中包含的交易数量增加,交易费在矿
工收益中所占的比重将会逐渐增加。在2140年之后,所有的矿工收益都将由交易费构成。

总量有限并且发行速度递减创造了一种抗通胀的货币供应模式。法币可被中央银行无限制地印刷出来,而比特币永远不会因超额印发而出现通胀。

通货紧缩

最重要并且最有争议的一个结论是一种事先确定的发行速率递减的货币发行模式会导致货币通货紧缩。通缩是一种由于货币的供应和需求不匹配导致的货币增值的现象。它与通胀相反,价格通缩意味着货币随着时间有越来越强的购买力。

许多经济学家提出通缩经济是一种无论如何都要避免的灾难型经济。因为在快速通缩时期,人们预期着商品价格会下跌,人们将会储存货币,避免花掉它。这种现象充斥了日本经济“失去的十年”,就是因为在需求坍塌之后导致了滞涨状态。

比特币专家们认为通缩本身并不坏。更确切地说,我们将通缩与需求坍塌联系在一起是因为过去出现的一个特例。在法币届,货币是有可能被无限制印刷出来的,除非遇到需求完全崩塌并且毫无发行货币意愿的情形,因此经济很难进入滞涨期。而比特币的通缩并不是需求坍塌引起的,它遵循一种预定且有节制的货币供应模型。

通货紧缩的积极因素当然是与通货膨胀相反。
通货膨胀导致货币缓慢但不可避免的贬值,这是一种隐性税收的形式,惩罚在银行存钱的人从而实现解救债务人(包括政府这个最大的债务人)。
政府控制下的货币容易遭受债务发行的道德风险,之后可能会以牺牲储蓄者为代价,通过贬值来抹去债务。

比特币这种不是因经济快速衰退而引起的通缩,是否会引发其他问题,仍有待观察。

可以将区块链看作一本记录所有交易的公开总帐簿,比特币网络中的每个参与
者都把能接受区块链,把它看作一本证明所有权的权威记录。

但在不考虑相信任何人的情况下,比特币网络中的所有参与者如何达成对任意一个所有权的共识呢?所有的传统支付系
统都依赖于一个中心认证机构,依靠中心机构提供的结算服务来验证并处理所有的交易。比特币没有中心机构,几乎所
有的完整节点都有一份公共总帐的备份,这份总帐可以被视为权威记录。区块链并不是由一个中心机构创造的,它是由比特币网络中的所有节点各自独立竞争完成的。

换句话说比特币网络中的所有节点,依靠着节点间的不稳定的网络连接所传输的信息,最终得出同样的结果并维护了同一个公共总帐。这一章将介绍比特币网络不依靠中心机构而达成共识的机制。

中本聪的主要发明就是这种去中心化的自发共识(emergent
consensus)机制。这种自发,是指共识没有明确的完成点,因为共识达成时,没有明确的选举和固定时刻。换句话说,共识是数以千计的独立节点遵守了简单的规则通过异步交互自发形成的产物。

比特币的去中心化共识由所有网络节点的4种独立过程相互作用而产生:

  • 每个全节点依据综合标准对每个交易进行独立验证
  • 澳门新葡亰平台9411,通过完成工作量证明算法的验算,挖矿节点将交易记录独立打包进新区块
  • 每个节点独立的对新区块进行校验并组装进区块链
  • 每个节点对区块链进行独立选择,在工作量证明机制下选择累计工作量最大的区块链。

在接下来的几节中,我们将审视这些过程,了解它们之间如何相互作用并达成全网的自发共识,从而使任意节点组合出
它自己的权威、可信、公开的总帐副本。

在交易一章中,我们知道了钱包软件通过收集UTXO、提供正确的解锁脚本、构造一个新的支出给接收者这一系列的方式来创建交易。产生的交易随后将被发送到比特币网络临近的节点,从而使得该交易能够在整个比特币网络中传播。

然而,在交易传递到临近的节点前,每一个收到交易的比特币节点将会首先验证该交易,这将确保只有有效的交易才会
在网络中传播,而无效的交易将会在第一个节点处被废弃。

每一个节点在校验每一笔交易时,都需要对照一个长长的标准列表:

  1. 交易的语法和数据结构必须正确。
  2. 输入与输出列表都不能为空。
  3. 交易的字节大小是小于 MAX_BLOCK_SIZE 的。
  4. 每一个输出值,以及总量,必须在规定值的范围内
    (小于2,100万个币,大于0)。
  5. 没有哈希等于0,N等于-1的输入(coinbase交易不应当被传递)。
  6. nLockTime是小于或等于 INT_MAX 的。或者nLocktime and
    nSequence的值满足MedianTimePast(译者注:MedianTime是这个块的前面11个块按照block
    time排序后的中间时间)
  7. 交易的字节大小是大于或等于100的。
  8. 交易中的签名数量应小于签名操作数量上限。
  9. 解锁脚本( scriptSig )只能够将数字压入栈中,并且锁定脚本(
    scriptPubkey )必须要符合isStandard的格式
    (该格式将会拒绝非标准交易)。
  10. 池中或位于主分支区块中的一个匹配交易必须是存在的。
  11. 对于每一个输入,引用的输出是必须存在的,并且没有被花费。
  12. 对于每一个输入,如果引用的输出存在于池中任何别的交易中(译者注:这笔输入引用的输出有人家自己的输入,不是你),该交易将被拒绝。
  13. 对于每一个输入,在主分支和交易池中寻找引用的输出交易。如果输出交易缺少任何一个输入,该交易将成为一个孤
    立的交易。如果与其匹配的交易还没有出现在池中,那么将被加入到孤立交易池中。
  14. 对于每一个输入,如果引用的输出交易是一个coinbase输出,该输入必须至少获得
    COINBASE_MATURITY个确认。
  15. 使用引用的输出交易获得输入值,并检查每一个输入值和总值是否在规定值的范围内
    (小于2100万个币,大于0)。
  16. 如果输入值的总和小于输出值的总和,交易将被中止。
  17. 如果交易费用太低以至于无法进入一个空的区块,交易将被拒绝。
  18. 每一个输入的解锁脚本必须依据相应输出的锁定脚本来验证。

这些条件能够在比特币标准客户端下的 AcceptToMemoryPool 、
CheckTransaction 和 CheckInputs 函数中获得更详细的阐述。
请注意,这些条件会随着时间发生变化,为了处理新型拒绝服务攻击,有时候也为交易类型多样化而放宽规则。

在收到交易后,每一个节点都会在全网广播前对这些交易进行独立校验,并以接收时的相应顺序,为有效的新交易建立一个验证池,这个池可以叫做交易池,或者memory
pool或者mempool。

交易(transaction)是比特币的核心所在,而区块链的唯一目的,也正是为了能够安全可靠地存储交易。在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它。在今天的文章中,我们将会开始实现交易这个部分。不过,由于交易是很大的话题,我会把它分为两部分来讲:在今天这个部分,我们会实现交易的通用机制。在第二部分,我们会继续讨论它的一些细节。

挖矿节点

同其他节点一样,Jing的节点时刻监听着传播到比特币网络的新区块。而这些新加入的区块对挖矿节点有着特殊的意
义。矿工间的竞争以新区块的传播而结束,如同宣布谁是最后的赢家。对于矿工们来说,收到一个新区块进行验证意味着别人已经赢了,而自己则输了这场竞争。然而,一轮竞争的结束也代表着下一轮竞争的开始。新区块并不仅仅是象征着竞赛结束的方格旗;它也是下一个区块竞赛的发令枪。

此外,由于代码实现变化很大,就不在文章中罗列所有代码了,这里
可以看到所有的改动。

打包交易至区块

验证交易后,比特币节点会将这些交易添加到自己的内存池中。内存池也称作交易池,用来暂存尚未被加入到区块的交
易记录。与其他节点一样,Jing的节点会收集、验证并传递新的交易。而与其他节点不同的是,Jing的节点会把这些交
易整合到一个候选区块中。

让我们继续跟进,看下Alice从Bob咖啡店购买咖啡时产生的那个区块。Alice的交易在区块
277,316。为了演示本章中提到的概念,我们假设这个区块是由Jing的挖矿系统挖出的,并且继续跟进Alice的交易,因
为这个交易已经成为了新区块的一部分。

如果以前开发过 web
应用,在支付的实现环节,你可能会在数据库中创建这样两张表:

过程

Jing的挖矿节点维护了一个区块链的本地副本。当Alice买咖啡
的时候,Jing节点的区块链已经收集到了区块277,314,并继续监听着网络上的交易,在尝试挖掘新区块的同时,也监
听着由其他节点发现的区块。当Jing的节点在挖矿时,它从比特币网络收到了区块277,315。这个区块的到来标志着终
结了产出区块277,315竞赛,与此同时也是产出区块277,316竞赛的开始。

Jing的节点立刻构建一个新的空区块,做为区块277,316的候选区块。称作候选区块是因为它还没有包含有效的工作量
证明,不是一个有效的区块,而只有在矿工成功找到一个工作量证明解之后,这个区块才生效。

现在,Jing的节点从内存池中整合到了全部的交易,新的候选区块包含有418笔交易,总的矿工费为0.09094925个比特币。

  • accounts
  • transactions
coinbase 创币交易

区块中的第一笔交易是笔特殊交易,称为创币交易或者coinbase交易。这个交易是由Jing的节点构造并用来奖励矿工们所做的贡献的。

注意:当块277,316开采时,每个块的奖励是25比特币。
此后,已经过了一个“减半”时期。
2016年七月份的奖励为12.5比特币,2020年达到210000区块时,将再次减半。

Jing的节点会创建“向Jing的地址支付25.09094928个比特币”这样一个交易,把生成交易的奖励发送到自己的钱包。Jing挖出区块获得的奖励金额是coinbase奖励(25个全新的比特币)和区块中全部交易矿工费的总和。

与常规交易不同,创币交易没有输入,不消耗UTXO。它只包含一个被称作coinbase的输入,仅仅用来创建新的比特
币。创币交易有一个输出,支付到这个矿工的比特币地址。

Coinbase奖励与矿工费为了构造创币交易,Jing的节点需要计算矿工费的总额,将这418个已添加到区块交易的输入和输出分别进行求和,然
后用输入总额减去输出总额得到矿工费总额,公式如下:

Total Fees = Sum - Sum

紧接着,Jing的节点计算出这个新区块正确的奖励额。奖励额的计算是基于区块高度的,以每个区块50个比特币为开
始,每产生210,000个区块减半一次。这个区块高度是277,316,所以正确的奖励额是25个比特币。

详细的计算过程可以参看比特币核心客户端中的GetBlockValue函数

注意:
如果Jing的挖矿节点把coinbase交易写入区块,那么如何防止Jing奖励自己100甚至1000比特币?
答案是,不正确的奖励将被其他人视为无效,浪费了Jing用于工作证明的投入。
只有这个区块被大家认可,Jing才能得到报酬。

经过计算,Jing的节点构造了一个创币交易,支付给自己25.09094928枚比特币。

例10-4所示,创币交易的结构比较特殊,与一般交易输入需要指定一个先前的UTXO不同,它包含一个“coinbase“输
入。

常规交易输入结构:

澳门新葡亰平台9411 1常规交易

coinbase交易输入结构:

澳门新葡亰平台9411 2coinbase

在Coinbase交易中,“交易哈希”字段32个字节全部填充0,“交易输出索引”字段全部填充0xFF,这两个字段的值表示不引用UTXO。“解锁脚本”由coinbase数据代替,数据可以由矿工自定义。

创币交易不包含“解锁脚本“(又称作
scriptSig)字段,这个字段被coinbase数据替代,长度最小2字节,最大100字节。除
了开始的几个字节外,矿工可以任意使用coinbase的其他部分,随意填充任何数据。

以创世块为例,中本聪在coinbase中填入了这样的数据“The Times 03/Jan/ 2009
Chancellor on brink of second bailout for banks“(泰晤士报 2009年1月3日
财政大臣将再次对银行施以援手),表示对日期的证明,同时也表达了对银行系统的不信任。现在,矿工使用coinbase数据实现extra
nonce功能,并嵌入字符串来标识挖出它的矿池。

coinbase前几个字节也曾是可以任意填写的,不过在后来的第34号比特币改进提议中
规定了版本2的区块(版本字段为2的区块),这个区块的高度必须跟在脚本操作“push“之后,填充在coinbase字段的起始处。

我们以例10-4中的区块277,316为例,coinbase就是交易输入的“解锁脚本“(或scriptSig)字段,这个字段的十六进制值
为03443b0403858402062f503253482f。下面让我们来解码这段数据。

第一个字节是03,脚本执行引擎执行这个指令将后面3个字节压入脚本栈;紧接着的3个字节——0x443b04,
是以小端格式编码的区块高度。翻转字节序得到0x043b44,表示为十进制是277,316。

紧接着的几个十六进制数(03858402062)用于编码extra nonce(参见”10.11.1
随机值升位方案”),或者一个随机值,从而求解一个适当的工作量证明。

coinbase数据结尾部分(2f503253482f)是ASCII编码字符
/P2SH/,表示挖出这个区块的挖矿节点支持BIP0016所定义的
pay-to-script-hash改进方案。在P2SH功能引入到比特币的时候,曾经有过一场对P2SH不同实现方式的投票,
候选者是BIP0016和BIP0017。支持BIP0016的矿工将/P2SH/放入coinbase数据中,支持BIP0017的矿工将
p2sh/CHV
放入他们的coinbase数据中。最后,BIP0016在选举中胜出,直到现在依然有很多矿工在他们的coinbase中填
入/P2SH/以表示支持这个功能。

account会存储用户信息,里面包括了个人信息和余额。transaction会存储资金转移信息,也就是资金从一个账户转移到另一个账户这样的内容。在比特币中,支付是另外一种完全不同的方式:

构造区块头

为了构造区块头,挖矿节点需要填充六个字段

澳门新葡亰平台9411 3header

在区块277,316被挖出的时候,区块结构中用来表示版本号的字段值为2,长度为4字节,以小端格式编码值为
0x20000000。

接着,挖矿节点需要填充“前区块哈希”,在本例中,这个值为Jing的节点从网络上接收到的区块277,315的区块头哈希值,它是区块277316候选区块的父区块。区块277,315的区块头哈希值为:

0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569

提示:通过选择候选块头中的先前块哈希字段指定的特定父区块,Jing正在通过确认挖矿能力来扩展区块链。
从本质上讲,这是用他的采矿权为最长难度的有效链进行的“投票”,即,选择难度总值最大的最长链

为了向区块头填充merkle根字段,要将全部的交易组成一个merkle树。创币交易作为区块中的首个交易,后将余下的
418笔交易添至其后,这样区块中的交易一共有419笔。在之前,我们已经见到过“Merkle树”,树中必须有偶数个叶子
节点,所以需要复制最后一个交易作为第420个叶子节点,每个叶子节点是对应交易的哈希值。这些交易的哈希值逐层地、成对地组合,直到最终组合并成一个根节点。merkle数的根节点将全部交易数据摘要为一个32字节长度的值

c91c008c26e50763e9f548bb8b2fc323735f73577effbc55502c51eb4cc7cf2e

挖矿节点会继续添加一个4字节的时间戳,以Unix纪元时间编码,即自1970年1月1日0点到当下总共流逝的秒数。本例
中的1388185914对应的时间是

2013年12月27日,星期五,UTC/GMT

接下来,Jing的节点需要填充Target字段,为了使得该区块有效,这个字段定义了所需满足的工作量证明的难度。难度在区块中以“尾数-指数”的格式,编码并存储,这种格式称作target
bits。这种编码的首字节表示指数,后面的3字节表示尾数。

以区块277316为例,难度位的值为0x1903a30c,0x19是指数的十六进制格式,后半部0x03a30c是系数。

最后一个字段是nonce,初始值为0

区块头完成全部的字段填充后,挖矿就可以开始进行了。挖矿的目标是找到一个使区块头哈希值小于难度目标的
nonce。挖矿节点通常需要尝试数十亿甚至数万亿个不同的nonce取值,直到找到一个满足条件的nonce值。

  1. 没有账户
  2. 没有余额
  3. 没有住址
  4. 没有货币
  5. 没有发送人和接收人(sender,receiver)(这里所说的发送人和接收人是基于目前现实生活场景,交易双方与人是一一对应的。而在比特币中,“交易双方”是地址,地址背后才是人,人与地址并不是一一对应的关系,一个人可能有很多个地址。)

构建区块

既然Jing的节点已经构建了一个候选区块,那么就轮到Jing的矿机对这个新区块进行“挖掘”,求解工作量证明算法以使这个区块有效。从本书中我们已经学习了比特币系统中不同地方用到的哈希加密函数。比特币挖矿过程使用的是
SHA256哈希函数。

用最简单的术语来说,挖矿就是重复计算区块头的哈希值,不断修改该参数,直到与哈希值匹配的一个过程。哈希函数
的结果无法提前得知,也没有能得到一个特定哈希值的模式。哈希函数的这个特性意味着:得到哈希值的唯一方法是不断的尝试,每次随机修改输入,直到出现适当的哈希值。

只能暴力破解

鉴于区块链是一个公开开放的数据库,所以我们并不想要存储钱包所有者的敏感信息(所以具有一定的匿名性)。资金不是通过账户来收集,交易也不是从一个地址将钱转移到另一个地址,也没有一个字段或者属性来保存账户余额。交易就是区块链要表达的所有内容。那么,交易里面到底有什么内容呢?

工作量证明算法

哈希函数输入一个任意长度的数据,输出一个长度固定且绝不雷同的值,可将其视为输入的数字指纹。对于特定输
入,哈希的结果每次都一样,任何人都可以用相同的哈希函数,计算和验证哈希结果。一个加密哈希函数的主要特征就是不同的
输入几乎不可能出现相同的数字指纹。因此,有意的选择一个输入去生成一个想要的哈希值值是几乎不可能的,更别提用随机的方式生成想要的哈希值了。

无论输入的大小是多少,SHA256函数的输出的长度总是256bit。

为了使这个哈希算法变得富有挑战,我们来设定一个具有任意性的目标:找到一个语句,使之哈希值的十六进制表示以
0开头。幸运的是,这很容易!在例10-10中语句 “I am Satoshi Nakamoto13”
的哈希值是

0ebc56d59a34f5082aaef3d66b37a661696c2b618e62432727216ba9531041a5

刚好满足条件。我们得到它用了13次。用概率的角度
来看,如果哈希函数的输出是平均分布的,我们可以期望每16次得到一个以0开头的哈希值(十六进制个位数字为0到
F)。从数字的角度来看,我们要找的是小于
0x1000000000000000000000000000000000000000000000000000000000000000
的哈希值。

我们称这个为Target目标阈值,我们的目的是找到一个小于这个目标的哈希值。如果我们减小这个目标值,那找到一个小于它的哈希值会越来越难。

简单打个比方,想象人们不断扔一对骰子以得到小于一个特定点数的游戏。第一局,目标是12。只要你不扔出两个6,
你就会赢。然后下一局目标为11。玩家只能扔10或更小的点数才能赢,不过也很简单。假如几局之后目标降低为了5。

现在有一半机率以上扔出来的骰子加起来点数会超过5,因此无效。随着目标越来越小,要想赢的话,扔骰子的次数会
指数级的上升。最终当目标为2时,只有一个人平均扔36次或2%扔的次数中,他才能赢。从一个知道骰子游戏目标为2的观察者的角度来看,如果有人要成功中奖,假设他平均尝试了36次。

换句话说,可以估计从实现目标难度取得成功所需的工作量。
当算法是基于诸如SHA256的确定性函数时,输入本身就成为证据,必须要一定的工作量才能产生低于目标的结果。
因此,称之为工作量证明

在例10-10中,成功的nonce为13,且这个结果能被所有人独立确认。任何人将13加到语句
“I am Satoshi Nakamoto”
后面再计算哈希值都能确认它比目标值要小。这个正确的结果同时也是工作量证明(Proof
of
Work),因为它证明我们的确花时间找到了这个nonce。验证这个哈希值只需要一次计算,而我们找到它却花了13次。如果目标值更小,那我们需要多得多的哈希计算才能找到合适的nonce,但其他人验证它时只需要一次哈希计算。此外,知道目标值后,任何人都可以用统计学来估算其难度,因此就能知道找到这个nonce需要多少工作。

比特币的工作量证明和例10-10中的挑战非常类似。矿工用一些交易构建一个候选区块。接下来,这个矿工计算这个区块头信息的哈希值,看其是否小于当前目标值。如果这个哈希值不小于目标值,矿工就会修改这个nonce然后再试一次。按当前比特币系统的难度,矿工得试10^15次才能找到一个合适的nonce使区块头信息哈希值足够小。

在写这本书的时候,比特币网络要寻找区块头信息哈希值小于

0000000000000000029AB9000000000000000000000000000000000000000000

可以看出,这个目标哈希值开头的0多了很多。这意味
着可接受的哈希值范围大幅缩减,因而找到正确的哈希值更加困难。生成下一个区块需要网络每秒计算1.8
septa-hashes((thousand billion
billion次哈希)。这看起来像是不可能的任务,但幸运的是比特币网络已经拥有3EH/sec的处理能力,平均每10分钟就可以找到一个新区块。

在例10-3中,我们在区块中看到难度目标,其被标为”难度位”或简称”bits”。在区块277,316中,它的值为
0x1903a30c。
这个标记的值被存为系数/指数格式,前两位十六进制数字为幂,接下来得六位为系数(coefficient)。在这个区块里,0x19为幂,而
0x03a30c为系数。

//计算难度目标的公式为:target = coefficient * 2^(8 * (exponent – 3))//按十进制计算为:=> target = 238,348 * 2^176^=> target = 22,829,202,948,393,929,850,749,706,076,701,368,331,072,452,018,388,575,715,328//转化为十六进制后为:=> target = 0x0000000000000003A30C00000000000000000000000000000000000000000000

也就是说高度为277,316的有效区块的头信息哈希值是小于这个目标值的。这个数字的二进制表示中前60位都是0。在
这个难度上,一个每秒可以处理1万亿个哈希计算的矿工(1 tera-hash per
second 或 1
TH/sec)平均每8,496个区块才能找到一个正确结果,换句话说,平均每59天,才能为某一个区块找到正确的哈希值。

一笔交易由一些输入和输出组合而来:

难度目标与难度调整

如前所述,目标决定了难度,进而影响求解工作量证明算法所需要的时间。那么问题来了:为什么这个难度值是可调整
的?由谁来调整?如何调整?

比特币的区块平均每10分钟生成一个。这就是比特币的心跳,是货币发行速率和交易达成速度的基础。不仅是在短期
内,而是在几十年内它都必须要保持恒定。在此期间,计算机性能将飞速提升。此外,参与挖矿的人和计算机也会不断变化。

为了能让新区块的保持10分钟一个的产生速率,挖矿的难度必须根据这些变化进行调整。事实上,难度是一个动
态的参数,会定期调整以达到每10分钟一个新区块的目标。简单地说,难度被设定在,无论挖矿能力如何,新区块产生
速率都保持在10分钟一个。

type Transaction struct { ID []byte Vin []TXInput Vout []TXOutput}
自动调整

难度的调整是在每个完整节点中独立自动发生的。
每2,016个区块中的所有节点都会调整难度。难度的调整公式是由最新2,016个区块的花费时长与20,160分钟(即
这些区块以10分钟一个速率所期望花费的时长)比较得出的。难度是根据实际时长与期望时长的比值进行相应调整的
。简单来说,如果网络发现区块产生速率比10分钟要快时会增加难度。如果发现比10分钟慢时则降低
难度。

工作量证明的难度调整 源文件( pow.cpp文件钟的CalculateNextWorkRequired

第43行函数 GetNextWorkRequired()// Limit adjustment step

// Limit adjustment step int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime; LogPrintf(" nActualTimespan = %d before boundsn", nActualTimespan); if (nActualTimespan < params.nPowTargetTimespan/4) nActualTimespan = params.nPowTargetTimespan/4; if (nActualTimespan > params.nPowTargetTimespan*4) nActualTimespan = params.nPowTargetTimespan*4; // Retarget const arith_uint256 bnPowLimit = UintToArith256(params.powLimit); arith_uint256 bnNew; arith_uint256 bnOld; bnNew.SetCompact(pindexLast->nBits); bnOld = bnNew; bnNew *= nActualTimespan; bnNew /= params.nPowTargetTimespan; if (bnNew > bnPowLimit) bnNew = bnPowLimit;

注意:虽然目标校准每2,016个块发生,但是由于Bitcoin
Core客户端的一个错误,它是基于之前的2,015个块的总时间(不应该是2,016个),导致重定向偏差向较高难度提高0.05%。

参数Interval和TargetTimespan(1,209,600秒即两周)
的定义在文件chainparams.cpp中。

简单总结就是

New Difficulty = Old Difficulty * (Actual Time of Last 2016 Blocks / 20160 minutes)

为了防止难度的变化过快,每个周期的调整幅度必须小于一个因子。如果要调整的幅度大于4倍,则按4倍调
整。由于在下一个2,016区块的周期不平衡的情况会继续存在,所以进一步的难度调整会在下一周期进行。因此平衡哈
希计算能力和难度的巨大差异有可能需要花费几个2,016区块周期才会完成。

提示:寻找一个比特币区块需要整个网络花费10分钟来处理,每发现2,016个区块时会根据前2,016个区块完成的时间对难度进行调整。

对于每一笔新的交易,它的输入会引用(reference)之前一笔交易的输出(这里有个例外,也就是我们待会儿要谈到的
coinbase
交易)。所谓引用之前的一个输出,也就是将之前的一个输出包含在另一笔交易的输入当中。交易的输出,也就是币实际存储的地方。下面的图示阐释了交易之间的互相关联:

题外话

值得注意的是目标难度与交易的数量和金额无关。这意味着哈希算力的强弱,即让比特币更安全的电力投入量,与交易
的数量完全无关。换句话说,当比特币的规模变得更大,使用它的人数更多时,即使哈希算力保持当前的水平,比特币
的安全性也不会受到影响。哈希算力的增加表明更多的人为得到比特币回报而加入了挖矿队伍。只要为了回报,公平正
当地从事挖矿的矿工群体保持足够的哈希算力,”接管”攻击就不会得逞,让比特币的安全无虞。

目标难度和挖矿电力消耗与将比特币兑换成现金以支付这些电力之间的关系密切相关。高性能挖矿系统就是要用当前硅
芯片以最高效的方式将电力转化为哈希算力。挖矿市场的关键因素就是每度电转换为比特币后的价格。因为这决定着挖
矿活动的营利性,也因此刺激着人们选择进入或退出挖矿市场。

比如最近BTC大跌,矿机入不敷出,因此矿工减少,难度下降,电力消耗下降,营利增加。

澳门新葡亰平台9411 4the
interconnection of transactions

成功构建区块

前面已经看到,Jing的节点创建了一个候选区块,准备拿它来挖矿。Jing有几个安装了ASIC的矿机,
上面有成千上万个集成电路可以超高速地并行运行SHA256算法。这些定制的硬件通过USB连接到他的挖矿节点上。接
下来,运行在Jing的桌面电脑上的挖矿节点将区块头信息传送给这些硬件,让它们以每秒亿万次的速度进行nonce测
试。

在对区块277,316的挖矿工作开始大概11分钟后,这些硬件里的其中一个求得了解并发回挖矿节点。当把这个结果放进
区块头时,nonce 4,215,469,401 就会产生一个区块哈希值:

0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569

而这个值小于难度目标值:

0000000000000003A30C00000000000000000000000000000000000000000000

Jing的挖矿节点立刻将这个区块发给它的所有相邻节点。这些节点在接收并验证这个新区块后,也会继续传播此区块。当这个新区块在网络中扩散时,每个节点都会将它作为区块277,316加到自身节点的区块链副本中。当挖矿节点收到并验证了这个新区块后,它们会放弃之前对构建这个相
同高度区块的计算,并立即开始计算区块链中下一个区块的工作。

下节将介绍节点进行区块验证、最长链选择、达成共识,并以此形成一个去中心化区块链的过程。

注意:

  1. 有一些输出并没有被关联到某个输入上
  2. 一笔交易的输入可以引用之前多笔交易的输出
  3. 一个输入必须引用一个输出

贯穿本文,我们将会使用像“钱”,“币”,“花费”,“发送”,“账户”
等等这样的词。但是在比特币中,实际并不存在这样的概念。交易仅仅是通过一个脚本来锁定一些价值,而这些价值只可以被锁定它们的人解锁。

让我们先从输出开始:

type TXOutput struct { Value int ScriptPubKey string}

实际上,正是输出里面存储了“币”(注意,也就是上面的 Value
字段)。而这里的存储,指的是用一个数学难题对输出进行锁定,这个难题被存储在
ScriptPubKey 里面。在内部,比特币使用了一个叫做 Script
的脚本语言,用它来定义锁定和解锁输出的逻辑。虽然这个语言相当的原始(这是为了避免潜在的黑客攻击和滥用而有意为之),并不复杂,但是我们并不会在这里讨论它的细节。你可以在这里
找到详细解释。

在比特币中,value 字段存储的是 satoshi 的数量,而不是>有 BTC
的数量。一个 satoshi 等于一百万分之一的 >BTC(0.00000001
BTC),这也是比特币里面最小的货币单位>(就像是 1 分的硬币)。

由于还没有实现地址,所以目前我们会避免涉及逻辑相关的完整脚本。ScriptPubKey
将会存储一个任意的字符串(用户定义的钱包地址)。

顺便说一下,有了一个这样的脚本语言,也意味着比特币其实也可以作为一个智能合约平台。

关于输出,非常重要的一点是:它们是不可再分的(invisible),这也就是说,你无法仅引用它的其中某一部分。要么不用,如果要用,必须一次性用完。当一个新的交易中引用了某个输出,那么这个输出必须被全部花费。如果它的值比需要的值大,那么就会产生一个找零,找零会返还给发送方。这跟现实世界的场景十分类似,当你想要支付的时候,如果一个东西值
1 美元,而你给了一个 5 美元的纸币,那么你会得到一个 4 美元的找零。

这里是输入:

type TXInput struct { Txid []byte Vout int ScriptSig string}

正如之前所提到的,一个输入引用了之前一笔交易的一个输出:Txid
存储的是这笔交易的 ID,Vout
存储的是该输出在这笔交易中所有输出的索引(因为一笔交易可能有多个输出,需要有信息指明是具体的哪一个)。ScriptSig
是一个脚本,提供了可作用于一个输出的 ScriptPubKey 的数据。如果
ScriptSig
提供的数据是正确的,那么输出就会被解锁,然后被解锁的值就可以被用于产生新的输出;如果数据不正确,输出就无法被引用在输入中,或者说,也就是无法使用这个输出。这种机制,保证了用户无法花费属于其他人的币。

再次强调,由于我们还没有实现地址,所以 ScriptSig
将仅仅存储一个任意用户定义的钱包地址。我们会在下一篇文章中实现公钥(public
key)和签名(signature)。

来简要总结一下。输出,就是 “币”
存储的地方。每个输出都会带有一个解锁脚本,这个脚本定义了解锁该输出的逻辑。每笔新的交易,必须至少有一个输入和输出。一个输入引用了之前一笔交易的输出,并提供了数据(也就是
ScriptSig
字段),该数据会被用在输出的解锁脚本中解锁输出,解锁完成后即可使用它的值去产生新的输出。

也就是说,每一笔输入都是之前一笔交易的输出,那么从一笔交易开始不断往前追溯,它涉及的输入和输出到底是谁先存在呢?换个说法,这是个鸡和蛋谁先谁后的问题,是先有蛋还是先有鸡呢?

在比特币中,是先有蛋,然后才有鸡。输入引用输出的逻辑,是经典的“蛋还是鸡”问题:输入先产生输出,然后输出使得输入成为可能。在比特币中,最先有输出,然后才有输入。换而言之,第一笔交易只有输出,没有输入。

当矿工挖出一个新的块时,它会向新的块中添加一个 coinbase
交易。coinbase
交易是一种特殊的交易,它不需要引用之前一笔交易的输出。它“凭空”产生了币,这也是矿工获得挖出新块的奖励,可以理解为“发行新币”。

在区块链的最初,也就是第一个块,叫做创世块。正是这个创世块,产生了区块链最开始的输出。对于创世块,不需要引用之前交易的输出。因为在创世块之前根本不存在交易,也就没有不存在有交易输出。

来创建一个 coinbase 交易:

func NewCoinbaseTX(to, data string) *Transaction { if data == "" { data = fmt.Sprintf("Reward to '%s'", to) } txin := TXInput{[]byte{}, -1, data} txout := TXOutput{subsidy, to} tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} tx.SetID() return &tx}

coinbase 交易只有一个输出,没有输入。在我们的实现中,它的 Txid
为空,Vout 等于 -1。并且,在目前的视线中,coinbase 交易也没有在
ScriptSig 中存储一个脚本,而只是存储了一个任意的字符串。

在比特币中,第一笔 coinbase 交易包含了如下信息:“The Times 03/Jan/2009
Chancellor on brink of second bailout for banks”。可点击这里查看.

subsidy
是奖励的数额。在比特币中,实际并没有存储这个数字,而是基于区块总数进行计算而得:区块总数除以
210000 就是 subsidy。挖出创世块的奖励是 50 BTC,每挖出 210000
个块后,奖励减半。在我们的实现中,这个奖励值将会是一个常量。

从现在开始,每个块必须存储至少一笔交易。如果没有交易,也就不可能挖出新的块。这意味着我们应该移除
BlockData 字段,取而代之的是存储交易:

type Block struct { Timestamp int64 Transactions []*Transaction PrevBlockHash []byte Hash []byte Nonce int}

NewBlockNewGenesisBlock 也必须做出相应改变:

func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { block := &Block{time.Now, transactions, prevBlockHash, []byte{}, 0} ...}func NewGenesisBlock(coinbase *Transaction) *Block { return NewBlock([]*Transaction{coinbase}, []byte{})}

接下来修改创建新链的函数:

func CreateBlockchain(address string) *Blockchain { ... err = db.Update(func(tx *bolt.Tx) error { cbtx := NewCoinbaseTX(address, genesisCoinbaseData) genesis := NewGenesisBlock b, err := tx.CreateBucket([]byte(blocksBucket)) err = b.Put(genesis.Hash, genesis.Serialize ...}

现在,这个函数会接受一个地址作为参数,这个地址会用来接收挖出创世块的奖励。

工作量证明算法必须要将存储在区块里面的交易考虑进去,以此保证区块链交易存储的一致性和可靠性。所以,我们必须修改
ProofOfWork.prepareData 方法:

func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, pow.block.HashTransactions(), // This line was changed IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64, }, []byte{}, ) return data}

不像之前使用 pow.block.Data,现在我们使用
pow.block.HashTransactions()

func  HashTransactions() []byte { var txHashes [][]byte var txHash [32]byte for _, tx := range b.Transactions { txHashes = append(txHashes, tx.ID) } txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) return txHash[:]}

我们使用哈希提供数据的唯一表示,这个之前也遇到过。我们想要通过仅仅一个哈希,就可以识别一个块里面的所有交易。为此,我们获得每笔交易的哈希,将它们关联起来,然后获得一个连接后的组合哈希。

比特币使用了一个更加复杂的技术:它将一个块里面包含的所有交易表示为一个
Merkle tree ,然后在工作量证明系统中使用树的根哈希(root
hash)。这个方法能够让我们快速检索一个块里面是否包含了某笔交易,即只需
root hash 而无需下载所有交易即可完成判断。

来检查一下到目前为止是否正确:

$ blockchain_go createblockchain -address Ivan00000093450837f8b52b78c25f8163bb6137caf43ff4d9a01d1b731fa8ddcc8aDone!

很好!我们已经获得了第一笔挖矿奖励,但是,我们要如何查看余额呢?

我们需要找到所有的未花费交易输出(unspent transactions outputs,
UTXO)。未花费
指的是这个输出还没有被包含在任何交易的输入中,或者说没有被任何输入引用。在上面的图示中,未花费的输出是:

  1. tx0, output 1;
  2. tx1, output 0;
  3. tx3, output 0;
  4. tx4, output 0.

当然了,当我们检查余额时,我们并不需要知道整个区块链上所有的
UTXO,只需要关注那些我们能够解锁的那些
UTXO(目前我们还没有实现密钥,所以我们将会使用用户定义的地址来代替)。首先,让我们定义在输入和输出上的锁定和解锁方法:

func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { return in.ScriptSig == unlockingData}func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { return out.ScriptPubKey == unlockingData}

在这里,我们只是将 script 字段与 unlockingData
进行了比较。在后续文章我们基于私钥实现了地址以后,会对这部分进行改进。

下一步,找到包含未花费输出的交易,这一步相当困难:

func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { var unspentTXs []Transaction spentTXOs := make(map[string][]int) bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { txID := hex.EncodeToString Outputs: for outIdx, out := range tx.Vout { // Was the output spent? if spentTXOs[txID] != nil { for _, spentOut := range spentTXOs[txID] { if spentOut == outIdx { continue Outputs } } } if out.CanBeUnlockedWith { unspentTXs = append(unspentTXs, *tx) } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { if in.CanUnlockOutputWith { inTxID := hex.EncodeToString spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } } } } if len(block.PrevBlockHash) == 0 { break } } return unspentTXs}

由于交易被存储在区块里,所以我们不得不检查区块链里的每一笔交易。从输出开始:

if out.CanBeUnlockedWith { unspentTXs = append(unspentTXs, tx)}

如果一个输出被一个地址锁定,并且这个地址恰好是我们要找的未花费交易输出的地址,那么这个输出就是我们想要的。不过在获取它之前,我们需要检查该输出是否已经被包含在一个输入中,也就是检查它是否已经被花费了:

if spentTXOs[txID] != nil { for _, spentOut := range spentTXOs[txID] { if spentOut == outIdx { continue Outputs } }}

我们跳过那些已经被包含在其他输入中的输出(被包含在输入中,也就是说明这个输出已经被花费,无法再用了)。检查完输出以后,我们将所有能够解锁给定地址锁定的输出的输入聚集起来(这并不适用于
coinbase 交易,因为它们不解锁输出):

if tx.IsCoinbase() == false { for _, in := range tx.Vin { if in.CanUnlockOutputWith { inTxID := hex.EncodeToString spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } }}

这个函数返回了一个交易列表,里面包含了未花费输出。为了计算余额,我们还需要一个函数将这些交易作为输入,然后仅返回一个输出:

func (bc *Blockchain) FindUTXO(address string) []TXOutput { var UTXOs []TXOutput unspentTransactions := bc.FindUnspentTransactions for _, tx := range unspentTransactions { for _, out := range tx.Vout { if out.CanBeUnlockedWith { UTXOs = append(UTXOs, out) } } } return UTXOs}

就是这么多了!现在我们来实现 getbalance 命令:

func  getBalance(address string) { bc := NewBlockchain defer bc.db.Close() balance := 0 UTXOs := bc.FindUTXO for _, out := range UTXOs { balance += out.Value } fmt.Printf("Balance of '%s': %dn", address, balance)}

账户余额就是由账户地址锁定的所有未花费交易输出的总和。

在挖出创世块以后,来检查一下我们的余额:

$ blockchain_go getbalance -address IvanBalance of 'Ivan': 10

这就是我们的第一笔钱!

现在,我们想要给其他人发送一些币。为此,我们需要创建一笔新的交易,将它放到一个块里,然后挖出这个块。之前我们只实现了
coinbase 交易(这是一种特殊的交易),现在我们需要一种通用的交易:

func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { var inputs []TXInput var outputs []TXOutput acc, validOutputs := bc.FindSpendableOutputs(from, amount) if acc < amount { log.Panic("ERROR: Not enough funds") } // Build a list of inputs for txid, outs := range validOutputs { txID, err := hex.DecodeString for _, out := range outs { input := TXInput{txID, out, from} inputs = append(inputs, input) } } // Build a list of outputs outputs = append(outputs, TXOutput{amount, to}) if acc > amount { outputs = append(outputs, TXOutput{acc - amount, from}) // a change } tx := Transaction{nil, inputs, outputs} tx.SetID() return &tx}

在创建新的输出前,我们首先必须找到所有的未花费输出,并且确保它们存储了足够的值,这就是
FindSpendableOutputs
方法做的事情。随后,对于每个找到的输出,会创建一个引用该输出的输入。接下来,我们创建两个输出:

  1. 一个由接收者地址锁定。这是给实际给其他地址转移的币。

  2. 一个由发送者地址锁定。这是一个找零。只有当未花费输出超过新交易所需时产生。记住:输出是不可再分的

FindSpendableOutputs 方法基于之前定义的 FindUnspentTransactions
方法:

func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) unspentTXs := bc.FindUnspentTransactions accumulated := 0Work: for _, tx := range unspentTXs { txID := hex.EncodeToString for outIdx, out := range tx.Vout { if out.CanBeUnlockedWith && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) if accumulated >= amount { break Work } } } } return accumulated, unspentOutputs}

这个方法对所有的未花费交易进行迭代,并对它的值进行累加。当累加值大于或等于我们想要传送的值时,它就会停止并返回累加值,同时返回的还有通过交易
ID 进行分组的输出索引。我们并不想要取出超出需要花费的钱。

现在,我们可以修改 Blockchain.MineBlock 方法:

func (bc *Blockchain) MineBlock(transactions []*Transaction) { ... newBlock := NewBlock(transactions, lastHash) ...}

最后,让我们来实现 send 方法:

func  send(from, to string, amount int) { bc := NewBlockchain defer bc.db.Close() tx := NewUTXOTransaction(from, to, amount, bc) bc.MineBlock([]*Transaction{tx}) fmt.Println("Success!")}

发送币意味着创建新的交易,并通过挖出新块的方式将交易打包到区块链中。不过,比特币并不是一连串立刻完成这些事情(不过我们的实现是这么做的)。相反,它会将所有新的交易放到一个内存池中,然后当一个矿工准备挖出一个新块时,它就从内存池中取出所有的交易,创建一个候选块。只有当包含这些交易的块被挖出来,并添加到区块链以后,里面的交易才开始确认。

让我们来检查一下发送币是否能工作:

$ blockchain_go send -from Ivan -to Pedro -amount 600000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37Success!$ blockchain_go getbalance -address IvanBalance of 'Ivan': 4$ blockchain_go getbalance -address PedroBalance of 'Pedro': 6

很好!现在,让我们创建更多的交易,确保从多个输出中发送币也正常工作:

$ blockchain_go send -from Pedro -to Helen -amount 200000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdfSuccess!$ blockchain_go send -from Ivan -to Helen -amount 2000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aaSuccess!

现在,Helen 的币被锁定在了两个输出中:一个来自 Pedro,一个来自
Ivan。让我们把它们发送给其他人:

$ blockchain_go send -from Helen -to Rachel -amount 3000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0Success!$ blockchain_go getbalance -address IvanBalance of 'Ivan': 2$ blockchain_go getbalance -address PedroBalance of 'Pedro': 4$ blockchain_go getbalance -address HelenBalance of 'Helen': 1$ blockchain_go getbalance -address RachelBalance of 'Rachel': 3

看起来没问题!现在,来测试一些失败的情况:

$ blockchain_go send -from Pedro -to Ivan -amount 5panic: ERROR: Not enough funds$ blockchain_go getbalance -address PedroBalance of 'Pedro': 4$ blockchain_go getbalance -address IvanBalance of 'Ivan': 2

虽然不容易,但是现在终于实现交易了!不过,我们依然缺少了一些像比特币那样的一些关键特性:

  1. 地址。我们还没有基于私钥(private key)的真实地址。

  2. 奖励。现在挖矿是肯定无法盈利的!

  3. UTXO
    集。获取余额需要扫描整个区块链,而当区块非常多的时候,这么做就会花费很长时间。并且,如果我们想要验证后续交易,也需要花费很长时间。而
    UTXO 集就是为了解决这些问题,加快交易相关的操作。

  4. 内存池。在交易被打包到块之前,这些交易被存储在内存池里面。在我们目前的实现中,一个块仅仅包含一笔交易,这是相当低效的。

链接:

  1. Full source codes

  2. Transaction

  3. Merkle tree

  4. Coinbase

本文源代码:part_4

原文链接:Building Blockchain in Go. Part 4: Transactions 1