Skip to content

Commit 0e7b13c

Browse files
authored
feature: implements config loading from env and file (#258)
This PR implements the config loading from a file and env vars. It provides 3 loading methods: - Load(filePath string) - Loads the config from the file path, with env var fallback. - LoadFile(filePath string) - Loads the config from the file path. - LoadEnv() - Loads the config from the env vars. Under the hood, it uses the viper library to load the config from the file and env vars.
1 parent 1d96752 commit 0e7b13c

File tree

7 files changed

+555
-1
lines changed

7 files changed

+555
-1
lines changed

.changeset/tiny-paws-visit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": minor
3+
---
4+
5+
Adds configuration loading to the CLD engine

deployment/ocr_secrets.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package deployment
22

3-
import "github.com/ethereum/go-ethereum/crypto"
3+
import (
4+
"errors"
5+
6+
"github.com/ethereum/go-ethereum/crypto"
7+
)
8+
9+
var (
10+
// ErrMnemonicRequired is returned when the OCR mnemonic is not set
11+
ErrMnemonicRequired = errors.New("xsigners or xproposers required")
12+
)
413

514
// OCRSecrets are used to disseminate a shared secret to OCR nodes
615
// through the blockchain where OCR configuration is stored. Its a low value secret used
@@ -22,3 +31,30 @@ func XXXGenerateTestOCRSecrets() OCRSecrets {
2231

2332
return s
2433
}
34+
35+
// SharedSecrets generates shared secrets from the BIP39 mnemonic phrases for the OCR signers
36+
// and proposers.
37+
//
38+
// Lifted from here
39+
// https://github.com/smartcontractkit/offchain-reporting/blob/14a57d70e50474a2104aa413214e464d6bc69e16/lib/offchainreporting/internal/config/shared_secret_test.go#L32
40+
// Historically signers (fixed secret) and proposers (ephemeral secret) were
41+
// combined in this manner. We simply leave that as is.
42+
func GenerateSharedSecrets(xSigners, xProposers string) (OCRSecrets, error) {
43+
if xSigners == "" || xProposers == "" {
44+
return OCRSecrets{}, ErrMnemonicRequired
45+
}
46+
47+
xSignersHash := crypto.Keccak256([]byte(xSigners))
48+
xProposersHash := crypto.Keccak256([]byte(xProposers))
49+
xSignersHashxProposersHashZero := append(append(append([]byte{}, xSignersHash...), xProposersHash...), 0)
50+
xSignersHashxProposersHashOne := append(append(append([]byte{}, xSignersHash...), xProposersHash...), 1)
51+
var sharedSecret [16]byte
52+
copy(sharedSecret[:], crypto.Keccak256(xSignersHashxProposersHashZero))
53+
var sk [32]byte
54+
copy(sk[:], crypto.Keccak256(xSignersHashxProposersHashOne))
55+
56+
return OCRSecrets{
57+
SharedSecret: sharedSecret,
58+
EphemeralSk: sk,
59+
}, nil
60+
}

engine/cld/config/env/config.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package config
2+
3+
import (
4+
"errors"
5+
"io/fs"
6+
"os"
7+
"slices"
8+
9+
"github.com/spf13/viper"
10+
)
11+
12+
// KMSConfig is the configuration for the AWS KMS.
13+
//
14+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
15+
// configuration.
16+
type KMSConfig struct {
17+
KeyID string `mapstructure:"key_id"` // Secret: AWS KMS Key ID
18+
KeyRegion string `mapstructure:"key_region"` // Secret: AWS KMS Key Region (e.g. us-west-1)
19+
}
20+
21+
// EVMConfig is the configuration for the EVM Chains.
22+
//
23+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
24+
// configuration.
25+
type EVMConfig struct {
26+
DeployerKey string `mapstructure:"deployer_key"` // Secret: The private key of the deployer account. Prefer to use KMS keys instead.
27+
Seth *SethConfig `mapstructure:"seth"` // Seth configuration for transaction tracing
28+
}
29+
30+
type SethConfig struct {
31+
ConfigFilePath string `mapstructure:"config_file_path"` // The path to the Seth config file
32+
GethWrapperDirs []string `mapstructure:"geth_wrapper_dirs"` // The paths to the Geth wrapper directories
33+
}
34+
35+
// SolanaConfig is the configuration for the Solana Chains.
36+
//
37+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
38+
// configuration.
39+
type SolanaConfig struct {
40+
WalletKey string `mapstructure:"wallet_key"` // Secret: The private key of the wallet account.
41+
ProgramsDirPath string `mapstructure:"programs_dir_path"` // The path to the Solana programs directory.
42+
}
43+
44+
// AptosConfig is the configuration for the Aptos Chains.
45+
//
46+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
47+
// configuration.
48+
type AptosConfig struct {
49+
DeployerKey string `mapstructure:"deployer_key"` // Secret: The private key of the deployer account.
50+
}
51+
52+
// TronConfig is the configuration for the Tron Chains.
53+
//
54+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
55+
// configuration.
56+
type TronConfig struct {
57+
DeployerKey string `mapstructure:"deployer_key"` // Secret: The private key of the deployer account.
58+
}
59+
60+
// JobDistributorConfig is the configuration for connecting and authenticating to the Job
61+
// Distributor.
62+
//
63+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
64+
// configuration.
65+
type JobDistributorConfig struct {
66+
Endpoints JobDistributorEndpoints `mapstructure:"endpoints"` // The URL endpoints for the Job Distributor
67+
Auth JobDistributorAuth `mapstructure:"auth"` // Secret: The authentication configuration for the Job Distributor
68+
}
69+
70+
// JobDistributorAuth is the configuration for authenticating to the Job Distributor via Cognito.
71+
//
72+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
73+
// configuration.
74+
type JobDistributorAuth struct {
75+
CognitoAppClientID string `mapstructure:"cognito_app_client_id"` // Secret: The Cognito App Client ID
76+
CognitoAppClientSecret string `mapstructure:"cognito_app_client_secret"` // Secret: The Cognito App Client Secret
77+
AWSRegion string `mapstructure:"aws_region"` // Secret: The AWS Region
78+
Username string `mapstructure:"username"` // Secret: The Cognito username for the Job Distributor
79+
Password string `mapstructure:"password"` // Secret: The Cognito password for the Job Distributor
80+
}
81+
82+
// JobDistributorEndpoints is the configuration for the URL endpoints for the Job Distributor.
83+
type JobDistributorEndpoints struct {
84+
WSRPC string `mapstructure:"wsrpc"` // The WebSocket RPC URL for the Job Distributor. Used to connect Job Distributor to CL nodes.
85+
GRPC string `mapstructure:"grpc"` // The gRPC URL for the Job Distributor. Used to interact with the Job Distributor API.
86+
}
87+
88+
// OCRConfig is the configuration for the OCR.
89+
//
90+
// WARNING: This data type contains sensitive fields and should not be logged or set in file
91+
// configuration.
92+
type OCRConfig struct {
93+
XSigners string `mapstructure:"x_signers"` // Secret: BIP39 mnemonic phrase for the OCR signer.
94+
XProposers string `mapstructure:"x_proposers"` // Secret: BIP39 mnemonic phrase for the OCR proposer.
95+
}
96+
97+
// CatalogConfig is the configuration to connect to the Catalog.
98+
type CatalogConfig struct {
99+
GRPC string `mapstructure:"grpc"` // The gRPC URL for the Catalog. Used to interact with the Catalog API.
100+
}
101+
102+
// OnchainConfig wraps the configuration for the onchain components.
103+
type OnchainConfig struct {
104+
KMS KMSConfig `mapstructure:"kms"`
105+
EVM EVMConfig `mapstructure:"evm"`
106+
Solana SolanaConfig `mapstructure:"solana"`
107+
Aptos AptosConfig `mapstructure:"aptos"`
108+
Tron TronConfig `mapstructure:"tron"`
109+
}
110+
111+
// OffchainConfig wraps the configuration for the offchain components.
112+
type OffchainConfig struct {
113+
JobDistributor JobDistributorConfig `mapstructure:"job_distributor"`
114+
OCR OCRConfig `mapstructure:"ocr"`
115+
}
116+
117+
// Config wraps the entire configuration for the CLD engine.
118+
type Config struct {
119+
Onchain OnchainConfig `mapstructure:"onchain"`
120+
Offchain OffchainConfig `mapstructure:"offchain"`
121+
Catalog CatalogConfig `mapstructure:"catalog"`
122+
}
123+
124+
// Load loads the config from the file path, falling back to env vars if the file does not exist.
125+
// If the file exists, any env vars that are set will override the values loaded from the file.
126+
func Load(filePath string) (*Config, error) {
127+
v := viper.New()
128+
v.SetConfigFile(filePath)
129+
130+
// Bind environment variables
131+
if err := bindEnvs(v); err != nil {
132+
return nil, err
133+
}
134+
135+
// If the config file exists, we continue to read it, otherwise we fallback to using
136+
// environment variables
137+
if _, err := os.Stat(filePath); !errors.Is(err, fs.ErrNotExist) {
138+
if err := v.ReadInConfig(); err != nil {
139+
return nil, err
140+
}
141+
}
142+
143+
cfg := &Config{}
144+
err := v.Unmarshal(cfg)
145+
146+
return cfg, err
147+
}
148+
149+
// LoadEnv loads the config from the environment variables.
150+
func LoadEnv() (*Config, error) {
151+
v := viper.New()
152+
153+
// Bind environment variables
154+
if err := bindEnvs(v); err != nil {
155+
return nil, err
156+
}
157+
158+
cfg := &Config{}
159+
err := v.Unmarshal(cfg)
160+
161+
return cfg, err
162+
}
163+
164+
// LoadFile loads the config from a file.
165+
func LoadFile(filePath string) (*Config, error) {
166+
v := viper.New()
167+
v.SetConfigFile(filePath)
168+
169+
if err := v.ReadInConfig(); err != nil {
170+
return nil, err
171+
}
172+
173+
cfg := &Config{}
174+
err := v.Unmarshal(cfg)
175+
176+
return cfg, err
177+
}
178+
179+
var (
180+
// envBindings defines how environment variables map to configuration keys used by Viper.
181+
// Each entry maps a config key (as used in the struct, e.g. "onchain.kms.key_id") to a list of
182+
// environment variable names that can provide its value.
183+
//
184+
// The first element in the list is the preferred (new) environment variable name, and the second
185+
// (if present) is a legacy or backwards-compatible name. This allows the config loader to support
186+
// both new and old environment variable conventions seamlessly, ensuring smooth transitions and
187+
// compatibility with existing deployments.
188+
//
189+
// When loading, Viper will check each listed environment variable in order and use the first one
190+
// that is set.
191+
envBindings = map[string][]string{
192+
"onchain.kms.key_id": {"ONCHAIN_KMS_KEY_ID", "KMS_KEY_ID"},
193+
"onchain.kms.key_region": {"ONCHAIN_KMS_KEY_REGION", "KMS_KEY_REGION"},
194+
"onchain.evm.deployer_key": {"ONCHAIN_EVM_DEPLOYER_KEY", "EVM_DEPLOYER_KEY"},
195+
"onchain.evm.seth.config_file_path": {"ONCHAIN_EVM_SETH_CONFIG_FILE_PATH", "SETH_CONFIG_FILE"},
196+
"onchain.evm.seth.geth_wrapper_dirs": {"ONCHAIN_EVM_SETH_GETH_WRAPPER_DIRS", "GETH_WRAPPERS_DIRS"},
197+
"onchain.solana.wallet_key": {"ONCHAIN_SOLANA_WALLET_KEY", "SOLANA_WALLET_KEY"},
198+
"onchain.solana.programs_dir_path": {"ONCHAIN_SOLANA_PROGRAMS_DIR_PATH", "SOLANA_PROGRAM_PATH"},
199+
"onchain.aptos.deployer_key": {"ONCHAIN_APTOS_DEPLOYER_KEY", "APTOS_DEPLOYER_KEY"},
200+
"onchain.tron.deployer_key": {"ONCHAIN_TRON_DEPLOYER_KEY", "TRON_DEPLOYER_KEY"},
201+
"offchain.job_distributor.auth.cognito_app_client_id": {"OFFCHAIN_JD_AUTH_COGNITO_APP_CLIENT_ID", "JD_AUTH_COGNITO_APP_CLIENT_ID"},
202+
"offchain.job_distributor.auth.cognito_app_client_secret": {"OFFCHAIN_JD_AUTH_COGNITO_APP_CLIENT_SECRET", "JD_AUTH_COGNITO_APP_CLIENT_SECRET"},
203+
"offchain.job_distributor.auth.aws_region": {"OFFCHAIN_JD_AUTH_AWS_REGION", "JD_AUTH_AWS_REGION"},
204+
"offchain.job_distributor.auth.username": {"OFFCHAIN_JD_AUTH_USERNAME", "JD_AUTH_USERNAME"},
205+
"offchain.job_distributor.auth.password": {"OFFCHAIN_JD_AUTH_PASSWORD", "JD_AUTH_PASSWORD"},
206+
"offchain.job_distributor.endpoints.wsrpc": {"OFFCHAIN_JD_ENDPOINTS_WSRPC", "JD_WS_RPC"},
207+
"offchain.job_distributor.endpoints.grpc": {"OFFCHAIN_JD_ENDPOINTS_GRPC", "JD_GRPC"},
208+
"offchain.ocr.x_signers": {"OFFCHAIN_OCR_X_SIGNERS", "OCR_X_SIGNERS"},
209+
"offchain.ocr.x_proposers": {"OFFCHAIN_OCR_X_PROPOSERS", "OCR_X_PROPOSERS"},
210+
"catalog.grpc": {"CATALOG_GRPC"},
211+
}
212+
)
213+
214+
// bindEnvs binds the environment variables to the viper instance.
215+
func bindEnvs(v *viper.Viper) error {
216+
// Bind environment variables mappings to the viper instance
217+
for key, envs := range envBindings {
218+
// Prepend the env key to the start of the arguments
219+
inputs := slices.Insert(envs, 0, key)
220+
221+
if err := v.BindEnv(inputs...); err != nil {
222+
return err
223+
}
224+
}
225+
226+
return nil
227+
}

0 commit comments

Comments
 (0)