-
Notifications
You must be signed in to change notification settings - Fork 0
Supernode client #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Supernode client #108
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f436b8f
Supernode client
akobrin1 5d5803f
Introduced structured CLI with support for secure gRPC reflection and…
akobrin1 bd19c62
Introduced structured CLI with support for secure gRPC reflection and…
akobrin1 0377b70
makefile change
akobrin1 861c186
changes
akobrin1 43ab9d6
fixed sonic warning
akobrin1 fa25610
Merge branch 'master' into sncli
akobrin1 da08fd6
go.sum update
akobrin1 21644d1
tests go.mod update
akobrin1 28bc133
Update cmd/sncli/go.mod
akobrin1 06ade91
Update cmd/sncli/cli/sn_cmds.go
akobrin1 3481bb5
error handling fix
akobrin1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,5 +25,6 @@ go.work.sum | |
| # env file | ||
| .env | ||
| /data | ||
| /release | ||
| /tests/system/data | ||
| tests/system/**/supernode-data* | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # sncli - Supernode CLI Client | ||
|
|
||
| `sncli` is a lightweight command-line interface for interacting with a Lumera Supernode over secure gRPC. It supports health checks, service discovery, task registration, and status queries. | ||
|
|
||
| --- | ||
|
|
||
| ## 🔧 Build Instructions | ||
|
|
||
| To build `sncli` from source: | ||
|
|
||
| ```bash | ||
| make build-sncli | ||
| ``` | ||
|
|
||
| > The binary will be located at: `release/sncli` | ||
|
|
||
| --- | ||
|
|
||
| ## ⚙️ Configuration | ||
|
|
||
| Create a `config.toml` file in the same directory where you run `sncli`: | ||
|
|
||
| ```toml | ||
| # Lumera blockchain connection settings | ||
| [lumera] | ||
| grpc_addr = "localhost:9090" | ||
| chain_id = "lumera-devnet-1" | ||
|
|
||
| # Keyring settings for managing keys and identities | ||
| [keyring] | ||
| backend = "test" # "file", "test", or "os" | ||
| dir = "~/.lumera" # Directory where keyring is stored | ||
| key_name = "sncli-account" # Name of local key | ||
| local_address = "lumera1abc..." # Bech32 address of local account (must exist on-chain) | ||
|
|
||
| # Supernode peer information | ||
| [supernode] | ||
| grpc_endpoint = "127.0.0.1:4444" | ||
| address = "lumera1supernodeabc123" # Bech32 address of the Supernode | ||
| ``` | ||
|
|
||
| > Ensure the `local_address` exists on-chain (i.e., has received funds or sent a tx). | ||
|
|
||
| --- | ||
|
|
||
| ## 🚀 Usage | ||
|
|
||
| Run the CLI by calling the built binary with a command: | ||
|
|
||
| ```bash | ||
| ./sncli [<options>] <command> [args...] | ||
| ``` | ||
|
|
||
| ### Supported Command-Line Options | ||
|
|
||
| | Option | Description | | ||
| | --------------- | ------------------------------------------------------------------------------------------------- | | ||
| | --config | Path to config file. Supports ~. Default: ./config.toml or SNCLI_CONFIG_PATH environment variable | | ||
| | --grpc_endpoint | Override gRPC endpoint for Supernode (e.g., 127.0.0.1:4444) | | ||
| | --address | Override Supernode's Lumera address | | ||
|
|
||
| ### Supported Commands | ||
|
|
||
| | Command | Description | | ||
| | ---------------------- | -------------------------------------------------------- | | ||
| | `help` | Show usage instructions | | ||
| | `list` | List available gRPC services from the Supernode | | ||
| | `list` `<service>` | List methods in a specific gRPC service | | ||
| | `health-check` | Check if the Supernode is alive | | ||
| | `get-status` | Query current CPU/memory usage reported by the Supernode | | ||
|
|
||
| ### Example | ||
|
|
||
| ```bash | ||
| ./sncli --config ~/.sncli.toml --grpc_endpoint 10.0.0.1:4444 --address lumera1xyzabc get-status | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 📝 Notes | ||
|
|
||
| - `sncli` uses a secure gRPC connection with a handshake based on the Lumera keyring. | ||
| - The Supernode address must match a known peer on the network. | ||
| - Make sure`sncli-account` has been initialized and exists on the chain. | ||
| - Config file path is resolved using this algorithm: | ||
| - First uses --config flag (if provided) | ||
| - Else uses SNCLI_CONFIG_PATH environment variable (if defined) | ||
| - Else defaults to ./config.toml |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "context" | ||
| "log" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "github.com/BurntSushi/toml" | ||
| "github.com/spf13/pflag" | ||
|
|
||
| "github.com/cosmos/cosmos-sdk/crypto/keyring" | ||
| "github.com/LumeraProtocol/supernode/pkg/net/credentials/alts/conn" | ||
| "github.com/LumeraProtocol/supernode/sdk/adapters/lumera" | ||
| snkeyring "github.com/LumeraProtocol/supernode/pkg/keyring" | ||
| sdkcfg "github.com/LumeraProtocol/supernode/sdk/config" | ||
| sdklog "github.com/LumeraProtocol/supernode/sdk/log" | ||
| sdknet "github.com/LumeraProtocol/supernode/sdk/net" | ||
| ) | ||
|
|
||
| const ( | ||
| defaultConfigFileName = "config.toml" | ||
| ) | ||
|
|
||
| type CLI struct { | ||
| opts CLIOptions | ||
| cfg *CLIConfig | ||
| kr keyring.Keyring | ||
| sdkConfig sdkcfg.Config | ||
| lumeraClient lumera.Client | ||
| snClient sdknet.SupernodeClient | ||
| } | ||
|
|
||
| func (c *CLI) parseCLIOptions() { | ||
| pflag.StringVar(&c.opts.ConfigPath, "config", "", "Path to config file") | ||
| pflag.StringVar(&c.opts.GrpcEndpoint, "grpc_endpoint", "", "Supernode gRPC endpoint") | ||
| pflag.StringVar(&c.opts.SupernodeAddr, "address", "", "Supernode Lumera address") | ||
| pflag.Parse() | ||
|
|
||
| args := pflag.Args() | ||
| if len(args) > 0 { | ||
| c.opts.Command = args[0] | ||
| c.opts.CommandArgs = args[1:] | ||
| } | ||
| } | ||
|
|
||
| func NewCLI() *CLI { | ||
| cli := &CLI{} | ||
| return cli | ||
| } | ||
|
|
||
| func processConfigPath(path string) string { | ||
| // expand environment variables if any | ||
| path = os.ExpandEnv(path) | ||
| // replaces ~ with the user's home directory | ||
| if strings.HasPrefix(path, "~") { | ||
| home, err := os.UserHomeDir() | ||
| if err != nil { | ||
| log.Fatalf("Unable to resolve home directory: %v", err) | ||
| } | ||
| path = filepath.Join(home, path[1:]) | ||
| } | ||
| // check if path defines directory | ||
| if info, err := os.Stat(path); err == nil && info.IsDir() { | ||
| path = filepath.Join(path, defaultConfigFileName) | ||
| } | ||
| path = filepath.Clean(path) | ||
| return path | ||
| } | ||
|
|
||
| // detectConfigPath resolves the configuration file path based on: | ||
| // 1. CLI argument (--config) if provided | ||
| // 2. SNCLI_CONFIG_PATH environment variable | ||
| // 3. Default to ./config.toml | ||
| func (c *CLI) detectConfigPath() string { | ||
| if c.opts.ConfigPath != "" { | ||
| return processConfigPath(c.opts.ConfigPath) | ||
| } | ||
| if envPath := os.Getenv("SNCLI_CONFIG_PATH"); envPath != "" { | ||
| return processConfigPath(envPath) | ||
| } | ||
| return defaultConfigFileName | ||
| } | ||
|
|
||
| func (c *CLI) loadCLIConfig() { | ||
| path := c.detectConfigPath() | ||
| _, err := toml.DecodeFile(path, &c.cfg) | ||
| if err != nil { | ||
| log.Fatalf("Failed to load config from %s: %v", path, err) | ||
| } | ||
| } | ||
|
|
||
| func (c *CLI) validateCLIConfig() { | ||
| if c.opts.GrpcEndpoint != "" { | ||
| c.cfg.Supernode.GRPCEndpoint = c.opts.GrpcEndpoint | ||
| } | ||
| if c.opts.SupernodeAddr != "" { | ||
| c.cfg.Supernode.Address = c.opts.SupernodeAddr | ||
| } | ||
| } | ||
|
|
||
| func (c *CLI) Initialize() { | ||
| // Parse command-line options | ||
| c.parseCLIOptions() | ||
| // Load options from toml configuration file | ||
| c.loadCLIConfig() | ||
| // Validate configuration & override with CLI options if provided | ||
| c.validateCLIConfig() | ||
|
|
||
| // Initialize Supernode SDK | ||
| snkeyring.InitSDKConfig() | ||
|
|
||
| // Initialize keyring | ||
| var err error | ||
| c.kr, err = snkeyring.InitKeyring(c.cfg.Keyring.Backend, c.cfg.Keyring.Dir) | ||
| if err != nil { | ||
| log.Fatalf("Keyring init failed: %v", err) | ||
| } | ||
|
|
||
| // Create Lumera client adapter | ||
| c.sdkConfig = sdkcfg.NewConfig( | ||
| sdkcfg.AccountConfig{ | ||
| LocalCosmosAddress: c.cfg.Keyring.LocalAddress, | ||
| KeyName: c.cfg.Keyring.KeyName, | ||
| Keyring: c.kr, | ||
| }, | ||
| sdkcfg.LumeraConfig{ | ||
| GRPCAddr: c.cfg.Lumera.GRPCAddr, | ||
| ChainID: c.cfg.Lumera.ChainID, | ||
| }, | ||
| ) | ||
|
|
||
| c.lumeraClient, err = lumera.NewAdapter(context.Background(), lumera.ConfigParams{ | ||
| GRPCAddr: c.sdkConfig.Lumera.GRPCAddr, | ||
| ChainID: c.sdkConfig.Lumera.ChainID, | ||
| KeyName: c.sdkConfig.Account.KeyName, | ||
| Keyring: c.kr, | ||
| }, sdklog.NewNoopLogger()) | ||
| if err != nil { | ||
| log.Fatalf("Lumera client init failed: %v", err) | ||
| } | ||
|
|
||
| conn.RegisterALTSRecordProtocols() | ||
| } | ||
|
|
||
| func (c *CLI) Finalize() { | ||
| conn.UnregisterALTSRecordProtocols() | ||
|
|
||
| if c.snClient != nil { | ||
| c.snClient.Close(context.Background()) | ||
| } | ||
| } | ||
|
|
||
| func (c *CLI) snClientInit() { | ||
| if c.snClient != nil { | ||
| return // Already initialized | ||
| } | ||
|
|
||
| if c.cfg.Supernode.Address == "" || c.cfg.Supernode.GRPCEndpoint == "" { | ||
| log.Fatal("Supernode address and gRPC endpoint must be configured") | ||
| } | ||
|
|
||
| supernode := lumera.Supernode{ | ||
| CosmosAddress: c.cfg.Supernode.Address, | ||
| GrpcEndpoint: c.cfg.Supernode.GRPCEndpoint, | ||
| } | ||
|
|
||
| clientFactory := sdknet.NewClientFactory( | ||
| context.Background(), | ||
| sdklog.NewNoopLogger(), | ||
| c.kr, | ||
| c.lumeraClient, | ||
| sdknet.FactoryConfig{ | ||
| LocalCosmosAddress: c.cfg.Keyring.LocalAddress, | ||
| PeerType: 1, // Simplenode | ||
| }, | ||
| ) | ||
|
|
||
| var err error | ||
| c.snClient, err = clientFactory.CreateClient(context.Background(), supernode) | ||
| if err != nil { | ||
| log.Fatalf("Supernode client init failed: %v", err) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "log" | ||
| ) | ||
|
|
||
| func showHelp() { | ||
| helpText := `Supernode CLI Usage: | ||
| ./sncli [options] <command> [args...] | ||
|
|
||
| Available Options: | ||
| --config <path> Path to config file (default: ./config.toml or SNCLI_CONFIG_PATH env) | ||
| --grpc_endpoint <addr> Override Supernode gRPC endpoint (e.g., localhost:9090) | ||
| --address <addr> Override Supernode Lumera address | ||
|
|
||
| Available Commands: | ||
| help Show this help message | ||
| list List available gRPC services on Supernode | ||
| list <service> List methods in a specific gRPC service | ||
| health-check Check Supernode health status | ||
| get-status Query Supernode's current status (CPU, memory)` | ||
| fmt.Println(helpText) | ||
| } | ||
|
|
||
| func (c *CLI) Run() { | ||
| // Dispatch command handler | ||
| switch c.opts.Command { | ||
| case "help", "": | ||
| showHelp() | ||
| case "list": | ||
| if err := c.listGRPCMethods(); err != nil { | ||
| log.Fatalf("List gRPC methods failed: %v", err) | ||
| } | ||
| case "health-check": | ||
| if err := c.healthCheck(); err != nil { | ||
| log.Fatalf("Health check failed: %v", err) | ||
| } | ||
| case "get-status": | ||
| if err := c.getSupernodeStatus(); err != nil { | ||
| log.Fatalf("Get supernode status failed: %v", err) | ||
| } | ||
| default: | ||
| log.Fatalf("Unknown command: %s", c.opts.Command) | ||
| } | ||
| } | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.