@@ -4,11 +4,14 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "sync"
78 "time"
89
910 "github.com/rs/zerolog"
1011
12+ "github.com/evstack/ev-node/block/internal/common"
1113 "github.com/evstack/ev-node/pkg/config"
14+ blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc"
1215 datypes "github.com/evstack/ev-node/pkg/da/types"
1316 "github.com/evstack/ev-node/types"
1417)
@@ -23,6 +26,12 @@ type ForcedInclusionRetriever struct {
2326 daEpochSize uint64
2427 daStartHeight uint64
2528 asyncFetcher AsyncBlockRetriever
29+
30+ mu sync.Mutex
31+ pendingEpochStart uint64
32+ pendingEpochEnd uint64
33+ lastProcessedEpochEnd uint64
34+ hasProcessedEpoch bool
2635}
2736
2837// ForcedInclusionEvent contains forced inclusion transactions retrieved from DA.
@@ -68,6 +77,25 @@ func (r *ForcedInclusionRetriever) Stop() {
6877 r .asyncFetcher .Stop ()
6978}
7079
80+ // HandleSubscriptionResponse caches forced inclusion blobs from subscription updates.
81+ func (r * ForcedInclusionRetriever ) HandleSubscriptionResponse (resp * blobrpc.SubscriptionResponse ) {
82+ if resp == nil {
83+ return
84+ }
85+ if ! r .client .HasForcedInclusionNamespace () {
86+ return
87+ }
88+
89+ r .asyncFetcher .UpdateCurrentHeight (resp .Height )
90+
91+ blobs := common .BlobsFromSubscription (resp )
92+ if len (blobs ) == 0 {
93+ return
94+ }
95+
96+ r .asyncFetcher .StoreBlock (context .Background (), resp .Height , blobs , time .Now ().UTC ())
97+ }
98+
7199// RetrieveForcedIncludedTxs retrieves forced inclusion transactions at the given DA height.
72100// It respects epoch boundaries and only fetches at epoch end.
73101// It tries to get blocks from the async fetcher cache first, then falls back to sync fetching.
@@ -86,12 +114,50 @@ func (r *ForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Context
86114 // Update the async fetcher's current height so it knows what to prefetch
87115 r .asyncFetcher .UpdateCurrentHeight (daHeight )
88116
117+ r .mu .Lock ()
118+ pendingStart := r .pendingEpochStart
119+ pendingEnd := r .pendingEpochEnd
120+ lastProcessed := r .lastProcessedEpochEnd
121+ hasProcessed := r .hasProcessedEpoch
122+ r .mu .Unlock ()
123+
124+ if pendingEnd != 0 {
125+ if daHeight < pendingEnd {
126+ return & ForcedInclusionEvent {
127+ StartDaHeight : daHeight ,
128+ EndDaHeight : daHeight ,
129+ Txs : [][]byte {},
130+ }, nil
131+ }
132+
133+ event , err := r .retrieveEpoch (ctx , pendingStart , pendingEnd )
134+ if err != nil {
135+ return nil , err
136+ }
137+
138+ r .mu .Lock ()
139+ r .pendingEpochStart = 0
140+ r .pendingEpochEnd = 0
141+ r .lastProcessedEpochEnd = pendingEnd
142+ r .hasProcessedEpoch = true
143+ r .mu .Unlock ()
144+
145+ return event , nil
146+ }
147+
89148 if daHeight != epochEnd {
90149 r .logger .Debug ().
91150 Uint64 ("da_height" , daHeight ).
92151 Uint64 ("epoch_end" , epochEnd ).
93152 Msg ("not at epoch end - returning empty transactions" )
153+ return & ForcedInclusionEvent {
154+ StartDaHeight : daHeight ,
155+ EndDaHeight : daHeight ,
156+ Txs : [][]byte {},
157+ }, nil
158+ }
94159
160+ if hasProcessed && epochEnd <= lastProcessed {
95161 return & ForcedInclusionEvent {
96162 StartDaHeight : daHeight ,
97163 EndDaHeight : daHeight ,
@@ -106,6 +172,24 @@ func (r *ForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Context
106172 Uint64 ("epoch_num" , currentEpochNumber ).
107173 Msg ("retrieving forced included transactions from DA epoch" )
108174
175+ event , err := r .retrieveEpoch (ctx , epochStart , epochEnd )
176+ if err != nil {
177+ r .mu .Lock ()
178+ r .pendingEpochStart = epochStart
179+ r .pendingEpochEnd = epochEnd
180+ r .mu .Unlock ()
181+ return nil , err
182+ }
183+
184+ r .mu .Lock ()
185+ r .lastProcessedEpochEnd = epochEnd
186+ r .hasProcessedEpoch = true
187+ r .mu .Unlock ()
188+
189+ return event , nil
190+ }
191+
192+ func (r * ForcedInclusionRetriever ) retrieveEpoch (ctx context.Context , epochStart , epochEnd uint64 ) (* ForcedInclusionEvent , error ) {
109193 event := & ForcedInclusionEvent {
110194 StartDaHeight : epochStart ,
111195 EndDaHeight : epochEnd ,
@@ -211,18 +295,12 @@ func (r *ForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Context
211295 // any error during process, need to retry at next call
212296 if processErrs != nil {
213297 r .logger .Warn ().
214- Uint64 ("da_height" , daHeight ).
215298 Uint64 ("epoch_start" , epochStart ).
216299 Uint64 ("epoch_end" , epochEnd ).
217- Uint64 ("epoch_num" , currentEpochNumber ).
218300 Err (processErrs ).
219- Msg ("Failed to retrieve DA epoch.. retrying next iteration " )
301+ Msg ("failed to retrieve DA epoch" )
220302
221- return & ForcedInclusionEvent {
222- StartDaHeight : daHeight ,
223- EndDaHeight : daHeight ,
224- Txs : [][]byte {},
225- }, nil
303+ return nil , processErrs
226304 }
227305
228306 return event , nil
0 commit comments