From e6b35bd2e5cebf9fdb3ddc8aae13feac8258f397 Mon Sep 17 00:00:00 2001 From: anhthii Date: Wed, 10 Sep 2025 14:33:53 +0700 Subject: [PATCH 1/8] Update consul api to latest version --- go.mod | 10 +++++----- go.sum | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 4b75cb98..d9cc99b5 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,20 @@ module github.com/fystack/mpcium -go 1.23.0 +go 1.23.8 -toolchain go1.23.5 +toolchain go1.24.7 require ( filippo.io/age v1.2.1 github.com/avast/retry-go v3.0.0+incompatible github.com/aws/aws-sdk-go-v2/config v1.31.4 + github.com/aws/aws-sdk-go-v2/credentials v1.18.8 github.com/aws/aws-sdk-go-v2/service/kms v1.45.0 github.com/bnb-chain/tss-lib/v2 v2.0.2 github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3 github.com/dgraph-io/badger/v4 v4.7.0 github.com/google/uuid v1.6.0 - github.com/hashicorp/consul/api v1.26.1 + github.com/hashicorp/consul/api v1.32.1 github.com/mitchellh/mapstructure v1.5.0 github.com/nats-io/nats.go v1.31.0 github.com/rs/zerolog v1.31.0 @@ -29,7 +30,6 @@ require ( github.com/agl/ed25519 v0.0.0-20200225211852-fd4d107ace12 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go-v2 v1.38.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.8 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.5 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.5 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.5 // indirect @@ -96,7 +96,7 @@ require ( go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.21.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.24.0 // indirect diff --git a/go.sum b/go.sum index 125692d3..da2768ea 100644 --- a/go.sum +++ b/go.sum @@ -160,8 +160,11 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= +github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE= +github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -406,6 +409,8 @@ golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= From 1c59894dc8f33d224e4fceba3fcccf1b174f7581 Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 11 Sep 2025 09:52:23 +0700 Subject: [PATCH 2/8] Improve error message --- pkg/identity/identity.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/identity/identity.go b/pkg/identity/identity.go index dbb32a5d..2d2f1eee 100644 --- a/pkg/identity/identity.go +++ b/pkg/identity/identity.go @@ -268,7 +268,8 @@ func loadPrivateKey(identityDir, nodeName string, decrypt bool, agePasswordFile if decrypt { // Use the encrypted age file if _, err := os.Stat(encryptedKeyPath); err != nil { - return "", fmt.Errorf("no encrypted private key found for node %s", nodeName) + return "", fmt.Errorf("failed to check encrypted private key for node %s at %s: %w", + nodeName, encryptedKeyPath, err) } logger.Infof("Using age-encrypted private key for %s", nodeName) From 3da79de754ca9175494a2c61afdb32dd507c0d0f Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 11 Sep 2025 12:58:33 +0700 Subject: [PATCH 3/8] Remove environment from .env file in setup-config.sh script --- deployments/systemd/setup-config.sh | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/deployments/systemd/setup-config.sh b/deployments/systemd/setup-config.sh index c1a285b2..30041f51 100755 --- a/deployments/systemd/setup-config.sh +++ b/deployments/systemd/setup-config.sh @@ -109,29 +109,6 @@ check_root() { fi } -# Get environment from config.yaml -get_environment_from_config() { - log_step "Reading environment from config.yaml..." - - local config_file="/etc/mpcium/config.yaml" - - if [[ ! -f "$config_file" ]]; then - log_error "Config file not found at $config_file" - exit 1 - fi - - # Extract environment from config.yaml - local environment=$(grep "^environment:" "$config_file" | sed 's/environment: *//g' | sed 's/"//g' | sed "s/'//g") - - if [[ -z "$environment" ]]; then - environment="production" # default - log_warn "Environment not specified in config.yaml, defaulting to production" - fi - - # Store the environment for later use - MPCIUM_ENVIRONMENT="$environment" - log_info "Environment from config.yaml: $MPCIUM_ENVIRONMENT" -} # Prompt for MPCIUM_NODE_NAME if not provided setup_node_name() { @@ -559,7 +536,6 @@ setup_environment_file() { # Mpcium Environment Variables # Generated on $(date) # Note: All credentials are now configured in /etc/mpcium/config.yaml -ENVIRONMENT=${MPCIUM_ENVIRONMENT} MPCIUM_NODE_NAME=${MPCIUM_NODE_NAME} EOF @@ -734,7 +710,6 @@ main() { log_info "Starting Mpcium configuration setup..." check_root - get_environment_from_config setup_node_name check_binaries ensure_configuration From fbcb3bbd21bece09b797a7dfd6ce39fdc0769b79 Mon Sep 17 00:00:00 2001 From: anhthii Date: Fri, 12 Sep 2025 23:41:48 +0700 Subject: [PATCH 4/8] Minor cleanup --- .gitignore | 3 ++- peers.json | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 peers.json diff --git a/.gitignore b/.gitignore index 769d7d3e..08c8a391 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ node0 node1 node2 config.yaml -.vscode \ No newline at end of file +.vscode +.vagrant diff --git a/peers.json b/peers.json deleted file mode 100644 index 5f44aed3..00000000 --- a/peers.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "node0": "0ce02715-0ead-48ef-9772-2583316cc860", - "node1": "c95c340e-5a18-472d-b9b0-5ac68218213a", - "node2": "ac37e85f-caca-4bee-8a3a-49a0fe35abff" -} From 18beff8270fdce4cefe681e88595e65c327f8255 Mon Sep 17 00:00:00 2001 From: anhthii Date: Fri, 12 Sep 2025 23:52:15 +0700 Subject: [PATCH 5/8] (fix) #102: allow to specify peers.json in mpcium-cli --- cmd/mpcium-cli/main.go | 8 ++++---- cmd/mpcium-cli/register-peers.go | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cmd/mpcium-cli/main.go b/cmd/mpcium-cli/main.go index 319ef314..20b3e634 100644 --- a/cmd/mpcium-cli/main.go +++ b/cmd/mpcium-cli/main.go @@ -51,10 +51,10 @@ func main() { Action: registerPeers, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "input", - Aliases: []string{"i"}, - Usage: "Input peers JSON file path (default: peers.json)", - Value: peersFileName, + Name: "peers", + Aliases: []string{"p"}, + Usage: "Path to peers.json file", + Required: true, }, &cli.StringFlag{ Name: "environment", diff --git a/cmd/mpcium-cli/register-peers.go b/cmd/mpcium-cli/register-peers.go index 85d66586..45646389 100644 --- a/cmd/mpcium-cli/register-peers.go +++ b/cmd/mpcium-cli/register-peers.go @@ -15,7 +15,7 @@ import ( ) func registerPeers(ctx context.Context, c *cli.Command) error { - inputPath := c.String("input") + inputPath := c.String("peers") environment := c.String("environment") // Hardcoded prefix for MPC peers in Consul @@ -58,10 +58,26 @@ func registerPeers(ctx context.Context, c *cli.Command) error { // Register peers in Consul for nodeName, nodeID := range peerMap { key := prefix + nodeName + + // Check if the key already exists + existing, _, err := kv.Get(key, nil) + if err != nil { + return fmt.Errorf("failed to check existing key %s: %w", key, err) + } + + if existing != nil { + existingID := string(existing.Value) + if existingID != nodeID { + return fmt.Errorf("conflict detected: peer %s already exists with ID %s, but trying to register with different ID %s", nodeName, existingID, nodeID) + } + fmt.Printf("Peer %s already registered with same ID %s, skipping\n", nodeName, nodeID) + continue + } + p := &api.KVPair{Key: key, Value: []byte(nodeID)} // Store the key-value pair - _, err := kv.Put(p, nil) + _, err = kv.Put(p, nil) if err != nil { return fmt.Errorf("failed to store key %s: %w", key, err) } From e77315d6365a833935f117a6fdaa37e9b0dfc214 Mon Sep 17 00:00:00 2001 From: anhthii Date: Sat, 13 Sep 2025 00:07:04 +0700 Subject: [PATCH 6/8] Parse config into struct --- cmd/mpcium/main.go | 35 ++++++++++++---------- pkg/config/config_test.go | 62 ++++++++++++++++++++++++++++++++++++--- pkg/config/init.go | 27 +++++++++++++---- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/cmd/mpcium/main.go b/cmd/mpcium/main.go index f1762ee4..a68eb0d8 100644 --- a/cmd/mpcium/main.go +++ b/cmd/mpcium/main.go @@ -113,7 +113,9 @@ func runNode(ctx context.Context, c *cli.Command) error { viper.SetDefault("backup_enabled", true) config.InitViperConfig(configPath) - environment := viper.GetString("environment") + + appConfig := config.LoadConfig() + environment := appConfig.Environment logger.Init(environment, debug) // Handle password file if provided @@ -127,7 +129,7 @@ func runNode(ctx context.Context, c *cli.Command) error { promptForSensitiveCredentials() } else { // Validate the config values - checkRequiredConfigValues() + checkRequiredConfigValues(appConfig) } consulClient := infra.GetConsulClient(environment) @@ -135,7 +137,7 @@ func runNode(ctx context.Context, c *cli.Command) error { peers := LoadPeersFromConsul(consulClient) nodeID := GetIDFromName(nodeName, peers) - badgerKV := NewBadgerKV(nodeName, nodeID) + badgerKV := NewBadgerKV(nodeName, nodeID, appConfig) defer badgerKV.Close() // Start background backup job @@ -151,7 +153,7 @@ func runNode(ctx context.Context, c *cli.Command) error { logger.Fatal("Failed to create identity store", err) } - natsConn, err := GetNATSConnection(environment) + natsConn, err := GetNATSConnection(environment, appConfig) if err != nil { logger.Fatal("Failed to connect to NATS", err) } @@ -388,9 +390,9 @@ func maskString(s string) string { } // Check required configuration values are present -func checkRequiredConfigValues() { +func checkRequiredConfigValues(appConfig *config.AppConfig) { // Show warning if we're using file-based config but no password is set - if viper.GetString("badger_password") == "" { + if appConfig.BadgerPassword == "" { logger.Fatal("Badger password is required", nil) } @@ -441,7 +443,7 @@ func GetIDFromName(name string, peers []config.Peer) string { return nodeID } -func NewBadgerKV(nodeName, nodeID string) *kvstore.BadgerKVStore { +func NewBadgerKV(nodeName, nodeID string, appConfig *config.AppConfig) *kvstore.BadgerKVStore { // Badger KV DB // Use configured db_path or default to current directory + "db" basePath := viper.GetString("db_path") @@ -459,8 +461,8 @@ func NewBadgerKV(nodeName, nodeID string) *kvstore.BadgerKVStore { // Create BadgerConfig struct config := kvstore.BadgerConfig{ NodeID: nodeName, - EncryptionKey: []byte(viper.GetString("badger_password")), - BackupEncryptionKey: []byte(viper.GetString("badger_password")), // Using same key for backup encryption + EncryptionKey: []byte(appConfig.BadgerPassword), + BackupEncryptionKey: []byte(appConfig.BadgerPassword), // Using same key for backup encryption BackupDir: backupDir, DBPath: dbPath, } @@ -499,8 +501,8 @@ func StartPeriodicBackup(ctx context.Context, badgerKV *kvstore.BadgerKVStore, p return backupCancel } -func GetNATSConnection(environment string) (*nats.Conn, error) { - url := viper.GetString("nats.url") +func GetNATSConnection(environment string, appConfig *config.AppConfig) (*nats.Conn, error) { + url := appConfig.NATs.URL opts := []nats.Option{ nats.MaxReconnects(-1), // retry forever nats.ReconnectWait(2 * time.Second), @@ -517,9 +519,12 @@ func GetNATSConnection(environment string) (*nats.Conn, error) { if environment == constant.EnvProduction { // Load TLS config from configuration - clientCert := viper.GetString("nats.tls.client_cert") - clientKey := viper.GetString("nats.tls.client_key") - caCert := viper.GetString("nats.tls.ca_cert") + var clientCert, clientKey, caCert string + if appConfig.NATs.TLS != nil { + clientCert = appConfig.NATs.TLS.ClientCert + clientKey = appConfig.NATs.TLS.ClientKey + caCert = appConfig.NATs.TLS.CACert + } // Fallback to default paths if not configured if clientCert == "" { @@ -535,7 +540,7 @@ func GetNATSConnection(environment string) (*nats.Conn, error) { opts = append(opts, nats.ClientCert(clientCert, clientKey), nats.RootCAs(caCert), - nats.UserInfo(viper.GetString("nats.username"), viper.GetString("nats.password")), + nats.UserInfo(appConfig.NATs.Username, appConfig.NATs.Password), ) } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 9de41e3e..ac69a128 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -26,9 +26,9 @@ func TestAppConfig_MarshalJSONMask(t *testing.T) { masked := config.MarshalJSONMask() // Verify that sensitive data is masked - assert.Contains(t, masked, "localhost:8500") // Address should not be masked - assert.Contains(t, masked, "admin") // Username should not be masked - assert.Contains(t, masked, "nats_user") // Username should not be masked + assert.Contains(t, masked, "localhost:8500") // Address should not be masked + assert.Contains(t, masked, "admin") // Username should not be masked + assert.Contains(t, masked, "nats_user") // Username should not be masked assert.Contains(t, masked, "nats://localhost:4222") // URL should not be masked // Verify that passwords are masked @@ -120,4 +120,58 @@ func TestAppConfig_PartialConfig(t *testing.T) { assert.Contains(t, masked, "localhost:8500") assert.NotContains(t, masked, "test") assert.Contains(t, masked, "****") // masked badger password -} \ No newline at end of file +} + +func TestValidateEnvironment(t *testing.T) { + tests := []struct { + name string + environment string + wantErr bool + }{ + { + name: "valid production environment", + environment: "production", + wantErr: false, + }, + { + name: "valid development environment", + environment: "development", + wantErr: false, + }, + { + name: "invalid environment", + environment: "staging", + wantErr: true, + }, + { + name: "empty environment", + environment: "", + wantErr: true, + }, + { + name: "case sensitive - Production", + environment: "Production", + wantErr: true, + }, + { + name: "case sensitive - PRODUCTION", + environment: "PRODUCTION", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateEnvironment(tt.environment) + if tt.wantErr { + assert.Error(t, err) + if err != nil { + assert.Contains(t, err.Error(), "invalid environment") + assert.Contains(t, err.Error(), "production, development") + } + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/config/init.go b/pkg/config/init.go index a9edbab2..db8e7336 100644 --- a/pkg/config/init.go +++ b/pkg/config/init.go @@ -2,6 +2,7 @@ package config import ( "encoding/json" + "fmt" "log" "strings" @@ -14,6 +15,7 @@ type AppConfig struct { Consul *ConsulConfig `mapstructure:"consul"` NATs *NATsConfig `mapstructure:"nats"` + Environment string `mapstructure:"environment"` BadgerPassword string `mapstructure:"badger_password"` } @@ -58,13 +60,12 @@ func InitViperConfig(configPath string) { viper.SetConfigFile(configPath) } else { // Use default behavior - search for config.yaml in common locations - viper.SetConfigName("config") // name of config file (without extension) - viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name - viper.AddConfigPath(".") // optionally look for config in the working directory - viper.AddConfigPath("/etc/mpcium/") // look for config in /etc/mpcium/ + viper.SetConfigName("config") // name of config file (without extension) + viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name + viper.AddConfigPath(".") // optionally look for config in the working directory + viper.AddConfigPath("/etc/mpcium/") // look for config in /etc/mpcium/ viper.AddConfigPath("$HOME/.mpcium/") // look for config in home directory } - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() err := viper.ReadInConfig() // Find and read the config file @@ -96,5 +97,21 @@ func LoadConfig() *AppConfig { log.Fatal("Failed to decode config", err) } + if err := validateEnvironment(config.Environment); err != nil { + log.Fatal("Config validation failed:", err) + } + return &config } + +func validateEnvironment(environment string) error { + validEnvironments := []string{"production", "development"} + + for _, validEnv := range validEnvironments { + if environment == validEnv { + return nil + } + } + + return fmt.Errorf("invalid environment '%s'. Must be one of: %s", environment, strings.Join(validEnvironments, ", ")) +} From 1408ccb2f89e2453bfe9a901f9db32b66d000973 Mon Sep 17 00:00:00 2001 From: anhthii Date: Sat, 13 Sep 2025 00:11:21 +0700 Subject: [PATCH 7/8] Add log for consul initialization --- pkg/infra/consul.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/infra/consul.go b/pkg/infra/consul.go index a438c933..f25f77ea 100644 --- a/pkg/infra/consul.go +++ b/pkg/infra/consul.go @@ -32,8 +32,21 @@ func GetConsulClient(environment string) *api.Client { config.Address = viper.GetString("consul.address") config.WaitTime = 10 * time.Second - // Ping the Consul server to verify connectivity + tokenLength := 0 + if config.Token != "" { + tokenLength = len(config.Token) + } + + logger.Info("Consul config", + "environment", environment, + "address", config.Address, + "wait_time", config.WaitTime, + "token_length", tokenLength, + "http_auth", config.HttpAuth, + ) + + // Ping the Consul server to verify connectivity client, err := api.NewClient(config) if err != nil { logger.Fatal("Failed to create consul client", err) From 12312b69d49b28ec2634aa99fa706b2205b36244 Mon Sep 17 00:00:00 2001 From: anhthii Date: Sat, 13 Sep 2025 00:23:40 +0700 Subject: [PATCH 8/8] Don't require peers in mpcium-cli, default to ./peers.json --- cmd/mpcium-cli/main.go | 4 ++-- cmd/mpcium-cli/register-peers.go | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/mpcium-cli/main.go b/cmd/mpcium-cli/main.go index 20b3e634..9731a2bb 100644 --- a/cmd/mpcium-cli/main.go +++ b/cmd/mpcium-cli/main.go @@ -53,8 +53,8 @@ func main() { &cli.StringFlag{ Name: "peers", Aliases: []string{"p"}, - Usage: "Path to peers.json file", - Required: true, + Usage: "Path to peers.json file (defaults to ./peers.json)", + Required: false, }, &cli.StringFlag{ Name: "environment", diff --git a/cmd/mpcium-cli/register-peers.go b/cmd/mpcium-cli/register-peers.go index 45646389..6f4aa57a 100644 --- a/cmd/mpcium-cli/register-peers.go +++ b/cmd/mpcium-cli/register-peers.go @@ -18,6 +18,11 @@ func registerPeers(ctx context.Context, c *cli.Command) error { inputPath := c.String("peers") environment := c.String("environment") + // If no peers path specified, check for peers.json in current directory + if inputPath == "" { + inputPath = "peers.json" + } + // Hardcoded prefix for MPC peers in Consul prefix := "mpc_peers/" @@ -28,6 +33,9 @@ func registerPeers(ctx context.Context, c *cli.Command) error { // Check if input file exists if _, err := os.Stat(inputPath); os.IsNotExist(err) { + if inputPath == "peers.json" { + return fmt.Errorf("peers.json not found in current directory. Please specify the path using --peers flag or create peers.json in the current directory") + } return fmt.Errorf("input file %s does not exist", inputPath) }