Ethers.js: Support for debug_traceTransaction

Created on 18 Apr 2018  Â·  18Comments  Â·  Source: ethers-io/ethers.js

Can you add support for debug_traceTransaction?

The scenario I have is I want to get the returned data from a failed transaction. This has been introduced in Solidity 0.4.22 with revert with reason. The error string from a revert or require is an ab-encoded call to function Error(string). For example https://twitter.com/LefterisJP/status/985625110781595653

As the transaction returnValue is not available in the transaction receipt, the only way I can think of getting it is with a debug_traceTransaction. Unless you can think of a better way?

enhancement

Most helpful comment

As a quick note, this is easy to use today in ethers, by using the JsonRpcProvider.prototype.send. Once we have a better specification, it will be added to ethers, but in the meantime:

var provider = new ethers.providers.JsonRpcProvider();
var result = await provider.send('debug_traceTransaction', [ transactionHash ])

This will also work with the IpcProvider. Please keep in mind you need a node that has been synced with tracing enabled.

All 18 comments

We also use this function to make sure all the required debug info is available in our product. It would be really helpful to have it in Ethers.

Would love to add this. debug_traceTransaction is specific to the JSON-RPC API's though, and only if tracing is enabled, no? Is it supported at least be INFURA yet?

Will the data be included in the receipt? Do you have any example transactions you can point to for testing?

So many questions... :)

Information on Geth's support for tracing is here.

For Geth to expose the debug_traceTransaction RPC API endpoint, the debug API namespace needs to be exposed via --rpcapi=debug

I've asked the Infura team whether they support debug_traceTransaction. I'll revert with their reply.

No, the transaction return data is not in the transaction receipt :-(. A lot of problems would be solved if the return data was included in the transaction receipt. Using events is a hacky workaround

So Infura does not yet support tracing. It's something they want to do but it's going to take them some time to develop an at scale solution.

They are putting a new ticket on their backlog to investigate an alternative method for exposing the return value in a more performant way. Stay tuned

I still need to think over the best way to implement this, but let me know what you think of this idea:

Make it part of the receipt API. So, getTransactionReceipt will have a field like revertReason. On platforms that support it, it is present, otherwise it is null. Under the hood it would still use debug_traceTransaction on JsonRpcProviders and once INFURA adds it, however they get it. And I'm sure etherscan will provide that data at some point, so then it can get it from their API.

Basically, I don't like conflating the JSON-RPC and node specific calls with general functionality. I think the receipt makes the most sense as a place to put it. But the end user shouldn't care what backend they connect to.

This is all still up for discussion, just my gut feeling right now. :)

I don't think it needs to be added to every call. If you are using it for revert with reason, you only need to do the extra call if the tx receipt returned status == 0 and the contract implemented revert/require with reason.

Another use case is you want the returned data from a successful transaction. For example, if there were a transferAll function on a token, then you'd want to know how much you actually transferred in the transaction. In this case, you'd want to do an extra call to debug_traceTransaction to get the returned balance that was transferred. For regular transfer transactions, you know exactly what happened if a status == 1 was returned in the tx receipt so you don't want to do an extra call to debug_traceTransaction.

My gut feel is let the apps using Ethers needs to control what calls they want to make. It's good for Ethers to abstract the different formats, but not the calls

Let's see what the Infura team come up with. I'll also try and connect with the Etherscan team

I think this would be really cool, BUT there is ultimately the big issue that you have to be connecting to a geth node that has "debug" enabled under the http methods. @ricmoo if you plan to build this out let me know, I'm doing a lot of tracing work right now so would love to help here and there.

I've added support for EIP838 for for call in the TypeScript branch.

Any news back from Etherscan or INFURA regarding support?

In my last update from the Infura team, they had to put in new infrastructure to make getting the return data performant and scalable. I'll get another update and hopefully some timelines

It's going to be several months before Infura will have a solution for getting the return data from a transaction

As a quick note, this is easy to use today in ethers, by using the JsonRpcProvider.prototype.send. Once we have a better specification, it will be added to ethers, but in the meantime:

var provider = new ethers.providers.JsonRpcProvider();
var result = await provider.send('debug_traceTransaction', [ transactionHash ])

This will also work with the IpcProvider. Please keep in mind you need a node that has been synced with tracing enabled.

I don't think there has really been any consensus on these type of operations, which are already fairly low-level and node-dependent, so I don't think I'll add any BaseProvider support for this.

However, there is plenty of room for a DebugProvider that sub-classes JsonRpcProvider, and adds the additional functionality, using the underlying provider.send. It could also detect whether Geth vs Parity is in use, and detect that tracing, et cetera is active.

I'm going to close this, but anyone who puts together such a Provider, let me know and I'll include it in the documentation. :)

Makes sense to close as the providers are not standard. The following works with Parity

const trace = await this.transactionsProvider.send(
    'trace_replayTransaction',
    [ transactionReceipt.transactionHash, [] ],
)

const output = trace.output.slice(2) // snip off 0x

// Get the length of the revert reason
const strLen = parseInt(output.slice(8 + 64, 8 + 128), 16)

// Using the length and known offset, extract and convert the revert reason
const reasonCodeHex = output.slice(8 + 128, 8 + 128 + (strLen * 2))
const reasonCode = utils.toUtf8String('0x' + reasonCodeHex)

There is already EIP-838 support for the Error(string) and once the compiler starts using custom errors they will be added too. They’ll also be added to the human-readable ABI. :)

Oh, I think you could also use:

let reason = ethers.utils.defaultAbiCoder.decode( [ “string” ], trace.output.slice(4));

@ricmoo it makes a lot of sense to use the ABI coder to decode the trace output, but Ethers 4 fails for large outputs which are greater than 2**53, like
0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001455494e543235365f5355425f4f564552464c4f57000000000000000000000000 which encodes UINT256_SUB_OVERFLOW.

Error: overflow (operation="setValue", fault="overflow", details="Number can only safely store up to 53 bits", version=4.0.26)
    at Object.throwError (/path/node_modules/ethers/errors.js:76:17)
    at BigNumber.Object.<anonymous>.BigNumber.toNumber (/path/node_modules/ethers/utils/bignumber.js:162:20)
    at /path/node_modules/ethers/utils/abi-coder.js:683:78
    at Array.forEach (<anonymous>)
    at unpack (/path/node_modules/ethers/utils/abi-coder.js:680:12)
    at CoderTuple.Object.<anonymous>.CoderTuple.decode (/patht/node_modules/ethers/utils/abi-coder.js:807:22)
    at AbiCoder.Object.<anonymous>.AbiCoder.decode (/path/node_modules/ethers/utils/abi-coder.js:956:61)
    at Token.sendSignedTransaction (/path/ts/BaseContract.ts:712:61)
    at process._tickCallback (internal/process/next_tick.js:68:7)

@weijiekoh What do you mean? I can parse the message:

let data = "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001455494e543235365f5355425f4f564552464c4f57000000000000000000000000"
console.log(ethers.utils.defaultAbiCoder.decode([ "string" ], arrayify(data).slice(4)))
// [ 'UINT256_SUB_OVERFLOW' ]

Can you explain more what you are doing?

I tried again and this worked:

utils.defaultAbiCoder.decode(['string'], ethers.utils.hexDataSlice(trace.output, 4))[0]
Was this page helpful?
0 / 5 - 0 ratings

Related issues

jochenonline picture jochenonline  Â·  3Comments

GFJHogue picture GFJHogue  Â·  3Comments

moshebeeri picture moshebeeri  Â·  3Comments

thegostep picture thegostep  Â·  3Comments

ricmoo picture ricmoo  Â·  3Comments