diff --git a/integration-tests/actions/ocr2_helpers_local.go b/integration-tests/actions/ocr2_helpers_local.go index 65e0a466bee..e1b470bd0d4 100644 --- a/integration-tests/actions/ocr2_helpers_local.go +++ b/integration-tests/actions/ocr2_helpers_local.go @@ -19,6 +19,7 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" + "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -273,3 +274,49 @@ func GetOracleIdentitiesWithKeyIndexLocal( return S, oracleIdentities, eg.Wait() } + +// DeleteJobs will delete ALL jobs from the nodes +func DeleteJobs(nodes []*client.ChainlinkClient) error { + for _, node := range nodes { + if node == nil { + return fmt.Errorf("found a nil chainlink node in the list of chainlink nodes while tearing down: %v", nodes) + } + jobs, _, err := node.ReadJobs() + if err != nil { + return fmt.Errorf("error reading jobs from chainlink node: %w", err) + } + for _, maps := range jobs.Data { + if _, ok := maps["id"]; !ok { + return fmt.Errorf("error reading job id from chainlink node's jobs %+v", jobs.Data) + } + id := maps["id"].(string) + _, err2 := node.DeleteJob(id) + if err2 != nil { + return fmt.Errorf("error deleting job from chainlink node: %w", err) + } + } + } + return nil +} + +// DeleteBridges will delete ALL bridges from the nodes +func DeleteBridges(nodes []*client.ChainlinkClient) error { + for _, node := range nodes { + if node == nil { + return fmt.Errorf("found a nil chainlink node in the list of chainlink nodes while tearing down: %v", nodes) + } + + bridges, _, err := node.ReadBridges() + if err != nil { + return err + } + for _, b := range bridges.Data { + _, err = node.DeleteBridge(b.Attributes.Name) + if err != nil { + return err + } + } + + } + return nil +} diff --git a/integration-tests/client/chainlink.go b/integration-tests/client/chainlink.go index 7d5825c261a..4603679ff85 100644 --- a/integration-tests/client/chainlink.go +++ b/integration-tests/client/chainlink.go @@ -5,12 +5,11 @@ import ( "fmt" "math/big" "net/http" + "os" "strings" "sync" "time" - "os" - "github.com/ethereum/go-ethereum/common" "github.com/go-resty/resty/v2" "github.com/rs/zerolog" @@ -303,8 +302,8 @@ func (c *ChainlinkClient) ReadBridge(name string) (*BridgeType, *http.Response, } // ReadBridges reads bridges from the Chainlink node -func (c *ChainlinkClient) ReadBridges() (*ResponseSlice, *resty.Response, error) { - result := &ResponseSlice{} +func (c *ChainlinkClient) ReadBridges() (*Bridges, *resty.Response, error) { + result := &Bridges{} c.l.Info().Str(NodeURL, c.Config.URL).Msg("Getting all bridges") resp, err := c.APIClient.R(). SetResult(&result). diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index c6d1209d2ea..17a0a50845b 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -107,6 +107,11 @@ type BridgeTypeData struct { Attributes BridgeTypeAttributes `json:"attributes"` } +// Bridges is the model that represents the bridges when read on a Chainlink node +type Bridges struct { + Data []BridgeTypeData `json:"data"` +} + // BridgeTypeAttributes is the model that represents the bridge when read or created on a Chainlink node type BridgeTypeAttributes struct { Name string `json:"name"` diff --git a/integration-tests/smoke/cron_test.go b/integration-tests/smoke/cron_test.go index e9d20f588e2..013e4c631c7 100644 --- a/integration-tests/smoke/cron_test.go +++ b/integration-tests/smoke/cron_test.go @@ -60,3 +60,74 @@ func TestCronBasic(t *testing.T) { } }, "2m", "3s").Should(gomega.Succeed()) } + +func TestCronJobReplacement(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithMockAdapter(). + WithCLNodes(1). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + err = env.MockAdapter.SetAdapterBasedIntValuePath("/variable", []string{http.MethodGet, http.MethodPost}, 5) + require.NoError(t, err, "Setting value path in mockserver shouldn't fail") + + bta := &client.BridgeTypeAttributes{ + Name: fmt.Sprintf("variable-%s", uuid.NewString()), + URL: fmt.Sprintf("%s/variable", env.MockAdapter.InternalEndpoint), + RequestData: "{}", + } + err = env.ClCluster.Nodes[0].API.MustCreateBridge(bta) + require.NoError(t, err, "Creating bridge in chainlink node shouldn't fail") + + // CRON job creation and replacement + job, err := env.ClCluster.Nodes[0].API.MustCreateJob(&client.CronJobSpec{ + Schedule: "CRON_TZ=UTC * * * * * *", + ObservationSource: client.ObservationSourceSpecBridge(bta), + }) + require.NoError(t, err, "Creating Cron Job in chainlink node shouldn't fail") + + gom := gomega.NewWithT(t) + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + if err != nil { + l.Info().Err(err).Msg("error while waiting for job runs") + } + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Reading Job run data shouldn't fail") + + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 5), "Expected number of job runs to be greater than 5, but got %d", len(jobRuns.Data)) + + for _, jr := range jobRuns.Data { + g.Expect(jr.Attributes.Errors).Should(gomega.Equal([]interface{}{nil}), "Job run %s shouldn't have errors", jr.ID) + } + }, "3m", "3s").Should(gomega.Succeed()) + + err = env.ClCluster.Nodes[0].API.MustDeleteJob(job.Data.ID) + require.NoError(t, err) + + job, err = env.ClCluster.Nodes[0].API.MustCreateJob(&client.CronJobSpec{ + Schedule: "CRON_TZ=UTC * * * * * *", + ObservationSource: client.ObservationSourceSpecBridge(bta), + }) + require.NoError(t, err, "Recreating Cron Job in chainlink node shouldn't fail") + + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + if err != nil { + l.Info().Err(err).Msg("error while waiting for job runs") + } + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Reading Job run data shouldn't fail") + + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 5), "Expected number of job runs to be greater than 5, but got %d", len(jobRuns.Data)) + + for _, jr := range jobRuns.Data { + g.Expect(jr.Attributes.Errors).Should(gomega.Equal([]interface{}{nil}), "Job run %s shouldn't have errors", jr.ID) + } + }, "3m", "3s").Should(gomega.Succeed()) + +} diff --git a/integration-tests/smoke/keeper_test.go b/integration-tests/smoke/keeper_test.go index b28ab1ff101..29a819af136 100644 --- a/integration-tests/smoke/keeper_test.go +++ b/integration-tests/smoke/keeper_test.go @@ -1126,3 +1126,69 @@ func setupKeeperTest(t *testing.T) ( return env.EVMClient, env.ClCluster.NodeAPIs(), env.ContractDeployer, linkTokenContract, env } + +func TestKeeperJobReplacement(t *testing.T) { + t.Parallel() + registryVersion := ethereum.RegistryVersion_1_3 + + l := logging.GetTestLogger(t) + chainClient, chainlinkNodes, contractDeployer, linkToken, _ := setupKeeperTest(t) + registry, _, consumers, upkeepIDs := actions.DeployKeeperContracts( + t, + registryVersion, + keeperDefaultRegistryConfig, + keeperDefaultUpkeepsToDeploy, + keeperDefaultUpkeepGasLimit, + linkToken, + contractDeployer, + chainClient, + big.NewInt(keeperDefaultLinkFunds), + ) + gom := gomega.NewGomegaWithT(t) + + _, err := actions.CreateKeeperJobsLocal(l, chainlinkNodes, registry, contracts.OCRv2Config{}, chainClient.GetChainID().String()) + require.NoError(t, err, "Error creating keeper jobs") + err = chainClient.WaitForEvents() + require.NoError(t, err, "Error creating keeper jobs") + + gom.Eventually(func(g gomega.Gomega) error { + // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 10 + for i := 0; i < len(upkeepIDs); i++ { + counter, err := consumers[i].Counter(utils.TestContext(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) + g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(10)), + "Expected consumer counter to be greater than 10, but got %d", counter.Int64()) + l.Info().Int64("Upkeep counter", counter.Int64()).Msg("Number of upkeeps performed") + } + return nil + }, "5m", "1s").Should(gomega.Succeed()) + + for _, n := range chainlinkNodes { + jobs, _, err := n.ReadJobs() + require.NoError(t, err) + for _, maps := range jobs.Data { + _, ok := maps["id"] + require.Equal(t, true, ok) + id := maps["id"].(string) + _, err := n.DeleteJob(id) + require.NoError(t, err) + } + } + + _, err = actions.CreateKeeperJobsLocal(l, chainlinkNodes, registry, contracts.OCRv2Config{}, chainClient.GetChainID().String()) + require.NoError(t, err, "Error creating keeper jobs") + err = chainClient.WaitForEvents() + require.NoError(t, err, "Error creating keeper jobs") + + gom.Eventually(func(g gomega.Gomega) error { + // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 10 + for i := 0; i < len(upkeepIDs); i++ { + counter, err := consumers[i].Counter(utils.TestContext(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) + g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(10)), + "Expected consumer counter to be greater than 10, but got %d", counter.Int64()) + l.Info().Int64("Upkeep counter", counter.Int64()).Msg("Number of upkeeps performed") + } + return nil + }, "5m", "1s").Should(gomega.Succeed()) +} diff --git a/integration-tests/smoke/keeper_test.go_test_list.json b/integration-tests/smoke/keeper_test.go_test_list.json index dfa2c49358c..b874bc65ee0 100644 --- a/integration-tests/smoke/keeper_test.go_test_list.json +++ b/integration-tests/smoke/keeper_test.go_test_list.json @@ -53,6 +53,9 @@ }, { "name": "TestKeeperUpdateCheckData" + }, + { + "name": "TestKeeperJobReplacement" } ] } diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index 5950e9febb6..bec82e633f8 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" @@ -92,3 +91,95 @@ func TestOCRv2Basic(t *testing.T) { roundData.Answer.Int64(), ) } + +func TestOCRv2JobReplacement(t *testing.T) { + l := logging.GetTestLogger(t) + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithMockAdapter(). + WithCLNodeConfig(node.NewConfig(node.NewBaseConfig(), + node.WithOCR2(), + node.WithP2Pv2(), + node.WithTracing(), + )). + WithCLNodes(6). + WithFunding(big.NewFloat(.1)). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + env.ParallelTransactions(true) + + nodeClients := env.ClCluster.NodeAPIs() + bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:] + + linkToken, err := env.ContractDeployer.DeployLinkTokenContract() + require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + + err = actions.FundChainlinkNodesLocal(workerNodes, env.EVMClient, big.NewFloat(.05)) + require.NoError(t, err, "Error funding Chainlink nodes") + + // Gather transmitters + var transmitters []string + for _, node := range workerNodes { + addr, err := node.PrimaryEthAddress() + if err != nil { + require.NoError(t, fmt.Errorf("error getting node's primary ETH address: %w", err)) + } + transmitters = append(transmitters, addr) + } + + ocrOffchainOptions := contracts.DefaultOffChainAggregatorOptions() + aggregatorContracts, err := actions.DeployOCRv2Contracts(1, linkToken, env.ContractDeployer, transmitters, env.EVMClient, ocrOffchainOptions) + require.NoError(t, err, "Error deploying OCRv2 aggregator contracts") + + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false) + require.NoError(t, err, "Error creating OCRv2 jobs") + + ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) + require.NoError(t, err, "Error building OCRv2 config") + + err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, aggregatorContracts) + require.NoError(t, err, "Error configuring OCRv2 aggregator contracts") + + err = actions.StartNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l) + require.NoError(t, err, "Error starting new OCR2 round") + roundData, err := aggregatorContracts[0].GetRound(utils.TestContext(t), big.NewInt(1)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(5), roundData.Answer.Int64(), + "Expected latest answer from OCR contract to be 5 but got %d", + roundData.Answer.Int64(), + ) + + err = env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10) + require.NoError(t, err) + err = actions.StartNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l) + require.NoError(t, err) + + roundData, err = aggregatorContracts[0].GetRound(utils.TestContext(t), big.NewInt(2)) + require.NoError(t, err, "Error getting latest OCR answer") + require.Equal(t, int64(10), roundData.Answer.Int64(), + "Expected latest answer from OCR contract to be 10 but got %d", + roundData.Answer.Int64(), + ) + + err = actions.DeleteJobs(nodeClients) + require.NoError(t, err) + + err = actions.DeleteBridges(nodeClients) + require.NoError(t, err) + + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 15, env.EVMClient.GetChainID().Uint64(), false) + require.NoError(t, err, "Error creating OCRv2 jobs") + + err = actions.StartNewOCR2Round(3, aggregatorContracts, env.EVMClient, time.Minute*3, l) + require.NoError(t, err, "Error starting new OCR2 round") + roundData, err = aggregatorContracts[0].GetRound(utils.TestContext(t), big.NewInt(3)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(15), roundData.Answer.Int64(), + "Expected latest answer from OCR contract to be 15 but got %d", + roundData.Answer.Int64(), + ) +} diff --git a/integration-tests/smoke/ocr_test.go b/integration-tests/smoke/ocr_test.go index 45205565e21..8fbc1109e2b 100644 --- a/integration-tests/smoke/ocr_test.go +++ b/integration-tests/smoke/ocr_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "github.com/smartcontractkit/chainlink/integration-tests/utils" @@ -59,3 +58,68 @@ func TestOCRBasic(t *testing.T) { require.NoError(t, err, "Error getting latest OCR answer") require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) } + +func TestOCRJobReplacement(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithMockAdapter(). + WithCLNodes(6). + WithFunding(big.NewFloat(.1)). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + env.ParallelTransactions(true) + + nodeClients := env.ClCluster.NodeAPIs() + bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:] + + linkTokenContract, err := env.ContractDeployer.DeployLinkTokenContract() + require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + + ocrInstances, err := actions.DeployOCRContractsLocal(1, linkTokenContract, env.ContractDeployer, workerNodes, env.EVMClient) + require.NoError(t, err) + err = env.EVMClient.WaitForEvents() + require.NoError(t, err, "Error waiting for events") + + err = actions.CreateOCRJobsLocal(ocrInstances, bootstrapNode, workerNodes, 5, env.MockAdapter, env.EVMClient.GetChainID().String()) + require.NoError(t, err) + + err = actions.StartNewRound(1, ocrInstances, env.EVMClient, l) + require.NoError(t, err) + + answer, err := ocrInstances[0].GetLatestAnswer(utils.TestContext(t)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(5), answer.Int64(), "Expected latest answer from OCR contract to be 5 but got %d", answer.Int64()) + + err = actions.SetAllAdapterResponsesToTheSameValueLocal(10, ocrInstances, workerNodes, env.MockAdapter) + require.NoError(t, err) + err = actions.StartNewRound(2, ocrInstances, env.EVMClient, l) + require.NoError(t, err) + + answer, err = ocrInstances[0].GetLatestAnswer(utils.TestContext(t)) + require.NoError(t, err, "Error getting latest OCR answer") + require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) + + err = actions.DeleteJobs(nodeClients) + require.NoError(t, err) + + err = actions.DeleteBridges(nodeClients) + require.NoError(t, err) + + //Recreate job + err = actions.CreateOCRJobsLocal(ocrInstances, bootstrapNode, workerNodes, 5, env.MockAdapter, env.EVMClient.GetChainID().String()) + require.NoError(t, err) + + err = actions.StartNewRound(1, ocrInstances, env.EVMClient, l) + require.NoError(t, err) + + answer, err = ocrInstances[0].GetLatestAnswer(utils.TestContext(t)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) + +} diff --git a/integration-tests/smoke/vrf_test.go b/integration-tests/smoke/vrf_test.go index 61d2c5cdd70..e477d459264 100644 --- a/integration-tests/smoke/vrf_test.go +++ b/integration-tests/smoke/vrf_test.go @@ -107,3 +107,114 @@ func TestVRFBasic(t *testing.T) { }, timeout, "1s").Should(gomega.Succeed()) } } + +func TestVRFJobReplacement(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithCLNodes(1). + WithFunding(big.NewFloat(.1)). + WithStandardCleanup(). + Build() + require.NoError(t, err) + env.ParallelTransactions(true) + + lt, err := actions.DeployLINKToken(env.ContractDeployer) + require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + contracts, err := vrfv1.DeployVRFContracts(env.ContractDeployer, env.EVMClient, lt) + require.NoError(t, err, "Deploying VRF Contracts shouldn't fail") + + err = lt.Transfer(contracts.Consumer.Address(), big.NewInt(2e18)) + require.NoError(t, err, "Funding consumer contract shouldn't fail") + _, err = env.ContractDeployer.DeployVRFContract() + require.NoError(t, err, "Deploying VRF contract shouldn't fail") + err = env.EVMClient.WaitForEvents() + require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") + + for _, n := range env.ClCluster.Nodes { + nodeKey, err := n.API.MustCreateVRFKey() + require.NoError(t, err, "Creating VRF key shouldn't fail") + l.Debug().Interface("Key JSON", nodeKey).Msg("Created proving key") + pubKeyCompressed := nodeKey.Data.ID + jobUUID := uuid.New() + os := &client.VRFTxPipelineSpec{ + Address: contracts.Coordinator.Address(), + } + ost, err := os.String() + require.NoError(t, err, "Building observation source spec shouldn't fail") + job, err := n.API.MustCreateJob(&client.VRFJobSpec{ + Name: fmt.Sprintf("vrf-%s", jobUUID), + CoordinatorAddress: contracts.Coordinator.Address(), + MinIncomingConfirmations: 1, + PublicKey: pubKeyCompressed, + ExternalJobID: jobUUID.String(), + EVMChainID: env.EVMClient.GetChainID().String(), + ObservationSource: ost, + }) + require.NoError(t, err, "Creating VRF Job shouldn't fail") + + oracleAddr, err := n.API.PrimaryEthAddress() + require.NoError(t, err, "Getting primary ETH address of chainlink node shouldn't fail") + provingKey, err := actions.EncodeOnChainVRFProvingKey(*nodeKey) + require.NoError(t, err, "Encoding on-chain VRF Proving key shouldn't fail") + err = contracts.Coordinator.RegisterProvingKey( + big.NewInt(1), + oracleAddr, + provingKey, + actions.EncodeOnChainExternalJobID(jobUUID), + ) + require.NoError(t, err, "Registering the on-chain VRF Proving key shouldn't fail") + encodedProvingKeys := make([][2]*big.Int, 0) + encodedProvingKeys = append(encodedProvingKeys, provingKey) + + requestHash, err := contracts.Coordinator.HashOfKey(utils.TestContext(t), encodedProvingKeys[0]) + require.NoError(t, err, "Getting Hash of encoded proving keys shouldn't fail") + err = contracts.Consumer.RequestRandomness(requestHash, big.NewInt(1)) + require.NoError(t, err, "Requesting randomness shouldn't fail") + + gom := gomega.NewGomegaWithT(t) + timeout := time.Minute * 2 + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Job execution shouldn't fail") + + out, err := contracts.Consumer.RandomnessOutput(utils.TestContext(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting the randomness output of the consumer shouldn't fail") + // Checks that the job has actually run + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 1), + fmt.Sprintf("Expected the VRF job to run once or more after %s", timeout)) + + g.Expect(out.Uint64()).ShouldNot(gomega.BeNumerically("==", 0), "Expected the VRF job give an answer other than 0") + l.Debug().Uint64("Output", out.Uint64()).Msg("Randomness fulfilled") + }, timeout, "1s").Should(gomega.Succeed()) + + err = n.API.MustDeleteJob(job.Data.ID) + require.NoError(t, err) + + job, err = n.API.MustCreateJob(&client.VRFJobSpec{ + Name: fmt.Sprintf("vrf-%s", jobUUID), + CoordinatorAddress: contracts.Coordinator.Address(), + MinIncomingConfirmations: 1, + PublicKey: pubKeyCompressed, + ExternalJobID: jobUUID.String(), + EVMChainID: env.EVMClient.GetChainID().String(), + ObservationSource: ost, + }) + require.NoError(t, err, "Recreating VRF Job shouldn't fail") + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Job execution shouldn't fail") + + out, err := contracts.Consumer.RandomnessOutput(utils.TestContext(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting the randomness output of the consumer shouldn't fail") + // Checks that the job has actually run + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 1), + fmt.Sprintf("Expected the VRF job to run once or more after %s", timeout)) + g.Expect(out.Uint64()).ShouldNot(gomega.BeNumerically("==", 0), "Expected the VRF job give an answer other than 0") + l.Debug().Uint64("Output", out.Uint64()).Msg("Randomness fulfilled") + }, timeout, "1s").Should(gomega.Succeed()) + } +}