Create a L1BlockInfoTx Variant for a new Hardfork

This example walks through creating a variant of the L1BlockInfoTx for a new Hardfork.

note

This example is very verbose. To grok required changes, view this PR diff which introduces Isthmus hardfork changes to the L1BlockInfoTx with a new variant.

Required Genesis Updates

The first updates that need to be made are to op-alloy-genesis types, namely the RollupConfig and HardForkConfiguration.

First, add a timestamp field to the RollupConfig. Let's use the hardfork name "Glacier" as an example.

#![allow(unused)]
fn main() {
pub struct RollupConfig {
   ...
   /// `glacier_time` sets the activation time for the Glacier network upgrade.
   /// Active if `glacier_time` != None && L2 block timestamp >= Some(glacier_time), inactive
   /// otherwise.
   #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
   pub glacier_time: Option<u64>,
   ...
}
}

Add an accessor on the RollupConfig to provide a way of checking whether the "Glacier" hardfork is active for a given timestamp. Also update the prior hardfork accessor to call this method (let's use "Isthmus" as the prior hardfork).

#![allow(unused)]
fn main() {
    /// Returns true if Isthmus is active at the given timestamp.
    pub fn is_isthmus_active(&self, timestamp: u64) -> bool {
        self.isthmus_time.map_or(false, |t| timestamp >= t) || self.is_glacier_active(timestamp)
    }

    /// Returns true if Glacier is active at the given timestamp.
    pub fn is_glacier_active(&self, timestamp: u64) -> bool {
        self.glacier_time.map_or(false, |t| timestamp >= t)
    }
}

Lastly, add the "Glacier" timestamp to the HardForkConfiguration.

#![allow(unused)]
fn main() {
pub struct HardForkConfiguration {
    ...
    /// Glacier hardfork activation time
    pub glacier_time: Option<u64>,
}
}

Protocol Changes

Introduce a new glacier.rs module containing a L1BlockInfoGlacier type in op_alloy_genesis::info module.

This should include a few methods used in the L1BlockInfoTx later.

#![allow(unused)]
fn main() {
    pub fn encode_calldata(&self) -> Bytes { ... }

    pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> { ... }
}

Use other hardfork variants like the L1BlockInfoEcotone for reference.

Next, add the new "Glacier" variant to the L1BlockInfoTx.

#![allow(unused)]
fn main() {
pub enum L1BlockInfoTx {
   ...
   Glacier(L1BlockInfoGlacier)
}
}

Update L1BlockInfoTx::try_new to construct the L1BlockInfoGlacier if the hardfork is active using the RollupConfig::is_glacier_active.

Also, be sure to update L1BlockInfoTx::decode_calldata with the new variant decoding, as well as other L1BlockInfoTx methods.

Once some tests are added surrounding the decoding and encoding of the new L1BlockInfoGlacier variant, all required changes are complete!

Now, this example PR diff introducing the Isthmus changes should make sense, since it effectively implements the above changes for the Isthmus hardfork (replacing "Glacier" with "Isthmus"). Notice, Isthmus introduces some new "operator fee" fields as part of it's L1BlockInfoIsthmus type. Some new error variants to the BlockInfoError are needed as well.