[Ethereum] How to the transaction status from a thrown error be detected when gas can be exactly the same as the gasUsed for a successful transaction

gastransactions

This question arose from answering Transaction Status.

In the following example, I am sending gas of 21,000 (the amount required for a regular transaction). In this situation gas == gasUsed, and so cannot be used to determine if the contract has thrown an exception?

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1.2345, "ether"), gas: 21000})
"0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3"
> eth.getTransaction("0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gas: 21000,
  gasPrice: 20000000000,
  hash: "0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3",
  input: "0x",
  nonce: 55,
  to: "0x4d5bbe7fbc80933ffa90ece988a764e41ee6d018",
  transactionIndex: null,
  value: 1234500000000000000
}
> eth.getTransactionReceipt("0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3")
{
  blockHash: "0xf0af8236ceec7ad1839d67c9934ab062a8d95fa1f88b06139f97dbdfbd1cd842",
  blockNumber: 2234,
  contractAddress: null,
  cumulativeGasUsed: 21000,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 21000,
  logs: [],
  root: "3280f47a0de1149ad5c5fda421faaf95f303da8a77e83c8ec6ac2b3d8ca27abc",
  to: "0x4d5bbe7fbc80933ffa90ece988a764e41ee6d018",
  transactionHash: "0xc7c63b67747c0c825229ce3d36d226423adb8cab6bebe12b6d5001e0dc3f79b3",
  transactionIndex: 0
}

A transaction can be set with the gas being exactly the same as the gas that will be used, using web3.eth.estimateGas function to firstly estimate the required gas.

So, how can the transaction status from a thrown error be detected when gas can be exactly the same as the gasUsed for a successful transaction?


EDIT 15/06/2016 – Explaining differences from How do I know when I've run out of gas programmatically?

In this question, we already KNOW how to detect that we have run out of gas programmatically. And we already know that this method to detect an error transaction is not reliable, as gas == gasUsed is a valid condition for a non-error transaction.

What this question is asking is what reliable alternatives are there to detect an error transaction when the "run out of gas" situation is not reliable?

Best Answer

Summary

I have sent a test transaction (#1) to firstly find out how much gas a successful smart contract transaction uses. From the results of this transaction, the gas required for a successful transaction is 26747.

I then used this 26747 gas amount in my going-to-be-successful transaction (#2). And gasUsed == gas.

I then use this same 26747 gas amount in my going-to-be-UNSUCCESSFUL transaction (#3). And gasUsed == gas.

The only reliable way I have found so far to easily check if a smart contract transaction is successful is to use debug.traceTransaction and check the last error message. If this is "" then no error occurred. If this is "Out of gas" or "invalid jump destination (PUSH1) 2", then an error occurred.

And here's a short bit of code to determine the status of your transaction.

> var status = debug.traceTransaction("0xd23219e2ea10528b245deb9e47993cae2ffd3ffe9fc27aeb808e94fc4e75d37b")
undefined
> if (status.structLogs.length > 0) {
  console.log(status.structLogs[status.structLogs.length-1].error)
}
"invalid jump destination (PUSH1) 2"

The return value above will be "" if there are no errors, or "Out of gas" if you run out of gas.

UPDATE 29/07/2016 - the geth --fast blockchain download does not contain the information for debug.traceTransaction(...) to display.



Details

I'm using the following example for a smart contract that throws an exception if _value < 12345:

contract TestStatus {
    uint public value;
    function setValue(uint256 _value) {
        value = _value;
        if (_value < 12345) {
            throw;
        }
    }
}

I flattened the source to:

var testStatusSource='contract TestStatus { uint public value; function setValue(uint256 _value) { value = _value; if (_value < 12345) { throw; } }}'

I compiled and inserted the contract into the blockchain:

var testStatusCompiled = web3.eth.compile.solidity(testStatusSource);

var testStatusContract = web3.eth.contract(testStatusCompiled.TestStatus.info.abiDefinition);
var testStatus = testStatusContract.new({
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code, gas: 1000000}, 
  function(e, contract) {
    if (!e) {
      if (!contract.address) {
        console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + 
          " waiting to be mined...");
      } else {
        console.log("Contract mined! Address: " + contract.address);
        console.log(contract);
      }
  }
})
Contract transaction send: TransactionHash: 0x4de11cc54484333036f45a2441563d6badae43d2e2789e0b113b6703a582a879 waiting to be mined...
undefined
...
Contract mined! Address: 0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c
[object Object]

I sent a transaction that won't cause an error to determine the gas required:

# Not an error
testStatus.setValue(123456, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code,
  gas: 41747
});
...
> eth.getTransactionReceipt("0x727cb3c6846bbb05c8437e97aa32db39a252426b09ed837a76bb364748ce73c4")
{
  blockHash: "0x293b4d357d72615c409ba37a6430253d64b4646e0366ac987d1364a258cf81fa",
  blockNumber: 2465,
  contractAddress: null,
  cumulativeGasUsed: 26747,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 26747,
  logs: [],
  root: "fbb29f9eff2340aa8d7df7feec74671c2b693fb9e7b6ec3b710ca6d64813da0e",
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionHash: "0x727cb3c6846bbb05c8437e97aa32db39a252426b09ed837a76bb364748ce73c4",
  transactionIndex: 0
}

The gas required is 26747.

I sent a transaction with value=123456 that will NOT fail, and gas==gasUsed:

testStatus.setValue(123456, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code,
  gas: 26747
});
0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1

I'm using debug.traceTransaction to confirm that this transaction has not encountered an error.

> var status = debug.traceTransaction("0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1")
undefined
> status.structLogs[status.structLogs.length-1].error
""

And in this situation gas(26747) == gasUsed(26747)

eth.getTransaction("0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1")
{
  blockHash: "0x961277e2bebfe8332a23d240672b2658eced493b70fd145c99991c3c8651adcc",
  blockNumber: 2475,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gas: 26747,
  gasPrice: 20000000000,
  hash: "0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1",
  input: "0x55241077000000000000000000000000000000000000000000000000000000000001e240",
  nonce: 66,
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionIndex: 0,
  value: 0
}
eth.getTransactionReceipt("0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1")
{
  blockHash: "0x961277e2bebfe8332a23d240672b2658eced493b70fd145c99991c3c8651adcc",
  blockNumber: 2475,
  contractAddress: null,
  cumulativeGasUsed: 26747,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 26747,
  logs: [],
  root: "f850e1e5c99352f47e3409e4e9f966a49c13152e63aaa7bb3765f0b37bfe09e5",
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionHash: "0xc0ab94adfe473811d055f98528a625dbebf66026b7ec5ad8eab939de862bc2d1",
  transactionIndex: 0
}

I then sent a transaction with value=123 that failed, and gas==gasUsed:

testStatus.setValue(123, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: testStatusCompiled.TestStatus.code,
  gas: 26747
});

0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a
> var status = debug.traceTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
undefined
> status.structLogs[status.structLogs.length-1].error
"invalid jump destination (PUSH1) 2"

> eth.getTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
{
  blockHash: "0x22fb5fcaef27dd017efcde8c3f78df7f5168c505210f5d08872a9c2877146044",
  blockNumber: 2484,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gas: 26747,
  gasPrice: 20000000000,
  hash: "0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a",
  input: "0x55241077000000000000000000000000000000000000000000000000000000000000007b",
  nonce: 67,
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionIndex: 0,
  value: 0
}
> eth.getTransactionReceipt("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
{
  blockHash: "0x22fb5fcaef27dd017efcde8c3f78df7f5168c505210f5d08872a9c2877146044",
  blockNumber: 2484,
  contractAddress: null,
  cumulativeGasUsed: 26747,
  from: "0xa7857047907d53a2e494d5f311b4b586dc6a96d2",
  gasUsed: 26747,
  logs: [],
  root: "ab51ab81c19eb8da7f8d0216d6c2f1d8946e88842b758ff0af13c28ff7e181b4",
  to: "0x87847eb0f944fbb6d5c5a4891e3b103a63cee45c",
  transactionHash: "0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a",
  transactionIndex: 0
}

So the only easy reliable way I've found to determine if a smart contract transaction has succeeded or failed is to use debug.traceTransaction.

Here's the output of debug.traceTransaction for the last transaction that failed.

debug.traceTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
{
  gas: 26747,
  returnValue: "",
  structLogs: [{
      depth: 1,
      error: "",
      gas: 5280,
      gasCost: 3,
      memory: null,
      op: "PUSH1",
      pc: 0,
      stack: [],
      storage: {}
  }, {
      depth: 1,
      error: "",
      gas: 5277,
      gasCost: 3,
      memory: null,
      op: "PUSH1",
      pc: 2,
      stack: ["0000000000000000000000000000000000000000000000000000000000000060"],
      storage: {}
  }, {
  ...
  }, {
      depth: 1,
      error: "invalid jump destination (PUSH1) 2",
      gas: 129,
      gasCost: 8,
      memory: ["0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000060"],
      op: "JUMP",
      pc: 66,
      stack: ["0000000000000000000000000000000000000000000000000000000055241077", "0000000000000000000000000000000000000000000000000000000000000022", "000000000000000000000000000000000000000000000000000000000000007b"],
      storage: {
        0000000000000000000000000000000000000000000000000000000000000000: "000000000000000000000000000000000000000000000000000000000000007b"
      }
  }]
}

And here's a short script to check if the transaction passed or failed:

> var status = debug.traceTransaction("0x9ee86a200528de32a695f1e2dd0d94a3871fefc7e49c5fd24a4a37eab1b99f7a")
undefined
> status.structLogs[status.structLogs.length-1].error
"invalid jump destination (PUSH1) 2"
Related Topic