分布式系统学习小记16 —— Bitcoin 比特币

“Hi,这是小5自学算力大集群相关知识的第16篇笔记。这期将是区块链技术的第二期——比特币相关技术与原理。”

摘要

我们的目标是要建立一个公共日志,这通常也被称为公共账本。该如何构建一个公共账本,以便所有人都对已发生的所有交易达成共识,并且进一步地,他们对交易的顺序也达成共识。因为如果 Y 试图同时将这枚硬币发送给 Z 和 Q ,我们希望第一个交易成功,而第二个交易被忽略,即所有人都能就哪笔交易先发生哪笔交易后发生并应被忽略达成共识。

  • 如何创建一个账本

最简单的想法是,只需选出一个大家都信任的人,并由这个人来维护账本。但这么做在实际中却往往不可行,根本原因在于,在一个大型系统、全球性系统中一很难找到一个为所有人信赖、确实值得信赖且不存在腐败员工的单一实体。我们希望构建一个系统,该系统由相互不信任的参与者组成,并能够在参与者恶意行为下保持稳定运行。

尝试构建一个系统,并且任何人都可以加入:

它将拥有成千上万台计算机,我们称这些计算机为对等体( peers ),它们散布在互联网的各个角落。

每当有人想要进行一笔新交易,比如 Y 想将一枚硬币发送给 Z , Y 会广播其新交易,将其新交易发送给所有对等节点。可以直接发送,或者采用另一种设计,实际上这就是比特市所采用的方式,即 Y 将新交易发送给几个对等节点,然后这些对等节点通过各自的 TCP 链接将其传播至系统中的其余部分。因此,在每个交易最终都会被发送给所有对等节点,而对等节点所做的,是每个节点都维护着一份完整的所有交易日志的副本。

我们真正希望的是,所有节点,至少是所有诚实的节点,其交易日志能够完全一致。他们将就哪些交易存在达成一致,同样重要的是就这些交易的顺序达成一致。

那么该如何安排所有这些对等节点以相同的顺序处理交易,并将这些交易添加到它们的日志中呢?

一种可能的情况是,各节点会就每一笔新交易相互沟通,并对日志中的每个新槽位进行投票,决定哪笔交易应填充该槽位并占据多数。由于节点们可能会因接收到不同交易而产生合法分歧,我们采用多数原则,即所有节点将审视所有投票,获得最多票数的交易将填入日志的下一个槽位,随后再对下一个槽位进行投票。但这需要知道有多少个对等节点,才能确定什么是多数。并且需要确保永远不会对任何一个对等多点计数超过一次。

遗憾的是,在像比特市这样的完全开放系统中实际上,我们无法做到上述任何一点。随着系统中对等体的加入和离开,该数字也在不断变化。因此我们无法得知有多少个对等节点,也就无法确定多数的含义。此外,我们还没有一种可靠的方法来计票,以确保每个参与者只有一票,即使假设这是可取的。

实际上,比特币并非采用投票机制,然而它仍然成功解决了如何在恶意环境下达成单一账本共识的问题。我们的目标是就单一的交易日志达成共识,再次强调,这是为了防止双重支付。

比特币正是构建了这样一个被称为区块链的东西,其中包含了所有硬币上的所有交易记录。因此,这个单一的区块链,描述了系统中所有交易。我们仍然拥有对等网络,每个对等节点都在其自身内存中构建日志副本和交易日志的完整副本。每当一个节点听到关于一笔新交易的消息,例如 Y 向 Z 或 Q 提交一笔支付交易时,该节点会将此交易发送给一个或多个其他节点,随后这些节点会在整个系统中广播该交易。

日志/区块链构建的方式,是每个节点积累交易并打包多笔交易,成千上万笔,形成一个区块,然后这些全新的交易区块实际上是被追加到账本上的,通过在整个系统中广播新块,以确保至少在理论上,每个节点都能看到每个新区块。所有区块链均由区块构成,每个区块的结构是其前一个区块的哈希值以及一组事务,如 Y 试图支付给 Q ,或者任何其他情况,涉及数百到数千笔个别交易。

每一笔交易实际上包含了该币种前一笔交易的哈希值,通常存在于前一个区块中;它还包含了该币种前所有者私钥的私密签名,以及新所有者的公钥。因此,这笔交易,即从 Y 转移到 Q 的交易,将包含 Q 的公钥、 Y 的私钥签名,以及前一笔交易的哈希值和前一个区块。此外,这些交易中还包含随机数( nonce ),它是一个32位的数字,然后是当前时间。

系统的工作方式是,各节点积累新的交易,并且大约每10分钟,其中一个节点会产生一个新的区块,该区块应是包含自上一个区块创建以来约10分钟内所有已发送至系统的交易的继承区块。如果你被告知有人要支付你比特币,那么在确认他们确实完成了支付之前,你需要观察区块链的发展变化。直到你看到一个包含你所期待的交易的新区块,这笔交易来自声称已向你转账的人,且包含你的公钥及有效、正确签名。

  • 那么每个区块是由谁创建的呢?

创建新块的行为称为“挖矿”,而用于生成新块的技术通常也被称为“工作量证明”,因为它需要大量的 CPU 时间来生成一个新块,因此,生成新块实质上证明了你控制着一个真正的活跃 CPU ,而不仅仅是在一个虚假的计算机上挖掘新块。

新块为了被认定为有效,当对其进行哈希运算时,该块的哈希值开头必须包含一定数量的零。负责挖矿的计算机可以在 nonce 为节点设置任意它喜欢的值。因此,挖矿计算机所做的就是尝试为结点设置不同的随机值。他们会将其插入到自己正在尝试挖掘的区块副本中,然后对该区块进行哈希计算。他们会检查这个特定节点所对应的哈希值中前导零的数量,若前置零足够,则该区块有效。

然后,无论哪个节点找到了这个 nonce ,使得区块哈希值拥有大量前导零,都可以将此区块通过网络广播出去。其将成为链中的下一个新块。但在通常情况下,对于任何给定nonce的区块哈希值,将不会拥有足够的开头零。而挖矿节点将不得不尝试另一个随机数,持续进行此操作,直至找到一个区块,其哈希值拥有足够多的前导零。因此,这会占用大量的CPU时间,这是一个随机过程。区块哈希开头所需零的数量被设定,以确保在考虑到所有不同的节点,即成千上万进行比特币挖矿的节点时,平均而言,他们中第一个找到具有足够前导零的区块随机数的所需时间大约为10分钟。

在这个方案中,由于我们无法确定参与者的身份和数量,人们可能会创建假计算机,假 IP 地址等,因此很难实现实际意义上的多数投票。在这里,我们必须使用CPU时间,这是一种无法伪造的真实资源。虽然它并非真正意义上的投票机制,但本质上,新块的产生是通过在参与挖矿的不同计算机中进行有效随机选择来实现的。因此,这种方案可以说是一种随机性较强、加密层面较为合理的随机选择过程。所以,如果攻击者数量较少,他们极不可能被这一过程选中,以贡献下一个区块。

这并非解决双重支付问题的即时方案。但我们将会看到,它是防御双重支付的关键所在。


假设我们拥有一个区块链。当前结构位于第五区块,且第五部分已发布给所有人。此时,所有节点正在共同挖掘潜在的区块六。如果此时 Y 广播其对 Z 的支付,那么矿工们已经在处理这个区块了。所以, Y 的新交易,即使现在发出。也很可能不会被纳入当前已挖出的区块中,但所有矿工都会将这个新交易暂时缓存在一边,一旦有矿工找到第6个区块, Y 的交易就会被纳入挖掘第7个区块的尝试中。

  • 一个区块是否可能有两个不同的后继者?

是的,实际上,这种情况发生得相当频繁。原因在于,有成千上万的节点正在不断挖掘,试图找到第六区块的后续区块。他们很可能正在挖矿,试图生成具有不同交易集合的略有差异的区块。由于交易在网络中传输的方式,某些节点恰好先观察到 Y 被转移给 Z ,并将此信息纳入其正在挖掘的区块中。对于其他同等节点,对于同一个后继区块。它们在挖掘作为第六区块后继的区块时,恰好首先看到了另一个交易并将其纳入了区块中。

如果其中两个节点恰好同时找到了一个满足哈希规则中前置零要求的 nonce ,那就意味着我们同时产生了两个完全不同的有效区块。它们两者都将把那些区块发送到网络中,并且所有其他节点将几乎同时看到这些区块。这被称为“fork”。

最直接的规则是,一旦其中任何一个节点看到一个新的后继区块从某个成功挖掘的节点广播出来,它就会停止在区块六上的工作,并立即切换到尝试在区块7上寻找后继者。

另一个关键规则是,如果有人正在挖掘,试图创建这样一个区块,而它看到了一个来自不同分支的新区块,且该分支更长,那么任何致力于扩展当前分支的人都将转而扩展这个更长的分支。即每个人都应该倾向于最长的链。这意味着这里存在一种不对称性,导致微小的差异。比如,如果某个分叉被矿工稍早地延长,那么它将吸引矿工转移到这个分叉上来,在这个分叉上,将会有越来越多的矿工进行挖矿。在新获胜的分叉上,区块的发现将变得越来越迅速。如果出现分叉,那么很有可能其中一条分叉会先看到下一个区块,从而变得更长。此时,所有节点都会转而在这条分叉上进行挖矿。并且大家会迅速达成共识,认定其中一条分叉为最长链。

在被废弃的分叉上的交易,通常这两个竞争区块包含的交易集相当相似,但也可能存在一些长链上没有的交易。那么这些交易会无效,在区块链系统本身中,并没有尝试去尝试转移这些交易。如果你发现自己的交易没有显示出来,你可能会重新发起它。

然而,也存在这样的情况:在短暂的时间内,这两笔交易都存在于区块链中。即在短暂的时间内,区块链上出现了 WiseCoin 的双重支付问题。直到这两条链中的一条变得更长之前,完全不清楚应该相信这两条链中的哪一条。同时,某些对等体可能仅了解其中之一或另一个。这就引出了一个颇为棘手的问题: Q 或 Z 应采取 何种程序,才能确保他们确实收到了付款。因此,在区块链中后面有几个区块之前,并不会真正相信交易已经完成。随着较长链不断延长,其他链可能超越它的概率变得微乎其微。

这是防止双重支付的机制,即使 Y 同时发出两个冲突的交易,尽管可能出现短暂的双重支付,一旦发生分叉,两个交易中将只有一个能迅速存在于最长链上。如果这第二个交易在 Y 向 Z 的转移被记录到链上之后才被发送给对等节点,那么所有对等节点将忽略那些针对已在链上某一分叉的交易中被花费的币的新到达交易。

对于高价值交易,实际上并不会立即交付货物,而是要等到至少有一定数量的区块(例如五个或六个)在包含该交易的区块之后生成,才会进行交付。


设想 Y 与一些节点勾结,能否将处于链中间的区块7取出,并修改它以生成一个不含此交易的全新区块,然后用这个新块替换旧的区块7,并假装区块8指向它?

这种非常直接的单个区块更改方式是行不通的。原因在于,在区块8中存在一个加密哈希,该哈希正是它所指向的区块7的哈希。区块8中的这个哈希值是对原始区块7的哈希,如果有人更改了这个内容,它将会生成一个不同的哈希值。

因此,无法诱使了解后续区块的人接受一个经过修改的中间区块。


假设存在一个已有的区块链,它有一定的长度,交易为 Y 到 Z 的交易,出现在某个区块链上,你现在想要移除,或者隐藏这笔交易,使其仿佛不复存在。

能否生成一种新的替代链,它与主链、真实链大体相同,但更长。并且恰好省略了从 Y 向 Z 的转账,而包含了 Y 向 Q 的转账。在某种程度上,答案是肯定的,这样做确实奏效。主区块链正以一定速率被非恶意参与者所扩展。如果你是攻击者,且拥有的 CPU 算力超过了整个非恶意节点集合,那么你将能够比真正的链更快地生成区块。

但是,可能抱有希望或略感自信地认为这种情况不会发生的原因在于,在一个庞大的系统中,拥有众多参与者的情况下。要集结超过系统其余部分总和的 CPU 算力可能极为困难。所以,一旦比特币规模变得庞大,人们便有信心认为这种主要的非恶意系统拥有足够的 CPU 算力,以至于攻击者要集结超过整个系统算力的成本将会非常高昂。


  • 为何我们不能将10分钟的新块发掘时间缩短?

新块在系统中扩散需要一定时间,这就是为什么它不会更短的实际原因。


从某种程度上说,这是一个非常昂贵的系统,因为我们讨论的是本质上要永久维护每一笔交易的记录。同时,这也将是一个庞大的系统,系统上将会有巨大的性能流。比特币并不真正具备支持每笔交易的能力,这里有一系列的限制条件。

一个限制是完整的比特币数据库已经消耗了数百 GB 的存储空间。但如果大了千倍,就连存储都将成为严峻的问题,更不用说在其中进行搜索了。

另一个棘手的限制是,这些区块存在一个大小上限。这些区块大小通常只有几兆字节,且新区块每10分钟才会出现次。由于区块大小限制和10分钟的时间限制,系统实际上只能处理数千或数万笔交易。所以目前的比特币架构容量远不足以处理全球所有金融交易。

再有,必须确保的是所有参与者都需就所需前置零的数量达成一致,即,它们都必须就寻找个 nonce 的难度达成共识。如果某个节点观察到区块生成的速率,并认为该速率过慢。因此决定减少所需的哈希前置零的数量,但其他节点并未做出相同的决定。那么该节点生成的区块将会被其他节点拒绝。因为所有节点都要求哈希中他们认为正确数量的前置零。因此,必须就寻找节点的难度达成一致,即对等体必须精确地就当前的难度达成共识。否则,它们将相互拒绝对方的区块。如何解决?

他们都在查看同一个区块链,除了临时分叉外,只有一条区块链。每个人在区块链中都拥有完全相同的比特副本。因此,比特币定义了一个确定性函数。该函数以当前区块链作为其参数,并利用该参数确定性地生成寻找节点的当前难度。其工作原理主要是通过查看区块中的时间戳来判断近期区块的产生速度。因此,这里存在一种有趣的共识,因为它们都看到了完全相同的日志。

再一个有趣的问题是,有些人对新型加密货币感兴趣的原因之一是,它们可能比信用卡更匿名。但如果我不更换我的公钥,并且始终使用同一公钥,那么一旦有人破解了我的公钥,人们便可以通过在比特币交易记录中寻找我的公钥或签名来追踪我的活动。大多数比特币钱包软件实际上是为每笔交易生成新的公钥。

然而事实证明,只要你频繁进行交易,由于这些交易往往与你的真实身份紧密相关,那么留下的线索足以被追踪。就像如果你用比特币在亚马逊上购买商品,或许比特币交易本身并不明确指向你,但它很可能需要通过联邦快递寄送到你的家庭地址。

所以,实际上,对于业余对手而言,比特币的匿名性是相对合理的,但对于专业的敌手则不然。比特币最终证明其并非特别匿名。


以上内容来自对 MIT 《分布式系统 6.824》的学习笔记,在此特别声明。