From 13024eded4b8a9283f3562a4b90aa1f5abf3f4a7 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:11:27 +0200 Subject: [PATCH 01/11] common: fix SetBytes on fixed-size byte types The Bytes4/48/64/96 copies of Hash.SetBytes kept length.Hash (32) in the crop and right-align arithmetic, so SetBytes panicked or misplaced bytes for most input lengths. Use the receiver's own length, matching Hash.SetBytes semantics (crop from the left, right-align short inputs). --- common/bytes4.go | 4 +-- common/bytes48.go | 4 +-- common/bytes64.go | 4 +-- common/bytes96.go | 4 +-- common/bytesn_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 common/bytesn_test.go diff --git a/common/bytes4.go b/common/bytes4.go index 04a3345b500..2146ae2e42d 100644 --- a/common/bytes4.go +++ b/common/bytes4.go @@ -96,10 +96,10 @@ func (b Bytes4) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes4) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Generate implements testing/quick.Generator. diff --git a/common/bytes48.go b/common/bytes48.go index b6b27e734fb..4e36668d33f 100644 --- a/common/bytes48.go +++ b/common/bytes48.go @@ -96,10 +96,10 @@ func (b Bytes48) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes48) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Generate implements testing/quick.Generator. diff --git a/common/bytes64.go b/common/bytes64.go index 40da3a4ca75..be0c60ee2f0 100644 --- a/common/bytes64.go +++ b/common/bytes64.go @@ -95,10 +95,10 @@ func (b Bytes64) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes64) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Value implements valuer for database/sql. diff --git a/common/bytes96.go b/common/bytes96.go index ee5a92241be..9c3d194fc1b 100644 --- a/common/bytes96.go +++ b/common/bytes96.go @@ -96,10 +96,10 @@ func (b Bytes96) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes96) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Generate implements testing/quick.Generator. diff --git a/common/bytesn_test.go b/common/bytesn_test.go new file mode 100644 index 00000000000..96db82e9181 --- /dev/null +++ b/common/bytesn_test.go @@ -0,0 +1,78 @@ +// 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 . + +package common + +import ( + "bytes" + "testing" +) + +// SetBytes on all fixed-size byte types must follow Hash.SetBytes semantics: +// inputs longer than the type keep the last N bytes, shorter inputs are +// right-aligned with zero padding. +func TestBytesNSetBytes(t *testing.T) { + seq := func(n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = byte(i + 1) + } + return b + } + + check := func(t *testing.T, got, in []byte) { + t.Helper() + n := len(got) + want := make([]byte, n) + if len(in) >= n { + copy(want, in[len(in)-n:]) + } else { + copy(want[n-len(in):], in) + } + if !bytes.Equal(got, want) { + t.Fatalf("SetBytes(%d bytes) = %x, want %x", len(in), got, want) + } + } + + t.Run("Bytes4", func(t *testing.T) { + for _, n := range []int{2, 4, 6} { + var b Bytes4 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) + t.Run("Bytes48", func(t *testing.T) { + for _, n := range []int{40, 48, 60} { + var b Bytes48 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) + t.Run("Bytes64", func(t *testing.T) { + for _, n := range []int{40, 64, 70} { + var b Bytes64 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) + t.Run("Bytes96", func(t *testing.T) { + for _, n := range []int{40, 96, 100} { + var b Bytes96 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) +} From a7a0e637487826b43b73df87840cdb51d587da26 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:16:27 +0200 Subject: [PATCH 02/11] cl/cltypes: fix BlindedBeaconBody EncodingSizeSSZ and constructor version Two divergences from the BeaconBody sibling: the Deneb gate in EncodingSizeSSZ added ExecutionChanges a second time instead of BlobKzgCommitments, and NewBlindedBeaconBody dropped its version argument (callers had to SetVersion by hand; block_production.go does, Clone did not). --- cl/cltypes/beacon_block_blinded.go | 4 +- cl/cltypes/beacon_block_blinded_test.go | 52 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 cl/cltypes/beacon_block_blinded_test.go diff --git a/cl/cltypes/beacon_block_blinded.go b/cl/cltypes/beacon_block_blinded.go index 30071e369b7..0f6699c0806 100644 --- a/cl/cltypes/beacon_block_blinded.go +++ b/cl/cltypes/beacon_block_blinded.go @@ -276,7 +276,7 @@ func NewBlindedBeaconBody(beaconCfg *clparams.BeaconChainConfig, version clparam ExecutionChanges: solid.NewStaticListSSZ[*SignedBLSToExecutionChange](MaxExecutionChanges, 172), BlobKzgCommitments: solid.NewStaticListSSZ[*KZGCommitment](int(beaconCfg.MaxBlobCommittmentsPerBlock), 48), ExecutionRequests: executionRequests, - Version: 0, + Version: version, beaconCfg: beaconCfg, } } @@ -360,7 +360,7 @@ func (b *BlindedBeaconBody) EncodingSizeSSZ() (size int) { size += b.ExecutionChanges.EncodingSizeSSZ() } if b.Version >= clparams.DenebVersion { - size += b.ExecutionChanges.EncodingSizeSSZ() + size += b.BlobKzgCommitments.EncodingSizeSSZ() } if b.Version >= clparams.ElectraVersion { if b.ExecutionRequests != nil { diff --git a/cl/cltypes/beacon_block_blinded_test.go b/cl/cltypes/beacon_block_blinded_test.go new file mode 100644 index 00000000000..fcb55d623ee --- /dev/null +++ b/cl/cltypes/beacon_block_blinded_test.go @@ -0,0 +1,52 @@ +// 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 . + +package cltypes + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/erigontech/erigon/cl/clparams" +) + +// EncodingSizeSSZ must grow by exactly the encoded delta when a field's +// content grows, for every field gated by a fork version. +func TestBlindedBeaconBodyEncodingSizeTracksEncoding(t *testing.T) { + for _, v := range []clparams.StateVersion{clparams.DenebVersion, clparams.ElectraVersion} { + t.Run(v.String(), func(t *testing.T) { + body := NewBlindedBeaconBody(&clparams.MainnetBeaconConfig, v) + require.Equal(t, v, body.Version, "constructor must honor the version argument") + + enc0, err := body.EncodeSSZ(nil) + require.NoError(t, err) + size0 := body.EncodingSizeSSZ() + + body.BlobKzgCommitments.Append(&KZGCommitment{}) + enc1, err := body.EncodeSSZ(nil) + require.NoError(t, err) + size1 := body.EncodingSizeSSZ() + require.Equal(t, len(enc1)-len(enc0), size1-size0, "BlobKzgCommitments not reflected in EncodingSizeSSZ") + + body.ExecutionChanges.Append(&SignedBLSToExecutionChange{Message: &BLSToExecutionChange{}}) + enc2, err := body.EncodeSSZ(nil) + require.NoError(t, err) + size2 := body.EncodingSizeSSZ() + require.Equal(t, len(enc2)-len(enc1), size2-size1, "ExecutionChanges over-counted in EncodingSizeSSZ") + }) + } +} From e0d61508a9f05e42d3907e7e5bd4664081f24017 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:21:53 +0200 Subject: [PATCH 03/11] cl/beacon/handler: return Eth-Consensus-Version on all pending queue endpoints pending_consolidations and pending_deposits were missing the version header and body field that pending_partial_withdrawals already returns; the three handlers are copies of each other. Also drop a stray tab from two error messages and make the forkchoice mock's pending getters settable for the test. --- cl/beacon/handler/states.go | 10 ++++- cl/beacon/handler/states_test.go | 36 ++++++++++++++++ .../mock_services/forkchoice_mock.go | 42 +++++++++++-------- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/cl/beacon/handler/states.go b/cl/beacon/handler/states.go index 0968ebfb004..2ccb523dee3 100644 --- a/cl/beacon/handler/states.go +++ b/cl/beacon/handler/states.go @@ -510,7 +510,7 @@ func (a *ApiHandler) GetEthV1BeaconStatesPendingConsolidations(w http.ResponseWr var ok bool pendingConsolidations, ok = a.forkchoiceStore.GetPendingConsolidations(blockRoot) if !ok { - return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Errorf("no pending consolidations found for block root: %x", blockRoot)) + return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Errorf("no pending consolidations found for block root: %x", blockRoot)) } // If we have the pending consolidations in memory, we can return them directly. } else { @@ -522,7 +522,10 @@ func (a *ApiHandler) GetEthV1BeaconStatesPendingConsolidations(w http.ResponseWr } } + version := a.ethClock.StateVersionByEpoch(*slot / a.beaconChainCfg.SlotsPerEpoch) return newBeaconResponse(pendingConsolidations). + WithHeader("Eth-Consensus-Version", version.String()). + WithVersion(version). WithOptimistic(isOptimistic). WithFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()), nil } @@ -569,7 +572,7 @@ func (a *ApiHandler) GetEthV1BeaconStatesPendingDeposits(w http.ResponseWriter, var ok bool pendingDeposits, ok = a.forkchoiceStore.GetPendingDeposits(blockRoot) if !ok { - return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Errorf("no pending deposits found for block root: %x", blockRoot)) + return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Errorf("no pending deposits found for block root: %x", blockRoot)) } } else { stateView := a.caplinStateSnapshots.View() @@ -580,7 +583,10 @@ func (a *ApiHandler) GetEthV1BeaconStatesPendingDeposits(w http.ResponseWriter, } } + version := a.ethClock.StateVersionByEpoch(*slot / a.beaconChainCfg.SlotsPerEpoch) return newBeaconResponse(pendingDeposits). + WithHeader("Eth-Consensus-Version", version.String()). + WithVersion(version). WithOptimistic(isOptimistic). WithFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()), nil } diff --git a/cl/beacon/handler/states_test.go b/cl/beacon/handler/states_test.go index 25bf3293a75..fa6a5114276 100644 --- a/cl/beacon/handler/states_test.go +++ b/cl/beacon/handler/states_test.go @@ -554,3 +554,39 @@ func TestGetRandao(t *testing.T) { }) } } + +func TestGetPendingQueuesConsensusVersionHeader(t *testing.T) { + _, blocks, _, _, _, handler, _, _, fcu, _ := setupTestingHandler(t, clparams.ElectraVersion, log.Root(), false) + + var err error + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + lowest := uint64(0) + fcu.LowestAvailableSlotVal = &lowest + fcu.PendingConsolidationsVal = map[common.Hash]*solid.ListSSZ[*solid.PendingConsolidation]{ + fcu.HeadVal: solid.NewPendingConsolidationList(&clparams.MainnetBeaconConfig), + } + fcu.PendingDepositsVal = map[common.Hash]*solid.ListSSZ[*solid.PendingDeposit]{ + fcu.HeadVal: solid.NewPendingDepositList(&clparams.MainnetBeaconConfig), + } + fcu.PendingPartialWithdrawalsVal = map[common.Hash]*solid.ListSSZ[*solid.PendingPartialWithdrawal]{ + fcu.HeadVal: solid.NewPendingWithdrawalList(&clparams.MainnetBeaconConfig), + } + + server := httptest.NewServer(handler.mux) + defer server.Close() + + versions := map[string]string{} + for _, endpoint := range []string{"pending_consolidations", "pending_deposits", "pending_partial_withdrawals"} { + resp, err := http.Get(server.URL + "/eth/v1/beacon/states/head/" + endpoint) + require.NoError(t, err) + resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, endpoint) + v := resp.Header.Get("Eth-Consensus-Version") + require.NotEmpty(t, v, "%s must set Eth-Consensus-Version", endpoint) + versions[endpoint] = v + } + require.Equal(t, versions["pending_partial_withdrawals"], versions["pending_consolidations"]) + require.Equal(t, versions["pending_partial_withdrawals"], versions["pending_deposits"]) +} diff --git a/cl/phase1/forkchoice/mock_services/forkchoice_mock.go b/cl/phase1/forkchoice/mock_services/forkchoice_mock.go index f9415ed4ea4..e065c72e584 100644 --- a/cl/phase1/forkchoice/mock_services/forkchoice_mock.go +++ b/cl/phase1/forkchoice/mock_services/forkchoice_mock.go @@ -58,21 +58,24 @@ type ForkChoiceStorageMock struct { ParticipationVal map[uint64]*solid.ParticipationBitList - StateAtBlockRootVal map[common.Hash]*state.CachingBeaconState - StateAtSlotVal map[uint64]*state.CachingBeaconState - GetSyncCommitteesVal map[uint64][2]*solid.SyncCommittee - GetFinalityCheckpointsVal map[common.Hash][3]solid.Checkpoint - WeightsMock []forkchoice.ForkNode - LightClientBootstraps map[common.Hash]*cltypes.LightClientBootstrap - NewestLCUpdate *cltypes.LightClientUpdate - LCUpdates map[uint64]*cltypes.LightClientUpdate - SyncContributionPool sync_contribution_pool.SyncContributionPool - Headers map[common.Hash]*cltypes.BeaconBlockHeader - Blocks map[common.Hash]*cltypes.SignedBeaconBlock - Envelopes map[common.Hash]*cltypes.SignedExecutionPayloadEnvelope - VerifiedPayloads map[common.Hash]bool - OnExecutionPayloadErr error - GetBeaconCommitteeMock func(slot, committeeIndex uint64) ([]uint64, error) + StateAtBlockRootVal map[common.Hash]*state.CachingBeaconState + StateAtSlotVal map[uint64]*state.CachingBeaconState + GetSyncCommitteesVal map[uint64][2]*solid.SyncCommittee + GetFinalityCheckpointsVal map[common.Hash][3]solid.Checkpoint + PendingConsolidationsVal map[common.Hash]*solid.ListSSZ[*solid.PendingConsolidation] + PendingDepositsVal map[common.Hash]*solid.ListSSZ[*solid.PendingDeposit] + PendingPartialWithdrawalsVal map[common.Hash]*solid.ListSSZ[*solid.PendingPartialWithdrawal] + WeightsMock []forkchoice.ForkNode + LightClientBootstraps map[common.Hash]*cltypes.LightClientBootstrap + NewestLCUpdate *cltypes.LightClientUpdate + LCUpdates map[uint64]*cltypes.LightClientUpdate + SyncContributionPool sync_contribution_pool.SyncContributionPool + Headers map[common.Hash]*cltypes.BeaconBlockHeader + Blocks map[common.Hash]*cltypes.SignedBeaconBlock + Envelopes map[common.Hash]*cltypes.SignedExecutionPayloadEnvelope + VerifiedPayloads map[common.Hash]bool + OnExecutionPayloadErr error + GetBeaconCommitteeMock func(slot, committeeIndex uint64) ([]uint64, error) Pool pool.OperationsPool @@ -523,15 +526,18 @@ func (f *ForkChoiceStorageMock) IsHeadOptimistic() bool { } func (f *ForkChoiceStorageMock) GetPendingConsolidations(blockRoot common.Hash) (*solid.ListSSZ[*solid.PendingConsolidation], bool) { - return nil, false + v, ok := f.PendingConsolidationsVal[blockRoot] + return v, ok } func (f *ForkChoiceStorageMock) GetPendingDeposits(blockRoot common.Hash) (*solid.ListSSZ[*solid.PendingDeposit], bool) { - return nil, false + v, ok := f.PendingDepositsVal[blockRoot] + return v, ok } func (f *ForkChoiceStorageMock) GetPendingPartialWithdrawals(blockRoot common.Hash) (*solid.ListSSZ[*solid.PendingPartialWithdrawal], bool) { - return nil, false + v, ok := f.PendingPartialWithdrawalsVal[blockRoot] + return v, ok } func (f *ForkChoiceStorageMock) GetProposerLookahead(slot uint64) (solid.Uint64VectorSSZ, bool) { From c77e0eba39033694a92345c470adc0e78f5d4112 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:53 +0200 Subject: [PATCH 04/11] db/state: fix crossed domains in integrity inverted-index check The StorageHistoryIdx arm checked the code domain's inverted index and CodeHistoryIdx checked storage's, so a targeted integrity check validated the wrong index. --- db/state/integrity.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/state/integrity.go b/db/state/integrity.go index 02e30ac31a3..e51698f01db 100644 --- a/db/state/integrity.go +++ b/db/state/integrity.go @@ -43,12 +43,12 @@ func (at *AggregatorRoTx) IntegrityInvertedIndexAllValuesAreInRange(ctx context. return err } case kv.StorageHistoryIdx: - err := at.d[kv.CodeDomain].ht.iit.IntegrityInvertedIndexAllValuesAreInRange(ctx, failFast, fromStep) + err := at.d[kv.StorageDomain].ht.iit.IntegrityInvertedIndexAllValuesAreInRange(ctx, failFast, fromStep) if err != nil { return err } case kv.CodeHistoryIdx: - err := at.d[kv.StorageDomain].ht.iit.IntegrityInvertedIndexAllValuesAreInRange(ctx, failFast, fromStep) + err := at.d[kv.CodeDomain].ht.iit.IntegrityInvertedIndexAllValuesAreInRange(ctx, failFast, fromStep) if err != nil { return err } From b3b25f8f99ce489b1c6b740a9bd378dcb36a85a4 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:53 +0200 Subject: [PATCH 05/11] execution/exec: fix AA bundle length in log startIdx-endIdx underflows; use endIdx-startIdx+1 like the historical trace worker's copy of this code. --- execution/exec/txtask.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/exec/txtask.go b/execution/exec/txtask.go index f7d505bd057..c536945dc2e 100644 --- a/execution/exec/txtask.go +++ b/execution/exec/txtask.go @@ -673,7 +673,7 @@ func (txTask *TxTask) executeAA(aaTxn *types.AccountAbstractionTransaction, result.Err = outerErr return &result } - log.Info("✅[aa] validated AA bundle", "len", startIdx-endIdx) + log.Info("✅[aa] validated AA bundle", "len", endIdx-startIdx+1) result.ValidationResults = validationResults } From 257a31992158892155ff53e3ec2aa9ffc370b288 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:53 +0200 Subject: [PATCH 06/11] p2p/sentry: don't error when peer is gone before headers response Both branches of the IsPeerNotFoundErr check returned the same error; mirror the bodies handler and treat a vanished peer as success. --- p2p/sentry/sentry_multi_client/sentry_multi_client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/sentry/sentry_multi_client/sentry_multi_client.go b/p2p/sentry/sentry_multi_client/sentry_multi_client.go index 963f49a0b8c..a90b7e74138 100644 --- a/p2p/sentry/sentry_multi_client/sentry_multi_client.go +++ b/p2p/sentry/sentry_multi_client/sentry_multi_client.go @@ -250,8 +250,8 @@ func (cs *MultiClient) getBlockHeaders66(ctx context.Context, inreq *sentryproto } _, err = sentry.SendMessageById(ctx, &outreq, &grpc.EmptyCallOption{}) if err != nil { - if !libsentry.IsPeerNotFoundErr(err) { - return fmt.Errorf("send header response 66: %w", err) + if libsentry.IsPeerNotFoundErr(err) { + return nil } return fmt.Errorf("send header response 66: %w", err) } From 639a9d27beac86b69f7aafdcfcafe02b3c3de48c Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:53 +0200 Subject: [PATCH 07/11] execution/stagedsync: fix per-domain put-rate metrics and gauge typos The four per-domain exec_domain_cache_put_rate gauges were set to raw counts while the aggregate sets a per-second rate; divide by the interval. Also fix mxExex*/mxCommitmentBrancg variable-name typos. --- execution/stagedsync/exec3_metrics.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/execution/stagedsync/exec3_metrics.go b/execution/stagedsync/exec3_metrics.go index eabdaee0e61..09b0fe81c28 100644 --- a/execution/stagedsync/exec3_metrics.go +++ b/execution/stagedsync/exec3_metrics.go @@ -73,7 +73,7 @@ var ( mxExecStorageDomainReads = metrics.NewGauge(`exec_domain_read_rate{domain="storage"}`) mxExecStorageDomainReadDuration = metrics.NewGauge(`exec_domain_read_dur{domain="storage"}`) - mxExexStorageDomainCacheReads = metrics.NewGauge(`exec_domain_cache_read_rate{domain="storage"}`) + mxExecStorageDomainCacheReads = metrics.NewGauge(`exec_domain_cache_read_rate{domain="storage"}`) mxExecStorageDomainCacheReadDuration = metrics.NewGauge(`exec_domain_cache_read_dur{domain="storage"}`) mxExecStorageDomainPutRate = metrics.NewGauge(`exec_domain_cache_put_rate{domain="storage"}`) mxExecStorageDomainPutSize = metrics.NewGauge(`exec_domain_cache_put_size{domain="storage"}`) @@ -86,7 +86,7 @@ var ( mxExecCodeDomainReads = metrics.NewGauge(`exec_domain_read_rate{domain="code"}`) mxExecCodeDomainReadDuration = metrics.NewGauge(`exec_domain_read_dur{domain="code"}`) - mxExexCodeDomainCacheReads = metrics.NewGauge(`exec_domain_cache_read_rate{domain="code"}`) + mxExecCodeDomainCacheReads = metrics.NewGauge(`exec_domain_cache_read_rate{domain="code"}`) mxExecCodeDomainCacheReadDuration = metrics.NewGauge(`exec_domain_cache_read_dur{domain="code"}`) mxExecCodeDomainPutRate = metrics.NewGauge(`exec_domain_cache_put_rate{domain="code"}`) mxExecCodeDomainPutSize = metrics.NewGauge(`exec_domain_cache_put_size{domain="code"}`) @@ -104,7 +104,7 @@ var ( mxCommitmentAccountReadRate = metrics.NewGauge("commit_account_read_rate") mxCommitmentStorageReadRate = metrics.NewGauge("commit_storage_read_rate") mxCommitmentBranchReadRate = metrics.NewGauge("commit_branch_read_rate") - mxCommitmentBrancgWriteRate = metrics.NewGauge("commit_branch_write_rate") + mxCommitmentBranchWriteRate = metrics.NewGauge("commit_branch_write_rate") mxCommitmentKeyRate = metrics.NewGauge("commit_key_rate") mxCommitmentAccountKeyRate = metrics.NewGauge("commit_account_key_rate") mxCommitmentStorageKeyRate = metrics.NewGauge("commit_storage_key_rate") @@ -232,15 +232,15 @@ func resetDomainGauges(ctx context.Context) { mxExecDomainFileReads, mxExecDomainFileReadDuration, mxExecAccountDomainReads, mxExecAccountDomainReadDuration, mxExecAccountDomainCacheReads, mxExecAccountDomainCacheReadDuration, mxExecAccountDomainDbReads, mxExecAccountDomainDbReadDuration, mxExecAccountDomainFileReads, mxExecAccountDomainFileReadDuration, mxExecStorageDomainReads, mxExecStorageDomainReadDuration, - mxExexStorageDomainCacheReads, mxExecStorageDomainCacheReadDuration, mxExecStorageDomainDbReads, mxExecStorageDomainDbReadDuration, + mxExecStorageDomainCacheReads, mxExecStorageDomainCacheReadDuration, mxExecStorageDomainDbReads, mxExecStorageDomainDbReadDuration, mxExecStorageDomainFileReads, mxExecStorageDomainFileReadDuration, mxExecCodeDomainReads, mxExecCodeDomainReadDuration, - mxExexCodeDomainCacheReads, mxExecCodeDomainCacheReadDuration, mxExecCodeDomainDbReads, mxExecCodeDomainDbReadDuration, + mxExecCodeDomainCacheReads, mxExecCodeDomainCacheReadDuration, mxExecCodeDomainDbReads, mxExecCodeDomainDbReadDuration, mxExecCodeDomainFileReads, mxExecCodeDomainFileReadDuration, mxExecDomainPutRate, mxExecDomainPutSize, mxExecDomainPutKeySize, mxExecDomainPutValueSize, mxExecAccountDomainPutRate, mxExecAccountDomainPutSize, mxExecAccountDomainPutKeySize, mxExecAccountDomainPutValueSize, mxExecStorageDomainPutRate, mxExecStorageDomainPutSize, mxExecStorageDomainPutKeySize, mxExecStorageDomainPutValueSize, mxExecCodeDomainPutRate, mxExecCodeDomainPutSize, mxExecCodeDomainPutKeySize, mxExecCodeDomainPutValueSize, mxCommitmentReadRate, mxCommitmentAccountReadRate, - mxCommitmentStorageReadRate, mxCommitmentBranchReadRate, mxCommitmentBrancgWriteRate, mxCommitmentKeyRate, + mxCommitmentStorageReadRate, mxCommitmentBranchReadRate, mxCommitmentBranchWriteRate, mxCommitmentKeyRate, mxCommitmentAccountKeyRate, mxCommitmentStorageKeyRate, mxCommitmentFoldRate, mxCommitmentUnfoldRate, mxCommitmentDomainReads, mxCommitmentDomainReadDuration, mxCommitmentDomainCacheReads, mxCommitmentDomainCacheReadDuration, mxCommitmentDomainDbReads, mxCommitmentDomainDbReadDuration, mxCommitmentDomainFileReads, mxCommitmentDomainFileReadDuration, mxCommitmentDomainPutRate, @@ -312,7 +312,7 @@ func updateExecDomainMetrics(metrics *kvmetrics.DomainMetrics, prevMetrics *kvme mxExecAccountDomainCacheReads.Set(float64(cacheReads) / seconds) mxExecAccountDomainCacheReadDuration.Set(float64(cacheDuration) / float64(cacheReads)) if executing { - mxExecAccountDomainPutRate.Set(float64(cachePutCount)) + mxExecAccountDomainPutRate.Set(float64(cachePutCount) / seconds) mxExecAccountDomainPutSize.Set(float64(cachePutSize)) mxExecAccountDomainPutKeySize.Set(float64(cachePutKeySize)) mxExecAccountDomainPutValueSize.Set(float64(cachePutValueSize)) @@ -346,10 +346,10 @@ func updateExecDomainMetrics(metrics *kvmetrics.DomainMetrics, prevMetrics *kvme mxExecStorageDomainReads.Set(float64(cacheReads+dbReads+fileReads) / seconds) mxExecStorageDomainReadDuration.Set(float64(cacheDuration+dbDuration+fileDuration) / float64(cacheReads+dbReads+fileReads)) - mxExexStorageDomainCacheReads.Set(float64(cacheReads) / seconds) + mxExecStorageDomainCacheReads.Set(float64(cacheReads) / seconds) mxExecStorageDomainCacheReadDuration.Set(float64(cacheDuration) / float64(cacheReads)) if executing { - mxExecStorageDomainPutRate.Set(float64(cachePutCount)) + mxExecStorageDomainPutRate.Set(float64(cachePutCount) / seconds) mxExecStorageDomainPutSize.Set(float64(cachePutSize)) mxExecStorageDomainPutKeySize.Set(float64(cachePutKeySize)) mxExecStorageDomainPutValueSize.Set(float64(cachePutValueSize)) @@ -383,9 +383,9 @@ func updateExecDomainMetrics(metrics *kvmetrics.DomainMetrics, prevMetrics *kvme mxExecCodeDomainReads.Set(float64(cacheReads+dbReads+fileReads) / seconds) mxExecCodeDomainReadDuration.Set(float64(cacheDuration+dbDuration+fileDuration) / float64(cacheReads+dbReads+fileReads)) - mxExexCodeDomainCacheReads.Set(float64(cacheReads) / seconds) + mxExecCodeDomainCacheReads.Set(float64(cacheReads) / seconds) mxExecCodeDomainCacheReadDuration.Set(float64(cacheDuration) / float64(cacheReads)) - mxExecCodeDomainPutRate.Set(float64(cachePutCount)) + mxExecCodeDomainPutRate.Set(float64(cachePutCount) / seconds) mxExecCodeDomainPutSize.Set(float64(cachePutSize)) mxExecCodeDomainPutKeySize.Set(float64(cachePutKeySize)) mxExecCodeDomainPutValueSize.Set(float64(cachePutValueSize)) @@ -420,7 +420,7 @@ func updateExecDomainMetrics(metrics *kvmetrics.DomainMetrics, prevMetrics *kvme mxCommitmentDomainReadDuration.Set(float64(cacheDuration+dbDuration+fileDuration) / float64(cacheReads+dbReads+fileReads)) mxCommitmentDomainCacheReads.Set(float64(cacheReads) / seconds) mxCommitmentDomainCacheReadDuration.Set(float64(cacheDuration) / float64(cacheReads)) - mxCommitmentDomainPutRate.Set(float64(cachePutCount)) + mxCommitmentDomainPutRate.Set(float64(cachePutCount) / seconds) mxCommitmentDomainPutSize.Set(float64(cachePutSize)) mxCommitmentDomainPutKeySize.Set(float64(cachePutKeySize)) mxCommitmentDomainPutValueSize.Set(float64(cachePutValueSize)) @@ -791,7 +791,7 @@ func (p *Progress) LogCommitments(rs *state.StateV3, ex executor, stepsInDb floa mxCommitmentAccountReadRate.Set(float64(curAccountReadCount) / interval.Seconds()) mxCommitmentStorageReadRate.Set(float64(curStorageReadCount) / interval.Seconds()) mxCommitmentBranchReadRate.Set(float64(curBranchReadCount) / interval.Seconds()) - mxCommitmentBrancgWriteRate.SetUint64(curBranchWriteRate) + mxCommitmentBranchWriteRate.SetUint64(curBranchWriteRate) mxCommitmentTransactions.Set(float64(committedTxSec)) mxCommitmentBlocks.Set(float64(committedDiffBlocks)) From 386568fd06adbfec63ca3103b935d2b90e6f69a2 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:54 +0200 Subject: [PATCH 08/11] execution/stagedsync: reuse shouldMarkExhaustedAtBlock in serial executor The serial loop re-inlined the condition that was extracted and unit-tested for the parallel path. --- execution/stagedsync/exec3_serial.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/execution/stagedsync/exec3_serial.go b/execution/stagedsync/exec3_serial.go index 9e454c001fb..77fb8040a63 100644 --- a/execution/stagedsync/exec3_serial.go +++ b/execution/stagedsync/exec3_serial.go @@ -265,12 +265,8 @@ func (se *serialExecutor) exec(ctx context.Context, execStage *StageState, u Unw lastExecutedStep := kv.Step(uint64(se.lastExecutedTxNum.Load()) / se.doms.StepSize()) - // if we're in the initialCycle before we consider the blockLimit we need to make sure we keep executing - // until we reach a transaction whose comittement which is writable to the db, otherwise the update will get lost - if !initialCycle || lastExecutedStep > 0 && lastExecutedStep > lastFrozenStep && !dbg.DiscardCommitment() { - if blockLimit > 0 && blockNum-startBlockNum+1 >= blockLimit && blockNum != maxBlockNum { - return b.HeaderNoCopy(), rwTx, &ErrLoopExhausted{From: startBlockNum, To: blockNum, Reason: "block limit reached"} - } + if shouldMarkExhaustedAtBlock(initialCycle, lastExecutedStep, lastFrozenStep, dbg.DiscardCommitment(), blockLimit, blockNum, startBlockNum, maxBlockNum) { + return b.HeaderNoCopy(), rwTx, &ErrLoopExhausted{From: startBlockNum, To: blockNum, Reason: "block limit reached"} } } se.doms.PrintCacheStats() From ab6073caebdba84e49f86691a0e6e287c6d18951 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:54 +0200 Subject: [PATCH 09/11] cmd/txpool: fix log file prefix SetupCobra was called with "integration", pasted from cmd/integration. --- cmd/txpool/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/txpool/main.go b/cmd/txpool/main.go index af94130d973..148cd4c77e4 100644 --- a/cmd/txpool/main.go +++ b/cmd/txpool/main.go @@ -113,7 +113,7 @@ var rootCmd = &cobra.Command{ debug.Exit() }, Run: func(cmd *cobra.Command, args []string) { - logger := debug.SetupCobra(cmd, "integration") + logger := debug.SetupCobra(cmd, "txpool") if err := doTxpool(cmd.Context(), logger); err != nil { if !errors.Is(err, context.Canceled) { log.Error(err.Error()) From 8425f98648e43a6d574bc2580ed3a684c8ac274c Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:54 +0200 Subject: [PATCH 10/11] db/kv/mdbx: fix LastDup error message The pseudo-dupsort LastDup wrapped its error as "in FirstDup". --- db/kv/mdbx/kv_mdbx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/kv/mdbx/kv_mdbx.go b/db/kv/mdbx/kv_mdbx.go index c0808c7eeb7..35ca589967f 100644 --- a/db/kv/mdbx/kv_mdbx.go +++ b/db/kv/mdbx/kv_mdbx.go @@ -1591,7 +1591,7 @@ func (c *MdbxCursorPseudoDupSort) LastDup() ([]byte, error) { if mdbx.IsNotFound(err) { return nil, nil } - return nil, fmt.Errorf("in FirstDup: tbl=%s, %w", c.bucketName, err) + return nil, fmt.Errorf("in LastDup: tbl=%s, %w", c.bucketName, err) } return v, nil } From d5bf545feb11eb7f4591acdece2dc31431a6c774 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:25:54 +0200 Subject: [PATCH 11/11] db/snapshotsync/freezeblocks: remove dead SegmentsCaplin copy Near-verbatim copy of snapshotsync.SegmentsCaplin with no callers. --- .../freezeblocks/block_snapshots.go | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/db/snapshotsync/freezeblocks/block_snapshots.go b/db/snapshotsync/freezeblocks/block_snapshots.go index 35e3cb1df0b..95cabb88134 100644 --- a/db/snapshotsync/freezeblocks/block_snapshots.go +++ b/db/snapshotsync/freezeblocks/block_snapshots.go @@ -93,37 +93,6 @@ func NewRoSnapshots(cfg ethconfig.BlocksFreezing, snapDir string, logger log.Log // transaction_hash -> transactions_segment_offset // transaction_hash -> block_number -func SegmentsCaplin(dir string, minBlock uint64) (res []snaptype.FileInfo, missingSnapshots []snapshotsync.Range, err error) { - list, err := snaptype.Segments(dir) - if err != nil { - return nil, missingSnapshots, err - } - - { - var l, lSidecars []snaptype.FileInfo - var m []snapshotsync.Range - for _, f := range list { - if f.Type.Enum() != snaptype.CaplinEnums.BeaconBlocks && f.Type.Enum() != snaptype.CaplinEnums.BlobSidecars { - continue - } - if f.Type.Enum() == snaptype.CaplinEnums.BlobSidecars { - lSidecars = append(lSidecars, f) // blobs are an exception - continue - } - l = append(l, f) - } - l, m = snapshotsync.NoGaps(snapshotsync.NoOverlaps(l)) - if len(m) > 0 { - lst := m[len(m)-1] - log.Debug("[snapshots] see gap", "type", snaptype.CaplinEnums.BeaconBlocks, "from", lst.From()) - } - res = append(res, l...) - res = append(res, lSidecars...) - missingSnapshots = append(missingSnapshots, m...) - } - return res, missingSnapshots, nil -} - func chooseSegmentEnd(from, to uint64, snapType snaptype.Enum, snCfg *snapcfg.Cfg) uint64 { blocksPerFile := snapcfg.MergeLimitFromCfg(snCfg, snapType, from)