Skip to content

feat(eth): ethereum sync finality support - #2184

Closed
kchojn wants to merge 61 commits into
stagefrom
finality-sync
Closed

feat(eth): ethereum sync finality support#2184
kchojn wants to merge 61 commits into
stagefrom
finality-sync

Conversation

@kchojn

@kchojn kchojn commented Apr 29, 2025

Copy link
Copy Markdown
Contributor

TODO:

  • set up the correct fork name (comments, vars, names, etc)

olegshmuelov and others added 4 commits April 20, 2025 11:01
…ity and consistency

refactor(execution_client.go): rename heads channel to headersCh for better naming
refactor(execution_client.go): update variable names for improved readability
refactor(execution_client.go): update log messages for consistency and clarity
…n_client_test.go, multi_client.go, multi_client_test.go, options.go): remove follow distance configuration option as it is no longer needed [skip ci]
@kchojn kchojn self-assigned this Apr 29, 2025
kchojn added 8 commits April 29, 2025 16:58
…ated logic to simplify the codebase and improve maintainability
…plify code

refactor(defaults.go): remove reconnection interval constants as they are no longer used
refactor(execution_client.go): remove reconnection interval fields to streamline struct
refactor(execution_client_test.go): remove reconnection interval option from test
refactor(multi_client.go): remove reconnection interval options to enhance clarity
refactor(multi_client_test.go): remove reconnection interval checks from test
refactor(options.go): remove reconnection interval options as they are no longer used
…tion logs to verify logs are received only after blocks are finalized and reorgs before finalization do not affect the final result
… clarity and consistency

feat(common_test.go): add MineAndFinalize method to ensure finality by mining blocks
feat(eth_e2e_test.go): replace CloseFollowDistance calls with MineAndFinalize for block finality
…e 32 for

               better readability and maintainability
               refactor(execution_client): update usage of finality distance constant to use
               DefaultFinalityDistance constant for consistency and clarity
               test(execution_client_test): update finalize function to use DefaultFinalityDistance
               constant instead of hardcoded value for improved maintainability [skip ci]
@moshe-blox
moshe-blox requested a review from Copilot May 5, 2025 08:01

Copilot AI 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.

Pull Request Overview

This PR implements Ethereum sync finality support by replacing the previous follow-distance mechanism with finalized block retrieval while also removing reconnection-related options. Key changes include:

  • Removal of the follow distance and reconnection options from the execution client and multi-client APIs.
  • Replacement of block retrieval logic with finalized block support throughout the execution client.
  • Update of test helpers and CLI commands to align with the new finality approach.

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
eth/executionclient/options.go Removed follow distance and reconnection options.
eth/executionclient/multi_client_test.go Removed tests for follow distance and reconnection intervals.
eth/executionclient/multi_client.go Removed follow distance and reconnection options from the multi-client.
eth/executionclient/execution_client.go Reworked block retrieval and log streaming to use finalized blocks.
eth/executionclient/defaults.go Replaced follow distance defaults with a finality distance constant.
eth/eventhandler/event_handler_test.go Updated client construction to remove obsolete follow distance option.
eth/ethtest/eth_e2e_test.go Updated tests to use MineAndFinalize instead of CloseFollowDistance.
eth/ethtest/common_test.go Renamed follow distance helpers to finality blocks helpers.
cli/operator/node.go Removed follow distance and reconnection options in node startup.

Comment thread eth/executionclient/defaults.go Outdated
Comment thread eth/executionclient/execution_client.go Outdated
kchojn and others added 4 commits May 5, 2025 10:08
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented May 5, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.22807% with 82 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.8%. Comparing base (f342e8b) to head (67f3024).
⚠️ Report is 278 commits behind head on stage.

Files with missing lines Patch % Lines
eth/executionclient/execution_client.go 67.3% 41 Missing and 8 partials ⚠️
eth/executionclient/multi_client.go 47.6% 11 Missing ⚠️
networkconfig/ssv_forks.go 82.0% 8 Missing and 1 partial ⚠️
eth/eventsyncer/event_syncer.go 75.0% 6 Missing and 1 partial ⚠️
cli/operator/node.go 0.0% 3 Missing ⚠️
networkconfig/ssv.go 50.0% 2 Missing ⚠️
utils/testutils.go 0.0% 1 Missing ⚠️

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

kchojn added 10 commits May 6, 2025 13:21
…adability and

use it to calculate DefaultFinalityDistance for better maintainability
test(IsFinalityActive): add test cases to cover different scenarios for finality
check based on the current epoch and finality fork epoch to determine if finality is active
Cover cases where finality is disabled, active, and inactive
Ensure the function returns the expected boolean value for each scenario
…ity fork epoch in NewMulti function

feat(options.go): add WithFollowDistance and WithFinalityForkEpoch options to set follow distance and finality fork epoch respectively
…Epoch(1) option to client creation for improved testing accuracy
@iurii-ssv

Copy link
Copy Markdown
Contributor

@kchojn now that #2307 was merged, I think we need to address #2307 (comment) here as well

@kchojn

kchojn commented Jun 25, 2025

Copy link
Copy Markdown
Contributor Author

@kchojn now that #2307 was merged, I think we need to address #2307 (comment) here as well

thanks @iurii-ssv

kchojn added 3 commits June 26, 2025 13:48
# Conflicts:
#	eth/executionclient/execution_client.go
#	networkconfig/hoodi-stage.go
#	networkconfig/hoodi.go
#	networkconfig/mainnet.go
#	networkconfig/sepolia.go
#	networkconfig/ssv.go
#	networkconfig/ssv_test.go
@github-actions

Copy link
Copy Markdown

This pull request has been marked as stale due to 60 days of inactivity. It will be closed in 30 days if there are no updates. Please comment if you would like to keep it open.

@github-actions github-actions Bot added the stale label Aug 26, 2025
@github-actions

Copy link
Copy Markdown

Closing this pull request as it has been inactive for 30 days after being marked stale. You are welcome to reopen it if you wish to continue.

@github-actions github-actions Bot closed this Sep 26, 2025
@y0sher y0sher reopened this Nov 19, 2025
@y0sher
y0sher requested review from a team as code owners November 19, 2025 13:56
@greptile-apps

greptile-apps Bot commented Nov 19, 2025

Copy link
Copy Markdown
Contributor

Greptile Summary

  • Implements Ethereum finality sync support by transitioning from follow-distance to finalized-blocks based synchronization for safer event processing and reduced reorganization risks
  • Refactors network configuration from single GasLimit36Epoch fields to structured SSVForkConfig system with three forks: "Alan", "Gas Limit36M", and "Finality Consensus"
  • Adds comprehensive fork management system with epoch-based feature activation, enabling protocol upgrades across different network configurations

PR Description Notes:

  • The PR body contains an unfinished TODO: "set up the correct fork name (comments, vars, names, etc)" which needs completion

Important Files Changed

Filename Overview
eth/executionclient/execution_client.go Core implementation of finality sync - switches from follow distance to finalized blocks post-fork with IsFinalizedFork() method
networkconfig/ssv_forks.go New fork management system defining SSV protocol forks with activation epochs and query methods
networkconfig/ssv.go Major interface change from GetGasLimit36Epoch() to GetForks() returning SSVForkConfig instead of single epoch
eth/eventsyncer/event_syncer.go Updates event sync health checks to use finalized blocks instead of current time for staleness detection post-fork
eth/executionclient/multi_client.go Adds network configuration support and IsFinalizedFork() delegation to enable finality sync in multi-client setups

Confidence score: 3/5

  • This PR requires careful review due to the significant architectural changes affecting core synchronization logic and potential breaking changes
  • Score lowered due to complex fork transition logic, incomplete TODO item in PR description, duplicate methods in SSVForks (ActiveFork vs ComputeActiveFork), and extensive changes to execution client synchronization behavior
  • Pay close attention to fork transition logic in execution_client.go and ensure all network configurations properly define fork epochs before deployment

Sequence Diagram

sequenceDiagram
    participant User
    participant Node as SSV Node
    participant ExecutionClient as Execution Client
    participant EventSyncer as Event Syncer
    participant EventHandler as Event Handler
    participant BeaconClient as Beacon Client
    
    User->>Node: "start-node"
    Note over Node: Load configuration and setup
    Node->>ExecutionClient: "New()"
    Node->>BeaconClient: "New()"
    Node->>EventHandler: "New()"
    Node->>EventSyncer: "New()"
    
    Note over ExecutionClient,EventSyncer: Determine sync approach
    ExecutionClient->>ExecutionClient: "IsFinalizedFork()"
    alt Finality Fork Active
        ExecutionClient->>ExecutionClient: "getFinalizedBlock()"
    else Pre-Fork
        ExecutionClient->>ExecutionClient: "Use follow distance"
    end
    
    Node->>EventSyncer: "SyncHistory()"
    EventSyncer->>ExecutionClient: "FetchHistoricalLogs()"
    ExecutionClient->>EventHandler: "HandleBlockEventsStream()"
    EventHandler-->>EventSyncer: "lastProcessedBlock"
    
    Node->>EventSyncer: "SyncOngoing()"
    EventSyncer->>ExecutionClient: "StreamLogs()"
    loop Ongoing Event Processing
        ExecutionClient->>ExecutionClient: "HeaderByNumber()"
        alt Finality Fork Active
            ExecutionClient->>ExecutionClient: "getFinalizedBlock()"
            Note over ExecutionClient: Process up to finalized block
        else Pre-Fork
            Note over ExecutionClient: Process with follow distance
        end
        ExecutionClient->>EventHandler: "HandleBlockEventsStream()"
    end
Loading

@greptile-apps greptile-apps 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.

Additional Comments (1)

  1. eth/executionclient/execution_client.go, line 405-457 (link)

    logic: The healthy() method duplicates the health invalidation check from the Healthy() method above (lines 411-414). This creates redundant logic that could be inconsistent if one is updated without the other.

30 files reviewed, 8 comments

Edit Code Review Agent Settings | Greptile
React with 👍 or 👎 to share your feedback on this new summary format

Comment on lines +94 to +144

func TestSSVForks_ComputeActiveFork(t *testing.T) {
t.Parallel()

forks := SSVForks{
{Name: "Fork1", Epoch: 100},
{Name: "Fork2", Epoch: 200},
}

testCases := []struct {
name string
epoch phase0.Epoch
expectedFork string
expectNilFork bool
}{
{
name: "before first fork",
epoch: 50,
expectNilFork: true,
},
{
name: "at first fork",
epoch: 100,
expectedFork: "Fork1",
},
{
name: "between first and second fork",
epoch: 150,
expectedFork: "Fork1",
},
{
name: "at second fork",
epoch: 200,
expectedFork: "Fork2",
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

result := forks.ComputeActiveFork(tt.epoch)
if tt.expectNilFork {
require.Nil(t, result)
} else {
require.NotNil(t, result)
require.Equal(t, tt.expectedFork, result.Name)
}
})
}
}

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.

style: TestSSVForks_ComputeActiveFork duplicates TestSSVForks_ActiveFork test cases exactly - consider removing or differentiating the test scenarios if these methods have different behaviors. Should ComputeActiveFork and ActiveFork methods have different test scenarios to validate their distinct behaviors?

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: networkconfig/ssv_forks_test.go
Line: 94:144

Comment:
**style:** TestSSVForks_ComputeActiveFork duplicates TestSSVForks_ActiveFork test cases exactly - consider removing or differentiating the test scenarios if these methods have different behaviors. Should ComputeActiveFork and ActiveFork methods have different test scenarios to validate their distinct behaviors?

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment thread networkconfig/hoodi.go
Comment on lines +34 to +35
Name: "Finality Consensus",
Epoch: 100, // TODO: MaxEpoch

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.

style: Consider using math.MaxUint64 for not-yet-defined epochs to follow Ethereum conventions

Suggested change
Name: "Finality Consensus",
Epoch: 100, // TODO: MaxEpoch
Name: "Finality Consensus",
Epoch: math.MaxUint64, // TODO: MaxEpoch

Context Used: Rule from dashboard - Use math.MaxUint64 for not-yet-defined epochs to follow Ethereum conventions, rather than custom v... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: networkconfig/hoodi.go
Line: 34:35

Comment:
**style:** Consider using `math.MaxUint64` for not-yet-defined epochs to follow Ethereum conventions

```suggestion
				Name:  "Finality Consensus",
				Epoch: math.MaxUint64, // TODO: MaxEpoch
```

**Context Used:** Rule from `dashboard` - Use `math.MaxUint64` for not-yet-defined epochs to follow Ethereum conventions, rather than custom v... ([source](https://app.greptile.com/review/custom-context?memory=c39ddeef-318a-487d-bc00-7bcaedbc8e56))

How can I resolve this? If you propose a fix, please make it concise.

Comment thread networkconfig/mainnet.go
},
{
Name: "Finality Consensus",
Epoch: MaxEpoch,

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.

style: Finality Consensus fork is set to MaxEpoch without TODO comment - should this also have a TODO indicating it needs proper epoch configuration? Is the Finality Consensus fork intentionally set to MaxEpoch indefinitely, or should it have a TODO comment like the Gas Limit 36M fork?

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: networkconfig/mainnet.go
Line: 44:44

Comment:
**style:** Finality Consensus fork is set to MaxEpoch without TODO comment - should this also have a TODO indicating it needs proper epoch configuration? Is the Finality Consensus fork intentionally set to MaxEpoch indefinitely, or should it have a TODO comment like the Gas Limit 36M fork?

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +715 to +745
t.Run("ValidatorExited incorrect event public key", func(t *testing.T) {
pk := validatorData1.masterPubKey.Serialize()
// Corrupt the public key
pk[len(pk)-1] ^= 1

_, err = boundContract.ExitValidator(
auth,
pk,
[]uint64{1, 2, 3, 4},
)
require.NoError(t, err)

sim.Commit()
for i := 0; i < 64; i++ {
sim.Commit()
}

block := getBlockWithLogs(logs)
require.NotEmpty(t, block.Logs)
require.Equal(t, ethcommon.HexToHash("0xb4b20ffb2eb1f020be3df600b2287914f50c07003526d3a9d89a9dd12351828c"), block.Logs[0].Topics[0])

eventsCh := make(chan executionclient.BlockLogs)
go func() {
defer close(eventsCh)
eventsCh <- block
}()
lastProcessedBlock, err := eh.HandleBlockEventsStream(ctx, eventsCh, false)

require.Equal(t, block.BlockNumber, lastProcessedBlock)
require.NoError(t, err)
})

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.

style: duplicate test case - this test is identical to the earlier test at lines 602-632

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: eth/eventhandler/event_handler_test.go
Line: 715:745

Comment:
**style:** duplicate test case - this test is identical to the earlier test at lines 602-632

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +48 to +67
// ActiveFork returns the active fork at the given epoch.
func (f SSVForks) ActiveFork(epoch phase0.Epoch) *SSVFork {
for i := len(f) - 1; i >= 0; i-- {
if f[i].Epoch <= epoch {
return f[i]
}
}
return nil
}

// ComputeActiveFork returns the active fork for the given epoch
func (f SSVForks) ComputeActiveFork(epoch phase0.Epoch) *SSVFork {
// Search from the end to find the most recent active fork
for i := len(f) - 1; i >= 0; i-- {
if epoch >= f[i].Epoch {
return f[i]
}
}
// If no active fork is found, return nil
return nil

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.

style: ActiveFork and ComputeActiveFork implement nearly identical logic with only a subtle difference in comparison operators (≤ vs ≥). Consider removing one method or documenting the semantic difference between them. Is there a semantic difference between ActiveFork and ComputeActiveFork that justifies having both methods?

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: networkconfig/ssv_forks.go
Line: 48:67

Comment:
**style:** ActiveFork and ComputeActiveFork implement nearly identical logic with only a subtle difference in comparison operators (≤ vs ≥). Consider removing one method or documenting the semantic difference between them. Is there a semantic difference between ActiveFork and ComputeActiveFork that justifies having both methods?

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

} else {
e.followDistance = executionclient.DefaultFollowDistance
e.networkConfig.SSVConfig.Forks.Forks[1].Epoch = phase0.Epoch(1000000)

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.

style: Extra empty line should be removed for consistency with code formatting.

Suggested change
e.networkConfig.SSVConfig.Forks.Forks[1].Epoch = phase0.Epoch(1000000)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: eth/ethtest/common_test.go
Line: 111:111

Comment:
**style:** Extra empty line should be removed for consistency with code formatting.

```suggestion
e.networkConfig.SSVConfig.Forks.Forks[1].Epoch = phase0.Epoch(1000000)
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +225 to +227
testNetwork := networkconfig.TestNetwork
testNetwork.GenesisTime = time.Now().Add(-1 * time.Minute) // Recent genesis
testNetwork.SSVConfig.Forks.Forks[1].Epoch = 10000 // High epoch to ensure pre-fork

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.

style: Direct mutation of testNetwork could cause issues if TestNetwork is a shared instance. Consider using a copy constructor or deep copy to avoid side effects.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: eth/executionclient/execution_client_test.go
Line: 225:227

Comment:
**style:** Direct mutation of testNetwork could cause issues if TestNetwork is a shared instance. Consider using a copy constructor or deep copy to avoid side effects.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

@github-actions github-actions Bot removed the stale label Nov 20, 2025
@github-actions

Copy link
Copy Markdown

This pull request has been marked as stale due to 60 days of inactivity. It will be closed in 30 days if there are no updates. Please comment if you would like to keep it open.

@github-actions github-actions Bot added stale and removed stale labels Jan 19, 2026
@vyzo vyzo added the next label Jan 20, 2026
@vyzo vyzo unassigned kchojn Jan 20, 2026
@nkryuchkov nkryuchkov self-assigned this Feb 18, 2026
nkryuchkov added a commit that referenced this pull request Feb 19, 2026
@nkryuchkov nkryuchkov mentioned this pull request Feb 19, 2026
@nkryuchkov

Copy link
Copy Markdown
Contributor

superseded by #2695

@nkryuchkov nkryuchkov closed this Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants