How safe is it to use block.timestamp as contract expiration time? If a miner provides an incorrect timestamp in a block header, how much can it be off before it is rejected by other nodes? Is there a strategy to make a contract more robust to this?
Not especially, but it depends on what the use case is.
Block times are subject to the following constraints:
- If you stamp your block with a time too far in the future, no one else will build on it (miners will not build on a block timestamped "from the future").
- Your block time cannot be stamped with an earlier time than its parent.
- Difficulty is kept lowest (best deal for miners) by not stamping blocks as earlier than they actually occur.
Taken together, these factors suggest that most blocks produced by small hashrate miners who are not trying to manipulate anything will be pretty close to accurate. However, it is trivial for a more powerful miner to manipulate timestamps over short periods, especially if there is something to be gained by doing this.
Ultimate, there is no cryptographic way to verify the timestamp itself--only the ordering of certain cryptographic structures. Therefore block.timestamp needs to be supplemented with some other strategy in the case of high-value/risk applications.
First, a basic: using strict equality
== block.timestamp would not be safe, since a block with that exact timestamp may never get mined. So use
Now it depends on what happens when the contract "expiration time" is reached. For reasons similar to the safe use of BLOCKHASH,
block.timestamp should only be safely used if the total amount of value resting on what the contract does at "expiration time", is lower than what a miner earns by mining a single block. If the outcome of a lottery worth millions depends on
block.timestamp, the timestamp will probably be manipulated.
The Yellow Paper does not have any answer to "how much can it be off before it is rejected by other nodes". If
block.timestamp is used, the only guarantee (equation 43) is that
block.timestamp is greater than that of its parent. But there are game theoretic reasons why
block.timestamp won't be significantly wrong.
From the Solidity docs another thing to watch for is:
You publish a transaction at a time X, this transaction contains some code that calls
block.timestamp and is included in a block whose timestamp is Y and this block is included into the canonical chain (published) at a time Z. The value of
block.timestamp will be identical to Y, and X <= Y <= Z
A more secure solution would be to rely on block.number and an estimated time per block increment. Let's say you wanted to create a lease for a contract with a specific end date. You could calculate the approximate time with:
estimated_time_in_blocks = lease_length_in_seconds / average_block_time_in_seconds
lease_end_block_number = current_block + estimated_time_in_blocks
In your contract you can then check:
block.number <= lease_end_block_number
The example in Mastering Ethereum is:
with a 10 second block time, 1 week equates to approximately, 60480 blocks. Thus, specifying a block number at which to change a contract state can be more secure, as miners are unable easily to manipulate the block number.
The BAT ICO contract uses this approach