ethereum-optimism / specs

OP Stack Specifications
https://specs.optimism.io
Creative Commons Zero v1.0 Universal
94 stars 88 forks source link

feat: Store historical block hashes to `L1Block` #243

Open pegahcarter opened 8 months ago

pegahcarter commented 8 months ago

Is your feature request related to a problem? Please describe. When it comes to updating the L2, the L1Block is regularly called to update its' state values, including the L1 block hash. This hash is extremely valuable as a user can create a proof for any state of the L1 at the current block hash.

The issue arises when dealing with historical data. As we are only ever using the most-recent hash within L1Block, we are unable to prove L1 state at a historical point in time on-chain without deploying our own contract with manually-synced block hashes.

The value of a chain is derived by the value of its' state. Therefore, by having the ability to prove state at a particular time in the past, we open up cross-chain communication.

An example of what this can be used for is to prove L1 token voting power at a particular point in time.

Describe the solution you'd like Have a mapping with L1Block to store historical hashes. The most simple way I've seen it done is through a basic mapping.

tynes commented 8 months ago

Previous work for this can be found here. We considered having a ring buffer instead of a mapping that grows forever. We decided to not merge this due to the inclusion of eip-4788 in the op stack. The proofs are larger when using 4788 but its still possible. I have been wondering if its a good idea to open a RIP that adds a precompile/predeploy for this functionality so that developers can have a more consistent experience between various L2s

pegahcarter commented 8 months ago

Do you have an idea of how much it would cost to post the updated mapping on L1 once we we're using blobs or no? Since blobs are pruned, my worry would be this additional overhead cost.

tynes commented 8 months ago

Blobs have no impact on this because L2 outputs are not posted to L1, it is the L2 inputs that are posted to L1. Meaning L2 txs (inputs) are posted to L1, not the L2 state (outputs)

Your implementation will cause additional state growth for every L1 block forever. It may be a tradeoff you are willing to take, but generally core devs use ring buffers for these sorts of constructions so that the max storage usage is bounded

pegahcarter commented 8 months ago

Does changing state on L2 have the same impact on L1 as changing state on L1? Ie. adding new variables increases state vs. modifying existing state variables does not. I've been digging through the docs but can't seem to find a distinction between the two.

tynes commented 7 months ago

The op stack batch submits inputs to L1, not state diffs. You can make as many modifications to L2 state as you want in a given L2 transaction and the batch size will be the same given the same sized tx that makes less modifications to L2 state

pegahcarter commented 7 months ago

Okay, that makes sense. What I'm asking is about the difference in:

tynes commented 7 months ago

Adding new variable to L2 predeploys only increase L2 state

pegahcarter commented 7 months ago

IMO it's worth having a predeploy to provide some bridge back to L1 state.

sambacha commented 7 months ago

Previous work for this can be found here. We considered having a ring buffer instead of a mapping that grows forever. We decided to not merge this due to the inclusion of eip-4788 in the op stack. The proofs are larger when using 4788 but its still possible. I have been wondering if its a good idea to open a RIP that adds a precompile/predeploy for this functionality so that developers can have a more consistent experience between various L2s

https://github.com/Dedaub/audits/blob/main/Ethereum%20Foundation/EIP%204788%20Bytecode%20Audit.pdf

zhiqiangxu commented 7 months ago

Previous work for this can be found here. We considered having a ring buffer instead of a mapping that grows forever. We decided to not merge this due to the inclusion of eip-4788 in the op stack. The proofs are larger when using 4788 but its still possible. I have been wondering if its a good idea to open a RIP that adds a precompile/predeploy for this functionality so that developers can have a more consistent experience between various L2s

I vote for adding this feature since the cost is fixed and trivial, and the usage is much easier compared to eip-4788 :)

pegahcarter commented 7 months ago

@tynes IMO is the state bloat of a mapping storing every hash is worth it- otherwise, additional contracts will need to store historical block hashes and the additional redundancy may exceed storing the hash once in a single place.

It also doesn't make sense to skip block hashes (to save space) as oracles, for example, rely on different intervals and may need a specific block.

If we're storing block hashes, we should also store the timestamp associated with the hash.

tynes commented 7 months ago

I think its worth pursuing this feature through the Rollup Improvement Process see https://github.com/ethereum/RIPs/issues/16