Skip to content

Commit 89cd61e

Browse files
committed
feat(preconf): trigger optimistic FCU for whitelisted proposers
1 parent 565ab1a commit 89cd61e

File tree

7 files changed

+78
-6
lines changed

7 files changed

+78
-6
lines changed

beacon/blockchain/payload_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func TestOptimisticBlockBuildingRejectedBlockStateChecks(t *testing.T) {
134134
ctx.ConsensusCtx(),
135135
types.NewConsensusBlock(invalidBlk, proposerAddress, consensusTime),
136136
true, // this block is next block proposer
137+
proposerAddress,
137138
)
138139
require.ErrorIs(t, err, core.ErrProposerMismatch)
139140

@@ -241,6 +242,7 @@ func TestOptimisticBlockBuildingVerifiedBlockStateChecks(t *testing.T) {
241242
ctx.ConsensusCtx(),
242243
types.NewConsensusBlock(validBlk, ctx.ProposerAddress(), consensusTime),
243244
true, // this block is next block proposer
245+
ctx.ProposerAddress(),
244246
)
245247
require.NoError(t, err)
246248

@@ -282,6 +284,8 @@ func setupOptimisticPayloadTests(t *testing.T, cs chain.Spec) (
282284
b,
283285
sp,
284286
ts,
287+
nil, // preconf.Config unused in this test
288+
nil, // preconf.Whitelist unused in this test
285289
)
286290
return chain, st, cms, ctx, sp, b, sb, eng, depStore
287291
}

beacon/blockchain/process_proposal.go

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ func (s *Service) ProcessProposal(
155155
ctx,
156156
consensusBlk,
157157
bytes.Equal(thisNodeAddress, req.NextProposerAddress),
158+
req.NextProposerAddress,
158159
)
159160
if err != nil {
160161
s.logger.Error("failed to verify incoming block", "error", err)
@@ -259,6 +260,7 @@ func (s *Service) VerifyIncomingBlock(
259260
ctx context.Context,
260261
blk *types.ConsensusBlock,
261262
isNextBlockProposer bool,
263+
nextProposerAddress []byte,
262264
) (transition.ValidatorUpdates, error) {
263265
beaconBlk := blk.GetBeaconBlock()
264266
state := s.storageBackend.StateFromContext(ctx)
@@ -302,7 +304,7 @@ func (s *Service) VerifyIncomingBlock(
302304
var (
303305
nextBlockData *builder.RequestPayloadData
304306
errFetch error
305-
shouldBuildNextPayload = s.shouldBuildNextPayload(isNextBlockProposer)
307+
shouldBuildNextPayload = s.shouldBuildNextPayload(isNextBlockProposer, state, nextProposerAddress)
306308
)
307309

308310
if shouldBuildNextPayload {
@@ -398,8 +400,51 @@ func (s *Service) verifyStateRoot(
398400
return valUpdates, err
399401
}
400402

401-
// shouldBuildNextPayload returns true if optimistic
402-
// payload builds are enabled.
403-
func (s *Service) shouldBuildNextPayload(isNextBlockProposer bool) bool {
404-
return isNextBlockProposer && s.localBuilder.Enabled()
403+
// shouldBuildNextPayload returns true if optimistic payload builds should be triggered.
404+
// In normal mode, builds only when this node is the next proposer.
405+
// In sequencer mode, also builds when the next proposer is whitelisted.
406+
func (s *Service) shouldBuildNextPayload(isNextBlockProposer bool, st *statedb.StateDB, nextProposerAddress []byte) bool {
407+
if !s.localBuilder.Enabled() {
408+
return false
409+
}
410+
411+
// Normal behavior: build if we are the proposer
412+
if isNextBlockProposer {
413+
return true
414+
}
415+
416+
// Sequencer mode: build if next proposer is whitelisted
417+
if s.preconfCfg != nil && s.preconfCfg.IsSequencer() {
418+
nextProposerPubkey, err := s.getNextProposerPubkey(st, nextProposerAddress)
419+
if err != nil {
420+
s.logger.Error("Failed to get next proposer pubkey", "error", err)
421+
return false
422+
}
423+
424+
isWhitelisted := s.preconfWhitelist.IsWhitelisted(nextProposerPubkey)
425+
if isWhitelisted {
426+
s.logger.Info("Sequencer mode: next proposer is whitelisted, triggering optimistic build")
427+
}
428+
429+
return isWhitelisted
430+
}
431+
432+
return false
433+
}
434+
435+
// getNextProposerPubkey retrieves the BLS public key for the next proposer given their CometBFT address.
436+
func (s *Service) getNextProposerPubkey(st *statedb.StateDB, nextProposerAddress []byte) (crypto.BLSPubkey, error) {
437+
// Convert CometBFT address to validator index
438+
proposerIndex, err := st.ValidatorIndexByCometBFTAddress(nextProposerAddress)
439+
if err != nil {
440+
return crypto.BLSPubkey{}, fmt.Errorf("failed to get validator index: %w", err)
441+
}
442+
443+
// Get validator record
444+
validator, err := st.ValidatorByIndex(proposerIndex)
445+
if err != nil {
446+
return crypto.BLSPubkey{}, fmt.Errorf("failed to get validator: %w", err)
447+
}
448+
449+
return validator.GetPubkey(), nil
405450
}

beacon/blockchain/service.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"sync"
2727
"sync/atomic"
2828

29+
"github.com/berachain/beacon-kit/beacon/preconf"
2930
engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives"
3031
"github.com/berachain/beacon-kit/execution/deposit"
3132
"github.com/berachain/beacon-kit/log"
@@ -69,6 +70,16 @@ type Service struct {
6970
// It helps avoid resending the same FCU data (and spares a network call)
7071
// in case optimistic block building is active
7172
latestFcuReq atomic.Pointer[engineprimitives.ForkchoiceStateV1]
73+
74+
// preconfCfg holds the preconfirmation configuration.
75+
preconfCfg *preconf.Config
76+
77+
// preconfWhitelist contains whitelisted validator pubkeys for preconfirmation.
78+
// Used by both sequencer and validators:
79+
// - Sequencer: checks if next proposer is whitelisted to trigger optimistic FCU
80+
// - Validator: checks if self is whitelisted to fetch payload from sequencer
81+
// Can be nil if preconf is disabled.
82+
preconfWhitelist preconf.Whitelist
7283
}
7384

7485
// NewService creates a new validator service.
@@ -82,6 +93,8 @@ func NewService(
8293
localBuilder LocalBuilder,
8394
stateProcessor StateProcessor,
8495
telemetrySink TelemetrySink,
96+
preconfCfg *preconf.Config,
97+
preconfWhitelist preconf.Whitelist,
8598
) *Service {
8699
return &Service{
87100
storageBackend: storageBackend,
@@ -96,6 +109,8 @@ func NewService(
96109
stateProcessor: stateProcessor,
97110
metrics: newChainMetrics(telemetrySink),
98111
forceStartupSyncOnce: new(sync.Once),
112+
preconfCfg: preconfCfg,
113+
preconfWhitelist: preconfWhitelist,
99114
}
100115
}
101116

beacon/preconf/loader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func LoadWhitelist(path string) ([]crypto.BLSPubkey, error) {
3535
return nil, errors.New("whitelist path is empty")
3636
}
3737

38-
data, err := os.ReadFile(path)
38+
data, err := os.ReadFile(path) // #nosec G304 -- path from operator-controlled config
3939
if err != nil {
4040
return nil, errors.Wrapf(err, "failed to read whitelist file: %s", path)
4141
}

cmd/beacond/defaults.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func DefaultComponents() []any {
4242
components.ProvideExecutionEngine,
4343
components.ProvideJWTSecret,
4444
components.ProvideLocalBuilder,
45+
components.ProvidePreconfWhitelist,
4546
components.ProvideReportingService,
4647
components.ProvideCometBFTService,
4748
components.ProvideServiceRegistry,

node-core/components/chain_service.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ package components
2323
import (
2424
"cosmossdk.io/depinject"
2525
"github.com/berachain/beacon-kit/beacon/blockchain"
26+
"github.com/berachain/beacon-kit/beacon/preconf"
2627
"github.com/berachain/beacon-kit/chain"
28+
"github.com/berachain/beacon-kit/config"
2729
"github.com/berachain/beacon-kit/execution/deposit"
2830
"github.com/berachain/beacon-kit/execution/engine"
2931
"github.com/berachain/beacon-kit/log/phuslu"
@@ -35,6 +37,7 @@ import (
3537
type ChainServiceInput struct {
3638
depinject.In
3739

40+
Cfg *config.Config
3841
ChainSpec chain.Spec
3942
ExecutionEngine *engine.Engine
4043
LocalBuilder LocalBuilder
@@ -44,6 +47,7 @@ type ChainServiceInput struct {
4447
BlobProcessor BlobProcessor
4548
TelemetrySink *metrics.TelemetrySink
4649
BeaconDepositContract deposit.Contract
50+
PreconfWhitelist preconf.Whitelist
4751
}
4852

4953
// ProvideChainService is a depinject provider for the blockchain service.
@@ -58,5 +62,7 @@ func ProvideChainService(in ChainServiceInput) *blockchain.Service {
5862
in.LocalBuilder,
5963
in.StateProcessor,
6064
in.TelemetrySink,
65+
&in.Cfg.Preconf,
66+
in.PreconfWhitelist,
6167
)
6268
}

testing/simulated/components.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func FixedComponents(t *testing.T) []any {
4949
components.ProvideExecutionEngine,
5050
components.ProvideJWTSecret,
5151
components.ProvideLocalBuilder,
52+
components.ProvidePreconfWhitelist,
5253
components.ProvideReportingService,
5354
components.ProvideServiceRegistry,
5455
components.ProvideSidecarFactory,

0 commit comments

Comments
 (0)