区块链基础part3-北大肖臻老师&Chainlink预言机 China学习笔记
1.BTC挖矿难度如何调整
之前有提到过,在比特币系统中,区块链的出块时间保持在平均10min左右。毫无疑问的是,伴随着参与挖矿的人增多,系统总算力不断增强,挖矿的难度绝对不能一成不变。实际上,在比特币系统开发过程中,中本聪便考虑到了这个问题,并设计了一个相应的难度调整算法。这一篇,便了解一下比特币系统中的挖矿难度调整算法。
为什么要调整挖矿难度
之前已经提过,挖矿本质上就是不断调整block header中的nonce值,使整个block header的哈希值小于等于给定的目标阈值。即:H(block header)<=target.(target便是目标阈值,target越小,目标难度就越大)对于挖矿难度的调整,可以视为调整目标空间在整个输出空间中所占比例大小。
之前有提及,比特币系统采用的哈希算法为SHA-256,所以整个输出空间大小为2^256,调整目标空间所占比例,简单的说需要目标值前需要多少个0。
当然,挖矿难度和目标阈值成反比,如下图所示,其中difficulty_1_target为是挖矿难度为1时候的target,即最小挖矿难度
如果不调整挖矿难度会怎么样?
系统总算力越来越强,若挖矿难度保持不变,则出块时间会越来越短。出块时间越来越短是好事吗?
出块时间缩短,那么交易可以很快便被写入区块链,并且提高了系统响应时间,增加了区块链系统效率。但是,出块时间并不是越短越好。出块时间太短,也会造成一定的问题。首先,区块在网络上传播具有时延,假如出块时间为1秒,但网络传播需要10秒,则会使得系统中节点经常性处于不一致的状态,增加了系统不稳定性,且系统经常性位于分叉状态(不仅二分叉,乃至多分叉)。分叉过多,则不利于系统达成共识,且会造成算力分散,使得黑客攻击成本大大降低(不再需要整个系统51%的算力)。10min的出块间隔是最优吗?
当然不是,但可以确定的是,系统出块时间需要维持在一个定值附近。后续文章中会介绍以太坊,以太坊中平均出块时间仅为15秒左右,但同样在以太坊中也有相应难度调整算法维持其平均出块时间(后续会写文介绍),当然15s的时间明显会产生经常性的分叉,所以以太坊设计了新的共识协议Ghost(后续文章中会介绍)。
当然,对于一个交易系统来说,10min这样一个交易时间是比较长的。但对于跨国交易来说,这个时间反而大大缩短了交易时间,减少了相应成本。
BTC系统如何调整挖矿难度
在BTC协议中规定,每隔2016个区块需要调整一次难度,根据10min产生一个新区块可以得到,大概需要14天的时间。具体调整公式如下:
可见,如果实际实际比较长,target会比较大,相应的挖矿难度会降低;如果实际实际比较短,target会比较小,相应的挖矿难度会增大。
当然,上调和下调都是有4倍的限制。例如:实际最近2016个区块出块时间超过8个星期(正常2个星期),计算也只按照8个星期计算;实际最近2016个区块出块时间小于0.5个个星期(正常2个星期),计算也只按照0.5个星期计算.这样是为了防止网络中出现黑天鹅事件。
如何让所有矿工都愿意调整这个挖矿难度呢?
这一调整算法在代码中已经写入,如果有恶意节点故意不调,其所产生的区块不会被大多数诚实的节点承认。
在block header中有一个nbits的域,它是对target的编码存储(target为256位,nbits为32位,也就是说block header并未直接存储target),其他节点在进行合法性验证时候会验证nbits域是否合法,不合法则对该区块不予以承认。
思考:比特币出现之后也曾经涌现出一大堆数字货币,为什么偏偏比特币存活了下来?
很大程度上是由于比特币设计本身注重鲁棒性而非高效,使得系统的健壮性极高。可见实际工业应用,不应该一味追逐新技术的应用,实际上来说,哪一种能更好解决我们的实际问题就用哪种。像区块链技术火起来之后的炒币浪潮,其中又有多少是披着区块链皮的项目呢?更别说火极一时的区块链养狗,养猫之类的项目,实际上根本不需要用上区块链技术!
其他
注:以下图片均来自肖老师视频中截图,若有侵权请联系我删除
比特币系统总算力变化情况图
需要注意的是,之前一段并非直线,而是之后增长太猛导致之前增长趋势看上去太低。挖矿难度变化图
可以看到,和系统算力变化情况基本同步(符合难度调整预期目标)
思考:挖矿难度变低是好事吗?
对于矿工来说,挖矿难度变低,挖矿变得更容易,这也说明大多数人对该币种不再看好,这个币种的价值也会大跳水,这对矿工来说可是一个坏消息。
- 2010-2018每天出块时间图
可见基本维持在10min左右上下波动,达到预期设计目标
2.BTC脚本
交易实例:
比特币系统中使用的脚本语言非常简单,唯一可以访问的内存空间只有栈,所以也被称为“基于栈的语言”
- 交易的宏观信息:
- Vin的内容:
如果存在 一个交易有多个输入,那么每个输入都要说明币的来源并给出签名(BTC中一个交易可能需要多个签名)
- Vout的内容:
输入输出脚本的执行
如图所示,为脚本执行流程。在早期,直接将两个脚本按照如图顺序(input script在前,output script在后) 拼接后执行,后来考虑到安全性问题,两个脚本改为分别执行:先执行input script,若无出错,再执行output script。
如果脚本可以顺利执行,最终栈顶结果为true,则验证通过,交易合法;如果执行过程中出现任何错误,则交易非法。
如果一个交易有多个输入脚本,则每个输入脚本都要和对应的输出脚本匹配执行,全部验证通过才能说明该交易合法。
输入输出脚本的几种形式
- P2PK形式(Pay to public key)
特点:输出脚本直接给出收款人公钥。(CHECKSIG为检查签名操作)
执行过程(将两个脚本拼接起来):
注:实际执行已经不再拼接两个脚本
实例:
- P2PKH形式(Pay to public key hash)——最常用
特点:输出脚本不直接给出收款人公钥,而是公钥的哈希。
执行过程(将两个脚本拼接起来):
注:实际执行已经不再拼接两个脚本
说明:
1.图中第5步,两个公钥哈希是不同的。上面一个是输出脚本提供的收款人的哈希,下面一个是要花钱时候输入脚本要给出的公钥通过HASH160操作得到的。
2..图中第6步,该操作的目的是为了防止冒名顶替(公钥)。假设比较正确,则两个元素消失(不往栈中压入TRUE或FALSE)。
实例:
P2SH形式(Pay to script hash)
特点:输出脚本给出的不是收款人公钥的哈希,而是收款人提供的一个脚本的哈希。该脚本称为redeemScript,即赎回脚本。等未来花钱的时候,输入脚本要给出redeemScript的具体内容以及可以使之正确运行需要的签名。
验证过程:
1.验证序列化的redeemScript是否与output script中哈希值匹配。
2.反序列化并执行redeemScript,验证iutput script中给出签名是否正确。(将赎回脚本内容当作操作指令执行一遍)
redeemScript的形式:
1.P2PK形式
2.P2PKH形式
3.多重签名形式
实例:
- 实例1:用P2SH实现P2PK
运行过程:
第一阶段执行拼接后的输入和输出脚本。
第二阶段执行反序列化后的赎回脚本(反序列化操作并未展现,因为其是每个节点需要自己执行的)
为什么要弄这么复杂?用之前介绍的P2PK不就可以了吗?为什么要将这部分功能嵌入到赎回脚本?
毫无疑问,针对这个例子,这样做确实复杂了。实际上P2SH在BTC系统中起初并没有,后来通过软分叉(后续会有一篇文章专门介绍硬分叉和软分叉)加入了这个功能。实际上,该功能的常见应用场景是对多重签名的支持。
在BTC系统中,一个输出可能需要多个签名才能取出钱来。例如,对于公司账户,可能会要求5个合伙人中任意3个的签名才能取走钱,这样便为私钥泄露和丢失提供了一定程度的保护。
多重签名
下为最早的多重签名实现方法:
该方法通过CHECKMULTISIG来实现,其中输入脚本提供N个签名,输出脚本给出N个公钥和阈值M,表示N个人至少有M个签名即可实现转账(N>=M)。输入脚本只需要提供N个公钥中M个合法签名即可。【给出的M个签名顺序要和N个公钥中相对顺序一致】
输出脚本最前面有一个红色的X,是因为比特币中CHECKMULTISIG的实现存在一个bug,执行时会从堆栈上多弹出一个元素。这个bug现在已经无法修改(去中心化系统中软件升级代价极大,需要硬分叉修改)。所以,实际中采用的方案是往栈中多压入一个无用元素。
执行实例:
如图为一个N=3,M=2的多重签名脚本执行过程。其中前三行为输入脚本内容,后续为输出脚本内容。
早期的实际应用中,多重签名就是这样写的。但是,在应用中体现出了一些问题。例如,在网上购物时候,某个电商使用多重签名,要求5个合伙人中任意3个人才能将钱取出。这就要求用户在生成,转账交易时候,要给出五个合伙人的转账公钥以及N个M的值。而对于用户来说,需要购物网站公布出来才能知道这些信息。不同电商对于数量要求不一致,会为用户转账交易带来不便之处(因为这些复杂性全暴露给了用户)。
为了解决这一问题,就需要用到P2SH
如图为使用P2SH实现多重签名
本质上是将复杂度从输出脚本转移到输入脚本,可见此时输出脚本只有三行,原本复杂度被转入到赎回脚本redeemScript中。输出脚本只需要给出赎回脚本的哈希值即可。该赎回脚本在输入脚本提供,即收款人提供。
这样做,类似之前提到的电商,收款人只需要公布赎回脚本哈希值即可,用户只要在输出脚本中包含该哈希值,用户无需知道收款人的相关规则(对用户更加友好)。
具体运行过程:
第一阶段验证(输入输出脚本):
第二阶段验证(赎回脚本):
实例:
现在的多重签名,大多都采用P2SH的形式
一个特殊的脚本
以RETURN开始,后面可以跟任何内容。
RETURN操作,无条件返回错误,所以该脚本永远不可能通过验证。执行到RETURN,后续操作不会再执行。
该方法是销毁比特币的一种方法。
Q:为什么要销毁比特币???现在比特币价值极高,销毁是不是很可惜?
1.部分小币种(AltCoin)要求销毁部分比特币才能得到该种小币种。例如,销毁一个BTC可以得到1000个小币。即,使用这种方法证明付出了一定代价,才能得到小币种。
2.往区块链中写入内容。我们经常说,区块链是不可篡改的账本,有人便利用该特性往其中添加想要永久保存的内容。例如:股票预测情况的哈希、知识产权保护——知识产权的哈希值(防止***)。
有没有觉得第二个应用场景有些熟悉?实际上,之前谈到BTC发行的唯一方法,便是通过铸币交易凭空产生(数据结构篇中)。在铸币交易中,有一个CoinBase域,其中便可以写入任何内容。那么为什么不使用这种方法呢,而且这种方法不需要销毁BTC,可以直接写入。
因为这种方法只有获得记账权的节点才可以写入内容。而上面的方法,可以保证任何一个BTC系统中节点乃至于单纯的用户,都可以向区块链上写入想写入的内容。【发布交易不需要有记账权,发布区块需要有记账权】
任何用户都可以使用这种方法,通过销毁很小一部分比特币,换取向区块链中写入数据的机会。实际上,很多交易并未销毁BTC,而是支付了交易费。
例如下图为一个铸币交易,其中包含两个交易,第二个交易便是仅仅想要往其中写入内容。
下图为一个普通的转账交易,其就是仅仅为了向区块链写入内容。该交易并未销毁BTC,只是将输入的费用作为交易费给了挖到矿的矿工。
这种交易永远不会兑现,所以矿工不会将其保存在UTXO中,对全节点比较友好。
实际中的脚本,都需要加上OP前缀,如:CHECKSIG应该为OP_CHECKSIG,这里仅仅为了学习友好,就删去了该前缀
总结
BTC系统中使用的脚本语言非常简单,简单到没有一个专门的名称,我们就称其为”比特币脚本语言“。而在后文的以太坊的智能合约中,则比此复杂得多。实际上,该脚本语言甚至连一般语言中的循环都不支持,但设计简单却也有其用意。
如果不支持循环,也就永远不会出现死循环,也就不用担心停机问题。而在以太坊中,由于其语言图灵完备,所以要依赖于汽油费机制来防止其陷入死循环。此外,该脚本语言虽然在某些方面功能很有限,但另外一些方面功能却很强大(密码学相关功能很强大,可能中本聪本人擅长于密码学???)
例如,前文提到的CHECKMULTISIG用一条语句便实现了检查多重签名的功能。这一点与很多通用编程语言相比,是很强大的。
3.BTC分叉
本节介绍比特币系统中的分叉(fork)
分叉指的是,原来的系统中为一条链,但分成了两条链。分叉形成的原因可能有多种,例如:挖矿时两个节点差不多同时挖出矿,都会发布区块(对比特币系统当前状态产生分歧导致的分叉——state fork);分叉攻击,同样也会导致分叉(forking attack,人为故意造成);比特币协议改变,在分布式系统中不能保证所有节点同时升级软件,假设存在少数节点未升级,导致出现分叉(protocal fork);
根据对比特币协议修改的不同,可以将分叉分为硬分叉和软分叉。(和头发分叉可没有关系哦,哭,摸摸秃掉的头)
很多人都听说过硬分叉和软分叉,但对其实际含义并不了解,本篇便专门介绍比特币系统中的分叉。
硬分叉(hard fork)
什么情况会出现硬分叉?
对比特币协议增加新协议,扩展新功能,未升级软件的旧节点会不认可这些修改,会认为这些特性是非法的。这也就是对比特币协议内容产生分歧,从而导致分叉。硬分叉的一个典型例子,就是对比特币区块大小的修改(之前有提到过,BTC区块大小限制1MB,但是否合适存在争议)。
在BTC系统中,区块大小最大为1MB,可以包含的交易最大数量为4000笔左右。而一个区块产生大概需要10min左右,也就是说,整个比特币系统,平均每10分钟最多只能处理4000笔交易(平均每秒7笔交易),相比目前银行等金融机构每秒数十万数百万的交易量来说,根本不在一个数量级上,严重影响吞吐率和交易处理(即上链)时间(因为交易太多,无法写入只能等待下一个区块)。
所以,有人便认为可以增大区块大小,使得一个区块中可以包含的交易数量增多,在此,我们假设将区块大小从1MB增大至4MB。
假设系统中大多数节点更新了软件,少数节点仍然遵从1MB限制的协议(注意,这里大多数和少数是按照算力来区分的,和账户数量无关)。即:新节点认为区块大小最大4MB,旧节点认为区块大小最大1MB,且新节点占据大多数。**
假设1为当前区块链,此时软件更新,有一个新节点挖出了一个区块如2。但对于旧节点来说,该区块为一个非法区块,旧节点不会对其认可,从而,旧节点仍然从其前一个区块开始挖矿,如3.
需要注意的是,旧节点挖出的区块,新节点是认可的(并未超过4MB限制),所以对旧节点来说,3中下面的链才是合法链,而对新节点来说,这两条链都是合法的链。因为新节点算力强,所以出现4中情况可能性大。对于新节点来说,上面的为最长合法链,新节点便都会沿着上面的链继续挖;对于旧节点来说,上面的链无论多么长,都是一条非法链,不会认可该链,所以旧节点就会沿着下面的链继续挖矿。
此时,就出现了新节点永远沿着上面的链挖矿,旧节点永远沿着下面的链挖矿,由于新节点算力足够强,所以形成两条永远都在延伸且平行的链。当然,上面的链,也有可能会挖出大小在1MB内的小区块,但对旧节点来说,该链上存在非法区块,不会认可该链。可见,这种分叉是持久性的。
只要这部分旧节点永远不更新软件,下面的链便永远不会消失。
1.BTC社区中有些人很保守,不愿意加大区块大小 2.区块大小并非越大越好,在网络篇中提到,比特币网络传输为”尽力而为”,区块加大会造成传输变慢等问题。 3.单纯增加区块大小,对交易数量的增加远不能达到数量级的提升。
出现hard fork后,便变成了两条平行的链,也就造成了社区分裂。社区中有一部分人,会认为下面的链才是”正统“(根正苗红),各个链上的货币独立。
实际上,这个事情真正出现过。后续会介绍以太坊,以太坊历史上的一件大事就是硬分叉事件。以太坊称为ETH,但目前看到的ETH已经不是最初的ETH了,以太坊在历史上发生过硬分叉,另一个链称为ETC(和过高速公路那个ETC可半毛钱关系都没有呀)。实际上,ETC才是以太坊设计原本的协议,而ETH是黑客攻击ETH上一个智能合约THE DAO后进行回滚的协议链(将黑客攻击偷取的以太币采用硬分叉方式回滚回到另一智能合约,然后退还给真正拥有者)。
但是这次硬分叉的后果,由于有人不愿意这么做,造成了以太坊社区的分裂。实际上,虽然ETC不如ETH又名,但实际它也是目前一种主流货币。
分叉之初,由于两个链分叉造成了互相影响,产生了很多麻烦。比如:在ETH链上有一笔转账B->C,有人便在ETC链上回放,将ETC链上的货币页转给了C(C收到两笔钱)。后来,对两条链各添加了一个chainID,将两个链区分开,才使得这两条链真正分开。
软分叉(soft fork)
如果对BTC协议添加限制,使得原本合法交易在新交易中不合法,便会形成软分叉。
同样,有人想将区块大小调大,也就会有人思考调小的好处。在这里,我们假设将区块大小从1MB减小至0.5MB(实际中,1MB已经足够小,不会调小了).
需要注意的是,区块链中区块大小调整并非简单修改一个参数,调改大小便很有可能会引发分叉,由于参数修改方式不同,有可能会是硬分叉,也有可能是软分叉。。
假设系统中大多数节点更新了软件,少数节点仍然遵从1MB限制的协议(注意,这里大多数和少数是按照算力来区分的,和账户数量无关)。即:**新节点认为区块大小最大0.5MB,旧节点认为区块大小最大1MB,且新节点占据大多数。
假设1为当前区块链,此时软件更新,有一个新节点挖出了一个区块如2。但对于旧节点来说,该区块符合1MB大小限制,旧节点对其认可,从而旧节点会沿着该新的小区块开始挖矿,如3.
但是新节点会认为该旧节点挖出区块超过0.5MB限制,为一个非法区块,不会认可该区块,会从其前一个小区块开始挖矿。如4所示。
而旧节点认可新区块,最终会造成5中的效果(绿色大节点为旧节点),旧节点挖出的区块一直被抛弃,无法得到出块奖励(不在最长合法链上)。这就倒逼旧节点升级软件,最终会实现区块链上的所有矿工共同认可新协议,实现软件协议的升级。
需要注意的是,旧节点如果不升级软件,挖出的区块可能就白挖了(大于0.5MB),但对于系统来说,不会存在永久性分叉。
系统中可能出现软分叉的情况及其实例
- 给某些目前协议中未规定的域赋予新的含义或规则。
最经典的就是,铸币交易中CoinBase域。在CoinBase域中写入任何内容都可以,没有任何规定。之前,在介绍挖矿时,提到挖矿本质是调整block header中的nonce,但其本身只有4个字节,搜索空间太小。所以实际使用中,将CoinBase域前8个自己作为另一个extra nonce,此时搜索空间从原本2^32 增长到2^96,对于目前挖矿难度来说已经足够。
但CoinBase中并不是只有8个字节,还剩下很多空间。有人便提出将其作为UTXO(当前还没花掉的交易结合,在数据结构篇中有详细介绍,还记得吗?)集合的根哈希值。目前UTXO是全节点自己在本地为了方便查询自行维护的,但UTXO内容并未写入区块链(还记得Merkle proof吗?Merkle proof用于验证某个交易是否在区块中,Merkle proof的交易信息是写入区块链的。)
由于UTXO存在本地,如果查询某账户余额,轻节点便需要询问全节点,全节点根据UTXO中信息可以计算得到账户余额,但如何确保全节点给的数据可信?由于直接修改block header会造成硬分叉,有人便提出了以上的方案(该域刚好无人用)。
可以看到,旧节点认可新节点的区块,但新节点对于旧节点CoinBase域检查时候,发行并没有这个UTXO的根哈希值,不会认可其发布的区块,所以这是软分叉。 - P2SH:Pay to Script Hash
还记得上一篇比特币脚本中该功能吗?上一篇中提到过,最初比特币版本中没有该功能,后来通过软分叉方法加入了进去。
总结
- soft fork
特点:只要系统中拥有半数以上算力节点更新软件,系统就不会产生永久性分叉 - hard fork
特点:必须系统中所有节点更新软件,系统才不会产生永久性分叉