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  }