日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

以太坊虚拟机EVM的缺陷与不足

發布時間:2025/3/15 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 以太坊虚拟机EVM的缺陷与不足 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
首先對作者做個簡單的自我介紹,Jordan Earls是Qtum量子鏈的聯合創始人之一,目前擔任Qtum量子鏈全球首席工程師。Qtum目前采用了以太坊虛擬機(Ethereum Virtual Machine, 以下簡稱EVM)并將其運用于非以太坊的區塊鏈中(當然Qtum項目還包含很多其他工作,因與本文不相關,故不贅述)。因為Qtum中采用了EVM,因此筆者在項目執行過程中對其進行了比較深入的了解和學習。知道我學習EVM后最大的感受是什么嗎?說實話我真的不喜歡EVM,因為在我個人看來,它無論從設計上還是實現上都有不少缺陷。利益相關:筆者計劃在Qtum中建立一個新的虛擬機,并試圖解決本文中提到的大部分問題。

那么接下來讓我們直接進入主題。首先,EVM的設計初衷是什么?它為什么被設計成目前我們看的樣子呢?根據以太坊官方提供的設計原理說明,EVM的設計目標主要針對以下方面:
  • 簡單性(Simplicity)
  • 確定性(Determinism)
  • 節省空間的bytecode
  • 專為區塊鏈設計
  • 更加簡單的安全性保證
  • 容易優化
  • 如果讀者瀏覽一下這個文檔,會發現EVM的設計看上去都非常的合理。那么問題在哪里呢?問題就出在它和目前主流的技術以及設計范例都格格不入。EVM如果作為一個毫無限制的非現實世界中的設計確實很不錯。接下來筆者會圍繞EVM各個方面的問題逐一進行描述,首先從筆者最不能忍受的一點開始。? 256bit整數目前大多數的處理器主要有以下4種選擇來實現快速的數學運算:
  • 8bit整數
  • 16bit整數
  • 32bit整數
  • 64bit整數
  • 當然,雖然在一些情況下32bit比16bit要快,以及在x86架構中8bit數學運算并不是完全支持(無原生的除法和乘法支持),但基本上如果你采用以上的任意一種,都可以保證數學運算在若干個時鐘周期中完成,并且這個過程非常迅速,往往是納秒級的。因此,我們可以說,這些位長的整數是目前主流處理器能夠“原生地”支持的,不需要任何額外的操作。EVM出于所謂運算速度和效率方面考慮,采用了非主流的256bit整數。

    讓我們通過對比x86匯編碼來看看它的表現。

    ?

    首先是兩個32bit整數相加的x86匯編碼(也就是大多數PC的處理器采用的):

    mov eax, dword [number1]

    add eax, dword [number2]

    然后是2個64bit整數相加,這里假設采用64位處理器:

    mov rax, qword [number1]

    add rax, qword [number2]

    接下來是在32位x86計算機上兩個256bit整數相加:

    mov eax, dword [number]add dword [number2], eaxmov eax, dword [number1+4]

    adc dword [number2+4], eax

    mov eax, dword [number1+8]

    adc dword [number2+8], eax

    mov eax, dword [number1+12]

    adc dword [number2+12], eax

    mov eax, dword [number1+16]

    adc dword [number2+16], eax

    mov eax, dword [number1+20]

    adc dword [number2+20], eax

    mov eax, dword [number1+24]

    adc dword [number2+24], eax

    mov eax, dword [number1+28]

    adc dword [number2+28], eax

    當然還有在64位x86計算機上兩個256bit整數相加:mov rax, qword [number]add qword [number2], raxmov rax, qword [number1+8]

    adc qword [number2+8], rax

    mov rax, qword [number1+16]

    adc qword [number2+16], rax

    mov rax, qword [number1+24]

    adc qword [number2+24], rax

    通過以上比較足以說明采用256bit整數遠比采用處理器原生支持的整數長度要復雜。EVM之所以選擇這種設計,主要是因為僅支持256bit整數會比增加額外的用于處理其他位寬整數的opcodes來的簡單得多。僅有的非256bit操作是一系列的push操作,用于從memory中獲取1-32字節的數據,以及一些專門針對8bit整數的操作。?

    那么對于所有操作都采用這種低效的整數位寬的設計初衷是什么呢?

    “4字節或8字節字長限制了更大的內存尋址和復雜的密碼學運算,同時無限制的值將很難實現安全的gas模型”

    關于地址,我必須承認,能夠僅用單個操作實現兩個地址的比較確實很酷。但是,在x86機器上采用32bit整數實現相同功能也并不復雜(無SSE和任何其他優化):

    mov esi, [address1]mov edi, [address2]mov ecx, 32 / 4

    repe cmpsd

    jne not_equal

    ; if reach here, then they’re equal

    假設address1和address2 都是確定的地址,僅需要6+5+5=16字節的opcodes,而如果地址都在棧上,則僅需要6+3+3=12字節的opcode。關于另一個理由“復雜的密碼學運算”,筆者從幾個月前第一次看到這個理由,直到現在都沒有看到過一個不涉及地址或哈希值比較的256bit整數的應用實例。密碼學運算如果在區塊鏈上運行顯然過于昂貴了。筆者在github上搜索了一個多小時,試圖找到一個在solidity合約中用到密碼學運算的實例,結果卻一無所獲。幾乎所有的密碼學運算對于目前的計算機來說都是復雜的,所以在以太坊公有鏈上進行這種運算是非常昂貴的(必須消耗大量的gas,更不用說把密碼學算法用solidity實現所需要的工作量)。當然,如果是一條私有鏈,gas消耗可能不是問題。但如果你是這條鏈的擁有者,你應該也不會選擇用低效的EVM智能合約來實現密碼學運算,而會選擇采用C++,Go,或者其他一些編程語言實現。綜上所述,EVM僅支持256bit整數的理由完全不成立。筆者認為這是EVM最根本也是最明顯的問題,除此之外,EVM還有不少問題,下面我們一一道來。

    EVM的內存分配模型

    EVM中主要有3個用于存儲數據的地方:

  • 棧(Stack)
  • 臨時內存(Temporary memory)
  • 永久內存(Permanent memory)
  • 棧存儲有許多限制,所以有時候你必須使用臨時內存(永久內存比較昂貴)。在EVM沒有allocate或類似的操作,所以必須通過直接寫數據來獲取內存空間。這看起來非常智能,但實際上卻有不少問題。比如,如果你需要尋址到0×10000,你的合約將分配64K字長(也就是64K的256bit的word)的內存并且你需要支付64K字長對應的gas。有個比較簡單的變通方法,就是你可以跟蹤你上一次被分配的內存,當你需要時可以繼續使用未使用的內存。這是有很有效的方法,直到你需要的內存超過了剩余可用的內存。我們假設你寫個某個算法,需要100字長的內存。你分配并使用了該內存,支付了100字長內存對應的gas,然后退出了這個函數。之后你回到了另一個函數中,它只需要1字長的內存,系統又重新分配了另外1字長的內存,這樣你總共使用了101字長的內存。EVM中沒有辦法釋放內存。理論上你可以通過記錄最后使用的內存地址,實現內存的釋放和復用,但這僅在你能確定這段內存不會再被引用的前提下才具有可行性。如果在這100個word中你需要用到第50和第90個word,那么你必須先把他們拷貝到其他地方(比如棧上)然后再釋放原來的內存。EVM并沒有為此提供相關的工具。是否對智能合約中函數對分配內存的使用進行檢查完全取決于你,如果你決定復用這些內存,但又沒有檢測出異常情況,那么你的智能合約將面臨潛在的重大bug。所以你要么承擔復用內存帶來的風險,要么支付足夠多的gas以獲取安全的內存分配。?

    除此之外,分配內存所需要花費的gas并不是線性的。比如你分配了100字長的內存,之后又分配1字長內存,這最后1字長內存的花費將明顯高于你一開始就只分配1字長內存的花費。這又大大增加了保證內存安全所需的花費。

    ?

    既然如此,那為什么非要使用內存呢?為什么不使用棧?實際上EVM中棧有明顯的限制。 EVM中的棧

    EVM是一個基于棧的虛擬機。這就意味著對于大多數操作都使用棧,而不是寄存器。基于棧的機器往往比較簡單,且易于優化,但其缺點就是比起基于寄存器的機器所需要的opcode更多。

    所以EVM有許多特有的操作,大多數都只在棧上使用。比如SWAP和DUP系列操作等,具體請參見EVM文檔。現在我們試著編譯如下合約:

    pragma solidity ^0.4.13;contract Something{

    ?

    function foo(address a1, address a2, address a3, address a4, address a5, address a6){

    address a7;

    address a8;

    address a9;

    address a10;

    address a11;

    address a12;

    address a13;

    address a14;

    address a15;

    address a16;

    address a17;

    }

    }

    你將看到如下錯誤:CompilerError: Stack too deep, try removing local variables.這個錯誤是因為當棧深超過16時發生了溢出。官方的“解決方案”是建議開發者減少變量的使用,并使函數盡量小。當然還有其他幾種變通方法,比如把變量封裝到struct或數組中,或是采用關鍵字memory(不知道出于何種原因,無法用于普通變量)。既然如此,讓我們試一試這個采用struct的解決方案:

    pragma solidity ^0.4.13;contract Something{

    struct meh{

    address x;

    }

    function foo(address a1, address a2, address a3, address a4, address a5, address a6){

    address a7;

    address a8;

    address a9;

    address a10;

    address a11;

    address a12;

    address a13;

    meh memory a14;

    meh memory a15;

    meh memory a16;

    meh memory a17;

    }

    }

    結果呢?CompilerError: Stack too deep, try removing local variables.我們明明采用了memory關鍵字,為什么還是有問題呢?關鍵在于,雖然這次我們沒有在棧上存放17個256bit整數,但我們試圖存放13個整數和4個256bit內存地址。這當中包含一些Solidity本身的問題,但主要問題還是EVM無法對棧進行隨機訪問。據我所知,其他一些虛擬機往往采用以下兩種方法之一來解決這個問題:

  • 鼓勵使用較小的棧深,但可以很方便地實現棧元素和內存或其他存儲(比如.NET中的本地變量)的交換;
  • 實現pick或類似的指令用于實現對棧元素的隨機訪問;
  • 然而,在EVM中,棧是唯一免費的存放數據的區域,其他區域都需要支付gas。因此,這相當于鼓勵盡量使用棧,因為其他區域都要收費。正因為如此,我們才會遇到上文所述的基本的語言實現問題。

    ?

    bytecode大小

    在EVM設計文檔中,設計者聲稱他們的目標是使得EVM的bytecode既簡單又高度壓縮。然而,這就像是試圖寫出既詳盡又簡潔的代碼一樣,實際上兩者是存在一定矛盾。要實現一個簡單的指令集就需要盡量限制操作的種類,并保持每種操作的盡量簡單;然而,要實現高度壓縮的bytecode則需要引入擁有豐富操作的指令集。

    即使是“高度壓縮的bytecode”這一目標也沒有在EVM中實現,他們更加側重于實現易于生成gas模型的指令集。我并不是說這是錯的,只是想表明作為官方聲明的EVM最重要的目標之一最終并沒有實現這一事實。同時,EVM設計文檔中給出了一個數據:C語言實現的“Hello World”簡單程序生成4000字節的bytecode。這一結果并不正確,很大程度取決于編譯環境以及優化程度。在他們所述的C程序中,應該同時包含了ELF數據,relocation數據以及alignment優化等。筆者嘗試編譯了一個非常簡單的C程序(只有一個程序骨架),只需要46字節的x86機器碼;同時還用C語言寫了一個簡單的greeter type程序(Solidity示例程序),最終生成大約700字節bytecode,而同樣的Solidity示例程序則需要1000字節bytecode。

    我當然明白簡化指令集是出于某些安全性因素考慮,但這顯然會導致區塊鏈更加臃腫。如果EVM智能合約的bytecode盡可能小的話確實是有害的。我們完全可以通過增加標準庫或是支持可以批處理某些基本操作的opcode來減小bytecode。

    ?

    256bit整數(補充)

    256bit整數確實令人頭疼,所以這里再做一些補充。最令人費解的是256bit整數被用到了一些根本沒必要的地方。比如,我們根本不可能在合約中使用超過4B(32bit)單位的gas,那么你猜在EVM中采用什么長度的整數來作為gas的計量呢?沒錯,當然是256bit。內存使用也非常昂貴,那內存大小的計量呢?自然也是256bit,當你的合約需要用到比宇宙中原子數量還多的地址時這個數字或許真的能派上用場。雖然我不認同在尋址或是永久內存的變量中使用256bit整數,但不得不說它使得計算某些數據的hash時能夠避免沖突,因此這還能勉強接受。但對于任一個instance,本可以采用任何整數長度,EVM還是使用了256bit。甚至JUMP也使用256bit,但他們限制了最大的JUMP地址為0x7FFFFFFFFFFFFFFF,相當于限制在64bit整數范圍內。最后,以太坊中的幣值當然也采用了256bit數來計算。ETH的最小單位是wei,所以總的幣的數量(單位為wei)為1000000000000000000 * 200000000 (200M只是估計值,目前僅有約92M)。而2^256約為1.157920892373162e+77,這足以表示所有已存在的所有ETH外加比全宇宙原子數還多的wei……歸根結底,256bit整數在EVM所設計的大多數應用中都沒有必要。

    ?

    缺少標準庫

    如果你曾經開發過Solidity智能合約的話,你應該也會碰到這個問題,因為Solidity中根本就沒有標準庫。如果你想比較兩個字符串,Solidity中根本就沒有類似strcmp或memcmp的標準庫函數供你調用,你必須自己用代碼實現或在網上拷貝代碼來實現。Zeppin項目使這一情況得到一定改善,他們提供了一個可供合約使用的標準庫(通過將代碼包含在合約中或是調用外部合約)。然而,這種方式的限制也很明顯,主要是在gas消耗方面。比如判斷字符串是否相等,進行兩次SHA3操作然后比較hash值顯然要比循環比較每個字符所要花費的gas要少。如果存在預編譯好的標準庫,并設定合理的gas價格,這將更加有利于整個智能合約生態的發展。目前的情況是,人們只能不斷的從一些開源軟件中復制黏貼代碼,首先這些代碼的安全性無法保證,再加上人們會為了更小的gas消耗而不斷修改代碼,這就有可能對他們的合約引入更嚴重的安全性問題。

    ?

    gas經濟模型中的博弈論

    我打算寫一篇新的博客單獨闡述這個主題。EVM不僅使寫出好的代碼變得很困難,還令其變得非常昂貴。比如,在區塊鏈上存儲數據需要耗費大量的gas。這意味著在智能合約中緩存數據的代價會非常大,因此往往在每次合約運行時重新計算數據。隨著合約被不斷執行,越來越多的gas和時間都被花在了重復計算完全相同的數據上。實際上單純通過交易在區塊鏈上存儲數據并不會消耗太多的gas,因為這并不會直接增加區塊的大小(不管以太坊還是Qtum都是如此)。真正花費比較大的其實是那些發送給合約的數據,因為這將直接增加區塊的大小。在以太坊中,通過交易在區塊鏈上記錄32byte的數據比在合約中存儲相同的數據消耗的gas要少一些,而如果是64byte的數據,則消耗的數據就少得多了(29,704 gas v.s. 80,000gas)。在合約中儲存數據會有“virtual”的花費,但比大多數人想象的要少得多。基本上就是遍歷區塊鏈上數據庫的花費。Qtum和以太坊采用的RLP和LevelDB數據庫系統在這方面非常高效,但持續的成本并不是線性的。

    EVM鼓勵這種低效率的代碼的另一原因就是其不支持直接調用智能合約中某個具體的函數。這當然是出于安全性考慮,如果允許直接調用在ERC20代幣合約中的withdraw函數,結果確實會是災難性的。但是這在標準庫調用中將會非常高效。目前EVM中要么執行智能合約的所有代碼,要么一點也不執行,完全不可能只執行其中部分代碼。程序總是從頭開始運行,無法跳過Solidity ABI引導代碼。所以這導致的結果就是一些小函數被不斷復制(因為通過外部調用將更加昂貴),并且鼓勵開發者在同一個合約中包含盡量多的函數。調用一個100bytes的合約并不比調用10000bytes的合約昂貴,盡管所有代碼都必須加載到內存中。

    最后一點,就是EVM中無法直接獲取合約中存儲的數據。合約代碼必須先被完全加載并執行,并且包含你所請求的數據,最終通過合約調用返回值的形式返回數據(還得保證沒有多個返回值)。同時,當你不確定你需要的是哪個數據,需要來來回回地調用合約時,第二次調用合約所需要的gas并沒有任何折扣(不過至少合約還在緩存中,對節點來說第二次調用稍微便宜一些)。實際上完全可以在不加載整個外部合約的基礎上訪問其數據,這其實和獲取當前合約的存儲數據沒什么兩樣,為什么偏要采用如此昂貴且低效的方式呢?

    ?

    難以調試和測試

    這個問題不僅僅是由于EVM的設計缺陷,也和其實現方式有關。當然,有一些項目正在做相關工作使整個過程變得簡單,比如Truffle項目。然而EVM的設計又使這些工作變得很困難。EVM唯一能拋出的異常就是“OutOfGas”,并且沒有調試日志,也無法調用外部代碼(比如test helpers和mock數據),同時以太坊區塊鏈本身很難生成一條測試網絡的私鏈,即使成功,私鏈的參數和行為也與公鏈不同。Qtum至少還有regtest模式可用,而在EVM中使用mock數據等進行測試則真的非常困難。據我所知目前還沒有任何針對Solidity的調試器,雖然有一款我知道的EVM assembly調試器,但其使用體驗極差。EVM和Solidity都沒有創建用于調試的符號格式或是數據格式,并且目前沒有任何一個EIP提出要建立像DWARF一樣標準的調試數格式。

    ?

    不支持浮點數

    對于那些支持EVM不需要浮點數的人來說,最常用的理由就是“沒有人會在貨幣中采用浮點數”。這其實是非常狹隘的想法。浮點數有很多應用實例,比如風險建模,科學計算,以及其他一些范圍和近似值比準確值更加重要的情況。這種認為智能合約只是用于處理貨幣相關問題的想法是非常局限的。

    ?

    不可修改的代碼

    智能合約在設計時需要考慮的重要問題之一就是是可升級性,因為合約的升級是必然的。在EVM中代碼是完全不可修改的,并且由于其采用哈佛計算機結構,也就不可能將代碼在內存中加載并執行,代碼和數據是被完全分離的。目前只能夠通過部署新的合約來達到升級的目的,這可能需要復制原合約中的所有代碼,并將老的合約重定向到新的合約地址。給合約打補丁或是部分升級合約代碼在EVM中是完全不可能的。

    ?

    小結

    不可否認,EVM作為第一個區塊鏈虛擬機存在諸多問題,這和絕大多數新生事物一樣(比如Javascript)。并且由于它的設計比較非主流,我認為不會有主流的編程語言能夠移植到EVM上。這種設計可以說對于近50年來的大多數編程范例來說都不太友好。比如JUMPDEST使得jump table優化更加困難,不支持尾遞歸,詭異且不靈活的內存模型,棧的限制,當然還有256bit整數等等。這種種問題都使得移植主流編程語言的代碼變得困難重重。我想這就是目前EVM只能支持專門定制的開發語言的原因。這是在是件令人遺憾的事。

    ?

    筆者寫這篇文章并不是想要攻擊EVM的設計者,只是就事論事的討論。事后諸葛亮總是看上去很容易,實際上我知道EVM設計者們已經意識到某些方面的不足,并因此感到懊悔。我并不想指責他們(雖然看起來我就是在吐槽),我真正的目的是想通過指出這些問題來引起整個區塊鏈開發者社區的重視,從而我們不會重蹈覆轍,同時相信也能解答諸如“為什么我在Solidity中不能實現blabla功能”等問題。EVM的設計比較復雜,我們都還在學習這種設計帶來的好處以及弊端。我們可以確信的是,目前的智能合約還遠沒有達到我們對它的期望,在未來它的功能會變得更加強大。EVM是這個領域的開拓者,通過它我們可以不斷加深對智能合約的認識,并從中總結出最合理的設計。路漫漫其修遠兮,吾將上下而求索。

    原文地址:http://www.8btc.com/evm-qtum-0817

    總結

    以上是生活随笔為你收集整理的以太坊虚拟机EVM的缺陷与不足的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。