【问题标题】:Read-only Functions Iteration Cost只读函数迭代成本
【发布时间】:2018-02-12 21:11:27
【问题描述】:

我正在为一些用例开发智能合约,目前我正在优化智能合约。我对在Hitchiker's Guide 上发现的有趣的东西感到困惑。在第 4 节 - 迭代合约代码

// returns true if proof is stored
// *read-only function*
function hasProof(bytes32 proof) constant returns (bool) {
for (uint256 i = 0; i < proofs.length; i++) {
    if (proofs[i] == proof) {
        return true;
    }
}
return false;
}

对于上面的这段代码,他说:“请注意,每次我们要检查文件是否经过公证时,我们都需要遍历所有现有的证明。这使得合约在每次检查上花费越来越多的气体作为更多的文件已添加。”

毫无疑问,实现它的正确方法是使用映射而不是数组结构。这是让我感到困惑的一点。它是只读功能,不是影响区块链的交易。当我观察我的 netstats 时,调用此函数时它没有显示任何事务(实际上,这是我在调用此函数之前所期望的)。

我不认为他误解了机制,有人可以清理我的这个评论吗?

【问题讨论】:

    标签: ethereum solidity


    【解决方案1】:

    罗曼的回答不正确。常量函数仍然消耗气体。但是,当它在本地 EVM 中运行时,您无需支付 gas 使用费。如果从事务中调用常量函数,则它不是免费的。无论哪种方式,您仍在消耗气体,而循环是消耗大量气体的好方法。

    编辑 - 这是一个说明这一点的例子

    pragma solidity ^0.4.19;
    
    contract LoopExample {
      bytes32[] proofs;
      
      function addProof(bytes32 proof) public {
        if (!hasProof(proof))
          proofs.push(proof);
      }
      
      function hasProof(bytes32 proof) public constant returns (bool) {
        for (uint256 i = 0; i < proofs.length; i++) {
          if (proofs[i] == proof) {
            return true;
          }
        }
    
        return false;
      }
    }
    

    这是调用addProof 4次的gas消耗结果:

    addProof("a"): 41226

    addProof("b"): 27023

    addProof("c"): 27820

    addProof("d"): 28617

    你不得不忽略第一个电话。一个比其他开销更大的原因是因为第一次推送到proofs 会花费更多(在第一次调用之前没有使用存储槽,所以推送将花费 20000 gas)。所以,这个问题的相关部分是查看addProof("b") 的成本,然后查看之后每次调用的增加。添加的项目越多,循环使用的 gas 就越多,最终你会遇到一个 out of gas 异常。

    这是另一个仅从客户端调用常量函数的示例:

    pragma solidity ^0.4.19;
    
    contract LoopExample {
      function constantLoop(uint256 iterations) public constant {
        uint256 someVal;
        
        for (uint256 i = 0; i < iterations; i++) {
          someVal = uint256(keccak256(now, i));
        }
      }
    }
    

    这里,如果你通过 Remix 调用它,你会在输出中看到类似这样的内容(注意关于 gas 使用的评论):

    最后,如果你尝试从客户端使用太多迭代运行这个常量方法,你会得到一个错误:

    $ truffle console
    truffle(development)> let contract;
    undefined
    truffle(development)> LoopExample.deployed().then(function(i) { contract = i; });
    undefined
    truffle(development)> contract.constantLoop.call(999);
    []
    truffle(development)> contract.constantLoop.call(9999);
    []
    truffle(development)> contract.constantLoop.call(99999);
    Error: VM Exception while processing transaction: out of gas
        at Object.InvalidResponse (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\errors.js:38:1)
        at C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\requestmanager.js:86:1
        at C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\truffle-provider\wrapper.js:134:1
        at XMLHttpRequest.request.onreadystatechange (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\httpprovider.js:128:1)
        at XMLHttpRequestEventTarget.dispatchEvent (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:64:1)
        at XMLHttpRequest._setReadyState (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:354:1)
        at XMLHttpRequest._onHttpResponseEnd (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:509:1)
        at IncomingMessage.<anonymous> (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:469:1)
        at emitNone (events.js:91:20)
        at IncomingMessage.emit (events.js:185:7)
    

    【讨论】:

    • 到目前为止,您的回答似乎最有效,但它并没有清楚地解决我的问题。您能否提供有关“如果从事务中调用常量函数......”的更多见解。样品是什么意思?谢谢,
    • 你是绝对正确的。我是从错误的角度考虑的。
    • @MehmetDoğan 这意味着你可以在本地机器上调用这个函数,它是免费的,但是如果这个常量函数将在矿工节点上已经执行的交易中被调用,它会花费你的 gas 费,因为这些计算不会像在个人 .call() 中那样在您的本地计算机上发生。
    • @MehmetDoğan - 请参阅编辑以获取示例。希望这能解决问题。
    • 如果我们确实需要这种功能,那么我们如何为这种情况支付gas?
    【解决方案2】:

    大部分人会说常量函数不会消耗gas。这是不正确的,因为常量函数在本地节点的硬件上使用它自己的区块链副本执行。这使得操作本质上是只读的,因为它们实际上从未广播到网络。

    假设我已经签订了具有​​ isEmp 常量函数的合同,当我调用estimateGas() 时,如果常量方法不消耗气体,它应该返回 0。但它返回 23301。

    truffle> contractObj.isEmp.estimateGas(web3.eth.accounts[0])

    23301

    这是我的 Solidity 代码

    function isEmp(address employee) public constant returns(bool) {
        return  emps[employee];  
    }
    

    因此上面的实验证明它会消耗gas。

    【讨论】:

      【解决方案3】:

      问题是常量(现在称为视图)函数从以前执行的普通函数中获取信息。为什么?因为视图函数正在访问在创建合约时创建的合约的存储,并且如果您以这种方式编写函数,则会使用更改它的函数进行更改。例如:

        contract calculateTotal{
          
         uint256 balance;
      
        constructor(uint256 _initialBalance){
      
             balance=_initialBalance;
      
             }
      
        function  purchaseSmth(uint256 _cost) external {
      
          balance=balance-_cost;
      
         }
      
        function getBalance() external view returns(uint256){
      
           return balance;
         }
      
      
      }
      

      现在,当您创建此合同时,您设置了余额,这样余额就已经计算过了,您不需要重新计算,因为它是相同的。当您使用 purchaseSmth 更改余额值时,将重新计算余额并且余额的存储值将发生变化。这意味着该功能将改变区块链的存储。但是你再次为这些处决买单,因为区块链必须改变。但是视图函数不会改变任何存储值。他们只是得到先前计算的已经存在于区块链中的值。当您在视图函数中进行一些计算时,例如:return balance*10; 执行不会影响区块链。它的计算将由您的设备完成。不是任何矿工。

      如果我们查看@AdamKipnis 的答案,我们会发现如果您立即调用常量/视图函数,它本身不会消耗任何气体。因为它总是会在之前计算。首先,构造函数会将其创建为空数组。然后,当您调用 hasProof 时,您将看到一个空数组。然后,当您使用 addProof 时,它会更改 proofArray。但是价格越来越高,因为它使用了 hasProof 函数。如果它不存在,那么价格不会永远上涨。但是,如果您手动调用 hasProof 函数,那么无论该函数是否具有,您的计算机(如果它是一个节点)将在 本地 执行该循环。但是由于区块链逻辑,任何存储、状态更改执行都将被定向到矿工。其他人应该执行该代码并验证它是否可以在该事务中进行更改并且代码中没有没有错误

      当您在某些情况下使用像元掩码钱包这样的钱包时,您可以在进行任何交易(执行)之前看到错误。但这不是一个已验证的错误,直到您进行交易并且矿工执行该代码然后发现该代码有一些错误或者它正在做,以一种不应该可能的方式更改某些内容。就像试图从另一个人的钱包中向自己转移一些 bnb 一样。该执行不可能发生,因为转移 bnb 的不是您的钱包。这听起来很明显,但随机矿工应该验证该执行以创建一个包含您的交易的区块,以及当时每个区块交易限制边界中的其他执行。

      编辑:设备、计算机、硬件是节点。节点可以是您,或者如果您使用远程节点进行执行,那么该节点将执行您的设备不会执行的所有操作。就像云计算一样。因为如果你不是节点,你就没有账本。

      【讨论】:

        【解决方案4】:

        仅当常量(视图或纯)函数由另一个外部智能合约执行或调用时才会消耗气体,而该智能合约不是该函数的所有者。但是,如果它是从声明常量(视图或纯)函数的智能合约中调用的,则不会使用任何气体。

        就像在其他编程语言中通过创建该类的对象来在另一个类中使用类成员函数一样。但实际上,无论它是否是一个常数函数,这都会让你付出代价。

        所以当 Jitendra Kumar。 Balla 做了一个实验,表明它会消耗 gas,如果它被另一个智能合约调用而不是从智能合约内部调用,那么显示的 gas 就是估计的 gas 成本。

        【讨论】:

          猜你喜欢
          • 2020-01-16
          • 2011-10-20
          • 2018-06-06
          • 2019-03-01
          • 2017-03-22
          • 2023-03-20
          • 1970-01-01
          • 2020-03-10
          • 1970-01-01
          相关资源
          最近更新 更多