Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。
欢迎来到Jack区块链第二部分,这一部分我将介绍不同节点之间的通信。
我原本的目标是写节点之间的同步以及如何与其它节点通信,包括挖矿和对其它节点广播获胜区块信息。最后我发现实现这所有目标的代码量和需要的解释放在这一篇里实在太多。所以我决定在第二部分只对这些话题开个头。
读完文章之后你应该能大概了解我做了些什么以及如何做的。但是我不会把所有的代码都贴出来。因为所涉及的代码实在太多了。如何你想看我的完整的实现,请移步到[Github part-2分支](https://github.com/jackschultz/jbc/tree/part-2)。
以下的代码并不是按照时间顺序写出来的,这是编程过程的一种常态。在这个过程中,我有时候会冒出不同的想法,尝试一些不同的方法,删除一些代码,又添加一些新的代码,直到最后完成如下内容。
我想提一下这个过程,这样读者就不会误以为我编程的过程和文章描述的顺序是一致的。如果可以的话,我很希望能把我尝试过的方法,一些不容易修复的bug,卡住我的一些地方都写出来。
这整个过程很难解释,我希望读者不是来了解如何编程,而是想看代码和实现。记住,编程很少是按顺序实现的。
欢迎在Twitter或者我的博客里与我联系。欢迎任何形式的留言,无论是指出我的错误,批评还是告诉我这篇文章对你有什么帮助。
长话短说
如果你想学习的是区块链挖矿如何工作,这一篇不会教你。你可以先去读第一部分,在那儿我给这一问题开了个头,在后续部分我会讲到更高级的挖矿原理。
在最后,我会展示如何在开始挖矿前与其它节点通信,获取区块链信息并存储在本地。为什么文章会这么长呢?这是因为考虑到如何能够将来在这个应用上工作更容易,所涉及到的东西是非常多的。
说了这么多,其实这里所讲的同步也并不是非常高级。我会讲到对区块链的类(Classes)的改进,测试新的功能,创建一些简单的节点,并在最后讲节点启动的时候同步的一种方法。
在这些小节里,我会讲解一下代码,然后贴出代码,准备好了。
扩展Blck类,添加Chain类
这个项目是一个绝佳的展现OOP(面向对象编程)的好处的例子。在这一节里,我会讲到对Block类的改进,然后创建一个Chain类。
Block类有以下要点:
1. 添加一种把键值转化为我们要想的类型的方法。这样我们就可以很容易的加载JSON格式的文件,转化为dict类型,并且将index, nonce, second timestamp这些键的string类型的值转化为int类型。将来对于新的变量也可以做类似的转化。比如说我想把datetime值转化为datetime类型的对象,这个方法就可以被重用到。
2. 检查block的hash是否是以要求数目的零开头。
3. Block能够自己保存在chaindata文件夹里。不用添加另外的函数做这件事情。
4. 覆盖一些操作符函数。
- `__repr__` 能够在调试的时候打印出更有意义的信息。在打印的时候它覆盖了`__str__`方法。
- `__eq__`允许你用`==`来比较两个block。否则Python就会用block的的内存地址来比较。这里我们要比较的是数据。
- `__ne__`这就是非`__eq__`。
- 将来,我们可能还需要覆盖大于`__gt__`,这样就可以用`>`来看哪一个区块链更好。但是现在我们还没有很好的方式来做这件事。
当然,这些操作符函数将来还可以演变。
再来看看Chain类。
1. 用block list来初始化Chain。比如Chain([block_zero, block_one])或者用Chain([])初始化一个空的Chain。
2. 用index加一,prev_hash等于前一个block的hash,hash有有效个数的零来验证合法性。
3. 能够将block保存到chaindata文件夹。
4. 可以通过index或者hash找到block。
5. 可以用len(chain_obj)获取chain的长度,它等于self.blocks的长度。
6. 长度相等,并且所有的blocks都相等的chain相等。
7. 大于或者小于是由chain的长度的大小决定。
8. 可以在chain上添加block,目前是在把它加到blocks列表的最后,没有做合法性检查。
9. chain上的所有blocks用字典对象的列表表示。
我列出了很多Chain类的函数。以后这些函数还可能有变化,尤其是`__gt__`和`__lt__`这些比较函数。现在,这些函数已经涵盖了我们需要的功能。
测试
我下面直接把我的测试代码贴出来。我们现有的类可以用来挖新的区块。所以一种测试的办法是修改类,运行挖矿程序,观察有什么样的错误,修正错误,然后重复这个过程。但是这种测试方法太浪费时间。
虽然用来做测试的库有很多,但是使用这些库都有格式上的要求和限制。现在我不想牵扯那么多,所以就用一个test.py来搞定。
为了拿到block字典的列表来初始化,我先运行了几次挖矿程序,获得一些有效的block字典。然后,我写了类的各个部分的测试。这样一来,如果我改动了或者添加了一些功能任何类,就不需要花多少时间写对应的测试了。当我运行python test.py的时候,程序运行的飞快,并且能够准确的告诉我哪一行有错误。
对test.py的进一步改进是用更好的测试库将所有的测试分开运行。这样你就能一次性发现所有失败的测试,而不用一次处理一个。另一个改进是可以将测试用的block字典列表数据放到文件中,而不是写到脚本里。比如:在测试文件夹中加一个chaindata文件夹,这样就可以测试创建和保存区块。
同辈节点和硬链接
这一部分的最重要的内容是关于如何创建能够自行运行挖矿程序,拥有自己的端口,能够把区块链存储在自己的区块链数据文件夹,并且能够告诉同辈节点它们的区块链信息的节点。
为了实现这个目标,我选择用硬链接的方式在这些文件夹之间共享文件,这样任何一个改动都能够在其他的节点上体现出来。
一开始的时候我尝试了rsync,通过运行bash脚本,把主要文件拷贝到本地不同的文件夹里。这种做法的问题是每次文件的改动和节点的重启都需要再次同步文件。我不希望一直需要重复这个过程。
相反,用硬链接的方式,操作系统让不同文件夹里的文件都直接指向我的jbc主文件夹里的文件。这样主文件夹里的任何变更都会在其它的节点上体现出来。
以下是我写的用来链接文件夹的bash脚本。
运行 `./linknodes 5001`就会创建jbc5001文件夹和相应的文件。
因为Flask默认是运行在5000端口上的,所以我就用5001,5002和5003作为我一开始的同辈节点。我们在config.py里定义它们,然后在config被import的时候使用它们。就像其它部分的代码一样,这种设计将来肯定会有所改变的,因为我们不想hardcode同辈节点。而且我们还想要能够获取新的节点。
酷!在第一部分,Flask节点已经实现了分享区块链信息的接口,所以这一块我们不用再关心。但是我们还需要实现询问同辈节点它们正在做什么的代码。
旁注:用Flask和http,对于我们的项目是可以的。但是像以太坊这样的区块链应用有它们自己的广播协议([Ethereum Wire Protocol](https://github.com/ethereum/wiki/wiki/Ethereum-Wire-Protocol))。
同步
当一个节点或者挖矿程序启动的时候,首先需要做的是同步,获取一个有效的区块在上面开始工作。这又分为两部分:1. 从节点自身的chaindata文件夹中获取本地区块链的信息,2. 能够询问同辈节点的区块链信息。
在同步中有一个很重要的问题我先暂时忽略,那就是决定哪一个区块链是最佳的。记得我们之前提到的Chain类里面的`__gt__`方法吗?我所做的就是看哪一个区块链最长。但是这种方法不能保证区块链的信息无法被篡改。
想象一下,如果一个节点自己埋头挖矿,在没有询问其它节点的时候自己朝着不同的方向前进,并且幸运地创建了新的有效的区块,最后这个节点比其它节点都长但是区块数据完全不同。这个时候我们能把这些区块当做合法的吗?答案应该是不能。但是目前,我还是以长度为依据。在项目以后的部分我会改进这一设计。
另外一点是我下面的代码并没有做多少错误检查。我假定文件和block字典数据都是有效的而且没有人尝试篡改它们。在生产环境下错误检查是非常重要的,但是这儿我们先忽略它。
注:sync_local()函数跟第一部分的sync_local()函数基本上是一样。新的sync_overall.py询问同辈节点的区块链,得出最佳的区块链长度。
现在,当我们运行node.py的时候,我们想要同步我们要用的区块链。这包括一开始的时候询问同辈它们的工作信息,保存最佳的区块链。然后,当我们被问到我们的区块链信息的时候我们用本地保存的区块链。node.py需要几个变更。同时你看到blockchain函数跟第一部分相比有多简洁了吧?这就是用类(classes)的好处。
测试时间到了!我们要做以下事情,证明我们的节点确实在同步。
1. 我们挖出大概6个区块作为初始的主节点。
2. 创建一个不同端口使用的文件夹,硬链接主节点文件到这个文件夹里。
3. 对第二个节点运行node.py,看到它的/blockchain.json路径上还没有任何区块。
4. 运行主节点。
5. 关闭并重启第二节点。
6. 看第二节点的chaindata文件夹是否有block的JSON格式文件,看它的/blockchain.json路径现在是不是显示跟主节点一样的区块链数据。
也许我可以分享这一部分的截屏,或者做个视频来讲解整个过程。不过你也可以信任我,或者去Github上看看,自己尝试运行一下。我说过编程不只是看,对吧?
就这么多了,暂时
读到这里,我还没有说到在挖矿之后如何与其它节点同步。这个我们留到第三部分。在那之后还有很多东西。比如创建一个更高级的Header类,而不是只有Block对象。
在同步的时候到底如何正确的选择区块链?这是一个我们没有谈到的重大问题。
另一个重大问题是如何同步数据交易。目前我们的数据都是些简单的语句。而且我们需要能够分发数据,告诉别的节点需要在区块链里包含什么样的信息。
任重道远,但是我们已经上路了。欢迎各种勾搭,这一部分的代码在part-2分支。下次见!
英文原文:https://bigishdata.com/2017/10/27/build-your-own-blockchain-part-2-syncing-chains-from-different-nodes/
译者:桂浩晋