@@ -3,6 +3,7 @@ package executing
33import (
44 "context"
55 crand "crypto/rand"
6+ "errors"
67 "testing"
78 "time"
89
@@ -207,3 +208,114 @@ func TestPendingLimit_SkipsProduction(t *testing.T) {
207208 require .NoError (t , err )
208209 assert .Equal (t , h1 , h2 , "height should not change when production is skipped" )
209210}
211+
212+ func TestExecutor_executeTxsWithRetry (t * testing.T ) {
213+ t .Parallel ()
214+
215+ tests := []struct {
216+ name string
217+ setupMock func (* testmocks.MockExecutor )
218+ expectSuccess bool
219+ expectHash []byte
220+ expectError string
221+ }{
222+ {
223+ name : "success on first attempt" ,
224+ setupMock : func (exec * testmocks.MockExecutor ) {
225+ exec .On ("ExecuteTxs" , mock .Anything , mock .Anything , uint64 (100 ), mock .Anything , mock .Anything ).
226+ Return ([]byte ("new-hash" ), uint64 (0 ), nil ).Once ()
227+ },
228+ expectSuccess : true ,
229+ expectHash : []byte ("new-hash" ),
230+ },
231+ {
232+ name : "success on second attempt" ,
233+ setupMock : func (exec * testmocks.MockExecutor ) {
234+ exec .On ("ExecuteTxs" , mock .Anything , mock .Anything , uint64 (100 ), mock .Anything , mock .Anything ).
235+ Return ([]byte (nil ), uint64 (0 ), errors .New ("temporary failure" )).Once ()
236+ exec .On ("ExecuteTxs" , mock .Anything , mock .Anything , uint64 (100 ), mock .Anything , mock .Anything ).
237+ Return ([]byte ("new-hash" ), uint64 (0 ), nil ).Once ()
238+ },
239+ expectSuccess : true ,
240+ expectHash : []byte ("new-hash" ),
241+ },
242+ {
243+ name : "success on third attempt" ,
244+ setupMock : func (exec * testmocks.MockExecutor ) {
245+ exec .On ("ExecuteTxs" , mock .Anything , mock .Anything , uint64 (100 ), mock .Anything , mock .Anything ).
246+ Return ([]byte (nil ), uint64 (0 ), errors .New ("temporary failure" )).Times (2 )
247+ exec .On ("ExecuteTxs" , mock .Anything , mock .Anything , uint64 (100 ), mock .Anything , mock .Anything ).
248+ Return ([]byte ("new-hash" ), uint64 (0 ), nil ).Once ()
249+ },
250+ expectSuccess : true ,
251+ expectHash : []byte ("new-hash" ),
252+ },
253+ {
254+ name : "failure after max retries" ,
255+ setupMock : func (exec * testmocks.MockExecutor ) {
256+ exec .On ("ExecuteTxs" , mock .Anything , mock .Anything , uint64 (100 ), mock .Anything , mock .Anything ).
257+ Return ([]byte (nil ), uint64 (0 ), errors .New ("persistent failure" )).Times (common .MaxRetriesBeforeHalt )
258+ },
259+ expectSuccess : false ,
260+ expectError : "failed to execute transactions" ,
261+ },
262+ {
263+ name : "context cancelled during retry" ,
264+ setupMock : func (exec * testmocks.MockExecutor ) {
265+ exec .On ("ExecuteTxs" , mock .Anything , mock .Anything , uint64 (100 ), mock .Anything , mock .Anything ).
266+ Return ([]byte (nil ), uint64 (0 ), errors .New ("temporary failure" )).Once ()
267+ },
268+ expectSuccess : false ,
269+ expectError : "context cancelled during retry" ,
270+ },
271+ }
272+
273+ for _ , tt := range tests {
274+ t .Run (tt .name , func (t * testing.T ) {
275+ t .Parallel ()
276+
277+ ctx := context .Background ()
278+ execCtx := ctx
279+
280+ // For context cancellation test, create a cancellable context
281+ if tt .name == "context cancelled during retry" {
282+ var cancel context.CancelFunc
283+ execCtx , cancel = context .WithCancel (ctx )
284+ // Cancel context after first failure to simulate cancellation during retry
285+ go func () {
286+ time .Sleep (100 * time .Millisecond )
287+ cancel ()
288+ }()
289+ }
290+
291+ mockExec := testmocks .NewMockExecutor (t )
292+ tt .setupMock (mockExec )
293+
294+ e := & Executor {
295+ exec : mockExec ,
296+ ctx : execCtx ,
297+ logger : zerolog .Nop (),
298+ }
299+
300+ rawTxs := [][]byte {[]byte ("tx1" ), []byte ("tx2" )}
301+ header := types.Header {
302+ BaseHeader : types.BaseHeader {Height : 100 , Time : uint64 (time .Now ().UnixNano ())},
303+ }
304+ currentState := types.State {AppHash : []byte ("current-hash" )}
305+
306+ result , err := e .executeTxsWithRetry (ctx , rawTxs , header , currentState )
307+
308+ if tt .expectSuccess {
309+ require .NoError (t , err )
310+ assert .Equal (t , tt .expectHash , result )
311+ } else {
312+ require .Error (t , err )
313+ if tt .expectError != "" {
314+ assert .Contains (t , err .Error (), tt .expectError )
315+ }
316+ }
317+
318+ mockExec .AssertExpectations (t )
319+ })
320+ }
321+ }
0 commit comments