北大肖臻老师《区块链技术与应用》系列课程学习笔记[23]以太坊-智能合约-3
Posted Unicorn_snow
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了北大肖臻老师《区块链技术与应用》系列课程学习笔记[23]以太坊-智能合约-3相关的知识,希望对你有一定的参考价值。
一、思考
1.假设某个全节点要打包一些交易到一个区块里,这些交易里有一些是对智能合约的调用,那么这个全节点应该先执行完智能合约再挖矿,还是先挖矿获得记账权再执行这些智能合约?
在区块链中,如果有一笔转账交易发布上去,需要所有的全节点都执行的,这不是一种浪费也不是出了某种问题,因为所有的全节点要同步状态,大家都要在本地执行这个转账交易,如果一个全节点不执行那就出问题了,他的状态跟别人的状态是不一样的。比特币系统也一样,比特币发布一个交易到区块链上,也需要所有全节点都得执行这个转账交易,以便更新UTXO。
全节点收到一个对合约的调用的时候,要一次性的先把这个调用,可能花掉的最大汽油费从发起这个调用的账户上扣掉。以太坊系统中存在三棵树,即状态树、交易树和收据树,这三棵树都是全节点在本地维护的数据结构,状态树记录了每个账户的状态包括账户余额。汽油费是怎么扣的?全节点收到调用时,从本地维护的数据结构中将账户余额减掉就可以,若余额不够,则这个交易不能执行,一次性要按GasLimit把余额减掉。如果执行完之后如果有剩余,再把其余额再加回去。
智能合约执行过程中任何对状态的修改都是在改本地的数据结构,只有在合约执行完了,而且发布到区块链上之后,本地的修改才会变成外部可见的,才会变成区块链上的共识。以太坊存在很多全节点,每个全节点都在本地做这个事情,执行的智能合约可能不完全一样(因为收到的交易可能执行不完全一样),如果某个全节点发布一个区块,收到这个区块之后,其他节点本地执行的就扔掉了,要将这个新发布区块里的交易再执行一遍,更新本地的三棵树。如果本来已经执行一遍了,但没有挖到矿,发布新区块了还得执行一遍,因为其他节点组装的在本地候选区块中包含的交易跟刚发布的那个交易中区块里包含的交易不一定完全一样,至少给出块奖励的那个地方肯定不一样,所以没有办法,都是得要重新执行一遍。
以太坊挖矿是尝试各种Nonce找到一个符合要求的,计算哈希的时候要用到Block Header的内容:Root,TxHash,ReceiptHash,是那三棵树的根哈希值。所以得先执行完这个区块中的所有交易包括智能合约的交易,这样才能更新这三棵树,这样才能知道这三个根哈希值,这样这个Block Header的内容才能确定然后才能尝试各个Nonce。
假设一个矿工费了半天劲执行这些智能合约,消耗了本地的很多资源,最后我挖矿没挖到,那该矿工能得到什么补偿,能得到汽油费吗?汽油费是给那些获得记账权发布区块的那个矿工没有挖到矿,得不到汽油费也得不到任何补偿,不仅如此,还要把别人发布的区块里的交易在本地执行一遍,以太坊中规定要验证发布区块的正确性,每个全节点要独立验证。别人发布一个交易区块,把那个区块里的所有交易执行完一遍,更新三棵树的内容,算出根哈希值,再跟他发布的那个根哈希值比较一下看是否一致,所有这些都是免费的,没有人给你补偿。所以呢,这种机制下,挖矿慢的矿工就特别吃亏,设置汽油费的目的是对于矿工执行这些智能合约所消耗的这些资源的一种补偿,但是这种补偿只有挖到矿的矿工才能得到,其他的矿工相当于陪跑。
2.会不会有的矿工不给汽油费,就不验证?挖半天没有挖到矿,你发布一个区块,按照协议需要验证一下这个区块的正确性,验证又不给我汽油费,大家直接认为新区块正确,直接接着往后挖就行,会不会出现这种情况?
如果这样做会导致的最直接的后果是危害区块链的安全,为保障区块链的安全,要求所有全节点要独立验证发布的区块的合法性,这样少数有恶意的节点没法篡改区块链上的内容。如果某个矿工想不通,不给钱我就不验证了,这样的风气蔓延开来就会危及区块链的安全,但是这样的事情会不会存在呢?跳过验证这个步骤,以后就没法再挖矿了,因为验证的时候是要把区块的交易再执行一遍,更新本地的三棵树,如果不去验证的话,本地三棵树的内容没有办法更新(本地的这些状态就不对了,算出的根哈希值发布出去之后别人认为是错的),所以根本没办法继续挖矿。为什么要执行才能更新状态?因为发布的区块里没有这三棵树的内容,只是块头里有了根哈希值,这三棵树的账户状态具体是余额等的东西,发布出来是没有的,之前学习过,不能将状态树的整个状态发布到区块链上,那太多了,而且很多是重复的,状态都不改了,所以不会跳过验证这个步骤。以太坊的安全还是有保证的。
在一个矿池中,存在验证阶段的“抄作业”情况,也就是全节点负责统一验证,其他矿工就负责相信全结点的验证情况。就是说,全节点分配给矿工的只是Pullze的内容,Pullze是根据区块链更新得到的,矿工则不需要考虑这部分内容。
3.发布到区块链上的交易是否都是成功执行的?如果智能合约执行过程中出现了错误,要不要也发布到区块链上去?
执行发生错误的交易也要发布到区块链上去,否则汽油费扣不掉,光是在本地的数据结构上把他的账户扣了汽油费,是没用的,拿不到钱,你得把区块发不上去之后形成共识扣掉的汽油费才能成为你账户上的钱。所以发布到区块链上的交易不一定都是成功执行的,而且要告诉大家为什么扣汽油费,而且别人得验证一遍,也要把这个交易执行完一遍,看扣的是不是对的。
前面说过那三棵树,每个交易执行完后形成一个收据,这个是这个收据的内容,Status这个域就是告诉你交易执行的情况如何,如下图1-1所示。
4.智能合约是不是支持多线程?现在多核处理器很普遍,一个计算器有十几核,几十个核,都是正常的,那么智能合约支不支持多核并行处理?
Solidity不支持多线程,他根本没有支持多线程的语句,原因是以太坊是一个交易驱动的状态机,这个状态机必须是完全确定性的,即给定一个智能合约。面对同一组输入,产生的输出或者说转移到的下一个状态必须是完全确定的。为什么要求这个?因为所有全节点都得执行同一组操作到达同一个状态,要进行验证,如果状态不确定的话,三棵树的根哈希值根本对不上,所以必须完全确定才行。
多线程的问题是什么?多个核对内存访问顺序不同的话,执行结果有可能是不确定的,感兴趣的话,可以看看北京大学肖臻老师的论文。以前肖老师研究过的就是在多核环境下如何实现确定性重演,这是一个难度很大的课题。除了多线程之外,其他可能造成执行结果不确定的操作,智能合约也都不支持,最简单的会导致执行结果不确定的操作:产生随机数,这个操作就是不确定性的,而且这个操作必须得是不确定的,如果不同的机器产生的随机数不一样那不叫随机数了。所以以太坊的智能合约没有办法产生真正意义下的随机数,他用的是一些伪随机数,不能是真的随机数,否则的话,又会出现前面的问题,每个全节点执行完一遍得到的结果都不一样。
待更新北京大学肖臻老师《区块链技术与应用》公开课笔记04-BTC-协议
北大肖臻老师《区块链技术与应用》课程链接:点击这里
全系列文章链接:点击这里
主要补充内容及图片来源:《区块链:技术驱动金融》
该系列文章如中有任何侵权内容,或者有链接无法打开、图片加载上传失败等情况,请及时与我个人联系删除或修改。
一、双花攻击(double spending attack)
怎么设计出一个加密货币出来?
假设央行发行数字货币。我们知道,央行发的人民币上有各种防伪标志。同样,央行发的数字货币上也要有央行的私钥签名。而央行公钥是公开的,我们用密码学中的公私钥原理,就可以验证数字货币是不是真的。
大家觉得上面这个方案怎么样?但其实这个例子根本没用到区块链。
而且问题在于:私钥容易泄露。如果央行私钥都发生泄露,那普通百姓的私钥更容易泄露,这样比特币就没法用。
数字货币其实就是个文件。虽然文件上有央行的签名,100元面值就是100元,但数字货币是可以复制的,它可以无限复制下去,这一点和人民币不一样。
A把人民币给B,A的那张钱就没了,没办法再花一次。但如果A花出去一个数字货币,他可以再复制第二次,再花一次,这就叫“双花攻击”。
数字货币主要挑战就是怎么防止双花攻击。
举例
央行发行的每个数字货币上都有一个编号,就和人民币一样,比如017、092等。此外,央行还要维护一个数据库,上面记录着每个编号的数字货币在谁手里。
比如017在A手里,A把017给B,B不仅需要验证这个数字货币有没有央行签名(即验证下币的真假),还要跟央行核实一下017这个数字货币,A之前有没有花出去过。
央行确认后说017之前确实是在A手里,所以A支付017给B是合法的,交易发生后,央行的数据库也需要改一下,将币改为017-B.
此时,如果A想再花一次017,想把它支付给C,C就能跟央行核实查到017已经花出去,已经在B手里了,此时这个交易就不能成立,这样就能防止017花两次。
以上交易方法,正确性没问题,实践中也可操作。但这是中心化的方案,由央行统一控制,而且每次交易都需要央行确认其合法性,这就太慢了。
我们需要一个去中性化的方案,让所有用户共同承担央行的职能,用一个数据结构来检测比特币交易,这个数据结构就是区块链。
区块链有两个核心问题:①数字货币的发行;②如何验证交易的有效性。
如何验证交易的有效性,实际就是怎么防范双花攻击。
举例
(4.1 小型区块链,source: 课程截图)
比特币每个交易都包含输入和输出两部分。输入要说明币的来源,输出要给出收款人的公钥的哈希。
假设,A获得铸币权(CreateCoin),A发行10个比特币。
A把钱平均分给B和C,一人5个比特币,这个交易需要有A的签名,证明是经过A同意的。
同时这个交易还要说明花掉的10个比特币从哪儿来,这里A花掉的币是由铸币交易来的。
A转给B,要说明B公钥哈希是什么,B又把钱转给C和D,也要说明币的来源。
此时,C一共有7个比特币,C又决定把7个币给E。
...
这就是构成了一个小型的区块链。
这里有两种哈希指针,一种把区块串起来构成一个链表,另一种指向前面某个交易来说明币的来源。这样就能证明这个币不是凭空捏造,并且防止双花。
举例
现在B要转给F5个币,签名还是有B的签名。
单纯验证签名币好像合法,但还要知道B从哪儿来。
B的来源回溯一下,这时候就不对了,因为B的币之前已经花出去了,这就能证明不合法,就能检测double spending.
(4.1 小型区块链,source: 课程截图)
转账交易中,A转给B,需要有A的签名,B的地址。
收款的地址是通过公钥推算出来的,B的公钥取哈希通过某些转换得来。
和银行转账一样,A要给B转钱,A需要知道B的地址,可是A怎样知道B的地址呢?
很多比特币地址在网上以二维码形式呈现
B不需要任何信息,但A要证明币的来源信息。B要知道A的公钥,A的公钥代表A的身份,所有节点都要知道A的身份。这个交易要合法得有A的签名。区块链上每个节点都要独立证明。旁观者也得验证一笔交易。
那么怎么才能知道A的公钥?这跟买东西还不一样,买东西需要通过购物网站
这是A和B做交易
A给B转账中,A的公钥是交易中自己给出来的,输入中要说明币的来源,还要说明A的公钥是什么
这不能让自己说,假如如果B的同伙C,伪造A到B转账交易,用自己的公钥在输入中伪造成A的公钥,又用自己的私钥来签名,那别的节点用这个假造的公钥验证这个签名,肯定是对的呀,这就把A账上的钱偷走了
我们知道,每个交易分为输入和输出两部分,在A转账给B的交易中,输入部分说出币的来源和A的公钥,输出要给出收款人的公钥的哈希,就类似于B的地址
这里A与B的交易的币是从铸币交易来的。
铸币交易中(coinbase tx),它的输出里面有A的公钥的哈希,所以A与B转账交易里,A的公钥,要和铸币交易里A的公钥的哈希对得上才行。
对不上,则你说的币的来源是不对的,这个币当初不是给你的。如果有人茂名顶底把自己的公钥说是A的公钥,那假的公钥就和铸币交易里A的指定的公钥的哈希就对不上,验证不通过。
实际上,这些哈希指针是很有作用的。
在比特币系统单中,这些验证过程是通过执行脚本来实现的。
每个交易的输入是一段脚本,公钥也是在输入的脚本里指定的。每个交易的输出也是一段脚本。验证是否合法,就是把当前交易的输入脚本,和前面那个提供币的来源的交易的输出脚本,拼在一起,合成一段程序,看能不能顺利执行,能执行才是合法的。
这叫做比特币脚本(Bitcoin Script)
以上这个图里好像每个区块只有一个交易,实际系统中每个区块可以包含很多交易,这些交易就组织成梅克尔树,每个区块分成块头和块身两部分。
Block header里包含区块的宏观信息,比如用的比特币哪个版本的协议(version)、区块链当中指向前一个区块的指针(hash of previous block header)、整棵梅克尔树的跟哈希值(Merkle root hash)、挖矿的难度目标阈值(target)、随机数(nonce)
上节课提到,挖矿求的puzzle,H(block header)≤target,整个块头的哈希要小于目标阈值,block header里存的就是目标阈值的一个编码nBits
前一个区块的哈希,只算得是区块的块头,block body是不管的
Block body
有交易列表(transaction list)
实际当中,节点分为全节点(full node)、轻节点(light node)。
全节点保护所有信息,验证所有交易,也叫做fully validating node。
轻节点只保存block header的信息,一般来说,无法验证独立验证交易的合法性,比如是不是double spending,轻节点不知道,因为它没存以前的交易信息,它查不出来。系统中大多数节点是轻节点。
这节课主要针对全节课来讲。
每个节点每个账户都可以发布交易,这些交易是广播给所有节点的,有些交易可能合法,有些非法,那么谁来决定哪些交易能写进下一个交易中呢,按照什么顺序呢?
区块链是一个去中心化的账本,这个账本内容要有同意的说法,
这叫做账本的内容要取得分布式的共识(distributed consensus)
简单来例子:分布式的哈希表(distributed hash table)
系统有很多台机器,共同维护一个全局的哈希表,这里需要取得共识的内容是什么?
需要取得的是哈希表中包含哪些键值对(key-value pair)
比如 'xiao'→12345,xiao这个key对应的是12345
另一台机器要能把它读出来
比特币中的共识协议(Consensus in Bitcoin)
假设系统中大多数节点都是好的,有恶意的是小部分,这种情况下怎样设计一个共识协议呢?
直接投票行不行?
更大的问题:
任何基于投票的方案,首先要确定谁有投票权(membership)
如果这个区块链的membership是有严格定义的,不是谁都能加入,比如联盟链(hyperledger)只有大公司才能加入。
比特币系统中,创建一个账户是很容易的,本地产生一个公私钥对儿就是一个账户,不参与交易外界是不知道账户存在的。
当有恶意的节点用超级计算机不停的产生账户,当账户超过总数一半,就有控制权了,就能操纵投票结果,这叫做女巫攻击(sybil attack)
这样投票不行,或者说简单的直接的投票是不行的。
比特币系统中用一个巧妙机制解决这个问题:
也是投票,但不按照账户数目,而是按照计算力来投票。
每个节点都可以在本地组装一个候选区块,把它认为合法的交易放在区块里,然后就开始尝试各种nonce值
我们之前提到的 H(block header)≤target,这个block header里有一个域,是一个随机数nonce。
组装好区块后就开始试各种随机数,(这是个4 bytes),看哪一个能满足不等式要求,求出来的哈希落在指定范围内,如果某个节点找到了符合要求的nonce,即获得了记账权。
记账权即有权力发布下一个区块。
其他节点收到区块后也要验证区块合法性。
比如:
先验证下 block header 的内容填的对不对,里面有一个nBits域(目标阈值的一个编码),检查一下这个 nBits域设置是否符合比特币协议规定的难度要求;
查一下nonce,选出的Nonce是不是符合上面不等式,即是否真的有权利发布区块,是否真的获得了记账权
把block header里面的那几项都查一遍,假设都符合要求,然后看一下block body里面的交易列表,验证一下每个交易都是合法的,1合法的签名,2以前没有被花过,任何一个条件不符合要求,区块不能接收。
假设一个区块全都查过是符合要求,那是不是就可以接受它呢?
hash of prev block header
验证一个分叉交易是否合法,不会查到另一个分叉上。
这个分叉上交易虽然合法,但不在最长合法链上。
分叉攻击,
回滚已经发生过的交易
以上是关于北大肖臻老师《区块链技术与应用》系列课程学习笔记[23]以太坊-智能合约-3的主要内容,如果未能解决你的问题,请参考以下文章
待更新北京大学肖臻老师《区块链技术与应用》公开课笔记04-BTC-协议
北京大学肖臻老师《区块链技术与应用》公开课笔记03-BTC-数据结构