【译】The Faults and Shortcomings of the EVM
無論如何,所以讓我們開始追逐。 EVM的重點是什么?它為什么首先被制造出來? 根據設計原理,它被設計用于:
如果您仔細閱讀該文檔,您會發現EVM的理由相當深思熟慮。 那么它出錯了? 那么,它不適用于今天的技術和范例。 這是一個為當前不存在的世界而建造的非常好的設計。 我會圍繞這一點回顧一下,但讓我從我最喜歡的事情開始討論EVM
256位整數
在大多數現代處理器上,你有幾乎所有的快速數學運算的好選擇:
當然,在某些情況下,32位比16位更快,并且至少在x86中,8位數學算法并不完全支持(即沒有本地分割或乘法),但大多數情況下,如果使用其中一種尺寸可以保證數學運算需要多少個周期,并且速度很快,如果不包括緩存未命中和內存延遲,則會在幾納秒內測量。 無論如何,足以說這些是現代處理器“本地”使用的整數大小,沒有任何翻譯或其他需要無關操作的東西。
所以當然,由于EVM旨在優化速度和效率,整數大小的選擇是:
作為參考,下面介紹如何在x86程序集中添加2個32位整數(例如,您的PC所在的處理器)
mov eax, dword [number1] add eax, dword [number2]假設您的處理器支持64位,那么在x86匯編中如何添加2個64位整數:
mov rax, qword [number1] add rax, qword [number2]以下是如何在32位x86計算機上添加2個256位整數
mov eax, dword [number] add dword [number2], eax mov 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計算機上添加這些256位整數會更好一些
mov rax, qword [number] add qword [number2], rax mov 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無論如何,足以說使用256位整數工作比處理器原生支持的整數長度工作要復雜得多。
盡管EVM支持這種設計,但僅支持256位整數要比使用其他整數大小添加其他操作碼更為簡單。 唯一的非256位操作是一系列用于從內存中提取1-32字節數據的推送指令,以及一些與8位整數一起工作的指令。
那么,為所有操作使用這種低效的整數大小的設計原理?
“4字節或8字節的字太嚴格了,無法存儲地址和加密計算的大值,并且無限制的值太難以建立安全的氣體模型了。”
我必須承認,能夠將2個地址與單個操作進行比較非常酷。 但是,以下是在32位模式下(不使用SSE和其他優化)如何在x86中執行相同操作的情況:
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個字節的操作碼,或者如果地址在堆棧中,則可能類似于6 + 3 + 3 = 12個字節的操作碼。
大整數大小的另一個理由是“密碼學計算的大值”,但是,自從幾個月前的閱讀中,我發現一個用于256位整數的用例存在一個問題,它不涉及比較一個地址或散列相等。 自定義密碼學在公開區塊鏈上執行起來顯然太昂貴了。 我在github上搜索了一個多小時,試圖找到一個可以完成我定義為密碼學的任何事情的固態合約,而我什么都沒有提出。 幾乎任何形式的密碼學在現代計算機上保證速度緩慢和復雜,并且由于天然氣成本(更不用說將任何真實算法移植到Solidity),在公開的以太坊區塊鏈上執行它是不經濟的。 但是,仍然存在私有區塊鏈,其中天然氣成本無關緊要。 但是,如果您擁有自己的區塊鏈,您不希望將其作為慢速EVM合同的一部分來執行此操作,則可以使用C ++或Go或任意數量的真實編程語言將本機代碼中的加密作為預編譯聰明的合同。 所以這真的打擊了只支持256位整數的理由。 我覺得這是EVM問題的真正基礎,但在不太明顯的領域潛伏著更多。
EVM的內存模型
EVM有3個主要地方可以放置數據
堆棧有一定的限制,所以有時你需要使用臨時內存而不是非常昂貴的永久內存。 在EVM中沒有allocate指令或類似的東西。 你寫信給它聲稱記憶。 這看起來很聰明,但它也是非常險惡的。 例如,如果你寫地址為0x10000,你的合同只是分配了64K字(即64K的256位字)的內存,并支付了天然氣費用,就像你使用了所有64K字的內存一樣。 那么,簡單的解決方法,只需跟蹤您使用的最后一個內存地址,并在需要更多內存地址時增加它。 這種方式運作得很好,除非你碰巧需要大量的內存,然后你不再需要這些內存。 假設你使用一些瘋狂的算法,使用100個字的內存。 所以,你分配這個,使用內存,不管什么和支付100字的內存......然后你退出該功能。 現在你又回到了一些其他的功能,它只需要1個字的內存空間或其他東西,所以它分配了另一個字。 你現在正在使用101個字的記憶。 沒有辦法釋放內存。 從理論上講,你可以減少你記憶最后一個空間的那個特殊指針,但是只有當你知道整個內存塊永遠不會再被引用并且可以安全地重用時,它才會起作用。 如果在這100個單詞中,你需要50個單詞和90個單詞,那么你必須將這些單詞復制到另一個位置(如堆棧),然后可以釋放該內存。 沒有EVM提供的工具來幫助解決這個問題。 技術術語是內存碎片 。 您需要審核每個函數沒有使用已分配且可全局訪問的內存,并且如果您重復使用了該內存,并且通過了審查流程,則您的合同現在具有潛在的嚴重狀態損壞錯誤。 所以你的選擇基本上是要么自己打開一大堆內存重用錯誤,要么為內存支付更多的內存,即使你已經分配了超過你需要的內存。
此外,分配內存不具有線性成本。 如果你已經分配了100個字的內存,并且你分配了1個字,那么當你的程序啟動時,它比分配第一個內存字要昂貴得多。 這一方面大大放大了安全方面的經濟成本,相比之下,由于大大降低了天然氣成本,讓自己面臨更多的合同錯誤。
那么,為什么要使用內存呢? 為什么不使用堆棧? 那么,堆棧是可笑的限制。
EVM的堆棧
EVM是基于堆棧的機器。 這意味著它在大多數操作中使用堆棧,而不是一組寄存器。 與基于寄存器的類似機器相比,基于堆棧的機器通常更容易優化,但導致大多數操作需要更多的操作碼。
無論如何,所以EVM有很多不同的操作 ,其中大部分操作在堆棧上。 請注意SWAP和DUP系列說明。 這些最多可以達到16個。現在嘗試編譯這個合同:
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層時,實際上不可能在沒有從堆疊中彈出物品的情況下進行存取。 這個問題的官方“解決方案”是使用更少的變量并使函數更小。 各種變通辦法還包括將變量填充到結構或數組中,并使用memory關鍵字(由于......原因,不能將其應用于正常變量?)。 所以,讓我們解決我們的合同,使用一些基于內存的結構:
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.但是我們用內存替換了這些變量? 這不是解決它嗎? 那么,不。 因為現在不是將17個256位整數存儲在堆棧中,而是將13個整數和4個256位存儲器地址(即參考)存儲到256位存儲器插槽中。 其中一部分是Solidity問題,但主要問題是EVM缺少訪問堆棧中任意項目的方法。 我所知道的每一個虛擬機實現都可以解決這個基本問題
但是,在EVM中,堆棧是數據和計算的唯一空余存儲空間,任何其他地方都會以氣體形式直接支付成本。 所以,這直接阻礙了小堆棧的大小,因為在其他地方更昂貴...所以我們得到這樣的基本語言實現問題。
字節碼大小
在基本原理文件中,他們表示他們的目標是讓EVM字節碼既簡單又緊湊。 然而,這就像是說你更愿意編寫既簡潔又簡潔的代碼。 他們從根本上完全不同的目標完成不同的目標。 一個簡單的指令集是通過限制操作的數量來完成的,并且保持操作的簡潔和簡單。 同時,產生小程序的緊湊字節碼是通過制作一個指令集來完成的,該指令集盡可能以盡可能少的代碼字節執行盡可能多的操作。
最終,盡管“緊湊字節碼大小”是其基本原理中的一個目標,但EVM的實際實現在任何意義上都沒有達到該目標。 而是專注于一個簡單的指令集,很容易創建一個氣體模型。 我并不是說這是錯誤的或不好的,只是EVM的主要目標之一與EVM的其他目標有著根本的聯系。 另外,該文件中給出的一個數字是C程序需要超過4000字節才能實現“hello world”。 這絕對不是這種情況,并且掩蓋了C程序中發生的不同環境和優化。 在他們測量的C程序中,我預計也會有ELF數據,重定位數據和對齊優化 - 在某些邊界(如32字節或4kb)上對齊代碼和數據可能會對物理處理器上的程序性能產生可測量的影響。 我個人已經構建了一個簡單的裸機C程序,它編譯為46個字節的x86機器代碼,以及一個編譯為?700字節的簡單迎賓型程序,而Solidity的示例編譯為超過1000字節的EVM字節碼。
我明白出于安全原因需要一套簡單的指令集,但它會在區塊鏈上造成重大膨脹。 像EVM智能合約字節碼盡可能小一樣傳遞,這是有害的。 通過包含一個標準庫和支持操作碼來執行一批常用操作,而不需要為這樣的操作執行幾個操作碼,它顯然可以變得更小。
256位整數(再次)
但是,真的,256位整數是可怕的。 而最荒謬的是,它們被用在沒有合理使用的地方。 使用超過4B(32位)單位的氣體實際上是不可能的,那么用什么樣的整數來指定和計算氣體呢? 當然是256位。 內存相當昂貴,因此EVM內存地址的地址大小是多少? 當然這是256位,因為當你的合約需要更多的記憶詞時,比宇宙中的原子還要多。 我會抱怨在永久性存儲中為地址和值使用256位整數,但實際上它提供了一些有趣的功能,可以使用散列來處理某些數據,并且不用擔心地址空間中的沖突,所以我想這會得到通過。 在您可以使用任何整數大小的每個實例中,EVM需要256位。 即使JUMP使用256位,但在他們的防守中,他們將最高跳轉目標限制為0x7FFFFFFFFFFFFFFF并有效地將跳轉目標限制為有符號的64位整數。 然后是貨幣價值本身。 ETH的最小單位是wei,所以我們到達的總硬幣供應量(在wei中)是1000000000000000000 * 200000000 (200M是估計值,目前供應量是~92M)。所以,如果我們從2減去那個數字到256的功率(最大值可存儲256位整數),我們得到.. 1.157920892373162e + 77。 只有足夠的空間發送比以往任何時候都多的wei,再加上大于宇宙中原子數量的數量級。 基本上,對于EVM所設計的幾乎任何應用來說,256位整數都是不切實際且不必要的。
缺乏標準庫
如果您曾經開發過Solidity智能合約,那么這可能是您遇到的第一個問題。 根本沒有標準庫。 如果你想確定兩個字符串是否相等,沒有strcmp或memcmp或類似的東西,你必須親自編寫代碼或從互聯網復制代碼。 Zepplin項目通過提供合同可以使用的標準庫(通過將其包含在合同本身中或通過調用外部合同)來使這種情況變得可承受。 但是,考慮到使用兩個SHA3操作并比較結果散列比使用字符串字節(每次32字節)循環來確定它們是否是更便宜的方法等于。 擁有標準庫的預編譯合同,使用本地代碼和合理的天然氣代價,對整個智能合約生態系統將是非常有利的。 如果沒有這個,人們會復制并粘貼來自開放源代碼的代碼,而這些代碼具有未知的安全性。 除此之外,人們還會優化他們的代碼,試圖找到捷徑和減少天然氣使用量,甚至可能會損害合同的安全性。
天然氣的經濟學和博弈論
我打算就此主題發表一篇完整的博客文章,但EVM不僅僅是好的做法,而且還很昂貴。 例如,將數據存儲在區塊鏈中需要相當多的天然氣。 這意味著在智能合約中緩存任何數量的數據可能會非常昂貴。 所以,它是與每個合同執行計算。 隨著時間的推移,更多的天然氣被消耗,區塊鏈節點浪費更多時間執行相同的代碼來計算相同的數據。 此外,存儲在區塊鏈上的數據的實際成本非常低。 它不會直接增加區塊鏈的大小(在以太坊或Qtum中)。 實際成本是以發送給合同的數據形式進入區塊鏈的數據,因為這直接增加了區塊鏈的大小。 在Etheruem中,以交易形式(23176 gas)將32字節的數據輸入區塊鏈要比在合同中存儲32字節的成本(20,000)要便宜得多,并且在縮放64字節的數據(tx為29704天然氣,而80,000天然氣為存儲)。 存儲在合同中的數據存在“虛擬”成本,但遠低于大多數人的假設。 這基本上只是通過遍歷存儲整個區塊鏈數據的數據庫的代價。 然而,Qtum和Ethereum使用的RLP和LevelDB數據庫系統在處理這個問題時非常高效,而且持續的成本不會接近線性。
鼓勵低效代碼的EVM的另一部分是無法在智能合約中調用特定功能。 這是為了安全,因為能夠直接調用ERC20合同中的withdraw()函數會很糟糕。 但是,這對于標準庫的高效性是必需的。 不是簡單地從外部協定加載特定代碼片段,而是全部或全部,并且執行始終始于代碼的第一個字節,因此無法跳過并跳過所有Solidity ABI引導代碼。 因此,最終鼓勵小型函數被復制(因為它們在外部調用會更昂貴),并盡可能地在合同中部署盡可能多的函數。 盡管所有的代碼都需要以任何方式加載到內存中,但調用100字節的合約或10,000字節的合約并沒有成本差異。
最后,直接訪問合同存儲是不可能的。 合同代碼必須從磁盤完全加載,執行,代碼必須從您請求的存儲中加載數據,然后最終將其返回給調用合同,同時確保不使用可變大小的數組 。 哦,如果你需要來回一些,因為你不知道你需要的確切數據,至少它是在緩存中,所以節點很便宜,但第二次調用外部合同的天然氣價格沒有折扣。 無需完全加載代碼即可訪問外部合同的存儲。 事實上,它就像訪問當前合同的存儲一樣,在計算上也是如此便宜,那么為什么要讓它變得如此昂貴并且不利于效率?
缺乏調試和可測試性
這個問題不僅在于EVM設計的錯誤,還在于它的實現。 當然,有些項目正在努力讓這個盡可能簡單,如松露。 然而,EVM的設計并沒有讓這一切變得簡單。 唯一可用的例外是“OutOfGas”,沒有日志工具,沒有簡單的方法來調用外部本地代碼(例如測試助手和模擬數據),而以太坊區塊鏈本身很難創建私有測試網,而且私人區塊鏈具有不同的參數和行為。 Qtum至少在這里感謝“regtest”模式,但是使用模擬數據等來測試EVM仍然非常困難,因為沒有實現是真正獨立的。 在Solidity級別上我沒有任何調試員了解這項工作,但我知道至少有1個EVM匯編調試器,但這遠不夠用戶友好。 沒有為EVM和/或Solidity建立符號格式或調試數據格式,我也沒有發現EIP或其他努力開始朝著像DWARF這樣的標準化調試格式工作。
浮點數字
我看到有人說當缺乏浮點支持出現時,常見的事情是“沒有人應該使用浮點數來處理貨幣值”。 盡管這非常狹隘。 浮點數有許多實際用例,如風險建模,科學計算以及范圍和近似值比精確值更重要的情況。 說智能合約在處理貨幣價值方面的潛在應用是不切實際和不必要的限制。
不可變的代碼
合同需要設計的主要事情之一是可升級??性,因為它不是合同需求是否需要改變,而是何時改變的問題。 在EVM中,代碼是完全不可變的,并且由于它使用哈佛計算體系結構,因此無法將代碼加載到內存中,然后執行它。 代碼和數據是完全分開的東西,對待不同。 因此,升級合同的唯一選擇是部署一個全新的合同,復制所有代碼并使舊合同重定向到它。 修補合同的部分和部分(或完全)替換代碼是不可能的。
結論
我喝完了我的啤酒(好吧,很難喝蘋果酒),我想我的咆哮即將結束。 EVM在這一點上是一個必要的罪惡。 它是這個領域的第一個,并且像大多數第一個(比如Javascript)一樣,存在很多問題。 而且它的設計非常非常規,所以我不認為我們會看到任何傳統的編程語言移植到EVM。 它的設計主動地反對過去50多年來建立的許多通用語言范例。 這包括諸如跳轉表優化困難,沒有尾遞歸支持,奇怪和不靈活的內存模型,難以理解的外部代碼的DELEGATECALL模型,缺少常用操作碼(如按位移,不靈活的堆棧大小限制)以及當然256位整數。 這些方面使得將傳統語言移植到EVM中效率低下,甚至是不可能。 我假設這就是為什么所有EVM語言目前都是專門為EVM和所有非常規模型而設計的。 這確實是一種令人傷心的事態。
我的意思是這整個帖子不是EVM設計者的毆打或任何東西,而是事情的方式。 事后總是20/20,我知道我已經看到他們對EVM設計某些方面的許多遺憾。 我不想攻擊它們(即使我的諷刺語氣有時看起來像是這樣),但是我想把這些缺點帶給更大區塊鏈開發者社區的注意力,以便它們不會被重復,也希望提供一些有關“為什么我不能在Solidity”類型問題同時進行的一些見解。 EVM擁有令人難以置信的設計,我們仍然在了解其優點和缺陷,顯而易見,在智能合約能夠像我們都知道的那樣高效和強大之前,我們還有很長的路要走。 EVM是這個領域的第一個競爭者,并且最終我們仍然在學習和發現智能合約的所有用例,以及哪種設計最有利于他們。 我們走了很長的路,但還有很長的路要走。
http://earlz.net/view/2017/08/13/0451/the-faults-and-shortcomings-of-the-evm
總結
以上是生活随笔為你收集整理的【译】The Faults and Shortcomings of the EVM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【译】Look-ahead Stakin
- 下一篇: 【译】The missing expla