diff --git a/Makefile b/Makefile index 2b938c20..b8133625 100644 --- a/Makefile +++ b/Makefile @@ -10,12 +10,7 @@ LDFLAGS = -X github.com/LumeraProtocol/supernode/supernode/cmd.Version=$(VERSION -X github.com/LumeraProtocol/supernode/supernode/cmd.GitCommit=$(GIT_COMMIT) \ -X github.com/LumeraProtocol/supernode/supernode/cmd.BuildTime=$(BUILD_TIME) -# Development build build: - go build -ldflags "$(LDFLAGS)" -o supernode ./supernode - -# Release build (matches GitHub workflow) -build-release: @mkdir -p release CGO_ENABLED=1 \ GOOS=linux \ @@ -27,15 +22,12 @@ build-release: ./supernode @chmod +x release/supernode-linux-amd64 -# Run unit tests (regular tests with code) test-unit: go test -v ./... -# Run integration tests test-integration: go test -v -p 1 -count=1 -tags=integration ./... -# Run system tests test-system: cd tests/system && go test -tags=system_test -v . diff --git a/README.md b/README.md index 6704e341..17a551dd 100644 --- a/README.md +++ b/README.md @@ -2,168 +2,188 @@ Lumera Supernode is a companion application for Lumera validators who want to provide cascade, sense, and other services to earn rewards. -## Prerequisites +## gRPC API -Before installing and running the Lumera Supernode, ensure you have the following prerequisites installed: +The supernode exposes two main gRPC services: -### Install Build Essentials +### SupernodeService -```bash -# Ubuntu/Debian -sudo apt update -sudo apt install build-essential -``` - -### Enable CGO +Provides system status and monitoring information. -CGO must be enabled for the supernode to compile and run properly. Set the environment variable: - -```bash -export CGO_ENABLED=1 +**Service Definition:** +```protobuf +service SupernodeService { + rpc GetStatus(StatusRequest) returns (StatusResponse); +} ``` -To make this permanent, add it to your shell profile: +**StatusResponse includes:** +- `CPU` - CPU usage and remaining capacity +- `Memory` - Total, used, available memory and usage percentage +- `ServiceTasks` - Task information for each active service +- `available_services` - List of available service names -```bash -echo 'export CGO_ENABLED=1' >> ~/.bashrc -source ~/.bashrc -``` - -## Installation +### CascadeService -### 1. Download the Binary +Handles cascade operations for data storage and retrieval. -Download the latest release from GitHub: +**Service Definition:** +```protobuf +service CascadeService { + rpc Register (stream RegisterRequest) returns (stream RegisterResponse); + rpc Download (DownloadRequest) returns (stream DownloadResponse); +} +``` -### 2. Create Configuration File +**Register Operation:** +```protobuf +message RegisterRequest { + oneof request_type { + DataChunk chunk = 1; + Metadata metadata = 2; + } +} -Create a `config.yml` file in your base directory (default: `~/.supernode/config.yml`): +message DataChunk { + bytes data = 1; +} -```yaml -# Supernode Configuration -supernode: - key_name: "mykey" # The name you'll use when creating your key - identity: "" # The address you get back after getting the key - ip_address: "0.0.0.0" - port: 4444 +message Metadata { + string task_id = 1; + string action_id = 2; +} -# Keyring Configuration -keyring: - backend: "test" # Options: test, file, os - dir: "keys" +message RegisterResponse { + SupernodeEventType event_type = 1; + string message = 2; + string tx_hash = 3; +} +``` -# P2P Network Configuration -p2p: - listen_address: "0.0.0.0" - port: 4445 - data_dir: "data/p2p" +**Download Operation:** +```protobuf +message DownloadRequest { + string action_id = 1; + string signature = 2; +} -# Lumera Chain Configuration -lumera: - grpc_addr: "localhost:9090" - chain_id: "lumera" +message DownloadResponse { + oneof response_type { + DownloadEvent event = 1; + DataChunk chunk = 2; + } +} -# RaptorQ Configuration -raptorq: - files_dir: "raptorq_files" +message DownloadEvent { + SupernodeEventType event_type = 1; + string message = 2; +} ``` -## Initialization and Key Management +**Event Types:** +- `ACTION_RETRIEVED`, `ACTION_FEE_VERIFIED`, `TOP_SUPERNODE_CHECK_PASSED` +- `METADATA_DECODED`, `DATA_HASH_VERIFIED`, `INPUT_ENCODED` +- `SIGNATURE_VERIFIED`, `RQID_GENERATED`, `RQID_VERIFIED` +- `ARTEFACTS_STORED`, `ACTION_FINALIZED`, `ARTEFACTS_DOWNLOADED` -### Initialize a Supernode +## CLI Commands -The easiest way to set up a supernode is to use the `init` command, which creates a configuration file and sets up keys in one step: +### Core Commands + +#### `supernode init` +Initialize a new supernode with interactive setup. ```bash -supernode init mykey --chain-id lumera +supernode init # Interactive setup +supernode init --force # Override existing installation +supernode init -y # Use defaults, skip prompts +supernode init --keyring-backend test # Specify keyring backend with -y ``` -This will: -1. Create a `config.yml` file in your base directory (default: `~/.supernode/config.yml`) -2. Generate a new key with the specified name -3. Update the configuration with the key's address -4. Output the key information, including the mnemonic +**Features:** +- Creates `~/.supernode/config.yml` +- Sets up keyring (test, file, or os backend) +- Key recovery from mnemonic or generates new key +- Network configuration (gRPC address, ports, chain ID) -To recover an existing key during initialization: +#### `supernode start` +Start the supernode service. ```bash -supernode init mykey --recover --mnemonic "your mnemonic words here" --chain-id lumera +supernode start # Use default config directory +supernode start -d /path/to/dir # Use custom base directory ``` -Additional options: -```bash -# Use a specific keyring backend -supernode init mykey --keyring-backend file --chain-id lumera +**Initializes:** +- P2P networking service +- gRPC server (default port 4444) +- Cascade service for data operations +- Connection to Lumera validator node -# Use a custom keyring directory -supernode init mykey --keyring-dir /path/to/keys --chain-id lumera +#### `supernode version` +Display version information. -# Use a custom base directory -supernode init -d /path/to/basedir mykey --chain-id lumera +```bash +supernode version ``` -### Manual Key Management - -If you prefer to manage keys manually, you can use the following commands: - -#### Create a New Key +### Key Management -Create a new key (use the same name you specified in your config): +#### `supernode keys list` +List all keys in the keyring with addresses. ```bash -supernode keys add mykey +supernode keys list ``` -This will output an address like: +**Output format:** ``` -lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr +NAME ADDRESS +---- ------- +mykey (selected) lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr +backup lumera1abc...xyz ``` -#### Recover an Existing Key +#### `supernode keys add ` +Create a new key (generates mnemonic). -If you have an existing mnemonic phrase: +#### `supernode keys recover ` +Recover key from existing mnemonic phrase. -```bash -supernode keys recover mykey # Use quotes if the mnemonic contains spaces, e.g., "word1 word2 word3" -``` +### Configuration Management -#### List Keys +#### `supernode config list` +Display current configuration parameters. ```bash -supernode keys list +supernode config list ``` -#### Update Configuration with Your Address +**Shows:** +- Key Name, Address, Supernode Address/Port +- Keyring Backend, Lumera gRPC Address, Chain ID -⚠️ **IMPORTANT:** After manually generating or recovering a key, you MUST update your `config.yml` with the generated address: +#### `supernode config update` +Interactive configuration parameter updates. -```yaml -supernode: - key_name: "mykey" - identity: "lumera15t2e8gjgmuqtj4jzjqfkf3tf5l8vqw69hmrzmr" # Your actual address - ip_address: "0.0.0.0" - port: 4444 -# ... rest of config +```bash +supernode config update ``` -Note: This step is done automatically when using the `init` command. +**Updatable parameters:** +- Supernode IP Address and Port +- Lumera gRPC Address and Chain ID +- Key Name (with key selection from keyring) +- Keyring Backend (with key migration) -## Running the Supernode +### Global Flags -### Start the Supernode +#### `--basedir, -d` +Specify custom base directory (default: `~/.supernode`). ```bash -supernode start -``` - -### Using Custom Configuration - -```bash -# Use specific config file -supernode start -c /path/to/config.yml - -# Use custom base directory -supernode start -d /path/to/basedir +supernode start -d /custom/path +supernode config list -d /custom/path ``` ## Configuration Parameters @@ -182,42 +202,3 @@ supernode start -d /path/to/basedir | `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 basedir (`~/.supernode/config.yml`) | -| `--basedir` | `-d` | Base directory for data storage | String | `-d /custom/path` | `~/.supernode` | - -### Usage Examples - -```bash -# Use default config.yml in basedir (~/.supernode/config.yml), with ~/.supernode as basedir -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 -``` - -⚠️ **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. - -### 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 diff --git a/go.mod b/go.mod index 5ac7fa93..17511556 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,7 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.5 // indirect github.com/benbjohnson/clock v1.3.0 // indirect @@ -127,6 +128,7 @@ require ( github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -135,6 +137,7 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index 1d335c9a..6a5f2370 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CosmWasm/wasmd v0.53.0 h1:kdaoAi20bIb4VCsxw9pRaT2g5PpIp82Wqrr9DRVN9ao= @@ -65,6 +67,7 @@ github.com/LumeraProtocol/lumera v1.6.0 h1:5I172U/f1Migt7tRxnywhz5aRKCpBOx/IMgOz github.com/LumeraProtocol/lumera v1.6.0/go.mod h1:c1M+sjewuCvxw+pznwlspUzenDJI8Y+suKB3RFKS2Wo= github.com/LumeraProtocol/rq-go v0.2.1 h1:8B3UzRChLsGMmvZ+UVbJsJj6JZzL9P9iYxbdUwGsQI4= github.com/LumeraProtocol/rq-go v0.2.1/go.mod h1:APnKCZRh1Es2Vtrd2w4kCLgAyaL5Bqrkz/BURoRJ+O8= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= @@ -205,6 +208,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -460,6 +464,7 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= @@ -498,6 +503,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -537,12 +544,14 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -554,6 +563,8 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= @@ -794,6 +805,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -862,6 +874,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -886,6 +899,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -909,11 +923,13 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -929,6 +945,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -940,6 +957,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -981,6 +999,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -999,6 +1018,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1026,6 +1046,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/supernode/cmd/config.go b/supernode/cmd/config.go new file mode 100644 index 00000000..6d857d32 --- /dev/null +++ b/supernode/cmd/config.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// configCmd represents the config command +var configCmd = &cobra.Command{ + Use: "config", + Short: "Configuration management", + Long: `Manage supernode configuration settings.`, +} + +func init() { + rootCmd.AddCommand(configCmd) +} \ No newline at end of file diff --git a/supernode/cmd/config_update.go b/supernode/cmd/config_update.go new file mode 100644 index 00000000..16bf9176 --- /dev/null +++ b/supernode/cmd/config_update.go @@ -0,0 +1,319 @@ +package cmd + +import ( + "fmt" + "path/filepath" + "strconv" + + "github.com/AlecAivazis/survey/v2" + "github.com/LumeraProtocol/supernode/pkg/keyring" + "github.com/LumeraProtocol/supernode/supernode/config" + "github.com/spf13/cobra" + cKeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" +) + +// configUpdateCmd represents the config update command +var configUpdateCmd = &cobra.Command{ + Use: "update", + Short: "Update configuration parameters", + Long: `Interactively update configuration parameters.`, + RunE: func(cmd *cobra.Command, args []string) error { + // Show parameter selection menu + paramToUpdate, err := promptParameterSelection() + if err != nil { + return fmt.Errorf("failed to select parameter: %w", err) + } + + // Update the selected parameter + switch paramToUpdate { + case "Supernode IP Address": + return updateSupernodeIP() + case "Supernode Port": + return updateSupernodePort() + case "Lumera GRPC Address": + return updateLumeraGRPC() + case "Chain ID": + return updateChainID() + case "Key Name": + return updateKeyName() + case "Keyring Backend": + return updateKeyringBackend() + } + + return nil + }, +} + +// promptParameterSelection shows menu of updatable parameters +func promptParameterSelection() (string, error) { + var param string + prompt := &survey.Select{ + Message: "Select parameter to update:", + Options: []string{ + "Supernode IP Address", + "Supernode Port", + "Lumera GRPC Address", + "Chain ID", + "Key Name", + "Keyring Backend", + }, + } + return param, survey.AskOne(prompt, ¶m) +} + +// Simple field updates +func updateSupernodeIP() error { + var newIP string + prompt := &survey.Input{ + Message: "Enter new supernode IP address:", + Default: appConfig.SupernodeConfig.IpAddress, + } + if err := survey.AskOne(prompt, &newIP); err != nil { + return err + } + + appConfig.SupernodeConfig.IpAddress = newIP + return saveConfig() +} + +func updateSupernodePort() error { + var portStr string + prompt := &survey.Input{ + Message: "Enter new supernode port:", + Default: fmt.Sprintf("%d", appConfig.SupernodeConfig.Port), + } + if err := survey.AskOne(prompt, &portStr); err != nil { + return err + } + + port, err := strconv.Atoi(portStr) + if err != nil || port < 1 || port > 65535 { + return fmt.Errorf("invalid port number: %s", portStr) + } + + appConfig.SupernodeConfig.Port = uint16(port) + return saveConfig() +} + +func updateLumeraGRPC() error { + var newAddr string + prompt := &survey.Input{ + Message: "Enter new Lumera GRPC address:", + Default: appConfig.LumeraClientConfig.GRPCAddr, + } + if err := survey.AskOne(prompt, &newAddr); err != nil { + return err + } + + appConfig.LumeraClientConfig.GRPCAddr = newAddr + return saveConfig() +} + +func updateChainID() error { + var newChainID string + prompt := &survey.Input{ + Message: "Enter new chain ID:", + Default: appConfig.LumeraClientConfig.ChainID, + } + if err := survey.AskOne(prompt, &newChainID, survey.WithValidator(survey.Required)); err != nil { + return err + } + + appConfig.LumeraClientConfig.ChainID = newChainID + return saveConfig() +} + +// Key name update with key selection +func updateKeyName() error { + // Initialize keyring + kr, err := initKeyringFromConfig(appConfig) + if err != nil { + return fmt.Errorf("failed to initialize keyring: %w", err) + } + + // Get existing keys + keyInfos, err := kr.List() + if err != nil { + return fmt.Errorf("failed to list keys: %w", err) + } + + return selectKeyFromKeyring(kr, keyInfos) +} + +func addNewKey(kr cKeyring.Keyring) error { + // Prompt for key name + var keyName string + namePrompt := &survey.Input{ + Message: "Enter key name:", + Help: "Alphanumeric characters and underscores only", + } + if err := survey.AskOne(namePrompt, &keyName, survey.WithValidator(survey.Required)); err != nil { + return err + } + + // Prompt for mnemonic + var mnemonic string + mnemonicPrompt := &survey.Password{ + Message: "Enter mnemonic phrase:", + Help: "Space-separated words (typically 12 or 24 words)", + } + if err := survey.AskOne(mnemonicPrompt, &mnemonic, survey.WithValidator(survey.Required)); err != nil { + return err + } + + // Process and validate mnemonic + processedMnemonic, err := processAndValidateMnemonic(mnemonic) + if err != nil { + return err + } + + // Recover account + _, err = keyring.RecoverAccountFromMnemonic(kr, keyName, processedMnemonic) + if err != nil { + return fmt.Errorf("failed to recover account: %w", err) + } + + // Update config + return selectExistingKey(kr, keyName) +} + +func selectExistingKey(kr cKeyring.Keyring, keyName string) error { + // Get address + addr, err := getAddressFromKeyName(kr, keyName) + if err != nil { + return fmt.Errorf("failed to get address: %w", err) + } + + // Update config + appConfig.SupernodeConfig.KeyName = keyName + appConfig.SupernodeConfig.Identity = addr.String() + + fmt.Printf("Updated key to: %s (%s)\n", keyName, addr.String()) + return saveConfig() +} + +// Keyring backend update with warning +func updateKeyringBackend() error { + // Show warning + fmt.Println("⚠️ WARNING: Changing keyring backend will switch to a different keyring.") + fmt.Println("You will need to select a key from the new keyring or recover one.") + + var proceed bool + confirmPrompt := &survey.Confirm{ + Message: "Do you want to continue?", + Default: false, + } + if err := survey.AskOne(confirmPrompt, &proceed); err != nil { + return err + } + + if !proceed { + fmt.Println("Operation cancelled.") + return nil + } + + // Select new backend + var backend string + prompt := &survey.Select{ + Message: "Choose new keyring backend:", + Options: []string{"os", "file", "test"}, + Default: appConfig.KeyringConfig.Backend, + } + if err := survey.AskOne(prompt, &backend); err != nil { + return err + } + + // Update keyring backend in config + appConfig.KeyringConfig.Backend = backend + + // Save config with new keyring backend + if err := saveConfig(); err != nil { + return err + } + + fmt.Printf("Updated keyring backend to: %s\n", backend) + + // Reload config to get the new keyring settings + cfgFile := filepath.Join(baseDir, DefaultConfigFile) + reloadedConfig, err := config.LoadConfig(cfgFile, baseDir) + if err != nil { + return fmt.Errorf("failed to reload config: %w", err) + } + appConfig = reloadedConfig + + // Initialize new keyring + kr, err := initKeyringFromConfig(appConfig) + if err != nil { + return fmt.Errorf("failed to initialize new keyring: %w", err) + } + + // Check for existing keys in new keyring + keyInfos, err := kr.List() + if err != nil { + return fmt.Errorf("failed to list keys in new keyring: %w", err) + } + + // Show available keys and let user select + return selectKeyFromNewKeyring(kr, keyInfos) +} + +func selectKeyFromNewKeyring(kr cKeyring.Keyring, keyInfos []*cKeyring.Record) error { + if len(keyInfos) == 0 { + fmt.Println("No existing keys found in the new keyring.") + } else { + fmt.Printf("Found %d key(s) in the new keyring.\n", len(keyInfos)) + } + return selectKeyFromKeyring(kr, keyInfos) +} + +func selectKeyFromKeyring(kr cKeyring.Keyring, keyInfos []*cKeyring.Record) error { + // Build options list with display format + options := []string{} + + // Add existing keys + for _, info := range keyInfos { + addr, err := info.GetAddress() + if err != nil { + continue + } + options = append(options, fmt.Sprintf("%s (%s)", info.Name, addr.String())) + } + + // Always add option to recover new key + options = append(options, "Add new key (recover from mnemonic)") + + // Show selection + var selectedIndex int + prompt := &survey.Select{ + Message: "Select key:", + Options: options, + } + if err := survey.AskOne(prompt, &selectedIndex); err != nil { + return err + } + + // Handle selection using index instead of parsing strings + if selectedIndex == len(keyInfos) { + // Last option is "Add new key" + return addNewKey(kr) + } else { + // Use the selected index to get the actual key info + selectedKey := keyInfos[selectedIndex] + return selectExistingKey(kr, selectedKey.Name) + } +} + +// saveConfig saves the updated configuration +func saveConfig() error { + cfgFile := filepath.Join(baseDir, DefaultConfigFile) + if err := config.SaveConfig(appConfig, cfgFile); err != nil { + return fmt.Errorf("failed to save config: %w", err) + } + + fmt.Printf("Configuration updated successfully: %s\n", cfgFile) + return nil +} + +func init() { + configCmd.AddCommand(configUpdateCmd) +} \ No newline at end of file diff --git a/supernode/cmd/init.go b/supernode/cmd/init.go index f9f7aa23..22c4e9a5 100644 --- a/supernode/cmd/init.go +++ b/supernode/cmd/init.go @@ -4,164 +4,388 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "github.com/AlecAivazis/survey/v2" "github.com/LumeraProtocol/supernode/pkg/keyring" "github.com/LumeraProtocol/supernode/supernode/config" + consmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/spf13/cobra" ) var ( - initKeyName string - initRecover bool - initMnemonic string - initKeyringBackend string - initKeyringDir string - initChainID string + forceInit bool + skipInteractive bool + keyringBackendFlag string +) + +// Default configuration values +const ( + DefaultKeyringBackend = "test" + DefaultKeyName = "" + DefaultSupernodeAddr = "0.0.0.0" + DefaultSupernodePort = 4444 + DefaultLumeraGRPC = "localhost:9090" + DefaultChainID = "lumera" ) +// InitInputs holds all user inputs for initialization +type InitInputs struct { + KeyringBackend string + KeyName string + ShouldRecover bool + Mnemonic string + SupernodeAddr string + SupernodePort int + LumeraGRPC string + ChainID string +} + // initCmd represents the init command var initCmd = &cobra.Command{ - Use: "init ", + Use: "init", Short: "Initialize a new supernode", Long: `Initialize a new supernode by creating a configuration file and setting up keys. -This command will: -1. Create a config.yml file at the default location (~/.supernode) or at a location specified with -d -2. Create a new key or recover an existing one -3. Update the config.yml file with the key's address -4. Output the key information +This command will guide you through an interactive setup process to: +1. Create a config.yml file at ~/.supernode +2. Select keyring backend (test, file, or os) +3. Recover an existing key from mnemonic +4. Configure network settings (GRPC address, port, chain ID) Example: - supernode init mykey --chain-id lumera - supernode init mykey --recover --mnemonic "your mnemonic words here" --chain-id lumera - supernode init mykey --keyring-backend file --chain-id lumera`, - Args: cobra.ExactArgs(1), + supernode init + supernode init --force # Override existing installation + supernode init -y # Use default values, skip interactive prompts`, + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - // Get key name from positional argument - initKeyName = args[0] + // Setup base directory + if err := setupBaseDirectory(); err != nil { + return err + } - // Validate required parameters - if initChainID == "" { - return fmt.Errorf("chain ID is required (--chain-id)") + // Get user inputs through interactive prompts or use defaults + inputs, err := gatherUserInputs() + if err != nil { + return err } - // Setup base directory - var err error - if baseDir == "" { - homeDir, err := os.UserHomeDir() + // Create and setup configuration + if err := createAndSetupConfig(inputs.KeyName, inputs.ChainID, inputs.KeyringBackend); err != nil { + return err + } + + // Setup keyring and handle key creation/recovery (skip if using -y flag) + var address string + if !skipInteractive { + address, err = setupKeyring(inputs.KeyName, inputs.ShouldRecover, inputs.Mnemonic) if err != nil { - return fmt.Errorf("failed to get home directory: %w", err) + return err } - baseDir = filepath.Join(homeDir, DefaultBaseDir) } - // Create base directory if it doesn't exist - if err := os.MkdirAll(baseDir, 0700); err != nil { - return fmt.Errorf("failed to create base directory: %w", err) + // Update config with gathered settings and save + if err := updateAndSaveConfig(address, inputs.SupernodeAddr, inputs.SupernodePort, inputs.LumeraGRPC, inputs.ChainID); err != nil { + return err } - // Set config file path - if cfgFile == "" { - cfgFile = filepath.Join(baseDir, DefaultConfigFile) + // Print success message + printSuccessMessage() + return nil + }, +} + +// setupBaseDirectory handles base directory creation and validation +func setupBaseDirectory() error { + if baseDir == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) } + baseDir = filepath.Join(homeDir, DefaultBaseDir) + } - fmt.Printf("Using base directory: %s\n", baseDir) - fmt.Printf("Using config file: %s\n", cfgFile) + // Check if base directory already exists + if _, err := os.Stat(baseDir); err == nil && !forceInit { + return fmt.Errorf("supernode directory already exists at %s\nUse --force to overwrite or remove the directory manually", baseDir) + } - // Create default configuration - appConfig = config.CreateDefaultConfig(initKeyName, "", initChainID, initKeyringBackend, initKeyringDir) - appConfig.BaseDir = baseDir + // If force flag is used, clean up config file and keys directory + if forceInit { + cfgFile := filepath.Join(baseDir, DefaultConfigFile) + if err := os.Remove(cfgFile); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove existing config file: %w", err) + } - // Create directories - if err := appConfig.EnsureDirs(); err != nil { - return fmt.Errorf("failed to create directories: %w", err) + keysDir := filepath.Join(baseDir, "keys") + if err := os.RemoveAll(keysDir); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove existing keys directory: %w", err) } - kr, err := initKeyringFromConfig(appConfig) - if err != nil { - return fmt.Errorf("failed to initialize keyring: %w", err) + fmt.Println("Cleaned up existing config file and keys directory") + } + + // Create base directory if it doesn't exist + if err := os.MkdirAll(baseDir, 0700); err != nil { + return fmt.Errorf("failed to create base directory: %w", err) + } + + fmt.Printf("BaseDirectory: %s\n", baseDir) + return nil +} + +// gatherUserInputs collects all user inputs through interactive prompts or uses defaults +func gatherUserInputs() (InitInputs, error) { + if skipInteractive { + // Use default values when -y flag is set + fmt.Println("Using default configuration values...") + backend := DefaultKeyringBackend + if keyringBackendFlag != "" { + backend = keyringBackendFlag } + return InitInputs{ + KeyringBackend: backend, + KeyName: "", + ShouldRecover: false, + Mnemonic: "", + SupernodeAddr: DefaultSupernodeAddr, + SupernodePort: DefaultSupernodePort, + LumeraGRPC: DefaultLumeraGRPC, + ChainID: DefaultChainID, + }, nil + } - var address string + var inputs InitInputs + var err error - // Create or recover key - if initRecover { - // Recover key from mnemonic - if initMnemonic == "" { - return fmt.Errorf("mnemonic is required when --recover is specified") - } + // Interactive setup + inputs.KeyringBackend, err = promptKeyringBackend() + if err != nil { + return InitInputs{}, fmt.Errorf("failed to select keyring backend: %w", err) + } - // Process and validate mnemonic using helper function - processedMnemonic, err := processAndValidateMnemonic(initMnemonic) - if err != nil { - fmt.Printf("Warning: %v\n", err) - // Continue with original mnemonic if validation fails - processedMnemonic = initMnemonic - } + inputs.KeyName, inputs.ShouldRecover, inputs.Mnemonic, err = promptKeyManagement() + if err != nil { + return InitInputs{}, fmt.Errorf("failed to configure key management: %w", err) + } - info, err := keyring.RecoverAccountFromMnemonic(kr, initKeyName, processedMnemonic) - if err != nil { - return fmt.Errorf("failed to recover account: %w", err) - } + inputs.SupernodeAddr, inputs.SupernodePort, inputs.LumeraGRPC, inputs.ChainID, err = promptNetworkConfig() + if err != nil { + return InitInputs{}, fmt.Errorf("failed to configure network settings: %w", err) + } - addr, err := getAddressFromKeyName(kr, initKeyName) - if err != nil { - return fmt.Errorf("failed to get address: %w", err) - } - address = addr.String() - - fmt.Println("Key recovered successfully!") - fmt.Printf("- Name: %s\n", info.Name) - fmt.Printf("- Address: %s\n", address) - } else { - // Generate mnemonic and create new account - var mnemonic string - mnemonic, info, err := keyring.CreateNewAccount(kr, initKeyName) - if err != nil { - return fmt.Errorf("failed to create new account: %w", err) - } + return inputs, nil +} - addr, err := getAddressFromKeyName(kr, initKeyName) - if err != nil { - return fmt.Errorf("failed to get address: %w", err) - } - address = addr.String() - - fmt.Println("Key generated successfully!") - fmt.Printf("- Name: %s\n", info.Name) - fmt.Printf("- Address: %s\n", address) - fmt.Printf("- Mnemonic: %s\n", mnemonic) - fmt.Println("\nIMPORTANT: Write down the mnemonic and keep it in a safe place.") - fmt.Println("The mnemonic is the only way to recover your account if you forget your password.") - } +// createAndSetupConfig creates default configuration and necessary directories +func createAndSetupConfig(keyName, chainID, keyringBackend string) error { + // Set config file path + cfgFile := filepath.Join(baseDir, DefaultConfigFile) + + fmt.Printf("Using config file: %s\n", cfgFile) + + // Create default configuration + appConfig = config.CreateDefaultConfig(keyName, "", chainID, keyringBackend, "") + appConfig.BaseDir = baseDir - // Update config with address - appConfig.SupernodeConfig.Identity = address + // Create directories + if err := appConfig.EnsureDirs(); err != nil { + return fmt.Errorf("failed to create directories: %w", err) + } - // Save config - if err := config.SaveConfig(appConfig, cfgFile); err != nil { - return fmt.Errorf("failed to save config: %w", err) + return nil +} + +// setupKeyring initializes keyring and handles key creation or recovery +func setupKeyring(keyName string, shouldRecover bool, mnemonic string) (string, error) { + kr, err := initKeyringFromConfig(appConfig) + if err != nil { + return "", fmt.Errorf("failed to initialize keyring: %w", err) + } + + var address string + + if shouldRecover { + address, err = recoverExistingKey(kr, keyName, mnemonic) + if err != nil { + return "", err } + } else { + address, err = createNewKey(kr, keyName) + if err != nil { + return "", err + } + } - fmt.Printf("\nConfiguration saved to %s\n", cfgFile) - fmt.Println("\nYour supernode has been initialized successfully!") - fmt.Println("You can now start your supernode with:") - fmt.Println(" supernode start") + return address, nil +} - return nil - }, +// recoverExistingKey handles the recovery of an existing key from mnemonic +func recoverExistingKey(kr consmoskeyring.Keyring, keyName, mnemonic string) (string, error) { + // Process and validate mnemonic using helper function + processedMnemonic, err := processAndValidateMnemonic(mnemonic) + if err != nil { + fmt.Printf("Warning: %v\n", err) + // Continue with original mnemonic if validation fails + processedMnemonic = mnemonic + } + + info, err := keyring.RecoverAccountFromMnemonic(kr, keyName, processedMnemonic) + if err != nil { + return "", fmt.Errorf("failed to recover account: %w", err) + } + + addr, err := getAddressFromKeyName(kr, keyName) + if err != nil { + return "", fmt.Errorf("failed to get address: %w", err) + } + address := addr.String() + + fmt.Printf("Key recovered successfully! Name: %s, Address: %s\n", info.Name, address) + return address, nil +} + +// createNewKey handles the creation of a new key +func createNewKey(kr consmoskeyring.Keyring, keyName string) (string, error) { + // Generate mnemonic and create new account + keyMnemonic, info, err := keyring.CreateNewAccount(kr, keyName) + if err != nil { + return "", fmt.Errorf("failed to create new account: %w", err) + } + + addr, err := getAddressFromKeyName(kr, keyName) + if err != nil { + return "", fmt.Errorf("failed to get address: %w", err) + } + address := addr.String() + + fmt.Printf("Key generated successfully! Name: %s, Address: %s, Mnemonic: %s\n", info.Name, address, keyMnemonic) + fmt.Println("\nIMPORTANT: Write down the mnemonic and keep it in a safe place.") + fmt.Println("The mnemonic is the only way to recover your account if you forget your password.") + + return address, nil +} + +// updateAndSaveConfig updates the configuration with network settings and saves it +func updateAndSaveConfig(address, supernodeAddr string, supernodePort int, lumeraGrpcAddr string, chainID string) error { + // Update config with address and network settings + appConfig.SupernodeConfig.Identity = address + appConfig.SupernodeConfig.IpAddress = supernodeAddr + appConfig.SupernodeConfig.Port = uint16(supernodePort) + appConfig.LumeraClientConfig.GRPCAddr = lumeraGrpcAddr + appConfig.LumeraClientConfig.ChainID = chainID + + // Save config + cfgFile := filepath.Join(baseDir, DefaultConfigFile) + if err := config.SaveConfig(appConfig, cfgFile); err != nil { + return fmt.Errorf("failed to save config: %w", err) + } + + fmt.Printf("\nConfiguration saved to %s\n", cfgFile) + return nil +} + +// printSuccessMessage displays the final success message +func printSuccessMessage() { + fmt.Println("\nYour supernode has been initialized successfully!") + fmt.Println("You can now start your supernode with:") + fmt.Println(" supernode start") +} + +// Interactive prompt functions +func promptKeyringBackend() (string, error) { + var backend string + prompt := &survey.Select{ + Message: "Choose keyring backend:", + Options: []string{"os", "file", "test"}, + Default: "os", + Help: "os: OS keyring (most secure), file: encrypted file, test: unencrypted (dev only)", + } + return backend, survey.AskOne(prompt, &backend) +} + +func promptKeyManagement() (keyName string, shouldRecover bool, mnemonic string, err error) { + shouldRecover = true + + // Key name input with validation + keyNamePrompt := &survey.Input{ + Message: "Enter key name:", + Help: "Alphanumeric characters and underscores only", + } + err = survey.AskOne(keyNamePrompt, &keyName, survey.WithValidator(survey.Required)) + if err != nil { + return "", false, "", err + } + + // Mnemonic input for recovery + mnemonicPrompt := &survey.Password{ + Message: "Enter your mnemonic phrase:", + Help: "Space-separated words (typically 12 or 24 words)", + } + err = survey.AskOne(mnemonicPrompt, &mnemonic, survey.WithValidator(survey.Required)) + if err != nil { + return "", false, "", err + } + + return keyName, shouldRecover, mnemonic, nil +} + +func promptNetworkConfig() (supernodeAddr string, supernodePort int, lumeraGrpcAddr string, chainID string, err error) { + // Supernode IP address + supernodePrompt := &survey.Input{ + Message: "Enter supernode IP address:", + Default: DefaultSupernodeAddr, + } + err = survey.AskOne(supernodePrompt, &supernodeAddr) + if err != nil { + return "", 0, "", "", err + } + + // Supernode port + var portStr string + supernodePortPrompt := &survey.Input{ + Message: "Enter supernode port:", + Default: fmt.Sprintf("%d", DefaultSupernodePort), + } + err = survey.AskOne(supernodePortPrompt, &portStr) + if err != nil { + return "", 0, "", "", err + } + + supernodePort, err = strconv.Atoi(portStr) + if err != nil || supernodePort < 1 || supernodePort > 65535 { + return "", 0, "", "", fmt.Errorf("invalid supernode port: %s", portStr) + } + + // Lumera GRPC address (full address with port) + lumeraPrompt := &survey.Input{ + Message: "Enter Lumera GRPC address:", + Default: DefaultLumeraGRPC, + } + err = survey.AskOne(lumeraPrompt, &lumeraGrpcAddr) + if err != nil { + return "", 0, "", "", err + } + + // Chain ID + chainPrompt := &survey.Input{ + Message: "Enter chain ID:", + Default: DefaultChainID, + } + err = survey.AskOne(chainPrompt, &chainID, survey.WithValidator(survey.Required)) + if err != nil { + return "", 0, "", "", err + } + + return supernodeAddr, supernodePort, lumeraGrpcAddr, chainID, nil } func init() { rootCmd.AddCommand(initCmd) // Add flags - initCmd.Flags().BoolVar(&initRecover, "recover", false, "Recover key from mnemonic") - initCmd.Flags().StringVar(&initMnemonic, "mnemonic", "", "Mnemonic for key recovery (required if --recover is specified)") - initCmd.Flags().StringVar(&initKeyringBackend, "keyring-backend", "", "Keyring backend (test, file, os)") - initCmd.Flags().StringVar(&initKeyringDir, "keyring-dir", "", "Directory to store keyring files") - initCmd.Flags().StringVar(&initChainID, "chain-id", "", "Chain ID (required)") - - // Mark required flags - initCmd.MarkFlagRequired("chain-id") + initCmd.Flags().BoolVar(&forceInit, "force", false, "Force initialization, overwriting existing directory") + initCmd.Flags().BoolVarP(&skipInteractive, "yes", "y", false, "Skip interactive prompts and use default values") + initCmd.Flags().StringVar(&keyringBackendFlag, "keyring-backend", "", "Keyring backend to use with -y flag (test, file, os)") } diff --git a/supernode/cmd/keys.go b/supernode/cmd/keys.go index 82cde93c..11f4de9f 100644 --- a/supernode/cmd/keys.go +++ b/supernode/cmd/keys.go @@ -9,7 +9,7 @@ var keysCmd = &cobra.Command{ Use: "keys", Short: "Manage keys", Long: `Manage keys for the Supernode. -This command provides subcommands for adding, recovering, and listing keys.`, +This command provides subcommands for recovering and listing keys.`, } func init() { diff --git a/supernode/cmd/keys_add.go b/supernode/cmd/keys_add.go deleted file mode 100644 index 2c53fde7..00000000 --- a/supernode/cmd/keys_add.go +++ /dev/null @@ -1,68 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/LumeraProtocol/supernode/pkg/keyring" -) - -// keysAddCmd represents the add command for creating a new key -var keysAddCmd = &cobra.Command{ - Use: "add [name]", - Short: "Add a new key", - Long: `Add a new key with the given name. -This command will generate a new mnemonic and derive a key pair from it. -The generated key pair will be stored in the keyring. - -Example: - supernode keys add mykey`, - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - var keyName string - if len(args) > 0 { - keyName = args[0] - } else { - // Use the key_name from config file as default - keyName = appConfig.SupernodeConfig.KeyName - } - - if keyName == "" { - return fmt.Errorf("key name is required") - } - - // Initialize keyring using helper function - kr, err := initKeyringFromConfig(appConfig) - if err != nil { - return fmt.Errorf("failed to initialize keyring: %w", err) - } - - // Generate mnemonic and create new account - // Default to 256 bits of entropy (24 words) - mnemonic, info, err := keyring.CreateNewAccount(kr, keyName) - if err != nil { - return fmt.Errorf("failed to create new account: %w", err) - } - - // Get address using helper function - address, err := getAddressFromKeyName(kr, keyName) - if err != nil { - return fmt.Errorf("failed to get address: %w", err) - } - - // Print results - fmt.Println("Key generated successfully!") - fmt.Printf("- Name: %s\n", info.Name) - fmt.Printf("- Address: %s\n", address.String()) - fmt.Printf("- Mnemonic: %s\n", mnemonic) - fmt.Println("\nIMPORTANT: Write down the mnemonic and keep it in a safe place.") - fmt.Println("The mnemonic is the only way to recover your account if you forget your password.") - - return nil - }, -} - -func init() { - keysCmd.AddCommand(keysAddCmd) -} diff --git a/supernode/cmd/keys_add_test.go b/supernode/cmd/keys_add_test.go deleted file mode 100644 index 6812ee93..00000000 --- a/supernode/cmd/keys_add_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package cmd_test - -import ( - "bytes" - "testing" - - "github.com/LumeraProtocol/supernode/pkg/keyring" - "github.com/spf13/cobra" - "github.com/stretchr/testify/require" -) - -func TestKeysAddCmd_RunE(t *testing.T) { - cmd := getTestKeysAddCmd(t) - - tests := []struct { - name string - args []string - wantErr bool - }{ - {"no_name_arg", []string{}, false}, - {"with_name_arg", []string{"testkey"}, false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - buf := new(bytes.Buffer) - cmd.SetOut(buf) - cmd.SetArgs(tt.args) - - err := cmd.Execute() - if (err != nil) != tt.wantErr { - t.Errorf("unexpected error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -// getTestKeysAddCmd initializes and returns a test instance of the cobra.Command for keys add. -func getTestKeysAddCmd(t *testing.T) *cobra.Command { - return &cobra.Command{ - Use: "add", - RunE: func(cmd *cobra.Command, args []string) error { - name := "testkey" - if len(args) > 0 { - name = args[0] - } - kr, err := keyring.InitKeyring("test", "/tmp") - require.NoError(t, err) - _, _, err = keyring.CreateNewAccount(kr, name) - return err - }, - } -} diff --git a/supernode/cmd/keys_list.go b/supernode/cmd/keys_list.go index df8f4bb0..1dde876c 100644 --- a/supernode/cmd/keys_list.go +++ b/supernode/cmd/keys_list.go @@ -39,14 +39,13 @@ Example: // Check if we found any keys if len(keyInfos) == 0 { fmt.Println("No keys found in keyring") - fmt.Printf("\nCreate a new key with:\n supernode keys add \n") return nil } // Format output with tabwriter for aligned columns w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "NAME\tTYPE\tADDRESS\tPUBLIC KEY") - fmt.Fprintln(w, "----\t----\t-------\t----------") + fmt.Fprintln(w, "NAME\tADDRESS") + fmt.Fprintln(w, "----\t-------") // Print key information for _, info := range keyInfos { @@ -56,24 +55,13 @@ Example: return fmt.Errorf("failed to get address for key %s: %w", info.Name, err) } - // Get public key - pubKey, err := info.GetPubKey() - if err != nil { - return fmt.Errorf("failed to get public key for key %s: %w", info.Name, err) - } - - // Highlight the default key (from config) + // Check if this key is selected in config name := info.Name - if name == appConfig.SupernodeConfig.KeyName { - name = name + " (default)" + if name == appConfig.SupernodeConfig.KeyName && address.String() == appConfig.SupernodeConfig.Identity { + name = name + " (selected)" } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", - name, - info.GetType(), - address.String(), - pubKey.String()[:20]+"...", // Show truncated public key - ) + fmt.Fprintf(w, "%s\t%s\n", name, address.String()) } w.Flush() diff --git a/supernode/cmd/keys_recover.go b/supernode/cmd/keys_recover.go index d9ab6659..2b0a83fd 100644 --- a/supernode/cmd/keys_recover.go +++ b/supernode/cmd/keys_recover.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/AlecAivazis/survey/v2" "github.com/spf13/cobra" "github.com/LumeraProtocol/supernode/pkg/keyring" @@ -50,11 +51,20 @@ Example: // If mnemonic wasn't provided as a flag, prompt for it if mnemonic == "" { - fmt.Print("Enter your mnemonic: ") - reader := bufio.NewReader(os.Stdin) - mnemonic, err = reader.ReadString('\n') + // Try interactive prompt first (better UX) + mnemonicPrompt := &survey.Password{ + Message: "Enter your mnemonic phrase:", + Help: "Space-separated words (typically 12 or 24 words)", + } + err = survey.AskOne(mnemonicPrompt, &mnemonic, survey.WithValidator(survey.Required)) if err != nil { - return fmt.Errorf("failed to read mnemonic: %w", err) + // Fall back to standard input for backwards compatibility + fmt.Print("Enter your mnemonic: ") + reader := bufio.NewReader(os.Stdin) + mnemonic, err = reader.ReadString('\n') + if err != nil { + return fmt.Errorf("failed to read mnemonic: %w", err) + } } } diff --git a/supernode/cmd/list_config.go b/supernode/cmd/list_config.go new file mode 100644 index 00000000..8ec0d53f --- /dev/null +++ b/supernode/cmd/list_config.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + "os" + "text/tabwriter" + + "github.com/spf13/cobra" +) + +// configListCmd represents the config list command +var configListCmd = &cobra.Command{ + Use: "list", + Short: "Display current configuration", + Long: `Display the current configuration parameters.`, + RunE: func(cmd *cobra.Command, args []string) error { + // Format output with tabwriter for aligned columns + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "PARAMETER\tVALUE") + fmt.Fprintln(w, "---------\t-------------") + + // Display configuration parameters + fmt.Fprintf(w, "Key Name\t%s\n", appConfig.SupernodeConfig.KeyName) + fmt.Fprintf(w, "Address\t%s\n", appConfig.SupernodeConfig.Identity) + fmt.Fprintf(w, "Supernode Address\t%s\n", appConfig.SupernodeConfig.IpAddress) + fmt.Fprintf(w, "Supernode Port\t%d\n", appConfig.SupernodeConfig.Port) + fmt.Fprintf(w, "Keyring Backend\t%s\n", appConfig.KeyringConfig.Backend) + fmt.Fprintf(w, "Lumera GRPC Address\t%s\n", appConfig.LumeraClientConfig.GRPCAddr) + fmt.Fprintf(w, "Chain ID\t%s\n", appConfig.LumeraClientConfig.ChainID) + + w.Flush() + return nil + }, +} + +func init() { + configCmd.AddCommand(configListCmd) +} diff --git a/supernode/cmd/root.go b/supernode/cmd/root.go index 1653e6fd..e3a2605a 100644 --- a/supernode/cmd/root.go +++ b/supernode/cmd/root.go @@ -10,7 +10,6 @@ import ( ) var ( - cfgFile string baseDir string appConfig *config.Config ) @@ -26,29 +25,9 @@ func fileExists(path string) bool { return err == nil } -// findConfigFile searches for config files in base directory only -func findConfigFile(baseDirPath string) string { - // If config file is explicitly specified, use it - if cfgFile != "" { - return cfgFile - } - - // Only search in base directory as default - if baseDirPath != "" { - searchPaths := []string{ - filepath.Join(baseDirPath, DefaultConfigFile), - filepath.Join(baseDirPath, "config.yaml"), - } - - // Return first existing config file - for _, path := range searchPaths { - if fileExists(path) { - return path - } - } - } - - return "" +// getConfigFile returns the config file path in base directory +func getConfigFile(baseDirPath string) string { + return filepath.Join(baseDirPath, DefaultConfigFile) } func setupBaseDir() (string, error) { @@ -64,26 +43,14 @@ func setupBaseDir() (string, error) { return filepath.Join(homeDir, DefaultBaseDir), nil } -// logConfig logs information about config and base directory -func logConfig(configPath, baseDirPath string) { - // For config file - 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) - } - - fmt.Printf("Using base directory: %s\n", baseDirPath) -} - var rootCmd = &cobra.Command{ Use: "supernode", Short: "Lumera CLI tool for key management", Long: `A command line tool for managing Lumera blockchain keys. This application allows you to create and recover keys using mnemonics.`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - // Skip config loading for help command and init command - if cmd.Name() == "help" || cmd.Name() == "init" { + // Skip config loading for help command, init command, and version command + if cmd.Name() == "help" || cmd.Name() == "init" || cmd.Name() == "version" { // For init command, we still need to set up the base directory if cmd.Name() == "init" { var err error @@ -102,15 +69,12 @@ This application allows you to create and recover keys using mnemonics.`, return err } - // Find config file (only searches in base directory by default) - cfgFile = findConfigFile(baseDir) - if cfgFile == "" { + // Get config file path + cfgFile := getConfigFile(baseDir) + if !fileExists(cfgFile) { return fmt.Errorf("no config file found in base directory (%s)", baseDir) } - // Log configuration - logConfig(cfgFile, baseDir) - // Load configuration appConfig, err = config.LoadConfig(cfgFile, baseDir) if err != nil { @@ -131,8 +95,6 @@ func Execute() { func init() { // Use default values in flag descriptions - rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", - fmt.Sprintf("Config file path (default is ~/%s/%s)", DefaultBaseDir, DefaultConfigFile)) rootCmd.PersistentFlags().StringVarP(&baseDir, "basedir", "d", "", fmt.Sprintf("Base directory for all data (default is ~/%s)", DefaultBaseDir)) } diff --git a/supernode/cmd/start.go b/supernode/cmd/start.go index e7e1511c..ddeb4168 100644 --- a/supernode/cmd/start.go +++ b/supernode/cmd/start.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/signal" + "path/filepath" "syscall" "github.com/LumeraProtocol/supernode/p2p" @@ -50,6 +51,7 @@ The supernode will connect to the Lumera network and begin participating in the ctx := logtrace.CtxWithCorrelationID(context.Background(), "supernode-start") // Log configuration info + cfgFile := filepath.Join(baseDir, DefaultConfigFile) logtrace.Info(ctx, "Starting supernode with configuration", logtrace.Fields{"config_file": cfgFile, "keyring_dir": appConfig.GetKeyringDir(), "key_name": appConfig.SupernodeConfig.KeyName}) // Initialize keyring diff --git a/supernode/cmd/version.go b/supernode/cmd/version.go index 517a0c13..e6d085d8 100644 --- a/supernode/cmd/version.go +++ b/supernode/cmd/version.go @@ -19,7 +19,7 @@ var versionCmd = &cobra.Command{ Short: "Show version information", Long: `Display version information for the supernode.`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println(Version) + fmt.Printf("Version: %s\n", Version) }, } diff --git a/supernode/config/config.go b/supernode/config/config.go index c77af4d7..ed1f89b6 100644 --- a/supernode/config/config.go +++ b/supernode/config/config.go @@ -110,11 +110,6 @@ func LoadConfig(filename string, baseDir string) (*Config, error) { return nil, fmt.Errorf("error getting absolute path for config file: %w", err) } - logtrace.Info(ctx, "Loading configuration", logtrace.Fields{ - "path": absPath, - "baseDir": baseDir, - }) - if _, err := os.Stat(absPath); os.IsNotExist(err) { return nil, fmt.Errorf("config file %s does not exist", absPath) } diff --git a/tests/scripts/setup-supernodes.sh b/tests/scripts/setup-supernodes.sh index 390933d6..da643f5a 100755 --- a/tests/scripts/setup-supernodes.sh +++ b/tests/scripts/setup-supernodes.sh @@ -60,9 +60,9 @@ else fi # Check if config already exists - if [ ! -f "$DATA_DIR/config.yaml" ]; then + if [ ! -f "$DATA_DIR/config.yml" ]; then info "Copying config file from $CONFIG_FILE to $DATA_DIR..." - cp "$CONFIG_FILE" "$DATA_DIR/config.yaml" || error "Failed to copy config file" + cp "$CONFIG_FILE" "$DATA_DIR/config.yml" || error "Failed to copy config file" success "Config file copied successfully" else info "Config file already exists in $DATA_DIR, skipping copy..." @@ -93,8 +93,8 @@ fi MNEMONIC="${MNEMONICS[$i]}" info "Recovering key: $KEY_NAME" - # Pipe the mnemonic directly to the command and specify the base directory - echo "$MNEMONIC" | "$DATA_DIR/supernode" keys recover "$KEY_NAME" --config="$DATA_DIR/config.yaml" --basedir="$DATA_DIR" 2>/dev/null || { + # Use mnemonic flag and specify the base directory + "$DATA_DIR/supernode" keys recover "$KEY_NAME" --mnemonic="$MNEMONIC" --basedir="$DATA_DIR" 2>/dev/null || { warning "Key recovery for $KEY_NAME may have failed if the key already exists. This is not necessarily an error." } done @@ -144,7 +144,7 @@ setup_secondary() { # Copy config file to the new directory info "Copying config file to $NEW_DIR..." - cp "$CONFIG_FILE" "$NEW_DIR/config.yaml" || error "Failed to copy config file to $NEW_DIR" + cp "$CONFIG_FILE" "$NEW_DIR/config.yml" || error "Failed to copy config file to $NEW_DIR" # Copy keyring from original directory to new directory if [ -d "$ORIGINAL_DIR/keys" ]; then diff --git a/tests/system/supernode-utils.go b/tests/system/supernode-utils.go index cfb3966a..089c94bd 100644 --- a/tests/system/supernode-utils.go +++ b/tests/system/supernode-utils.go @@ -27,7 +27,6 @@ func StartAllSupernodes(t *testing.T) []*exec.Cmd { // Start each supernode for i, dataDir := range dataDirs { binPath := filepath.Join(dataDir, "supernode") - configPath := filepath.Join(dataDir, "config.yaml") // Ensure the binary exists if _, err := os.Stat(binPath); os.IsNotExist(err) { @@ -37,7 +36,6 @@ func StartAllSupernodes(t *testing.T) []*exec.Cmd { // Build and start the command cmd := exec.Command(binPath, "start", - "--config", configPath, "--basedir", dataDir, )