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