43

web3.eth.estimateGas and JSON-RPC estimateGas can be used to see how much gas should be specified for a transaction, before crafting the transaction.

Since it is an estimate, are there any limitations to it? What cases should its use be relied upon less as it may produce an estimate that is not meaningful or accurate?

3 Answers 3

65

estimateGas works by pretending the transaction was actually being included in the blockchain, and then returning the exact gas amount that would have been charged if that pretend operation was real. In other words, it uses the exact same procedure a miner would use to calculate the actual fee.

Given this approach, a casual observer might think that there are no limitations and that the estimate will always be exactly right. Unfortunately, they would be mistaken.

Imagine a simple contract which checks the most recent block hash and then calls another large, gas-guzzling contract only if the 10th bit in the block hash is a 1. If you use estimateGas to measure the consumption of a transaction which calls this simple contract, your result will depend entirely on whether the most recent block contained a 1 in the 10th bit of its block hash. If a new block is published between when you call estimateGas and when the transaction is actually included, your estimate has a 50% chance of being wildly wrong. This may seem like a contrived example, but lots of contracts use block hashes as a source of entropy, and change their behaviour based on what the hash is. So this is something that could definitely happen in the wild.

But it gets worse. What if the contract your transaction is calling on changes its behaviour based on the transactions submitted to it? Now it's possible for the gas consumption to be different based on the order in which transactions were submitted.

For example, suppose a commonly called contract nearly always costs only a few hundred gas to call. But it contains one weird clause in its code so that a message signed by a specific key alters its behaviour, making it consume millions of gas every time it is called. The same key is then able to alter the behaviour back. Now the owner of that key can alter the contract's gas consumption at will. If the owner is a miner, they could use this "trapped contract" to turn this gas-wasting behaviour on at the beginning of every block they compute, and off at the end. As long as they never publicly publish these private transactions, then every call of estimateGas will return a low value, while every genuinely submitted transaction will cost millions of gas (which goes straight into the miner's pocket). With a careful contract design, they wouldn't even have to actually perform the calculations, because they know what the result will be. And they could execute this "fee-stealing attack" at random, only performing it occasionally so no one realises until many users have been taken in.

Again, this example is contrived. But the point is that you should always consider if an attacker has something to gain by you making a bad estimate. If there's a possible vector, put additional code into the part of your app to handle the situation where your estimate is wrong. One simple technique you should always use is to include a sane gas limit. That way there is at least an upper bound on what an attacker can do. Beyond that, remember that it's called the estimateGas call, not the guaranteedMaximumGas call and just be reasonable in your reliance on it.

tl;dr:
If there is any way for the consequences of your transaction to change depending on when or to whom it is submitted, there is a potential for estimateGas to be wrong by arbitrarily large amounts. Take extra precautions.

8
  • 2
    Great answer. How can miners turn the gas-wasting behavior on and "never publicly publish these private transactions" ? Or should that be a separate question?
    – eth
    Commented Jan 26, 2016 at 0:19
  • 1
    By "never publicly publish" I mean only that they never send the transactions out for other miners to include. Of course when they include the start/stop transaction in their own block it is published as part of the block. So a careful blockchain analysis would still catch what they are doing. I only meant that they never "take a chance" on whether or when someone else may include their transaction by sending it around the p2p network the way a normal transaction would be sent. Does that make sense? Commented Jan 26, 2016 at 5:36
  • Yes, thanks. In tldr, "to whom it is submitted" is not so clear, because the "to" is fixed in the transaction. By "to whom", are you referring to your example of a transaction signed by a specific key?
    – eth
    Commented Jan 26, 2016 at 7:25
  • 1
    If all you mean is the miner, I do think rephrasing would be much clearer :) example "... change depending on who the miner is or when it is mined"
    – eth
    Commented Jan 26, 2016 at 10:51
  • 1
    @NikaKurashvili You're right that only the specific miner can take advantage and their chances of mining a block is proportional to their hashrate. But what's being described isn't intended to be an attack that can be done all the time by all miners. Nothing inconsistent in what you and the answer are saying.
    – eth
    Commented Jan 29, 2021 at 2:19
10

One limitation I have found, or at least a noteworthy aspect of the eth.estimateGas method, is that it only works as intended if the actual call does not throw an exception.

This may well be a bug in the current version of geth, but what I have found is that a call to a method that throws an exception always yields the same gas estimate result, which is a value of 50000000 units of gas. At a gas price of 20000000000, my estimateGas calls were yielding exactly 1 ether, which seemed to be a huge amount for a simple call throwing an exception.

This is a little bit of a bummer, since I have not found a reliable way to estimate gas in transactions that actually throw an exception (which do consume gas) - but at least it is worth knowing ;-) So it looks like a good idea to debug.traceTransaction your transaction prior to estimating its gas consumption to make sure it will not throw an exception and it will not give you widely wrong gas estimate.

Incidentally, this "50000000" value seems to be same that is used to initialize gas related variables in the the source code of the go API (https://github.com/ethereum/go-ethereum/blob/master/eth/api.go), which seems to hint that this is more of a bug than a feature ;-)

2
  • Upvoted. If you can, please file it github.com/ethereum/go-ethereum/issues (and update answer with what happens).
    – eth
    Commented Aug 17, 2016 at 11:46
  • 2
    Interestingly, we actually used the estimateGas function in this way on purpose to detect if someone had already voted on a DAO proposal or was trying to send DAO when it was locked up. Since we knew the contract would throw under those circumstances, we would estimateGas and if it ate up all the gas, we would display a warning instead of sending the TX. This prevented people trying to vote twice / trying to send DAO on the frontend, where we could control the messaging and therefore cut down on support emails.
    – tayvano
    Commented Sep 2, 2016 at 2:46
4

One limitation (from my own observation, hopefully someone will correct me if I'm miunderstanding) is that even if estimateGas estimates correctly, that doesn't give you the gas limit that you need to set when sending your transaction.

The issue is that refunds are credited only at the end of the transaction, so if you have a transaction that does some work, and cleans up some storage as it goes, you need to set a high enough gas limit to do all the work without the benefit of the refund.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.