Skip to content

Commit b72b7bd

Browse files
committed
support p2p-only followers without DA
1 parent e437a62 commit b72b7bd

14 files changed

Lines changed: 196 additions & 58 deletions

File tree

apps/evm/cmd/init.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ func InitCmd() *cobra.Command {
3434
// we use load in order to parse all the flags
3535
cfg, _ := rollconf.Load(cmd)
3636
cfg.Node.Aggregator = aggregator
37+
if cfg.Node.Aggregator && cfg.DA.Address == "" {
38+
cfg.DA.Address = cfg.EffectiveDAAddress()
39+
}
3740
if err := cfg.Validate(); err != nil {
3841
return fmt.Errorf("error validating config: %w", err)
3942
}

apps/evm/cmd/run.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,17 @@ var RunCmd = &cobra.Command{
5959
return err
6060
}
6161

62-
blobClient, err := blobrpc.NewWSClient(cmd.Context(), logger, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "")
63-
if err != nil {
64-
return fmt.Errorf("failed to create blob client: %w", err)
62+
var daClient block.FullDAClient
63+
if nodeConfig.DAEnabled() {
64+
blobClient, err := blobrpc.NewWSClient(cmd.Context(), logger, nodeConfig.EffectiveDAAddress(), nodeConfig.DA.AuthToken, "")
65+
if err != nil {
66+
return fmt.Errorf("failed to create blob client: %w", err)
67+
}
68+
defer blobClient.Close()
69+
daClient = block.NewDAClient(blobClient, nodeConfig, logger)
70+
} else {
71+
logger.Info().Msg("DA address is not configured; syncing through P2P only")
6572
}
66-
defer blobClient.Close()
67-
68-
daClient := block.NewDAClient(blobClient, nodeConfig, logger)
6973

7074
headerNamespace := da.NamespaceFromString(nodeConfig.DA.GetNamespace())
7175
dataNamespace := da.NamespaceFromString(nodeConfig.DA.GetDataNamespace())
@@ -100,6 +104,10 @@ var RunCmd = &cobra.Command{
100104
}
101105

102106
if forceInclusionAddr != "" {
107+
if daClient == nil {
108+
return fmt.Errorf("force inclusion server requires a DA address")
109+
}
110+
103111
ethURL, err := cmd.Flags().GetString(evm.FlagEvmEthURL)
104112
if err != nil {
105113
return fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmEthURL, err)

apps/testapp/cmd/init.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ func InitCmd() *cobra.Command {
3535
// we use load in order to parse all the flags
3636
cfg, _ := rollconf.Load(cmd)
3737
cfg.Node.Aggregator = aggregator
38+
if cfg.Node.Aggregator && cfg.DA.Address == "" {
39+
cfg.DA.Address = cfg.EffectiveDAAddress()
40+
}
3841
cfg.Node.BlockTime = rollconf.DurationWrapper{Duration: 100 * time.Millisecond}
3942
if err := cfg.Validate(); err != nil {
4043
return fmt.Errorf("error validating config: %w", err)

apps/testapp/cmd/run.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ func createSequencer(
113113
genesis genesis.Genesis,
114114
executor execution.Executor,
115115
) (coresequencer.Sequencer, error) {
116+
if !nodeConfig.Node.Aggregator {
117+
return nil, nil
118+
}
119+
116120
if enabled, _ := cmd.Flags().GetBool(flagSoloSequencer); enabled {
117121
if nodeConfig.Node.BasedSequencer {
118122
return nil, fmt.Errorf("solo sequencer cannot be used with based")
@@ -121,7 +125,7 @@ func createSequencer(
121125
return solo.NewSoloSequencer(logger, []byte(genesis.ChainID), executor), nil
122126
}
123127

124-
blobClient, err := blobrpc.NewWSClient(ctx, logger, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "")
128+
blobClient, err := blobrpc.NewWSClient(ctx, logger, nodeConfig.EffectiveDAAddress(), nodeConfig.DA.AuthToken, "")
125129
if err != nil {
126130
return nil, fmt.Errorf("failed to create blob client: %w", err)
127131
}

block/components.go

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -184,24 +184,27 @@ func NewSyncComponents(
184184
}
185185
pruner := pruner.New(logger, store, execPruner, config.Pruning, config.Node.BlockTime.Duration, config.DA.Address)
186186

187-
// Create submitter for sync nodes (no signer, only DA inclusion processing)
188-
var daSubmitter submitting.DASubmitterAPI = submitting.NewDASubmitter(daClient, config, genesis, blockOpts, metrics, logger, headerDAHintAppender, dataDAHintAppender)
189-
if config.Instrumentation.IsTracingEnabled() {
190-
daSubmitter = submitting.WithTracingDASubmitter(daSubmitter)
187+
var submitter *submitting.Submitter
188+
if daClient != nil {
189+
// Create submitter for DA-enabled sync nodes (no signer, only DA inclusion processing).
190+
var daSubmitter submitting.DASubmitterAPI = submitting.NewDASubmitter(daClient, config, genesis, blockOpts, metrics, logger, headerDAHintAppender, dataDAHintAppender)
191+
if config.Instrumentation.IsTracingEnabled() {
192+
daSubmitter = submitting.WithTracingDASubmitter(daSubmitter)
193+
}
194+
submitter = submitting.NewSubmitter(
195+
store,
196+
exec,
197+
cacheManager,
198+
metrics,
199+
config,
200+
genesis,
201+
daSubmitter,
202+
nil, // No sequencer for sync nodes
203+
nil, // No signer for sync nodes
204+
logger,
205+
errorCh,
206+
)
191207
}
192-
submitter := submitting.NewSubmitter(
193-
store,
194-
exec,
195-
cacheManager,
196-
metrics,
197-
config,
198-
genesis,
199-
daSubmitter,
200-
nil, // No sequencer for sync nodes
201-
nil, // No signer for sync nodes
202-
logger,
203-
errorCh,
204-
)
205208

206209
return &Components{
207210
Syncer: syncer,

block/components_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,39 @@ func TestNewSyncComponents_Creation(t *testing.T) {
132132
assert.Nil(t, components.Executor) // Sync nodes don't have executors
133133
}
134134

135+
func TestNewSyncComponents_WithoutDA(t *testing.T) {
136+
ds := sync.MutexWrap(datastore.NewMapDatastore())
137+
memStore := store.New(ds)
138+
139+
cfg := config.DefaultConfig()
140+
gen := genesis.Genesis{
141+
ChainID: "test-chain",
142+
InitialHeight: 1,
143+
StartTime: time.Now(),
144+
ProposerAddress: []byte("test-proposer"),
145+
}
146+
147+
components, err := NewSyncComponents(
148+
cfg,
149+
gen,
150+
memStore,
151+
testmocks.NewMockExecutor(t),
152+
nil,
153+
extmocks.NewMockStore[*types.P2PSignedHeader](t),
154+
extmocks.NewMockStore[*types.P2PData](t),
155+
noopDAHintAppender{},
156+
noopDAHintAppender{},
157+
zerolog.Nop(),
158+
NopMetrics(),
159+
DefaultBlockOptions(),
160+
nil,
161+
)
162+
163+
require.NoError(t, err)
164+
assert.NotNil(t, components.Syncer)
165+
assert.Nil(t, components.Submitter)
166+
}
167+
135168
func TestNewAggregatorComponents_Creation(t *testing.T) {
136169
ds := sync.MutexWrap(datastore.NewMapDatastore())
137170
memStore := store.New(ds)

block/internal/syncing/syncer.go

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -181,16 +181,18 @@ func (s *Syncer) Start(ctx context.Context) (err error) {
181181
return fmt.Errorf("failed to initialize syncer state: %w", err)
182182
}
183183

184-
// Initialize handlers
185-
daRetriever := NewDARetriever(s.daClient, s.cache, s.genesis, s.logger)
186-
daRetriever.setExpectedProposerProvider(s.expectedProposerForHeight)
187-
s.daRetriever = daRetriever
188-
if s.config.Instrumentation.IsTracingEnabled() {
189-
s.daRetriever = WithTracingDARetriever(s.daRetriever)
190-
}
184+
if s.daClient != nil {
185+
// Initialize DA-specific handlers only when DA is configured.
186+
daRetriever := NewDARetriever(s.daClient, s.cache, s.genesis, s.logger)
187+
daRetriever.setExpectedProposerProvider(s.expectedProposerForHeight)
188+
s.daRetriever = daRetriever
189+
if s.config.Instrumentation.IsTracingEnabled() {
190+
s.daRetriever = WithTracingDARetriever(s.daRetriever)
191+
}
191192

192-
s.fiRetriever = da.NewForcedInclusionRetriever(s.daClient, s.logger, s.config.DA.BlockTime.Duration, s.config.Instrumentation.IsTracingEnabled(), s.genesis.DAStartHeight, s.genesis.DAEpochForcedInclusion)
193-
s.fiRetriever.Start(ctx)
193+
s.fiRetriever = da.NewForcedInclusionRetriever(s.daClient, s.logger, s.config.DA.BlockTime.Duration, s.config.Instrumentation.IsTracingEnabled(), s.genesis.DAStartHeight, s.genesis.DAEpochForcedInclusion)
194+
s.fiRetriever.Start(ctx)
195+
}
194196
s.p2pHandler = NewP2PHandler(s.headerStore, s.dataStore, s.cache, s.genesis, s.logger)
195197

196198
currentHeight, initErr := s.store.Height(ctx)
@@ -213,19 +215,21 @@ func (s *Syncer) Start(ctx context.Context) (err error) {
213215
// Start main processing loop
214216
s.wg.Go(func() { s.processLoop(ctx) })
215217

216-
// Start the DA follower (subscribe + catchup) and other workers
217-
s.daFollower = NewDAFollower(DAFollowerConfig{
218-
Client: s.daClient,
219-
Retriever: s.daRetriever,
220-
Logger: s.logger,
221-
EventSink: s,
222-
Namespace: s.daClient.GetHeaderNamespace(),
223-
DataNamespace: s.daClient.GetDataNamespace(),
224-
StartDAHeight: s.daRetrieverHeight.Load(),
225-
DABlockTime: s.config.DA.BlockTime.Duration,
226-
})
227-
if err = s.daFollower.Start(ctx); err != nil {
228-
return fmt.Errorf("failed to start DA follower: %w", err)
218+
if s.daClient != nil {
219+
// Start the DA follower (subscribe + catchup) when DA is configured.
220+
s.daFollower = NewDAFollower(DAFollowerConfig{
221+
Client: s.daClient,
222+
Retriever: s.daRetriever,
223+
Logger: s.logger,
224+
EventSink: s,
225+
Namespace: s.daClient.GetHeaderNamespace(),
226+
DataNamespace: s.daClient.GetDataNamespace(),
227+
StartDAHeight: s.daRetrieverHeight.Load(),
228+
DABlockTime: s.config.DA.BlockTime.Duration,
229+
})
230+
if err = s.daFollower.Start(ctx); err != nil {
231+
return fmt.Errorf("failed to start DA follower: %w", err)
232+
}
229233
}
230234

231235
s.startSyncWorkers(ctx)
@@ -602,7 +606,7 @@ func (s *Syncer) processHeightEvent(ctx context.Context, event *common.DAHeightE
602606

603607
// If this is a P2P event with a DA height hint, trigger targeted DA retrieval
604608
// This allows us to fetch the block directly from the specified DA height instead of sequential scanning
605-
if event.Source == common.SourceP2P {
609+
if event.Source == common.SourceP2P && s.daFollower != nil {
606610
var daHeightHints []uint64
607611
switch {
608612
case event.DaHeightHints == [2]uint64{0, 0}:

docs/learn/config.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ da:
472472
### DA Service Address
473473

474474
**Description:**
475-
The network address (host:port) of the Data Availability layer service. Evolve connects to this endpoint to submit and retrieve block data.
475+
The network address (host:port) of the Data Availability layer service. A full follower with this value unset syncs exclusively through P2P and does not create a DA client. Aggregators retain the legacy `http://localhost:7980` fallback when this value is unset.
476476

477477
**YAML:**
478478

@@ -484,7 +484,7 @@ da:
484484
**Command-line Flag:**
485485
`--evnode.da.address <string>`
486486
_Example:_ `--evnode.da.address 192.168.1.100:26659`
487-
_Default:_ `"http://localhost:7980"`
487+
_Default:_ `""` (empty; P2P-only follower)
488488
_Constant:_ `FlagDAAddress`
489489

490490
### DA Authentication Token

docs/learn/specs/da.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Evolve provides a generic [data availability interface][da-interface] for modula
66

77
`Client` can connect via JSON-RPC transports using Evolve's [jsonrpc][jsonrpc] implementations. The connection can be configured using the following cli flags:
88

9-
* `--rollkit.da.address`: url address of the DA service (default: "grpc://localhost:26650")
9+
* `--rollkit.da.address`: URL address of the DA service (default: empty for P2P-only followers; aggregators fall back to `http://localhost:7980`)
1010
* `--rollkit.da.auth_token`: authentication token of the DA service
1111
* `--rollkit.da.namespace`: namespace to use when submitting blobs to the DA service (deprecated)
1212
* `--rollkit.da.header_namespace`: namespace to use when submitting headers to the DA service

docs/reference/specs/da.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Evolve provides a generic [data availability interface][da-interface] for modula
66

77
`Client` can connect via JSON-RPC transports using Evolve's [jsonrpc][jsonrpc] implementations. The connection can be configured using the following cli flags:
88

9-
* `--rollkit.da.address`: url address of the DA service (default: "grpc://localhost:26650")
9+
* `--rollkit.da.address`: URL address of the DA service (default: empty for P2P-only followers; aggregators fall back to `http://localhost:7980`)
1010
* `--rollkit.da.auth_token`: authentication token of the DA service
1111
* `--rollkit.da.namespace`: namespace to use when submitting blobs to the DA service (deprecated)
1212
* `--rollkit.da.header_namespace`: namespace to use when submitting headers to the DA service

0 commit comments

Comments
 (0)