Here is summary of our experience working on the rollup-geth
during our “experimentation phase.”
Our approach
1. “Feature flags”
All newly developed “features,” i.e., EIPs/RIPs, are behind the “feature flags,” i.e. (hard) forks. This ensures that:
- We don’t introduce any breaking changes to L2s
- All the changes are opt-in.
This should reduce friction and hopefully accelerate adoption.
2. “Conflict-free” code
We should aim for the “conflict-free code” - this is not practically possible, but the fewer conflicts/smaller the diff, the better. This is important for four couple of reasons:
- It reduces the chances of introducing new bugs.
- The cost of maintaining the newly added feature vastly reduces
- What I have found is that it leads to more modular/maintainable code.
- The sum of all the above is increased dev velocity.
What does this mean in practice?
The approach that yielded the best results is to, if possible, encapsulate newly added logic into a separate function and move this function to a separate file.
Code-example:
func (st *StateTransition) preCheckGas() error {
if st.evm.ChainConfig().IsEIP7706(st.evm.Context.BlockNumber, st.evm.Context.Time) {
return st.preCheckGasEIP7706()
}
return st.preCheckGasEIP4484()
}
In the upstream geth
code, there is a preCheckGas
function, instead of “bloating” it with the EIP-7706 specific code, I have split the gas handling in “pre-EIP-7706” and “EIP-7706”. The benefits of this approach are:
- The “old code” hasn’t changed, thus it’s easier to review, and I didn’t introduce any bugs “there”
- The logic for “pre-checking gas” is in a separate file - again, it is easier to review
- It’s easy to extend
preCheckGas
with aswitch
to handle more EIPs in the future if need be
3. Familiarity with the codebases.
Of course, the developer(s) working on the rollup-geth must be familiar with the geth codebase, but they should also be familiar with the L2-geth codebase(s). This is important because it drives implementation design and choices.
To exemplify this, take a look at L1SLOAD precompile. The RIP requires that L2 has some notion of the latest L1 block. But the way the L2s implement this (have an idea of) is different. E.g., Scroll and Op-stack have a “system contract,” and by querying the state of this contract, the node can “get” the latest L1 block number. But as of the time of writing, this is not how, e.g., Arbitrum works. This means that for rollup-geth
, we must consider these differences, and we cannot implement the L1SLOAD in the same way Scroll does (implementation assumes the existence of the “system contract”)
Challenges
Here are the biggest challenges we’ve faced during our research phase:
- L2s “using”
geth
differently, e.g. Arbitrum usesgeth
as a “library” and e.g. op-stack ”uses”geth
by mimicking CL <> EL “architecture” which complicates RIP/EIP implementation decisions. - Knowledge requirements: understanding not only the
geth
codebase but variousL2-geth
forks and their specifics (again, this is necessary to make better implementation decisions, but also to better understand what “this common core” needs to look like) - It’s not always “easy” to write “conflict-free” code, especially in case of “huge” EIPs (eg. EIP-7706 where core components, like
Header
were changed - Draft PR link) - In some coding practices, where certain parts of code get “bloated” by various L1 EIPs but, also L2-specific stuff - code editing in these “hot-paths” is “hard” because it can yield hard-to-resolve conflicts or huge diffs.
- Some other coding practices make it hard to effectively maintain
rollup-geth
/L2-geth
across multiple forks
Recommendations
Some recommendations for streamlining future dev process:
- Have agreed upon “governance” process (i.e. how to choose what will “common core” look like, both short-term and long-term), effort for this is already underway both during the RollCalls, and specifically for the first version of the CommonCore in this Telegram group
L2-geth
providing smth. akin “fork-diff view” like op-geth does (rollup-geth
will also do this)- If possible make sure future code additions are more modular/encapsulated
- Better/more docs are always appricated
- More feedback for
rollup-geth
team/project
Specific goals accomplished
This research phase of the project started ~ 2 months ago.
We have: