github.com/516108736/tendermint@v0.36.0/cmd/tendermint/commands/light.go (about) 1 package commands 2 3 import ( 4 "bufio" 5 "context" 6 "errors" 7 "fmt" 8 "net/http" 9 "os" 10 "path/filepath" 11 "regexp" 12 "strings" 13 "time" 14 15 "github.com/spf13/cobra" 16 17 dbm "github.com/tendermint/tm-db" 18 19 "github.com/tendermint/tendermint/crypto/merkle" 20 "github.com/tendermint/tendermint/libs/log" 21 tmmath "github.com/tendermint/tendermint/libs/math" 22 tmos "github.com/tendermint/tendermint/libs/os" 23 "github.com/tendermint/tendermint/light" 24 lproxy "github.com/tendermint/tendermint/light/proxy" 25 lrpc "github.com/tendermint/tendermint/light/rpc" 26 dbs "github.com/tendermint/tendermint/light/store/db" 27 rpchttp "github.com/tendermint/tendermint/rpc/client/http" 28 rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" 29 ) 30 31 // LightCmd represents the base command when called without any subcommands 32 var LightCmd = &cobra.Command{ 33 Use: "light [chainID]", 34 Short: "Run a light client proxy server, verifying Tendermint rpc", 35 Long: `Run a light client proxy server, verifying Tendermint rpc. 36 37 All calls that can be tracked back to a block header by a proof 38 will be verified before passing them back to the caller. Other than 39 that, it will present the same interface as a full Tendermint node. 40 41 Furthermore to the chainID, a fresh instance of a light client will 42 need a primary RPC address, a trusted hash and height and witness RPC addresses 43 (if not using sequential verification). To restart the node, thereafter 44 only the chainID is required. 45 46 When /abci_query is called, the Merkle key path format is: 47 48 /{store name}/{key} 49 50 Please verify with your application that this Merkle key format is used (true 51 for applications built w/ Cosmos SDK). 52 `, 53 RunE: runProxy, 54 Args: cobra.ExactArgs(1), 55 Example: `light cosmoshub-3 -p http://52.57.29.196:26657 -w http://public-seed-node.cosmoshub.certus.one:26657 56 --height 962118 --hash 28B97BE9F6DE51AC69F70E0B7BFD7E5C9CD1A595B7DC31AFF27C50D4948020CD`, 57 } 58 59 var ( 60 listenAddr string 61 primaryAddr string 62 witnessAddrsJoined string 63 chainID string 64 home string 65 maxOpenConnections int 66 67 sequential bool 68 trustingPeriod time.Duration 69 trustedHeight int64 70 trustedHash []byte 71 trustLevelStr string 72 73 verbose bool 74 75 primaryKey = []byte("primary") 76 witnessesKey = []byte("witnesses") 77 ) 78 79 func init() { 80 LightCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888", 81 "serve the proxy on the given address") 82 LightCmd.Flags().StringVarP(&primaryAddr, "primary", "p", "", 83 "connect to a Tendermint node at this address") 84 LightCmd.Flags().StringVarP(&witnessAddrsJoined, "witnesses", "w", "", 85 "tendermint nodes to cross-check the primary node, comma-separated") 86 LightCmd.Flags().StringVar(&home, "home-dir", os.ExpandEnv(filepath.Join("$HOME", ".tendermint-light")), 87 "specify the home directory") 88 LightCmd.Flags().IntVar( 89 &maxOpenConnections, 90 "max-open-connections", 91 900, 92 "maximum number of simultaneous connections (including WebSocket).") 93 LightCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour, 94 "trusting period that headers can be verified within. Should be significantly less than the unbonding period") 95 LightCmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height") 96 LightCmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash") 97 LightCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output") 98 LightCmd.Flags().StringVar(&trustLevelStr, "trust-level", "1/3", 99 "trust level. Must be between 1/3 and 3/3", 100 ) 101 LightCmd.Flags().BoolVar(&sequential, "sequential", false, 102 "sequential verification. Verify all headers sequentially as opposed to using skipping verification", 103 ) 104 } 105 106 func runProxy(cmd *cobra.Command, args []string) error { 107 // Initialise logger. 108 logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) 109 var option log.Option 110 if verbose { 111 option, _ = log.AllowLevel("debug") 112 } else { 113 option, _ = log.AllowLevel("info") 114 } 115 logger = log.NewFilter(logger, option) 116 117 chainID = args[0] 118 logger.Info("Creating client...", "chainID", chainID) 119 120 witnessesAddrs := []string{} 121 if witnessAddrsJoined != "" { 122 witnessesAddrs = strings.Split(witnessAddrsJoined, ",") 123 } 124 125 db, err := dbm.NewGoLevelDB("light-client-db", home) 126 if err != nil { 127 return fmt.Errorf("can't create a db: %w", err) 128 } 129 130 if primaryAddr == "" { // check to see if we can start from an existing state 131 var err error 132 primaryAddr, witnessesAddrs, err = checkForExistingProviders(db) 133 if err != nil { 134 return fmt.Errorf("failed to retrieve primary or witness from db: %w", err) 135 } 136 if primaryAddr == "" { 137 return errors.New("no primary address was provided nor found. Please provide a primary (using -p)." + 138 " Run the command: tendermint light --help for more information") 139 } 140 } else { 141 err := saveProviders(db, primaryAddr, witnessAddrsJoined) 142 if err != nil { 143 logger.Error("Unable to save primary and or witness addresses", "err", err) 144 } 145 } 146 147 trustLevel, err := tmmath.ParseFraction(trustLevelStr) 148 if err != nil { 149 return fmt.Errorf("can't parse trust level: %w", err) 150 } 151 152 options := []light.Option{ 153 light.Logger(logger), 154 light.ConfirmationFunction(func(action string) bool { 155 fmt.Println(action) 156 scanner := bufio.NewScanner(os.Stdin) 157 for { 158 scanner.Scan() 159 response := scanner.Text() 160 switch response { 161 case "y", "Y": 162 return true 163 case "n", "N": 164 return false 165 default: 166 fmt.Println("please input 'Y' or 'n' and press ENTER") 167 } 168 } 169 }), 170 } 171 172 if sequential { 173 options = append(options, light.SequentialVerification()) 174 } else { 175 options = append(options, light.SkippingVerification(trustLevel)) 176 } 177 178 var c *light.Client 179 if trustedHeight > 0 && len(trustedHash) > 0 { // fresh installation 180 c, err = light.NewHTTPClient( 181 context.Background(), 182 chainID, 183 light.TrustOptions{ 184 Period: trustingPeriod, 185 Height: trustedHeight, 186 Hash: trustedHash, 187 }, 188 primaryAddr, 189 witnessesAddrs, 190 dbs.New(db, chainID), 191 options..., 192 ) 193 } else { // continue from latest state 194 c, err = light.NewHTTPClientFromTrustedStore( 195 chainID, 196 trustingPeriod, 197 primaryAddr, 198 witnessesAddrs, 199 dbs.New(db, chainID), 200 options..., 201 ) 202 } 203 if err != nil { 204 return err 205 } 206 207 rpcClient, err := rpchttp.New(primaryAddr, "/websocket") 208 if err != nil { 209 return fmt.Errorf("http client for %s: %w", primaryAddr, err) 210 } 211 212 cfg := rpcserver.DefaultConfig() 213 cfg.MaxBodyBytes = config.RPC.MaxBodyBytes 214 cfg.MaxHeaderBytes = config.RPC.MaxHeaderBytes 215 cfg.MaxOpenConnections = maxOpenConnections 216 // If necessary adjust global WriteTimeout to ensure it's greater than 217 // TimeoutBroadcastTxCommit. 218 // See https://github.com/tendermint/tendermint/issues/3435 219 if cfg.WriteTimeout <= config.RPC.TimeoutBroadcastTxCommit { 220 cfg.WriteTimeout = config.RPC.TimeoutBroadcastTxCommit + 1*time.Second 221 } 222 223 p := lproxy.Proxy{ 224 Addr: listenAddr, 225 Config: cfg, 226 Client: lrpc.NewClient(rpcClient, c, lrpc.KeyPathFn(defaultMerkleKeyPathFn())), 227 Logger: logger, 228 } 229 // Stop upon receiving SIGTERM or CTRL-C. 230 tmos.TrapSignal(logger, func() { 231 p.Listener.Close() 232 }) 233 234 logger.Info("Starting proxy...", "laddr", listenAddr) 235 if err := p.ListenAndServe(); err != http.ErrServerClosed { 236 // Error starting or closing listener: 237 logger.Error("proxy ListenAndServe", "err", err) 238 } 239 240 return nil 241 } 242 243 func checkForExistingProviders(db dbm.DB) (string, []string, error) { 244 primaryBytes, err := db.Get(primaryKey) 245 if err != nil { 246 return "", []string{""}, err 247 } 248 witnessesBytes, err := db.Get(witnessesKey) 249 if err != nil { 250 return "", []string{""}, err 251 } 252 witnessesAddrs := strings.Split(string(witnessesBytes), ",") 253 return string(primaryBytes), witnessesAddrs, nil 254 } 255 256 func saveProviders(db dbm.DB, primaryAddr, witnessesAddrs string) error { 257 err := db.Set(primaryKey, []byte(primaryAddr)) 258 if err != nil { 259 return fmt.Errorf("failed to save primary provider: %w", err) 260 } 261 err = db.Set(witnessesKey, []byte(witnessesAddrs)) 262 if err != nil { 263 return fmt.Errorf("failed to save witness providers: %w", err) 264 } 265 return nil 266 } 267 268 func defaultMerkleKeyPathFn() lrpc.KeyPathFunc { 269 // regexp for extracting store name from /abci_query path 270 storeNameRegexp := regexp.MustCompile(`\/store\/(.+)\/key`) 271 272 return func(path string, key []byte) (merkle.KeyPath, error) { 273 matches := storeNameRegexp.FindStringSubmatch(path) 274 if len(matches) != 2 { 275 return nil, fmt.Errorf("can't find store name in %s using %s", path, storeNameRegexp) 276 } 277 storeName := matches[1] 278 279 kp := merkle.KeyPath{} 280 kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) 281 kp = kp.AppendKey(key, merkle.KeyEncodingURL) 282 return kp, nil 283 } 284 }