Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ debug.env
operator_ui/install
.devenv
event_dump.ndjson
CLAUDE.md

# neovim
.nvim.lua
Expand Down
62 changes: 43 additions & 19 deletions core/cmd/shell_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types"

"github.com/smartcontractkit/chainlink/v2/core/build"
"github.com/smartcontractkit/chainlink/v2/core/config"
"github.com/smartcontractkit/chainlink/v2/core/logger"
beholderServices "github.com/smartcontractkit/chainlink/v2/core/services/beholder"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
Expand Down Expand Up @@ -318,6 +319,28 @@ func (s *Shell) RunNode(c *cli.Context) error {
return nil
}

type ks[K any] interface {
Import(ctx context.Context, keyJSON []byte, password string) (K, error)
}

func importKeyIfProvided[K any](ctx context.Context, lggr logger.Logger, keyType string, importableKey config.ImportableKey, k ks[K]) error {
keyJSON := importableKey.JSON()
if keyJSON == "" {
return nil
}

lggr.Debugf("Importing %s %s", keyType, keyJSON)
_, err := k.Import(ctx, []byte(keyJSON), importableKey.Password())
if errors.Is(err, keystore.ErrKeyExists) {
lggr.Debugf("%s key already exists %s", keyType, keyJSON)
return nil
} else if err != nil {
return fmt.Errorf("error importing %s key: %w", keyType, err)
}

return nil
}

func (s *Shell) runNode(c *cli.Context) error {
ctx := s.ctx()
lggr := logger.Sugared(s.Logger.Named("RunNode"))
Expand Down Expand Up @@ -430,6 +453,11 @@ func (s *Shell) runNode(c *cli.Context) error {
}
}
if s.Config.OCR2().Enabled() {
err2 := importKeyIfProvided(rootCtx, lggr, "OCR2Key", s.Config.ImportedOCR2Key(), app.GetKeyStore().OCR2())
if err2 != nil {
return s.errorOut(err2)
}

var enabledChains []chaintype.ChainType
if s.Config.EVMEnabled() {
enabledChains = append(enabledChains, chaintype.EVM)
Expand All @@ -455,23 +483,19 @@ func (s *Shell) runNode(c *cli.Context) error {
if s.Config.SuiEnabled() {
enabledChains = append(enabledChains, chaintype.Sui)
}
err2 := app.GetKeyStore().OCR2().EnsureKeys(rootCtx, enabledChains...)
err2 = app.GetKeyStore().OCR2().EnsureKeys(rootCtx, enabledChains...)
if err2 != nil {
return fmt.Errorf("failed to ensure ocr key: %w", err2)
}
}

if s.Config.P2P().Enabled() {
if s.Config.ImportedP2PKey().JSON() != "" {
lggr.Debugf("Importing p2p key %s", s.Config.ImportedP2PKey().JSON())
_, err2 := app.GetKeyStore().P2P().Import(rootCtx, []byte(s.Config.ImportedP2PKey().JSON()), s.Config.ImportedP2PKey().Password())
if errors.Is(err2, keystore.ErrKeyExists) {
lggr.Debugf("P2P key already exists %s", s.Config.ImportedP2PKey().JSON())
} else if err2 != nil {
return s.errorOut(fmt.Errorf("error importing p2p key: %w", err2))
}
err2 := importKeyIfProvided(rootCtx, lggr, "P2P", s.Config.ImportedP2PKey(), app.GetKeyStore().P2P())
if err2 != nil {
return s.errorOut(err2)
}
err2 := app.GetKeyStore().P2P().EnsureKey(rootCtx)

err2 = app.GetKeyStore().P2P().EnsureKey(rootCtx)
if err2 != nil {
return fmt.Errorf("failed to ensure p2p key: %w", err2)
}
Expand Down Expand Up @@ -532,17 +556,12 @@ func (s *Shell) runNode(c *cli.Context) error {
}
}
if s.Config.CRE().EnableDKGRecipient() {
if s.Config.ImportedDKGRecipientKey().JSON() != "" {
lggr.Debugf("Importing DKG recipient key %s", s.Config.ImportedDKGRecipientKey().JSON())
_, err2 := app.GetKeyStore().DKGRecipient().Import(rootCtx, []byte(s.Config.ImportedDKGRecipientKey().JSON()), s.Config.ImportedDKGRecipientKey().Password())
if errors.Is(err2, keystore.ErrKeyExists) {
lggr.Debugf("DKG recipient key already exists %s", s.Config.ImportedDKGRecipientKey().JSON())
} else if err2 != nil {
return s.errorOut(fmt.Errorf("error importing dkg recipient key: %w", err2))
}
err2 := importKeyIfProvided(rootCtx, lggr, "DKG Recipient", s.Config.ImportedDKGRecipientKey(), app.GetKeyStore().DKGRecipient())
if err2 != nil {
return s.errorOut(err2)
}

err2 := app.GetKeyStore().DKGRecipient().EnsureKey(rootCtx)
err2 = app.GetKeyStore().DKGRecipient().EnsureKey(rootCtx)
if err2 != nil {
return fmt.Errorf("failed to ensure dkg recipient key: %w", err2)
}
Expand All @@ -553,6 +572,11 @@ func (s *Shell) runNode(c *cli.Context) error {
return fmt.Errorf("failed to ensure workflow key: %w", err2)
}

err2 = importKeyIfProvided(rootCtx, lggr, "CSA", s.Config.ImportedCSAKey(), app.GetKeyStore().CSA())
if err2 != nil {
return s.errorOut(err2)
}

err2 = app.GetKeyStore().CSA().EnsureKey(rootCtx)
if err2 != nil {
return fmt.Errorf("failed to ensure CSA key: %w", err2)
Expand Down
74 changes: 74 additions & 0 deletions core/config/toml/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ type Secrets struct {

P2PKey P2PKey `toml:",omitempty"`
DKGRecipientKey DKGRecipientKey `toml:",omitempty"`
CSAKey CSAKey `toml:",omitempty"`
OCR2Key OCR2Key `toml:",omitempty"`

CRE CreSecrets `toml:",omitempty"`
CCV CCVSecrets `toml:",omitempty"`
Expand Down Expand Up @@ -496,6 +498,78 @@ func (p *DKGRecipientKey) ValidateConfig() (err error) {
return err
}

type CSAKey struct {
JSON *models.Secret
Password *models.Secret
}

func (p *CSAKey) SetFrom(f *CSAKey) (err error) {
err = p.validateMerge(f)
if err != nil {
return err
}
if v := f.JSON; v != nil {
p.JSON = v
}
if v := f.Password; v != nil {
p.Password = v
}
return nil
}

func (p *CSAKey) validateMerge(f *CSAKey) (err error) {
if p.JSON != nil && f.JSON != nil {
err = errors.Join(err, configutils.ErrOverride{Name: "JSON"})
}
if p.Password != nil && f.Password != nil {
err = errors.Join(err, configutils.ErrOverride{Name: "Password"})
}
return err
}

func (p *CSAKey) ValidateConfig() (err error) {
if (p.JSON != nil) != (p.Password != nil) {
err = errors.Join(err, configutils.ErrInvalid{Name: "CSAKey", Value: p.JSON, Msg: "all fields must be nil or non-nil"})
}
return err
}

type OCR2Key struct {
JSON *models.Secret
Password *models.Secret
}

func (p *OCR2Key) SetFrom(f *OCR2Key) (err error) {
err = p.validateMerge(f)
if err != nil {
return err
}
if v := f.JSON; v != nil {
p.JSON = v
}
if v := f.Password; v != nil {
p.Password = v
}
return nil
}

func (p *OCR2Key) validateMerge(f *OCR2Key) (err error) {
if p.JSON != nil && f.JSON != nil {
err = errors.Join(err, configutils.ErrOverride{Name: "JSON"})
}
if p.Password != nil && f.Password != nil {
err = errors.Join(err, configutils.ErrOverride{Name: "Password"})
}
return err
}

func (p *OCR2Key) ValidateConfig() (err error) {
if (p.JSON != nil) != (p.Password != nil) {
err = errors.Join(err, configutils.ErrInvalid{Name: "OCR2Key", Value: p.JSON, Msg: "all fields must be nil or non-nil"})
}
return err
}

type Passwords struct {
Keystore *models.Secret
VRF *models.Secret
Expand Down
1 change: 1 addition & 0 deletions core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ require (
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d // indirect
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5 // indirect
github.com/smartcontractkit/chainlink-ccv v0.0.0-20260122132406-0ada7a3fe04a // indirect
github.com/smartcontractkit/chainlink-common/keystore v1.0.1 // indirect
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4 // indirect
github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260107191744-4b93f62cffe3 // indirect
github.com/smartcontractkit/chainlink-feeds v0.1.2-0.20250227211209-7cd000095135 // indirect
Expand Down
2 changes: 2 additions & 0 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.0-20260122132406-0ada7a3fe04a h1:
github.com/smartcontractkit/chainlink-ccv v0.0.0-20260122132406-0ada7a3fe04a/go.mod h1:Xe0SH5IHtGkCW6sy/EdBRPKD5L+U52HgoGfl0KDP/lw=
github.com/smartcontractkit/chainlink-common v0.9.6-0.20260203182613-20f261f2d612 h1:StSBoBt9m8gX76B6saXTGJ2eV6rBZIsfu3j6eUTzuNk=
github.com/smartcontractkit/chainlink-common v0.9.6-0.20260203182613-20f261f2d612/go.mod h1:zBuRC7el/pQQB95t7JnLOvCfZ3lmi5jjXYRUY2XUD+g=
github.com/smartcontractkit/chainlink-common/keystore v1.0.1 h1:yvg+wR1HZJ18f2+saZsfnwTAo3/NFEATKN3JbhWDVhU=
github.com/smartcontractkit/chainlink-common/keystore v1.0.1/go.mod h1:pd4CKmMJHQxzDk1xxxX0/uhQwq8N0iyKd0/RCv54/S8=
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4 h1:NOUsjsMzNecbjiPWUQGlRSRAutEvCFrqqyETDJeh5q4=
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4/go.mod h1:Zpvul9sTcZNAZOVzt5vBl1XZGNvQebFpnpn3/KOQvOQ=
github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340 h1:PsjEI+5jZIz9AS4eOsLS5VpSWJINf38clXV3wryPyMk=
Expand Down
8 changes: 8 additions & 0 deletions core/services/chainlink/config_general.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,14 @@ func (g *generalConfig) ImportedP2PKey() coreconfig.ImportableKey {
return &importedP2PKeyConfig{s: g.secrets.P2PKey}
}

func (g *generalConfig) ImportedCSAKey() coreconfig.ImportableKey {
return &importedCSAKeyConfig{s: g.secrets.CSAKey}
}

func (g *generalConfig) ImportedOCR2Key() coreconfig.ImportableKey {
return &importedOCR2KeyConfig{s: g.secrets.OCR2Key}
}

func (g *generalConfig) Tracing() coreconfig.Tracing {
return &tracingConfig{s: g.c.Tracing}
}
Expand Down
21 changes: 21 additions & 0 deletions core/services/chainlink/config_imported_csa_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package chainlink

import "github.com/smartcontractkit/chainlink/v2/core/config/toml"

type importedCSAKeyConfig struct {
s toml.CSAKey
}

func (t *importedCSAKeyConfig) JSON() string {
if t.s.JSON == nil {
return ""
}
return string(*t.s.JSON)
}

func (t *importedCSAKeyConfig) Password() string {
if t.s.Password == nil {
return ""
}
return string(*t.s.Password)
}
21 changes: 21 additions & 0 deletions core/services/chainlink/config_imported_ocr2_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package chainlink

import "github.com/smartcontractkit/chainlink/v2/core/config/toml"

type importedOCR2KeyConfig struct {
s toml.OCR2Key
}

func (t *importedOCR2KeyConfig) JSON() string {
if t.s.JSON == nil {
return ""
}
return string(*t.s.JSON)
}

func (t *importedOCR2KeyConfig) Password() string {
if t.s.Password == nil {
return ""
}
return string(*t.s.Password)
}
94 changes: 94 additions & 0 deletions core/services/chainlink/mocks/general_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions core/services/chainlink/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ type ImportedSecretConfig interface {
ImportedEthKeys() coreconfig.ImportableChainKeyLister
ImportedSolKeys() coreconfig.ImportableChainKeyLister
ImportedDKGRecipientKey() coreconfig.ImportableKey
ImportedCSAKey() coreconfig.ImportableKey
ImportedOCR2Key() coreconfig.ImportableKey
}
Loading
Loading