只读函数迭代成本

时间:2018-02-12 21:11:28

标签: ethereum solidity

我正在为一些用例开发智能合约,目前我正致力于优化智能合约。我对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;
}

对于上面的代码,他说&#34;请注意,每次我们要检查文档是否经过公证时,我们都需要遍历所有现有的证明。这使得合同在每张支票上花费越来越多的气体,因为增加了更多的文件。 &#34;

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

我不认为他误解了这个机制,有人会清楚我对这个评论的看法吗?

3 个答案:

答案 0 :(得分:5)

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

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

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次的耗气量结果:

  

addProof(“a”):41226

     

addProof(“b”):27023

     

addProof(“c”):27820

     

addProof(“d”):28617

你必须忽略第一个电话。一个人比其他人花费更多的原因是因为第一次推送到proofs将花费更多(在第一次通话之前没有使用存储槽,所以推动将花费20000气体)。因此,此问题的相关部分是查看addProof("b")的成本,然后查看随后每次调用的增加。你添加的物品越多,循环使用的气体就越多,最终你会遇到燃气异常。

以下是另一个只从客户端调用常量函数的示例:

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调用它,你会在输出中看到这样的东西(注意关于燃气使用的评论):

Remix Output Screencap

最后,如果您尝试使用过多的迭代从客户端运行此常量方法,则会出现错误:

$ 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)

答案 1 :(得分:2)

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

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

  

松露&GT; contractObj.isEmp.estimateGas(web3.eth.accounts [0])

     

23301

这是我的可靠性代码

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

因此,上述实验证明它会消耗气体。

答案 2 :(得分:0)

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

  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 函数,那么您的计算机(如果它是一个节点)将本地执行该循环,无论该函数是否具有该循环。但是由于区块链逻辑,任何存储、状态更改执行都将被定向到矿工。其他人应该执行该代码并验证它是否可以在该事务中进行更改并且代码中没有错误

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

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