Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions cl/beacon/handler/block_production.go
Original file line number Diff line number Diff line change
Expand Up @@ -2572,16 +2572,13 @@ func (a *ApiHandler) cacheExecutionBody(payload *cltypes.Eth1Block) {
})
}
var ws []*types.Withdrawal
if payload.Withdrawals != nil {
payload.Withdrawals.Range(func(idx int, w *cltypes.Withdrawal, total int) bool {
ws = append(ws, &types.Withdrawal{
Index: w.Index,
Validator: w.Validator,
Address: w.Address,
Amount: w.Amount,
})
if payload.Withdrawals != nil && payload.Withdrawals.Len() > 0 {
consensusWithdrawals := make([]*cltypes.Withdrawal, payload.Withdrawals.Len())
payload.Withdrawals.Range(func(idx int, w *cltypes.Withdrawal, _ int) bool {
consensusWithdrawals[idx] = w
return true
})
ws = cltypes.ConvertConsensusWithdrawalsToExecutionWithdrawals(consensusWithdrawals)
}
a.blockReader.CacheBlockBody(payload.BlockNumber, rawTxs, ws)
}
11 changes: 11 additions & 0 deletions cl/cltypes/withdrawal.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ func convertExecutionWithdrawalsToConsensusWithdrawals(executionWithdrawal []*ty
return ret
}

// ConvertConsensusWithdrawalsToExecutionWithdrawals converts a withdrawal list to its execution
// representation, in order and with no shared pointers. The result is never nil, which matters
// because the execution layer rejects a nil list and an empty one under opposite conditions.
func ConvertConsensusWithdrawalsToExecutionWithdrawals(consensusWithdrawals []*Withdrawal) []*types.Withdrawal {
ret := make([]*types.Withdrawal, len(consensusWithdrawals))
for i, w := range consensusWithdrawals {
ret[i] = convertConsensusWithdrawalToExecutionWithdrawal(w)
}
return ret
}

// ExpectedWithdrawals represents the expected withdrawals for a beacon state
type ExpectedWithdrawals struct {
Withdrawals []*Withdrawal `json:"withdrawals"`
Expand Down
56 changes: 56 additions & 0 deletions cl/cltypes/withdrawal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2026 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package cltypes

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/erigontech/erigon/common"
"github.com/erigontech/erigon/execution/types"
)

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

source := []*Withdrawal{
{Index: 1, Validator: 10, Address: common.Address{0xaa}, Amount: 100},
{Index: 2, Validator: 20, Address: common.Address{0xbb}, Amount: 200},
}

converted := ConvertConsensusWithdrawalsToExecutionWithdrawals(source)

require.Equal(t, []*types.Withdrawal{
{Index: 1, Validator: 10, Address: common.Address{0xaa}, Amount: 100},
{Index: 2, Validator: 20, Address: common.Address{0xbb}, Amount: 200},
}, converted)

// The execution layer owns its copy: mutating the source afterwards must not reach it.
source[0].Amount = 999
require.Equal(t, uint64(100), converted[0].Amount)
}

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

// A nil list and an empty one are rejected by the execution layer under opposite conditions,
// so an absent input must not become an absent list.
require.NotNil(t, ConvertConsensusWithdrawalsToExecutionWithdrawals(nil))
require.Empty(t, ConvertConsensusWithdrawalsToExecutionWithdrawals(nil))
require.NotNil(t, ConvertConsensusWithdrawalsToExecutionWithdrawals([]*Withdrawal{}))
}
12 changes: 2 additions & 10 deletions cl/phase1/stages/forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/erigontech/erigon/cl/beacon/beaconevents"
"github.com/erigontech/erigon/cl/beacon/synced_data"
"github.com/erigontech/erigon/cl/clparams"
"github.com/erigontech/erigon/cl/cltypes"
"github.com/erigontech/erigon/cl/monitor"
"github.com/erigontech/erigon/cl/monitor/shuffling_metrics"
"github.com/erigontech/erigon/cl/persistence/beacon_indicies"
Expand All @@ -26,7 +27,6 @@ import (
"github.com/erigontech/erigon/common/log/v3"
"github.com/erigontech/erigon/db/kv"
"github.com/erigontech/erigon/execution/engineapi/engine_types"
"github.com/erigontech/erigon/execution/types"
)

// computeAndNotifyServicesOfNewForkChoice calculates the new head of the fork choice and notifies relevant services.
Expand Down Expand Up @@ -247,19 +247,11 @@ func emitNextPaylodAttributesEvent(cfg *Cfg, headSlot uint64, headRoot common.Ha
log.Warn("failed to get proposer index", "err", err)
return err
}
withdrawals := []*types.Withdrawal{}
expWithdrawals, err := state.GetExpectedWithdrawals(s, epoch)
if err != nil {
return err
}
for _, w := range expWithdrawals.Withdrawals {
withdrawals = append(withdrawals, &types.Withdrawal{
Amount: w.Amount,
Index: w.Index,
Validator: w.Validator,
Address: w.Address,
})
}
withdrawals := cltypes.ConvertConsensusWithdrawalsToExecutionWithdrawals(expWithdrawals.Withdrawals)
payloadAttributes := engine_types.PayloadAttributes{
Timestamp: hexutil.Uint64(headPayloadHeader.Time + cfg.beaconCfg.SecondsPerSlot),
PrevRandao: randaoMix,
Expand Down
Loading