solidity return data和revert/require的reason string的獲得


前言:

在使用solidity寫智能合約的時候,會使用到revert和require來進行斷言,比如:

require(tokenOwner[tokenId] == 0x0,'this is not the first create');

在上面的斷言中,只有當你滿足了tokenOwner[tokenId] == 0x0這個 條件,你才能繼續往下執行,否則就會報錯“this is not the first create”。

然后當我們使用remix這個編譯器的時候,是能夠在出錯的時候得到reason string這個錯誤信息的,如下:

Transact to  token.create errored:VM error:revert.
revert The transaction has been reverted to the initial state.
Reason provided by the contract: “this is not the first create”. Debug the transaction to get more information.

在remix-ide中當transaction出錯,即require斷言沒通過時,在terminal處會返回這樣的信息,但是如果你在geth私有鏈中運行的時候,你只能知道你的transaction失敗了,但是這個時候我們更多的是希望知道是哪里出錯了,而不是僅僅得到交易失敗的結果。

目的:

就是查找如何能夠在使用geth搭建的私有鏈及web3js來實現revert/require的reason string的獲得。

過程:

首先去查看了remix-ide的源碼,然后發現這個返回的錯誤信息是通過讀取獲得的
網址:https://github.com/ethereum/remix/blob/73849adc6bf0eb5f1b8198842cfb9a8f417264b9/remix-lib/src/execution/txExecution.js

 

然后在GitHub中的EIP758(https://github.com/ethereum/EIPs/blob/master/EIPS/eip-758.md)可以看見這的確是一個沒有實現的功能,就是在remix中能夠返回require和revert中的信息,但是geth是會把這個信息直接扔掉的。除此之外,從EIP758也知道,如果你智能合約中的函數有返回值(return data),那么也是會被拋棄的。

⚠️但是EIP758一直講的都是return data的問題,但是很多地方談reason string的時候又把他們合在一起,可能兩者的實現是一起的吧!!!!!
這個好像就是大家也都發現的問題,就是geth是會把內部得到的返回值(或者require和revert中的信息reason string)信息直接扔掉的,但是后面EIP758提出了解決了這個問題的方案:
This EIP proposes that callers should be able to subscribe to (or poll for) completed transactions. The Ethereum node sends the return data to the caller when the transactions are sealed.
目前如果當你的函數是通過eth_sendTransaction or eth_sendRawTransaction RPC request來執行的,那么外部訪問者是沒有方法獲得return data的。

解決方法是在web3js中的函數eth_subscribe中添加查看交易的return data的功能,詳細情況自己看。

 

后面在GitHub中的確有看見別人問和我一樣的問題:https://github.com/ethereum/go-ethereum/issues/15721,是2017/11/21號的時候問的,那時候是說還沒有辦法:
Directly - not yet, pending ethereum/solidity#1686.
You should be able to step it through a debugger, like the one in Remix.
您應該能夠通過調試器進行調試,比如Remix中的調試器。其實就是使用remix,但我就是想知道能不能用geth。

 

參考:https://github.com/ethereum/go-ethereum/pull/16424

         https://github.com/ethereum/EIPs/issues/963

這里有詳細解釋了要如何實現EIP758這個提案:
This functions in two different ways,:
one via the JSON-RPC command eth_subscribe. If the client has a full-duplex connection (ipc or ws), it can issue an eth_subscribe command with params= [“returnData”].For any transactions submitted by the same client after that, the tx hash is saved internally while the transaction is pending, and the appropriate client is notified when it completes.
就是如果你的連接方式是ipc or ws,那你就可以使用命令eth_subscribe,參數指定是“returnData”,即返回信息。然后當交易pending的時候,tx hash並不會給你,而是會保存在內部,直到成功寫上區塊的時候才會通知你信息
the client has only a half-duplex connection (http), then eth_subscribe is not allowed since there are no callbacks. Instead, eth_newReturnDataFilter is sent, the return data of subsequent transactions are stored up in a queue internally, and returned to the client when eth_getFilterChanges is called with the same subscription id.
如果你使用的是http的話,就不能使用命令eth_subscribe,要變成使用eth_newReturnDataFilter,並且返回數據會以隊列的形式保存在內部,知道你調用命令eth_getFilterChanges才會返回給你,不像上面那個直接給你

My proposal is to add a from field in the params of eth_newReturnDataFilter, so that they only listen to transactions sent from a specific address, or a list of addresses--not everyone's transactions. (I've also mentioned an alternative of adding a subID param to eth_sendTransaction and eth_sendRawTransaction, which would also solve the problem.)
就是eth_newReturnDataFilter會監聽所有的返回數據,可以通過設置{from:…}來限制只監聽哪個from的返回數據,或者在發送數據時(即使用eth_sendTransaction和eth_sendRawTransaction)添加subID參數??

Another improvement I plan to add soon is an optional bool param noEmpty, which can suppress notifications for transactions where there is no return data. For noEmpty=false, the client will still be notified when the requested transactions complete, but the return data field will be []
noEmpty=true,就是在當沒有返回數據時就可以不用返回東西,否則仍會返回一個[]空數組

 

因為EIP758的status是Draft,說明該建議在ethereum還沒有真正實現,還在實現中,我們只好等待了!!!!!!

EIP Status Terms

Draft - an EIP that is undergoing rapid iteration and changes
Last Call - an EIP that is done with its initial iteration and ready for review by a wide audience
Accepted - a core EIP that has been in Last Call for at least 2 weeks and any technical changes that were requested have been addressed by the author
Final (non-Core) - an EIP that has been in Last Call for at least 2 weeks and any technical changes that were requested have been addressed by the author.
Final (Core) - an EIP that the Core Devs have decide to implement and release in a future hard fork or has already been released in a hard fork
Deferred - an EIP that is not being considered for immediate adoption. May be reconsidered in the future for a subsequent hard fork.

 

這個建議的依據是EIP 658

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md

EIP 658是建議將return data寫到 transaction receipts,但是這樣會導致 return data is not charged for (as it is not stored on the blockchain), so adding it to transaction receipts could result in DoS and spam opportunities.
所以最后的解決方案是在transaction receipts加上一個狀態status,通過eth.getTransactionReceipt()查看,status為0x0,則交易失敗;status為0x1,則交易成功。該建議的status是final,說明已經實現。

 

但是后面有其他的驚喜:
https://github.com/trufflesuite/ganache-core/issues/116
https://github.com/trufflesuite/truffle/issues/976

在這里我們能夠發現現在ganache-core和truffle的新版本v5.0(beta)已經實現了能夠查看reason string的功能,但是我還沒試過,大家可以自己去看看(但是我就是想使用geth的)

  • Remix gets the reason string because it runs ethereumjs-vm in the browser and can just grab the return data directly out of the vm. The clients' default behavior is to throw that data away rather than include it in the response, so we need changes at that layer to be able to do the same thing.

  • Ganache just merged a PR last week that attaches return data to the error message on eth_call. That change is queued for the next release. As soon as it becomes available we'll begin work on integrating this into the next version of truffle-contract.

通過查看ganache-core的代碼來看它是如何實現這個操作的:

https://github.com/trufflesuite/ganache-core

感覺自己好像低估了ganache的功能,后面好好學學。

 

最終:

終於查明這個功能還沒有實現,只能等待了
https://github.com/OpenZeppelin/openzeppelin-solidity/issues/917

web3.js is not the culprit. There is actually no way to retrieve the revert reason from a node. 

EIP 758 is a proposal to solve that, and there's Geth (ethereum/go-ethereum#16424) and Ganache (trufflesuite/ganache-core#116) issues to implement it.

即上面提到的:
https://github.com/ethereum/go-ethereum/pull/16424(ethereum/go-ethereum#16424)

https://github.com/trufflesuite/ganache-core/issues/116(trufflesuite/ganache-core#116)


I guess we'll have to wait and see how that plays out.


looks like truffle 5.0 will allow for this to be possible: see the relevant beta release notes.

https://github.com/trufflesuite/truffle/releases/tag/v5.0.0-beta.0#reason-strings(truffle的新版本)

truffle v5.0.0-beta.0 是如何解決這個問題的呢:

 Revert with reason strings!!

Find out the reason. At the moment this feature is only supported by the ganache-cli client (>= 6.1.3). Parity and Geth are still working out their implementations.

Solidity中運行:


require(msg.sender == owner, 'not authorized'); 

 再在Javascript中運行

try { await example.transferToSelf({from: nonOwner}) } catch (err) { assert(err.reason === 'not authorized'); assert(err.message.includes('not authorized'); }

 
軟件版本階段說明

Alpha版: 此版本表示該軟件在此階段主要是以實現軟件功能為主,通常只在軟件開發者內部交流,一般而言,該版本軟件的Bug較多,需要繼續修改。
Beta版: 該版本相對於α版已有了很大的改進,消除了嚴重的錯誤,但還是存在着一些缺陷,需要經過多次測試來進一步消除,此版本主要的修改對像是軟件的UI。
RC版: 該版本已經相當成熟了,基本上不存在導致錯誤的BUG,與即將發行的正式版相差無幾。
Release版: 該版本意味“最終版本”,在前面版本的一系列測試版之后,終歸會有一個正式版本,是最終交付用戶使用的一個版本。該版本有時也稱為標准版。一般情況下,Release不會以單詞形式出現在軟件封面上,取而代之的是符號(R)。

所以該truffle的beta版還是測試版

其他:

(1)

一開始以為eth_subscribe已經實現了上面的功能,所以去查看並希望使用web3的1.0版本,在這里遇見了個問題:為了實現上面的函數eth_subscribe,來獲得solidity中require里的斷言失敗信息:

新生成一個文件remix-revert,將之前運行智能合約的文件夾中的package.json復制粘貼過去,然后將里面的web3版本改為1.0.0,而不是還使用0.20.1,然后使用npm init來把package.json中的模塊都安裝下來。但是后面發現這樣不能成功,老是報錯,所以打算還是直接一個個安裝吧。

npm init :生成package.json
安裝時出現了這樣的錯誤:
npm ERR! Unexpected end of JSON input while parsing near ‘...xpress/-/express-2.1.
解決方法是:
npm cache clean —-force

為什么要改web3
因為要試着使用web3.eth.subscribe(“pendingTransactions”)來獲得solidity中require中的信息,但是這個是1.0版本才有的功能,0.20中報錯

(2)
再后面我看見一個問題就是有人問他發現就是如果使用了reason string,它所使用的gas會變得很高,就是使用reason string是一件十分奢侈的事情:
https://github.com/ethereum/solidity/issues/4588

It will not be part of storage, but more likely be stored as a combination of push and mstore
原因可能就是在編譯的過程中,需要將這些reason string壓到內存當中,這個過程當然是會花費gas的。所以我的交易總是頻繁地出現out of gas的原因很有可能就是使用了require的reason string的原因了,所以我的tx的gas才會這么高

https://github.com/ethereum/solidity/issues/4774

說明gas高的原因
Currently all strings are broken up into 32 byte items, PUSHd and MSTOREd.
The helper function CompilerUtils::storeStringData is doing this, however it has a fixed rule for only doing it for >128 characters.
It should be exposed to the optimiser (or user) to decide based on cost.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM