github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/cmd/tendermint/commands/light.go (about) 1 package commands 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 "os" 8 "os/signal" 9 "path/filepath" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/spf13/cobra" 15 dbm "github.com/tendermint/tm-db" 16 17 "github.com/ari-anchor/sei-tendermint/config" 18 "github.com/ari-anchor/sei-tendermint/libs/log" 19 tmmath "github.com/ari-anchor/sei-tendermint/libs/math" 20 "github.com/ari-anchor/sei-tendermint/light" 21 lproxy "github.com/ari-anchor/sei-tendermint/light/proxy" 22 lrpc "github.com/ari-anchor/sei-tendermint/light/rpc" 23 dbs "github.com/ari-anchor/sei-tendermint/light/store/db" 24 rpcserver "github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/server" 25 ) 26 27 // LightCmd constructs the base command called when invoked without any subcommands. 28 func MakeLightCommand(conf *config.Config, logger log.Logger) *cobra.Command { 29 var ( 30 listenAddr string 31 primaryAddr string 32 witnessAddrsJoined string 33 chainID string 34 dir string 35 maxOpenConnections int 36 37 sequential bool 38 trustingPeriod time.Duration 39 trustedHeight int64 40 trustedHash []byte 41 trustLevelStr string 42 43 logLevel string 44 logFormat string 45 46 primaryKey = []byte("primary") 47 witnessesKey = []byte("witnesses") 48 ) 49 50 checkForExistingProviders := func(db dbm.DB) (string, []string, error) { 51 primaryBytes, err := db.Get(primaryKey) 52 if err != nil { 53 return "", []string{""}, err 54 } 55 witnessesBytes, err := db.Get(witnessesKey) 56 if err != nil { 57 return "", []string{""}, err 58 } 59 witnessesAddrs := strings.Split(string(witnessesBytes), ",") 60 return string(primaryBytes), witnessesAddrs, nil 61 } 62 63 saveProviders := func(db dbm.DB, primaryAddr, witnessesAddrs string) error { 64 err := db.Set(primaryKey, []byte(primaryAddr)) 65 if err != nil { 66 return fmt.Errorf("failed to save primary provider: %w", err) 67 } 68 err = db.Set(witnessesKey, []byte(witnessesAddrs)) 69 if err != nil { 70 return fmt.Errorf("failed to save witness providers: %w", err) 71 } 72 return nil 73 } 74 75 cmd := &cobra.Command{ 76 Use: "light [chainID]", 77 Short: "Run a light client proxy server, verifying Tendermint rpc", 78 Long: `Run a light client proxy server, verifying Tendermint rpc. 79 80 All calls that can be tracked back to a block header by a proof 81 will be verified before passing them back to the caller. Other than 82 that, it will present the same interface as a full Tendermint node. 83 84 Furthermore to the chainID, a fresh instance of a light client will 85 need a primary RPC address and a trusted hash and height. It is also highly 86 recommended to provide additional witness RPC addresses, especially if 87 not using sequential verification. 88 89 To restart the node, thereafter only the chainID is required. 90 91 When /abci_query is called, the Merkle key path format is: 92 93 /{store name}/{key} 94 95 Please verify with your application that this Merkle key format is used (true 96 for applications built w/ Cosmos SDK). 97 `, 98 RunE: func(cmd *cobra.Command, args []string) error { 99 chainID = args[0] 100 logger.Info("Creating client...", "chainID", chainID) 101 102 var witnessesAddrs []string 103 if witnessAddrsJoined != "" { 104 witnessesAddrs = strings.Split(witnessAddrsJoined, ",") 105 } 106 107 lightDB, err := dbm.NewGoLevelDB("light-client-db", dir) 108 if err != nil { 109 return fmt.Errorf("can't create a db: %w", err) 110 } 111 // create a prefixed db on the chainID 112 db := dbm.NewPrefixDB(lightDB, []byte(chainID)) 113 114 if primaryAddr == "" { // check to see if we can start from an existing state 115 var err error 116 primaryAddr, witnessesAddrs, err = checkForExistingProviders(db) 117 if err != nil { 118 return fmt.Errorf("failed to retrieve primary or witness from db: %w", err) 119 } 120 if primaryAddr == "" { 121 return errors.New("no primary address was provided nor found. Please provide a primary (using -p)." + 122 " Run the command: tendermint light --help for more information") 123 } 124 } else { 125 err := saveProviders(db, primaryAddr, witnessAddrsJoined) 126 if err != nil { 127 logger.Error("Unable to save primary and or witness addresses", "err", err) 128 } 129 } 130 131 if len(witnessesAddrs) < 1 && !sequential { 132 logger.Info("In skipping verification mode it is highly recommended to provide at least one witness") 133 } 134 135 trustLevel, err := tmmath.ParseFraction(trustLevelStr) 136 if err != nil { 137 return fmt.Errorf("can't parse trust level: %w", err) 138 } 139 140 options := []light.Option{light.Logger(logger)} 141 142 vo := light.SkippingVerification(trustLevel) 143 if sequential { 144 vo = light.SequentialVerification() 145 } 146 options = append(options, vo) 147 148 // Initiate the light client. If the trusted store already has blocks in it, this 149 // will be used else we use the trusted options. 150 c, err := light.NewHTTPClient( 151 cmd.Context(), 152 chainID, 153 light.TrustOptions{ 154 Period: trustingPeriod, 155 Height: trustedHeight, 156 Hash: trustedHash, 157 }, 158 primaryAddr, 159 witnessesAddrs, 160 dbs.New(db), 161 options..., 162 ) 163 if err != nil { 164 return err 165 } 166 167 cfg := rpcserver.DefaultConfig() 168 cfg.MaxBodyBytes = conf.RPC.MaxBodyBytes 169 cfg.MaxHeaderBytes = conf.RPC.MaxHeaderBytes 170 cfg.MaxOpenConnections = maxOpenConnections 171 // If necessary adjust global WriteTimeout to ensure it's greater than 172 // TimeoutBroadcastTxCommit. 173 // See https://github.com/ari-anchor/sei-tendermint/issues/3435 174 // Note we don't need to adjust anything if the timeout is already unlimited. 175 if cfg.WriteTimeout > 0 && cfg.WriteTimeout <= conf.RPC.TimeoutBroadcastTxCommit { 176 cfg.WriteTimeout = conf.RPC.TimeoutBroadcastTxCommit + 1*time.Second 177 } 178 179 p, err := lproxy.NewProxy(c, listenAddr, primaryAddr, cfg, logger, lrpc.KeyPathFn(lrpc.DefaultMerkleKeyPathFn())) 180 if err != nil { 181 return err 182 } 183 184 ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM) 185 defer cancel() 186 187 go func() { 188 <-ctx.Done() 189 p.Listener.Close() 190 }() 191 192 logger.Info("Starting proxy...", "laddr", listenAddr) 193 if err := p.ListenAndServe(ctx); err != http.ErrServerClosed { 194 // Error starting or closing listener: 195 logger.Error("proxy ListenAndServe", "err", err) 196 } 197 198 return nil 199 }, 200 Args: cobra.ExactArgs(1), 201 Example: `light cosmoshub-3 -p http://52.57.29.196:26657 -w http://public-seed-node.cosmoshub.certus.one:26657 202 --height 962118 --hash 28B97BE9F6DE51AC69F70E0B7BFD7E5C9CD1A595B7DC31AFF27C50D4948020CD`, 203 } 204 205 cmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888", 206 "serve the proxy on the given address") 207 cmd.Flags().StringVarP(&primaryAddr, "primary", "p", "", 208 "connect to a Tendermint node at this address") 209 cmd.Flags().StringVarP(&witnessAddrsJoined, "witnesses", "w", "", 210 "tendermint nodes to cross-check the primary node, comma-separated") 211 cmd.Flags().StringVarP(&dir, "dir", "d", os.ExpandEnv(filepath.Join("$HOME", ".tendermint-light")), 212 "specify the directory") 213 cmd.Flags().IntVar( 214 &maxOpenConnections, 215 "max-open-connections", 216 900, 217 "maximum number of simultaneous connections (including WebSocket).") 218 cmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour, 219 "trusting period that headers can be verified within. Should be significantly less than the unbonding period") 220 cmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height") 221 cmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash") 222 cmd.Flags().StringVar(&logLevel, "log-level", log.LogLevelInfo, "The logging level (debug|info|warn|error|fatal)") 223 cmd.Flags().StringVar(&logFormat, "log-format", log.LogFormatPlain, "The logging format (text|json)") 224 cmd.Flags().StringVar(&trustLevelStr, "trust-level", "1/3", 225 "trust level. Must be between 1/3 and 3/3", 226 ) 227 cmd.Flags().BoolVar(&sequential, "sequential", false, 228 "sequential verification. Verify all headers sequentially as opposed to using skipping verification", 229 ) 230 231 return cmd 232 233 }