维持人群账本的一致
接下来我们看如何使分散在世界各地的计算机节点维持账户余额保持一致。
回到最开始的理想模型,大家的记账过程是这样的:
- 在最开始,每人手里都拿着一个空的账本;
- 然后在账本的第一行,大家都记录A存了10块进来
- 在账本的第二行,大家又都记录A给B转了3块
- 第三行,大家又都记录C存了8块
- .....
- 在第4323行,大家又都记录W给K转了8块
- ....
也就是说,从一个空的账本开始,在每一笔交易产生的时候,大家都同时把交易追加到自己的账本的同一行上。如果这种行为可以一直维持下来,那么每个人余额是多少,以及谁给谁转了多少钱,自然会一直保持一致。
这种做法有点像我们小时候被老师听写生字:
- 老师读一个字,我们在本子上写一个字;
- 老师问我们有没有写完,我们齐声确认,然后下一个字
- 最后听写完成后,每个人交上去的结果都一模一样。
在最开始我们也分析过,这种“听写”的模式是无法适用于计算机世界的,这里再次重温下原因:
- 不存在真正意义的广播和确认。我们并没有一个集中畅通的信道,可以让“老师读字给我们听”。计算机网络中的广播,都是节点发送者把消息传给周围的节点,然后再一传十、十传百,从而传遍网络中的所有节点。
- 消息的传播过程中,可能会有丢失、乱序、伪造、否认等。
- 消息的产生源头不仅仅是“老师”一个人,因为同一时刻可能会有很多人发起交易。在这样的情况下,让所有交易记录都保持一样的顺序更是困难:因为可能一部分节点先听到消息1,另一部分先听到消息2。
如果非要用“教室课堂”的模式类比,比特币系统有点像一场奇怪的听力考试:
- 最开始,老师给每个人发了一个空的笔记本;
- 每个人都带着一个耳机,考试开始后,耳机里开始播放单词。大家的单词可能相同,也可能不同,也可能有人的耳机里根本不播放单词。
- 每个人听到单词后,把单词记追加到笔记本的空白行上,并且可以把它写成小纸条传给别人。
- 收到小纸条的人,可以选择抄写并继续传递这张纸条,或者无视。
- 在考试结束后,老师会把笔记本收走。
-
最后的计分规则是这样的:从第一行开始,如果该行的单词内容在每个人本子上都一模一样,那大家都加一分;直到遇到某行的单词有冲突了,计分结束。
如果我们深入去思考这场“诡异的考试”,一定就会为它难到“急火攻心”。乍一想,我们也许可以指定某个人是单词唯一的来源,但我们很难保证他的耳机里会一直放单词。为保险起见,还是每个人都可记录并传播单词,并且找一些规则来维持笔记本内容的一致为妙。
那么我们就需要静下心来想想,阻止我们就“笔记本内容达成一致”的最大阻碍是什么?主要原因在于:同时产生并传来传去的单词太多了,大家不管记自己的,还是抄别人的纸条,都会有一种手忙脚乱的感觉。如果每个时间段内只有一个单词产生,那么大家记起来就容易多了。
换句话说:只有降低单词产生的速度,才更有可能让大家达成一致。为了达到这一目标,我们可以在考试系统中先引入规则一:
-
在听到单词并传递纸条之前,先让大家在桌上玩一把掷骰子。成功掷到三个六的人,才允许他传纸条
道理也很简单:虽然大家都可以快速记下单词,但运气总不会一样好。在掷骰子的约束之下,单词的产生速度会瞬间慢下来。最理想的情况:
- 第一行,某个人成功掷出,然后答案传遍班级;
- 第二行,另一个人掷出三个六,答案又传遍班级……
- 最后每一行的单词都一样。
可这么做有个很明显的问题:记单词的速度太慢了,最后会影响总分的。为了解决这个问题,我们引入规则二:
- 学生每写完一页后,才开始掷骰子,相应的,纸条传播的是一整页的单词。在这种情况下,大家开始就“某一页内容是什么”寻求一致。
那么收到别人的整页单词后,纸条的接受者该怎么做呢?我们引入规则三:
- 停下手头的记单词或者掷骰子工作,验证别人单词的正确性。如果全部正确,就把它抄到最后一页上;同时,传播这一页。自己则进入下一页的“记单词-掷骰子”环节。
- 如果纸条上有错误单词,就忽略掉,继续自己的工作
有了三这个规则,我们可以在脑子里过一下整个考试的过程:
- 大家开始写第一页,有人第一页写完了,比别人都先掷到了骰子,然后这页传遍了整个班;
- 然后都开始第二页....
好,假设这样的方法是奏效的。但不可避免的,到某一页的时候,突然A和B两个人先后都掷出了六,也都开始传播自己的该页内容。很快的,这两份答案蔓延到了班级的每一个同学手中。此时,大家要么有A的那一份,要么有B的那一份,要么两份都有,分歧产生了。
在这种情况下,我们需要引入接收者对答案验证的规则四:
- 先到先得:如果别人传过来一页内容,但这页自己已经完成了(自己答的,或者抄别人的都可以),就无视后来的这份
在这一规则下,每个人手上的答案不会有两份答案。无论是A的那份还是B的那份,谁的先到他们手上他们就选谁的。
有了这一规则,尽管每个人不会拥有多份答案,但就整个集体而言,两个帮派还是不可避免的形成了。他们就“最近一页内容是什么”这一问题而相持不下。
为了避免这一局面,我们需要再次引入一个新的规则。
在介绍下一个规则之前,我们先搁置争议,用发展的眼光来继续审视下这场考试。尽管帮派已经产生,但班级里的每个成员仍在继续热火朝天的进行着“写单词-掷骰子”过程。在某个时间点,又有新的一页产生了,这页内容也会和任何其他页一样,迅速在班里扩散开来。当这页扩散到另一个帮派时,我们的规则五就会起作用了:
-
内容多的胜出:如果别人传过来的页号比我的大,我就按规则3来验证、传播他的内容。同时,为了防止它前面的内容和我记录的不一样,我会核对这页之前的所有内容,把不一样的内容全换成他的
规则五其实给传纸条悄悄引入了一个新的约束:大家在传播纸条时,传播的根本不是某一页内容,而是一整个笔记本。因为不传播整个笔记本,没法对前面的内容进行对比。
当然,这样的传答案方式在你看来可能极其搞笑:把别人的一整本笔记抄下来传纸条似乎会慢的可怜,对整本笔记逐行对比时也算苦逼到家了。这两个问题并非不可以解决,我们后面会再次回过头来看这个问题。这里,就让我们暂时无脑的假设“传整本笔记”是可行的。而且,我们也可以“鸵鸟心态”的更彻底一些:
- 假设我们有无限多的笔记本,可以很快的把手头的笔记复制好多份出来。同时,给别人扔起笔记来也是又快又容易
- 逐条内容检验也不是什么难事
有了规则五之后,即便班级里暂时分成了两个帮派,也不再是一件非常头疼的事了。所有的分歧都是暂时的、不稳定的。随着笔记页数的持续推进,一定会有一方产生比别人都新的答案,这份更多的答案会在传播中慢慢把别的帮派吞并掉,最后整个班级归于一致。再朝深一步想就是:在最近一页或几页的内容上大家可能会有不一致,但一些比较靠前的页面,大家基本上是会保持一致的。
如果把上面“考试答题”的过程映射回比特币系统,我们大体就可以知道比特币维持系统一致的雏形方法了。
- “记单词”的过程就是计算机节点记账的过程:
- 一行单词对应一条交易, 一页单词对应一个“区块”。
- 为了降低一页的冲突,需要每个同学执行掷骰子的过程。相应的,为了降低各个节点的区块冲突,每个节点在组织好一个区块后,随机等待一段时间
- 传播的不是一页试题,而是一整个笔记本,内容多的笔记胜出。对应的,所有的区块内容也都前后关联形成一个整体。系统中可能会同时存在多个这样的区块集,在区块集相互传播的时候,少的内容会被多的覆盖掉。最后大家会就最多的内容达成一致。
区块链
在前面介绍“页数多的胜出”这一规则时,为了保证其有效实施,我们要求班级学生在传播答案的时候,要传播“整个笔记本”。之所以要这么做,是为了大家在接受最大的页面内容时,可以检验并维持前面内容的一致。
还拿100页的例子说事。假如仅仅只传递新生成的第101页,收到该的人无法验证“自己的第100页”和“收到的101页的前一页”内容是不是一样;同理,也无从验证“自己的第99页”和“收到的101页的前两页”的内容是不是一样;依次类推,前面的每一页也无从验证。而传播“整个笔记本”,从物理上保证了全部内容的连贯完整性,从而也给了内容验证以强有力的保证。
那么,我们能不能找到一种更快速有效的方式来进行笔记本内容的验证呢?
答案是可以的:只要我们能对班级中产生的每一页单词笔记进行唯一的编号就可以了。我们在描述一页的时候,不能仅仅说“第x页”,而是应该说“xxx产生的第xxx页”。更精确一点,如果用“学号+页号”的方式来编码,我们就可以避免传播整个笔记内容了。
例如,当X产生出第101页时,或者他收到别人新发来的第101页时,他只需把这一页的完整内容抄成纸条传播即可;而对于所有的前序页,他只传播页的编号即可。
W在收到X的纸条后,如果自己的内容不少于101页:
- 根据“先到先得”的规则,他自然会选择无视X的纸条;
- 如若不然,他就会开始把自己页面的编号和X传过来的编号,从后向前进行逐页对比,并且丢弃掉自己不一致的部分;同时,他也会请求X(或者其他人)把对应页号的完整内容发送一下,以补上自己丢掉的内容。
值得强调的是,W和X页面不一致的地方,一定是发生在W的尾部的。也就是说,两人的页面内容,一定是从某一页开始“分叉”的;交错式的不一致,在我们的协议里一定式不会发生的。这里我不打算对原因展开做解释,有兴趣的读者可以自己想一下原因。
有了“页面编号”的概念后,我们的纸条传播规则可以再进一步的简化:每次只传播新产生的一页,并且把它的前序页也一起写到该页的纸条中去。也就是说,每一页纸条的格式是这样的:
接收者只需要按照页面前一页组成的链条“顺藤摸瓜”解决冲突就好了。
在比特币中,每个“区块”都会记录其前一区块的编号,从而使得系统中所有的区块都串到某一条固定的链上,就像一个完整的笔记本一样。这样的链就叫做区块链。每个记账人手中的账目,就可以对应到系统中的某一条链中。“最长的链”,就是维持大家账目一致的核心秘密。
有了这个概念后,我们就可以回答下“A给B转账,B如何确认收到”的问题了。B无需向系统中的每个节点进行询问(事实上,B也做不到向每个节点询问),他只要耐心的等待A向他发起的那笔交易被串在链中比较靠前的地方就好了。在比特币中,如果一个区块后面又接了五六个区块,就可以确认交易了。
数字摘要
在上一节中,我们给“考试模型”中的每页纸条引入了一个非常重要的概念:唯一的页编号,也给了“学号+页码”这样一个简单的编号规则。在比特币系统中,每个区块也的确有个“唯一编号”,但是其编号规则却大不相同——其规则叫“数字摘要”。
所谓“数字摘要(也叫数字指纹、密码学哈希)”,是这样一种技术:它可以把给定的普通的电脑文件快速转化成一段固定长度的数字串(严格来说:是16进制数字串),这个数字串就叫做原来文件的数字摘要。并且,这种数字串满足三种性质:
- 无论两个文件内容多么相似,他们生成的数字摘要也是大不相同的。而如果两个文件完全相同,那么他们的数字摘要也一模一样。这种特征保证了:一旦一个文件发生了极微小的改动,我们也能通过其摘要快速发现。而不用把源文件的内容进行完整比对。
- 对于一个给定的文件,你想创造一个新的文件和它有相同的数字摘要,是非常困难的。这种特征保证了:你无法随意伪造一个新的文件来把原来的替换掉。
- 你难以构造两个文件,使得他们有相同的数字摘要。这种特征保证了:如果你在一个系统中持续的产生新的文件,你可以通过摘要这种技术给不同的文件进行“编号”。
数字摘要的这些性质,就跟我们的指纹一样,是我们每个文件独一无二特质的一种证明。如果用人的观点来看这三种性质就是:
- 哪怕你是双胞胎,你们的指纹也不相同。
- 你想找一个人和你自己的指纹一样,这几乎是不可能的
- 你想从人群中抓两个人,让他们的指纹一样,这几乎也是不可能的。
在比特币系统中,区块的“唯一编号”就是它全部内容的数字摘要,而之所以能这么干,就是利用性质3。
数字摘要的引入,也给比特币的账本带来了一个有趣的特性:修改链上任何一个区块的任何内容,必须得顺带将所有后续区块做改正。因为根据性质1:
- 内容的改动带来摘要的改动
- 摘要的改动,相当于是后继1的内容改动,从而后继1的摘要也得改动
-
从而扩展到后继2,直到最后一个区块
所以,记账人想改了某条账本且仍旧想维护账本的有效性的话,必须得将所有后继都做一遍改动。现在请大家默默记住这一平平无奇的特性。待后面引入其他一些规则后,该特性将成为比特币系统坚不可摧的最重要基石。
简单小结
到此为止,我们在不考虑“拜占庭问题”的前提下,构建起了保证各节点账本一致的基本方法:
- 收到交易时,验证交易的合法性。
- 把多个交易打包成一个区块,掷骰子到合适点数后再广播区块,从而减少冲突。
- 收到别人发来的区块后,接受合法的区块,且继续广播,加速区块在系统中的传播。
- 对于相同深度的区块,只接受最先到达的。
- 当接受一个深度更大的区块时,顺藤摸瓜地检测前序区块,产生冲突时丢弃自己那份
- 为了支持5,每个区块用数字摘要来给区块编号,且在每个内部记录自己的前序区块