refactor configs (#479)

* refactor(config): improve readability/maintainability
* refactor(lib): make config more readable

- export variables from struct
- lock/defer lock

Signed-off-by: Julian Strobl <jmastr@mailbox.org>
This commit is contained in:
Julian Strobl 2024-11-19 09:44:53 +01:00 committed by GitHub
parent de63f1dddc
commit 190f24a10b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 258 additions and 168 deletions

View File

@ -2,25 +2,57 @@ package cmd
import ( import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/planetmint/planetmint-go/app" "github.com/planetmint/planetmint-go/app"
) )
func initSDKConfig() *sdk.Config { const (
// Set prefixes // Address prefix suffixes
accountPubKeyPrefix := app.AccountAddressPrefix + "pub" pubKeySuffix = "pub"
validatorAddressPrefix := app.AccountAddressPrefix + "valoper" valOperSuffix = "valoper"
validatorPubKeyPrefix := app.AccountAddressPrefix + "valoperpub" valOperPubSuffix = "valoperpub"
consNodeAddressPrefix := app.AccountAddressPrefix + "valcons" valConsSuffix = "valcons"
consNodePubKeyPrefix := app.AccountAddressPrefix + "valconspub" 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 := sdk.GetConfig()
config.SetBech32PrefixForAccount(app.AccountAddressPrefix, accountPubKeyPrefix)
config.SetBech32PrefixForValidator(validatorAddressPrefix, validatorPubKeyPrefix) // Configure address prefixes
config.SetBech32PrefixForConsensusNode(consNodeAddressPrefix, consNodePubKeyPrefix) configureBech32Prefixes(config)
// Set to PLMNT coin type as defined in SLIP44 (https://github.com/satoshilabs/slips/blob/master/slip-0044.md)
config.SetCoinType(8680) // Set coin type for PLMNT
config.SetCoinType(plmntCoinType)
// Seal the configuration to prevent further modifications
config.Seal() config.Seal()
return config 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,
)
}

View File

@ -16,29 +16,40 @@ const DefaultConfigTemplate = `
############################################################################### ###############################################################################
[planetmint] [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 }}" 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 // ValAddr to be reomved see: https://github.com/planetmint/planetmint-go/issues/454
const ValAddr = "VALIDATOR_ADDRESS" 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 // Config defines Planetmint's top level configuration
type Config struct { 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"` 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 // cosmos-sdk wide global singleton
@ -50,14 +61,14 @@ var (
// DefaultConfig returns planetmint's default configuration. // DefaultConfig returns planetmint's default configuration.
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
MqttDomain: "testnet-mqtt.rddl.io", CertsPath: defaultCertsPath,
MqttPort: 1886, ClaimHost: defaultClaimHost,
MqttUser: "user", IssuerHost: defaultIssuerHost,
MqttPassword: "password", MqttDomain: defaultMqttDomain,
ClaimHost: "https://testnet-p2r.rddl.io", MqttPassword: defaultMqttPassword,
MqttTLS: true, MqttPort: defaultMqttPort,
IssuerHost: "https://testnet-issuer.rddl.io", MqttTLS: defaultMqttTLS,
CertsPath: "./certs/", MqttUser: defaultMqttUser,
} }
} }
@ -75,27 +86,37 @@ func (config *Config) SetPlanetmintConfig(planetmintconfig interface{}) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = json.Unmarshal(jsonConfig, &config) if err := json.Unmarshal(jsonConfig, config); err != nil {
if err != nil {
panic(err) panic(err)
} }
} }
// GetValidatorAddress retrieves the validator address through multiple methods
func (config *Config) GetValidatorAddress() string { func (config *Config) GetValidatorAddress() string {
// Case: testing // Check environment variable first
if os.Getenv(ValAddr) != "" { if envAddr := os.Getenv(ValAddr); envAddr != "" {
return os.Getenv(ValAddr) return envAddr
} }
libConfig := lib.GetConfig() libConfig := lib.GetConfig()
// Case: No Trust Wallet connected // Handle no Trust Wallet connected scenario
if libConfig.GetSerialPort() == "" { if libConfig.GetSerialPort() == "" {
return getDefaultValidatorAddress(libConfig)
}
// Handle Trust Wallet connected scenario
return getTrustWalletValidatorAddress(libConfig)
}
// getDefaultValidatorAddress retrieves the default validator address
func getDefaultValidatorAddress(libConfig *lib.Config) string {
defaultRecord, err := libConfig.GetDefaultValidatorRecord() defaultRecord, err := libConfig.GetDefaultValidatorRecord()
if err != nil { if err != nil {
logger.GetLogger(logger.ERROR).Error("msg", err.Error()) logger.GetLogger(logger.ERROR).Error("msg", err.Error())
return "" return ""
} }
addr, err := defaultRecord.GetAddress() addr, err := defaultRecord.GetAddress()
if err != nil { if err != nil {
logger.GetLogger(logger.ERROR).Error("msg", err.Error()) logger.GetLogger(logger.ERROR).Error("msg", err.Error())
@ -103,9 +124,10 @@ func (config *Config) GetValidatorAddress() string {
} }
return addr.String() return addr.String()
} }
// Case: Trust Wallet connected // getTrustWalletValidatorAddress retrieves validator address from Trust Wallet
func getTrustWalletValidatorAddress(libConfig *lib.Config) string {
connector, err := trustwallet.NewTrustWalletConnector(libConfig.GetSerialPort()) connector, err := trustwallet.NewTrustWalletConnector(libConfig.GetSerialPort())
if err != nil { if err != nil {
logger.GetLogger(logger.ERROR).Error("msg", err.Error()) logger.GetLogger(logger.ERROR).Error("msg", err.Error())

View File

@ -5,7 +5,12 @@ import (
) )
// LiquidNetParams defines the network parameters for the Liquid network. // 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. // PlmntNetParams defines the network parameters for the Planetmint network.
var PlmntNetParams = chaincfg.Params{ var PlmntNetParams = chaincfg.Params{
@ -21,17 +26,9 @@ var PlmntNetParams = chaincfg.Params{
} }
func init() { 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 // Need to register PlmntNetParams, otherwise we get an "unknown hd
// private extended key bytes" error. // private extended key bytes" error.
err := chaincfg.Register(&PlmntNetParams) if err := chaincfg.Register(&PlmntNetParams); err != nil {
if err != nil {
panic(err) panic(err)
} }
} }

View File

@ -1,6 +1,7 @@
package lib package lib
import ( import (
"errors"
"os" "os"
"sync" "sync"
@ -10,134 +11,168 @@ import (
"github.com/planetmint/planetmint-go/lib/params" "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 ( var (
libConfig *Config // 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 sdkConfig *sdk.Config
initConfig sync.Once
changeLock sync.Mutex
) )
// DefaultConfig returns library default configuration. // Config defines the top-level configuration for the Planetmint library.
func DefaultConfig() *Config { // 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{ return &Config{
chainID: "planetmint-testnet-1", ChainID: "planetmint-testnet-1",
clientCtx: client.Context{}, ClientCtx: client.Context{},
encodingConfig: params.EncodingConfig{}, EncodingConfig: params.EncodingConfig{},
feeDenom: "plmnt", FeeDenom: "plmnt",
rootDir: "~/.planetmint-go/", RPCEndpoint: "http://127.0.0.1:26657",
rpcEndpoint: "http://127.0.0.1:26657", RootDir: "~/.planetmint-go/",
txGas: 200000, TxGas: 200000,
serialPort: "",
} }
} }
// GetConfig returns the config instance for the SDK. // GetConfig returns the singleton Config instance, initializing it if necessary.
func GetConfig() *Config { func GetConfig() *Config {
initConfig.Do(func() { once.Do(func() {
libConfig = DefaultConfig() instance = NewConfig()
sdkConfig = sdk.GetConfig() sdkConfig = sdk.GetConfig()
libConfig.SetBech32PrefixForAccount("plmnt")
// Initialize default configuration
instance.SetBech32PrefixForAccount("plmnt")
encodingConfig := MakeEncodingConfig() encodingConfig := MakeEncodingConfig()
libConfig.SetEncodingConfig(encodingConfig) instance.SetEncodingConfig(encodingConfig)
}) })
return libConfig return instance
} }
// SetBech32PrefixForAccount sets the bech32 account prefix. // Validate checks if the configuration is valid.
func (config *Config) SetBech32PrefixForAccount(bech32Prefix string) *Config { func (c *Config) Validate() error {
changeLock.Lock() mu.RLock()
defer changeLock.Unlock() defer mu.RUnlock()
sdkConfig.SetBech32PrefixForAccount(bech32Prefix, "pub")
return config 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. // Builder methods
func (config *Config) SetEncodingConfig(encodingConfig params.EncodingConfig) *Config {
changeLock.Lock() func (c *Config) SetBech32PrefixForAccount(prefix string) *Config {
defer changeLock.Unlock() mu.Lock()
config.encodingConfig = encodingConfig defer mu.Unlock()
return config sdkConfig.SetBech32PrefixForAccount(prefix, "pub")
return c
} }
// SetChainID sets the chain ID parameter. func (c *Config) SetEncodingConfig(config params.EncodingConfig) *Config {
func (config *Config) SetChainID(chainID string) *Config { mu.Lock()
changeLock.Lock() defer mu.Unlock()
defer changeLock.Unlock() c.EncodingConfig = config
config.chainID = chainID return c
return config
} }
// SetClientCtx sets the client context parameter. func (c *Config) SetChainID(chainID string) *Config {
func (config *Config) SetClientCtx(clientCtx client.Context) *Config { mu.Lock()
changeLock.Lock() defer mu.Unlock()
defer changeLock.Unlock() c.ChainID = chainID
config.clientCtx = clientCtx return c
return config
} }
// SetFeeDenom sets the fee denominator parameter. func (c *Config) SetClientCtx(ctx client.Context) *Config {
func (config *Config) SetFeeDenom(feeDenom string) *Config { mu.Lock()
changeLock.Lock() defer mu.Unlock()
defer changeLock.Unlock() c.ClientCtx = ctx
config.feeDenom = feeDenom return c
return config
} }
// SetRoot sets the root directory where to find the keyring. func (c *Config) SetFeeDenom(denom string) *Config {
func (config *Config) SetRoot(root string) *Config { mu.Lock()
changeLock.Lock() defer mu.Unlock()
defer changeLock.Unlock() c.FeeDenom = denom
config.rootDir = root return c
return config
} }
// SetRPCEndpoint sets the RPC endpoint to send requests to. func (c *Config) SetRoot(root string) *Config {
func (config *Config) SetRPCEndpoint(rpcEndpoint string) *Config { mu.Lock()
changeLock.Lock() defer mu.Unlock()
defer changeLock.Unlock() c.RootDir = root
config.rpcEndpoint = rpcEndpoint return c
return config
} }
// SetTxGas sets the amount of Gas for the TX that is send to the network func (c *Config) SetRPCEndpoint(endpoint string) *Config {
func (config *Config) SetTxGas(txGas uint64) *Config { mu.Lock()
changeLock.Lock() defer mu.Unlock()
defer changeLock.Unlock() c.RPCEndpoint = endpoint
config.txGas = txGas return c
return config
} }
func (config *Config) SetSerialPort(port string) *Config { func (c *Config) SetTxGas(gas uint64) *Config {
changeLock.Lock() mu.Lock()
defer changeLock.Unlock() defer mu.Unlock()
config.serialPort = port c.TxGas = gas
return config return c
} }
func (config *Config) GetSerialPort() string { func (c *Config) SetSerialPort(port string) *Config {
return config.serialPort mu.Lock()
defer mu.Unlock()
c.SerialPort = port
return c
} }
func (config *Config) getLibKeyring() (keyring.Keyring, error) { // Getter methods
return keyring.New("lib", keyring.BackendTest, config.rootDir, os.Stdin, config.encodingConfig.Marshaler, []keyring.Option{}...)
func (c *Config) GetSerialPort() string {
mu.RLock()
defer mu.RUnlock()
return c.SerialPort
} }
func (config *Config) GetDefaultValidatorRecord() (*keyring.Record, error) { // Keyring operations
keyring, err := config.getLibKeyring()
// 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 { if err != nil {
return nil, err return nil, err
} }
@ -147,5 +182,9 @@ func (config *Config) GetDefaultValidatorRecord() (*keyring.Record, error) {
return nil, err return nil, err
} }
if len(records) == 0 {
return nil, errors.New("no keyring records found")
}
return records[0], nil return records[0], nil
} }

View File

@ -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) { 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 // 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 // it would be better to check for an empty client context, but that does not work at the moment
if clientCtx.AccountRetriever == nil { if clientCtx.AccountRetriever == nil {
@ -81,22 +81,22 @@ func getTxFactoryWithAccountNumberAndSequence(clientCtx client.Context, accountN
WithAccountRetriever(clientCtx.AccountRetriever). WithAccountRetriever(clientCtx.AccountRetriever).
WithChainID(clientCtx.ChainID). WithChainID(clientCtx.ChainID).
WithFeeGranter(clientCtx.FeeGranter). WithFeeGranter(clientCtx.FeeGranter).
WithGas(GetConfig().txGas). WithGas(GetConfig().TxGas).
WithGasPrices(gasPrice + GetConfig().feeDenom). WithGasPrices(gasPrice + GetConfig().FeeDenom).
WithKeybase(clientCtx.Keyring). WithKeybase(clientCtx.Keyring).
WithSequence(sequence). WithSequence(sequence).
WithTxConfig(clientCtx.TxConfig) WithTxConfig(clientCtx.TxConfig)
} }
func getClientContext(fromAddress sdk.AccAddress) (clientCtx client.Context, err error) { 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 input := os.Stdin
codec := encodingConfig.Marshaler codec := encodingConfig.Marshaler
keyringOptions := []keyring.Option{} keyringOptions := []keyring.Option{}
keyring, err := GetConfig().getLibKeyring() keyring, err := GetConfig().GetLibKeyring()
if err != nil { if err != nil {
return return
} }
@ -106,7 +106,7 @@ func getClientContext(fromAddress sdk.AccAddress) (clientCtx client.Context, err
return return
} }
remote := GetConfig().rpcEndpoint remote := GetConfig().RPCEndpoint
wsClient, err := comethttp.New(remote, "/websocket") wsClient, err := comethttp.New(remote, "/websocket")
if err != nil { if err != nil {
return return
@ -117,7 +117,7 @@ func getClientContext(fromAddress sdk.AccAddress) (clientCtx client.Context, err
clientCtx = client.Context{ clientCtx = client.Context{
AccountRetriever: authtypes.AccountRetriever{}, AccountRetriever: authtypes.AccountRetriever{},
BroadcastMode: "sync", BroadcastMode: "sync",
ChainID: GetConfig().chainID, ChainID: GetConfig().ChainID,
Client: wsClient, Client: wsClient,
Codec: codec, Codec: codec,
From: fromAddress.String(), From: fromAddress.String(),
@ -222,7 +222,7 @@ func BroadcastTxWithFileLock(fromAddress sdk.AccAddress, msgs ...sdk.Msg) (out *
// Set new sequence number // Set new sequence number
txf = txf.WithSequence(sequence) txf = txf.WithSequence(sequence)
if GetConfig().serialPort != "" { if GetConfig().SerialPort != "" {
out, err = broadcastTxWithTrustWalletSignature(clientCtx, txf, msgs...) out, err = broadcastTxWithTrustWalletSignature(clientCtx, txf, msgs...)
} else { } else {
out, err = broadcastTx(clientCtx, txf, msgs...) 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 { 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 { if err != nil {
return err return err
} }

View File

@ -60,7 +60,7 @@ func createSequenceDirectory() (path string, err error) {
return return
} }
homeDir := usr.HomeDir homeDir := usr.HomeDir
path = filepath.Join(GetConfig().rootDir, "sequence") path = filepath.Join(GetConfig().RootDir, "sequence")
// expand tilde to user's home directory // expand tilde to user's home directory
if strings.HasPrefix(path, "~/") { if strings.HasPrefix(path, "~/") {
path = filepath.Join(homeDir, path[2:]) path = filepath.Join(homeDir, path[2:])