diff --git a/README.md b/README.md index daff1480..3b3b09c3 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,66 @@ Lumera Supernode is a companion application for Lumera validators who want to provide cascade, sense, and other services to earn rewards. -## Quick Start +## Hardware Requirements -### 1. Install the Supernode +### Minimum Hardware Requirements -Download the latest relase from github +- **CPU**: 8 cores, x86_64 architecture +- **RAM**: 16 GB RAM +- **Storage**: 1 TB NVMe SSD +- **Network**: 1 Gbps +- **Operating System**: Ubuntu 22.04 LTS or higher -### 2. Configure Your Supernode +### Recommended Hardware Requirements -Create a `config.yml` file in the same directory as your binary: +- **CPU**: 16 cores, x86_64 architecture +- **RAM**: 64 GB RAM +- **Storage**: 4 TB NVMe SSD +- **Network**: 5 Gbps +- **Operating System**: Ubuntu 22.04 LTS or higher + +## Prerequisites + +Before installing and running the Lumera Supernode, ensure you have the following prerequisites installed: + +### Install Build Essentials + +```bash +# Ubuntu/Debian +sudo apt update +sudo apt install build-essential + +# CentOS/RHEL/Fedora +sudo yum groupinstall "Development Tools" +# or for newer versions +sudo dnf groupinstall "Development Tools" +``` + +### Enable CGO + +CGO must be enabled for the supernode to compile and run properly. Set the environment variable: + +```bash +export CGO_ENABLED=1 +``` + +To make this permanent, add it to your shell profile: + +```bash +echo 'export CGO_ENABLED=1' >> ~/.bashrc +source ~/.bashrc +``` + +## Installation + +### 1. Download the Binary + +Download the latest release from GitHub: + + +### 2. Create Configuration File + +Create a `config.yml` file in the same directory as your binary ```yaml # Supernode Configuration @@ -24,7 +75,6 @@ supernode: keyring: backend: "test" # Options: test, file, os dir: "keys" - # P2P Network Configuration p2p: @@ -38,67 +88,190 @@ p2p: lumera: grpc_addr: "localhost:9090" chain_id: "lumera" - timeout: 10 # RaptorQ Configuration raptorq: files_dir: "raptorq_files" ``` -### 3. Create or Recover a Key +## Key Management + +### Create a New Key + +Create a new key (use the same name you specified in your config): -Create a new key (use the same name you will add in your config): ```bash supernode keys add mykey ``` -Or recover an existing key: +This will output an address like: +``` +lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr +``` + +### Recover an Existing Key + +If you have an existing mnemonic phrase: + ```bash -supernode keys recover mykey +supernode keys recover mykey ``` -After running either command, you'll receive an address like `lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr`. +### List Keys -⚠️ **IMPORTANT:** After generating or recovering a key, you MUST update your `config.yml` with the address: +```bash +supernode keys list +``` + +### Update Configuration with Your Address + +⚠️ **IMPORTANT:** After generating or recovering a key, you MUST update your `config.yml` with the generated address: ```yaml supernode: key_name: "mykey" - identity: "lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr" # Update with your generated address - # ... rest of config + identity: "lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr" # Your actual address + ip_address: "0.0.0.0" + port: 4444 +# ... rest of config ``` -### 4. Start Your Supernode +## Running the Supernode + +### Start the Supernode ```bash supernode start ``` -## Key Management Commands +### Using Custom Configuration -List all keys: ```bash -supernode keys list +# Use specific config file +supernode start -c /path/to/config.yml + +# Use custom base directory +supernode start -d /path/to/basedir +``` + +## Configuration Parameters + +| Parameter | Description | Required | Default | Example | Notes | +|-----------|-------------|----------|---------|---------|--------| +| `supernode.key_name` | Name of the key for signing transactions | **Yes** | - | `"mykey"` | Must match the name used with `supernode keys add` | +| `supernode.identity` | Lumera address for this supernode | **Yes** | - | `"lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr"` | Obtained after creating/recovering a key | +| `supernode.ip_address` | IP address to bind the supernode service | **Yes** | - | `"0.0.0.0"` | Use `"0.0.0.0"` to listen on all interfaces | +| `supernode.port` | Port for the supernode service | **Yes** | - | `4444` | Choose an available port | +| `keyring.backend` | Key storage backend type | **Yes** | - | `"test"` | `"test"` for development, `"file"` for encrypted storage, `"os"` for OS keyring | +| `keyring.dir` | Directory to store keyring files | No | `"keys"` | `"keys"` | Relative paths are appended to basedir, absolute paths used as-is | +| `p2p.listen_address` | IP address for P2P networking | **Yes** | - | `"0.0.0.0"` | Use `"0.0.0.0"` to listen on all interfaces | +| `p2p.port` | P2P communication port | **Yes** | - | `4445` | **Do not change this default value** | +| `p2p.data_dir` | Directory for P2P data storage | No | `"data/p2p"` | `"data/p2p"` | Relative paths are appended to basedir, absolute paths used as-is | +| `p2p.bootstrap_nodes` | Initial peer nodes for network discovery | No | `""` | `""` | Comma-separated list of peer addresses, leave empty for auto-discovery | +| `p2p.external_ip` | Your public IP address | No | `""` | `""` | Leave empty for auto-detection, or specify your public IP | +| `lumera.grpc_addr` | gRPC endpoint of Lumera validator node | **Yes** | - | `"localhost:9090"` | Must be accessible from supernode | +| `lumera.chain_id` | Lumera blockchain chain identifier | **Yes** | - | `"lumera"` | Must match the actual chain ID | +| `raptorq.files_dir` | Directory to store RaptorQ files | No | `"raptorq_files"` | `"raptorq_files"` | Relative paths are appended to basedir, absolute paths used as-is | + +## Command Line Flags + +The supernode binary supports the following command-line flags: + +| Flag | Short | Description | Value Type | Example | Default | +|------|-------|-------------|------------|---------|---------| +| `--config` | `-c` | Path to configuration file | String | `-c /path/to/config.yml` | `config.yml` in current directory | +| `--basedir` | `-d` | Base directory for data storage | String | `-d /custom/path` | `~/.supernode` | + +### Usage Examples + +```bash +# Use default config.yml in current directory +supernode start + +# Use custom config file +supernode start -c /etc/supernode/config.yml +supernode start --config /etc/supernode/config.yml + +# Use custom base directory +supernode start -d /opt/supernode +supernode start --basedir /opt/supernode + +# Combine flags +supernode start -c /etc/supernode/config.yml -d /opt/supernode +``` + +## Important Notes + +⚠️ **CRITICAL: Consistent Flag Usage Across Commands** + +If you use custom flags (`--config` or `--basedir`) for key management operations, you **MUST** use the same flags for ALL subsequent commands, including the `start` command. Otherwise, your configuration will break and keys will not be found. + +### Examples of Correct Flag Usage: + +**Scenario 1: Using custom config file** +```bash +# Create key with custom config +supernode keys add mykey -c /etc/supernode/config.yml + +# Start supernode with same config +supernode start -c /etc/supernode/config.yml ``` -## Configuration Guide +**Scenario 2: Using custom base directory** +```bash +# Create key with custom basedir +supernode keys add mykey -d /opt/supernode -- **key_name**: Must match the name you used when creating your key -- **identity**: Must match the address generated for your key -- **backend**: Use "test" for development, "file" or "os" for production -- **p2p.port**: Default is 4445 - don't change this value -- ⚠️ **IMPORTANT:** Make sure you have sufficient balance in the account to broadcast transactions to the Lumera chain +# Recover key with same basedir +supernode keys recover mykey "your mnemonic phrase here" -d /opt/supernode -## Data Storage +# Start supernode with same basedir +supernode start -d /opt/supernode +``` -All data is stored in `~/.supernode` by default. +**Scenario 3: Using both custom config and basedir** +```bash +# Create key with both flags +supernode keys add mykey -c /etc/supernode/config.yml -d /opt/supernode -## Common Flags +# Start supernode with both flags +supernode start -c /etc/supernode/config.yml -d /opt/supernode +``` +**❌ WRONG - This will cause configuration breakage:** ```bash -# Custom config file --c, --config FILE Use specific config file +# Create key with custom basedir +supernode keys add mykey -d /opt/supernode + +# Start without the same basedir flag - keys won't be found! +supernode start +``` + +### Additional Important Notes: + +- Make sure you have sufficient balance in your Lumera account to broadcast transactions +- The P2P port (4445) should not be changed from the default +- Your `key_name` in the config must match the name you used when creating the key +- Your `identity` in the config must match the address generated for your key +- Ensure your Lumera validator node is running and accessible at the configured gRPC address + +## Troubleshooting + +### Key Not Found +Make sure the `key_name` in your config matches the name you used with `supernode keys add`. Also ensure you're using the same `--config` and `--basedir` flags across all commands. + +### Address Mismatch +Ensure the `identity` field in your config matches the address returned when you created your key. + +### Connection Issues +Verify that your Lumera validator node is running and accessible at the configured `grpc_addr`. + +### Insufficient Balance +Check that your account has sufficient LUMERA tokens to pay for transaction fees. + +### Configuration Break +If your configuration seems broken, verify that you're using consistent command-line flags (`--config` and `--basedir`) across all supernode commands. + +## Support -# Custom data directory --d, --basedir DIR Use custom base directory instead of ~/.supernode -``` \ No newline at end of file +For issues and support, please refer to the Lumera Protocol documentation or community channels. \ No newline at end of file diff --git a/supernode/cmd/root.go b/supernode/cmd/root.go index 12baefa0..18f5d0f4 100644 --- a/supernode/cmd/root.go +++ b/supernode/cmd/root.go @@ -15,6 +15,11 @@ var ( appConfig *config.Config ) +const ( + DefaultConfigFile = "config.yaml" + DefaultBaseDir = ".supernode" +) + // fileExists checks if a file exists at the given path func fileExists(path string) bool { _, err := os.Stat(path) @@ -28,39 +33,36 @@ func findConfigFile() string { return cfgFile } - // Try current working directory first (for go run) - workingDir, err := os.Getwd() - if err == nil { - yamlPath := filepath.Join(workingDir, "config.yaml") - ymlPath := filepath.Join(workingDir, "config.yml") + searchPaths := []string{} - if fileExists(yamlPath) { - return yamlPath - } - if fileExists(ymlPath) { - return ymlPath - } + // Try current working directory first (for go run) + if workingDir, err := os.Getwd(); err == nil { + searchPaths = append(searchPaths, + filepath.Join(workingDir, DefaultConfigFile), + filepath.Join(workingDir, "config.yml"), + ) } // Then try executable directory (for binary) - execPath, err := os.Executable() - if err == nil { + if execPath, err := os.Executable(); err == nil { execDir := filepath.Dir(execPath) - yamlPath := filepath.Join(execDir, "config.yaml") - ymlPath := filepath.Join(execDir, "config.yml") + searchPaths = append(searchPaths, + filepath.Join(execDir, DefaultConfigFile), + filepath.Join(execDir, "config.yml"), + ) + } - if fileExists(yamlPath) { - return yamlPath - } - if fileExists(ymlPath) { - return ymlPath + // Return first existing config file + for _, path := range searchPaths { + if fileExists(path) { + return path } } return "" } -// setupBaseDir configures the base directory if not specified +// setupBaseDir configures the base directory, using default if not specified func setupBaseDir() (string, error) { if baseDir != "" { return baseDir, nil @@ -71,20 +73,18 @@ func setupBaseDir() (string, error) { return "", fmt.Errorf("failed to get home directory: %w", err) } - return filepath.Join(homeDir, ".supernode"), nil + return filepath.Join(homeDir, DefaultBaseDir), nil } // logConfig logs information about config and base directory func logConfig(configPath, baseDirPath string) { // For config file - absPath, err := filepath.Abs(configPath) - if err == nil { + if absPath, err := filepath.Abs(configPath); err == nil { fmt.Printf("Using config file: %s\n", absPath) } else { fmt.Printf("Using config file: %s\n", configPath) } - // For base directory fmt.Printf("Using base directory: %s\n", baseDirPath) } @@ -100,11 +100,11 @@ This application allows you to create and recover keys using mnemonics.`, } // Setup base directory - setupDir, err := setupBaseDir() + var err error + baseDir, err = setupBaseDir() if err != nil { return err } - baseDir = setupDir // Find config file cfgFile = findConfigFile() @@ -134,7 +134,9 @@ func Execute() { } func init() { - // Allow user to override config file location with --config flag - rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "Config file path (default is ./config.yaml or ./config.yml)") - rootCmd.PersistentFlags().StringVarP(&baseDir, "basedir", "d", "", "Base directory for all data (default is ~/.supernode)") + // Use default values in flag descriptions + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", + fmt.Sprintf("Config file path (default is ./%s or ./config.yml)", DefaultConfigFile)) + rootCmd.PersistentFlags().StringVarP(&baseDir, "basedir", "d", "", + fmt.Sprintf("Base directory for all data (default is ~/%s)", DefaultBaseDir)) } diff --git a/supernode/config/config.go b/supernode/config/config.go index 634f39a2..b651706e 100644 --- a/supernode/config/config.go +++ b/supernode/config/config.go @@ -10,6 +10,12 @@ import ( "gopkg.in/yaml.v3" ) +const ( + DefaultKeyringDir = "keys" + DefaultP2PDataDir = "p2p" + DefaultRaptorQDir = "raptorq" +) + type SupernodeConfig struct { KeyName string `yaml:"key_name"` Identity string `yaml:"identity"` @@ -52,6 +58,19 @@ type Config struct { BaseDir string `yaml:"-"` } +// applyDefaults sets default values for empty directory fields +func (c *Config) applyDefaults() { + if c.KeyringConfig.Dir == "" { + c.KeyringConfig.Dir = DefaultKeyringDir + } + if c.P2PConfig.DataDir == "" { + c.P2PConfig.DataDir = DefaultP2PDataDir + } + if c.RaptorQConfig.FilesDir == "" { + c.RaptorQConfig.FilesDir = DefaultRaptorQDir + } +} + // GetFullPath returns the absolute path by combining base directory with relative path func (c *Config) GetFullPath(relativePath string) string { if relativePath == "" { @@ -128,6 +147,9 @@ func LoadConfig(filename string, baseDir string) (*Config, error) { // Set the base directory config.BaseDir = baseDir + // Apply default values for empty directory fields + config.applyDefaults() + // Create directories if err := config.EnsureDirs(); err != nil { return nil, err