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