EIP-2935: Save historical block hashes in state

Store historical block hashes in a contract, and modify the BLOCKHASH (0x40) opcode to read this contract.

1 Like

Big fan personally, because it would make it so https://github.com/Keydonix/uniswap-oracle/ could work over longer periods of time than 1 hour.

While it’s probably ok to have this list be ever increasing in size, given we currently have a 256 block limit on block hash it seems sensible to put an upper bound on the history stored. That should be as simple as using sstore(HISTORRY_STORAGE_ADDRESS, block.number - 1 % 256, block.prevhash) for storage and sload(HISTORY_STORAGE_ADDRESS, arg % 256) to load, preserving the current checks that arg < 256.

We could still increase the limit from 256 if desired but avoiding the assumption that history will be available indefinitely seems wise, especially since the ETH2 history only preserves a limited range and it’s unlikely that ETH1 will be moved into ETH2 within that timeframe (~13 hours). Otherwise we risk it becoming a backwards incompatible change when moving to ETH2 or being stuck storing unlimited block history forever.

This EIP doesn’t mention any repricing of BLOCKHASH. Seems to me that the current 20(?) will be far too low, seeing as this will have to traverse a (with time) pretty large trie.

After this I had a few discussions and it seems having the blockhash in state, but not in a contract/account, is what would work the best with witnesses and transaction packaging. If it is part of an account’s state, then witnesses have to have a special case for this account or it would not be easy for “relayers” to batch and update witnesses for the blockhash.

By having it in the state I mean to store its root in the block header and keep it separate to the account trie, similar to historical hashes on the beacon chain.

Interesting. It would definitely reduce the ‘witness-churn’, and one could imagine having a BLOCKROOT opcode to retrieve the cht-root. That type of solution could, if given a (number,hash) check if it’s in the history, whereas the proposed 2935 is more powerful; given a number, it could look up the hash.

Dunno enough about the indended usecases to know whether that’s sufficient or not.

Actually that is a good question, what are the intended use cases:

  1. I would think many still use it as a terrible source of randomness. Which is discouraged.

  2. For creating proofs, one can definitely expect the sender to provide their expected root hash (supposedly they compare that against what BLOCKHASH returns) and it would not be unheard of to ask for the appropriate block number too. So I think a system like BLOCKROOT would not hinder the use cases where a proof is validated.

Is there any other use case out there?

Although… this would also suffer from the same churn. Say you provide hashes 15 root elements, and a few more hashes along the way to prove your BLOCKHASH. And ship that off in a transaction… Well, whether it goes into block N or N+1 will make all the difference whether your proof is valid or not.

Btw, sidenode: geth uses chts, both hardcoded (we update the cht when we make new releases) and in contract-form (which we submit new cht signatures into around the time of releases). Those cht:s are in certain section sizes, of 32768 blocks. See https://github.com/ethereum/go-ethereum/blob/master/contracts/checkpointoracle/contract/oracle.sol

This would also make it more straightforward to implement a FlyClient-type client.

One use case to consider is an “introspection engine” that allows contracts to consider assertions about any past state. Similar to verifying a fraud proof against optimistic rollup data, but more generalized. It becomes possible to trustlessly look at any blockchain data from inside EVM.

For this use case, the block number and block hash are known when making the transaction, so it would work fine even if the block hash can only be verified in EVM rather than queried. It’s just proof validation.

One use I’m exploring for such introspection engine is trustless blockchain read access through a network of staked nodes. A client would be able to query the history (at cost), get a signed reply, and be able to verify it with other staked nodes who would be able to slash any node that provided clients with bogus history. That makes ultra-light clients quite easy to implement.

By having it in the state I mean to store its root in the block header and keep it separate to the account trie, similar to historical hashes on the beacon chain.

This is indeed how it’s done on eth2. The challenge I see is just that on eth1 this would be more challenging to implement, requiring a whole new data structure etc etc. Whereas the storage-based solution is much more surgical and non-intrusive. Any opcodes would just seamlessly migrate from being based off of the eth1 blockhash store to being based off of the eth2 blockhash store.