diff --git a/cmd/planetmint-god/cmd/config.go b/cmd/planetmint-god/cmd/config.go index a3e874e..4ac256a 100644 --- a/cmd/planetmint-god/cmd/config.go +++ b/cmd/planetmint-god/cmd/config.go @@ -2,25 +2,57 @@ package cmd import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/planetmint/planetmint-go/app" ) -func initSDKConfig() *sdk.Config { - // Set prefixes - accountPubKeyPrefix := app.AccountAddressPrefix + "pub" - validatorAddressPrefix := app.AccountAddressPrefix + "valoper" - validatorPubKeyPrefix := app.AccountAddressPrefix + "valoperpub" - consNodeAddressPrefix := app.AccountAddressPrefix + "valcons" - consNodePubKeyPrefix := app.AccountAddressPrefix + "valconspub" +const ( + // Address prefix suffixes + pubKeySuffix = "pub" + valOperSuffix = "valoper" + valOperPubSuffix = "valoperpub" + valConsSuffix = "valcons" + valConsPubSuffix = "valconspub" - // Set and seal config + // PLMNT coin type as defined in SLIP44 + // https://github.com/satoshilabs/slips/blob/master/slip-0044.md + plmntCoinType = 8680 +) + +// initSDKConfig initializes and returns the SDK configuration with proper Bech32 prefixes +// and coin type settings for the Planetmint network. +func initSDKConfig() *sdk.Config { config := sdk.GetConfig() - config.SetBech32PrefixForAccount(app.AccountAddressPrefix, accountPubKeyPrefix) - config.SetBech32PrefixForValidator(validatorAddressPrefix, validatorPubKeyPrefix) - config.SetBech32PrefixForConsensusNode(consNodeAddressPrefix, consNodePubKeyPrefix) - // Set to PLMNT coin type as defined in SLIP44 (https://github.com/satoshilabs/slips/blob/master/slip-0044.md) - config.SetCoinType(8680) + + // Configure address prefixes + configureBech32Prefixes(config) + + // Set coin type for PLMNT + config.SetCoinType(plmntCoinType) + + // Seal the configuration to prevent further modifications config.Seal() + return config } + +// configureBech32Prefixes sets up all the Bech32 prefixes for different address types +// using the base account address prefix defined in the app package. +func configureBech32Prefixes(config *sdk.Config) { + // Account addresses + config.SetBech32PrefixForAccount( + app.AccountAddressPrefix, + app.AccountAddressPrefix+pubKeySuffix, + ) + + // Validator addresses + config.SetBech32PrefixForValidator( + app.AccountAddressPrefix+valOperSuffix, + app.AccountAddressPrefix+valOperPubSuffix, + ) + + // Consensus node addresses + config.SetBech32PrefixForConsensusNode( + app.AccountAddressPrefix+valConsSuffix, + app.AccountAddressPrefix+valConsPubSuffix, + ) +} diff --git a/config/config.go b/config/config.go index 123a235..acafe69 100644 --- a/config/config.go +++ b/config/config.go @@ -16,29 +16,40 @@ const DefaultConfigTemplate = ` ############################################################################### [planetmint] -mqtt-domain = "{{ .PlmntConfig.MqttDomain }}" -mqtt-port = {{ .PlmntConfig.MqttPort }} -mqtt-user = "{{ .PlmntConfig.MqttUser }}" -mqtt-password = "{{ .PlmntConfig.MqttPassword }}" -claim-host = "{{ .PlmntConfig.ClaimHost }}" -mqtt-tls = {{ .PlmntConfig.MqttTLS }} -issuer-host = "{{ .PlmntConfig.IssuerHost }}" certs-path = "{{ .PlmntConfig.CertsPath }}" +claim-host = "{{ .PlmntConfig.ClaimHost }}" +issuer-host = "{{ .PlmntConfig.IssuerHost }}" +mqtt-domain = "{{ .PlmntConfig.MqttDomain }}" +mqtt-password = "{{ .PlmntConfig.MqttPassword }}" +mqtt-port = {{ .PlmntConfig.MqttPort }} +mqtt-tls = {{ .PlmntConfig.MqttTLS }} +mqtt-user = "{{ .PlmntConfig.MqttUser }}" ` // ValAddr to be reomved see: https://github.com/planetmint/planetmint-go/issues/454 const ValAddr = "VALIDATOR_ADDRESS" +const ( + defaultCertsPath = "./certs/" + defaultClaimHost = "https://testnet-p2r.rddl.io" + defaultIssuerHost = "https://testnet-issuer.rddl.io" + defaultMqttDomain = "testnet-mqtt.rddl.io" + defaultMqttPassword = "password" + defaultMqttPort = 1886 + defaultMqttTLS = true + defaultMqttUser = "user" +) + // Config defines Planetmint's top level configuration type Config struct { - MqttDomain string `json:"mqtt-domain" mapstructure:"mqtt-domain"` - MqttPort int `json:"mqtt-port" mapstructure:"mqtt-port"` - MqttUser string `json:"mqtt-user" mapstructure:"mqtt-user"` - MqttPassword string `json:"mqtt-password" mapstructure:"mqtt-password"` - ClaimHost string `json:"claim-host" mapstructure:"claim-host"` - MqttTLS bool `json:"mqtt-tls" mapstructure:"mqtt-tls"` - IssuerHost string `json:"issuer-host" mapstructure:"issuer-host"` CertsPath string `json:"certs-path" mapstructure:"certs-path"` + ClaimHost string `json:"claim-host" mapstructure:"claim-host"` + IssuerHost string `json:"issuer-host" mapstructure:"issuer-host"` + MqttDomain string `json:"mqtt-domain" mapstructure:"mqtt-domain"` + MqttPassword string `json:"mqtt-password" mapstructure:"mqtt-password"` + MqttPort int `json:"mqtt-port" mapstructure:"mqtt-port"` + MqttTLS bool `json:"mqtt-tls" mapstructure:"mqtt-tls"` + MqttUser string `json:"mqtt-user" mapstructure:"mqtt-user"` } // cosmos-sdk wide global singleton @@ -50,14 +61,14 @@ var ( // DefaultConfig returns planetmint's default configuration. func DefaultConfig() *Config { return &Config{ - MqttDomain: "testnet-mqtt.rddl.io", - MqttPort: 1886, - MqttUser: "user", - MqttPassword: "password", - ClaimHost: "https://testnet-p2r.rddl.io", - MqttTLS: true, - IssuerHost: "https://testnet-issuer.rddl.io", - CertsPath: "./certs/", + CertsPath: defaultCertsPath, + ClaimHost: defaultClaimHost, + IssuerHost: defaultIssuerHost, + MqttDomain: defaultMqttDomain, + MqttPassword: defaultMqttPassword, + MqttPort: defaultMqttPort, + MqttTLS: defaultMqttTLS, + MqttUser: defaultMqttUser, } } @@ -75,37 +86,48 @@ func (config *Config) SetPlanetmintConfig(planetmintconfig interface{}) { if err != nil { panic(err) } - err = json.Unmarshal(jsonConfig, &config) - if err != nil { + if err := json.Unmarshal(jsonConfig, config); err != nil { panic(err) } } +// GetValidatorAddress retrieves the validator address through multiple methods func (config *Config) GetValidatorAddress() string { - // Case: testing - if os.Getenv(ValAddr) != "" { - return os.Getenv(ValAddr) + // Check environment variable first + if envAddr := os.Getenv(ValAddr); envAddr != "" { + return envAddr } libConfig := lib.GetConfig() - // Case: No Trust Wallet connected + // Handle no Trust Wallet connected scenario if libConfig.GetSerialPort() == "" { - defaultRecord, err := libConfig.GetDefaultValidatorRecord() - if err != nil { - logger.GetLogger(logger.ERROR).Error("msg", err.Error()) - return "" - } - addr, err := defaultRecord.GetAddress() - if err != nil { - logger.GetLogger(logger.ERROR).Error("msg", err.Error()) - return "" - } - - return addr.String() + return getDefaultValidatorAddress(libConfig) } - // Case: Trust Wallet connected + // Handle Trust Wallet connected scenario + return getTrustWalletValidatorAddress(libConfig) +} + +// getDefaultValidatorAddress retrieves the default validator address +func getDefaultValidatorAddress(libConfig *lib.Config) string { + defaultRecord, err := libConfig.GetDefaultValidatorRecord() + if err != nil { + logger.GetLogger(logger.ERROR).Error("msg", err.Error()) + return "" + } + + addr, err := defaultRecord.GetAddress() + if err != nil { + logger.GetLogger(logger.ERROR).Error("msg", err.Error()) + return "" + } + + return addr.String() +} + +// getTrustWalletValidatorAddress retrieves validator address from Trust Wallet +func getTrustWalletValidatorAddress(libConfig *lib.Config) string { connector, err := trustwallet.NewTrustWalletConnector(libConfig.GetSerialPort()) if err != nil { logger.GetLogger(logger.ERROR).Error("msg", err.Error()) diff --git a/config/params.go b/config/params.go index b093c5f..31f707d 100644 --- a/config/params.go +++ b/config/params.go @@ -5,7 +5,12 @@ import ( ) // LiquidNetParams defines the network parameters for the Liquid network. -var LiquidNetParams chaincfg.Params +var LiquidNetParams = func() chaincfg.Params { + params := chaincfg.MainNetParams + params.Name = "liquidv1" + params.HDCoinType = 1776 + return params +}() // PlmntNetParams defines the network parameters for the Planetmint network. var PlmntNetParams = chaincfg.Params{ @@ -21,17 +26,9 @@ var PlmntNetParams = chaincfg.Params{ } func init() { - // Not allowed to register LiquidNetParams, because it's just another - // Bitcoin network with different coin type. - // See https://github.com/satoshilabs/slips/blob/master/slip-0044.md - LiquidNetParams = chaincfg.MainNetParams - LiquidNetParams.Name = "liquidv1" - LiquidNetParams.HDCoinType = 1776 - // Need to register PlmntNetParams, otherwise we get an "unknown hd // private extended key bytes" error. - err := chaincfg.Register(&PlmntNetParams) - if err != nil { + if err := chaincfg.Register(&PlmntNetParams); err != nil { panic(err) } } diff --git a/lib/config.go b/lib/config.go index 2a5fa30..aecf471 100644 --- a/lib/config.go +++ b/lib/config.go @@ -1,6 +1,7 @@ package lib import ( + "errors" "os" "sync" @@ -10,134 +11,168 @@ import ( "github.com/planetmint/planetmint-go/lib/params" ) -// Config defines library top level configuration. -type Config struct { - chainID string - clientCtx client.Context - encodingConfig params.EncodingConfig - feeDenom string - rootDir string - rpcEndpoint string - txGas uint64 - serialPort string -} - -// lib wide global singleton var ( - libConfig *Config - sdkConfig *sdk.Config - initConfig sync.Once - changeLock sync.Mutex + // ErrInvalidConfig is returned when configuration validation fails + ErrInvalidConfig = errors.New("invalid configuration") + + // Global singleton instances + instance *Config + mu sync.RWMutex + once sync.Once + sdkConfig *sdk.Config ) -// DefaultConfig returns library default configuration. -func DefaultConfig() *Config { +// Config defines the top-level configuration for the Planetmint library. +// All fields are exported to allow external access while maintaining +// thread-safe modifications through methods. +type Config struct { + ChainID string + ClientCtx client.Context + EncodingConfig params.EncodingConfig + FeeDenom string + RPCEndpoint string + RootDir string + SerialPort string + TxGas uint64 +} + +// NewConfig creates a new Config instance with default values. +func NewConfig() *Config { return &Config{ - chainID: "planetmint-testnet-1", - clientCtx: client.Context{}, - encodingConfig: params.EncodingConfig{}, - feeDenom: "plmnt", - rootDir: "~/.planetmint-go/", - rpcEndpoint: "http://127.0.0.1:26657", - txGas: 200000, - serialPort: "", + ChainID: "planetmint-testnet-1", + ClientCtx: client.Context{}, + EncodingConfig: params.EncodingConfig{}, + FeeDenom: "plmnt", + RPCEndpoint: "http://127.0.0.1:26657", + RootDir: "~/.planetmint-go/", + TxGas: 200000, } } -// GetConfig returns the config instance for the SDK. +// GetConfig returns the singleton Config instance, initializing it if necessary. func GetConfig() *Config { - initConfig.Do(func() { - libConfig = DefaultConfig() + once.Do(func() { + instance = NewConfig() sdkConfig = sdk.GetConfig() - libConfig.SetBech32PrefixForAccount("plmnt") + // Initialize default configuration + instance.SetBech32PrefixForAccount("plmnt") encodingConfig := MakeEncodingConfig() - libConfig.SetEncodingConfig(encodingConfig) + instance.SetEncodingConfig(encodingConfig) }) - return libConfig + return instance } -// SetBech32PrefixForAccount sets the bech32 account prefix. -func (config *Config) SetBech32PrefixForAccount(bech32Prefix string) *Config { - changeLock.Lock() - defer changeLock.Unlock() - sdkConfig.SetBech32PrefixForAccount(bech32Prefix, "pub") - return config +// Validate checks if the configuration is valid. +func (c *Config) Validate() error { + mu.RLock() + defer mu.RUnlock() + + if c.ChainID == "" { + return errors.New("chain ID cannot be empty") + } + if c.RPCEndpoint == "" { + return errors.New("RPC endpoint cannot be empty") + } + if c.TxGas == 0 { + return errors.New("transaction gas cannot be zero") + } + return nil } -// SetEncodingConfig sets the encoding config and must not be nil. -func (config *Config) SetEncodingConfig(encodingConfig params.EncodingConfig) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.encodingConfig = encodingConfig - return config +// Builder methods + +func (c *Config) SetBech32PrefixForAccount(prefix string) *Config { + mu.Lock() + defer mu.Unlock() + sdkConfig.SetBech32PrefixForAccount(prefix, "pub") + return c } -// SetChainID sets the chain ID parameter. -func (config *Config) SetChainID(chainID string) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.chainID = chainID - return config +func (c *Config) SetEncodingConfig(config params.EncodingConfig) *Config { + mu.Lock() + defer mu.Unlock() + c.EncodingConfig = config + return c } -// SetClientCtx sets the client context parameter. -func (config *Config) SetClientCtx(clientCtx client.Context) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.clientCtx = clientCtx - return config +func (c *Config) SetChainID(chainID string) *Config { + mu.Lock() + defer mu.Unlock() + c.ChainID = chainID + return c } -// SetFeeDenom sets the fee denominator parameter. -func (config *Config) SetFeeDenom(feeDenom string) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.feeDenom = feeDenom - return config +func (c *Config) SetClientCtx(ctx client.Context) *Config { + mu.Lock() + defer mu.Unlock() + c.ClientCtx = ctx + return c } -// SetRoot sets the root directory where to find the keyring. -func (config *Config) SetRoot(root string) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.rootDir = root - return config +func (c *Config) SetFeeDenom(denom string) *Config { + mu.Lock() + defer mu.Unlock() + c.FeeDenom = denom + return c } -// SetRPCEndpoint sets the RPC endpoint to send requests to. -func (config *Config) SetRPCEndpoint(rpcEndpoint string) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.rpcEndpoint = rpcEndpoint - return config +func (c *Config) SetRoot(root string) *Config { + mu.Lock() + defer mu.Unlock() + c.RootDir = root + return c } -// SetTxGas sets the amount of Gas for the TX that is send to the network -func (config *Config) SetTxGas(txGas uint64) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.txGas = txGas - return config +func (c *Config) SetRPCEndpoint(endpoint string) *Config { + mu.Lock() + defer mu.Unlock() + c.RPCEndpoint = endpoint + return c } -func (config *Config) SetSerialPort(port string) *Config { - changeLock.Lock() - defer changeLock.Unlock() - config.serialPort = port - return config +func (c *Config) SetTxGas(gas uint64) *Config { + mu.Lock() + defer mu.Unlock() + c.TxGas = gas + return c } -func (config *Config) GetSerialPort() string { - return config.serialPort +func (c *Config) SetSerialPort(port string) *Config { + mu.Lock() + defer mu.Unlock() + c.SerialPort = port + return c } -func (config *Config) getLibKeyring() (keyring.Keyring, error) { - return keyring.New("lib", keyring.BackendTest, config.rootDir, os.Stdin, config.encodingConfig.Marshaler, []keyring.Option{}...) +// Getter methods + +func (c *Config) GetSerialPort() string { + mu.RLock() + defer mu.RUnlock() + return c.SerialPort } -func (config *Config) GetDefaultValidatorRecord() (*keyring.Record, error) { - keyring, err := config.getLibKeyring() +// Keyring operations + +// GetLibKeyring returns a new keyring instance configured with the current settings. +func (c *Config) GetLibKeyring() (keyring.Keyring, error) { + mu.RLock() + defer mu.RUnlock() + + return keyring.New( + "lib", + keyring.BackendTest, + c.RootDir, + os.Stdin, + c.EncodingConfig.Marshaler, + []keyring.Option{}..., + ) +} + +// GetDefaultValidatorRecord returns the first validator record from the keyring. +func (c *Config) GetDefaultValidatorRecord() (*keyring.Record, error) { + keyring, err := c.GetLibKeyring() if err != nil { return nil, err } @@ -147,5 +182,9 @@ func (config *Config) GetDefaultValidatorRecord() (*keyring.Record, error) { return nil, err } + if len(records) == 0 { + return nil, errors.New("no keyring records found") + } + return records[0], nil } diff --git a/lib/tx.go b/lib/tx.go index daa31b0..a4f7e38 100644 --- a/lib/tx.go +++ b/lib/tx.go @@ -45,7 +45,7 @@ func getAccountNumberAndSequence(clientCtx client.Context) (accountNumber, seque } func getClientContextAndTxFactory(fromAddress sdk.AccAddress, withoutFee bool) (clientCtx client.Context, txf tx.Factory, err error) { - clientCtx = GetConfig().clientCtx + clientCtx = GetConfig().ClientCtx // at least we need an account retriever // it would be better to check for an empty client context, but that does not work at the moment if clientCtx.AccountRetriever == nil { @@ -81,22 +81,22 @@ func getTxFactoryWithAccountNumberAndSequence(clientCtx client.Context, accountN WithAccountRetriever(clientCtx.AccountRetriever). WithChainID(clientCtx.ChainID). WithFeeGranter(clientCtx.FeeGranter). - WithGas(GetConfig().txGas). - WithGasPrices(gasPrice + GetConfig().feeDenom). + WithGas(GetConfig().TxGas). + WithGasPrices(gasPrice + GetConfig().FeeDenom). WithKeybase(clientCtx.Keyring). WithSequence(sequence). WithTxConfig(clientCtx.TxConfig) } func getClientContext(fromAddress sdk.AccAddress) (clientCtx client.Context, err error) { - encodingConfig := GetConfig().encodingConfig + encodingConfig := GetConfig().EncodingConfig - rootDir := GetConfig().rootDir + rootDir := GetConfig().RootDir input := os.Stdin codec := encodingConfig.Marshaler keyringOptions := []keyring.Option{} - keyring, err := GetConfig().getLibKeyring() + keyring, err := GetConfig().GetLibKeyring() if err != nil { return } @@ -106,7 +106,7 @@ func getClientContext(fromAddress sdk.AccAddress) (clientCtx client.Context, err return } - remote := GetConfig().rpcEndpoint + remote := GetConfig().RPCEndpoint wsClient, err := comethttp.New(remote, "/websocket") if err != nil { return @@ -117,7 +117,7 @@ func getClientContext(fromAddress sdk.AccAddress) (clientCtx client.Context, err clientCtx = client.Context{ AccountRetriever: authtypes.AccountRetriever{}, BroadcastMode: "sync", - ChainID: GetConfig().chainID, + ChainID: GetConfig().ChainID, Client: wsClient, Codec: codec, From: fromAddress.String(), @@ -222,7 +222,7 @@ func BroadcastTxWithFileLock(fromAddress sdk.AccAddress, msgs ...sdk.Msg) (out * // Set new sequence number txf = txf.WithSequence(sequence) - if GetConfig().serialPort != "" { + if GetConfig().SerialPort != "" { out, err = broadcastTxWithTrustWalletSignature(clientCtx, txf, msgs...) } else { out, err = broadcastTx(clientCtx, txf, msgs...) @@ -304,7 +304,7 @@ func writeClientCtxOutputToBuffer(clientCtx client.Context) (out *bytes.Buffer, } func signWithTrustWallet(txf tx.Factory, clientCtx client.Context, txBuilder client.TxBuilder) error { - connector, err := trustwallet.NewTrustWalletConnector(GetConfig().serialPort) + connector, err := trustwallet.NewTrustWalletConnector(GetConfig().SerialPort) if err != nil { return err } diff --git a/lib/utils.go b/lib/utils.go index 2174cd1..4d4c7fc 100644 --- a/lib/utils.go +++ b/lib/utils.go @@ -60,7 +60,7 @@ func createSequenceDirectory() (path string, err error) { return } homeDir := usr.HomeDir - path = filepath.Join(GetConfig().rootDir, "sequence") + path = filepath.Join(GetConfig().RootDir, "sequence") // expand tilde to user's home directory if strings.HasPrefix(path, "~/") { path = filepath.Join(homeDir, path[2:])