├── ssh_config ├── .gitignore ├── Dockerfile.local ├── main.go ├── Dockerfile ├── config.yaml ├── cmd ├── run.go ├── modules.go ├── config.go ├── root.go └── keys.go ├── pkg ├── config │ └── config.go └── runner │ └── run.go └── go.mod /ssh_config: -------------------------------------------------------------------------------- 1 | Host github.com 2 | HostName github.com 3 | IdentityFile /src/app/test 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## deploy keys (temporary) 2 | test 3 | test.pub 4 | 5 | ## binary 6 | interchain-queries 7 | icq 8 | 9 | ## misc 10 | .DS_Store -------------------------------------------------------------------------------- /Dockerfile.local: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | 3 | COPY ./interchain-queries /bin/interchain-queries 4 | 5 | RUN adduser --system --home /icq --disabled-password --disabled-login icq -U 1000 6 | USER icq 7 | 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package main 6 | 7 | import "github.com/Stride-Labs/interchain-queries/cmd" 8 | 9 | func main() { 10 | cmd.Execute() 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM golang:1.17-bullseye 2 | 3 | # RUN apt update && apt install git 4 | # WORKDIR /src/app 5 | # COPY test test 6 | # COPY ssh_config /root/.ssh/config 7 | # ENV GIT_SSH_COMMAND="ssh -i /src/app/test -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" 8 | # RUN chmod 0600 test 9 | # RUN git config --global url."git@github.com:".insteadOf "https://github.com/" 10 | # RUN go env -w GOPRIVATE=github.com/ingenuity-build/* 11 | # COPY go.mod go.mod 12 | # COPY go.sum go.sum 13 | 14 | # RUN go mod download 15 | 16 | # COPY . . 17 | 18 | # RUN go build 19 | 20 | # RUN ln -s /src/app/interchain-queries /usr/local/bin 21 | # RUN adduser --system --home /icq --disabled-password --disabled-login icq -U 1000 22 | # USER icq 23 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | default_chain: stride-testnet 2 | chains: 3 | gaia-testnet: 4 | key: wallet 5 | chain-id: GAIA 6 | rpc-addr: http://127.0.0.1:23657 7 | 8 | grpc-addr: http://127.0.0.1:23090 9 | 10 | account-prefix: cosmos 11 | keyring-backend: test 12 | gas-adjustment: 1.2 13 | gas-prices: 0.001uatom 14 | key-directory: /root/.icq/keys 15 | debug: false 16 | timeout: 20s 17 | block-timeout: "" 18 | output-format: json 19 | sign-mode: direct 20 | stride-testnet: 21 | key: wallet 22 | chain-id: STRIDE-TESTNET-4 23 | rpc-addr: http://127.0.0.1:16659 24 | 25 | grpc-addr: http://127.0.0.1:16090 26 | account-prefix: stride 27 | keyring-backend: test 28 | gas-adjustment: 1.2 29 | gas-prices: 0.001ustrd 30 | key-directory: /root/.icq/keys 31 | debug: false 32 | timeout: 20s 33 | block-timeout: "" 34 | output-format: json 35 | sign-mode: direct 36 | cl: {} 37 | -------------------------------------------------------------------------------- /cmd/run.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "github.com/Stride-Labs/interchain-queries/pkg/runner" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // runCmd represents the run command 13 | var runCmd = &cobra.Command{ 14 | Use: "run", 15 | Short: "A brief description of your command", 16 | Long: `A longer description that spans multiple lines and likely contains examples 17 | and usage of using your command. For example: 18 | 19 | Cobra is a CLI library for Go that empowers applications. 20 | This application is a tool to generate the needed files 21 | to quickly create a Cobra application.`, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | runner.Run(cfg, cmd.Flag("home").Value.String()) 24 | }, 25 | } 26 | 27 | func init() { 28 | rootCmd.AddCommand(runCmd) 29 | // Here you will define your flags and configuration settings. 30 | 31 | // Cobra supports Persistent Flags which will work for this command 32 | // and all subcommands, e.g.: 33 | // runCmd.PersistentFlags().String("foo", "", "A help for foo") 34 | 35 | // Cobra supports local flags which will only run when this command 36 | // is called directly, e.g.: 37 | // runCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 38 | } 39 | -------------------------------------------------------------------------------- /cmd/modules.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | interchainquery "github.com/Stride-Labs/stride/x/interchainquery" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | "github.com/cosmos/cosmos-sdk/x/auth" 7 | authz "github.com/cosmos/cosmos-sdk/x/authz/module" 8 | "github.com/cosmos/cosmos-sdk/x/bank" 9 | "github.com/cosmos/cosmos-sdk/x/capability" 10 | "github.com/cosmos/cosmos-sdk/x/crisis" 11 | "github.com/cosmos/cosmos-sdk/x/distribution" 12 | distrclient "github.com/cosmos/cosmos-sdk/x/distribution/client" 13 | feegrant "github.com/cosmos/cosmos-sdk/x/feegrant/module" 14 | "github.com/cosmos/cosmos-sdk/x/gov" 15 | "github.com/cosmos/cosmos-sdk/x/mint" 16 | "github.com/cosmos/cosmos-sdk/x/params" 17 | paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" 18 | "github.com/cosmos/cosmos-sdk/x/slashing" 19 | "github.com/cosmos/cosmos-sdk/x/staking" 20 | "github.com/cosmos/cosmos-sdk/x/upgrade" 21 | upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" 22 | "github.com/cosmos/ibc-go/v3/modules/apps/transfer" 23 | ibc "github.com/cosmos/ibc-go/v3/modules/core" 24 | ) 25 | 26 | // TODO: Import a bunch of custom modules like cosmwasm and osmosis 27 | // Problem is SDK versioning. Need to find a fix for this. 28 | 29 | var ModuleBasics = []module.AppModuleBasic{ 30 | auth.AppModuleBasic{}, 31 | authz.AppModuleBasic{}, 32 | bank.AppModuleBasic{}, 33 | capability.AppModuleBasic{}, 34 | // TODO: add osmosis governance proposal types here 35 | // TODO: add other proposal types here 36 | gov.NewAppModuleBasic( 37 | paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, 38 | ), 39 | crisis.AppModuleBasic{}, 40 | distribution.AppModuleBasic{}, 41 | feegrant.AppModuleBasic{}, 42 | mint.AppModuleBasic{}, 43 | params.AppModuleBasic{}, 44 | slashing.AppModuleBasic{}, 45 | staking.AppModuleBasic{}, 46 | upgrade.AppModuleBasic{}, 47 | transfer.AppModuleBasic{}, 48 | ibc.AppModuleBasic{}, 49 | interchainquery.AppModuleBasic{}, 50 | } 51 | -------------------------------------------------------------------------------- /cmd/config.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | 9 | "github.com/Stride-Labs/interchain-queries/pkg/config" 10 | "github.com/cosmos/cosmos-sdk/client/flags" 11 | "github.com/cosmos/cosmos-sdk/types/module" 12 | "github.com/spf13/cobra" 13 | "github.com/spf13/viper" 14 | "github.com/strangelove-ventures/lens/client" 15 | "gopkg.in/yaml.v2" 16 | ) 17 | 18 | // initConfig reads in config file and ENV variables if set. 19 | func initConfig(cmd *cobra.Command) error { 20 | home, err := cmd.PersistentFlags().GetString(flags.FlagHome) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | debug, err := cmd.Flags().GetBool("debug") 26 | if err != nil { 27 | return err 28 | } 29 | 30 | localMode, err := cmd.Flags().GetBool("local") 31 | if err != nil { 32 | return err 33 | } 34 | 35 | cfg = &config.Config{} 36 | cfgPath := path.Join(home, "config.yaml") 37 | _, err = os.Stat(cfgPath) 38 | if err != nil { 39 | err = config.CreateConfig(home, debug, localMode) 40 | if err != nil { 41 | return err 42 | } 43 | } 44 | viper.SetConfigFile(cfgPath) 45 | err = viper.ReadInConfig() 46 | if err != nil { 47 | fmt.Println("Failed to read in config:", err) 48 | os.Exit(1) 49 | } 50 | 51 | // read the config file bytes 52 | file, err := ioutil.ReadFile(viper.ConfigFileUsed()) 53 | if err != nil { 54 | fmt.Println("Error reading file:", err) 55 | os.Exit(1) 56 | } 57 | 58 | // unmarshall them into the struct 59 | if err = yaml.Unmarshal(file, cfg); err != nil { 60 | fmt.Println("Error unmarshalling config:", err) 61 | os.Exit(1) 62 | } 63 | 64 | // instantiate chain client 65 | // TODO: this is a bit of a hack, we should probably have a 66 | // better way to inject modules into the client 67 | cfg.Cl = make(map[string]*client.ChainClient) 68 | for name, chain := range cfg.Chains { 69 | chain.Modules = append([]module.AppModuleBasic{}, ModuleBasics...) 70 | cl, err := client.NewChainClient(nil, chain, home, os.Stdin, os.Stdout) 71 | if err != nil { 72 | fmt.Println("Error creating chain client:", err) 73 | os.Exit(1) 74 | } 75 | cfg.Cl[name] = cl 76 | } 77 | 78 | // override chain if needed 79 | if cmd.PersistentFlags().Changed("chain") { 80 | defaultChain, err := cmd.PersistentFlags().GetString("chain") 81 | if err != nil { 82 | return err 83 | } 84 | 85 | cfg.DefaultChain = defaultChain 86 | } 87 | 88 | if cmd.PersistentFlags().Changed("output") { 89 | output, err := cmd.PersistentFlags().GetString("output") 90 | if err != nil { 91 | return err 92 | } 93 | 94 | // Should output be a global configuration item? 95 | for chain, _ := range cfg.Chains { 96 | cfg.Chains[chain].OutputFormat = output 97 | } 98 | } 99 | 100 | // validate configuration 101 | if err = config.ValidateConfig(cfg); err != nil { 102 | fmt.Println("Error parsing chain config:", err) 103 | os.Exit(1) 104 | } 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/Stride-Labs/interchain-queries/pkg/config" 7 | "github.com/cosmos/cosmos-sdk/client/flags" 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | var ( 13 | homePath string 14 | overridenChain string 15 | localMode bool 16 | debug bool 17 | cfg *config.Config 18 | defaultHome = os.ExpandEnv("$HOME/.icq") 19 | appName = "lens" 20 | ) 21 | 22 | // rootCmd represents the base command when called without any subcommands 23 | var rootCmd = &cobra.Command{ 24 | Use: "interchain-queries", 25 | Short: "A brief description of your application", 26 | Long: `A longer description that spans multiple lines and likely contains 27 | examples and usage of using your application. For example: 28 | 29 | Cobra is a CLI library for Go that empowers applications. 30 | This application is a tool to generate the needed files 31 | to quickly create a Cobra application.`, 32 | // Uncomment the following line if your bare application 33 | // has an action associated with it: 34 | // Run: func(cmd *cobra.Command, args []string) { }, 35 | } 36 | 37 | // Execute adds all child commands to the root command and sets flags appropriately. 38 | // This is called by main.main(). It only needs to happen once to the rootCmd. 39 | func Execute() { 40 | cobra.EnableCommandSorting = false 41 | 42 | rootCmd.SilenceUsage = true 43 | rootCmd.CompletionOptions.DisableDefaultCmd = true 44 | err := rootCmd.Execute() 45 | if err != nil { 46 | os.Exit(1) 47 | } 48 | } 49 | 50 | func init() { 51 | // Here you will define your flags and configuration settings. 52 | // Cobra supports persistent flags, which, if defined here, 53 | // will be global for your application. 54 | 55 | // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.claim-and-delegate.yaml)") 56 | 57 | // Cobra also supports local flags, which will only run 58 | // when this action is called directly. 59 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 60 | rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error { 61 | // reads `homeDir/config.yaml` into `var config *Config` before each command 62 | if err := initConfig(rootCmd); err != nil { 63 | return err 64 | } 65 | return nil 66 | } 67 | 68 | // --home flag 69 | rootCmd.PersistentFlags().StringVar(&homePath, flags.FlagHome, defaultHome, "set home directory") 70 | if err := viper.BindPFlag(flags.FlagHome, rootCmd.PersistentFlags().Lookup(flags.FlagHome)); err != nil { 71 | panic(err) 72 | } 73 | 74 | // --local flag 75 | rootCmd.PersistentFlags().BoolVarP(&localMode, "local", "l", false, "local mode") 76 | if err := viper.BindPFlag("local", rootCmd.PersistentFlags().Lookup("local")); err != nil { 77 | panic(err) 78 | } 79 | 80 | // --debug flag 81 | rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "debug output") 82 | if err := viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug")); err != nil { 83 | panic(err) 84 | } 85 | 86 | rootCmd.PersistentFlags().StringP("output", "o", "json", "output format (json, indent, yaml)") 87 | if err := viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output")); err != nil { 88 | panic(err) 89 | } 90 | 91 | rootCmd.PersistentFlags().StringVar(&overridenChain, "chain", "", "override default chain") 92 | if err := viper.BindPFlag("chain", rootCmd.PersistentFlags().Lookup("chain")); err != nil { 93 | panic(err) 94 | } 95 | 96 | rootCmd.AddCommand(keysCmd()) 97 | } 98 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path" 8 | 9 | "github.com/spf13/viper" 10 | "github.com/strangelove-ventures/lens/client" 11 | "gopkg.in/yaml.v2" 12 | ) 13 | 14 | //createConfig idempotently creates the config. 15 | func CreateConfig(home string, debug bool, localMode bool) error { 16 | cfgPath := path.Join(home, "config.yaml") 17 | 18 | // If the config doesn't exist... 19 | if _, err := os.Stat(cfgPath); os.IsNotExist(err) { 20 | // And the config folder doesn't exist... 21 | // And the home folder doesn't exist 22 | if _, err := os.Stat(home); os.IsNotExist(err) { 23 | // Create the home folder 24 | if err = os.Mkdir(home, os.ModePerm); err != nil { 25 | return err 26 | } 27 | } 28 | } 29 | 30 | // Then create the file... 31 | f, err := os.Create(cfgPath) 32 | if err != nil { 33 | return err 34 | } 35 | defer f.Close() 36 | 37 | // And write the default config to that location... 38 | if _, err = f.Write(defaultConfig(path.Join(home, "keys"), debug, localMode)); err != nil { 39 | return err 40 | } 41 | return nil 42 | } 43 | 44 | func OverwriteConfig(cfg *Config) error { 45 | home := viper.GetString("home") 46 | cfgPath := path.Join(home, "config.yaml") 47 | f, err := os.Create(cfgPath) 48 | if err != nil { 49 | return err 50 | } 51 | defer f.Close() 52 | if _, err := f.Write(cfg.MustYAML()); err != nil { 53 | return err 54 | } 55 | 56 | log.Printf("updated lens configuration at %s", cfgPath) 57 | return nil 58 | } 59 | 60 | // Config represents the config file for the relayer 61 | type Config struct { 62 | DefaultChain string `yaml:"default_chain" json:"default_chain"` 63 | Chains map[string]*client.ChainClientConfig `yaml:"chains" json:"chains"` 64 | 65 | Cl map[string]*client.ChainClient 66 | } 67 | 68 | func (c *Config) GetDefaultClient() *client.ChainClient { 69 | return c.GetClient(c.DefaultChain) 70 | } 71 | 72 | func (c *Config) GetClient(chainID string) *client.ChainClient { 73 | if v, ok := c.Cl[chainID]; ok { 74 | return v 75 | } 76 | return nil 77 | } 78 | 79 | // Called to initialize the relayer.Chain types on Config 80 | func ValidateConfig(c *Config) error { 81 | for _, chain := range c.Chains { 82 | if err := chain.Validate(); err != nil { 83 | return err 84 | } 85 | } 86 | if c.GetDefaultClient() == nil { 87 | return fmt.Errorf("default chain (%s) configuration not found", c.DefaultChain) 88 | } 89 | return nil 90 | } 91 | 92 | // MustYAML returns the yaml string representation of the Paths 93 | func (c Config) MustYAML() []byte { 94 | out, err := yaml.Marshal(c) 95 | if err != nil { 96 | panic(err) 97 | } 98 | return out 99 | } 100 | 101 | func defaultConfig(keyHome string, debug bool, localMode bool) []byte { 102 | var defaultChain string 103 | var chains map[string]*client.ChainClientConfig 104 | 105 | if localMode { 106 | defaultChain = "stride-local" 107 | chains = map[string]*client.ChainClientConfig{ 108 | "stride-local": client.GetStrideLocalConfig(keyHome, debug), 109 | "gaia-local": client.GetGaiaLocalConfig(keyHome, debug), 110 | "juno-local": client.GetJunoLocalConfig(keyHome, debug), 111 | "osmo-local": client.GetOsmoLocalConfig(keyHome, debug), 112 | } 113 | } else { 114 | defaultChain = "stride-testnet" 115 | chains = map[string]*client.ChainClientConfig{ 116 | "stride-testnet": client.GetStrideTestnetConfig(keyHome, debug), 117 | "gaia-testnet": client.GetGaiaTestnetConfig(keyHome, debug), 118 | } 119 | } 120 | 121 | return Config{ 122 | DefaultChain: defaultChain, 123 | Chains: chains, 124 | }.MustYAML() 125 | } 126 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Stride-Labs/interchain-queries 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/Stride-Labs/stride v0.0.6 7 | github.com/cosmos/cosmos-sdk v0.45.4 8 | github.com/cosmos/ibc-go/v3 v3.0.0 9 | github.com/spf13/cobra v1.4.0 10 | github.com/spf13/viper v1.11.0 11 | github.com/strangelove-ventures/lens v0.4.0 12 | github.com/tendermint/tendermint v0.34.19 13 | google.golang.org/grpc v1.46.2 14 | gopkg.in/yaml.v2 v2.4.0 15 | ) 16 | 17 | require ( 18 | filippo.io/edwards25519 v1.0.0-beta.2 // indirect 19 | github.com/99designs/keyring v1.1.6 // indirect 20 | github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect 21 | github.com/DataDog/zstd v1.4.8 // indirect 22 | github.com/armon/go-metrics v0.3.10 // indirect 23 | github.com/avast/retry-go/v4 v4.0.4 // indirect 24 | github.com/beorn7/perks v1.0.1 // indirect 25 | github.com/bgentry/speakeasy v0.1.0 // indirect 26 | github.com/btcsuite/btcd v0.22.0-beta // indirect 27 | github.com/cespare/xxhash v1.1.0 // indirect 28 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 29 | github.com/confio/ics23/go v0.7.0 // indirect 30 | github.com/cosmos/btcutil v1.0.4 // indirect 31 | github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect 32 | github.com/cosmos/go-bip39 v1.0.0 // indirect 33 | github.com/cosmos/gorocksdb v1.2.0 // indirect 34 | github.com/cosmos/iavl v0.17.3 // indirect 35 | github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect 36 | github.com/cosmos/ledger-go v0.9.2 // indirect 37 | github.com/danieljoos/wincred v1.0.2 // indirect 38 | github.com/davecgh/go-spew v1.1.1 // indirect 39 | github.com/dgraph-io/badger/v2 v2.2007.3 // indirect 40 | github.com/dgraph-io/ristretto v0.1.0 // indirect 41 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect 42 | github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect 43 | github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect 44 | github.com/fsnotify/fsnotify v1.5.1 // indirect 45 | github.com/go-kit/kit v0.12.0 // indirect 46 | github.com/go-kit/log v0.2.0 // indirect 47 | github.com/go-logfmt/logfmt v0.5.1 // indirect 48 | github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect 49 | github.com/gogo/protobuf v1.3.3 // indirect 50 | github.com/golang/glog v1.0.0 // indirect 51 | github.com/golang/protobuf v1.5.2 // indirect 52 | github.com/golang/snappy v0.0.4 // indirect 53 | github.com/google/btree v1.0.1 // indirect 54 | github.com/gorilla/mux v1.8.0 // indirect 55 | github.com/gorilla/websocket v1.5.0 // indirect 56 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect 57 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect 58 | github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect 59 | github.com/gtank/merlin v0.1.1 // indirect 60 | github.com/gtank/ristretto255 v0.1.2 // indirect 61 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 62 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect 63 | github.com/hashicorp/hcl v1.0.0 // indirect 64 | github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87 // indirect 65 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 66 | github.com/jmhodges/levigo v1.0.0 // indirect 67 | github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect 68 | github.com/libp2p/go-buffer-pool v0.0.2 // indirect 69 | github.com/magiconair/properties v1.8.6 // indirect 70 | github.com/mattn/go-isatty v0.0.14 // indirect 71 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 72 | github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect 73 | github.com/mitchellh/go-homedir v1.1.0 // indirect 74 | github.com/mitchellh/mapstructure v1.4.3 // indirect 75 | github.com/mtibben/percent v0.2.1 // indirect 76 | github.com/pelletier/go-toml v1.9.4 // indirect 77 | github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect 78 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect 79 | github.com/pkg/errors v0.9.1 // indirect 80 | github.com/pmezard/go-difflib v1.0.0 // indirect 81 | github.com/prometheus/client_golang v1.12.1 // indirect 82 | github.com/prometheus/client_model v0.2.0 // indirect 83 | github.com/prometheus/common v0.32.1 // indirect 84 | github.com/prometheus/procfs v0.7.3 // indirect 85 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 86 | github.com/regen-network/cosmos-proto v0.3.1 // indirect 87 | github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect 88 | github.com/spf13/afero v1.8.2 // indirect 89 | github.com/spf13/cast v1.4.1 // indirect 90 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 91 | github.com/spf13/pflag v1.0.5 // indirect 92 | github.com/stretchr/testify v1.7.1 // indirect 93 | github.com/subosito/gotenv v1.2.0 // indirect 94 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect 95 | github.com/tendermint/btcd v0.1.1 // indirect 96 | github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect 97 | github.com/tendermint/go-amino v0.16.0 // indirect 98 | github.com/tendermint/tm-db v0.6.7 // indirect 99 | github.com/zondax/hid v0.9.0 // indirect 100 | go.etcd.io/bbolt v1.3.6 // indirect 101 | go.uber.org/atomic v1.9.0 // indirect 102 | go.uber.org/multierr v1.8.0 // indirect 103 | go.uber.org/zap v1.21.0 // indirect 104 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect 105 | golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect 106 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect 107 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 108 | golang.org/x/text v0.3.7 // indirect 109 | google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect 110 | google.golang.org/protobuf v1.28.0 // indirect 111 | gopkg.in/ini.v1 v1.66.4 // indirect 112 | gopkg.in/yaml.v3 v3.0.1 // indirect 113 | ) 114 | 115 | replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 116 | 117 | replace github.com/strangelove-ventures/lens => github.com/Stride-Labs/lens v0.0.24 118 | -------------------------------------------------------------------------------- /cmd/keys.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "os" 9 | "sort" 10 | "strings" 11 | 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | "github.com/spf13/cobra" 14 | "github.com/spf13/viper" 15 | ) 16 | 17 | const ( 18 | flagCoinType = "coin-type" 19 | defaultCoinType uint32 = sdk.CoinType 20 | ) 21 | 22 | var ( 23 | // FlagAccountPrefix allows the user to override the prefix for a given account 24 | FlagAccountPrefix = "" 25 | ) 26 | 27 | // keysCmd represents the keys command 28 | func keysCmd() *cobra.Command { 29 | cmd := &cobra.Command{ 30 | Use: "keys", 31 | Aliases: []string{"k"}, 32 | Short: "manage keys held by the relayer for each chain", 33 | } 34 | 35 | cmd.AddCommand(keysAddCmd()) 36 | cmd.AddCommand(keysRestoreCmd()) 37 | cmd.AddCommand(keysDeleteCmd()) 38 | cmd.AddCommand(keysListCmd()) 39 | cmd.AddCommand(keysShowCmd()) 40 | cmd.AddCommand(keysEnumerateCmd()) 41 | cmd.AddCommand(keysExportCmd()) 42 | 43 | return cmd 44 | } 45 | 46 | // keysAddCmd respresents the `keys add` command 47 | func keysAddCmd() *cobra.Command { 48 | cmd := &cobra.Command{ 49 | Use: "add [name]", 50 | Aliases: []string{"a"}, 51 | Short: "adds a key to the keychain associated with a particular chain", 52 | Long: "if no name is passed, 'default' is used", 53 | Args: cobra.RangeArgs(0, 1), 54 | Example: strings.TrimSpace(fmt.Sprintf(` 55 | $ %s keys add 56 | $ %s keys add test_key 57 | $ %s k a osmo_key --chain osmosis`, appName, appName, appName)), 58 | RunE: func(cmd *cobra.Command, args []string) error { 59 | cl := cfg.GetDefaultClient() 60 | var keyName string 61 | if len(args) == 0 { 62 | keyName = cl.Config.Key 63 | } else { 64 | keyName = args[0] 65 | } 66 | if cl.KeyExists(keyName) { 67 | return errKeyExists(keyName) 68 | } 69 | 70 | ko, err := cl.AddKey(keyName, 118) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | out, err := json.Marshal(&ko) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | fmt.Println(string(out)) 81 | return nil 82 | }, 83 | } 84 | // TODO: wire this up 85 | cmd.Flags().Uint32(flagCoinType, defaultCoinType, "coin type number for HD derivation") 86 | 87 | return cmd 88 | } 89 | 90 | func keysRestoreCmd() *cobra.Command { 91 | cmd := &cobra.Command{ 92 | Use: "restore [name]", 93 | Aliases: []string{"r"}, 94 | Short: "restores a mnemonic to the keychain associated with a particular chain", 95 | Args: cobra.ExactArgs(1), 96 | Example: strings.TrimSpace(fmt.Sprintf(` 97 | $ %s keys restore --chain ibc-0 testkey 98 | $ %s k r --chain ibc-1 faucet-key`, appName, appName)), 99 | RunE: func(cmd *cobra.Command, args []string) error { 100 | cl := cfg.GetDefaultClient() 101 | keyName := args[0] 102 | if cl.KeyExists(keyName) { 103 | return errKeyExists(keyName) 104 | } 105 | 106 | fmt.Print("Enter mnemonic 🔑: ") 107 | reader := bufio.NewReader(os.Stdin) 108 | mnemonic, _ := reader.ReadString('\n') 109 | mnemonic = strings.Replace(mnemonic, "\n", "", -1) 110 | fmt.Println() 111 | 112 | address, err := cl.RestoreKey(keyName, string(mnemonic), 118) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | fmt.Println(address) 118 | return nil 119 | }, 120 | } 121 | // TODO: wire this up 122 | cmd.Flags().Uint32(flagCoinType, defaultCoinType, "coin type number for HD derivation") 123 | return cmd 124 | } 125 | 126 | // keysDeleteCmd respresents the `keys delete` command 127 | func keysDeleteCmd() *cobra.Command { 128 | cmd := &cobra.Command{ 129 | Use: "delete [name]", 130 | Aliases: []string{"d"}, 131 | Short: "deletes a key from the keychain associated with a particular chain", 132 | Args: cobra.ExactArgs(1), 133 | Example: strings.TrimSpace(fmt.Sprintf(` 134 | $ %s keys delete ibc-0 -y 135 | $ %s keys delete ibc-1 key2 -y 136 | $ %s k d ibc-2 testkey`, appName, appName, appName)), 137 | RunE: func(cmd *cobra.Command, args []string) error { 138 | cl := cfg.GetDefaultClient() 139 | chainName := cl.Config.ChainID 140 | keyName := args[0] 141 | if !cl.KeyExists(keyName) { 142 | return errKeyDoesntExist(keyName) 143 | } 144 | 145 | if skip, _ := cmd.Flags().GetBool("skip"); !skip { 146 | fmt.Printf("Are you sure you want to delete key(%s) from chain(%s)? (Y/n)\n", keyName, chainName) 147 | if !askForConfirmation() { 148 | return nil 149 | } 150 | } 151 | 152 | if err := cl.DeleteKey(keyName); err != nil { 153 | panic(err) 154 | } 155 | 156 | fmt.Printf("key %s deleted\n", keyName) 157 | return nil 158 | }, 159 | } 160 | 161 | return skipConfirm(cmd) 162 | } 163 | 164 | func askForConfirmation() bool { 165 | var response string 166 | 167 | _, err := fmt.Scanln(&response) 168 | if err != nil { 169 | log.Fatal(err) 170 | } 171 | 172 | switch strings.ToLower(response) { 173 | case "y", "yes": 174 | return true 175 | case "n", "no": 176 | return false 177 | default: 178 | fmt.Println("please type (y)es or (n)o and then press enter") 179 | return askForConfirmation() 180 | } 181 | } 182 | 183 | // keysListCmd respresents the `keys list` command 184 | func keysListCmd() *cobra.Command { 185 | cmd := &cobra.Command{ 186 | Use: "list", 187 | Aliases: []string{"l"}, 188 | Short: "lists keys from the keychain associated with a particular chain", 189 | Args: cobra.NoArgs, 190 | Example: strings.TrimSpace(fmt.Sprintf(` 191 | $ %s keys list ibc-0 192 | $ %s k l ibc-1`, appName, appName)), 193 | RunE: func(cmd *cobra.Command, args []string) error { 194 | cl := cfg.GetDefaultClient() 195 | info, err := cl.ListAddresses() 196 | if err != nil { 197 | return err 198 | } 199 | 200 | for key, val := range info { 201 | fmt.Printf("key(%s) -> %s\n", key, val) 202 | } 203 | 204 | return nil 205 | }, 206 | } 207 | 208 | return cmd 209 | } 210 | 211 | // keysShowCmd respresents the `keys show` command 212 | func keysShowCmd() *cobra.Command { 213 | cmd := &cobra.Command{ 214 | Use: "show [name]", 215 | Aliases: []string{"s"}, 216 | Short: "shows a key from the keychain associated with a particular chain", 217 | Long: "if no name is passed, name in config is used", 218 | Args: cobra.RangeArgs(0, 1), 219 | Example: strings.TrimSpace(fmt.Sprintf(` 220 | $ %s keys show ibc-0 221 | $ %s keys show ibc-1 key2 222 | $ %s k s ibc-2 testkey`, appName, appName, appName)), 223 | RunE: func(cmd *cobra.Command, args []string) error { 224 | cl := cfg.GetDefaultClient() 225 | var keyName string 226 | if len(args) == 0 { 227 | keyName = cl.Config.Key 228 | } else { 229 | keyName = args[0] 230 | } 231 | if !cl.KeyExists(keyName) { 232 | return errKeyDoesntExist(keyName) 233 | } 234 | 235 | if FlagAccountPrefix != "" { 236 | cl.Config.AccountPrefix = FlagAccountPrefix 237 | } 238 | 239 | address, err := cl.ShowAddress(keyName) 240 | if err != nil { 241 | return err 242 | } 243 | 244 | fmt.Println(address) 245 | return nil 246 | }, 247 | } 248 | 249 | cmd.Flags().StringVar(&FlagAccountPrefix, "prefix", "", "Encode the key with the user specified prefix") 250 | 251 | return cmd 252 | } 253 | 254 | type KeyEnumeration struct { 255 | KeyName string `json:"key_name"` 256 | Addresses map[string]string `json:"addresses"` 257 | } 258 | 259 | // keysEnumerateCmd respresents the `keys enumerate` command 260 | func keysEnumerateCmd() *cobra.Command { 261 | cmd := &cobra.Command{ 262 | Use: "enumerate [name]", 263 | Aliases: []string{"e"}, 264 | Short: "enumerates the address for a given key across all configured chains", 265 | Long: "if no name is passed, name in config is used", 266 | Args: cobra.RangeArgs(0, 1), 267 | Example: strings.TrimSpace(fmt.Sprintf(` 268 | $ %s keys enumerate 269 | $ %s keys enumerate key2 270 | $ %s k e key2`, appName, appName, appName)), 271 | RunE: func(cmd *cobra.Command, args []string) error { 272 | cl := cfg.GetDefaultClient() 273 | var keyName string 274 | if len(args) == 0 { 275 | keyName = cl.Config.Key 276 | } else { 277 | keyName = args[0] 278 | } 279 | account, err := cl.AccountFromKeyOrAddress(keyName) 280 | if err != nil { 281 | return err 282 | } 283 | 284 | var chains []string 285 | for chain := range cfg.Chains { 286 | chains = append(chains, chain) 287 | } 288 | sort.Strings(chains) 289 | 290 | addresses := make(map[string]string) 291 | for _, chain := range chains { 292 | client := cfg.GetClient(chain) 293 | address, err := client.EncodeBech32AccAddr(account) 294 | if err != nil { 295 | return err 296 | } 297 | addresses[chain] = address 298 | } 299 | 300 | return cl.PrintObject(addresses) 301 | }, 302 | } 303 | 304 | // cmd.Flags().StringVar(&FlagAccountPrefix, "prefix", "", "Encode the key with the user specified prefix") 305 | 306 | return cmd 307 | } 308 | 309 | // keysExportCmd respresents the `keys export` command 310 | func keysExportCmd() *cobra.Command { 311 | cmd := &cobra.Command{ 312 | Use: "export [name]", 313 | Aliases: []string{"e"}, 314 | Short: "exports a privkey from the keychain associated with a particular chain", 315 | Args: cobra.ExactArgs(1), 316 | Example: strings.TrimSpace(fmt.Sprintf(` 317 | $ %s keys export ibc-0 testkey 318 | $ %s k e ibc-2 testkey`, appName, appName)), 319 | RunE: func(cmd *cobra.Command, args []string) error { 320 | cl := cfg.GetDefaultClient() 321 | keyName := args[1] 322 | if !cl.KeyExists(keyName) { 323 | return errKeyDoesntExist(keyName) 324 | } 325 | 326 | info, err := cl.ExportPrivKeyArmor(keyName) 327 | if err != nil { 328 | return err 329 | } 330 | 331 | fmt.Println(info) 332 | return nil 333 | }, 334 | } 335 | 336 | return cmd 337 | } 338 | 339 | func errKeyExists(name string) error { 340 | return fmt.Errorf("a key with name %s already exists", name) 341 | } 342 | 343 | func errKeyDoesntExist(name string) error { 344 | return fmt.Errorf("a key with name %s doesn't exist", name) 345 | } 346 | 347 | func skipConfirm(cmd *cobra.Command) *cobra.Command { 348 | cmd.Flags().BoolP("skip", "y", false, "output using yaml") 349 | viper.BindPFlag("skip", cmd.Flags().Lookup("skip")) 350 | return cmd 351 | } 352 | -------------------------------------------------------------------------------- /pkg/runner/run.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/hex" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/Stride-Labs/interchain-queries/pkg/config" 15 | qstypes "github.com/Stride-Labs/stride/x/interchainquery/types" 16 | sdk "github.com/cosmos/cosmos-sdk/types" 17 | lensclient "github.com/strangelove-ventures/lens/client" 18 | lensquery "github.com/strangelove-ventures/lens/client/query" 19 | abcitypes "github.com/tendermint/tendermint/abci/types" 20 | tmquery "github.com/tendermint/tendermint/libs/pubsub/query" 21 | coretypes "github.com/tendermint/tendermint/rpc/core/types" 22 | "google.golang.org/grpc/metadata" 23 | 24 | clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" 25 | tmclient "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" 26 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 27 | tmtypes "github.com/tendermint/tendermint/types" 28 | ) 29 | 30 | type Clients []*lensclient.ChainClient 31 | 32 | var ( 33 | WaitInterval = time.Second * 3 34 | clients = Clients{} 35 | ctx = context.Background() 36 | sendQueue = map[string]chan sdk.Msg{} 37 | ) 38 | 39 | func (clients Clients) GetForChainId(chainId string) *lensclient.ChainClient { 40 | for _, client := range clients { 41 | if client.Config.ChainID == chainId { 42 | return client 43 | } 44 | } 45 | return nil 46 | } 47 | 48 | func Run(cfg *config.Config, home string) error { 49 | defer Close() 50 | for _, c := range cfg.Chains { 51 | client, err := lensclient.NewChainClient(nil, c, home, os.Stdin, os.Stdout) 52 | if err != nil { 53 | return err 54 | } 55 | sendQueue[client.Config.ChainID] = make(chan sdk.Msg) 56 | clients = append(clients, client) 57 | } 58 | 59 | query := tmquery.MustParse(fmt.Sprintf("message.module='%s'", "interchainquery")) 60 | 61 | wg := &sync.WaitGroup{} 62 | 63 | for _, client := range clients { 64 | err := client.RPCClient.Start() 65 | if err != nil { 66 | fmt.Println(err) 67 | } 68 | 69 | ch, err := client.RPCClient.Subscribe(ctx, client.Config.ChainID+"-icq", query.String()) 70 | if err != nil { 71 | fmt.Println(err) 72 | return err 73 | } 74 | 75 | wg.Add(1) 76 | go func(chainId string, ch <-chan coretypes.ResultEvent) { 77 | for v := range ch { 78 | v.Events["source"] = []string{chainId} 79 | go handleEvent(v) 80 | } 81 | }(client.Config.ChainID, ch) 82 | } 83 | 84 | for _, client := range clients { 85 | wg.Add(1) 86 | go FlushSendQueue(client.Config.ChainID) 87 | } 88 | 89 | wg.Wait() 90 | return nil 91 | } 92 | 93 | type Query struct { 94 | SourceChainId string 95 | ConnectionId string 96 | ChainId string 97 | QueryId string 98 | Type string 99 | Height int64 100 | Request []byte 101 | } 102 | 103 | func handleEvent(event coretypes.ResultEvent) { 104 | queries := []Query{} 105 | source := event.Events["source"] 106 | connections := event.Events["message.connection_id"] 107 | chains := event.Events["message.chain_id"] 108 | queryIds := event.Events["message.query_id"] 109 | types := event.Events["message.type"] 110 | request := event.Events["message.request"] 111 | height := event.Events["message.height"] 112 | 113 | items := len(queryIds) 114 | 115 | for i := 0; i < items; i++ { 116 | req, err := hex.DecodeString(request[i]) 117 | if err != nil { 118 | panic(err) 119 | } 120 | h, err := strconv.ParseInt(height[i], 10, 64) 121 | if err != nil { 122 | panic(err) 123 | } 124 | queries = append(queries, Query{source[0], connections[i], chains[i], queryIds[i], types[i], h, req}) 125 | } 126 | 127 | for _, q := range queries { 128 | go doRequest(q) 129 | } 130 | } 131 | 132 | func RunGRPCQuery(ctx context.Context, client *lensclient.ChainClient, method string, reqBz []byte, md metadata.MD) (abcitypes.ResponseQuery, metadata.MD, error) { 133 | // parse height header 134 | height, err := lensclient.GetHeightFromMetadata(md) 135 | if err != nil { 136 | return abcitypes.ResponseQuery{}, nil, err 137 | } 138 | fmt.Println("height parsed from GetHeightFromMetadata=", height) 139 | 140 | prove, err := lensclient.GetProveFromMetadata(md) 141 | if err != nil { 142 | return abcitypes.ResponseQuery{}, nil, err 143 | } 144 | 145 | abciReq := abcitypes.RequestQuery{ 146 | Path: method, 147 | Data: reqBz, 148 | Height: height, 149 | Prove: prove, 150 | } 151 | 152 | abciRes, err := client.QueryABCI(ctx, abciReq) 153 | if err != nil { 154 | return abcitypes.ResponseQuery{}, nil, err 155 | } 156 | 157 | return abciRes, md, nil 158 | } 159 | 160 | func retryLightblock(ctx context.Context, client *lensclient.ChainClient, height int64, maxTime int) (*tmtypes.LightBlock, error) { 161 | interval := 1 162 | lightBlock, err := client.LightProvider.LightBlock(ctx, height) 163 | if err != nil { 164 | for { 165 | time.Sleep(time.Duration(interval) * time.Second) 166 | fmt.Println("Requerying lightblock") 167 | lightBlock, err = client.LightProvider.LightBlock(ctx, height) 168 | interval = interval + 1 169 | if err == nil { 170 | break 171 | } else if interval > maxTime { 172 | return nil, fmt.Errorf("unable to query light block, max interval exceeded") 173 | } 174 | } 175 | } 176 | return lightBlock, err 177 | } 178 | 179 | func doRequest(query Query) { 180 | client := clients.GetForChainId(query.ChainId) 181 | if client == nil { 182 | fmt.Println("No chain") 183 | return 184 | } 185 | fmt.Println(query.Type) 186 | newCtx := lensclient.SetHeightOnContext(ctx, query.Height) 187 | pathParts := strings.Split(query.Type, "/") 188 | if pathParts[len(pathParts)-1] == "key" { // fetch proof if the query is 'key' 189 | newCtx = lensclient.SetProveOnContext(newCtx, true) 190 | } 191 | inMd, ok := metadata.FromOutgoingContext(newCtx) 192 | if !ok { 193 | panic("failed on not ok") 194 | } 195 | 196 | res, _, err := RunGRPCQuery(ctx, client, "/"+query.Type, query.Request, inMd) 197 | if err != nil { 198 | panic(err) 199 | } 200 | // get latest client chain block height to use in LC update and proof 201 | // abciInfo, _ := client.RPCClient.ABCIInfo(ctx) 202 | // lastBlockHeight := abciInfo.Response.LastBlockHeight 203 | // fmt.Println("Latest block height on Gaia from ABCI: ", lastBlockHeight) 204 | 205 | // submit tx to queue 206 | submitClient := clients.GetForChainId(query.SourceChainId) 207 | from, _ := submitClient.GetKeyAddress() 208 | if pathParts[len(pathParts)-1] == "key" { 209 | 210 | submitQuerier := lensquery.Query{Client: submitClient, Options: lensquery.DefaultOptions()} 211 | connection, err := submitQuerier.Ibc_Connection(query.ConnectionId) 212 | if err != nil { 213 | fmt.Println("Error: Could not get connection from chain: ", err) 214 | panic("Error: Could not fetch updated LC from chain - bailing") 215 | } 216 | 217 | clientId := connection.Connection.ClientId 218 | state, err := submitQuerier.Ibc_ClientState(clientId) // pass in from request 219 | if err != nil { 220 | fmt.Println("Error: Could not get state from chain: ", err) 221 | panic("Could not get state from chain") 222 | } 223 | unpackedState, err := clienttypes.UnpackClientState(state.ClientState) 224 | if err != nil { 225 | fmt.Println("Error: Could not unpack state from chain: ", err) 226 | panic("Could not unpack state from chain") 227 | } 228 | 229 | trustedHeight := unpackedState.GetLatestHeight() 230 | clientHeight := trustedHeight.(clienttypes.Height) 231 | if !ok { 232 | fmt.Println("Error: Could not coerce trusted height") 233 | panic("Error: Could coerce trusted height") 234 | } 235 | 236 | consensus, err := submitQuerier.Ibc_ConsensusState(clientId, clientHeight) // pass in from request 237 | if err != nil { 238 | fmt.Println("Error: Could not get consensus state from chain: ", err) 239 | panic("Error: Could not get consensus state from chain: ") 240 | } 241 | unpackedConsensus, err := clienttypes.UnpackConsensusState(consensus.ConsensusState) 242 | if err != nil { 243 | fmt.Println("Error: Could not unpack consensus state from chain: ", err) 244 | panic("Error: Could not unpack consensus state from chain: ") 245 | } 246 | 247 | tmConsensus := unpackedConsensus.(*tmclient.ConsensusState) 248 | 249 | //------------------------------------------------------ 250 | 251 | // update client 252 | fmt.Println("Fetching client update for height", "height", res.Height+1) 253 | lightBlock, err := retryLightblock(ctx, client, res.Height+1, 5) 254 | if err != nil { 255 | fmt.Println("Error: Could not fetch updated LC from chain - bailing: ", err) // requeue 256 | return 257 | } 258 | valSet := tmtypes.NewValidatorSet(lightBlock.ValidatorSet.Validators) 259 | protoVal, err := valSet.ToProto() 260 | if err != nil { 261 | fmt.Println("Error: Could not get valset from chain: ", err) 262 | return 263 | } 264 | 265 | var trustedValset *tmproto.ValidatorSet 266 | if bytes.Equal(valSet.Hash(), tmConsensus.NextValidatorsHash) { 267 | trustedValset = protoVal 268 | } else { 269 | fmt.Println("Fetching client update for height", "height", res.Height+1) 270 | lightBlock2, err := retryLightblock(ctx, client, int64(res.Height), 5) 271 | if err != nil { 272 | fmt.Println("Error: Could not fetch updated LC2 from chain - bailing: ", err) // requeue 273 | panic("Error: Could not fetch updated LC2 from chain - bailing: ") 274 | } 275 | valSet := tmtypes.NewValidatorSet(lightBlock2.ValidatorSet.Validators) 276 | trustedValset, err = valSet.ToProto() 277 | if err != nil { 278 | fmt.Println("Error: Could not get valset2 from chain: ", err) 279 | panic("Error: Could not get valset2 from chain: ") 280 | } 281 | } 282 | 283 | header := &tmclient.Header{ 284 | SignedHeader: lightBlock.SignedHeader.ToProto(), 285 | ValidatorSet: protoVal, 286 | TrustedHeight: clientHeight, 287 | TrustedValidators: trustedValset, 288 | } 289 | 290 | anyHeader, err := clienttypes.PackHeader(header) 291 | if err != nil { 292 | fmt.Println("Error: Could not get pack header: ", err) 293 | panic("Error: Could not get pack header: ") 294 | } 295 | 296 | msg := &clienttypes.MsgUpdateClient{ 297 | ClientId: clientId, // needs to be passed in as part of request. 298 | Header: anyHeader, 299 | Signer: submitClient.MustEncodeAccAddr(from), 300 | } 301 | 302 | sendQueue[query.SourceChainId] <- msg 303 | 304 | } 305 | 306 | fmt.Println("ICQ RELAYER | query.Height=", query.Height) 307 | fmt.Println("ICQ RELAYER | res.Height=", res.Height) 308 | msg := &qstypes.MsgSubmitQueryResponse{ChainId: query.ChainId, QueryId: query.QueryId, Result: res.Value, ProofOps: res.ProofOps, Height: res.Height, FromAddress: submitClient.MustEncodeAccAddr(from)} 309 | sendQueue[query.SourceChainId] <- msg 310 | } 311 | 312 | func FlushSendQueue(chainId string) { 313 | time.Sleep(WaitInterval) 314 | toSend := []sdk.Msg{} 315 | ch := sendQueue[chainId] 316 | 317 | for { 318 | if len(toSend) > 12 { 319 | flush(chainId, toSend) 320 | toSend = []sdk.Msg{} 321 | } 322 | select { 323 | case msg := <-ch: 324 | toSend = append(toSend, msg) 325 | case <-time.After(time.Millisecond * 800): 326 | flush(chainId, toSend) 327 | toSend = []sdk.Msg{} 328 | } 329 | } 330 | } 331 | 332 | func flush(chainId string, toSend []sdk.Msg) { 333 | if len(toSend) > 0 { 334 | fmt.Printf("Send batch of %d messages\n", len(toSend)) 335 | client := clients.GetForChainId(chainId) 336 | if client == nil { 337 | fmt.Println("No chain") 338 | return 339 | } 340 | // dedupe on queryId 341 | msgs := unique(toSend) 342 | resp, err := client.SendMsgs(context.Background(), msgs) 343 | if err != nil { 344 | if resp != nil && resp.Code == 19 && resp.Codespace == "sdk" { 345 | //if err.Error() == "transaction failed with code: 19" { 346 | fmt.Println("Tx in mempool") 347 | } else { 348 | panic(err) 349 | } 350 | } 351 | fmt.Printf("Sent batch of %d (deduplicated) messages\n", len(unique(toSend))) 352 | // zero messages 353 | 354 | } 355 | } 356 | 357 | func unique(msgSlice []sdk.Msg) []sdk.Msg { 358 | keys := make(map[string]bool) 359 | clientUpdateHeights := make(map[string]bool) 360 | 361 | list := []sdk.Msg{} 362 | for _, entry := range msgSlice { 363 | msg, ok := entry.(*clienttypes.MsgUpdateClient) 364 | if ok { 365 | header, _ := clienttypes.UnpackHeader(msg.Header) 366 | key := header.GetHeight().String() 367 | if _, value := clientUpdateHeights[key]; !value { 368 | clientUpdateHeights[key] = true 369 | list = append(list, entry) 370 | fmt.Println("1 ClientUpdate message") 371 | } 372 | continue 373 | } 374 | msg2, ok2 := entry.(*qstypes.MsgSubmitQueryResponse) 375 | if ok2 { 376 | if _, value := keys[msg2.QueryId]; !value { 377 | keys[msg2.QueryId] = true 378 | list = append(list, entry) 379 | fmt.Println("1 SubmitResponse message") 380 | } 381 | } 382 | } 383 | return list 384 | } 385 | 386 | func Close() error { 387 | 388 | query := tmquery.MustParse(fmt.Sprintf("message.module='%s'", "interchainquery")) 389 | 390 | for _, client := range clients { 391 | err := client.RPCClient.Unsubscribe(ctx, client.Config.ChainID+"-icq", query.String()) 392 | if err != nil { 393 | return err 394 | } 395 | } 396 | return nil 397 | } 398 | --------------------------------------------------------------------------------