github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/cmd/tendermint/commands/light.go (about)

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