Skip to content
Draft
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
40 changes: 39 additions & 1 deletion cl/beacon/handler/block_production.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
peerdasutils "github.com/erigontech/erigon/cl/das/utils"
"github.com/erigontech/erigon/cl/gossip"
"github.com/erigontech/erigon/cl/persistence/beacon_indicies"
"github.com/erigontech/erigon/cl/persistence/blob_storage"
"github.com/erigontech/erigon/cl/phase1/core/state"
"github.com/erigontech/erigon/cl/phase1/forkchoice"
"github.com/erigontech/erigon/cl/phase1/network/subnets"
Expand Down Expand Up @@ -1787,7 +1788,7 @@
return nil, errors.New("invalid content type")
}

func (a *ApiHandler) broadcastBlock(ctx context.Context, blk *cltypes.SignedBeaconBlock, signedEnvelope ...*cltypes.SignedExecutionPayloadEnvelope) error {

Check failure on line 1791 in cl/beacon/handler/block_production.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 69 to the 60 allowed.

See more on https://sonarcloud.io/project/issues?id=erigontech_erigon&issues=AaAQBiBie9jGw-ClOZJ4&open=AaAQBiBie9jGw-ClOZJ4&pullRequest=23292
blkSSZ, err := blk.EncodeSSZ(nil)
if err != nil {
return err
Expand Down Expand Up @@ -2079,7 +2080,12 @@
if err != nil {
return err
}
// TODO: write column sidecars if needed
commitments := block.GetBlobKzgCommitments()
if block.Version() >= clparams.FuluVersion && commitments != nil && commitments.Len() > 0 {
if err := storeProducedDataColumns(ctx, a.columnStorage, a.beaconChainCfg, blockRoot, columnSidecars); err != nil {
return err
}
}

if block.Version() < clparams.FuluVersion {
if err := a.blobStoage.WriteBlobSidecars(ctx, blockRoot, sidecars); err != nil {
Expand Down Expand Up @@ -2141,6 +2147,38 @@
return nil
}

func storeProducedDataColumns(
ctx context.Context,
storage blob_storage.DataColumnStorage,
cfg *clparams.BeaconChainConfig,
blockRoot common.Hash,
columns []*cltypes.DataColumnSidecar,
) error {
expected := int(cfg.NumberOfColumns)
if len(columns) != expected {
return fmt.Errorf("expected %d data columns, got %d", expected, len(columns))
}
if storage == nil {
return errors.New("data column storage is not configured")
}
seen := make([]bool, expected)
for _, column := range columns {
if column == nil || column.Index >= cfg.NumberOfColumns {
return errors.New("produced data column has an invalid index")
}
if seen[column.Index] {
return fmt.Errorf("produced data column %d is duplicated", column.Index)
}
seen[column.Index] = true
}
for _, column := range columns {
if err := storage.WriteColumnSidecars(ctx, blockRoot, int64(column.Index), column); err != nil {
return fmt.Errorf("store data column %d: %w", column.Index, err)
}
}
return nil
}

func (a *ApiHandler) selectedHeadState(auxiliaryRoot common.Hash) (common.Hash, uint64, *state.CachingBeaconState, error) {
auxiliaryState, err := a.forkchoiceStore.GetStateAtBlockRoot(auxiliaryRoot, false)
if err != nil {
Expand Down Expand Up @@ -2171,7 +2209,7 @@
reward uint64
}

func (a *ApiHandler) electraMergedAttestationCandidates(s abstract.BeaconState) (map[common.Hash][]*solid.Attestation, error) {

Check failure on line 2212 in cl/beacon/handler/block_production.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 62 to the 60 allowed.

See more on https://sonarcloud.io/project/issues?id=erigontech_erigon&issues=AaAQBiBie9jGw-ClOZJ5&open=AaAQBiBie9jGw-ClOZJ5&pullRequest=23292
pool := map[common.Hash]map[uint64][]*solid.Attestation{} // map root -> committee -> att candidates
// step 1: Group attestations by data root and committee index for merging
// so after this step, pool[dataRoot][committeeIndex] will contain all the attestation candidates for that data root and committee index
Expand Down
24 changes: 24 additions & 0 deletions cl/beacon/handler/block_production_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/erigontech/erigon/cl/clparams"
"github.com/erigontech/erigon/cl/cltypes"
"github.com/erigontech/erigon/cl/cltypes/solid"
blobstoragemock "github.com/erigontech/erigon/cl/persistence/blob_storage/mock_services"
"github.com/erigontech/erigon/cl/phase1/core/state"
"github.com/erigontech/erigon/cl/phase1/execution_client"
"github.com/erigontech/erigon/common"
Expand All @@ -52,6 +53,29 @@ import (
"github.com/erigontech/erigon/node/gointerfaces/typesproto"
)

func TestStoreProducedDataColumns(t *testing.T) {
cfg := clparams.MainnetBeaconConfig
cfg.NumberOfColumns = 2
blockRoot := common.Hash{1}
columns := []*cltypes.DataColumnSidecar{{Index: 0}, {Index: 1}}

ctrl := gomock.NewController(t)
storage := blobstoragemock.NewMockDataColumnStorage(ctrl)
for _, column := range columns {
storage.EXPECT().WriteColumnSidecars(gomock.Any(), blockRoot, int64(column.Index), column).Return(nil)
}
require.NoError(t, storeProducedDataColumns(t.Context(), storage, &cfg, blockRoot, columns))
}

func TestStoreProducedDataColumnsRequiresEveryColumn(t *testing.T) {
cfg := clparams.MainnetBeaconConfig
cfg.NumberOfColumns = 2
storage := blobstoragemock.NewMockDataColumnStorage(gomock.NewController(t))

err := storeProducedDataColumns(t.Context(), storage, &cfg, common.Hash{}, []*cltypes.DataColumnSidecar{{Index: 0}})
require.Error(t, err)
}

func TestBlockBuilderWindowPreGloas(t *testing.T) {
cfg := &clparams.BeaconChainConfig{
SecondsPerSlot: 12,
Expand Down
5 changes: 1 addition & 4 deletions cl/cltypes/beacon_block_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import (
"github.com/erigontech/erigon/common"
)

// ColumnSyncableSignedBlock is implemented by both SignedBeaconBlock and SignedBlindedBeaconBlock
// for PeerDAS column synchronization operations.
// [New in Gloas:EIP7732] This interface allows PeerDAS to work with both block types
// without needing to call Blinded() which fails for GLOAS blocks.
// ColumnSyncableSignedBlock provides the block metadata needed to request PeerDAS columns.
type ColumnSyncableSignedBlock interface {
Version() clparams.StateVersion
GetSlot() uint64
Expand Down
32 changes: 32 additions & 0 deletions cl/das/availability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 das

import "github.com/erigontech/erigon/cl/clparams"

// IsDataAvailabilityRequired reports whether a Fulu block is inside the protocol's column request window.
func IsDataAvailabilityRequired(cfg *clparams.BeaconChainConfig, currentSlot, blockSlot uint64, version clparams.StateVersion) bool {
if version != clparams.FuluVersion {
return false
}
if cfg.SlotsPerEpoch == 0 {
return true
}
currentEpoch := currentSlot / cfg.SlotsPerEpoch
blockEpoch := blockSlot / cfg.SlotsPerEpoch
return blockEpoch >= currentEpoch || currentEpoch-blockEpoch <= cfg.MinEpochsForDataColumnSidecarsRequests
}
51 changes: 51 additions & 0 deletions cl/das/availability_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 das

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/erigontech/erigon/cl/clparams"
)

func TestIsDataAvailabilityRequired(t *testing.T) {
cfg := clparams.MainnetBeaconConfig
lastSlotInRetentionWindow := (cfg.MinEpochsForDataColumnSidecarsRequests+1)*cfg.SlotsPerEpoch - 1
firstSlotAfterRetentionWindow := lastSlotInRetentionWindow + 1
tests := []struct {
name string
version clparams.StateVersion
currentSlot uint64
blockSlot uint64
want bool
}{
{name: "current Fulu block", version: clparams.FuluVersion, currentSlot: 100, blockSlot: 100, want: true},
{name: "future Fulu block", version: clparams.FuluVersion, currentSlot: 100, blockSlot: 101, want: true},
{name: "last slot in retention window", version: clparams.FuluVersion, currentSlot: lastSlotInRetentionWindow, blockSlot: 0, want: true},
{name: "first slot after retention window", version: clparams.FuluVersion, currentSlot: firstSlotAfterRetentionWindow, blockSlot: 0},
{name: "Electra", version: clparams.ElectraVersion, currentSlot: 100, blockSlot: 100},
{name: "Gloas envelope owns availability", version: clparams.GloasVersion, currentSlot: 100, blockSlot: 100},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, IsDataAvailabilityRequired(&cfg, tt.currentSlot, tt.blockSlot, tt.version))
})
}
}
Loading
Loading