44 "context"
55 "fmt"
66 "strings"
7+ "sync"
78
89 "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/auth"
910 "github.com/cosmos/cosmos-sdk/crypto/keyring"
@@ -19,6 +20,8 @@ type TxHelper struct {
1920 txmod Module
2021 config * TxConfig
2122
23+ mu sync.Mutex
24+
2225 accountNumber uint64
2326 nextSequence uint64
2427 seqInit bool
@@ -77,6 +80,9 @@ func (h *TxHelper) ExecuteTransaction(
7780 msgCreator func (creator string ) (types.Msg , error ),
7881) (* sdktx.BroadcastTxResponse , error ) {
7982
83+ h .mu .Lock ()
84+ defer h .mu .Unlock ()
85+
8086 // --- Step 1: Resolve creator address ---
8187 key , err := h .config .Keyring .Key (h .config .KeyName )
8288 if err != nil {
@@ -95,6 +101,9 @@ func (h *TxHelper) ExecuteTransaction(
95101 if err != nil {
96102 return nil , fmt .Errorf ("failed to fetch initial account info: %w" , err )
97103 }
104+ if accInfoRes == nil || accInfoRes .Info == nil {
105+ return nil , fmt .Errorf ("empty account info response for creator %s" , creator )
106+ }
98107
99108 h .accountNumber = accInfoRes .Info .AccountNumber
100109 h .nextSequence = accInfoRes .Info .Sequence
@@ -129,18 +138,27 @@ func (h *TxHelper) ExecuteTransaction(
129138
130139 // Check if this is a sequence mismatch error
131140 if ! isSequenceMismatch (err ) {
132- return nil , err // unrelated error → bail out
141+ return resp , err // unrelated error → bail out (preserve response for debugging)
133142 }
134143
135144 // If retry unavailable, bubble error
136145 if attempt == maxAttempts {
137- return nil , fmt .Errorf ("sequence mismatch after retry: %w" , err )
146+ return resp , fmt .Errorf ("sequence mismatch after retry: %w" , err )
147+ }
148+
149+ // --- Retry logic: prefer expected sequence from the error ---
150+ if expectedSeq , ok := parseExpectedSequence (err ); ok {
151+ h .nextSequence = expectedSeq
152+ continue
138153 }
139154
140- // --- Retry logic : resync from chain ---
155+ // Fallback : resync from chain state.
141156 accInfoRes , err2 := h .authmod .AccountInfoByAddress (ctx , creator )
142157 if err2 != nil {
143- return nil , fmt .Errorf ("failed to resync account info after mismatch: %w" , err2 )
158+ return resp , fmt .Errorf ("failed to resync account info after mismatch: %w" , err2 )
159+ }
160+ if accInfoRes == nil || accInfoRes .Info == nil {
161+ return resp , fmt .Errorf ("empty account info response for creator %s after mismatch" , creator )
144162 }
145163
146164 h .accountNumber = accInfoRes .Info .AccountNumber
@@ -155,12 +173,30 @@ func isSequenceMismatch(err error) bool {
155173 return false
156174 }
157175
158- msg := err .Error ()
176+ msg := strings . ToLower ( err .Error () )
159177
160178 return strings .Contains (msg , "incorrect account sequence" ) ||
161179 strings .Contains (msg , "account sequence mismatch" ) ||
162- (strings .Contains (msg , "expected" ) && strings .Contains (msg , "got" ))
180+ strings .Contains (msg , "wrong sequence" )
181+ }
163182
183+ func parseExpectedSequence (err error ) (uint64 , bool ) {
184+ if err == nil {
185+ return 0 , false
186+ }
187+
188+ msg := strings .ToLower (err .Error ())
189+ idx := strings .Index (msg , "expected " )
190+ if idx == - 1 {
191+ return 0 , false
192+ }
193+
194+ var expected , got uint64
195+ if _ , scanErr := fmt .Sscanf (msg [idx :], "expected %d, got %d" , & expected , & got ); scanErr == nil {
196+ return expected , true
197+ }
198+
199+ return 0 , false
164200}
165201
166202// ExecuteTransactionWithMsgs processes a transaction with pre-created messages and account info
@@ -199,6 +235,9 @@ func (h *TxHelper) GetAccountInfo(ctx context.Context) (*authtypes.BaseAccount,
199235}
200236
201237func (h * TxHelper ) UpdateConfig (config * TxHelperConfig ) {
238+ h .mu .Lock ()
239+ defer h .mu .Unlock ()
240+
202241 if h .config == nil {
203242 h .config = & TxConfig {}
204243 }
0 commit comments