肖臻區(qū)塊鏈b站網課筆記——19-26
視頻鏈接:【北京大學肖臻老師《區(qū)塊鏈技術與應用》公開課】 https://www.bilibili.com/video/BV1Vt411X7JF?p=6&;;;share_source=copy_web&vd_source=33abd457d9415a4317112e8206e5360e
【筆記的內容參考了評論區(qū)里其他同學的筆記,標注了自以為的重點,其實這部分我還很多看不懂,還是要另外仔細學】?
19-ETH-挖礦算法
一、工作量證明
1.比特幣系統(tǒng)的工作量證明
對于基于工作量證明的系統(tǒng)來說,挖礦是保障區(qū)塊鏈安全的重要手段。
Bounty(賞金),Bug Bounty:有的公司懸賞來找軟件中的漏洞。比特幣的挖礦算法是一個天然的Bug Bounty,如果你能找到里面的漏洞,或者是某一個挖礦的捷徑就能取得很大的利益。但是到目前為止還沒有人發(fā)現(xiàn),所以比特幣的挖礦算法總的來說是比較成功的。
?
2.比特幣系統(tǒng)挖礦算法的問題
比特幣的挖礦算法有一個飽受爭議的問題:挖礦設備的專業(yè)化,只能用專門的設備,專用的ASIC芯片來挖礦,那么很多人認為這種做法與去中心化的理念和比特幣的設計初衷相違背。
中本聰最早的一篇論文,提出One CPU,one vote,理想狀況下,應該讓普通老百姓也能參與挖礦過程,這樣也更安全,因為算力分散之后,有惡意的攻擊者想要聚集到51%的算力發(fā)動攻擊,難度就會大得多。所以比特幣之后出現(xiàn)的加密貨幣包括以太坊設計Mining Puzzle的時候,就想要做到ASIC Resistance。
?
3.如何設計出對ASIC芯片不友好的Mining Puzzle(挖礦謎題)
一個常用的做法就是增加Mining Puzzle對內存訪問的需求(memory hard mining puzzle)。同樣的價格買一個ASIC礦機和買一個普通的計算機,這個ASIC礦機的計算能力是普通計算機的幾千倍,但是內存訪問方面的性能差距遠沒有這么大,所以能設計出一個對內存要求很高的Puzzle,就能起到遏制ASIC芯片的作用。
?
4.LiteCoin
LiteCoin(萊特幣),曾經是市值僅次于比特幣的第二大加密貨幣,其Puzzle是基于Scrypt(一個對內存要求很高的哈希函數(shù),以前用于計算機安全領域,跟密碼相關)。其具體設計思想是,開一個很大的數(shù)組,然后按照順序填充一些偽隨機數(shù),比如說有一個種子節(jié)點,seed的值通過一些運算,算出一個數(shù)來,填在第一個位置,然后后面每個位置都是前一個位置的值取哈希得到的,如圖。

偽隨機數(shù)是說取哈希值后的值是不知道的,不可能真的用隨機數(shù),不然就沒法驗證。填充完后,里面的數(shù)有前后依賴關系,是從第一個數(shù)依次算出來的,需要求解這個puzzle的時候,按照偽隨機數(shù)的順序從數(shù)組當中讀取一些數(shù),每次讀取的位置跟前一個數(shù)相關,如圖。

(1)LiteCoin的好處
如果這個數(shù)組開的足夠大的時候,挖礦的礦工就是Memory Hard,因為如果不保存數(shù)組,那么挖礦的計算復雜度會大幅度上升,需要從頭計算。所以要想高效地挖礦,這個內存區(qū)域是需要保存的,有的礦工可能保存一部分內存區(qū)的內容,如,只保留數(shù)組中奇數(shù)位置的元素,這樣數(shù)組可以少一半,用到偶數(shù)位置的數(shù)的話,要根據另外一半算一下,計算復雜度會提高一點,但是內存量可以減小一半,叫做time-memory trade off。
該設計的核心思想:不能僅僅進行運算,增加其對內存的訪問,從而實現(xiàn)對ASIC芯片不友好,普通計算機能參與的。
?
(2)LiteCoin的缺點
LiteCoin的優(yōu)點是對于礦工挖礦的時候是memory hard,缺點是對輕節(jié)點來說也是memory hard。設計puzzle的一個原則:difficult to solve, but easy to verify。這個問題在于驗證這個puzzle需要的內存區(qū)域跟求解需要的區(qū)域幾乎是一樣,輕節(jié)點驗證的時候也需要保存數(shù)組,否則計算復雜度也會大幅度提高。對于scrypt早期計算機的安全領域,不存在輕節(jié)點驗證這個問題,但對于區(qū)塊鏈來說是不行的。
?
(3)LiteCoin的實踐
萊特幣在真正使用的時候,這個內存區(qū)域不敢設置的太大,比如說設一個1G的數(shù)組,這對于計算機來說是不大的,但是如果是一個手機上的app,1G的內存可能就太大了。因為這個原因,實際萊特幣在使用的時候,這個數(shù)組只有128Ks,連1M都不到,就是為了照顧輕節(jié)點。
當初萊特幣在發(fā)行的時候,目標不僅僅是ASIC resistance,還是GPU resistance,就是挖礦最好連GPU都不要用,都用普通的CPU挖礦就行了。結果后來就出現(xiàn)GPU挖礦的,再后來就出現(xiàn)用ASIC芯片挖礦的,實踐證明萊特幣要求的128k內存不足以對ASIC芯片的生產和設計帶來實際上的障礙。
?
萊特幣跟比特幣的另一個區(qū)別是萊特幣的出塊速度是比特幣的4倍,他的出塊間隔是2min30s,而不是10min,除此之外,這兩種加密貨幣基本上是一樣的。
?
二、以太坊的工作量證明
1.Memory hard mining puzzle
以太坊也是用一種Memory hard mining puzzle,具體設計上,跟萊特幣有很大的不同。 以太坊用的是兩個數(shù)據集,一大一小,小的是16M cache,大的數(shù)據集是一個1G 的dataset(DAG),這1G的數(shù)據集是從16M的cache生成出來的。
為什么設計成一大一小的兩個數(shù)據集呢?
就是為了便于驗證。輕節(jié)點只要保存16M cache就行了,只有需要挖礦的礦工才需要保存1G的dataset。
?
2.基本思想
16MB的小Cache的生成方式跟LiteCoin中數(shù)組的生成方式是比較類似的。
通過Seed進行一些運算獲得第一個數(shù),后每個數(shù)字都通過前一個位置的值取哈希獲得。
(不同):
萊特幣:直接從數(shù)組中按照偽隨機順序讀取一些數(shù)據進行運算
以太坊:先生成一個更大的數(shù)組(注:以太坊中這兩個數(shù)組大小并不固定,因為考慮到計算機內存不斷增大,因此該兩個數(shù)組需要定期增大)
?
大的DAG生成方式:
如第一次讀取A位置數(shù)據,對當前哈希值更新迭代算出下一次讀取位置B,再進行哈希值更新迭代計算出C位置元素。如此來回迭代讀取256次,最終算出一個數(shù)作為DAG中第一個元素,如此類推,DAG中每個元素生成方式都依次類推。
?
以太坊挖礦過程:【太坊設計的挖礦算法(Ethash)】
根據區(qū)塊block header和其中的nonce值計算一個初始哈希,根據其映射到某個初始位置A,讀取A位置的數(shù)及其相鄰的后一個位置A’上的數(shù),根據該兩個數(shù)進行運算,算得下一個位置B,讀取B和B’位置上的數(shù),依次類推,迭代讀取64次,共讀取128個數(shù)。最后算出一個哈希值來,跟挖礦難度的目標域值比較是不是符合難度要求,若不符合就重新更換Nonce,因為換了Nonce之后,第一次算的那個哈希值就變了,然后重復以上操作直到最終計算哈希值符合難度要求或當前區(qū)塊已經被挖出。
每次讀取大數(shù)據集中兩個相鄰位置的哈希值,這兩個哈希值其實并沒有什么聯(lián)系,他們雖然位置相鄰,但是生成的過程是獨立的,每個都是由前面那個16M的cache中的256個數(shù)生成的,而且256個數(shù)的位置是按照偽隨機數(shù)的順序產生的,這個是構造大數(shù)據集的一個特點,每個元素獨立生成,這才給輕節(jié)點的驗證提供了方便,所以每次讀取的相鄰兩個位置的哈希值是沒有什么聯(lián)系的。
?
?3.ethash算法偽代碼
(1)生成16M cache。
def mkcache(cache_size,seed):
??? ??o = [hash(seed)]
??? ??for i in range(1,cache_size):
??????? ??o.append(hash(o[-1]))
return o
cache中每個元素都是64個字節(jié)的哈希值,生成的方法與萊特幣類似(第一個元素是Seed的哈希,后面每個元素是前一個的哈希),哈希的內容每隔3萬個區(qū)塊會變化一次,Seed每隔3萬個區(qū)塊會發(fā)生變化,然后重新生成cache中的內容,同時cache的容量要增加原始大小的1/128,也就是16M的1/128=128K。
?
(2)通過cache來生成dataset中的第i個元素。
def calc_ dataset item(cache,i):
??? ??cache size = cache.size
??? ??mix hash(cache[i % cache_size] ^ i)
??? ??for j in range(256):
???? ?????cache_ index =?get_ int_ from_item(mix)
?????? ???mix = make_item(mix,cache[cache_index % cache_size])
?? ???return hash(mix)
先通過cache中的第i % cache size個元素生成初始的mix。因為兩個不同的dataset元素可能對應向一個cache中的元素為了保證每個初始的mix都不同,注意到i也參與了哈希計算。
【自己定義的兩個函數(shù)】,get_int_from_item函數(shù):用當前算出來的哈希值求出下一個要讀取的位置,make_item函數(shù):用cache中這個位置的數(shù)和當前的哈希值計算出下一個哈希值,這樣迭代256輪,最后得到一個64字節(jié)的哈希值,作為大數(shù)據集中的第 i 個元素。
?
(3)此函數(shù)不斷調用calc_dataset_item函數(shù)來依次生成dataset中全部full_size個元素。
def calc_dataset(full_size, cache);
return [calc_dataset_item(cache,i) for i in range(full_size)]
?
(4)礦工用來挖礦的函數(shù)。
def hashimoto_full(header,nonce,full_size,dataset):
?? ??mix = hash(header,nonce)
?? ??for i in range(64) :
????????dataset_index = get_int_from item(mix) % full_size
????????mix = make_item(mix,dataset[dataset_index])
????????mix = make_item(mix, dataset[dataset_index + 1])
??? ??return hash(mix)
?
礦工用來挖礦的函數(shù),有四個參數(shù):
header:當前要生成的區(qū)塊的塊頭,以太坊和比特幣的挖礦只用到塊頭的信息【輕節(jié)點只下載塊頭,就可以驗證這個區(qū)塊是否符合挖礦的難度要求】
nonce:當前嘗試的nonce值
full_size:大數(shù)據集中元素的個數(shù),每3萬個區(qū)塊會增加一次,增加原始大小的1/128也就是1G的1/128=8M
Dataset:前面生成的大數(shù)據集
?
(5)輕節(jié)點用來驗證的函數(shù)。
def hashimoto light(header,nonce,full_size,cache):
??? ?mix = hash(header, nonce)
???? for i in range(64):
??????? ?dataset_index = get_int _from item(mix) % full_size
???????? mix = make_item(mix,calc_ dataset _item(cache,dataset_index))
???????? mix = make_item(mix,calc_dataset_item(cache,dataset_index + 1))
??? ?return hash(mix)
?
header:這個區(qū)塊的塊頭【礦工挖礦函數(shù)里是要生成的區(qū)塊的塊頭】
nonce:包含在這個塊頭里的nonce,是發(fā)布這個區(qū)塊的礦工選好的
輕節(jié)點的任務是驗證這個nonce是否符合要求,驗證用的是16M的cache,也就是最后的參數(shù)cache。full_size仍是大數(shù)據集的元素個數(shù),驗證的過程也是64輪循環(huán),看上去與挖礦的過程類似。
?
每次從當前的哈希值算出要讀取的元素的位置【指在在大數(shù)據集中的位置】,但是輕節(jié)點并沒有這個大數(shù)據集,所以要從cache中生成大數(shù)據集中這個位置的元素,我們前面說過大數(shù)據集中每個元素都可以獨立生成出來。
?
為何驗證只需保存cache,而礦工需要保存大數(shù)組DAG?由于礦工需要驗證非常多的nonce,如果每次都要從16M的cache中重新生成的話,效率太低了。而且這里面有大量的重復計算【隨機選取的dataset的元素中有很多是重復的,可能是之前嘗試別的nonce時用過的】。所以,礦工采取以空間換時間的策略,把整個dataset保存下來。輕節(jié)點由于只驗證一個nonce,驗證的時候就直接生成要用到的dataset中的元素就行了。
?
(6)礦工挖礦的主循環(huán)
def mine(full size,dataset,header,target):
?? ??nonce = random.randint(0,2**64)
??????while hashimoto_full(header,nonce,full_size,dataset) > target:
??????? ??nonce = (nonce + 1) % 2**64
??????return nonce
這個函數(shù),其實是不斷嘗試nonce的過程,這里的target就是挖礦的難度目標,跟比特幣類似,也是可以動態(tài)調整的,nonce的可能取值是從0-2的64次方,對每個nonce用前面講的那個函數(shù)算出一個哈希值,看看是不是小于難度目標,如果不行的話,就再試下一個nonce。
?
4.實際效果
目前為止,以太坊挖礦主要還是以GPU為主,用ASIC礦機的很少,所以從這一點來說,他比萊特幣來說要成功,起到了ASIC Resistance的作用,這個跟以太坊的挖礦算法需要的大內存是很有關系的,這個挖礦算法就是ethash。礦工挖礦需要1G的內存,跟萊特幣的128K比差別非常大,即使是16M的cache跟128K比差距也很大。而且這個大小還會定期增長。
以太坊沒有出現(xiàn)ASIC礦機還有另一個原因,以太坊從很早就計劃要從工作量證明轉移到權益證明,即PoW(Proof of Work)→PoS(Proof of Stake)。所謂的權益證明,就是按照所占的權益進行投票來形成共識,權益證明是不挖礦,就類似于股份公司按照股票多少來進行投票。這對于ASIC礦機的廠商來說是個威脅,因為ASIC芯片的研發(fā)周期很長,且研發(fā)的成本也很高,將來以太坊轉入權益證明之后,投入的研發(fā)費用就白費了。其實到目前為止,以太坊是基于工作量證明,以太坊很早就說要轉入權益證明,但是轉移的時間點一再往后推遲,到現(xiàn)在也沒轉過來,但是他不停的宣稱要這么做,所以要想達到ASIC Resistance一個簡單的辦法就是不斷地嚇唬大家:大家注意哦,下面要搞權益證明就不挖礦了,所以你就不要設計ASIC礦機了,你設計出來到時候也沒用了,因為要設計一年嘛,一年以后,我們就不挖礦了。
?
5.預挖礦(pre-mining)
以太坊中采用了預挖礦:在當初發(fā)行貨幣的時候,預留一部分貨幣給以太坊的開發(fā)者。
比特幣就沒有采用pre-mining的模式,所有的比特幣都是挖出來的,只不過早期的挖礦的難度低。與pre-mining相關的一個概念叫pre-sale(就是把pre-mining預留的那些幣通過出售的方法來換取一些資產用于加密貨幣的開發(fā)工作),有點類似于眾籌。
?
?
20-ETH難度調整
為了維持出塊時間在十分鐘左右,比特幣每隔2016個區(qū)塊會調整一下挖礦難度。以太坊是每個區(qū)塊都有可能調整挖礦難度,調整的方法也比較復雜。
?
1.難度調整公式

H是指當前這個區(qū)塊,Hi是這個區(qū)塊的序號,D(H)是這個區(qū)塊當前的難度。
max括號里的是第一部分【基礎部分】,為了維持出塊時間大概在十五秒左右,∈是第二部分【難度炸彈】,為了向權益證明過渡。


難度炸彈這部分的取值,是從指數(shù)形式增長的。以太坊剛剛上線不久的時候,區(qū)塊號都比較小,難度炸彈這部分算出來的值是很小的,基本上可以忽略不計,所以難度調整主要還是由基礎部分(系統(tǒng)中的出塊時間)來決定的。隨著區(qū)塊號越來越大,難度炸彈的威力開始顯現(xiàn)。
本來擔心大家不愿意轉,現(xiàn)在變成了想轉也沒法轉。以太坊最后決定計算難度炸彈的時候,把區(qū)塊號回退三百萬個區(qū)塊,即把真實的區(qū)塊號減去三百萬,算出一個假的區(qū)塊號,然后再算難度炸彈,給權益證明的上限爭取了一些時間。

?大概是370萬個區(qū)塊左右,難度炸彈的威力開始指數(shù)上升,到上面這個尖峰(就是以太坊決定回調這個難度炸彈的時候),減了三百萬個區(qū)塊,這個難度炸彈的取值一下就掉下來了,看上去好像是個平的直線,其實也是在增長。
?
二、以太坊發(fā)展的四個階段
我們目前處于第三個階段中的拜占庭階段,難度炸彈回調就是在拜占庭階段進行的。

以太坊系統(tǒng)在難度回調的同時,把出塊獎勵從5ETH降到了3ETH(如果不調的話,對于回調之前的礦工是不公平的),而且要維護總供應量的穩(wěn)定,挖礦變得容易,就應該相應將出塊獎勵減少一些。比特幣當中每隔一段時間出塊獎勵減半,以太坊中是沒有的,這次是一次性的。
?
三、代碼實現(xiàn)
1.Byzantium階段,挖礦難度調整的代碼
輸入是父區(qū)塊的時間戳和難度,計算出當前挖的這個區(qū)塊的難度。代碼中的BigTime就是當前區(qū)塊的時間戳,bigParentTime就是父區(qū)塊的時間戳。

2.計算基礎部分的難度調整

第一行就是把當前時間戳減去父區(qū)塊的時間戳算出出塊時間,然后第二行除以9向下取整。判斷一下是不是有叔父區(qū)塊,有的話,是用2減去前面這個數(shù)x,沒有的話用1減去前面這個數(shù)x,然后接下來跟負的99相比,接下來算的是難度調整的力度,父區(qū)塊的難度除以這個DifficultyBoundDivisor【實際上就是2048】,然后跟前面算出的系數(shù)相乘,加到父區(qū)塊的難度上面去,基礎部分的難度調整有一個下限,難度再小也不能小于那個D0,這個MinimumDifficulty就是那個D0=131072。
?
3.難度炸彈的計算

?為什么不是減去3000000,而是2999999?因為這里判斷的父區(qū)塊號,而公式中是根據當前區(qū)塊來算的。
?
四、以太坊實際統(tǒng)計統(tǒng)計
1.以太坊中的難度統(tǒng)計
到尖峰的位置就是以太坊決定回滾難度炸彈,回滾三百萬個區(qū)塊,挖礦難度一下就掉下來了,目前以太坊的挖礦難度基本上是區(qū)域穩(wěn)定的。

?
2.出塊時間統(tǒng)計
出塊時間穩(wěn)定在15s左右。

3.兩個區(qū)塊
difficulty為當前區(qū)塊難度,total difficulty為當前區(qū)塊鏈上所有區(qū)塊難度相加。
最長合法鏈等同于最難合法鏈(難度最大合法鏈)。每個區(qū)塊的難度,反應的是挖出這個區(qū)塊所需要的工作量。一般來說,靠后的區(qū)塊挖出來需要的工作量比較大。

21-權益證明
比特幣和以太坊目前都是基于工作量的證明,這種共識機制的一大典型缺點就是浪費電。
以太坊平均每個交易能耗遠遠低于比特幣,由于比特幣系統(tǒng)中,出塊時間過長導致的。
?
系統(tǒng)給予出塊獎勵的目的是激勵礦工參與區(qū)塊鏈系統(tǒng)維護,進行記賬,而挖礦本質上是看礦工投入資金來決定的(投入資金買設備->設備決定算力->算力比例決定收益)。
那么,為什么不直接拼“錢”呢?為什么不大家都將錢投入到系統(tǒng)開發(fā)和維護中,而根據投入錢的多少來進行收益分配呢?這就是權益證明的基本思想。
?
權益證明
一般來說,采用權益證明的貨幣,會先預留一些貨幣給開發(fā)者,開發(fā)者也會出售一些貨幣換取開發(fā)所需要的資金,在系統(tǒng)進入穩(wěn)定狀態(tài)后,每個人都按照持有貨幣的數(shù)量進行投票。
優(yōu)點:
①省去挖礦過程,避免了因此產生的能耗和對環(huán)境影響,減少了溫室氣體的排放。
②發(fā)動攻擊的資源只能從加密貨幣系統(tǒng)內部得到【維護區(qū)塊鏈安全的資源形成閉環(huán)】。POW中維護其安全的資源需要通過現(xiàn)實中流通的貨幣購買礦機等設備進去區(qū)塊鏈的,攻擊者只需要外部聚集足夠資金就可以攻擊成功(小型幣種很容易被攻擊,也就是在搖籃里就扼殺掉)。
?
權益證明和工作量證明混合模型:
POS與POW并不互斥。有的加密貨幣采用的是一種混合模型。持有的幣越多,挖礦的難度就越小,根據持有的這個幣的權益降低調整你的挖礦難度(實際并不能這么簡單設置,因為會導致“旱的旱死,澇的澇死”,需要添加一定限制)。所以,有的加密貨幣要求你投入的幣會被鎖定一段時間,不能重復使用,這種情況叫做Proof of Deposit。
?
權益證明這么好,為什么實際中并未得到大規(guī)模應用呢?
仍存在很多挑戰(zhàn),如“雙邊下注”:

?區(qū)塊鏈系統(tǒng)產生了分叉,采用權益證明的方法就是所有持幣者對這兩個區(qū)塊投入幣進行投票,從而決定哪一個區(qū)塊成為最長合法鏈上的區(qū)塊。假如有一個人,在A和B同時進行了下注。最終A區(qū)塊勝出,那么他能夠獲得A區(qū)塊相應收益,而在B區(qū)塊進行投票放入的“籌碼”也會被退還,這也就導致其每次都能獲得收益。
一個人可以擁有多個賬戶,無法強迫一個人一次只能投向一個區(qū)塊。越有錢的人,通過“雙邊下注”得到的收益也就越多。
?
以太坊擬采用的權益證明
以太坊中,準備采用的權益證明協(xié)議為Casper the Friendly Finality Gadget(FFG),該協(xié)議在過渡階段是要和POW結合使用的。為工作量證明提供Finality,F(xiàn)inality是一種最終的狀態(tài),包含在Finality中的交易不會被取消,單純基于工作量證明的交易是有可能被回滾的。
在比特幣系統(tǒng)中,為了防范分叉攻擊,一個交易在其獲得6次確認(其后跟著6個區(qū)塊)后認為該區(qū)塊安全。但實際上,這種安全只是概率意義上的安全,仍然可能會被擁有強大算力的用戶在其前面發(fā)動分叉攻擊進行回滾。
Casper協(xié)議引入一個概念:Validator(驗證者),一個用戶想要成為Validator,需要上交一筆“保證金”,這筆保證金會被系統(tǒng)鎖定。Validator的職責是推動系統(tǒng)達成共識,投票決定哪一條鏈成為最長合法鏈,投票權重取決于保證金數(shù)目。
?混用的時候還是有人挖礦的,挖礦每挖出100個區(qū)塊就作為一個epoch,然后決定能不能成為Finality,要進行一些投票,第一輪投票是Prepare Message,然后第二輪是Commit Message,Casper規(guī)定每一輪投票都要得到2/3的驗證者才能通過(按照保證金的金額大小來算)。實際系統(tǒng)當中不再區(qū)分這兩個Message,而且把這個epoch從原來的100個區(qū)塊減少到50個區(qū)塊,變成了每50個區(qū)塊就是一個epoch,每個epoch只用一輪投票的就行了,這一輪投票對于上一個epoch來說是個Commit Message,對于下一個來說是一個Prepare Message,要連續(xù)兩輪投票,兩個epoch都得到2/3以上的多數(shù),才算有效。

礦工挖礦會獲得出塊獎勵,而驗證者也會得到相應獎勵。當然,為了防止驗證者的不良行為,規(guī)定其被發(fā)現(xiàn)時要受到處罰。如“行政不作為”,扣掉部分保證金;如“亂作為”,沒收全部保證金。沒收的保證金被銷毀,從而減少系統(tǒng)中貨幣總量。驗證者存在“任期”,在任期結束后,進入“等待期”,在此期間等待其他節(jié)點檢舉揭發(fā)是否存在不良行為,若通過等待期,則可以取回保證金并獲得一定投票獎勵。
?
?
思考
(1)Casper 協(xié)議可以給挖礦完成一個區(qū)塊鏈的某一種狀態(tài)做一個檢查點,做一個check point,那這個check point是不是絕對安全的?通過這個驗證者投票達成的Finality有沒有可能被推翻?
包含在Finality里的交易是不會被推翻的。單純是有惡意的礦工,無論他算力有多強,如果沒有驗證者作為同伙是不可能推翻的。攻擊成功的情況:有大量【至少1/3(該協(xié)議規(guī)定超過2/3才有效)】的驗證者兩邊下注。
?
(2)以太坊是要逐步從工作量證明過渡到權益證明。隨著時間的推移,挖礦得到的獎勵是越來越少的,權益證明得到的獎勵是越來越多的,最后達到完全不用挖礦的境界。以太坊為什么從一開始就不用權益證明呢?
權益證明仍然存在缺陷,但工作量證明已經得到了事實檢驗,該機制較為成熟。
目前,EOS加密貨幣,即“柚子”,2018年上線,就是采用權益證明的共識機制,其采用的是DPOS:Delegated Proof of Stake。該協(xié)議核心思想是通過投票選21個超級節(jié)點,再由超級節(jié)點產生區(qū)塊。但目前,權益證明仍然處于探索階段。
?
?
22-ETH-智能合約
一、什么是智能合約
1.智能合約的本質是運行在區(qū)塊鏈上的一段代碼,代碼的邏輯定義了智能合約的內容。
2.智能合約的賬戶保存了合約當前的運行狀態(tài)
(1)balance:當前余額;
(2)nonce:交易次數(shù);
(3)code:合約代碼;
(4)storage:存儲,數(shù)據結構一是一顆MPT;
3.Solidity是智能合約最常用的語言,語法上與JavaScript很接近。
?
1.智能合約的創(chuàng)建
智能合約由一個外部賬戶發(fā)起一個轉賬交易,轉給0x0這個地址,然后把這個要發(fā)布合約的代碼放到data域里面。合約的代碼要編譯成bytecode,然后在EVM上運行。JVM,Java Virtual Machine,目的是增強可移植性。EVM類似這種設計思想,通過加一層虛擬機,對智能合約的運行提供一個一致性平臺。EVM有時叫做Worldwide Computer(全世界的一個計算機),EVM的尋址空間非常大,為256位,unsigned int就是256位。
?
比特幣設計理念是簡單,腳本語言的功能很有限,如:不支持循環(huán)。以太坊要提供一個圖靈完備的編程模型,Turing-complete Programming Mode。問題:當一個全節(jié)點收到一個對智能合約的調用,怎么知道這個調用執(zhí)行起來會不會導致死循環(huán)?
沒有辦法,這實際上是一個Halting Problem停機問題,是不可解的。不是NPC的,NPC的問題可解,但沒有多項式時間的解法。從理論上可以證明不存在這樣一個算法,能夠對任意給定的輸入程序判斷出這個程序是否會停機。這時我們會把這個問題推給發(fā)起交易的那個賬戶,以太坊引入了汽油費機制,即發(fā)起對智能合約的調用時需要支付相應的汽油費。
?
二、智能合約的代碼結構

1.Solidity語言
Solidity是面向對象的編程語言,定義了很多狀態(tài)變量。Solidity是強類型語言,address類型是Solidity語言所特有的。
上述代碼段中的event事件,是用來記錄日志的。事件①HighestBidIncreased,拍賣的最高出價增加了,記錄最新高價的參數(shù)(address bidder),金額是amount;事件②是Pay2Beneficiary,參數(shù)是贏得拍賣的人的地址及最后出價amount。
Solidity語言跟其他普通編程語言相比,有一些特別之處。如:mapping,mapping是一個哈希表,保存了從地址到unit的一個映射。Solidity語言中哈希表不支持遍歷,如果想遍歷,需要記錄哈希表中有哪些元素,用bidders數(shù)組記錄下來,Solidity語言中的數(shù)組可以是固定長度的,也可以是動態(tài)改變長度的。上述代碼是一個動態(tài)改變長度的數(shù)組,如果要在數(shù)組里增加一個元素,就用push操作,即bidders.push(bidder):新增加一個出價人在數(shù)組的末尾;想知道這個數(shù)組有多少個元素,可以用bidders.length。
Solidity語言中定義構造函數(shù)有兩種方法,構造函數(shù)只能有一個。一種方法就是像c++構造函數(shù)一樣,定一個與contract同名的函數(shù),這個函數(shù)可以有參數(shù),但是不能有返回值。實際上新版本Solidity語言更推薦用這里使用的方法:用一個constructor來定義一個構造函數(shù),這個構造函數(shù)只有在合約創(chuàng)建的時候會被調用一次。
最后是三個成員函數(shù),三個函數(shù)都是public,說明其他賬戶可以調用這些函數(shù)。
?
2.bid函數(shù)
在bid函數(shù)中,有一個payable(另外兩個函數(shù)都沒有),以太坊中規(guī)定如果這個合約賬戶要能接收外部轉賬的話,那么必須標注成payable。
拍賣規(guī)則是,調用bid函數(shù)時要把拍賣的出價100個以太幣也發(fā)送過去,存儲到這個合約里,鎖定到拍賣結束。避免有人憑空出價。所以bid函數(shù)要有能夠接收外部轉賬的能力,所以才標注一個payable。withdraw函數(shù)就沒有payable,拍賣結束了,沒贏得拍賣的人可以調用withdraw把鎖定在智能合約里的以太幣取回來。
?
3.fallback()函數(shù)
function()public [payable]{……}
這個函數(shù)沒有參數(shù)、返回值、函數(shù)名,是個匿名函數(shù),fallback這個關鍵字也沒有出現(xiàn)在這個函數(shù)名里。調用這個合約的時候,A調用B這個合約,要在轉賬交易的data域說明你調用的是B當中的哪個函數(shù)。若沒說調用哪個函數(shù),或者調用的函數(shù)不存在,就調用這個fallback()函數(shù)。
如果fallback()函數(shù)需要有接收轉賬的能力的話,也需要寫成是payable,一般情況下,都是寫上payable的,如果合約賬戶沒有任何函數(shù)標識為payable,那么這個合約沒有任何能力接受外部的轉賬。
fallback()函數(shù)不是必須定義的。
fallback()函數(shù)和payable都是在合約定義的時候寫的,我給你轉賬時候不用寫payable,也不用寫fallback()。
轉賬金額可以是0,但汽油費是要給的,轉賬金額是給收款人的,汽油費是給發(fā)布這個區(qū)塊的礦工的,如果汽油費不給的話,礦工不會把你這個交易打包發(fā)布到區(qū)塊鏈。
?
汽油費
當一個全節(jié)點收到一個對智能合約的調用,先按照最大汽油費收取,從其賬戶一次性扣除,再根據實際執(zhí)行情況,多退少補(汽油費不夠會引發(fā)回滾,而非簡單的補齊)。
以太坊中存在gaslimit,通過收取汽油費保障系統(tǒng)中不會存在對資源消耗特別大的調用。但與比特幣不同,比特幣直接通過限制區(qū)塊大小1MB保障對網絡資源壓力不會過大。而以太坊中,每個礦工都可以以前一個區(qū)塊中gaslimt為基數(shù),進行上調或下調1/1024,從而,通過絕大多數(shù)區(qū)塊不斷上下調整,保證得到一個較為理想化的gaslimt值。最終整個系統(tǒng)的gaslimt就是所有礦工希望的平均值。
?
為什么要引入汽油費?
在比特幣系統(tǒng)中,交易較簡單【僅轉賬】,可通過交易的字節(jié)數(shù)衡量出交易所需要消耗的資源多少。以太坊中引入了智能合約,而智能合約邏輯很復雜,其字節(jié)數(shù)與消耗資源數(shù)并無關聯(lián)。在block header中包含了gaslimit,其并非將所有交易的消耗汽油費相加,而是該區(qū)塊中所有交易能夠消耗的資源的上限。
?
錯誤處理:
(1)交易執(zhí)行完之后,發(fā)現(xiàn)汽油費沒有達到當初的GasLimit,那么多余的汽油費會被退回到這個賬戶里。若交易執(zhí)行到一半,GasLimit已經用盡,該合約的執(zhí)行要退回到開始執(zhí)行之前的狀態(tài),這是一種錯誤處理,這個時候已經消耗掉的汽油費是不退的。否則會有惡意的節(jié)點發(fā)動delayous service attack,發(fā)布一個計算量很大的合約,然后不停的調這個合約,每次調的時候給的汽油費都不夠,最后汽油費還會退回來,那么對這個節(jié)點來說沒有什么損失,但礦工白白浪費了很多資源。
(2)assert語句和require語句,都是用來判斷某種條件,如果條件不滿足的話,就會導致拋出異常。assert語句一般來說是用于判斷某種內部條件,require語句一般用于判斷某種外部條件,如判斷函數(shù)的輸入是否符合要求。
function bid() public payable {
????//對于能接收以太幣的函數(shù),關鍵字payable是必須的。
????//拍賣尚未結束
????require(now <= auctionEnd);
}
當前時間now<=拍賣的結束時間auctionEnd,則繼續(xù)執(zhí)行,否則,會拋出異常。第三個語句是revert(),無條件的拋出異常,如果執(zhí)行到revert語句,那么會自動回滾。早期版本里用throw語句,新版本建議改用revert這個語句。solidity當中沒有這種try-catch這種結構,有的編程語言像Java(用戶自己可以定義出現(xiàn)問題后怎么辦,他有這種try-catch)。
?
嵌套調用是否發(fā)生回滾,取決于調用方式。一個合約向一個合約賬戶直接轉賬,因為fallback函數(shù)的存在,仍有可能會引發(fā)嵌套調用。
?
?二、外部賬戶如何調用智能合約
調用智能合約其實跟轉賬是類似的。如A發(fā)起一個交易轉賬給B,如果B是一個普通的賬戶,那么這就是一個普通的轉賬交易,與比特幣轉賬交易是一樣的。如果B是一個合約賬戶的話,那么這個轉賬實際上是發(fā)起一次對B這個合約的調用,那么具體是調用合約中的哪個函數(shù),是在數(shù)據域data域中說明的,如圖。

三、一個合約如何調用另一個合約中的函數(shù)
1.直接調用

A這個合約就只是寫成log,event定義事件LogCallFoo,emit LogCallFoo():用emit這個操作來調用這個事件,作用就是寫一個log,對于程序的運行邏輯是沒有影響的。
B合約中,函數(shù)參數(shù)是一個地址(A合約的地址),然后把這個地址轉換成A這個合約的一個實例,然后調用其中的foo這個函數(shù)。
以太坊中規(guī)定一個交易只有外部賬戶才能夠發(fā)起,合約賬戶不能自己主動發(fā)起一個交易。這個例子當中需要有一個外部賬戶調用了合約B當中的這個callAFooDirectly函數(shù),然后這個函數(shù)再調用合約A當中的foo函數(shù)。
?
錯誤處理:直接調用的方式,一方產生異常會導致另一方也進行回滾操作。如果調用的合約在執(zhí)行過程中出現(xiàn)錯誤,那么會導致發(fā)起調用的合約也跟著一起回滾,如果在直接調用方法中A在執(zhí)行過程出現(xiàn)異常,B這個合約也跟著一起出錯。
?
?2.使用address類型的call()函數(shù)
funcsing:要調用函數(shù)的簽名,然后后面跟的是調用的參數(shù)。該方法與直接調用方法相比,區(qū)別是對于錯誤處理的不同。address.call()這種形式,如果在調用過程中,被調用的合約拋出異常,那么這個call函數(shù)會返回false,表明這個調用是失敗的,但發(fā)起調用的這個函數(shù)不會拋出異常,可以繼續(xù)執(zhí)行。

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

?
一、思考【挖礦與智能約合執(zhí)行】
1.假設某個全節(jié)點要打包一些交易到一個區(qū)塊里,其中有一些是對智能合約的調用,那么這個全節(jié)點應該先執(zhí)行完智能合約再挖礦,還是先挖礦獲得記賬權再執(zhí)行這些智能合約?
在區(qū)塊鏈中,如果有一筆轉賬交易發(fā)布上去,需要所有的全節(jié)點都執(zhí)行的,因為所有的全節(jié)點要同步狀態(tài),都要在本地執(zhí)行這個轉賬交易。比特幣系統(tǒng)也一樣,比特幣發(fā)布一個交易到區(qū)塊鏈上,也需要所有全節(jié)點都得執(zhí)行這個轉賬交易,以便更新UTXO。
全節(jié)點收到一個對合約的調用的時候,要一次性的先把這個調用,可能花掉的最大汽油費從發(fā)起這個調用的賬戶上扣掉。以太坊系統(tǒng)中存在三棵樹,即狀態(tài)樹、交易樹和收據樹,這三棵樹都是全節(jié)點在本地維護的數(shù)據結構,狀態(tài)樹記錄了每個賬戶的狀態(tài)包括賬戶余額。
汽油費是怎么扣的?全節(jié)點收到調用時,從本地維護的數(shù)據結構中將賬戶余額減掉就可以,若余額不夠,則這個交易不能執(zhí)行,一次性要按GasLimit把余額減掉。
智能合約執(zhí)行過程中任何對狀態(tài)的修改都是在改※本地※的數(shù)據結構,只有在合約執(zhí)行完了,而且發(fā)布到區(qū)塊鏈上之后,本地的修改才會變成外部可見的,才會變成區(qū)塊鏈上的共識。以太坊存在很多全節(jié)點,每個全節(jié)點都在本地做這個事情,執(zhí)行的智能合約可能不完全一樣(因為收到的交易可能執(zhí)行不完全一樣),如果某個全節(jié)點發(fā)布一個區(qū)塊,收到這個區(qū)塊之后,其他節(jié)點本地執(zhí)行的就扔掉了,要將這個新發(fā)布區(qū)塊里的交易再執(zhí)行一遍,更新本地的三棵樹。如果本來已經執(zhí)行一遍了,但沒有挖到礦,發(fā)布新區(qū)塊了還得執(zhí)行一遍,因為其他節(jié)點組裝的在本地候選區(qū)塊中包含的交易跟剛發(fā)布的那個交易中區(qū)塊里包含的交易不一定完全一樣,至少給出塊獎勵的那個地方肯定不一樣,所以沒有辦法,都是得要重新執(zhí)行一遍。 ???
以太坊挖礦是嘗試各種Nonce找到一個符合要求的,計算哈希的時候要用到Block Header的內容:Root,TxHash,ReceiptHash,是那三棵樹的根哈希值。所以得先執(zhí)行完這個區(qū)塊中的所有交易包括智能合約的交易,這樣才能更新這三棵樹,這樣才能知道這三個根哈希值,這樣這個Block Header的內容才能確定然后才能嘗試各個Nonce。
?
假設一個礦工費了半天勁執(zhí)行這些智能合約,消耗了本地的很多資源,最后我挖礦沒挖到,那該礦工能得到什么補償,能得到汽油費嗎?
設置汽油費的目的是對于礦工執(zhí)行這些智能合約所消耗的這些資源的一種補償,但是這種補償只有挖到礦的礦工才能得到,其他的礦工相當于陪跑。
?
2.會不會有的礦工不給汽油費,就不驗證?
為保障區(qū)塊鏈的安全,要求所有全節(jié)點要獨立驗證發(fā)布的區(qū)塊的合法性。
跳過驗證這個步驟,以后就沒法再挖礦了。因為驗證的時候是要把區(qū)塊的交易再執(zhí)行一遍,更新本地的三棵樹。如果不去驗證的話,本地三棵樹的內容沒有辦法更新(本地的這些狀態(tài)就不對了,算出的根哈希值發(fā)布出去之后別人認為是錯的)。
為什么要執(zhí)行才能更新狀態(tài)?因為發(fā)布的區(qū)塊里沒有這三棵樹的內容【不能將狀態(tài)樹的整個狀態(tài)發(fā)布到區(qū)塊鏈上,多且重復】。
?
在一個礦池中,存在驗證階段的“抄作業(yè)”情況,也就是全節(jié)點負責統(tǒng)一驗證,其他礦工就負責相信全結點的驗證情況。就是說,全節(jié)點分配給礦工的只是Pullze的內容,Pullze是根據區(qū)塊鏈更新得到的,礦工則不需要考慮這部分內容。
?
3.發(fā)布到區(qū)塊鏈上的交易是否都是成功執(zhí)行的?如果智能合約執(zhí)行過程中出現(xiàn)了錯誤,要不要也發(fā)布到區(qū)塊鏈上去?
執(zhí)行發(fā)生錯誤的交易也要發(fā)布到區(qū)塊鏈上去,否則汽油費扣不掉,光是在本地的數(shù)據結構上把他的賬戶扣了汽油費,是沒用的,拿不到錢,你得把區(qū)塊發(fā)布上去之后形成共識扣掉的汽油費才能成為你賬戶上的錢。所以發(fā)布到區(qū)塊鏈上的交易不一定都是成功執(zhí)行的,而且要告訴大家為什么扣汽油費,而且別人得驗證一遍,也要把這個交易執(zhí)行完一遍,看扣的是不是對的。
?
Receipt數(shù)據結構
前面說過那三棵樹,每個交易執(zhí)行完后形成一個收據,這個是這個收據的內容,Status這個域就是告訴你交易執(zhí)行的情況如何,如圖。

4.智能合約是不是支持多線程?
Solidity不支持多線程,他根本沒有支持多線程的語句,原因是以太坊是一個交易驅動的狀態(tài)機,這個狀態(tài)機必須是完全確定性的,即給定一個智能合約。所有全節(jié)點都得執(zhí)行同一組操作到達同一個狀態(tài),要進行驗證,若狀態(tài)不確定,三棵樹的根哈希值根本對不上。
多線程的問題是什么?多個核對內存訪問順序不同的話,執(zhí)行結果有可能是不確定的。除了多線程之外,其他可能造成執(zhí)行結果不確定的操作,智能合約也都不支持,最簡單的會導致執(zhí)行結果不確定的操作:產生隨機數(shù)。所以以太坊的智能合約沒有辦法產生真正意義下的隨機數(shù),他用的是一些偽隨機數(shù),否則,又會出現(xiàn)前面的問題,每個全節(jié)點執(zhí)行完一遍得到的結果都不一樣。
?
其不支持多線程,所以無法通過系統(tǒng)調用獲得系統(tǒng)信息,因為每個全節(jié)點環(huán)境并非完全一樣。因此只能通過固定的結構獲取。下圖分別為為其可以獲得的區(qū)塊鏈信息和調用信息。


msg.sender是發(fā)起調用的人,tx.origin是交易的發(fā)起者,不一樣。如有個外部賬戶A調用了一個合約C1,C1中有一個函數(shù)f1,f1又調用另外一個合約,里面的函數(shù)是f2。那么對于函數(shù)f2來說,msg.sender是這個合約,當前msg call這個調用,是這個合約發(fā)起的,而tx.origin是A這個賬戶,因為整個交易的發(fā)起者是A賬戶。
· msg.data就是數(shù)據域,在里面寫了調用哪些函數(shù)和這些函數(shù)的參數(shù)取值。msg.sig是msg.data的前四個字節(jié),就是函數(shù)標志符(調用的是哪個函數(shù))。now和block.timestamp是當前區(qū)塊的時間戳,智能合約里沒有辦法獲得很精確的時間,只能獲得跟當前區(qū)塊信息的一些時間。
?
以太坊地址類型
第一個,以wei為單位的地址類型的余額中,uint256并不是指其包含一個類型為uint256的參數(shù),而是指該變量本身為uint256類型的變量。
address.balance指的是address這個賬戶的余額
address.transfer(12345),當前合約向address地址中轉入12345Wei。后面的函數(shù)同理。

在以太坊中,轉賬有以下三種方法:
transfer在轉賬失敗后會導致連鎖性回滾,拋出異常;而send轉賬失敗會返回false,不會導致連鎖性回滾。call的方式本意是用于發(fā)動函數(shù)調用,但是也可以進行轉賬。
前兩者在調用時,只發(fā)生2300wei的汽油費,這點汽油費很少,只能寫一個log,而call的方式則是將自己還剩下的所有汽油費全部發(fā)送過去(合約調用合約時常用call,沒用完的汽油費會退回)。例如A合約調用B合約,而A不知道B要調用哪些合約,為了防止汽油費不足導致交易失敗,A將自己所有汽油費發(fā)給B來減少失敗可能性。

拍賣例子:
(1)主函數(shù)
pragma solidity ^0.4.21;
contract SimpleAuction {
????address??public ?beneficiary;? ?//拍賣受益人
????uint ?public ?auctionEnd; ? //結束時間
????address??public ?highestBidder; //當前的最高出價人
????mapping( address => uint) bids; ?//所有競拍者的出價
????address[] ?bidders; ? ?//所有競拍者
????//需要記錄的事件
????event HighestBidIncreased(address bidder,uint amount);
????event -Pay2Beneficiary( address - winner , uint amount);
????//以受益者地址 `_beneficiary` 的名義,創(chuàng)建拍賣,拍賣時間為 `_biddingTime` 秒。
????constructor(uint _biddingTime,address _beneficiary
????????)public {
????????beneficiary = _beneficiary;
????????auctionEnd = now + biddingTime;
}
(2)拍賣用的函數(shù)——bid函數(shù)
//對拍賣進行出價,隨交易一起發(fā)送的ether與之前已經發(fā)送的ether的和為本次出價。
function bid()public payable { ?//能接收以太幣的函數(shù),關鍵字payable是必須的。
????require(now <= auctionEnd); //拍賣尚未結束
????//如果出價不夠高,本次出價無效,直接報錯返回
????require(bids[msg.sender]+msg.value > bids[highestBidder]);
????//如果此人之前未出價,則加入到競拍者列表中
????if (!(bids[msg.sender] == uint(0))){
????????bidders.push(msg.sender);
????}
????//本次出價比當前最高價高,取代之
????highestBidder = msg.sender;
????bids[msg.sender] +=?msg.value;
????emit HighestBidIncreased(msg.sender,bids[msg.sender]);
}
要競拍就發(fā)起一個交易調用這個拍賣中合約的bid函數(shù),這個bid函數(shù)沒有參數(shù),競拍的時候出的價格其實是在msg.value寫的。邏輯是:①查拍賣是否結束,結束了還出價則拋出異常;②查該賬戶上一次的出價加上你當前發(fā)的以太幣是否為最高出價(bids是個哈希表,Solidity中哈希表的特點是,如果你要查詢的那個鍵值不存在,那么他返回默認值就是0,所以如果沒有出過價,第一部分就是0);③如果是第一次拍賣,把拍賣者的信息放到bidders數(shù)組里(Solidity哈希表不支持遍歷,要遍歷哈希表的話,要保存一下它包含哪些元素,然后記錄一下新的最高出價人是誰,寫一些日志之類的)。
?
(3)拍賣用的函數(shù)——拍賣結束的函數(shù)
//結束拍賣,把最高的出價發(fā)送給受益人,并把未中標的出價者的錢返還
function auctionEnd( ) public {
????//拍賣已截止
????require(now > auctionEnd); ?//該函數(shù)未被調用過
????require(!ended) ;
????//把最高的出價發(fā)送給受益人
????beneficiary.transfer(bids[highestBidder]);
????for (uint i = 0;i<bidders.length;i++){
????????address bidder = bidders[i];
????????if(bidder == highestBidder) continue;
????????bidder.transfer(bids[bidder]);
????}
????ended = true;
????emit AuctionEnded(highestBidder, bids[highestBidder]);
}
①查拍賣是否已結束,如果拍賣還沒有結束,調用這個函數(shù)就是非法的,會拋出異常;②判斷這個函數(shù)是不是已被調過,如果已經被調過了,就不用再調一遍了;如果沒有,首先把這個金額給這個beneficiary,beneficiary.transfer是當前這個合約把這個金額給這個beneficiary轉過去,最高出價人的錢是給受益人了;③那些剩下的沒有競拍成功的用一個循環(huán),把這個金額退回給這個bidder,然后標明一下,這個函數(shù)已經執(zhí)行完了寫一個log。
?
(4)上面智能合約存在的問題
智能合約是不可篡改的,有bug也沒法改。
auctionEnd這個函數(shù)必須要某個人調用才能執(zhí)行,這也是Solidity語言跟其他編程語言不同的一個地方,沒法自動執(zhí)行。可能是拍賣的受益人beneficiary,也可能是參與競拍沒有成功的人去調用,如果兩個人都調用auctionEnd,礦工在執(zhí)行的時候把第一個調用執(zhí)行完了,第二個就不再執(zhí)行了(因為第一個執(zhí)行完之后,ended就是true了,沒有并發(fā)執(zhí)行)。
?
假設有一個人通過這樣的一個合約賬戶參與競拍,會有什么結果?
pragma solidity ^0.4.21;
import "./simpleAuctionV1.sol";
contract hackV1 {
????function?hack_bid(address addr) payable public {
????????simpleAuctionV1 sa = simpleAuctionV1(addr);
????????sa.bid.value(msg.value)();
????}
}
函數(shù)hack_bid,參數(shù)是拍賣合約的地址,然后把它轉成這個拍賣合約的一個實例,然后調用拍賣合約用的bid函數(shù),把這個錢發(fā)送過去。這是一個合約賬戶,合約賬戶不能自己發(fā)起交易,所以實際上得有一個黑客從他自己的外部賬戶發(fā)起一個交易,調用這份合約賬戶的hack_bid函數(shù),然后這個函數(shù)再去調用拍賣合約的bid函數(shù),把他自己收到的轉過來的錢(這個黑客外部賬戶轉過來的錢再)轉給這個拍賣合約中的bid函數(shù),就參與拍賣了。
這個auctionEnd,這個合約參與拍賣沒有問題,最后拍賣結束退款的時候會有什么問題?如圖紅框里的循環(huán)退款,退到合約賬戶上的錢會有什么情況,退到黑客合約賬戶上的錢會有什么情況?

黑客外部賬戶對這個合約來說是不可見的,拍賣合約能看到的只是這個黑客的合約,轉賬的時候沒有調用任何函數(shù),調用fallback函數(shù),而這個合約沒有定義fallback函數(shù),所以會調用失敗,拋出異常。transfer函數(shù)會引起連鎖式的回滾,就會導致這個轉賬操作是失敗的,會收不到錢。
如有20個人參與競拍,黑客合約排在第10個,最高出價人排在第16個,一開始執(zhí)行時先把錢轉給受益人了(轉賬實際上是你這個礦工或是全節(jié)點執(zhí)行到beneficiary.transfer時把相應賬戶的余額進行了調整)。這個合約當中無論是排在黑客合約前面還是后面,都是在改本地數(shù)據結構,只不過排在后面的bidder根本沒有機會來得及執(zhí)行,然后整個都回滾了,所以排在前面的這些轉賬并沒有執(zhí)行,就是改本地結構。如果都順利執(zhí)行完了,發(fā)布出去之后,別的礦工也把這個auctionEnd重頭到尾執(zhí)行一遍,也改它本地的數(shù)據結構跟你的能對得上就叫形成共識了,而不是說沒有一個轉賬交易的語句是產生一個新的交易寫到區(qū)塊鏈上,所以都收不到錢,沒有任何一個人能收到錢。
?
發(fā)起這個攻擊的有可能是故意搗亂,寫這樣一個程序讓大家都拿不到錢;也可能是這個人不懂,他就忘了寫fallback函數(shù)了,那出現(xiàn)這種情況怎么辦呢?
現(xiàn)在的問題是你已經把錢投進去,鎖里面了,怎么把它取出來的問題,出現(xiàn)這種情況是沒有辦法的。智能合約設計的不好的話,有可能把以太幣永久的鎖起來。以前有用智能合約鎖倉的,如要開發(fā)一個新的加密貨幣,然后pre-mining先預留一部分幣給開發(fā)者,這些幣都打到一個智能合約賬戶鎖三年,三年以后這些幣才能賣,這樣做為了大家一開始能集中精力開發(fā)加密貨幣。智能合約鎖倉是個常用的操作,在發(fā)布一個智能合約之前一定要多次測試。
?
如何解決?
那能不能在這個智能合約里留一個后門,用來修復bug,比如給合約的創(chuàng)建者超級用戶的權利。這樣做的前提是所有人都要信任這個系統(tǒng)管理員。
?
解決重入攻擊
網上競拍第二版:由投標者自己取回出價
//使用withdraw模式
function withdraw( ) public returns (bool) {
????//競拍成功者需要把錢給受益人,當前地址有錢可取,賬戶余額是否為正
require(now > auctionEnd); ?//拍賣已截止
????require(msg.sender != highestBidder);
????require(bids[msg.sender] > 0); ?
????uint amount = bids[msg.sender]; ?//賬戶余額
????if(msg.sender.call.value(amount)()) { //把賬戶余額轉給msg.sender
????????bids[msg.sender] = 0; ? //把賬戶余額清成0
????????return true;
????}
????return false;
}
//結束拍賣,把最高的出價發(fā)送給受益人
event Pay2Beneficiary( address winner,uint amount);
function pay2Beneficiary ()public returns(bool) {
????require(now > auctionEnd); ?? //拍賣已截止
????require(bids[highestBidder] > 0); ?//有錢可以支付,最高出價的金額大于零
????uint amount = bids[highestBidder];
????bids[highestBidder] = 0;
????emit Pay2Beneficiary(highestBidder,bids[highestBidder]);
????if(!beneficiary.call.value(amount)()){
????????bids[highestBidder] = amount;
????????return false;
????}
????return true;
}
?
(1)存在的問題
重入攻擊,如果有黑客寫了一個如下方程序會怎么樣?
pragma solidity ^0.4.21;
import "./simpleAuctionv2.sol";
contract HackV2 {
????uint stack = 0;
????function hack_bid( address addr) payable public {
????????simpleAuctionv2 sa = simpleAuctionv2(addr);
????????sa.bid.value(msg.value)();
????}
????function hack_withdraw(address addr) public payable{
????????SimpleAuctionv2(addr).withdraw();
????}
????function() public payable{
????????stack += 2;
????????//當前調用的剩余汽油,msg.gas還有6000個單位以上,調用棧的深度不超過500
????????if (msg.sender.balance >= msg.value && msg.gas > 6000 && stack < 500{
????????????SimpleAuctionV2(msg.sender).withdraw();
????????}
????}
}
?
這個hack_bid跟前面的那個黑客合約hack_bid合約是一樣的,通過調用拍賣bid函數(shù)參與競拍,hack_withdraw就在拍賣結束的時候調用withdraw函數(shù),把錢取回來,這兩個看上去好像都沒有問題,問題在于fallback函數(shù),他又把錢取了一遍。
在hack_withdraw調用withdraw函數(shù)的時候,執(zhí)行到“if(msg.sender.call.value(amount)())”會向黑客合約轉賬,這個msg.sender就是黑客的合約,把當初出價的金額轉給他,而在這個合約中,又調用了拍賣函數(shù)的withdraw函數(shù)“SimpleAuctionV2(msg.sender).withdraw();”,又去取錢,fallback函數(shù)這里的msg.sender就是這個拍賣合約,因為是拍賣合約把這個錢轉給這個合約的,這個左邊的拍賣合約執(zhí)行到if那里,再給他轉一次錢,注意這個清零的操作,把黑客合約賬戶清零的操作,只有在轉賬交易完成之后,才會進行,而前面if這個轉賬的語句已經陷入到了跟黑客合約當中的遞歸調用當中,根本執(zhí)行不到下面這個清零操作,所以最后的結果就是這個黑客一開始出價的時候給出了一個價格,拍賣結束之后,就按照這個價格不停地從這個智能合約中去取錢,第一次取得是他自己的出價,后面取得就是別人的錢了。
?
遞歸重復取錢,持續(xù)到什么時候會結束?有三種情況:
①這個拍賣合約上的余額不夠,不足以支持轉賬的語句
②汽油費不夠
③調用棧溢出了,所以黑客合約的fallback函數(shù)判斷一下這個拍賣合約的余額還足以支持轉賬,當前調用的剩余汽油,msg.gas還有6000個單位以上,調用棧的深度不超過500。那么就再發(fā)起一輪攻擊,那怎么辦呢?
?
(2)如何處理
最簡單的就是先清零再轉賬,就是Pay2Beneficiary的這種寫法,把highestBidder的賬戶余額清成零了(在bids哈希表里面的余額已經清成0了),然后再轉賬,轉賬如果不成功的話,再把余額恢復。實際上是對于可能跟其他合約發(fā)生交互的情況的一種經典的編程模式,就先要判斷條件,然后改變條件,最后再跟別的合約發(fā)生交互。
另一種修改方式:

不用call.value的形式轉賬,對比一下修改前后的兩段代碼(綠框的部分),把清零的位置提前了(先清零再轉賬);轉賬的時候用的是sender(transfer也可),sender和transfer一個共同的特點就是轉賬的時候發(fā)送過去的汽油費只有2300個單位,這個不足以讓接收的那個合約再發(fā)起一個新的調用,只夠寫一個log而已。

?智能合約中安全漏洞的例子:The DAO(2016年)、美鏈(2018年04月)。
?
23-ETH-TheDAO
一、TheDao
1.提出背景
重入攻擊在現(xiàn)實中真的發(fā)生過,造成了以太坊的分裂,可以說以太坊的歷史都被它改寫。
比特幣實現(xiàn)了去中心化的貨幣,以太坊實現(xiàn)了去中心化的合約,既然去中心化這么好,為什么不把所有的都改成去中心化,所以有人提出一個口號:decentralized everthing,DAO(Decentralized Automous Organization,去中心化的自治組織)就是在這個背景下產生的。
在區(qū)塊鏈上,DAO這個組織就是建立在代碼基礎上的,組織的規(guī)章制度是寫在代碼里的,通過區(qū)塊鏈的共識協(xié)議維護這種規(guī)章制度的正常執(zhí)行。
?
2.The DAO的工作原理
在2016年出現(xiàn)了一個致力于眾籌投資的DAO——The DAO,投資的錢是在區(qū)塊鏈上眾籌得到的,其本質是一個運行在以太坊上的智能合約。如果想要參與The DAO,可以把以太幣發(fā)給這個智能合約,然后換回The DAO的代幣。需要決定投資哪個項目的時候是大家投票決定的,手里的代幣越多,投票權中就越大。投資后如果有了收益,也是按照智能合約中的規(guī)章制度進行收益分配的。
The DAO工作原理有點像DAC(Decentralized Automous Corporation,去中心化的自治公司)。區(qū)別是:DAC是處于盈利目的的,DAO可以是處于非盈利性目的。雖然是Corporation,但是現(xiàn)實中不具有公司應有的法人地位,也就是董事長之類的職務。
The DAO只存活了3個月,問題在哪呢?假如投資者需要用錢了,想把以前投資的以太幣換回來,在The DAO的基金里,以拆分的方法實現(xiàn)。
?
3.拆分
拆分過程,就是split DAO(拆分DAO),這個拆分的方法并不僅僅是取回收益,還是建立子資金(chlid DAO)的方法。如果有一小部分人和其他人的投資理念不一樣,這種情況下,這一小部分人可以用拆分的方法成立自己的子資金,拆分的時候手中的代幣是要被收回的,換成相應數(shù)量的以太幣,然后就可以投想投的項目。極端情況下,單個投資者成立一個子資金,在子資金里就能把所有的錢投給自己,這是投資者取回投資和收益的唯一途經。
拆分的時候有7天的討論期,拆分之后有28天鎖定期,給了以太坊一個緩沖期。拆分的理念沒有錯,而且可以說是民主制度的進一步體現(xiàn),問題出現(xiàn)在splitDao實現(xiàn)上,如圖,他是先轉賬后扣除總資金,然后把賬戶清零,正確的操作順序是先清零后轉賬,黑客就用這個漏洞進行重入攻擊。
?
4.暴露問題

5.討論階段
針對這樣的重入攻擊,以太坊社區(qū)進行了激烈的討論,社區(qū)討論的補救措施大致分為兩類:
(1)回滾交易。成立的子基金有28天的鎖定期,所以黑客暫時還沒有辦法把錢取走,還有時間可以采取補救措施。通過回滾交易,不讓黑客得逞,以此保護廣大投資者的利益。(如果出了問題就回滾,就不是去中心化的);
(2)不需要采取補救措施。因為黑客沒有做錯,沒有違反法律。
以太坊有一部分人認為,不應該回滾交易,因為區(qū)塊鏈最重要的特性是不可篡改性。出問題的只是以太坊上的一個應用而已,The DAO是以太坊上的一個智能合約,以太坊沒有問題。以太坊有那么多的智能合約,如果每個合約出了問題都回滾的話那不就亂套了。
?
6.補救措施
Vitalik Buterin團隊認為因為該事件影響非常大,The DAO又占據了超過10%的以太幣,too big to fail,所以還是決定回滾了交易。如果就是一個小的智能合約出了問題,或者轉賬轉錯了,以太坊社區(qū)是不管的,開發(fā)團隊也是不管的。
如果使用分叉攻擊,從黑客最開始重入攻擊的區(qū)塊開始分叉,是不行的,因為還存在一些其他交易,如果要回滾必須精確定位到黑客盜取以太幣的交易,其他發(fā)布的正常交易不能受到影響,這是采取補救措施的一個原則。
?
具體的補救措施:
以太坊團隊對此制定兩步走戰(zhàn)略:①鎖定黑客賬戶;②清退The DAO基金上的這些錢。
?
(1)軟分叉補救
“凡是跟The DAO這個基金上的賬戶相關的,不允許做任何交易”。這里形成的是軟分叉(臨時性分叉),因為增加這個規(guī)則之后,新礦工挖出的區(qū)塊舊礦工是認可的,但是舊礦工挖出的區(qū)塊,新礦工有可能不認可。
Bug:不予執(zhí)行的非法交易不用收取汽油費。以太坊發(fā)布的這個軟件升級,沒有收取汽油費(檢查到地址錯誤的時候沒有收汽油費)。導致網上有大量的Denial of Service,非法交易進行攻擊,于是后來就很多人恢復了原來的版本。
????
(2)硬分叉補救
通過軟件升級的方法,把The DAO賬戶上的所有資金,強行轉到新的智能合約上去。這個新的智能合約只有一個功能:退錢,把代幣退回成以太幣。升級的軟件里規(guī)定了強制執(zhí)行的具體日期:升級了軟件的礦工,挖到第192W個區(qū)塊的時候,自動執(zhí)行轉賬交易,不用合不合法簽名。這是在升級的軟件里寫死的規(guī)則,舊礦工是不會認可這些區(qū)塊的,因為沒有合法簽名,屬于非法交易,所以這是硬分叉。
?
(3)后續(xù)
硬分叉之后,舊的那條鏈并沒有消亡,還是有一部分礦工留在上面繼續(xù)挖,只不過算力大幅度下降了,不到原來的1/10,但是相應的挖礦難度也降低了。過了一段時間,有一些交易所開始上市交易舊鏈上的以太幣。以太幣ETH,硬分叉后新鏈繼承了這個符號,仍叫ETH,舊鏈則為ETC(Ethereum Classic)。
在舊鏈上挖礦的礦工有些是為了投機,也有一些是為了信仰,他們堅持著純粹的去中心化理念,認為舊鏈才是正宗的以太坊,那些搞硬分叉的是在搞修正主義。一開始大家認為ETC的前途非常迷茫,但是一直到現(xiàn)在,仍然是新鏈和舊鏈并存。舊鏈和新鏈并存,這會導致重放攻擊,在新鏈上的也可以在舊鏈上執(zhí)行,舊鏈上的合法交易在新鏈上也可以執(zhí)行,于是增加一個chainID區(qū)分它們。
?
?
24-ETH-反思
1.?智能合約真的智能嗎?
智能合約沒有用到任何人工智能的技術,所以有人認為應該將其稱為自動合約:按照寫好的代碼,自動執(zhí)行某些操作,并不智能,寫好之后不能修改。
?
2. 不可篡改性實際上是一把雙刃劍。
(1)優(yōu)點:增加了合約的公信力,所有人只能按照合約中的規(guī)則來,沒人能夠篡改規(guī)則。
(2)缺點:如果規(guī)則有漏洞,想要修補漏洞或者軟件升級都是很困難的。
(3)缺點:已經發(fā)現(xiàn)了系統(tǒng)漏洞,有人進行惡意攻擊了,想要凍結賬戶終止交易也是很困難的。
個人的私鑰泄露,想要凍結賬戶需要軟分叉(發(fā)行一個軟件的更新并設置凡是跟這個賬戶相關的交易都是不合法的),但是對于個人賬戶沒有辦法進行軟分叉,只能盡快把賬戶剩下的錢轉到安全的賬戶。
?
智能合約一旦發(fā)布到區(qū)塊鏈上,沒有辦法阻止對它的調用。要阻止的話就要軟分叉,唯一的辦法是用黑客的方法把錢轉到另一個安全的合約,再用安全的智能合約將來把錢退還給大家。
?
2.?沒有什么是真的不可篡改的。
分叉攻擊中,如果有人從本來已經寫入區(qū)塊鏈的內容的前面開始分叉,可能會導致后面的交易被回滾。The DAO的盜幣事件中,開發(fā)團隊強行修改數(shù)據使得交易恢復被攻擊之前的賬戶狀態(tài),所以沒有什么是絕對不可篡改的。
?
代碼開源的雙刃劍
去中心化的系統(tǒng)像如區(qū)塊鏈一般都是開源的,也就是透明的,因為必須要讓所有的節(jié)點都執(zhí)行同樣的內容才能達成共識。開源的一個好處就是增加合約的公信力,接受群眾的監(jiān)督。
理論上,代碼開源,任何人想看都可以去看,好像更安全,但實際上是真正有時間看代碼的人少之又少,也不一定能看得懂。
?
三、去中心化
1.What does decentralized mean?
以太坊的硬分叉是以太坊的開發(fā)團隊說了算的嗎?不是,以太坊的團隊升級軟件之后,也是90%絕大多數(shù)的礦工用行動支持了硬分叉,而剩下的一小部分雖然沒有支持,但是也依然在舊鏈上繼續(xù)挖礦,以太坊團隊也沒有辦法強制所有人都升級軟件。去中心化并不是全自動化,不是說不能修改已經制定的規(guī)則,而是修改規(guī)則要用去中心化的方式進行。
?
2.分叉
分叉正好是去中心化系統(tǒng)的體現(xiàn),因為只有去中心化系統(tǒng),用戶才可以選擇分叉,中心化系統(tǒng)只能選擇繼續(xù)或者放棄。存在分叉的現(xiàn)象恰恰是民主的體現(xiàn),比如系統(tǒng)私自增多以太幣供給量,使得以太幣貶值,礦工就可以選擇分叉繼續(xù)維護原來的以太幣。
?
3.Decentralized ≠ Distributed
一個去中心化的系統(tǒng)一定是分布式的,如果這個系統(tǒng)只運行在一臺計算機上,顯然不能叫去中心化;但是分布式系統(tǒng)不一定是去中心化的,即使這個系統(tǒng)運行在成千上萬的計算機上,如果計算機都是由同一個組織管轄的。在一個分布式的平臺上可以運行一個中心化的應用,也可以運行一個去中心化的應用。
?
比特幣和以太坊都是交易驅動的狀態(tài)機,State Machine,特點是讓系統(tǒng)中幾千臺機器重復做同一組操作,付出很大的代價來維護狀態(tài)的一致性,這個并不是分布式系統(tǒng)常用的工作模式,大多數(shù)的分布式系統(tǒng)是讓每臺機器做不同的事情,然后再把各臺機器的工作結果匯總起來,目的是比單機速度快。
狀態(tài)機的目的不是為了比一臺計算機的處理速度快,而是為了容錯。
?
智能合約是編寫控制邏輯的,只有那些互不信任的實體之間建立共識的操作才需要寫在智能合約里。大規(guī)模存儲和計算不適用,又慢又貴,因為要耗汽油費,云服務更好。如果需要大規(guī)模計算服務,可以使用亞馬遜的云服務平臺。
?
?
?
25-ETH-美鏈
美鏈(Beauty Chain)是一個部署在以太坊上的智能合約,有自己的代幣BEC。
IPO,Initial Public Offering(首次公開募股)
ICO,Initial Coin Offering(首次代幣發(fā)行)
這些發(fā)行的代幣沒有自己的區(qū)塊鏈,而是以智能合約的形式運行在以太坊的EVM平臺上。發(fā)行代幣的智能合約對應的是以太坊狀態(tài)樹中的一個節(jié)點。這個節(jié)點有他自己的賬戶余額,相當于這個智能合約一共有多少個以太幣,也就是這個發(fā)行代幣的智能合約的總資產是多少個以太幣,然后在這個合約里每個賬戶上有多少個代幣是作為存儲樹的一個變量存儲在智能合約的賬戶里的。
代幣的發(fā)行、轉賬、銷毀都是通過調用智能合約中的函數(shù)來實現(xiàn)的。跟以太坊上的以太幣不太一樣,它不需要通過挖礦來維護一個底層的基礎鏈,像以太坊上每個賬戶有多少以太幣是直接保存在狀態(tài)樹上的變量。以太坊中兩個賬戶轉賬是通過發(fā)布一個交易到區(qū)塊鏈上,這個交易會打包到將要發(fā)布的區(qū)塊里,而在代幣中如果想要轉賬的話,實際上是智能合約里兩個賬戶之間轉賬,通過調用智能合約的函數(shù)就可以完成。
每個代幣可以制定自己的發(fā)行規(guī)則,如某個代幣可以是1ETH=100代幣,如果從外部賬戶給智能合約發(fā)送1ETH,智能合約就可以給相應的代幣賬戶發(fā)送100代幣。每個代幣賬戶上有多少代幣的信息是維護在發(fā)行這個智能合約的存儲樹上的。
?
以太坊平臺的出現(xiàn),為各種代幣的發(fā)展提供了很多方便。ERC 20(Ethereum Request for Comments)是以太坊上發(fā)行代幣的一個標準,規(guī)范了所有發(fā)行代幣的合約應該實現(xiàn)的功能和遵循的接口。美鏈中有一個叫batchTransfer的函數(shù), 它的功能是向多個接收者發(fā)送代幣,然后把這些代幣從調用者的帳戶上扣除。
?
2.batchTransfer的實現(xiàn)
?
?
3.batchTransfer的問題
uint256 amount =uint256(cnt)*_value;
如果value的值很大話可能會產生溢出,發(fā)生溢出之后,amount會變?yōu)橐粋€很小的值;這樣的話減去的是很小的值,給每個賬戶增加的是很大的value,相當于系統(tǒng)憑空多發(fā)行了很多的代幣。
?
4.攻擊細節(jié)

【0】是_receivers數(shù)組在參數(shù)列表中的位置,即從第64個byte開始,【2】;【2】先指明數(shù)組長度為2,【3】和【4】表明兩個接受者的地址第1號參數(shù)是給每個接收者轉賬的金額,通過這樣的參數(shù)計算出來的amount恰好溢出為0。
?
5.發(fā)生攻擊的實際情況

6.攻擊結果
攻擊在2018年4月22日發(fā)生,攻擊發(fā)生后,代幣的幣值斷崖式暴跌。

?
發(fā)生這個事件之后,發(fā)行該代幣的交易所馬上暫停了提幣功能,防止黑客卷款潛逃,兩天之后,就決定將交易回滾了。
?
7.反思
在進行數(shù)學運算的時候,一定要考慮到溢出的可能性。Solidity實際上有一個檢測溢出的庫——SafeMath庫,里面提供的操作運算都會自動檢測有沒有溢出。SafeMath庫里對乘法的運算大致如下:

如果出現(xiàn)溢出的話,這里的assert就不會成立,會拋出異常。由于Solidity里面都是256位的整數(shù),所以這里不會存在精度損失而導致的誤差。而且觀察batchTransfer的代碼可以發(fā)現(xiàn),代碼中的減法調用的是sub庫,加法調用的是add庫,實際上加法和減法都有溢出檢查,只有乘法不小心,釀成悲劇。
?
?
?
26-總結
質疑區(qū)塊鏈的原因:
(1)區(qū)塊鏈的概念被濫用
①保險公司的理賠問題
國外有人提出將保險理賠業(yè)務放在區(qū)塊鏈上,如果把保險理賠業(yè)務用比特幣系統(tǒng)實現(xiàn)的話,等待6個確認區(qū)塊(也就是大約1h的時間),理賠速度和理賠效率會快很多。區(qū)塊鏈本身并不能解決人工審核方面的問題。
?
②防偽溯源問題
如將有機蔬菜的生產到銷售的全過程都寫在區(qū)塊鏈上,利用區(qū)塊鏈的不可篡改性,使區(qū)塊鏈生產銷售的全過程透明。主要問題是區(qū)塊鏈不能自己輸入數(shù)據,區(qū)塊鏈技術不可篡改也沒有辦法檢測出哪些數(shù)據是不真實的。
?
③信任機制相關的問題
在互不信任的實體之間建立共識,有些人認為這是一個偽命題。在現(xiàn)實世界中,“中心化”和“去中心化”并不是黑白分明的。
?
(2)不可篡改性的相關問題
如果發(fā)生轉賬出錯的情況,是不能撤銷的,這種質疑是存在問題的。日常生活中使用的退款交易并不是說將原先的轉賬交易撤銷掉,而是發(fā)起一筆新的轉賬交易,實現(xiàn)轉賬金額的退回。用比特幣支付實際上是可以達到同樣效果的,這個跟區(qū)塊鏈的不可篡改性是無關的。
?
(3)法律監(jiān)管和保護相關問題
區(qū)塊鏈目前處于缺乏法律監(jiān)管的狀態(tài),同樣意味著沒有法律保護。比特幣本就不應該用于跟已有的支付方式進行競爭,加密貨幣應該用在已有的支付方式解決不是很好的領域,如跨國轉賬等。貨幣的支付方式可以和信息傳播的方式融合在一起。下一代互聯(lián)網可能是價值交換網絡,支付渠道和信息渠道相互融合,使得價值獲得和信息獲得一樣方便。
?
(4)支付方式的效率相關問題
比特幣和以太坊的能耗都是非常大的,比現(xiàn)有的支付方式耗費的能源大很多。
????????①加密貨幣本來就不是用來和已有的支付方式競爭;
????????②區(qū)塊鏈的發(fā)展以及共識協(xié)議的改進,一些新的加密貨幣已經在支付效率上已經是大大提高了;
????????③評價支付的效率要放在當時的歷史背景之下比較。