From b0b4354e8c9148ba6624df5e9d939972fb7a08b0 Mon Sep 17 00:00:00 2001 From: Ilja Pavlovs Date: Tue, 7 May 2024 17:35:42 +0300 Subject: [PATCH] VRF-330: enabling batch fulfillment for VRF e2e tests (#13091) * VRF-330: enabling batch fulfillment for VRF e2e tests * VRF-330: fixing lint * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: fixing tests * VRF-330: PR comments * VRF-330: PR comments * VRF-330: PR comments --- .github/workflows/integration-tests.yml | 4 +- .../actions/vrf/common/errors.go | 28 +- .../actions/vrf/common/models.go | 21 +- .../actions/vrf/vrfv2/contract_steps.go | 43 ++- integration-tests/actions/vrf/vrfv2/errors.go | 12 +- .../actions/vrf/vrfv2/setup_steps.go | 7 +- .../actions/vrf/vrfv2plus/contract_steps.go | 19 +- .../actions/vrf/vrfv2plus/errors.go | 2 + .../actions/vrf/vrfv2plus/setup_steps.go | 11 +- integration-tests/client/chainlink_models.go | 8 +- .../contracts/contract_deployer.go | 2 + .../contracts/contract_vrf_models.go | 10 + .../contracts/ethereum_ocr2vrf_contracts.go | 19 -- .../contracts/ethereum_vrf_common.go | 24 +- .../contracts/ethereum_vrf_contracts.go | 18 ++ .../contracts/ethereum_vrfv2_contracts.go | 43 +++ .../contracts/ethereum_vrfv2plus_contracts.go | 62 ++++ integration-tests/smoke/vrfv2_test.go | 302 +++++++++++++++++- integration-tests/smoke/vrfv2plus_test.go | 295 ++++++++++++++++- integration-tests/testconfig/vrfv2/vrfv2.toml | 2 +- .../testconfig/vrfv2plus/vrfv2plus.toml | 2 +- 21 files changed, 833 insertions(+), 101 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 8dcf32b127e..10d3e53d619 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -529,12 +529,12 @@ jobs: pyroscope_env: ci-smoke-vrf-evm-simulated - name: vrfv2 id: vrfv2 - nodes: 5 + nodes: 6 os: ubuntu-latest pyroscope_env: ci-smoke-vrf2-evm-simulated - name: vrfv2plus id: vrfv2plus - nodes: 8 + nodes: 9 os: ubuntu-latest pyroscope_env: ci-smoke-vrf2plus-evm-simulated - name: forwarder_ocr diff --git a/integration-tests/actions/vrf/common/errors.go b/integration-tests/actions/vrf/common/errors.go index f174609823f..62164d0b274 100644 --- a/integration-tests/actions/vrf/common/errors.go +++ b/integration-tests/actions/vrf/common/errors.go @@ -10,20 +10,20 @@ const ( ErrEncodingProvingKey = "error encoding proving key" ErrDeployBlockHashStore = "error deploying blockhash store" ErrDeployBatchBlockHashStore = "error deploying batch blockhash store" - ErrDeployCoordinator = "error deploying VRF CoordinatorV2" - ErrABIEncodingFunding = "error Abi encoding subscriptionID" - ErrSendingLinkToken = "error sending Link token" - ErrCreatingBHSJob = "error creating BHS job" - ErrParseJob = "error parsing job definition" - ErrSetVRFCoordinatorConfig = "error setting config for VRF Coordinator contract" - ErrCreateVRFSubscription = "error creating VRF Subscription" - ErrAddConsumerToSub = "error adding consumer to VRF Subscription" - ErrFundSubWithLinkToken = "error funding subscription with Link tokens" - ErrRestartCLNode = "error restarting CL node" - ErrWaitTXsComplete = "error waiting for TXs to complete" - ErrRequestRandomness = "error requesting randomness" - ErrLoadingCoordinator = "error loading coordinator contract" - ErrCreatingVRFKey = "error creating VRF key" + + ErrABIEncodingFunding = "error Abi encoding subscriptionID" + ErrSendingLinkToken = "error sending Link token" + ErrCreatingBHSJob = "error creating BHS job" + ErrParseJob = "error parsing job definition" + ErrSetVRFCoordinatorConfig = "error setting config for VRF Coordinator contract" + ErrCreateVRFSubscription = "error creating VRF Subscription" + ErrAddConsumerToSub = "error adding consumer to VRF Subscription" + ErrFundSubWithLinkToken = "error funding subscription with Link tokens" + ErrRestartCLNode = "error restarting CL node" + ErrWaitTXsComplete = "error waiting for TXs to complete" + ErrRequestRandomness = "error requesting randomness" + ErrLoadingCoordinator = "error loading coordinator contract" + ErrCreatingVRFKey = "error creating VRF key" ErrWaitRandomWordsRequestedEvent = "error waiting for RandomWordsRequested event" ErrWaitRandomWordsFulfilledEvent = "error waiting for RandomWordsFulfilled event" diff --git a/integration-tests/actions/vrf/common/models.go b/integration-tests/actions/vrf/common/models.go index d5c1c2b95b0..c88839b4c3b 100644 --- a/integration-tests/actions/vrf/common/models.go +++ b/integration-tests/actions/vrf/common/models.go @@ -43,15 +43,17 @@ type VRFNode struct { } type VRFContracts struct { - CoordinatorV2 contracts.VRFCoordinatorV2 - CoordinatorV2Plus contracts.VRFCoordinatorV2_5 - VRFOwner contracts.VRFOwner - BHS contracts.BlockHashStore - BatchBHS contracts.BatchBlockhashStore - VRFV2Consumers []contracts.VRFv2LoadTestConsumer - VRFV2PlusConsumer []contracts.VRFv2PlusLoadTestConsumer - LinkToken contracts.LinkToken - MockETHLINKFeed contracts.VRFMockETHLINKFeed + CoordinatorV2 contracts.VRFCoordinatorV2 + BatchCoordinatorV2 contracts.BatchVRFCoordinatorV2 + CoordinatorV2Plus contracts.VRFCoordinatorV2_5 + BatchCoordinatorV2Plus contracts.BatchVRFCoordinatorV2Plus + VRFOwner contracts.VRFOwner + BHS contracts.BlockHashStore + BatchBHS contracts.BatchBlockhashStore + VRFV2Consumers []contracts.VRFv2LoadTestConsumer + VRFV2PlusConsumer []contracts.VRFv2PlusLoadTestConsumer + LinkToken contracts.LinkToken + MockETHLINKFeed contracts.VRFMockETHLINKFeed } type VRFOwnerConfig struct { @@ -62,6 +64,7 @@ type VRFOwnerConfig struct { type VRFJobSpecConfig struct { ForwardingAllowed bool CoordinatorAddress string + BatchCoordinatorAddress string FromAddresses []string EVMChainID string MinIncomingConfirmations int diff --git a/integration-tests/actions/vrf/vrfv2/contract_steps.go b/integration-tests/actions/vrf/vrfv2/contract_steps.go index 7c1ee634f98..495de0dd268 100644 --- a/integration-tests/actions/vrf/vrfv2/contract_steps.go +++ b/integration-tests/actions/vrf/vrfv2/contract_steps.go @@ -51,7 +51,7 @@ func DeployVRFV2Contracts( if useTestCoordinator { testCoordinator, err := env.ContractDeployer.DeployVRFCoordinatorTestV2(linkTokenContract.Address(), bhs.Address(), linkEthFeedContract.Address()) if err != nil { - return nil, fmt.Errorf("%s, err %w", vrfcommon.ErrDeployCoordinator, err) + return nil, fmt.Errorf("%s, err %w", ErrDeployCoordinatorV2, err) } err = evmClient.WaitForEvents() if err != nil { @@ -61,7 +61,7 @@ func DeployVRFV2Contracts( } else { coordinator, err := env.ContractDeployer.DeployVRFCoordinatorV2(linkTokenContract.Address(), bhs.Address(), linkEthFeedContract.Address()) if err != nil { - return nil, fmt.Errorf("%s, err %w", vrfcommon.ErrDeployCoordinator, err) + return nil, fmt.Errorf("%s, err %w", ErrDeployCoordinatorV2, err) } err = evmClient.WaitForEvents() if err != nil { @@ -74,31 +74,44 @@ func DeployVRFV2Contracts( if err != nil { return nil, fmt.Errorf("%s, err %w", vrfcommon.ErrLoadingCoordinator, err) } + + batchCoordinator, err := env.ContractDeployer.DeployBatchVRFCoordinatorV2(coordinator.Address()) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrDeployBatchCoordinatorV2, err) + } + + err = evmClient.WaitForEvents() + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrWaitTXsComplete, err) + } + if useVRFOwner { vrfOwner, err := env.ContractDeployer.DeployVRFOwner(coordinatorAddress) if err != nil { - return nil, fmt.Errorf("%s, err %w", vrfcommon.ErrDeployCoordinator, err) + return nil, fmt.Errorf("%s, err %w", ErrDeployCoordinatorV2, err) } err = evmClient.WaitForEvents() if err != nil { return nil, fmt.Errorf("%s, err %w", vrfcommon.ErrWaitTXsComplete, err) } return &vrfcommon.VRFContracts{ - CoordinatorV2: coordinator, - VRFOwner: vrfOwner, - BHS: bhs, - VRFV2Consumers: nil, - LinkToken: linkTokenContract, - MockETHLINKFeed: linkEthFeedContract, + CoordinatorV2: coordinator, + BatchCoordinatorV2: batchCoordinator, + VRFOwner: vrfOwner, + BHS: bhs, + VRFV2Consumers: nil, + LinkToken: linkTokenContract, + MockETHLINKFeed: linkEthFeedContract, }, nil } return &vrfcommon.VRFContracts{ - CoordinatorV2: coordinator, - VRFOwner: nil, - BHS: bhs, - VRFV2Consumers: nil, - LinkToken: linkTokenContract, - MockETHLINKFeed: linkEthFeedContract, + CoordinatorV2: coordinator, + BatchCoordinatorV2: batchCoordinator, + VRFOwner: nil, + BHS: bhs, + VRFV2Consumers: nil, + LinkToken: linkTokenContract, + MockETHLINKFeed: linkEthFeedContract, }, nil } diff --git a/integration-tests/actions/vrf/vrfv2/errors.go b/integration-tests/actions/vrf/vrfv2/errors.go index 3ca94dd630d..dc98270ad4c 100644 --- a/integration-tests/actions/vrf/vrfv2/errors.go +++ b/integration-tests/actions/vrf/vrfv2/errors.go @@ -1,9 +1,11 @@ package vrfv2 const ( - ErrDeployVRFV2Wrapper = "error deploying VRFV2Wrapper" - ErrCreateVRFV2Jobs = "error creating VRF V2 Jobs" - ErrDeployVRFV2Contracts = "error deploying VRFV2 contracts" - ErrCreatingVRFv2Job = "error creating VRFv2 job" - ErrAdvancedConsumer = "error deploying VRFv2 Advanced Consumer" + ErrDeployCoordinatorV2 = "error deploying VRF CoordinatorV2" + ErrDeployBatchCoordinatorV2 = "error deploying Batch VRF CoordinatorV2" + ErrDeployVRFV2Wrapper = "error deploying VRFV2Wrapper" + ErrCreateVRFV2Jobs = "error creating VRF V2 Jobs" + ErrDeployVRFV2Contracts = "error deploying VRFV2 contracts" + ErrCreatingVRFv2Job = "error creating VRFv2 job" + ErrAdvancedConsumer = "error deploying VRFv2 Advanced Consumer" ) diff --git a/integration-tests/actions/vrf/vrfv2/setup_steps.go b/integration-tests/actions/vrf/vrfv2/setup_steps.go index ca85bdb5f19..5c441bf811e 100644 --- a/integration-tests/actions/vrf/vrfv2/setup_steps.go +++ b/integration-tests/actions/vrf/vrfv2/setup_steps.go @@ -61,10 +61,8 @@ func CreateVRFV2Job( spec.VRFOwner = vrfJobSpecConfig.VRFOwnerConfig.OwnerAddress spec.UseVRFOwner = true } - - if err != nil { - return nil, fmt.Errorf("%s, err %w", vrfcommon.ErrParseJob, err) - + if vrfJobSpecConfig.BatchFulfillmentEnabled { + spec.BatchCoordinatorAddress = vrfJobSpecConfig.BatchCoordinatorAddress } job, err := chainlinkNode.MustCreateJob(spec) if err != nil { @@ -200,6 +198,7 @@ func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, vrfv2Conf vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ ForwardingAllowed: *vrfv2Config.VRFJobForwardingAllowed, CoordinatorAddress: contracts.CoordinatorV2.Address(), + BatchCoordinatorAddress: contracts.BatchCoordinatorV2.Address(), FromAddresses: vrfNode.TXKeyAddressStrings, EVMChainID: chainID.String(), MinIncomingConfirmations: int(*vrfv2Config.MinimumConfirmations), diff --git a/integration-tests/actions/vrf/vrfv2plus/contract_steps.go b/integration-tests/actions/vrf/vrfv2plus/contract_steps.go index 26532e9b8e8..7bd734b1026 100644 --- a/integration-tests/actions/vrf/vrfv2plus/contract_steps.go +++ b/integration-tests/actions/vrf/vrfv2plus/contract_steps.go @@ -38,23 +38,28 @@ func DeployVRFV2_5Contracts( if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrDeployBatchBlockHashStore, err) } + coordinator, err := contractDeployer.DeployVRFCoordinatorV2_5(bhs.Address()) + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployCoordinatorV2Plus, err) + } err = chainClient.WaitForEvents() if err != nil { - return nil, fmt.Errorf("%s, batchBHS err %w", vrfcommon.ErrWaitTXsComplete, err) + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrWaitTXsComplete, err) } - coordinator, err := contractDeployer.DeployVRFCoordinatorV2_5(bhs.Address()) + batchCoordinator, err := contractDeployer.DeployBatchVRFCoordinatorV2Plus(coordinator.Address()) if err != nil { - return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrDeployCoordinator, err) + return nil, fmt.Errorf("%s, err %w", ErrDeployBatchCoordinatorV2Plus, err) } err = chainClient.WaitForEvents() if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrWaitTXsComplete, err) } return &vrfcommon.VRFContracts{ - CoordinatorV2Plus: coordinator, - BHS: bhs, - BatchBHS: batchBHS, - VRFV2PlusConsumer: nil, + CoordinatorV2Plus: coordinator, + BatchCoordinatorV2Plus: batchCoordinator, + BHS: bhs, + BatchBHS: batchBHS, + VRFV2PlusConsumer: nil, }, nil } diff --git a/integration-tests/actions/vrf/vrfv2plus/errors.go b/integration-tests/actions/vrf/vrfv2plus/errors.go index d39e2002c13..250c4da85a7 100644 --- a/integration-tests/actions/vrf/vrfv2plus/errors.go +++ b/integration-tests/actions/vrf/vrfv2plus/errors.go @@ -1,6 +1,8 @@ package vrfv2plus const ( + ErrDeployCoordinatorV2Plus = "error deploying VRF CoordinatorV2Plus" + ErrDeployBatchCoordinatorV2Plus = "error deploying Batch VRF CoordinatorV2Plus" ErrCreatingVRFv2PlusKey = "error creating VRFv2Plus key" ErrAdvancedConsumer = "error deploying VRFv2Plus Advanced Consumer" ErrCreatingVRFv2PlusJob = "error creating VRFv2Plus job" diff --git a/integration-tests/actions/vrf/vrfv2plus/setup_steps.go b/integration-tests/actions/vrf/vrfv2plus/setup_steps.go index ed81935fa2b..ab973ffe110 100644 --- a/integration-tests/actions/vrf/vrfv2plus/setup_steps.go +++ b/integration-tests/actions/vrf/vrfv2plus/setup_steps.go @@ -43,7 +43,7 @@ func CreateVRFV2PlusJob( return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrParseJob, err) } - job, err := chainlinkNode.MustCreateJob(&client.VRFV2PlusJobSpec{ + jobSpec := client.VRFV2PlusJobSpec{ Name: fmt.Sprintf("vrf-v2-plus-%s", jobUUID), CoordinatorAddress: vrfJobSpecConfig.CoordinatorAddress, FromAddresses: vrfJobSpecConfig.FromAddresses, @@ -56,7 +56,13 @@ func CreateVRFV2PlusJob( BatchFulfillmentGasMultiplier: vrfJobSpecConfig.BatchFulfillmentGasMultiplier, PollPeriod: vrfJobSpecConfig.PollPeriod, RequestTimeout: vrfJobSpecConfig.RequestTimeout, - }) + } + + if vrfJobSpecConfig.BatchFulfillmentEnabled { + jobSpec.BatchCoordinatorAddress = vrfJobSpecConfig.BatchCoordinatorAddress + } + + job, err := chainlinkNode.MustCreateJob(&jobSpec) if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrCreatingVRFv2PlusJob, err) } @@ -201,6 +207,7 @@ func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, config *v vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ ForwardingAllowed: *config.VRFJobForwardingAllowed, CoordinatorAddress: contracts.CoordinatorV2Plus.Address(), + BatchCoordinatorAddress: contracts.BatchCoordinatorV2Plus.Address(), FromAddresses: vrfNode.TXKeyAddressStrings, EVMChainID: chainID.String(), MinIncomingConfirmations: int(*config.MinimumConfirmations), diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index d19888fb706..3eb34d0491e 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -1160,7 +1160,8 @@ observationSource = """ type VRFV2PlusJobSpec struct { Name string `toml:"name"` CoordinatorAddress string `toml:"coordinatorAddress"` // Address of the VRF CoordinatorV2 contract - PublicKey string `toml:"publicKey"` // Public key of the proving key + BatchCoordinatorAddress string `toml:"batchCoordinatorAddress"` + PublicKey string `toml:"publicKey"` // Public key of the proving key ExternalJobID string `toml:"externalJobID"` ObservationSource string `toml:"observationSource"` // List of commands for the Chainlink node MinIncomingConfirmations int `toml:"minIncomingConfirmations"` @@ -1185,6 +1186,7 @@ type = "vrf" schemaVersion = 1 name = "{{.Name}}" coordinatorAddress = "{{.CoordinatorAddress}}" +{{ if .BatchFulfillmentEnabled }}batchCoordinatorAddress = "{{.BatchCoordinatorAddress}}"{{ else }}{{ end }} fromAddresses = [{{range .FromAddresses}}"{{.}}",{{end}}] evmChainID = "{{.EVMChainID}}" minIncomingConfirmations = {{.MinIncomingConfirmations}} @@ -1207,7 +1209,8 @@ observationSource = """ type VRFV2JobSpec struct { Name string `toml:"name"` CoordinatorAddress string `toml:"coordinatorAddress"` // Address of the VRF CoordinatorV2 contract - PublicKey string `toml:"publicKey"` // Public key of the proving key + BatchCoordinatorAddress string `toml:"batchCoordinatorAddress"` + PublicKey string `toml:"publicKey"` // Public key of the proving key ExternalJobID string `toml:"externalJobID"` ObservationSource string `toml:"observationSource"` // List of commands for the Chainlink node MinIncomingConfirmations int `toml:"minIncomingConfirmations"` @@ -1236,6 +1239,7 @@ schemaVersion = 1 name = "{{.Name}}" forwardingAllowed = {{.ForwardingAllowed}} coordinatorAddress = "{{.CoordinatorAddress}}" +{{ if .BatchFulfillmentEnabled }}batchCoordinatorAddress = "{{.BatchCoordinatorAddress}}"{{ else }}{{ end }} fromAddresses = [{{range .FromAddresses}}"{{.}}",{{end}}] evmChainID = "{{.EVMChainID}}" minIncomingConfirmations = {{.MinIncomingConfirmations}} diff --git a/integration-tests/contracts/contract_deployer.go b/integration-tests/contracts/contract_deployer.go index e2511c7292e..9e9c429d3ed 100644 --- a/integration-tests/contracts/contract_deployer.go +++ b/integration-tests/contracts/contract_deployer.go @@ -136,7 +136,9 @@ type ContractDeployer interface { DeployVRFV2PlusWrapperLoadTestConsumer(vrfV2PlusWrapperAddr string) (VRFv2PlusWrapperLoadTestConsumer, error) DeployVRFCoordinator(linkAddr string, bhsAddr string) (VRFCoordinator, error) DeployVRFCoordinatorV2(linkAddr string, bhsAddr string, linkEthFeedAddr string) (VRFCoordinatorV2, error) + DeployBatchVRFCoordinatorV2(coordinatorAddress string) (BatchVRFCoordinatorV2, error) DeployVRFCoordinatorV2_5(bhsAddr string) (VRFCoordinatorV2_5, error) + DeployBatchVRFCoordinatorV2Plus(coordinatorAddress string) (BatchVRFCoordinatorV2Plus, error) DeployVRFCoordinatorV2PlusUpgradedVersion(bhsAddr string) (VRFCoordinatorV2PlusUpgradedVersion, error) DeployVRFV2Wrapper(linkAddr string, linkEthFeedAddr string, coordinatorAddr string) (VRFV2Wrapper, error) DeployVRFV2PlusWrapper(linkAddr string, linkEthFeedAddr string, coordinatorAddr string, subId *big.Int) (VRFV2PlusWrapper, error) diff --git a/integration-tests/contracts/contract_vrf_models.go b/integration-tests/contracts/contract_vrf_models.go index f54d1a25936..97551a1b9ee 100644 --- a/integration-tests/contracts/contract_vrf_models.go +++ b/integration-tests/contracts/contract_vrf_models.go @@ -66,6 +66,7 @@ type VRFCoordinatorV2 interface { OwnerCancelSubscription(subID uint64) (*types.Transaction, error) ParseSubscriptionCanceled(log types.Log) (*vrf_coordinator_v2.VRFCoordinatorV2SubscriptionCanceled, error) ParseRandomWordsRequested(log types.Log) (*CoordinatorRandomWordsRequested, error) + ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) ParseLog(log types.Log) (generated.AbigenLog, error) CancelSubscription(subID uint64, to common.Address) (*types.Transaction, error) FindSubscriptionID(subID uint64) (uint64, error) @@ -122,6 +123,7 @@ type VRFCoordinatorV2_5 interface { WaitForRandomWordsFulfilledEvent(filter RandomWordsFulfilledEventFilter) (*CoordinatorRandomWordsFulfilled, error) WaitForMigrationCompletedEvent(timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25MigrationCompleted, error) ParseRandomWordsRequested(log types.Log) (*CoordinatorRandomWordsRequested, error) + ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) WaitForConfigSetEvent(timeout time.Duration) (*CoordinatorConfigSet, error) } @@ -160,6 +162,7 @@ type VRFCoordinatorV2PlusUpgradedVersion interface { WaitForRandomWordsFulfilledEvent(filter RandomWordsFulfilledEventFilter) (*CoordinatorRandomWordsFulfilled, error) WaitForMigrationCompletedEvent(timeout time.Duration) (*vrf_v2plus_upgraded_version.VRFCoordinatorV2PlusUpgradedVersionMigrationCompleted, error) ParseRandomWordsRequested(log types.Log) (*CoordinatorRandomWordsRequested, error) + ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) WaitForConfigSetEvent(timeout time.Duration) (*CoordinatorConfigSet, error) } @@ -365,6 +368,13 @@ type BatchBlockhashStore interface { Address() string } +type BatchVRFCoordinatorV2 interface { + Address() string +} +type BatchVRFCoordinatorV2Plus interface { + Address() string +} + type VRFMockETHLINKFeed interface { Address() string LatestRoundData() (*big.Int, error) diff --git a/integration-tests/contracts/ethereum_ocr2vrf_contracts.go b/integration-tests/contracts/ethereum_ocr2vrf_contracts.go index 473b308dc42..18948ec38df 100644 --- a/integration-tests/contracts/ethereum_ocr2vrf_contracts.go +++ b/integration-tests/contracts/ethereum_ocr2vrf_contracts.go @@ -13,7 +13,6 @@ import ( "github.com/rs/zerolog/log" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ocr2vrf/generated/dkg" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ocr2vrf/generated/vrf_beacon" @@ -114,24 +113,6 @@ func (e *EthereumContractDeployer) DeployVRFBeacon(vrfCoordinatorAddress string, }, err } -// DeployBatchBlockhashStore deploys DeployBatchBlockhashStore contract -func (e *EthereumContractDeployer) DeployBatchBlockhashStore(blockhashStoreAddr string) (BatchBlockhashStore, error) { - address, _, instance, err := e.client.DeployContract("BatchBlockhashStore", func( - auth *bind.TransactOpts, - backend bind.ContractBackend, - ) (common.Address, *types.Transaction, interface{}, error) { - return batch_blockhash_store.DeployBatchBlockhashStore(auth, backend, common.HexToAddress(blockhashStoreAddr)) - }) - if err != nil { - return nil, err - } - return &LegacyEthereumBatchBlockhashStore{ - client: e.client, - batchBlockhashStore: instance.(*batch_blockhash_store.BatchBlockhashStore), - address: address, - }, err -} - // todo - solve import cycle func DecodeHexTo32ByteArray(val string) ([32]byte, error) { var byteArray [32]byte diff --git a/integration-tests/contracts/ethereum_vrf_common.go b/integration-tests/contracts/ethereum_vrf_common.go index d7eafe42a07..a57cfec8933 100644 --- a/integration-tests/contracts/ethereum_vrf_common.go +++ b/integration-tests/contracts/ethereum_vrf_common.go @@ -15,6 +15,7 @@ import ( type Coordinator interface { ParseRandomWordsRequested(log types.Log) (*CoordinatorRandomWordsRequested, error) + ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) Address() string WaitForRandomWordsFulfilledEvent(filter RandomWordsFulfilledEventFilter) (*CoordinatorRandomWordsFulfilled, error) WaitForConfigSetEvent(timeout time.Duration) (*CoordinatorConfigSet, error) @@ -89,21 +90,33 @@ func parseRequestRandomnessLogs(coordinator Coordinator, logs []*types.Log) (*Co var err error for _, eventLog := range logs { for _, topic := range eventLog.Topics { - if topic.Cmp(vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsRequested{}.Topic()) == 0 { + if topic.Cmp(vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsRequested{}.Topic()) == 0 || + topic.Cmp(vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested{}.Topic()) == 0 { randomWordsRequestedEvent, err = coordinator.ParseRandomWordsRequested(*eventLog) if err != nil { return nil, fmt.Errorf("parse RandomWordsRequested log failed, err: %w", err) } } - if topic.Cmp(vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested{}.Topic()) == 0 { - randomWordsRequestedEvent, err = coordinator.ParseRandomWordsRequested(*eventLog) + } + } + return randomWordsRequestedEvent, nil +} + +func ParseRandomWordsFulfilledLogs(coordinator Coordinator, logs []*types.Log) ([]*CoordinatorRandomWordsFulfilled, error) { + var randomWordsFulfilledEventArr []*CoordinatorRandomWordsFulfilled + for _, eventLog := range logs { + for _, topic := range eventLog.Topics { + if topic.Cmp(vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled{}.Topic()) == 0 || + topic.Cmp(vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled{}.Topic()) == 0 { + randomWordsFulfilledEvent, err := coordinator.ParseRandomWordsFulfilled(*eventLog) if err != nil { - return nil, fmt.Errorf("parse RandomWordsRequested log failed, err: %w", err) + return nil, fmt.Errorf("parse RandomWordsFulfilled log failed, err: %w", err) } + randomWordsFulfilledEventArr = append(randomWordsFulfilledEventArr, randomWordsFulfilledEvent) } } } - return randomWordsRequestedEvent, nil + return randomWordsFulfilledEventArr, nil } func RetrieveRequestRandomnessLogs(coordinator Coordinator, client blockchain.EVMClient, tx *types.Transaction) (*CoordinatorRandomWordsRequested, error) { @@ -120,5 +133,4 @@ func RetrieveRequestRandomnessLogs(coordinator Coordinator, client blockchain.EV return nil, fmt.Errorf("GetTxReceipt failed, err: %w", err) } return parseRequestRandomnessLogs(coordinator, receipt.Logs) - } diff --git a/integration-tests/contracts/ethereum_vrf_contracts.go b/integration-tests/contracts/ethereum_vrf_contracts.go index c2f12e29446..8484979164b 100644 --- a/integration-tests/contracts/ethereum_vrf_contracts.go +++ b/integration-tests/contracts/ethereum_vrf_contracts.go @@ -95,6 +95,24 @@ func (e *EthereumContractDeployer) DeployBlockhashStore() (BlockHashStore, error }, err } +// DeployBatchBlockhashStore deploys DeployBatchBlockhashStore contract +func (e *EthereumContractDeployer) DeployBatchBlockhashStore(blockhashStoreAddr string) (BatchBlockhashStore, error) { + address, _, instance, err := e.client.DeployContract("BatchBlockhashStore", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return batch_blockhash_store.DeployBatchBlockhashStore(auth, backend, common.HexToAddress(blockhashStoreAddr)) + }) + if err != nil { + return nil, err + } + return &LegacyEthereumBatchBlockhashStore{ + client: e.client, + batchBlockhashStore: instance.(*batch_blockhash_store.BatchBlockhashStore), + address: address, + }, err +} + // DeployVRFCoordinator deploys VRF coordinator contract func (e *EthereumContractDeployer) DeployVRFCoordinator(linkAddr string, bhsAddr string) (VRFCoordinator, error) { address, _, instance, err := e.client.DeployContract("VRFCoordinator", func( diff --git a/integration-tests/contracts/ethereum_vrfv2_contracts.go b/integration-tests/contracts/ethereum_vrfv2_contracts.go index c5116856d48..e617d8a0c03 100644 --- a/integration-tests/contracts/ethereum_vrfv2_contracts.go +++ b/integration-tests/contracts/ethereum_vrfv2_contracts.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink/integration-tests/wrappers" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_test_v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_mock_ethlink_aggregator" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_owner" @@ -37,6 +38,12 @@ type EthereumVRFCoordinatorV2 struct { coordinator *vrf_coordinator_v2.VRFCoordinatorV2 } +type EthereumBatchVRFCoordinatorV2 struct { + address *common.Address + client blockchain.EVMClient + batchCoordinator *batch_vrf_coordinator_v2.BatchVRFCoordinatorV2 +} + type EthereumVRFOwner struct { address *common.Address client blockchain.EVMClient @@ -112,6 +119,23 @@ func (e *EthereumContractDeployer) DeployVRFCoordinatorV2(linkAddr string, bhsAd }, err } +func (e *EthereumContractDeployer) DeployBatchVRFCoordinatorV2(coordinatorAddress string) (BatchVRFCoordinatorV2, error) { + address, _, instance, err := e.client.DeployContract("BatchVRFCoordinatorV2", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return batch_vrf_coordinator_v2.DeployBatchVRFCoordinatorV2(auth, backend, common.HexToAddress(coordinatorAddress)) + }) + if err != nil { + return nil, err + } + return &EthereumBatchVRFCoordinatorV2{ + client: e.client, + batchCoordinator: instance.(*batch_vrf_coordinator_v2.BatchVRFCoordinatorV2), + address: address, + }, err +} + func (e *EthereumContractDeployer) DeployVRFOwner(coordinatorAddr string) (VRFOwner, error) { address, _, instance, err := e.client.DeployContract("VRFOwner", func( auth *bind.TransactOpts, @@ -475,6 +499,21 @@ func (v *EthereumVRFCoordinatorV2) ParseRandomWordsRequested(log types.Log) (*Co }, nil } +func (v *EthereumVRFCoordinatorV2) ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) { + fulfilled, err := v.coordinator.ParseRandomWordsFulfilled(log) + if err != nil { + return nil, fmt.Errorf("failed to parse RandomWordsFulfilled event: %w", err) + } + + return &CoordinatorRandomWordsFulfilled{ + RequestId: fulfilled.RequestId, + OutputSeed: fulfilled.OutputSeed, + Payment: fulfilled.Payment, + Success: fulfilled.Success, + Raw: fulfilled.Raw, + }, nil +} + func (v *EthereumVRFCoordinatorV2) ParseLog(log types.Log) (generated.AbigenLog, error) { return v.coordinator.ParseLog(log) } @@ -1229,3 +1268,7 @@ func (v *EthereumVRFMockETHLINKFeed) SetBlockTimestampDeduction(blockTimestampDe } return v.client.ProcessTransaction(tx) } + +func (v *EthereumBatchVRFCoordinatorV2) Address() string { + return v.address.Hex() +} diff --git a/integration-tests/contracts/ethereum_vrfv2plus_contracts.go b/integration-tests/contracts/ethereum_vrfv2plus_contracts.go index 6d4bfcea647..3755b78f75b 100644 --- a/integration-tests/contracts/ethereum_vrfv2plus_contracts.go +++ b/integration-tests/contracts/ethereum_vrfv2plus_contracts.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink/integration-tests/wrappers" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2plus" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_upgraded_version" @@ -27,6 +28,12 @@ type EthereumVRFCoordinatorV2_5 struct { coordinator vrf_coordinator_v2_5.VRFCoordinatorV25Interface } +type EthereumBatchVRFCoordinatorV2Plus struct { + address *common.Address + client blockchain.EVMClient + batchCoordinator *batch_vrf_coordinator_v2plus.BatchVRFCoordinatorV2Plus +} + type EthereumVRFCoordinatorV2PlusUpgradedVersion struct { address *common.Address client blockchain.EVMClient @@ -127,10 +134,31 @@ func (e *EthereumContractDeployer) DeployVRFCoordinatorV2_5(bhsAddr string) (VRF }, err } +func (e *EthereumContractDeployer) DeployBatchVRFCoordinatorV2Plus(coordinatorAddress string) (BatchVRFCoordinatorV2Plus, error) { + address, _, instance, err := e.client.DeployContract("BatchVRFCoordinatorV2Plus", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return batch_vrf_coordinator_v2plus.DeployBatchVRFCoordinatorV2Plus(auth, backend, common.HexToAddress(coordinatorAddress)) + }) + if err != nil { + return nil, err + } + return &EthereumBatchVRFCoordinatorV2Plus{ + client: e.client, + batchCoordinator: instance.(*batch_vrf_coordinator_v2plus.BatchVRFCoordinatorV2Plus), + address: address, + }, err +} + func (v *EthereumVRFCoordinatorV2_5) Address() string { return v.address.Hex() } +func (v *EthereumBatchVRFCoordinatorV2Plus) Address() string { + return v.address.Hex() +} + func (v *EthereumVRFCoordinatorV2_5) HashOfKey(ctx context.Context, pubKey [2]*big.Int) ([32]byte, error) { opts := &bind.CallOpts{ From: common.HexToAddress(v.client.GetDefaultWallet().Address()), @@ -187,6 +215,23 @@ func (v *EthereumVRFCoordinatorV2_5) ParseRandomWordsRequested(log types.Log) (* return coordinatorRandomWordsRequested, nil } +func (v *EthereumVRFCoordinatorV2_5) ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) { + fulfilled, err := v.coordinator.ParseRandomWordsFulfilled(log) + if err != nil { + return nil, fmt.Errorf("failed to parse RandomWordsFulfilled event: %w", err) + } + return &CoordinatorRandomWordsFulfilled{ + RequestId: fulfilled.RequestId, + OutputSeed: fulfilled.OutputSeed, + Payment: fulfilled.Payment, + SubId: fulfilled.SubId.String(), + NativePayment: fulfilled.NativePayment, + OnlyPremium: fulfilled.OnlyPremium, + Success: fulfilled.Success, + Raw: fulfilled.Raw, + }, nil +} + func (v *EthereumVRFCoordinatorV2_5) GetSubscription(ctx context.Context, subID *big.Int) (Subscription, error) { opts := &bind.CallOpts{ From: common.HexToAddress(v.client.GetDefaultWallet().Address()), @@ -1023,6 +1068,23 @@ func (v *EthereumVRFCoordinatorV2PlusUpgradedVersion) ParseRandomWordsRequested( return coordinatorRandomWordsRequested, nil } +func (v *EthereumVRFCoordinatorV2PlusUpgradedVersion) ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) { + fulfilled, err := v.coordinator.ParseRandomWordsFulfilled(log) + if err != nil { + return nil, fmt.Errorf("failed to parse RandomWordsFulfilled event: %w", err) + } + return &CoordinatorRandomWordsFulfilled{ + RequestId: fulfilled.RequestId, + OutputSeed: fulfilled.OutputSeed, + Payment: fulfilled.Payment, + SubId: fulfilled.SubId.String(), + NativePayment: fulfilled.NativePayment, + OnlyPremium: fulfilled.OnlyPremium, + Success: fulfilled.Success, + Raw: fulfilled.Raw, + }, nil +} + func (v *EthereumVRFCoordinatorV2PlusUpgradedVersion) WaitForConfigSetEvent(timeout time.Duration) (*CoordinatorConfigSet, error) { eventsChannel := make(chan *vrf_v2plus_upgraded_version.VRFCoordinatorV2PlusUpgradedVersionConfigSet) subscription, err := v.coordinator.WatchConfigSet(nil, eventsChannel) diff --git a/integration-tests/smoke/vrfv2_test.go b/integration-tests/smoke/vrfv2_test.go index 4dd23bce26f..ce9f448ee66 100644 --- a/integration-tests/smoke/vrfv2_test.go +++ b/integration-tests/smoke/vrfv2_test.go @@ -136,8 +136,7 @@ func TestVRFv2Basic(t *testing.T) { require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") } }) - - t.Run("VRF Node waits block confirmation number specified by the consumer in the rand request before sending fulfilment on-chain", func(t *testing.T) { + t.Run("VRF Node waits block confirmation number specified by the consumer before sending fulfilment on-chain", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) testConfig := configCopy.VRFv2.General @@ -184,7 +183,6 @@ func TestVRFv2Basic(t *testing.T) { require.True(t, status.Fulfilled) l.Info().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") }) - t.Run("CL Node VRF Job Runs", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) consumers, subIDsForJobRuns, err := vrfv2.SetupNewConsumersAndSubs( @@ -228,7 +226,6 @@ func TestVRFv2Basic(t *testing.T) { require.NoError(t, err, "error reading job runs") require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) }) - t.Run("Direct Funding (VRFV2Wrapper)", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) wrapperContracts, wrapperSubID, err := vrfv2.SetupVRFV2WrapperEnvironment( @@ -308,7 +305,6 @@ func TestVRFv2Basic(t *testing.T) { Str("TX Hash", randomWordsFulfilledEvent.Raw.TxHash.String()). Msg("Random Words Fulfilment Details For Link Billing") }) - t.Run("Oracle Withdraw", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) consumers, subIDsForOracleWithDraw, err := vrfv2.SetupNewConsumersAndSubs( @@ -370,7 +366,6 @@ func TestVRFv2Basic(t *testing.T) { "LINK funds were not returned after oracle withdraw", ) }) - t.Run("Canceling Sub And Returning Funds", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) _, subIDsForCancelling, err := vrfv2.SetupNewConsumersAndSubs( @@ -453,7 +448,6 @@ func TestVRFv2Basic(t *testing.T) { require.Equal(t, 0, subBalanceLink.Cmp(subFundsReturnedLinkActual), "Returned LINK funds are not equal to sub balance that was cancelled") }) - t.Run("Owner Canceling Sub And Returning Funds While Having Pending Requests", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) // Underfund subscription to force fulfillments to fail @@ -1193,3 +1187,297 @@ func TestVRFV2NodeReorg(t *testing.T) { require.Error(t, err, "fulfillment should not be generated for the request which was made on reorged fork on Simulated Chain") }) } + +func TestVRFv2BatchFulfillmentEnabledDisabled(t *testing.T) { + t.Parallel() + var ( + env *test_env.CLClusterTestEnv + vrfContracts *vrfcommon.VRFContracts + subIDsForCancellingAfterTest []uint64 + defaultWalletAddress string + vrfKey *vrfcommon.VRFKeyData + nodeTypeToNodeMap map[vrfcommon.VRFNodeType]*vrfcommon.VRFNode + ) + l := logging.GetTestLogger(t) + + config, err := tc.GetConfig("Smoke", tc.VRFv2) + require.NoError(t, err, "Error getting config") + vrfv2Config := config.VRFv2 + chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + + cleanupFn := func() { + evmClient, err := env.GetEVMClient(chainID) + require.NoError(t, err, "Getting EVM client shouldn't fail") + + if evmClient.NetworkSimulated() { + l.Info(). + Str("Network Name", evmClient.GetNetworkName()). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if *vrfv2Config.General.CancelSubsAfterTestRun { + //cancel subs and return funds to sub owner + vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, defaultWalletAddress, subIDsForCancellingAfterTest, l) + } + } + if !*vrfv2Config.General.UseExistingEnv { + if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { + l.Error().Err(err).Msg("Error cleaning up test environment") + } + } + } + newEnvConfig := vrfcommon.NewEnvConfig{ + NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, + NumberOfTxKeysToCreate: 0, + UseVRFOwner: false, + UseTestCoordinator: false, + } + + env, vrfContracts, vrfKey, nodeTypeToNodeMap, err = vrfv2.SetupVRFV2Universe(testcontext.Get(t), t, config, chainID, cleanupFn, newEnvConfig, l) + require.NoError(t, err, "Error setting up VRFv2 universe") + + evmClient, err := env.GetEVMClient(chainID) + require.NoError(t, err, "Getting EVM client shouldn't fail") + defaultWalletAddress = evmClient.GetDefaultWallet().Address() + + //batchMaxGas := config.MaxGasLimit() (2.5 mill) + 400_000 = 2.9 mill + //callback gas limit set by consumer = 500k + // so 4 requests should be fulfilled inside 1 tx since 500k*4 < 2.9 mill + + batchFulfilmentMaxGas := *config.VRFv2.General.MaxGasLimitCoordinatorConfig + 400_000 + config.VRFv2.General.CallbackGasLimit = ptr.Ptr(uint32(500_000)) + + expectedNumberOfFulfillmentsInsideOneBatchFulfillment := (batchFulfilmentMaxGas / *config.VRFv2.General.CallbackGasLimit) - 1 + randRequestCount := expectedNumberOfFulfillmentsInsideOneBatchFulfillment + + t.Run("Batch Fulfillment Enabled", func(t *testing.T) { + configCopy := config.MustCopy().(tc.TestConfig) + + vrfNode, exists := nodeTypeToNodeMap[vrfcommon.VRF] + require.True(t, exists, "VRF Node does not exist") + + //ensure that no job present on the node + err = actions.DeleteJobs([]*client.ChainlinkClient{vrfNode.CLNode.API}) + require.NoError(t, err) + + batchFullfillmentEnabled := true + // create job with batch fulfillment enabled + vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ + ForwardingAllowed: *configCopy.VRFv2.General.VRFJobForwardingAllowed, + CoordinatorAddress: vrfContracts.CoordinatorV2.Address(), + BatchCoordinatorAddress: vrfContracts.BatchCoordinatorV2.Address(), + FromAddresses: vrfNode.TXKeyAddressStrings, + EVMChainID: fmt.Sprint(chainID), + MinIncomingConfirmations: int(*configCopy.VRFv2.General.MinimumConfirmations), + PublicKey: vrfKey.PubKeyCompressed, + EstimateGasMultiplier: *configCopy.VRFv2.General.VRFJobEstimateGasMultiplier, + BatchFulfillmentEnabled: batchFullfillmentEnabled, + BatchFulfillmentGasMultiplier: *configCopy.VRFv2.General.VRFJobBatchFulfillmentGasMultiplier, + PollPeriod: configCopy.VRFv2.General.VRFJobPollPeriod.Duration, + RequestTimeout: configCopy.VRFv2.General.VRFJobRequestTimeout.Duration, + SimulationBlock: configCopy.VRFv2.General.VRFJobSimulationBlock, + VRFOwnerConfig: &vrfcommon.VRFOwnerConfig{ + UseVRFOwner: false, + }, + } + + l.Info(). + Msg("Creating VRFV2 Job with `batchFulfillmentEnabled = true`") + job, err := vrfv2.CreateVRFV2Job( + vrfNode.CLNode.API, + vrfJobSpecConfig, + ) + require.NoError(t, err, "error creating job with higher timeout") + vrfNode.Job = job + + consumers, subIDs, err := vrfv2.SetupNewConsumersAndSubs( + env, + chainID, + vrfContracts.CoordinatorV2, + configCopy, + vrfContracts.LinkToken, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up new consumers and subs") + subID := subIDs[0] + subscription, err := vrfContracts.CoordinatorV2.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + vrfcommon.LogSubDetails(l, subscription, strconv.FormatUint(subID, 10), vrfContracts.CoordinatorV2) + subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDs...) + + configCopy.VRFv2.General.RandomnessRequestCountPerRequest = ptr.Ptr(uint16(randRequestCount)) + + // test and assert + _, randomWordsFulfilledEvent, err := vrfv2.RequestRandomnessAndWaitForFulfillment( + l, + consumers[0], + vrfContracts.CoordinatorV2, + subID, + vrfKey, + *configCopy.VRFv2.General.MinimumConfirmations, + *configCopy.VRFv2.General.CallbackGasLimit, + *configCopy.VRFv2.General.NumberOfWords, + *configCopy.VRFv2.General.RandomnessRequestCountPerRequest, + *configCopy.VRFv2.General.RandomnessRequestCountPerRequestDeviation, + configCopy.VRFv2.General.RandomWordsFulfilledEventTimeout.Duration, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + var wgAllRequestsFulfilled sync.WaitGroup + wgAllRequestsFulfilled.Add(1) + requestCount, fulfilmentCount, err := vrfcommon.WaitForRequestCountEqualToFulfilmentCount(testcontext.Get(t), consumers[0], 2*time.Minute, &wgAllRequestsFulfilled) + require.NoError(t, err) + wgAllRequestsFulfilled.Wait() + + l.Info(). + Interface("Request Count", requestCount). + Interface("Fulfilment Count", fulfilmentCount). + Msg("Request/Fulfilment Stats") + + clNodeTxs, resp, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.ReadTransactions() + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + var batchFulfillmentTxs []client.TransactionData + for _, tx := range clNodeTxs.Data { + if common.HexToAddress(tx.Attributes.To).Cmp(common.HexToAddress(vrfContracts.BatchCoordinatorV2.Address())) == 0 { + batchFulfillmentTxs = append(batchFulfillmentTxs, tx) + } + } + // verify that all fulfillments should be inside one tx + require.Equal(t, 1, len(batchFulfillmentTxs)) + + fulfillmentTx, _, err := actions.GetTxByHash(testcontext.Get(t), evmClient, randomWordsFulfilledEvent.Raw.TxHash) + require.NoError(t, err, "error getting tx from hash") + + fulfillmentTXToAddress := fulfillmentTx.To().String() + l.Info(). + Str("Actual Fulfillment Tx To Address", fulfillmentTXToAddress). + Str("BatchCoordinatorV2 Address", vrfContracts.BatchCoordinatorV2.Address()). + Msg("Fulfillment Tx To Address should be the BatchCoordinatorV2 Address when batch fulfillment is enabled") + + // verify that VRF node sends fulfillments via BatchCoordinator contract + require.Equal(t, vrfContracts.BatchCoordinatorV2.Address(), fulfillmentTXToAddress, "Fulfillment Tx To Address should be the BatchCoordinatorV2 Address when batch fulfillment is enabled") + + fulfillmentTxReceipt, err := evmClient.GetTxReceipt(fulfillmentTx.Hash()) + require.NoError(t, err) + + randomWordsFulfilledLogs, err := contracts.ParseRandomWordsFulfilledLogs(vrfContracts.CoordinatorV2, fulfillmentTxReceipt.Logs) + require.NoError(t, err) + + // verify that all fulfillments should be inside one tx + require.Equal(t, int(randRequestCount), len(randomWordsFulfilledLogs)) + }) + t.Run("Batch Fulfillment Disabled", func(t *testing.T) { + configCopy := config.MustCopy().(tc.TestConfig) + + vrfNode, exists := nodeTypeToNodeMap[vrfcommon.VRF] + require.True(t, exists, "VRF Node does not exist") + //ensure that no job present on the node + err = actions.DeleteJobs([]*client.ChainlinkClient{vrfNode.CLNode.API}) + require.NoError(t, err) + + batchFullfillmentEnabled := false + + //create job with batchFulfillmentEnabled = false + vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ + ForwardingAllowed: *configCopy.VRFv2.General.VRFJobForwardingAllowed, + CoordinatorAddress: vrfContracts.CoordinatorV2.Address(), + BatchCoordinatorAddress: vrfContracts.BatchCoordinatorV2.Address(), + FromAddresses: vrfNode.TXKeyAddressStrings, + EVMChainID: fmt.Sprint(chainID), + MinIncomingConfirmations: int(*configCopy.VRFv2.General.MinimumConfirmations), + PublicKey: vrfKey.PubKeyCompressed, + EstimateGasMultiplier: *configCopy.VRFv2.General.VRFJobEstimateGasMultiplier, + BatchFulfillmentEnabled: batchFullfillmentEnabled, + BatchFulfillmentGasMultiplier: *configCopy.VRFv2.General.VRFJobBatchFulfillmentGasMultiplier, + PollPeriod: configCopy.VRFv2.General.VRFJobPollPeriod.Duration, + RequestTimeout: configCopy.VRFv2.General.VRFJobRequestTimeout.Duration, + SimulationBlock: configCopy.VRFv2.General.VRFJobSimulationBlock, + VRFOwnerConfig: &vrfcommon.VRFOwnerConfig{ + UseVRFOwner: false, + }, + } + + l.Info(). + Msg("Creating VRFV2 Job with `batchFulfillmentEnabled = false`") + job, err := vrfv2.CreateVRFV2Job( + vrfNode.CLNode.API, + vrfJobSpecConfig, + ) + require.NoError(t, err, "error creating job with higher timeout") + vrfNode.Job = job + + consumers, subIDs, err := vrfv2.SetupNewConsumersAndSubs( + env, + chainID, + vrfContracts.CoordinatorV2, + configCopy, + vrfContracts.LinkToken, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up new consumers and subs") + subID := subIDs[0] + subscription, err := vrfContracts.CoordinatorV2.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + vrfcommon.LogSubDetails(l, subscription, strconv.FormatUint(subID, 10), vrfContracts.CoordinatorV2) + subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDs...) + + configCopy.VRFv2.General.RandomnessRequestCountPerRequest = ptr.Ptr(uint16(randRequestCount)) + + // test and assert + _, randomWordsFulfilledEvent, err := vrfv2.RequestRandomnessAndWaitForFulfillment( + l, + consumers[0], + vrfContracts.CoordinatorV2, + subID, + vrfKey, + *configCopy.VRFv2.General.MinimumConfirmations, + *configCopy.VRFv2.General.CallbackGasLimit, + *configCopy.VRFv2.General.NumberOfWords, + *configCopy.VRFv2.General.RandomnessRequestCountPerRequest, + *configCopy.VRFv2.General.RandomnessRequestCountPerRequestDeviation, + configCopy.VRFv2.General.RandomWordsFulfilledEventTimeout.Duration, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + var wgAllRequestsFulfilled sync.WaitGroup + wgAllRequestsFulfilled.Add(1) + requestCount, fulfilmentCount, err := vrfcommon.WaitForRequestCountEqualToFulfilmentCount(testcontext.Get(t), consumers[0], 2*time.Minute, &wgAllRequestsFulfilled) + require.NoError(t, err) + wgAllRequestsFulfilled.Wait() + + l.Info(). + Interface("Request Count", requestCount). + Interface("Fulfilment Count", fulfilmentCount). + Msg("Request/Fulfilment Stats") + + fulfillmentTx, _, err := actions.GetTxByHash(testcontext.Get(t), evmClient, randomWordsFulfilledEvent.Raw.TxHash) + require.NoError(t, err, "error getting tx from hash") + + fulfillmentTXToAddress := fulfillmentTx.To().String() + l.Info(). + Str("Actual Fulfillment Tx To Address", fulfillmentTXToAddress). + Str("CoordinatorV2 Address", vrfContracts.CoordinatorV2.Address()). + Msg("Fulfillment Tx To Address should be the CoordinatorV2 Address when batch fulfillment is disabled") + + // verify that VRF node sends fulfillments via Coordinator contract + require.Equal(t, vrfContracts.CoordinatorV2.Address(), fulfillmentTXToAddress, "Fulfillment Tx To Address should be the CoordinatorV2 Address when batch fulfillment is disabled") + + clNodeTxs, resp, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.ReadTransactions() + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + + var singleFulfillmentTxs []client.TransactionData + for _, tx := range clNodeTxs.Data { + if common.HexToAddress(tx.Attributes.To).Cmp(common.HexToAddress(vrfContracts.CoordinatorV2.Address())) == 0 { + singleFulfillmentTxs = append(singleFulfillmentTxs, tx) + } + } + // verify that all fulfillments should be in separate txs + require.Equal(t, int(randRequestCount), len(singleFulfillmentTxs)) + }) + +} diff --git a/integration-tests/smoke/vrfv2plus_test.go b/integration-tests/smoke/vrfv2plus_test.go index ae190e060ee..0ef4e716733 100644 --- a/integration-tests/smoke/vrfv2plus_test.go +++ b/integration-tests/smoke/vrfv2plus_test.go @@ -193,8 +193,7 @@ func TestVRFv2Plus(t *testing.T) { require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") } }) - - t.Run("VRF Node waits block confirmation number specified by the consumer in the rand request before sending fulfilment on-chain", func(t *testing.T) { + t.Run("VRF Node waits block confirmation number specified by the consumer before sending fulfilment on-chain", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) testConfig := configCopy.VRFv2Plus.General var isNativeBilling = true @@ -238,7 +237,6 @@ func TestVRFv2Plus(t *testing.T) { require.True(t, status.Fulfilled) l.Info().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") }) - t.Run("CL Node VRF Job Runs", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) var isNativeBilling = false @@ -823,7 +821,6 @@ func TestVRFv2PlusMultipleSendingKeys(t *testing.T) { ) require.NoError(t, err, "error requesting randomness and waiting for fulfilment") - //todo - move TransactionByHash to EVMClient in CTF fulfillmentTx, _, err := actions.GetTxByHash(testcontext.Get(t), evmClient, randomWordsFulfilledEvent.Raw.TxHash) require.NoError(t, err, "error getting tx from hash") fulfillmentTxFromAddress, err := actions.GetTxFromAddress(fulfillmentTx) @@ -958,7 +955,7 @@ func TestVRFv2PlusMigration(t *testing.T) { MinIncomingConfirmations: int(*configCopy.VRFv2Plus.General.MinimumConfirmations), PublicKey: vrfKey.VRFKey.Data.ID, EstimateGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobEstimateGasMultiplier, - BatchFulfillmentEnabled: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentEnabled, + BatchFulfillmentEnabled: false, BatchFulfillmentGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentGasMultiplier, PollPeriod: configCopy.VRFv2Plus.General.VRFJobPollPeriod.Duration, RequestTimeout: configCopy.VRFv2Plus.General.VRFJobRequestTimeout.Duration, @@ -1142,7 +1139,7 @@ func TestVRFv2PlusMigration(t *testing.T) { MinIncomingConfirmations: int(*configCopy.VRFv2Plus.General.MinimumConfirmations), PublicKey: vrfKey.VRFKey.Data.ID, EstimateGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobEstimateGasMultiplier, - BatchFulfillmentEnabled: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentEnabled, + BatchFulfillmentEnabled: false, BatchFulfillmentGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentGasMultiplier, PollPeriod: configCopy.VRFv2Plus.General.VRFJobPollPeriod.Duration, RequestTimeout: configCopy.VRFv2Plus.General.VRFJobRequestTimeout.Duration, @@ -1788,7 +1785,7 @@ func TestVRFv2PlusReplayAfterTimeout(t *testing.T) { MinIncomingConfirmations: int(*configCopy.VRFv2Plus.General.MinimumConfirmations), PublicKey: vrfKey.PubKeyCompressed, EstimateGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobEstimateGasMultiplier, - BatchFulfillmentEnabled: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentEnabled, + BatchFulfillmentEnabled: false, BatchFulfillmentGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentGasMultiplier, PollPeriod: configCopy.VRFv2Plus.General.VRFJobPollPeriod.Duration, RequestTimeout: configCopy.VRFv2Plus.General.VRFJobRequestTimeout.Duration, @@ -2083,3 +2080,287 @@ func TestVRFv2PlusNodeReorg(t *testing.T) { }) } + +func TestVRFv2PlusBatchFulfillmentEnabledDisabled(t *testing.T) { + t.Parallel() + var ( + env *test_env.CLClusterTestEnv + vrfContracts *vrfcommon.VRFContracts + subIDsForCancellingAfterTest []*big.Int + defaultWalletAddress string + vrfKey *vrfcommon.VRFKeyData + nodeTypeToNodeMap map[vrfcommon.VRFNodeType]*vrfcommon.VRFNode + ) + l := logging.GetTestLogger(t) + + config, err := tc.GetConfig("Smoke", tc.VRFv2Plus) + require.NoError(t, err, "Error getting config") + vrfv2PlusConfig := config.VRFv2Plus + chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + + cleanupFn := func() { + evmClient, err := env.GetEVMClient(chainID) + require.NoError(t, err, "Getting EVM client shouldn't fail") + + if evmClient.NetworkSimulated() { + l.Info(). + Str("Network Name", evmClient.GetNetworkName()). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { + //cancel subs and return funds to sub owner + vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, defaultWalletAddress, subIDsForCancellingAfterTest, l) + } + } + if !*vrfv2PlusConfig.General.UseExistingEnv { + if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { + l.Error().Err(err).Msg("Error cleaning up test environment") + } + } + } + newEnvConfig := vrfcommon.NewEnvConfig{ + NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, + NumberOfTxKeysToCreate: 0, + UseVRFOwner: false, + UseTestCoordinator: false, + } + + env, vrfContracts, vrfKey, nodeTypeToNodeMap, err = vrfv2plus.SetupVRFV2PlusUniverse(testcontext.Get(t), t, config, chainID, cleanupFn, newEnvConfig, l) + require.NoError(t, err, "Error setting up VRFv2Plus universe") + + evmClient, err := env.GetEVMClient(chainID) + require.NoError(t, err, "Getting EVM client shouldn't fail") + defaultWalletAddress = evmClient.GetDefaultWallet().Address() + + //batchMaxGas := config.MaxGasLimit() (2.5 mill) + 400_000 = 2.9 mill + //callback gas limit set by consumer = 500k + // so 4 requests should be fulfilled inside 1 tx since 500k*4 < 2.9 mill + + batchFulfilmentMaxGas := *config.VRFv2Plus.General.MaxGasLimitCoordinatorConfig + 400_000 + config.VRFv2Plus.General.CallbackGasLimit = ptr.Ptr(uint32(500_000)) + + expectedNumberOfFulfillmentsInsideOneBatchFulfillment := (batchFulfilmentMaxGas / *config.VRFv2Plus.General.CallbackGasLimit) - 1 + randRequestCount := expectedNumberOfFulfillmentsInsideOneBatchFulfillment + + t.Run("Batch Fulfillment Enabled", func(t *testing.T) { + configCopy := config.MustCopy().(tc.TestConfig) + var isNativeBilling = true + + vrfNode, exists := nodeTypeToNodeMap[vrfcommon.VRF] + require.True(t, exists, "VRF Node does not exist") + + //ensure that no job present on the node + err = actions.DeleteJobs([]*client.ChainlinkClient{vrfNode.CLNode.API}) + require.NoError(t, err) + + batchFullfillmentEnabled := true + // create job with batch fulfillment enabled + vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ + ForwardingAllowed: *configCopy.VRFv2Plus.General.VRFJobForwardingAllowed, + CoordinatorAddress: vrfContracts.CoordinatorV2Plus.Address(), + BatchCoordinatorAddress: vrfContracts.BatchCoordinatorV2Plus.Address(), + FromAddresses: vrfNode.TXKeyAddressStrings, + EVMChainID: fmt.Sprint(chainID), + MinIncomingConfirmations: int(*configCopy.VRFv2Plus.General.MinimumConfirmations), + PublicKey: vrfKey.PubKeyCompressed, + EstimateGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobEstimateGasMultiplier, + BatchFulfillmentEnabled: batchFullfillmentEnabled, + BatchFulfillmentGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentGasMultiplier, + PollPeriod: configCopy.VRFv2Plus.General.VRFJobPollPeriod.Duration, + RequestTimeout: configCopy.VRFv2Plus.General.VRFJobRequestTimeout.Duration, + SimulationBlock: configCopy.VRFv2Plus.General.VRFJobSimulationBlock, + VRFOwnerConfig: nil, + } + + l.Info(). + Msg("Creating VRFV2 Plus Job with `batchFulfillmentEnabled = true`") + job, err := vrfv2plus.CreateVRFV2PlusJob( + vrfNode.CLNode.API, + vrfJobSpecConfig, + ) + require.NoError(t, err, "error creating job with higher timeout") + vrfNode.Job = job + + consumers, subIDs, err := vrfv2plus.SetupNewConsumersAndSubs( + env, + chainID, + vrfContracts.CoordinatorV2Plus, + configCopy, + vrfContracts.LinkToken, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up new consumers and subs") + subID := subIDs[0] + subscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + vrfcommon.LogSubDetails(l, subscription, subID.String(), vrfContracts.CoordinatorV2Plus) + subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDs...) + + configCopy.VRFv2Plus.General.RandomnessRequestCountPerRequest = ptr.Ptr(uint16(randRequestCount)) + + // test and assert + _, randomWordsFulfilledEvent, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( + consumers[0], + vrfContracts.CoordinatorV2Plus, + vrfKey, + subID, + isNativeBilling, + configCopy.VRFv2Plus.General, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + var wgAllRequestsFulfilled sync.WaitGroup + wgAllRequestsFulfilled.Add(1) + requestCount, fulfilmentCount, err := vrfcommon.WaitForRequestCountEqualToFulfilmentCount(testcontext.Get(t), consumers[0], 2*time.Minute, &wgAllRequestsFulfilled) + require.NoError(t, err) + wgAllRequestsFulfilled.Wait() + + l.Info(). + Interface("Request Count", requestCount). + Interface("Fulfilment Count", fulfilmentCount). + Msg("Request/Fulfilment Stats") + + clNodeTxs, resp, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.ReadTransactions() + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + var batchFulfillmentTxs []client.TransactionData + for _, tx := range clNodeTxs.Data { + if common.HexToAddress(tx.Attributes.To).Cmp(common.HexToAddress(vrfContracts.BatchCoordinatorV2Plus.Address())) == 0 { + batchFulfillmentTxs = append(batchFulfillmentTxs, tx) + } + } + // verify that all fulfillments should be inside one tx + require.Equal(t, 1, len(batchFulfillmentTxs)) + + fulfillmentTx, _, err := actions.GetTxByHash(testcontext.Get(t), evmClient, randomWordsFulfilledEvent.Raw.TxHash) + require.NoError(t, err, "error getting tx from hash") + + fulfillmentTXToAddress := fulfillmentTx.To().String() + l.Info(). + Str("Actual Fulfillment Tx To Address", fulfillmentTXToAddress). + Str("BatchCoordinatorV2Plus Address", vrfContracts.BatchCoordinatorV2Plus.Address()). + Msg("Fulfillment Tx To Address should be the BatchCoordinatorV2Plus Address when batch fulfillment is enabled") + + // verify that VRF node sends fulfillments via BatchCoordinator contract + require.Equal(t, vrfContracts.BatchCoordinatorV2Plus.Address(), fulfillmentTXToAddress, "Fulfillment Tx To Address should be the BatchCoordinatorV2Plus Address when batch fulfillment is enabled") + + fulfillmentTxReceipt, err := evmClient.GetTxReceipt(fulfillmentTx.Hash()) + require.NoError(t, err) + + randomWordsFulfilledLogs, err := contracts.ParseRandomWordsFulfilledLogs(vrfContracts.CoordinatorV2Plus, fulfillmentTxReceipt.Logs) + require.NoError(t, err) + + // verify that all fulfillments should be inside one tx + require.Equal(t, int(randRequestCount), len(randomWordsFulfilledLogs)) + }) + t.Run("Batch Fulfillment Disabled", func(t *testing.T) { + configCopy := config.MustCopy().(tc.TestConfig) + var isNativeBilling = true + + vrfNode, exists := nodeTypeToNodeMap[vrfcommon.VRF] + require.True(t, exists, "VRF Node does not exist") + //ensure that no job present on the node + err = actions.DeleteJobs([]*client.ChainlinkClient{vrfNode.CLNode.API}) + require.NoError(t, err) + + batchFullfillmentEnabled := false + + //create job with batchFulfillmentEnabled = false + vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ + ForwardingAllowed: *configCopy.VRFv2Plus.General.VRFJobForwardingAllowed, + CoordinatorAddress: vrfContracts.CoordinatorV2Plus.Address(), + BatchCoordinatorAddress: vrfContracts.BatchCoordinatorV2Plus.Address(), + FromAddresses: vrfNode.TXKeyAddressStrings, + EVMChainID: fmt.Sprint(chainID), + MinIncomingConfirmations: int(*configCopy.VRFv2Plus.General.MinimumConfirmations), + PublicKey: vrfKey.PubKeyCompressed, + EstimateGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobEstimateGasMultiplier, + BatchFulfillmentEnabled: batchFullfillmentEnabled, + BatchFulfillmentGasMultiplier: *configCopy.VRFv2Plus.General.VRFJobBatchFulfillmentGasMultiplier, + PollPeriod: configCopy.VRFv2Plus.General.VRFJobPollPeriod.Duration, + RequestTimeout: configCopy.VRFv2Plus.General.VRFJobRequestTimeout.Duration, + SimulationBlock: configCopy.VRFv2Plus.General.VRFJobSimulationBlock, + VRFOwnerConfig: nil, + } + + l.Info(). + Msg("Creating VRFV2 Plus Job with `batchFulfillmentEnabled = false`") + job, err := vrfv2plus.CreateVRFV2PlusJob( + vrfNode.CLNode.API, + vrfJobSpecConfig, + ) + require.NoError(t, err, "error creating job with higher timeout") + vrfNode.Job = job + + consumers, subIDs, err := vrfv2plus.SetupNewConsumersAndSubs( + env, + chainID, + vrfContracts.CoordinatorV2Plus, + configCopy, + vrfContracts.LinkToken, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up new consumers and subs") + subID := subIDs[0] + subscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + vrfcommon.LogSubDetails(l, subscription, subID.String(), vrfContracts.CoordinatorV2Plus) + subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDs...) + + configCopy.VRFv2Plus.General.RandomnessRequestCountPerRequest = ptr.Ptr(uint16(randRequestCount)) + + // test and assert + _, randomWordsFulfilledEvent, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( + consumers[0], + vrfContracts.CoordinatorV2Plus, + vrfKey, + subID, + isNativeBilling, + configCopy.VRFv2Plus.General, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + var wgAllRequestsFulfilled sync.WaitGroup + wgAllRequestsFulfilled.Add(1) + requestCount, fulfilmentCount, err := vrfcommon.WaitForRequestCountEqualToFulfilmentCount(testcontext.Get(t), consumers[0], 2*time.Minute, &wgAllRequestsFulfilled) + require.NoError(t, err) + wgAllRequestsFulfilled.Wait() + + l.Info(). + Interface("Request Count", requestCount). + Interface("Fulfilment Count", fulfilmentCount). + Msg("Request/Fulfilment Stats") + + fulfillmentTx, _, err := actions.GetTxByHash(testcontext.Get(t), evmClient, randomWordsFulfilledEvent.Raw.TxHash) + require.NoError(t, err, "error getting tx from hash") + + fulfillmentTXToAddress := fulfillmentTx.To().String() + l.Info(). + Str("Actual Fulfillment Tx To Address", fulfillmentTXToAddress). + Str("CoordinatorV2Plus Address", vrfContracts.CoordinatorV2Plus.Address()). + Msg("Fulfillment Tx To Address should be the CoordinatorV2Plus Address when batch fulfillment is disabled") + + // verify that VRF node sends fulfillments via Coordinator contract + require.Equal(t, vrfContracts.CoordinatorV2Plus.Address(), fulfillmentTXToAddress, "Fulfillment Tx To Address should be the CoordinatorV2Plus Address when batch fulfillment is disabled") + + clNodeTxs, resp, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.ReadTransactions() + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + + var singleFulfillmentTxs []client.TransactionData + for _, tx := range clNodeTxs.Data { + if common.HexToAddress(tx.Attributes.To).Cmp(common.HexToAddress(vrfContracts.CoordinatorV2Plus.Address())) == 0 { + singleFulfillmentTxs = append(singleFulfillmentTxs, tx) + } + } + // verify that all fulfillments should be in separate txs + require.Equal(t, int(randRequestCount), len(singleFulfillmentTxs)) + }) + +} diff --git a/integration-tests/testconfig/vrfv2/vrfv2.toml b/integration-tests/testconfig/vrfv2/vrfv2.toml index 012352f49b8..59affd85f5a 100644 --- a/integration-tests/testconfig/vrfv2/vrfv2.toml +++ b/integration-tests/testconfig/vrfv2/vrfv2.toml @@ -44,7 +44,7 @@ wrapper_consumer_funding_amount_link = 10 # VRF Job config vrf_job_forwarding_allowed = false vrf_job_estimate_gas_multiplier = 1.0 -vrf_job_batch_fulfillment_enabled = false +vrf_job_batch_fulfillment_enabled = true vrf_job_batch_fulfillment_gas_multiplier = 1.15 vrf_job_poll_period = "1s" vrf_job_request_timeout = "24h" diff --git a/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml b/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml index 902ca3a2966..b420a1c1d88 100644 --- a/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml +++ b/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml @@ -48,7 +48,7 @@ coordinator_link_premium_percentage=20 # VRF Job config vrf_job_forwarding_allowed = false vrf_job_estimate_gas_multiplier = 1.1 -vrf_job_batch_fulfillment_enabled = false +vrf_job_batch_fulfillment_enabled = true vrf_job_batch_fulfillment_gas_multiplier = 1.15 vrf_job_poll_period = "1s" vrf_job_request_timeout = "24h"