Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow retries for messages signed by relayer. #3402

56 changes: 55 additions & 1 deletion e2e/testsuite/testsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type E2ETestSuite struct {

grpcClients map[string]GRPCClients
paths map[string]path
relayers map[ibc.Wallet]bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while we do currently just run a single test in each run, we should try and make sure that if we change that we need to make as few changes as possible. With relayers being at the top level, this means that these relayer wallets would be shared across all tests. What might make sense to do is to create a mapping of testName -> relayer pair. Similar to how we do here.

This could even be a map[string]map[ibc.Wallet]bool which isn't super pretty, but maybe we could work with an alias of some sort here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did go with an alias here and basically just added a couple of functions on it. Lmk what you think.

logger *zap.Logger
DockerClient *dockerclient.Client
network string
Expand Down Expand Up @@ -117,6 +118,12 @@ func (s *E2ETestSuite) GetRelayerUsers(ctx context.Context, chainOpts ...testcon
chainARelayerUser := cosmos.NewWallet(ChainARelayerName, chainAAccountBytes, "", chainA.Config())
chainBRelayerUser := cosmos.NewWallet(ChainBRelayerName, chainBAccountBytes, "", chainB.Config())

if s.relayers == nil {
s.relayers = make(map[ibc.Wallet]bool)
}
s.relayers[chainARelayerUser] = true
s.relayers[chainBRelayerUser] = true

return chainARelayerUser, chainBRelayerUser
}

Expand Down Expand Up @@ -281,7 +288,17 @@ func (s *E2ETestSuite) BroadcastMessages(ctx context.Context, chain *cosmos.Cosm
return factory.WithGas(DefaultGasValue)
})

resp, err := cosmos.BroadcastTx(ctx, broadcaster, user, msgs...)
// Retry the operation a few times if the user signing the transaction is a relayer. (See issue #3264)
var resp sdk.TxResponse
var err error
broadcastFunc := func() (sdk.TxResponse, error) {
return cosmos.BroadcastTx(ctx, broadcaster, user, msgs...)
}
if s.IsRelayer(user) {
resp, err = s.retryNtimes(broadcastFunc, 5)
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
} else {
resp, err = broadcastFunc()
}
if err != nil {
return sdk.TxResponse{}, err
}
Expand Down Expand Up @@ -611,6 +628,43 @@ func (s *E2ETestSuite) QueryGranterGrants(ctx context.Context, chain *cosmos.Cos
return grants.Grants, nil
}

// IsRelayer returns true if the provided wallet is used by a relayer.
func (s *E2ETestSuite) IsRelayer(user ibc.Wallet) bool {
if s.relayers == nil {
return false
}
return s.relayers[user]
}

// retryNtimes retries the provided function up to the provided number of attempts.
func (s *E2ETestSuite) retryNtimes(f func() (sdk.TxResponse, error), attempts int) (sdk.TxResponse, error) {
// Ignore account sequence mismatch errors.
allowedMessages := []string{"account sequence mismatch"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a configuration parameter, maybe we can move it to the top together with the const? We can make it an unexpected var.

DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
var err error
var resp sdk.TxResponse
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
for i := 0; i < attempts; i++ {
// If the response's raw log doesn't contain any of the allowed prefixes we return, else, we retry.
resp, err = f()
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
if !containsMessage(resp.RawLog, allowedMessages) {
return resp, err
}
s.T().Logf("retrying tx due to ignored raw log: %s", resp.RawLog)
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
}
return resp, err
}

// containsPrefix returns true if the string s contains any of the prefixes in the slice.
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
func containsMessage(s string, messages []string) bool {
var hasPrefix bool
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
for _, message := range messages {
if strings.Contains(s, message) {
hasPrefix = true
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
break
}
}
return hasPrefix
}

// GetIBCToken returns the denomination of the full token denom sent to the receiving channel
func GetIBCToken(fullTokenDenom string, portID, channelID string) transfertypes.DenomTrace {
return transfertypes.ParseDenomTrace(fmt.Sprintf("%s/%s/%s", portID, channelID, fullTokenDenom))
Expand Down