@IT·互联网

比特币探究之一:区块链

2018-07-07  本文已影响213人  魏兆华

再闲不能睡懒觉,再忙不能断学习。

话说胖兔我其实已经停写技术博客十来年,当然个中原因很多,不完全是懒,况且本来也没什么水平,但仍然觉得,写技术博客确实是非常好的一种学习方式。有所心得,写出来,既是巩固,也能交流,利人利己,不亦快哉。

闲话少说。2017年极其火爆的比特币暴涨,连带其背后的区块链技术,吸引了无数的眼球,自然也少不了热衷于投资的中国大妈们。原本胖兔一直是不懂区块链的,但被几位大妈询问并鄙视以后,便也不那么淡定了。于是下定决心,抽空要好好研究一下,这便有了本系列文章的由来,顺便作为重开技术博客的开门砖。

当前区块链领域的几根台柱子,第一是比特币,毫无疑问的开山鼻祖,代表了区块链1.0时代,创始人是至今也不知道何方神圣的中本聪。第二是以太坊,引入了智能合约,开启了区块链2.0时代,创始人Vitalik Buterin,国人喜欢称为V神,标准的90后大男孩,19岁就写出了以太坊白皮书。第三是EOS,号称将开启区块链3.0时代,其主网6月份刚刚上线,目前各方仍然褒贬不一,但不可否认的是,EOS已经并将继续推进区块链技术的深入发展。

既然要学习,那么从易到难,先从比特币开始。胖兔向来笃信Linus Torvalds的名言:Talk is cheap, show me the code. 翻成中文就是:别扯那没用的,代码拿我瞧瞧。这跟有图有真相差不多是一个道理。截止本文写作时,比特币源码版本是0.16.1,基于C++ 11。幸好胖兔C++功底还在,OK,那就从这开始吧。


什么是区块链?其实就是把一堆区块串在一起,形成的一个链条。就像下面的图那样:

图1-1 区块链示意图

每个区块包括一个区块头,以及一堆交易数据。区块链牛逼的地方就在于,第一,所有串在一起的区块都是相关的,换句话说不是随便整一个区块就能连到链上去,得经过一个复杂的计算和验证,也就是传说中的挖矿;第二,整个区块链网络里每一个节点上的链条,都是完全一模一样的,不管你是在中国还是在南美,就算隔着整个地球,大家看到的链条也完全相同,这就是传说中的共识,你想偷偷摸摸改一个?对不起,所有其他节点都会告诉你,兄弟,别想玩花样,老老实实一起嗨吧!

我们来看比特币源码,里面的相关数据结构是怎么定义的呢?先看胖兔简要绘制的UML类图:

图1-2 区块链基本类

CBlockHeader就是区块头,CBlock就是区块,这两个类定义在primitives/block.h里。

CBlockHeader 区块头

区块头主要包含的信息,首先是版本号nVersion,这是为了方便软件的升级,只要区块的数据结构改变了,那么这个版本号就要跟着变化,要不然就会出现不兼容。比特币创建之初,这个版本号一直是1,直到2012年有人提出比特币改进方案BIP-34,要求在区块中添加块高度信息(也就是区块总数),最后方案讨论通过,将这个高度信息添加在Coinbase解锁脚本当中(这个脚本在区块头后面的交易数据里,将来我们再解释),区块头结构实际上没有变化,但版本号修改为2。这个改动发生在2013年3月24日下午15点49分,从227,835块之后,所有块的nVersion全部变成2。截止本文发表时,版本号仍然为2。

hashPrevBlock,指的是前一个区块头的哈希。什么叫哈希?就是一段数据的加密结果,它的一个牛逼之处在于:只要原始数据稍有改动,哪怕只是一个Bit,整个加密结果也会发生翻天覆地的变化。每一个区块都指向它前一个区块的哈希,就形成了一个连续的链条,这其实就是“区块链”的由来。为什么说区块链是不可篡改的?因为每个区块一旦生成,它的哈希值就确定了,假如想改动哪怕是一点点,那对不起,后面的链条上所有的区块都要重新生成,而这是要花费巨大的代价的,将来我们再详细解释。注意它的类型是uint256,因为它是使用SHA256加密算法生成的。

hashMerkleRoot,是指交易树的树根。每个区块里都包含许多交易信息,这些交易信息是以一种Merkle(默克尔)树的形式存储,这种形式非常高效,而且很有利于节省硬盘空间。在区块头里只需要存储这颗树的树根,便可以有效检验所有交易信息的真伪,防止有人非法篡改。假如有人修改了某条交易信息,那么相应的哈希值就会改变,这个改变会依次上传到Merkle树的树根,树根一改变,hashPrevBlock也要跟着变,最后会导致整个区块,以及它后面所有区块的全部改变。

nTime是时间戳,表示的是这个区块生成的时间。它的格式是32位无符号整数,代表的是1970年以来的所有秒数。

nBits代表的是生成难度。nNonce是个随机数,这两个都是用于挖矿的,留待将来再专门讨论。

CBlock 区块

CBlock 继承于 CBlockHeader,它除了区块头的数据以外,另外增加了两个:

vtx代表本区块中所有交易的集合。它是个vector,每个元素的类型是CTransactionRef,这是一个指向CTransaction交易信息的指针。关于交易信息,后面会专文详细介绍。

fChecked,代表本区块是否已经通过合法性检查,包括工作量检查和交易信息检查。每个区块在链入区块链之前,都必须经过检查,检查通过了才能链入。

CBlockIndex 区块索引

CBlockIndex和下面的CChain两个类,声明于chain.h里。

CBlockIndex是区块索引。实际上,区块链在内存中构建的时候,其实并不一定需要知道完整的区块内容,只要知道区块头就可以了。而CBlockIndex这个区块索引里已经包含了区块头的必要内容,足够构建起完整的区块链条。另一方面,区块链节点有全节点和轻节点之分,全节点完整地存储了区块链的所有数据,包括所有的CBlockIndex和所有的CBlock,而轻节点只是保存了CBlockIndex(包含了各个区块头信息),以及与本节点相关的交易信息,这样可以节省大量的硬盘空间(一个区块可能几百K到1M,而一个区块头才80字节)。如果轻节点需要查询其他交易信息,可以向其他全节点提出查询。

CBlockIndex包含的数据有:

phashBlock是这个区块的哈希值。

pprev是前一个CBlockIndex指针,pskip是更远处某个CBlockIndex指针。

nHeight是当前区块的高度,也就是整个区块链上当前的区块总数。对创始区块来说,它的值就是0。

nFile是当前区块在磁盘上的存储编号。对应的是blk?????.dat这样的文件名。

nDataPos是本区块在存储文件中的偏移量。一个文件可以存储多个区块,因此每个区块需要知道自己在文件中的具体位置。nUndoPos是本区块在rev?????.dat中的偏移量,这个文件中存储的是每个区块的撤销数据。

nChainWork指的是所有区块的工作量总和,包括本区块在内。这个数据只在内存中生成,不会写入磁盘。

nTx是本区块中存储的所有交易数量。nChainTx是所有区块的交易数量总和,这个总和只有在本区块以及所有父区块交易都数据都 可用时才会生成,而且也是不存入磁盘的。nChainTx目前仍是32位的数据,不久的将来可能需要改为64位,但预计起码也在2030年之后了。

nStatus是本区块的校验状态。留待后面再详细解释。

接下来,nVersion、hashMerkleRoot、nTime、nBits、nNonce是与区块头对应的数据。当CBlockIndex被生成的时候,hashPrevBlock已经被检查过了,通过pprev->phashBlock就可以获得,因此这里不需要再额外存储了。

nSequenceId是区块序号,这个只用于内存中,用于分辨当前接收的是哪个区块的数据。

nTimeMax是截止目前为止所有区块的最大时间戳,只用于内存中。

CChain 区块链

CChain就是内存中存储的整个区块链。它其实只有一个数据成员vChain。

vChain的类型是vector<CBlockIndex*>,是所有的区块索引构成的一个向量,可以很便利地访问每个区块索引。

OK,目前为止,最基础的区块链数据结构我们已经基本清楚了。那么区块里存储的主要是什么?当然是交易信息了!可以说它是整个比特币的核心,一切都是围绕交易展开的。下一篇文章我们再详细探究。


本文原创作者胖兔(魏兆华),首次发表于简书,欢迎转载,请注明出处。

上一篇 下一篇

猜你喜欢

热点阅读