如何在以太坊中用尽天然气-Coinmonks
或者,如何编写可伸缩的智能合约。
©Gab Pili /未飞溅
人类对无能为力的人的尊重各有不同,但总有一个极限。 — H. P. Lovecraft
我已经设计和编码以太坊区块链解决方案已有一段时间了。如果您一直在关注这个空间,则会发现我们在这里的处事方式有所不同。我们痴迷于极简代码,忍受着荒谬的局限,我们总是像混乱的消息一样出现在新闻中。
在本文中,我将讨论将以太坊开发人员约束到核心的那些限制之一:块大小。除了不变性,这很容易成为区块链开发的最大限制。
与普通计算机不同,以太坊网络需要按块计算,每个块只能运行有限数量的代码。确切的限制有所不同,但目前大约为1000万天然气。每个以太坊EVM操作都有不同的gas成本,但是最后您需要记住,从存储中读取一个数据元素为200 gas,向存储中写入一个数据元素为20000 gas。
如果运行数学运算,您会发现一个块可以进行大约50,000个读操作,500个写操作或两者的组合。
当人们谈论区块链是实现业务自动化的解决方案时,他们并不总是能正确理解这一概念。区块链根据不变的订单集自动执行反应。
如果我向以太坊主网部署一份合约,说对向我发送加密货币猫的任何人,我都会铸造并发送加密货币狗,这将永远发生。行动之后是确定性反应。
以太坊区块链不做的是运行不会终止的智能合约算法。
尝试执行此操作,您将收到可怕的错误消息。我的智能合约可以为每个动作执行一个不超过一千万天然气的反应。这意味着我们需要少量使用计算资源或将任务分解为若干步骤。如果我想使用智能合约向股东分配股息,则每个人都必须来要求股息。
如果我进行循环分配股利,那么我在用到第500名股东之前就没钱了。
在编写日常应用程序使我进入数学和基本数据结构时,我喜欢它。就像回到大学。快乐的时光。
在编写智能合约时,需要非常小心循环。循环是导致出现错误的邀请,这会破坏您的应用程序。
但是完全没有循环的编码也不是一件好事。
使用最大效用编写智能合约的关键是要对我们使用的数据结构非常谨慎,以了解阻止天然气限制所固有的计算限制,并在其他所有操作失败时将工作分解为单独的调用。
要谈论计算限制,我们将需要使用一些O表示法。如果您需要复习,请访问Wikipedia并阅读有关O(1),O(log N)和O(N)的信息,我们暂时不需要任何其他信息。我会给你一个线索,所有以太坊智能合约都需要在以下微小的优秀银条中运行:
BigOCheatSheet。
如果我们考虑一次读取操作的气体成本为200,而一次写入操作的气体成本为20000,并且块气体限制为1000万气体,那么我们可以断言可以在块中运行的算法。
线性算法
基本数据结构取决于倾向于为O(N)的算法。例如,这将是将数据保留在数组中并循环遍历以执行操作。
如果数据结构增长到大约N = 50000,则任何读取该数据结构的O(N)算法都将耗尽资源。为简单起见,您可以假定这也仅包含一个写操作。我们可以将其称为搜索和写入操作。
如果数据结构增长到大约N = 500,那么任何写在数据结构所有元素上的O(N)算法都将耗尽资源。我将其称为多次写入操作。
在现实环境中进行解释时,这更有意义。如果您拥有可跟踪所有令牌持有者的令牌,并且对于某些操作,您需要在更新一个合约变量之前检查所有令牌持有者,则令牌持有者不能超过50,000。如果您的代币向代币持有者提供奖励,并且您在同一次通话中更新所有余额,那么代币持有者的最大数量约为500。
对数算法
在更复杂的数据结构中,处理数据的算法为O(log N)。这意味着要在包含N个元素的数据结构中查找特定值,只需要执行log N个步骤即可,该步骤的数目要少得多。
如果数据结构增长到大约N = 2 ** 50000,那么在数据结构上读取的任何O(log2 N)算法都将用光。为简单起见,您可以假定它也仅包含一个写操作。我们可以将其称为搜索,这意味着,如果您要在数据结构中搜索的算法为O(log2 N),则智能合约将进行伸缩操作。 Solidity中可以表示的最大数量为2 ** 256,这也是您在任何Solidity数据结构中可以容纳的最大元素数量。
这意味着,如果您在数据结构中搜索的算法为O(log2 N),则智能合约将扩展。
任何在数据结构上写入的O(log2 N)算法都不会在数据结构的所有N个元素上进行写入,最多只能在其中的log2 N个元素上进行写入。这意味着,如果数据结构增长到N = 2 ** 500(仍大于Solidity中存在的最大数量),则具有多次写入操作的O(log2 N)算法将用光。
这意味着,如果您要写入数据结构的算法为O(log2 N),则智能合约将扩展。
我喜欢为自己和他人简化事情。现在我们知道了一般限制及其原因,现在我们可以回到大学并规划出我们可以做和不能做的所有事情:
BigOCheatSheet。
计算机科学中基本上有四个数据结构:
哈希表
除了Solidity之外,哈希表在大多数计算语言中都是相当先进的数据结构。 Solidity中的所有内容都是哈希表,我们称为映射。偶数数组在后台被实现为映射。当实现链接列表时,我使用映射。只需获得映射,即可使用映射。
从映射读取始终为O(1),向映射写入始终为O(1)。没有内置的搜索功能,因此您需要实现其他数据结构之一。这一切都意味着您应尽可能只使用映射,这将确保您的安全。 Solidity中映射的大小限制为2 ** 256。
数组
数组是Solidity中一个有趣的问题,但它们也是唯一可以容纳多个元素并支持搜索功能的内置数据结构。
极客
如果您不需要在一次调用中遍历所有位置,并且仅在末尾插入或删除元素,则数组可以容纳2 ** 256个元素。如果需要在写入存储之前检查阵列中的所有位置,则需要将阵列的长度限制为大约5万,甚至可能更少。如果必须在末尾插入任何地方,则不应该使用数组。
链表
当您需要保留插入顺序以及想要插入任意位置时,链表是您选择的数据结构。您可以使用它们来保存2 ** 256个元素,例如用于访问和任意插入的数组。与数组相同,如果您需要在单个调用中访问所有职位,则需要将其长度限制为5万个元素。
极客
您可以使用链表通过强制插入在适当的位置来使数据元素保持特定顺序。该插入操作将耗费O(N)次读取和O(1)次写入,因此它将在不做进一步优化的情况下将列表的长度限制为几万个。
树木
如果需要有效地搜索有序数据集,则树是在Solidity中使用的数据结构。它们很复杂,但是如果有勇气,可以使用几种实现(订单统计树,红黑树)。树中的所有操作的复杂度为O(log N),这意味着您可以维护一棵天文大小的树。
极客
如果使用树来存储数据,那么对搜索和写入操作的结构大小没有实际限制,而对于多次写入操作则没有十亿亿个元素的限制。不过,您一次调用最多只能进行几百次写操作。
但是,使用树木有其自身的缺点。它们编写和测试很复杂。它们的部署成本很高。我认为使用如此复杂的数据结构对您的项目构成了巨大的风险。
不要相信我,自己测试。您可以使用此小合约来测试一个块中可以容纳多少个读或写操作:
实用性固体^ 0.5.10;合约气体{
事件GasEvent(uint256数据); mapping(uint256 => uint256)公共mappingStorage;函数writeData(uint256 _times)
上市
{
for(uint256 i = 0; i <_times; i ++){
mappingStorage(i)= 1;
}
}
函数readData(uint256 _times)
上市
{
uint256 tmp;
for(uint256 i = 0; i <_times; i ++){
tmp = mappingStorage(i);
}
发出GasEvent(_times);
}
}
通过发出事件来管理这些循环并使读取操作成为状态更改事务,会产生一些额外的开销,但我希望在执行数百次写入或数万次读取后,这些函数会耗尽所有开销。如果将块大小设置为1000万,则结果如下:
合约:天然气
✓writeData(100)(484毫秒)
✓writeData(200)(888ms)
✓writeData(300)(1067毫秒)
✓writeData(400)(661毫秒)
x writeData(500)-气体耗尽✓readData(10000)(3422ms)
✓readData(20000)(3372ms)
x readData(30000)-没气了
x readData(40000)-气体不足
x readData(50000)-气体不足
繁荣。
我花了一段时间才终于了解了智能合约的计算限制。在本文中,我给出了一个简洁明了的指南,说明了在调用以太坊智能合约时可以做什么和不能做什么。
对于其他任何事情,您将需要将代码分解为不同的调用并构建状态机。您最好考虑是否应该在区块链中进行编码。
如果必须访问数据结构的所有元素,则大小限制为数万。如果必须为数据结构中的每个元素编写数据,那么大小限制将是几百个。
不要用尽汽油