@@ -2,10 +2,16 @@ package clientcontroller
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
6
7
"math"
8
+ "regexp"
9
+ "strconv"
10
+ "strings"
7
11
"time"
8
12
13
+ sdkErrors "cosmossdk.io/errors"
14
+
9
15
sdkmath "cosmossdk.io/math"
10
16
"github.com/btcsuite/btcd/btcec/v2"
11
17
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
@@ -30,19 +36,23 @@ import (
30
36
var (
31
37
_ ClientController = & BabylonController {}
32
38
MaxPaginationLimit = uint64 (1000 )
39
+ messageIndexRegex = regexp .MustCompile (`message index:\s*(\d+)` )
33
40
)
34
41
35
42
type BabylonController struct {
36
43
bbnClient * bbnclient.Client
37
44
cfg * config.BBNConfig
38
45
btcParams * chaincfg.Params
39
46
logger * zap.Logger
47
+
48
+ MaxRetiresBatchRemovingMsgs uint64
40
49
}
41
50
42
51
func NewBabylonController (
43
52
cfg * config.BBNConfig ,
44
53
btcParams * chaincfg.Params ,
45
54
logger * zap.Logger ,
55
+ maxRetiresBatchRemovingMsgs uint64 ,
46
56
) (* BabylonController , error ) {
47
57
bbnConfig := config .BBNConfigToBabylonConfig (cfg )
48
58
@@ -63,6 +73,7 @@ func NewBabylonController(
63
73
cfg ,
64
74
btcParams ,
65
75
logger ,
76
+ maxRetiresBatchRemovingMsgs ,
66
77
}, nil
67
78
}
68
79
@@ -154,7 +165,7 @@ func (bc *BabylonController) reliablySendMsgs(msgs []sdk.Msg) (*babylonclient.Re
154
165
return bc .bbnClient .ReliablySendMsgs (
155
166
context .Background (),
156
167
msgs ,
157
- expectedErrors ,
168
+ nil ,
158
169
unrecoverableErrors ,
159
170
)
160
171
}
@@ -182,16 +193,92 @@ func (bc *BabylonController) SubmitCovenantSigs(covSigs []*types.CovenantSigs) (
182
193
183
194
msgs = append (msgs , msg )
184
195
}
185
- res , err := bc .reliablySendMsgs (msgs )
186
- if err != nil {
187
- return nil , err
196
+
197
+ return bc .reliablySendMsgsResendingOnMsgErr (msgs )
198
+ }
199
+
200
+ // reliablySendMsgsResendingOnMsgErr sends the msgs to the chain, if some msg fails to execute
201
+ // and contains 'message index: %d', it will remove that msg from the batch and send again
202
+ // if there is no more message available, returns the last error.
203
+ func (bc * BabylonController ) reliablySendMsgsResendingOnMsgErr (msgs []sdk.Msg ) (* types.TxResponse , error ) {
204
+ var err error
205
+
206
+ maxRetries := BatchRetries (msgs , bc .MaxRetiresBatchRemovingMsgs )
207
+ for i := uint64 (0 ); i < maxRetries ; i ++ {
208
+ res , errSendMsg := bc .reliablySendMsgs (msgs )
209
+ if errSendMsg != nil {
210
+ // concatenate the errors, to throw out if needed
211
+ err = errors .Join (err , errSendMsg )
212
+
213
+ if strings .Contains (errSendMsg .Error (), "message index: " ) {
214
+ // remove the failed msg from the batch and send again
215
+ failedIndex , found := FailedMessageIndex (errSendMsg )
216
+ if ! found {
217
+ return nil , errSendMsg
218
+ }
219
+
220
+ msgs = RemoveMsgAtIndex (msgs , failedIndex )
221
+
222
+ continue
223
+ }
224
+
225
+ return nil , errSendMsg
226
+ }
227
+
228
+ if res == nil { // expected error happened
229
+ return & types.TxResponse {}, nil
230
+ }
231
+
232
+ return & types.TxResponse {TxHash : res .TxHash , Events : res .Events }, nil
188
233
}
189
234
190
- if res == nil {
235
+ if err != nil && errorContained ( err , expectedErrors ) {
191
236
return & types.TxResponse {}, nil
192
237
}
193
238
194
- return & types.TxResponse {TxHash : res .TxHash , Events : res .Events }, nil
239
+ return nil , fmt .Errorf ("failed to send batch of msgs: %w" , err )
240
+ }
241
+
242
+ // BatchRetries returns the max number of retries it should execute based on the
243
+ // amount of messages in the batch
244
+ func BatchRetries (msgs []sdk.Msg , maxRetiresBatchRemovingMsgs uint64 ) uint64 {
245
+ maxRetriesByMsgLen := uint64 (len (msgs ))
246
+
247
+ if maxRetiresBatchRemovingMsgs == 0 {
248
+ return maxRetriesByMsgLen
249
+ }
250
+
251
+ if maxRetiresBatchRemovingMsgs > maxRetriesByMsgLen {
252
+ return maxRetriesByMsgLen
253
+ }
254
+
255
+ return maxRetiresBatchRemovingMsgs
256
+ }
257
+
258
+ // RemoveMsgAtIndex removes any msg inside the slice, based on the index is given
259
+ // if the index is out of bounds, it just returns the slice of msgs.
260
+ func RemoveMsgAtIndex (msgs []sdk.Msg , index int ) []sdk.Msg {
261
+ if index < 0 || index >= len (msgs ) {
262
+ return msgs
263
+ }
264
+
265
+ return append (msgs [:index ], msgs [index + 1 :]... )
266
+ }
267
+
268
+ // FailedMessageIndex finds the message index which failed in a error which contains
269
+ // the substring 'message index: %d'.
270
+ // ex.: rpc error: code = Unknown desc = failed to execute message; message index: 1: the covenant signature is already submitted
271
+ func FailedMessageIndex (err error ) (int , bool ) {
272
+ matches := messageIndexRegex .FindStringSubmatch (err .Error ())
273
+
274
+ if len (matches ) > 1 {
275
+ index , errAtoi := strconv .Atoi (matches [1 ])
276
+ if errAtoi == nil {
277
+ return index , true
278
+ }
279
+ }
280
+
281
+ return 0 , false
195
282
}
196
283
197
284
func (bc * BabylonController ) QueryPendingDelegations (limit uint64 , filter FilterFn ) ([]* types.Delegation , error ) {
@@ -589,3 +676,13 @@ func (bc *BabylonController) QueryBtcLightClientTip() (*btclctypes.BTCHeaderInfo
589
676
590
677
return res .Header , nil
591
678
}
679
+
680
+ func errorContained (err error , errList []* sdkErrors.Error ) bool {
681
+ for _ , e := range errList {
682
+ if strings .Contains (err .Error (), e .Error ()) {
683
+ return true
684
+ }
685
+ }
686
+
687
+ return false
688
+ }
0 commit comments