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