From 913427dc06fdf36dd0d5c4bd8403544463721c05 Mon Sep 17 00:00:00 2001 From: krehermann <16602512+krehermann@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:41:23 -0700 Subject: [PATCH 1/5] resurrect update don changeset --- deployment/environment/clo/utils.go | 32 ++++ .../keystone/changeset/internal/test/utils.go | 55 +++++- .../keystone/changeset/internal/update_don.go | 146 +++++++++++++++ .../changeset/internal/update_don_test.go | 172 ++++++++++++++++++ deployment/keystone/changeset/types.go | 50 +++++ deployment/keystone/changeset/update_don.go | 29 +++ deployment/keystone/deploy.go | 4 +- 7 files changed, 485 insertions(+), 3 deletions(-) create mode 100644 deployment/environment/clo/utils.go create mode 100644 deployment/keystone/changeset/internal/update_don.go create mode 100644 deployment/keystone/changeset/internal/update_don_test.go create mode 100644 deployment/keystone/changeset/types.go create mode 100644 deployment/keystone/changeset/update_don.go diff --git a/deployment/environment/clo/utils.go b/deployment/environment/clo/utils.go new file mode 100644 index 00000000000..79502ef6706 --- /dev/null +++ b/deployment/environment/clo/utils.go @@ -0,0 +1,32 @@ +package clo + +import ( + jd "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" +) + +// NewChainConfig creates a new JobDistributor ChainConfig from a clo model NodeChainConfig +func NewChainConfig(chain *models.NodeChainConfig) *jd.ChainConfig { + return &jd.ChainConfig{ + Chain: &jd.Chain{ + Id: chain.Network.ChainID, + Type: jd.ChainType_CHAIN_TYPE_EVM, // TODO: support other chain types + }, + + AccountAddress: chain.AccountAddress, + AdminAddress: chain.AdminAddress, + Ocr2Config: &jd.OCR2Config{ + Enabled: chain.Ocr2Config.Enabled, + P2PKeyBundle: &jd.OCR2Config_P2PKeyBundle{ + PeerId: chain.Ocr2Config.P2pKeyBundle.PeerID, + PublicKey: chain.Ocr2Config.P2pKeyBundle.PublicKey, + }, + OcrKeyBundle: &jd.OCR2Config_OCRKeyBundle{ + BundleId: chain.Ocr2Config.OcrKeyBundle.BundleID, + OnchainSigningAddress: chain.Ocr2Config.OcrKeyBundle.OnchainSigningAddress, + OffchainPublicKey: chain.Ocr2Config.OcrKeyBundle.OffchainPublicKey, + ConfigPublicKey: chain.Ocr2Config.OcrKeyBundle.ConfigPublicKey, + }, + }, + } +} diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index 64b08a70150..a11f8ecde0b 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -8,8 +8,11 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/environment/memory" @@ -19,10 +22,15 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) +type Don struct { + Name string + P2PIDs []p2pkey.PeerID + CapabilityConfigs []internal.CapabilityConfig +} type SetupTestRegistryRequest struct { P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc - DonToNodes map[string][]*internal.P2PSignerEnc + Dons []Don } type SetupTestRegistryResponse struct { @@ -74,6 +82,8 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR nodeParams, err := phonyRequest.NodeParams() require.NoError(t, err) addNodes(t, lggr, chain, registry, nodeParams) + addDons(t, lggr, chain, registry, capCache, req.Dons) + return &SetupTestRegistryResponse{ Registry: registry, Chain: chain, @@ -108,6 +118,45 @@ func addNodes(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry require.NoError(t, err) } +func addDons(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry *kcr.CapabilitiesRegistry, cc *CapabilityCache, dons []Don) { + for _, don := range dons { + acceptsWorkflows := false + // lookup the capabilities + var capConfigs []kcr.CapabilitiesRegistryCapabilityConfiguration + for _, ccfg := range don.CapabilityConfigs { + if ccfg.Config == nil { + ccfg.Config = defaultCapConfig(t, ccfg.Capability) + } + var exists bool + ccfg.CapabilityId, exists = cc.Get(ccfg.Capability) + require.True(t, exists, "capability not found in cache %v", ccfg.Capability) + capConfigs = append(capConfigs, ccfg.CapabilitiesRegistryCapabilityConfiguration) + if ccfg.Capability.CapabilityType == 2 { // ocr3 capabilities + acceptsWorkflows = true + } + } + // add the don + isPublic := true + f := len(don.P2PIDs)/3 + 1 + tx, err := registry.AddDON(chain.DeployerKey, internal.PeerIDsToBytes(don.P2PIDs), capConfigs, isPublic, acceptsWorkflows, uint8(f)) + if err != nil { + err2 := kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) + require.Fail(t, fmt.Sprintf("failed to call AddDON: %s: %s", err, err2)) + } + _, err = chain.Confirm(tx) + require.NoError(t, err) + } +} + +func defaultCapConfig(t *testing.T, cap kcr.CapabilitiesRegistryCapability) []byte { + empty := &capabilitiespb.CapabilityConfig{ + DefaultConfig: values.Proto(values.EmptyMap()).GetMapValue(), + } + emptyb, err := proto.Marshal(empty) + require.NoError(t, err) + return emptyb +} + // CapabilityCache tracks registered capabilities by name type CapabilityCache struct { t *testing.T @@ -120,6 +169,10 @@ func NewCapabiltyCache(t *testing.T) *CapabilityCache { nameToId: make(map[string][32]byte), } } +func (cc *CapabilityCache) Get(cap kcr.CapabilitiesRegistryCapability) ([32]byte, bool) { + id, exists := cc.nameToId[kslib.CapabilityID(cap)] + return id, exists +} // AddCapabilities adds the capabilities to the registry and returns the registered capabilities // if the capability is already registered, it will not be re-registered diff --git a/deployment/keystone/changeset/internal/update_don.go b/deployment/keystone/changeset/internal/update_don.go new file mode 100644 index 00000000000..4e09560135a --- /dev/null +++ b/deployment/keystone/changeset/internal/update_don.go @@ -0,0 +1,146 @@ +package internal + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "sort" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "google.golang.org/protobuf/proto" + + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" +) + +// CapabilityConfig is a struct that holds a capability and its configuration +type CapabilityConfig struct { + Capability kcr.CapabilitiesRegistryCapability + kcr.CapabilitiesRegistryCapabilityConfiguration // embed bc otherwise with ~ config.config +} + +type UpdateDonRequest struct { + Registry *kcr.CapabilitiesRegistry + Chain deployment.Chain + + Name string + P2PIDs []p2pkey.PeerID // this is the unique identifier for the done + // CapabilityId field is ignored. it is determined dynamically by the registry + // If the Config is nil, a default config is used + CapabilityConfigs []CapabilityConfig +} + +func (r *UpdateDonRequest) Validate() error { + if r.Registry == nil { + return fmt.Errorf("registry is required") + } + if r.Name == "" { + return fmt.Errorf("name is required") + } + if len(r.P2PIDs) == 0 { + return fmt.Errorf("p2pIDs is required") + } + return nil +} + +type UpdateDonResponse struct { + DonInfo kcr.CapabilitiesRegistryDONInfo +} + +func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("failed to validate request: %w", err) + } + + getDonsResp, err := req.Registry.GetDONs(&bind.CallOpts{}) + if err != nil { + return nil, fmt.Errorf("failed to get Dons: %w", err) + } + wantedDonID := SortedHash(PeerIDsToBytes(req.P2PIDs)) + var don kcr.CapabilitiesRegistryDONInfo + found := false + for i, di := range getDonsResp { + gotID := SortedHash(di.NodeP2PIds) + if gotID == wantedDonID { + don = getDonsResp[i] + found = true + break + } + } + if !found { + type debugDonInfo struct { + OnchainID uint32 + P2PIDsHash string + } + debugIds := make([]debugDonInfo, len(getDonsResp)) + for i, di := range getDonsResp { + debugIds[i] = debugDonInfo{ + OnchainID: di.Id, + P2PIDsHash: SortedHash(di.NodeP2PIds), + } + } + return nil, fmt.Errorf("don not found by p2pIDs %s in %v", wantedDonID, debugIds) + } + cfgs, err := computeConfigs(req.Registry, req.CapabilityConfigs, don) + if err != nil { + return nil, fmt.Errorf("failed to compute configs: %w", err) + } + tx, err := req.Registry.UpdateDON(req.Chain.DeployerKey, don.Id, don.NodeP2PIds, cfgs, don.IsPublic, don.F) + if err != nil { + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) + return nil, fmt.Errorf("failed to call UpdateDON: %w", err) + } + + _, err = req.Chain.Confirm(tx) + if err != nil { + return nil, fmt.Errorf("failed to confirm UpdateDON transaction %s: %w", tx.Hash().String(), err) + } + out := don + out.CapabilityConfigurations = cfgs + return &UpdateDonResponse{DonInfo: out}, nil +} + +func PeerIDsToBytes(p2pIDs []p2pkey.PeerID) [][32]byte { + out := make([][32]byte, len(p2pIDs)) + for i, p2pID := range p2pIDs { + out[i] = p2pID + } + return out +} + +func computeConfigs(registry *kcr.CapabilitiesRegistry, caps []CapabilityConfig, donInfo kcr.CapabilitiesRegistryDONInfo) ([]kcr.CapabilitiesRegistryCapabilityConfiguration, error) { + out := make([]kcr.CapabilitiesRegistryCapabilityConfiguration, len(caps)) + for i, cap := range caps { + out[i] = cap.CapabilitiesRegistryCapabilityConfiguration + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, cap.Capability.LabelledName, cap.Capability.Version) + if err != nil { + return nil, fmt.Errorf("failed to get capability id: %w", err) + } + out[i].CapabilityId = id + if out[i].Config == nil { + c := kslib.DefaultCapConfig(cap.Capability.CapabilityType, int(donInfo.F)) + cb, err := proto.Marshal(c) + if err != nil { + return nil, fmt.Errorf("failed to marshal capability config for %v: %w", c, err) + } + out[i].Config = cb + } + } + return out, nil +} + +func SortedHash(p2pids [][32]byte) string { + sha256Hash := sha256.New() + sort.Slice(p2pids, func(i, j int) bool { + return bytes.Compare(p2pids[i][:], p2pids[j][:]) < 0 + }) + for _, id := range p2pids { + sha256Hash.Write(id[:]) + } + return hex.EncodeToString(sha256Hash.Sum(nil)) +} diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go new file mode 100644 index 00000000000..e4ba9c3afda --- /dev/null +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -0,0 +1,172 @@ +package internal_test + +import ( + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + kscs "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/test-go/testify/require" +) + +func TestUpdateDon(t *testing.T) { + type args struct { + lggr logger.Logger + req *internal.UpdateDonRequest + setupDonCfg setupUpdateDonTestConfig + } + tests := []struct { + name string + args args + want *internal.UpdateDonResponse + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setupUpdateDonTest(t, tt.args.lggr, tt.args.setupDonCfg) + got, err := internal.UpdateDon(tt.args.lggr, tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("UpdateDon() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("UpdateDon() = %v, want %v", got, tt.want) + } + }) + } +} + +type setupUpdateDonTestConfig struct { + dons []kslib.DonCapabilities +} + +type setupUpdateDonTestResult struct { + registry *kcr.CapabilitiesRegistry + chain deployment.Chain +} + +func setupUpdateDonTest(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTestConfig) { + t.Helper() + req := newSetupTestRegistryRequest(t, cfg.dons) + kstest.SetupTestRegistry(t, lggr, req) + return +} + +func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonCapabilities) *kstest.SetupTestRegistryRequest { + t.Helper() + allNops := make(map[string]*models.NodeOperator) + for _, don := range dons { + for _, nop := range don.Nops { + nop := nop + n, exists := allNops[nop.ID] + if exists { + nop.Nodes = append(n.Nodes, nop.Nodes...) + } + allNops[nop.ID] = nop + } + } + var nops []*models.NodeOperator + for _, nop := range allNops { + nops = append(nops, nop) + } + nopsToNodes := makeNopToNodes(t, nops) + testDons := makeTestDon(t, dons) + p2pToCapabilities := makeP2PToCapabilities(t, dons) + req := &kstest.SetupTestRegistryRequest{ + NopToNodes: nopsToNodes, + Dons: testDons, + P2pToCapabilities: p2pToCapabilities, + } + return req +} + +func makeNopToNodes(t *testing.T, cloNops []*models.NodeOperator) map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc { + nopToNodes := make(map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc) + + for _, nop := range cloNops { + // all chain configs are the same wrt admin address & node keys + // so we can just use the first one + chainConfig := nop.Nodes[0].ChainConfigs[0] + crnop := kcr.CapabilitiesRegistryNodeOperator{ + Name: nop.Name, + Admin: common.HexToAddress(chainConfig.AdminAddress), + } + var nodes []*internal.P2PSignerEnc + for _, node := range nop.Nodes { + require.NotNil(t, node.PublicKey, "public key is nil %s", node.ID) + p, err := kscs.NewP2PSignerEncFromCLO(chainConfig, *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + nodes = append(nodes, p) + } + nopToNodes[crnop] = nodes + } + return nopToNodes +} + +func makeP2PToCapabilities(t *testing.T, dons []kslib.DonCapabilities) map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability { + p2pToCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for _, don := range dons { + for _, nop := range don.Nops { + for _, node := range nop.Nodes { + for _, cap := range don.Capabilities { + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + p2pToCapabilities[p.P2PKey] = append(p2pToCapabilities[p.P2PKey], cap) + } + } + } + } + return p2pToCapabilities +} + +func makeTestDon(t *testing.T, dons []kslib.DonCapabilities) []kstest.Don { + out := make([]kstest.Don, len(dons)) + for i, don := range dons { + out[i] = testDon(t, don) + } + return out +} + +func testDon(t *testing.T, don kslib.DonCapabilities) kstest.Don { + var p2pids []p2pkey.PeerID + for _, nop := range don.Nops { + // all chain configs are the same wrt admin address & node keys + // so we can just use the first one + chainConfig := nop.Nodes[0].ChainConfigs[0] + for _, node := range nop.Nodes { + p, err := kscs.NewP2PSignerEncFromCLO(chainConfig, *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + p2pids = append(p2pids, p.P2PKey) + } + } + + var capabilityConfigs []internal.CapabilityConfig + for _, cap := range don.Capabilities { + capabilityConfigs = append(capabilityConfigs, internal.CapabilityConfig{ + Capability: cap, + }) + } + return kstest.Don{ + Name: don.Name, + P2PIDs: p2pids, + CapabilityConfigs: capabilityConfigs, + } +} + +func newP2PSignerEnc(signer [32]byte, p2pkey p2pkey.PeerID, encryptionPublicKey [32]byte) *internal.P2PSignerEnc { + return &internal.P2PSignerEnc{ + Signer: signer, + P2PKey: p2pkey, + EncryptionPublicKey: encryptionPublicKey, + } +} diff --git a/deployment/keystone/changeset/types.go b/deployment/keystone/changeset/types.go new file mode 100644 index 00000000000..16abad56c33 --- /dev/null +++ b/deployment/keystone/changeset/types.go @@ -0,0 +1,50 @@ +package changeset + +import ( + "encoding/hex" + "errors" + "fmt" + + v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "github.com/smartcontractkit/chainlink/deployment/environment/clo" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +func NewP2PSignerEncFromCLO(cc *models.NodeChainConfig, pubkey string) (*P2PSignerEnc, error) { + ccfg := clo.NewChainConfig(cc) + var pubkeyB [32]byte + if _, err := hex.Decode(pubkeyB[:], []byte(pubkey)); err != nil { + return nil, fmt.Errorf("failed to decode pubkey %s: %w", pubkey, err) + } + return newP2PSignerEncFromJD(ccfg, pubkeyB) +} + +func newP2PSignerEncFromJD(ccfg *v1.ChainConfig, pubkey [32]byte) (*P2PSignerEnc, error) { + if ccfg == nil { + return nil, errors.New("nil ocr2config") + } + ocfg := ccfg.Ocr2Config + p2p := p2pkey.PeerID{} + if err := p2p.UnmarshalString(ocfg.P2PKeyBundle.PeerId); err != nil { + return nil, fmt.Errorf("failed to unmarshal peer id %s: %w", ocfg.P2PKeyBundle.PeerId, err) + } + + signer := ocfg.OcrKeyBundle.OnchainSigningAddress + if len(signer) != 40 { + return nil, fmt.Errorf("invalid onchain signing address %s", ocfg.OcrKeyBundle.OnchainSigningAddress) + } + signerB, err := hex.DecodeString(signer) + if err != nil { + return nil, fmt.Errorf("failed to convert signer %s: %w", signer, err) + } + + var sigb [32]byte + copy(sigb[:], signerB) + + return &P2PSignerEnc{ + Signer: sigb, + P2PKey: p2p, + EncryptionPublicKey: pubkey, //[32]byte{3: 84, 2: 79, 1: 68, 0: 79}, // TODO. no current way to get this from the node itself (and therefore not in clo or jd) + }, nil +} diff --git a/deployment/keystone/changeset/update_don.go b/deployment/keystone/changeset/update_don.go new file mode 100644 index 00000000000..e51f353cf81 --- /dev/null +++ b/deployment/keystone/changeset/update_don.go @@ -0,0 +1,29 @@ +package changeset + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +var _ deployment.ChangeSet = UpdateDonX + +// CapabilityConfig is a struct that holds a capability and its configuration +type CapabilityConfig = internal.CapabilityConfig + +type UpdateDonRequest = internal.UpdateDonRequest + +type UpdateDonResponse struct { + DonInfo kcr.CapabilitiesRegistryDONInfo +} + +func UpdateDonX(env deployment.Environment, cfg any) (deployment.ChangesetOutput, error) { + req := cfg.(*UpdateDonRequest) + _, err := internal.UpdateDon(env.Logger, req) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to update don: %w", err) + } + return deployment.ChangesetOutput{}, nil +} diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index d6a1b2bf157..f565586fd83 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -456,7 +456,7 @@ func RegisterNOPS(ctx context.Context, req RegisterNOPSRequest) (*RegisterNOPSRe return resp, nil } -func defaultCapConfig(capType uint8, nNodes int) *capabilitiespb.CapabilityConfig { +func DefaultCapConfig(capType uint8, nNodes int) *capabilitiespb.CapabilityConfig { switch capType { // TODO: use the enum defined in ?? case uint8(0): // trigger @@ -706,7 +706,7 @@ func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsRes wfSupported = true } // TODO: accept configuration from external source for each (don,capability) - capCfg := defaultCapConfig(cap.CapabilityType, len(p2pIds)) + capCfg := DefaultCapConfig(cap.CapabilityType, len(p2pIds)) cfgb, err := proto.Marshal(capCfg) if err != nil { return nil, fmt.Errorf("failed to marshal capability config for %v: %w", cap, err) From 74fe3b70f0b2a1eaae58b29c29af8a0c77e6a923 Mon Sep 17 00:00:00 2001 From: krehermann <16602512+krehermann@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:28:35 -0700 Subject: [PATCH 2/5] fix setup --- .../keystone/changeset/internal/test/utils.go | 15 +- .../keystone/changeset/internal/update_don.go | 27 ++- .../changeset/internal/update_don_test.go | 188 ++++++++++++++---- 3 files changed, 183 insertions(+), 47 deletions(-) diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index a11f8ecde0b..505cf39acfa 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -118,19 +118,24 @@ func addNodes(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry require.NoError(t, err) } -func addDons(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry *kcr.CapabilitiesRegistry, cc *CapabilityCache, dons []Don) { +func addDons(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry *kcr.CapabilitiesRegistry, capCache *CapabilityCache, dons []Don) { for _, don := range dons { acceptsWorkflows := false // lookup the capabilities var capConfigs []kcr.CapabilitiesRegistryCapabilityConfiguration for _, ccfg := range don.CapabilityConfigs { - if ccfg.Config == nil { - ccfg.Config = defaultCapConfig(t, ccfg.Capability) + var cc = kcr.CapabilitiesRegistryCapabilityConfiguration{ + CapabilityId: [32]byte{}, + Config: ccfg.Config, + } + if cc.Config == nil { + cc.Config = defaultCapConfig(t, ccfg.Capability) } var exists bool - ccfg.CapabilityId, exists = cc.Get(ccfg.Capability) + //var cc kcr.CapabilitiesRegistryCapabilityConfiguration{} + cc.CapabilityId, exists = capCache.Get(ccfg.Capability) require.True(t, exists, "capability not found in cache %v", ccfg.Capability) - capConfigs = append(capConfigs, ccfg.CapabilitiesRegistryCapabilityConfiguration) + capConfigs = append(capConfigs, cc) if ccfg.Capability.CapabilityType == 2 { // ocr3 capabilities acceptsWorkflows = true } diff --git a/deployment/keystone/changeset/internal/update_don.go b/deployment/keystone/changeset/internal/update_don.go index 4e09560135a..0495a5c8052 100644 --- a/deployment/keystone/changeset/internal/update_don.go +++ b/deployment/keystone/changeset/internal/update_don.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/sha256" "encoding/hex" + "encoding/json" "fmt" "sort" @@ -20,15 +21,14 @@ import ( // CapabilityConfig is a struct that holds a capability and its configuration type CapabilityConfig struct { - Capability kcr.CapabilitiesRegistryCapability - kcr.CapabilitiesRegistryCapabilityConfiguration // embed bc otherwise with ~ config.config + Capability kcr.CapabilitiesRegistryCapability + Config []byte // this is the marshalled proto config. if nil, a default config is used } type UpdateDonRequest struct { Registry *kcr.CapabilitiesRegistry Chain deployment.Chain - Name string P2PIDs []p2pkey.PeerID // this is the unique identifier for the done // CapabilityId field is ignored. it is determined dynamically by the registry // If the Config is nil, a default config is used @@ -39,9 +39,6 @@ func (r *UpdateDonRequest) Validate() error { if r.Registry == nil { return fmt.Errorf("registry is required") } - if r.Name == "" { - return fmt.Errorf("name is required") - } if len(r.P2PIDs) == 0 { return fmt.Errorf("p2pIDs is required") } @@ -76,14 +73,22 @@ func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, e type debugDonInfo struct { OnchainID uint32 P2PIDsHash string + Want []p2pkey.PeerID + Got []p2pkey.PeerID } debugIds := make([]debugDonInfo, len(getDonsResp)) for i, di := range getDonsResp { debugIds[i] = debugDonInfo{ OnchainID: di.Id, P2PIDsHash: SortedHash(di.NodeP2PIds), + Want: req.P2PIDs, + Got: BytesToPeerIDs(di.NodeP2PIds), } } + b, err2 := json.Marshal(debugIds) + if err2 == nil { + return nil, fmt.Errorf("don not found by p2pIDs %s in %s", wantedDonID, b) + } return nil, fmt.Errorf("don not found by p2pIDs %s in %v", wantedDonID, debugIds) } cfgs, err := computeConfigs(req.Registry, req.CapabilityConfigs, don) @@ -113,10 +118,18 @@ func PeerIDsToBytes(p2pIDs []p2pkey.PeerID) [][32]byte { return out } +func BytesToPeerIDs(p2pIDs [][32]byte) []p2pkey.PeerID { + out := make([]p2pkey.PeerID, len(p2pIDs)) + for i, p2pID := range p2pIDs { + out[i] = p2pID + } + return out +} + func computeConfigs(registry *kcr.CapabilitiesRegistry, caps []CapabilityConfig, donInfo kcr.CapabilitiesRegistryDONInfo) ([]kcr.CapabilitiesRegistryCapabilityConfiguration, error) { out := make([]kcr.CapabilitiesRegistryCapabilityConfiguration, len(caps)) for i, cap := range caps { - out[i] = cap.CapabilitiesRegistryCapabilityConfiguration + out[i] = kcr.CapabilitiesRegistryCapabilityConfiguration{} id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, cap.Capability.LabelledName, cap.Capability.Version) if err != nil { return nil, fmt.Errorf("failed to get capability id: %w", err) diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go index e4ba9c3afda..087f55f3b09 100644 --- a/deployment/keystone/changeset/internal/update_don_test.go +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -1,10 +1,12 @@ package internal_test import ( - "reflect" + "math/big" + "strconv" "testing" "github.com/ethereum/go-ethereum/common" + chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" @@ -15,34 +17,152 @@ import ( kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/test-go/testify/require" + "gotest.tools/v3/assert" ) func TestUpdateDon(t *testing.T) { - type args struct { - lggr logger.Logger - req *internal.UpdateDonRequest - setupDonCfg setupUpdateDonTestConfig - } - tests := []struct { - name string - args args - want *internal.UpdateDonResponse - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - setupUpdateDonTest(t, tt.args.lggr, tt.args.setupDonCfg) - got, err := internal.UpdateDon(tt.args.lggr, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("UpdateDon() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("UpdateDon() = %v, want %v", got, tt.want) - } + var ( + registryChain = chainsel.TEST_90000001 + //nop_1 = "test nop 1" + //nop_2 = "test nop 2" + // nodes + p2p_1 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(100)) + pubKey_1 = "11114981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e1098e7" // valid csa key + admin_1 = common.HexToAddress("0x1111567890123456789012345678901234567890") // valid eth address + signing_1 = "11117293a4cc2621b61193135a95928735e4795f" // valid eth address + node_1 = newNode(t, minimalNodeCfg{ + id: "test node 1", + pubKey: pubKey_1, + registryChain: registryChain, + p2p: p2p_1, + signingAddr: signing_1, + admin: admin_1, + }) + + p2p_2 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(200)) + pubKey_2 = "22224981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e109000" // valid csa key + admin_2 = common.HexToAddress("0x2222567890123456789012345678901234567891") // valid eth address + signing_2 = "22227293a4cc2621b61193135a95928735e4ffff" // valid eth address + node_2 = newNode(t, minimalNodeCfg{ + id: "test node 2", + pubKey: pubKey_2, + registryChain: registryChain, + p2p: p2p_2, + signingAddr: signing_2, + admin: admin_2, + }) + + p2p_3 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(300)) + pubKey_3 = "33334981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e109111" // valid csa key + admin_3 = common.HexToAddress("0x3333567890123456789012345678901234567892") // valid eth address + signing_3 = "33337293a4cc2621b61193135a959287aaaaffff" // valid eth address + node_3 = newNode(t, minimalNodeCfg{ + id: "test node 3", + pubKey: pubKey_3, + registryChain: registryChain, + p2p: p2p_3, + signingAddr: signing_3, + admin: admin_3, + }) + + p2p_4 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(400)) + pubKey_4 = "44444981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e109222" // valid csa key + admin_4 = common.HexToAddress("0x4444567890123456789012345678901234567893") // valid eth address + signing_4 = "44447293a4cc2621b61193135a959287aaaaffff" // valid eth address + node_4 = newNode(t, minimalNodeCfg{ + id: "test node 4", + pubKey: pubKey_4, + registryChain: registryChain, + p2p: p2p_4, + signingAddr: signing_4, + admin: admin_4, }) + // capabilities + cap_A = kcr.CapabilitiesRegistryCapability{ + LabelledName: "test", + Version: "1.0.0", + CapabilityType: 0, + } + /* + cap_B = kcr.CapabilitiesRegistryCapability{ + LabelledName: "cap b", + Version: "1.0.0", + CapabilityType: 1, + } + + */ + ) + // p2p_3 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(300)) + + lggr := logger.Test(t) + + t.Run("empty", func(t *testing.T) { + cfg := setupUpdateDonTestConfig{ + dons: []kslib.DonCapabilities{ + { + Name: "don 1", + Nops: []*models.NodeOperator{ + { + Name: "nop 1", + Nodes: []*models.Node{node_1, node_2, node_3, node_4}, + }, + }, + Capabilities: []kcr.CapabilitiesRegistryCapability{cap_A}, + }, + }, + } + + testCfg := setupUpdateDonTest(t, lggr, cfg) + req := &internal.UpdateDonRequest{ + Registry: testCfg.Registry, + Chain: testCfg.Chain, + P2PIDs: []p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}, + CapabilityConfigs: []internal.CapabilityConfig{ + {}, + }, + } + want := &internal.UpdateDonResponse{} + + got, err := internal.UpdateDon(lggr, req) + require.NoError(t, err) + assert.Equal(t, want, got) + }) +} + +type minimalNodeCfg struct { + id string + pubKey string + registryChain chainsel.Chain + p2p p2pkey.KeyV2 + signingAddr string + admin common.Address +} + +func newNode(t *testing.T, cfg minimalNodeCfg) *models.Node { + t.Helper() + + return &models.Node{ + ID: cfg.id, + PublicKey: &cfg.pubKey, + ChainConfigs: []*models.NodeChainConfig{ + { + ID: "test chain", + Network: &models.Network{ + ID: "test network 1", + ChainID: strconv.FormatUint(cfg.registryChain.EvmChainID, 10), + ChainType: models.ChainTypeEvm, + }, + AdminAddress: cfg.admin.String(), + Ocr2Config: &models.NodeOCR2Config{ + P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ + PeerID: cfg.p2p.PeerID().String(), + }, + OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ + OnchainSigningAddress: cfg.signingAddr, + }, + }, + }, + }, } } @@ -55,11 +175,10 @@ type setupUpdateDonTestResult struct { chain deployment.Chain } -func setupUpdateDonTest(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTestConfig) { +func setupUpdateDonTest(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTestConfig) *kstest.SetupTestRegistryResponse { t.Helper() req := newSetupTestRegistryRequest(t, cfg.dons) - kstest.SetupTestRegistry(t, lggr, req) - return + return kstest.SetupTestRegistry(t, lggr, req) } func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonCapabilities) *kstest.SetupTestRegistryRequest { @@ -96,15 +215,15 @@ func makeNopToNodes(t *testing.T, cloNops []*models.NodeOperator) map[kcr.Capabi for _, nop := range cloNops { // all chain configs are the same wrt admin address & node keys // so we can just use the first one - chainConfig := nop.Nodes[0].ChainConfigs[0] crnop := kcr.CapabilitiesRegistryNodeOperator{ Name: nop.Name, - Admin: common.HexToAddress(chainConfig.AdminAddress), + Admin: common.HexToAddress(nop.Nodes[0].ChainConfigs[0].AdminAddress), } var nodes []*internal.P2PSignerEnc for _, node := range nop.Nodes { require.NotNil(t, node.PublicKey, "public key is nil %s", node.ID) - p, err := kscs.NewP2PSignerEncFromCLO(chainConfig, *node.PublicKey) + // all chain configs are the same wrt admin address & node keys + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) nodes = append(nodes, p) } @@ -140,11 +259,10 @@ func makeTestDon(t *testing.T, dons []kslib.DonCapabilities) []kstest.Don { func testDon(t *testing.T, don kslib.DonCapabilities) kstest.Don { var p2pids []p2pkey.PeerID for _, nop := range don.Nops { - // all chain configs are the same wrt admin address & node keys - // so we can just use the first one - chainConfig := nop.Nodes[0].ChainConfigs[0] for _, node := range nop.Nodes { - p, err := kscs.NewP2PSignerEncFromCLO(chainConfig, *node.PublicKey) + // all chain configs are the same wrt admin address & node keys + // so we can just use the first one + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) p2pids = append(p2pids, p.P2PKey) } From 9499c3dbef49c9207d94245247092cacb640b4d2 Mon Sep 17 00:00:00 2001 From: krehermann <16602512+krehermann@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:46:49 -0700 Subject: [PATCH 3/5] refactor test suite setup --- .../changeset/append_node_capbilities.go | 1 - .../internal/append_node_capabilities.go | 7 +- .../internal/append_node_capabilities_test.go | 2 +- .../keystone/changeset/internal/test/utils.go | 60 +++++++-- .../internal/update_node_capabilities.go | 1 - .../changeset/internal/update_nodes.go | 92 +++++++------- .../changeset/internal/update_nodes_test.go | 116 +++++++++--------- 7 files changed, 156 insertions(+), 123 deletions(-) diff --git a/deployment/keystone/changeset/append_node_capbilities.go b/deployment/keystone/changeset/append_node_capbilities.go index 20988825110..ca88c0e999d 100644 --- a/deployment/keystone/changeset/append_node_capbilities.go +++ b/deployment/keystone/changeset/append_node_capbilities.go @@ -95,6 +95,5 @@ func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*in Chain: registryChain, Registry: registry, P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, }, nil } diff --git a/deployment/keystone/changeset/internal/append_node_capabilities.go b/deployment/keystone/changeset/internal/append_node_capabilities.go index f917039e8e0..d49aba9859c 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities.go @@ -15,16 +15,13 @@ type AppendNodeCapabilitiesRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc + // NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *AppendNodeCapabilitiesRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } if req.Registry == nil { return fmt.Errorf("registry is nil") } @@ -59,7 +56,7 @@ func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesR Chain: req.Chain, Registry: req.Registry, P2pToCapabilities: capsByPeer, - NopToNodes: req.NopToNodes, + // NopToNodes: req.NopToNodes, } resp, err := UpdateNodes(lggr, updateNodesReq) if err != nil { diff --git a/deployment/keystone/changeset/internal/append_node_capabilities_test.go b/deployment/keystone/changeset/internal/append_node_capabilities_test.go index 48a9af3e38d..d36b6f97e9b 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities_test.go @@ -84,7 +84,7 @@ func TestAppendNodeCapabilities(t *testing.T) { }, }, }, - NopToNodes: nopToNodes, + // NopToNodes: nopToNodes, }, }, want: deployment.ChangesetOutput{}, diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index 505cf39acfa..ffe9bd1d6b4 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -18,6 +18,7 @@ import ( kslib "github.com/smartcontractkit/chainlink/deployment/keystone" internal "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) @@ -69,18 +70,47 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR // add the nodes with the phony capabilities. cannot register a node without a capability and capability must exist // to do this make an initial phony request and extract the node params - initialp2pToCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + initialp2pToCapabilities := make(map[p2pkey.PeerID][][32]byte) + var err error for p2pID := range req.P2pToCapabilities { - initialp2pToCapabilities[p2pID] = vanillaCapabilities(registeredCapabilities) + initialp2pToCapabilities[p2pID], err = capabilityIds(registry, registeredCapabilities) + require.NoError(t, err) } - phonyRequest := &internal.UpdateNodesRequest{ - Chain: chain, - Registry: registry, - P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, + // register the nodes + var np []kcr.CapabilitiesRegistryNodeParams + for i, nop := range nops { + if _, exists := req.NopToNodes[nop]; !exists { + require.Fail(t, "missing nopToNodes for %s", nop.Name) + } + for _, p2pSignerEnc := range req.NopToNodes[nop] { + np = append(np, kcr.CapabilitiesRegistryNodeParams{ + Signer: p2pSignerEnc.Signer, + P2pId: p2pSignerEnc.P2PKey, + EncryptionPublicKey: p2pSignerEnc.EncryptionPublicKey, + HashedCapabilityIds: initialp2pToCapabilities[p2pSignerEnc.P2PKey], + NodeOperatorId: uint32(i + 1), // 1-indexed + }) + } } - nodeParams, err := phonyRequest.NodeParams() - require.NoError(t, err) + /* + tx, err := registry.AddNodes(chain.DeployerKey, np) + if err != nil { + err2 := kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) + require.Fail(t, fmt.Sprintf("failed to call AddNodes: %s: %s", err, err2)) + } + _, err = chain.Confirm(tx) + require.NoError(t, err) + */ + /* phonyRequest := &internal.UpdateNodesRequest{ + Chain: chain, + Registry: registry, + P2pToCapabilities: req.P2pToCapabilities, + // NopToNodes: req.NopToNodes, + } + nodeParams, err := phonyRequest.NodeParams() + require.NoError(t, err) + */ + nodeParams := np addNodes(t, lggr, chain, registry, nodeParams) addDons(t, lggr, chain, registry, capCache, req.Dons) @@ -240,10 +270,14 @@ func testChain(t *testing.T) deployment.Chain { return chain } -func vanillaCapabilities(rcs []kslib.RegisteredCapability) []kcr.CapabilitiesRegistryCapability { - out := make([]kcr.CapabilitiesRegistryCapability, len(rcs)) +func capabilityIds(registry *capabilities_registry.CapabilitiesRegistry, rcs []kslib.RegisteredCapability) ([][32]byte, error) { + out := make([][32]byte, len(rcs)) for i := range rcs { - out[i] = rcs[i].CapabilitiesRegistryCapability + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, rcs[i].LabelledName, rcs[i].Version) + if err != nil { + return nil, fmt.Errorf("failed to get capability id: %w", err) + } + out[i] = id } - return out + return out, nil } diff --git a/deployment/keystone/changeset/internal/update_node_capabilities.go b/deployment/keystone/changeset/internal/update_node_capabilities.go index 3d3e1e80607..3c35727952d 100644 --- a/deployment/keystone/changeset/internal/update_node_capabilities.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities.go @@ -50,7 +50,6 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI Chain: req.Chain, Registry: req.Registry, P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, } resp, err := UpdateNodes(lggr, updateNodesReq) if err != nil { diff --git a/deployment/keystone/changeset/internal/update_nodes.go b/deployment/keystone/changeset/internal/update_nodes.go index 48face7086b..c09d3206057 100644 --- a/deployment/keystone/changeset/internal/update_nodes.go +++ b/deployment/keystone/changeset/internal/update_nodes.go @@ -19,11 +19,11 @@ type UpdateNodesRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc + //NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *UpdateNodesRequest) NodeParams() ([]kcr.CapabilitiesRegistryNodeParams, error) { - return makeNodeParams(req.Registry, req.NopToNodes, req.P2pToCapabilities) + return makeNodeParams(req.Registry, req.P2pToCapabilities) } // P2PSignerEnc represent the key fields in kcr.CapabilitiesRegistryNodeParams @@ -38,9 +38,18 @@ func (req *UpdateNodesRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return errors.New("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return errors.New("nopToNodes is empty") + // no duplicate capabilities + for peer, caps := range req.P2pToCapabilities { + seen := make(map[string]struct{}) + for _, cap := range caps { + id := kslib.CapabilityID(cap) + if _, exists := seen[id]; exists { + return fmt.Errorf("duplicate capability %s for %s", id, peer) + } + seen[id] = struct{}{} + } } + if req.Registry == nil { return errors.New("registry is nil") } @@ -61,6 +70,7 @@ func UpdateNodes(lggr logger.Logger, req *UpdateNodesRequest) (*UpdateNodesRespo params, err := req.NodeParams() if err != nil { + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to make node params: %w", err) } tx, err := req.Registry.UpdateNodes(req.Chain.DeployerKey, params) @@ -125,58 +135,38 @@ func AppendCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, } func makeNodeParams(registry *kcr.CapabilitiesRegistry, - nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc, p2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) ([]kcr.CapabilitiesRegistryNodeParams, error) { - out := make([]kcr.CapabilitiesRegistryNodeParams, 0) - // get all the node operators from chain - registeredNops, err := registry.GetNodeOperators(&bind.CallOpts{}) - if err != nil { - return nil, fmt.Errorf("failed to get node operators: %w", err) + var out []kcr.CapabilitiesRegistryNodeParams + var p2pIds []p2pkey.PeerID + for p2pID := range p2pToCapabilities { + p2pIds = append(p2pIds, p2pID) } - // make a cache of capability from chain - var allCaps []kcr.CapabilitiesRegistryCapability - for _, caps := range p2pToCapabilities { - allCaps = append(allCaps, caps...) - } - capMap, err := fetchCapabilityIDs(registry, allCaps) + nodes, err := registry.GetNodesByP2PIds(&bind.CallOpts{}, PeerIDsToBytes(p2pIds)) if err != nil { - return nil, fmt.Errorf("failed to fetch capability ids: %w", err) + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) + return nil, fmt.Errorf("failed to get nodes by p2p ids: %w", err) } - - // flatten the onchain state to list of node params filtered by the input nops and nodes - for idx, rnop := range registeredNops { - // nop id is 1-indexed. no way to get value from chain. must infer from index - nopID := uint32(idx + 1) - nodes, ok := nopToNodes[rnop] + for _, node := range nodes { + caps, ok := p2pToCapabilities[node.P2pId] if !ok { - continue + return nil, fmt.Errorf("capabilities not found for node %s", node.P2pId) } - for _, node := range nodes { - caps, ok := p2pToCapabilities[node.P2PKey] - if !ok { - return nil, fmt.Errorf("capabilities not found for node %s", node.P2PKey) - } - hashedCaps := make([][32]byte, len(caps)) - for i, cap := range caps { - hashedCap, exists := capMap[kslib.CapabilityID(cap)] - if !exists { - return nil, fmt.Errorf("capability id not found for %s", kslib.CapabilityID(cap)) - } - hashedCaps[i] = hashedCap - } - out = append(out, kcr.CapabilitiesRegistryNodeParams{ - NodeOperatorId: nopID, - P2pId: node.P2PKey, - HashedCapabilityIds: hashedCaps, - EncryptionPublicKey: node.EncryptionPublicKey, - Signer: node.Signer, - }) + ids, err := capabilityIds(registry, caps) + if err != nil { + return nil, fmt.Errorf("failed to get capability ids: %w", err) } + out = append(out, kcr.CapabilitiesRegistryNodeParams{ + NodeOperatorId: node.NodeOperatorId, + P2pId: node.P2pId, + HashedCapabilityIds: ids, + EncryptionPublicKey: node.EncryptionPublicKey, + Signer: node.Signer, + }) } - return out, nil + } // fetchkslib.CapabilityIDs fetches the capability ids for the given capabilities @@ -195,3 +185,15 @@ func fetchCapabilityIDs(registry *kcr.CapabilitiesRegistry, caps []kcr.Capabilit } return out, nil } + +func capabilityIds(registry *kcr.CapabilitiesRegistry, caps []kcr.CapabilitiesRegistryCapability) ([][32]byte, error) { + out := make([][32]byte, len(caps)) + for i, cap := range caps { + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, cap.LabelledName, cap.Version) + if err != nil { + return nil, fmt.Errorf("failed to get capability id: %w", err) + } + out[i] = id + } + return out, nil +} diff --git a/deployment/keystone/changeset/internal/update_nodes_test.go b/deployment/keystone/changeset/internal/update_nodes_test.go index 3515ef13cbe..1627990f930 100644 --- a/deployment/keystone/changeset/internal/update_nodes_test.go +++ b/deployment/keystone/changeset/internal/update_nodes_test.go @@ -50,9 +50,9 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { req := &internal.UpdateNodesRequest{ P2pToCapabilities: tt.fields.p2pToCapabilities, - NopToNodes: tt.fields.nopToNodes, - Chain: tt.fields.chain, - Registry: tt.fields.registry, + // NopToNodes: tt.fields.nopToNodes, + Chain: tt.fields.chain, + Registry: tt.fields.registry, } if err := req.Validate(); (err != nil) != tt.wantErr { t.Errorf("internal.UpdateNodesRequest.validate() error = %v, wantErr %v", err, tt.wantErr) @@ -67,8 +67,9 @@ func TestUpdateNodes(t *testing.T) { lggr := logger.Test(t) type args struct { - lggr logger.Logger - req *internal.UpdateNodesRequest + lggr logger.Logger + req *internal.UpdateNodesRequest + nopsToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc } tests := []struct { name string @@ -90,18 +91,18 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nop1"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 1: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, - }, - }, Chain: chain, Registry: nil, // set in test to ensure no conflicts }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 1: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, + }, + }, + }, }, want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ @@ -116,6 +117,7 @@ func TestUpdateNodes(t *testing.T) { }, wantErr: false, }, + { name: "one node, two capabilities", args: args{ @@ -135,18 +137,18 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nop1"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 1: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, - }, - }, Chain: chain, Registry: nil, // set in test to ensure no conflicts }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 1: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, + }, + }, + }, }, want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ @@ -189,24 +191,24 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nopA"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 31: 1}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + Chain: chain, + Registry: nil, // set in test to ensure no conflicts + }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 31: 1}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, - testNop(t, "nopB"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_2"), - Signer: [32]byte{0: 2, 31: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + }, + testNop(t, "nopB"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_2"), + Signer: [32]byte{0: 2, 31: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts }, }, want: &internal.UpdateNodesResponse{ @@ -250,24 +252,24 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nopA"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 31: 1}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + Chain: chain, + Registry: nil, // set in test to ensure no conflicts + }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 31: 1}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, - testNop(t, "nopB"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_2"), - Signer: [32]byte{0: 2, 31: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + }, + testNop(t, "nopB"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_2"), + Signer: [32]byte{0: 2, 31: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts }, }, want: &internal.UpdateNodesResponse{ @@ -305,7 +307,7 @@ func TestUpdateNodes(t *testing.T) { } setupResp := kstest.SetupTestRegistry(t, tt.args.lggr, &kstest.SetupTestRegistryRequest{ P2pToCapabilities: initMap, - NopToNodes: tt.args.req.NopToNodes, + NopToNodes: tt.args.nopsToNodes, }) registry := setupResp.Registry tt.args.req.Registry = setupResp.Registry @@ -414,9 +416,9 @@ func TestUpdateNodes(t *testing.T) { var req = &internal.UpdateNodesRequest{ P2pToCapabilities: p2pToCapabilitiesUpdated, - NopToNodes: nopToNodes, - Chain: chain, - Registry: registry, + // NopToNodes: nopToNodes, + Chain: chain, + Registry: registry, } _, err = internal.UpdateNodes(lggr, req) require.NoError(t, err) From dc3974044e7c6f70c04e3dc707e9f86453cfccfd Mon Sep 17 00:00:00 2001 From: krehermann <16602512+krehermann@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:07:43 -0700 Subject: [PATCH 4/5] make update don work --- .../keystone/changeset/internal/test/utils.go | 18 +++++- .../keystone/changeset/internal/update_don.go | 23 ++++++++ .../changeset/internal/update_don_test.go | 58 +++++++++++++++---- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index ffe9bd1d6b4..11f5d9c2082 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -71,10 +71,8 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR // add the nodes with the phony capabilities. cannot register a node without a capability and capability must exist // to do this make an initial phony request and extract the node params initialp2pToCapabilities := make(map[p2pkey.PeerID][][32]byte) - var err error for p2pID := range req.P2pToCapabilities { - initialp2pToCapabilities[p2pID], err = capabilityIds(registry, registeredCapabilities) - require.NoError(t, err) + initialp2pToCapabilities[p2pID] = mustCapabilityIds(t, registry, registeredCapabilities) } // register the nodes var np []kcr.CapabilitiesRegistryNodeParams @@ -281,3 +279,17 @@ func capabilityIds(registry *capabilities_registry.CapabilitiesRegistry, rcs []k } return out, nil } + +func mustCapabilityIds(t *testing.T, registry *capabilities_registry.CapabilitiesRegistry, rcs []kslib.RegisteredCapability) [][32]byte { + t.Helper() + out, err := capabilityIds(registry, rcs) + require.NoError(t, err) + return out +} + +func MustCapabilityId(t *testing.T, registry *capabilities_registry.CapabilitiesRegistry, cap capabilities_registry.CapabilitiesRegistryCapability) [32]byte { + t.Helper() + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, cap.LabelledName, cap.Version) + require.NoError(t, err) + return id +} diff --git a/deployment/keystone/changeset/internal/update_don.go b/deployment/keystone/changeset/internal/update_don.go index 0495a5c8052..2ce419c9620 100644 --- a/deployment/keystone/changeset/internal/update_don.go +++ b/deployment/keystone/changeset/internal/update_don.go @@ -35,6 +35,23 @@ type UpdateDonRequest struct { CapabilityConfigs []CapabilityConfig } +func (r *UpdateDonRequest) appendNodeCapabilitiesRequest() *AppendNodeCapabilitiesRequest { + out := &AppendNodeCapabilitiesRequest{ + Chain: r.Chain, + Registry: r.Registry, + P2pToCapabilities: make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability), + } + for _, p2pid := range r.P2PIDs { + if _, exists := out.P2pToCapabilities[p2pid]; !exists { + out.P2pToCapabilities[p2pid] = make([]kcr.CapabilitiesRegistryCapability, 0) + } + for _, cc := range r.CapabilityConfigs { + out.P2pToCapabilities[p2pid] = append(out.P2pToCapabilities[p2pid], cc.Capability) + } + } + return out +} + func (r *UpdateDonRequest) Validate() error { if r.Registry == nil { return fmt.Errorf("registry is required") @@ -95,6 +112,12 @@ func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, e if err != nil { return nil, fmt.Errorf("failed to compute configs: %w", err) } + + _, err = AppendNodeCapabilitiesImpl(lggr, req.appendNodeCapabilitiesRequest()) + if err != nil { + return nil, fmt.Errorf("failed to append node capabilities: %w", err) + } + tx, err := req.Registry.UpdateDON(req.Chain.DeployerKey, don.Id, don.NodeP2PIds, cfgs, don.IsPublic, don.F) if err != nil { err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go index 087f55f3b09..beab2c50521 100644 --- a/deployment/keystone/changeset/internal/update_don_test.go +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -1,7 +1,9 @@ package internal_test import ( + "bytes" "math/big" + "sort" "strconv" "testing" @@ -16,8 +18,8 @@ import ( kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" - "github.com/test-go/testify/require" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestUpdateDon(t *testing.T) { @@ -83,14 +85,12 @@ func TestUpdateDon(t *testing.T) { Version: "1.0.0", CapabilityType: 0, } - /* - cap_B = kcr.CapabilitiesRegistryCapability{ - LabelledName: "cap b", - Version: "1.0.0", - CapabilityType: 1, - } - */ + cap_B = kcr.CapabilitiesRegistryCapability{ + LabelledName: "cap b", + Version: "1.0.0", + CapabilityType: 1, + } ) // p2p_3 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(300)) @@ -113,22 +113,56 @@ func TestUpdateDon(t *testing.T) { } testCfg := setupUpdateDonTest(t, lggr, cfg) + req := &internal.UpdateDonRequest{ Registry: testCfg.Registry, Chain: testCfg.Chain, P2PIDs: []p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}, CapabilityConfigs: []internal.CapabilityConfig{ - {}, + {Capability: cap_A}, {Capability: cap_B}, + }, + } + want := &internal.UpdateDonResponse{ + DonInfo: kcr.CapabilitiesRegistryDONInfo{ + Id: 1, + ConfigCount: 1, + NodeP2PIds: internal.PeerIDsToBytes([]p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}), + CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ + {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, cap_A)}, + {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, cap_B)}, + }, }, } - want := &internal.UpdateDonResponse{} got, err := internal.UpdateDon(lggr, req) require.NoError(t, err) - assert.Equal(t, want, got) + assert.Equal(t, want.DonInfo.Id, got.DonInfo.Id) + assert.Equal(t, want.DonInfo.ConfigCount, got.DonInfo.ConfigCount) + assert.Equal(t, sortedP2Pids(want.DonInfo.NodeP2PIds), sortedP2Pids(got.DonInfo.NodeP2PIds)) + assert.Equal(t, capIds(want.DonInfo.CapabilityConfigurations), capIds(got.DonInfo.CapabilityConfigurations)) + }) } +func sortedP2Pids(p2pids [][32]byte) [][32]byte { + // sha256Hash := sha256.New() + sort.Slice(p2pids, func(i, j int) bool { + return bytes.Compare(p2pids[i][:], p2pids[j][:]) < 0 + }) + return p2pids +} + +func capIds(ccs []kcr.CapabilitiesRegistryCapabilityConfiguration) [][32]byte { + out := make([][32]byte, len(ccs)) + for i, cc := range ccs { + out[i] = cc.CapabilityId + } + sort.Slice(out, func(i, j int) bool { + return bytes.Compare(out[i][:], out[j][:]) < 0 + }) + return out +} + type minimalNodeCfg struct { id string pubKey string From 2aac5149cf4392112872e8ff9a94b3df53cfa486 Mon Sep 17 00:00:00 2001 From: krehermann <16602512+krehermann@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:41:55 -0700 Subject: [PATCH 5/5] code cleanup --- .../internal/append_node_capabilities.go | 2 - .../internal/append_node_capabilities_test.go | 2 - .../keystone/changeset/internal/test/utils.go | 32 ++----- .../keystone/changeset/internal/update_don.go | 84 +++++++++++-------- .../changeset/internal/update_don_test.go | 3 - .../internal/update_node_capabilities.go | 60 ------------- .../internal/update_node_capabilities_test.go | 2 - .../changeset/internal/update_nodes.go | 10 ++- .../changeset/internal/update_nodes_test.go | 19 ++--- deployment/keystone/changeset/types.go | 2 +- deployment/keystone/changeset/update_don.go | 7 +- .../changeset/update_node_capabilities.go | 5 -- 12 files changed, 77 insertions(+), 151 deletions(-) diff --git a/deployment/keystone/changeset/internal/append_node_capabilities.go b/deployment/keystone/changeset/internal/append_node_capabilities.go index d49aba9859c..e74d3178ef2 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities.go @@ -15,7 +15,6 @@ type AppendNodeCapabilitiesRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - // NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *AppendNodeCapabilitiesRequest) Validate() error { @@ -56,7 +55,6 @@ func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesR Chain: req.Chain, Registry: req.Registry, P2pToCapabilities: capsByPeer, - // NopToNodes: req.NopToNodes, } resp, err := UpdateNodes(lggr, updateNodesReq) if err != nil { diff --git a/deployment/keystone/changeset/internal/append_node_capabilities_test.go b/deployment/keystone/changeset/internal/append_node_capabilities_test.go index d36b6f97e9b..d28dcd73230 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities_test.go @@ -84,7 +84,6 @@ func TestAppendNodeCapabilities(t *testing.T) { }, }, }, - // NopToNodes: nopToNodes, }, }, want: deployment.ChangesetOutput{}, @@ -93,7 +92,6 @@ func TestAppendNodeCapabilities(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // chagen the name and args to be mor egeneral setupResp := kstest.SetupTestRegistry(t, lggr, tt.args.initialState) tt.args.req.Registry = setupResp.Registry diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index 11f5d9c2082..f7ff6845254 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -68,48 +68,30 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR } require.Len(t, registeredCapabilities, len(expectedDeduped)) - // add the nodes with the phony capabilities. cannot register a node without a capability and capability must exist - // to do this make an initial phony request and extract the node params + // make the nodes and register node + var nodeParams []kcr.CapabilitiesRegistryNodeParams initialp2pToCapabilities := make(map[p2pkey.PeerID][][32]byte) for p2pID := range req.P2pToCapabilities { initialp2pToCapabilities[p2pID] = mustCapabilityIds(t, registry, registeredCapabilities) } - // register the nodes - var np []kcr.CapabilitiesRegistryNodeParams + // create node with initial capabilities assigned to nop for i, nop := range nops { if _, exists := req.NopToNodes[nop]; !exists { require.Fail(t, "missing nopToNodes for %s", nop.Name) } for _, p2pSignerEnc := range req.NopToNodes[nop] { - np = append(np, kcr.CapabilitiesRegistryNodeParams{ + nodeParams = append(nodeParams, kcr.CapabilitiesRegistryNodeParams{ Signer: p2pSignerEnc.Signer, P2pId: p2pSignerEnc.P2PKey, EncryptionPublicKey: p2pSignerEnc.EncryptionPublicKey, HashedCapabilityIds: initialp2pToCapabilities[p2pSignerEnc.P2PKey], - NodeOperatorId: uint32(i + 1), // 1-indexed + NodeOperatorId: uint32(i + 1), // nopid in contract is 1-indexed }) } } - /* - tx, err := registry.AddNodes(chain.DeployerKey, np) - if err != nil { - err2 := kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) - require.Fail(t, fmt.Sprintf("failed to call AddNodes: %s: %s", err, err2)) - } - _, err = chain.Confirm(tx) - require.NoError(t, err) - */ - /* phonyRequest := &internal.UpdateNodesRequest{ - Chain: chain, - Registry: registry, - P2pToCapabilities: req.P2pToCapabilities, - // NopToNodes: req.NopToNodes, - } - nodeParams, err := phonyRequest.NodeParams() - require.NoError(t, err) - */ - nodeParams := np addNodes(t, lggr, chain, registry, nodeParams) + + // add the Dons addDons(t, lggr, chain, registry, capCache, req.Dons) return &SetupTestRegistryResponse{ diff --git a/deployment/keystone/changeset/internal/update_don.go b/deployment/keystone/changeset/internal/update_don.go index 2ce419c9620..4883368dc4d 100644 --- a/deployment/keystone/changeset/internal/update_don.go +++ b/deployment/keystone/changeset/internal/update_don.go @@ -29,10 +29,8 @@ type UpdateDonRequest struct { Registry *kcr.CapabilitiesRegistry Chain deployment.Chain - P2PIDs []p2pkey.PeerID // this is the unique identifier for the done - // CapabilityId field is ignored. it is determined dynamically by the registry - // If the Config is nil, a default config is used - CapabilityConfigs []CapabilityConfig + P2PIDs []p2pkey.PeerID // this is the unique identifier for the don + CapabilityConfigs []CapabilityConfig // if Config subfield is nil, a default config is used } func (r *UpdateDonRequest) appendNodeCapabilitiesRequest() *AppendNodeCapabilitiesRequest { @@ -75,38 +73,10 @@ func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, e if err != nil { return nil, fmt.Errorf("failed to get Dons: %w", err) } - wantedDonID := SortedHash(PeerIDsToBytes(req.P2PIDs)) - var don kcr.CapabilitiesRegistryDONInfo - found := false - for i, di := range getDonsResp { - gotID := SortedHash(di.NodeP2PIds) - if gotID == wantedDonID { - don = getDonsResp[i] - found = true - break - } - } - if !found { - type debugDonInfo struct { - OnchainID uint32 - P2PIDsHash string - Want []p2pkey.PeerID - Got []p2pkey.PeerID - } - debugIds := make([]debugDonInfo, len(getDonsResp)) - for i, di := range getDonsResp { - debugIds[i] = debugDonInfo{ - OnchainID: di.Id, - P2PIDsHash: SortedHash(di.NodeP2PIds), - Want: req.P2PIDs, - Got: BytesToPeerIDs(di.NodeP2PIds), - } - } - b, err2 := json.Marshal(debugIds) - if err2 == nil { - return nil, fmt.Errorf("don not found by p2pIDs %s in %s", wantedDonID, b) - } - return nil, fmt.Errorf("don not found by p2pIDs %s in %v", wantedDonID, debugIds) + + don, err := lookupDonByPeerIDs(getDonsResp, req.P2PIDs) + if err != nil { + return nil, fmt.Errorf("failed to lookup don by p2pIDs: %w", err) } cfgs, err := computeConfigs(req.Registry, req.CapabilityConfigs, don) if err != nil { @@ -180,3 +150,45 @@ func SortedHash(p2pids [][32]byte) string { } return hex.EncodeToString(sha256Hash.Sum(nil)) } + +func lookupDonByPeerIDs(donResp []kcr.CapabilitiesRegistryDONInfo, wanted []p2pkey.PeerID) (kcr.CapabilitiesRegistryDONInfo, error) { + var don kcr.CapabilitiesRegistryDONInfo + wantedDonID := SortedHash(PeerIDsToBytes(wanted)) + found := false + for i, di := range donResp { + gotID := SortedHash(di.NodeP2PIds) + if gotID == wantedDonID { + don = donResp[i] + found = true + break + } + } + if !found { + return don, verboseDonNotFound(donResp, wanted) + } + return don, nil +} + +func verboseDonNotFound(donResp []kcr.CapabilitiesRegistryDONInfo, wanted []p2pkey.PeerID) error { + type debugDonInfo struct { + OnchainID uint32 + P2PIDsHash string + Want []p2pkey.PeerID + Got []p2pkey.PeerID + } + debugIds := make([]debugDonInfo, len(donResp)) + for i, di := range donResp { + debugIds[i] = debugDonInfo{ + OnchainID: di.Id, + P2PIDsHash: SortedHash(di.NodeP2PIds), + Want: wanted, + Got: BytesToPeerIDs(di.NodeP2PIds), + } + } + wantedID := SortedHash(PeerIDsToBytes(wanted)) + b, err2 := json.Marshal(debugIds) + if err2 == nil { + return fmt.Errorf("don not found by p2pIDs %s in %s", wantedID, b) + } + return fmt.Errorf("don not found by p2pIDs %s in %v", wantedID, debugIds) +} diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go index beab2c50521..baedda5e93d 100644 --- a/deployment/keystone/changeset/internal/update_don_test.go +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -25,8 +25,6 @@ import ( func TestUpdateDon(t *testing.T) { var ( registryChain = chainsel.TEST_90000001 - //nop_1 = "test nop 1" - //nop_2 = "test nop 2" // nodes p2p_1 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(100)) pubKey_1 = "11114981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e1098e7" // valid csa key @@ -92,7 +90,6 @@ func TestUpdateDon(t *testing.T) { CapabilityType: 1, } ) - // p2p_3 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(300)) lggr := logger.Test(t) diff --git a/deployment/keystone/changeset/internal/update_node_capabilities.go b/deployment/keystone/changeset/internal/update_node_capabilities.go index 3c35727952d..c7e2e902437 100644 --- a/deployment/keystone/changeset/internal/update_node_capabilities.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities.go @@ -15,16 +15,12 @@ type UpdateNodeCapabilitiesImplRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } if req.Registry == nil { return fmt.Errorf("registry is nil") } @@ -57,59 +53,3 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI } return resp, nil } - -/* -// AddCapabilities adds the capabilities to the registry -// it tries to add all capabilities in one go, if that fails, it falls back to adding them one by one -func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, chain deployment.Chain, capabilities []kcr.CapabilitiesRegistryCapability) error { - if len(capabilities) == 0 { - return nil - } - // dedup capabilities - var deduped []kcr.CapabilitiesRegistryCapability - seen := make(map[string]struct{}) - for _, cap := range capabilities { - if _, ok := seen[CapabilityID(cap)]; !ok { - seen[CapabilityID(cap)] = struct{}{} - deduped = append(deduped, cap) - } - } - - tx, err := registry.AddCapabilities(chain.DeployerKey, deduped) - if err != nil { - err = DecodeErr(kcr.CapabilitiesRegistryABI, err) - // no typed errors in the abi, so we have to do string matching - // try to add all capabilities in one go, if that fails, fall back to 1-by-1 - if !strings.Contains(err.Error(), "CapabilityAlreadyExists") { - return fmt.Errorf("failed to call AddCapabilities: %w", err) - } - lggr.Warnw("capabilities already exist, falling back to 1-by-1", "capabilities", deduped) - for _, cap := range deduped { - tx, err = registry.AddCapabilities(chain.DeployerKey, []kcr.CapabilitiesRegistryCapability{cap}) - if err != nil { - err = DecodeErr(kcr.CapabilitiesRegistryABI, err) - if strings.Contains(err.Error(), "CapabilityAlreadyExists") { - lggr.Warnw("capability already exists, skipping", "capability", cap) - continue - } - return fmt.Errorf("failed to call AddCapabilities for capability %v: %w", cap, err) - } - // 1-by-1 tx is pending and we need to wait for it to be mined - _, err = chain.Confirm(tx) - if err != nil { - return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) - } - lggr.Debugw("registered capability", "capability", cap) - - } - } else { - // the bulk add tx is pending and we need to wait for it to be mined - _, err = chain.Confirm(tx) - if err != nil { - return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) - } - lggr.Info("registered capabilities", "capabilities", deduped) - } - return nil -} -*/ diff --git a/deployment/keystone/changeset/internal/update_node_capabilities_test.go b/deployment/keystone/changeset/internal/update_node_capabilities_test.go index d90840f5d13..0346ff20dd6 100644 --- a/deployment/keystone/changeset/internal/update_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities_test.go @@ -8,7 +8,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" - //"github.com/smartcontractkit/chainlink/deployment/keystone/changeset" kslib "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" @@ -84,7 +83,6 @@ func TestUpdateNodeCapabilities(t *testing.T) { }, }, }, - NopToNodes: nopToNodes, }, }, want: deployment.ChangesetOutput{}, diff --git a/deployment/keystone/changeset/internal/update_nodes.go b/deployment/keystone/changeset/internal/update_nodes.go index c09d3206057..d263623cdc6 100644 --- a/deployment/keystone/changeset/internal/update_nodes.go +++ b/deployment/keystone/changeset/internal/update_nodes.go @@ -1,8 +1,10 @@ package internal import ( + "bytes" "errors" "fmt" + "sort" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -19,7 +21,6 @@ type UpdateNodesRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - //NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *UpdateNodesRequest) NodeParams() ([]kcr.CapabilitiesRegistryNodeParams, error) { @@ -165,6 +166,13 @@ func makeNodeParams(registry *kcr.CapabilitiesRegistry, Signer: node.Signer, }) } + sort.Slice(out, func(i, j int) bool { + if out[i].NodeOperatorId == out[j].NodeOperatorId { + return bytes.Compare(out[i].P2pId[:], out[j].P2pId[:]) < 0 + } + return out[i].NodeOperatorId < out[j].NodeOperatorId + }) + return out, nil } diff --git a/deployment/keystone/changeset/internal/update_nodes_test.go b/deployment/keystone/changeset/internal/update_nodes_test.go index 1627990f930..5488e5c761d 100644 --- a/deployment/keystone/changeset/internal/update_nodes_test.go +++ b/deployment/keystone/changeset/internal/update_nodes_test.go @@ -25,10 +25,9 @@ import ( func Test_UpdateNodesRequest_validate(t *testing.T) { type fields struct { p2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - //nopToNodes map[uint32][]*internal.P2PSigner - nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc - chain deployment.Chain - registry *kcr.CapabilitiesRegistry + nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc + chain deployment.Chain + registry *kcr.CapabilitiesRegistry } tests := []struct { name string @@ -50,9 +49,8 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { req := &internal.UpdateNodesRequest{ P2pToCapabilities: tt.fields.p2pToCapabilities, - // NopToNodes: tt.fields.nopToNodes, - Chain: tt.fields.chain, - Registry: tt.fields.registry, + Chain: tt.fields.chain, + Registry: tt.fields.registry, } if err := req.Validate(); (err != nil) != tt.wantErr { t.Errorf("internal.UpdateNodesRequest.validate() error = %v, wantErr %v", err, tt.wantErr) @@ -313,8 +311,6 @@ func TestUpdateNodes(t *testing.T) { tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain - //registry := kstest.SetupUpdateNodes(t, tt.args.lggr, tt.args.req) - //tt.args.req.Registry = registry // register the capabilities that the Update will use expectedUpdatedCaps := make(map[p2pkey.PeerID][]kslib.RegisteredCapability) capCache := kstest.NewCapabiltyCache(t) @@ -416,9 +412,8 @@ func TestUpdateNodes(t *testing.T) { var req = &internal.UpdateNodesRequest{ P2pToCapabilities: p2pToCapabilitiesUpdated, - // NopToNodes: nopToNodes, - Chain: chain, - Registry: registry, + Chain: chain, + Registry: registry, } _, err = internal.UpdateNodes(lggr, req) require.NoError(t, err) diff --git a/deployment/keystone/changeset/types.go b/deployment/keystone/changeset/types.go index 16abad56c33..e8a86fa4272 100644 --- a/deployment/keystone/changeset/types.go +++ b/deployment/keystone/changeset/types.go @@ -45,6 +45,6 @@ func newP2PSignerEncFromJD(ccfg *v1.ChainConfig, pubkey [32]byte) (*P2PSignerEnc return &P2PSignerEnc{ Signer: sigb, P2PKey: p2p, - EncryptionPublicKey: pubkey, //[32]byte{3: 84, 2: 79, 1: 68, 0: 79}, // TODO. no current way to get this from the node itself (and therefore not in clo or jd) + EncryptionPublicKey: pubkey, // TODO. no current way to get this from the node itself (and therefore not in clo or jd) }, nil } diff --git a/deployment/keystone/changeset/update_don.go b/deployment/keystone/changeset/update_don.go index e51f353cf81..1a535c5aa11 100644 --- a/deployment/keystone/changeset/update_don.go +++ b/deployment/keystone/changeset/update_don.go @@ -8,7 +8,7 @@ import ( kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" ) -var _ deployment.ChangeSet = UpdateDonX +var _ deployment.ChangeSet = UpdateDon // CapabilityConfig is a struct that holds a capability and its configuration type CapabilityConfig = internal.CapabilityConfig @@ -19,7 +19,10 @@ type UpdateDonResponse struct { DonInfo kcr.CapabilitiesRegistryDONInfo } -func UpdateDonX(env deployment.Environment, cfg any) (deployment.ChangesetOutput, error) { +// UpdateDon updates the capabilities of a Don +// This a complex action in practice that involves registering missing capabilities, adding the nodes, and updating +// the capabilities of the DON +func UpdateDon(env deployment.Environment, cfg any) (deployment.ChangesetOutput, error) { req := cfg.(*UpdateDonRequest) _, err := internal.UpdateDon(env.Logger, req) if err != nil { diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index 422411e9061..09cf351cc85 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -39,7 +39,6 @@ type MutateNodeCapabilitiesRequest struct { RegistryChainSel uint64 P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *MutateNodeCapabilitiesRequest) Validate() error { @@ -49,9 +48,6 @@ func (req *MutateNodeCapabilitiesRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } _, exists := chainsel.ChainBySelector(req.RegistryChainSel) if !exists { return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) @@ -84,7 +80,6 @@ func (req *MutateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e de Chain: registryChain, Registry: registry, P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, }, nil }