Skip to content

Commit cbb6451

Browse files
committed
feat: forced inclusion for executor
1 parent d1f4125 commit cbb6451

22 files changed

Lines changed: 335 additions & 73 deletions

.mockery.yaml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ packages:
4848
filename: external/hstore.go
4949
github.com/evstack/ev-node/block/internal/syncing:
5050
interfaces:
51-
daRetriever:
51+
DaRetrieverI:
5252
config:
5353
dir: ./block/internal/syncing
5454
pkgname: syncing
@@ -65,8 +65,3 @@ packages:
6565
dir: ./block/internal/common
6666
pkgname: common
6767
filename: broadcaster_mock.go
68-
p2pHandler:
69-
config:
70-
dir: ./block/internal/syncing
71-
pkgname: syncing
72-
filename: syncer_mock.go

block/components.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,13 @@ func NewAggregatorComponents(
208208
// error channel for critical failures
209209
errorCh := make(chan error, 1)
210210

211+
daRetriever := syncing.NewDARetriever(da, cacheManager, config, genesis, logger)
212+
211213
executor, err := executing.NewExecutor(
212214
store,
213215
exec,
214216
sequencer,
217+
daRetriever,
215218
signer,
216219
cacheManager,
217220
metrics,

block/internal/common/event.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ type DAHeightEvent struct {
2121
// Source indicates where this event originated from (DA or P2P)
2222
Source EventSource
2323
}
24+
25+
// ForcedIncluded represents a forced inclusion event for caching
26+
type ForcedIncludedEvent struct {
27+
Txs [][]byte
28+
StartDaHeight uint64
29+
EndDaHeight uint64
30+
}

block/internal/executing/executor.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/evstack/ev-node/block/internal/cache"
1717
"github.com/evstack/ev-node/block/internal/common"
18+
"github.com/evstack/ev-node/block/internal/syncing"
1819
coreexecutor "github.com/evstack/ev-node/core/execution"
1920
coresequencer "github.com/evstack/ev-node/core/sequencer"
2021
"github.com/evstack/ev-node/pkg/config"
@@ -27,10 +28,11 @@ import (
2728
// Executor handles block production, transaction processing, and state management
2829
type Executor struct {
2930
// Core components
30-
store store.Store
31-
exec coreexecutor.Executor
32-
sequencer coresequencer.Sequencer
33-
signer signer.Signer
31+
store store.Store
32+
exec coreexecutor.Executor
33+
sequencer coresequencer.Sequencer
34+
signer signer.Signer
35+
daRetriever syncing.DaRetrieverI
3436

3537
// Shared components
3638
cache cache.Manager
@@ -71,6 +73,7 @@ func NewExecutor(
7173
store store.Store,
7274
exec coreexecutor.Executor,
7375
sequencer coresequencer.Sequencer,
76+
daRetriever syncing.DaRetrieverI,
7477
signer signer.Signer,
7578
cache cache.Manager,
7679
metrics *common.Metrics,
@@ -99,6 +102,7 @@ func NewExecutor(
99102
store: store,
100103
exec: exec,
101104
sequencer: sequencer,
105+
daRetriever: daRetriever,
102106
signer: signer,
103107
cache: cache,
104108
metrics: metrics,
@@ -199,7 +203,7 @@ func (e *Executor) initializeState() error {
199203
LastBlockHeight: e.genesis.InitialHeight - 1,
200204
LastBlockTime: e.genesis.StartTime,
201205
AppHash: stateRoot,
202-
DAHeight: 0,
206+
DAHeight: e.genesis.DAStartHeight,
203207
}
204208
}
205209

@@ -330,6 +334,12 @@ func (e *Executor) produceBlock() error {
330334
}
331335
}
332336

337+
// fetch forced included txs
338+
forcedIncludedTxsEvent, err := e.daRetriever.RetrieveForcedIncludedTxsFromDA(e.ctx, currentState.DAHeight)
339+
if err != nil {
340+
e.logger.Error().Err(err).Msg("failed to retrieve forced included txs")
341+
}
342+
333343
var (
334344
header *types.SignedHeader
335345
data *types.Data
@@ -356,6 +366,12 @@ func (e *Executor) produceBlock() error {
356366
return fmt.Errorf("failed to retrieve batch: %w", err)
357367
}
358368

369+
// append forced included txs to batch data
370+
// TODO(@julienrbrt): if the batch is at size, adding more txs isn't what we want.
371+
// maybe we need to add a limit to retrieveBatch based on the forced included txs size.
372+
// for the poc this is ok as is.
373+
batchData.Transactions = append(batchData.Transactions, forcedIncludedTxsEvent.Txs...)
374+
359375
header, data, err = e.createBlock(e.ctx, newHeight, batchData)
360376
if err != nil {
361377
return fmt.Errorf("failed to create block: %w", err)
@@ -379,6 +395,11 @@ func (e *Executor) produceBlock() error {
379395
return fmt.Errorf("failed to apply block: %w", err)
380396
}
381397

398+
// update da height, based on last retrieved.
399+
if forcedIncludedTxsEvent.EndDaHeight > newState.DAHeight {
400+
newState.DAHeight = forcedIncludedTxsEvent.EndDaHeight
401+
}
402+
382403
// signing the header is done after applying the block
383404
// as for signing, the state of the block may be required by the signature payload provider.
384405
signature, err := e.signHeader(header.Header)

block/internal/executing/executor_lazy_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/evstack/ev-node/block/internal/cache"
1616
"github.com/evstack/ev-node/block/internal/common"
17+
"github.com/evstack/ev-node/block/internal/syncing"
1718
coreseq "github.com/evstack/ev-node/core/sequencer"
1819
"github.com/evstack/ev-node/pkg/config"
1920
"github.com/evstack/ev-node/pkg/genesis"
@@ -56,6 +57,7 @@ func TestLazyMode_ProduceBlockLogic(t *testing.T) {
5657
memStore,
5758
mockExec,
5859
mockSeq,
60+
syncing.NewMockDaRetrieverI(t),
5961
signerWrapper,
6062
cacheManager,
6163
metrics,
@@ -166,6 +168,7 @@ func TestRegularMode_ProduceBlockLogic(t *testing.T) {
166168
memStore,
167169
mockExec,
168170
mockSeq,
171+
syncing.NewMockDaRetrieverI(t),
169172
signerWrapper,
170173
cacheManager,
171174
metrics,

block/internal/executing/executor_logic_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/evstack/ev-node/block/internal/cache"
1818
"github.com/evstack/ev-node/block/internal/common"
19+
"github.com/evstack/ev-node/block/internal/syncing"
1920
coreseq "github.com/evstack/ev-node/core/sequencer"
2021
"github.com/evstack/ev-node/pkg/config"
2122
"github.com/evstack/ev-node/pkg/genesis"
@@ -78,6 +79,7 @@ func TestProduceBlock_EmptyBatch_SetsEmptyDataHash(t *testing.T) {
7879
memStore,
7980
mockExec,
8081
mockSeq,
82+
syncing.NewMockDaRetrieverI(t),
8183
signerWrapper,
8284
cacheManager,
8385
metrics,
@@ -165,6 +167,7 @@ func TestPendingLimit_SkipsProduction(t *testing.T) {
165167
memStore,
166168
mockExec,
167169
mockSeq,
170+
syncing.NewMockDaRetrieverI(t),
168171
signerWrapper,
169172
cacheManager,
170173
metrics,

block/internal/executing/executor_restart_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/evstack/ev-node/block/internal/cache"
1616
"github.com/evstack/ev-node/block/internal/common"
17+
"github.com/evstack/ev-node/block/internal/syncing"
1718
coreseq "github.com/evstack/ev-node/core/sequencer"
1819
"github.com/evstack/ev-node/pkg/config"
1920
"github.com/evstack/ev-node/pkg/genesis"
@@ -56,6 +57,7 @@ func TestExecutor_RestartUsesPendingHeader(t *testing.T) {
5657
memStore,
5758
mockExec1,
5859
mockSeq1,
60+
syncing.NewMockDaRetrieverI(t),
5961
signerWrapper,
6062
cacheManager,
6163
metrics,
@@ -175,6 +177,7 @@ func TestExecutor_RestartUsesPendingHeader(t *testing.T) {
175177
memStore, // same store
176178
mockExec2,
177179
mockSeq2,
180+
syncing.NewMockDaRetrieverI(t),
178181
signerWrapper,
179182
cacheManager,
180183
metrics,
@@ -273,6 +276,7 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) {
273276
memStore,
274277
mockExec1,
275278
mockSeq1,
279+
syncing.NewMockDaRetrieverI(t),
276280
signerWrapper,
277281
cacheManager,
278282
metrics,
@@ -325,6 +329,7 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) {
325329
memStore,
326330
mockExec2,
327331
mockSeq2,
332+
syncing.NewMockDaRetrieverI(t),
328333
signerWrapper,
329334
cacheManager,
330335
metrics,

block/internal/executing/executor_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/evstack/ev-node/block/internal/cache"
1515
"github.com/evstack/ev-node/block/internal/common"
16+
"github.com/evstack/ev-node/block/internal/syncing"
1617
"github.com/evstack/ev-node/pkg/config"
1718
"github.com/evstack/ev-node/pkg/genesis"
1819
"github.com/evstack/ev-node/pkg/store"
@@ -46,8 +47,9 @@ func TestExecutor_BroadcasterIntegration(t *testing.T) {
4647
// Create executor with broadcasters
4748
executor, err := NewExecutor(
4849
memStore,
49-
nil, // nil executor (we're not testing execution)
50-
nil, // nil sequencer (we're not testing sequencing)
50+
nil, // nil executor (we're not testing execution)
51+
nil, // nil sequencer (we're not testing sequencing)
52+
syncing.NewMockDaRetrieverI(t),
5153
testSigner, // test signer (required for executor)
5254
cacheManager,
5355
metrics,
@@ -96,8 +98,9 @@ func TestExecutor_NilBroadcasters(t *testing.T) {
9698
// Create executor with nil broadcasters (light node scenario)
9799
executor, err := NewExecutor(
98100
memStore,
99-
nil, // nil executor
100-
nil, // nil sequencer
101+
nil, // nil executor
102+
nil, // nil sequencer
103+
syncing.NewMockDaRetrieverI(t),
101104
testSigner, // test signer (required for executor)
102105
cacheManager,
103106
metrics,

block/internal/reaping/reaper_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func newTestExecutor(t *testing.T) *executing.Executor {
4040
nil, // store (unused)
4141
nil, // core executor (unused)
4242
nil, // sequencer (unused)
43+
nil, // daretriever (unused)
4344
s, // signer (required)
4445
nil, // cache (unused)
4546
nil, // metrics (unused)

block/internal/syncing/da_retriever.go

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ type DARetriever struct {
3030
logger zerolog.Logger
3131

3232
// calculate namespaces bytes once and reuse them
33-
namespaceBz []byte
34-
namespaceDataBz []byte
33+
namespaceBz []byte
34+
namespaceDataBz []byte
35+
namespaceForcedInclusionBz []byte
36+
37+
hasForcedInclusionNs bool
38+
daEpochSize uint64
3539

3640
// transient cache, only full event need to be passed to the syncer
3741
// on restart, will be refetch as da height is updated by syncer
@@ -47,15 +51,26 @@ func NewDARetriever(
4751
genesis genesis.Genesis,
4852
logger zerolog.Logger,
4953
) *DARetriever {
54+
forcedInclusionNs := config.DA.GetForcedInclusionNamespace()
55+
hasForcedInclusionNs := forcedInclusionNs != ""
56+
57+
var namespaceForcedInclusionBz []byte
58+
if hasForcedInclusionNs {
59+
namespaceForcedInclusionBz = coreda.NamespaceFromString(forcedInclusionNs).Bytes()
60+
}
61+
5062
return &DARetriever{
51-
da: da,
52-
cache: cache,
53-
genesis: genesis,
54-
logger: logger.With().Str("component", "da_retriever").Logger(),
55-
namespaceBz: coreda.NamespaceFromString(config.DA.GetNamespace()).Bytes(),
56-
namespaceDataBz: coreda.NamespaceFromString(config.DA.GetDataNamespace()).Bytes(),
57-
pendingHeaders: make(map[uint64]*types.SignedHeader),
58-
pendingData: make(map[uint64]*types.Data),
63+
da: da,
64+
cache: cache,
65+
genesis: genesis,
66+
logger: logger.With().Str("component", "da_retriever").Logger(),
67+
namespaceBz: coreda.NamespaceFromString(config.DA.GetNamespace()).Bytes(),
68+
namespaceDataBz: coreda.NamespaceFromString(config.DA.GetDataNamespace()).Bytes(),
69+
namespaceForcedInclusionBz: namespaceForcedInclusionBz,
70+
hasForcedInclusionNs: hasForcedInclusionNs,
71+
daEpochSize: config.DA.ForcedInclusionDAEpoch,
72+
pendingHeaders: make(map[uint64]*types.SignedHeader),
73+
pendingData: make(map[uint64]*types.Data),
5974
}
6075
}
6176

@@ -76,6 +91,40 @@ func (r *DARetriever) RetrieveFromDA(ctx context.Context, daHeight uint64) ([]co
7691
return r.processBlobs(ctx, blobsResp.Data, daHeight), nil
7792
}
7893

94+
// RetrieveForcedIncludedTxsFromDA retrieves forced inclusion transactions from the DA layer.
95+
// It fetches from the daHeight for the da epoch range defined in the config.
96+
func (r *DARetriever) RetrieveForcedIncludedTxsFromDA(ctx context.Context, daHeight uint64) (*common.ForcedIncludedEvent, error) {
97+
if !r.hasForcedInclusionNs {
98+
return nil, fmt.Errorf("forced inclusion namespace not configured")
99+
}
100+
101+
event := &common.ForcedIncludedEvent{
102+
StartDaHeight: daHeight,
103+
}
104+
105+
r.logger.Debug().Uint64("da_height", daHeight).Uint64("range", r.daEpochSize).Msg("retrieving forced included transactions from DA")
106+
107+
for epochHeight := daHeight + 1; epochHeight <= daHeight+r.daEpochSize; epochHeight++ {
108+
result := types.RetrieveWithHelpers(ctx, r.da, r.logger, epochHeight, r.namespaceForcedInclusionBz, defaultDATimeout)
109+
110+
// quickly break if we are too ahead.
111+
if result.Code == coreda.StatusHeightFromFuture {
112+
break
113+
}
114+
115+
if result.Code == coreda.StatusSuccess {
116+
if err := r.validateBlobResponse(result, epochHeight); !errors.Is(err, coreda.ErrBlobNotFound) && err != nil {
117+
return nil, err
118+
}
119+
120+
event.StartDaHeight = epochHeight
121+
event.Txs = append(event.Txs, result.Data...)
122+
}
123+
}
124+
125+
return event, nil
126+
}
127+
79128
// fetchBlobs retrieves blobs from the DA layer
80129
func (r *DARetriever) fetchBlobs(ctx context.Context, daHeight uint64) (coreda.ResultRetrieve, error) {
81130
// Retrieve from both namespaces

0 commit comments

Comments
 (0)