EIP-2935: Save historical block hashes in state

Implementation in Nethermind:

I am suggesting the cost of SLOAD (800) instead of the cost of BLOCKHASH (20) when searching.
Interesting case is - > shall we always charge 20 for the last 256 blocks or start charging 800 for everything after the FORK_BLKNUM + 256.
I suggest 800 always as we wanted to raise the BLOCKHASH cost anyway.

And the last thing -> I suggest creating an account with the bytecode of [‘STOP’] at the BlockhashStorage address at FORK_BLKNUM to avoid the strange case where we have an account with no code but with storage. This could lead to various problems with existing behaviours.

1 Like

I’d like to argue for adding all block hashes since genesis to the history contract and not only the ones since the HF block. Otherwise if we want to build a light-client sync protocol on top of this EIP, the HF block becomes sort of a new hard-coded genesis. It should also be useful for the L2 networks stated in the motivations of the EIP.

I’d like to argue for adding all block hashes since genesis to the history contract and not only the ones since the HF block.

Unfortunately doing large one-time state changes like that would require machinery that we don’t yet have.

I’d recommend that we just have an “untrusted setup ceremony” where people run a script to generate a Merkle root of the first FORK_BLKNUM block hashes, and that gets hardcoded into all the wallets and anything else that needs to access history; anyone can run the script to check the root themselves later if they wish.

can this be done without the storage trie write? Couldn’t BLOCKHASH simply be re-written to say “either 256 blocks or since the fork” and let the clients choose their own storage mechanism? Perhaps Kate Commitments instead? I really don’t like the idea of adding an unbound merkle trie update to every block. With the miner reward and other contracts at least the trie is not mandated to grow for each block, whereas this requires one storage trie to always update.

I agree the BLOCKHASH opcode and history contract are not dependent on each other as you say, but most of the use-cases become feasible with the history contract. So ideally we’d have that even if without the BLOCKHASH opcode modification (since you can prove old blocks in EVM when you have the commitment).

whereas this requires one storage trie to always update.

If the concern is the cost of this update, can’t the tx base fee be increased to account for it?

EIP-2935 explained by Tomasz Stanczak - https://youtu.be/QH5yuNd3B6o

Good talk by @tkstanczak.
Regarding the comment about not feeding prehistory (block hashes from before the fork) I think that’s fine although I intend to (rarely) access prehistory in one of my contracts.

I intend to solve by running a script, after the fork, which generates a merkle tree of all prehistorical hashes, and stores the root in a contract that has a verification function. Caller would provide block number, block hash, and merkle path to prove it. The script itself will be published so anyone would be able to run it independently to verify the merkle root before trusting that contract. Since prehistory never changes after the fork, you only need to run it once.

Storage is O(1), at the cost of merkle verification on every access. Probably a reasonable trade-off for those rare cases where a contract needs to access ancient blocks.

Can we please use an address close to the bottom of the address space? Anything smaller than 2*32 will be fine.

Before this opcode becomes available, the problem with storing a merkle root of blockhashes up to a certain block number, is that you can’t update the merkle root securely to get the hash of more recent blocks. To combat that, before this EIP is implemented, I wrote a fun little optimistic rollup (based on interactive fraud proofs) to figure out determine historical blockhashes optimistically. It’s super experimental and not tested, but all of the elements are there :slight_smile:.

Are there any plans to implement this proposal please?

2 Likes

Just to mention that this EIP would be very helpfull for L2 bridging.

1 Like

I’m trying to implement this proposal in geth after it appears that it is required for verkle trees.

This raised a couple of questions:

  1. Any thought on how to best adapt this to the timestamp-based forks that we now use?
  2. In particular, why is the activation at block.number > FORK_BLKNUM and not block.number >= FORK_BLKNUM ? It makes things difficult to handle when FORK_BLOCKNUM isn’t readily available.
  3. Given that there is a complete state overall at the boundary, could we simply insert all block hashes in the tree as well, thus ensuring that all historical blocks see their hashes available in the state?
  4. How are the costs of the BLOCKHASH instruction meant to evolve? For instance, in the case of verkle:
  • should only the witness gas costs be charged or not?
  • what extra costs that should be added besides the witness costs?

Clarifying point #2: in stateless mode, having block.time > FORK_BLKTIME forces me to get the parent to check if its timestamp is after the FORK_BLKTIME. Whereas if it’s block.time >= FORK_BLKTIME then all I need is to check if the current block’s time is past the fork time.

1 Like
  1. if 3 then this is irrelevant
  2. I think it may have been an omission
  3. I think it is a good idea
  4. Witness cost and extra processing in case of > 256 (around 100?)

I’m not a fan of storing the entire block hash history in storage, even if it is from a fixed point in time. I’d prefer we adapt what was done for Beacon Roots in EIP-4788 and have a rolling storage set. However instead of TIMESTAMP we would need to use NUMBER and tune the buffer length down. From this we would ensure that at least the last 256 hashes are in storage and that’s all we need for the opcode to work.

I think since it is a new thing anyway, one could store a ZK-friendly Merkle root of the current state and block transactions.

MOD 0x2000 is AND 0x1FFF. Update EIP-2935: replace MOD with AND by chfast · Pull Request #8487 · ethereum/EIPs · GitHub

in the blocknumber > input + 8192 the ADD can overflow. Update EIP-2935: note possible ADD overflow by chfast · Pull Request #8488 · ethereum/EIPs · GitHub

To double check, the list of block hashes here refer to EL block hashes, not CL blocks? That would make this work well across long gaps as well, i.e., on Goerli towards the end there were timestamp gaps between some blocks that exceed 8192 CL slots – as this EIP suggests to keep historical EL block hashes, having long stretches of empty slots shouldn’t matter.

I am working on EIP-7709 (discussion here), which is just the result from splitting the verkle-specific stuff from the historical saving in a storage contract.

Judging from discussions at the interop, it seems like there’s been agreement that:

  • The BLOCKHASH opcode (0x40) should remain the same as it was before (gas cost of 20, return a valid blockhash for the last 256 blocks, and return 0 for block before.)
  • From the (EIP-2935) fork, users can call the storage contract to fetch historical block hashes

Reading this thread, this seems to somewhat differ from the initial intention behind EIP-2935, so wanted to document this here.

3 Likes