古典区块链的实现

 主页   资讯   文章   代码   电子书 

定义区块结构

区块链是由许多区块按照时间顺序链接而成的,而每个区块中则存储有交易数据。我们可以这样理解,区块就是交易账本当中的每一页,而每页上面记录了所有交易的变更情况。所有交易都会放入交易池,而矿工负责选出适量的交易并打包成区块,最终添加到现有区块链的末尾。鉴于区块链上早先的区块会由于链条的变成而变得在链条上越来越深,因此会变得越来越难以修改。也正因如此,使得区块链具有很高的防篡改性。

区块结构

字段 描述 类型
Version 版本号 Byte
PreviousBlockHash 前一区块的哈希值 UInt256
Time 时间戳 DateTime
Txs 所有交易 Tx^*^
Nonce 随机数 UInt32
Hash 本区块的哈希值 UInt256

*注:该类型会在下一节中具体解释,在此先介绍区块有助于对系统整体的理解。

该表格中的除了本区块的哈希值以外的所有字段,均会被作为本区块的哈希值计算的基础数据,故这些字段的任意一点变化均会导致本区块的哈希值的不同,以下对每个字段进行详细解释:

版本号。用以标记该区块的数据结构版本,从0开始,每次数据结构发生变化时,将该版本提升1。该版本发生变化,即认为分叉(分叉的解释可参见1.3.1软分叉、硬分叉)发生。软件系统可以借助于该字段来对系统进行升级变更。值得注意的是,区块链具有不可修改的特性,故与传统软件系统升级不同的是,区块链的升级通常需要保留对历史数据的处理代码的情况下,添加新的数据结构的处理代码。

前一区块的哈希值。将前一区块的哈希值记录在本区块的头部,使得区块之间形成链状,也是因此使得前一区块的任意修改,会导致后续链接的所有区块的哈希值的变化,使得原区块链断链,这会导致由于有计算量的存在,使得修改较早区块后变短的区块链在共识(共识将在后续章节中详细讲解)中失去竞争优势,最终不被区块链网络所认可而被抛弃,这也是区块链之所以成为链状结果,并能够防篡改的核心所在。

时间戳。创建该区块时的时间,由矿工填入,该字段只可以作为参考值,因为矿工可能因为私利而不诚实的填写该字段,在比特币网络中,并不会对该字段做验证,但通常情况,因为没有程序依赖该字段,错误的填写该字段并无法获利,故绝大数情况,该字段是填写正确,但无论如何绝对不可依赖该字段的值。

所有交易。每个区块均包含至少一笔交易,我们称之为CoinBase交易,也是我们常说的挖矿中挖到的矿。在比特币中,最初的CoinBase交易可获得50个比特币,而后每过210,000个区块后,交易可获得的比特币数量减半,直到所有的21,000,000个比特币被挖完。在我们的程序中简化为始终所有的CoinBase交易均为50个币。

随机数。为了在无任何受信三方存在公共网络环境中达成信任,比特币选择解决拜占庭将军问题(详见1.3.3)的工作量证明(详见1.3.1)。为了证明该名矿工进行了足够的工作量,矿工需要不断的修改该随机数,使得本区块的哈希值的十六进制表示中的前面指定位数的字符为0。对于比特币来说,指定位数会根据过去的2016个区块的耗费时间来调整,使得每个区块的平均工作量为10分钟。在本章的程序中,我们简化为固定难度,即始终是相同的指定位数,并不会进行调整。

本区块的哈希值。将以上所有字段的数据作为哈希运算的基础数据,计算出本区块的哈希值,但值得再次提出的是,只有这个哈希值的十六进制表示中的前面指定位数的字符为0时,才是有效的哈希值,即该区块有效,可以被视为区块链中的一个区块。

区块随机数

我们使用以下程序寻找使得区块有效的随机数。

private static Block FindValidBlock(Block originBlock, int difficulty)  
{  
    var block = originBlock;  

    // 我们的随机数从0开始尝试;
    block.Nonce = 0;  
    while (true)  
    {  
        // 我们每次循环就递增1,并判断是否使得区块有效了;
        block.Nonce++;  

        // 本章的程序简单的判断哈希值的前几位的字节原始数据,
        // 所以对应到比特币的实现来看,这里的难度相当于翻倍了;
        if (((byte[])block.Hash).Take(difficulty).All(_ => _ == 0))  
        {  
            return block;  
        }  
    }
}  

扩展知识

关于随机数,有一个常见的问题,如果随机数循环找完了所有的可能性, 仍旧没有找到使得区块有效的随机数,这时应该怎么办?

该随机数的类型为整形32位,即最大值为4,294,967,295,虽算是一个大数字, 但在难度不断增加的情况下,这个数字仍旧很容易被用尽,不过遇到这样的情况, 只需要将时间戳字段更新为当前的时间,重新从0开始计算即可。


创世区块

创世区块的定义如下代码:

GenesisBlock = FindValidBlock(new Block  
{  
    PreviousBlockHash = null,  
    Time = new DateTime(2017, 6, 30, 9, 0, 0, DateTimeKind.Utc),  
    Version = BlockChainVersion,  
    Txs = new Tx[] { },  
}, Difficulty);  

区块链是一系列的区块链接在一起的,这其中第一个区块,即为创世区块。它有如下几个特点:

  • 从时间上讲,该区块是第一个被创建出来的;
  • 从特点上讲,该区块是区块链中唯一一个没有前一区块的哈希值的区块;
  • 从内容上讲,该区块的内容通常可以由创世者随意指定,例如本章范例代码中,时间使用了固定值, 而非创建时的确定值,也例如创世区块中无任何交易(比特币设计为创世区块中含有一笔CoinBase交易);

在比特币中,创世区块的哈希值是写在代码中的,不过我们此处为了让读者了解到该哈希值的生成过程, 所以设计成运行时生成的模式,有兴趣的读者可以通过调试方法在运行时查看该哈希值的生成过程。