Skip to content

Commit 4e84a41

Browse files
RafilxTenfenVvaradinovLazar955
authored
feat: resend batch msgs if some failed (#112)
* feat: send msg again, just removing the failed msg in batch by parsing the failed msg * chore: add #112 to cl * chore: add godoc to reliablySendMsgsResendingOnMsgErr * chore: address pr comment set regex compiled as vars * chore: add max retries to resend batch * fix: babylon retry send reliably msg * chore: remove withespace * test: add e2e test testing the retry logic * fix: add new param to test manager * fix: e2e test * fix: e2e test delegations retry * fix: test * test: 2 invalid delegations * revert e2e test. * lint fix * lint fix * adds e2e for sending batch * lint fix * fix e2e * lint fix --------- Co-authored-by: Vlad <[email protected]> Co-authored-by: Lazar <[email protected]>
1 parent 3e569b7 commit 4e84a41

File tree

8 files changed

+389
-23
lines changed

8 files changed

+389
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
4141

4242
* [#134](https://github.com/babylonlabs-io/covenant-emulator/pull/134) chore: add golangci lint rules
4343
* [#141](https://github.com/babylonlabs-io/covenant-emulator/pull/141) chore: bumps go1.25
44+
* [#112](https://github.com/babylonlabs-io/covenant-emulator/pull/112) feat: resend batch msgs if some failed
4445

4546
## v0.16.0-rc.1
4647

clientcontroller/babylon.go

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@ package clientcontroller
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"math"
8+
"regexp"
9+
"strconv"
10+
"strings"
711
"time"
812

13+
sdkErrors "cosmossdk.io/errors"
14+
915
sdkmath "cosmossdk.io/math"
1016
"github.com/btcsuite/btcd/btcec/v2"
1117
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
@@ -30,19 +36,23 @@ import (
3036
var (
3137
_ ClientController = &BabylonController{}
3238
MaxPaginationLimit = uint64(1000)
39+
messageIndexRegex = regexp.MustCompile(`message index:\s*(\d+)`)
3340
)
3441

3542
type BabylonController struct {
3643
bbnClient *bbnclient.Client
3744
cfg *config.BBNConfig
3845
btcParams *chaincfg.Params
3946
logger *zap.Logger
47+
48+
MaxRetiresBatchRemovingMsgs uint64
4049
}
4150

4251
func NewBabylonController(
4352
cfg *config.BBNConfig,
4453
btcParams *chaincfg.Params,
4554
logger *zap.Logger,
55+
maxRetiresBatchRemovingMsgs uint64,
4656
) (*BabylonController, error) {
4757
bbnConfig := config.BBNConfigToBabylonConfig(cfg)
4858

@@ -63,6 +73,7 @@ func NewBabylonController(
6373
cfg,
6474
btcParams,
6575
logger,
76+
maxRetiresBatchRemovingMsgs,
6677
}, nil
6778
}
6879

@@ -154,7 +165,7 @@ func (bc *BabylonController) reliablySendMsgs(msgs []sdk.Msg) (*babylonclient.Re
154165
return bc.bbnClient.ReliablySendMsgs(
155166
context.Background(),
156167
msgs,
157-
expectedErrors,
168+
nil,
158169
unrecoverableErrors,
159170
)
160171
}
@@ -182,16 +193,92 @@ func (bc *BabylonController) SubmitCovenantSigs(covSigs []*types.CovenantSigs) (
182193

183194
msgs = append(msgs, msg)
184195
}
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
188233
}
189234

190-
if res == nil {
235+
if err != nil && errorContained(err, expectedErrors) {
191236
return &types.TxResponse{}, nil
192237
}
193238

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
195282
}
196283

197284
func (bc *BabylonController) QueryPendingDelegations(limit uint64, filter FilterFn) ([]*types.Delegation, error) {
@@ -589,3 +676,13 @@ func (bc *BabylonController) QueryBtcLightClientTip() (*btclctypes.BTCHeaderInfo
589676

590677
return res.Header, nil
591678
}
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

Comments
 (0)