This keeps coming up on the gitter forums so I thought I'd ask and answer this question: How do you know when you've run out of gas?
[Ethereum] How to know when I’ve run out of gas programmatically
contract-debuggingcontract-developmentgasout-of-gasweb3js
Related Solutions
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"
It was due to the GAS LIMIT issue, I set it to 2x of what it was at 70k and it works now. 35k and under is too low.
Best Answer
There currently isn't a clear signal that you've run out of gas. A log message is being added to some future release.
Currently what I do is check to see if gasSent == gasUsed. This requires your code to remember the amount of gas you sent on a particular transaction and wait for the transaction to be mined. If gasSent == gasUsed, you most likely ran out of gas. In my javascript code I throw an exception. This has saved me several embarrassing forum posts and quite a bit of time.
In theory the transaction might be mined if it takes exactly the amount of gas used. There's no way I can think of to figure that out right now, perhaps someone could add to this answer. If you are running that close to the edge of running out of gas you should probably be sending more gas anyways.
I offer gratis some code I used to extend web3 functionality that waits for a transaction to be mined by a local node. It uses promises but adapting it to callback style shouldn't be difficult if you don't want to use promises. I plan on extending this in the future to await consensus from a plurality of nodes but I probably won't share that.
You won't be able to copy and paste this code in your code if you haven't instantiated a log or made a helper function to see if a var is set. If you haven't done those already, you should...
It should be much smaller code to write this for contract-contract calls, so I won't show that example. Same concept - just check to see if gasSent == gasUsed.
(And yes, Promise aficionados, I'm using a promise anti-pattern. I just haven't gotten around to rewriting it. The functionality is correct.)
}