diff --git a/shutter/Dockerfile b/shutter/Dockerfile index 33abb02..34b56ac 100644 --- a/shutter/Dockerfile +++ b/shutter/Dockerfile @@ -24,7 +24,7 @@ RUN apt-get update && \ ENV SHUTTER_GNOSIS_SM_BLOCKTIME=10 \ SHUTTER_GNOSIS_GENESIS_KEYPER=0x440Dc6F164e9241F04d282215ceF2780cd0B755e \ SHUTTER_GNOSIS_MAXTXPOINTERAGE=5 \ - SHUTTER_DATABASEURL=postgres://postgres@db.shutter-${NETWORK}.dappnode:5432/keyper \ + SHUTTER_DATABASE_URL=postgres://postgres@db.shutter-${NETWORK}.dappnode:5432/keyper \ SHUTTER_SHUTTERMINT_SHUTTERMINTURL=http://localhost:26657 \ CHAIN_LISTEN_PORT=26657 \ SHUTTER_BIN=/rolling-shutter \ diff --git a/shutter/go-dappnode-shutter/go.mod b/shutter/go-dappnode-shutter/go.mod new file mode 100644 index 0000000..7094bae --- /dev/null +++ b/shutter/go-dappnode-shutter/go.mod @@ -0,0 +1,7 @@ +module go-dappnode-shutter + +go 1.22.2 + +require github.com/joho/godotenv v1.5.1 + +require github.com/pelletier/go-toml/v2 v2.2.3 // indirect diff --git a/shutter/go-dappnode-shutter/go.sum b/shutter/go-dappnode-shutter/go.sum new file mode 100644 index 0000000..b994414 --- /dev/null +++ b/shutter/go-dappnode-shutter/go.sum @@ -0,0 +1,6 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= diff --git a/shutter/go-dappnode-shutter/main.go b/shutter/go-dappnode-shutter/main.go new file mode 100644 index 0000000..a26d3d3 --- /dev/null +++ b/shutter/go-dappnode-shutter/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "flag" + "fmt" + "go-dappnode-shutter/settings" + "log" + "os" + + "github.com/joho/godotenv" +) + +func main() { + // Define flags for the template, config, and output paths + templateFilePath := flag.String("template", "", "Path to the template file where settings will be included") + configFilePath := flag.String("config", "", "Path to the config file where the settings will be read") + outputFilePath := flag.String("output", "", "Path where the modified settings will be saved") + + // Parse the flags + flag.Parse() + + // Load environment variables from the .env file + err := godotenv.Load(os.Getenv("ASSETS_DIR") + "/variables.env") + if err != nil { + log.Fatalf("Error loading .env file: %v", err) + } + + // Check for additional arguments, e.g., keyper or chain + if len(flag.Args()) < 1 { + fmt.Println("Error: missing argument. Use 'include-keyper-settings' or 'include-chain-settings'.") + os.Exit(1) + } + + // Read the argument passed to the program + argument := flag.Arg(0) + + // Call appropriate function based on the command + switch argument { + case "include-keyper-settings": + // Ensure template, config, and output paths are provided + if *templateFilePath == "" || *configFilePath == "" || *outputFilePath == "" { + fmt.Println("Error: --template, --config, and --output flags must be provided for keyper settings.") + flag.Usage() + os.Exit(1) + } + + // Call the function to configure keyper + err := settings.AddSettingsToKeyper(*templateFilePath, *configFilePath, *outputFilePath) + if err != nil { + log.Fatalf("Failed to configure keyper: %v", err) + } + + case "include-chain-settings": + // Ensure config and output paths are provided + if *configFilePath == "" || *outputFilePath == "" { + fmt.Println("Error: --config and --output flags must be provided for chain settings.") + flag.Usage() + os.Exit(1) + } + + // Call the function to configure chain + err := settings.AddSettingsToChain(*configFilePath, *outputFilePath) + if err != nil { + log.Fatalf("Failed to configure chain: %v", err) + } + + default: + fmt.Println("Invalid argument. Use 'include-keyper-settings' or 'include-chain-settings'.") + os.Exit(1) + } + + fmt.Println("Configuration completed successfully!") +} diff --git a/shutter/go-dappnode-shutter/settings/chain.go b/shutter/go-dappnode-shutter/settings/chain.go new file mode 100644 index 0000000..1ab4dd8 --- /dev/null +++ b/shutter/go-dappnode-shutter/settings/chain.go @@ -0,0 +1,105 @@ +package settings + +import ( + "fmt" + "os" + + "github.com/pelletier/go-toml/v2" +) + +type ChainConfig struct { + PrivateKey string + InstanceID string +} + +/** +* This function should: +* - Read all the values from the template configuration file +* - Read the values defined in ChainConfig from the chain configuration file +* - Read the environment variables +* - Copy all the values from the template configuration file to the output configuration file +* - Modify the values using the chain configuration file and environment variables (these have more priority) +* - Save the modified configuration file +* - Return an error if any +*/ +func AddSettingsToChain(templateFilePath, outputFilePath string) error { + + var chainConfig ChainConfig + + chainConfigPath := os.Getenv("SHUTTER_CHAIN_CONFIG_FILE") + + // Check that the chain configuration file exists + if _, err := os.Stat(chainConfigPath); os.IsNotExist(err) { + fmt.Println("Chain configuration file does not exist:", chainConfigPath) + os.Exit(1) + } + + + chainConfigFile, err := os.ReadFile(chainConfigPath) + if err != nil { + fmt.Println("Error reading TOML file:", err) + os.Exit(1) + } + + err = toml.Unmarshal(chainConfigFile, &chainConfig) + if err != nil { + fmt.Println("Error unmarshalling TOML file:", err) + os.Exit(1) + } + + // Read the template configuration file + templateFile, err := os.ReadFile(templateFilePath) + if err != nil { + return fmt.Errorf("error reading template TOML file: %v", err) + } + + // Create a map to hold the template configuration + var templateConfig map[string]interface{} + err = toml.Unmarshal(templateFile, &templateConfig) + if err != nil { + return fmt.Errorf("error unmarshalling template TOML file: %v", err) + } + + // Modify the template configuration based on ChainConfig and environment variables + // ChainConfig values take priority over the template values + applyChainConfig(&templateConfig, chainConfig) + + // Apply environment variables, which have even higher priority than the ChainConfig + applyEnvOverrides(&templateConfig) + + // Marshal the modified configuration to TOML format + modifiedConfig, err := toml.Marshal(templateConfig) + if err != nil { + return fmt.Errorf("error marshalling modified config to TOML: %v", err) + } + + // Write the modified configuration to the output file + err = os.WriteFile(outputFilePath, modifiedConfig, 0644) + if err != nil { + return fmt.Errorf("error writing modified TOML file: %v", err) + } + + fmt.Println("TOML file modified successfully and saved to", outputFilePath) + return nil +} + +// applyChainConfig modifies the template configuration based on the values from ChainConfig +func applyChainConfig(templateConfig *map[string]interface{}, chainConfig ChainConfig) { + if chainConfig.PrivateKey != "" { + (*templateConfig)["PrivateKey"] = chainConfig.PrivateKey + } + if chainConfig.InstanceID != "" { + (*templateConfig)["InstanceID"] = chainConfig.InstanceID + } +} + +// applyEnvOverrides applies environment variables to the template configuration, giving them the highest priority +func applyEnvOverrides(templateConfig *map[string]interface{}) { + // Example: Check for environment variables and override values if they exist + if privateKey := os.Getenv("PRIVATE_KEY"); privateKey != "" { + (*templateConfig)["PrivateKey"] = privateKey + } + if instanceID := os.Getenv("INSTANCE_ID"); instanceID != "" { + (*templateConfig)["InstanceID"] = instanceID + } +} \ No newline at end of file diff --git a/shutter/go-dappnode-shutter/settings/keyper.go b/shutter/go-dappnode-shutter/settings/keyper.go new file mode 100644 index 0000000..b09239c --- /dev/null +++ b/shutter/go-dappnode-shutter/settings/keyper.go @@ -0,0 +1,152 @@ +package settings + +import ( + "fmt" + "os" + "reflect" + "strconv" + "strings" + + "github.com/pelletier/go-toml/v2" +) + +type KeyperConfig struct { + InstanceID int `env:"_ASSETS_INSTANCE_ID"` + MaxNumKeysPerMessage int `env:"_ASSETS_MAX_NUM_KEYS_PER_MESSAGE"` + EncryptedGasLimit int `env:"_ASSETS_ENCRYPTED_GAS_LIMIT"` + GenesisSlotTimestamp int `env:"_ASSETS_GENESIS_SLOT_TIMESTAMP"` + SyncStartBlockNumber int `env:"_ASSETS_SYNC_START_BLOCK_NUMBER"` + KeyperSetManager string `env:"_ASSETS_KEYPER_SET_MANAGER"` + KeyBroadcastContract string `env:"_ASSETS_KEY_BROADCAST_CONTRACT"` + Sequencer string `env:"_ASSETS_SEQUENCER"` + ValidatorRegistry string `env:"_ASSETS_VALIDATOR_REGISTRY"` + DiscoveryNamespace string `env:"_ASSETS_DISCOVERY_NAME_PREFIX"` + CustomBootstrapAddresses []string `env:"_ASSETS_CUSTOM_BOOTSTRAP_ADDRESSES"` + DKGPhaseLength int `env:"_ASSETS_DKG_PHASE_LENGTH"` + DKGStartBlockDelta int `env:"_ASSETS_DKG_START_BLOCK_DELTA"` + + DatabaseURL string `env:"SHUTTER_DATABASE_URL"` + BeaconAPIURL string `env:"SHUTTER_BEACONAPIURL"` + ContractsURL string `env:"SHUTTER_GNOSIS_NODE_CONTRACTSURL"` + MaxTxPointerAge int `env:"SHUTTER_GNOSIS_MAXTXPOINTERAGE"` + DeploymentDir string `env:"SHUTTER_DEPLOYMENT_DIR"` // Unused, but you can still add an env if needed + EthereumURL string `env:"SHUTTER_GNOSIS_NODE_ETHEREUMURL"` + ShuttermintURL string `env:"SHUTTER_SHUTTERMINT_SHUTTERMINTURL"` + ListenAddresses string `env:"SHUTTER_P2P_LISTENADDRESSES"` + AdvertiseAddresses string `env:"SHUTTER_P2P_ADVERTISEADDRESSES"` + ValidatorPublicKey string `env:"VALIDATOR_PUBLIC_KEY"` + Enabled bool `env:"SHUTTER_ENABLED"` +} + +// AddSettingsToKeyper modifies the keyper settings by combining the template, config, and environment variables. +func AddSettingsToKeyper(templateFilePath, configFilePath, outputFilePath string) error { + var keyperConfig KeyperConfig + + fmt.Println("Adding user settings to keyper...") + + // Read the keyper config file + configFile, err := os.ReadFile(configFilePath) + if err != nil { + return fmt.Errorf("error reading chain config TOML file: %v", err) + } + + // Unmarshal the chain config TOML into the chainConfig struct + err = toml.Unmarshal(configFile, &keyperConfig) + if err != nil { + return fmt.Errorf("error unmarshalling chain config TOML file: %v", err) + } + + // Read the template file + templateFile, err := os.ReadFile(templateFilePath) + if err != nil { + return fmt.Errorf("error reading template TOML file: %v", err) + } + + // Create a map to hold the template configuration + var templateConfig map[string]interface{} + err = toml.Unmarshal(templateFile, &templateConfig) + if err != nil { + return fmt.Errorf("error unmarshalling template TOML file: %v", err) + } + + // Modify the template configuration based on ChainConfig and environment variables + applyKeyperConfig(&templateConfig, keyperConfig) + applyEnvOverrides(&templateConfig) + + // Marshal the modified configuration to TOML format + modifiedConfig, err := toml.Marshal(templateConfig) + if err != nil { + return fmt.Errorf("error marshalling modified config to TOML: %v", err) + } + + // Write the modified configuration to the output file + err = os.WriteFile(outputFilePath, modifiedConfig, 0644) + if err != nil { + return fmt.Errorf("error writing modified TOML file: %v", err) + } + + fmt.Println("Keyper TOML file modified successfully and saved to", outputFilePath) + return nil +} + +// applyKeyperConfig modifies the template based on environment variables, config, and default template values +func applyKeyperConfig(templateConfig *map[string]interface{}, keyperConfig KeyperConfig) { + v := reflect.ValueOf(keyperConfig) + t := reflect.TypeOf(keyperConfig) + + for i := 0; i < v.NumField(); i++ { + fieldValue := v.Field(i) + fieldType := t.Field(i) + + // Get the environment variable tag + envVar := fieldType.Tag.Get("env") + + // Get the corresponding field value based on the priority (env > config > template) + fieldName := fieldType.Name + + var finalValue interface{} + + // 1. Check environment variable + envValue := os.Getenv(envVar) + if envValue != "" { + finalValue = parseEnvValue(envValue, fieldType.Type) + } else if !isZeroValue(fieldValue.Interface()) { + // 2. Use config value if it exists and is non-zero + finalValue = fieldValue.Interface() + } else { + // 3. Fallback to template value (do nothing as it's already in template) + finalValue = (*templateConfig)[fieldName] + } + + setTemplateField(templateConfig, fieldName, finalValue) + } +} + +// Helper function to update template configuration +func setTemplateField(templateConfig *map[string]interface{}, key string, value interface{}) { + if !isZeroValue(value) { + (*templateConfig)[key] = value + } +} + +// Parse environment variable value into the correct type +func parseEnvValue(value string, valueType reflect.Type) interface{} { + switch valueType.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intValue, _ := strconv.ParseInt(value, 10, 64) + return intValue + case reflect.Bool: + boolValue, _ := strconv.ParseBool(value) + return boolValue + case reflect.Slice: + // Assume comma-separated values for slice + return strings.Split(value, ",") + default: + return value + } +} + +// Check if a value is the zero value of its type +func isZeroValue(value interface{}) bool { + return reflect.DeepEqual(value, reflect.Zero(reflect.TypeOf(value)).Interface()) +} diff --git a/shutter/scripts/configure.sh b/shutter/scripts/configure.sh index a056901..d62aa04 100755 --- a/shutter/scripts/configure.sh +++ b/shutter/scripts/configure.sh @@ -12,8 +12,8 @@ generate_keyper_config() { # Check if the configuration file already exists if [ -f "$KEYPER_GENERATED_CONFIG_FILE" ]; then - echo "[INFO | configure] Configuration file already exists. Skipping generation..." - return + echo "[INFO | configure] Configuration file already exists. Removing it..." + rm "$KEYPER_GENERATED_CONFIG_FILE" fi echo "[INFO | configure] Generating configuration files..." diff --git a/shutter/scripts/configure_keyper.sh b/shutter/scripts/configure_keyper.sh index 346a32f..31da1dd 100755 --- a/shutter/scripts/configure_keyper.sh +++ b/shutter/scripts/configure_keyper.sh @@ -50,7 +50,7 @@ sed -i "/^DKGPhaseLength/c\DKGPhaseLength = ${_ASSETS_DKG_PHASE_LENGTH}" "$KEYPE sed -i "/^DKGStartBlockDelta/c\DKGStartBlockDelta = ${_ASSETS_DKG_START_BLOCK_DELTA}" "$KEYPER_CONFIG_FILE" # Dynamic values (regenerated on each start) -sed -i "/^DatabaseURL/c\DatabaseURL = \"${SHUTTER_DATABASEURL}\"" "$KEYPER_CONFIG_FILE" +sed -i "/^DatabaseURL/c\DatabaseURL = \"${SHUTTER_DATABASE_URL}\"" "$KEYPER_CONFIG_FILE" sed -i "/^BeaconAPIURL/c\BeaconAPIURL = \"${SHUTTER_BEACONAPIURL}\"" "$KEYPER_CONFIG_FILE" sed -i "/^ContractsURL/c\ContractsURL = \"${SHUTTER_GNOSIS_NODE_CONTRACTSURL}\"" "$KEYPER_CONFIG_FILE" sed -i "/^MaxTxPointerAge/c\MaxTxPointerAge = ${SHUTTER_GNOSIS_MAXTXPOINTERAGE}" "$KEYPER_CONFIG_FILE" diff --git a/shutter/scripts/configure_shuttermint.sh b/shutter/scripts/configure_shuttermint.sh index 92391ff..b4200d9 100755 --- a/shutter/scripts/configure_shuttermint.sh +++ b/shutter/scripts/configure_shuttermint.sh @@ -3,16 +3,23 @@ # shellcheck disable=SC1091 . "${ASSETS_DIR}/variables.env" +if [ ! -f "$SHUTTER_CHAIN_CONFIG_FILE" ]; then + echo "[ERROR | configure] Missing chain configuration file (${SHUTTER_CHAIN_CONFIG_FILE})" + exit 1 +fi + ASSETS_GENESIS_FILE="/assets/genesis.json" CHAIN_GENESIS_FILE="${SHUTTER_CHAIN_DIR}/config/genesis.json" rm "$CHAIN_GENESIS_FILE" ln -s "$ASSETS_GENESIS_FILE" "$CHAIN_GENESIS_FILE" -SHUTTERMINT_MONIKER=${KEYPER_NAME:-$(openssl rand -hex 8)} +# KEYPER_NAME=${KEYPER_NAME:-$(openssl rand -hex 8)} + +# TODO: Call the go binary sed -i "/^seeds =/c\seeds = \"${_ASSETS_SHUTTERMINT_SEED_NODES}\"" "$SHUTTER_CHAIN_CONFIG_FILE" -sed -i "/^moniker =/c\moniker = \"${SHUTTERMINT_MONIKER}\"" "$SHUTTER_CHAIN_CONFIG_FILE" +sed -i "/^moniker =/c\moniker = \"${KEYPER_NAME}\"" "$SHUTTER_CHAIN_CONFIG_FILE" sed -i "/^genesis_file =/c\genesis_file = \"${ASSETS_GENESIS_FILE}\"" "$SHUTTER_CHAIN_CONFIG_FILE" sed -i "/^external_address =/c\external_address = \"${_DAPPNODE_GLOBAL_PUBLIC_IP}:${CHAIN_PORT}\"" "$SHUTTER_CHAIN_CONFIG_FILE" sed -i "/^addr_book_strict =/c\addr_book_strict = true" "$SHUTTER_CHAIN_CONFIG_FILE"