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

19 changes: 19 additions & 0 deletions e2e/relayer/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,22 @@ func newCosmosRelayer(t *testing.T, tag string, logger *zap.Logger, dockerClient
func newHermesRelayer() ibc.Relayer {
panic("hermes relayer not yet implemented for interchaintest")
}

// RelayerMap is a mapping from test names to a relayer set for that test.
type RelayerMap map[string]map[ibc.Wallet]bool

// AddRelayer adds the given relayer to the relayer set for the given test name.
func (r RelayerMap) AddRelayer(testName string, relayer ibc.Wallet) {
if _, ok := r[testName]; !ok {
r[testName] = make(map[ibc.Wallet]bool)
}
r[testName][relayer] = true
}

// containsRelayer returns true if the given relayer is in the relayer set for the given test name.
func (r RelayerMap) ContainsRelayer(testName string, wallet ibc.Wallet) bool {
if relayerSet, ok := r[testName]; ok {
return relayerSet[wallet]
}
return false
}
50 changes: 49 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 relayer.RelayerMap
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(relayer.RelayerMap)
}
s.relayers.AddRelayer(s.T().Name(), chainARelayerUser)
s.relayers.AddRelayer(s.T().Name(), chainBRelayerUser)

return chainARelayerUser, chainBRelayerUser
}

Expand Down Expand Up @@ -281,7 +288,18 @@ 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.relayers.ContainsRelayer(s.T().Name(), user) {
// Retry five times, the value of 5 chosen is arbitrary.
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 +629,36 @@ func (s *E2ETestSuite) QueryGranterGrants(ctx context.Context, chain *cosmos.Cos
return grants.Grants, nil
}

// 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.
retryMessages := []string{"account sequence mismatch"}
var resp sdk.TxResponse
var err error
// If the response's raw log doesn't contain any of the allowed prefixes we return, else, we retry.
for i := 0; i < attempts; i++ {
resp, err = f()
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return sdk.TxResponse{}, err
}
if !containsMessage(resp.RawLog, retryMessages) {
return resp, err
}
s.T().Logf("retrying tx due to non deterministic failure: %+v", resp)
}
return resp, err
}

// containsMessages returns true if the string s contains any of the messages in the slice.
func containsMessage(s string, messages []string) bool {
for _, message := range messages {
if strings.Contains(s, message) {
return true
}
}
return false
}

// 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