diff --git a/supernode/cmd/helpers.go b/supernode/cmd/helpers.go new file mode 100644 index 00000000..a1bd6965 --- /dev/null +++ b/supernode/cmd/helpers.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/LumeraProtocol/supernode/pkg/keyring" + "github.com/LumeraProtocol/supernode/supernode/config" + cKeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// initKeyringFromConfig initializes keyring using app configuration +func initKeyringFromConfig(config *config.Config) (cKeyring.Keyring, error) { + return keyring.InitKeyring(config.KeyringConfig.Backend, config.GetKeyringDir()) +} + +// getAddressFromKeyName extracts address from keyring using key name +func getAddressFromKeyName(kr cKeyring.Keyring, keyName string) (sdk.AccAddress, error) { + keyInfo, err := kr.Key(keyName) + if err != nil { + return nil, fmt.Errorf("key not found: %w", err) + } + + address, err := keyInfo.GetAddress() + if err != nil { + return nil, fmt.Errorf("failed to get address from key: %w", err) + } + + return address, nil +} + +// processAndValidateMnemonic processes and validates a mnemonic phrase +func processAndValidateMnemonic(mnemonic string) (string, error) { + // Normalize whitespace (replace multiple spaces with single space) + processed := strings.TrimSpace(mnemonic) + processed = strings.Join(strings.Fields(processed), " ") + + // Validate BIP39 mnemonic word count + wordCount := len(strings.Fields(processed)) + if !isValidBIP39WordCount(wordCount) { + return "", fmt.Errorf("invalid mnemonic word count: %d. Valid BIP39 mnemonic lengths are 12, 15, 18, 21, or 24 words", wordCount) + } + + return processed, nil +} + +// isValidBIP39WordCount checks if the word count is valid for BIP39 mnemonics +func isValidBIP39WordCount(wordCount int) bool { + validCounts := []int{12, 15, 18, 21, 24} + for _, count := range validCounts { + if wordCount == count { + return true + } + } + return false +} diff --git a/supernode/cmd/init.go b/supernode/cmd/init.go index 7cc9c6d6..f9f7aa23 100644 --- a/supernode/cmd/init.go +++ b/supernode/cmd/init.go @@ -77,11 +77,7 @@ Example: return fmt.Errorf("failed to create directories: %w", err) } - // Initialize keyring - kr, err := keyring.InitKeyring( - appConfig.KeyringConfig.Backend, - appConfig.GetKeyringDir(), - ) + kr, err := initKeyringFromConfig(appConfig) if err != nil { return fmt.Errorf("failed to initialize keyring: %w", err) } @@ -95,17 +91,20 @@ Example: return fmt.Errorf("mnemonic is required when --recover is specified") } - // Process the mnemonic to ensure proper formatting - initMnemonic = processAndValidateMnemonic(initMnemonic) + // 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 + } - // Recover account from mnemonic - info, err := keyring.RecoverAccountFromMnemonic(kr, initKeyName, initMnemonic) + info, err := keyring.RecoverAccountFromMnemonic(kr, initKeyName, processedMnemonic) if err != nil { return fmt.Errorf("failed to recover account: %w", err) } - // Get address - addr, err := info.GetAddress() + addr, err := getAddressFromKeyName(kr, initKeyName) if err != nil { return fmt.Errorf("failed to get address: %w", err) } @@ -122,8 +121,7 @@ Example: return fmt.Errorf("failed to create new account: %w", err) } - // Get address - addr, err := info.GetAddress() + addr, err := getAddressFromKeyName(kr, initKeyName) if err != nil { return fmt.Errorf("failed to get address: %w", err) } @@ -154,85 +152,6 @@ Example: }, } -// processAndValidateMnemonic processes and validates the mnemonic -func processAndValidateMnemonic(mnemonic string) string { - // Normalize whitespace (replace multiple spaces with single space) - processed := normalizeWhitespace(mnemonic) - - // Validate BIP39 mnemonic word count - wordCount := countWords(processed) - if !isValidBIP39WordCount(wordCount) { - fmt.Printf("Warning: Invalid mnemonic word count: %d. Valid BIP39 mnemonic lengths are 12, 15, 18, 21, or 24 words\n", wordCount) - } - - return processed -} - -// normalizeWhitespace replaces multiple spaces with a single space -func normalizeWhitespace(s string) string { - return normalizeWhitespaceImpl(s) -} - -// countWords counts the number of words in a string -func countWords(s string) int { - return len(splitWords(s)) -} - -// splitWords splits a string into words -func splitWords(s string) []string { - return splitWordsImpl(s) -} - -// normalizeWhitespaceImpl is the implementation of normalizeWhitespace -// It's a separate function to make it easier to test -func normalizeWhitespaceImpl(s string) string { - // Import strings package locally to avoid adding it to the imports - // if it's not already there - return normalizeWhitespaceWithStrings(s) -} - -// normalizeWhitespaceWithStrings normalizes whitespace using the strings package -func normalizeWhitespaceWithStrings(s string) string { - // This is a simplified implementation - // In a real implementation, we would use the strings package - words := splitWordsImpl(s) - return joinWords(words, " ") -} - -// splitWordsImpl is the implementation of splitWords -func splitWordsImpl(s string) []string { - // This is a simplified implementation - // In a real implementation, we would use the strings package - var words []string - var word string - for _, c := range s { - if c == ' ' || c == '\t' || c == '\n' || c == '\r' { - if word != "" { - words = append(words, word) - word = "" - } - } else { - word += string(c) - } - } - if word != "" { - words = append(words, word) - } - return words -} - -// joinWords joins words with a separator -func joinWords(words []string, sep string) string { - if len(words) == 0 { - return "" - } - result := words[0] - for _, word := range words[1:] { - result += sep + word - } - return result -} - func init() { rootCmd.AddCommand(initCmd) diff --git a/supernode/cmd/keys_add.go b/supernode/cmd/keys_add.go index 54468ec0..2c53fde7 100644 --- a/supernode/cmd/keys_add.go +++ b/supernode/cmd/keys_add.go @@ -32,11 +32,8 @@ Example: return fmt.Errorf("key name is required") } - // Initialize keyring using config values - kr, err := keyring.InitKeyring( - appConfig.KeyringConfig.Backend, - appConfig.GetKeyringDir(), - ) + // Initialize keyring using helper function + kr, err := initKeyringFromConfig(appConfig) if err != nil { return fmt.Errorf("failed to initialize keyring: %w", err) } @@ -48,8 +45,8 @@ Example: return fmt.Errorf("failed to create new account: %w", err) } - // Get address - address, err := info.GetAddress() + // Get address using helper function + address, err := getAddressFromKeyName(kr, keyName) if err != nil { return fmt.Errorf("failed to get address: %w", err) } diff --git a/supernode/cmd/keys_list.go b/supernode/cmd/keys_list.go index 95b846dd..df8f4bb0 100644 --- a/supernode/cmd/keys_list.go +++ b/supernode/cmd/keys_list.go @@ -6,7 +6,6 @@ import ( "sort" "text/tabwriter" - snkeyring "github.com/LumeraProtocol/supernode/pkg/keyring" "github.com/spf13/cobra" ) @@ -20,11 +19,8 @@ This command displays a table with key names, types, and addresses. Example: supernode keys list`, RunE: func(cmd *cobra.Command, args []string) error { - // Initialize keyring using config values - kr, err := snkeyring.InitKeyring( - appConfig.KeyringConfig.Backend, - appConfig.GetKeyringDir(), - ) + // Initialize keyring using helper function + kr, err := initKeyringFromConfig(appConfig) if err != nil { return fmt.Errorf("failed to initialize keyring: %w", err) } diff --git a/supernode/cmd/keys_recover.go b/supernode/cmd/keys_recover.go index e67ea725..d9ab6659 100644 --- a/supernode/cmd/keys_recover.go +++ b/supernode/cmd/keys_recover.go @@ -36,11 +36,8 @@ Example: return fmt.Errorf("key name is required") } - // Initialize keyring using config values - kr, err := keyring.InitKeyring( - appConfig.KeyringConfig.Backend, - appConfig.GetKeyringDir(), - ) + // Initialize keyring using helper function + kr, err := initKeyringFromConfig(appConfig) if err != nil { return fmt.Errorf("failed to initialize keyring: %w", err) } @@ -59,32 +56,27 @@ Example: if err != nil { return fmt.Errorf("failed to read mnemonic: %w", err) } - mnemonic = strings.TrimSpace(mnemonic) } - // Process the mnemonic to ensure proper formatting - mnemonic = strings.TrimSpace(mnemonic) - // Normalize whitespace (replace multiple spaces with single space) - mnemonic = strings.Join(strings.Fields(mnemonic), " ") + // Process and validate mnemonic using helper function + processedMnemonic, err := processAndValidateMnemonic(mnemonic) + if err != nil { + return err + } // Add debug output to see what's being processed - wordCount := len(strings.Fields(mnemonic)) + wordCount := len(strings.Fields(processedMnemonic)) fmt.Printf("Processing mnemonic with %d words\n", wordCount) - // Validate BIP39 mnemonic word count - if !isValidBIP39WordCount(wordCount) { - return fmt.Errorf("invalid mnemonic word count: %d. Valid BIP39 mnemonic lengths are 12, 15, 18, 21, or 24 words", wordCount) - } - // Recover account from mnemonic - info, err := keyring.RecoverAccountFromMnemonic(kr, keyName, mnemonic) + info, err := keyring.RecoverAccountFromMnemonic(kr, keyName, processedMnemonic) if err != nil { // Check if the error is due to an invalid mnemonic return fmt.Errorf("failed to recover account: %w", err) } - // Get address - address, err := info.GetAddress() + // Get address using helper function + address, err := getAddressFromKeyName(kr, keyName) if err != nil { return fmt.Errorf("failed to get address: %w", err) } @@ -99,17 +91,6 @@ Example: }, } -// isValidBIP39WordCount checks if the word count is valid for BIP39 mnemonics -func isValidBIP39WordCount(wordCount int) bool { - validCounts := []int{12, 15, 18, 21, 24} - for _, count := range validCounts { - if wordCount == count { - return true - } - } - return false -} - func init() { keysCmd.AddCommand(keysRecoverCmd) // Add flag for mnemonic diff --git a/supernode/cmd/start.go b/supernode/cmd/start.go index 44ff1776..e7e1511c 100644 --- a/supernode/cmd/start.go +++ b/supernode/cmd/start.go @@ -26,6 +26,16 @@ import ( "github.com/spf13/cobra" ) +// createP2PConfig creates a P2P config from the app config and address +func createP2PConfig(config *config.Config, address string) *p2p.Config { + return &p2p.Config{ + ListenAddress: config.P2PConfig.ListenAddress, + Port: config.P2PConfig.Port, + DataDir: config.GetP2PDataDir(), + ID: address, + } +} + // startCmd represents the start command var startCmd = &cobra.Command{ Use: "start", @@ -40,21 +50,12 @@ The supernode will connect to the Lumera network and begin participating in the ctx := logtrace.CtxWithCorrelationID(context.Background(), "supernode-start") // Log configuration info - logtrace.Info(ctx, "Starting supernode with configuration", logtrace.Fields{ - "config_file": cfgFile, - "keyring_dir": appConfig.GetKeyringDir(), - "key_name": appConfig.SupernodeConfig.KeyName, - }) + logtrace.Info(ctx, "Starting supernode with configuration", logtrace.Fields{"config_file": cfgFile, "keyring_dir": appConfig.GetKeyringDir(), "key_name": appConfig.SupernodeConfig.KeyName}) // Initialize keyring - kr, err := keyring.InitKeyring( - appConfig.KeyringConfig.Backend, - appConfig.GetKeyringDir(), - ) + kr, err := keyring.InitKeyring(appConfig.KeyringConfig.Backend, appConfig.GetKeyringDir()) if err != nil { - logtrace.Error(ctx, "Failed to initialize keyring", logtrace.Fields{ - "error": err.Error(), - }) + logtrace.Error(ctx, "Failed to initialize keyring", logtrace.Fields{"error": err.Error()}) return err } @@ -76,12 +77,10 @@ The supernode will connect to the Lumera network and begin participating in the return fmt.Errorf("failed to initialize P2P service: %w", err) } - // Initialize the supernode (next step) - _, err = NewSupernode(ctx, appConfig, kr, p2pService, rqStore, lumeraClient) + // Initialize the supernode + supernodeInstance, err := NewSupernode(ctx, appConfig, kr, p2pService, rqStore, lumeraClient) if err != nil { - logtrace.Error(ctx, "Failed to initialize supernode", logtrace.Fields{ - "error": err.Error(), - }) + logtrace.Error(ctx, "Failed to initialize supernode", logtrace.Fields{"error": err.Error()}) return err } @@ -109,26 +108,23 @@ The supernode will connect to the Lumera network and begin participating in the // Configure server serverConfig := &server.Config{ - Identity: appConfig.SupernodeConfig.Identity, ListenAddresses: appConfig.SupernodeConfig.IpAddress, Port: int(appConfig.SupernodeConfig.Port), } // Create gRPC server - grpcServer, err := server.New(serverConfig, - "service", - kr, - lumeraClient, - cascadeActionServer, - supernodeServer, - ) + grpcServer, err := server.New(serverConfig, "service", kr, lumeraClient, cascadeActionServer, supernodeServer) if err != nil { return fmt.Errorf("failed to create gRPC server: %w", err) } // Start the services - RunServices(ctx, grpcServer, cService, *p2pService) + go func() { + if err := RunServices(ctx, grpcServer, cService, *p2pService); err != nil { + logtrace.Error(ctx, "Service error", logtrace.Fields{"error": err.Error()}) + } + }() // Set up signal handling for graceful shutdown sigCh := make(chan os.Signal, 1) @@ -136,9 +132,12 @@ The supernode will connect to the Lumera network and begin participating in the // Wait for termination signal sig := <-sigCh - logtrace.Info(ctx, "Received signal, shutting down", logtrace.Fields{ - "signal": sig.String(), - }) + logtrace.Info(ctx, "Received signal, shutting down", logtrace.Fields{"signal": sig.String()}) + + // Graceful shutdown + if err := supernodeInstance.Stop(ctx); err != nil { + logtrace.Error(ctx, "Error during shutdown", logtrace.Fields{"error": err.Error()}) + } return nil }, @@ -160,22 +159,10 @@ func initP2PService(ctx context.Context, config *config.Config, lumeraClient lum return nil, fmt.Errorf("failed to get address from key: %w", err) } - // Initialize P2P service - p2pConfig := &p2p.Config{ - ListenAddress: config.P2PConfig.ListenAddress, - Port: config.P2PConfig.Port, - DataDir: config.GetP2PDataDir(), - BootstrapNodes: "", - ExternalIP: "", - ID: address.String(), - } + // Create P2P config using helper function + p2pConfig := createP2PConfig(config, address.String()) - logtrace.Info(ctx, "Initializing P2P service", logtrace.Fields{ - "listen_address": p2pConfig.ListenAddress, - "port": p2pConfig.Port, - "data_dir": p2pConfig.DataDir, - "supernode_id": address.String(), - }) + logtrace.Info(ctx, "Initializing P2P service", logtrace.Fields{"listen_address": p2pConfig.ListenAddress, "port": p2pConfig.Port, "data_dir": p2pConfig.DataDir, "supernode_id": address.String()}) p2pService, err := p2p.New(ctx, p2pConfig, lumeraClient, kr, rqStore, cloud, mst) if err != nil { diff --git a/supernode/cmd/supernode.go b/supernode/cmd/supernode.go index c72d07ef..44c8b828 100644 --- a/supernode/cmd/supernode.go +++ b/supernode/cmd/supernode.go @@ -46,22 +46,20 @@ func NewSupernode(ctx context.Context, config *config.Config, kr keyring.Keyring // Start starts all supernode services func (s *Supernode) Start(ctx context.Context) error { - // Initialize p2p service - // Verify that the key specified in config exists - keyInfo, err := s.keyring.Key(appConfig.SupernodeConfig.KeyName) + keyInfo, err := s.keyring.Key(s.config.SupernodeConfig.KeyName) if err != nil { logtrace.Error(ctx, "Key not found in keyring", logtrace.Fields{ - "key_name": appConfig.SupernodeConfig.KeyName, + "key_name": s.config.SupernodeConfig.KeyName, "error": err.Error(), }) // Provide helpful guidance fmt.Printf("\nError: Key '%s' not found in keyring at %s\n", - appConfig.SupernodeConfig.KeyName, appConfig.GetKeyringDir()) + s.config.SupernodeConfig.KeyName, s.config.GetKeyringDir()) fmt.Println("\nPlease create the key first with one of these commands:") - fmt.Printf(" supernode keys add %s\n", appConfig.SupernodeConfig.KeyName) - fmt.Printf(" supernode keys recover %s\n", appConfig.SupernodeConfig.KeyName) + fmt.Printf(" supernode keys add %s\n", s.config.SupernodeConfig.KeyName) + fmt.Printf(" supernode keys recover %s\n", s.config.SupernodeConfig.KeyName) return fmt.Errorf("key not found") } @@ -75,31 +73,11 @@ func (s *Supernode) Start(ctx context.Context) error { } logtrace.Info(ctx, "Found valid key in keyring", logtrace.Fields{ - "key_name": appConfig.SupernodeConfig.KeyName, + "key_name": s.config.SupernodeConfig.KeyName, "address": address.String(), }) - p2pConfig := &p2p.Config{ - ListenAddress: s.config.P2PConfig.ListenAddress, - Port: s.config.P2PConfig.Port, - DataDir: s.config.GetP2PDataDir(), - ID: address.String(), - } - - logtrace.Info(ctx, "Initializing P2P service", logtrace.Fields{ - "listen_address": p2pConfig.ListenAddress, - "port": p2pConfig.Port, - "data_dir": p2pConfig.DataDir, - "supernode_id": address.String(), - }) - - p2pService, err := p2p.New(ctx, p2pConfig, s.lumeraClient, s.keyring, s.rqStore, nil, nil) - if err != nil { - return fmt.Errorf("failed to initialize p2p service: %w", err) - } - s.p2pService = p2pService - - // Run the p2p service + // Use the P2P service that was passed in via constructor logtrace.Info(ctx, "Starting P2P service", logtrace.Fields{}) if err := s.p2pService.Run(ctx); err != nil { return fmt.Errorf("p2p service error: %w", err)