github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/config/config.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"regexp"
     8  	"slices"
     9  
    10  	"dario.cat/mergo"
    11  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
    12  	cns "github.com/gnolang/gno/tm2/pkg/bft/consensus/config"
    13  	mem "github.com/gnolang/gno/tm2/pkg/bft/mempool/config"
    14  	rpc "github.com/gnolang/gno/tm2/pkg/bft/rpc/config"
    15  	eventstore "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types"
    16  	"github.com/gnolang/gno/tm2/pkg/db"
    17  	"github.com/gnolang/gno/tm2/pkg/errors"
    18  	osm "github.com/gnolang/gno/tm2/pkg/os"
    19  	p2p "github.com/gnolang/gno/tm2/pkg/p2p/config"
    20  	telemetry "github.com/gnolang/gno/tm2/pkg/telemetry/config"
    21  )
    22  
    23  var (
    24  	errInvalidMoniker                    = errors.New("moniker not set")
    25  	errInvalidDBBackend                  = errors.New("invalid DB backend")
    26  	errInvalidDBPath                     = errors.New("invalid DB path")
    27  	errInvalidPrivValidatorKeyPath       = errors.New("invalid private validator key path")
    28  	errInvalidPrivValidatorStatePath     = errors.New("invalid private validator state file path")
    29  	errInvalidABCIMechanism              = errors.New("invalid ABCI mechanism")
    30  	errInvalidPrivValidatorListenAddress = errors.New("invalid PrivValidator listen address")
    31  	errInvalidProfListenAddress          = errors.New("invalid profiling server listen address")
    32  	errInvalidNodeKeyPath                = errors.New("invalid p2p node key path")
    33  )
    34  
    35  const (
    36  	LocalABCI  = "local"
    37  	SocketABCI = "socket"
    38  )
    39  
    40  // Regular expression for TCP or UNIX socket address
    41  // TCP address: host:port (IPv4 example)
    42  // UNIX address: unix:// followed by the path
    43  var tcpUnixAddressRegex = regexp.MustCompile(`^(?:[0-9]{1,3}(\.[0-9]{1,3}){3}:[0-9]+|unix://.+)`)
    44  
    45  // Config defines the top level configuration for a Tendermint node
    46  type Config struct {
    47  	// Top level options use an anonymous struct
    48  	BaseConfig `toml:",squash"`
    49  
    50  	// Options for services
    51  	RPC          *rpc.RPCConfig       `toml:"rpc" comment:"##### rpc server configuration options #####"`
    52  	P2P          *p2p.P2PConfig       `toml:"p2p" comment:"##### peer to peer configuration options #####"`
    53  	Mempool      *mem.MempoolConfig   `toml:"mempool" comment:"##### mempool configuration options #####"`
    54  	Consensus    *cns.ConsensusConfig `toml:"consensus" comment:"##### consensus configuration options #####"`
    55  	TxEventStore *eventstore.Config   `toml:"tx_event_store" comment:"##### event store #####"`
    56  	Telemetry    *telemetry.Config    `toml:"telemetry" comment:"##### node telemetry #####"`
    57  }
    58  
    59  // DefaultConfig returns a default configuration for a Tendermint node
    60  func DefaultConfig() *Config {
    61  	return &Config{
    62  		BaseConfig:   DefaultBaseConfig(),
    63  		RPC:          rpc.DefaultRPCConfig(),
    64  		P2P:          p2p.DefaultP2PConfig(),
    65  		Mempool:      mem.DefaultMempoolConfig(),
    66  		Consensus:    cns.DefaultConsensusConfig(),
    67  		TxEventStore: eventstore.DefaultEventStoreConfig(),
    68  		Telemetry:    telemetry.DefaultTelemetryConfig(),
    69  	}
    70  }
    71  
    72  type Option func(cfg *Config)
    73  
    74  // LoadOrMakeConfigWithOptions loads the configuration located in the given
    75  // root directory, at [defaultConfigFilePath].
    76  //
    77  // If the config does not exist, it is created, starting from the values in
    78  // `DefaultConfig` and applying the defaults in opts.
    79  func LoadOrMakeConfigWithOptions(root string, opts ...Option) (*Config, error) {
    80  	// Initialize the config as default
    81  	var (
    82  		cfg        = DefaultConfig()
    83  		configPath = filepath.Join(root, defaultConfigPath)
    84  	)
    85  
    86  	// Config doesn't exist, create it
    87  	// from the default one
    88  	for _, opt := range opts {
    89  		opt(cfg)
    90  	}
    91  
    92  	// Check if the config exists
    93  	if osm.FileExists(configPath) {
    94  		// Load the configuration
    95  		loadedCfg, loadErr := LoadConfigFile(configPath)
    96  		if loadErr != nil {
    97  			return nil, loadErr
    98  		}
    99  
   100  		// Merge the loaded config with the default values
   101  		if err := mergo.Merge(loadedCfg, cfg); err != nil {
   102  			return nil, err
   103  		}
   104  
   105  		// Set the root directory
   106  		loadedCfg.SetRootDir(root)
   107  
   108  		// Make sure the directories are initialized
   109  		if err := loadedCfg.EnsureDirs(); err != nil {
   110  			return nil, err
   111  		}
   112  
   113  		return loadedCfg, nil
   114  	}
   115  
   116  	cfg.SetRootDir(root)
   117  
   118  	// Make sure the directories are initialized
   119  	if err := cfg.EnsureDirs(); err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	// Validate the configuration
   124  	if validateErr := cfg.ValidateBasic(); validateErr != nil {
   125  		return nil, fmt.Errorf("unable to validate config, %w", validateErr)
   126  	}
   127  
   128  	// Save the config
   129  	if err := WriteConfigFile(configPath, cfg); err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	return cfg, nil
   134  }
   135  
   136  // TestConfig returns a configuration that can be used for testing
   137  func TestConfig() *Config {
   138  	return &Config{
   139  		BaseConfig:   testBaseConfig(),
   140  		RPC:          rpc.TestRPCConfig(),
   141  		P2P:          p2p.TestP2PConfig(),
   142  		Mempool:      mem.TestMempoolConfig(),
   143  		Consensus:    cns.TestConsensusConfig(),
   144  		TxEventStore: eventstore.DefaultEventStoreConfig(),
   145  		Telemetry:    telemetry.TestTelemetryConfig(),
   146  	}
   147  }
   148  
   149  // SetRootDir sets the RootDir for all Config structs
   150  func (cfg *Config) SetRootDir(root string) *Config {
   151  	cfg.BaseConfig.RootDir = root
   152  	cfg.RPC.RootDir = root
   153  	cfg.P2P.RootDir = root
   154  	cfg.Mempool.RootDir = root
   155  	cfg.Consensus.RootDir = root
   156  
   157  	return cfg
   158  }
   159  
   160  // EnsureDirs ensures default directories in root dir (and root dir).
   161  func (cfg *Config) EnsureDirs() error {
   162  	rootDir := cfg.BaseConfig.RootDir
   163  
   164  	if err := osm.EnsureDir(rootDir, DefaultDirPerm); err != nil {
   165  		return fmt.Errorf("no root directory, %w", err)
   166  	}
   167  
   168  	if err := osm.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil {
   169  		return fmt.Errorf("no config directory, %w", err)
   170  	}
   171  
   172  	if err := osm.EnsureDir(filepath.Join(rootDir, defaultSecretsDir), DefaultDirPerm); err != nil {
   173  		return fmt.Errorf("no secrets directory, %w", err)
   174  	}
   175  
   176  	if err := osm.EnsureDir(filepath.Join(rootDir, DefaultDBDir), DefaultDirPerm); err != nil {
   177  		return fmt.Errorf("no DB directory, %w", err)
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  // ValidateBasic performs basic validation (checking param bounds, etc.) and
   184  // returns an error if any check fails.
   185  func (cfg *Config) ValidateBasic() error {
   186  	if err := cfg.BaseConfig.ValidateBasic(); err != nil {
   187  		return err
   188  	}
   189  	if err := cfg.RPC.ValidateBasic(); err != nil {
   190  		return errors.Wrap(err, "Error in [rpc] section")
   191  	}
   192  	if err := cfg.P2P.ValidateBasic(); err != nil {
   193  		return errors.Wrap(err, "Error in [p2p] section")
   194  	}
   195  	if err := cfg.Mempool.ValidateBasic(); err != nil {
   196  		return errors.Wrap(err, "Error in [mempool] section")
   197  	}
   198  	if err := cfg.Consensus.ValidateBasic(); err != nil {
   199  		return errors.Wrap(err, "Error in [consensus] section")
   200  	}
   201  	return nil
   202  }
   203  
   204  // -----------------------------------------------------------------------------
   205  
   206  var (
   207  	DefaultDBDir      = "db"
   208  	defaultConfigDir  = "config"
   209  	defaultSecretsDir = "secrets"
   210  
   211  	defaultConfigFileName   = "config.toml"
   212  	defaultNodeKeyName      = "node_key.json"
   213  	defaultPrivValKeyName   = "priv_validator_key.json"
   214  	defaultPrivValStateName = "priv_validator_state.json"
   215  
   216  	defaultConfigPath       = filepath.Join(defaultConfigDir, defaultConfigFileName)
   217  	defaultPrivValKeyPath   = filepath.Join(defaultSecretsDir, defaultPrivValKeyName)
   218  	defaultPrivValStatePath = filepath.Join(defaultSecretsDir, defaultPrivValStateName)
   219  	defaultNodeKeyPath      = filepath.Join(defaultSecretsDir, defaultNodeKeyName)
   220  )
   221  
   222  // BaseConfig defines the base configuration for a Tendermint node.
   223  type BaseConfig struct {
   224  	// chainID is unexposed and immutable but here for convenience
   225  	chainID string
   226  
   227  	// The root directory for all data.
   228  	// The node directory contains:
   229  	//
   230  	//	┌── db/
   231  	//	│   ├── blockstore.db (folder)
   232  	//	│   ├── gnolang.db (folder)
   233  	//	│   └── state.db (folder)
   234  	//	├── wal/
   235  	//	│   └── cs.wal (folder)
   236  	//	├── secrets/
   237  	//	│   ├── priv_validator_state.json
   238  	//	│   ├── node_key.json
   239  	//	│   └── priv_validator_key.json
   240  	//	└── config/
   241  	//	    └── config.toml (optional)
   242  	RootDir string `toml:"home"`
   243  
   244  	// TCP or UNIX socket address of the ABCI application,
   245  	// or the name of an ABCI application compiled in with the Tendermint binary,
   246  	// or empty if local application instance.
   247  	ProxyApp string `toml:"proxy_app" comment:"TCP or UNIX socket address of the ABCI application, \n or the name of an ABCI application compiled in with the Tendermint binary"`
   248  
   249  	// Local application instance in lieu of remote app.
   250  	LocalApp abci.Application `toml:"-"`
   251  
   252  	// A custom human readable name for this node
   253  	Moniker string `toml:"moniker" comment:"A custom human readable name for this node"`
   254  
   255  	// If this node is many blocks behind the tip of the chain, FastSync
   256  	// allows them to catchup quickly by downloading blocks in parallel
   257  	// and verifying their commits
   258  	FastSyncMode bool `toml:"fast_sync" comment:"If this node is many blocks behind the tip of the chain, FastSync\n allows them to catchup quickly by downloading blocks in parallel\n and verifying their commits"`
   259  
   260  	// Database backend: goleveldb | boltdb
   261  	// * goleveldb (github.com/syndtr/goleveldb - most popular implementation)
   262  	//   - pure go
   263  	//   - stable
   264  	// * boltdb (uses etcd's fork of bolt - go.etcd.io/bbolt)
   265  	//   - EXPERIMENTAL
   266  	//   - may be faster is some use-cases (random reads - indexer)
   267  	//   - use boltdb build tag (go build -tags boltdb)
   268  	DBBackend string `toml:"db_backend" comment:"Database backend: goleveldb | boltdb\n * goleveldb (github.com/syndtr/goleveldb - most popular implementation)\n  - pure go\n  - stable\n* boltdb (uses etcd's fork of bolt - go.etcd.io/bbolt)\n  - EXPERIMENTAL\n  - may be faster is some use-cases (random reads - indexer)\n  - use boltdb build tag (go build -tags boltdb)"`
   269  
   270  	// Database directory
   271  	DBPath string `toml:"db_dir" comment:"Database directory"`
   272  
   273  	// Path to the JSON file containing the private key to use as a validator in the consensus protocol
   274  	PrivValidatorKey string `toml:"priv_validator_key_file" comment:"Path to the JSON file containing the private key to use as a validator in the consensus protocol"`
   275  
   276  	// Path to the JSON file containing the last sign state of a validator
   277  	PrivValidatorState string `toml:"priv_validator_state_file" comment:"Path to the JSON file containing the last sign state of a validator"`
   278  
   279  	// TCP or UNIX socket address for Tendermint to listen on for
   280  	// connections from an external PrivValidator process
   281  	PrivValidatorListenAddr string `toml:"priv_validator_laddr" comment:"TCP or UNIX socket address for Tendermint to listen on for\n connections from an external PrivValidator process"`
   282  
   283  	// A JSON file containing the private key to use for p2p authenticated encryption
   284  	NodeKey string `toml:"node_key_file" comment:"Path to the JSON file containing the private key to use for node authentication in the p2p protocol"`
   285  
   286  	// Mechanism to connect to the ABCI application: local | socket
   287  	ABCI string `toml:"abci" comment:"Mechanism to connect to the ABCI application: socket | grpc"`
   288  
   289  	// TCP or UNIX socket address for the profiling server to listen on
   290  	ProfListenAddress string `toml:"prof_laddr" comment:"TCP or UNIX socket address for the profiling server to listen on"`
   291  
   292  	// If true, query the ABCI app on connecting to a new peer
   293  	// so the app can decide if we should keep the connection or not
   294  	FilterPeers bool `toml:"filter_peers" comment:"If true, query the ABCI app on connecting to a new peer\n so the app can decide if we should keep the connection or not"` // false
   295  }
   296  
   297  // DefaultBaseConfig returns a default base configuration for a Tendermint node
   298  func DefaultBaseConfig() BaseConfig {
   299  	return BaseConfig{
   300  		PrivValidatorKey:   defaultPrivValKeyPath,
   301  		PrivValidatorState: defaultPrivValStatePath,
   302  		NodeKey:            defaultNodeKeyPath,
   303  		Moniker:            defaultMoniker,
   304  		ProxyApp:           "tcp://127.0.0.1:26658",
   305  		ABCI:               SocketABCI,
   306  		ProfListenAddress:  "",
   307  		FastSyncMode:       true,
   308  		FilterPeers:        false,
   309  		DBBackend:          db.GoLevelDBBackend.String(),
   310  		DBPath:             DefaultDBDir,
   311  	}
   312  }
   313  
   314  // testBaseConfig returns a base configuration for testing a Tendermint node
   315  func testBaseConfig() BaseConfig {
   316  	cfg := DefaultBaseConfig()
   317  	cfg.chainID = "tendermint_test"
   318  	cfg.ProxyApp = "mock://kvstore"
   319  	cfg.FastSyncMode = false
   320  	cfg.DBBackend = "memdb"
   321  	return cfg
   322  }
   323  
   324  func (cfg BaseConfig) ChainID() string {
   325  	return cfg.chainID
   326  }
   327  
   328  // PrivValidatorKeyFile returns the full path to the priv_validator_key.json file
   329  func (cfg BaseConfig) PrivValidatorKeyFile() string {
   330  	return filepath.Join(cfg.RootDir, cfg.PrivValidatorKey)
   331  }
   332  
   333  // PrivValidatorStateFile returns the full path to the priv_validator_state.json file
   334  func (cfg BaseConfig) PrivValidatorStateFile() string {
   335  	return filepath.Join(cfg.RootDir, cfg.PrivValidatorState)
   336  }
   337  
   338  // NodeKeyFile returns the full path to the node_key.json file
   339  func (cfg BaseConfig) NodeKeyFile() string {
   340  	return filepath.Join(cfg.RootDir, cfg.NodeKey)
   341  }
   342  
   343  // DBDir returns the full path to the database directory
   344  func (cfg BaseConfig) DBDir() string {
   345  	return filepath.Join(cfg.RootDir, cfg.DBPath)
   346  }
   347  
   348  var defaultMoniker = getDefaultMoniker()
   349  
   350  // getDefaultMoniker returns a default moniker, which is the host name. If runtime
   351  // fails to get the host name, "anonymous" will be returned.
   352  func getDefaultMoniker() string {
   353  	moniker, err := os.Hostname()
   354  	if err != nil {
   355  		moniker = "anonymous"
   356  	}
   357  	return moniker
   358  }
   359  
   360  // ValidateBasic performs basic validation (checking param bounds, etc.) and
   361  // returns an error if any check fails.
   362  func (cfg BaseConfig) ValidateBasic() error {
   363  	// Verify the moniker
   364  	if cfg.Moniker == "" {
   365  		return errInvalidMoniker
   366  	}
   367  
   368  	// Verify the DB backend
   369  	// This will reject also any databases that haven't been added with build tags.
   370  	// always reject memdb, as it shouldn't be used as a real-life database.
   371  	if cfg.DBBackend == "memdb" ||
   372  		!slices.Contains(db.BackendList(), db.BackendType(cfg.DBBackend)) {
   373  		return errInvalidDBBackend
   374  	}
   375  
   376  	// Verify the DB path is set
   377  	if cfg.DBPath == "" {
   378  		return errInvalidDBPath
   379  	}
   380  
   381  	// Verify the validator private key path is set
   382  	if cfg.PrivValidatorKey == "" {
   383  		return errInvalidPrivValidatorKeyPath
   384  	}
   385  
   386  	// Verify the validator state file path is set
   387  	if cfg.PrivValidatorState == "" {
   388  		return errInvalidPrivValidatorStatePath
   389  	}
   390  
   391  	// Verify the PrivValidator listen address
   392  	if cfg.PrivValidatorListenAddr != "" &&
   393  		!tcpUnixAddressRegex.MatchString(cfg.PrivValidatorListenAddr) {
   394  		return errInvalidPrivValidatorListenAddress
   395  	}
   396  
   397  	// Verify the p2p private key exists
   398  	if cfg.NodeKey == "" {
   399  		return errInvalidNodeKeyPath
   400  	}
   401  
   402  	// Verify the correct ABCI mechanism is set
   403  	if cfg.ABCI != LocalABCI &&
   404  		cfg.ABCI != SocketABCI {
   405  		return errInvalidABCIMechanism
   406  	}
   407  
   408  	// Verify the profiling listen address
   409  	if cfg.ProfListenAddress != "" && !tcpUnixAddressRegex.MatchString(cfg.ProfListenAddress) {
   410  		return errInvalidProfListenAddress
   411  	}
   412  
   413  	return nil
   414  }