免责声明:本文旨在传递更多市场信息,不构成任何投资建议。文章仅代表作者观点,不代表火星财经官方立场。
边肖:记得要集中注意力。
来源:以太坊爱好者
原标题:一文澄清以太坊开发者常见误区:气、交易和智能合约等。
撰文:spalladino
翻译 校对:闵敏 阿剑
最近偶然看到一篇文章,题目是《程序员关于时区的误解》,让我捧腹大笑。这篇文章让我想起了程序员的其他误解,比如名字和时间,于是我开始寻找任何关于以太坊的资料。找来找去一无所获,只好尽力而为。
关于 Gas 的误解
调用 估计天然气 会返回交易所需消耗的 gas 量
调用estimateGas确实会返回气体消耗量,但这是在当前状态下打包事务时将消耗的气体量。区块链目前的状态可能与你需要结束这笔交易时的状态大不相同。因此,当你的事务被有效地打包成块时,可能会采用不同的代码路径,消耗的气体量也可能完全不同。
如果执行的代码相同,我的交易所需消耗的 gas 量也相同。
这是不对的。即使使用相同的参数执行相同的指令,气费也可能不同。例如,如果要写入一个新的存储位置,SSTORE(写存储操作)的开销将远远高于已经有非零值的存储位置的开销(参见EIP2200)。这意味着,如果你向一个新地址发送两次ERC20令牌转移,第一次交易的成本将远高于第二次,即使两者执行的代码完全相同。
如果状态完全相同,我的交易所需消耗的 gas 量也相同
通常都是,除非你运气不好,硬分叉,导致一些操作被重新定价。虽然这听起来很复杂,但说白了,你无法安全地在dApp中硬编码事务的gas cap,除非你决心在每次硬分叉发生时发布dApp更新。
如果代码相同,状态也相同,且没有发生硬分叉,我就可以相信 estimateGas 的返回值了吗?
现在你可以相信estimateGas的返回值就是你交易消耗的天然气量,但是你不知道这个交易会不会如你所愿。所谓气估计,就是节点会用不同的气值尝试你的交易,并返回最低的气值,保证你的交易不会失败。但是,节点只会查看您的事务,而不会查看事务的内部调用。这意味着,如果您调用的协定代码有一个try/catch块,导致无法撤消内部调用,那么您获得的估计gas值对于调用协定是足够的,但对于被调用的协定是不够的。
在多签名钱包中,这种情况经常发生:即使交易失败,大多数多签名钱包也会将操作标记为已执行,这意味着它们无法撤销最外层的交易。因此,由本地gas估计返回的值可能对于多符号代码来说足够了,但是对于您实际想要运行的操作来说不一定足够。这就是为什么灵知保险箱有一个特殊的气体估算方法。
请注意,这就是为什么由于气体不足而难以检测到操作失败的原因。内部调用可能会因为分配的gas太少而耗尽gas,而事务本身可能仍有许多gas可用。这意味着通过检查交易的用气量和用气量上限来检测用气量错误不是一种可靠的方法。
管他呢,我每次多发送点 gas 就好了
在大多数情况下,这种方法是有效的。但是记住,契约可以检查它在一个交易中收到的汽油。所以合同可以很容易的写成一旦收气过多,交易就会失败。但是,我怀疑这样做除了证明之外,没有任何意义。
只要节点接受了交易,交易就会被挖出
不错的尝试。以太坊的网络拥堵会导致气价大幅波动,所以你的交易可能会被驱逐出mempool(等待被挖出来的交易集合)。如果气价暴涨,需要重新发送交易。
我可以略微提高 gas 价格然后重新提交交易
只要你把气价提高到你所交互的节点所要求的最低量(见txpool.pricebump)就不会有问题,否则还是会被拒绝。
矿工总选择 gas 价格最高的交易
不一定。矿工可以选择任何他们想要的。他们可能会为了自己的利益,把自己的交易塞得满满的,甚至会开放一个协议外的通道,为符合自己要求的用户打包交易。
但是即使他
们依据收益来决定打包优先级,如何以最优方式填满区块也是一个背包问题(knapsack problem)。由于交易无法被分割成几部分,所以,在 gas 上限为 10M 的区块中打包两个 5M gas 交易,而不是一个 6M gas 的交易,可能更为有利可图,即使 5M gas 交易的 gas 价格低于 6M gas 交易。如果我以更高的 gas 价格发送相同的交易,矿工会选择后一个交易来替换前一个交易吗
替换交易必须在旧交易上链之前发送到矿工那里。也就是说,如果你发送了替换交易,你依然需要监控你之前发送的同一个 nonce 下的所有交易的哈希值。
这取决于你所使用的区块参数。如果你根据最新区块来查询你的交易记数,就会忽略你的未打包交易,并进一步导致你不小心覆盖你的某笔未打包交易。
我可以通过 getTransactionCount('pending') 得到我的下一笔交易的 nonce
虽然这在大多数情况下可行,但是你不能保证你的所有未打包交易都在你所查询的节点的 mempool 中。如果你有很多未打包交易,你所通信的节点可能已经丢弃了其中一些交易,但是这些交易仍有可能存在于其它地方!
尽管这是一个非常管用的方法(没错,说的就是轮询!),但是遇上链重组就会出问题。如果你要轮询最新区块上的新 log,你不会收到关于区块重组的通知,也不知道你所看到的事件是否需要重新调整。
我可以通过安装过滤程序来有效监控事件
直到两周前,这还不是一种常见选择,因为 Infura 不支持基于 http 的过滤程序,MetaMask 默认使用基于 http 的过滤程序,也就是说你的 dApp 有 99% 的用户都使用这种过滤程序(注:我可能有些夸大)。除了新事件之外,过滤程序还会通知你因区块重组而删除的事件。但是,这就要求你正在与之交互的基础设施和节点保持在线。如果它们碰巧丢失了过滤程序的状态,你就有可能错过重组事件。
我可以通过 websocket 订阅来有效监控事件
太好了!这样下来,除了要相信你的节点会保持在线之外,你还要相信你本人会保持在线,你和节点之间的连接是可靠的。我想知道这周你在参加 Zoom 会议时掉线了几次?
现在,我必须承认,我已经对这个话题有点着迷了,以至于我在 Devcon 5 上就此进行了一场闪电演讲。如果你想了解更多内容,EIP234 很好地阐述了这些挑战的基本原理,ethereumjs-blockstream则解决了这一问题。
兄弟,如果你还有这种想法,你真的 out 了。我在一篇长达 30 页的文章中阐述过这一点,真的非常长。
不包含任何 DELEGATECALL 的智能合约就是不可更改的
实际上,合约可以定期调用( CALL)到一个可变地址中,并将结果作为计算的一部分,或者作为更改状态的指令,从而更改正在运行的代码。
那不包含任何 DELEGATECALL 或 CALL 的智能合约,总是不可更改的了吧?
还有 STATICCALL。别忘了STATICCALL!
不包含任何 CALL 的智能合约是不可更改的
你还得排除一种情况:这个智能合约是通过 CREATE2 部署的,会在其初始码(initcode)中动态载入运行时,并且可以自毁。在这种情况下,“所有者” 可以销毁合约,并使用不同的代码在同一个地址上重新创建这个合约。
不包含任何 CALL 且不通过 CREATE2 部署的智能合约是不可更改的
还得排除一种情况:这个合约是通过由 CREATE2 部署的合约部署的。因此,你需要追溯整个部署链条,找到最初创建合约的以太坊外部账户,确保没有任何猫腻,而且不存在自毁操作。这篇文章深入探究了这一问题。
我们都知道,有很多以太币是无法使用的,有的是因为外部账户的私钥丢失,有的是因为意外发送到全零地址,还有的是因为被卡在合约中无法处理(对不起,我没忍住)。总而言之,这部分以太币依然存在,但是无法访问。
不过,有一种方法可以销毁以太币。如果你指令一个合约自毁 selfdestruct并指定其自身作为资金的接收方,这个合约内的所有以太币都将被销毁。这就意味着,只要愿意销毁比区块奖励更多的以太币,就可以让以太币通缩。
我可以写一个能拒绝任何以太币转入的合约
你或许知道,如果你没有声明任何 payable方法,Solidity 会拒绝所有发送到你的合约的以太币转账,防止资金被卡在合约内。但是,我们也可以在不触发任何代码的情况下,将资金发送到合约内:要么将该合约指定为自毁操作奖励的接收方,要么将其指定为区块奖励的接收方。正如 @gorgos 在评论中指出的那样,可以预先计算出合约部署地址,并在合约部署前将以太币发送到该地址。
也就是说,如果你追踪所有发送到你的合约的以太币转账,你的总余额可能大于你处理的所有转账的总和。
来源链接:gist.github.com