所以,先介绍一下。 我共同创立了Qtum ,这是一个基本上采用了以太坊虚拟机(EVM)的项目,并把它放到了不是以太坊的区块链(以及其他一些东西)上。 在我这样做的过程中,我被迫反对自己的意愿去学习比我想要了解EVM更多的方法。 我从这些学习中主要拿走什么? 好吧,我不喜欢它。 我个人认为这是一个不切实际的设计,更不切实际的实施。 作为一个免责声明,我打算透彻地看到,我们为Qtum添加了另一个虚拟机,它至少可以修复大部分这些问题。

无论如何,所以让我们开始追逐。 EVM的重点是什么?它为什么首先被制造出来? 根据设计原理,它被设计用于:

  1. 简单
  2. 确定性
  3. 紧凑的字节码大小
  4. 区块链专业化
  5. 简单(呃?)
  6. 可优化

如果您仔细阅读该文档,您会发现EVM的理由相当深思熟虑。 那么它出错了? 那么,它不适用于今天的技术和范例。 这是一个为当前不存在的世界而建造的非常好的设计。 我会围绕这一点回顾一下,但让我从我最喜欢的事情开始讨论EVM

256位整数

在大多数现代处理器上,你有几乎所有的快速数学运算的好选择:

  1. 8位整数
  2. 16位整数
  3. 32位整数
  4. 64位整数

当然,在某些情况下,32位比16位更快,并且至少在x86中,8位数学算法并不完全支持(即没有本地分割或乘法),但大多数情况下,如果使用其中一种尺寸可以保证数学运算需要多少个周期,并且速度很快,如果不包括缓存未命中和内存延迟,则会在几纳秒内测量。 无论如何,足以说这些是现代处理器“本地”使用的整数大小,没有任何翻译或其他需要无关操作的东西。

所以当然,由于EVM旨在优化速度和效率,整数大小的选择是:

  1. 256位整数

作为参考,下面介绍如何在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个主要地方可以放置数据

  1. 临时记忆
  2. 永久记忆

堆栈有一定的限制,所以有时你需要使用临时内存而不是非常昂贵的永久内存。 在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缺少访问堆栈中任意项目的方法。 我所知道的每一个虚拟机实现都可以解决这个基本问题

  1. 鼓励小型堆栈大小,并且可以轻松地将堆栈项目交换到内存或其他存储器(如.NET中的局部变量)
  2. 实现一个pick指令或类似的,允许访问任意的栈槽

但是,在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相关推荐

  1. 英语语法基础篇-foundation

    2010年四级语法讲义   主讲:屠浩民 欢迎使用新东方在线电子教材 句子类型:英语语言由词汇和语法构成.大学阶段的语法是融入到阅读.写作.完形.翻译等题目中,帮助读者更好地推测作者想要表达的意愿和情 ...

  2. 手把手教创建你的第一个以太智能合约:ETHEREUM PET SHOP(译)

    手把手教创建你的第一个以太智能合约:ETHEREUM PET SHOP(译) 原文地址 : http://truffleframework.com/tutorials/pet-shop 译者:luci ...

  3. [译转] eBPF 概念和基本原理

    译文:https://cloud.tencent.com/developer/article/1749470 https://tonydeng.github.io/sdn-handbook/linux ...

  4. 3 分钟带你看懂 Acala EVM

    本文译自 Dan receer < Acala Launches the 'Acala EVM' for DeFi on Polkadot | Ethereum Compatibility wi ...

  5. 【杨镇】【中译修订版】以太坊的分片技术官方介绍

    杨镇,资深软件架构师,资深开发工程师.以太坊技术爱好者与布道者. 是Solidity官方文档中译项目的重要贡献者,以太坊Homestead官方文档中文版译者,并对以太坊黄皮书中文版.Thunder共识 ...

  6. 附录2 以太坊:下一代智能合约和去中心化应用平台(选译)

    以太坊基金会 著 李志阔(网名:面神护法) 赵海涛 焦锋 译 中本聪2009年发明的比特币经常被视作货币和通货领域内一次激进的发展,这种激进首先表现为一种没有资产担保或内生价值[1],也没有中央发行者 ...

  7. Windows内核的分析(内存与进程管理器)译自gloomy的文章,由董岩 译

    内存与进程管理器 ========================== But I fear tomorrow I'll be crying, Yes I fear tomorrow I'll be ...

  8. java程序a-z b-y_有一行电文,以按下面规律译成密码: A---Z a---z B---Y b---Y C---X c---x …… 即第1个字母编程第26个字...

    有一行电文,以按下面规律译成密码: A--->Z a--->z B--->Y b--->Y C--->X c--->x -- 即第1个字母编程第26个字母,第i个字 ...

  9. [译] ASP.NET 生命周期 – ASP.NET 上下文对象(六)

    使用 HttpApplication 对象 ASP.NET 框架中的许多类都提供了许多很方便的属性可以直接映射到 HttpContext 类中定义的属性.这种交叠有一个很好的例子就是 HttpAppl ...

最新文章

  1. LinearLayout增加divider分割线
  2. 推荐系统之基于邻域的算法-------协同过滤算法
  3. css实现图片虚化_HTML+CSS入门 如何实现背景图片虚化效果
  4. 一些PHP性能优化汇总
  5. Windows: 在系统启动时运行程序、定时计划任务、定时关机
  6. 阿里云高级技术专家带你全面了解云主机性能评测
  7. spark与Hive的整合入门
  8. iStat Menus 6.51 mac中文版
  9. DB2数据库v10.5安装过程
  10. AI人工智能的5种绝佳编程语言
  11. Java前后端分离处理跨域请求与Nginx跨域配置
  12. Hadoop之日志分析
  13. 漏洞系列一一看我一招征服漏洞SSRF
  14. Java常见问题之Data too long for column 'orResponse' at row 1
  15. (3)登录界面——登录
  16. 微分,变分,差分的确切定义与区别
  17. 华为云classroom Java练习
  18. 问卷星刷问卷python_Python+Selenium自动刷问卷星问卷
  19. Vue入门:(v-for v-model)
  20. MindSpore技术专栏 | AI框架中图层IR的分析

热门文章

  1. 文件上传到ubuntu -- WinSCP 登录ftp服务器
  2. 1028: 在霍格沃茨找零钱
  3. linux下配置某程序的sudo不用输密码
  4. 日积月累-从细节做起
  5. 读书笔记《单核工作法》_6:颠倒you'xian'ji
  6. 最美的Linux中文版的吗,号称最美的Linux发行版——Elementary OS
  7. [云炬学英语]每日一句2020.9.1
  8. USTC English Club Note20171015
  9. python迭代器生成器使用技巧(2):切片、遍历、索引值、多序列、多容器对象
  10. 【CyberSecurityLearning 8】PKI技术与应用