From ac3523aaa4cee6f30b9ac0f25cc7cce559067594 Mon Sep 17 00:00:00 2001 From: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> Date: Mon, 16 Sep 2024 07:12:37 -0700 Subject: [PATCH] CCIP-3055 Creating Local docker tests for ccip smoke (#14357) * ccip deployment local dev env * jd interface update * fix environment building * devenv set up * changes * delete non-required file * ccip common config * remove task * add docker env * delete * all changes * changes * adding ccip integration tests * move test to smoke * remove redundant * remove unnecessary changes * remove * fix go mod * fix lint * add jobtype * add ccip jobtype * add ccip job type in feeds service * add assertions * all changes * add changeset * lint fix * reduce flakey test * add changeset tag * revert unwanted * rename func * lint fix --- .changeset/unlucky-dolphins-flash.md | 5 + core/services/feeds/service.go | 11 + core/services/job/mocks/orm.go | 57 ++ core/services/job/orm.go | 13 + core/services/job/validate.go | 1 + core/web/jobs_controller.go | 4 +- core/web/resolver/mutation.go | 4 + integration-tests/.tool-versions | 1 + .../ccip-tests/testsetups/test_env.go | 14 +- integration-tests/client/chainlink.go | 4 +- .../deployment/ccip/add_chain_test.go | 7 +- .../deployment/ccip/add_lane_test.go | 6 +- .../ccip/changeset/2_initial_deploy_test.go | 117 +--- integration-tests/deployment/ccip/deploy.go | 1 + .../deployment/ccip/deploy_home_chain.go | 1 + .../deployment/ccip/test_assertions.go | 203 ++++++ .../deployment/ccip/test_helpers.go | 100 ++- .../deployment/devenv/.sample.env | 5 + .../deployment/devenv/build_env.go | 219 +++++++ integration-tests/deployment/devenv/chain.go | 121 ++++ integration-tests/deployment/devenv/don.go | 288 +++++++++ .../deployment/devenv/environment.go | 54 ++ integration-tests/deployment/devenv/jd.go | 71 ++ integration-tests/deployment/environment.go | 10 +- integration-tests/deployment/helpers.go | 82 +++ .../deployment/jd/node/v1/node.pb.go | 2 +- .../deployment/memory/environment.go | 9 +- integration-tests/deployment/memory/node.go | 8 +- .../deployment/memory/node_test.go | 4 +- integration-tests/go.mod | 5 +- integration-tests/load/go.mod | 2 + integration-tests/smoke/ccip_test.go | 89 +++ .../smoke/job_distributor_test.go | 5 +- integration-tests/testconfig/README.md | 2 +- integration-tests/testconfig/ccip/ccip.toml | 143 +++++ integration-tests/testconfig/ccip/config.go | 92 +++ integration-tests/testconfig/configs_embed.go | 2 + integration-tests/testconfig/testconfig.go | 12 + integration-tests/types/testconfigs.go | 7 + integration-tests/web/sdk/client/client.go | 108 +++- integration-tests/web/sdk/client/types.go | 16 +- .../web/sdk/client/types_test.go | 8 +- .../web/sdk/internal/generated/generated.go | 430 +++++++++++-- .../web/sdk/internal/genqlient.graphql | 606 ++++++++++-------- .../web/sdk/internal/schema.graphql | 11 +- 45 files changed, 2424 insertions(+), 536 deletions(-) create mode 100644 .changeset/unlucky-dolphins-flash.md create mode 100644 integration-tests/deployment/ccip/test_assertions.go create mode 100644 integration-tests/deployment/devenv/.sample.env create mode 100644 integration-tests/deployment/devenv/build_env.go create mode 100644 integration-tests/deployment/devenv/chain.go create mode 100644 integration-tests/deployment/devenv/don.go create mode 100644 integration-tests/deployment/devenv/environment.go create mode 100644 integration-tests/deployment/devenv/jd.go create mode 100644 integration-tests/deployment/helpers.go create mode 100644 integration-tests/smoke/ccip_test.go create mode 100644 integration-tests/testconfig/ccip/ccip.toml create mode 100644 integration-tests/testconfig/ccip/config.go diff --git a/.changeset/unlucky-dolphins-flash.md b/.changeset/unlucky-dolphins-flash.md new file mode 100644 index 00000000000..116b56c87a6 --- /dev/null +++ b/.changeset/unlucky-dolphins-flash.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal Add ccip JobType in feeds service and other jobtype validations diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index 0ccba8ff2a2..87f0ae60f6b 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -17,6 +17,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + + ccip "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate" "github.com/smartcontractkit/chainlink/v2/plugins" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -818,6 +820,13 @@ func (s *service) ApproveSpec(ctx context.Context, id int64, force bool) error { return fmt.Errorf("failed while checking for existing workflow job: %w", txerr) } } + case job.CCIP: + existingJobID, txerr = tx.jobORM.FindJobIDByCapabilityNameAndVersion(ctx, *j.CCIPSpec) + // Return an error if the repository errors. If there is a not found + // error we want to continue with approving the job. + if txerr != nil && !errors.Is(txerr, sql.ErrNoRows) { + return fmt.Errorf("failed while checking for existing ccip job: %w", txerr) + } default: return errors.Errorf("unsupported job type when approving job proposal specs: %s", j.Type) } @@ -1202,6 +1211,8 @@ func (s *service) generateJob(ctx context.Context, spec string) (*job.Job, error js, err = fluxmonitorv2.ValidatedFluxMonitorSpec(s.jobCfg, spec) case job.Workflow: js, err = workflows.ValidatedWorkflowJobSpec(spec) + case job.CCIP: + js, err = ccip.ValidatedCCIPSpec(spec) default: return nil, errors.Errorf("unknown job type: %s", jobType) } diff --git a/core/services/job/mocks/orm.go b/core/services/job/mocks/orm.go index 0174d6208cc..7d3e3de7711 100644 --- a/core/services/job/mocks/orm.go +++ b/core/services/job/mocks/orm.go @@ -543,6 +543,63 @@ func (_c *ORM_FindJobIDByAddress_Call) RunAndReturn(run func(context.Context, ty return _c } +// FindJobIDByCapabilityNameAndVersion provides a mock function with given fields: ctx, spec +func (_m *ORM) FindJobIDByCapabilityNameAndVersion(ctx context.Context, spec job.CCIPSpec) (int32, error) { + ret := _m.Called(ctx, spec) + + if len(ret) == 0 { + panic("no return value specified for FindJobIDByCapabilityNameAndVersion") + } + + var r0 int32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, job.CCIPSpec) (int32, error)); ok { + return rf(ctx, spec) + } + if rf, ok := ret.Get(0).(func(context.Context, job.CCIPSpec) int32); ok { + r0 = rf(ctx, spec) + } else { + r0 = ret.Get(0).(int32) + } + + if rf, ok := ret.Get(1).(func(context.Context, job.CCIPSpec) error); ok { + r1 = rf(ctx, spec) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ORM_FindJobIDByCapabilityNameAndVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindJobIDByCapabilityNameAndVersion' +type ORM_FindJobIDByCapabilityNameAndVersion_Call struct { + *mock.Call +} + +// FindJobIDByCapabilityNameAndVersion is a helper method to define mock.On call +// - ctx context.Context +// - spec job.CCIPSpec +func (_e *ORM_Expecter) FindJobIDByCapabilityNameAndVersion(ctx interface{}, spec interface{}) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + return &ORM_FindJobIDByCapabilityNameAndVersion_Call{Call: _e.mock.On("FindJobIDByCapabilityNameAndVersion", ctx, spec)} +} + +func (_c *ORM_FindJobIDByCapabilityNameAndVersion_Call) Run(run func(ctx context.Context, spec job.CCIPSpec)) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(job.CCIPSpec)) + }) + return _c +} + +func (_c *ORM_FindJobIDByCapabilityNameAndVersion_Call) Return(_a0 int32, _a1 error) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ORM_FindJobIDByCapabilityNameAndVersion_Call) RunAndReturn(run func(context.Context, job.CCIPSpec) (int32, error)) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + _c.Call.Return(run) + return _c +} + // FindJobIDByWorkflow provides a mock function with given fields: ctx, spec func (_m *ORM) FindJobIDByWorkflow(ctx context.Context, spec job.WorkflowSpec) (int32, error) { ret := _m.Called(ctx, spec) diff --git a/core/services/job/orm.go b/core/services/job/orm.go index ac3bb655306..5e7328b87d7 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -79,6 +79,7 @@ type ORM interface { WithDataSource(source sqlutil.DataSource) ORM FindJobIDByWorkflow(ctx context.Context, spec WorkflowSpec) (int32, error) + FindJobIDByCapabilityNameAndVersion(ctx context.Context, spec CCIPSpec) (int32, error) } type ORMConfig interface { @@ -1123,6 +1124,18 @@ INNER JOIN workflow_specs ws on jobs.workflow_spec_id = ws.id AND ws.workflow_ow return } +func (o *orm) FindJobIDByCapabilityNameAndVersion(ctx context.Context, spec CCIPSpec) (jobID int32, err error) { + stmt := ` +SELECT jobs.id FROM jobs +INNER JOIN ccip_specs ccip on jobs.ccip_spec_id = ccip.id AND ccip.capability_labelled_name = $1 AND ccip.capability_version = $2 +` + err = o.ds.GetContext(ctx, &jobID, stmt, spec.CapabilityLabelledName, spec.CapabilityVersion) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + err = fmt.Errorf("error searching for job for CCIP (capabilityName,capabilityVersion) ('%s','%s'): %w", spec.CapabilityLabelledName, spec.CapabilityVersion, err) + } + return +} + // PipelineRunsByJobsIDs returns pipeline runs for multiple jobs, not preloading data func (o *orm) PipelineRunsByJobsIDs(ctx context.Context, ids []int32) (runs []pipeline.Run, err error) { err = o.transact(ctx, false, func(tx *orm) error { diff --git a/core/services/job/validate.go b/core/services/job/validate.go index 92a08823fcf..7cd93b91f00 100644 --- a/core/services/job/validate.go +++ b/core/services/job/validate.go @@ -29,6 +29,7 @@ var ( Webhook: {}, Workflow: {}, StandardCapabilities: {}, + CCIP: {}, } ) diff --git a/core/web/jobs_controller.go b/core/web/jobs_controller.go index 2e005d6d230..1a80ca9693b 100644 --- a/core/web/jobs_controller.go +++ b/core/web/jobs_controller.go @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" + ccip "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/blockhashstore" "github.com/smartcontractkit/chainlink/v2/core/services/blockheaderfeeder" @@ -258,7 +259,8 @@ func (jc *JobsController) validateJobSpec(ctx context.Context, tomlString string jb, err = workflows.ValidatedWorkflowJobSpec(tomlString) case job.StandardCapabilities: jb, err = standardcapabilities.ValidatedStandardCapabilitiesSpec(tomlString) - + case job.CCIP: + jb, err = ccip.ValidatedCCIPSpec(tomlString) default: return jb, http.StatusUnprocessableEntity, errors.Errorf("unknown job type: %s", jobType) } diff --git a/core/web/resolver/mutation.go b/core/web/resolver/mutation.go index a9c1f634dc3..4c9e409cbbf 100644 --- a/core/web/resolver/mutation.go +++ b/core/web/resolver/mutation.go @@ -14,8 +14,10 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" + ccip "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/blockhashstore" "github.com/smartcontractkit/chainlink/v2/core/services/blockheaderfeeder" @@ -1066,6 +1068,8 @@ func (r *Resolver) CreateJob(ctx context.Context, args struct { jb, err = standardcapabilities.ValidatedStandardCapabilitiesSpec(args.Input.TOML) case job.Stream: jb, err = streams.ValidatedStreamSpec(args.Input.TOML) + case job.CCIP: + jb, err = ccip.ValidatedCCIPSpec(args.Input.TOML) default: return NewCreateJobPayload(r.App, nil, map[string]string{ "Job Type": fmt.Sprintf("unknown job type: %s", jbt), diff --git a/integration-tests/.tool-versions b/integration-tests/.tool-versions index d623afb2832..7ca4249a799 100644 --- a/integration-tests/.tool-versions +++ b/integration-tests/.tool-versions @@ -3,3 +3,4 @@ k3d 5.4.6 kubectl 1.25.5 nodejs 20.13.1 golangci-lint 1.59.1 +task 3.35.1 diff --git a/integration-tests/ccip-tests/testsetups/test_env.go b/integration-tests/ccip-tests/testsetups/test_env.go index 1ba54fc7a0d..4d968e83315 100644 --- a/integration-tests/ccip-tests/testsetups/test_env.go +++ b/integration-tests/ccip-tests/testsetups/test_env.go @@ -53,7 +53,7 @@ func SetResourceProfile(cpu, mem string) map[string]interface{} { } } -func setNodeConfig(nets []blockchain.EVMNetwork, nodeConfig, commonChain string, configByChain map[string]string) (*corechainlink.Config, string, error) { +func SetNodeConfig(nets []blockchain.EVMNetwork, nodeConfig, commonChain string, configByChain map[string]string) (*corechainlink.Config, string, error) { var tomlCfg *corechainlink.Config var err error var commonChainConfig *evmcfg.Chain @@ -122,7 +122,7 @@ func ChainlinkPropsForUpdate( chainConfigByChain = testInputs.EnvInput.NewCLCluster.Common.ChainConfigTOMLByChain } - _, tomlStr, err := setNodeConfig( + _, tomlStr, err := SetNodeConfig( testInputs.SelectedNetworks, nodeConfig, commonChainConfig, chainConfigByChain, ) @@ -150,7 +150,7 @@ func ChainlinkPropsForUpdate( "version": upgradeTag, }, } - _, tomlStr, err := setNodeConfig( + _, tomlStr, err := SetNodeConfig( testInputs.SelectedNetworks, testInputs.EnvInput.NewCLCluster.Common.BaseConfigTOML, testInputs.EnvInput.NewCLCluster.Common.CommonChainConfigTOML, @@ -216,7 +216,7 @@ func ChainlinkChart( chainConfigByChain = testInputs.EnvInput.NewCLCluster.Common.ChainConfigTOMLByChain } - _, tomlStr, err := setNodeConfig(nets, nodeConfig, commonChainConfig, chainConfigByChain) + _, tomlStr, err := SetNodeConfig(nets, nodeConfig, commonChainConfig, chainConfigByChain) require.NoError(t, err) nodesMap = append(nodesMap, map[string]any{ "name": clNode.Name, @@ -240,7 +240,7 @@ func ChainlinkChart( return chainlink.New(0, clProps) } clProps["replicas"] = pointer.GetInt(testInputs.EnvInput.NewCLCluster.NoOfNodes) - _, tomlStr, err := setNodeConfig( + _, tomlStr, err := SetNodeConfig( nets, testInputs.EnvInput.NewCLCluster.Common.BaseConfigTOML, testInputs.EnvInput.NewCLCluster.Common.CommonChainConfigTOML, @@ -335,7 +335,7 @@ func DeployLocalCluster( // if individual nodes are specified, then deploy them with specified configs if len(testInputs.EnvInput.NewCLCluster.Nodes) > 0 { for _, clNode := range testInputs.EnvInput.NewCLCluster.Nodes { - toml, _, err := setNodeConfig( + toml, _, err := SetNodeConfig( selectedNetworks, clNode.BaseConfigTOML, clNode.CommonChainConfigTOML, @@ -364,7 +364,7 @@ func DeployLocalCluster( } else { // if no individual nodes are specified, then deploy the number of nodes specified in the env input with common config for i := 0; i < noOfNodes; i++ { - toml, _, err := setNodeConfig( + toml, _, err := SetNodeConfig( selectedNetworks, testInputs.EnvInput.NewCLCluster.Common.BaseConfigTOML, testInputs.EnvInput.NewCLCluster.Common.CommonChainConfigTOML, diff --git a/integration-tests/client/chainlink.go b/integration-tests/client/chainlink.go index da17dcf0d75..ebfd97c48b4 100644 --- a/integration-tests/client/chainlink.go +++ b/integration-tests/client/chainlink.go @@ -1112,7 +1112,9 @@ func CreateNodeKeysBundle(nodes []*ChainlinkClient, chainName string, chainId st if err != nil { return nil, nil, err } - + if len(p2pkeys.Data) == 0 { + return nil, nil, fmt.Errorf("found no P2P Keys on the Chainlink node. Node URL: %s", n.URL()) + } peerID := p2pkeys.Data[0].Attributes.PeerID // If there is already a txkey present for the chain skip creating a new one // otherwise the test logic will need multiple key management (like funding all the keys, diff --git a/integration-tests/deployment/ccip/add_chain_test.go b/integration-tests/deployment/ccip/add_chain_test.go index a484bda0f2d..fdfbb6e69a6 100644 --- a/integration-tests/deployment/ccip/add_chain_test.go +++ b/integration-tests/deployment/ccip/add_chain_test.go @@ -8,6 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/integration-tests/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" @@ -152,6 +154,9 @@ func TestAddChainInbound(t *testing.T) { // TODO: Send via all inbound lanes and use parallel helper // Now that the proposal has been executed we expect to be able to send traffic to this new 4th chain. + startBlock, err := e.Env.Chains[newChain].LatestBlockNum(testcontext.Get(t)) + require.NoError(t, err) seqNr := SendRequest(t, e.Env, state, initialDeploy[0], newChain, true) - ConfirmExecution(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, seqNr) + require.NoError(t, + ConfirmExecWithSeqNr(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, seqNr)) } diff --git a/integration-tests/deployment/ccip/add_lane_test.go b/integration-tests/deployment/ccip/add_lane_test.go index 77b82348e4a..63af3b69c45 100644 --- a/integration-tests/deployment/ccip/add_lane_test.go +++ b/integration-tests/deployment/ccip/add_lane_test.go @@ -5,6 +5,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -48,9 +50,11 @@ func TestAddLane(t *testing.T) { require.Len(t, offRamps, 0) } } + startBlock, err := e.Env.Chains[to].LatestBlockNum(testcontext.Get(t)) + require.NoError(t, err) seqNum := SendRequest(t, e.Env, state, from, to, false) require.Equal(t, uint64(1), seqNum) - ConfirmExecution(t, e.Env.Chains[from], e.Env.Chains[to], state.Chains[to].OffRamp, seqNum) + require.NoError(t, ConfirmExecWithSeqNr(t, e.Env.Chains[from], e.Env.Chains[to], state.Chains[to].OffRamp, &startBlock, seqNum)) // TODO: Add a second lane, then disable the first and // ensure we can send on the second but not the first. diff --git a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go b/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go index fa0fbb9141b..8de2c4617b4 100644 --- a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go +++ b/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go @@ -1,22 +1,14 @@ package changeset import ( - "context" - "sync" "testing" - "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/integration-tests/deployment" - - "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" ccipdeployment "github.com/smartcontractkit/chainlink/integration-tests/deployment/ccip" jobv1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/job/v1" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -58,118 +50,31 @@ func Test0002_InitialDeploy(t *testing.T) { require.NoError(t, err) } } - // Wait for plugins to register filters? - // TODO: Investigate how to avoid. - time.Sleep(30 * time.Second) - - // Ensure job related logs are up to date. - require.NoError(t, ccipdeployment.ReplayAllLogs(nodes, chains)) // Add all lanes - for source := range e.Chains { - for dest := range e.Chains { - if source != dest { - require.NoError(t, ccipdeployment.AddLane(e, state, source, dest)) - } - } - } - + require.NoError(t, ccipdeployment.AddLanesForAll(e, state)) + // Need to keep track of the block number for each chain so that event subscription can be done from that block. + startBlocks := make(map[uint64]*uint64) // Send a message from each chain to every other chain. expectedSeqNum := make(map[uint64]uint64) for src := range e.Chains { - for dest := range e.Chains { + for dest, destChain := range e.Chains { if src == dest { continue } + block, err := destChain.LatestBlockNum(testcontext.Get(t)) + require.NoError(t, err) + startBlocks[dest] = &block seqNum := ccipdeployment.SendRequest(t, e, state, src, dest, false) expectedSeqNum[dest] = seqNum } } // Wait for all commit reports to land. - cStart := time.Now() - var wg sync.WaitGroup - for src, srcChain := range e.Chains { - for dest, dstChain := range e.Chains { - if src == dest { - continue - } - srcChain := srcChain - dstChain := dstChain - wg.Add(1) - go func(src, dest uint64) { - defer wg.Done() - waitForCommitWithInterval(t, srcChain, dstChain, state.Chains[dest].OffRamp, - ccipocr3.SeqNumRange{ccipocr3.SeqNum(expectedSeqNum[dest]), ccipocr3.SeqNum(expectedSeqNum[dest])}) - }(src, dest) - } - } - wg.Wait() - cEnd := time.Now() + ccipdeployment.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) // Wait for all exec reports to land - for src, srcChain := range e.Chains { - for dest, dstChain := range e.Chains { - if src == dest { - continue - } - srcChain := srcChain - dstChain := dstChain - wg.Add(1) - go func(src, dest deployment.Chain) { - defer wg.Done() - ccipdeployment.ConfirmExecution(t, - src, dest, state.Chains[dest.Selector].OffRamp, - expectedSeqNum[dest.Selector]) - }(srcChain, dstChain) - } - } - wg.Wait() - eEnd := time.Now() - t.Log("Commit time:", cEnd.Sub(cStart)) - t.Log("Exec time:", eEnd.Sub(cEnd)) - // TODO: Apply the proposal. -} + ccipdeployment.ConfirmExecWithSeqNrForAll(t, e, state, expectedSeqNum, startBlocks) -func waitForCommitWithInterval( - t *testing.T, - src deployment.Chain, - dest deployment.Chain, - offRamp *offramp.OffRamp, - expectedSeqNumRange ccipocr3.SeqNumRange, -) { - sink := make(chan *offramp.OffRampCommitReportAccepted) - subscription, err := offRamp.WatchCommitReportAccepted(&bind.WatchOpts{ - Context: context.Background(), - }, sink) - require.NoError(t, err) - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - - //revive:disable - for { - select { - case <-ticker.C: - src.Client.(*backends.SimulatedBackend).Commit() - dest.Client.(*backends.SimulatedBackend).Commit() - t.Logf("Waiting for commit report on chain selector %d from source selector %d expected seq nr range %s", - dest.Selector, src.Selector, expectedSeqNumRange.String()) - case subErr := <-subscription.Err(): - t.Fatalf("Subscription error: %+v", subErr) - case report := <-sink: - if len(report.Report.MerkleRoots) > 0 { - // Check the interval of sequence numbers and make sure it matches - // the expected range. - for _, mr := range report.Report.MerkleRoots { - if mr.SourceChainSelector == src.Selector && - uint64(expectedSeqNumRange.Start()) == mr.MinSeqNr && - uint64(expectedSeqNumRange.End()) == mr.MaxSeqNr { - t.Logf("Received commit report on selector %d from source selector %d expected seq nr range %s", - dest.Selector, src.Selector, expectedSeqNumRange.String()) - return - } - } - } - } - } + // TODO: Apply the proposal. } diff --git a/integration-tests/deployment/ccip/deploy.go b/integration-tests/deployment/ccip/deploy.go index 72ab5d7d6ee..dab50f58205 100644 --- a/integration-tests/deployment/ccip/deploy.go +++ b/integration-tests/deployment/ccip/deploy.go @@ -14,6 +14,7 @@ import ( owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/tools/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/integration-tests/deployment" diff --git a/integration-tests/deployment/ccip/deploy_home_chain.go b/integration-tests/deployment/ccip/deploy_home_chain.go index ec078e4a9db..6ff2c25d2ab 100644 --- a/integration-tests/deployment/ccip/deploy_home_chain.go +++ b/integration-tests/deployment/ccip/deploy_home_chain.go @@ -17,6 +17,7 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/integration-tests/deployment" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" diff --git a/integration-tests/deployment/ccip/test_assertions.go b/integration-tests/deployment/ccip/test_assertions.go new file mode 100644 index 00000000000..335b465d207 --- /dev/null +++ b/integration-tests/deployment/ccip/test_assertions.go @@ -0,0 +1,203 @@ +package ccipdeployment + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" +) + +// ConfirmCommitForAllWithExpectedSeqNums waits for all chains in the environment to commit the given expectedSeqNums. +// expectedSeqNums is a map of destinationchain selector to expected sequence number +// startBlocks is a map of destination chain selector to start block number to start watching from. +// If startBlocks is nil, it will start watching from the latest block. +func ConfirmCommitForAllWithExpectedSeqNums( + t *testing.T, + e deployment.Environment, + state CCIPOnChainState, + expectedSeqNums map[uint64]uint64, + startBlocks map[uint64]*uint64, +) { + var wg errgroup.Group + for src, srcChain := range e.Chains { + for dest, dstChain := range e.Chains { + if src == dest { + continue + } + srcChain := srcChain + dstChain := dstChain + wg.Go(func() error { + return func(src, dest uint64) error { + var startBlock *uint64 + if startBlocks != nil { + startBlock = startBlocks[dest] + } + return ConfirmCommitWithExpectedSeqNumRange(t, srcChain, dstChain, state.Chains[dest].OffRamp, startBlock, + ccipocr3.SeqNumRange{ccipocr3.SeqNum(expectedSeqNums[dest]), ccipocr3.SeqNum(expectedSeqNums[dest])}) + }(src, dest) + }) + } + } + require.NoError(t, wg.Wait()) +} + +// ConfirmCommitWithExpectedSeqNumRange waits for a commit report on the destination chain with the expected sequence number range. +// startBlock is the block number to start watching from. +// If startBlock is nil, it will start watching from the latest block. +func ConfirmCommitWithExpectedSeqNumRange( + t *testing.T, + src deployment.Chain, + dest deployment.Chain, + offRamp *offramp.OffRamp, + startBlock *uint64, + expectedSeqNumRange ccipocr3.SeqNumRange, +) error { + sink := make(chan *offramp.OffRampCommitReportAccepted) + subscription, err := offRamp.WatchCommitReportAccepted(&bind.WatchOpts{ + Context: context.Background(), + Start: startBlock, + }, sink) + if err != nil { + return fmt.Errorf("error to subscribe CommitReportAccepted : %w", err) + } + + defer subscription.Unsubscribe() + timer := time.NewTimer(5 * time.Minute) + defer timer.Stop() + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + // if it's simulated backend, commit to ensure mining + if backend, ok := src.Client.(*backends.SimulatedBackend); ok { + backend.Commit() + } + if backend, ok := dest.Client.(*backends.SimulatedBackend); ok { + backend.Commit() + } + t.Logf("Waiting for commit report on chain selector %d from source selector %d expected seq nr range %s", + dest.Selector, src.Selector, expectedSeqNumRange.String()) + case subErr := <-subscription.Err(): + return fmt.Errorf("subscription error: %w", subErr) + case <-timer.C: + return fmt.Errorf("timed out waiting for commit report on chain selector %d from source selector %d expected seq nr range %s", + dest.Selector, src.Selector, expectedSeqNumRange.String()) + case report := <-sink: + if len(report.Report.MerkleRoots) > 0 { + // Check the interval of sequence numbers and make sure it matches + // the expected range. + for _, mr := range report.Report.MerkleRoots { + if mr.SourceChainSelector == src.Selector && + uint64(expectedSeqNumRange.Start()) == mr.MinSeqNr && + uint64(expectedSeqNumRange.End()) == mr.MaxSeqNr { + t.Logf("Received commit report on selector %d from source selector %d expected seq nr range %s", + dest.Selector, src.Selector, expectedSeqNumRange.String()) + return nil + } + } + } + } + } +} + +// ConfirmExecWithSeqNrForAll waits for all chains in the environment to execute the given expectedSeqNums. +// expectedSeqNums is a map of destinationchain selector to expected sequence number +// startBlocks is a map of destination chain selector to start block number to start watching from. +// If startBlocks is nil, it will start watching from the latest block. +func ConfirmExecWithSeqNrForAll( + t *testing.T, + e deployment.Environment, + state CCIPOnChainState, + expectedSeqNums map[uint64]uint64, + startBlocks map[uint64]*uint64, +) { + var wg errgroup.Group + for src, srcChain := range e.Chains { + for dest, dstChain := range e.Chains { + if src == dest { + continue + } + srcChain := srcChain + dstChain := dstChain + wg.Go(func() error { + return func(src, dest deployment.Chain) error { + var startBlock *uint64 + if startBlocks != nil { + startBlock = startBlocks[dest.Selector] + } + return ConfirmExecWithSeqNr( + t, src, dest, state.Chains[dest.Selector].OffRamp, startBlock, + expectedSeqNums[dstChain.Selector], + ) + }(srcChain, dstChain) + }) + } + } + require.NoError(t, wg.Wait()) +} + +// ConfirmExecWithSeqNr waits for an execution state change on the destination chain with the expected sequence number. +// startBlock is the block number to start watching from. +// If startBlock is nil, it will start watching from the latest block. +func ConfirmExecWithSeqNr( + t *testing.T, + source, dest deployment.Chain, + offRamp *offramp.OffRamp, + startBlock *uint64, + expectedSeqNr uint64, +) error { + timer := time.NewTimer(5 * time.Minute) + defer timer.Stop() + tick := time.NewTicker(5 * time.Second) + defer tick.Stop() + sink := make(chan *offramp.OffRampExecutionStateChanged) + subscription, err := offRamp.WatchExecutionStateChanged(&bind.WatchOpts{ + Context: context.Background(), + Start: startBlock, + }, sink, nil, nil, nil) + if err != nil { + return fmt.Errorf("error to subscribe ExecutionStateChanged : %w", err) + } + defer subscription.Unsubscribe() + for { + select { + case <-tick.C: + // TODO: Clean this up + // if it's simulated backend, commit to ensure mining + if backend, ok := source.Client.(*backends.SimulatedBackend); ok { + backend.Commit() + } + if backend, ok := dest.Client.(*backends.SimulatedBackend); ok { + backend.Commit() + } + scc, err := offRamp.GetSourceChainConfig(nil, source.Selector) + if err != nil { + return fmt.Errorf("error to get source chain config : %w", err) + } + t.Logf("Waiting for ExecutionStateChanged on chain %d from chain %d with expected sequence number %d, current onchain minSeqNr: %d", + dest.Selector, source.Selector, expectedSeqNr, scc.MinSeqNr) + case execEvent := <-sink: + if execEvent.SequenceNumber == expectedSeqNr && execEvent.SourceChainSelector == source.Selector { + t.Logf("Received ExecutionStateChanged on chain %d from chain %d with expected sequence number %d", + dest.Selector, source.Selector, expectedSeqNr) + return nil + } + case <-timer.C: + return fmt.Errorf("timed out waiting for ExecutionStateChanged on chain %d from chain %d with expected sequence number %d", + dest.Selector, source.Selector, expectedSeqNr) + case subErr := <-subscription.Err(): + return fmt.Errorf("Subscription error: %w", subErr) + } + } +} diff --git a/integration-tests/deployment/ccip/test_helpers.go b/integration-tests/deployment/ccip/test_helpers.go index 779b29a7496..4458a49abc7 100644 --- a/integration-tests/deployment/ccip/test_helpers.go +++ b/integration-tests/deployment/ccip/test_helpers.go @@ -7,20 +7,21 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" jobv1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/job/v1" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/integration-tests/deployment" "github.com/smartcontractkit/chainlink/integration-tests/deployment/memory" "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment/devenv" ) // Context returns a context with the test's deadline, if available. @@ -67,7 +68,7 @@ func NewEnvironmentWithCR(t *testing.T, lggr logger.Logger, numChains int) Deplo ab, capReg, err := DeployCapReg(lggr, chains, homeChainSel) require.NoError(t, err) - nodes := memory.NewNodes(t, zapcore.InfoLevel, chains, 4, 1, memory.RegistryConfig{ + nodes := memory.NewNodes(t, zapcore.InfoLevel, chains, 4, 1, deployment.CapabilityRegistryConfig{ EVMChainID: homeChainEVM, Contract: capReg, }) @@ -160,33 +161,74 @@ func SendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainState, return it.Event.Message.Header.SequenceNumber } -func ConfirmExecution(t *testing.T, - source, dest deployment.Chain, - offramp *offramp.OffRamp, - expectedSeqNr uint64) { - tick := time.NewTicker(5 * time.Second) - defer tick.Stop() - for range tick.C { - // TODO: Clean this up - source.Client.(*backends.SimulatedBackend).Commit() - dest.Client.(*backends.SimulatedBackend).Commit() - scc, err := offramp.GetSourceChainConfig(nil, source.Selector) - require.NoError(t, err) - t.Logf("Waiting for ExecutionStateChanged on chain %d from chain %d with expected sequence number %d, current onchain minSeqNr: %d", - dest.Selector, source.Selector, expectedSeqNr, scc.MinSeqNr) - iter, err := offramp.FilterExecutionStateChanged(nil, - []uint64{source.Selector}, []uint64{expectedSeqNr}, nil) - require.NoError(t, err) - var count int - for iter.Next() { - if iter.Event.SequenceNumber == expectedSeqNr && iter.Event.SourceChainSelector == source.Selector { - count++ +// DeployedLocalDevEnvironment is a helper struct for setting up a local dev environment with docker +type DeployedLocalDevEnvironment struct { + Ab deployment.AddressBook + Env deployment.Environment + HomeChainSel uint64 + Nodes []devenv.Node +} + +func NewDeployedLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedLocalDevEnvironment { + ctx := Context(t) + // create a local docker environment with simulated chains and job-distributor + // we cannot create the chainlink nodes yet as we need to deploy the capability registry first + envConfig, testEnv, cfg := devenv.CreateDockerEnv(t) + require.NotNil(t, envConfig) + require.NotEmpty(t, envConfig.Chains, "chainConfigs should not be empty") + require.NotEmpty(t, envConfig.JDConfig, "jdUrl should not be empty") + chains, err := devenv.NewChains(lggr, envConfig.Chains) + require.NoError(t, err) + homeChainSel := uint64(0) + homeChainEVM := uint64(0) + + // Say first chain is home chain. + for chainSel := range chains { + homeChainEVM, _ = chainsel.ChainIdFromSelector(chainSel) + homeChainSel = chainSel + break + } + // deploy the capability registry + ab, capReg, err := DeployCapReg(lggr, chains, homeChainSel) + require.NoError(t, err) + + // start the chainlink nodes with the CR address + err = devenv.StartChainlinkNodes(t, + envConfig, deployment.CapabilityRegistryConfig{ + EVMChainID: homeChainEVM, + Contract: capReg, + }, + testEnv, cfg) + require.NoError(t, err) + + e, don, err := devenv.NewEnvironment(ctx, lggr, *envConfig) + require.NoError(t, err) + require.NotNil(t, e) + require.NotNil(t, don) + + // fund the nodes + require.NoError(t, don.FundNodes(ctx, deployment.E18Mult(10), e.Chains)) + + return DeployedLocalDevEnvironment{ + Ab: ab, + Env: *e, + HomeChainSel: homeChainSel, + Nodes: don.Nodes, + } +} + +// AddLanesForAll adds densely connected lanes for all chains in the environment so that each chain +// is connected to every other chain except itself. +func AddLanesForAll(e deployment.Environment, state CCIPOnChainState) error { + for source := range e.Chains { + for dest := range e.Chains { + if source != dest { + err := AddLane(e, state, source, dest) + if err != nil { + return err + } } } - if count == 1 { - t.Logf("Received ExecutionStateChanged on chain %d from chain %d with expected sequence number %d", - dest.Selector, source.Selector, expectedSeqNr) - return - } } + return nil } diff --git a/integration-tests/deployment/devenv/.sample.env b/integration-tests/deployment/devenv/.sample.env new file mode 100644 index 00000000000..97d550079a9 --- /dev/null +++ b/integration-tests/deployment/devenv/.sample.env @@ -0,0 +1,5 @@ +E2E_JD_IMAGE= +E2E_JD_VERSION= + +E2E_TEST_CHAINLINK_IMAGE=public.ecr.aws/w0i8p0z9/chainlink-ccip +E2E_TEST_CHAINLINK_VERSION=2.14.0-ccip1.5.0 \ No newline at end of file diff --git a/integration-tests/deployment/devenv/build_env.go b/integration-tests/deployment/devenv/build_env.go new file mode 100644 index 00000000000..0373cf0b214 --- /dev/null +++ b/integration-tests/deployment/devenv/build_env.go @@ -0,0 +1,219 @@ +package devenv + +import ( + "fmt" + "math/big" + "os" + "strconv" + "testing" + + "github.com/AlekSi/pointer" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/crypto" + chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/require" + "github.com/subosito/gotenv" + + ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" + "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env/job_distributor" + "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" + + "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" + clclient "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/deployment" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" +) + +// CreateDockerEnv creates a new test environment with simulated private ethereum networks and job distributor +// It returns the EnvironmentConfig which holds the chain config and JD config +// The test environment is then used to start chainlink nodes +func CreateDockerEnv(t *testing.T) ( + *EnvironmentConfig, + *test_env.CLClusterTestEnv, + tc.TestConfig, +) { + if _, err := os.Stat(".env"); err == nil || !os.IsNotExist(err) { + require.NoError(t, gotenv.Load(".env"), "Error loading .env file") + } + + cfg, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.CCIP) + require.NoError(t, err, "Error getting config") + + var privateEthereumNetworks []*ctf_config.EthereumNetworkConfig + for _, network := range cfg.CCIP.PrivateEthereumNetworks { + privateEthereumNetworks = append(privateEthereumNetworks, network) + } + env, err := test_env.NewCLTestEnvBuilder(). + WithTestConfig(&cfg). + WithTestInstance(t). + WithPrivateEthereumNetworks(privateEthereumNetworks). + WithStandardCleanup(). + Build() + require.NoError(t, err, "Error building test environment") + chains := CreateChainConfigFromPrivateEthereumNetworks(t, env, cfg.CCIP.PrivateEthereumNetworks, cfg.GetNetworkConfig()) + + var jdConfig JDConfig + // TODO : move this as a part of test_env setup with an input in testconfig + // if JD is not provided, we will spin up a new JD + if cfg.CCIP.GetJDGRPC() == "" && cfg.CCIP.GetJDWSRPC() == "" { + jdDB, err := ctftestenv.NewPostgresDb( + []string{env.DockerNetwork.Name}, + ctftestenv.WithPostgresDbName(cfg.CCIP.GetJDDBName()), + ctftestenv.WithPostgresImageVersion(cfg.CCIP.GetJDDBVersion()), + ) + require.NoError(t, err) + err = jdDB.StartContainer() + require.NoError(t, err) + + jd := job_distributor.New([]string{env.DockerNetwork.Name}, + job_distributor.WithImage(cfg.CCIP.GetJDImage()), + job_distributor.WithVersion(cfg.CCIP.GetJDVersion()), + job_distributor.WithDBURL(jdDB.InternalURL.String()), + ) + err = jd.StartContainer() + require.NoError(t, err) + jdConfig = JDConfig{ + GRPC: jd.Grpc, + // we will use internal wsrpc for nodes on same docker network to connect to JD + WSRPC: jd.InternalWSRPC, + } + } else { + jdConfig = JDConfig{ + GRPC: cfg.CCIP.GetJDGRPC(), + WSRPC: cfg.CCIP.GetJDWSRPC(), + } + } + require.NotEmpty(t, jdConfig, "JD config is empty") + + return &EnvironmentConfig{ + Chains: chains, + JDConfig: jdConfig, + }, env, cfg +} + +// StartChainlinkNodes starts docker containers for chainlink nodes on the existing test environment based on provided test config +// Once the nodes starts, it updates the devenv EnvironmentConfig with the node info +// which includes chainlink API URL, email, password and internal IP +func StartChainlinkNodes( + t *testing.T, + envConfig *EnvironmentConfig, + registryConfig deployment.CapabilityRegistryConfig, + env *test_env.CLClusterTestEnv, + cfg tc.TestConfig, +) error { + evmNetworks := networks.MustGetSelectedNetworkConfig(cfg.GetNetworkConfig()) + for i, net := range evmNetworks { + rpcProvider, err := env.GetRpcProvider(net.ChainID) + require.NoError(t, err, "Error getting rpc provider") + evmNetworks[i].HTTPURLs = rpcProvider.PrivateHttpUrls() + evmNetworks[i].URLs = rpcProvider.PrivateWsUrsl() + } + noOfNodes := pointer.GetInt(cfg.CCIP.CLNode.NoOfPluginNodes) + pointer.GetInt(cfg.CCIP.CLNode.NoOfBootstraps) + var nodeInfo []NodeInfo + for i := 1; i <= noOfNodes; i++ { + if i <= pointer.GetInt(cfg.CCIP.CLNode.NoOfBootstraps) { + nodeInfo = append(nodeInfo, NodeInfo{ + IsBootstrap: true, + Name: fmt.Sprintf("bootstrap-%d", i), + // TODO : make this configurable + P2PPort: "6690", + }) + } else { + nodeInfo = append(nodeInfo, NodeInfo{ + IsBootstrap: false, + Name: fmt.Sprintf("node-%d", i-1), + // TODO : make this configurable + P2PPort: "6690", + }) + } + toml, _, err := testsetups.SetNodeConfig( + evmNetworks, + cfg.NodeConfig.BaseConfigTOML, + cfg.NodeConfig.CommonChainConfigTOML, + cfg.NodeConfig.ChainConfigTOMLByChainID, + ) + + toml.Capabilities.ExternalRegistry.NetworkID = ptr.Ptr(relay.NetworkEVM) + toml.Capabilities.ExternalRegistry.ChainID = ptr.Ptr(strconv.FormatUint(registryConfig.EVMChainID, 10)) + toml.Capabilities.ExternalRegistry.Address = ptr.Ptr(registryConfig.Contract.String()) + + if err != nil { + return err + } + ccipNode, err := test_env.NewClNode( + []string{env.DockerNetwork.Name}, + pointer.GetString(cfg.GetChainlinkImageConfig().Image), + pointer.GetString(cfg.GetChainlinkImageConfig().Version), + toml, + env.LogStream, + test_env.WithPgDBOptions( + ctftestenv.WithPostgresImageVersion(pointer.GetString(cfg.GetChainlinkImageConfig().PostgresVersion)), + ), + ) + if err != nil { + return err + } + ccipNode.SetTestLogger(t) + env.ClCluster.Nodes = append(env.ClCluster.Nodes, ccipNode) + } + err := env.ClCluster.Start() + if err != nil { + return err + } + for i, n := range env.ClCluster.Nodes { + nodeInfo[i].CLConfig = clclient.ChainlinkConfig{ + URL: n.API.URL(), + Email: n.UserEmail, + Password: n.UserPassword, + InternalIP: n.API.InternalIP(), + } + } + + envConfig.nodeInfo = nodeInfo + return nil +} + +// CreateChainConfigFromPrivateEthereumNetworks creates a list of ChainConfig from the private ethereum networks created by the test environment. +// It uses the private keys from the network config to create the deployer key for each chain. +func CreateChainConfigFromPrivateEthereumNetworks( + t *testing.T, + env *test_env.CLClusterTestEnv, + privateEthereumNetworks map[string]*ctf_config.EthereumNetworkConfig, + networkConfig *ctf_config.NetworkConfig, +) []ChainConfig { + evmNetworks := networks.MustGetSelectedNetworkConfig(networkConfig) + networkPvtKeys := make(map[int64]string) + for _, net := range evmNetworks { + require.Greater(t, len(net.PrivateKeys), 0, "No private keys found for network") + networkPvtKeys[net.ChainID] = net.PrivateKeys[0] + } + var chains []ChainConfig + for _, networkCfg := range privateEthereumNetworks { + chainId := networkCfg.EthereumChainConfig.ChainID + chainName, err := chainselectors.NameFromChainId(uint64(chainId)) + require.NoError(t, err, "Error getting chain name") + rpcProvider, err := env.GetRpcProvider(int64(chainId)) + require.NoError(t, err, "Error getting rpc provider") + pvtKeyStr, exists := networkPvtKeys[int64(chainId)] + require.Truef(t, exists, "Private key not found for chain id %d", chainId) + pvtKey, err := crypto.HexToECDSA(pvtKeyStr) + require.NoError(t, err) + deployer, err := bind.NewKeyedTransactorWithChainID(pvtKey, big.NewInt(int64(chainId))) + require.NoError(t, err) + chains = append(chains, ChainConfig{ + ChainID: uint64(chainId), + ChainName: chainName, + ChainType: "EVM", + WSRPCs: rpcProvider.PublicWsUrls(), + HTTPRPCs: rpcProvider.PublicHttpUrls(), + PrivateHTTPRPCs: rpcProvider.PrivateHttpUrls(), + PrivateWSRPCs: rpcProvider.PrivateWsUrsl(), + DeployerKey: deployer, + }) + } + return chains +} diff --git a/integration-tests/deployment/devenv/chain.go b/integration-tests/deployment/devenv/chain.go new file mode 100644 index 00000000000..6374a2c213d --- /dev/null +++ b/integration-tests/deployment/devenv/chain.go @@ -0,0 +1,121 @@ +package devenv + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/sethvargo/go-retry" + chainselectors "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment" +) + +// ChainConfig holds the configuration for a with a deployer key which can be used to send transactions to the chain. +type ChainConfig struct { + ChainID uint64 // chain id as per EIP-155, mainly applicable for EVM chains + ChainName string // name of the chain populated from chainselector repo + ChainType string // should denote the chain family. Acceptable values are EVM, COSMOS, SOLANA, STARKNET, APTOS etc + WSRPCs []string // websocket rpcs to connect to the chain + HTTPRPCs []string // http rpcs to connect to the chain + PrivateWSRPCs []string // applicable for private chains spun up with docker/K8s only so that nodes within same cluster can connect internally + PrivateHTTPRPCs []string // applicable for private chains spun up with docker/K8s only so that nodes within same cluster can connect internally + DeployerKey *bind.TransactOpts // key to send transactions to the chain +} + +func NewChains(logger logger.Logger, configs []ChainConfig) (map[uint64]deployment.Chain, error) { + chains := make(map[uint64]deployment.Chain) + for _, chainCfg := range configs { + selector, err := chainselectors.SelectorFromChainId(chainCfg.ChainID) + if err != nil { + return nil, fmt.Errorf("failed to get selector from chain id %d: %w", chainCfg.ChainID, err) + } + // TODO : better client handling + var ec *ethclient.Client + for _, rpc := range chainCfg.WSRPCs { + ec, err = ethclient.Dial(rpc) + if err != nil { + logger.Warnf("failed to dial ws rpc %s", rpc) + continue + } + logger.Infof("connected to ws rpc %s", rpc) + break + } + if ec == nil { + return nil, fmt.Errorf("failed to connect to chain %s", chainCfg.ChainName) + } + chains[selector] = deployment.Chain{ + Selector: selector, + Client: ec, + DeployerKey: chainCfg.DeployerKey, + LatestBlockNum: ec.BlockNumber, + Confirm: func(tx *types.Transaction) (uint64, error) { + var blockNumber uint64 + if tx == nil { + return 0, fmt.Errorf("tx was nil, nothing to confirm") + } + err := retry.Do(context.Background(), + retry.WithMaxDuration(3*time.Minute, retry.NewFibonacci(1*time.Second)), + func(ctx context.Context) error { + receipt, err := ec.TransactionReceipt(ctx, tx.Hash()) + if err != nil { + return retry.RetryableError(fmt.Errorf("failed to get receipt: %w", err)) + } + if receipt != nil { + blockNumber = receipt.BlockNumber.Uint64() + } + if receipt.Status == 0 { + t, _, err := ec.TransactionByHash(context.Background(), tx.Hash()) + if err != nil { + return fmt.Errorf("tx %s reverted, failed to get transaction: %w", tx.Hash().Hex(), err) + } + errReason, err := deployment.GetErrorReasonFromTx(ec, chainCfg.DeployerKey.From, *t, receipt) + if err == nil && errReason != "" { + return fmt.Errorf("tx %s reverted,error reason: %s", tx.Hash().Hex(), errReason) + } + return fmt.Errorf("tx %s reverted, could not decode error reason", tx.Hash().Hex()) + } + return nil + }) + return blockNumber, err + }, + } + } + return chains, nil +} + +// TODO : Remove this when seth is integrated. +func FundAddress(ctx context.Context, from *bind.TransactOpts, to common.Address, amount *big.Int, c deployment.Chain) error { + nonce, err := c.Client.PendingNonceAt(ctx, from.From) + if err != nil { + return fmt.Errorf("failed to get nonce: %w", err) + } + gp, err := c.Client.SuggestGasPrice(ctx) + if err != nil { + return fmt.Errorf("failed to suggest gas price: %w", err) + } + rawTx := types.NewTx(&types.LegacyTx{ + Nonce: nonce, + GasPrice: gp, + Gas: 21000, + To: &to, + Value: amount, + }) + signedTx, err := from.Signer(from.From, rawTx) + if err != nil { + return fmt.Errorf("failed to sign tx: %w", err) + } + err = c.Client.SendTransaction(ctx, signedTx) + if err != nil { + return fmt.Errorf("failed to send tx: %w", err) + } + _, err = c.Confirm(signedTx) + return err +} diff --git a/integration-tests/deployment/devenv/don.go b/integration-tests/deployment/devenv/don.go new file mode 100644 index 00000000000..663f4c3329a --- /dev/null +++ b/integration-tests/deployment/devenv/don.go @@ -0,0 +1,288 @@ +package devenv + +import ( + "context" + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/AlekSi/pointer" + "github.com/ethereum/go-ethereum/common" + "github.com/hashicorp/go-multierror" + chainselectors "github.com/smartcontractkit/chain-selectors" + + clclient "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/deployment" + nodev1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/node/v1" + "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/shared/ptypes" + "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/client" +) + +const ( + NodeLabelKeyType = "type" + NodeLabelValueBootstrap = "bootstrap" + NodeLabelValuePlugin = "plugin" +) + +// NodeInfo holds the information required to create a node +type NodeInfo struct { + CLConfig clclient.ChainlinkConfig // config to connect to chainlink node via API + P2PPort string // port for P2P communication + IsBootstrap bool // denotes if the node is a bootstrap node + Name string // name of the node, used to identify the node, helpful in logs + AdminAddr string // admin address to send payments to, applicable only for non-bootstrap nodes +} + +type DON struct { + Nodes []Node +} + +func (don *DON) NodeIds() []string { + var nodeIds []string + for _, node := range don.Nodes { + nodeIds = append(nodeIds, node.NodeId) + } + return nodeIds +} + +func (don *DON) FundNodes(ctx context.Context, amount *big.Int, chains map[uint64]deployment.Chain) error { + var err error + for sel, chain := range chains { + for _, node := range don.Nodes { + // if node is bootstrap, no need to fund it + if node.multiAddr != "" { + continue + } + accountAddr, ok := node.AccountAddr[sel] + if !ok { + err = multierror.Append(err, fmt.Errorf("node %s has no account address for chain %d", node.Name, sel)) + continue + } + if err1 := FundAddress(ctx, chain.DeployerKey, common.HexToAddress(accountAddr), amount, chain); err1 != nil { + err = multierror.Append(err, err1) + } + } + } + return err +} + +func (don *DON) CreateSupportedChains(ctx context.Context, chains []ChainConfig) error { + var err error + for i, node := range don.Nodes { + if err1 := node.CreateCCIPOCRSupportedChains(ctx, chains); err1 != nil { + err = multierror.Append(err, err1) + } + don.Nodes[i] = node + } + return err +} + +// NewRegisteredDON creates a DON with the given node info, registers the nodes with the job distributor +// and sets up the job distributor in the nodes +func NewRegisteredDON(ctx context.Context, nodeInfo []NodeInfo, jd JobDistributor) (*DON, error) { + don := &DON{ + Nodes: make([]Node, 0), + } + for i, info := range nodeInfo { + if info.Name == "" { + info.Name = fmt.Sprintf("node-%d", i) + } + node, err := NewNode(info) + if err != nil { + return nil, fmt.Errorf("failed to create node %d: %w", i, err) + } + // node Labels so that it's easier to query them + if info.IsBootstrap { + // create multi address for OCR2, applicable only for bootstrap nodes + + node.multiAddr = fmt.Sprintf("%s:%s", info.CLConfig.InternalIP, info.P2PPort) + // no need to set admin address for bootstrap nodes, as there will be no payment + node.adminAddr = "" + node.labels = append(node.labels, &ptypes.Label{ + Key: NodeLabelKeyType, + Value: pointer.ToString(NodeLabelValueBootstrap), + }) + } else { + // multi address is not applicable for non-bootstrap nodes + // explicitly set it to empty string to denote that + node.multiAddr = "" + node.labels = append(node.labels, &ptypes.Label{ + Key: NodeLabelKeyType, + Value: pointer.ToString(NodeLabelValuePlugin), + }) + } + // Set up Job distributor in node and register node with the job distributor + err = node.SetUpAndLinkJobDistributor(ctx, jd) + if err != nil { + return nil, fmt.Errorf("failed to set up job distributor in node %s: %w", info.Name, err) + } + + don.Nodes = append(don.Nodes, *node) + } + return don, nil +} + +func NewNode(nodeInfo NodeInfo) (*Node, error) { + gqlClient, err := client.New(nodeInfo.CLConfig.URL, client.Credentials{ + Email: nodeInfo.CLConfig.Email, + Password: nodeInfo.CLConfig.Password, + }) + if err != nil { + return nil, fmt.Errorf("failed to create FMS client: %w", err) + } + return &Node{ + gqlClient: gqlClient, + Name: nodeInfo.Name, + adminAddr: nodeInfo.AdminAddr, + }, nil +} + +type Node struct { + NodeId string // node id returned by job distributor after node is registered with it + JDId string // job distributor id returned by node after Job distributor is created in node + Name string // name of the node + AccountAddr map[uint64]string // chain selector to node's account address mapping for supported chains + gqlClient client.Client // graphql client to interact with the node + labels []*ptypes.Label // labels with which the node is registered with the job distributor + adminAddr string // admin address to send payments to, applicable only for non-bootstrap nodes + multiAddr string // multi address denoting node's FQN (needed for deriving P2PBootstrappers in OCR), applicable only for bootstrap nodes +} + +// CreateCCIPOCRSupportedChains creates a JobDistributorChainConfig for the node. +// It works under assumption that the node is already registered with the job distributor. +// It expects bootstrap nodes to have label with key "type" and value as "bootstrap". +// It fetches the account address, peer id, and OCR2 key bundle id and creates the JobDistributorChainConfig. +func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []ChainConfig) error { + for _, chain := range chains { + chainId := strconv.FormatUint(chain.ChainID, 10) + selector, err := chainselectors.SelectorFromChainId(chain.ChainID) + if err != nil { + return fmt.Errorf("failed to get selector from chain id %d: %w", chain.ChainID, err) + } + accountAddr, err := n.gqlClient.FetchAccountAddress(ctx, chainId) + if err != nil { + return fmt.Errorf("failed to fetch account address for node %s: %w", n.Name, err) + } + if accountAddr == nil { + return fmt.Errorf("no account address found for node %s", n.Name) + } + if n.AccountAddr == nil { + n.AccountAddr = make(map[uint64]string) + } + n.AccountAddr[selector] = *accountAddr + peerID, err := n.gqlClient.FetchP2PPeerID(ctx) + if err != nil { + return fmt.Errorf("failed to fetch peer id for node %s: %w", n.Name, err) + } + if peerID == nil { + return fmt.Errorf("no peer id found for node %s", n.Name) + } + + ocr2BundleId, err := n.gqlClient.FetchOCR2KeyBundleID(ctx, chain.ChainType) + if err != nil { + return fmt.Errorf("failed to fetch OCR2 key bundle id for node %s: %w", n.Name, err) + } + if ocr2BundleId == "" { + return fmt.Errorf("no OCR2 key bundle id found for node %s", n.Name) + } + // fetch node labels to know if the node is bootstrap or plugin + isBootstrap := false + for _, label := range n.labels { + if label.Key == NodeLabelKeyType && pointer.GetString(label.Value) == NodeLabelValueBootstrap { + isBootstrap = true + break + } + } + err = n.gqlClient.CreateJobDistributorChainConfig(ctx, client.JobDistributorChainConfigInput{ + JobDistributorID: n.JDId, + ChainID: chainId, + ChainType: chain.ChainType, + AccountAddr: pointer.GetString(accountAddr), + AdminAddr: n.adminAddr, + Ocr2Enabled: true, + Ocr2IsBootstrap: isBootstrap, + Ocr2Multiaddr: n.multiAddr, + Ocr2P2PPeerID: pointer.GetString(peerID), + Ocr2KeyBundleID: ocr2BundleId, + Ocr2Plugins: `{"commit":true,"execute":true,"median":false,"mercury":false}`, + }) + if err != nil { + return fmt.Errorf("failed to create CCIPOCR2SupportedChains for node %s: %w", n.Name, err) + } + } + return nil +} + +func (n *Node) AcceptJob(ctx context.Context, id string) error { + spec, err := n.gqlClient.ApproveJobProposalSpec(ctx, id, false) + if err != nil { + return err + } + if spec == nil { + return fmt.Errorf("no job proposal spec found for job id %s", id) + } + return nil +} + +// RegisterNodeToJobDistributor fetches the CSA public key of the node and registers the node with the job distributor +// it sets the node id returned by JobDistributor as a result of registration in the node struct +func (n *Node) RegisterNodeToJobDistributor(ctx context.Context, jd JobDistributor) error { + // Get the public key of the node + csaKeyRes, err := n.gqlClient.FetchCSAPublicKey(ctx) + if err != nil { + return err + } + if csaKeyRes == nil { + return fmt.Errorf("no csa key found for node %s", n.Name) + } + csaKey := strings.TrimPrefix(*csaKeyRes, "csa_") + // register the node in the job distributor + registerResponse, err := jd.RegisterNode(ctx, &nodev1.RegisterNodeRequest{ + PublicKey: csaKey, + Labels: n.labels, + Name: n.Name, + }) + + if err != nil { + return fmt.Errorf("failed to register node %s: %w", n.Name, err) + } + if registerResponse.GetNode().GetId() == "" { + return fmt.Errorf("no node id returned from job distributor for node %s", n.Name) + } + n.NodeId = registerResponse.GetNode().GetId() + return nil +} + +// CreateJobDistributor fetches the keypairs from the job distributor and creates the job distributor in the node +// and returns the job distributor id +func (n *Node) CreateJobDistributor(ctx context.Context, jd JobDistributor) (string, error) { + // Get the keypairs from the job distributor + csaKey, err := jd.GetCSAPublicKey(ctx) + if err != nil { + return "", err + } + // create the job distributor in the node with the csa key + return n.gqlClient.CreateJobDistributor(ctx, client.JobDistributorInput{ + Name: "Job Distributor", + Uri: jd.WSRPC, + PublicKey: csaKey, + }) +} + +// SetUpAndLinkJobDistributor sets up the job distributor in the node and registers the node with the job distributor +// it sets the job distributor id for node +func (n *Node) SetUpAndLinkJobDistributor(ctx context.Context, jd JobDistributor) error { + // register the node in the job distributor + err := n.RegisterNodeToJobDistributor(ctx, jd) + if err != nil { + return err + } + // now create the job distributor in the node + id, err := n.CreateJobDistributor(ctx, jd) + if err != nil { + return err + } + n.JDId = id + return nil +} diff --git a/integration-tests/deployment/devenv/environment.go b/integration-tests/deployment/devenv/environment.go new file mode 100644 index 00000000000..a62f7f5e84f --- /dev/null +++ b/integration-tests/deployment/devenv/environment.go @@ -0,0 +1,54 @@ +package devenv + +import ( + "context" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment" +) + +const ( + DevEnv = "devenv" +) + +type EnvironmentConfig struct { + Chains []ChainConfig + nodeInfo []NodeInfo + JDConfig JDConfig +} + +func NewEnvironment(ctx context.Context, lggr logger.Logger, config EnvironmentConfig) (*deployment.Environment, *DON, error) { + chains, err := NewChains(lggr, config.Chains) + if err != nil { + return nil, nil, fmt.Errorf("failed to create chains: %w", err) + } + offChain, err := NewJDClient(config.JDConfig) + if err != nil { + return nil, nil, fmt.Errorf("failed to create JD client: %w", err) + } + + jd, ok := offChain.(JobDistributor) + if !ok { + return nil, nil, fmt.Errorf("offchain client does not implement JobDistributor") + } + don, err := NewRegisteredDON(ctx, config.nodeInfo, jd) + if err != nil { + return nil, nil, fmt.Errorf("failed to create registered DON: %w", err) + } + nodeIDs := don.NodeIds() + + err = don.CreateSupportedChains(ctx, config.Chains) + if err != nil { + return nil, nil, err + } + + return &deployment.Environment{ + Name: DevEnv, + Offchain: offChain, + NodeIDs: nodeIDs, + Chains: chains, + Logger: lggr, + }, don, nil +} diff --git a/integration-tests/deployment/devenv/jd.go b/integration-tests/deployment/devenv/jd.go new file mode 100644 index 00000000000..671e6e4cea3 --- /dev/null +++ b/integration-tests/deployment/devenv/jd.go @@ -0,0 +1,71 @@ +package devenv + +import ( + "context" + "fmt" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment" + csav1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/csa/v1" + jobv1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/job/v1" + nodev1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/node/v1" +) + +type JDConfig struct { + GRPC string + WSRPC string + creds credentials.TransportCredentials +} + +func NewJDConnection(cfg JDConfig) (*grpc.ClientConn, error) { + var opts []grpc.DialOption + // TODO: add auth details + if cfg.creds != nil { + opts = append(opts, grpc.WithTransportCredentials(cfg.creds)) + } else { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + + } + + conn, err := grpc.NewClient(cfg.GRPC, opts...) + if err != nil { + return nil, fmt.Errorf("failed to connect Job Distributor service. Err: %w", err) + } + + return conn, nil +} + +type JobDistributor struct { + WSRPC string + nodev1.NodeServiceClient + jobv1.JobServiceClient + csav1.CSAServiceClient +} + +func NewJDClient(cfg JDConfig) (deployment.OffchainClient, error) { + conn, err := NewJDConnection(cfg) + if err != nil { + return nil, fmt.Errorf("failed to connect Job Distributor service. Err: %w", err) + } + return JobDistributor{ + WSRPC: cfg.WSRPC, + NodeServiceClient: nodev1.NewNodeServiceClient(conn), + JobServiceClient: jobv1.NewJobServiceClient(conn), + CSAServiceClient: csav1.NewCSAServiceClient(conn), + }, err +} + +func (jd JobDistributor) GetCSAPublicKey(ctx context.Context) (string, error) { + keypairs, err := jd.ListKeypairs(ctx, &csav1.ListKeypairsRequest{}) + if err != nil { + return "", err + } + if keypairs == nil || len(keypairs.Keypairs) == 0 { + return "", fmt.Errorf("no keypairs found") + } + csakey := keypairs.Keypairs[0].PublicKey + return csakey, nil +} diff --git a/integration-tests/deployment/environment.go b/integration-tests/deployment/environment.go index 692d7744b12..8d8fc909a93 100644 --- a/integration-tests/deployment/environment.go +++ b/integration-tests/deployment/environment.go @@ -45,8 +45,9 @@ type Chain struct { Selector uint64 Client OnchainClient // Note the Sign function can be abstract supporting a variety of key storage mechanisms (e.g. KMS etc). - DeployerKey *bind.TransactOpts - Confirm func(tx *types.Transaction) (uint64, error) + DeployerKey *bind.TransactOpts + LatestBlockNum func(ctx context.Context) (uint64, error) + Confirm func(tx *types.Transaction) (uint64, error) } type Environment struct { @@ -259,3 +260,8 @@ func NodeInfo(nodeIDs []string, oc OffchainClient) (Nodes, error) { return nodes, nil } + +type CapabilityRegistryConfig struct { + EVMChainID uint64 // chain id of the chain the CR is deployed on + Contract common.Address // address of the CR contract +} diff --git a/integration-tests/deployment/helpers.go b/integration-tests/deployment/helpers.go new file mode 100644 index 00000000000..5e81eadd39d --- /dev/null +++ b/integration-tests/deployment/helpers.go @@ -0,0 +1,82 @@ +package deployment + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" +) + +func GetErrorReasonFromTx(client bind.ContractBackend, from common.Address, tx types.Transaction, receipt *types.Receipt) (string, error) { + call := ethereum.CallMsg{ + From: from, + To: tx.To(), + Data: tx.Data(), + Value: tx.Value(), + Gas: tx.Gas(), + GasPrice: tx.GasPrice(), + } + _, err := client.CallContract(context.Background(), call, receipt.BlockNumber) + if err != nil { + errorReason, err := parseError(err) + if err == nil { + return errorReason, nil + } + } + return "", fmt.Errorf("tx %s reverted with no reason", tx.Hash().Hex()) +} + +func parseError(txError error) (string, error) { + b, err := json.Marshal(txError) + if err != nil { + return "", err + } + var callErr struct { + Code int + Data string `json:"data"` + Message string `json:"message"` + } + if json.Unmarshal(b, &callErr) != nil { + return "", err + } + + if callErr.Data == "" && strings.Contains(callErr.Message, "missing trie node") { + return "", errors.Errorf("please use an archive node") + } + + return callErr.Data, nil +} + +func ParseErrorFromABI(errorString string, contractABI string) (string, error) { + parsedAbi, err := abi.JSON(strings.NewReader(contractABI)) + if err != nil { + return "", errors.Wrap(err, "error loading ABI") + } + errorString = strings.TrimPrefix(errorString, "Reverted ") + errorString = strings.TrimPrefix(errorString, "0x") + + data, err := hex.DecodeString(errorString) + if err != nil { + return "", errors.Wrap(err, "error decoding error string") + } + for errorName, abiError := range parsedAbi.Errors { + if bytes.Equal(data[:4], abiError.ID.Bytes()[:4]) { + // Found a matching error + v, err3 := abiError.Unpack(data) + if err3 != nil { + return "", errors.Wrap(err3, "error unpacking data") + } + return fmt.Sprintf("error is \"%v\" args %v\n", errorName, v), nil + } + } + return "", errors.New("error not found in ABI") +} diff --git a/integration-tests/deployment/jd/node/v1/node.pb.go b/integration-tests/deployment/jd/node/v1/node.pb.go index 172e2856454..224109b8269 100644 --- a/integration-tests/deployment/jd/node/v1/node.pb.go +++ b/integration-tests/deployment/jd/node/v1/node.pb.go @@ -817,7 +817,7 @@ func (x *GetNodeResponse) GetNode() *Node { // * // ListNodesRequest is the request object for the ListNodes method. // -// Provide a filter to return a subset of data. Nodes can be filtered by: +// Provide a filter to return a subset of data. NodesByPeerID can be filtered by: // - ids - A list of node ids. // - archived - The archived state of the node. // - selectors - A list of selectors to filter nodes by their labels. diff --git a/integration-tests/deployment/memory/environment.go b/integration-tests/deployment/memory/environment.go index 30606f2e9e7..5ae94464940 100644 --- a/integration-tests/deployment/memory/environment.go +++ b/integration-tests/deployment/memory/environment.go @@ -26,7 +26,7 @@ type MemoryEnvironmentConfig struct { Chains int Nodes int Bootstraps int - RegistryConfig RegistryConfig + RegistryConfig deployment.CapabilityRegistryConfig } // Needed for environment variables on the node which point to prexisitng addresses. @@ -41,6 +41,9 @@ func NewMemoryChains(t *testing.T, numChains int) map[uint64]deployment.Chain { Selector: sel, Client: chain.Backend, DeployerKey: chain.DeployerKey, + LatestBlockNum: func(ctx context.Context) (uint64, error) { + return chain.Backend.Blockchain().CurrentBlock().Number.Uint64(), nil + }, Confirm: func(tx *types.Transaction) (uint64, error) { if tx == nil { return 0, fmt.Errorf("tx was nil, nothing to confirm") @@ -63,7 +66,7 @@ func NewMemoryChains(t *testing.T, numChains int) map[uint64]deployment.Chain { return chains } -func NewNodes(t *testing.T, logLevel zapcore.Level, chains map[uint64]deployment.Chain, numNodes, numBootstraps int, registryConfig RegistryConfig) map[string]Node { +func NewNodes(t *testing.T, logLevel zapcore.Level, chains map[uint64]deployment.Chain, numNodes, numBootstraps int, registryConfig deployment.CapabilityRegistryConfig) map[string]Node { mchains := make(map[uint64]EVMChain) for _, chain := range chains { evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector) @@ -114,7 +117,7 @@ func NewMemoryEnvironmentFromChainsNodes(t *testing.T, //func NewMemoryEnvironmentExistingChains(t *testing.T, lggr logger.Logger, // chains map[uint64]deployment.Chain, config MemoryEnvironmentConfig) deployment.Environment { -// nodes := NewNodes(t, chains, config.Nodes, config.Bootstraps, config.RegistryConfig) +// nodes := NewNodes(t, chains, config.Nodes, config.Bootstraps, config.CapabilityRegistryConfig) // var nodeIDs []string // for id := range nodes { // nodeIDs = append(nodeIDs, id) diff --git a/integration-tests/deployment/memory/node.go b/integration-tests/deployment/memory/node.go index 6512788baf7..1befa38dc69 100644 --- a/integration-tests/deployment/memory/node.go +++ b/integration-tests/deployment/memory/node.go @@ -22,6 +22,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + "github.com/smartcontractkit/chainlink/integration-tests/deployment" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" v2toml "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" @@ -75,11 +76,6 @@ func (n Node) ReplayLogs(chains map[uint64]uint64) error { return nil } -type RegistryConfig struct { - EVMChainID uint64 - Contract common.Address -} - // Creates a CL node which is: // - Configured for OCR // - Configured for the chains specified @@ -90,7 +86,7 @@ func NewNode( chains map[uint64]EVMChain, logLevel zapcore.Level, bootstrap bool, - registryConfig RegistryConfig, + registryConfig deployment.CapabilityRegistryConfig, ) *Node { // Do not want to load fixtures as they contain a dummy chainID. // Create database and initial configuration. diff --git a/integration-tests/deployment/memory/node_test.go b/integration-tests/deployment/memory/node_test.go index d64c7717fc1..4a791bfc1fb 100644 --- a/integration-tests/deployment/memory/node_test.go +++ b/integration-tests/deployment/memory/node_test.go @@ -6,12 +6,14 @@ import ( "github.com/hashicorp/consul/sdk/freeport" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment" ) func TestNode(t *testing.T) { chains := GenerateChains(t, 3) ports := freeport.GetN(t, 1) - node := NewNode(t, ports[0], chains, zapcore.DebugLevel, false, RegistryConfig{}) + node := NewNode(t, ports[0], chains, zapcore.DebugLevel, false, deployment.CapabilityRegistryConfig{}) // We expect 3 transmitter keys keys, err := node.App.GetKeyStore().Eth().GetAll(Context(t)) require.NoError(t, err) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 6816cb32bab..d802c6eacc9 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -20,6 +20,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/hashicorp/consul/sdk v0.16.0 + github.com/hashicorp/go-multierror v1.1.1 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/manifoldco/promptui v0.9.0 @@ -31,6 +32,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/scylladb/go-reflectx v1.0.1 github.com/segmentio/ksuid v1.0.4 + github.com/sethvargo/go-retry v0.2.4 github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240910151738-3f318badcfb5 @@ -47,6 +49,7 @@ require ( github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + github.com/subosito/gotenv v1.6.0 github.com/test-go/testify v1.1.4 github.com/testcontainers/testcontainers-go v0.28.0 github.com/umbracle/ethgo v0.1.3 @@ -283,7 +286,6 @@ require ( github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect @@ -422,7 +424,6 @@ require ( github.com/status-im/keycard-go v0.2.0 // indirect github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 46b82a1e1ae..f08ac194243 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -29,6 +29,8 @@ require ( go.uber.org/ratelimit v0.3.0 ) +require github.com/AlekSi/pointer v1.1.0 // indirect + require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go new file mode 100644 index 00000000000..d6c65f3c0e6 --- /dev/null +++ b/integration-tests/smoke/ccip_test.go @@ -0,0 +1,89 @@ +package smoke + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + + ccipdeployment "github.com/smartcontractkit/chainlink/integration-tests/deployment/ccip" + "github.com/smartcontractkit/chainlink/integration-tests/deployment/ccip/changeset" + jobv1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/job/v1" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func Test0002_InitialDeployOnLocal(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := ccipdeployment.Context(t) + tenv := ccipdeployment.NewDeployedLocalDevEnvironment(t, lggr) + e := tenv.Env + nodes := tenv.Nodes + + state, err := ccipdeployment.LoadOnchainState(tenv.Env, tenv.Ab) + require.NoError(t, err) + + // Apply migration + output, err := changeset.Apply0002(tenv.Env, ccipdeployment.DeployCCIPContractConfig{ + HomeChainSel: tenv.HomeChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + // Capreg/config already exist. + CCIPOnChainState: state, + }) + require.NoError(t, err) + // Get new state after migration. + state, err = ccipdeployment.LoadOnchainState(e, output.AddressBook) + require.NoError(t, err) + + // Apply the jobs. + nodeIdToJobIds := make(map[string][]string) + for nodeID, jobs := range output.JobSpecs { + nodeIdToJobIds[nodeID] = make([]string, 0, len(jobs)) + for _, job := range jobs { + res, err := e.Offchain.ProposeJob(ctx, + &jobv1.ProposeJobRequest{ + NodeId: nodeID, + Spec: job, + }) + require.NoError(t, err) + require.NotNil(t, res.Proposal) + nodeIdToJobIds[nodeID] = append(nodeIdToJobIds[nodeID], res.Proposal.JobId) + } + } + + // Accept all the jobs for this node. + for _, n := range nodes { + jobsToAccept, exists := nodeIdToJobIds[n.NodeId] + require.True(t, exists, "node %s has no jobs to accept", n.NodeId) + for i, jobID := range jobsToAccept { + require.NoError(t, n.AcceptJob(ctx, strconv.Itoa(i+1)), "node -%s failed to accept job %s", n.Name, jobID) + } + } + t.Log("Jobs accepted") + + // Add all lanes + require.NoError(t, ccipdeployment.AddLanesForAll(e, state)) + // Need to keep track of the block number for each chain so that event subscription can be done from that block. + startBlocks := make(map[uint64]*uint64) + // Send a message from each chain to every other chain. + expectedSeqNum := make(map[uint64]uint64) + for src := range e.Chains { + for dest, destChain := range e.Chains { + if src == dest { + continue + } + block, err := destChain.LatestBlockNum(testcontext.Get(t)) + require.NoError(t, err) + startBlocks[dest] = &block + seqNum := ccipdeployment.SendRequest(t, e, state, src, dest, false) + expectedSeqNum[dest] = seqNum + } + } + + // Wait for all commit reports to land. + ccipdeployment.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) + + // Wait for all exec reports to land + ccipdeployment.ConfirmExecWithSeqNrForAll(t, e, state, expectedSeqNum, startBlocks) +} diff --git a/integration-tests/smoke/job_distributor_test.go b/integration-tests/smoke/job_distributor_test.go index 09ad7c2812c..2d1657faf7e 100644 --- a/integration-tests/smoke/job_distributor_test.go +++ b/integration-tests/smoke/job_distributor_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" + "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" @@ -36,14 +37,14 @@ func TestRegisteringMultipleJobDistributor(t *testing.T) { require.NoError(t, err) ctx := context.Background() - err = env.ClCluster.Nodes[0].GraphqlAPI.CreateJobDistributor(ctx, graphqlClient.FeedsManagerInput{ + _, err = env.ClCluster.Nodes[0].GraphqlAPI.CreateJobDistributor(ctx, graphqlClient.JobDistributorInput{ Name: "job-distributor-1", Uri: "http://job-distributor-1:8080", PublicKey: "54227538d9352e0a24550a80ab6a7af6e4f1ffbb8a604e913cbb81c484a7f97d", }) require.NoError(t, err, "Creating first job distributor in chainlink node shouldn't fail") - err = env.ClCluster.Nodes[0].GraphqlAPI.CreateJobDistributor(ctx, graphqlClient.FeedsManagerInput{ + _, err = env.ClCluster.Nodes[0].GraphqlAPI.CreateJobDistributor(ctx, graphqlClient.JobDistributorInput{ Name: "job-distributor-2", Uri: "http://job-distributor-2:8080", PublicKey: "37346b7ea98af21e1309847e00f772826ac3689fe990b1920d01efc58ad2f250", diff --git a/integration-tests/testconfig/README.md b/integration-tests/testconfig/README.md index 2959c1af02c..c698281b76e 100644 --- a/integration-tests/testconfig/README.md +++ b/integration-tests/testconfig/README.md @@ -230,7 +230,7 @@ For local testing, it is advisable to place these variables in the `overrides.to ## Embedded config -Because Go automatically excludes TOML files during the compilation of binaries, we must take deliberate steps to include our configuration files in the compiled binary. This can be accomplished by using a custom build tag `-o embed`. Implementing this tag will incorporate all the default configurations located in the `./testconfig` folder directly into the binary. Therefore, when executing tests from the binary, you'll only need to supply the `overrides.toml` file. This file should list only the settings you wish to modify; all other configurations will be sourced from the embedded configurations. You can access these embedded configurations [here](.integration-tests/testconfig/configs_embed.go). +Because Go automatically excludes TOML files during the compilation of binaries, we must take deliberate steps to include our configuration files in the compiled binary. This can be accomplished by using a custom build tag `-o embed`. Implementing this tag will incorporate all the default configurations located in the `./testconfig` folder directly into the binary. Therefore, when executing tests from the binary, you'll only need to supply the `overrides.toml` file. This file should list only the settings you wish to modify; all other configurations will be sourced from the embedded configurations. You can access these embedded configurations [here](./configs_embed.go). ## To bear in mind diff --git a/integration-tests/testconfig/ccip/ccip.toml b/integration-tests/testconfig/ccip/ccip.toml new file mode 100644 index 00000000000..cc8d82f2468 --- /dev/null +++ b/integration-tests/testconfig/ccip/ccip.toml @@ -0,0 +1,143 @@ +[Network] +selected_networks = ['SIMULATED_1', 'SIMULATED_2'] + +[Network.EVMNetworks.SIMULATED_1] +evm_name = 'chain-1337' +evm_chain_id = 1337 +evm_keys = [ + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", +] +evm_simulated = true +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 50000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_depth = 1 + +[Network.EVMNetworks.SIMULATED_2] +evm_name = 'chain-2337' +evm_chain_id = 2337 +evm_keys = [ + "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", +] +evm_simulated = true +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 50000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_depth = 1 + +[NodeConfig] +BaseConfigTOML = """ +[Feature] +FeedsManager = true +LogPoller = true +UICSAKeys = true + +[Log] +Level = 'debug' +JSONConsole = true + +[Log.File] +MaxSize = '0b' + +[WebServer] +AllowOrigins = '*' +HTTPPort = 6688 +SecureCookies = false +HTTPWriteTimeout = '3m' +SessionTimeout = '999h0m0s' + +[WebServer.RateLimit] +Authenticated = 2000 +Unauthenticated = 1000 + +[WebServer.TLS] +HTTPSPort = 0 + +[Database] +MaxIdleConns = 20 +MaxOpenConns = 40 +MigrateOnStartup = true + +[OCR2] +Enabled = true +ContractPollInterval = '5s' + +[OCR] +Enabled = false +DefaultTransactionQueueDepth = 200 + +[P2P] +[P2P.V2] +Enabled = true +ListenAddresses = ['0.0.0.0:6690'] +AnnounceAddresses = ['0.0.0.0:6690'] +DeltaDial = '500ms' +DeltaReconcile = '5s' +""" + +[CCIP] +[CCIP.CLNode] +NoOfPluginNodes = 4 +NoOfBootstraps = 1 + +[CCIP.PrivateEthereumNetworks.SIMULATED_1] +# either eth1 or eth2 (for post-Merge); for eth2 Prysm is used for consensus layer. +ethereum_version = "eth1" +# geth, besu, erigon or nethermind +execution_layer = "geth" +# eth2-only, if set to true environment startup will wait until at least 1 epoch has been finalised +wait_for_finalization=false + +[CCIP.PrivateEthereumNetworks.SIMULATED_1.EthereumChainConfig] +# eth2-only, the lower the value the faster the block production (3 is minimum) +seconds_per_slot = 3 +# eth2-only, the lower the value the faster the epoch finalisation (2 is minimum) +slots_per_epoch = 2 +# eht2-only, the lower tha value the faster the chain starts (10 is minimum) +genesis_delay = 15 +# eth2-only, number of validators +validator_count = 4 +chain_id = 1337 +# address that should be founded in genesis wih ETH +addresses_to_fund = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", +] + +[CCIP.PrivateEthereumNetworks.SIMULATED_1.EthereumChainConfig.HardForkEpochs] +# eth2-only, epoch at which chain will upgrade do Dencun or Deneb/Cancun (1 is minimum) +Deneb = 500 + +#[CCIP.Env.PrivateEthereumNetworks.SIMULATED_1.CustomDockerImages] +# custom docker image that will be used for execution layer client. It has to be one of: hyperledger/besu, nethermind/nethermind, thorax/erigon or ethereum/client-go. +# instead of using a specific tag you can also use "latest_available" to use latest published tag in Github or "latest_stable" to use latest stable release from Github +# (if corresponding Docker image on Docker Hub has not been published environment creation will fail). +#execution_layer="hyperledger/besu:latest_stable" + +[CCIP.PrivateEthereumNetworks.SIMULATED_2] +ethereum_version = "eth1" +execution_layer = "geth" + +[CCIP.PrivateEthereumNetworks.SIMULATED_2.EthereumChainConfig] +seconds_per_slot = 3 +slots_per_epoch = 2 +genesis_delay = 15 +validator_count = 4 +chain_id = 2337 +addresses_to_fund = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", +] + +[CCIP.PrivateEthereumNetworks.SIMULATED_2.EthereumChainConfig.HardForkEpochs] +Deneb = 500 \ No newline at end of file diff --git a/integration-tests/testconfig/ccip/config.go b/integration-tests/testconfig/ccip/config.go new file mode 100644 index 00000000000..a5b168bec2f --- /dev/null +++ b/integration-tests/testconfig/ccip/config.go @@ -0,0 +1,92 @@ +package ccip + +import ( + "github.com/AlekSi/pointer" + + ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + + "github.com/smartcontractkit/chainlink/integration-tests/client" +) + +const ( + E2E_JD_IMAGE = "E2E_JD_IMAGE" + E2E_JD_VERSION = "E2E_JD_VERSION" + E2E_JD_GRPC = "E2E_JD_GRPC" + E2E_JD_WSRPC = "E2E_JD_WSRPC" + DEFAULT_DB_NAME = "JD_DB" + DEFAULT_DB_VERSION = "14.1" +) + +type Config struct { + PrivateEthereumNetworks map[string]*ctfconfig.EthereumNetworkConfig `toml:",omitempty"` + CLNode *NodeConfig `toml:",omitempty"` + JobDistributorConfig JDConfig `toml:",omitempty"` +} + +type NodeConfig struct { + NoOfPluginNodes *int `toml:",omitempty"` + NoOfBootstraps *int `toml:",omitempty"` + ClientConfig *client.ChainlinkConfig `toml:",omitempty"` +} + +type JDConfig struct { + Image *string `toml:",omitempty"` + Version *string `toml:",omitempty"` + DBName *string `toml:",omitempty"` + DBVersion *string `toml:",omitempty"` + JDGRPC *string `toml:",omitempty"` + JDWSRPC *string `toml:",omitempty"` +} + +func (o *Config) Validate() error { + return nil +} + +// TODO: include all JD specific input in generic secret handling +func (o *Config) GetJDGRPC() string { + grpc := pointer.GetString(o.JobDistributorConfig.JDGRPC) + if grpc == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_GRPC) + } + return grpc +} + +func (o *Config) GetJDWSRPC() string { + wsrpc := pointer.GetString(o.JobDistributorConfig.JDWSRPC) + if wsrpc == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_WSRPC) + } + return wsrpc +} + +func (o *Config) GetJDImage() string { + image := pointer.GetString(o.JobDistributorConfig.Image) + if image == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_IMAGE) + } + return image +} + +func (o *Config) GetJDVersion() string { + version := pointer.GetString(o.JobDistributorConfig.Version) + if version == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_VERSION) + } + return version +} + +func (o *Config) GetJDDBName() string { + dbname := pointer.GetString(o.JobDistributorConfig.DBName) + if dbname == "" { + return DEFAULT_DB_NAME + } + return dbname +} + +func (o *Config) GetJDDBVersion() string { + dbversion := pointer.GetString(o.JobDistributorConfig.DBVersion) + if dbversion == "" { + return DEFAULT_DB_VERSION + } + return dbversion +} diff --git a/integration-tests/testconfig/configs_embed.go b/integration-tests/testconfig/configs_embed.go index 31303357a43..5de81acb7d9 100644 --- a/integration-tests/testconfig/configs_embed.go +++ b/integration-tests/testconfig/configs_embed.go @@ -18,6 +18,8 @@ import "embed" //go:embed vrf/vrf.toml //go:embed vrfv2/vrfv2.toml //go:embed vrfv2plus/vrfv2plus.toml +//go:embed ccip/ccip.toml + var embeddedConfigsFs embed.FS func init() { diff --git a/integration-tests/testconfig/testconfig.go b/integration-tests/testconfig/testconfig.go index 23d8d126c9d..fb692c56a75 100644 --- a/integration-tests/testconfig/testconfig.go +++ b/integration-tests/testconfig/testconfig.go @@ -27,6 +27,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" a_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/automation" + ccip_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ccip" f_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/functions" keeper_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/keeper" lp_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/log_poller" @@ -72,6 +73,10 @@ type Ocr2TestConfig interface { GetOCR2Config() *ocr_config.Config } +type CCIPTestConfig interface { + GetCCIPConfig() *ccip_config.Config +} + const ( E2E_TEST_DATA_STREAMS_URL_ENV = "E2E_TEST_DATA_STREAMS_URL" E2E_TEST_DATA_STREAMS_USERNAME_ENV = "E2E_TEST_DATA_STREAMS_USERNAME" @@ -91,6 +96,7 @@ type TestConfig struct { VRF *vrf_config.Config `toml:"VRF"` VRFv2 *vrfv2_config.Config `toml:"VRFv2"` VRFv2Plus *vrfv2plus_config.Config `toml:"VRFv2Plus"` + CCIP *ccip_config.Config `toml:"CCIP"` ConfigurationNames []string `toml:"-"` } @@ -204,6 +210,10 @@ func (c TestConfig) GetOCRConfig() *ocr_config.Config { return c.OCR } +func (c TestConfig) GetCCIPConfig() *ccip_config.Config { + return c.CCIP +} + func (c TestConfig) GetConfigurationNames() []string { return c.ConfigurationNames } @@ -259,6 +269,8 @@ const ( VRF Product = "vrf" VRFv2 Product = "vrfv2" VRFv2Plus Product = "vrfv2plus" + + CCIP Product = "ccip" ) const TestTypeEnvVarName = "TEST_TYPE" diff --git a/integration-tests/types/testconfigs.go b/integration-tests/types/testconfigs.go index ee9183589c2..e0b1f7cc03e 100644 --- a/integration-tests/types/testconfigs.go +++ b/integration-tests/types/testconfigs.go @@ -3,6 +3,7 @@ package types import ( ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" + tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" ) @@ -51,3 +52,9 @@ type Ocr2TestConfig interface { tc.CommonTestConfig tc.Ocr2TestConfig } + +type CCIPTestConfig interface { + ctf_config.GlobalTestConfig + tc.CommonTestConfig + tc.CCIPTestConfig +} diff --git a/integration-tests/web/sdk/client/client.go b/integration-tests/web/sdk/client/client.go index 74be13562e0..783a8a88565 100644 --- a/integration-tests/web/sdk/client/client.go +++ b/integration-tests/web/sdk/client/client.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" + "github.com/AlekSi/pointer" "github.com/Khan/genqlient/graphql" "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/client/doer" @@ -14,18 +15,19 @@ import ( ) type Client interface { - GetCSAKeys(ctx context.Context) (*generated.GetCSAKeysResponse, error) + FetchCSAPublicKey(ctx context.Context) (*string, error) + FetchP2PPeerID(ctx context.Context) (*string, error) + FetchAccountAddress(ctx context.Context, chainID string) (*string, error) + FetchOCR2KeyBundleID(ctx context.Context, chainType string) (string, error) GetJob(ctx context.Context, id string) (*generated.GetJobResponse, error) ListJobs(ctx context.Context, offset, limit int) (*generated.ListJobsResponse, error) - GetBridge(ctx context.Context, id string) (*generated.GetBridgeResponse, error) - ListBridges(ctx context.Context, offset, limit int) (*generated.ListBridgesResponse, error) GetJobDistributor(ctx context.Context, id string) (*generated.GetFeedsManagerResponse, error) ListJobDistributors(ctx context.Context) (*generated.ListFeedsManagersResponse, error) - CreateJobDistributor(ctx context.Context, cmd FeedsManagerInput) error - UpdateJobDistributor(ctx context.Context, id string, cmd FeedsManagerInput) error - CreateJobDistributorChainConfig(ctx context.Context, in CreateFeedsManagerChainConfigInput) error + CreateJobDistributor(ctx context.Context, cmd JobDistributorInput) (string, error) + UpdateJobDistributor(ctx context.Context, id string, cmd JobDistributorInput) error + CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) error GetJobProposal(ctx context.Context, id string) (*generated.GetJobProposalResponse, error) - ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*generated.ApproveJobProposalSpecResponse, error) + ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*JobProposalApprovalSuccessSpec, error) CancelJobProposalSpec(ctx context.Context, id string) (*generated.CancelJobProposalSpecResponse, error) RejectJobProposalSpec(ctx context.Context, id string) (*generated.RejectJobProposalSpecResponse, error) UpdateJobProposalSpecDefinition(ctx context.Context, id string, cmd generated.UpdateJobProposalSpecDefinitionInput) (*generated.UpdateJobProposalSpecDefinitionResponse, error) @@ -70,8 +72,58 @@ func New(baseURI string, creds Credentials) (Client, error) { return c, nil } -func (c *client) GetCSAKeys(ctx context.Context) (*generated.GetCSAKeysResponse, error) { - return generated.GetCSAKeys(ctx, c.gqlClient) +func (c *client) FetchCSAPublicKey(ctx context.Context) (*string, error) { + keys, err := generated.FetchCSAKeys(ctx, c.gqlClient) + if err != nil { + return nil, err + } + if keys == nil || len(keys.CsaKeys.GetResults()) == 0 { + return nil, fmt.Errorf("no CSA keys found") + } + return &keys.CsaKeys.GetResults()[0].PublicKey, nil +} + +func (c *client) FetchP2PPeerID(ctx context.Context) (*string, error) { + keys, err := generated.FetchP2PKeys(ctx, c.gqlClient) + if err != nil { + return nil, err + } + if keys == nil || len(keys.P2pKeys.GetResults()) == 0 { + return nil, fmt.Errorf("no P2P keys found") + } + return &keys.P2pKeys.GetResults()[0].PeerID, nil +} + +func (c *client) FetchOCR2KeyBundleID(ctx context.Context, chainType string) (string, error) { + keyBundles, err := generated.FetchOCR2KeyBundles(ctx, c.gqlClient) + if err != nil { + return "", err + } + if keyBundles == nil || len(keyBundles.GetOcr2KeyBundles().Results) == 0 { + return "", fmt.Errorf("no ocr2 keybundle found, check if ocr2 is enabled") + } + for _, keyBundle := range keyBundles.GetOcr2KeyBundles().Results { + if keyBundle.ChainType == generated.OCR2ChainType(chainType) { + return keyBundle.GetId(), nil + } + } + return "", fmt.Errorf("no ocr2 keybundle found for chain type %s", chainType) +} + +func (c *client) FetchAccountAddress(ctx context.Context, chainID string) (*string, error) { + keys, err := generated.FetchAccounts(ctx, c.gqlClient) + if err != nil { + return nil, err + } + if keys == nil || len(keys.EthKeys.GetResults()) == 0 { + return nil, fmt.Errorf("no accounts found") + } + for _, keyDetail := range keys.EthKeys.GetResults() { + if keyDetail.GetChain().Enabled && keyDetail.GetChain().Id == chainID { + return pointer.ToString(keyDetail.Address), nil + } + } + return nil, fmt.Errorf("no account found for chain %s", chainID) } func (c *client) GetJob(ctx context.Context, id string) (*generated.GetJobResponse, error) { @@ -98,17 +150,25 @@ func (c *client) ListJobDistributors(ctx context.Context) (*generated.ListFeedsM return generated.ListFeedsManagers(ctx, c.gqlClient) } -func (c *client) CreateJobDistributor(ctx context.Context, in FeedsManagerInput) error { +func (c *client) CreateJobDistributor(ctx context.Context, in JobDistributorInput) (string, error) { var cmd generated.CreateFeedsManagerInput err := DecodeInput(in, &cmd) if err != nil { - return err + return "", err } - _, err = generated.CreateFeedsManager(ctx, c.gqlClient, cmd) - return err + response, err := generated.CreateFeedsManager(ctx, c.gqlClient, cmd) + if err != nil { + return "", err + } + // Access the FeedsManager ID + if success, ok := response.GetCreateFeedsManager().(*generated.CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess); ok { + feedsManager := success.GetFeedsManager() + return feedsManager.GetId(), nil + } + return "", fmt.Errorf("failed to create feeds manager") } -func (c *client) UpdateJobDistributor(ctx context.Context, id string, in FeedsManagerInput) error { +func (c *client) UpdateJobDistributor(ctx context.Context, id string, in JobDistributorInput) error { var cmd generated.UpdateFeedsManagerInput err := DecodeInput(in, &cmd) if err != nil { @@ -118,7 +178,7 @@ func (c *client) UpdateJobDistributor(ctx context.Context, id string, in FeedsMa return err } -func (c *client) CreateJobDistributorChainConfig(ctx context.Context, in CreateFeedsManagerChainConfigInput) error { +func (c *client) CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) error { var cmd generated.CreateFeedsManagerChainConfigInput err := DecodeInput(in, &cmd) if err != nil { @@ -132,8 +192,22 @@ func (c *client) GetJobProposal(ctx context.Context, id string) (*generated.GetJ return generated.GetJobProposal(ctx, c.gqlClient, id) } -func (c *client) ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*generated.ApproveJobProposalSpecResponse, error) { - return generated.ApproveJobProposalSpec(ctx, c.gqlClient, id, force) +func (c *client) ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*JobProposalApprovalSuccessSpec, error) { + res, err := generated.ApproveJobProposalSpec(ctx, c.gqlClient, id, force) + if err != nil { + return nil, err + } + if success, ok := res.GetApproveJobProposalSpec().(*generated.ApproveJobProposalSpecApproveJobProposalSpecApproveJobProposalSpecSuccess); ok { + var cmd JobProposalApprovalSuccessSpec + if success.Spec.Status == generated.SpecStatusApproved { + err := DecodeInput(success.Spec, &cmd) + if err != nil { + return nil, fmt.Errorf("failed to decode job proposal spec: %w ; and job proposal spec not approved", err) + } + return &cmd, nil + } + } + return nil, fmt.Errorf("failed to approve job proposal spec") } func (c *client) CancelJobProposalSpec(ctx context.Context, id string) (*generated.CancelJobProposalSpecResponse, error) { diff --git a/integration-tests/web/sdk/client/types.go b/integration-tests/web/sdk/client/types.go index 49330ee621b..d213ee161c6 100644 --- a/integration-tests/web/sdk/client/types.go +++ b/integration-tests/web/sdk/client/types.go @@ -7,14 +7,14 @@ import ( "reflect" ) -type FeedsManagerInput struct { +type JobDistributorInput struct { Name string `json:"name"` Uri string `json:"uri"` PublicKey string `json:"publicKey"` } -type CreateFeedsManagerChainConfigInput struct { - FeedsManagerID string `json:"feedsManagerID"` +type JobDistributorChainConfigInput struct { + JobDistributorID string `json:"feedsManagerID"` ChainID string `json:"chainID"` ChainType string `json:"chainType"` AccountAddr string `json:"accountAddr"` @@ -35,6 +35,16 @@ type CreateFeedsManagerChainConfigInput struct { Ocr2Plugins string `json:"ocr2Plugins"` } +type JobProposalApprovalSuccessSpec struct { + Id string `json:"id"` + Definition string `json:"definition"` + Version int `json:"version"` + Status string `json:"status"` + StatusUpdatedAt string `json:"statusUpdatedAt"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + func DecodeInput(in, out any) error { if reflect.TypeOf(out).Kind() != reflect.Ptr || reflect.ValueOf(out).IsNil() { return fmt.Errorf("out type must be a non-nil pointer") diff --git a/integration-tests/web/sdk/client/types_test.go b/integration-tests/web/sdk/client/types_test.go index f67cb099543..dd9b48274da 100644 --- a/integration-tests/web/sdk/client/types_test.go +++ b/integration-tests/web/sdk/client/types_test.go @@ -19,7 +19,7 @@ func TestDecodeInput(t *testing.T) { }{ { name: "success", - args: args{&FeedsManagerInput{ + args: args{&JobDistributorInput{ Name: "name", Uri: "uri", PublicKey: "publicKey", @@ -29,7 +29,7 @@ func TestDecodeInput(t *testing.T) { }, { name: "non-pointer", - args: args{&FeedsManagerInput{ + args: args{&JobDistributorInput{ Name: "name", Uri: "uri", PublicKey: "publicKey", @@ -39,7 +39,7 @@ func TestDecodeInput(t *testing.T) { }, { name: "incorrect type", - args: args{&FeedsManagerInput{ + args: args{&JobDistributorInput{ Name: "name", Uri: "uri", PublicKey: "publicKey", @@ -49,7 +49,7 @@ func TestDecodeInput(t *testing.T) { }, { name: "success", - args: args{&FeedsManagerInput{ + args: args{&JobDistributorInput{ Name: "name", Uri: "uri", PublicKey: "publicKey", diff --git a/integration-tests/web/sdk/internal/generated/generated.go b/integration-tests/web/sdk/internal/generated/generated.go index b70fc156146..8efde4c453f 100644 --- a/integration-tests/web/sdk/internal/generated/generated.go +++ b/integration-tests/web/sdk/internal/generated/generated.go @@ -1032,6 +1032,7 @@ func (v *CreateFeedsManagerChainConfigResponse) __premarshalJSON() (*__premarsha // // CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload is implemented by the following types: // CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess +// CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError // CreateFeedsManagerCreateFeedsManagerInputErrors // CreateFeedsManagerCreateFeedsManagerNotFoundError // CreateFeedsManagerCreateFeedsManagerSingleFeedsManagerError @@ -1043,6 +1044,8 @@ type CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload interface { func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { } +func (v *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { +} func (v *CreateFeedsManagerCreateFeedsManagerInputErrors) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { } func (v *CreateFeedsManagerCreateFeedsManagerNotFoundError) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { @@ -1067,6 +1070,9 @@ func __unmarshalCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload(b case "CreateFeedsManagerSuccess": *v = new(CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess) return json.Unmarshal(b, *v) + case "DuplicateFeedsManagerError": + *v = new(CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError) + return json.Unmarshal(b, *v) case "InputErrors": *v = new(CreateFeedsManagerCreateFeedsManagerInputErrors) return json.Unmarshal(b, *v) @@ -1097,6 +1103,14 @@ func __marshalCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload(v *C *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess }{typename, v} return json.Marshal(result) + case *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError: + typename = "DuplicateFeedsManagerError" + + result := struct { + TypeName string `json:"__typename"` + *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError + }{typename, v} + return json.Marshal(result) case *CreateFeedsManagerCreateFeedsManagerInputErrors: typename = "InputErrors" @@ -1239,6 +1253,16 @@ func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManag return &retval, nil } +// CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError includes the requested fields of the GraphQL type DuplicateFeedsManagerError. +type CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError struct { + Typename string `json:"__typename"` +} + +// GetTypename returns CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError.Typename, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError) GetTypename() string { + return v.Typename +} + // CreateFeedsManagerCreateFeedsManagerInputErrors includes the requested fields of the GraphQL type InputErrors. type CreateFeedsManagerCreateFeedsManagerInputErrors struct { Typename string `json:"__typename"` @@ -1437,6 +1461,186 @@ func (v *FeedsManagerParts) GetIsConnectionActive() bool { return v.IsConnection // GetCreatedAt returns FeedsManagerParts.CreatedAt, and is useful for accessing the field via an interface. func (v *FeedsManagerParts) GetCreatedAt() string { return v.CreatedAt } +// FetchAccountsEthKeysEthKeysPayload includes the requested fields of the GraphQL type EthKeysPayload. +type FetchAccountsEthKeysEthKeysPayload struct { + Results []FetchAccountsEthKeysEthKeysPayloadResultsEthKey `json:"results"` +} + +// GetResults returns FetchAccountsEthKeysEthKeysPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayload) GetResults() []FetchAccountsEthKeysEthKeysPayloadResultsEthKey { + return v.Results +} + +// FetchAccountsEthKeysEthKeysPayloadResultsEthKey includes the requested fields of the GraphQL type EthKey. +type FetchAccountsEthKeysEthKeysPayloadResultsEthKey struct { + Address string `json:"address"` + IsDisabled bool `json:"isDisabled"` + Chain FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain `json:"chain"` + EthBalance string `json:"ethBalance"` + LinkBalance string `json:"linkBalance"` +} + +// GetAddress returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.Address, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetAddress() string { return v.Address } + +// GetIsDisabled returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.IsDisabled, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetIsDisabled() bool { return v.IsDisabled } + +// GetChain returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.Chain, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetChain() FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain { + return v.Chain +} + +// GetEthBalance returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.EthBalance, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetEthBalance() string { return v.EthBalance } + +// GetLinkBalance returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.LinkBalance, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetLinkBalance() string { + return v.LinkBalance +} + +// FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain includes the requested fields of the GraphQL type Chain. +type FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain struct { + Id string `json:"id"` + Enabled bool `json:"enabled"` +} + +// GetId returns FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain.Id, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain) GetId() string { return v.Id } + +// GetEnabled returns FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain.Enabled, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain) GetEnabled() bool { return v.Enabled } + +// FetchAccountsResponse is returned by FetchAccounts on success. +type FetchAccountsResponse struct { + EthKeys FetchAccountsEthKeysEthKeysPayload `json:"ethKeys"` +} + +// GetEthKeys returns FetchAccountsResponse.EthKeys, and is useful for accessing the field via an interface. +func (v *FetchAccountsResponse) GetEthKeys() FetchAccountsEthKeysEthKeysPayload { return v.EthKeys } + +// FetchCSAKeysCsaKeysCSAKeysPayload includes the requested fields of the GraphQL type CSAKeysPayload. +type FetchCSAKeysCsaKeysCSAKeysPayload struct { + Results []FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey `json:"results"` +} + +// GetResults returns FetchCSAKeysCsaKeysCSAKeysPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayload) GetResults() []FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey { + return v.Results +} + +// FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey includes the requested fields of the GraphQL type CSAKey. +type FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey struct { + Id string `json:"id"` + PublicKey string `json:"publicKey"` + Version int `json:"version"` +} + +// GetId returns FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Id, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetId() string { return v.Id } + +// GetPublicKey returns FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.PublicKey, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetPublicKey() string { return v.PublicKey } + +// GetVersion returns FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Version, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetVersion() int { return v.Version } + +// FetchCSAKeysResponse is returned by FetchCSAKeys on success. +type FetchCSAKeysResponse struct { + CsaKeys FetchCSAKeysCsaKeysCSAKeysPayload `json:"csaKeys"` +} + +// GetCsaKeys returns FetchCSAKeysResponse.CsaKeys, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysResponse) GetCsaKeys() FetchCSAKeysCsaKeysCSAKeysPayload { return v.CsaKeys } + +// FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload includes the requested fields of the GraphQL type OCR2KeyBundlesPayload. +type FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload struct { + Results []FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle `json:"results"` +} + +// GetResults returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload) GetResults() []FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle { + return v.Results +} + +// FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle includes the requested fields of the GraphQL type OCR2KeyBundle. +type FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle struct { + Id string `json:"id"` + ChainType OCR2ChainType `json:"chainType"` + ConfigPublicKey string `json:"configPublicKey"` + OnChainPublicKey string `json:"onChainPublicKey"` + OffChainPublicKey string `json:"offChainPublicKey"` +} + +// GetId returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.Id, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetId() string { + return v.Id +} + +// GetChainType returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.ChainType, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetChainType() OCR2ChainType { + return v.ChainType +} + +// GetConfigPublicKey returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.ConfigPublicKey, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetConfigPublicKey() string { + return v.ConfigPublicKey +} + +// GetOnChainPublicKey returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.OnChainPublicKey, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetOnChainPublicKey() string { + return v.OnChainPublicKey +} + +// GetOffChainPublicKey returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.OffChainPublicKey, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetOffChainPublicKey() string { + return v.OffChainPublicKey +} + +// FetchOCR2KeyBundlesResponse is returned by FetchOCR2KeyBundles on success. +type FetchOCR2KeyBundlesResponse struct { + Ocr2KeyBundles FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload `json:"ocr2KeyBundles"` +} + +// GetOcr2KeyBundles returns FetchOCR2KeyBundlesResponse.Ocr2KeyBundles, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesResponse) GetOcr2KeyBundles() FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload { + return v.Ocr2KeyBundles +} + +// FetchP2PKeysP2pKeysP2PKeysPayload includes the requested fields of the GraphQL type P2PKeysPayload. +type FetchP2PKeysP2pKeysP2PKeysPayload struct { + Results []FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey `json:"results"` +} + +// GetResults returns FetchP2PKeysP2pKeysP2PKeysPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayload) GetResults() []FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey { + return v.Results +} + +// FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey includes the requested fields of the GraphQL type P2PKey. +type FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey struct { + Id string `json:"id"` + PeerID string `json:"peerID"` + PublicKey string `json:"publicKey"` +} + +// GetId returns FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey.Id, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey) GetId() string { return v.Id } + +// GetPeerID returns FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey.PeerID, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey) GetPeerID() string { return v.PeerID } + +// GetPublicKey returns FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey.PublicKey, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey) GetPublicKey() string { return v.PublicKey } + +// FetchP2PKeysResponse is returned by FetchP2PKeys on success. +type FetchP2PKeysResponse struct { + P2pKeys FetchP2PKeysP2pKeysP2PKeysPayload `json:"p2pKeys"` +} + +// GetP2pKeys returns FetchP2PKeysResponse.P2pKeys, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysResponse) GetP2pKeys() FetchP2PKeysP2pKeysP2PKeysPayload { return v.P2pKeys } + // GetBridgeBridge includes the requested fields of the GraphQL type Bridge. type GetBridgeBridge struct { Typename string `json:"__typename"` @@ -1696,40 +1900,6 @@ func (v *GetBridgeResponse) __premarshalJSON() (*__premarshalGetBridgeResponse, return &retval, nil } -// GetCSAKeysCsaKeysCSAKeysPayload includes the requested fields of the GraphQL type CSAKeysPayload. -type GetCSAKeysCsaKeysCSAKeysPayload struct { - Results []GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey `json:"results"` -} - -// GetResults returns GetCSAKeysCsaKeysCSAKeysPayload.Results, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayload) GetResults() []GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey { - return v.Results -} - -// GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey includes the requested fields of the GraphQL type CSAKey. -type GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey struct { - Id string `json:"id"` - PublicKey string `json:"publicKey"` - Version int `json:"version"` -} - -// GetId returns GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Id, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetId() string { return v.Id } - -// GetPublicKey returns GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.PublicKey, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetPublicKey() string { return v.PublicKey } - -// GetVersion returns GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Version, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetVersion() int { return v.Version } - -// GetCSAKeysResponse is returned by GetCSAKeys on success. -type GetCSAKeysResponse struct { - CsaKeys GetCSAKeysCsaKeysCSAKeysPayload `json:"csaKeys"` -} - -// GetCsaKeys returns GetCSAKeysResponse.CsaKeys, and is useful for accessing the field via an interface. -func (v *GetCSAKeysResponse) GetCsaKeys() GetCSAKeysCsaKeysCSAKeysPayload { return v.CsaKeys } - // GetFeedsManagerFeedsManager includes the requested fields of the GraphQL type FeedsManager. type GetFeedsManagerFeedsManager struct { Typename string `json:"__typename"` @@ -3712,6 +3882,16 @@ type ListJobsResponse struct { // GetJobs returns ListJobsResponse.Jobs, and is useful for accessing the field via an interface. func (v *ListJobsResponse) GetJobs() ListJobsJobsJobsPayload { return v.Jobs } +type OCR2ChainType string + +const ( + OCR2ChainTypeEvm OCR2ChainType = "EVM" + OCR2ChainTypeCosmos OCR2ChainType = "COSMOS" + OCR2ChainTypeSolana OCR2ChainType = "SOLANA" + OCR2ChainTypeStarknet OCR2ChainType = "STARKNET" + OCR2ChainTypeAptos OCR2ChainType = "APTOS" +) + // #################### // Jobs and Job Proposals // #################### @@ -4962,44 +5142,35 @@ func CreateFeedsManagerChainConfig( return &data_, err_ } -// The query or mutation executed by GetBridge. -const GetBridge_Operation = ` -query GetBridge ($id: ID!) { - bridge(id: $id) { - __typename - ... BridgeParts - ... on NotFoundError { - message - code +// The query or mutation executed by FetchAccounts. +const FetchAccounts_Operation = ` +query FetchAccounts { + ethKeys { + results { + address + isDisabled + chain { + id + enabled + } + ethBalance + linkBalance } } } -fragment BridgeParts on Bridge { - id - name - url - confirmations - outgoingToken - minimumContractPayment - createdAt -} ` -func GetBridge( +func FetchAccounts( ctx_ context.Context, client_ graphql.Client, - id string, -) (*GetBridgeResponse, error) { +) (*FetchAccountsResponse, error) { req_ := &graphql.Request{ - OpName: "GetBridge", - Query: GetBridge_Operation, - Variables: &__GetBridgeInput{ - Id: id, - }, + OpName: "FetchAccounts", + Query: FetchAccounts_Operation, } var err_ error - var data_ GetBridgeResponse + var data_ FetchAccountsResponse resp_ := &graphql.Response{Data: &data_} err_ = client_.MakeRequest( @@ -5011,9 +5182,9 @@ func GetBridge( return &data_, err_ } -// The query or mutation executed by GetCSAKeys. -const GetCSAKeys_Operation = ` -query GetCSAKeys { +// The query or mutation executed by FetchCSAKeys. +const FetchCSAKeys_Operation = ` +query FetchCSAKeys { csaKeys { results { id @@ -5024,17 +5195,138 @@ query GetCSAKeys { } ` -func GetCSAKeys( +func FetchCSAKeys( + ctx_ context.Context, + client_ graphql.Client, +) (*FetchCSAKeysResponse, error) { + req_ := &graphql.Request{ + OpName: "FetchCSAKeys", + Query: FetchCSAKeys_Operation, + } + var err_ error + + var data_ FetchCSAKeysResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by FetchOCR2KeyBundles. +const FetchOCR2KeyBundles_Operation = ` +query FetchOCR2KeyBundles { + ocr2KeyBundles { + results { + id + chainType + configPublicKey + onChainPublicKey + offChainPublicKey + } + } +} +` + +func FetchOCR2KeyBundles( + ctx_ context.Context, + client_ graphql.Client, +) (*FetchOCR2KeyBundlesResponse, error) { + req_ := &graphql.Request{ + OpName: "FetchOCR2KeyBundles", + Query: FetchOCR2KeyBundles_Operation, + } + var err_ error + + var data_ FetchOCR2KeyBundlesResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by FetchP2PKeys. +const FetchP2PKeys_Operation = ` +query FetchP2PKeys { + p2pKeys { + results { + id + peerID + publicKey + } + } +} +` + +func FetchP2PKeys( ctx_ context.Context, client_ graphql.Client, -) (*GetCSAKeysResponse, error) { +) (*FetchP2PKeysResponse, error) { req_ := &graphql.Request{ - OpName: "GetCSAKeys", - Query: GetCSAKeys_Operation, + OpName: "FetchP2PKeys", + Query: FetchP2PKeys_Operation, } var err_ error - var data_ GetCSAKeysResponse + var data_ FetchP2PKeysResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by GetBridge. +const GetBridge_Operation = ` +query GetBridge ($id: ID!) { + bridge(id: $id) { + __typename + ... BridgeParts + ... on NotFoundError { + message + code + } + } +} +fragment BridgeParts on Bridge { + id + name + url + confirmations + outgoingToken + minimumContractPayment + createdAt +} +` + +func GetBridge( + ctx_ context.Context, + client_ graphql.Client, + id string, +) (*GetBridgeResponse, error) { + req_ := &graphql.Request{ + OpName: "GetBridge", + Query: GetBridge_Operation, + Variables: &__GetBridgeInput{ + Id: id, + }, + } + var err_ error + + var data_ GetBridgeResponse resp_ := &graphql.Response{Data: &data_} err_ = client_.MakeRequest( diff --git a/integration-tests/web/sdk/internal/genqlient.graphql b/integration-tests/web/sdk/internal/genqlient.graphql index c6307f916a3..cd1912b88cf 100644 --- a/integration-tests/web/sdk/internal/genqlient.graphql +++ b/integration-tests/web/sdk/internal/genqlient.graphql @@ -2,118 +2,168 @@ # CSA Keys ##################### -query GetCSAKeys { - csaKeys { - results { - id - publicKey - version +query FetchCSAKeys { + csaKeys { + results { + id + publicKey + version + } + } +} + +##################### +# P2P Keys +##################### + +query FetchP2PKeys { + p2pKeys { + results { + id + peerID + publicKey + } + } +} + +##################### +# ethKeys +##################### + +query FetchAccounts { + ethKeys { + results { + address + isDisabled + chain { + id + enabled + } + ethBalance + linkBalance + } } - } } +##################### +# ocr2KeyBundles +##################### + +query FetchOCR2KeyBundles { + ocr2KeyBundles { + results { + id + chainType + configPublicKey + onChainPublicKey + offChainPublicKey + } + } +} + + ##################### # Jobs and Job Proposals ##################### fragment OCR2Spec on OCR2Spec { - blockchainTimeout - contractID - contractConfigConfirmations - contractConfigTrackerPollInterval - createdAt - ocrKeyBundleID - monitoringEndpoint - p2pv2Bootstrappers - relay - relayConfig - transmitterID - pluginType - pluginConfig + blockchainTimeout + contractID + contractConfigConfirmations + contractConfigTrackerPollInterval + createdAt + ocrKeyBundleID + monitoringEndpoint + p2pv2Bootstrappers + relay + relayConfig + transmitterID + pluginType + pluginConfig } fragment JobParts on Job { - id - name - schemaVersion - gasLimit - forwardingAllowed - maxTaskDuration - externalJobID - type - spec { - ... on OCR2Spec { - ...OCR2Spec - } - } - observationSource - errors { id - description - occurrences - createdAt - updatedAt - } + name + schemaVersion + gasLimit + forwardingAllowed + maxTaskDuration + externalJobID + type + spec { + ... on OCR2Spec { + ...OCR2Spec + } + } + observationSource + errors { + id + description + occurrences + createdAt + updatedAt + } } query GetJob($id: ID!) { - job(id: $id) { - ...JobParts - ... on NotFoundError { - message - code + job(id: $id) { + ...JobParts + ... on NotFoundError { + message + code + } } - } } query ListJobs($offset: Int, $limit: Int) { - jobs(offset: $offset, limit: $limit) { - results { - ...JobParts - } - metadata { - total + jobs(offset: $offset, limit: $limit) { + results { + ...JobParts + } + metadata { + total + } } - } } query GetJobProposal($id: ID!) { - jobProposal(id: $id) { - ... on JobProposal { - id - name - status - remoteUUID - externalJobID - jobID - feedsManager { - ...FeedsManagerParts - } - multiAddrs - pendingUpdate - specs { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - latestSpec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + jobProposal(id: $id) { + ... on JobProposal { + id + name + status + remoteUUID + externalJobID + jobID + feedsManager { + ...FeedsManagerParts + } + multiAddrs + pendingUpdate + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } ##################### @@ -121,34 +171,34 @@ query GetJobProposal($id: ID!) { ##################### fragment BridgeParts on Bridge { - id - name - url - confirmations - outgoingToken - minimumContractPayment - createdAt + id + name + url + confirmations + outgoingToken + minimumContractPayment + createdAt } query ListBridges($offset: Int, $limit: Int) { - bridges(offset: $offset, limit: $limit) { - results { - ...BridgeParts - } - metadata { - total + bridges(offset: $offset, limit: $limit) { + results { + ...BridgeParts + } + metadata { + total + } } - } } query GetBridge($id: ID!) { - bridge(id: $id) { - ...BridgeParts - ... on NotFoundError { - message - code + bridge(id: $id) { + ...BridgeParts + ... on NotFoundError { + message + code + } } - } } ##################### @@ -156,126 +206,126 @@ query GetBridge($id: ID!) { ##################### fragment FeedsManagerParts on FeedsManager { - id - name - uri - publicKey - isConnectionActive - createdAt + id + name + uri + publicKey + isConnectionActive + createdAt } query GetFeedsManager($id: ID!) { - feedsManager(id: $id) { - ...FeedsManagerParts - ... on NotFoundError { - message - code + feedsManager(id: $id) { + ...FeedsManagerParts + ... on NotFoundError { + message + code + } } - } } query ListFeedsManagers { - feedsManagers { - results { - ...FeedsManagerParts + feedsManagers { + results { + ...FeedsManagerParts + } } - } } mutation CreateFeedsManager($input: CreateFeedsManagerInput!) { - createFeedsManager(input: $input) { - ... on CreateFeedsManagerSuccess { - feedsManager { - ...FeedsManagerParts - } - } - ... on SingleFeedsManagerError { - message - code - } - ... on NotFoundError { - message - code - } - ... on InputErrors { - errors { - message - code - path - } + createFeedsManager(input: $input) { + ... on CreateFeedsManagerSuccess { + feedsManager { + ...FeedsManagerParts + } + } + ... on SingleFeedsManagerError { + message + code + } + ... on NotFoundError { + message + code + } + ... on InputErrors { + errors { + message + code + path + } + } } - } } mutation UpdateFeedsManager($id: ID!, $input: UpdateFeedsManagerInput!) { - updateFeedsManager(id: $id, input: $input) { - ... on UpdateFeedsManagerSuccess { - feedsManager { - ...FeedsManagerParts - } - } - ... on NotFoundError { - message - code - } - ... on InputErrors { - errors { - message - code - path - } + updateFeedsManager(id: $id, input: $input) { + ... on UpdateFeedsManagerSuccess { + feedsManager { + ...FeedsManagerParts + } + } + ... on NotFoundError { + message + code + } + ... on InputErrors { + errors { + message + code + path + } + } } - } } # createFeedsManagerChainConfig.graphql mutation CreateFeedsManagerChainConfig($input: CreateFeedsManagerChainConfigInput!) { - createFeedsManagerChainConfig(input: $input) { - ... on CreateFeedsManagerChainConfigSuccess { - chainConfig { - id - chainID - chainType - accountAddr - adminAddr - fluxMonitorJobConfig { - enabled - } - ocr1JobConfig { - enabled - isBootstrap - multiaddr - p2pPeerID - keyBundleID - } - ocr2JobConfig { - enabled - isBootstrap - multiaddr - forwarderAddress - p2pPeerID - keyBundleID - plugins { - commit - execute - median - mercury - rebalancer - } - } - } - } - ... on NotFoundError { - message - code - } - ... on InputErrors { - errors { - message - path - } + createFeedsManagerChainConfig(input: $input) { + ... on CreateFeedsManagerChainConfigSuccess { + chainConfig { + id + chainID + chainType + accountAddr + adminAddr + fluxMonitorJobConfig { + enabled + } + ocr1JobConfig { + enabled + isBootstrap + multiaddr + p2pPeerID + keyBundleID + } + ocr2JobConfig { + enabled + isBootstrap + multiaddr + forwarderAddress + p2pPeerID + keyBundleID + plugins { + commit + execute + median + mercury + rebalancer + } + } + } + } + ... on NotFoundError { + message + code + } + ... on InputErrors { + errors { + message + path + } + } } - } } @@ -284,88 +334,88 @@ mutation CreateFeedsManagerChainConfig($input: CreateFeedsManagerChainConfigInpu ##################### mutation ApproveJobProposalSpec($id: ID!, $force: Boolean) { - approveJobProposalSpec(id: $id, force: $force) { - ... on ApproveJobProposalSpecSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on JobAlreadyExistsError { - message - code - } - ... on NotFoundError { - message - code + approveJobProposalSpec(id: $id, force: $force) { + ... on ApproveJobProposalSpecSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on JobAlreadyExistsError { + message + code + } + ... on NotFoundError { + message + code + } } - } } mutation CancelJobProposalSpec($id: ID!) { - cancelJobProposalSpec(id: $id) { - ... on CancelJobProposalSpecSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + cancelJobProposalSpec(id: $id) { + ... on CancelJobProposalSpecSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } mutation RejectJobProposalSpec($id: ID!) { - rejectJobProposalSpec(id: $id) { - ... on RejectJobProposalSpecSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + rejectJobProposalSpec(id: $id) { + ... on RejectJobProposalSpecSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } mutation UpdateJobProposalSpecDefinition( - $id: ID! - $input: UpdateJobProposalSpecDefinitionInput! + $id: ID! + $input: UpdateJobProposalSpecDefinitionInput! ) { - updateJobProposalSpecDefinition(id: $id, input: $input) { - ... on UpdateJobProposalSpecDefinitionSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + updateJobProposalSpecDefinition(id: $id, input: $input) { + ... on UpdateJobProposalSpecDefinitionSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } diff --git a/integration-tests/web/sdk/internal/schema.graphql b/integration-tests/web/sdk/internal/schema.graphql index 64b7e479abc..be09d61ec78 100644 --- a/integration-tests/web/sdk/internal/schema.graphql +++ b/integration-tests/web/sdk/internal/schema.graphql @@ -287,6 +287,7 @@ type EthTransactionAttemptsPayload implements PaginatedPayload { type Features { csa: Boolean! feedsManager: Boolean! + multiFeedsManagers: Boolean! } # FeaturesPayload defines the response of fetching the features availability in the UI @@ -370,6 +371,13 @@ type CreateFeedsManagerSuccess { feedsManager: FeedsManager! } +type DuplicateFeedsManagerError implements Error { + message: String! + code: ErrorCode! +} + +# DEPRECATED: No longer used since we now support multiple feeds manager. +# Keeping this to avoid breaking change. type SingleFeedsManagerError implements Error { message: String! code: ErrorCode! @@ -377,7 +385,8 @@ type SingleFeedsManagerError implements Error { # CreateFeedsManagerPayload defines the response when creating a feeds manager union CreateFeedsManagerPayload = CreateFeedsManagerSuccess - | SingleFeedsManagerError + | DuplicateFeedsManagerError + | SingleFeedsManagerError # // TODO: delete once multiple feeds managers support is released | NotFoundError | InputErrors