Skip to content

chore: remove merge transition code - #145

Open
guha-rahul wants to merge 2 commits into
ChainSafe:mainfrom
guha-rahul:remove_merge
Open

chore: remove merge transition code#145
guha-rahul wants to merge 2 commits into
ChainSafe:mainfrom
guha-rahul:remove_merge

Conversation

@guha-rahul

Copy link
Copy Markdown
Contributor

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @guha-rahul, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request performs a significant cleanup of the codebase by removing all remaining vestiges of the 'pre-merge' transition logic. This includes eliminating the pre_merge status from relevant enums and checks, as well as refactoring and deleting utility functions that handled the state before and during the Ethereum Merge. The changes simplify the code by removing obsolete conditions and states, aligning it with the current post-Merge operational reality of the network.

Highlights

  • Removal of pre_merge status: The pre_merge status from the ExecutionPayloadStatus enum and all associated checks have been removed, as this state is no longer relevant for the Ethereum network.
  • Simplification of execution status checks: The logic within processBlobKzgCommitments and processExecutionPayload has been streamlined by removing the pre_merge condition, focusing only on invalid or valid execution payload statuses.
  • Deletion of obsolete merge transition utility functions: The isMergeTransitionBlock function has been entirely removed, and the isExecutionEnabled and isMergeTransitionComplete functions have been significantly refactored to remove outdated logic related to the Merge transition, reflecting that the Merge is now complete.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively removes the merge transition code, simplifying several functions. The changes are generally clean and follow the PR's motivation. However, I've found a critical issue in isExecutionEnabled where a necessary check was removed, which could lead to a crash. I've provided a detailed comment and a suggested fix for this. There's also a logical issue in the same function that you may want to look at.

Comment on lines 7 to 25
pub fn isExecutionEnabled(state: *const BeaconStateAllForks, block: Block) bool {
if (!state.isPostBellatrix()) return false;
if (isMergeTransitionComplete(state)) return true;

// TODO(bing): in lodestar prod, state root comparison should be enough but spec tests were failing. This switch block is a failsafe for that.
//
// Ref: https://github.com/ChainSafe/lodestar/blob/7f2271a1e2506bf30378da98a0f548290441bdc5/packages/state-transition/src/util/execution.ts#L37-L42
switch (block) {
.blinded => |b| {
const body = b.beaconBlockBody();

return switch (body) {
.capella => |bd| !types.capella.ExecutionPayloadHeader.equals(&bd.execution_payload_header, &types.capella.ExecutionPayloadHeader.default_value),
.deneb => |bd| !types.deneb.ExecutionPayloadHeader.equals(&bd.execution_payload_header, &types.deneb.ExecutionPayloadHeader.default_value),
.electra => |bd| !types.electra.ExecutionPayloadHeader.equals(&bd.execution_payload_header, &types.electra.ExecutionPayloadHeader.default_value),
};
},
.regular => |b| {
const body = b.beaconBlockBody();

return switch (body) {
.phase0, .altair => @panic("Unsupported"),
.bellatrix => |bd| !types.bellatrix.ExecutionPayload.equals(&bd.execution_payload, &types.bellatrix.ExecutionPayload.default_value),
.capella => |bd| !types.capella.ExecutionPayload.equals(&bd.execution_payload, &types.capella.ExecutionPayload.default_value),
.deneb => |bd| !types.deneb.ExecutionPayload.equals(&bd.execution_payload, &types.deneb.ExecutionPayload.default_value),
.electra, .fulu => |bd| !types.electra.ExecutionPayload.equals(&bd.execution_payload, &types.electra.ExecutionPayload.default_value),
};
},
}
}

pub fn isMergeTransitionBlock(state: *const BeaconStateAllForks, body: *const BeaconBlockBody) bool {
if (!state.isBellatrix()) {
return false;
if (isMergeTransitionComplete(state)) {
return true;
}

return (!isMergeTransitionComplete(state) and
!types.bellatrix.ExecutionPayload.equals(body.getExecutionPayload().bellatrix, types.bellatrix.ExecutionPayload.default_value));
return switch (block) {
.blinded => !types.bellatrix.ExecutionPayloadHeader.equals(
&state.bellatrix.latest_execution_payload_header,
&types.bellatrix.ExecutionPayloadHeader.default_value,
),
.regular => |b| switch (b.beaconBlockBody()) {
.bellatrix => |bd| !types.bellatrix.ExecutionPayload.equals(
&bd.execution_payload,
&types.bellatrix.ExecutionPayload.default_value,
),
else => false,
},
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This function has a couple of issues after the refactoring:

  1. Potential Crash: The check for pre-Bellatrix states (if (!state.isPostBellatrix()) return false;) was removed. Without it, calling this function with a pre-Bellatrix state (e.g., Altair) will cause a crash. isMergeTransitionComplete will correctly return false, and the code will then try to access state.bellatrix, which is invalid for an Altair state. This violates the style guide rule to 'Assert all function arguments and return values, pre/postconditions and invariants' (line 51), as the function proceeds without ensuring the state is at least Bellatrix.

  2. Incorrect Logic for Blinded Blocks: The logic for .blinded blocks now checks the state's latest_execution_payload_header. However, the purpose here is to check if the block is a transition block. If isMergeTransitionComplete was false, it's because the state's header is empty, so this check will always be false. The logic should inspect the execution_payload_header from the block itself.

I'm providing a suggestion to fix the critical crash. The logical issue with blinded blocks might require more extensive changes to your type definitions, so I've left that part as is for you to address.

pub fn isExecutionEnabled(state: *const BeaconStateAllForks, block: Block) bool {
    if (!state.isPostBellatrix()) return false;

    if (isMergeTransitionComplete(state)) {
        return true;
    }

    return switch (block) {
        .blinded => !types.bellatrix.ExecutionPayloadHeader.equals(
            &state.bellatrix.latest_execution_payload_header,
            &types.bellatrix.ExecutionPayloadHeader.default_value,
        ),
        .regular => |b| switch (b.beaconBlockBody()) {
            .bellatrix => |bd| !types.bellatrix.ExecutionPayload.equals(
                &bd.execution_payload,
                &types.bellatrix.ExecutionPayload.default_value,
            ),
            else => false,
        },
    };
}
References
  1. The function proceeds to access fork-specific state fields without first asserting that the state is of the correct fork or a later one. This violates the rule to assert all preconditions. (link)

@guha-rahul
guha-rahul marked this pull request as ready for review December 27, 2025 15:41

@lodekeeper-z lodekeeper-z left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: chore: remove merge transition code

Thanks for the cleanup effort @guha-rahul — the intent here is sound. The merge transition (Bellatrix → PoS activation) happened on mainnet in Sept 2022 and all live networks have long passed it. Removing pre_merge, ExecutionPayloadStatusPreMerge, and isMergeTransitionBlock reduces dead code paths.

Problem: PR is stale against current main

Since this was opened (Dec 2025), main has gone through a major refactor — PR #190 (feat: comptime fork) rewrote execution.zig to use comptime fork parameters and ForkTypes(fork) dispatch instead of runtime BeaconStateAllForks switches. The current file looks nothing like what this PR patches against:

Current main (execution.zig):

  • Uses comptime fork: ForkSeq parameters
  • isExecutionEnabled takes BeaconState(fork) + BeaconBlock(block_type, fork)
  • isMergeTransitionComplete checks latestExecutionPayloadHeaderBlockHash() against ZERO_HASH
  • isMergeTransitionBlock uses ForkTypes(fork).ExecutionPayload.equals()

This PR's diff targets the old runtime-dispatched version with BeaconStateAllForks and inline fork switches — which no longer exists.

The PR correctly shows CONFLICTING merge status.

What needs to happen to land this

  1. Rebase onto current main — the diff needs to be rewritten against the comptime-fork versions of these functions
  2. Remove pre_merge from ExecutionPayloadStatus (still exists at src/state_transition/state_transition.zig:35)
  3. Remove pre_merge checks in process_blob_kzg_commitments.zig and process_execution_payload.zig (still reference .pre_merge)
  4. Remove isMergeTransitionBlock — currently has zero callers (confirmed via grep)
  5. Keep isMergeTransitionComplete — still called from isExecutionEnabled and process_execution_payload.zig

The actual changes on current main would be quite small (~15 lines removed).

Devil's advocate: should we keep merge transition code?

For a production client that needs to sync from genesis, yes. But lodestar-z doesn't support pre-Bellatrix sync yet, and when it does, it'll likely use checkpoint sync (post-merge). The spec tests also don't test the merge transition path for post-Bellatrix forks. So removing this dead code is the right call.

However, isMergeTransitionComplete must stay — isExecutionEnabled depends on it for the edge case where a post-Bellatrix state hasn't yet seen an execution payload (unlikely in practice but spec-correct).

Verdict

The cleanup is welcome, but the PR needs a full rebase. @guha-rahul — are you still interested in updating this? If not, we can pick up the remaining cleanup. The work is small on current main.

spiral-ladder added a commit that referenced this pull request Jun 17, 2026
## chore: remove merge transition code

Closes #130 (supersedes stale PR #145)

### Context

The merge transition (Bellatrix → PoS activation) happened on mainnet in
September 2022. All live networks have long passed it, and lodestar-z
doesn't support pre-Bellatrix sync. The related code paths are dead.

PR #145 targeted the old runtime-dispatched version of these functions,
which was replaced by the comptime fork refactor in #190. This PR
applies the removal against current `main`.

### Changes

- Removed `pre_merge` variant from `ExecutionPayloadStatus` enum
- Removed `.pre_merge` check in `process_blob_kzg_commitments.zig`
- Removed `.pre_merge` check in `process_execution_payload.zig`
- Removed `isMergeTransitionBlock` function from `execution.zig` (zero
callers)

`isMergeTransitionComplete` is intentionally kept — it's still used by
`isExecutionEnabled` for the edge case where a post-Bellatrix state
hasn't yet seen an execution payload.

*AI disclosure: Claude was consulted for reviewing the diff and drafting
this description. All code changes were authored manually.*

---------

Co-authored-by: bing <spiralladder@fastmail.com>
Co-authored-by: Chen Kai <281165273grape@gmail.com>
Co-authored-by: Cayman <caymannava@gmail.com>
Co-authored-by: Nazar Hussain <nazarhussain@gmail.com>
Co-authored-by: NC <17676176+ensi321@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@matthewkeil matthewkeil added this to the mainnet-stf milestone Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

Remove merge transition code

3 participants