Skip to content

Commit

Permalink
[CAPPL-217] expose workflow key to clo (#15287)
Browse files Browse the repository at this point in the history
* feat: expose workflow key to clo

* fix: not all nodes will have the workflowkey, allow it to be empty

* fix: return empty if publickey is nil

* chore: change method getWorkflowPublicKey to return a string instead of the key

* chore: bump chainlink-protos/orchestrator v0.3.2

* fix: workflowKey is optional

* fix: re-adjust PublicKey() method to satisfy the secrets.X25519Key interface
  • Loading branch information
agparadiso authored Dec 3, 2024
1 parent 8fa3cda commit f986e0e
Show file tree
Hide file tree
Showing 15 changed files with 583 additions and 45 deletions.
1 change: 1 addition & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ packages:
config:
filename: starknet.go
VRF:
Workflow:
github.com/smartcontractkit/chainlink/v2/core/services/ocr:
interfaces:
OCRContractTrackerDB:
Expand Down
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ require (
github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect
github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect
github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 // indirect
github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect
github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 // indirect
github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749 // indirect
github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1152,8 +1152,8 @@ github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6An
github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4=
github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 h1:0ewLMbAz3rZrovdRUCgd028yOXX8KigB4FndAUdI2kM=
github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE=
github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI=
github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo=
github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 h1:onBe3DqNrbtOAzKS4PrPIiJX65BGo1aYiYZxFVEW+jc=
github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo=
github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749 h1:gkrjGJAtbKMOliJPaZ73EyJmO8AyDVi80+PEJocRMn4=
github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749/go.mod h1:nkIegLHodyrrZguxkYEHcNw2vAXv8H8xlCoLzwylcL0=
github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA=
Expand Down
18 changes: 18 additions & 0 deletions core/services/feeds/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ type service struct {
p2pKeyStore keystore.P2P
ocr1KeyStore keystore.OCR
ocr2KeyStore keystore.OCR2
workflowKeyStore keystore.Workflow
jobSpawner job.Spawner
gCfg GeneralConfig
featCfg FeatureConfig
Expand Down Expand Up @@ -170,6 +171,7 @@ func NewService(
csaKeyStore: keyStore.CSA(),
ocr1KeyStore: keyStore.OCR(),
ocr2KeyStore: keyStore.OCR2(),
workflowKeyStore: keyStore.Workflow(),
gCfg: gCfg,
featCfg: fCfg,
insecureCfg: insecureCfg,
Expand Down Expand Up @@ -277,9 +279,11 @@ func (s *service) SyncNodeInfo(ctx context.Context, id int64) error {
cfgMsgs = append(cfgMsgs, cfgMsg)
}

workflowKey := s.getWorkflowPublicKey()
if _, err = fmsClient.UpdateNode(ctx, &pb.UpdateNodeRequest{
Version: s.version,
ChainConfigs: cfgMsgs,
WorkflowKey: &workflowKey,
}); err != nil {
return err
}
Expand Down Expand Up @@ -1173,6 +1177,20 @@ func (s *service) getCSAPrivateKey() (privkey []byte, err error) {
return keys[0].Raw(), nil
}

// getWorkflowPublicKey retrieves the server's Workflow public key.
// Since there will be at most one key, it returns the first key found.
// If an error occurs or no keys are found, it returns blank.
func (s *service) getWorkflowPublicKey() string {
keys, err := s.workflowKeyStore.GetAll()
if err != nil {
return ""
}
if len(keys) < 1 {
return ""
}
return keys[0].PublicKeyString()
}

// observeJobProposalCounts is a helper method that queries the repository for the count of
// job proposals by status and then updates prometheus gauges.
func (s *service) observeJobProposalCounts(ctx context.Context) error {
Expand Down
89 changes: 59 additions & 30 deletions core/services/feeds/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocrkey"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey"
ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks"
"github.com/smartcontractkit/chainlink/v2/core/services/pipeline"
"github.com/smartcontractkit/chainlink/v2/core/services/versioning"
Expand Down Expand Up @@ -183,16 +184,17 @@ chainID = 1337

type TestService struct {
feeds.Service
orm *mocks.ORM
jobORM *jobmocks.ORM
connMgr *mocks.ConnectionsManager
spawner *jobmocks.Spawner
fmsClient *mocks.FeedsManagerClient
csaKeystore *ksmocks.CSA
p2pKeystore *ksmocks.P2P
ocr1Keystore *ksmocks.OCR
ocr2Keystore *ksmocks.OCR2
legacyChains legacyevm.LegacyChainContainer
orm *mocks.ORM
jobORM *jobmocks.ORM
connMgr *mocks.ConnectionsManager
spawner *jobmocks.Spawner
fmsClient *mocks.FeedsManagerClient
csaKeystore *ksmocks.CSA
p2pKeystore *ksmocks.P2P
ocr1Keystore *ksmocks.OCR
ocr2Keystore *ksmocks.OCR2
workflowKeystore *ksmocks.Workflow
legacyChains legacyevm.LegacyChainContainer
}

func setupTestService(t *testing.T) *TestService {
Expand All @@ -205,15 +207,16 @@ func setupTestServiceCfg(t *testing.T, overrideCfg func(c *chainlink.Config, s *
t.Helper()

var (
orm = mocks.NewORM(t)
jobORM = jobmocks.NewORM(t)
connMgr = mocks.NewConnectionsManager(t)
spawner = jobmocks.NewSpawner(t)
fmsClient = mocks.NewFeedsManagerClient(t)
csaKeystore = ksmocks.NewCSA(t)
p2pKeystore = ksmocks.NewP2P(t)
ocr1Keystore = ksmocks.NewOCR(t)
ocr2Keystore = ksmocks.NewOCR2(t)
orm = mocks.NewORM(t)
jobORM = jobmocks.NewORM(t)
connMgr = mocks.NewConnectionsManager(t)
spawner = jobmocks.NewSpawner(t)
fmsClient = mocks.NewFeedsManagerClient(t)
csaKeystore = ksmocks.NewCSA(t)
p2pKeystore = ksmocks.NewP2P(t)
ocr1Keystore = ksmocks.NewOCR(t)
ocr2Keystore = ksmocks.NewOCR2(t)
workflowKeystore = ksmocks.NewWorkflow(t)
)

lggr := logger.TestLogger(t)
Expand All @@ -229,21 +232,23 @@ func setupTestServiceCfg(t *testing.T, overrideCfg func(c *chainlink.Config, s *
keyStore.On("P2P").Return(p2pKeystore)
keyStore.On("OCR").Return(ocr1Keystore)
keyStore.On("OCR2").Return(ocr2Keystore)
keyStore.On("Workflow").Return(workflowKeystore)
svc := feeds.NewService(orm, jobORM, db, spawner, keyStore, gcfg, gcfg.Feature(), gcfg.Insecure(), gcfg.JobPipeline(), gcfg.OCR(), gcfg.OCR2(), legacyChains, lggr, "1.0.0", nil)
svc.SetConnectionsManager(connMgr)

return &TestService{
Service: svc,
orm: orm,
jobORM: jobORM,
connMgr: connMgr,
spawner: spawner,
fmsClient: fmsClient,
csaKeystore: csaKeystore,
p2pKeystore: p2pKeystore,
ocr1Keystore: ocr1Keystore,
ocr2Keystore: ocr2Keystore,
legacyChains: legacyChains,
Service: svc,
orm: orm,
jobORM: jobORM,
connMgr: connMgr,
spawner: spawner,
fmsClient: fmsClient,
csaKeystore: csaKeystore,
p2pKeystore: p2pKeystore,
ocr1Keystore: ocr1Keystore,
ocr2Keystore: ocr2Keystore,
workflowKeystore: workflowKeystore,
legacyChains: legacyChains,
}
}

Expand Down Expand Up @@ -613,10 +618,15 @@ func Test_Service_CreateChainConfig(t *testing.T) {
svc = setupTestService(t)
)

workflowKey, err := workflowkey.New()
require.NoError(t, err)
svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil)

svc.orm.On("CreateChainConfig", mock.Anything, cfg).Return(int64(1), nil)
svc.orm.On("GetManager", mock.Anything, mgr.ID).Return(&mgr, nil)
svc.connMgr.On("GetClient", mgr.ID).Return(svc.fmsClient, nil)
svc.orm.On("ListChainConfigsByManagerIDs", mock.Anything, []int64{mgr.ID}).Return([]feeds.ChainConfig{cfg}, nil)
wkID := workflowKey.ID()
svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{
Version: nodeVersion.Version,
ChainConfigs: []*proto.ChainConfig{
Expand All @@ -633,6 +643,7 @@ func Test_Service_CreateChainConfig(t *testing.T) {
Ocr2Config: &proto.OCR2Config{Enabled: false},
},
},
WorkflowKey: &wkID,
}).Return(&proto.UpdateNodeResponse{}, nil)

actual, err := svc.CreateChainConfig(testutils.Context(t), cfg)
Expand Down Expand Up @@ -677,14 +688,20 @@ func Test_Service_DeleteChainConfig(t *testing.T) {
svc = setupTestService(t)
)

workflowKey, err := workflowkey.New()
require.NoError(t, err)
svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil)

svc.orm.On("GetChainConfig", mock.Anything, cfg.ID).Return(&cfg, nil)
svc.orm.On("DeleteChainConfig", mock.Anything, cfg.ID).Return(cfg.ID, nil)
svc.orm.On("GetManager", mock.Anything, mgr.ID).Return(&mgr, nil)
svc.connMgr.On("GetClient", mgr.ID).Return(svc.fmsClient, nil)
svc.orm.On("ListChainConfigsByManagerIDs", mock.Anything, []int64{mgr.ID}).Return([]feeds.ChainConfig{}, nil)
wkID := workflowKey.ID()
svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{
Version: nodeVersion.Version,
ChainConfigs: []*proto.ChainConfig{},
WorkflowKey: &wkID,
}).Return(&proto.UpdateNodeResponse{}, nil)

actual, err := svc.DeleteChainConfig(testutils.Context(t), cfg.ID)
Expand Down Expand Up @@ -762,10 +779,15 @@ func Test_Service_UpdateChainConfig(t *testing.T) {
svc = setupTestService(t)
)

workflowKey, err := workflowkey.New()
require.NoError(t, err)
svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil)

svc.orm.On("UpdateChainConfig", mock.Anything, cfg).Return(int64(1), nil)
svc.orm.On("GetChainConfig", mock.Anything, cfg.ID).Return(&cfg, nil)
svc.connMgr.On("GetClient", mgr.ID).Return(svc.fmsClient, nil)
svc.orm.On("ListChainConfigsByManagerIDs", mock.Anything, []int64{mgr.ID}).Return([]feeds.ChainConfig{cfg}, nil)
wkID := workflowKey.ID()
svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{
Version: nodeVersion.Version,
ChainConfigs: []*proto.ChainConfig{
Expand All @@ -782,6 +804,7 @@ func Test_Service_UpdateChainConfig(t *testing.T) {
Ocr2Config: &proto.OCR2Config{Enabled: false},
},
},
WorkflowKey: &wkID,
}).Return(&proto.UpdateNodeResponse{}, nil)

actual, err := svc.UpdateChainConfig(testutils.Context(t), cfg)
Expand Down Expand Up @@ -1707,6 +1730,9 @@ func Test_Service_SyncNodeInfo(t *testing.T) {
ocrKey, err := ocrkey.NewV2()
require.NoError(t, err)

workflowKey, err := workflowkey.New()
require.NoError(t, err)

var (
multiaddr = "/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju"
mgr = &feeds.FeedsManager{ID: 1}
Expand Down Expand Up @@ -1754,6 +1780,8 @@ func Test_Service_SyncNodeInfo(t *testing.T) {
svc.p2pKeystore.On("Get", p2pKey.PeerID()).Return(p2pKey, nil)
svc.ocr1Keystore.On("Get", ocrKey.GetID()).Return(ocrKey, nil)

svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil)
wkID := workflowKey.ID()
svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{
Version: nodeVersion.Version,
ChainConfigs: []*proto.ChainConfig{
Expand Down Expand Up @@ -1794,6 +1822,7 @@ func Test_Service_SyncNodeInfo(t *testing.T) {
},
},
},
WorkflowKey: &wkID,
}).Return(&proto.UpdateNodeResponse{}, nil)

err = svc.SyncNodeInfo(testutils.Context(t), mgr.ID)
Expand Down
16 changes: 16 additions & 0 deletions core/services/keystore/keys/workflowkey/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,18 @@ func New() (Key, error) {
}

func (k Key) PublicKey() [curve25519.PointSize]byte {
if k.publicKey == nil {
return [curve25519.PointSize]byte{}
}

return *k.publicKey
}

func (k Key) PublicKeyString() string {
if k.publicKey == nil {
return ""
}

return hex.EncodeToString(k.publicKey[:])
}

Expand All @@ -78,6 +86,10 @@ func (k Key) GoString() string {
// Encrypt encrypts a message using the public key
func (k Key) Encrypt(plaintext []byte) ([]byte, error) {
publicKey := k.PublicKey()
if publicKey == [curve25519.PointSize]byte{} {
return nil, errors.New("public key is empty")
}

encrypted, err := box.SealAnonymous(nil, plaintext, &publicKey, cryptorand.Reader)
if err != nil {
return nil, err
Expand All @@ -89,6 +101,10 @@ func (k Key) Encrypt(plaintext []byte) ([]byte, error) {
// Decrypt decrypts a message that was encrypted using the private key
func (k Key) Decrypt(ciphertext []byte) (plaintext []byte, err error) {
publicKey := k.PublicKey()
if publicKey == [curve25519.PointSize]byte{} {
return nil, errors.New("public key is empty")
}

decrypted, success := box.OpenAnonymous(nil, ciphertext, &publicKey, k.privateKey)
if !success {
return nil, errors.New("decryption failed")
Expand Down
Loading

0 comments on commit f986e0e

Please sign in to comment.