github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/test/e2e/node/main.go (about)

     1  package main
     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/viper"
    14  
    15  	"github.com/badrootd/nibiru-cometbft/abci/server"
    16  	"github.com/badrootd/nibiru-cometbft/config"
    17  	"github.com/badrootd/nibiru-cometbft/crypto/ed25519"
    18  	cmtflags "github.com/badrootd/nibiru-cometbft/libs/cli/flags"
    19  	"github.com/badrootd/nibiru-cometbft/libs/log"
    20  	cmtnet "github.com/badrootd/nibiru-cometbft/libs/net"
    21  	"github.com/badrootd/nibiru-cometbft/light"
    22  	lproxy "github.com/badrootd/nibiru-cometbft/light/proxy"
    23  	lrpc "github.com/badrootd/nibiru-cometbft/light/rpc"
    24  	dbs "github.com/badrootd/nibiru-cometbft/light/store/db"
    25  	"github.com/badrootd/nibiru-cometbft/node"
    26  	"github.com/badrootd/nibiru-cometbft/p2p"
    27  	"github.com/badrootd/nibiru-cometbft/privval"
    28  	"github.com/badrootd/nibiru-cometbft/proxy"
    29  	rpcserver "github.com/badrootd/nibiru-cometbft/rpc/jsonrpc/server"
    30  	"github.com/badrootd/nibiru-cometbft/test/e2e/app"
    31  	e2e "github.com/badrootd/nibiru-cometbft/test/e2e/pkg"
    32  )
    33  
    34  var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
    35  
    36  // main is the binary entrypoint.
    37  func main() {
    38  	if len(os.Args) != 2 {
    39  		fmt.Printf("Usage: %v <configfile>", os.Args[0])
    40  		return
    41  	}
    42  	configFile := ""
    43  	if len(os.Args) == 2 {
    44  		configFile = os.Args[1]
    45  	}
    46  
    47  	if err := run(configFile); err != nil {
    48  		logger.Error(err.Error())
    49  		os.Exit(1)
    50  	}
    51  }
    52  
    53  // run runs the application - basically like main() with error handling.
    54  func run(configFile string) error {
    55  	cfg, err := LoadConfig(configFile)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	// Start remote signer (must start before node if running builtin).
    61  	if cfg.PrivValServer != "" {
    62  		if err = startSigner(cfg); err != nil {
    63  			return err
    64  		}
    65  		if cfg.Protocol == "builtin" {
    66  			time.Sleep(1 * time.Second)
    67  		}
    68  	}
    69  
    70  	// Start app server.
    71  	switch cfg.Protocol {
    72  	case "socket", "grpc":
    73  		err = startApp(cfg)
    74  	case "builtin":
    75  		if cfg.Mode == string(e2e.ModeLight) {
    76  			err = startLightClient(cfg)
    77  		} else {
    78  			err = startNode(cfg)
    79  		}
    80  	default:
    81  		err = fmt.Errorf("invalid protocol %q", cfg.Protocol)
    82  	}
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	// Apparently there's no way to wait for the server, so we just sleep
    88  	for {
    89  		time.Sleep(1 * time.Hour)
    90  	}
    91  }
    92  
    93  // startApp starts the application server, listening for connections from CometBFT.
    94  func startApp(cfg *Config) error {
    95  	app, err := app.NewApplication(cfg.App())
    96  	if err != nil {
    97  		return err
    98  	}
    99  	server, err := server.NewServer(cfg.Listen, cfg.Protocol, app)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	err = server.Start()
   104  	if err != nil {
   105  		return err
   106  	}
   107  	logger.Info("start app", "msg", log.NewLazySprintf("Server listening on %v (%v protocol)", cfg.Listen, cfg.Protocol))
   108  	return nil
   109  }
   110  
   111  // startNode starts a CometBFT node running the application directly. It assumes the CometBFT
   112  // configuration is in $CMTHOME/config/cometbft.toml.
   113  //
   114  // FIXME There is no way to simply load the configuration from a file, so we need to pull in Viper.
   115  func startNode(cfg *Config) error {
   116  	app, err := app.NewApplication(cfg.App())
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	cmtcfg, nodeLogger, nodeKey, err := setupNode()
   122  	if err != nil {
   123  		return fmt.Errorf("failed to setup config: %w", err)
   124  	}
   125  
   126  	n, err := node.NewNode(cmtcfg,
   127  		privval.LoadOrGenFilePV(cmtcfg.PrivValidatorKeyFile(), cmtcfg.PrivValidatorStateFile()),
   128  		nodeKey,
   129  		proxy.NewLocalClientCreator(app),
   130  		node.DefaultGenesisDocProviderFunc(cmtcfg),
   131  		node.DefaultDBProvider,
   132  		node.DefaultMetricsProvider(cmtcfg.Instrumentation),
   133  		nodeLogger,
   134  	)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	return n.Start()
   139  }
   140  
   141  func startLightClient(cfg *Config) error {
   142  	cmtcfg, nodeLogger, _, err := setupNode()
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	dbContext := &node.DBContext{ID: "light", Config: cmtcfg}
   148  	lightDB, err := node.DefaultDBProvider(dbContext)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	providers := rpcEndpoints(cmtcfg.P2P.PersistentPeers)
   154  
   155  	c, err := light.NewHTTPClient(
   156  		context.Background(),
   157  		cfg.ChainID,
   158  		light.TrustOptions{
   159  			Period: cmtcfg.StateSync.TrustPeriod,
   160  			Height: cmtcfg.StateSync.TrustHeight,
   161  			Hash:   cmtcfg.StateSync.TrustHashBytes(),
   162  		},
   163  		providers[0],
   164  		providers[1:],
   165  		dbs.New(lightDB, "light"),
   166  		light.Logger(nodeLogger),
   167  	)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	rpccfg := rpcserver.DefaultConfig()
   173  	rpccfg.MaxBodyBytes = cmtcfg.RPC.MaxBodyBytes
   174  	rpccfg.MaxHeaderBytes = cmtcfg.RPC.MaxHeaderBytes
   175  	rpccfg.MaxOpenConnections = cmtcfg.RPC.MaxOpenConnections
   176  	// If necessary adjust global WriteTimeout to ensure it's greater than
   177  	// TimeoutBroadcastTxCommit.
   178  	// See https://github.com/tendermint/tendermint/issues/3435
   179  	if rpccfg.WriteTimeout <= cmtcfg.RPC.TimeoutBroadcastTxCommit {
   180  		rpccfg.WriteTimeout = cmtcfg.RPC.TimeoutBroadcastTxCommit + 1*time.Second
   181  	}
   182  
   183  	p, err := lproxy.NewProxy(c, cmtcfg.RPC.ListenAddress, providers[0], rpccfg, nodeLogger,
   184  		lrpc.KeyPathFn(lrpc.DefaultMerkleKeyPathFn()))
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	logger.Info("Starting proxy...", "laddr", cmtcfg.RPC.ListenAddress)
   190  	if err := p.ListenAndServe(); err != http.ErrServerClosed {
   191  		// Error starting or closing listener:
   192  		logger.Error("proxy ListenAndServe", "err", err)
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  // startSigner starts a signer server connecting to the given endpoint.
   199  func startSigner(cfg *Config) error {
   200  	filePV := privval.LoadFilePV(cfg.PrivValKey, cfg.PrivValState)
   201  
   202  	protocol, address := cmtnet.ProtocolAndAddress(cfg.PrivValServer)
   203  	var dialFn privval.SocketDialer
   204  	switch protocol {
   205  	case "tcp":
   206  		dialFn = privval.DialTCPFn(address, 3*time.Second, ed25519.GenPrivKey())
   207  	case "unix":
   208  		dialFn = privval.DialUnixFn(address)
   209  	default:
   210  		return fmt.Errorf("invalid privval protocol %q", protocol)
   211  	}
   212  
   213  	endpoint := privval.NewSignerDialerEndpoint(logger, dialFn,
   214  		privval.SignerDialerEndpointRetryWaitInterval(1*time.Second),
   215  		privval.SignerDialerEndpointConnRetries(100))
   216  	err := privval.NewSignerServer(endpoint, cfg.ChainID, filePV).Start()
   217  	if err != nil {
   218  		return err
   219  	}
   220  	logger.Info("start signer", "msg", log.NewLazySprintf("Remote signer connecting to %v", cfg.PrivValServer))
   221  	return nil
   222  }
   223  
   224  func setupNode() (*config.Config, log.Logger, *p2p.NodeKey, error) {
   225  	var cmtcfg *config.Config
   226  
   227  	home := os.Getenv("CMTHOME")
   228  	if home == "" {
   229  		return nil, nil, nil, errors.New("CMTHOME not set")
   230  	}
   231  
   232  	viper.AddConfigPath(filepath.Join(home, "config"))
   233  	viper.SetConfigName("config")
   234  
   235  	if err := viper.ReadInConfig(); err != nil {
   236  		return nil, nil, nil, err
   237  	}
   238  
   239  	cmtcfg = config.DefaultConfig()
   240  
   241  	if err := viper.Unmarshal(cmtcfg); err != nil {
   242  		return nil, nil, nil, err
   243  	}
   244  
   245  	cmtcfg.SetRoot(home)
   246  
   247  	if err := cmtcfg.ValidateBasic(); err != nil {
   248  		return nil, nil, nil, fmt.Errorf("error in config file: %w", err)
   249  	}
   250  
   251  	if cmtcfg.LogFormat == config.LogFormatJSON {
   252  		logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout))
   253  	}
   254  
   255  	nodeLogger, err := cmtflags.ParseLogLevel(cmtcfg.LogLevel, logger, config.DefaultLogLevel)
   256  	if err != nil {
   257  		return nil, nil, nil, err
   258  	}
   259  
   260  	nodeLogger = nodeLogger.With("module", "main")
   261  
   262  	nodeKey, err := p2p.LoadOrGenNodeKey(cmtcfg.NodeKeyFile())
   263  	if err != nil {
   264  		return nil, nil, nil, fmt.Errorf("failed to load or gen node key %s: %w", cmtcfg.NodeKeyFile(), err)
   265  	}
   266  
   267  	return cmtcfg, nodeLogger, nodeKey, nil
   268  }
   269  
   270  // rpcEndpoints takes a list of persistent peers and splits them into a list of rpc endpoints
   271  // using 26657 as the port number
   272  func rpcEndpoints(peers string) []string {
   273  	arr := strings.Split(peers, ",")
   274  	endpoints := make([]string, len(arr))
   275  	for i, v := range arr {
   276  		urlString := strings.SplitAfter(v, "@")[1]
   277  		hostName := strings.Split(urlString, ":26656")[0]
   278  		// use RPC port instead
   279  		port := 26657
   280  		rpcEndpoint := "http://" + hostName + ":" + fmt.Sprint(port)
   281  		endpoints[i] = rpcEndpoint
   282  	}
   283  	return endpoints
   284  }