diff --git a/crecore/docs/CONFIG.md b/crecore/docs/CONFIG.md new file mode 100644 index 000000000..b7ca4b273 --- /dev/null +++ b/crecore/docs/CONFIG.md @@ -0,0 +1,199 @@ +# main Configuration + +## Example + +```toml +# ----- Global Configuration ----- +proxy-listen-address = ':50051' +capabilities-registry-address = '0xYourRegistryAddress' +capabilities-registry-sync-interval = '12s' +[database] +url = 'postgresql://user:password@localhost:5432/chainlink?sslmode=disable' +[ocr] +listen-addresses = ['127.0.0.1:1234'] +delta-reconcile = '1m0s' +delta-dial = '5s' +incoming-buffer-size = 100 +outgoing-buffer-size = 100 +keystore-password = 'xxxxx' +[evm] +http-url = ['https://rpc.example.com'] +chain-id = '1' +chain-type = '' +finality-tag-enabled = true +finality-depth = 50 +poll-interval = '10s' + + +``` + +## Global +```toml +proxy-listen-address = ':50051' # Default +capabilities-registry-address = '0xYourRegistryAddress' # Example +capabilities-registry-sync-interval = '12s' # Default +``` + + +# Global Configuration + +### proxy-listen-address +```toml +proxy-listen-address = ':50051' # Default +``` +proxy-listen-address address the proxy gRPC server listens on + +### capabilities-registry-address +```toml +capabilities-registry-address = '0xYourRegistryAddress' # Example +``` +capabilities-registry-address on-chain CapabilitiesRegistry (v2) contract address + +### capabilities-registry-sync-interval +```toml +capabilities-registry-sync-interval = '12s' # Default +``` +capabilities-registry-sync-interval how often the on-chain registry is re-read + +### fake +```toml +fake = false # Docs only +``` +fake use fake dependencies instead of real ones + +## database +```toml +[database] +url = 'postgresql://user:password@localhost:5432/chainlink?sslmode=disable' # Example +``` + + +### url +```toml +url = 'postgresql://user:password@localhost:5432/chainlink?sslmode=disable' # Example +``` +url database url; required unless running with --fake and without --real-db + +### real-db +```toml +real-db = false # Docs only +``` +real-db use a real database even though --fake is set; requires --fake, and a url to point at + +## ocr +```toml +[ocr] +listen-addresses = ['127.0.0.1:1234'] # Example +announce-addresses = [] # Default +delta-reconcile = '1m0s' # Default +delta-dial = '5s' # Default +incoming-buffer-size = 100 # Default +outgoing-buffer-size = 100 # Default +keystore-password = 'xxxxx' # Example +proxy-address = '' # Default +``` + + +### listen-addresses +```toml +listen-addresses = ['127.0.0.1:1234'] # Example +``` +listen-addresses rage p2p V2 listen addresses (host:port); creates a local peer (required unless proxy-address is set; must not be set when proxy-address is set) + +### announce-addresses +```toml +announce-addresses = [] # Default +``` +announce-addresses rage p2p V2 announce addresses (host:port); defaults to the listen addresses (must not be set unless listen-addresses is set) + +### delta-reconcile +```toml +delta-reconcile = '1m0s' # Default +``` +delta-reconcile rage p2p V2 delta reconcile interval + +### delta-dial +```toml +delta-dial = '5s' # Default +``` +delta-dial rage p2p V2 minimum interval between dial attempts + +### incoming-buffer-size +```toml +incoming-buffer-size = 100 # Default +``` +incoming-buffer-size per-remote incoming message buffer size + +### outgoing-buffer-size +```toml +outgoing-buffer-size = 100 # Default +``` +outgoing-buffer-size per-remote outgoing message buffer size + +### keystore-password +```toml +keystore-password = 'xxxxx' # Example +``` +keystore-password password for the node keystore holding the shared P2P identity + +### proxy-address +```toml +proxy-address = '' # Default +``` +proxy-address delegate rage networking to a proxy at this gRPC address instead of creating a local peer (must not be set when listen-addresses is set) + +## evm +```toml +[evm] +http-url = ['https://rpc.example.com'] # Example +ws-url = [] # Default +chain-id = '1' # Example +chain-type = '' # Default +finality-tag-enabled = true # Default +finality-depth = 50 # Default +poll-interval = '10s' # Default +``` + + +### http-url +```toml +http-url = ['https://rpc.example.com'] # Example +``` +http-url EVM RPC HTTP URL(s); repeat or comma-separate for a multinode pool + +### ws-url +```toml +ws-url = [] # Default +``` +ws-url EVM RPC WebSocket URL(s), positionally paired with --evm.http-url; optional (must not be set unless http-url is set) + +### chain-id +```toml +chain-id = '1' # Example +``` +chain-id EVM chain ID + +### chain-type +```toml +chain-type = '' # Default +``` +chain-type EVM chain type (empty for a generic EVM chain) + +### finality-tag-enabled +```toml +finality-tag-enabled = true # Default +``` +finality-tag-enabled use the finalized block tag instead of a finality depth + +### finality-depth +```toml +finality-depth = 50 # Default +``` +finality-depth finality depth, used when --evm.finality-tag-enabled=false + +### poll-interval +```toml +poll-interval = '10s' # Default +``` +poll-interval per-node health poll interval + diff --git a/crecore/go.mod b/crecore/go.mod index d024806dc..74ed8179c 100644 --- a/crecore/go.mod +++ b/crecore/go.mod @@ -4,8 +4,8 @@ go 1.26.2 require ( github.com/ethereum/go-ethereum v1.17.3 - github.com/smartcontractkit/capabilities/libs v0.0.0-20260806142134-6ce85e626bf1 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260805174449-86b0bd040b49 + github.com/smartcontractkit/capabilities/libs v0.0.0-20260807195051-f3d5f6d13400 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260807193849-47d010760510 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260728111445-96c471be2872 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6 github.com/smartcontractkit/chainlink-protos/cre/impl v0.0.0-20260724132051-f39bd9ab890d diff --git a/crecore/go.sum b/crecore/go.sum index 5fbf39803..190d10094 100644 --- a/crecore/go.sum +++ b/crecore/go.sum @@ -710,12 +710,12 @@ github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartcontractkit/capabilities/libs v0.0.0-20260806142134-6ce85e626bf1 h1:evnfpGLpPS6igAjyl1LNNt3cVZ8ewo2u/0dqjWnOyQk= -github.com/smartcontractkit/capabilities/libs v0.0.0-20260806142134-6ce85e626bf1/go.mod h1:Y+x7ksG6LxrJp8dLp5KTDIHJc1uspy4GIYHSpL9GEJQ= +github.com/smartcontractkit/capabilities/libs v0.0.0-20260807195051-f3d5f6d13400 h1:V+CUSgMC6z4jySgI2PSpmbUaVu03go+gY3AYAFTK1NY= +github.com/smartcontractkit/capabilities/libs v0.0.0-20260807195051-f3d5f6d13400/go.mod h1:LexpT2LlA33BVwb4hKw5DHbnG9S+lHzS0HOkEOxLZ7U= github.com/smartcontractkit/chain-selectors v1.0.100 h1:wpiSpmI/eFjY+wx/nPr5VuNF4hki0prIBMKEaQWn3g4= github.com/smartcontractkit/chain-selectors v1.0.100/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260805174449-86b0bd040b49 h1:XnMKhJDTl+JY+W7mIU1NJfaHqC4ykMXW0rz8hk9x5PQ= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260805174449-86b0bd040b49/go.mod h1:+ygsaXb7Us63JhgPc+MWy48ydNz4zAT3g/vpYEvRo2k= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260807193849-47d010760510 h1:4GjFJWcytRCoLzmLr/QXWq/zokaZrP+lfS+zeIcP2zM= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260807193849-47d010760510/go.mod h1:DUnczFmJPvNHsQc9er8wdBvt4bPJzPOgr62i7mQ1jhM= github.com/smartcontractkit/chainlink-common/keystore v1.3.0 h1:V05Rp9/dTc4Wyipsk1EYnW3riZyjDVKmhz3769JpnHU= github.com/smartcontractkit/chainlink-common/keystore v1.3.0/go.mod h1:vHV8BGm6TN7jBbMsWxq1Hqm3HbCtYFwzvKS0CCczxG8= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 h1:uWEwl7i2ryuRVoV4DmIKm6mqYevf1lH/8cQYhw/JXko= diff --git a/crecore/main.go b/crecore/main.go index 927b3d779..c73e4888f 100644 --- a/crecore/main.go +++ b/crecore/main.go @@ -4,7 +4,6 @@ import ( "context" "embed" "log" - "time" "github.com/spf13/cobra" @@ -13,8 +12,7 @@ import ( "github.com/smartcontractkit/capabilities/libs/standalone/evm" "github.com/smartcontractkit/capabilities/libs/standalone/ocr" - "github.com/smartcontractkit/capabilities/crecore/registry" - + "github.com/smartcontractkit/chainlink-common/pkg/config/flags" "github.com/smartcontractkit/chainlink-common/pkg/services" evmclient "github.com/smartcontractkit/chainlink-evm/pkg/client" ) @@ -35,35 +33,28 @@ func main() { } func run() error { - cfg := &Config{} - - var ( - registryAddress string - registrySyncInterval time.Duration - ) + cfg := defaultConfig root := &cobra.Command{ Use: "main", Short: "P2P proxy for the CRE", Long: `Runs a single shared rage (libocr) peer and exposes it over gRPC so that core can delegate its OCR networking (and, in future, DON-to-DON networking) to -this process. The peer's identity is loaded from the node's keystore (shared DB -via CL_DATABASE_URL, decrypted with CL_PASSWORD_KEYSTORE). - -Provide exactly one networking mode: --listen-addresses to run a local libocr -peer, or --proxy-address to delegate to another proxy. +this process. The peer's identity is the node's own, loaded from the keystore in +the database the two share. It also serves the CapabilitiesRegistry on that same gRPC address, read directly from chain with an EVM client (no relayer). Core uses that in place of its own -registrysyncer whenever it is delegating rage networking to this process, so ---capabilities-registry-address is required: this process running is what enables -the registry, and core does not start without it.`, +registrysyncer whenever it is delegating rage networking to this process, which +is why the registry address is required: this process running is what enables the +registry, and core does not start without it. + +Settings can come from flags, from CRE_/CL_ env vars, or from a --config file; +run "docs" to write the full reference to docs/CONFIG.md.`, } + root.PersistentFlags().String("config", "", "Path to config file") - root.PersistentFlags().StringVar(&cfg.ProxyListenAddress, "proxy-listen-address", ":50051", "address the proxy gRPC server listens on") - root.PersistentFlags().StringVar(®istryAddress, "capabilities-registry-address", "", "on-chain CapabilitiesRegistry (v2) contract address (required)") - root.PersistentFlags().DurationVar(®istrySyncInterval, "capabilities-registry-sync-interval", registry.DefaultSyncInterval, "how often the on-chain registry is re-read") - if err := root.MarkPersistentFlagRequired("capabilities-registry-address"); err != nil { + if err := flags.RegisterCommandFlags(root, &cfg, flags.DefaultTOMLOptions("CRE", "CL")); err != nil { return err } @@ -71,12 +62,13 @@ the registry, and core does not start without it.`, lggr := bootstrapper.Logger() // The ocr dependency owns the libocr networking config (create vs proxy - // mode) and wraps the database dependency it needs for the P2P identity and - // OCR discoverer table. + // mode, and the keystore password that unlocks the peer identity) and wraps + // the database dependency it needs for that identity and the OCR discoverer + // table. dbDep := db.Dependency(embeddedMigrations, migrationsTable) ocrDep := ocr.Dependency(lggr.Named("OCR"), dbDep, ocrDiscovererTable) // The registry always runs, so the EVM client is always resolved and the - // --evm-* flags are as required in practice as the registry address is. + // evm settings are as required in practice as the registry address is. evmDep := evm.Dependency(lggr.Named("EVM")) return standalone.Run2(bootstrapper, func( @@ -85,11 +77,11 @@ the registry, and core does not start without it.`, factories standalone.Dependency[*ocr.Factories], evmClient standalone.Dependency[evmclient.Client], ) []services.Service { - regSvc := newRegistryService(registryAddress, registrySyncInterval, + regSvc := newRegistryService(cfg.RegistryAddress, cfg.RegistrySyncInterval.Duration(), scfg.Logger.Named("capabilities registry"), evmClient, factories) // The registry attaches to the proxy's gRPC server, so core reaches both // over the single --proxy-listen-address it already configures. - proxySvc := newProxyService(cfg, scfg.Logger.Named("proxy service"), factories, regSvc.Register) + proxySvc := newProxyService(&cfg, scfg.Logger.Named("proxy service"), factories, regSvc.Register) return []services.Service{proxySvc, regSvc} }, ocrDep, evmDep) } diff --git a/crecore/proxy_service.go b/crecore/proxy_service.go index 08151d23a..181d7b77f 100644 --- a/crecore/proxy_service.go +++ b/crecore/proxy_service.go @@ -8,20 +8,34 @@ import ( "google.golang.org/grpc" + "github.com/smartcontractkit/capabilities/crecore/registry" "github.com/smartcontractkit/capabilities/libs/standalone" "github.com/smartcontractkit/capabilities/libs/standalone/ocr" + "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" creproxy "github.com/smartcontractkit/chainlink-protos/cre/impl/proxy" ) -// Config is the proxy gRPC server configuration, populated from CLI flags (see -// main.go). The libocr peer / proxy-client configuration lives on the ocr -// bootstrap dependency instead. +// Config is the root command's configuration, populated by flags.RegisterCommandFlags (see +// main.go). The libocr peer / proxy-client configuration lives on the ocr bootstrap dependency +// instead. type Config struct { // ProxyListenAddress is the address the proxy gRPC server listens on. - ProxyListenAddress string + ProxyListenAddress string `toml:"proxy-listen-address" usage:"address the proxy gRPC server listens on"` + + // RegistryAddress is the on-chain CapabilitiesRegistry (v2) contract address. The registry + // always runs, so this is as required in practice as the registry itself. + RegistryAddress string `toml:"capabilities-registry-address" usage:"on-chain CapabilitiesRegistry (v2) contract address" validate:"required" example:"'0xYourRegistryAddress'"` + + // RegistrySyncInterval is how often the on-chain registry is re-read. + RegistrySyncInterval config.Duration `toml:"capabilities-registry-sync-interval" usage:"how often the on-chain registry is re-read"` +} + +var defaultConfig = Config{ + ProxyListenAddress: ":50051", + RegistrySyncInterval: *config.MustNewDuration(registry.DefaultSyncInterval), } // proxyService exposes the libocr rage networking factories over gRPC so that diff --git a/libs/go.mod b/libs/go.mod index 384016456..84d2ca200 100644 --- a/libs/go.mod +++ b/libs/go.mod @@ -13,14 +13,13 @@ require ( github.com/pressly/goose/v3 v3.27.1 github.com/prometheus/client_golang v1.23.2 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260805174449-86b0bd040b49 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260807193849-47d010760510 github.com/smartcontractkit/chainlink-common/keystore v1.3.0 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260728111445-96c471be2872 github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260622152157-c8e129347b8b github.com/smartcontractkit/chainlink-protos/cre/impl v0.0.0-20260724132051-f39bd9ab890d github.com/smartcontractkit/libocr v0.0.0-20260529134643-c101335a64cd github.com/spf13/cobra v1.10.2 - github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 @@ -223,6 +222,7 @@ require ( github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stellar/go-stellar-sdk v0.4.0 // indirect github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect diff --git a/libs/go.sum b/libs/go.sum index 096a902b7..0a0ef2a47 100644 --- a/libs/go.sum +++ b/libs/go.sum @@ -713,8 +713,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartcontractkit/chain-selectors v1.0.100 h1:wpiSpmI/eFjY+wx/nPr5VuNF4hki0prIBMKEaQWn3g4= github.com/smartcontractkit/chain-selectors v1.0.100/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260805174449-86b0bd040b49 h1:XnMKhJDTl+JY+W7mIU1NJfaHqC4ykMXW0rz8hk9x5PQ= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260805174449-86b0bd040b49/go.mod h1:+ygsaXb7Us63JhgPc+MWy48ydNz4zAT3g/vpYEvRo2k= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260807193849-47d010760510 h1:4GjFJWcytRCoLzmLr/QXWq/zokaZrP+lfS+zeIcP2zM= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260807193849-47d010760510/go.mod h1:DUnczFmJPvNHsQc9er8wdBvt4bPJzPOgr62i7mQ1jhM= github.com/smartcontractkit/chainlink-common/keystore v1.3.0 h1:V05Rp9/dTc4Wyipsk1EYnW3riZyjDVKmhz3769JpnHU= github.com/smartcontractkit/chainlink-common/keystore v1.3.0/go.mod h1:vHV8BGm6TN7jBbMsWxq1Hqm3HbCtYFwzvKS0CCczxG8= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 h1:uWEwl7i2ryuRVoV4DmIKm6mqYevf1lH/8cQYhw/JXko= diff --git a/libs/standalone/bootstrapper.go b/libs/standalone/bootstrapper.go index 6aa7147a4..690a9da80 100644 --- a/libs/standalone/bootstrapper.go +++ b/libs/standalone/bootstrapper.go @@ -16,6 +16,7 @@ import ( "github.com/spf13/cobra" "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/config/flags" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/services" @@ -86,7 +87,12 @@ func NewBootstrapper(root *cobra.Command, opts ...Option) *Bootstrapper { config: &StandaloneConfig{Logger: slggr, BeholderClient: beholderClient}, profiler: profiler, } - root.PersistentFlags().BoolVar(&bs.commonConfig.Fake, "fake", false, "use fake dependencies instead of real ones") + // Registered through pkg/config/flags like every other config struct, so --fake is + // documented alongside the settings it interacts with rather than being invisible to the + // generated docs. It stays at the top level: it is process-wide, not one dependency's. + if err := flags.RegisterCommandFlags(root, &bs.commonConfig, flags.DefaultTOMLOptions("CRE", "CL")); err != nil { + slggr.Fatalf("Failed to register common flags: %s", err) + } return bs } @@ -251,7 +257,9 @@ func underPluginHost() bool { } type CommonConfig struct { - Fake bool + // Kept out of the example config: the example shows a real run, and --fake selects a + // different set of dependencies than the ones it illustrates. + Fake bool `toml:"fake" usage:"use fake dependencies instead of real ones" flagdocs:"noexample"` } type Dependency[T any] interface { @@ -260,6 +268,12 @@ type Dependency[T any] interface { type BootstrapCommand interface { AddCommands(*cobra.Command) + + // Namespace roots this dependency's configuration, so its settings group together and + // same-named settings from different dependencies don't collide - "database" gives + // --database.url, the key database.url and the env var CRE_DATABASE_URL. Return "" to + // keep the settings at the top level. + Namespace() string } type BootstrapDependency[T any] interface { @@ -314,3 +328,5 @@ func (o *onceBootstrapper[T]) Get(ctx context.Context, c CommonConfig) (T, error func (o *onceBootstrapper[T]) AddCommands(cmd *cobra.Command) { o.bd.AddCommands(cmd) } + +func (o *onceBootstrapper[T]) Namespace() string { return o.bd.Namespace() } diff --git a/libs/standalone/cmd_helpers.go b/libs/standalone/cmd_helpers.go index 4962be7be..fe0547ed3 100644 --- a/libs/standalone/cmd_helpers.go +++ b/libs/standalone/cmd_helpers.go @@ -1,18 +1,8 @@ +// Package standalone bootstraps single-binary CRE processes. +// +// Flag/env/config-file binding is handled by chainlink-common's pkg/config/flags: a +// dependency declares a tagged config struct and registers it with +// flags.RegisterCommandFlags(cmd, &cfg, "CRE", "CL") from AddCommands, which binds each field +// as a CLI flag, a viper key, and CRE_*/CL_* env vars, then decodes and validates it (see the +// `validate:"..."` tags) before the command runs. package standalone - -import ( - "fmt" - "strings" - - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -func BindWithEnvVar(flag *pflag.Flag) { - formatted := strings.ToUpper(strings.ReplaceAll(flag.Name, "-", "_")) - creEnv := "CRE_" + formatted - clEnv := "CL_" + formatted - flag.Usage += fmt.Sprintf("[env in order: %s, %s]", creEnv, clEnv) - _ = viper.BindPFlag(flag.Name, flag) - _ = viper.BindEnv(flag.Name, creEnv, clEnv) -} diff --git a/libs/standalone/config_dependency.go b/libs/standalone/config_dependency.go index 1576c366b..e1c7db8c6 100644 --- a/libs/standalone/config_dependency.go +++ b/libs/standalone/config_dependency.go @@ -26,6 +26,9 @@ func (c *configDependency[T]) Get(_ context.Context, _ CommonConfig) (T, error) return c.parse(content) } +// Namespace is empty: --config-file is a process-wide setting, not one dependency's. +func (c *configDependency[T]) Namespace() string { return "" } + func (c *configDependency[T]) AddCommands(command *cobra.Command) { command.PersistentFlags().StringVar(&c.file, "config-file", "", "specifies the config file to load from") } diff --git a/libs/standalone/db/db_dependency.go b/libs/standalone/db/db_dependency.go index 0714eaed5..24a370f01 100644 --- a/libs/standalone/db/db_dependency.go +++ b/libs/standalone/db/db_dependency.go @@ -5,40 +5,72 @@ import ( "database/sql" "fmt" "io/fs" - "os" // Register the pgx database/sql driver under the name "pgx". _ "github.com/jackc/pgx/v5/stdlib" - "github.com/smartcontractkit/capabilities/libs/standalone" "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/smartcontractkit/chainlink-common/pkg/config/flags" + + "github.com/smartcontractkit/capabilities/libs/standalone" ) -const dbURLEnvVar = "CL_DATABASE_URL" +// fakeFlag is the bootstrapper's --fake flag (see standalone.NewBootstrapper). It belongs to a +// different config struct than this one, so the rules below can't be `validate` tags - those +// only see sibling fields - and are checked in Config.validate against the value read back out +// of viper instead. +const fakeFlag = "fake" + +// Config is the database configuration. Its two settings are related to --fake, which this +// struct does not own: +// +// - real-db only means something in fake mode, so it is rejected without --fake. +// - database-url is needed whenever a real database is actually used: always in normal mode, +// and in fake mode only when --real-db asks for one. +// +// Neither rule is expressible as a `validate` tag (both reference --fake), so neither shows up +// in the generated docs on its own - the usage text spells them out so the docs still explain +// when each setting applies. +type Config struct { + URL string `toml:"url" usage:"database url; required unless running with --fake and without --real-db" example:"'postgresql://user:password@localhost:5432/chainlink?sslmode=disable'"` + + // Kept out of the example config: the example shows a normal run against a real database, + // where this setting does not apply. It is still documented. + UseRealDBForFake bool `toml:"real-db" usage:"use a real database even though --fake is set; requires --fake, and a url to point at" flagdocs:"noexample"` +} + +// validate enforces the --fake relationships described on Config. +func (c Config) validate(fake bool) error { + if c.UseRealDBForFake && !fake { + return fmt.Errorf("--database.real-db only applies in fake mode: pass --fake as well, or drop --database.real-db to use a real database normally") + } + if (c.UseRealDBForFake || !fake) && c.URL == "" { + return fmt.Errorf("--database.url is required when a real database is used (it is only optional with --fake and without --real-db)") + } + return nil +} func Dependency(migrationsFS fs.FS, migrationTable string) standalone.BootstrapDependency[*sql.DB] { - // Wrap in OnceBootstrapper so Get (which opens the DB and runs migrations) - // runs at most once even if several services resolve this dependency. return standalone.OnceBootstrapper[*sql.DB](&dependency{migrationsFS: migrationsFS, migrationTable: migrationTable}) } type dependency struct { - db *sql.DB - url string - useRealDbForFake bool - migrationsFS fs.FS - migrationTable string + db *sql.DB + cfg Config + migrationsFS fs.FS + migrationTable string } func (d *dependency) Get(ctx context.Context, commonConfig standalone.CommonConfig) (*sql.DB, error) { - dbFn := pgDb - if commonConfig.Fake && !d.useRealDbForFake { + if commonConfig.Fake && !d.cfg.UseRealDBForFake { // TODO set db to an in-memory one // Also add a subcommand to override with real DB even if fake is used } var err error - d.db, err = dbFn() + d.db, err = sql.Open("pgx", d.cfg.URL) if err != nil { return nil, err } @@ -50,21 +82,41 @@ func (d *dependency) Get(ctx context.Context, commonConfig standalone.CommonConf return d.db, nil } -func (d *dependency) AddCommands(command *cobra.Command) { - f := command.PersistentFlags() - f.BoolVar(&d.useRealDbForFake, "real-db", false, "uses a real db even if fake is set for the program") - f.StringVar(&d.url, "db-url", "", "database url") - standalone.BindWithEnvVar(f.Lookup("db-url")) -} +// Namespace groups the database settings under database.* (--database.url, CRE_DATABASE_URL). +func (d *dependency) Namespace() string { return "database" } -func pgDb() (*sql.DB, error) { +func (d *dependency) AddCommands(command *cobra.Command) { + opts := flags.DefaultTOMLOptions("CRE", "CL") + opts.Namespace = d.Namespace() + if err := flags.RegisterCommandFlags(command, &d.cfg, opts); err != nil { + panic(err) + } - dbURL := os.Getenv(dbURLEnvVar) - if dbURL == "" { - return nil, fmt.Errorf("%s must be set", dbURLEnvVar) + // --fake is owned by the bootstrapper, so bind it into viper to read it back here the same + // way any other setting is resolved (flag, then CRE_FAKE / CL_FAKE). + if f := command.PersistentFlags().Lookup(fakeFlag); f != nil { + _ = viper.BindPFlag(fakeFlag, f) + _ = viper.BindEnv(fakeFlag, "CRE_FAKE", "CL_FAKE") } - return sql.Open("pgx", dbURL) + // Chain after whatever is already wired (notably the decode step RegisterCommandFlags just + // installed), so cfg is populated by the time these cross-flag rules are checked - and + // checked before any service starts, rather than at connect time. + prev := command.PersistentPreRunE + command.PersistentPreRunE = func(c *cobra.Command, args []string) error { + if prev != nil { + if err := prev(c, args); err != nil { + return err + } + } + // help and completion describe the program rather than run it, so they must work + // without a usable configuration - the library skips its own validation for them, and + // this check has to do the same. + if flags.IsBuiltinCommand(c) { + return nil + } + return d.cfg.validate(viper.GetBool(fakeFlag)) + } } var _ standalone.BootstrapDependency[*sql.DB] = (*dependency)(nil) diff --git a/libs/standalone/evm/dependency.go b/libs/standalone/evm/dependency.go index afd5f91a7..3c3141383 100644 --- a/libs/standalone/evm/dependency.go +++ b/libs/standalone/evm/dependency.go @@ -1,28 +1,15 @@ -// Package evm provides a standalone.BootstrapDependency that supplies an EVM RPC -// client to a standalone binary. -// -// The client is chainlink-evm's multinode-backed client.Client, not a bare geth -// *ethclient.Client. That is deliberate: multinode is where the RPC reliability -// behaviour lives (per-node health polling, sync-threshold detection, dead-node -// declaration, primary selection, load-balanced RPC support). A standalone process -// reading a contract needs exactly those properties and should not reimplement -// them. -// -// What this does not bring along is the relayer / ContractReader stack. Callers -// get a client.Client and make bind.ContractCaller view calls against generated -// gethwrappers directly, which is a far shorter path to follow than -// relayer -> ContractReader -> codec -> ReadIdentifier. package evm import ( "context" - "errors" "fmt" "math/big" "time" "github.com/spf13/cobra" + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/config/flags" "github.com/smartcontractkit/chainlink-common/pkg/logger" evmclient "github.com/smartcontractkit/chainlink-evm/pkg/client" @@ -54,7 +41,7 @@ const ( // Dependency returns a standalone.BootstrapDependency that resolves a dialed, // multinode-backed EVM client. // -// At least one --evm-http-url is required. WebSocket URLs are optional; without +// At least one --evm.http-url is required. WebSocket URLs are optional; without // them the client polls for heads rather than subscribing, which is enough for // view calls. func Dependency(lggr logger.Logger) standalone.BootstrapDependency[evmclient.Client] { @@ -63,65 +50,72 @@ func Dependency(lggr logger.Logger) standalone.BootstrapDependency[evmclient.Cli return standalone.OnceBootstrapper[evmclient.Client](&dependency{lggr: lggr}) } +// Config is the EVM client configuration. At least one http-url is required; WebSocket URLs +// are optional, and without them the client polls for heads rather than subscribing, which is +// enough for view calls. +type Config struct { + HTTPURLs []string `toml:"http-url" usage:"EVM RPC HTTP URL(s); repeat or comma-separate for a multinode pool" validate:"required" example:"['https://rpc.example.com']"` + WSURLs []string `toml:"ws-url" usage:"EVM RPC WebSocket URL(s), positionally paired with --evm.http-url; optional" validate:"excluded_without=HTTPURLs"` + ChainID string `toml:"chain-id" usage:"EVM chain ID" validate:"required" example:"'1'"` + ChainType string `toml:"chain-type" usage:"EVM chain type (empty for a generic EVM chain)"` + + FinalityTagEnabled bool `toml:"finality-tag-enabled" usage:"use the finalized block tag instead of a finality depth"` + FinalityDepth uint32 `toml:"finality-depth" usage:"finality depth, used when --evm.finality-tag-enabled=false"` + PollInterval config.Duration `toml:"poll-interval" usage:"per-node health poll interval"` +} + +var defaultConfig = Config{ + FinalityTagEnabled: true, + FinalityDepth: defaultFinalityDepth, + PollInterval: *config.MustNewDuration(defaultPollInterval), +} + type dependency struct { lggr logger.Logger client evmclient.Client - - httpURLs []string - wsURLs []string - chainID string - chainType string - finalityTagEnabled bool - finalityDepth uint32 - pollInterval time.Duration + cfg Config } var _ standalone.BootstrapDependency[evmclient.Client] = (*dependency)(nil) +// Namespace groups the EVM settings under evm.* (--evm.http-url, CRE_EVM_HTTP_URL). +func (d *dependency) Namespace() string { return "evm" } + func (d *dependency) AddCommands(cmd *cobra.Command) { - f := cmd.PersistentFlags() - f.StringSliceVar(&d.httpURLs, "evm-http-url", nil, "EVM RPC HTTP URL(s); repeat or comma-separate for a multinode pool") - f.StringSliceVar(&d.wsURLs, "evm-ws-url", nil, "EVM RPC WebSocket URL(s), positionally paired with --evm-http-url; optional") - f.StringVar(&d.chainID, "evm-chain-id", "", "EVM chain ID") - f.StringVar(&d.chainType, "evm-chain-type", "", "EVM chain type (empty for a generic EVM chain)") - f.BoolVar(&d.finalityTagEnabled, "evm-finality-tag-enabled", true, "use the finalized block tag instead of a finality depth") - f.Uint32Var(&d.finalityDepth, "evm-finality-depth", defaultFinalityDepth, "finality depth, used when --evm-finality-tag-enabled=false") - f.DurationVar(&d.pollInterval, "evm-poll-interval", defaultPollInterval, "per-node health poll interval") - - standalone.BindWithEnvVar(f.Lookup("evm-http-url")) - standalone.BindWithEnvVar(f.Lookup("evm-ws-url")) - standalone.BindWithEnvVar(f.Lookup("evm-chain-id")) + d.cfg = defaultConfig + opts := flags.DefaultTOMLOptions("CRE", "CL") + opts.Namespace = d.Namespace() + if err := flags.RegisterCommandFlags(cmd, &d.cfg, opts); err != nil { + panic(err) + } } func (d *dependency) Get(ctx context.Context, _ standalone.CommonConfig) (evmclient.Client, error) { - if len(d.httpURLs) == 0 { - return nil, errors.New("at least one --evm-http-url is required") - } - if len(d.wsURLs) > 0 && len(d.wsURLs) != len(d.httpURLs) { - return nil, fmt.Errorf("--evm-ws-url count (%d) must match --evm-http-url count (%d) when provided", len(d.wsURLs), len(d.httpURLs)) + if len(d.cfg.WSURLs) > 0 && len(d.cfg.WSURLs) != len(d.cfg.HTTPURLs) { + return nil, fmt.Errorf("--evm.ws-url count (%d) must match --evm.http-url count (%d) when provided", len(d.cfg.WSURLs), len(d.cfg.HTTPURLs)) } - chainID, ok := new(big.Int).SetString(d.chainID, 10) + chainID, ok := new(big.Int).SetString(d.cfg.ChainID, 10) if !ok { - return nil, fmt.Errorf("invalid --evm-chain-id %q", d.chainID) + return nil, fmt.Errorf("invalid --evm.chain-id %q", d.cfg.ChainID) } - nodeCfgs := make([]evmclient.NodeConfig, len(d.httpURLs)) - for i := range d.httpURLs { + nodeCfgs := make([]evmclient.NodeConfig, len(d.cfg.HTTPURLs)) + for i := range d.cfg.HTTPURLs { name := fmt.Sprintf("node-%d", i) order := int32(1) sendOnly := false loadBalanced := false cfg := evmclient.NodeConfig{ Name: &name, - HTTPURL: &d.httpURLs[i], + HTTPURL: &d.cfg.HTTPURLs[i], Order: &order, SendOnly: &sendOnly, IsLoadBalancedRPC: &loadBalanced, } - if len(d.wsURLs) > 0 { - cfg.WSURL = &d.wsURLs[i] + if len(d.cfg.WSURLs) > 0 { + cfg.WSURL = &d.cfg.WSURLs[i] } nodeCfgs[i] = cfg } @@ -131,8 +125,8 @@ func (d *dependency) Get(ctx context.Context, _ standalone.CommonConfig) (evmcli pollSuccessThreshold := defaultPollSuccessThreshold syncThreshold := defaultSyncThreshold nodeIsSyncingEnabled := false - finalityDepth := d.finalityDepth - finalityTagEnabled := d.finalityTagEnabled + finalityDepth := d.cfg.FinalityDepth + finalityTagEnabled := d.cfg.FinalityTagEnabled safeTagSupported := false finalizedBlockOffset := defaultFinalizedBlockOffset enforceRepeatableRead := true @@ -141,11 +135,11 @@ func (d *dependency) Get(ctx context.Context, _ standalone.CommonConfig) (evmcli chainCfg, nodePool, nodes, err := evmclient.NewClientConfigs( &selectionMode, defaultLeaseDuration, - d.chainType, + d.cfg.ChainType, nodeCfgs, &pollFailureThreshold, &pollSuccessThreshold, - d.pollInterval, + d.cfg.PollInterval.Duration(), &syncThreshold, &nodeIsSyncingEnabled, defaultNoNewHeadsThreshold, @@ -167,7 +161,7 @@ func (d *dependency) Get(ctx context.Context, _ standalone.CommonConfig) (evmcli // clientErrors is nil: it only feeds ClassifySendError on the transaction send // path, and this client is read-only. - cl, err := evmclient.NewEvmClient(nodePool, chainCfg, nil, d.lggr, chainID, nodes, chaintype.ChainType(d.chainType)) + cl, err := evmclient.NewEvmClient(nodePool, chainCfg, nil, d.lggr, chainID, nodes, chaintype.ChainType(d.cfg.ChainType)) if err != nil { return nil, fmt.Errorf("failed to create evm client: %w", err) } diff --git a/libs/standalone/ocr/dependency.go b/libs/standalone/ocr/dependency.go index a6b906ec5..d733037ce 100644 --- a/libs/standalone/ocr/dependency.go +++ b/libs/standalone/ocr/dependency.go @@ -5,20 +5,19 @@ // It has two mutually exclusive modes, mirroring core's SingletonPeerWrapper: // // - create: build a local libocr peer (networking.NewPeer) and expose its -// factories. Requires --listen-addresses and uses the node's P2P identity +// factories. Requires --ocr.listen-addresses and uses the node's P2P identity // and OCR discoverer table from the database. // - proxy: delegate rage networking to an out-of-process proxy at -// --proxy-address, exposing proxy-client-backed factories instead of a +// --ocr.proxy-address, exposing proxy-client-backed factories instead of a // local peer. // // The two modes are wired as a cobra "one of" set: exactly one of -// --listen-addresses / --proxy-address may (and must) be provided. +// --ocr.listen-addresses / --ocr.proxy-address may (and must) be provided. package ocr import ( "context" "database/sql" - "errors" "fmt" "io" "time" @@ -32,6 +31,8 @@ import ( ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/config/flags" commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" commonocr "github.com/smartcontractkit/chainlink-common/pkg/ocrcommon" creproxy "github.com/smartcontractkit/chainlink-protos/cre/impl/proxy" @@ -78,47 +79,65 @@ func Dependency(lggr commonlogger.Logger, db standalone.BootstrapDependency[*sql return standalone.OnceBootstrapper[*Factories](&dependency{lggr: lggr, db: db, discovererTable: discovererTable}) } +// Config is the libocr networking configuration. +// +// The two modes are expressed as validator tags rather than cobra's +// MarkFlagsMutuallyExclusive/MarkFlagsOneRequired: those only inspect whether a flag was +// literally typed on the command line, so they would reject a mode that was selected via a +// config file or env var. required_without/excluded_with are checked against the decoded +// values instead, so exactly-one-of holds no matter which source supplied it. +type Config struct { + // create-mode config + ListenAddresses []string `toml:"listen-addresses" usage:"rage p2p V2 listen addresses (host:port); creates a local peer" validate:"required_without=ProxyAddress,excluded_with=ProxyAddress" example:"['127.0.0.1:1234']"` + AnnounceAddresses []string `toml:"announce-addresses" usage:"rage p2p V2 announce addresses (host:port); defaults to the listen addresses" validate:"excluded_without=ListenAddresses"` + DeltaReconcile config.Duration `toml:"delta-reconcile" usage:"rage p2p V2 delta reconcile interval"` + DeltaDial config.Duration `toml:"delta-dial" usage:"rage p2p V2 minimum interval between dial attempts"` + + IncomingBufferSize int `toml:"incoming-buffer-size" usage:"per-remote incoming message buffer size"` + OutgoingBufferSize int `toml:"outgoing-buffer-size" usage:"per-remote outgoing message buffer size"` + + // KeystorePassword unlocks the node's key ring, which is where the shared P2P identity + // comes from. Both modes need it: the peer ID is what other DON members expect at this + // address, whether this process runs the peer itself or delegates to a proxy. Typed as a + // SecretString so it redacts itself in logs, docs and the example config. + KeystorePassword config.SecretString `toml:"keystore-password" usage:"password for the node keystore holding the shared P2P identity" validate:"required"` + + // proxy-mode config + ProxyAddress string `toml:"proxy-address" usage:"delegate rage networking to a proxy at this gRPC address instead of creating a local peer" validate:"excluded_with=ListenAddresses"` +} + +var defaultConfig = Config{ + DeltaReconcile: *config.MustNewDuration(time.Minute), + DeltaDial: *config.MustNewDuration(5 * time.Second), + IncomingBufferSize: 100, + OutgoingBufferSize: 100, +} + type dependency struct { lggr commonlogger.Logger db standalone.BootstrapDependency[*sql.DB] discovererTable string - // create-mode config - listenAddresses []string - announceAddresses []string - deltaReconcile time.Duration - deltaDial time.Duration - incomingBufferSize int - outgoingBufferSize int - - // proxy-mode config - proxyAddress string + cfg Config } var _ standalone.BootstrapDependency[*Factories] = (*dependency)(nil) +// Namespace groups the libocr networking settings under ocr.* (--ocr.listen-addresses, +// CRE_OCR_LISTEN_ADDRESSES). +func (d *dependency) Namespace() string { return "ocr" } + func (d *dependency) AddCommands(cmd *cobra.Command) { // The database is a create/proxy-shared input (P2P identity, discoverer - // table), so surface its flags too. + // table), so surface its flags too - under its own namespace. d.db.AddCommands(cmd) - f := cmd.PersistentFlags() - // create-mode flags - f.StringSliceVar(&d.listenAddresses, "listen-addresses", nil, "rage p2p V2 listen addresses (host:port); creates a local peer") - f.StringSliceVar(&d.announceAddresses, "announce-addresses", nil, "rage p2p V2 announce addresses (host:port); defaults to the listen addresses") - f.DurationVar(&d.deltaReconcile, "delta-reconcile", time.Minute, "rage p2p V2 delta reconcile interval") - f.DurationVar(&d.deltaDial, "delta-dial", 5*time.Second, "rage p2p V2 minimum interval between dial attempts") - f.IntVar(&d.incomingBufferSize, "incoming-buffer-size", 100, "per-remote incoming message buffer size") - f.IntVar(&d.outgoingBufferSize, "outgoing-buffer-size", 100, "per-remote outgoing message buffer size") - // proxy-mode flag - f.StringVar(&d.proxyAddress, "proxy-address", "", "delegate rage networking to a proxy at this gRPC address instead of creating a local peer") - - // Exactly one mode: --listen-addresses (create) xor --proxy-address (proxy). - cmd.MarkFlagsMutuallyExclusive("listen-addresses", "proxy-address") - cmd.MarkFlagsOneRequired("listen-addresses", "proxy-address") - // announce-addresses is optional in create mode (libocr defaults it to the - // listen addresses) but is meaningless in proxy mode. - cmd.MarkFlagsMutuallyExclusive("announce-addresses", "proxy-address") + d.cfg = defaultConfig + opts := flags.DefaultTOMLOptions("CRE", "CL") + opts.Namespace = d.Namespace() + if err := flags.RegisterCommandFlags(cmd, &d.cfg, opts); err != nil { + panic(err) + } } func (d *dependency) Get(ctx context.Context, cc standalone.CommonConfig) (*Factories, error) { @@ -130,13 +149,13 @@ func (d *dependency) Get(ctx context.Context, cc standalone.CommonConfig) (*Fact // Both modes use the node's own P2P identity so this process is the same // peer as the node it fronts. - keyring, err := loadPeerKeyring(ctx, ds) + keyring, err := loadPeerKeyring(ctx, ds, string(d.cfg.KeystorePassword)) if err != nil { return nil, err } peerID := ragetypes.PeerIDFromKeyring(keyring) - if d.proxyAddress != "" { + if d.cfg.ProxyAddress != "" { return d.proxyFactories(peerID) } return d.localFactories(ds, keyring, peerID) @@ -144,29 +163,25 @@ func (d *dependency) Get(ctx context.Context, cc standalone.CommonConfig) (*Fact // localFactories builds a real libocr peer and exposes its factories. func (d *dependency) localFactories(ds *sqlx.DB, keyring ragetypes.PeerKeyring, peerID ragetypes.PeerID) (*Factories, error) { - if len(d.listenAddresses) == 0 { - return nil, errors.New("at least one --listen-addresses is required") - } - discovererDB := commonocr.NewDiscovererDatabase(ds, peerID.String(), d.discovererTable) d.lggr.Infow("Creating local p2p peer", "peerID", peerID.String(), - "listenAddresses", d.listenAddresses, - "announceAddresses", d.announceAddresses, + "listenAddresses", d.cfg.ListenAddresses, + "announceAddresses", d.cfg.AnnounceAddresses, ) peer, err := networking.NewPeer(networking.PeerConfig{ PeerKeyring: keyring, Logger: commonlogger.NewOCRWrapper(d.lggr, false, func(string) {}), - V2ListenAddresses: d.listenAddresses, - V2AnnounceAddresses: d.announceAddresses, - V2DeltaReconcile: d.deltaReconcile, - V2DeltaDial: d.deltaDial, + V2ListenAddresses: d.cfg.ListenAddresses, + V2AnnounceAddresses: d.cfg.AnnounceAddresses, + V2DeltaReconcile: d.cfg.DeltaReconcile.Duration(), + V2DeltaDial: d.cfg.DeltaDial.Duration(), V2DiscovererDatabase: discovererDB, V2EndpointConfig: networking.EndpointConfigV2{ - IncomingMessageBufferSize: d.incomingBufferSize, - OutgoingMessageBufferSize: d.outgoingBufferSize, + IncomingMessageBufferSize: d.cfg.IncomingBufferSize, + OutgoingMessageBufferSize: d.cfg.OutgoingBufferSize, }, MetricsRegisterer: prometheus.DefaultRegisterer, LatencyMetricsServiceConfigs: rageping.DefaultConfigs(), @@ -186,26 +201,26 @@ func (d *dependency) localFactories(ds *sqlx.DB, keyring ragetypes.PeerKeyring, // proxyFactories delegates rage networking to an out-of-process proxy: no local // peer is created; the factories are backed by proxy clients connected to -// d.proxyAddress. The node's raw peer ID is passed to the endpoint factories, +// d.cfg.ProxyAddress. The node's raw peer ID is passed to the endpoint factories, // as libocr compares it against the peer IDs in the OCR config. func (d *dependency) proxyFactories(peerID ragetypes.PeerID) (*Factories, error) { - endpointFactory, err := creproxy.NewProxyEndpointFactory(peerID.String(), d.proxyAddress) + endpointFactory, err := creproxy.NewProxyEndpointFactory(peerID.String(), d.cfg.ProxyAddress) if err != nil { return nil, fmt.Errorf("failed to create proxy OCR endpoint factory: %w", err) } - endpoint2Factory, err := creproxy.NewProxyEndpoint2Factory(peerID.String(), d.proxyAddress) + endpoint2Factory, err := creproxy.NewProxyEndpoint2Factory(peerID.String(), d.cfg.ProxyAddress) if err != nil { _ = endpointFactory.Close() return nil, fmt.Errorf("failed to create proxy OCR3.1 endpoint factory: %w", err) } - pgFactory, err := creproxy.NewProxyPeerGroupFactory(d.proxyAddress) + pgFactory, err := creproxy.NewProxyPeerGroupFactory(d.cfg.ProxyAddress) if err != nil { _ = endpointFactory.Close() _ = endpoint2Factory.Close() return nil, fmt.Errorf("failed to create proxy peer group factory: %w", err) } - d.lggr.Infow("Delegating rage networking to proxy", "proxyAddress", d.proxyAddress, "peerID", peerID.String()) + d.lggr.Infow("Delegating rage networking to proxy", "proxyAddress", d.cfg.ProxyAddress, "peerID", peerID.String()) return &Factories{ OCR2Endpoint: endpointFactory, diff --git a/libs/standalone/ocr/keyring.go b/libs/standalone/ocr/keyring.go index 78a600279..2be0aa214 100644 --- a/libs/standalone/ocr/keyring.go +++ b/libs/standalone/ocr/keyring.go @@ -6,7 +6,6 @@ import ( "crypto/rand" "errors" "fmt" - "os" "github.com/jmoiron/sqlx" @@ -15,10 +14,6 @@ import ( "github.com/smartcontractkit/chainlink-common/keystore/corekeys/models" ) -// keystorePasswordEnvVar is the keystore password used to decrypt the node's -// key ring. This process shares the node's DB (CL_DATABASE_URL) and password. -const keystorePasswordEnvVar = "CL_PASSWORD_KEYSTORE" - // loadPeerKeyring loads the P2P key from the node's keystore so this process // uses the SAME peer identity as the node it fronts (other DON members expect // this node's peer ID at this address). It reads the node's existing encrypted @@ -27,15 +22,18 @@ const keystorePasswordEnvVar = "CL_PASSWORD_KEYSTORE" // deliberately small copy of core's keyManager.Unlock using only // chainlink-common packages, so this binary needn't import chainlink core. // +// password is the node's keystore password: this process shares the node's +// database and therefore its keystore password. +// // TODO: drop this once the keystore is migrated to chainlink-common's // keystore.Keystore + pgstore (as chainlink-ccv already uses), after which we // can LoadKeystore from the shared table directly. -func loadPeerKeyring(ctx context.Context, ds *sqlx.DB) (*peerKeyring, error) { +func loadPeerKeyring(ctx context.Context, ds *sqlx.DB, password string) (*peerKeyring, error) { var encrypted []byte if err := ds.GetContext(ctx, &encrypted, "SELECT encrypted_keys FROM encrypted_key_rings LIMIT 1"); err != nil { return nil, fmt.Errorf("failed to read node key ring: %w", err) } - kr, err := models.EncryptedKeyRing{EncryptedKeys: encrypted}.Decrypt(os.Getenv(keystorePasswordEnvVar)) + kr, err := models.EncryptedKeyRing{EncryptedKeys: encrypted}.Decrypt(password) if err != nil { return nil, fmt.Errorf("failed to decrypt node key ring: %w", err) }