From 0b43f545cfba85bf88210dc0b761ad88af222fbc Mon Sep 17 00:00:00 2001 From: Andrey Kobrin Date: Tue, 26 Aug 2025 12:53:50 -0400 Subject: [PATCH] implemented p2p ping --- .vscode/launch.json | 13 +- Makefile | 5 +- cmd/sncli/README.md | 87 ++++++++-- cmd/sncli/cli/cli.go | 148 +++++++++-------- .../cli/{sn_cmds.go => cmd_get_status.go} | 15 +- cmd/sncli/cli/cmd_health_check.go | 18 ++ .../cli/{list_grpc.go => cmd_list_grpc.go} | 23 +-- cmd/sncli/cli/cmd_p2p_ping.go | 72 ++++++++ cmd/sncli/cli/dispatch.go | 47 ------ cmd/sncli/cli/p2p.go | 155 ++++++++++++++++++ cmd/sncli/cli/types.go | 16 +- cmd/sncli/cli/utils.go | 48 ++++++ cmd/sncli/cli/validator.go | 40 +++++ cmd/sncli/cmd/get-status.go | 19 +++ cmd/sncli/cmd/health-check.go | 21 +++ cmd/sncli/cmd/list.go | 21 +++ cmd/sncli/cmd/p2p-ping.go | 45 +++++ cmd/sncli/cmd/p2p.go | 30 ++++ cmd/sncli/cmd/root.go | 53 ++++++ cmd/sncli/{config.toml => config.toml.sample} | 20 ++- cmd/sncli/go.mod | 18 +- cmd/sncli/go.sum | 46 +++++- cmd/sncli/main.go | 9 +- p2p/kademlia/dht.go | 29 ++-- 24 files changed, 801 insertions(+), 197 deletions(-) rename cmd/sncli/cli/{sn_cmds.go => cmd_get_status.go} (77%) create mode 100644 cmd/sncli/cli/cmd_health_check.go rename cmd/sncli/cli/{list_grpc.go => cmd_list_grpc.go} (86%) create mode 100644 cmd/sncli/cli/cmd_p2p_ping.go delete mode 100644 cmd/sncli/cli/dispatch.go create mode 100644 cmd/sncli/cli/p2p.go create mode 100644 cmd/sncli/cli/utils.go create mode 100644 cmd/sncli/cli/validator.go create mode 100644 cmd/sncli/cmd/get-status.go create mode 100644 cmd/sncli/cmd/health-check.go create mode 100644 cmd/sncli/cmd/list.go create mode 100644 cmd/sncli/cmd/p2p-ping.go create mode 100644 cmd/sncli/cmd/p2p.go create mode 100644 cmd/sncli/cmd/root.go rename cmd/sncli/{config.toml => config.toml.sample} (65%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 31f32e75..b7cbc8e3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,6 +13,17 @@ "showLog": true, "trace": "verbose" }, - + { + "name": "sncli debug", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/cmd/sncli/main.go", + "cwd": "${workspaceFolder}/cmd/sncli", + "args": ["list"], + "env": {}, + "showLog": true, + "trace": "verbose" + }, ] } \ No newline at end of file diff --git a/Makefile b/Makefile index 3977dc38..fe5a9852 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,9 @@ build: build-sncli: release/sncli +SNCLI_SRC := $(wildcard cmd/sncli/*.go) \ + $(wildcard cmd/sncli/**/*.go) + release/sncli: $(SNCLI_SRC) cmd/sncli/go.mod cmd/sncli/go.sum @mkdir -p release @echo "Building sncli..." @@ -49,8 +52,6 @@ release/sncli: $(SNCLI_SRC) cmd/sncli/go.mod cmd/sncli/go.sum chmod +x $$RELEASE_DIR/sncli && \ echo "sncli built successfully at $$RELEASE_DIR/sncli" -SNCLI_SRC=$(shell find cmd/sncli -name "*.go") - build-sn-manager: @mkdir -p release @echo "Building sn-manager..." diff --git a/cmd/sncli/README.md b/cmd/sncli/README.md index 5f79db2e..f3a33110 100644 --- a/cmd/sncli/README.md +++ b/cmd/sncli/README.md @@ -18,9 +18,13 @@ make build-sncli ## ⚙️ Configuration -Create a `config.toml` file in the same directory where you run `sncli`: +**Default config location:** `~/.sncli/config.toml` + +You can override the location with `--config ` or `SNCLI_CONFIG_PATH`. ```toml +# ~/.sncli/config.toml + # Lumera blockchain connection settings [lumera] grpc_addr = "localhost:9090" @@ -36,10 +40,11 @@ local_address = "lumera1abc..." # Bech32 address of local account (must exi # Supernode peer information [supernode] grpc_endpoint = "127.0.0.1:4444" +p2p_endpoint = "127.0.0.1:4445" address = "lumera1supernodeabc123" # Bech32 address of the Supernode ``` -> Ensure the `local_address` exists on-chain (i.e., has received funds or sent a tx). +> Ensure the `keyring.local_address` exists on-chain (i.e., has received funds or sent a tx). --- @@ -48,26 +53,41 @@ address = "lumera1supernodeabc123" # Bech32 address of the Supernode Run the CLI by calling the built binary with a command: ```bash -./sncli [] [args...] +sncli [global flags] [command flags] [args...] ``` -### Supported Command-Line Options +### Global flags -| 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 | +* `--config string` Path to config file (default `~/.sncli/config.toml`) +* `--grpc_endpoint string` Supernode gRPC endpoint +* `--p2p_endpoint string` Supernode P2P endpoint (Kademlia) +* `--address string` Supernode Lumera address +* `-h, --help` Help for `sncli` ### Supported Commands +* `completion` Generate shell completion +* `get-status` Query Supernode status (CPU, memory, tasks, peers) +* `health-check` Check Supernode health status +* `list` List available gRPC services or methods in a specific service +* `list` `` List methods in a specific gRPC service +* `p2p` **Supernode P2P utilities** + +## 🌐 P2P command group + +The `p2p` group contains tools for interacting with the Supernode’s **Kademlia P2P** service. + +### `sncli p2p` + +Shows P2P help and available subcommands. -| Command | Description | -| ---------------------- | -------------------------------------------------------- | -| `help` | Show usage instructions | -| `list` | List available gRPC services from the Supernode | -| `list` `` | 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 | +``` +Supernode P2P utilities + +Usage: + sncli p2p [command] + +Available Commands: + ping Check the connectivity to a Supernode's kademlia P2P server ### Example @@ -75,14 +95,45 @@ Run the CLI by calling the built binary with a command: ./sncli --config ~/.sncli.toml --grpc_endpoint 10.0.0.1:4444 --address lumera1xyzabc get-status ``` +### `sncli p2p ping` — check connectivity + +Checks TCP reachability and does a lightweight ping of the Supernode’s **P2P** endpoint. + +**Usage** + +``` +sncli p2p ping [timeout] +``` + +* `timeout` is optional (e.g., `5s`, `1m`). If omitted, a sensible default is used (e.g., `10s`). +* You can also set `--timeout` explicitly (if supported by your build). +* The endpoint can be supplied via `--p2p_endpoint` or taken from the config at `[supernode].p2p_endpoint`. + --- +**Examples** + +```bash +# Use endpoint from config (~/.sncli/config.toml) +sncli p2p ping + +# Override endpoint with a flag +sncli p2p ping --p2p_endpoint 172.18.0.6:4445 +# Output: +# P2P is alive (172.18.0.6:4445) + +# Use a custom timeout (positional) +sncli p2p ping 3s + +# Use a custom timeout (flag) +sncli p2p ping --timeout 30s +``` + ## 📝 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 + - Else defaults to ~/.sncli/config.toml diff --git a/cmd/sncli/cli/cli.go b/cmd/sncli/cli/cli.go index 2f69d7a6..45f88a6c 100644 --- a/cmd/sncli/cli/cli.go +++ b/cmd/sncli/cli/cli.go @@ -2,14 +2,12 @@ package cli import ( "context" - "github.com/LumeraProtocol/supernode/v2/supernode/config" "log" "os" - "path/filepath" - "strings" + "fmt" + "time" "github.com/BurntSushi/toml" - "github.com/spf13/pflag" snkeyring "github.com/LumeraProtocol/supernode/v2/pkg/keyring" "github.com/LumeraProtocol/supernode/v2/pkg/net/credentials/alts/conn" @@ -17,33 +15,27 @@ import ( sdkcfg "github.com/LumeraProtocol/supernode/v2/sdk/config" sdklog "github.com/LumeraProtocol/supernode/v2/sdk/log" sdknet "github.com/LumeraProtocol/supernode/v2/sdk/net" + snconfig "github.com/LumeraProtocol/supernode/v2/supernode/config" "github.com/cosmos/cosmos-sdk/crypto/keyring" ) const ( + // defaultConfigFileName is the default path to the configuration file. defaultConfigFileName = "config.toml" + + // defaultConfigFolder is the default folder for configuration files. + defaultConfigFolder = "~/.sncli" ) type CLI struct { - opts CLIOptions - cfg *CLIConfig + cliOpts CLIOptions + CfgOpts *CLIConfig + ConfigPath string kr keyring.Keyring - sdkConfig sdkcfg.Config + 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:] - } + p2p *P2P } func NewCLI() *CLI { @@ -51,94 +43,97 @@ func NewCLI() *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 +func (c *CLI) SetOptions(o CLIOptions) { + c.cliOpts = o +} + +func (c *CLI) SetP2P(p *P2P) { + c.p2p = p +} + +func (c *CLI) P2P() *P2P { + return c.p2p } // 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 +// 3. Default to ~/.sncli/config.toml func (c *CLI) detectConfigPath() string { - if c.opts.ConfigPath != "" { - return processConfigPath(c.opts.ConfigPath) + if c.cliOpts.ConfigPath != "" { + return processConfigPath(c.cliOpts.ConfigPath) } if envPath := os.Getenv("SNCLI_CONFIG_PATH"); envPath != "" { return processConfigPath(envPath) } - return defaultConfigFileName + return processConfigPath(defaultConfigFolder) } -func (c *CLI) loadCLIConfig() { +func (c *CLI) loadCLIConfig() error { path := c.detectConfigPath() - _, err := toml.DecodeFile(path, &c.cfg) + _, err := toml.DecodeFile(path, &c.CfgOpts) if err != nil { - log.Fatalf("Failed to load config from %s: %v", path, err) + if os.IsNotExist(err) { + return fmt.Errorf("Config file not found at %s", path) + } + return fmt.Errorf("Failed to load config from %s: %v", path, err) } -} + c.ConfigPath = path -func (c *CLI) validateCLIConfig() { - if c.opts.GrpcEndpoint != "" { - c.cfg.Supernode.GRPCEndpoint = c.opts.GrpcEndpoint + // override Supernode GRPC endpoint and address if provided in CLI options + if c.cliOpts.GrpcEndpoint != "" { + c.CfgOpts.Supernode.GRPCEndpoint = c.cliOpts.GrpcEndpoint } - if c.opts.SupernodeAddr != "" { - c.cfg.Supernode.Address = c.opts.SupernodeAddr + if c.cliOpts.SupernodeAddr != "" { + c.CfgOpts.Supernode.Address = c.cliOpts.SupernodeAddr } + if c.cliOpts.P2PEndpoint != "" { + c.CfgOpts.Supernode.P2PEndpoint = c.cliOpts.P2PEndpoint + } + return nil } -func (c *CLI) Initialize() { - // Parse command-line options - c.parseCLIOptions() +func (c *CLI) Initialize() error { + var err error + // Load options from toml configuration file - c.loadCLIConfig() // Validate configuration & override with CLI options if provided - c.validateCLIConfig() + if err = c.loadCLIConfig(); err != nil { + return err + } // Initialize Supernode SDK snkeyring.InitSDKConfig() // Initialize keyring - var err error - c.kr, err = snkeyring.InitKeyring(config.KeyringConfig{ - Backend: c.cfg.Keyring.Backend, - Dir: c.cfg.Keyring.Dir, - }) + var krConfig snconfig.KeyringConfig + krConfig.Backend = c.CfgOpts.Keyring.Backend + krConfig.Dir = c.CfgOpts.Keyring.Dir + krConfig.PassPlain = c.CfgOpts.Keyring.PassPlain + krConfig.PassEnv = c.CfgOpts.Keyring.PassEnv + krConfig.PassFile = c.CfgOpts.Keyring.PassFile + c.kr, err = snkeyring.InitKeyring(krConfig) if err != nil { log.Fatalf("Keyring init failed: %v", err) } // Create Lumera client adapter - c.sdkConfig = sdkcfg.NewConfig( + c.SdkConfig = sdkcfg.NewConfig( sdkcfg.AccountConfig{ - LocalCosmosAddress: c.cfg.Keyring.LocalAddress, - KeyName: c.cfg.Keyring.KeyName, + LocalCosmosAddress: c.CfgOpts.Keyring.LocalAddress, + KeyName: c.CfgOpts.Keyring.KeyName, Keyring: c.kr, }, sdkcfg.LumeraConfig{ - GRPCAddr: c.cfg.Lumera.GRPCAddr, - ChainID: c.cfg.Lumera.ChainID, + GRPCAddr: c.CfgOpts.Lumera.GRPCAddr, + ChainID: c.CfgOpts.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, + GRPCAddr: c.SdkConfig.Lumera.GRPCAddr, + ChainID: c.SdkConfig.Lumera.ChainID, + KeyName: c.SdkConfig.Account.KeyName, Keyring: c.kr, }, sdklog.NewNoopLogger()) if err != nil { @@ -146,6 +141,8 @@ func (c *CLI) Initialize() { } conn.RegisterALTSRecordProtocols() + + return nil } func (c *CLI) Finalize() { @@ -161,13 +158,13 @@ func (c *CLI) snClientInit() { return // Already initialized } - if c.cfg.Supernode.Address == "" || c.cfg.Supernode.GRPCEndpoint == "" { + if c.CfgOpts.Supernode.Address == "" || c.CfgOpts.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, + CosmosAddress: c.CfgOpts.Supernode.Address, + GrpcEndpoint: c.CfgOpts.Supernode.GRPCEndpoint, } clientFactory := sdknet.NewClientFactory( @@ -176,7 +173,7 @@ func (c *CLI) snClientInit() { c.kr, c.lumeraClient, sdknet.FactoryConfig{ - LocalCosmosAddress: c.cfg.Keyring.LocalAddress, + LocalCosmosAddress: c.CfgOpts.Keyring.LocalAddress, PeerType: 1, // Simplenode }, ) @@ -187,3 +184,10 @@ func (c *CLI) snClientInit() { log.Fatalf("Supernode client init failed: %v", err) } } + +func (c *CLI) P2PPing(timeout time.Duration) error { + if c.p2p == nil { + return fmt.Errorf("P2P: not initialized") + } + return c.p2p.Ping(timeout) +} \ No newline at end of file diff --git a/cmd/sncli/cli/sn_cmds.go b/cmd/sncli/cli/cmd_get_status.go similarity index 77% rename from cmd/sncli/cli/sn_cmds.go rename to cmd/sncli/cli/cmd_get_status.go index 2360a69b..9603089b 100644 --- a/cmd/sncli/cli/sn_cmds.go +++ b/cmd/sncli/cli/cmd_get_status.go @@ -5,19 +5,8 @@ import ( "fmt" ) -func (c *CLI) healthCheck() error { - c.snClientInit() - - resp, err := c.snClient.HealthCheck(context.Background()) - if err != nil { - return fmt.Errorf("Supernode health check failed: %v", err) - } - fmt.Println("✅ Health status:", resp.Status) - return nil -} - -// getSupernodeStatus retrieves and displays the status of the supernode -func (c *CLI) getSupernodeStatus() error { +// GetSupernodeStatus retrieves and displays the status of the supernode +func (c *CLI) GetSupernodeStatus() error { c.snClientInit() resp, err := c.snClient.GetSupernodeStatus(context.Background()) diff --git a/cmd/sncli/cli/cmd_health_check.go b/cmd/sncli/cli/cmd_health_check.go new file mode 100644 index 00000000..160ddcb5 --- /dev/null +++ b/cmd/sncli/cli/cmd_health_check.go @@ -0,0 +1,18 @@ +package cli + +import ( + "context" + "fmt" +) + +// HealthCheck performs a health check on the Supernode +func (c *CLI) HealthCheck() error { + c.snClientInit() + + resp, err := c.snClient.HealthCheck(context.Background()) + if err != nil { + return fmt.Errorf("Supernode health check failed: %v", err) + } + fmt.Println("✅ Health status:", resp.Status) + return nil +} diff --git a/cmd/sncli/cli/list_grpc.go b/cmd/sncli/cli/cmd_list_grpc.go similarity index 86% rename from cmd/sncli/cli/list_grpc.go rename to cmd/sncli/cli/cmd_list_grpc.go index 43ac0916..ece2ad7e 100644 --- a/cmd/sncli/cli/list_grpc.go +++ b/cmd/sncli/cli/cmd_list_grpc.go @@ -4,22 +4,23 @@ import ( "context" "fmt" "log" - + reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/types/descriptorpb" - grpcclient "github.com/LumeraProtocol/supernode/v2/pkg/net/grpc/client" + "github.com/LumeraProtocol/lumera/x/lumeraid/securekeyx" "github.com/LumeraProtocol/supernode/v2/pkg/net/credentials" + grpcclient "github.com/LumeraProtocol/supernode/v2/pkg/net/grpc/client" ) -func (c *CLI) listGRPCMethods() error { +func (c *CLI) ListGRPCMethods(service string) error { clientCreds, err := credentials.NewClientCreds(&credentials.ClientOptions{ CommonOptions: credentials.CommonOptions{ Keyring: c.kr, - LocalIdentity: c.cfg.Keyring.LocalAddress, - PeerType: 1, // Simplenode + LocalIdentity: c.CfgOpts.Keyring.LocalAddress, + PeerType: securekeyx.Simplenode, Validator: c.lumeraClient, }, }) @@ -28,7 +29,7 @@ func (c *CLI) listGRPCMethods() error { } grpcClient := grpcclient.NewClient(clientCreds) - target := credentials.FormatAddressWithIdentity(c.cfg.Supernode.Address, c.cfg.Supernode.GRPCEndpoint) + target := credentials.FormatAddressWithIdentity(c.CfgOpts.Supernode.Address, c.CfgOpts.Supernode.GRPCEndpoint) conn, err := grpcClient.Connect(context.Background(), target, grpcclient.DefaultClientOptions()) if err != nil { @@ -43,7 +44,7 @@ func (c *CLI) listGRPCMethods() error { } // If no specific service is requested, list all services - if len(c.opts.CommandArgs) == 0 || c.opts.CommandArgs[0] == "" { + if len(service) == 0 || service == "" { if err := stream.Send(&reflectpb.ServerReflectionRequest{ MessageRequest: &reflectpb.ServerReflectionRequest_ListServices{ ListServices: "*", @@ -68,10 +69,10 @@ func (c *CLI) listGRPCMethods() error { // Describe methods in the specified service if err := stream.Send(&reflectpb.ServerReflectionRequest{ MessageRequest: &reflectpb.ServerReflectionRequest_FileContainingSymbol{ - FileContainingSymbol: c.opts.CommandArgs[0], + FileContainingSymbol: service, }, }); err != nil { - return fmt.Errorf("failed to send list services request containing symbol %q: %w", c.opts.CommandArgs[0], err) + return fmt.Errorf("failed to send list services request containing symbol %q: %w", service, err) } resp, err := stream.Recv() @@ -93,7 +94,7 @@ func (c *CLI) listGRPCMethods() error { } for i := 0; i < file.Services().Len(); i++ { svc := file.Services().Get(i) - if string(svc.FullName()) == c.opts.CommandArgs[0] { + if string(svc.FullName()) == service { fmt.Printf("\n\U0001F50D Methods in %s:\n", svc.FullName()) for j := 0; j < svc.Methods().Len(); j++ { m := svc.Methods().Get(j) diff --git a/cmd/sncli/cli/cmd_p2p_ping.go b/cmd/sncli/cli/cmd_p2p_ping.go new file mode 100644 index 00000000..02f513cf --- /dev/null +++ b/cmd/sncli/cli/cmd_p2p_ping.go @@ -0,0 +1,72 @@ +package cli + +import ( + "fmt" + "time" + "net" + "context" + "strconv" + + "github.com/LumeraProtocol/supernode/v2/p2p/kademlia" +) + +const ( + defaultConnDeadline = 10 * time.Minute +) + +// Ping dials and sends a Ping message to the remote P2P endpoint +func (p *P2P) Ping(timeout time.Duration) error { + if p.clientTC == nil { + return fmt.Errorf("P2P: client credentials not initialized") + } + if timeout <= 0 { + timeout = p.Timeout + } + + clientCtx, clientCancel := context.WithTimeout(context.Background(), timeout) + defer clientCancel() + + // dial the remote address with tcp + remoteAddress := p.receiver.IP + ":" + strconv.Itoa(int(p.receiver.Port)) + var d net.Dialer + rawConn, err := d.DialContext(clientCtx, "tcp", remoteAddress) + if err != nil { + return fmt.Errorf("P2P: failed to dial remote address %s: %w", remoteAddress, err) + } + defer rawConn.Close() + + // set the deadline for read and write + rawConn.SetDeadline(time.Now().UTC().Add(defaultConnDeadline)) + + secureConn, _, err := p.clientTC.ClientHandshake(clientCtx, "", rawConn) + if err != nil { + return fmt.Errorf("P2P: failed to perform client handshake: %w", err) + } + defer secureConn.Close() + + request := &kademlia.Message{ + Sender: p.sender, + Receiver: p.receiver, + MessageType: kademlia.Ping, + } + // encode and send the request message + data, err := encode(request) + if err != nil { + return fmt.Errorf("P2P: failed to encode request message: %w", err) + } + if _, err := secureConn.Write(data); err != nil { + return fmt.Errorf("P2P: failed to write to secure connection: %w", err) + } + + // receive and decode the response message + response, err := decode(secureConn) + if err != nil { + return fmt.Errorf("conn read: %w", err) + } + if response.MessageType != kademlia.Ping { + return fmt.Errorf("P2P: unexpected response message type: %v", response.MessageType) + } + fmt.Printf("P2P is alive (%s)\n", remoteAddress) + + return nil +} \ No newline at end of file diff --git a/cmd/sncli/cli/dispatch.go b/cmd/sncli/cli/dispatch.go deleted file mode 100644 index 4b8fa345..00000000 --- a/cmd/sncli/cli/dispatch.go +++ /dev/null @@ -1,47 +0,0 @@ -package cli - -import ( - "fmt" - "log" -) - -func showHelp() { - helpText := `Supernode CLI Usage: - ./sncli [options] [args...] - -Available Options: - --config Path to config file (default: ./config.toml or SNCLI_CONFIG_PATH env) - --grpc_endpoint Override Supernode gRPC endpoint (e.g., localhost:9090) - --address Override Supernode Lumera address - -Available Commands: - help Show this help message - list List available gRPC services on Supernode - list 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) - } -} - diff --git a/cmd/sncli/cli/p2p.go b/cmd/sncli/cli/p2p.go new file mode 100644 index 00000000..ddbf2a82 --- /dev/null +++ b/cmd/sncli/cli/p2p.go @@ -0,0 +1,155 @@ +package cli + +import ( + "fmt" + "io" + "net" + "time" + "strconv" + "encoding/binary" + "encoding/gob" + "bytes" + + "github.com/LumeraProtocol/lumera/x/lumeraid/securekeyx" + ltc "github.com/LumeraProtocol/supernode/v2/pkg/net/credentials" + "google.golang.org/grpc/credentials" + snUtils "github.com/LumeraProtocol/supernode/v2/pkg/utils" + "github.com/LumeraProtocol/supernode/v2/p2p/kademlia" +) + +const ( + defaultMaxPayloadSize = 2 // MB +) + +// P2P keeps all state needed for secure P2P operations. +type P2P struct { + cfgOpts *CLIConfig + clientTC credentials.TransportCredentials + sender *kademlia.Node + receiver *kademlia.Node + + Timeout time.Duration + Conn net.Conn + Listener net.Listener +} + +// encode the message +func encode(message *kademlia.Message) ([]byte, error) { + var buf bytes.Buffer + + encoder := gob.NewEncoder(&buf) + // encode the message with gob library + if err := encoder.Encode(message); err != nil { + return nil, err + } + + if snUtils.BytesIntToMB(buf.Len()) > defaultMaxPayloadSize { + return nil, fmt.Errorf("payload too big") + } + + var header [8]byte + // prepare the header + binary.PutUvarint(header[:], uint64(buf.Len())) + + var data []byte + data = append(data, header[:]...) + data = append(data, buf.Bytes()...) + + return data, nil +} + +// decode the message +func decode(conn io.Reader) (*kademlia.Message, error) { + // read the header + header := make([]byte, 8) + if _, err := io.ReadFull(conn, header); err != nil { + return nil, err + } + + // parse the length of message + length, err := binary.ReadUvarint(bytes.NewBuffer(header)) + if err != nil { + return nil, fmt.Errorf("parse header length: %w", err) + } + + if snUtils.BytesToMB(length) > defaultMaxPayloadSize { + return nil, fmt.Errorf("payload too big") + } + + // read the message body + data := make([]byte, length) + if _, err := io.ReadFull(conn, data); err != nil { + return nil, err + } + + // new a decoder + decoder := gob.NewDecoder(bytes.NewBuffer(data)) + // decode the message structure + message := &kademlia.Message{} + if err = decoder.Decode(message); err != nil { + return nil, err + } + + return message, nil +} + +func (p *P2P) Initialize(cli *CLI) error { + if cli == nil || cli.CfgOpts == nil { + return fmt.Errorf("P2P: CLI/config not initialized") + } + p.cfgOpts = cli.CfgOpts + + if p.cfgOpts.GetLocalIdentity() == "" { + return fmt.Errorf("P2P: keyring.local_address is required") + } + if p.cfgOpts.GetRemoteIdentity() == "" { + return fmt.Errorf("P2P: supernode.address is required") + } + if p.cfgOpts.Supernode.P2PEndpoint == "" { + return fmt.Errorf("P2P: --p2p_endpoint (REMOTE host:port) is required") + } + host, portStr, err := net.SplitHostPort(p.cfgOpts.Supernode.P2PEndpoint) + if err != nil { + return fmt.Errorf("P2P: failed to parse --p2p_endpoint: %w", err) + } + port, err := strconv.Atoi(portStr) + if err != nil || port <= 0 || port > 65535 { + return fmt.Errorf("P2P: invalid port number in --p2p_endpoint: %w", err) + } + + p.clientTC, err = ltc.NewClientCreds(<c.ClientOptions{ + CommonOptions: ltc.CommonOptions{ + Keyring: cli.kr, + LocalIdentity: p.cfgOpts.GetLocalIdentity(), + PeerType: securekeyx.Simplenode, + Validator: NewSecureKeyExchangeValidator(cli.lumeraClient), + }, + }) + if err != nil { + return fmt.Errorf("P2P: failed to create client credentials: %w", err) + } + lumeraTC, ok := p.clientTC.(*ltc.LumeraTC) + if !ok { + return fmt.Errorf("invalid credentials type") + } + + lumeraTC.SetRemoteIdentity(p.cfgOpts.GetRemoteIdentity()) + + p.sender = &kademlia.Node{ + ID: []byte(p.cfgOpts.GetLocalIdentity()), + IP: "localhost", + Port: 4445, + } + p.sender.SetHashedID() + + p.receiver = &kademlia.Node{ + ID: []byte(p.cfgOpts.GetRemoteIdentity()), + IP: host, + Port: uint16(port), + } + p.receiver.SetHashedID() + p.Timeout = 15 * time.Second + + return nil +} + diff --git a/cmd/sncli/cli/types.go b/cmd/sncli/cli/types.go index 7f471cf3..69b41526 100644 --- a/cmd/sncli/cli/types.go +++ b/cmd/sncli/cli/types.go @@ -4,9 +4,7 @@ type CLIOptions struct { ConfigPath string GrpcEndpoint string SupernodeAddr string - - Command string - CommandArgs []string + P2PEndpoint string } type CLIConfig struct { @@ -20,10 +18,22 @@ type CLIConfig struct { Dir string `toml:"dir"` KeyName string `toml:"key_name"` LocalAddress string `toml:"local_address"` + PassPlain string `toml:"passphrase_plain"` + PassFile string `toml:"passphrase_file"` + PassEnv string `toml:"passphrase_env"` } `toml:"keyring"` Supernode struct { GRPCEndpoint string `toml:"grpc_endpoint"` + P2PEndpoint string `toml:"p2p_endpoint"` Address string `toml:"address"` } `toml:"supernode"` } + +func (c *CLIConfig) GetLocalIdentity() string { + return c.Keyring.LocalAddress +} + +func (c *CLIConfig) GetRemoteIdentity() string { + return c.Supernode.Address +} \ No newline at end of file diff --git a/cmd/sncli/cli/utils.go b/cmd/sncli/cli/utils.go new file mode 100644 index 00000000..b691d4aa --- /dev/null +++ b/cmd/sncli/cli/utils.go @@ -0,0 +1,48 @@ +package cli + +import ( + "log" + "os" + "path/filepath" + "strings" + "net" + "strconv" + "fmt" +) + +func NormalizePath(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:]) + } + path = filepath.Clean(path) + return path +} + +func processConfigPath(path string) string { + path = NormalizePath(path) + // 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 +} + +func splitHostPort(hp string) (string, int, error) { + host, portStr, err := net.SplitHostPort(hp) + if err != nil { + return "", 0, err + } + p, err := strconv.Atoi(portStr) + if err != nil || p <= 0 || p > 65535 { + return "", 0, fmt.Errorf("invalid port: %s", portStr) + } + return host, p, nil +} \ No newline at end of file diff --git a/cmd/sncli/cli/validator.go b/cmd/sncli/cli/validator.go new file mode 100644 index 00000000..21d59cb0 --- /dev/null +++ b/cmd/sncli/cli/validator.go @@ -0,0 +1,40 @@ +package cli + +import ( + "context" + "fmt" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/LumeraProtocol/supernode/v2/sdk/adapters/lumera" + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" +) + +type SecureKeyExchangeValidator struct { + lumeraClient lumera.Client +} + +func NewSecureKeyExchangeValidator(lumeraClient lumera.Client) *SecureKeyExchangeValidator { + return &SecureKeyExchangeValidator{ + lumeraClient: lumeraClient, + } +} + +func (v *SecureKeyExchangeValidator) AccountInfoByAddress(ctx context.Context, addr string) (*authtypes.QueryAccountInfoResponse, error) { + accountInfo, err := v.lumeraClient.AccountInfoByAddress(ctx, addr) + if err != nil { + return nil, fmt.Errorf("failed to get account info: %w", err) + } + return accountInfo, nil +} + +func (v *SecureKeyExchangeValidator) GetSupernodeBySupernodeAddress(ctx context.Context, address string) (*sntypes.SuperNode, error) { + supernodeInfo, err := v.lumeraClient.GetSupernodeBySupernodeAddress(ctx, address) + if err != nil { + return nil, fmt.Errorf("failed to get supernode info: %w", err) + } + if supernodeInfo == nil { + return nil, fmt.Errorf("supernode info is nil") + } + return supernodeInfo, nil +} diff --git a/cmd/sncli/cmd/get-status.go b/cmd/sncli/cmd/get-status.go new file mode 100644 index 00000000..f0ed49fd --- /dev/null +++ b/cmd/sncli/cmd/get-status.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// get-status: Query Supernode's current status (CPU, memory, tasks, peers, etc.) +var statusCmd = &cobra.Command{ + Use: "get-status", + Aliases: []string{"status"}, + Short: "Query Supernode status (CPU, memory, tasks, peers)", + RunE: func(cmd *cobra.Command, args []string) error { + return app.GetSupernodeStatus() + }, +} + +func init() { + rootCmd.AddCommand(statusCmd) +} diff --git a/cmd/sncli/cmd/health-check.go b/cmd/sncli/cmd/health-check.go new file mode 100644 index 00000000..61b406f6 --- /dev/null +++ b/cmd/sncli/cmd/health-check.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "log" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(healthCmd) +} + +var healthCmd = &cobra.Command{ + Use: "health-check", + Short: "Check Supernode health status", + Run: func(cmd *cobra.Command, args []string) { + if err := app.HealthCheck(); err != nil { + log.Fatal(err) + } + }, +} \ No newline at end of file diff --git a/cmd/sncli/cmd/list.go b/cmd/sncli/cmd/list.go new file mode 100644 index 00000000..56cfc018 --- /dev/null +++ b/cmd/sncli/cmd/list.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list [service]", + Short: "List available gRPC services or methods in a specific service", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 1 { + return app.ListGRPCMethods(args[0]) + } + return app.ListGRPCMethods("") + }, +} + +func init() { + rootCmd.AddCommand(listCmd) +} \ No newline at end of file diff --git a/cmd/sncli/cmd/p2p-ping.go b/cmd/sncli/cmd/p2p-ping.go new file mode 100644 index 00000000..ea3399b3 --- /dev/null +++ b/cmd/sncli/cmd/p2p-ping.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "time" + "fmt" + + "github.com/spf13/cobra" +) + +var ( + pingTimeout time.Duration +) + +var p2pPingCmd = &cobra.Command{ + Use: "ping", + Short: "Check the connectivity to a Supernode's kademlia P2P server", + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) error { + // default if not provided + t := pingTimeout + if t == 0 { + t = 10 * time.Second + } + + // If user supplied positional, parse it; it wins over default + if len(args) == 1 { + d, err := time.ParseDuration(args[0]) + if err != nil { + return fmt.Errorf("invalid timeout %q: %w", args[0], err) + } + t = d + } + + if t <= 0 { + return fmt.Errorf("timeout must be > 0") + } + + return app.P2PPing(t) + }, +} + +func init() { + p2pCmd.AddCommand(p2pPingCmd) + p2pPingCmd.Flags().DurationVar(&pingTimeout, "timeout", 5*time.Second, "Ping timeout") +} diff --git a/cmd/sncli/cmd/p2p.go b/cmd/sncli/cmd/p2p.go new file mode 100644 index 00000000..3266979f --- /dev/null +++ b/cmd/sncli/cmd/p2p.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/LumeraProtocol/supernode/v2/cmd/sncli/cli" +) + +var p2pCmd = &cobra.Command{ + Use: "p2p", + Short: "Supernode P2P utilities", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if r := cmd.Root(); r != nil && r.PersistentPreRunE != nil { + if err := r.PersistentPreRunE(cmd, args); err != nil { + return err + } + } + if app.P2P() == nil { + p := &cli.P2P{} + if err := p.Initialize(app); err != nil { + return err + } + app.SetP2P(p) + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(p2pCmd) +} diff --git a/cmd/sncli/cmd/root.go b/cmd/sncli/cmd/root.go new file mode 100644 index 00000000..c14c2a9f --- /dev/null +++ b/cmd/sncli/cmd/root.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "fmt" + "os" + "sync" + + "github.com/spf13/cobra" + "github.com/LumeraProtocol/supernode/v2/cmd/sncli/cli" +) + +var ( + opts cli.CLIOptions + app *cli.CLI + initOnce sync.Once +) + +var rootCmd = &cobra.Command{ + Use: "sncli", + Short: "Supernode CLI for Lumera", + Long: "sncli is a command-line tool to interact with Lumera supernodes.", + SilenceUsage: true, + SilenceErrors: false, + + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + var err error + initOnce.Do(func() { + app = cli.NewCLI() + app.SetOptions(opts) + err = app.Initialize() + }) + return err + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if app != nil { + app.Finalize() + } + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + rootCmd.PersistentFlags().StringVar(&opts.ConfigPath, "config", "", "Path to config file") + rootCmd.PersistentFlags().StringVar(&opts.GrpcEndpoint, "grpc_endpoint", "", "Supernode gRPC endpoint") + rootCmd.PersistentFlags().StringVar(&opts.SupernodeAddr, "address", "", "Supernode Lumera address") + rootCmd.PersistentFlags().StringVar(&opts.P2PEndpoint, "p2p_endpoint", "", "Supernode P2P endpoint") +} \ No newline at end of file diff --git a/cmd/sncli/config.toml b/cmd/sncli/config.toml.sample similarity index 65% rename from cmd/sncli/config.toml rename to cmd/sncli/config.toml.sample index 4ace7f9e..f0f9866b 100644 --- a/cmd/sncli/config.toml +++ b/cmd/sncli/config.toml.sample @@ -6,10 +6,12 @@ grpc_addr = "localhost:9090" # Chain ID of the Lumera network (used for signing and verification) chain_id = "lumera-devnet-1" + +# -------------------------------------------------- # Keyring settings for managing keys and identities [keyring] -# Backend for storing keys: "file", "test", or "os" +# Backend for storing keys: "file", "test", or "os" (default: test) backend = "test" # Directory where the keyring files are stored @@ -23,11 +25,25 @@ key_name = "sncli-account" # This is used as the "local peer" for secure key exchange and authentication local_address = "lumera1abc..." +# Keyring passphrase in a plain text +#passphrase_plain = "passphrase" + +# Keyring passphrase in a text file +#passphrase_file = "~/.path_to_passphrase_file" + +# Keyring passphrase in an environment variable +#passphrase_env = "passphrase_env_var" + + +# -------------------------------------------------- # Supernode connection settings [supernode] # gRPC endpoint of the Supernode you want to connect to -grpc_endpoint = "127.0.0.1:4444" +grpc_endpoint = "localhost:4444" + +# p2p endpoint of the Supernode you want to connect to +p2p_endpoint = "localhost:4445" # Bech32-encoded Cosmos address of the Supernode peer # Used to authenticate the remote peer during secure key exchange diff --git a/cmd/sncli/go.mod b/cmd/sncli/go.mod index 0f3a0c23..ef7bb7e0 100644 --- a/cmd/sncli/go.mod +++ b/cmd/sncli/go.mod @@ -11,9 +11,10 @@ replace ( require ( github.com/BurntSushi/toml v1.4.0 - github.com/LumeraProtocol/supernode/v2 v2.0.0-00010101000000-000000000000 + github.com/LumeraProtocol/lumera v1.7.0 + github.com/LumeraProtocol/supernode/v2 v2.1.0 github.com/cosmos/cosmos-sdk v0.50.14 - github.com/spf13/pflag v1.0.7 + github.com/spf13/cobra v1.9.1 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 ) @@ -33,9 +34,10 @@ require ( github.com/99designs/keyring v1.2.2 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.5 // indirect - github.com/LumeraProtocol/lumera v1.7.0 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect + github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -105,6 +107,8 @@ 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/jmoiron/sqlx v1.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -113,12 +117,16 @@ 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/mattn/go-sqlite3 v1.14.24 // 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 + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/oklog/run v1.1.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -137,7 +145,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.19.0 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -151,6 +159,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect + go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.36.0 // indirect @@ -166,6 +175,7 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect + lukechampine.com/blake3 v1.4.0 // indirect nhooyr.io/websocket v1.8.10 // indirect pgregory.net/rapid v1.2.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/cmd/sncli/go.sum b/cmd/sncli/go.sum index 62b166f6..95b01d4a 100644 --- a/cmd/sncli/go.sum +++ b/cmd/sncli/go.sum @@ -69,6 +69,7 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX 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= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -88,6 +89,8 @@ github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+ github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -99,12 +102,21 @@ github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2 github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bufbuild/protocompile v0.14.0 h1:z3DW4IvXE5G/uTOnSQn+qwQQxvhckkTWLS/0No/o7KU= github.com/bufbuild/protocompile v0.14.0/go.mod h1:N6J1NYzkspJo3ZwyL4Xjvli86XOj1xq4qAasUFxGups= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= @@ -190,11 +202,12 @@ github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIG github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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/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= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -282,6 +295,8 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -454,6 +469,7 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -461,13 +477,18 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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= @@ -475,6 +496,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW 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= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -518,6 +540,9 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= @@ -536,9 +561,12 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -594,6 +622,8 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= @@ -690,12 +720,12 @@ github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNo github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -776,6 +806,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -786,6 +818,8 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= @@ -794,12 +828,14 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= diff --git a/cmd/sncli/main.go b/cmd/sncli/main.go index 8eac23c6..f2c82ba5 100644 --- a/cmd/sncli/main.go +++ b/cmd/sncli/main.go @@ -1,14 +1,9 @@ package main import ( - "github.com/LumeraProtocol/supernode/v2/cmd/sncli/cli" + "github.com/LumeraProtocol/supernode/v2/cmd/sncli/cmd" ) func main() { - c := cli.NewCLI() - - c.Initialize() - defer c.Finalize() - - c.Run() + cmd.Execute() } diff --git a/p2p/kademlia/dht.go b/p2p/kademlia/dht.go index f6d93421..d47155ea 100644 --- a/p2p/kademlia/dht.go +++ b/p2p/kademlia/dht.go @@ -6,10 +6,11 @@ import ( "encoding/hex" "fmt" "math" - "strings" "sync" "sync/atomic" "time" + "net" + "net/url" "github.com/btcsuite/btcutil/base58" "github.com/cenkalti/backoff/v4" @@ -181,20 +182,24 @@ func (s *DHT) getSupernodeAddress(ctx context.Context) (string, error) { return supernodeInfo.LatestAddress, nil } -// parseSupernodeAddress extracts the host from various address formats +// parseSupernodeAddress extracts the host part from a URL or address string. +// It handles http/https prefixes, optional ports, and raw host:port formats. func parseSupernodeAddress(address string) string { - // Remove protocol prefixes - if strings.HasPrefix(address, "https://") { - address = strings.TrimPrefix(address, "https://") - } else if strings.HasPrefix(address, "http://") { - address = strings.TrimPrefix(address, "http://") + // If it looks like a URL, parse with net/url + if u, err := url.Parse(address); err == nil && u.Host != "" { + host, _, err := net.SplitHostPort(u.Host) + if err == nil { + return host + } + return u.Host // no port present } - // Extract host part (remove port if present) - if idx := strings.LastIndex(address, ":"); idx != -1 { - return address[:idx] + // If it’s just host:port, handle with SplitHostPort + if host, _, err := net.SplitHostPort(address); err == nil { + return host } + // Otherwise return as-is (probably just a bare host) return address } @@ -371,7 +376,7 @@ func (s *DHT) Stats(ctx context.Context) (map[string]interface{}, error) { return dhtStats, nil } -// new a message +// newMessage creates a new message func (s *DHT) newMessage(messageType int, receiver *Node, data interface{}) *Message { ctx := context.Background() supernodeAddr, _ := s.getSupernodeAddress(ctx) @@ -389,7 +394,7 @@ func (s *DHT) newMessage(messageType int, receiver *Node, data interface{}) *Mes } } -// GetValueFromNode get values from node +// GetValueFromNode gets values from node func (s *DHT) GetValueFromNode(ctx context.Context, target []byte, n *Node) ([]byte, error) { messageType := FindValue data := &FindValueRequest{Target: target}