package relayer import ( "context" "errors" "fmt" "testing" "github.com/cosmos/interchaintest/v10" "github.com/cosmos/interchaintest/v10/ibc" "github.com/cosmos/interchaintest/v10/relayer" "github.com/cosmos/interchaintest/v10/relayer/hermes" mobycli "github.com/moby/moby/client" "github.com/pelletier/go-toml" "go.uber.org/zap" ) const ( Rly = "rly" Hermes = "hermes" HermesRelayerRepository = "ghcr.io/informalsystems/hermes" hermesRelayerUser = "2000:2000" RlyRelayerRepository = "ghcr.io/cosmos/relayer" rlyRelayerUser = "100:1000" // relativeHermesConfigFilePath is the path to the hermes config file relative to the home directory within the container. relativeHermesConfigFilePath = ".hermes/config.toml" ) // Config holds configuration values for the relayer used in the tests. type Config struct { // Tag is the tag used for the relayer image. Tag string `yaml:"tag"` // ID specifies the type of relayer that this is. ID string `yaml:"id"` // Image is the image that should be used for the relayer. Image string `yaml:"image"` } // New returns an implementation of ibc.Relayer depending on the provided RelayerType. func New(t *testing.T, cfg Config, logger *zap.Logger, dockerClient *mobycli.Client, network string) ibc.Relayer { t.Helper() switch cfg.ID { case Rly: return newCosmosRelayer(t, cfg.Tag, logger, dockerClient, network, cfg.Image) case Hermes: return newHermesRelayer(t, cfg.Tag, logger, dockerClient, network, cfg.Image) default: panic(fmt.Errorf("unknown relayer specified: %s", cfg.ID)) } } // ApplyPacketFilter applies a packet filter to the hermes config file, which specifies a complete set of channels // to watch for packets. func ApplyPacketFilter(ctx context.Context, t *testing.T, r ibc.Relayer, chainID string, channels []ibc.ChannelOutput) error { t.Helper() h, ok := r.(*hermes.Relayer) if !ok { t.Logf("relayer %T does not support packet filtering, or it has not been implemented yet.", r) return nil } return modifyHermesConfigFile(ctx, h, func(config map[string]any) error { chains, ok := config["chains"].([]map[string]any) if !ok { return errors.New("failed to get chains from hermes config") } var chain map[string]any for _, c := range chains { if c["id"] == chainID { chain = c break } } if chain == nil { return fmt.Errorf("failed to find chain with id %s", chainID) } var channelEndpoints [][]string for _, c := range channels { channelEndpoints = append(channelEndpoints, []string{c.PortID, c.ChannelID}) } // [chains.packet_filter] // # policy = 'allow' // # list = [ // # ['ica*', '*'], // # ['transfer', 'channel-0'], // # ] // TODO(chatton): explicitly enable watching of ICA channels // this will ensure the ICA tests pass, but this will need to be modified to make sure // ICA tests will succeed in parallel. channelEndpoints = append(channelEndpoints, []string{"ica*", "*"}) // we explicitly override the full list, this allows this function to provide a complete set of channels to watch. chain["packet_filter"] = map[string]any{ "policy": "allow", "list": channelEndpoints, } return nil }) } // modifyHermesConfigFile reads the hermes config file, applies a modification function and returns an error if any. func modifyHermesConfigFile(ctx context.Context, h *hermes.Relayer, modificationFn func(map[string]any) error) error { bz, err := h.ReadFileFromHomeDir(ctx, relativeHermesConfigFilePath) if err != nil { return fmt.Errorf("failed to read hermes config file: %w", err) } var config map[string]any if err := toml.Unmarshal(bz, &config); err != nil { return errors.New("failed to unmarshal hermes config bytes") } if modificationFn != nil { if err := modificationFn(config); err != nil { return fmt.Errorf("failed to modify hermes config: %w", err) } } bz, err = toml.Marshal(config) if err != nil { return errors.New("failed to marshal hermes config bytes") } return h.WriteFileToHomeDir(ctx, relativeHermesConfigFilePath, bz) } // newCosmosRelayer returns an instance of the go relayer. // Options are used to allow for relayer version selection and specifying the default processing option. func newCosmosRelayer(t *testing.T, tag string, logger *zap.Logger, dockerClient *mobycli.Client, network, relayerImage string) ibc.Relayer { t.Helper() customImageOption := relayer.CustomDockerImage(relayerImage, tag, rlyRelayerUser) relayerProcessingOption := relayer.StartupFlags("-p", "events") // relayer processes via events relayerFactory := interchaintest.NewBuiltinRelayerFactory(ibc.CosmosRly, logger, customImageOption, relayerProcessingOption) return relayerFactory.Build( t, dockerClient, network, ) } // newHermesRelayer returns an instance of the hermes relayer. func newHermesRelayer(t *testing.T, tag string, logger *zap.Logger, dockerClient *mobycli.Client, network, relayerImage string) ibc.Relayer { t.Helper() customImageOption := relayer.CustomDockerImage(relayerImage, tag, hermesRelayerUser) relayerFactory := interchaintest.NewBuiltinRelayerFactory(ibc.Hermes, logger, customImageOption) return relayerFactory.Build( t, dockerClient, network, ) } // Map is a mapping from test names to a relayer set for that test. type Map map[string]map[ibc.Wallet]bool // AddRelayer adds the given relayer to the relayer set for the given test name. func (r Map) AddRelayer(testName string, ibcrelayer ibc.Wallet) { if _, ok := r[testName]; !ok { r[testName] = make(map[ibc.Wallet]bool) } r[testName][ibcrelayer] = true } // ContainsRelayer returns true if the given relayer is in the relayer set for the given test name. func (r Map) ContainsRelayer(testName string, wallet ibc.Wallet) bool { if relayerSet, ok := r[testName]; ok { return relayerSet[wallet] } return false }