Skip to content

Commit fce3562

Browse files
committed
blobreactor implementation
1 parent b4a1676 commit fce3562

44 files changed

Lines changed: 1657 additions & 135 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

beacon/blockchain/common.go

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,42 +27,45 @@ import (
2727
"github.com/berachain/beacon-kit/consensus/cometbft/service/encoding"
2828
"github.com/berachain/beacon-kit/da/types"
2929
"github.com/berachain/beacon-kit/primitives/math"
30+
cmtabci "github.com/cometbft/cometbft/abci/types"
3031
)
3132

32-
func (s *Service) ParseBeaconBlock(req encoding.ABCIRequest) (
33+
func (s *Service) ParseProcessProposalRequest(req *cmtabci.ProcessProposalRequest) (
3334
*ctypes.SignedBeaconBlock,
3435
types.BlobSidecars,
3536
error,
3637
) {
37-
if countTx := len(req.GetTxs()); countTx > MaxConsensusTxsCount {
38-
return nil, nil, fmt.Errorf("max expected %d, got %d: %w",
39-
MaxConsensusTxsCount, countTx,
40-
ErrTooManyConsensusTxs,
41-
)
38+
blobConsensusEnabled := s.chainSpec.BlobConsensusEnableHeight() > 0 && req.Height >= s.chainSpec.BlobConsensusEnableHeight()
39+
40+
maxTxCount := MaxConsensusTxsCount
41+
if blobConsensusEnabled {
42+
maxTxCount = 1 // After BlobEnableHeight: only 1 tx expected (block), blobs in Blob field
43+
}
44+
45+
if len(req.GetTxs()) > maxTxCount {
46+
return nil, nil, fmt.Errorf("max expected %d txs, got %d", maxTxCount, len(req.GetTxs()))
4247
}
4348

4449
forkVersion := s.chainSpec.ActiveForkVersionForTimestamp(math.U64(req.GetTime().Unix())) //#nosec: G115
45-
// Decode signed block and sidecars.
46-
signedBlk, sidecars, err := encoding.ExtractBlobsAndBlockFromRequest(
47-
req,
48-
BeaconBlockTxIndex,
49-
BlobSidecarsTxIndex,
50-
forkVersion,
51-
)
50+
signedBlk, err := encoding.UnmarshalBeaconBlockFromABCIRequest(req.GetTxs(), BeaconBlockTxIndex, forkVersion)
5251
if err != nil {
5352
return nil, nil, err
5453
}
54+
5555
if signedBlk == nil {
56-
s.logger.Warn(
57-
"Aborting block verification - beacon block not found in proposal",
58-
)
56+
s.logger.Warn("Aborting block verification - beacon block not found in proposal")
5957
return nil, nil, ErrNilBlk
6058
}
61-
if sidecars == nil {
62-
s.logger.Warn(
63-
"Aborting block verification - blob sidecars not found in proposal",
64-
)
65-
return nil, nil, ErrNilBlob
59+
60+
// Extract sidecars using the common helper
61+
sidecars, err := encoding.ExtractBlobSidecarsFromRequest(
62+
req.GetTxs(),
63+
req.GetBlob(),
64+
req.Height,
65+
s.chainSpec.BlobConsensusEnableHeight(),
66+
)
67+
if err != nil {
68+
return nil, nil, fmt.Errorf("failed to extract blob sidecars at height %d: %w", req.Height, err)
6669
}
6770

6871
return signedBlk, sidecars, nil

beacon/blockchain/finalize_block.go

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"time"
2727

2828
ctypes "github.com/berachain/beacon-kit/consensus-types/types"
29+
"github.com/berachain/beacon-kit/consensus/cometbft/service/encoding"
2930
"github.com/berachain/beacon-kit/consensus/types"
3031
datypes "github.com/berachain/beacon-kit/da/types"
3132
"github.com/berachain/beacon-kit/primitives/crypto"
@@ -39,12 +40,27 @@ import (
3940
func (s *Service) FinalizeBlock(
4041
ctx sdk.Context,
4142
req *cmtabci.FinalizeBlockRequest,
43+
blobs datypes.BlobSidecars,
4244
) (transition.ValidatorUpdates, error) {
45+
maxTxCount := MaxConsensusTxsCount
46+
if s.chainSpec.BlobConsensusEnableHeight() > 0 && req.Height >= s.chainSpec.BlobConsensusEnableHeight() {
47+
maxTxCount = 1
48+
}
49+
4350
// STEP 1: Decode block and blobs.
44-
signedBlk, blobs, err := s.ParseBeaconBlock(req)
51+
if countTx := len(req.GetTxs()); countTx > maxTxCount {
52+
return nil, fmt.Errorf("invalid tx count in FinalizeBlock at height %d: expected max %d, got %d",
53+
req.Height, maxTxCount, countTx)
54+
}
55+
56+
forkVersion := s.chainSpec.ActiveForkVersionForTimestamp(math.U64(req.GetTime().Unix())) //#nosec: G115
57+
signedBlk, err := encoding.UnmarshalBeaconBlockFromABCIRequest(
58+
req.GetTxs(),
59+
BeaconBlockTxIndex,
60+
forkVersion,
61+
)
4562
if err != nil {
46-
s.logger.Error("Failed to decode block and blobs", "error", err)
47-
return nil, fmt.Errorf("failed to decode block and blobs: %w", err)
63+
return nil, fmt.Errorf("failed to decode block at height %d with fork version %s: %w", req.Height, forkVersion, err)
4864
}
4965
blk := signedBlk.GetBeaconBlock()
5066
st := s.storageBackend.StateFromContext(ctx)
@@ -73,43 +89,95 @@ func (s *Service) FinalizeBlock(
7389
consensusBlk := types.NewConsensusBlock(blk, req.GetProposerAddress(), req.GetTime())
7490
valUpdates, err := s.finalizeBeaconBlock(ctx, st, consensusBlk)
7591
if err != nil {
76-
s.logger.Error("Failed to process verified beacon block",
77-
"error", err,
78-
)
79-
return nil, err
92+
return nil, fmt.Errorf("failed finalizing beacon block at height %d: %w", req.Height, err)
8093
}
8194

8295
// STEP 4: Post Finalizations cleanups.
8396
return valUpdates, s.PostFinalizeBlockOps(ctx, blk)
8497
}
8598

99+
//nolint:gocognit // ok for now
86100
func (s *Service) FinalizeSidecars(
87101
ctx sdk.Context,
88102
syncingToHeight int64,
89103
blk *ctypes.BeaconBlock,
90104
blobs datypes.BlobSidecars,
91105
) error {
106+
// Check the block to see if there should be blobs
107+
expectedBlobs := len(blk.GetBody().GetBlobKzgCommitments())
108+
if expectedBlobs == 0 {
109+
return nil // No blobs expected for this block
110+
}
111+
112+
processBlobsFunc := func(blobs datypes.BlobSidecars) error {
113+
// Process the blobs which saves them to storage
114+
if err := s.blobProcessor.ProcessSidecars(s.storageBackend.AvailabilityStore(), blobs); err != nil {
115+
return fmt.Errorf("failed to process %d blob sidecars for slot %d: %w", len(blobs), blk.GetSlot(), err)
116+
}
117+
118+
// Final verification
119+
if !s.storageBackend.AvailabilityStore().IsDataAvailable(ctx, blk.GetSlot(), blk.GetBody()) {
120+
return fmt.Errorf("data not available after processing blobs for slot %d: %w", blk.GetSlot(), ErrDataNotAvailable)
121+
}
122+
123+
return nil
124+
}
125+
92126
// SyncingToHeight is always the tip of the chain both during sync and when
93127
// caught up. We don't need to process sidecars unless they are within DA period.
94128
//
95129
//#nosec: G115 // SyncingToHeight will never be negative.
130+
//nolint:nestif // ok for now
96131
if s.chainSpec.WithinDAPeriod(blk.GetSlot(), math.Slot(syncingToHeight)) {
97-
err := s.blobProcessor.ProcessSidecars(
98-
s.storageBackend.AvailabilityStore(),
99-
blobs,
100-
)
132+
if s.storageBackend.AvailabilityStore().IsDataAvailable(ctx, blk.GetSlot(), blk.GetBody()) {
133+
return nil // Data already available
134+
}
135+
136+
// if we are before blob enable height then blobs should be in storage already
137+
if s.chainSpec.BlobConsensusEnableHeight() <= 0 || int64(blk.GetSlot()) < s.chainSpec.BlobConsensusEnableHeight() {
138+
return processBlobsFunc(blobs)
139+
}
140+
141+
// If blobs were passed in (normal consensus mode), use them
142+
if len(blobs) > 0 {
143+
return processBlobsFunc(blobs)
144+
}
145+
146+
// Otherwise we need to fetch the blobs from peers (sync mode)
147+
if s.blobRequester == nil {
148+
return fmt.Errorf("blob requester not initialized for slot %d after BlobEnableHeight %d",
149+
blk.GetSlot(), s.chainSpec.BlobConsensusEnableHeight())
150+
}
151+
152+
// Check if we're at the current consensus height (switching from sync to consensus). In this case, we're finalizing a block
153+
// that was just agreed upon in consensus, and blob fetching would fail since peers are still processing the same height
154+
if int64(blk.GetSlot()) >= syncingToHeight {
155+
s.logger.Warn("At consensus height during mode switch, skipping blob fetch",
156+
"slot", blk.GetSlot(),
157+
"syncingToHeight", syncingToHeight,
158+
"expected_blobs", expectedBlobs)
159+
return nil
160+
}
161+
162+
s.logger.Info("Fetching missing blobs during sync", "slot", blk.GetSlot(), "expected_blobs", expectedBlobs)
163+
now := time.Now()
164+
fetchedBlobs, err := s.blobRequester.RequestBlobs(blk.GetSlot().Unwrap())
101165
if err != nil {
102-
s.logger.Error("Failed to process blob sidecars", "error", err)
103-
return fmt.Errorf("failed to process blob sidecars: %w", err)
166+
return fmt.Errorf("failed to fetch blobs, slot: %d: %w", blk.GetSlot(), err)
104167
}
105168

106-
// Ensure we can access the data using the commitments from the block.
107-
if !s.storageBackend.AvailabilityStore().IsDataAvailable(
108-
ctx, blk.GetSlot(), blk.GetBody(),
109-
) {
110-
return ErrDataNotAvailable
169+
// Validate blobs fetched from peers before processing
170+
err = s.blobProcessor.VerifySidecars(ctx, fetchedBlobs, blk.GetHeader(), blk.GetBody().GetBlobKzgCommitments())
171+
if err != nil {
172+
return fmt.Errorf("failed to verify fetched blobs from peers at slot %d: %w", blk.GetSlot(), err)
111173
}
112-
return nil
174+
175+
s.logger.Info("Successfully fetched and verified blobs",
176+
"slot", blk.GetSlot(),
177+
"count", len(fetchedBlobs),
178+
"elapsed", time.Since(now).String())
179+
180+
return processBlobsFunc(fetchedBlobs)
113181
}
114182

115183
// Here outside Data Availability window. Just log if needed
@@ -141,6 +209,11 @@ func (s *Service) PostFinalizeBlockOps(ctx sdk.Context, blk *ctypes.BeaconBlock)
141209
return err
142210
}
143211

212+
// Update the BlobReactor with the current head slot so it can advertise to peers
213+
if s.blobRequester != nil {
214+
s.blobRequester.SetHeadSlot(slot.Unwrap())
215+
}
216+
144217
// Prune the availability and deposit store.
145218
if err := s.processPruning(ctx, blk); err != nil {
146219
s.logger.Error("failed to processPruning", "error", err)

beacon/blockchain/interfaces.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import (
2626

2727
"github.com/berachain/beacon-kit/chain"
2828
ctypes "github.com/berachain/beacon-kit/consensus-types/types"
29+
"github.com/berachain/beacon-kit/consensus/cometbft/service/blobreactor"
2930
"github.com/berachain/beacon-kit/consensus/cometbft/service/delay"
30-
"github.com/berachain/beacon-kit/consensus/cometbft/service/encoding"
3131
dastore "github.com/berachain/beacon-kit/da/store"
3232
datypes "github.com/berachain/beacon-kit/da/types"
3333
engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives"
@@ -45,6 +45,17 @@ import (
4545
sdk "github.com/cosmos/cosmos-sdk/types"
4646
)
4747

48+
// BlobRequester is the interface for requesting blobs from peers.
49+
type BlobRequester interface {
50+
// RequestBlobs fetches all blobs for a given slot from peers.
51+
// Returns all blob sidecars for the slot, or an error if none could be retrieved.
52+
RequestBlobs(slot uint64) ([]*datypes.BlobSidecar, error)
53+
54+
// SetHeadSlot updates the reactor's view of the current blockchain head slot.
55+
// Called by the blockchain service after processing each block.
56+
SetHeadSlot(slot uint64)
57+
}
58+
4859
// ExecutionEngine is the interface for the execution engine.
4960
type ExecutionEngine interface {
5061
// NotifyNewPayload notifies the execution client of new payload.
@@ -135,7 +146,7 @@ type BlockchainI interface {
135146
context.Context,
136147
[]byte,
137148
) (transition.ValidatorUpdates, error)
138-
ParseBeaconBlock(req encoding.ABCIRequest) (
149+
ParseProcessProposalRequest(*cmtabci.ProcessProposalRequest) (
139150
*ctypes.SignedBeaconBlock,
140151
datypes.BlobSidecars,
141152
error,
@@ -154,6 +165,7 @@ type BlockchainI interface {
154165
FinalizeBlock(
155166
sdk.Context,
156167
*cmtabci.FinalizeBlockRequest,
168+
datypes.BlobSidecars,
157169
) (transition.ValidatorUpdates, error)
158170
PostFinalizeBlockOps(
159171
sdk.Context,
@@ -189,6 +201,7 @@ type ServiceChainSpec interface {
189201
chain.ForkSpec
190202
chain.ForkVersionSpec
191203
delay.ConfigGetter
204+
blobreactor.ConfigGetter
192205

193206
EpochsPerHistoricalVector() uint64
194207
SlotToEpoch(slot math.Slot) math.Epoch

beacon/blockchain/payload_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ func setupOptimisticPayloadTests(t *testing.T, cs chain.Spec, optimisticPayloadB
277277
chain := blockchain.NewService(
278278
sb,
279279
nil, // blockchain.BlobProcessor unused in this test
280+
nil, // blockchain.BlobRequester unused in this test
280281
nil, // deposit.Contract unused in this test
281282
logger,
282283
cs,

beacon/blockchain/process_proposal.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import (
4545
)
4646

4747
const (
48-
// BeaconBlockTxIndex represents the index of the beacon block transaction.
4948
// It is the first transaction in the tx list.
5049
BeaconBlockTxIndex uint = iota
5150
// BlobSidecarsTxIndex represents the index of the blob sidecar transaction.
@@ -62,7 +61,7 @@ func (s *Service) ProcessProposal(
6261
req *cmtabci.ProcessProposalRequest,
6362
thisNodeAddress []byte,
6463
) (transition.ValidatorUpdates, error) {
65-
signedBlk, sidecars, err := s.ParseBeaconBlock(req)
64+
signedBlk, sidecars, err := s.ParseProcessProposalRequest(req)
6665
if err != nil {
6766
s.logger.Error("Failed to decode block and blobs", "error", err)
6867
return nil, fmt.Errorf("failed to decode block and blobs: %w", err)

beacon/blockchain/service.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type Service struct {
3535
storageBackend StorageBackend
3636
// blobProcessor is used for processing sidecars.
3737
blobProcessor BlobProcessor
38+
// blobRequester is used for requesting missing blobs from peers during sync.
39+
blobRequester BlobRequester
3840
// depositContract is the contract interface for interacting with the
3941
// deposit contract.
4042
depositContract deposit.Contract
@@ -70,6 +72,7 @@ type Service struct {
7072
func NewService(
7173
storageBackend StorageBackend,
7274
blobProcessor BlobProcessor,
75+
blobRequester BlobRequester,
7376
depositContract deposit.Contract,
7477
logger log.Logger,
7578
chainSpec ServiceChainSpec,
@@ -82,6 +85,7 @@ func NewService(
8285
return &Service{
8386
storageBackend: storageBackend,
8487
blobProcessor: blobProcessor,
88+
blobRequester: blobRequester,
8589
depositContract: depositContract,
8690
eth1FollowDistance: math.U64(chainSpec.Eth1FollowDistance()),
8791
failedBlocks: make(map[math.Slot]struct{}),

chain/data.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package chain
2222

2323
import (
24+
"github.com/berachain/beacon-kit/consensus/cometbft/service/blobreactor"
2425
"github.com/berachain/beacon-kit/consensus/cometbft/service/delay"
2526
"github.com/berachain/beacon-kit/primitives/common"
2627
)
@@ -29,6 +30,7 @@ import (
2930
// `mapstructure` tag are required.
3031
type SpecData struct {
3132
delay.Config `mapstructure:"block-delay-configuration"`
33+
BlobConfig blobreactor.Config `mapstructure:"blob-reactor-configuration"`
3234

3335
// Gwei value constants.
3436
//

0 commit comments

Comments
 (0)