国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

  • 北京大學(xué)肖臻老師《區(qū)塊鏈技術(shù)與應(yīng)用》公開課筆記:以太坊原理(三):智能合約

    這篇具有很好參考價值的文章主要介紹了北京大學(xué)肖臻老師《區(qū)塊鏈技術(shù)與應(yīng)用》公開課筆記:以太坊原理(三):智能合約。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

    9、ETH-智能合約

    智能合約是以太坊的精髓,也是以太坊和比特幣一個最大的區(qū)別

    1)、什么是智能合約

    智能合約的本質(zhì)是運行在區(qū)塊鏈上的一段代碼,代碼的邏輯定義了智能合約的內(nèi)容

    智能合約的賬戶保存了合約當(dāng)前的運行狀態(tài)

    • balance:當(dāng)前余額
    • nonce:交易次數(shù)
    • code:合約代碼
    • storage:存儲,數(shù)據(jù)結(jié)構(gòu)是一棵MPT

    Solidity是智能合約最常用的語言,語法上與JavaScript很接近

    2)、智能合約的代碼結(jié)構(gòu)
    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    Solidity是面向?qū)ο蟮木幊陶Z言,這里的contract類似于C++當(dāng)中的類class,這里的contract定義了很多狀態(tài)變量,Solidity是強類型語言,這里的類型跟普通的編程語言像C++之類的是比較接近的,比如說uint(unsigned int)是無符號的整數(shù),address類型是Solidity語言所特有的

    接下來是兩個event事件,作用是用來記錄日志的

    第一個事件是HighestBidIncreased,拍賣的最高出價增加了,上圖是一個網(wǎng)上拍賣的例子,如果有人出現(xiàn)新的最高價,記錄一下參數(shù)是address bidder,金額是amount,第二個事件是Pay2Beneficiary,參數(shù)是贏得拍賣的人的地址winner以及他最后的出價amount

    Solidity語言跟別的普通編程語言相比有一些特別之處:

    比如mapping,mapping是一個哈希表,保存了從地址到unit的一個映射。Solidity語言中哈希表不支持遍歷,如果想遍歷哈希表里的所有元素,需要自己想辦法記錄哈希表中有哪些元素,這里是用bidders數(shù)組來記錄的。Solidity語言中的數(shù)組可以是固定長度的,也可以是動態(tài)改變長度的,這里是一個動態(tài)改變長度的數(shù)組。如果想在數(shù)組里增加一個元素,就用push操作,bidders.push(bidder),新增加一個出價人在數(shù)組的末尾,要想知道這個數(shù)組有多少個元素,可以用bidders.length,如果是固定長度的數(shù)組的話,就要寫明數(shù)組的長度,比如說address[1024],這個就是長度為1024的數(shù)組

    再往下是構(gòu)造函數(shù),構(gòu)造函數(shù)只能有一個,Solidity語言中定義構(gòu)造函數(shù)有兩種方法

    • 一種方法就是像C++構(gòu)造函數(shù)一樣,定一個與contract同名的函數(shù),這個函數(shù)可以有參數(shù),但是不能有返回值
    • 新版本Solidity語言更推薦用這個例子的方法,就用一個constructor來定義一個構(gòu)造函數(shù),這個構(gòu)造函數(shù)只有在合約創(chuàng)建的時候會被調(diào)用一次

    接下來是三個成員函數(shù),三個函數(shù)都是public,說明其他賬戶可以調(diào)用這些函數(shù)

    3)、賬戶調(diào)用

    1)外部賬戶如何調(diào)用智能合約?

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    調(diào)用智能合約其實跟轉(zhuǎn)賬是類似的,比如說A發(fā)起一個交易轉(zhuǎn)賬給B:

    • 如果B是一個普通的賬戶,那么這就是一個普通的轉(zhuǎn)賬交易,就跟比特幣當(dāng)中的轉(zhuǎn)賬交易時一樣的
    • 如果B是一個合約賬戶的話,那么這個轉(zhuǎn)賬實際上是發(fā)起一次對B這個合約的調(diào)用,那么具體是調(diào)用合約中的哪個函數(shù)呢,是在data域(數(shù)據(jù)域)說明的

    上圖這個例子中,sender address是發(fā)起這個調(diào)用的賬戶的地址,to contract address是被調(diào)用的合約的地址,調(diào)用的函數(shù)是txdata,如果函數(shù)是有參數(shù)的話,那么參數(shù)的取值也是在data域里說明的,上面看的網(wǎng)上拍賣的例子中,三個成員函數(shù)都沒有參數(shù),但是有的成員函數(shù)是可以有參數(shù)的

    中間那一行是調(diào)用的參數(shù),value是說發(fā)起調(diào)用的時候轉(zhuǎn)過去多少錢,這里是0,這個調(diào)用的目的僅僅是為了調(diào)用它的函數(shù),并不是真的要轉(zhuǎn)帳,所以value=0,gas used是這個交易花了多少汽油費,gas price是單位汽油的價格,gas limit是這個交易我最多原意支付多少汽油費

    2)一個合約如何調(diào)用另一個合約中的函數(shù)?

    方法一:直接調(diào)用

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    上圖這個例子中,有A和B兩個合約:

    A這個合約就只是寫log,event定義事件LogCallFoo,emit LogCallFoo()是用emit這個操作來調(diào)用這個事件,emit語句的作用就是寫一個log,對于程序的運行邏輯是沒有影響的

    B這個合約,callAFooDirectly這個函數(shù)參數(shù)是一個地址,就是A這個合約的地址,然后就這個語句把這個地址轉(zhuǎn)換成A這個合約的一個實例,然后調(diào)用其中的foo這個函數(shù)

    以太坊中規(guī)定一個交易只有外部賬戶才能夠發(fā)起,合約賬戶不能自己主動發(fā)起一個交易。所以這個例子中需要有一個外部賬戶調(diào)用了合約B當(dāng)中的這個callAFooDirectly函數(shù),然后這個函數(shù)再調(diào)用合約A當(dāng)中的foo函數(shù)

    方法二:使用address類型的call()函數(shù)

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    address類型的call()函數(shù),第一個參數(shù)是要調(diào)用函數(shù)的簽名,然后后面跟的是調(diào)用的參數(shù)

    這種調(diào)用的方法跟上一個調(diào)用的方法相比,一個區(qū)別是對于錯誤處理的不同,直接調(diào)用時,如果你調(diào)用了那個合約在執(zhí)行過程中出現(xiàn)錯誤,那么會導(dǎo)致發(fā)起調(diào)用的這個合約也跟著一起回滾,在直接調(diào)用的例子中如果A在執(zhí)行過程出現(xiàn)什么異常,會導(dǎo)致B這個合約也跟著一起出錯

    而這種address.call()這種形式如果在調(diào)用過程中,被調(diào)用的合約拋出異常,那么這個call函數(shù)會返回false,表明這個調(diào)用是失敗的,但是發(fā)起調(diào)用的這個函數(shù)并不會拋出異常,而是可以繼續(xù)執(zhí)行

    方法三:代理調(diào)用delegatecall()

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    代理調(diào)用和call()這種方法基本上是一樣的,一個主要的區(qū)別是delegatecall不需要切換到被調(diào)用的合約的環(huán)境中去執(zhí)行,而是在當(dāng)前合約環(huán)境中執(zhí)行就可以了,比如就用當(dāng)前賬戶的賬戶余額存儲之類的

    4)、payable

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    上圖中,bid函數(shù)有一個payable,另外兩個函數(shù)都沒有。以太坊中規(guī)定如果這個合約賬戶要能接收外部轉(zhuǎn)賬的話,那么必須標(biāo)注成payable

    這個例子中bid函數(shù)是什么意思?

    這是一個網(wǎng)上拍賣的合約,bid函數(shù)是用來進(jìn)行競拍出價的,比如說你要參與拍賣,你說你出100個以太幣,那么就調(diào)用合約當(dāng)中的bid函數(shù)。拍賣規(guī)則是調(diào)用bid函數(shù)時要把拍賣的出價100個以太幣也發(fā)送過去,存儲到這個合約里,鎖定到拍賣結(jié)束,避免有人憑空出價,所以這個bid函數(shù)要有能夠接收外部轉(zhuǎn)賬的能力,才標(biāo)注一個payable

    第二個withdraw函數(shù)沒有payable,withdraw是拍賣結(jié)束了,出價最高的那個人贏得了拍賣,其他人沒有拍到想要的東西,可以調(diào)用withdraw把自己當(dāng)初出的價錢,就是原來bid的時候鎖定在智能合約里的以太幣再取回來,因為這個的目的不是為了真的轉(zhuǎn)賬,不是要把錢轉(zhuǎn)給智能合約,而僅僅是調(diào)用withdraw函數(shù)把當(dāng)初鎖定在智能合約里的那一部分錢取回來,所以沒必要標(biāo)注payable

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    上圖轉(zhuǎn)賬交易的例子,value=0,這個交易就屬于并沒有真的把錢轉(zhuǎn)出去,所以to contract address這個函數(shù)就不用定義成payable

    以太坊中凡是要接收外部轉(zhuǎn)賬的函數(shù),都必須標(biāo)識為payable,否則你給這個函數(shù)轉(zhuǎn)出錢的話,會引發(fā)錯誤處理,會拋出異常,如果你不需要接收外部轉(zhuǎn)賬你就不用標(biāo)識為payable

    5)、fallback()函數(shù)

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    有一個特殊的函數(shù)叫fallback()函數(shù),這個函數(shù)既沒有參數(shù)也沒有返回值,而且也沒有函數(shù)名是個匿名函數(shù),這個fallback關(guān)鍵字也沒有出現(xiàn)在這個函數(shù)名里

    調(diào)用合約的時候,A調(diào)用B這個合約,然后要在轉(zhuǎn)賬交易的data域說明你調(diào)用的是B當(dāng)中的哪個函數(shù),如果A給合約B轉(zhuǎn)賬了一筆錢,沒有說明調(diào)用的是哪個函數(shù),它的data域是空的,那怎么辦呢?那么這個時候缺省的就是調(diào)用這個fallback()函數(shù),為什么叫fallback()函數(shù),因為沒有別的函數(shù)可調(diào)了,就調(diào)它

    還有一種情況是你要調(diào)的函數(shù)不存在,在那個data域里,你說要調(diào)這個函數(shù),而實際這個合約當(dāng)中沒有這個函數(shù),那怎么辦呢?也是調(diào)用這個fallback()函數(shù)。這就是為什么這個函數(shù)沒有參數(shù)也沒有返回值,因為它沒法提供參數(shù)

    對于fallback()函數(shù)來說,也可能需要標(biāo)注payable關(guān)鍵字,如果fallback()函數(shù)需要有接收轉(zhuǎn)賬的能力的話,也需要寫成是payable,一般情況下,都是寫上payable的,如果合約賬戶沒有任何函數(shù)標(biāo)識為payable,包括fallback()函數(shù)函數(shù)也沒有標(biāo)識成payable,那么這個合約沒有任何能力接受外部的轉(zhuǎn)賬。如果這個合約沒有fallback()函數(shù)或者是有fallback()函數(shù)但是沒有寫payable,那么其他人往這個合約里轉(zhuǎn)一筆錢,別的都不說,data域是空的就會引發(fā)異常

    fallback()函數(shù)不是必須定義的,合約里可以沒有fallback()函數(shù),如果沒有fallback()函數(shù)的話,出現(xiàn)前面說的幾種情況,就會拋出異常。另外只有合約賬戶才有這些東西,外部賬戶跟這個都沒有關(guān)系,外部賬戶都沒有代碼

    還有一點,轉(zhuǎn)賬金額可以是0,但是汽油費是要給的,這是兩碼事,轉(zhuǎn)賬金額是給收款人的,汽油費是給發(fā)布這個區(qū)塊的礦工的,如果汽油費不給的話,礦工不會把你這個交易打包發(fā)布到區(qū)塊鏈

    6)、智能合約的創(chuàng)建和運行

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    智能合約是怎么創(chuàng)建的呢?是由一個外部賬戶發(fā)起一個轉(zhuǎn)賬交易,轉(zhuǎn)給0x0這個地址,然后把這個要發(fā)布合約的代碼放到data域里面。你要創(chuàng)建一個合約,要發(fā)起一個轉(zhuǎn)賬交易,給0這個地址轉(zhuǎn)賬,轉(zhuǎn)賬的金額都是0,因為你實際上不是真的想轉(zhuǎn)帳,只是想發(fā)布一個智能合約,發(fā)布的這個智能合約的代碼放到數(shù)據(jù)域就行了

    合約的代碼寫完之后都是要編譯成bytecode,然后運行在EVM上。EVM是類似于JVM的設(shè)計思想,通過加一層虛擬機,對智能合約的運行提供一個一致性的平臺,所以EVM有時叫做Worldwide Computer(全世界的一個計算機),EVM的尋址空間是非常大,是256位,像前面講的unsigned int就是256位

    7)、汽油費(gas fee)

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    比特幣和以太坊這兩種區(qū)塊鏈的編程模型,設(shè)計理念是有很大差別的

    比特幣設(shè)計理念是簡單,腳本語言的功能很有限,比如說不支持循環(huán)

    而以太坊是要提供一個圖靈完備的編程模型(Turing-complete Programming Model),很多功能在比特幣平臺上實現(xiàn)起來很困難,甚至是根本實現(xiàn)不了,而到以太坊平臺上呢,實現(xiàn)起來就很容易,當(dāng)然,這樣也帶來一個問題,出現(xiàn)死循環(huán)怎么辦,當(dāng)一個全節(jié)點收到一個對智能合約的調(diào)用,怎么知道這個調(diào)用執(zhí)行起來會不會導(dǎo)致死循環(huán)?有什么辦法嗎?

    沒有辦法,這實際上是一個Halting Problem(停機問題),停機問題是不可解的,從理論上可以證明不存在這樣一個算法,能夠?qū)θ我饨o定的輸入程序判斷出這個程序是否會停機。那怎么辦呢?辦法就是把這個問題推給發(fā)起交易的那個賬戶,以太坊引入了汽油費機制,發(fā)起一個對智能合約的調(diào)用要支付相應(yīng)的汽油費

    上圖中間是一個交易的數(shù)據(jù)結(jié)構(gòu):

    • AccountNonce就是這個交易的序號,用于防止replay attack
    • Price和GasLimit就是跟汽油費相關(guān)的,GasLimit是這個交易原意支付的最大汽油量,Price是單位汽油的價格,兩個乘在一起就是這個交易可能消耗的最大汽油費
    • Recipient就是收款人的地址,轉(zhuǎn)賬交易轉(zhuǎn)給誰的收款人地址
    • Amount是轉(zhuǎn)賬金額,把Amount這么多錢轉(zhuǎn)給Recipient,也可以看到交易當(dāng)中的汽油費跟轉(zhuǎn)賬金額是分開的
    • Payload就是前面說的data域,用于存放調(diào)用的是合約中的哪一個函數(shù),函數(shù)的參數(shù)取值是什么,都在Payload里面

    當(dāng)一個全節(jié)點收到一個對智能合約的調(diào)用的時候,先按照調(diào)用過程中給出的GasLimit算出可能花掉的最大汽油費,然后一次性的把這個汽油費從這個發(fā)起調(diào)用的賬戶上扣掉,然后再根據(jù)實際執(zhí)行的情況,算出實際花了多少錢,如果汽油費不夠的會引起回滾

    不同的指令消耗的汽油費是不一樣的。一些簡單的指令,比如說加法減法消耗的汽油費是很少的,復(fù)雜的指令消耗的汽油費就比較多,比如說取哈希,這個運算一條指令就可以完成,但是汽油費就比較貴,除了計算量之外,需要存儲狀態(tài)的指令消耗的汽油費也是比較大的,那么相比之下,如果僅僅是為了讀取公共數(shù)據(jù),那么那些指令可以是免費的

    8)、錯誤處理

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    以太坊中的交易執(zhí)行起來具有原子性,一個交易要么全部執(zhí)行,要么完全不執(zhí)行,不會只執(zhí)行一部分,這個交易既包含普通的轉(zhuǎn)賬交易,也包含對智能合約的調(diào)用,所以如果在執(zhí)行智能合約的過程當(dāng)中,出現(xiàn)任何錯誤,會導(dǎo)致整個交易的執(zhí)行回滾,退回到開始執(zhí)行的之前的狀態(tài),就好像這個交易完全沒有執(zhí)行過

    那么什么情況下會出現(xiàn)錯誤呢?

    一種情況就是剛才說的汽油費,如果這個交易執(zhí)行完之后,沒有達(dá)到當(dāng)初的GasLimit,那么多余的汽油費會被退回到這個賬戶里,一開始的時候是按照最大的GasLimit把汽油費扣掉了,如果最后運行完了,還有剩下來的,實際上是用的多少汽油收多少錢,剩的可以退回去。相反,如果執(zhí)行到一半,GasLimit已經(jīng)都用完了,那么這個時候這個合約的執(zhí)行要退回到開始執(zhí)行之前的狀態(tài),這就是一種錯誤處理,而且這個時候已經(jīng)消耗掉的汽油費是不退的

    為什么要這么設(shè)計呢?執(zhí)行的狀態(tài)要回滾,但已經(jīng)耗掉的汽油費是不退的

    因為要么的話就會有惡意的節(jié)點可能會發(fā)動delays service attack,可能他發(fā)布一個計算量很大的合約,然后不停的調(diào)這個合約,每次調(diào)的時候給的汽油費都不夠,反正最后汽油費還會退回來,那么對攻擊者來說沒有什么損失,但是對礦工來說是白白浪費了很多的資源,這就是為什么說,汽油費不夠的話,執(zhí)行到一半會回滾,花掉的汽油費是不退的

    除了這種汽油費不夠的情況,還有一種情況是引起錯誤處理的,比如說assert語句和require語句,這兩個語句都是用來判斷某種條件,如果條件不滿足的話,就會導(dǎo)致拋出異常

    assert語句一般用于判斷某種內(nèi)部條件,有點像C語言中的assert是一樣的,require語句一般用于判斷某種外部條件,比如說判斷函數(shù)的輸入是否符合要求。上圖中給出了一個簡單的例子,bid這個競拍的函數(shù)判斷一下,當(dāng)前的時間now<=拍賣的結(jié)束時間auctionEnd,如果符合條件繼續(xù)執(zhí)行,如果不符合的話,拍賣都已經(jīng)結(jié)束了,你還在出價,這個時候就會拋出異常

    第三個語句是revert,revert是無條件的拋出異常,如果執(zhí)行到revert語句,那么自動的就會導(dǎo)致回滾,早期的版本里用的是throw語句,新版本Solidity里建議改用revert這個語句

    Solidity當(dāng)中沒有這種try-catch這種結(jié)構(gòu),有的編程語言像Java,用戶自己可以定義出現(xiàn)問題后怎么辦,有這種try-catch,Solidity里沒有這種結(jié)構(gòu)

    9)、嵌套調(diào)用

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    智能合約出現(xiàn)錯誤會導(dǎo)致回滾,那么如果是嵌套調(diào)用,一個智能合約調(diào)用另外一個智能合約,那么被調(diào)用的這個智能合約出現(xiàn)錯誤,是不是會導(dǎo)致發(fā)起調(diào)用的智能合約,也跟著一起回滾呢?所謂的叫連鎖式回滾

    不一定,這個取決于調(diào)用這個智能合約的方式。如果是直接調(diào)用的話,會出現(xiàn)連鎖式的回滾,整個交易都會回滾,如果調(diào)用的方式是用比如說call這種方式,就不會引起連鎖式回滾,只會使當(dāng)前的調(diào)用失敗返回一個false的返回值

    有些情況下,從表面上看你并沒有調(diào)用任何一個函數(shù),比如說,你就是往一個賬戶里轉(zhuǎn)賬,但是這個賬戶是合約賬戶的話,轉(zhuǎn)賬這個操作本身就有可能觸發(fā)對函數(shù)的調(diào)用,因為有fallback()函數(shù),這就是一種嵌套調(diào)用,一個合約往另一個合約里轉(zhuǎn)賬,就有可能調(diào)用這個合約里的fallback函數(shù)

    10)、Block Header中的GasLimit和GasUsed

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    Block Header中的GasLimit和GasUsed也是跟汽油費相關(guān)的,Block Header里面的GasUsed是這個區(qū)塊里所有交易所消耗的汽油費加在一起

    發(fā)布區(qū)塊需要消耗一定的資源,這個消耗的資源要不要有一個限制,比特幣當(dāng)中對于發(fā)布的的區(qū)塊也是有一個限制的,大小的限制,最多不能超過1M,因為發(fā)布的區(qū)塊如果沒有任何限制,有的礦工可能把特別多的交易全部打包到一個區(qū)塊里面然后發(fā)布出去,那么這個超大的區(qū)塊在區(qū)塊鏈上會消耗很多資源,所以它規(guī)定每個區(qū)塊最多不能超過1M,比特幣交易是比較簡單的,基本上可以用交易的字節(jié)數(shù)來衡量出這個交易消耗的資源有多少,但以太坊中如果這么規(guī)定是不行的,因為以太坊中智能合約的邏輯很復(fù)雜,有的交易可能從字節(jié)數(shù)上看是很小的,但它消耗的資源可能很大,比如它可能調(diào)用別的合約之類的,所以要根據(jù)交易的具體操作來收費,這就是汽油費

    Block Header里面的GasLimit是這個區(qū)塊里所有交易能夠消耗的汽油的一個上限,不是說把區(qū)塊里每個交易的GasLimit加在一起,如果那樣的話,就等于沒有限制了,因為每個交易的GasLimit是發(fā)布這個交易的賬戶自己定的,定多少是自己說了算,但是這個區(qū)塊中的所有交易,實際能夠消耗的汽油是有一個上限的,不能無限的消耗,否則你也可能發(fā)布一個對資源消耗很大的區(qū)塊,對整個系統(tǒng)的運行是沒有好處的

    GasLimit跟比特幣的區(qū)別:

    比特幣限制資源是按照大小來限制的,而且這個1M的上限是固定了的,是寫死在協(xié)議里面的,有些人認(rèn)為1M太小了,而且有的分叉幣的產(chǎn)生就是為了提高這個上限

    以太坊中也有一個上限,這個GasLimit,但是每個礦工在發(fā)布區(qū)塊的時候可以對GasLimit進(jìn)行微調(diào),可以在上一個GasLimit的基礎(chǔ)上上調(diào)或者下調(diào) 1 1024 \frac{1}{1024} 10241?。如果出現(xiàn)像比特幣那種情況,大家都覺得這個GasLimit設(shè)置的太小了,那輪到你發(fā)布區(qū)塊的時候可以增加 1 1024 \frac{1}{1024} 10241?,1/1024聽起來很小,以太坊的出塊速度很快,十幾秒就是一個新的區(qū)塊,所以的話,如果大家都覺得當(dāng)前的GasLimit太小,那么很快就可以翻一番。當(dāng)然,也可能下調(diào),有礦工認(rèn)為GasLimit太大了需要下調(diào),所以這種機制實際上求出的GasLimit,是所有礦工認(rèn)為比較合理的GasLimit的一個平均值,有的礦工認(rèn)為要上調(diào),有的礦工認(rèn)為要下調(diào),那么每個礦工在獲得記賬權(quán)之后就按照自己的意愿進(jìn)行這種上調(diào)或者下調(diào)的微調(diào),所以最后整個系統(tǒng)的GasLimit就趨向于所有礦工的一個平均意見

    11)、一些問題

    問題1:某個全節(jié)點要打包一些交易到一個區(qū)塊里面,這些交易里有一些是對智能合約的調(diào)用,那么這個全節(jié)點應(yīng)該先把這個智能合約都執(zhí)行完之后再去挖礦呢,還是說先挖礦獲得了記賬權(quán)然后再執(zhí)行這些智能合約?

    區(qū)塊鏈里有一筆轉(zhuǎn)賬交易發(fā)布上去的話,本來就是需要所有的全節(jié)點都執(zhí)行的,這不是一種浪費也不是一種出問題了,就是所有的全節(jié)點要同步狀態(tài),大家都要在本地執(zhí)行這個轉(zhuǎn)賬交易,如果一個全節(jié)點不執(zhí)行那就出問題了,那他的狀態(tài)跟別人的狀態(tài)是不一樣的,比特幣也是一樣的,比特幣發(fā)布一個交易到區(qū)塊鏈上,也是要所有的全節(jié)點都得執(zhí)行這個轉(zhuǎn)賬交易,要不然怎么更新UTXO啊

    先往回退一步,不回答這個問題,在全節(jié)點收到一個對合約的調(diào)用的時候,要一次性的先把這個調(diào)用可能花掉的最大汽油費從發(fā)起這個調(diào)用的賬戶上扣掉,這個具體是怎么操作的?

    狀態(tài)樹、交易樹和收據(jù)樹,這三棵樹都是全節(jié)點在本地維護(hù)的數(shù)據(jù)結(jié)構(gòu),狀態(tài)樹記錄了每個賬戶的狀態(tài)包括賬戶余額,所以扣汽油費的時候?qū)嶋H怎么扣的?全節(jié)點收到調(diào)用的時候,從本地維護(hù)的數(shù)據(jù)結(jié)構(gòu)里把賬戶的余額減掉就行了,如果余額不夠的話,這個交易就不能執(zhí)行,一次性要按GasLimit把他這個余額減掉,執(zhí)行完之后如果有剩的,再把他的余額再加回去一點

    智能合約執(zhí)行過程中任何對狀態(tài)的修改都是在改本地的數(shù)據(jù)結(jié)構(gòu),只有在合約執(zhí)行完了,而且發(fā)布到區(qū)塊鏈上之后,本地的修改才會變成外部可見的,才會變成區(qū)塊鏈上的共識。有很多全節(jié)點,每個全節(jié)點都在本地做這個事情,執(zhí)行的智能合約可能不完全一樣,因為根據(jù)你收到的交易可能執(zhí)行不完全一樣,如果某個全節(jié)點發(fā)布一個區(qū)塊,我收到這個區(qū)塊之后,我本地執(zhí)行的就扔掉了,我把收到這個區(qū)塊里的交易再執(zhí)行一遍,更新我本地的三棵樹。如果我本來已經(jīng)執(zhí)行一遍了,我沒有挖到礦,那個人發(fā)過來我又得執(zhí)行一遍,我得執(zhí)行兩遍多浪費啊,問題是你不這樣還能怎么辦,你本地那個候選區(qū)塊中包含的交易跟他發(fā)布的那個交易不一定完全一樣,至少有一個肯定不一樣,給出塊獎勵的那個肯定不一樣,他不會給你,別的交易也不一定就一樣,所以這個沒有辦法,都是得要重新執(zhí)行一遍

    以太坊挖礦其實也是嘗試各種nonce找到一個符合要求的,計算哈希的時候要用到什么?要用到這個Block Header的內(nèi)容,Block Header的內(nèi)容這個Root、TxHash、ReceiptHash,是那三棵樹的根哈希值,所以要先執(zhí)行完這個區(qū)塊中的所有交易包括智能合約的交易,這樣才能更新這三棵樹,這樣才能知道這三個根哈希值,這樣這個Block Header的內(nèi)容才能確定,然后才能嘗試各個nonce

    問題2:假設(shè)我是一個礦工我費了半天勁執(zhí)行這些智能合約,消耗了我本地的好多資源,最后我挖礦沒挖到怎么辦,因為挖礦是競爭,很多礦工競爭,記賬權(quán)被別人搶先了,那我能得到什么補償,我能得到汽油費嗎?

    汽油費是沒有的,因為汽油費是給獲得記賬權(quán)發(fā)布區(qū)塊的那個礦工,那我能得到啥補償?以太坊中沒有任何補償,他得不到汽油費也得不到任何補償,不僅如此,他還要把別人發(fā)布的區(qū)塊里的交易在本地執(zhí)行一遍,以太坊中規(guī)定要驗證發(fā)布區(qū)塊的正確性,每個全節(jié)點要獨立驗證,那怎么驗證呢?別人發(fā)布一個交易區(qū)塊,你把那個區(qū)塊里的所有交易在本地執(zhí)行完一遍,更新三棵樹的內(nèi)容,算出根哈希值,再跟他發(fā)布的那個根哈希值比較一下看是不是一致,所有這些都是免費的,沒有人給你補償。所以呢,這種機制下,挖礦慢的礦工就特別吃虧,本來汽油費的設(shè)置的目的是對于礦工執(zhí)行這些智能合約所消耗的這些資源的一種補償,但是這種補償只有挖到礦的礦工才能得到,其他的礦工等于是陪太子讀書

    問題3:會不會有的礦工你不給我汽油費,那我就不驗證?比如說我挖半天沒有挖到礦,你發(fā)布一個區(qū)塊,按照協(xié)議我要驗證一下你這個區(qū)塊的正確性,我驗證他有啥好處,你又不給我汽油費,我驗證他干嘛,我就認(rèn)為你是正確的不就行了嗎,我就接著挖,會不會有礦工想不通?

    先說一下,如果這樣做會導(dǎo)致什么后果,最直接的后果是危害區(qū)塊鏈的安全,區(qū)塊鏈的安全是是怎么保證的,就是要求所有的全節(jié)點要獨立驗證發(fā)布的區(qū)塊的合法性,這樣少數(shù)有惡意的節(jié)點沒法篡改區(qū)塊鏈上的內(nèi)容。如果某個礦工想不通,不給錢我就不驗證了,這樣的風(fēng)氣蔓延開來就會危及區(qū)塊鏈的安全

    會不會有這樣的情況?如果他跳過驗證這個步驟,他以后就沒法再挖礦了,因為你驗證的時候是要把區(qū)塊的交易再執(zhí)行一遍,更新本地的那三棵樹,如果不去驗證的話,本地三棵樹的內(nèi)容沒有辦法更新,以后再發(fā)布區(qū)塊你怎么發(fā)布,你本地的這些狀態(tài)就不對了,你算出的根哈希值發(fā)布出去之后別人認(rèn)為是錯的。沒有辦法跳過驗證這個步驟

    為什么要執(zhí)行才能更新狀態(tài)?因為發(fā)布的區(qū)塊里沒有這三棵樹的內(nèi)容,只是塊頭里有三個根哈希值,這三棵樹的賬戶狀態(tài)具體是什么余額什么內(nèi)容,發(fā)布出來是沒有的,不能把狀態(tài)樹的整個狀態(tài)發(fā)布到區(qū)塊鏈上,那太多了,而且很多是重復(fù)的,狀態(tài)都不改了,所以不會跳過驗證這個步驟,以太坊的安全還是有保證的

    問題4:發(fā)布到區(qū)塊鏈上的交易是不是都是成功執(zhí)行的?如果智能合約執(zhí)行過程中出現(xiàn)了錯誤,要不要也發(fā)布到區(qū)塊鏈上去?

    執(zhí)行發(fā)生錯誤的交易也要發(fā)布到區(qū)塊鏈上去,否則汽油費扣不掉,光是在本地的數(shù)據(jù)結(jié)構(gòu)上把他的賬戶扣了汽油費,是沒用的,你拿不到錢,你得把區(qū)塊發(fā)布上去之后形成共識,扣掉的汽油費才能成為你賬戶上的錢,所以發(fā)布到區(qū)塊鏈上的交易不一定都是成功執(zhí)行的。要告訴大家為什么扣汽油費,而且別人得驗證一遍,也要把這個交易執(zhí)行完一遍,看你扣的是不是對的

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    那怎么知道一個交易是不是執(zhí)行成功了呢,前面說過那三棵樹,每個交易執(zhí)行完后形成一個收據(jù),上圖是這個收據(jù)的內(nèi)容,Status這個域就是告訴你交易執(zhí)行的情況是怎么樣的

    問題5:智能合約是不是支持多線程,現(xiàn)在多核處理器很普遍,一個計算器有十幾核,幾十個核,都是正常的,那么智能合約支不支持多核并行處理?

    Solidity不支持多線程,它根本沒有支持多線程的語句,原因是以太坊是一個交易驅(qū)動的狀態(tài)機,這個狀態(tài)機必須是完全確定性的,就是給定一個智能合約,面對同一組輸入,產(chǎn)生的輸出或者說轉(zhuǎn)移到的下一個狀態(tài)必須是完全確定的

    為什么要求這個?因為所有的全節(jié)點都得執(zhí)行同一組操作到達(dá)同一個狀態(tài),要驗證,如果狀態(tài)不確定的話,那三棵樹得根哈希值根本對不上,必須完全確定才行

    多線程的問題在于什么?多個核對內(nèi)存訪問順序不同的話,執(zhí)行結(jié)果有可能是不確定的,除了多線程之外,其他可能造成執(zhí)行結(jié)果不確定的操作也都不支持,最直接最簡單的會導(dǎo)致執(zhí)行結(jié)果不確定的操作:產(chǎn)生隨機數(shù),這個操作就是不確定性的,而且這個操作必須得是不確定的,所以以太坊的智能合約沒有辦法產(chǎn)生真正意義下的隨機數(shù),可以用一些偽隨機數(shù),不能是真的隨機數(shù),否則的話,又會出現(xiàn)前面的問題,每個全節(jié)點執(zhí)行完一遍得到的結(jié)果都不一樣

    13)、智能合約可以獲得的信息

    1)區(qū)塊信息

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    智能合約的執(zhí)行必須是確定性的,這也就導(dǎo)致了智能合約不能像通用的編程語言那樣通過系統(tǒng)調(diào)用來得到一些環(huán)境信息,因為每個全節(jié)點的執(zhí)行環(huán)境不是完全一樣的,所以它只有通過一些固定的一些變量的值能夠得到一些狀態(tài)信息,上圖就是智能合約能夠得到的區(qū)塊鏈的一些信息

    2)調(diào)用信息

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    上圖是智能合約可以獲得的調(diào)用信息:

    • msg.sender是發(fā)起這個調(diào)用的人是誰,這個跟最后一個tx.origin交易的發(fā)起者是不一樣的。比如說有一個外部賬戶A調(diào)用了一個合約叫 C 1 C_1 C1?, C 1 C_1 C1?當(dāng)中有一個函數(shù) f 1 f_1 f1? f 1 f_1 f1?又調(diào)用另外一個合約 C 2 C_2 C2?,里面的函數(shù) f 2 f_2 f2?,那么對這個 f 2 f_2 f2?函數(shù)來說,msg.sender C 1 C_1 C1?這個合約,因為當(dāng)前這個調(diào)用,是 C 1 C_1 C1?這個合約發(fā)起的,但是tx.origin是A這個賬戶,因為整個交易的發(fā)起者是A這個賬戶
    • msg.gas是當(dāng)前調(diào)用還剩下多少汽油費,這個決定了我還能做哪些操作,包括你還想調(diào)用別的合約前提是還有足夠的汽油費剩下來
    • msg.data就是所謂的叫數(shù)據(jù)域,在里面寫了調(diào)用哪些函數(shù)和這些函數(shù)的參數(shù)取值
    • msg.sigmsg.data的前四個字節(jié),就是函數(shù)標(biāo)志符調(diào)用的是哪個函數(shù)
    • now是當(dāng)前區(qū)塊的時間戳,跟區(qū)塊信息中的block.timestamp是一個意思,就是智能合約里沒有辦法獲得很精確的時間,只能獲得跟當(dāng)前區(qū)塊信息的一些時間
    14)、地址類型

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    上圖中第一個是個成員變量,剩下的都是成員函數(shù)。成員變量就是賬戶的余額balance,unit256是這個成員變量的類型,是以Wei為單位的,是個很小的單位

    下面這些成員函數(shù)的話,有一點要注意的,這些成員函數(shù)的語義跟我們直觀上的理解不是很一樣,跟第一個成員變量balance也不太一樣

    addr.balance是address這個地址上他的賬戶他的余額,那addr.transfer(12345)是什么意思呢?感覺像是addr這個賬戶往外轉(zhuǎn)了12345個Wei,是不是這個意思?如果是這個意思的話,問題在于他只有一個參數(shù),他只有轉(zhuǎn)賬的金額,沒有說轉(zhuǎn)給誰,所以addr.transfer(unit amount)是什么意思呢?并不是說addr這個賬戶往外轉(zhuǎn)了多少錢,而是當(dāng)前這個合約往addr這個地址里轉(zhuǎn)入多少錢,這個addr是轉(zhuǎn)入的地址不是轉(zhuǎn)出的地址,轉(zhuǎn)出的地址是哪一個?比如說這是個智能合約C,里面有一個函數(shù)f,它包含這條語句addr.transfer(12345),意思是說C這個合約的賬上往這個addr地址里轉(zhuǎn)入12345這么多的錢

    addr.call也是一樣的語句,并不是說addr這個合約賬戶發(fā)起了一個調(diào)用,調(diào)哪個別的合約賬戶,而是說當(dāng)前這個合約發(fā)起一個調(diào)用,調(diào)得是addr這個合約

    delegatecall區(qū)別就是說不需要切換到被調(diào)用的函數(shù)的環(huán)境中,就用當(dāng)前合約的余額,當(dāng)前合約的存儲這些狀態(tài)去運行就可以了

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    問題:我向一個帳戶轉(zhuǎn)賬說這個賬戶沒有定fallback函數(shù)會引起錯誤,會不會連鎖回滾?

    這取決于你怎么轉(zhuǎn)賬的,轉(zhuǎn)賬有三種方法,上圖中這三種形式都可以發(fā)送ETH

    區(qū)別是這個transfer和send,這兩個是專門為了轉(zhuǎn)賬的函數(shù),區(qū)別在于transfer會導(dǎo)致連鎖性回滾,類似于你直接調(diào)用那個函數(shù)直接調(diào)用的方法是一樣的,失敗的時候拋出異常,而send返回一個false,不會導(dǎo)致連鎖式回滾。call其實也是可以轉(zhuǎn)賬的,call.value(unit256 amount)(),最后一個參數(shù)如果不用調(diào)用函數(shù)可以是空的。區(qū)別在于transfer和send是專門用來轉(zhuǎn)賬的,call的話本意是發(fā)動函數(shù)調(diào)用,但是也可以用來轉(zhuǎn)賬,call也不會引起連鎖式回滾,失敗時返回false

    另外一個區(qū)別是transfer和send在發(fā)起調(diào)用的時候,只給了一點兒的汽油,是2300個單位,非常少的,那么收到這個轉(zhuǎn)賬的合約基本上干不了別的事,寫一個log就行了,別的事都干不了,而call是把當(dāng)前這個調(diào)用剩下的所有的汽油都發(fā)過去,比如說call所在的合約本身被調(diào)用的時候,可能還剩8000個汽油,然后去調(diào)別的合約的時候如果是用call這種方法去轉(zhuǎn)賬,就把剩多少汽油都發(fā)過去了

    15)、拍賣的例子

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    回到一開始講的拍賣的例子,拍賣有一個受益人beneficiary,比如說你有一個古董要拍賣,那么這個受益人就是你;auctionEnd是整個拍賣的結(jié)束時間;highestBidder是最高出價人

    拍賣的規(guī)則:

    在拍賣結(jié)束之前,每個人都可以去出價,去競拍,競拍的時候為了保證誠信,要把競拍的價格相應(yīng)的以太幣發(fā)過去,比如你出價100個以太幣,那么你競拍的時候要把100個以太幣發(fā)到這個智能合約里,它就會鎖在這里面直到拍賣結(jié)束,拍賣的規(guī)則不允許中途退出,我去競拍發(fā)了100個以太幣,過一會兒我后悔了想把錢要回來,這個不行。拍賣結(jié)束的時侯出價最高的那個人highestBidder,他投出去的錢會給這個受益人beneficiary,當(dāng)然你也要想辦法把這個古董給最高出價人,其他沒有拍賣成功的人可以把當(dāng)初投進(jìn)去的錢再取回來

    競拍是可以多次出價的,比如說我出個價錢,100個以太幣,然后呢,另外一個人出價110個以太幣,我再出價120個以太幣,這個時候我只要補差價就行了,就把我這一次的出價跟上一次的出價差額發(fā)到智能合約里,我上次投標(biāo)的時候已經(jīng)發(fā)了100個以太幣,這次只要再發(fā)20個以太幣就行了。出價要有效的話,必須比最高出價還要高,比如說當(dāng)前的最高出價是100個以太幣,我去競拍,我投80個以太幣,這個是無效的,等于是非法的拍賣

    constructor會記錄下收益人是誰,結(jié)束時間是什么時候,這個構(gòu)造函數(shù),在合約創(chuàng)建的時候,把這兩個就記下來了

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    上圖是拍賣用的兩個函數(shù),左邊的bid函數(shù)是競拍時候用的,你要競拍你就發(fā)起一個交易調(diào)用這個拍賣合約中的bid函數(shù),這個bid函數(shù)有一個奇怪的地方,它沒有參數(shù),感覺上你競拍的時候你不需要告訴對方你出的價格是多少嗎?它其實是在msg.value這個地方寫的,這個是發(fā)起調(diào)用的時候,轉(zhuǎn)賬轉(zhuǎn)過去的以太幣數(shù)目,以Wei為單位的轉(zhuǎn)賬金額,這個的邏輯是:

    首先查一下當(dāng)前的拍賣還沒有結(jié)束,如果拍賣結(jié)束了,你還出價會拋出異常,然后查一下你上一次的出價加上你當(dāng)前發(fā)過去的以太幣大于最高出價,如果你以前沒有出價過會怎么樣?這個bids是個哈希表,Solidity中哈希表的特點是,如果你要查詢的那個鍵值不存在,那么它返回默認(rèn)值就是0,所以如果沒有出過價,第一部分就是0,然后呢,第一次拍賣的時候把拍賣者的信息放到bidders數(shù)組里,原因是Solidity哈希表不支持遍歷,要遍歷哈希表的話,要保存一下它包含哪些元素,然后記錄一下新的最高出價人是誰,寫一些日志之類的

    右邊是拍賣結(jié)束的函數(shù),首先查一下拍賣是不是已經(jīng)結(jié)束了,如果拍賣還沒有結(jié)束,有人調(diào)用這個函數(shù),就是非法的會拋出異常,然后判斷一下這個函數(shù)是不是已經(jīng)被調(diào)過了,如果已經(jīng)被調(diào)過了,就不用再調(diào)一遍了,首先把這個金額給這個beneficiary,beneficiary.transfer是當(dāng)前這個合約把這個金額給這個beneficiary轉(zhuǎn)過去,最高出價人的錢是給受益人了,然后那些剩下的沒有競拍成功的用一個循環(huán),把這個金額退回給這個bidder,然后標(biāo)明一下,這個函數(shù)已經(jīng)執(zhí)行完了寫一個log

    智能合約是怎么工作的?

    你寫完一個智能合約,你寫一個拍賣程序要先把它發(fā)布到區(qū)塊鏈上,往那個0地址發(fā)一筆轉(zhuǎn)賬交易,轉(zhuǎn)賬的金額是0,然后把智能合約的代碼放到data域里面,汽油費是要交的,然后礦工把這個智能合約發(fā)布到區(qū)塊鏈上之后會返回這個合約的地址,然后這個合約就在區(qū)塊鏈上了,所有人都可以調(diào)用它

    每次競拍存在哪?

    智能合約本身有一個合約賬戶,里面有一個狀態(tài)信息,它的存儲都是在一個MPT存著的

    拍賣的流程:

    比如你的外部賬戶要拍賣,你要發(fā)起一個交易,這個交易要調(diào)用這個bid函數(shù),然后這個交易要調(diào)用這個bid函數(shù)要礦工寫到區(qū)塊鏈里。任何一個人出價參與這個競拍,調(diào)用這個bid函數(shù)的操作都需要發(fā)布到區(qū)塊鏈里

    你要競拍就是寫一個Solidity程序,然后你發(fā)布一個交易把這個合約放到網(wǎng)上,那別人怎么知道你這個合約,你需要線下宣傳,用別的方法宣傳,區(qū)塊鏈不負(fù)責(zé)給你做這個宣傳,就像你的比特幣地址別人怎么能知道,你自己去宣傳

    上圖智能合約這么寫的問題是什么?

    寫智能合約一定要小心因為智能合約是不可篡改的,說的好聽點兒叫不可篡改,說的不好聽點兒叫你沒法改bug

    auctionEnd這個函數(shù)必須要某個人調(diào)用才能執(zhí)行,這個也是Solidity語言跟其他編程語言不同的一個地方,就是沒有辦法把它設(shè)置成拍賣結(jié)束了自動執(zhí)行auctionEnd,可能是拍賣的受益人beneficiary去調(diào)用這個auctionEnd,也可能是參與競拍沒有成功的人去調(diào)用,總之得有一個人去調(diào)用。如果兩個人都去調(diào)用auctionEnd,礦工在執(zhí)行的時候把第一個調(diào)用執(zhí)行完了,然后第二個再執(zhí)行就執(zhí)行不了了,因為第一個執(zhí)行完之后,ended就是true了,沒有并發(fā)執(zhí)行

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    假設(shè)有一個人通過上圖這樣的一個合約賬戶參與競拍,會有什么結(jié)果?

    這個合約實際上就一個函數(shù)hack_bid,這個函數(shù)的參數(shù)是拍賣合約的地址,然后把它轉(zhuǎn)成這個拍賣合約的一個實例,然后調(diào)用拍賣合約用的bid函數(shù),把這個錢發(fā)送過去。這是一個合約賬戶,合約賬戶不能自己發(fā)起交易,所以實際上得有一個黑客從他自己的外部賬戶發(fā)起一個交易,調(diào)用這個合約賬戶的hack_bid函數(shù),然后這個函數(shù)再去調(diào)用拍賣合約的bid函數(shù),把這個黑客外部賬戶轉(zhuǎn)過來的錢再轉(zhuǎn)給這個拍賣合約中的bid函數(shù),就參與拍賣了

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    這個合約參與拍賣沒有問題,最后拍賣結(jié)束退款的時候會有什么問題?這個紅框里循環(huán)退款,退到合約賬戶上的錢會有什么情況,退到黑客合約賬戶上的錢會有什么情況?

    黑客外部賬戶對拍賣合約來說是不可見的,拍賣合約能看到的只是這個黑客的合約。轉(zhuǎn)賬的時候沒有調(diào)用任何函數(shù),那么當(dāng)一個合約賬戶收到轉(zhuǎn)賬沒有調(diào)用任何函數(shù)的時候應(yīng)該調(diào)用fallback函數(shù),而這個合約沒有定義fallback函數(shù),所以會調(diào)用失敗,會拋出異常,這個transfer函數(shù)會引起連鎖式的回滾,就會導(dǎo)致這個轉(zhuǎn)賬操作是失敗的,所有人都收不到錢了

    再具體點,比如有20個人參與競拍了,這個黑客是排在第10個,最高出價人排在第16個,那么最后是有哪些收得到錢,哪些收不到錢?

    這個轉(zhuǎn)賬實際上是全節(jié)點執(zhí)行到beneficiary.transfer的時候把相應(yīng)賬戶的余額進(jìn)行了調(diào)整,所有的Solidity語句就是智能合約執(zhí)行過程中的任何對狀態(tài)的修改改的都是本地的狀態(tài),都是改的本地的數(shù)據(jù)結(jié)構(gòu)。所以這個循環(huán)當(dāng)中無論是排在黑客合約前面還是后面,都是在改本地數(shù)據(jù)結(jié)構(gòu),只不過排在后面的bidder根本沒有機會來得及執(zhí)行,然后整個都回滾了,就好像這個智能合約從來沒有被執(zhí)行過。所以排在前面的這些轉(zhuǎn)賬并沒有執(zhí)行,就是改本地結(jié)構(gòu),然后如果都順利執(zhí)行完了,發(fā)布出去之后,別的礦工也把這個auctionEnd重頭到尾執(zhí)行一遍,也改它本地的數(shù)據(jù)結(jié)構(gòu),跟你的能對得上就叫形成共識了,而不是說每有一個轉(zhuǎn)賬交易的語句是產(chǎn)生一個新的交易寫到區(qū)塊鏈上。所以都收不到錢,沒有任何一個人能收到錢

    發(fā)起這個攻擊的有可能是故意搗亂,寫這樣一個程序讓大家都拿不到錢,也可能是這個人不懂,他就忘了寫fallback函數(shù)了,那出現(xiàn)這種情況怎么辦呢?比如說你發(fā)布一個拍賣合約到區(qū)塊鏈上,吸引很多人來拍賣,拍賣完之后發(fā)現(xiàn)有這樣一個問題這個黑客合約,你怎么辦?

    現(xiàn)在的問題是你已經(jīng)把錢投進(jìn)去了,鎖在里面了,你怎么把它取出來。答案是沒有辦法,出現(xiàn)這種情況沒有辦法了。Code is law,智能合約的規(guī)則是由代碼邏輯決定的,而代碼一旦發(fā)布到區(qū)塊鏈上就改不了了,所謂的叫區(qū)塊鏈的不可篡改性,這樣的好處是沒有人能夠篡改規(guī)則,這樣的壞處是規(guī)則中有漏洞你也改不了了

    智能合約如果設(shè)計的不好的話,有可能把以太幣永久的鎖起來,誰也取不出來,所以在你發(fā)布一個智能合約之前一定要測試測試再測試,你可以在專門的那種測試的網(wǎng)上用假的以太幣,做測試確認(rèn)完全沒有問題的情況下再發(fā)布

    那我能不能在這個智能合約里留一個后門,用來修復(fù)bug,比如給合約的創(chuàng)建者超級用戶的權(quán)利,在這個構(gòu)造函數(shù)里加一個域叫owner,記錄一下這個owner是誰,然后對這個owner的地址允許他做一些系統(tǒng)管理員的操作,比如可以任意轉(zhuǎn)賬,把錢轉(zhuǎn)給哪個地址都行

    那樣的話,如果出現(xiàn)像這種bug,超級管理員就可以發(fā)揮作用,把鎖進(jìn)去的錢給轉(zhuǎn)出來了,因為反正對他沒有限制,他轉(zhuǎn)給誰都行。但這樣有可能出現(xiàn)卷款跑路的情況,這樣做的前提是所有人都要信任這個超級用戶,這個跟去中心化的理念是背道而馳的,也是絕大多數(shù)區(qū)塊鏈的用戶不能接受的

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    第二個版本,把前面那個auctionEnd拆成兩個函數(shù),左邊是withdraw,右邊是Pay2Beneficiary

    withdraw函數(shù)這里就不用循環(huán)了,每個競拍失敗的人自己調(diào)用withdraw函數(shù),把那一部分錢取回來。首先判斷一下拍賣是不是結(jié)束了,然后看一看調(diào)用的那個人是不是最高出價者,如果是的話,不能把錢給他,因為要留著給那個拍賣的beneficiary,然后看一下這個人賬戶的余額是不是正的,amount是他的賬戶余額,把賬戶余額轉(zhuǎn)給msg.sender,就是發(fā)起調(diào)用的這個人,然后把他賬戶余額清成0,免得他下次再來取一下錢

    Pay2Beneficiary函數(shù)是說把最高出價給這個受益人,也是判斷一下拍賣已經(jīng)結(jié)束了,最高出價的金額大于零,下面再把它轉(zhuǎn)過去

    這樣可以了嗎?

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    還是有一個問題:重入攻擊,如果有黑客寫了上圖右邊這樣一個程序會怎么樣?

    這個hack_bid跟前面的那個黑客合約hack_bid合約是一樣的,通過調(diào)用拍賣bid函數(shù)參與競拍,hack_withdraw就在拍賣結(jié)束的時候調(diào)用withdraw函數(shù),把錢取回來,這兩個看上去好像都沒有問題

    問題在于fallback函數(shù),他又把錢取了一遍,左邊是智能合約中的withdraw函數(shù),hack_withdraw調(diào)用withdraw函數(shù)的時候,執(zhí)行到左邊第47行會向黑客合約轉(zhuǎn)賬,這個msg.sender就是黑客的合約,把它當(dāng)初出價的金額轉(zhuǎn)給他,而右邊這個合約在干嘛?它又調(diào)用了拍賣函數(shù)的withdraw函數(shù),又去取錢,fallback函數(shù)這里的msg.sender就是這個拍賣合約,因為是拍賣合約把這個錢轉(zhuǎn)給黑客合約的,這個左邊的拍賣合約執(zhí)行到if那里,再給他轉(zhuǎn)一次錢

    注意這個清零的操作,把黑客合約賬戶清零的操作,只有在轉(zhuǎn)賬交易完成之后,才會進(jìn)行,而第47行這個轉(zhuǎn)賬的語句已經(jīng)陷入到了跟黑客合約當(dāng)中的遞歸調(diào)用當(dāng)中,根本執(zhí)行不到下面這個清零操作,所以最后的結(jié)果就是這個黑客一開始出價的時候給出了一個價格,拍賣結(jié)束之后,就按照這個價格不停地從這個智能合約中去取錢,第一次取得是他自己的出價,后面取得就是別人的錢了

    那這個遞歸重復(fù)取錢,持續(xù)到什么時候會結(jié)束?有三種情況,一個是這個拍賣合約上的余額不夠了,不足以在支持這個轉(zhuǎn)賬的語句;第二種情況是汽油費不夠了,因為每次調(diào)用的時候還是消耗汽油費的,到最后沒有足夠的汽油剩下來了;第三種情況,調(diào)用棧溢出了。所以右邊部分黑客合約的fallback函數(shù)判斷一下這個拍賣合約的余額還足以支持轉(zhuǎn)賬,當(dāng)前調(diào)用的剩余汽油msg.gas還有6000個單位以上,調(diào)用棧的深度不超過500,那么就再發(fā)起一輪攻擊

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    其實最簡單的就是先清零再轉(zhuǎn)賬,就是我們右邊的這種寫法,右邊Pay2Beneficiary寫法是正確的,已經(jīng)把highestBidder的賬戶余額清成零了,就在bids哈希表里面的余額已經(jīng)清成0了,然后再轉(zhuǎn)賬,轉(zhuǎn)賬如果不成功的話,再把余額恢復(fù)

    這個實際上是對于可能跟其他合約發(fā)生交互的情況的一種經(jīng)典的編程模式,就先要判斷條件,然后改變條件,最后再跟別的合約發(fā)生交互。在區(qū)塊鏈上,任何未知的合約都可能使有惡意的,所以每次你向?qū)Ψ睫D(zhuǎn)賬或者使調(diào)用對方某個函數(shù)的時候,都要提醒下自己,這個合約,這個函數(shù)有可能反過來調(diào)用你當(dāng)前的這個合約,并且修改狀態(tài),小心一點總是好的

    怎么證明以太坊的交易是原子性的,區(qū)塊鏈,區(qū)塊鏈,以太坊,智能合約

    還有一種方法,就是不要用call.value的形式轉(zhuǎn)賬,對比一下修改前后的兩段代碼,區(qū)別就是綠框的部分。首先我們把清零的位置提前了,先清零再轉(zhuǎn)賬,而且轉(zhuǎn)賬的時候用的使sender,用transfer也可以,sender和transfer一個共同的特點就是轉(zhuǎn)賬的時候發(fā)送過去的汽油費只有2300個單位,這個不足以讓接收的那個合約再發(fā)起一個新的調(diào)用,只夠?qū)懸粋€log而已

    對應(yīng)課程

    北京大學(xué)肖臻老師《區(qū)塊鏈技術(shù)與應(yīng)用》公開課文章來源地址http://www.zghlxwxcb.cn/news/detail-806819.html

    到了這里,關(guān)于北京大學(xué)肖臻老師《區(qū)塊鏈技術(shù)與應(yīng)用》公開課筆記:以太坊原理(三):智能合約的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

    本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

    領(lǐng)支付寶紅包贊助服務(wù)器費用

    相關(guān)文章

    覺得文章有用就打賞一下文章作者

    支付寶掃一掃打賞

    博客贊助

    微信掃一掃打賞

    請作者喝杯咖啡吧~博客贊助

    支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

    二維碼1

    領(lǐng)取紅包

    二維碼2

    領(lǐng)紅包