区块链技术是在比特币(Bitcoin)诞生的同时诞生的,应该这么说,在“中本聪”设计比特币系统的时候采用了某种设计思想,这种设计思想被我们称为“区块链(Block Chain)”。区块链不是一门编程语言,而是一种需要使用语言表达出来的技术方案(或者说思想)。区块链有一个特性就是“去中心化”,即没有所谓的中心,整个区块链是分布式存储在互联网中的若干个节点上的,并且每个节点都拥有一个完整的区块链副本,这样做有什么好处呢?其一是如果某个节点发生故障,其余节点可以正常提供服务保证程序的正常执行。其二是这样可以保证数据的高安全性和高可靠性,因为对区块链中的某个区块的数据修改并不是单纯对单一节点中的数据做修改就可以的,只有互联网中相同区块链数据的占大多数的节点的数据才会被承认为有效数据,因此如果想要修改数据,需要对大多数节点的数据同步做修改,否则的话单一节点数据的修改是没有任何意义的。借助这个特性区块链产品可以将副本保存于互联网中若干可信节点,如:第三方安全机构等。
在比特币系统中的货币交易是点对点进行的,即没有任何第三方机构介入,完全是2个用户的直接交易行为(就好像现实中两个人现金交易一样,没有借助任何的第三方)。像:支付宝、微信、网上银行等都属于第三方机构,因为我们的货币是以数据的形式保存在这些第三方机构的数据库中,如果要实现转账功能的话操作的就是这些机构内部的数据库,一方加一方减,就实现了一次转账的操作。在区块链交易系统中,要产生交易,就需要有账户;因为没有第三方机构,所以账户可以自己创建(账户的创建其实是生成一对公钥和私钥)。账户有了,就会想到挖矿和转账。挖矿其实就是拼计算机的算力,挖矿的实质就是在一段代码中不断地用一个随机值去测试,如果测试出随机值经过一系列运算后能满足我们的要求,那么就有机会被认为你成功挖矿(因为可能在同一时刻有互联网上的其他节也算出这个随机值),这时候你们就得看运气了,你们的挖矿信息会被构建成一个区块接入区块链中(区块链从结构上看就是若干个区块构成的链表),后续新的区块会随机在你们这些同时接入区块链中的区块进行选择,一旦选择了某一个区块,那么另一个分支将会被舍弃。转账就是2个账户之间的货币交易了。总之,账户内的货币要么来自挖矿奖励,要么来自其他账户的转账。
区块链的简单结构:
区块链的第一个区块被称为“创世纪块(genesis block)”,每个区块中都存有当前区块的Hash值(这个hash是根据当前整个区块内的所有数据算出来的,为的是后面可以检测区块中信息是否发生篡改),除了创世纪块之外所有的区块都有一个前驱Hash,即指向前一个区块的Hash,这个前驱Hash就是前一个区块的当前Hash。这个Hash我们也称作:Hash指针,前面我们说区块链是由若干区块构成的链表,实际上它并不是一个普通的链表。将区块链和链表对应,区块和链表中的节点对应。我们都知道链表中的节点(除最后一个节点外)都有一个指向下一个节点的指针(这里只考虑单链表),这个指针存的是下一个节点的地址,仅此而已,就算是你对某个节点做了修改,只要地址不变,对于其他节点而言都不会有任何影响。但是区块链不同,每个区块中都存有前一个节点的Hash值(创世纪块除外),我们称为前驱Hash,它是指向前一个区块的Hash指针(这里说的指针是指可以通过这个Hash值有指向性地找到前一个区块,并非说它真的是一个指针类型)。这个Hash指针是由前一个区块中所有的信息共同进行Hash运算得出的结果,因此如果某一个区块的数据发生了篡改,那么它本身Hash出来的结果和其下一个区块保存的前驱Hash肯定是对不上的,这样的话我们就认为这个区块的数据发生了篡改。假设说这个数据修改是合法的,那么就会引发“多米诺骨牌效应”:从这个区块之后的所有的区块都需要重新计算Hash值及修改其前驱Hash。
区块链开发中我们有时候会说到全节点和轻节点,全节点中存的是块头和块身,而轻节点存的只有块头。块头中通常存的是区块的当前Hash、前驱Hash、区块创建的时间戳、目标阈值、随机数、比特币协议版本等信息,块身中存的主要是一些交易信息(即所谓的交易列表)。在用户转账的时候,假设A给B转账,这时候A需要给B提供一个证明说A已经给B转账了。在比特币系统中就采用了名为“默克尔树(Merkle Tree)”的数据结构来存储区块中的交易列表,当需要做交易证明时取相关交易的路径向上求证Hash值得一致性,这个过程称为“默克尔证明(Merkle Proof)”。
Merkle Tree结构如下图:
Merkle Proof过程如下图:
说明:红色线路即Merkle Proof,当A向B转账之后,A需要向B证明完成转账交易,这时候就需要拿这个交易的Hash值和相邻交易的Hash拼接并做一次Hash,然后一直向上验证,直到与根Hash成功验证表示确实完成了交易,反之在中间如果出现验证失败表示交易不成立。
Tip:可能有人会说“如果一个交易的数据发生修改那么其Hash值也会被修改,这时伪造一个交易的Hash值,使得他们Hash之后的结果保持不变”,如下图:
这种做法是无法实现的,因为就目前而言是“无法人为制造Hash碰撞”的。所谓Hash碰撞是指“不同的内容,Hash出相同的结果”。举例:x != y,H(x) == H(y)【说明:x和y是两个不同的值,但是经过Hash之后得到相同的结果,这种情况就是Hash碰撞。但是就目前而言是基本不可能产生这种结果的,因此如果想伪造Hash值来制造Hash碰撞是根本不可能实现的】
说到交易,在区块链中的交易都有一个输入和输出【输入:说明币的来源;输出:说明币的去向,即转给谁,需要记录对方的公钥Hash】。但有一个交易例外,那就是区块创建(也叫“挖矿”)时的第一笔交易——币基交易(Coinbase Transaction),也叫“挖矿奖励”,它只有输出而没有输入,因为币是由系统凭空创造出来的,因此没有所谓的来源。
互联网中的节点都可以通过拼算力(就是看谁先算出符合条件的nonce【随机数】),如果某个节点优先算出那么该节点就拥有“记账权”【区块链就相当于一个分布式账本,通过节点创建并发布区块后在获得互联网中其他节点承认之后可接入区块链,因为区块中是含有若干笔交易的,所以区块接入区块链就像是记账行为】。区块创建(挖矿)有一个尴尬的可能性,那就是互联网中不同的2个节点(也可能有多个)可能同时获得记账权,这时候这两个节点都会各自创建出一个区块,然后接入区块链中。
这并不是最终的情况,因为区块链是一条单链,所以不会有分支选择的情况。因此这两个分支势必有一个会被舍弃,具体舍弃哪一个看后续其他区块选择了这两个区块中的哪一个,那么另一个区块所在分支将被舍弃,这就是区块链的最长链原则。
从图中可以看出,后续区块选择的那一分支构成了当前的最长链,因此另一分支将被舍弃。里面的交易(币基交易或转账交易)将没有意义。
从中间插入的区块同样需要服从最长链原则而被舍弃,因为区块中可能含有某些交易,如下图:
如果区块链承认下面插入的区块,意味着与该区块同级的区块中的交易将被回滚,为了避免这一情况,也必须将新插入区块舍弃。
UTXO(Unspent Transaction Outputs)[未花费的交易输出]:一条区块链中有若干个区块,每个区块中有若干笔交易。每笔交易中都有一个或若干个输出(这些输出即某个账户收到的转账),如果这些交易输出(转账)没有被花过(没有被花完指花了一部分,剩下的币作为交易的输出就是没有花过的币了),那么它们组成的集合就被称为未花费的交易输出。比特币就是采用这么一个交易模型来实现在花费币的时候快速地对币的存在和数量做验证(因为不存在的币或者是数量不够的币是无法被花费的)。有时候会有一些特殊情况:假设某笔交易的交易输入是0.05BTC,但是交易输出是0BTC,这是因为这0.05BTC正好作为交易费给了当前区块的矿工了,这笔交易没有交易输出,即交易输出不可能作为其他交易的交易输入,因此就无需记录到UTXO集合中(上面提过之所以需要建立一个UTXO集合是为了对新建交易的交易输入【即币的来源】做可花费验证,如果币不存在或者币的数量不够,自然是不可发费的;反之就可花费)。
比特币系统中采用的Hash算法是sha256算法,即将串Hash成256位的2进制数,不过为了缩短Hash串的篇幅,就采用16进制串来表示。
挖矿难度:比特币系统中的区块链下的区块的块头中都有一个target字段来表示目标阈值,这个值是挖矿的标准。也有存在于块头作为其中一个名为nonce的字段,只要生成的nonce让整个块头Hash出来的结果<=target,那么就认为这个随机数nonce是合适的,也就说明该节点成功挖矿,可以获得挖矿奖励。有这样一个关系:H(blockHead)<=target,target就是一个目标值,随机生成的nonce将影响H(blockHead)即整个块头的Hash值,只要算出来的结果满足这个<=的关系就表示节点成功挖矿。从关系中可以看出:如果target越大那么挖矿的困难度越小,因为可能有很多nonce都能够使得H(blockHead)满足这个关系,反之如果target越小,那么挖矿的困难度就越高。可以理解为目标阈值target和挖矿难度成反比。
挖矿难度调整:区块链挖矿是需要经常调整挖矿难度的,因为现在的科技越来越发达,设备也愈发先进,如果不调整挖矿难度,那么挖矿会变得越来越简单,时间也会变得越来越短。倘若出块时间变短,使得短时间内同时有多个节点出块,则可能会造成分叉攻击的问题。
Tip:比特币系统中规定每隔2016个区块需要调整一次挖矿难度,使得挖矿的平均时间保持在10分钟左右,以避免由于出块时间变得太短造成问题。
挖矿难度调整实际上是调整目标阈值target,从前面我们得出结论:目标阈值target和挖矿难度成反比。所以只要最近的2016个区块生成的时间间隔大于我们的期望值2016 * 10,则表示挖矿难度有点大了,我们就让目标阈值target变大使得挖矿难度变小。反之如果最近的2016个区块生成的时间间隔小于期望值2016 * 10,则表示出块速度有点快,即挖矿难度有点小了,我们就让目标阈值target变小使得挖矿难度变大。在代码中具体的公式是:target = target * (最近2016个区块生成的时间 / (2016 * 10))
分叉:区块链中的分叉是指出现了多个分支,分叉又分为“硬分叉(hard fork)”和“软分叉(soft fork)”。实际上产生分叉的原因之前也有提到过一个,那就是可能有2个或以上的节点同时获得记账权,这时候这些节点产生的区块会同时接入区块链中,从而造成分叉。这种分叉算是比较好解决,只需要后续的区块随机选择某个分支使得整条链延续下去即可(最长链原则)。但这里我所要提及的分叉并不是这种情况,而是由于比特币协议版本的改变而造成的分叉。假设从某一时刻起比特币协议的版本发生改变,那么相当于在比特币协议中可能增加了一些新的特性(总之与原来不一样了),区块的大小可能由之前规定的1MB(比特币中规定每个区块最大1MB)变成4MB(假设),照理说就算变成了4MB一个的区块也可以以同样的方式接入区块链,说实话也没什么问题。但是有一点需要注意到:并不是所有节点都能同步更新比特币协议版本(即可能有的节点更新了比特币协议版本,从此以后产生的区块都遵循新的比特币协议。但有的节点更新较慢甚至拒绝更新比特币协议,因此就造成了系统中同时存在2种比特币协议,由此生成的区块就会出现2种),如下图:
硬分叉
从图中可看出,新节点(更新了比特币协议版本的节点)和旧节点(即没有更新比特币协议版本的节点)产生的区块分成了2条线路,但因为新节点的区块大小要求最大是4MB(假设),因此新区块构成的分支上除了可以接收新区块,同样也可以接收旧节点产生的旧区块。反之,旧区块构成的分支认为区块大小最大是1MB,那些新区块最大是4MB,对于旧区块构成的分支而言新区块是非法的区块,因此只能选择上面的分支接入,下面的分支只能接入旧区块。只要旧节点没有更新比特币协议版本,那么这两个分叉就会永久存在,这就是“硬分叉(hard fork)”。
软分叉
我们假设对节点中块的内容做了调整,使得产生出来的新区块比原来的旧区块要小。新区块只会认同新区块,也就是说如果产生的是旧区块不管是上面还是下面的分支都可以接入,但要是产生的是新区块,那么它只会从新区块处接入,久而久之新区块构成的分支将成为最长合法链,而旧区块分支将被舍弃,这就是“软分叉(soft fork)”。
总结:硬分叉要求所有的节点都必须更新软件,否则会出现永久性分叉;软分叉要求至少有半数以上算力的节点必须更新软件,才不会出现分叉,即使出现分叉了也是临时性分叉,软分叉不会出现永久性分叉。
问答环节
问:在做转账交易的时候,假设接收方不在线怎么办?会影响转账吗?
答:不会,转账只是相当于把数据写入到文件中进行存储而已,不需要对方在线接收。
问:全节点接收到的交易信息中是否有可能存在交易接收方的地址是从来没有听说过的?
答:是有可能的,因为账户的创建是在用户本地创建的一对公私钥对,这是一个本地行为,只有在进行转账等网络行为的时候才会通知到互联网中的其他节点,让其他节点知道这个账户的存在。
问:如果账户的私钥丢失了怎么办?
答:在没有其他第三方保管私钥的情况下,如果私钥丢失了那么账户就相当于丢失了,是没有办法找回账户的。这个就是去中心化的一个特征,因为像银行、微信、支付宝等第三方机构我们是需要将自己的个人信息以及身份验证信息进行登记的,以便后续的操作对身份的验证。但是比特币系统中采用的是去中心化的做法,用户账户是没有所谓的第三方管理,而完全由创建者本地创建。如果丢失私钥那么账户就直接丢失了。当然有一些虚拟货币交易所等机构可以作为第三方机构帮我们管理私钥,这样如果自己的私钥丢失了还可以通过交易所找回账户。但交易所也不是绝对安全的,因为之前就出过黑客攻击交易所导致虚拟货币大量被盗的情况。
问:如果账户私钥泄露或者账户中出现可疑交易怎么办?
答:应该尽快将账户中的货币转到其他账户。因为在系统中没有类似找回账户、修改密码等功能【没有第三方机构】,所以一旦发生账户私钥的泄露,那这个账户基本就相当于公开了,只能通过建新的账户来防止先前账户的货币被盗。
问:如果转账地址写错了怎么办(即写错转账地址后给那个地址转账过去了)?
答:如果转账转错对象了是没有办法直接找回的。如果知道对方的地址还可以去和对方协商退回转账金额,不过对方接受与否全凭他个人;如果不知道对方地址或者对方地址不存在那么转账货币就算是丢失了。
问:存在区块偷答案(nonce)的情况吗?
问题说明:这里的偷答案是指可能A节点算不出nonce,但是B节点算出这个nonce了,这时候A节点验证nonce合适之后将nonce作为自己算出的答案发布区块。
答:这是不可能实现的。因为B节点算出nonce之后系统会给该区块发起第一笔交易(Coinbase Transaction,即币基交易),交易输出地址就是矿工(B节点)的公钥Hash。如果A节点偷了nonce其区块中的币基交易的地址变成自己的公钥Hash,那么交易信息改变会导致区块内的交易列表构成的Merkle Tree发生改变从而改变了根Hash值,由于根Hash值存在于块头中,使得整个块头的Hash发生改变,nonce会被视为无效。
问:交易的时候有时候会产生交易费(或者说手续费),那么需要事先知道这个交易费应该给谁吗?
答:不需要。因为从交易列表中可以得知 total inputs(所有交易的交易输入) 和 total outputs(所有交易的交易输出),只需要计算 total inputs - total outputs 就可以算出当前区块中所有交易产生的交易费总和,至于这交易费给谁当然是给这个区块的矿工了。