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 }