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  }