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  }