diff --git a/.changeset/long-wombats-find.md b/.changeset/long-wombats-find.md new file mode 100644 index 00000000000..ee93d7cd99b --- /dev/null +++ b/.changeset/long-wombats-find.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Remove VRF v1 support including coordinators, the legacy `vrf` pipeline task, and related configuration. Migrate to VRF v2 or VRF v2 Plus. #removed #breaking_change diff --git a/.changeset/minor-bump-1778011583.md b/.changeset/minor-bump-1778011583.md deleted file mode 100644 index cd0ab0f3f81..00000000000 --- a/.changeset/minor-bump-1778011583.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"chainlink": minor ---- - -Minor bump to start next version diff --git a/.github/workflows/legacy-system-tests.yml b/.github/workflows/legacy-system-tests.yml index 2d7d15e9fc3..6d2d95d1d23 100644 --- a/.github/workflows/legacy-system-tests.yml +++ b/.github/workflows/legacy-system-tests.yml @@ -134,12 +134,6 @@ jobs: runner: "ubuntu-latest" tests_dir: "flux" logs_archive_name: "flux" - - display_name: "Test VRF Smoke" - testcmd: "go test -v -timeout 10m -run TestVRFBasic\\|TestVRFJobReplacement" - envcmd: "cl u env.toml,products/vrf/basic.toml" - runner: "ubuntu-latest" - tests_dir: "vrf" - logs_archive_name: "vrf" - display_name: "Test DF1 (OCR2) Smoke" testcmd: "go test -v -timeout 20m -run TestSmoke/rounds" envcmd: "cl u env.toml,products/ocr2/basic.toml" diff --git a/core/scripts/vrfv1/README.md b/core/scripts/vrfv1/README.md deleted file mode 100644 index 7b8056b4b5f..00000000000 --- a/core/scripts/vrfv1/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Using the Ownerless Consumer Example - -The [ownerless consumer example contract](../../../contracts/src/v0.8/tests/VRFOwnerlessConsumerExample.sol) -allows anyone to request randomness from VRF V1 without needing to deploy their -own consuming contract. It does not hold any ETH or LINK; a caller must send it -LINK and spend that LINK on a randomness request within the same transaction. - -This guide covers requesting randomness and optionally deploying the contract. - -## Setup - -Before starting, you will need: - 1. An EVM chain endpoint URL - 2. The chain ID corresponding to your chain - 3. The private key of an account funded with LINK, and the chain's native token - (to pay transaction fees) - 4. [The LINK address, VRF coordinator address, and key hash](https://docs.chain.link/docs/vrf-contracts/) - for your chain - 5. [Go](https://go.dev/doc/install) - -The endpoint URL can be a locally running node, or an externally hosted one like -[alchemy](https://www.alchemy.com/). Your chain ID will be a number -corresponding to the chain you pick. For example the Rinkeby testnet has chain -ID 4. Your private key can be exported from [MetaMask](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key). - -Note: Be careful with your key. When using testnets, it's best to use a separate -account that does not hold real funds. - -Run the following command to set up your environment: - -```shell -export ETH_URL= -export ETH_CHAIN_ID= -export ACCOUNT_KEY= -export LINK= -export COORDINATOR= -export KEY_HASH= -``` - -Now "cd" into the VRF V1 scripts directory: - -```shell -cd /core/scripts/vrfv1 -``` - -## Getting a Consumer - -Since this contract is ownerless, you can use an existing instance instead of -deploying your own. To use an existing instance, copy the command corresponding -to the chain you want to use below, otherwise go to the -[deployment](#deploying-a-new-consumer) section. - -Once you have chosen or deployed a consumer, run: -```shell -export CONSUMER= -``` - -### Existing Consumers - -#### Testnets - -##### Ethereum Rinkeby Testnet - -```0x1b7D5F1bD3054474cC043207aA1e7f8C152d263F``` - -#### BSC Testnet - -```0x640F2D8fd734cb53a6938CeC4CfC0543BbcC0348``` - -#### Polygon Mumbai Testnet - -```0x640F2D8fd734cb53a6938CeC4CfC0543BbcC0348``` - -### Deploying a New Consumer - -To deploy the contract, run: -```shell -go run main.go ownerless-consumer-deploy --coordinator-address=$COORDINATOR --link-address=$LINK -``` - -You should see output like: -``` -Ownerless Consumer: TX Hash: -``` - -## Requesting Randomness - -Since the ownerless consumer does not hold LINK funds, it can only request -randomness through a transferAndCall from the -[LINK contract](../../../contracts/src/v0.8/shared/token/ERC677/LinkToken.sol). The transaction has -the following steps: -1. An externally owned account (controlled by your private key) initiates a - transferAndCall on the LinkToken contract. -2. The LinkToken contract transfers funds to the ownerless consumer. -3. The ownerless consumer requests randomness from the - [VRF Coordinator](https://github.com/smartcontractkit/chainlink-contracts-deprecated/blob/main/contracts/src/v0.6/VRFCoordinator.sol), using the - LINK from step 2 to pay for it. - -To request randomness for your chosen consumer, run: -```shell -go run main.go ownerless-consumer-request --link-address=$LINK --consumer-address=$CONSUMER --key-hash=$KEY_HASH -``` - -You should see the output: -``` -TX Hash: -``` - -You can put this transaction hash into a block explorer to check its progress. -Shortly after it's confirmed, usually only a few minutes, you should see a -second incoming transaction to your consumer containing the randomness -result. - -## Debugging Reverted Transactions - -A reverted transaction could have number of root causes, for example -insufficient funds / LINK, or incorrect contract addresses. - -[Tenderly](https://dashboard.tenderly.co/explorer) can be useful for debugging -why a transaction failed. For example [this Rinkeby transaction](https://dashboard.tenderly.co/tx/rinkeby/0x71a7279033b47472ca453f7a19ccb685d0f32cdb4854a45052f1aaccd80436e9) -failed because a non-owner tried to request random words from -[VRFExternalSubOwnerExample](../../../../contracts/src/v0.8/tests/VRFExternalSubOwnerExample.sol). diff --git a/core/scripts/vrfv1/main.go b/core/scripts/vrfv1/main.go deleted file mode 100644 index 2f4f63569bc..00000000000 --- a/core/scripts/vrfv1/main.go +++ /dev/null @@ -1,251 +0,0 @@ -package main - -import ( - "context" - "encoding/hex" - "flag" - "fmt" - "math/big" - "os" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/google/uuid" - "github.com/shopspring/decimal" - - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/blockhash_store" - linktoken "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/link_token_interface" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" - vrfltoc "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_load_test_ownerless_consumer" - vrfoc "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_ownerless_consumer_example" - evmutils "github.com/smartcontractkit/chainlink-evm/pkg/utils" - helpers "github.com/smartcontractkit/chainlink/core/scripts/common" - "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/utils" -) - -func main() { - e := helpers.SetupEnv(false) - - switch os.Args[1] { - case "topics": - randomnessRequestTopic := solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest{}.Topic() - randomnessFulfilledTopic := solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled{}.Topic() - fmt.Println("RandomnessRequest:", randomnessRequestTopic.String(), - "RandomnessRequestFulfilled:", randomnessFulfilledTopic.String()) - case "request-report": - cmd := flag.NewFlagSet("request-report", flag.ExitOnError) - txHashes := cmd.String("tx-hashes", "", "comma separated transaction hashes") - requestIDs := cmd.String("request-ids", "", "comma separated request IDs in hex") - bhsAddress := cmd.String("bhs-address", "", "BHS contract address") - coordinatorAddress := cmd.String("coordinator-address", "", "VRF coordinator address") - - helpers.ParseArgs(cmd, os.Args[2:], "tx-hashes", "bhs-address", "request-ids", "coordinator-address") - - hashes := helpers.ParseHashSlice(*txHashes) - reqIDs := parseRequestIDs(*requestIDs) - bhs, err := blockhash_store.NewBlockhashStore( - common.HexToAddress(*bhsAddress), - e.Ec) - helpers.PanicErr(err) - coordinator, err := solidity_vrf_coordinator_interface.NewVRFCoordinator( - common.HexToAddress(*coordinatorAddress), - e.Ec) - helpers.PanicErr(err) - - if len(hashes) != len(reqIDs) { - panic(fmt.Errorf("len(hashes) [%d] != len(reqIDs) [%d]", len(hashes), len(reqIDs))) - } - - var bhsMissedBlocks []*big.Int - for i := range hashes { - receipt, err := e.Ec.TransactionReceipt(context.Background(), hashes[i]) - helpers.PanicErr(err) - - reqID := reqIDs[i] - callbacks, err := coordinator.Callbacks(nil, reqID) - helpers.PanicErr(err) - fulfilled := utils.IsEmpty(callbacks.SeedAndBlockNum[:]) - - _, err = bhs.GetBlockhash(nil, receipt.BlockNumber) - if err != nil { - fmt.Println("Blockhash for block", receipt.BlockNumber, "not stored (tx", hashes[i].String(), - ", request ID", hex.EncodeToString(reqID[:]), ", fulfilled:", fulfilled, ")") - if !fulfilled { - // not fulfilled and bh not stored means the feeder missed a store - bhsMissedBlocks = append(bhsMissedBlocks, receipt.BlockNumber) - } - } else { - fmt.Println("Blockhash for block", receipt.BlockNumber, "stored (tx", hashes[i].String(), - ", request ID", hex.EncodeToString(reqID[:]), ", fulfilled:", fulfilled, ")") - } - } - - if len(bhsMissedBlocks) == 0 { - fmt.Println("Didn't miss any bh stores!") - return - } - fmt.Println("Missed stores:") - for _, blockNumber := range bhsMissedBlocks { - fmt.Println("\t* ", blockNumber.String()) - } - case "get-receipt": - cmd := flag.NewFlagSet("get-tx", flag.ExitOnError) - txHashes := cmd.String("tx-hashes", "", "comma separated transaction hashes") - helpers.ParseArgs(cmd, os.Args[2:], "tx-hashes") - hashes := helpers.ParseHashSlice(*txHashes) - - for _, h := range hashes { - receipt, err := e.Ec.TransactionReceipt(context.Background(), h) - helpers.PanicErr(err) - fmt.Println("Tx", h.String(), "Included in block:", receipt.BlockNumber, - ", blockhash:", receipt.BlockHash.String()) - } - case "get-callback": - cmd := flag.NewFlagSet("get-callback", flag.ExitOnError) - coordinatorAddress := cmd.String("coordinator-address", "", "VRF coordinator address") - requestIDs := cmd.String("request-ids", "", "comma separated request IDs in hex") - helpers.ParseArgs(cmd, os.Args[2:], "coordinator-address", "request-ids") - coordinator, err := solidity_vrf_coordinator_interface.NewVRFCoordinator( - common.HexToAddress(*coordinatorAddress), - e.Ec) - helpers.PanicErr(err) - reqIDs := parseRequestIDs(*requestIDs) - for _, reqID := range reqIDs { - callbacks, err := coordinator.Callbacks(nil, reqID) - helpers.PanicErr(err) - if utils.IsEmpty(callbacks.SeedAndBlockNum[:]) { - fmt.Println("request", hex.EncodeToString(reqID[:]), "fulfilled") - } else { - fmt.Println("request", hex.EncodeToString(reqID[:]), "not fulfilled") - } - } - case "coordinator-deploy": - cmd := flag.NewFlagSet("coordinator-deploy", flag.ExitOnError) - linkAddress := cmd.String("link-address", "", "LINK token contract address") - bhsAddress := cmd.String("bhs-address", "", "blockhash store contract address") - helpers.ParseArgs(cmd, os.Args[2:], "link-address", "bhs-address") - _, tx, _, err := solidity_vrf_coordinator_interface.DeployVRFCoordinator( - e.Owner, e.Ec, common.HexToAddress(*linkAddress), common.HexToAddress(*bhsAddress)) - helpers.PanicErr(err) - helpers.ConfirmContractDeployed(context.Background(), e.Ec, tx, e.ChainID) - case "coordinator-register-key": - cmd := flag.NewFlagSet("coordinator-register-key", flag.ExitOnError) - coordinatorAddress := cmd.String("coordinator-address", "", "address of VRF coordinator") - pubKeyUncompressed := cmd.String("pubkey-uncompressed", "", "uncompressed VRF public key in hex") - oracleAddress := cmd.String("oracle-address", "", "oracle address") - fee := cmd.String("fee", "", "VRF fee in juels") - jobID := cmd.String("job-id", "", "Job UUID on the chainlink node (UUID)") - helpers.ParseArgs(cmd, os.Args[2:], - "coordinator-address", "pubkey-uncompressed", "oracle-address", "fee", "job-id") - - coordinator, err := solidity_vrf_coordinator_interface.NewVRFCoordinator( - common.HexToAddress(*coordinatorAddress), - e.Ec) - helpers.PanicErr(err) - - // Put key in ECDSA format - if strings.HasPrefix(*pubKeyUncompressed, "0x") { - *pubKeyUncompressed = strings.Replace(*pubKeyUncompressed, "0x", "04", 1) - } - pubBytes, err := hex.DecodeString(*pubKeyUncompressed) - helpers.PanicErr(err) - pk, err := crypto.UnmarshalPubkey(pubBytes) - helpers.PanicErr(err) - - uid := uuid.MustParse(*jobID) - tx, err := coordinator.RegisterProvingKey( - e.Owner, - decimal.RequireFromString(*fee).BigInt(), - common.HexToAddress(*oracleAddress), - [2]*big.Int{pk.X, pk.Y}, - job.ExternalJobIDEncodeStringToTopic(uid), - ) - helpers.PanicErr(err) - helpers.ConfirmTXMined(context.Background(), e.Ec, tx, e.ChainID) - case "ownerless-consumer-deploy": - cmd := flag.NewFlagSet("ownerless-consumer-deploy", flag.ExitOnError) - coordAddr := cmd.String("coordinator-address", "", "address of VRF coordinator") - linkAddr := cmd.String("link-address", "", "address of link token") - helpers.ParseArgs(cmd, os.Args[2:], "coordinator-address", "link-address") - _, tx, _, err := vrfoc.DeployVRFOwnerlessConsumerExample( - e.Owner, - e.Ec, - common.HexToAddress(*coordAddr), - common.HexToAddress(*linkAddr)) - helpers.PanicErr(err) - helpers.ConfirmContractDeployed(context.Background(), e.Ec, tx, e.ChainID) - case "loadtest-ownerless-consumer-deploy": - cmd := flag.NewFlagSet("loadtest-ownerless-consumer-deploy", flag.ExitOnError) - coordAddr := cmd.String("coordinator-address", "", "address of VRF coordinator") - linkAddr := cmd.String("link-address", "", "address of link token") - priceStr := cmd.String("price", "", "the price of each VRF request in Juels") - helpers.ParseArgs(cmd, os.Args[2:], "coordinator-address", "link-address") - price := decimal.RequireFromString(*priceStr).BigInt() - _, tx, _, err := vrfltoc.DeployVRFLoadTestOwnerlessConsumer( - e.Owner, - e.Ec, - common.HexToAddress(*coordAddr), - common.HexToAddress(*linkAddr), - price) - helpers.PanicErr(err) - helpers.ConfirmContractDeployed(context.Background(), e.Ec, tx, e.ChainID) - case "ownerless-consumer-request": - cmd := flag.NewFlagSet("ownerless-consumer-request", flag.ExitOnError) - linkAddr := cmd.String("link-address", "", "address of link token") - consumerAddr := cmd.String("consumer-address", "", "address of the deployed ownerless consumer") - paymentStr := cmd.String("payment", "" /* 0.1 LINK */, "the payment amount in LINK") - keyHash := cmd.String("key-hash", "", "key hash") - helpers.ParseArgs(cmd, os.Args[2:], "link-address", "consumer-address", "payment", "key-hash") - payment, ok := big.NewInt(0).SetString(*paymentStr, 10) - if !ok { - panic("failed to parse payment amount: " + *paymentStr) - } - link, err := linktoken.NewLinkToken(common.HexToAddress(*linkAddr), e.Ec) - helpers.PanicErr(err) - data, err := evmutils.ABIEncode(`[{"type":"bytes32"}]`, common.HexToHash(*keyHash)) - helpers.PanicErr(err) - tx, err := link.TransferAndCall(e.Owner, common.HexToAddress(*consumerAddr), payment, data) - helpers.PanicErr(err) - helpers.ConfirmTXMined(context.Background(), e.Ec, tx, e.ChainID) - case "load-test-read": - cmd := flag.NewFlagSet("load-test-read", flag.ExitOnError) - consumerAddress := cmd.String("consumer-address", "", "load test consumer address") - helpers.ParseArgs(cmd, os.Args[2:], "consumer-address") - consumer, err := vrfltoc.NewVRFLoadTestOwnerlessConsumer(common.HexToAddress(*consumerAddress), e.Ec) - helpers.PanicErr(err) - count, err := consumer.SResponseCount(nil) - helpers.PanicErr(err) - fmt.Println("response count:", count.String(), "consumer:", *consumerAddress) - case "ownerless-consumer-read": - cmd := flag.NewFlagSet("ownerless-consumer-read", flag.ExitOnError) - consumerAddr := cmd.String("consumer-address", "", "address of the deployed ownerless consumer") - helpers.ParseArgs(cmd, os.Args[2:], "consumer-address") - consumer, err := vrfoc.NewVRFOwnerlessConsumerExample( - common.HexToAddress(*consumerAddr), - e.Ec) - helpers.PanicErr(err) - requestID, err := consumer.SRequestId(nil) - helpers.PanicErr(err) - fmt.Println("request ID:", requestID) - output, err := consumer.SRandomnessOutput(nil) - helpers.PanicErr(err) - fmt.Println("randomness:", output) - } -} - -func parseRequestIDs(arg string) (ret [][32]byte) { - split := strings.SplitSeq(arg, ",") - for rid := range split { - if strings.HasPrefix(rid, "0x") { - rid = strings.Replace(rid, "0x", "", 1) - } - reqID, err := hex.DecodeString(rid) - helpers.PanicErr(err) - var reqIDFixed [32]byte - copy(reqIDFixed[:], reqID) - ret = append(ret, reqIDFixed) - } - return -} diff --git a/core/services/blockhashstore/coordinators.go b/core/services/blockhashstore/coordinators.go index 61544e288cc..05e4cd6a55e 100644 --- a/core/services/blockhashstore/coordinators.go +++ b/core/services/blockhashstore/coordinators.go @@ -2,13 +2,11 @@ package blockhashstore import ( "context" - "encoding/hex" "fmt" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - v1 "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" v2 "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2" v2plus "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2plus_interface" "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" @@ -16,7 +14,6 @@ import ( var ( _ Coordinator = MultiCoordinator{} - _ Coordinator = &V1Coordinator{} _ Coordinator = &V2Coordinator{} _ Coordinator = &V2PlusCoordinator{} ) @@ -63,95 +60,6 @@ func (m MultiCoordinator) Fulfillments(ctx context.Context, fromBlock uint64) ([ return fuls, nil } -// V1Coordinator fetches request and fulfillment logs from a VRF V1 coordinator contract. -type V1Coordinator struct { - c v1.VRFCoordinatorInterface - lp logpoller.LogPoller -} - -// NewV1Coordinator creates a new V1Coordinator from the given contract. -func NewV1Coordinator(ctx context.Context, c v1.VRFCoordinatorInterface, lp logpoller.LogPoller) (*V1Coordinator, error) { - err := lp.RegisterFilter(ctx, logpoller.Filter{ - Name: logpoller.FilterName("VRFv1CoordinatorFeeder", c.Address()), - EventSigs: []common.Hash{ - v1.VRFCoordinatorRandomnessRequest{}.Topic(), - v1.VRFCoordinatorRandomnessRequestFulfilled{}.Topic(), - }, Addresses: []common.Address{c.Address()}, - }) - if err != nil { - return nil, err - } - return &V1Coordinator{c, lp}, nil -} - -// Requests satisfies the Coordinator interface. -func (v *V1Coordinator) Requests( - ctx context.Context, - fromBlock uint64, - toBlock uint64, -) ([]Event, error) { - logs, err := v.lp.LogsWithSigs( - ctx, - int64(fromBlock), - int64(toBlock), - []common.Hash{ - v1.VRFCoordinatorRandomnessRequest{}.Topic(), - }, - v.c.Address()) - if err != nil { - return nil, errors.Wrap(err, "filter v1 requests") - } - - var reqs []Event - for _, l := range logs { - requestLog, err := v.c.ParseLog(l.ToGethLog()) - if err != nil { - continue // malformed log should not break flow - } - request, ok := requestLog.(*v1.VRFCoordinatorRandomnessRequest) - if !ok { - continue // malformed log should not break flow - } - reqs = append(reqs, Event{ID: hex.EncodeToString(request.RequestID[:]), Block: request.Raw.BlockNumber}) - } - - return reqs, nil -} - -// Fulfillments satisfies the Coordinator interface. -func (v *V1Coordinator) Fulfillments(ctx context.Context, fromBlock uint64) ([]Event, error) { - toBlock, err := v.lp.LatestBlock(ctx) - if err != nil { - return nil, errors.Wrap(err, "fetching latest block") - } - - logs, err := v.lp.LogsWithSigs( - ctx, - int64(fromBlock), - toBlock.BlockNumber, - []common.Hash{ - v1.VRFCoordinatorRandomnessRequestFulfilled{}.Topic(), - }, - v.c.Address()) - if err != nil { - return nil, errors.Wrap(err, "filter v1 fulfillments") - } - - var fuls []Event - for _, l := range logs { - requestLog, err := v.c.ParseLog(l.ToGethLog()) - if err != nil { - continue // malformed log should not break flow - } - request, ok := requestLog.(*v1.VRFCoordinatorRandomnessRequestFulfilled) - if !ok { - continue // malformed log should not break flow - } - fuls = append(fuls, Event{ID: hex.EncodeToString(request.RequestId[:]), Block: request.Raw.BlockNumber}) - } - return fuls, nil -} - // V2Coordinator fetches request and fulfillment logs from a VRF V2 coordinator contract. type V2Coordinator struct { c v2.VRFCoordinatorV2Interface diff --git a/core/services/blockhashstore/delegate.go b/core/services/blockhashstore/delegate.go index 5c172f7ee03..3631c961026 100644 --- a/core/services/blockhashstore/delegate.go +++ b/core/services/blockhashstore/delegate.go @@ -12,7 +12,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/blockhash_store" - v1 "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/trusted_blockhash_store" v2 "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2" v2plus "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2plus_interface" @@ -120,20 +119,6 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) ([]job.Servi lp := chain.LogPoller() var coordinators []Coordinator - if jb.BlockhashStoreSpec.CoordinatorV1Address != nil { - var c *v1.VRFCoordinator - if c, err = v1.NewVRFCoordinator( - jb.BlockhashStoreSpec.CoordinatorV1Address.Address(), chain.Client()); err != nil { - return nil, errors.Wrap(err, "building V1 coordinator") - } - - var coord *V1Coordinator - coord, err = NewV1Coordinator(ctx, c, lp) - if err != nil { - return nil, errors.Wrap(err, "building V1 coordinator") - } - coordinators = append(coordinators, coord) - } if jb.BlockhashStoreSpec.CoordinatorV2Address != nil { var c *v2.VRFCoordinatorV2 if c, err = v2.NewVRFCoordinatorV2( diff --git a/core/services/blockhashstore/delegate_test.go b/core/services/blockhashstore/delegate_test.go index 55f977af9da..9e9fec23ab5 100644 --- a/core/services/blockhashstore/delegate_test.go +++ b/core/services/blockhashstore/delegate_test.go @@ -107,13 +107,11 @@ func TestDelegate_ServicesForSpec(t *testing.T) { }) t.Run("happy with coordinators", func(t *testing.T) { - coordinatorV1 := cltest.NewEIP55Address() coordinatorV2 := cltest.NewEIP55Address() coordinatorV2Plus := cltest.NewEIP55Address() spec := job.Job{BlockhashStoreSpec: &job.BlockhashStoreSpec{ WaitBlocks: defaultWaitBlocks, - CoordinatorV1Address: &coordinatorV1, CoordinatorV2Address: &coordinatorV2, CoordinatorV2PlusAddress: &coordinatorV2Plus, EVMChainID: (*sqlutil.Big)(testutils.FixtureChainID), diff --git a/core/services/blockhashstore/feeder_test.go b/core/services/blockhashstore/feeder_test.go index 736e4abe92d..2194f4debcf 100644 --- a/core/services/blockhashstore/feeder_test.go +++ b/core/services/blockhashstore/feeder_test.go @@ -16,7 +16,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/mathutil" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2plus_interface" "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" @@ -33,14 +32,11 @@ const ( randomWordsFulfilledV2Plus string = "RandomWordsFulfilled" randomWordsRequestedV2 string = "RandomWordsRequested" randomWordsFulfilledV2 string = "RandomWordsFulfilled" - randomWordsRequestedV1 string = "RandomnessRequest" - randomWordsFulfilledV1 string = "RandomnessRequestFulfilled" ) var ( vrfCoordinatorV2PlusABI = evmtypes.MustGetABI(vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalMetaData.ABI) vrfCoordinatorV2ABI = evmtypes.MustGetABI(vrf_coordinator_v2.VRFCoordinatorV2MetaData.ABI) - vrfCoordinatorV1ABI = evmtypes.MustGetABI(solidity_vrf_coordinator_interface.VRFCoordinatorMetaData.ABI) _ Coordinator = &TestCoordinator{} _ BHS = &TestBHS{} @@ -397,100 +393,6 @@ func (test testCase) testFeeder(t *testing.T) { require.ElementsMatch(t, test.expectedStoredMapBlocks, maps.Keys(feeder.stored)) } -func TestFeederWithLogPollerVRFv1(t *testing.T) { - for _, test := range tests { - t.Run(test.name, test.testFeederWithLogPollerVRFv1) - } -} - -func (test testCase) testFeederWithLogPollerVRFv1(t *testing.T) { - var coordinatorAddress = common.HexToAddress("0x514910771AF9Ca656af840dff83E8264EcF986CA") - - // Instantiate log poller & coordinator. - lp := &lpmocks.LogPoller{} - lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil) - c, err := solidity_vrf_coordinator_interface.NewVRFCoordinator(coordinatorAddress, nil) - require.NoError(t, err) - coordinator := &V1Coordinator{ - c: c, - lp: lp, - } - - // Assert search window. - latest := int64(test.latest) - fromBlock := mathutil.Max(latest-int64(test.lookback), 0) - toBlock := mathutil.Max(latest-int64(test.wait), 0) - - // Construct request logs. - var requestLogs []logpoller.Log - for _, r := range test.requests { - if r.Block < uint64(fromBlock) || r.Block > uint64(toBlock) { - continue // do not include blocks outside our search window - } - requestLogs = append( - requestLogs, - newRandomnessRequestedLogV1(t, r.Block, r.ID, coordinatorAddress), - ) - } - - // Construct fulfillment logs. - var fulfillmentLogs []logpoller.Log - for _, r := range test.fulfillments { - fulfillmentLogs = append( - fulfillmentLogs, - newRandomnessFulfilledLogV1(t, r.Block, r.ID, coordinatorAddress), - ) - } - - // Mock log poller. - lp.On("LatestBlock", mock.Anything). - Return(logpoller.Block{BlockNumber: latest}, nil) - lp.On( - "LogsWithSigs", - mock.Anything, - fromBlock, - toBlock, - []common.Hash{ - solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest{}.Topic(), - }, - coordinatorAddress, - ).Return(requestLogs, nil) - lp.On( - "LogsWithSigs", - mock.Anything, - fromBlock, - latest, - []common.Hash{ - solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled{}.Topic(), - }, - coordinatorAddress, - ).Return(fulfillmentLogs, nil) - - // Instantiate feeder. - feeder := NewFeeder( - logger.TestLogger(t), - coordinator, - &test.bhs, - lp, - 0, - test.wait, - test.lookback, - 600*time.Second, - func(ctx context.Context) (uint64, error) { - return test.latest, nil - }) - - // Run feeder and assert correct results. - err = feeder.Run(testutils.Context(t)) - if test.expectedErrMsg == "" { - require.NoError(t, err) - } else { - require.EqualError(t, err, test.expectedErrMsg) - } - require.ElementsMatch(t, test.expectedStored, test.bhs.Stored) - require.ElementsMatch(t, test.expectedStoredMapBlocks, maps.Keys(feeder.stored)) -} - func TestFeederWithLogPollerVRFv2(t *testing.T) { for _, test := range tests { t.Run(test.name, test.testFeederWithLogPollerVRFv2) @@ -726,99 +628,6 @@ func TestFeeder_CachesStoredBlocks(t *testing.T) { require.Empty(t, feeder.stored) } -func newRandomnessRequestedLogV1( - t *testing.T, - requestBlock uint64, - requestID string, - coordinatorAddress common.Address, -) logpoller.Log { - e := solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest{ - KeyHash: common.HexToHash("keyhash"), - Seed: big.NewInt(0), - Sender: common.Address{}, - JobID: common.HexToHash("job"), - Fee: big.NewInt(0), - RequestID: common.HexToHash(requestID), - } - var unindexed abi.Arguments - for _, a := range vrfCoordinatorV1ABI.Events[randomWordsRequestedV1].Inputs { - if !a.Indexed { - unindexed = append(unindexed, a) - } - } - nonIndexedData, err := unindexed.Pack( - e.KeyHash, - e.Seed, - e.Sender, - e.Fee, - e.RequestID, - ) - require.NoError(t, err) - - jobIDType, err := abi.NewType("bytes32", "", nil) - require.NoError(t, err) - - jobIDArg := abi.Arguments{abi.Argument{ - Name: "jobID", - Type: jobIDType, - Indexed: true, - }} - - topic1, err := jobIDArg.Pack(e.JobID) - require.NoError(t, err) - - topic0 := vrfCoordinatorV1ABI.Events[randomWordsRequestedV1].ID - lg := logpoller.Log{ - Address: coordinatorAddress, - Data: nonIndexedData, - Topics: [][]byte{ - // first topic is the event signature - topic0.Bytes(), - // second topic is JobID since it's indexed - topic1, - }, - BlockNumber: int64(requestBlock), - EventSig: topic0, - } - return lg -} - -func newRandomnessFulfilledLogV1( - t *testing.T, - requestBlock uint64, - requestID string, - coordinatorAddress common.Address, -) logpoller.Log { - e := solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled{ - RequestId: common.HexToHash(requestID), - Output: big.NewInt(0), - } - var unindexed abi.Arguments - for _, a := range vrfCoordinatorV1ABI.Events[randomWordsFulfilledV1].Inputs { - if !a.Indexed { - unindexed = append(unindexed, a) - } - } - nonIndexedData, err := unindexed.Pack( - e.RequestId, - e.Output, - ) - require.NoError(t, err) - - topic0 := vrfCoordinatorV1ABI.Events[randomWordsFulfilledV1].ID - lg := logpoller.Log{ - Address: coordinatorAddress, - Data: nonIndexedData, - Topics: [][]byte{ - // first topic is the event signature - topic0.Bytes(), - }, - BlockNumber: int64(requestBlock), - EventSig: topic0, - } - return lg -} - func newRandomnessRequestedLogV2( t *testing.T, requestBlock uint64, diff --git a/core/services/blockhashstore/validate.go b/core/services/blockhashstore/validate.go index 84f5a206a25..ca1bdc9a900 100644 --- a/core/services/blockhashstore/validate.go +++ b/core/services/blockhashstore/validate.go @@ -41,9 +41,15 @@ func ValidatedSpec(tomlString string) (job.Job, error) { } // Required fields - if spec.CoordinatorV1Address == nil && spec.CoordinatorV2Address == nil && spec.CoordinatorV2PlusAddress == nil { + if spec.CoordinatorV1Address != nil && spec.CoordinatorV1Address.Hex() != EmptyAddress { return jb, errors.New( - `at least one of "coordinatorV1Address", "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`) + `coordinatorV1Address is no longer supported; use coordinatorV2Address or coordinatorV2PlusAddress`) + } + hasV2 := spec.CoordinatorV2Address != nil && spec.CoordinatorV2Address.Hex() != EmptyAddress + hasV2Plus := spec.CoordinatorV2PlusAddress != nil && spec.CoordinatorV2PlusAddress.Hex() != EmptyAddress + if !hasV2 && !hasV2Plus { + return jb, errors.New( + `at least one of "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`) } if spec.BlockhashStoreAddress == "" { return jb, notSet("blockhashStoreAddress") diff --git a/core/services/blockhashstore/validate_test.go b/core/services/blockhashstore/validate_test.go index c57b926ae75..0e371b56ff4 100644 --- a/core/services/blockhashstore/validate_test.go +++ b/core/services/blockhashstore/validate_test.go @@ -12,7 +12,6 @@ import ( ) func TestValidate(t *testing.T) { - v1Coordinator := types.EIP55Address("0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139") v2Coordinator := types.EIP55Address("0x2be990eE17832b59E0086534c5ea2459Aa75E38F") fromAddresses := []types.EIP55Address{("0x469aA2CD13e037DC5236320783dCfd0e641c0559")} @@ -26,7 +25,6 @@ func TestValidate(t *testing.T) { toml: ` type = "blockhashstore" name = "valid-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" waitBlocks = 59 lookbackBlocks = 159 @@ -39,8 +37,7 @@ fromAddresses = ["0x469aA2CD13e037DC5236320783dCfd0e641c0559"]`, require.NoError(t, err) require.Equal(t, job.BlockhashStore, os.Type) require.Equal(t, "valid-test", os.Name.String) - require.Equal(t, &v1Coordinator, - os.BlockhashStoreSpec.CoordinatorV1Address) + require.Nil(t, os.BlockhashStoreSpec.CoordinatorV1Address) require.Equal(t, &v2Coordinator, os.BlockhashStoreSpec.CoordinatorV2Address) require.Equal(t, int32(59), os.BlockhashStoreSpec.WaitBlocks) @@ -59,7 +56,6 @@ fromAddresses = ["0x469aA2CD13e037DC5236320783dCfd0e641c0559"]`, toml: ` type = "blockhashstore" name = "defaults-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" blockhashStoreAddress = "0x3e20Cef636EdA7ba135bCbA4fe6177Bd3cE0aB17" evmChainID = "4"`, @@ -78,7 +74,6 @@ evmChainID = "4"`, toml: ` type = "blockhashstore" name = "heartbeat-blocks-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" blockhashStoreAddress = "0x3e20Cef636EdA7ba135bCbA4fe6177Bd3cE0aB17" heartbeatPeriod = "650s" @@ -94,7 +89,7 @@ evmChainID = "4"`, }, }, { - name: "v1 only", + name: "v1 coordinator not supported", toml: ` type = "blockhashstore" name = "defaults-test" @@ -103,10 +98,7 @@ blockhashStoreAddress = "0x3e20Cef636EdA7ba135bCbA4fe6177Bd3cE0aB17" evmChainID = "4" fromAddresses = ["0x469aA2CD13e037DC5236320783dCfd0e641c0559"]`, assertion: func(t *testing.T, os job.Job, err error) { - require.NoError(t, err) - require.Equal(t, &v1Coordinator, - os.BlockhashStoreSpec.CoordinatorV1Address) - require.Nil(t, os.BlockhashStoreSpec.CoordinatorV2Address) + require.EqualError(t, err, `coordinatorV1Address is no longer supported; use coordinatorV2Address or coordinatorV2PlusAddress`) }, }, { @@ -133,7 +125,7 @@ blockhashStoreAddress = "0x3e20Cef636EdA7ba135bCbA4fe6177Bd3cE0aB17" evmChainID = "4" fromAddresses = ["0x469aA2CD13e037DC5236320783dCfd0e641c0559"]`, assertion: func(t *testing.T, os job.Job, err error) { - require.EqualError(t, err, `at least one of "coordinatorV1Address", "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`) + require.EqualError(t, err, `at least one of "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`) }, }, { @@ -165,7 +157,6 @@ fromAddresses = ["0x469aA2CD13e037DC5236320783dCfd0e641c0559"]`, toml: ` type = "blockhashstore" name = "valid-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" waitBlocks = 257 lookbackBlocks = 258 @@ -180,7 +171,6 @@ evmChainID = "4"`, toml: ` type = "blockhashstore" name = "valid-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" lookbackBlocks = 257 blockhashStoreAddress = "0x3e20Cef636EdA7ba135bCbA4fe6177Bd3cE0aB17" @@ -194,7 +184,6 @@ evmChainID = "4"`, toml: ` type = "blockhashstore" name = "valid-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" waitBlocks = 200 lookbackBlocks = 100 @@ -209,7 +198,6 @@ evmChainID = "4"`, toml: ` type = "blockhashstore" name = "valid-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" waitBlocks = 10 lookbackBlocks = 100 diff --git a/core/services/blockheaderfeeder/delegate.go b/core/services/blockheaderfeeder/delegate.go index 19954f4908b..de9273d2e74 100644 --- a/core/services/blockheaderfeeder/delegate.go +++ b/core/services/blockheaderfeeder/delegate.go @@ -14,7 +14,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/blockhash_store" - v1 "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" v2 "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2" v2plus "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2plus_interface" "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" @@ -118,19 +117,6 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) ([]job.Servi lp := chain.LogPoller() var coordinators []blockhashstore.Coordinator - if jb.BlockHeaderFeederSpec.CoordinatorV1Address != nil { - var c *v1.VRFCoordinator - if c, err = v1.NewVRFCoordinator( - jb.BlockHeaderFeederSpec.CoordinatorV1Address.Address(), chain.Client()); err != nil { - return nil, errors.Wrap(err, "building V1 coordinator") - } - var coord *blockhashstore.V1Coordinator - coord, err = blockhashstore.NewV1Coordinator(ctx, c, lp) - if err != nil { - return nil, errors.Wrap(err, "building V1 coordinator") - } - coordinators = append(coordinators, coord) - } if jb.BlockHeaderFeederSpec.CoordinatorV2Address != nil { var c *v2.VRFCoordinatorV2 if c, err = v2.NewVRFCoordinatorV2( diff --git a/core/services/blockheaderfeeder/validate.go b/core/services/blockheaderfeeder/validate.go index 77b0935c95c..fbd763ea49f 100644 --- a/core/services/blockheaderfeeder/validate.go +++ b/core/services/blockheaderfeeder/validate.go @@ -7,9 +7,12 @@ import ( "github.com/pelletier/go-toml" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-evm/pkg/utils" "github.com/smartcontractkit/chainlink/v2/core/services/job" ) +var emptyCoordinatorAddress = utils.ZeroAddress.Hex() + // ValidatedSpec validates and converts the given toml string to a job.Job. func ValidatedSpec(tomlString string) (job.Job, error) { jb := job.Job{ @@ -38,9 +41,15 @@ func ValidatedSpec(tomlString string) (job.Job, error) { } // Required fields - if spec.CoordinatorV1Address == nil && spec.CoordinatorV2Address == nil && spec.CoordinatorV2PlusAddress == nil { + if spec.CoordinatorV1Address != nil && spec.CoordinatorV1Address.Hex() != emptyCoordinatorAddress { + return jb, errors.New( + `coordinatorV1Address is no longer supported; use coordinatorV2Address or coordinatorV2PlusAddress`) + } + hasV2 := spec.CoordinatorV2Address != nil && spec.CoordinatorV2Address.Hex() != emptyCoordinatorAddress + hasV2Plus := spec.CoordinatorV2PlusAddress != nil && spec.CoordinatorV2PlusAddress.Hex() != emptyCoordinatorAddress + if !hasV2 && !hasV2Plus { return jb, errors.New( - `at least one of "coordinatorV1Address", "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`) + `at least one of "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`) } if spec.BlockhashStoreAddress == "" { return jb, notSet("blockhashStoreAddress") diff --git a/core/services/blockheaderfeeder/validate_test.go b/core/services/blockheaderfeeder/validate_test.go index 341c077974e..59809d8690e 100644 --- a/core/services/blockheaderfeeder/validate_test.go +++ b/core/services/blockheaderfeeder/validate_test.go @@ -12,7 +12,6 @@ import ( ) func TestValidate(t *testing.T) { - v1Coordinator := types.EIP55Address("0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139") v2Coordinator := types.EIP55Address("0x2be990eE17832b59E0086534c5ea2459Aa75E38F") v2PlusCoordinator := types.EIP55Address("0x92B5e28Ac583812874e4271380c7d070C5FB6E6b") fromAddresses := []types.EIP55Address{("0x469aA2CD13e037DC5236320783dCfd0e641c0559")} @@ -27,7 +26,6 @@ func TestValidate(t *testing.T) { toml: ` type = "blockheaderfeeder" name = "valid-test" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" coordinatorV2PlusAddress = "0x92B5e28Ac583812874e4271380c7d070C5FB6E6b" lookbackBlocks = 2000 @@ -45,8 +43,7 @@ storeBlockhashesBatchSize = 10 require.NoError(t, err) require.Equal(t, job.BlockHeaderFeeder, os.Type) require.Equal(t, "valid-test", os.Name.String) - require.Equal(t, &v1Coordinator, - os.BlockHeaderFeederSpec.CoordinatorV1Address) + require.Nil(t, os.BlockHeaderFeederSpec.CoordinatorV1Address) require.Equal(t, &v2Coordinator, os.BlockHeaderFeederSpec.CoordinatorV2Address) require.Equal(t, &v2PlusCoordinator, @@ -74,7 +71,6 @@ storeBlockhashesBatchSize = 10 type = "blockheaderfeeder" name = "defaults-test" evmChainID = "4" -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" coordinatorV2Address = "0x2be990eE17832b59E0086534c5ea2459Aa75E38F" blockhashStoreAddress = "0x3e20Cef636EdA7ba135bCbA4fe6177Bd3cE0aB17" batchBlockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" @@ -132,7 +128,7 @@ getBlockhashesBatchSize = 20 storeBlockhashesBatchSize = 10 `, assertion: func(t *testing.T, os job.Job, err error) { - require.Equal(t, `at least one of "coordinatorV1Address", "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`, err.Error()) + require.Equal(t, `at least one of "coordinatorV2Address" and "coordinatorV2PlusAddress" must be set`, err.Error()) }, }, { @@ -142,7 +138,7 @@ type = "blockheaderfeeder" name = "missing blockhash store address" lookbackBlocks = 2000 waitBlocks = 500 -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" +coordinatorV2Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" batchBlockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" pollPeriod = "23s" runTimeout = "7s" @@ -162,7 +158,7 @@ type = "blockheaderfeeder" name = "missing batch blockhash store address" lookbackBlocks = 2000 waitBlocks = 500 -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" +coordinatorV2Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" blockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" pollPeriod = "23s" runTimeout = "7s" @@ -182,7 +178,7 @@ type = "blockheaderfeeder" name = "missing evmChainID" lookbackBlocks = 2000 waitBlocks = 500 -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" +coordinatorV2Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" blockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" batchBlockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" pollPeriod = "23s" @@ -202,7 +198,7 @@ type = "blockheaderfeeder" name = "wait block lower than 256 blocks" lookbackBlocks = 2000 waitBlocks = 255 -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" +coordinatorV2Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" blockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" batchBlockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" pollPeriod = "23s" @@ -223,7 +219,7 @@ type = "blockheaderfeeder" name = "lookback block lower than 256 blocks" lookbackBlocks = 255 waitBlocks = 256 -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" +coordinatorV2Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" blockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" batchBlockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" pollPeriod = "23s" @@ -244,7 +240,7 @@ type = "blockheaderfeeder" name = "lookback blocks lower than wait blocks" lookbackBlocks = 300 waitBlocks = 500 -coordinatorV1Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" +coordinatorV2Address = "0x1F72B4A5DCf7CC6d2E38423bF2f4BFA7db97d139" blockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" batchBlockhashStoreAddress = "0xD04E5b2ea4e55AEbe6f7522bc2A69Ec6639bfc63" pollPeriod = "23s" diff --git a/core/services/job/job_orm_test.go b/core/services/job/job_orm_test.go index 24455b054e3..34e4ad8b345 100644 --- a/core/services/job/job_orm_test.go +++ b/core/services/job/job_orm_test.go @@ -296,7 +296,7 @@ func TestORM(t *testing.T) { t.Run("it creates and deletes records for blockhash store jobs", func(t *testing.T) { ctx := testutils.Context(t) bhsJob, err := blockhashstore.ValidatedSpec( - testspecs.GenerateBlockhashStoreSpec(testspecs.BlockhashStoreSpecParams{CoordinatorV1Address: "0x613a38AC1659769640aaE063C651F48E0250454C"}).Toml()) + testspecs.GenerateBlockhashStoreSpec(testspecs.BlockhashStoreSpecParams{CoordinatorV2Address: "0x613a38AC1659769640aaE063C651F48E0250454C"}).Toml()) require.NoError(t, err) err = orm.CreateJob(ctx, &bhsJob) @@ -578,7 +578,10 @@ func TestORM_CreateJob_VRFV2(t *testing.T) { cltest.AssertCount(t, db, "vrf_specs", 0) cltest.AssertCount(t, db, "jobs", 0) - jb, err = vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{RequestTimeout: 1 * time.Hour}).Toml()) + jb, err = vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ + RequestTimeout: 1 * time.Hour, + FromAddresses: fromAddresses, + }).Toml()) require.NoError(t, err) require.NoError(t, jobORM.CreateJob(ctx, &jb)) cltest.AssertCount(t, db, "vrf_specs", 1) diff --git a/core/services/job/models.go b/core/services/job/models.go index 13b896b59a7..cf219833433 100644 --- a/core/services/job/models.go +++ b/core/services/job/models.go @@ -611,8 +611,7 @@ type VRFSpec struct { type BlockhashStoreSpec struct { ID int32 - // CoordinatorV1Address is the VRF V1 coordinator to watch for unfulfilled requests. If empty, - // no V1 coordinator will be watched. + // CoordinatorV1Address is a legacy field from VRF V1. It remains for API/DB compatibility; non-zero values are rejected at spec validation. CoordinatorV1Address *evmtypes.EIP55Address `toml:"coordinatorV1Address"` // CoordinatorV2Address is the VRF V2 coordinator to watch for unfulfilled requests. If empty, @@ -668,8 +667,7 @@ type BlockhashStoreSpec struct { type BlockHeaderFeederSpec struct { ID int32 - // CoordinatorV1Address is the VRF V1 coordinator to watch for unfulfilled requests. If empty, - // no V1 coordinator will be watched. + // CoordinatorV1Address is a legacy field from VRF V1. It remains for API/DB compatibility; non-zero values are rejected at spec validation. CoordinatorV1Address *evmtypes.EIP55Address `toml:"coordinatorV1Address"` // CoordinatorV2Address is the VRF V2 coordinator to watch for unfulfilled requests. If empty, diff --git a/core/services/pipeline/common.go b/core/services/pipeline/common.go index 47486f6dbd5..542f3ce04e6 100644 --- a/core/services/pipeline/common.go +++ b/core/services/pipeline/common.go @@ -340,7 +340,7 @@ const ( TaskTypeMultiply TaskType = "multiply" TaskTypeSum TaskType = "sum" TaskTypeUppercase TaskType = "uppercase" - TaskTypeVRF TaskType = "vrf" + TaskTypeVRF TaskType = "vrf" // legacy VRF v1; UnmarshalTaskFromMap returns a removal error TaskTypeVRFV2 TaskType = "vrfv2" TaskTypeVRFV2Plus TaskType = "vrfv2plus" @@ -400,7 +400,7 @@ func UnmarshalTaskFromMap(taskType TaskType, taskMap any, ID int, dotID string) case TaskTypeDivide: task = &DivideTask{BaseTask: BaseTask{id: ID, dotID: dotID}} case TaskTypeVRF: - task = &VRFTask{BaseTask: BaseTask{id: ID, dotID: dotID}} + return nil, pkgerrors.Errorf("pipeline task type %q (VRF v1) has been removed and is no longer supported; migrate the observationSource to vrfv2 or vrfv2plus", taskType) case TaskTypeVRFV2: task = &VRFTaskV2{BaseTask: BaseTask{id: ID, dotID: dotID}} case TaskTypeVRFV2Plus: diff --git a/core/services/pipeline/common_test.go b/core/services/pipeline/common_test.go index af798e158f5..e3bbadfc137 100644 --- a/core/services/pipeline/common_test.go +++ b/core/services/pipeline/common_test.go @@ -137,6 +137,14 @@ func TestUnmarshalTaskFromMap(t *testing.T) { require.EqualError(t, err, `UnmarshalTaskFromMap: unknown task type: "xxx"`) }) + t.Run("legacy vrf pipeline task type is rejected", func(t *testing.T) { + taskMap := map[string]string{} + _, err := pipeline.UnmarshalTaskFromMap(pipeline.TaskTypeVRF, taskMap, 0, "foo-dot-id") + require.ErrorContains(t, err, `UnmarshalTaskFromMap: pipeline task type "vrf"`) + require.ErrorContains(t, err, "has been removed") + require.ErrorContains(t, err, "vrfv2 or vrfv2plus") + }) + tests := []struct { taskType pipeline.TaskType expectedTaskType any @@ -154,7 +162,6 @@ func TestUnmarshalTaskFromMap(t *testing.T) { {pipeline.TaskTypeJSONParse, &pipeline.JSONParseTask{}}, {pipeline.TaskTypeCBORParse, &pipeline.CBORParseTask{}}, {pipeline.TaskTypeAny, &pipeline.AnyTask{}}, - {pipeline.TaskTypeVRF, &pipeline.VRFTask{}}, {pipeline.TaskTypeVRFV2, &pipeline.VRFTaskV2{}}, {pipeline.TaskTypeVRFV2Plus, &pipeline.VRFTaskV2Plus{}}, {pipeline.TaskTypeEstimateGasLimit, &pipeline.EstimateGasLimitTask{}}, diff --git a/core/services/pipeline/runner.go b/core/services/pipeline/runner.go index 72e535687b9..35d675f7e58 100644 --- a/core/services/pipeline/runner.go +++ b/core/services/pipeline/runner.go @@ -355,8 +355,6 @@ func (r *runner) InitializePipeline(spec Spec) (pipeline *Pipeline, err error) { task.(*ETHCallTask).config = r.config task.(*ETHCallTask).specGasLimit = spec.GasLimit task.(*ETHCallTask).jobType = spec.JobType - case TaskTypeVRF: - task.(*VRFTask).keyStore = r.vrfKeyStore case TaskTypeVRFV2: task.(*VRFTaskV2).keyStore = r.vrfKeyStore case TaskTypeVRFV2Plus: diff --git a/core/services/pipeline/task.vrf.go b/core/services/pipeline/task.vrf.go deleted file mode 100644 index 27c54a028ff..00000000000 --- a/core/services/pipeline/task.vrf.go +++ /dev/null @@ -1,118 +0,0 @@ -package pipeline - -import ( - "bytes" - "context" - "encoding/hex" - stderrors "errors" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/pkg/errors" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" -) - -type VRFTask struct { - BaseTask `mapstructure:",squash"` - PublicKey string `json:"publicKey"` - RequestBlockHash string `json:"requestBlockHash"` - RequestBlockNumber string `json:"requestBlockNumber"` - Topics string `json:"topics"` - - keyStore VRFKeyStore -} - -type VRFKeyStore interface { - GenerateProof(id string, seed *big.Int) (vrfkey.Proof, error) -} - -var _ Task = (*VRFTask)(nil) - -func (t *VRFTask) Type() TaskType { - return TaskTypeVRF -} - -func (t *VRFTask) Run(_ context.Context, _ logger.Logger, vars Vars, inputs []Result) (result Result, runInfo RunInfo) { - if len(inputs) != 1 { - return Result{Error: ErrWrongInputCardinality}, runInfo - } - if inputs[0].Error != nil { - return Result{Error: ErrInputTaskErrored}, runInfo - } - logValues, ok := inputs[0].Value.(map[string]any) - if !ok { - return Result{Error: errors.Wrap(ErrBadInput, "expected map input")}, runInfo - } - var ( - pubKey BytesParam - requestBlockHash BytesParam - requestBlockNumber Uint64Param - topics HashSliceParam - ) - err := stderrors.Join( - errors.Wrap(ResolveParam(&pubKey, From(VarExpr(t.PublicKey, vars))), "publicKey"), - errors.Wrap(ResolveParam(&requestBlockHash, From(VarExpr(t.RequestBlockHash, vars))), "requestBlockHash"), - errors.Wrap(ResolveParam(&requestBlockNumber, From(VarExpr(t.RequestBlockNumber, vars))), "requestBlockNumber"), - errors.Wrap(ResolveParam(&topics, From(VarExpr(t.Topics, vars))), "topics"), - ) - if err != nil { - return Result{Error: err}, runInfo - } - - requestKeyHash, ok := logValues["keyHash"].([32]byte) - if !ok { - return Result{Error: errors.Wrapf(ErrBadInput, "invalid keyHash")}, runInfo - } - requestPreSeed, ok := logValues["seed"].(*big.Int) - if !ok { - return Result{Error: errors.Wrapf(ErrBadInput, "invalid preSeed")}, runInfo - } - requestJobID, ok := logValues["jobID"].([32]byte) - if !ok { - return Result{Error: errors.Wrapf(ErrBadInput, "invalid requestJobID")}, runInfo - } - pk, err := secp256k1.NewPublicKeyFromBytes(pubKey) - if err != nil { - return Result{Error: fmt.Errorf("failed to create PublicKey from bytes %w", err)}, runInfo - } - pkh := pk.MustHash() - // Validate the key against the spec - if !bytes.Equal(requestKeyHash[:], pkh[:]) { - return Result{Error: fmt.Errorf("invalid key hash %v expected %v", hex.EncodeToString(requestKeyHash[:]), hex.EncodeToString(pkh[:]))}, runInfo - } - preSeed, err := proof.BigToSeed(requestPreSeed) - if err != nil { - return Result{Error: fmt.Errorf("unable to parse preseed %v", preSeed)}, runInfo - } - if !bytes.Equal(topics[0][:], requestJobID[:]) && !bytes.Equal(topics[1][:], requestJobID[:]) { - return Result{Error: fmt.Errorf("request jobID %v doesn't match expected %v or %v", requestJobID[:], topics[0][:], topics[1][:])}, runInfo - } - if len(requestBlockHash) != common.HashLength { - return Result{Error: fmt.Errorf("invalid BlockHash length %d expected %d", len(requestBlockHash), common.HashLength)}, runInfo - } - preSeedData := proof.PreSeedData{ - PreSeed: preSeed, - BlockHash: common.BytesToHash(requestBlockHash), - BlockNum: uint64(requestBlockNumber), - } - finalSeed := proof.FinalSeed(preSeedData) - p, err := t.keyStore.GenerateProof(pk.String(), finalSeed) - if err != nil { - return Result{Error: err}, runInfo - } - onChainProof, err := proof.GenerateProofResponseFromProof(p, preSeedData) - if err != nil { - return Result{Error: err}, retryableRunInfo() - } - var results = make(map[string]any) - results["onChainProof"] = hexutil.Encode(onChainProof[:]) - - return Result{Value: hexutil.Encode(onChainProof[:])}, runInfo -} diff --git a/core/services/pipeline/vrf_keystore.go b/core/services/pipeline/vrf_keystore.go new file mode 100644 index 00000000000..e7ae88b249c --- /dev/null +++ b/core/services/pipeline/vrf_keystore.go @@ -0,0 +1,12 @@ +package pipeline + +import ( + "math/big" + + "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" +) + +// VRFKeyStore is the keystore surface required by vrfv2 and vrfv2plus pipeline tasks. +type VRFKeyStore interface { + GenerateProof(id string, seed *big.Int) (vrfkey.Proof, error) +} diff --git a/core/services/relay/relay.go b/core/services/relay/relay.go index 649f052be2e..2811fc050df 100644 --- a/core/services/relay/relay.go +++ b/core/services/relay/relay.go @@ -59,7 +59,7 @@ func (r *ServerAdapter) NewPluginProvider(ctx context.Context, rargs types.Relay return r.NewAutomationProvider(ctx, rargs, pargs) case types.OCR3Capability, types.DonTimePlugin, types.RingPlugin: return r.NewOCR3CapabilityProvider(ctx, rargs, pargs) - case types.DKG, types.OCR2VRF, types.GenericPlugin, types.VaultPlugin: + case types.DKG, types.GenericPlugin, types.VaultPlugin: return r.Relayer.NewPluginProvider(ctx, rargs, pargs) case types.LLO: return nil, fmt.Errorf("provider type not supported: %s", rargs.ProviderType) diff --git a/core/services/vrf/delegate.go b/core/services/vrf/delegate.go index 97990388003..dd24c3cb4a9 100644 --- a/core/services/vrf/delegate.go +++ b/core/services/vrf/delegate.go @@ -10,24 +10,20 @@ import ( "github.com/avast/retry-go/v4" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/theodesp/go-heaps/pairing" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/batch_vrf_coordinator_v2" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_coordinator_v2_5" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/vrf_owner" "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/aggregator_v3_interface" "github.com/smartcontractkit/chainlink-evm/pkg/assets" "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" - "github.com/smartcontractkit/chainlink-evm/pkg/log" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - v1 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v1" v2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" ) @@ -100,10 +96,6 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) ([]job.Servi if !ok { return nil, fmt.Errorf("vrf is not available in LOOP Plugin mode: %w", stderrors.ErrUnsupported) } - coordinator, err := solidity_vrf_coordinator_interface.NewVRFCoordinator(jb.VRFSpec.CoordinatorAddress.Address(), chain.Client()) - if err != nil { - return nil, err - } coordinatorV2, err := vrf_coordinator_v2.NewVRFCoordinatorV2(jb.VRFSpec.CoordinatorAddress.Address(), chain.Client()) if err != nil { return nil, err @@ -138,7 +130,6 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) ([]job.Servi "externalJobID", jb.ExternalJobID, "coordinatorAddress", jb.VRFSpec.CoordinatorAddress, ) - lV1 := l.Named("VRFListener") lV2 := l.Named("VRFListenerV2") lV2Plus := l.Named("VRFListenerV2Plus") @@ -256,30 +247,8 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) ([]job.Servi ), }, nil } - if _, ok := task.(*pipeline.VRFTask); ok { - return []job.ServiceCtx{&v1.Listener{ - Cfg: chain.Config().EVM(), - FeeCfg: chain.Config().EVM().GasEstimator(), - L: logger.Sugared(lV1), - Coordinator: coordinator, - PipelineRunner: d.pr, - GethKs: d.ks.Eth(), - Job: jb, - MailMon: d.mailMon, - // Note the mailbox size effectively sets a limit on how many logs we can replay - // in the event of a VRF outage. - ReqLogs: mailbox.NewHighCapacity[log.Broadcast](), - ChStop: make(chan struct{}), - WaitOnStop: make(chan struct{}), - NewHead: make(chan struct{}, 1), - BlockNumberToReqID: pairing.New(), - ReqAdded: func() {}, - Deduper: vrfcommon.NewLogDeduper(int(chain.Config().EVM().FinalityDepth())), - Chain: chain, - }}, nil - } } - return nil, errors.New("invalid job spec expected a vrf task") + return nil, errors.New("invalid job spec expected a vrfv2 or vrfv2plus task") } // CheckFromAddressesExist returns an error if and only if one of the addresses diff --git a/core/services/vrf/delegate_test.go b/core/services/vrf/delegate_test.go index 3a703ccfa4c..4f33bd95782 100644 --- a/core/services/vrf/delegate_test.go +++ b/core/services/vrf/delegate_test.go @@ -1,14 +1,11 @@ package vrf_test import ( - "bytes" "math/big" "testing" "time" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -16,10 +13,8 @@ import ( commonkeystore "github.com/smartcontractkit/chainlink-common/keystore" "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox/mailboxtest" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink-evm/pkg/assets" "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" "github.com/smartcontractkit/chainlink-evm/pkg/client/clienttest" @@ -27,11 +22,8 @@ import ( "github.com/smartcontractkit/chainlink-evm/pkg/heads" "github.com/smartcontractkit/chainlink-evm/pkg/heads/headstest" "github.com/smartcontractkit/chainlink-evm/pkg/keys" - "github.com/smartcontractkit/chainlink-evm/pkg/log" "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" "github.com/smartcontractkit/chainlink-evm/pkg/txmgr" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" - evmutils "github.com/smartcontractkit/chainlink-evm/pkg/utils" log_mocks "github.com/smartcontractkit/chainlink/v2/common/log/mocks" "github.com/smartcontractkit/chainlink/v2/core/bridges" @@ -47,8 +39,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/vrf" vrf_mocks "github.com/smartcontractkit/chainlink/v2/core/services/vrf/mocks" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/solidity_cross_tests" - v1 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v1" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/testdata/testspecs" ) @@ -70,7 +60,6 @@ type vrfUniverse struct { func buildVrfUni(t *testing.T, db *sqlx.DB, cfg chainlink.GeneralConfig) vrfUniverse { ctx := testutils.Context(t) - // Mock all chain interactions lb := log_mocks.NewBroadcaster(t) servicetest.SetupNoOpMock(lb) lb.On("AddDependents", 1).Maybe() @@ -81,7 +70,6 @@ func buildVrfUni(t *testing.T, db *sqlx.DB, cfg chainlink.GeneralConfig) vrfUniv lggr := logger.TestLogger(t) hb := heads.NewBroadcaster(lggr) - // Don't mock db interactions prm := pipeline.NewORM(db, lggr, cfg.JobPipeline().MaxSuccessfulRuns()) btORM := bridges.NewORM(db) ks := keystore.NewInMemory(db, commonkeystore.FastScryptParams, lggr.Infof) @@ -121,7 +109,6 @@ func buildVrfUni(t *testing.T, db *sqlx.DB, cfg chainlink.GeneralConfig) vrfUniv k, err2 := ks.Eth().Create(testutils.Context(t), testutils.FixtureChainID) require.NoError(t, err2) submitter := k.Address - require.NoError(t, err) vrfkey, err3 := ks.VRF().Create(ctx) require.NoError(t, err3) @@ -141,387 +128,14 @@ func buildVrfUni(t *testing.T, db *sqlx.DB, cfg chainlink.GeneralConfig) vrfUniv } } -func generateCallbackReturnValues(t *testing.T, fulfilled bool) []byte { - callback, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ - {Name: "callback_contract", Type: "address"}, - {Name: "randomness_fee", Type: "int256"}, - {Name: "seed_and_block_num", Type: "bytes32"}}) - require.NoError(t, err) - var args abi.Arguments = []abi.Argument{{Type: callback}} - if fulfilled { - // Empty callback - b, err2 := args.Pack(solidity_vrf_coordinator_interface.Callbacks{ - RandomnessFee: big.NewInt(10), - SeedAndBlockNum: evmutils.EmptyHash, - }) - require.NoError(t, err2) - return b - } - b, err := args.Pack(solidity_vrf_coordinator_interface.Callbacks{ - RandomnessFee: big.NewInt(10), - SeedAndBlockNum: evmutils.NewHash(), - }) - require.NoError(t, err) - return b -} - -func waitForChannel(t *testing.T, c chan struct{}, timeout time.Duration, errMsg string) { - select { - case <-c: - case <-time.After(timeout): - t.Error(errMsg) - } -} - -func setup(t *testing.T) (vrfUniverse, *v1.Listener, job.Job) { - db := pgtest.NewSqlxDB(t) - cfg := configtest.NewTestGeneralConfig(t) - vuni := buildVrfUni(t, db, cfg) - - mailMon := servicetest.Run(t, mailboxtest.NewMonitor(t)) - - vd := vrf.NewDelegate( - db, - vuni.ks, - vuni.pr, - vuni.prm, - vuni.legacyChains, - logger.TestLogger(t), - mailMon) - vs := testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{PublicKey: vuni.vrfkey.PublicKey.String(), EVMChainID: testutils.FixtureChainID.String()}) - jb, err := vrfcommon.ValidatedVRFSpec(vs.Toml()) - require.NoError(t, err) - ctx := testutils.Context(t) - err = vuni.jrm.CreateJob(ctx, &jb) - require.NoError(t, err) - vl, err := vd.ServicesForSpec(testutils.Context(t), jb) - require.NoError(t, err) - require.Len(t, vl, 1) - listener := vl[0].(*v1.Listener) - // Start the listenerV1 - go func() { - listener.RunLogListener([]func(){}, 6) - }() - go func() { - listener.RunHeadListener(func() {}) - }() - servicetest.Run(t, listener) - return vuni, listener, jb -} - -func TestDelegate_ReorgAttackProtection(t *testing.T) { - vuni, listener, jb := setup(t) - - // Same request has already been fulfilled twice - reqID := evmutils.NewHash() - var reqIDBytes [32]byte - copy(reqIDBytes[:], reqID.Bytes()) - listener.SetRespCount(reqIDBytes, 2) - - // Send in the same request again - pk, err := secp256k1.NewPublicKeyFromHex(vuni.vrfkey.PublicKey.String()) - require.NoError(t, err) - added := make(chan struct{}) - listener.SetReqAdded(func() { - added <- struct{}{} - }) - preSeed := common.BigToHash(big.NewInt(42)).Bytes() - txHash := evmutils.NewHash() - vuni.lb.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(false, nil).Maybe() - vuni.lb.On("MarkConsumed", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe() - vuni.ec.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(generateCallbackReturnValues(t, false), nil).Maybe() - ctx := testutils.Context(t) - listener.HandleLog(ctx, log.NewLogBroadcast(types.Log{ - // Data has all the NON-indexed parameters - Data: bytes.Join([][]byte{pk.MustHash().Bytes(), // key hash - preSeed, // preSeed - evmutils.NewHash().Bytes(), // sender - evmutils.NewHash().Bytes(), // fee - reqID.Bytes()}, []byte{}, // requestID - ), - // JobID is indexed, thats why it lives in the Topics. - Topics: []common.Hash{ - solidity_cross_tests.VRFRandomnessRequestLogTopic(), - jb.ExternalIDEncodeStringToTopic(), // jobID - }, - BlockNumber: 10, - TxHash: txHash, - }, vuni.cid, nil)) - - // Wait until the log is present - waitForChannel(t, added, time.Second, "request not added to the queue") - reqs := listener.ReqsConfirmedAt() - if assert.Len(t, reqs, 1) { - // It should be confirmed at 10+6*(2^2) - assert.Equal(t, uint64(34), reqs[0]) - } -} - -func TestDelegate_ValidLog(t *testing.T) { - vuni, listener, jb := setup(t) - txHash := evmutils.NewHash() - reqID1 := evmutils.NewHash() - reqID2 := evmutils.NewHash() - keyID := vuni.vrfkey.PublicKey.String() - pk, err := secp256k1.NewPublicKeyFromHex(keyID) - require.NoError(t, err) - added := make(chan struct{}) - listener.SetReqAdded(func() { - added <- struct{}{} - }) - preSeed := common.BigToHash(big.NewInt(42)).Bytes() - bh := evmutils.NewHash() - var tt = []struct { - reqID [32]byte - log types.Log - }{ - { - reqID: reqID1, - log: types.Log{ - // Data has all the NON-indexed parameters - Data: bytes.Join([][]byte{ - pk.MustHash().Bytes(), // key hash - common.BigToHash(big.NewInt(42)).Bytes(), // seed - evmutils.NewHash().Bytes(), // sender - evmutils.NewHash().Bytes(), // fee - reqID1.Bytes()}, // requestID - []byte{}), - // JobID is indexed, thats why it lives in the Topics. - Topics: []common.Hash{ - solidity_cross_tests.VRFRandomnessRequestLogTopic(), - jb.ExternalIDEncodeStringToTopic(), // jobID STRING - }, - TxHash: txHash, - BlockNumber: 10, - BlockHash: bh, - Index: 1, - }, - }, - { - - reqID: reqID2, - log: types.Log{ - Data: bytes.Join([][]byte{ - pk.MustHash().Bytes(), // key hash - common.BigToHash(big.NewInt(42)).Bytes(), // seed - evmutils.NewHash().Bytes(), // sender - evmutils.NewHash().Bytes(), // fee - reqID2.Bytes()}, // requestID - []byte{}), - Topics: []common.Hash{ - solidity_cross_tests.VRFRandomnessRequestLogTopic(), - jb.ExternalIDEncodeBytesToTopic(), // jobID BYTES - }, - TxHash: txHash, - BlockNumber: 10, - BlockHash: bh, - Index: 2, - }, - }, - } - - runComplete := make(chan struct{}) - vuni.pr.OnRunFinished(func(run *pipeline.Run) { - if run.State == pipeline.RunStatusCompleted { - runComplete <- struct{}{} - } - }) - - consumed := make(chan struct{}) - for i, tc := range tt { - ctx := testutils.Context(t) - vuni.lb.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(false, nil) - vuni.lb.On("MarkConsumed", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - consumed <- struct{}{} - }).Return(nil).Once() - // Expect a call to check if the req is already fulfilled. - vuni.ec.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(generateCallbackReturnValues(t, false), nil) - - listener.HandleLog(ctx, log.NewLogBroadcast(tc.log, vuni.cid, nil)) - // Wait until the log is present - waitForChannel(t, added, time.Second, "request not added to the queue") - // Feed it a head which confirms it. - listener.OnNewLongestChain(testutils.Context(t), &evmtypes.Head{Number: 16}) - waitForChannel(t, consumed, 2*time.Second, "did not mark consumed") - - // Ensure we created a successful run. - waitForChannel(t, runComplete, 2*time.Second, "pipeline not complete") - runs, err := vuni.prm.GetAllRuns(ctx) - require.NoError(t, err) - require.Len(t, runs, i+1) - assert.False(t, runs[0].FatalErrors.HasError()) - // Should have 4 tasks all completed - assert.Len(t, runs[0].PipelineTaskRuns, 4) - - p, err := vuni.ks.VRF().GenerateProof(keyID, evmutils.MustHash(string(bytes.Join([][]byte{preSeed, bh.Bytes()}, []byte{}))).Big()) - require.NoError(t, err) - vuni.lb.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(false, nil) - vuni.lb.On("MarkConsumed", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - consumed <- struct{}{} - }).Return(nil).Once() - // If we send a completed log we should the respCount increase - var reqIDBytes []byte - copy(reqIDBytes, tc.reqID[:]) - listener.HandleLog(ctx, log.NewLogBroadcast(types.Log{ - // Data has all the NON-indexed parameters - Data: bytes.Join([][]byte{reqIDBytes, // output - p.Output.Bytes(), - }, []byte{}, - ), - BlockNumber: 10, - TxHash: txHash, - Index: uint(i), - }, vuni.cid, &solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled{RequestId: tc.reqID})) - waitForChannel(t, consumed, 2*time.Second, "fulfillment log not marked consumed") - // Should record that we've responded to this request - assert.Equal(t, uint64(1), listener.RespCount(tc.reqID)) - } -} - -func TestDelegate_InvalidLog(t *testing.T) { - vuni, listener, jb := setup(t) - vuni.lb.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(false, nil) - done := make(chan struct{}) - vuni.lb.On("MarkConsumed", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - done <- struct{}{} - }).Return(nil).Once() - // Expect a call to check if the req is already fulfilled. - vuni.ec.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(generateCallbackReturnValues(t, false), nil) - - added := make(chan struct{}) - listener.SetReqAdded(func() { - added <- struct{}{} - }) - // Send an invalid log (keyhash doesnt match) - ctx := testutils.Context(t) - listener.HandleLog(ctx, log.NewLogBroadcast(types.Log{ - // Data has all the NON-indexed parameters - Data: append(append(append(append( - evmutils.NewHash().Bytes(), // key hash - common.BigToHash(big.NewInt(42)).Bytes()...), // seed - evmutils.NewHash().Bytes()...), // sender - evmutils.NewHash().Bytes()...), // fee - evmutils.NewHash().Bytes()...), // requestID - // JobID is indexed, that's why it lives in the Topics. - Topics: []common.Hash{ - solidity_cross_tests.VRFRandomnessRequestLogTopic(), - jb.ExternalIDEncodeBytesToTopic(), // jobID - }, - Address: common.Address{}, - BlockNumber: 10, - TxHash: common.Hash{}, - TxIndex: 0, - BlockHash: common.Hash{}, - Index: 0, - Removed: false, - }, vuni.cid, nil)) - waitForChannel(t, added, time.Second, "request not queued") - // Feed it a head which confirms it. - listener.OnNewLongestChain(testutils.Context(t), &evmtypes.Head{Number: 16}) - waitForChannel(t, done, time.Second, "log not consumed") - - // Should create a run that errors in the vrf task - runs, err := vuni.prm.GetAllRuns(ctx) - require.NoError(t, err) - require.Len(t, runs, 1) - for _, tr := range runs[0].PipelineTaskRuns { - if tr.Type == pipeline.TaskTypeVRF { - assert.Contains(t, tr.Error.String, "invalid key hash") - } - // Log parsing task itself should succeed. - if tr.Type != pipeline.TaskTypeETHABIDecodeLog { - assert.False(t, tr.Output.Valid) - } - } - - db := pgtest.NewSqlxDB(t) - txStore := txmgr.NewTxStore(db, logger.TestLogger(t)) - - txes, err := txStore.GetAllTxes(testutils.Context(t)) - require.NoError(t, err) - require.Empty(t, txes) -} - -func TestFulfilledCheck(t *testing.T) { - vuni, listener, jb := setup(t) - vuni.lb.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(false, nil) - done := make(chan struct{}) - vuni.lb.On("MarkConsumed", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - done <- struct{}{} - }).Return(nil).Once() - // Expect a call to check if the req is already fulfilled. - // We return already fulfilled - vuni.ec.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(generateCallbackReturnValues(t, true), nil) - - added := make(chan struct{}) - listener.SetReqAdded(func() { - added <- struct{}{} - }) - // Send an invalid log (keyhash doesn't match) - ctx := testutils.Context(t) - listener.HandleLog(ctx, log.NewLogBroadcast( - types.Log{ - // Data has all the NON-indexed parameters - Data: bytes.Join([][]byte{ - vuni.vrfkey.PublicKey.MustHash().Bytes(), // key hash - common.BigToHash(big.NewInt(42)).Bytes(), // seed - evmutils.NewHash().Bytes(), // sender - evmutils.NewHash().Bytes(), // fee - evmutils.NewHash().Bytes()}, // requestID - []byte{}), - // JobID is indexed, that's why it lives in the Topics. - Topics: []common.Hash{ - solidity_cross_tests.VRFRandomnessRequestLogTopic(), - jb.ExternalIDEncodeBytesToTopic(), // jobID STRING - }, - //TxHash: evmutils.NewHash().Bytes(), - BlockNumber: 10, - //BlockHash: evmutils.NewHash().Bytes(), - }, vuni.cid, nil)) - - // Should queue the request, even though its already fulfilled - waitForChannel(t, added, time.Second, "request not queued") - listener.OnNewLongestChain(testutils.Context(t), &evmtypes.Head{Number: 16}) - waitForChannel(t, done, time.Second, "log not consumed") - - // Should consume the log with no run - runs, err := vuni.prm.GetAllRuns(ctx) - require.NoError(t, err) - require.Empty(t, runs) -} - func Test_CheckFromAddressMaxGasPrices(t *testing.T) { t.Run("returns nil error if gasLanePrice not set in job spec", func(tt *testing.T) { - spec := ` -type = "vrf" -schemaVersion = 1 -minIncomingConfirmations = 10 -publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" -coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" -requestTimeout = "168h" # 7 days -chunkSize = 25 -backoffInitialDelay = "1m" -backoffMaxDelay = "2h" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" -` - jb, err := vrfcommon.ValidatedVRFSpec(spec) + jb, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec( + testspecs.VRFSpecParams{ + PublicKey: "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800", + FromAddresses: []string{"0x1111111111111111111111111111111111111111"}, + OmitGasLanePrice: true, + }).Toml()) require.NoError(tt, err) cfg := vrf_mocks.NewFeeConfig(t) @@ -564,7 +178,6 @@ decode_log->vrf->encode_tx->submit_tx cfg := vrf_mocks.NewFeeConfig(t) cfg.On("PriceMaxKey", common.HexToAddress(fromAddresses[0])).Return(assets.GWei(100)).Once() cfg.On("PriceMaxKey", common.HexToAddress(fromAddresses[1])).Return(assets.GWei(100)).Once() - // last from address has wrong key-specific max gas price cfg.On("PriceMaxKey", common.HexToAddress(fromAddresses[2])).Return(assets.GWei(50)).Once() defer cfg.AssertExpectations(tt) @@ -626,7 +239,6 @@ func Test_CheckFromAddressesExist(t *testing.T) { assert.NoError(t, err) fromAddresses = append(fromAddresses, k.Address.Hex()) } - // add an address that isn't in the keystore fromAddresses = append(fromAddresses, testutils.NewAddress().Hex()) jb, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec( testspecs.VRFSpecParams{ @@ -694,7 +306,7 @@ func Test_FromAddressMaxGasPricesAllEqual(t *testing.T) { cfg.On("PriceMaxKey", common.HexToAddress(a)).Return(assets.GWei(100)) } cfg.On("PriceMaxKey", common.HexToAddress(fromAddresses[len(fromAddresses)-1])). - Return(assets.GWei(200)) // doesn't match the rest + Return(assets.GWei(200)) defer cfg.AssertExpectations(tt) assert.False(tt, vrf.FromAddressMaxGasPricesAllEqual(jb, cfg.PriceMaxKey)) diff --git a/core/services/vrf/proof/proof_response_test.go b/core/services/vrf/proof/proof_response_test.go index 35fc6f8a12c..f76d3a78caa 100644 --- a/core/services/vrf/proof/proof_response_test.go +++ b/core/services/vrf/proof/proof_response_test.go @@ -4,16 +4,9 @@ import ( "math/big" "testing" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/pkg/errors" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_verifier_wrapper" - "github.com/smartcontractkit/chainlink-evm/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" @@ -36,20 +29,6 @@ func TestMarshaledProof(t *testing.T) { require.NoError(t, err) actualProof, err := goProof.CryptoProof(s) require.NoError(t, err) - proof, err := proof2.MarshalForSolidityVerifier(&actualProof) - require.NoError(t, err) - // NB: For changes to the VRF solidity code to be reflected here, "go generate" - // must be run in core/services/vrf. - ethereumKey, _ := crypto.GenerateKey() - auth, err := bind.NewKeyedTransactorWithChainID(ethereumKey, big.NewInt(1337)) - require.NoError(t, err) - genesisData := gethtypes.GenesisAlloc{auth.From: {Balance: assets.Ether(100).ToInt()}} - backend := cltest.NewSimulatedBackend(t, genesisData, ethconfig.Defaults.Miner.GasCeil) - _, _, verifier, err := solidity_vrf_verifier_wrapper.DeployVRFTestHelper(auth, backend.Client()) - if err != nil { - panic(errors.Wrapf(err, "while initializing EVM contract wrapper")) - } - backend.Commit() - _, err = verifier.RandomValueFromVRFProof(nil, proof[:]) + _, err = proof2.MarshalForSolidityVerifier(&actualProof) require.NoError(t, err) } diff --git a/core/services/vrf/solidity_cross_tests/vrf_consumer_base_test.go b/core/services/vrf/solidity_cross_tests/vrf_consumer_base_test.go deleted file mode 100644 index e876d653d71..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_consumer_base_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package solidity_cross_tests_test - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" -) - -func TestConsumerBaseRejectsBadVRFCoordinator(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coordinator := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - keyHash, _ /* jobID */, fee := registerProvingKey(t, coordinator) - log := requestRandomness(t, coordinator, keyHash, fee) - // Ensure that VRFConsumerBase.rawFulfillRandomness's check, - // require(msg.sender==vrfCoordinator), by using the wrong sender address. - _, err := coordinator.ConsumerContract.RawFulfillRandomness(coordinator.Neil, - keyHash, big.NewInt(0).SetBytes([]byte("a bad random value"))) - require.Error(t, err) - // Verify that correct fulfilment is possible, in this setup - _ = fulfillRandomnessRequest(t, coordinator, *log) -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_coordinator_abi_values.go b/core/services/vrf/solidity_cross_tests/vrf_coordinator_abi_values.go deleted file mode 100644 index 039692aecc8..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_coordinator_abi_values.go +++ /dev/null @@ -1,63 +0,0 @@ -package solidity_cross_tests - -import ( - "fmt" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" -) - -// VRFRandomnessRequestLogTopic returns the signature of the RandomnessRequest log -// emitted by the VRFCoordinator contract -func VRFRandomnessRequestLogTopic() common.Hash { - return coordinatorABIValues().randomnessRequestLogTopic -} - -// randomnessRequestRawDataArgs returns a list of the arguments to the -// RandomnessRequest log emitted by the VRFCoordinator contract -func randomnessRequestRawDataArgs() abi.Arguments { - return coordinatorABIValues().randomnessRequestRawDataArgs -} - -var fulfillMethodName = "fulfillRandomnessRequest" - -// abiValues is a singleton carrying information parsed once from the -// VRFCoordinator abi string -type abiValues struct { - // CoordinatorABI is the ABI of the VRFCoordinator - coordinatorABI abi.ABI - fulfillSelector string - fulfillMethod abi.Method - // RandomnessRequestLogTopic is the signature of the RandomnessRequest log - randomnessRequestLogTopic common.Hash - randomnessRequestRawDataArgs abi.Arguments -} - -var coordinatorABIValues = sync.OnceValue(func() (v *abiValues) { - v = new(abiValues) - var err error - v.coordinatorABI, err = abi.JSON(strings.NewReader( - solidity_vrf_coordinator_interface.VRFCoordinatorABI)) - if err != nil { - panic(err) - } - var found bool - v.fulfillMethod, found = v.coordinatorABI.Methods[fulfillMethodName] - if !found { - panic(fmt.Errorf("could not find method %s in VRFCoordinator ABI", fulfillMethodName)) - } - v.fulfillSelector = hexutil.Encode(v.fulfillMethod.ID) - randomnessRequestABI := v.coordinatorABI.Events["RandomnessRequest"] - v.randomnessRequestLogTopic = randomnessRequestABI.ID - for _, arg := range randomnessRequestABI.Inputs { - if !arg.Indexed { - v.randomnessRequestRawDataArgs = append(v.randomnessRequestRawDataArgs, arg) - } - } - return -}) diff --git a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface.go b/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface.go deleted file mode 100644 index 968d3804a4e..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface.go +++ /dev/null @@ -1,100 +0,0 @@ -package solidity_cross_tests - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/pkg/errors" - - "github.com/smartcontractkit/chainlink-common/pkg/assets" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" - "github.com/smartcontractkit/chainlink-evm/pkg/utils" -) - -// RawRandomnessRequestLog is used to parse a RandomnessRequest log into types -// go-ethereum knows about. -type RawRandomnessRequestLog solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest - -// RandomnessRequestLog contains the data for a RandomnessRequest log, -// represented as compatible golang types. -type RandomnessRequestLog struct { - KeyHash common.Hash - Seed *big.Int // uint256 - JobID common.Hash - Sender common.Address - Fee *assets.Link // uint256 - RequestID common.Hash - Raw RawRandomnessRequestLog -} - -var dummyCoordinator, _ = solidity_vrf_coordinator_interface.NewVRFCoordinator( - common.Address{}, nil) - -func toGethLog(log types.Log) types.Log { - return types.Log{ - Address: log.Address, - Topics: log.Topics, - Data: log.Data, - BlockNumber: log.BlockNumber, - TxHash: log.TxHash, - TxIndex: log.TxIndex, - BlockHash: log.BlockHash, - Index: log.Index, - Removed: log.Removed, - } -} - -// ParseRandomnessRequestLog returns the RandomnessRequestLog corresponding to -// the raw logData -func ParseRandomnessRequestLog(log types.Log) (*RandomnessRequestLog, error) { - rawLog, err := dummyCoordinator.ParseRandomnessRequest(toGethLog(log)) - if err != nil { - return nil, errors.Wrapf(err, - "while parsing %x as RandomnessRequestLog", log.Data) - } - return RawRandomnessRequestLogToRandomnessRequestLog( - (*RawRandomnessRequestLog)(rawLog)), nil -} - -// RawData returns the raw bytes corresponding to l in a solidity log -// -// This serialization does not include the JobID, because that's an indexed field. -func (l *RandomnessRequestLog) RawData() ([]byte, error) { - return randomnessRequestRawDataArgs().Pack(l.KeyHash, - l.Seed, l.Sender, (*big.Int)(l.Fee), l.RequestID) -} - -// Equal(ol) is true iff l is the same log as ol, and both represent valid -// RandomnessRequest logs. -func (l *RandomnessRequestLog) Equal(ol RandomnessRequestLog) bool { - return l.KeyHash == ol.KeyHash && - equal(l.Seed, ol.Seed) && - l.JobID == ol.JobID && - l.Sender == ol.Sender && - l.Fee.Cmp(ol.Fee) == 0 && - l.RequestID == ol.RequestID -} - -func (l *RandomnessRequestLog) ComputedRequestID() common.Hash { - soliditySeed, err := utils.Uint256ToBytes(l.Seed) - if err != nil { - panic(errors.Wrapf(err, "vrf seed out of bounds in %#+v", l)) - } - return utils.MustHash(string(append(l.KeyHash[:], soliditySeed...))) -} - -func RawRandomnessRequestLogToRandomnessRequestLog( - l *RawRandomnessRequestLog) *RandomnessRequestLog { - return &RandomnessRequestLog{ - KeyHash: l.KeyHash, - Seed: l.Seed, - JobID: l.JobID, - Sender: l.Sender, - Fee: (*assets.Link)(l.Fee), - RequestID: l.RequestID, - Raw: *l, - } -} - -func equal(left, right *big.Int) bool { return left.Cmp(right) == 0 } diff --git a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface_test.go b/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface_test.go deleted file mode 100644 index 47c47af7c9b..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package solidity_cross_tests_test - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/solidity_cross_tests" -) - -var ( - keyHash = secretKey.PublicKey.MustHash() - jobID = common.BytesToHash([]byte("1234567890abcdef1234567890abcdef")) - seed = big.NewInt(1) - sender = common.HexToAddress("0xecfcab0a285d3380e488a39b4bb21e777f8a4eac") - fee = big.NewInt(100) - requestID = common.HexToHash("0xcafe") - raw = solidity_cross_tests.RawRandomnessRequestLog{ - KeyHash: keyHash, - Seed: seed, - JobID: jobID, - Sender: sender, - Fee: fee, - RequestID: requestID, - Raw: types.Log{ - // A raw, on-the-wire RandomnessRequestLog is the concat of fields as uint256's - Data: append(append(append(append( - keyHash.Bytes(), - common.BigToHash(seed).Bytes()...), - common.BytesToHash(sender.Bytes()).Bytes()...), - common.BigToHash(fee).Bytes()...), - requestID.Bytes()...), - Topics: []common.Hash{{}, jobID}, - }, - } -) - -func TestVRFParseRandomnessRequestLog(t *testing.T) { - r := solidity_cross_tests.RawRandomnessRequestLogToRandomnessRequestLog(&raw) - rawLog, err := r.RawData() - require.NoError(t, err) - assert.Equal(t, rawLog, raw.Raw.Data) - nR, err := solidity_cross_tests.ParseRandomnessRequestLog(types.Log{ - Data: rawLog, - Topics: []common.Hash{solidity_cross_tests.VRFRandomnessRequestLogTopic(), jobID}, - }) - require.NoError(t, err) - require.True(t, r.Equal(*nR), - "Round-tripping RandomnessRequestLog through serialization and parsing "+ - "resulted in a different log.") -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_coordinator_solidity_crosscheck_test.go b/core/services/vrf/solidity_cross_tests/vrf_coordinator_solidity_crosscheck_test.go deleted file mode 100644 index f57b8121f34..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_coordinator_solidity_crosscheck_test.go +++ /dev/null @@ -1,256 +0,0 @@ -package solidity_cross_tests_test - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink-evm/pkg/utils" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - proof2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/solidity_cross_tests" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" -) - -const defaultGasLimit uint32 = 500000 - -func TestRequestIDMatches(t *testing.T) { - keyHash := common.HexToHash("0x01") - key := cltest.MustGenerateRandomKey(t) - baseContract := vrftesthelpers.NewVRFCoordinatorUniverse(t, key).RequestIDBase - var seed = big.NewInt(1) - solidityRequestID, err := baseContract.MakeRequestId(nil, keyHash, seed) - require.NoError(t, err, "failed to calculate VRF requestID on simulated ethereum blockchain") - goRequestLog := &solidity_cross_tests.RandomnessRequestLog{KeyHash: keyHash, Seed: seed} - assert.Equal(t, common.Hash(solidityRequestID), goRequestLog.ComputedRequestID(), - "solidity VRF requestID differs from golang requestID!") -} - -var ( - rawSecretKey = big.NewInt(1) // never do this in production! - secretKey = vrfkey.MustNewV2XXXTestingOnly(rawSecretKey) - publicKey = (&secp256k1.Secp256k1{}).Point().Mul(secp256k1.IntToScalar( - rawSecretKey), nil) - hardcodedSeed = big.NewInt(0) - vrfFee = big.NewInt(7) -) - -// registerProvingKey registers keyHash to neil in the VRFCoordinator universe -// represented by coordinator, with the given jobID and fee. -func registerProvingKey(t *testing.T, coordinator vrftesthelpers.CoordinatorUniverse) ( - keyHash [32]byte, jobID [32]byte, fee *big.Int) { - copy(jobID[:], []byte("exactly 32 characters in length.")) - _, err := coordinator.RootContract.RegisterProvingKey( - coordinator.Neil, vrfFee, coordinator.Neil.From, pair(secp256k1.Coordinates(publicKey)), jobID) - require.NoError(t, err, "failed to register VRF proving key on VRFCoordinator contract") - coordinator.Backend.Commit() - keyHash = utils.MustHash(string(secp256k1.LongMarshal(publicKey))) - return keyHash, jobID, vrfFee -} - -func TestRegisterProvingKey(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coord := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - keyHash, jobID, fee := registerProvingKey(t, coord) - log, err := coord.RootContract.FilterNewServiceAgreement(nil) - require.NoError(t, err, "failed to subscribe to NewServiceAgreement logs on simulated ethereum blockchain") - logCount := 0 - for log.Next() { - logCount++ - assert.Equal(t, log.Event.KeyHash, keyHash, "VRFCoordinator logged a different keyHash than was registered") - assert.Equal(t, 0, fee.Cmp(log.Event.Fee), "VRFCoordinator logged a different fee than was registered") - } - require.Equal(t, 1, logCount, "unexpected NewServiceAgreement log generated by key VRF key registration") - serviceAgreement, err := coord.RootContract.ServiceAgreements(nil, keyHash) - require.NoError(t, err, "failed to retrieve previously registered VRF service agreement from VRFCoordinator") - assert.Equal(t, coord.Neil.From, serviceAgreement.VRFOracle, - "VRFCoordinator registered wrong provider, on service agreement!") - assert.Equal(t, jobID, serviceAgreement.JobID, - "VRFCoordinator registered wrong jobID, on service agreement!") - assert.Equal(t, 0, fee.Cmp(serviceAgreement.Fee), - "VRFCoordinator registered wrong fee, on service agreement!") -} - -func TestFailToRegisterProvingKeyFromANonOwnerAddress(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coordinator := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - - var jobID [32]byte - copy(jobID[:], []byte("exactly 32 characters in length.")) - _, err := coordinator.RootContract.RegisterProvingKey( - coordinator.Ned, vrfFee, coordinator.Neil.From, pair(secp256k1.Coordinates(publicKey)), jobID) - - require.Error(t, err, "expected an error") - require.Contains(t, err.Error(), "Ownable: caller is not the owner") -} - -// requestRandomness sends a randomness request via Carol's consuming contract, -// in the VRFCoordinator universe represented by coordinator, specifying the -// given keyHash and seed, and paying the given fee. It returns the log emitted -// from the VRFCoordinator in response to the request -func requestRandomness(t *testing.T, coordinator vrftesthelpers.CoordinatorUniverse, - keyHash common.Hash, fee *big.Int) *solidity_cross_tests.RandomnessRequestLog { - _, err := coordinator.ConsumerContract.TestRequestRandomness(coordinator.Carol, - keyHash, fee) - require.NoError(t, err, "problem during initial VRF randomness request") - coordinator.Backend.Commit() - log, err := coordinator.RootContract.FilterRandomnessRequest(nil, nil) - require.NoError(t, err, "failed to subscribe to RandomnessRequest logs") - logCount := 0 - for log.Next() { - logCount++ - } - require.Equal(t, 1, logCount, "unexpected log generated by randomness request to VRFCoordinator") - return solidity_cross_tests.RawRandomnessRequestLogToRandomnessRequestLog( - (*solidity_cross_tests.RawRandomnessRequestLog)(log.Event)) -} - -func requestRandomnessV08(t *testing.T, coordinator vrftesthelpers.CoordinatorUniverse, - keyHash common.Hash, fee *big.Int) *solidity_cross_tests.RandomnessRequestLog { - _, err := coordinator.ConsumerContractV08.DoRequestRandomness(coordinator.Carol, - keyHash, fee) - require.NoError(t, err, "problem during initial VRF randomness request") - coordinator.Backend.Commit() - log, err := coordinator.RootContract.FilterRandomnessRequest(nil, nil) - require.NoError(t, err, "failed to subscribe to RandomnessRequest logs") - logCount := 0 - for log.Next() { - if log.Event.Sender == coordinator.ConsumerContractAddressV08 { - logCount++ - } - } - require.Equal(t, 1, logCount, "unexpected log generated by randomness request to VRFCoordinator") - return solidity_cross_tests.RawRandomnessRequestLogToRandomnessRequestLog( - (*solidity_cross_tests.RawRandomnessRequestLog)(log.Event)) -} - -func TestRandomnessRequestLog(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coord := vrftesthelpers.NewVRFCoordinatorUniverseWithV08Consumer(t, key) - keyHash_, jobID_, fee := registerProvingKey(t, coord) - keyHash := common.BytesToHash(keyHash_[:]) - jobID := common.BytesToHash(jobID_[:]) - var tt = []struct { - rr func(t *testing.T, coordinator vrftesthelpers.CoordinatorUniverse, - keyHash common.Hash, fee *big.Int) *solidity_cross_tests.RandomnessRequestLog - ms func() (*big.Int, error) - consumerAddress common.Address - }{ - { - rr: requestRandomness, - ms: func() (*big.Int, error) { - return coord.RequestIDBase.MakeVRFInputSeed(nil, keyHash, hardcodedSeed, coord.ConsumerContractAddress, big.NewInt(0)) - }, - consumerAddress: coord.ConsumerContractAddress, - }, - { - rr: requestRandomnessV08, - ms: func() (*big.Int, error) { - return coord.RequestIDBaseV08.MakeVRFInputSeed(nil, keyHash, hardcodedSeed, coord.ConsumerContractAddressV08, big.NewInt(0)) - }, - consumerAddress: coord.ConsumerContractAddressV08, - }, - } - for _, tc := range tt { - log := tc.rr(t, coord, keyHash, fee) - assert.Equal(t, keyHash, log.KeyHash, "VRFCoordinator logged wrong KeyHash for randomness request") - nonce := big.NewInt(0) - actualSeed, err := tc.ms() - require.NoError(t, err, "failure while using VRFCoordinator to calculate actual VRF input seed") - assert.Equal(t, 0, actualSeed.Cmp(log.Seed), - "VRFCoordinator logged wrong actual input seed from randomness request") - golangSeed := utils.MustHash(string(append(append(append( - keyHash[:], - common.BigToHash(hardcodedSeed).Bytes()...), - common.BytesToHash(tc.consumerAddress.Bytes()).Bytes()...), - common.BigToHash(nonce).Bytes()...))) - assert.Equal(t, golangSeed, common.BigToHash((log.Seed)), "VRFCoordinator logged different actual input seed than expected by golang code!") - assert.Equal(t, jobID, log.JobID, "VRFCoordinator logged different JobID from randomness request!") - assert.Equal(t, tc.consumerAddress, log.Sender, "VRFCoordinator logged different requester address from randomness request!") - assert.Equal(t, 0, fee.Cmp((*big.Int)(log.Fee)), "VRFCoordinator logged different fee from randomness request!") - parsedLog, err := solidity_cross_tests.ParseRandomnessRequestLog(log.Raw.Raw) - assert.NoError(t, err, "could not parse randomness request log generated by VRFCoordinator") - assert.True(t, parsedLog.Equal(*log), "got a different randomness request log by parsing the raw data than reported by simulated backend") - } -} - -// fulfillRandomnessRequest is neil fulfilling randomness requested by log. -func fulfillRandomnessRequest(t *testing.T, coordinator vrftesthelpers.CoordinatorUniverse, log solidity_cross_tests.RandomnessRequestLog) vrfkey.Proof { - preSeed, err := proof2.BigToSeed(log.Seed) - require.NoError(t, err, "pre-seed %x out of range", preSeed) - s := proof2.PreSeedData{ - PreSeed: preSeed, - BlockHash: log.Raw.Raw.BlockHash, - BlockNum: log.Raw.Raw.BlockNumber, - } - seed := proof2.FinalSeed(s) - proof, err := secretKey.GenerateProofWithNonce(seed, big.NewInt(1) /* nonce */) - require.NoError(t, err) - proofBlob, err := vrftesthelpers.GenerateProofResponseFromProof(proof, s) - require.NoError(t, err, "could not generate VRF proof!") - // Seems to be a bug in the simulated backend: without this extra Commit, the - // EVM seems to think it's still on the block in which the request was made, - // which means that the relevant blockhash is unavailable. - coordinator.Backend.Commit() - // This is simulating a node response, so set the gas limit as chainlink does - var neil = *coordinator.Neil - neil.GasLimit = uint64(defaultGasLimit) - _, err = coordinator.RootContract.FulfillRandomnessRequest(&neil, proofBlob[:]) - require.NoError(t, err, "failed to fulfill randomness request!") - coordinator.Backend.Commit() - return proof -} - -func TestFulfillRandomness(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coordinator := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - keyHash, _, fee := registerProvingKey(t, coordinator) - randomnessRequestLog := requestRandomness(t, coordinator, keyHash, fee) - proof := fulfillRandomnessRequest(t, coordinator, *randomnessRequestLog) - output, err := coordinator.ConsumerContract.RandomnessOutput(nil) - require.NoError(t, err, "failed to get VRF output from consuming contract, "+ - "after randomness request was fulfilled") - assert.Equal(t, 0, proof.Output.Cmp(output), "VRF output from randomness "+ - "request fulfillment was different than provided! Expected %d, got %d. "+ - "This can happen if you update the VRFCoordinator wrapper without a "+ - "corresponding update to the VRFConsumer", proof.Output, output) - requestID, err := coordinator.ConsumerContract.RequestId(nil) - require.NoError(t, err, "failed to get requestId from VRFConsumer") - assert.Equal(t, randomnessRequestLog.RequestID, common.Hash(requestID), - "VRFConsumer has different request ID than logged from randomness request!") - neilBalance, err := coordinator.RootContract.WithdrawableTokens( - nil, coordinator.Neil.From) - require.NoError(t, err, "failed to get neil's token balance, after he "+ - "successfully fulfilled a randomness request") - assert.Equal(t, 0, neilBalance.Cmp(fee), "neil's balance on VRFCoordinator "+ - "was not paid his fee, despite successful fulfillment of randomness request!") -} - -func TestWithdraw(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coordinator := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - keyHash, _, fee := registerProvingKey(t, coordinator) - log := requestRandomness(t, coordinator, keyHash, fee) - fulfillRandomnessRequest(t, coordinator, *log) - payment := big.NewInt(4) - peteThePunter := common.HexToAddress("0xdeadfa11deadfa11deadfa11deadfa11deadfa11") - _, err := coordinator.RootContract.Withdraw(coordinator.Neil, peteThePunter, payment) - require.NoError(t, err, "failed to withdraw LINK from neil's balance") - coordinator.Backend.Commit() - peteBalance, err := coordinator.LinkContract.BalanceOf(nil, peteThePunter) - require.NoError(t, err, "failed to get balance of payee on LINK contract, after payment") - assert.Equal(t, 0, payment.Cmp(peteBalance), - "LINK balance is wrong, following payment") - neilBalance, err := coordinator.RootContract.WithdrawableTokens( - nil, coordinator.Neil.From) - require.NoError(t, err, "failed to get neil's balance on VRFCoordinator") - assert.Equal(t, 0, big.NewInt(0).Sub(fee, payment).Cmp(neilBalance), - "neil's VRFCoordinator balance is wrong, after he's made a withdrawal!") - _, err = coordinator.RootContract.Withdraw(coordinator.Neil, peteThePunter, fee) - assert.Error(t, err, "VRFcoordinator allowed overdraft") -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_fulfillment_cost_test.go b/core/services/vrf/solidity_cross_tests/vrf_fulfillment_cost_test.go deleted file mode 100644 index 0e12fe668c0..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_fulfillment_cost_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package solidity_cross_tests_test - -import ( - "math/big" - "testing" - - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - proof2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestMeasureFulfillmentGasCost establishes rough bounds on the cost of -// providing a proof to the VRF coordinator. -func TestMeasureFulfillmentGasCost(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coordinator := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - keyHash, _, fee := registerProvingKey(t, coordinator) - // Set up a request to fulfill - log := requestRandomness(t, coordinator, keyHash, fee) - preSeed, err := proof2.BigToSeed(log.Seed) - require.NoError(t, err, "pre-seed %x out of range", preSeed) - s := proof2.PreSeedData{ - PreSeed: preSeed, - BlockHash: log.Raw.Raw.BlockHash, - BlockNum: log.Raw.Raw.BlockNumber, - } - seed := proof2.FinalSeed(s) - proof, err := secretKey.GenerateProofWithNonce(seed, big.NewInt(1) /* nonce */) - require.NoError(t, err) - proofBlob, err := vrftesthelpers.GenerateProofResponseFromProof(proof, s) - require.NoError(t, err, "could not generate VRF proof!") - coordinator.Backend.Commit() // Work around simbackend/EVM block number bug - estimate := estimateGas(t, coordinator.Backend.Client(), coordinator.Neil.From, - coordinator.RootContractAddress, coordinator.CoordinatorABI, - "fulfillRandomnessRequest", proofBlob[:]) - - assert.Greater(t, estimate, uint64(108000), - "fulfillRandomness tx cost less gas than expected") - t.Log("estimate", estimate) - // Note that this is probably a very loose upper bound on gas usage. - // TODO:https://www.pivotaltracker.com/story/show/175040572 - assert.Less(t, estimate, uint64(500000), - "fulfillRandomness tx cost more gas than expected") -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_hash_to_curve_cost_test.go b/core/services/vrf/solidity_cross_tests/vrf_hash_to_curve_cost_test.go deleted file mode 100644 index af81bf8c2fb..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_hash_to_curve_cost_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package solidity_cross_tests_test - -import ( - "crypto/ecdsa" - "math/big" - "strings" - "testing" - - "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/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethclient/simulated" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_verifier_wrapper" - "github.com/smartcontractkit/chainlink-evm/pkg/assets" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" -) - -type contract struct { - contract *bind.BoundContract - address common.Address - abi *abi.ABI - backend evmtypes.Backend -} - -// deployVRFContract returns a deployed VRF contract, with some extra attributes -// which are useful for gas measurements. -func deployVRFContract(t *testing.T) (contract, common.Address) { - x, y := secp256k1.Coordinates(vrfkey.Generator) - key := ecdsa.PrivateKey{ - PublicKey: ecdsa.PublicKey{Curve: crypto.S256(), X: x, Y: y}, - D: big.NewInt(1), - } - auth, _ := bind.NewKeyedTransactorWithChainID(&key, testutils.SimulatedChainID) - genesisData := types.GenesisAlloc{auth.From: {Balance: assets.Ether(100).ToInt()}} - backend := cltest.NewSimulatedBackend(t, genesisData, ethconfig.Defaults.Miner.GasCeil) - parsed, err := abi.JSON(strings.NewReader( - solidity_vrf_verifier_wrapper.VRFTestHelperABI)) - require.NoError(t, err, "could not parse VRF ABI") - address, _, vRFContract, err := bind.DeployContract(auth, parsed, - common.FromHex(solidity_vrf_verifier_wrapper.VRFTestHelperBin), backend.Client()) - require.NoError(t, err, "failed to deploy VRF contract to simulated blockchain") - backend.Commit() - return contract{vRFContract, address, &parsed, backend}, crypto.PubkeyToAddress( - key.PublicKey) -} - -// estimateGas returns the estimated gas cost of running the given method on the -// contract at address to, on the given backend, with the given args, and given -// that the transaction is sent from the from address. -func estimateGas(t *testing.T, client simulated.Client, - from, to common.Address, abi *abi.ABI, method string, args ...any, -) uint64 { - rawData, err := abi.Pack(method, args...) - require.NoError(t, err, "failed to construct raw %s transaction with args %s", - method, args) - callMsg := ethereum.CallMsg{From: from, To: &to, Data: rawData} - estimate, err := client.EstimateGas(testutils.Context(t), callMsg) - require.NoError(t, err, "failed to estimate gas from %s call with args %s", - method, args) - return estimate -} - -func measureHashToCurveGasCost(t *testing.T, contract contract, - owner common.Address, input int64) (gasCost, numOrdinates uint64) { - estimate := estimateGas(t, contract.backend.Client(), owner, contract.address, - contract.abi, "hashToCurve_", pair(secp256k1.Coordinates(vrfkey.Generator)), - big.NewInt(input)) - - _, err := vrfkey.HashToCurve(vrfkey.Generator, big.NewInt(input), - func(*big.Int) { numOrdinates++ }) - require.NoError(t, err, "corresponding golang HashToCurve calculation failed") - return estimate, numOrdinates -} - -var baseCost uint64 = 25000 -var marginalCost uint64 = 15555 - -func HashToCurveGasCostBound(numOrdinates uint64) uint64 { - return baseCost + marginalCost*numOrdinates -} - -func TestMeasureHashToCurveGasCost(t *testing.T) { - contract, owner := deployVRFContract(t) - numSamples := int64(numSamples()) - for i := range numSamples { - gasCost, numOrdinates := measureHashToCurveGasCost(t, contract, owner, i) - assert.Less(t, gasCost, HashToCurveGasCostBound(numOrdinates), - "on-chain hashToCurve gas cost exceeded estimate function") - } - require.Less(t, HashToCurveGasCostBound(128), uint64(2.017e6), - "estimate for on-chain hashToCurve gas cost with 128 iterations is greater "+ - "than stated in the VRF.sol documentation") -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_randomness_output_cost_test.go b/core/services/vrf/solidity_cross_tests/vrf_randomness_output_cost_test.go deleted file mode 100644 index de9830322a4..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_randomness_output_cost_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package solidity_cross_tests_test - -import ( - mrand "math/rand" - "testing" - - proof2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" -) - -func TestMeasureRandomValueFromVRFProofGasCost(t *testing.T) { - r := mrand.New(mrand.NewSource(10)) - sk := randomScalar(t, r) - skNum := secp256k1.ToInt(sk) - pk := vrfkey.MustNewV2XXXTestingOnly(skNum) - nonce := randomScalar(t, r) - randomSeed := randomUint256(t, r) - proof, err := pk.GenerateProofWithNonce(randomSeed, secp256k1.ToInt(nonce)) - require.NoError(t, err, "failed to generate VRF proof") - mproof, err := proof2.MarshalForSolidityVerifier(&proof) - require.NoError(t, err, "failed to marshal VRF proof for on-chain verification") - contract, _ := deployVRFContract(t) - - estimate := estimateGas(t, contract.backend.Client(), common.Address{}, - contract.address, contract.abi, "randomValueFromVRFProof_", mproof[:]) - - require.NoError(t, err, "failed to estimate gas cost for VRF verification") - require.Less(t, estimate, uint64(100000)) -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_request_cost_test.go b/core/services/vrf/solidity_cross_tests/vrf_request_cost_test.go deleted file mode 100644 index 5d183358582..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_request_cost_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package solidity_cross_tests_test - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" -) - -func TestMeasureRandomnessRequestGasCost(t *testing.T) { - key := cltest.MustGenerateRandomKey(t) - coordinator := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - keyHash_, _, fee := registerProvingKey(t, coordinator) - - estimate := estimateGas(t, coordinator.Backend.Client(), common.Address{}, - coordinator.ConsumerContractAddress, coordinator.ConsumerABI, - "testRequestRandomness", common.BytesToHash(keyHash_[:]), fee) - - assert.Greater(t, estimate, uint64(134000), - "requestRandomness tx gas cost lower than expected") - // Note: changed from 160000 to 164079 in the Berlin hard fork (Geth 1.10) - assert.Less(t, estimate, uint64(167000), - "requestRandomness tx gas cost higher than expected") -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_solidity_crosscheck_test.go b/core/services/vrf/solidity_cross_tests/vrf_solidity_crosscheck_test.go deleted file mode 100644 index c432bae81d6..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_solidity_crosscheck_test.go +++ /dev/null @@ -1,394 +0,0 @@ -package solidity_cross_tests_test - -import ( - "crypto/ecdsa" - "math/big" - mrand "math/rand" - "strings" - "testing" - - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.dedis.ch/kyber/v3" - - "github.com/smartcontractkit/chainlink-common/pkg/utils" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_verifier_wrapper" - "github.com/smartcontractkit/chainlink-evm/pkg/assets" - "github.com/smartcontractkit/chainlink-evm/pkg/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - proof2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" -) - -// Cross-checks of golang implementation details vs corresponding solidity -// details. -// -// It's worth automatically checking these implementation details because they -// can help to quickly locate any disparity between the solidity and golang -// implementations. - -// deployVRFContract returns the wrapper of the EVM verifier contract. -// -// NB: For changes to the VRF solidity code to be reflected here, "go generate" -// must be run in core/services/vrf. -// -// TODO(alx): This suit used to be much faster, presumably because all tests -// were sharing a common global verifier (which is fine, because all methods are -// pure.) Revert to that, and see if it helps. -func deployVRFTestHelper(t *testing.T) *solidity_vrf_verifier_wrapper.VRFTestHelper { - auth := testutils.MustNewSimTransactor(t) - genesisData := gethtypes.GenesisAlloc{auth.From: {Balance: assets.Ether(100).ToInt()}} - backend := cltest.NewSimulatedBackend(t, genesisData, ethconfig.Defaults.Miner.GasCeil) - _, _, verifier, err := solidity_vrf_verifier_wrapper.DeployVRFTestHelper(auth, backend.Client()) - require.NoError(t, err, "failed to deploy VRF contract to simulated blockchain") - backend.Commit() - return verifier -} - -// randomUint256 deterministically simulates a uniform sample of uint256's, -// given r's seed -// -// Never use this if cryptographic security is required -func randomUint256(t *testing.T, r *mrand.Rand) *big.Int { - b := make([]byte, 32) - _, err := r.Read(b) - require.NoError(t, err, "failed to read random sample") // deterministic, though - return big.NewInt(0).SetBytes(b) -} - -// numSamples returns the number of examples which should be checked, in -// generative tests -func numSamples() int { - return 10 -} - -func TestVRF_CompareProjectiveECAddToVerifier(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(11)) - for j := 0; j < numSamples(); j++ { - p := randomPoint(t, r) - q := randomPoint(t, r) - px, py := secp256k1.Coordinates(p) - qx, qy := secp256k1.Coordinates(q) - actualX, actualY, actualZ := vrfkey.ProjectiveECAdd(p, q) - verifier := deployVRFTestHelper(t) - expectedX, expectedY, expectedZ, err := verifier.ProjectiveECAdd( - nil, px, py, qx, qy) - require.NoError(t, err, "failed to compute secp256k1 sum in projective coords") - assert.Equal(t, [3]*big.Int{expectedX, expectedY, expectedZ}, - [3]*big.Int{actualX, actualY, actualZ}, - "got different answers on-chain vs off-chain, for ProjectiveECAdd") - } -} - -func TestVRF_CompareBigModExpToVerifier(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(0)) - for j := 0; j < numSamples(); j++ { - base := randomUint256(t, r) - exponent := randomUint256(t, r) - actual, err := deployVRFTestHelper(t).BigModExp(nil, base, exponent) - require.NoError(t, err, "while computing bigmodexp on-chain") - expected := big.NewInt(0).Exp(base, exponent, vrfkey.FieldSize) - assert.Equal(t, expected, actual, - "%x ** %x %% %x = %x ≠ %x from solidity calculation", - base, exponent, vrfkey.FieldSize, expected, actual) - } -} - -func TestVRF_CompareSquareRoot(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(1)) - for j := 0; j < numSamples(); j++ { - maybeSquare := randomUint256(t, r) // Might not be square; should get same result anyway - squareRoot, err := deployVRFTestHelper(t).SquareRoot(nil, maybeSquare) - require.NoError(t, err, "failed to compute square root on-chain") - golangSquareRoot := vrfkey.SquareRoot(maybeSquare) - assert.Equal(t, golangSquareRoot, squareRoot, - "expected square root in GF(fieldSize) of %x to be %x, got %x on-chain", - maybeSquare, golangSquareRoot, squareRoot) - assert.True(t, - (!vrfkey.IsSquare(maybeSquare)) || big.NewInt(1).Exp(squareRoot, - big.NewInt(2), vrfkey.FieldSize).Cmp(maybeSquare) == 0, - "maybeSquare is a square, but failed to calculate its square root!") - assert.NotEqual(t, vrfkey.IsSquare(maybeSquare), vrfkey.IsSquare( - big.NewInt(1).Sub(vrfkey.FieldSize, maybeSquare)), - "negative of a non square should be square, and vice-versa, since -1 is not a square") - } -} - -func TestVRF_CompareYSquared(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(2)) - for i := 0; i < numSamples(); i++ { - x := randomUint256(t, r) - actual, err := deployVRFTestHelper(t).YSquared(nil, x) - require.NoError(t, err, "failed to compute y² given x, on-chain") - assert.Equal(t, vrfkey.YSquared(x), actual, - "different answers for y², on-chain vs off-chain") - } -} - -func TestVRF_CompareFieldHash(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(3)) - msg := make([]byte, 32) - for j := 0; j < numSamples(); j++ { - _, err := r.Read(msg) - require.NoError(t, err, "failed to randomize intended hash message") - actual, err := deployVRFTestHelper(t).FieldHash(nil, msg) - require.NoError(t, err, "failed to compute fieldHash on-chain") - expected := vrfkey.FieldHash(msg) - require.Equal(t, expected, actual, - "fieldHash value on-chain differs from off-chain") - } -} - -// randomKey deterministically generates a secp256k1 key. -// -// Never use this if cryptographic security is required -func randomKey(t *testing.T, r *mrand.Rand) *ecdsa.PrivateKey { - secretKey := vrfkey.FieldSize - for secretKey.Cmp(vrfkey.FieldSize) >= 0 { // Keep picking until secretKey < fieldSize - secretKey = randomUint256(t, r) - } - cKey := crypto.ToECDSAUnsafe(secretKey.Bytes()) - return cKey -} - -// pair returns the inputs as a length-2 big.Int array. Useful for translating -// coordinates to the uint256[2]'s VRF.sol uses to represent secp256k1 points. -func pair(x, y *big.Int) [2]*big.Int { return [2]*big.Int{x, y} } -func asPair(p kyber.Point) [2]*big.Int { return pair(secp256k1.Coordinates(p)) } - -func TestVRF_CompareHashToCurve(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(4)) - for i := 0; i < numSamples(); i++ { - input := randomUint256(t, r) - cKey := randomKey(t, r) - pubKeyCoords := pair(cKey.X, cKey.Y) - actual, err := deployVRFTestHelper(t).HashToCurve(nil, pubKeyCoords, input) - require.NoError(t, err, "failed to compute hashToCurve on-chain") - pubKeyPoint := secp256k1.SetCoordinates(cKey.X, cKey.Y) - expected, err := vrfkey.HashToCurve(pubKeyPoint, input, func(*big.Int) {}) - require.NoError(t, err, "failed to compute HashToCurve in golang") - require.Equal(t, asPair(expected), actual, - "on-chain and off-chain calculations of HashToCurve gave different secp256k1 points") - } -} - -// randomPoint deterministically simulates a uniform sample of secp256k1 points, -// given r's seed -// -// Never use this if cryptographic security is required -func randomPoint(t *testing.T, r *mrand.Rand) kyber.Point { - p, err := vrfkey.HashToCurve(vrfkey.Generator, randomUint256(t, r), func(*big.Int) {}) - require.NoError(t, err, - "failed to hash random value to secp256k1 while generating random point") - if r.Int63n(2) == 1 { // Uniform sample of ±p - p.Neg(p) - } - return p -} - -// randomPointWithPair returns a random secp256k1, both as a kyber.Point and as -// a pair of *big.Int's. Useful for translating between the types needed by the -// golang contract wrappers. -func randomPointWithPair(t *testing.T, r *mrand.Rand) (kyber.Point, [2]*big.Int) { - p := randomPoint(t, r) - return p, asPair(p) -} - -// randomScalar deterministically simulates a uniform sample of secp256k1 -// scalars, given r's seed -// -// Never use this if cryptographic security is required -func randomScalar(t *testing.T, r *mrand.Rand) kyber.Scalar { - s := randomUint256(t, r) - for s.Cmp(secp256k1.GroupOrder) >= 0 { - s = randomUint256(t, r) - } - return secp256k1.IntToScalar(s) -} - -func TestVRF_CheckSolidityPointAddition(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(5)) - for j := 0; j < numSamples(); j++ { - p1 := randomPoint(t, r) - p2 := randomPoint(t, r) - p1x, p1y := secp256k1.Coordinates(p1) - p2x, p2y := secp256k1.Coordinates(p2) - psx, psy, psz, err := deployVRFTestHelper(t).ProjectiveECAdd( - nil, p1x, p1y, p2x, p2y) - require.NoError(t, err, "failed to compute ProjectiveECAdd, on-chain") - apx, apy, apz := vrfkey.ProjectiveECAdd(p1, p2) - require.Equal(t, []*big.Int{apx, apy, apz}, []*big.Int{psx, psy, psz}, - "got different values on-chain and off-chain for ProjectiveECAdd") - zInv := big.NewInt(1).ModInverse(psz, vrfkey.FieldSize) - require.Equal(t, big.NewInt(1).Mod(big.NewInt(1).Mul(psz, zInv), - vrfkey.FieldSize), big.NewInt(1), - "failed to calculate correct inverse of z ordinate") - actualSum, err := deployVRFTestHelper(t).AffineECAdd( - nil, pair(p1x, p1y), pair(p2x, p2y), zInv) - require.NoError(t, err, - "failed to deploy VRF contract to simulated blockchain") - assert.Equal(t, asPair((&secp256k1.Secp256k1{}).Point().Add(p1, p2)), - actualSum, - "got different answers, on-chain vs off-chain, for secp256k1 sum in affine coordinates") - } -} - -func TestVRF_CheckSolidityECMulVerify(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(6)) - for j := 0; j < numSamples(); j++ { - p := randomPoint(t, r) - pxy := pair(secp256k1.Coordinates(p)) - s := randomScalar(t, r) - product := asPair((&secp256k1.Secp256k1{}).Point().Mul(s, p)) - actual, err := deployVRFTestHelper(t).EcmulVerify(nil, pxy, secp256k1.ToInt(s), - product) - require.NoError(t, err, "failed to check on-chain that s*p=product") - assert.True(t, actual, - "EcmulVerify rejected a valid secp256k1 scalar product relation") - shouldReject, err := deployVRFTestHelper(t).EcmulVerify(nil, pxy, - big.NewInt(0).Add(secp256k1.ToInt(s), big.NewInt(1)), product) - require.NoError(t, err, "failed to check on-chain that (s+1)*p≠product") - assert.False(t, shouldReject, - "failed to reject a false secp256k1 scalar product relation") - } -} - -func TestVRF_CheckSolidityVerifyLinearCombinationWithGenerator(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(7)) - for j := 0; j < numSamples(); j++ { - c := randomScalar(t, r) - s := randomScalar(t, r) - p := randomPoint(t, r) - expectedPoint := (&secp256k1.Secp256k1{}).Point().Add( - (&secp256k1.Secp256k1{}).Point().Mul(c, p), - (&secp256k1.Secp256k1{}).Point().Mul(s, vrfkey.Generator)) // cp+sg - expectedAddress := secp256k1.EthereumAddress(expectedPoint) - pPair := asPair(p) - actual, err := deployVRFTestHelper(t).VerifyLinearCombinationWithGenerator(nil, - secp256k1.ToInt(c), pPair, secp256k1.ToInt(s), expectedAddress) - require.NoError(t, err, - "failed to check on-chain that secp256k1 linear relationship holds") - assert.True(t, actual, - "VerifyLinearCombinationWithGenerator rejected a valid secp256k1 linear relationship") - shouldReject, err := deployVRFTestHelper(t).VerifyLinearCombinationWithGenerator(nil, - big.NewInt(0).Add(secp256k1.ToInt(c), big.NewInt(1)), pPair, - secp256k1.ToInt(s), expectedAddress) - require.NoError(t, err, - "failed to check on-chain that address((c+1)*p+s*g)≠expectedAddress") - assert.False(t, shouldReject, - "VerifyLinearCombinationWithGenerator accepted an invalid secp256k1 linear relationship!") - } -} - -func TestVRF_CheckSolidityLinearComination(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(8)) - for j := 0; j < numSamples(); j++ { - c := randomScalar(t, r) - cNum := secp256k1.ToInt(c) - p1, p1Pair := randomPointWithPair(t, r) - s := randomScalar(t, r) - sNum := secp256k1.ToInt(s) - p2, p2Pair := randomPointWithPair(t, r) - cp1 := (&secp256k1.Secp256k1{}).Point().Mul(c, p1) - cp1Pair := asPair(cp1) - sp2 := (&secp256k1.Secp256k1{}).Point().Mul(s, p2) - sp2Pair := asPair(sp2) - expected := asPair((&secp256k1.Secp256k1{}).Point().Add(cp1, sp2)) - _, _, z := vrfkey.ProjectiveECAdd(cp1, sp2) - zInv := big.NewInt(0).ModInverse(z, vrfkey.FieldSize) - actual, err := deployVRFTestHelper(t).LinearCombination(nil, cNum, p1Pair, - cp1Pair, sNum, p2Pair, sp2Pair, zInv) - require.NoError(t, err, "failed to compute c*p1+s*p2, on-chain") - assert.Equal(t, expected, actual, - "on-chain computation of c*p1+s*p2 gave wrong answer") - _, err = deployVRFTestHelper(t).LinearCombination(nil, big.NewInt(0).Add( - cNum, big.NewInt(1)), p1Pair, cp1Pair, sNum, p2Pair, sp2Pair, zInv) - assert.Error(t, err, - "on-chain LinearCombination accepted a bad product relation! ((c+1)*p1)") - assert.Contains(t, err.Error(), "First multiplication check failed", - "revert message wrong.") - _, err = deployVRFTestHelper(t).LinearCombination(nil, cNum, p1Pair, - cp1Pair, big.NewInt(0).Add(sNum, big.NewInt(1)), p2Pair, sp2Pair, zInv) - assert.Error(t, err, - "on-chain LinearCombination accepted a bad product relation! ((s+1)*p2)") - assert.Contains(t, err.Error(), "Second multiplication check failed", - "revert message wrong.") - } -} - -func TestVRF_CompareSolidityScalarFromCurvePoints(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(9)) - for j := 0; j < numSamples(); j++ { - hash, hashPair := randomPointWithPair(t, r) - pk, pkPair := randomPointWithPair(t, r) - gamma, gammaPair := randomPointWithPair(t, r) - var uWitness [20]byte - require.NoError(t, utils.JustError(r.Read(uWitness[:])), - "failed to randomize uWitness") - v, vPair := randomPointWithPair(t, r) - expected := vrfkey.ScalarFromCurvePoints(hash, pk, gamma, uWitness, v) - actual, err := deployVRFTestHelper(t).ScalarFromCurvePoints(nil, hashPair, pkPair, - gammaPair, uWitness, vPair) - require.NoError(t, err, "on-chain ScalarFromCurvePoints calculation failed") - assert.Equal(t, expected, actual, - "on-chain ScalarFromCurvePoints output does not match off-chain output!") - } -} - -func TestVRF_MarshalProof(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(10)) - for j := 0; j < numSamples(); j++ { - sk := randomScalar(t, r) - skNum := secp256k1.ToInt(sk) - pk := vrfkey.MustNewV2XXXTestingOnly(skNum) - nonce := randomScalar(t, r) - randomSeed := randomUint256(t, r) - proof, err := pk.GenerateProofWithNonce(randomSeed, secp256k1.ToInt(nonce)) - require.NoError(t, err, "failed to generate VRF proof!") - mproof, err := proof2.MarshalForSolidityVerifier(&proof) - require.NoError(t, err, "failed to marshal VRF proof for on-chain verification") - response, err := deployVRFTestHelper(t).RandomValueFromVRFProof(nil, mproof[:]) - require.NoError(t, err, "failed on-chain to verify VRF proof / get its output") - require.Equal(t, 0, response.Cmp(proof.Output), - "on-chain VRF output differs from off-chain!") - corruptionTargetByte := r.Int63n(int64(len(mproof))) - // Only the lower 160 bits of the word containing uWitness have any effect - inAddressZeroBytes := func(b int64) bool { return b >= 224 && b < 236 } - originalByte := mproof[corruptionTargetByte] - mproof[corruptionTargetByte]++ - _, err = deployVRFTestHelper(t).RandomValueFromVRFProof(nil, mproof[:]) - require.True(t, inAddressZeroBytes(corruptionTargetByte) || err != nil, - "VRF verification accepted a bad proof! Changed byte %d from %d to %d in %s, which is of length %d", - corruptionTargetByte, originalByte, mproof[corruptionTargetByte], - mproof.String(), len(mproof)) - require.True(t, - inAddressZeroBytes(corruptionTargetByte) || - strings.Contains(err.Error(), "invZ must be inverse of z") || - strings.Contains(err.Error(), "First multiplication check failed") || - strings.Contains(err.Error(), "Second multiplication check failed") || - strings.Contains(err.Error(), "cGammaWitness is not on curve") || - strings.Contains(err.Error(), "sHashWitness is not on curve") || - strings.Contains(err.Error(), "gamma is not on curve") || - strings.Contains(err.Error(), "addr(c*pk+s*g)≠_uWitness") || - strings.Contains(err.Error(), "public key is not on curve"), - "VRF verification returned an unknown error: %s", err, - ) - } -} diff --git a/core/services/vrf/solidity_cross_tests/vrf_v08_solidity_crosscheck_test.go b/core/services/vrf/solidity_cross_tests/vrf_v08_solidity_crosscheck_test.go deleted file mode 100644 index 18e5ec5b68c..00000000000 --- a/core/services/vrf/solidity_cross_tests/vrf_v08_solidity_crosscheck_test.go +++ /dev/null @@ -1,327 +0,0 @@ -package solidity_cross_tests_test - -import ( - "math/big" - mrand "math/rand" - "testing" - - gethtypes "github.com/ethereum/go-ethereum/core/types" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_v08_verifier_wrapper" - proof2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" - - "github.com/ethereum/go-ethereum/eth/ethconfig" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/utils" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-evm/pkg/assets" - "github.com/smartcontractkit/chainlink-evm/pkg/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" -) - -// Note these tests are identical to the ones in vrf_solidity_crosscheck_test.go, -// (with the exception of TestVRFV08_InvalidPointCoordinates which is a new check in v0.8) -// except we are testing against the v0.8 implementation of VRF.sol. -func deployVRFV08TestHelper(t *testing.T) *solidity_vrf_v08_verifier_wrapper.VRFTestHelper { - auth := testutils.MustNewSimTransactor(t) - genesisData := gethtypes.GenesisAlloc{auth.From: {Balance: assets.Ether(100).ToInt()}} - backend := cltest.NewSimulatedBackend(t, genesisData, ethconfig.Defaults.Miner.GasCeil) - _, _, verifier, err := solidity_vrf_v08_verifier_wrapper.DeployVRFTestHelper(auth, backend.Client()) - require.NoError(t, err, "failed to deploy VRF contract to simulated blockchain") - backend.Commit() - return verifier -} - -func TestVRFV08_InvalidPointCoordinates(t *testing.T) { - verifier := deployVRFV08TestHelper(t) - // A value outside [0, ..., FIELD_SIZE-1] should fail - _, err := verifier.IsOnCurve(nil, - [2]*big.Int{big.NewInt(10), secp256k1.FieldSize}) - require.Error(t, err) - assert.Equal(t, "execution reverted: invalid y-ordinate", err.Error()) - _, err = verifier.IsOnCurve(nil, - [2]*big.Int{secp256k1.FieldSize, big.NewInt(10)}) - require.Error(t, err) - assert.Equal(t, "execution reverted: invalid x-ordinate", err.Error()) - // Values inside should succeed - _, err = verifier.IsOnCurve(nil, - [2]*big.Int{big.NewInt(10), big.NewInt(0).Sub(secp256k1.FieldSize, big.NewInt(1))}) - require.NoError(t, err) - _, err = verifier.IsOnCurve(nil, - [2]*big.Int{big.NewInt(0).Sub(secp256k1.FieldSize, big.NewInt(1)), big.NewInt(10)}) - require.NoError(t, err) -} - -func TestVRFV08_CompareProjectiveECAddToVerifier(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(11)) - for j := 0; j < numSamples(); j++ { - p := randomPoint(t, r) - q := randomPoint(t, r) - px, py := secp256k1.Coordinates(p) - qx, qy := secp256k1.Coordinates(q) - actualX, actualY, actualZ := vrfkey.ProjectiveECAdd(p, q) - verifier := deployVRFV08TestHelper(t) - expectedX, expectedY, expectedZ, err := verifier.ProjectiveECAdd( - nil, px, py, qx, qy) - require.NoError(t, err, "failed to compute secp256k1 sum in projective coords") - assert.Equal(t, [3]*big.Int{expectedX, expectedY, expectedZ}, - [3]*big.Int{actualX, actualY, actualZ}, - "got different answers on-chain vs off-chain, for ProjectiveECAdd") - } -} - -func TestVRFV08_CompareBigModExpToVerifier(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(0)) - for j := 0; j < numSamples(); j++ { - base := randomUint256(t, r) - exponent := randomUint256(t, r) - actual, err := deployVRFV08TestHelper(t).BigModExp(nil, base, exponent) - require.NoError(t, err, "while computing bigmodexp on-chain") - expected := big.NewInt(0).Exp(base, exponent, vrfkey.FieldSize) - assert.Equal(t, expected, actual, - "%x ** %x %% %x = %x ≠ %x from solidity calculation", - base, exponent, vrfkey.FieldSize, expected, actual) - } -} - -func TestVRFV08_CompareSquareRoot(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(1)) - for j := 0; j < numSamples(); j++ { - maybeSquare := randomUint256(t, r) // Might not be square; should get same result anyway - squareRoot, err := deployVRFV08TestHelper(t).SquareRoot(nil, maybeSquare) - require.NoError(t, err, "failed to compute square root on-chain") - golangSquareRoot := vrfkey.SquareRoot(maybeSquare) - assert.Equal(t, golangSquareRoot, squareRoot, - "expected square root in GF(fieldSize) of %x to be %x, got %x on-chain", - maybeSquare, golangSquareRoot, squareRoot) - assert.True(t, - (!vrfkey.IsSquare(maybeSquare)) || big.NewInt(1).Exp(squareRoot, - big.NewInt(2), vrfkey.FieldSize).Cmp(maybeSquare) == 0, - "maybeSquare is a square, but failed to calculate its square root!") - assert.NotEqual(t, vrfkey.IsSquare(maybeSquare), vrfkey.IsSquare( - big.NewInt(1).Sub(vrfkey.FieldSize, maybeSquare)), - "negative of a non square should be square, and vice-versa, since -1 is not a square") - } -} - -func TestVRFV08_CompareYSquared(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(2)) - for i := 0; i < numSamples(); i++ { - x := randomUint256(t, r) - actual, err := deployVRFV08TestHelper(t).YSquared(nil, x) - require.NoError(t, err, "failed to compute y² given x, on-chain") - assert.Equal(t, vrfkey.YSquared(x), actual, - "different answers for y², on-chain vs off-chain") - } -} - -func TestVRFV08_CompareFieldHash(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(3)) - msg := make([]byte, 32) - for j := 0; j < numSamples(); j++ { - _, err := r.Read(msg) - require.NoError(t, err, "failed to randomize intended hash message") - actual, err := deployVRFV08TestHelper(t).FieldHash(nil, msg) - require.NoError(t, err, "failed to compute fieldHash on-chain") - expected := vrfkey.FieldHash(msg) - require.Equal(t, expected, actual, - "fieldHash value on-chain differs from off-chain") - } -} - -func TestVRFV08_CompareHashToCurve(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(4)) - for i := 0; i < numSamples(); i++ { - input := randomUint256(t, r) - cKey := randomKey(t, r) - pubKeyCoords := pair(cKey.X, cKey.Y) - actual, err := deployVRFV08TestHelper(t).HashToCurve(nil, pubKeyCoords, input) - require.NoError(t, err, "failed to compute hashToCurve on-chain") - pubKeyPoint := secp256k1.SetCoordinates(cKey.X, cKey.Y) - expected, err := vrfkey.HashToCurve(pubKeyPoint, input, func(*big.Int) {}) - require.NoError(t, err, "failed to compute HashToCurve in golang") - require.Equal(t, asPair(expected), actual, - "on-chain and off-chain calculations of HashToCurve gave different secp256k1 points") - } -} - -func TestVRFV08_CheckSolidityPointAddition(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(5)) - for j := 0; j < numSamples(); j++ { - p1 := randomPoint(t, r) - p2 := randomPoint(t, r) - p1x, p1y := secp256k1.Coordinates(p1) - p2x, p2y := secp256k1.Coordinates(p2) - psx, psy, psz, err := deployVRFV08TestHelper(t).ProjectiveECAdd( - nil, p1x, p1y, p2x, p2y) - require.NoError(t, err, "failed to compute ProjectiveECAdd, on-chain") - apx, apy, apz := vrfkey.ProjectiveECAdd(p1, p2) - require.Equal(t, []*big.Int{apx, apy, apz}, []*big.Int{psx, psy, psz}, - "got different values on-chain and off-chain for ProjectiveECAdd") - zInv := big.NewInt(1).ModInverse(psz, vrfkey.FieldSize) - require.Equal(t, big.NewInt(1).Mod(big.NewInt(1).Mul(psz, zInv), - vrfkey.FieldSize), big.NewInt(1), - "failed to calculate correct inverse of z ordinate") - actualSum, err := deployVRFV08TestHelper(t).AffineECAdd( - nil, pair(p1x, p1y), pair(p2x, p2y), zInv) - require.NoError(t, err, - "failed to deploy VRF contract to simulated blockchain") - assert.Equal(t, asPair((&secp256k1.Secp256k1{}).Point().Add(p1, p2)), - actualSum, - "got different answers, on-chain vs off-chain, for secp256k1 sum in affine coordinates") - } -} - -func TestVRFV08_CheckSolidityECMulVerify(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(6)) - for j := 0; j < numSamples(); j++ { - p := randomPoint(t, r) - pxy := pair(secp256k1.Coordinates(p)) - s := randomScalar(t, r) - product := asPair((&secp256k1.Secp256k1{}).Point().Mul(s, p)) - actual, err := deployVRFV08TestHelper(t).EcmulVerify(nil, pxy, secp256k1.ToInt(s), - product) - require.NoError(t, err, "failed to check on-chain that s*p=product") - assert.True(t, actual, - "EcmulVerify rejected a valid secp256k1 scalar product relation") - shouldReject, err := deployVRFV08TestHelper(t).EcmulVerify(nil, pxy, - big.NewInt(0).Add(secp256k1.ToInt(s), big.NewInt(1)), product) - require.NoError(t, err, "failed to check on-chain that (s+1)*p≠product") - assert.False(t, shouldReject, - "failed to reject a false secp256k1 scalar product relation") - } -} - -func TestVRFV08_CheckSolidityVerifyLinearCombinationWithGenerator(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(7)) - for j := 0; j < numSamples(); j++ { - c := randomScalar(t, r) - s := randomScalar(t, r) - p := randomPoint(t, r) - expectedPoint := (&secp256k1.Secp256k1{}).Point().Add( - (&secp256k1.Secp256k1{}).Point().Mul(c, p), - (&secp256k1.Secp256k1{}).Point().Mul(s, vrfkey.Generator)) // cp+sg - expectedAddress := secp256k1.EthereumAddress(expectedPoint) - pPair := asPair(p) - actual, err := deployVRFV08TestHelper(t).VerifyLinearCombinationWithGenerator(nil, - secp256k1.ToInt(c), pPair, secp256k1.ToInt(s), expectedAddress) - require.NoError(t, err, - "failed to check on-chain that secp256k1 linear relationship holds") - assert.True(t, actual, - "VerifyLinearCombinationWithGenerator rejected a valid secp256k1 linear relationship") - shouldReject, err := deployVRFV08TestHelper(t).VerifyLinearCombinationWithGenerator(nil, - big.NewInt(0).Add(secp256k1.ToInt(c), big.NewInt(1)), pPair, - secp256k1.ToInt(s), expectedAddress) - require.NoError(t, err, - "failed to check on-chain that address((c+1)*p+s*g)≠expectedAddress") - assert.False(t, shouldReject, - "VerifyLinearCombinationWithGenerator accepted an invalid secp256k1 linear relationship!") - } -} - -func TestVRFV08_CheckSolidityLinearComination(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(8)) - for j := 0; j < numSamples(); j++ { - c := randomScalar(t, r) - cNum := secp256k1.ToInt(c) - p1, p1Pair := randomPointWithPair(t, r) - s := randomScalar(t, r) - sNum := secp256k1.ToInt(s) - p2, p2Pair := randomPointWithPair(t, r) - cp1 := (&secp256k1.Secp256k1{}).Point().Mul(c, p1) - cp1Pair := asPair(cp1) - sp2 := (&secp256k1.Secp256k1{}).Point().Mul(s, p2) - sp2Pair := asPair(sp2) - expected := asPair((&secp256k1.Secp256k1{}).Point().Add(cp1, sp2)) - _, _, z := vrfkey.ProjectiveECAdd(cp1, sp2) - zInv := big.NewInt(0).ModInverse(z, vrfkey.FieldSize) - actual, err := deployVRFV08TestHelper(t).LinearCombination(nil, cNum, p1Pair, - cp1Pair, sNum, p2Pair, sp2Pair, zInv) - require.NoError(t, err, "failed to compute c*p1+s*p2, on-chain") - assert.Equal(t, expected, actual, - "on-chain computation of c*p1+s*p2 gave wrong answer") - _, err = deployVRFV08TestHelper(t).LinearCombination(nil, big.NewInt(0).Add( - cNum, big.NewInt(1)), p1Pair, cp1Pair, sNum, p2Pair, sp2Pair, zInv) - assert.Error(t, err, - "on-chain LinearCombination accepted a bad product relation! ((c+1)*p1)") - assert.Contains(t, err.Error(), "First mul check failed", - "revert message wrong.") - _, err = deployVRFV08TestHelper(t).LinearCombination(nil, cNum, p1Pair, - cp1Pair, big.NewInt(0).Add(sNum, big.NewInt(1)), p2Pair, sp2Pair, zInv) - assert.Error(t, err, - "on-chain LinearCombination accepted a bad product relation! ((s+1)*p2)") - assert.Contains(t, err.Error(), "Second mul check failed", - "revert message wrong.") - } -} - -func TestVRFV08_CompareSolidityScalarFromCurvePoints(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(9)) - for j := 0; j < numSamples(); j++ { - hash, hashPair := randomPointWithPair(t, r) - pk, pkPair := randomPointWithPair(t, r) - gamma, gammaPair := randomPointWithPair(t, r) - var uWitness [20]byte - require.NoError(t, utils.JustError(r.Read(uWitness[:])), - "failed to randomize uWitness") - v, vPair := randomPointWithPair(t, r) - expected := vrfkey.ScalarFromCurvePoints(hash, pk, gamma, uWitness, v) - actual, err := deployVRFV08TestHelper(t).ScalarFromCurvePoints(nil, hashPair, pkPair, - gammaPair, uWitness, vPair) - require.NoError(t, err, "on-chain ScalarFromCurvePoints calculation failed") - assert.Equal(t, expected, actual, - "on-chain ScalarFromCurvePoints output does not match off-chain output!") - } -} - -func TestVRFV08_MarshalProof(t *testing.T) { - t.Parallel() - r := mrand.New(mrand.NewSource(10)) - for j := 0; j < numSamples(); j++ { - sk := randomScalar(t, r) - skNum := secp256k1.ToInt(sk) - pk := vrfkey.MustNewV2XXXTestingOnly(skNum) - nonce := randomScalar(t, r) - randomSeed := randomUint256(t, r) - proof, err := pk.GenerateProofWithNonce(randomSeed, secp256k1.ToInt(nonce)) - require.NoError(t, err, "failed to generate VRF proof!") - require.NoError(t, err, "failed to marshal VRF proof for on-chain verification") - seed, err := proof2.BigToSeed(randomSeed) - require.NoError(t, err) - // Don't care about the request commitment for this test. - solProof, _, err := proof2.GenerateProofResponseFromProofV2(proof, proof2.PreSeedDataV2{ - PreSeed: seed, - }) - require.NoError(t, err) - response, err := deployVRFV08TestHelper(t).RandomValueFromVRFProof(nil, solidity_vrf_v08_verifier_wrapper.VRFProof{ - Pk: solProof.Pk, - Gamma: solProof.Gamma, - C: solProof.C, - S: solProof.S, - Seed: solProof.Seed, - UWitness: solProof.UWitness, - CGammaWitness: solProof.CGammaWitness, - SHashWitness: solProof.SHashWitness, - ZInv: solProof.ZInv, - }, randomSeed) - require.NoError(t, err, "failed on-chain to verify VRF proof / get its output") - require.Equal(t, 0, response.Cmp(proof.Output), - "on-chain VRF output differs from off-chain!") - } -} diff --git a/core/services/vrf/v1/integration_test.go b/core/services/vrf/v1/integration_test.go deleted file mode 100644 index 2481b51450f..00000000000 --- a/core/services/vrf/v1/integration_test.go +++ /dev/null @@ -1,274 +0,0 @@ -package v1_test - -import ( - "encoding/hex" - "math/big" - "strings" - "testing" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/google/uuid" - "github.com/onsi/gomega" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" - "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" - "github.com/smartcontractkit/chainlink-evm/pkg/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" - "github.com/smartcontractkit/chainlink/v2/core/testdata/testspecs" - "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" -) - -func TestIntegration_VRF_JPV2(t *testing.T) { - t.Parallel() - tests := []struct { - name string - eip1559 bool - }{ - {"legacy", false}, - {"eip1559", true}, - } - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - if tt.name == "eip1559" { - t.Skip("fails after geth upgrade https://github.com/smartcontractkit/chainlink/pull/11809") - } - ctx := testutils.Context(t) - config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].GasEstimator.EIP1559DynamicFees = &test.eip1559 - c.EVM[0].ChainID = (*sqlutil.Big)(testutils.SimulatedChainID) - }) - key1 := cltest.MustGenerateRandomKey(t) - key2 := cltest.MustGenerateRandomKey(t) - cu := vrftesthelpers.NewVRFCoordinatorUniverse(t, key1, key2) - incomingConfs := 2 - app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, cu.Backend, key1, key2) - require.NoError(t, app.Start(ctx)) - - jb, vrfKey := createVRFJobRegisterKey(t, cu, app, incomingConfs) - require.NoError(t, app.JobSpawner().CreateJob(ctx, nil, &jb)) - - _, err := cu.ConsumerContract.TestRequestRandomness(cu.Carol, - vrfKey.PublicKey.MustHash(), big.NewInt(100)) - require.NoError(t, err) - - _, err = cu.ConsumerContract.TestRequestRandomness(cu.Carol, - vrfKey.PublicKey.MustHash(), big.NewInt(100)) - require.NoError(t, err) - cu.Backend.Commit() - t.Log("Sent 2 test requests") - // Mine the required number of blocks - // So our request gets confirmed. - for range incomingConfs { - cu.Backend.Commit() - } - var runs []pipeline.Run - require.Eventually(t, func() bool { - runs, err = app.PipelineORM().GetAllRuns(ctx) - require.NoError(t, err) - // It possible that we send the test request - // before the Job spawner has started the vrf services, which is fine - // the lb will backfill the logs. However we need to - // keep blocks coming in for the lb to send the backfilled logs. - cu.Backend.Commit() - return len(runs) == 2 && runs[0].State == pipeline.RunStatusCompleted && runs[1].State == pipeline.RunStatusCompleted - }, testutils.WaitTimeout(t), 1*time.Second) - assert.Equal(t, pipeline.RunErrors([]null.String{{}}), runs[0].FatalErrors) - assert.Len(t, runs[0].PipelineTaskRuns, 4) - assert.Len(t, runs[1].PipelineTaskRuns, 4) - assert.NotNil(t, 0, runs[0].Outputs.Val) - assert.NotNil(t, 0, runs[1].Outputs.Val) - - // stop jobs as to not cause a race condition in geth simulated backend - // between job creating new tx and fulfillment logs polling below - require.NoError(t, app.JobSpawner().DeleteJob(ctx, nil, jb.ID)) - - // Ensure the eth transaction gets confirmed on chain. - require.Eventually(t, func() bool { - orm := txmgr.NewTxStore(app.GetDB(), app.GetLogger()) - uc, err2 := orm.CountUnconfirmedTransactions(ctx, key1.Address, testutils.SimulatedChainID) - require.NoError(t, err2) - return uc == 0 - }, testutils.WaitTimeout(t), 100*time.Millisecond) - - // Assert the request was fulfilled on-chain. - var rf []*solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled - require.Eventually(t, func() bool { - rfIterator, err2 := cu.RootContract.FilterRandomnessRequestFulfilled(nil) - require.NoError(t, err2, "failed to subscribe to RandomnessRequest logs") - rf = nil - for rfIterator.Next() { - rf = append(rf, rfIterator.Event) - } - return len(rf) == 2 - }, testutils.WaitTimeout(t), 500*time.Millisecond) - - // Check that each sending address sent one transaction - n1, err := cu.Backend.Client().PendingNonceAt(ctx, key1.Address) - require.NoError(t, err) - require.EqualValues(t, 1, n1) - - n2, err := cu.Backend.Client().PendingNonceAt(ctx, key2.Address) - require.NoError(t, err) - require.EqualValues(t, 1, n2) - }) - } -} - -func TestIntegration_VRF_WithBHS(t *testing.T) { - t.Parallel() - ctx := testutils.Context(t) - config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) - c.EVM[0].BlockBackfillDepth = ptr[uint32](500) - c.Feature.LogPoller = ptr(true) - c.EVM[0].FinalityDepth = ptr[uint32](2) - c.EVM[0].LogPollInterval = commonconfig.MustNewDuration(time.Second) - c.EVM[0].ChainID = (*sqlutil.Big)(testutils.SimulatedChainID) - }) - key := cltest.MustGenerateRandomKey(t) - cu := vrftesthelpers.NewVRFCoordinatorUniverse(t, key) - incomingConfs := 2 - app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, cu.Backend, key) - require.NoError(t, app.Start(ctx)) - - // Create VRF Job but do not start it yet - jb, vrfKey := createVRFJobRegisterKey(t, cu, app, incomingConfs) - - sendingKeys := []string{key.Address.String()} - - // Create BHS Job and start it - bhsJob := vrftesthelpers.CreateAndStartBHSJob(t, sendingKeys, app, cu.BHSContractAddress.String(), - cu.RootContractAddress.String(), "", "", "", 0, 200, 0, 100) - - // Ensure log poller is ready and has all logs. - chain, ok := app.GetRelayers().LegacyEVMChains().Slice()[0].(legacyevm.Chain) - require.True(t, ok) - require.NoError(t, chain.LogPoller().Ready()) - require.NoError(t, chain.LogPoller().Replay(ctx, 1)) - - // Create a VRF request - _, err := cu.ConsumerContract.TestRequestRandomness(cu.Carol, - vrfKey.PublicKey.MustHash(), big.NewInt(100)) - require.NoError(t, err) - - cu.Backend.Commit() - h, err := cu.Backend.Client().HeaderByNumber(testutils.Context(t), nil) - require.NoError(t, err) - requestBlock := h.Number - - // Wait 101 blocks. - for range 100 { - cu.Backend.Commit() - } - - // Wait for the blockhash to be stored - gomega.NewGomegaWithT(t).Eventually(func() bool { - cu.Backend.Commit() - _, err2 := cu.BHSContract.GetBlockhash(&bind.CallOpts{ - Pending: false, - From: common.Address{}, - BlockNumber: nil, - Context: nil, - }, requestBlock) - if err2 == nil { - return true - } else if strings.Contains(err2.Error(), "execution reverted") { - return false - } - t.Fatal(err2) - return false - }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - - // Wait another 160 blocks so that the request is outside the 256 block window - for range 160 { - cu.Backend.Commit() - } - - // Start the VRF Job and wait until it's processed - require.NoError(t, app.JobSpawner().CreateJob(ctx, nil, &jb)) - - var runs []pipeline.Run - require.Eventually(t, func() bool { - runs, err = app.PipelineORM().GetAllRuns(ctx) - require.NoError(t, err) - cu.Backend.Commit() - return len(runs) == 1 && runs[0].State == pipeline.RunStatusCompleted - }, 10*time.Second, 1*time.Second) - assert.Equal(t, pipeline.RunErrors([]null.String{{}}), runs[0].FatalErrors) - assert.Len(t, runs[0].PipelineTaskRuns, 4) - assert.NotNil(t, 0, runs[0].Outputs.Val) - - // stop jobs as to not cause a race condition in geth simulated backend - // between job creating new tx and fulfillment logs polling below - require.NoError(t, app.JobSpawner().DeleteJob(ctx, nil, jb.ID)) - require.NoError(t, app.JobSpawner().DeleteJob(ctx, nil, bhsJob.ID)) - - // Ensure the eth transaction gets confirmed on chain. - require.Eventually(t, func() bool { - orm := txmgr.NewTxStore(app.GetDB(), app.GetLogger()) - uc, err2 := orm.CountUnconfirmedTransactions(ctx, key.Address, testutils.SimulatedChainID) - require.NoError(t, err2) - return uc == 0 - }, 5*time.Second, 100*time.Millisecond) - - // Assert the request was fulfilled on-chain. - require.Eventually(t, func() bool { - rfIterator, err := cu.RootContract.FilterRandomnessRequestFulfilled(nil) - require.NoError(t, err, "failed to subscribe to RandomnessRequest logs") - var rf []*solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled - for rfIterator.Next() { - rf = append(rf, rfIterator.Event) - } - return len(rf) == 1 - }, 5*time.Second, 500*time.Millisecond) -} - -func createVRFJobRegisterKey(t *testing.T, u vrftesthelpers.CoordinatorUniverse, app *cltest.TestApplication, incomingConfs int) (job.Job, vrfkey.KeyV2) { - ctx := testutils.Context(t) - vrfKey, err := app.KeyStore.VRF().Create(ctx) - require.NoError(t, err) - - jid := uuid.MustParse("96a8a26f-d426-4784-8d8f-fb387d4d8345") - expectedOnChainJobID, err := hex.DecodeString("3936613861323666643432363437383438643866666233383764346438333435") - require.NoError(t, err) - s := testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ - JobID: jid.String(), - Name: "vrf-primary", - CoordinatorAddress: u.RootContractAddress.String(), - MinIncomingConfirmations: incomingConfs, - PublicKey: vrfKey.PublicKey.String(), - EVMChainID: testutils.SimulatedChainID.String(), - }).Toml() - jb, err := vrfcommon.ValidatedVRFSpec(s) - require.NoError(t, err) - assert.Equal(t, expectedOnChainJobID, jb.ExternalIDEncodeStringToTopic().Bytes()) - - p, err := vrfKey.PublicKey.Point() - require.NoError(t, err) - _, err = u.RootContract.RegisterProvingKey( - u.Neil, big.NewInt(7), u.Neil.From, pair(secp256k1.Coordinates(p)), jb.ExternalIDEncodeStringToTopic()) - require.NoError(t, err) - u.Backend.Commit() - return jb, vrfKey -} - -func ptr[T any](t T) *T { return &t } - -func pair(x, y *big.Int) [2]*big.Int { return [2]*big.Int{x, y} } diff --git a/core/services/vrf/v1/listener_v1.go b/core/services/vrf/v1/listener_v1.go deleted file mode 100644 index 724f9135478..00000000000 --- a/core/services/vrf/v1/listener_v1.go +++ /dev/null @@ -1,556 +0,0 @@ -package v1 - -import ( - "context" - "encoding/hex" - "errors" - "fmt" - "math/big" - "strconv" - "strings" - "sync" - "time" - - "github.com/avast/retry-go/v4" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - heaps "github.com/theodesp/go-heaps" - "github.com/theodesp/go-heaps/pairing" - - "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" - "github.com/smartcontractkit/chainlink-common/pkg/utils/mathutil" - - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" - "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" - "github.com/smartcontractkit/chainlink-evm/pkg/log" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/recovery" - "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" - "github.com/smartcontractkit/chainlink/v2/core/utils" -) - -var ( - _ log.Listener = &Listener{} - _ job.ServiceCtx = &Listener{} -) - -const callbacksTimeout = 10 * time.Second - -type request struct { - confirmedAtBlock uint64 - req *solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest - lb log.Broadcast - utcTimestamp time.Time -} - -type Listener struct { - services.StateMachine - - Cfg vrfcommon.Config - FeeCfg vrfcommon.FeeConfig - L logger.SugaredLogger - Coordinator *solidity_vrf_coordinator_interface.VRFCoordinator - PipelineRunner pipeline.Runner - Job job.Job - GethKs vrfcommon.GethKeyStore - MailMon *mailbox.Monitor - ReqLogs *mailbox.Mailbox[log.Broadcast] - ChStop services.StopChan - WaitOnStop chan struct{} - NewHead chan struct{} - LatestHead uint64 - LatestHeadMu sync.RWMutex - Chain legacyevm.Chain - - // We can keep these pending logs in memory because we - // only mark them confirmed once we send a corresponding fulfillment transaction. - // So on node restart in the middle of processing, the lb will resend them. - ReqsMu sync.Mutex // Both goroutines write to Reqs - Reqs []request - ReqAdded func() // A simple debug helper - - // Data structures for reorg attack protection - // We want a map so we can do an O(1) count update every fulfillment log we get. - RespCountMu sync.Mutex - ResponseCount map[[32]byte]uint64 - // This auxiliary heap is to used when we need to purge the - // ResponseCount map - we repeatedly want remove the minimum log. - // You could use a sorted list if the completed logs arrive in order, but they may not. - BlockNumberToReqID *pairing.PairHeap - - // Deduper prevents processing duplicate requests from the log broadcaster. - Deduper *vrfcommon.LogDeduper -} - -// Note that we have 2 seconds to do this processing -func (lsn *Listener) OnNewLongestChain(_ context.Context, head *evmtypes.Head) { - lsn.setLatestHead(head) - select { - case lsn.NewHead <- struct{}{}: - default: - } -} - -func (lsn *Listener) setLatestHead(h *evmtypes.Head) { - lsn.LatestHeadMu.Lock() - defer lsn.LatestHeadMu.Unlock() - num := uint64(h.Number) - if num > lsn.LatestHead { - lsn.LatestHead = num - } -} - -func (lsn *Listener) getLatestHead() uint64 { - lsn.LatestHeadMu.RLock() - defer lsn.LatestHeadMu.RUnlock() - return lsn.LatestHead -} - -// Start complies with job.Service -func (lsn *Listener) Start(ctx context.Context) error { - return lsn.StartOnce("VRFListener", func() error { - spec := job.LoadDefaultVRFPollPeriod(*lsn.Job.VRFSpec) - - unsubscribeLogs := lsn.Chain.LogBroadcaster().Register(lsn, log.ListenerOpts{ - Contract: lsn.Coordinator.Address(), - ParseLog: lsn.Coordinator.ParseLog, - LogsWithTopics: map[common.Hash][][]log.Topic{ - solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest{}.Topic(): { - { - log.Topic(lsn.Job.ExternalIDEncodeStringToTopic()), - log.Topic(lsn.Job.ExternalIDEncodeBytesToTopic()), - }, - }, - solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled{}.Topic(): {}, - }, - // If we set this to MinIncomingConfirmations, since both the log broadcaster and head broadcaster get heads - // at the same time from the head tracker whether we process the log at MinIncomingConfirmations or - // MinIncomingConfirmations+1 would depend on the order in which their OnNewLongestChain callbacks got - // called. - // We listen one block early so that the log can be stored in pendingRequests to avoid this. - MinIncomingConfirmations: spec.MinIncomingConfirmations - 1, - ReplayStartedCallback: lsn.ReplayStartedCallback, - }) - // Subscribe to the head broadcaster for handling - // per request conf requirements. - latestHead, unsubscribeHeadBroadcaster := lsn.Chain.HeadBroadcaster().Subscribe(lsn) - if latestHead != nil { - lsn.setLatestHead(latestHead) - } - - // Populate the response count map - lsn.RespCountMu.Lock() - defer lsn.RespCountMu.Unlock() - respCount, err := lsn.GetStartingResponseCountsV1(ctx) - if err != nil { - return err - } - lsn.ResponseCount = respCount - go lsn.RunLogListener([]func(){unsubscribeLogs}, spec.MinIncomingConfirmations) - go lsn.RunHeadListener(unsubscribeHeadBroadcaster) - - lsn.MailMon.Monitor(lsn.ReqLogs, "VRFListener", "RequestLogs", strconv.Itoa(int(lsn.Job.ID))) - return nil - }) -} - -func (lsn *Listener) GetStartingResponseCountsV1(ctx context.Context) (respCount map[[32]byte]uint64, err error) { - respCounts := make(map[[32]byte]uint64) - var latestBlockNum *big.Int - // Retry client call for LatestBlockHeight if fails - // Want to avoid failing startup due to potential faulty RPC call - err = retry.Do(func() error { - latestBlockNum, err = lsn.Chain.Client().LatestBlockHeight(ctx) - return err - }, retry.Attempts(10), retry.Delay(500*time.Millisecond)) - if err != nil { - return nil, err - } - if latestBlockNum == nil { - return nil, errors.New("LatestBlockHeight return nil block num") - } - confirmedBlockNum := latestBlockNum.Int64() - int64(lsn.Chain.Config().EVM().FinalityDepth()) - // Only check as far back as the evm finality depth for completed transactions. - var counts []vrfcommon.RespCountEntry - counts, err = vrfcommon.GetRespCounts(ctx, lsn.Chain.TxManager(), lsn.Chain.Client().ConfiguredChainID(), confirmedBlockNum) - if err != nil { - // Continue with an empty map, do not block job on this. - lsn.L.Errorw("Unable to read previous confirmed fulfillments", "err", err) - return respCounts, nil - } - - for _, c := range counts { - // Remove the quotes from the json - req := strings.Replace(c.RequestID, `"`, ``, 2) - // Remove the 0x prefix - b, err := hex.DecodeString(req[2:]) - if err != nil { - lsn.L.Errorw("Unable to read fulfillment", "err", err, "reqID", c.RequestID) - continue - } - var reqID [32]byte - copy(reqID[:], b) - respCounts[reqID] = uint64(c.Count) - } - - return respCounts, nil -} - -// Removes and returns all the confirmed logs from -// the pending queue. -func (lsn *Listener) extractConfirmedLogs() []request { - lsn.ReqsMu.Lock() - defer lsn.ReqsMu.Unlock() - vrfcommon.UpdateQueueSize(lsn.Job.Name.ValueOrZero(), lsn.Job.ExternalJobID, vrfcommon.V1, len(lsn.Reqs)) - var toProcess, toKeep []request - for i := 0; i < len(lsn.Reqs); i++ { - if lsn.Reqs[i].confirmedAtBlock <= lsn.getLatestHead() { - toProcess = append(toProcess, lsn.Reqs[i]) - } else { - toKeep = append(toKeep, lsn.Reqs[i]) - } - } - lsn.Reqs = toKeep - return toProcess -} - -type fulfilledReq struct { - blockNumber uint64 - reqID [32]byte -} - -func (a fulfilledReq) Compare(b heaps.Item) int { - a1 := a - a2 := b.(fulfilledReq) - switch { - case a1.blockNumber > a2.blockNumber: - return 1 - case a1.blockNumber < a2.blockNumber: - return -1 - default: - return 0 - } -} - -// Remove all entries 10000 blocks or older -// to avoid a memory leak. -func (lsn *Listener) pruneConfirmedRequestCounts() { - lsn.RespCountMu.Lock() - defer lsn.RespCountMu.Unlock() - min := lsn.BlockNumberToReqID.FindMin() - for min != nil { - m := min.(fulfilledReq) - if m.blockNumber > (lsn.getLatestHead() - 10000) { - break - } - delete(lsn.ResponseCount, m.reqID) - lsn.BlockNumberToReqID.DeleteMin() - min = lsn.BlockNumberToReqID.FindMin() - } -} - -// Listen for new heads -func (lsn *Listener) RunHeadListener(unsubscribe func()) { - ctx, cancel := lsn.ChStop.NewCtx() - defer cancel() - - for { - select { - case <-ctx.Done(): - unsubscribe() - lsn.WaitOnStop <- struct{}{} - return - case <-lsn.NewHead: - recovery.WrapRecover(lsn.L, func() { - toProcess := lsn.extractConfirmedLogs() - var toRetry []request - for _, r := range toProcess { - if success := lsn.ProcessRequest(ctx, r); !success { - toRetry = append(toRetry, r) - } - } - lsn.ReqsMu.Lock() - defer lsn.ReqsMu.Unlock() - lsn.Reqs = append(lsn.Reqs, toRetry...) - lsn.pruneConfirmedRequestCounts() - }) - } - } -} - -func (lsn *Listener) RunLogListener(unsubscribes []func(), minConfs uint32) { - ctx, cancel := lsn.ChStop.NewCtx() - defer cancel() - lsn.L.Infow("Listening for run requests", - "gasLimit", lsn.FeeCfg.LimitDefault(), - "minConfs", minConfs) - for { - select { - case <-lsn.ChStop: - for _, f := range unsubscribes { - f() - } - lsn.WaitOnStop <- struct{}{} - return - case <-lsn.ReqLogs.Notify(): - // Process all the logs in the queue if one is added - for { - lb, exists := lsn.ReqLogs.Retrieve() - if !exists { - break - } - recovery.WrapRecover(lsn.L, func() { - lsn.handleLog(ctx, lb, minConfs) - }) - } - } - } -} - -func (lsn *Listener) handleLog(ctx context.Context, lb log.Broadcast, minConfs uint32) { - lggr := lsn.L.With( - "log", lb.String(), - "decodedLog", lb.DecodedLog(), - "blockNumber", lb.RawLog().BlockNumber, - "blockHash", lb.RawLog().BlockHash, - "txHash", lb.RawLog().TxHash, - ) - - lggr.Infow("Log received") - if v, ok := lb.DecodedLog().(*solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled); ok { - lggr.Debugw("Got fulfillment log", - "requestID", hex.EncodeToString(v.RequestId[:])) - if !lsn.shouldProcessLog(ctx, lb) { - return - } - lsn.RespCountMu.Lock() - lsn.ResponseCount[v.RequestId]++ - lsn.BlockNumberToReqID.Insert(fulfilledReq{ - blockNumber: v.Raw.BlockNumber, - reqID: v.RequestId, - }) - lsn.RespCountMu.Unlock() - lsn.markLogAsConsumed(ctx, lb) - return - } - - req, err := lsn.Coordinator.ParseRandomnessRequest(lb.RawLog()) - if err != nil { - lggr.Errorw("Failed to parse RandomnessRequest log", "err", err) - if !lsn.shouldProcessLog(ctx, lb) { - return - } - lsn.markLogAsConsumed(ctx, lb) - return - } - - confirmedAt := lsn.getConfirmedAt(req, minConfs) - lsn.ReqsMu.Lock() - lsn.Reqs = append(lsn.Reqs, request{ - confirmedAtBlock: confirmedAt, - req: req, - lb: lb, - utcTimestamp: time.Now().UTC(), - }) - lsn.ReqAdded() - lsn.ReqsMu.Unlock() - lggr.Infow("Enqueued randomness request", - "requestID", hex.EncodeToString(req.RequestID[:]), - "requestJobID", hex.EncodeToString(req.JobID[:]), - "keyHash", hex.EncodeToString(req.KeyHash[:]), - "fee", req.Fee, - "sender", req.Sender.Hex(), - "txHash", lb.RawLog().TxHash) -} - -func (lsn *Listener) shouldProcessLog(ctx context.Context, lb log.Broadcast) bool { - consumed, err := lsn.Chain.LogBroadcaster().WasAlreadyConsumed(ctx, lb) - if err != nil { - lsn.L.Errorw("Could not determine if log was already consumed", "err", err, "txHash", lb.RawLog().TxHash) - // Do not process, let lb resend it as a retry mechanism. - return false - } - return !consumed -} - -func (lsn *Listener) markLogAsConsumed(ctx context.Context, lb log.Broadcast) { - err := lsn.Chain.LogBroadcaster().MarkConsumed(ctx, nil, lb) - lsn.L.ErrorIf(err, fmt.Sprintf("Unable to mark log %v as consumed", lb.String())) -} - -func (lsn *Listener) getConfirmedAt(req *solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest, minConfs uint32) uint64 { - lsn.RespCountMu.Lock() - defer lsn.RespCountMu.Unlock() - newConfs := min( - // We cap this at 200 because solidity only supports the most recent 256 blocks - // in the contract so if it was older than that, fulfillments would start failing - // without the blockhash store feeder. We use 200 to give the node plenty of time - // to fulfill even on fast chains. - uint64(minConfs)*(1< 0 { - lsn.L.Warnw("Duplicate request found after fulfillment, doubling incoming confirmations", - "txHash", req.Raw.TxHash, - "blockNumber", req.Raw.BlockNumber, - "blockHash", req.Raw.BlockHash, - "requestID", hex.EncodeToString(req.RequestID[:]), - "newConfs", newConfs) - vrfcommon.IncDupeReqs(lsn.Job.Name.ValueOrZero(), lsn.Job.ExternalJobID, vrfcommon.V1) - } - return req.Raw.BlockNumber + newConfs -} - -// ProcessRequest attempts to process the VRF request. Returns true if successful, false otherwise. -func (lsn *Listener) ProcessRequest(ctx context.Context, req request) bool { - // This check to see if the log was consumed needs to be in the same - // goroutine as the mark consumed to avoid processing duplicates. - if !lsn.shouldProcessLog(ctx, req.lb) { - return true - } - - lggr := lsn.L.With( - "log", req.lb.String(), - "requestID", hex.EncodeToString(req.req.RequestID[:]), - "txHash", req.req.Raw.TxHash, - "keyHash", hex.EncodeToString(req.req.KeyHash[:]), - "jobID", hex.EncodeToString(req.req.JobID[:]), - "sender", req.req.Sender.Hex(), - "blockNumber", req.req.Raw.BlockNumber, - "blockHash", req.req.Raw.BlockHash, - "seed", req.req.Seed, - "fee", req.req.Fee, - ) - - // Check if the vrf req has already been fulfilled - // Note we have to do this after the log has been confirmed. - // If not, the following problematic (example) scenario can arise: - // 1. Request log comes in block 100 - // 2. Fulfill the request in block 110 - // 3. Reorg both request and fulfillment, now request lives at - // block 101 and fulfillment lives at block 115 - // 4. The eth node sees the request reorg and tells us about it. We do our fulfillment - // check and the node says its already fulfilled (hasn't seen the fulfillment reorged yet), - // so we don't process the request. - // Subtract 5 since the newest block likely isn't indexed yet and will cause "header not - // found" errors. - m := mathutil.Max(req.confirmedAtBlock, lsn.getLatestHead()-5) - ctx, cancel := context.WithTimeout(ctx, callbacksTimeout) - defer cancel() - callback, err := lsn.Coordinator.Callbacks(&bind.CallOpts{ - BlockNumber: big.NewInt(int64(m)), - Context: ctx, - }, req.req.RequestID) - if err != nil { - lggr.Errorw("Unable to check if already fulfilled, processing anyways", "err", err) - } else if utils.IsEmpty(callback.SeedAndBlockNum[:]) { - // If seedAndBlockNumber is zero then the response has been fulfilled - // and we should skip it - lggr.Infow("Request already fulfilled") - lsn.markLogAsConsumed(ctx, req.lb) - return true - } - - // Check if we can ignore the request due to its age. - if time.Now().UTC().Sub(req.utcTimestamp) >= lsn.Job.VRFSpec.RequestTimeout { - lggr.Infow("Request too old, dropping it") - lsn.markLogAsConsumed(ctx, req.lb) - return true - } - - lggr.Infow("Processing log request") - - vars := pipeline.NewVarsFrom(map[string]any{ - "jobSpec": map[string]any{ - "databaseID": lsn.Job.ID, - "externalJobID": lsn.Job.ExternalJobID, - "name": lsn.Job.Name.ValueOrZero(), - "publicKey": lsn.Job.VRFSpec.PublicKey[:], - "from": lsn.fromAddresses(), - "evmChainID": lsn.Job.VRFSpec.EVMChainID.String(), - }, - "jobRun": map[string]any{ - "logBlockHash": req.req.Raw.BlockHash[:], - "logBlockNumber": req.req.Raw.BlockNumber, - "logTxHash": req.req.Raw.TxHash, - "logTopics": req.req.Raw.Topics, - "logData": req.req.Raw.Data, - }, - }) - - run := pipeline.NewRun(*lsn.Job.PipelineSpec, vars) - // The VRF pipeline has no async tasks, so we don't need to check for `incomplete` - if _, err = lsn.PipelineRunner.Run(ctx, run, true, func(tx sqlutil.DataSource) error { - // Always mark consumed regardless of whether the proof failed or not. - if err = lsn.Chain.LogBroadcaster().MarkConsumed(ctx, tx, req.lb); err != nil { - lggr.Errorw("Failed mark consumed", "err", err) - } - return nil - }); err != nil { - lggr.Errorw("Failed to execute VRFV1 pipeline run", - "err", err) - return false - } - - // At this point the pipeline runner has completed the run of the pipeline, - // but it may have errored out. - if run.HasErrors() || run.HasFatalErrors() { - lggr.Error("VRFV1 pipeline run failed with errors", - "runErrors", run.AllErrors.ToError(), - "runFatalErrors", run.FatalErrors.ToError(), - ) - return false - } - - // At this point, the pipeline run executed successfully, and we mark - // the request as processed. - lggr.Infow("Executed VRFV1 fulfillment run") - vrfcommon.IncProcessedReqs(lsn.Job.Name.ValueOrZero(), lsn.Job.ExternalJobID, vrfcommon.V1) - return true -} - -// Close complies with job.Service -func (lsn *Listener) Close() error { - return lsn.StopOnce("VRFListener", func() error { - close(lsn.ChStop) - <-lsn.WaitOnStop // Log Listener - <-lsn.WaitOnStop // Head Listener - return lsn.ReqLogs.Close() - }) -} - -func (lsn *Listener) HandleLog(ctx context.Context, lb log.Broadcast) { - if !lsn.Deduper.ShouldDeliver(lb.RawLog()) { - lsn.L.Tracew("skipping duplicate log broadcast", "log", lb.RawLog()) - return - } - - wasOverCapacity := lsn.ReqLogs.Deliver(lb) - if wasOverCapacity { - lsn.L.Error("log mailbox is over capacity - dropped the oldest log") - vrfcommon.IncDroppedReqs(lsn.Job.Name.ValueOrZero(), lsn.Job.ExternalJobID, vrfcommon.V1, vrfcommon.ReasonMailboxSize) - } -} - -func (lsn *Listener) fromAddresses() []common.Address { - var addresses []common.Address - for _, a := range lsn.Job.VRFSpec.FromAddresses { - addresses = append(addresses, a.Address()) - } - return addresses -} - -// Job complies with log.Listener -func (lsn *Listener) JobID() int32 { - return lsn.Job.ID -} - -// ReplayStartedCallback is called by the log broadcaster when a replay is about to start. -func (lsn *Listener) ReplayStartedCallback() { - // Clear the log Deduper cache so that we don't incorrectly ignore logs that have been sent that - // are already in the cache. - lsn.Deduper.Clear() -} diff --git a/core/services/vrf/v1/listener_v1_test.go b/core/services/vrf/v1/listener_v1_test.go deleted file mode 100644 index ac25a1912fa..00000000000 --- a/core/services/vrf/v1/listener_v1_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package v1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/theodesp/go-heaps/pairing" - - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" - "github.com/smartcontractkit/chainlink-evm/pkg/utils" -) - -func TestConfirmedLogExtraction(t *testing.T) { - lsn := Listener{} - lsn.Reqs = []request{ - { - confirmedAtBlock: 2, - req: &solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest{ - RequestID: utils.PadByteToHash(0x02), - }, - }, - { - confirmedAtBlock: 1, - req: &solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest{ - RequestID: utils.PadByteToHash(0x01), - }, - }, - { - confirmedAtBlock: 3, - req: &solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest{ - RequestID: utils.PadByteToHash(0x03), - }, - }, - } - // None are confirmed - lsn.LatestHead = 0 - logs := lsn.extractConfirmedLogs() - assert.Empty(t, logs) // None ready - assert.Len(t, lsn.Reqs, 3) // All pending - lsn.LatestHead = 2 - logs = lsn.extractConfirmedLogs() - assert.Len(t, logs, 2) // 1 and 2 should be confirmed - assert.Len(t, lsn.Reqs, 1) // 3 is still pending - assert.Equal(t, uint64(3), lsn.Reqs[0].confirmedAtBlock) - // Another block way in the future should clear it - lsn.LatestHead = 10 - logs = lsn.extractConfirmedLogs() - assert.Len(t, logs, 1) // remaining log - assert.Empty(t, lsn.Reqs) // all processed -} - -func TestResponsePruning(t *testing.T) { - lsn := Listener{} - lsn.LatestHead = 10000 - lsn.ResponseCount = map[[32]byte]uint64{ - utils.PadByteToHash(0x00): 1, - utils.PadByteToHash(0x01): 1, - } - lsn.BlockNumberToReqID = pairing.New() - lsn.BlockNumberToReqID.Insert(fulfilledReq{ - blockNumber: 1, - reqID: utils.PadByteToHash(0x00), - }) - lsn.BlockNumberToReqID.Insert(fulfilledReq{ - blockNumber: 2, - reqID: utils.PadByteToHash(0x01), - }) - lsn.pruneConfirmedRequestCounts() - assert.Len(t, lsn.ResponseCount, 2) - lsn.LatestHead = 10001 - lsn.pruneConfirmedRequestCounts() - assert.Len(t, lsn.ResponseCount, 1) - lsn.LatestHead = 10002 - lsn.pruneConfirmedRequestCounts() - assert.Empty(t, lsn.ResponseCount) -} diff --git a/core/services/vrf/v1/listener_v1_test_helpers.go b/core/services/vrf/v1/listener_v1_test_helpers.go deleted file mode 100644 index f9532bf83e6..00000000000 --- a/core/services/vrf/v1/listener_v1_test_helpers.go +++ /dev/null @@ -1,36 +0,0 @@ -package v1 - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func (lsn *Listener) SetReqAdded(fn func()) { - lsn.ReqAdded = fn -} - -func (lsn *Listener) Stop(t *testing.T) { - assert.NoError(t, lsn.Close()) - select { - case <-lsn.WaitOnStop: - case <-time.After(time.Second): - t.Error("did not clean up properly") - } -} - -func (lsn *Listener) ReqsConfirmedAt() (us []uint64) { - for i := range lsn.Reqs { - us = append(us, lsn.Reqs[i].confirmedAtBlock) - } - return us -} - -func (lsn *Listener) RespCount(reqIDBytes [32]byte) uint64 { - return lsn.ResponseCount[reqIDBytes] -} - -func (lsn *Listener) SetRespCount(reqIDBytes [32]byte, c uint64) { - lsn.ResponseCount[reqIDBytes] = c -} diff --git a/core/services/vrf/v2/bhs_feeder_test.go b/core/services/vrf/v2/bhs_feeder_test.go index c566d831189..7eb9c5ac4d6 100644 --- a/core/services/vrf/v2/bhs_feeder_test.go +++ b/core/services/vrf/v2/bhs_feeder_test.go @@ -69,7 +69,7 @@ func TestStartHeartbeats(t *testing.T) { require.NoError(t, app.Start(testutils.Context(t))) _ = vrftesthelpers.CreateAndStartBHSJob( - t, bhsKeyAddresses, app, uni.bhsContractAddress.String(), "", + t, bhsKeyAddresses, app, uni.bhsContractAddress.String(), uni.rootContractAddress.String(), "", "", 0, 200, heartbeatPeriod, 100) // Ensure log poller is ready and has all logs. diff --git a/core/services/vrf/v2/integration_helpers_test.go b/core/services/vrf/v2/integration_helpers_test.go index 141d2c53a16..9ff4da31a5c 100644 --- a/core/services/vrf/v2/integration_helpers_test.go +++ b/core/services/vrf/v2/integration_helpers_test.go @@ -246,13 +246,12 @@ func testMultipleConsumersNeedBHS( v2CoordinatorAddress = coordinatorAddress.String() case vrfcommon.V2Plus: v2PlusCoordinatorAddress = coordinatorAddress.String() - // Also set V2 to satisfy the database constraint which requires V1 OR V2 (but not V2Plus) - // Using the same contract address is safe since it's a valid contract + // Also set V2 to satisfy validation when using a V2Plus coordinator. v2CoordinatorAddress = coordinatorAddress.String() } _ = vrftesthelpers.CreateAndStartBHSJob( - t, bhsKeyAddresses, app, uni.bhsContractAddress.String(), "", + t, bhsKeyAddresses, app, uni.bhsContractAddress.String(), v2CoordinatorAddress, v2PlusCoordinatorAddress, "", 0, 200, 0, 100, ) @@ -402,8 +401,7 @@ func testMultipleConsumersNeedTrustedBHS( v2CoordinatorAddress = coordinatorAddress.String() case vrfcommon.V2Plus: v2PlusCoordinatorAddress = coordinatorAddress.String() - // Also set V2 to satisfy the database constraint which requires V1 OR V2 (but not V2Plus) - // Using the same contract address is safe since it's a valid contract (if not log poller will fail) + // Also set V2 to satisfy validation when using a V2Plus coordinator. v2CoordinatorAddress = coordinatorAddress.String() } @@ -412,8 +410,7 @@ func testMultipleConsumersNeedTrustedBHS( waitBlocks = 400 } _ = vrftesthelpers.CreateAndStartBHSJob( - t, bhsKeyAddressesStrings, app, "", "", - v2CoordinatorAddress, v2PlusCoordinatorAddress, uni.trustedBhsContractAddress.String(), 20, 1000, 0, waitBlocks) + t, bhsKeyAddressesStrings, app, "", v2CoordinatorAddress, v2PlusCoordinatorAddress, uni.trustedBhsContractAddress.String(), 20, 1000, 0, waitBlocks) // Ensure log poller is ready and has all logs. chain, ok := app.GetRelayers().LegacyEVMChains().Slice()[0].(legacyevm.Chain) @@ -809,7 +806,7 @@ func testBlockHeaderFeeder( } _ = vrftesthelpers.CreateAndStartBlockHeaderFeederJob( - t, bhfKeys, app, uni.bhsContractAddress.String(), uni.batchBHSContractAddress.String(), "", + t, bhfKeys, app, uni.bhsContractAddress.String(), uni.batchBHSContractAddress.String(), v2coordinatorAddress, v2plusCoordinatorAddress) // Ensure log poller is ready and has all logs. diff --git a/core/services/vrf/v2/integration_v2_test.go b/core/services/vrf/v2/integration_v2_test.go index d2a0410227d..57fbecc90f0 100644 --- a/core/services/vrf/v2/integration_v2_test.go +++ b/core/services/vrf/v2/integration_v2_test.go @@ -81,7 +81,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" - v1 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v1" v22 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" @@ -2187,12 +2186,9 @@ func TestStartingCountsV1(t *testing.T) { require.NoError(t, err) chain, ok := chainService.(legacyevm.Chain) require.True(t, ok) - listenerV1 := &v1.Listener{ - Chain: chain, - } listenerV2 := v22.MakeTestListenerV2(chain) var counts map[[32]byte]uint64 - counts, err = listenerV1.GetStartingResponseCountsV1(testutils.Context(t)) + counts, err = vrfcommon.GetStartingResponseCountsV1(testutils.Context(t), chain) require.NoError(t, err) assert.Empty(t, counts) err = ks.Unlock(ctx, testutils.Password) @@ -2346,7 +2342,7 @@ func TestStartingCountsV1(t *testing.T) { require.NoError(t, err) } - counts, err = listenerV1.GetStartingResponseCountsV1(testutils.Context(t)) + counts, err = vrfcommon.GetStartingResponseCountsV1(testutils.Context(t), chain) require.NoError(t, err) assert.Len(t, counts, 3) assert.Equal(t, uint64(1), counts[evmutils.PadByteToHash(0x10)]) diff --git a/core/services/vrf/vrfcommon/metrics.go b/core/services/vrf/vrfcommon/metrics.go index cd64b5f12ce..0cf0544da16 100644 --- a/core/services/vrf/vrfcommon/metrics.go +++ b/core/services/vrf/vrfcommon/metrics.go @@ -12,7 +12,6 @@ import ( type Version string const ( - V1 Version = "V1" V2 Version = "V2" V2Plus Version = "V2Plus" ) diff --git a/core/services/vrf/vrfcommon/starting_response_counts_v1.go b/core/services/vrf/vrfcommon/starting_response_counts_v1.go new file mode 100644 index 00000000000..744e9b16bb7 --- /dev/null +++ b/core/services/vrf/vrfcommon/starting_response_counts_v1.go @@ -0,0 +1,58 @@ +package vrfcommon + +import ( + "context" + "encoding/hex" + "errors" + "math/big" + "strings" + "time" + + "github.com/avast/retry-go/v4" + + "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" + + "github.com/smartcontractkit/chainlink/v2/core/utils/safe" +) + +// GetStartingResponseCountsV1 returns fulfilled-request counts keyed by raw +// request ID (as used by legacy VRF v1 meta encoding). It is shared by VRF v2 +// startup logic that reads the same tx meta shape. +func GetStartingResponseCountsV1(ctx context.Context, chain legacyevm.Chain) (map[[32]byte]uint64, error) { + respCounts := make(map[[32]byte]uint64) + var latestBlockNum *big.Int + err := retry.Do(func() error { + var err2 error + latestBlockNum, err2 = chain.Client().LatestBlockHeight(ctx) + return err2 + }, retry.Attempts(10), retry.Delay(500*time.Millisecond)) + if err != nil { + return nil, err + } + if latestBlockNum == nil { + return nil, errors.New("LatestBlockHeight return nil block num") + } + confirmedBlockNum := latestBlockNum.Int64() - int64(chain.Config().EVM().FinalityDepth()) + var counts []RespCountEntry + counts, err = GetRespCounts(ctx, chain.TxManager(), chain.Client().ConfiguredChainID(), confirmedBlockNum) + if err != nil { + return respCounts, nil + } + + for _, c := range counts { + req := strings.Replace(c.RequestID, `"`, ``, 2) + b, err2 := hex.DecodeString(req[2:]) + if err2 != nil { + continue + } + var reqID [32]byte + copy(reqID[:], b) + count, err3 := safe.IntToUint64(c.Count) + if err3 != nil { + continue + } + respCounts[reqID] = count + } + + return respCounts, nil +} diff --git a/core/services/vrf/vrfcommon/validate.go b/core/services/vrf/vrfcommon/validate.go index 0ac0a472943..dd3a3b41e26 100644 --- a/core/services/vrf/vrfcommon/validate.go +++ b/core/services/vrf/vrfcommon/validate.go @@ -81,7 +81,7 @@ func ValidatedVRFSpec(tomlString string) (job.Job, error) { var foundVRFTask bool for _, t := range jb.Pipeline.Tasks { - if t.Type() == pipeline.TaskTypeVRF || t.Type() == pipeline.TaskTypeVRFV2 || t.Type() == pipeline.TaskTypeVRFV2Plus { + if t.Type() == pipeline.TaskTypeVRFV2 || t.Type() == pipeline.TaskTypeVRFV2Plus { foundVRFTask = true } @@ -92,7 +92,7 @@ func ValidatedVRFSpec(tomlString string) (job.Job, error) { } } if !foundVRFTask { - return jb, errors.Wrapf(ErrKeyNotSet, "invalid pipeline, expected a vrf task") + return jb, errors.Wrapf(ErrKeyNotSet, "invalid pipeline, expected a vrfv2 or vrfv2plus task") } jb.VRFSpec = &spec diff --git a/core/services/vrf/vrfcommon/validate_test.go b/core/services/vrf/vrfcommon/validate_test.go index 5afa182bee1..dca444204a2 100644 --- a/core/services/vrf/vrfcommon/validate_test.go +++ b/core/services/vrf/vrfcommon/validate_test.go @@ -12,6 +12,20 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/job" ) +// vrfV2Observation is a minimal valid VRF v2 pipeline for validation tests. +const vrfV2Observation = ` +decode_log [type=ethabidecodelog + abi="RandomWordsRequested(bytes32 indexed keyHash,uint256 requestId,uint256 preSeed,uint64 indexed subId,uint16 minimumRequestConfirmations,uint32 callbackGasLimit,uint32 numWords,address indexed sender)" + data="$(jobRun.logData)" + topics="$(jobRun.logTopics)"] +vrf [type=vrfv2 + publicKey="$(jobSpec.publicKey)" + requestBlockHash="$(jobRun.logBlockHash)" + requestBlockNumber="$(jobRun.logBlockNumber)" + topics="$(jobRun.logTopics)"] +decode_log->vrf +` + func TestValidateVRFJobSpec(t *testing.T) { var tt = []struct { name string @@ -31,24 +45,8 @@ requestTimeout = "168h" # 7 days chunkSize = 25 backoffInitialDelay = "1m" backoffMaxDelay = "2h" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.NoError(t, err) @@ -70,24 +68,8 @@ type = "vrf" schemaVersion = 1 minIncomingConfirmations = 10 coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) @@ -106,25 +88,8 @@ requestTimeout = "168h" # 7 days chunkSize = 25 backoffInitialDelay = "1m" backoffMaxDelay = "2h" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrfv2 - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" - `, +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) require.True(t, errors.Is(ErrKeyNotSet, errors.Cause(err))) @@ -137,24 +102,8 @@ type = "vrf" schemaVersion = 1 minIncomingConfirmations = 10 publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) @@ -170,24 +119,8 @@ minIncomingConfirmations = 10 publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.NoError(t, err) @@ -197,31 +130,15 @@ decode_log->vrf->encode_tx->submit_tx { name: "no requested confs delay", toml: ` - type = "vrf" - schemaVersion = 1 - minIncomingConfirmations = 10 - publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" - coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" - observationSource = """ - decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] - vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] - encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] - submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] - decode_log->vrf->encode_tx->submit_tx - """ - `, +type = "vrf" +schemaVersion = 1 +minIncomingConfirmations = 10 +publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" +coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) require.Equal(t, int64(0), os.VRFSpec.RequestedConfsDelay) @@ -230,32 +147,16 @@ decode_log->vrf->encode_tx->submit_tx { name: "with requested confs delay", toml: ` - type = "vrf" - schemaVersion = 1 - minIncomingConfirmations = 10 - requestedConfsDelay = 10 - publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" - coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" - observationSource = """ - decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] - vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] - encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] - submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] - decode_log->vrf->encode_tx->submit_tx - """ - `, +type = "vrf" +schemaVersion = 1 +minIncomingConfirmations = 10 +requestedConfsDelay = 10 +publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" +coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) require.Equal(t, int64(10), os.VRFSpec.RequestedConfsDelay) @@ -264,32 +165,16 @@ decode_log->vrf->encode_tx->submit_tx { name: "negative (illegal) requested confs delay", toml: ` - type = "vrf" - schemaVersion = 1 - minIncomingConfirmations = 10 - requestedConfsDelay = -10 - publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" - coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" - observationSource = """ - decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] - vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] - encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] - submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] - decode_log->vrf->encode_tx->submit_tx - """ - `, +type = "vrf" +schemaVersion = 1 +minIncomingConfirmations = 10 +requestedConfsDelay = -10 +publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" +coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) }, @@ -297,32 +182,16 @@ decode_log->vrf->encode_tx->submit_tx { name: "no request timeout provided, sets default of 1 day", toml: ` - type = "vrf" - schemaVersion = 1 - minIncomingConfirmations = 10 - requestedConfsDelay = 10 - publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" - coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" - observationSource = """ - decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] - vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] - encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] - submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] - decode_log->vrf->encode_tx->submit_tx - """ - `, +type = "vrf" +schemaVersion = 1 +minIncomingConfirmations = 10 +requestedConfsDelay = 10 +publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" +coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) require.Equal(t, 24*time.Hour, os.VRFSpec.RequestTimeout) @@ -331,33 +200,17 @@ decode_log->vrf->encode_tx->submit_tx { name: "request timeout provided, uses that", toml: ` - type = "vrf" - schemaVersion = 1 - minIncomingConfirmations = 10 - requestedConfsDelay = 10 - requestTimeout = "168h" # 7 days - publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" - coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" - observationSource = """ - decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] - vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] - encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] - submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] - decode_log->vrf->encode_tx->submit_tx - """ - `, +type = "vrf" +schemaVersion = 1 +minIncomingConfirmations = 10 +requestedConfsDelay = 10 +requestTimeout = "168h" # 7 days +publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" +coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) require.Equal(t, 7*24*time.Hour, os.VRFSpec.RequestTimeout) @@ -366,33 +219,17 @@ decode_log->vrf->encode_tx->submit_tx { name: "batch fulfillment enabled, no batch coordinator address", toml: ` - type = "vrf" - schemaVersion = 1 - minIncomingConfirmations = 10 - requestedConfsDelay = 10 - batchFulfillmentEnabled = true - publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" - coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" - observationSource = """ - decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] - vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] - encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] - submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] - decode_log->vrf->encode_tx->submit_tx - """ - `, +type = "vrf" +schemaVersion = 1 +minIncomingConfirmations = 10 +requestedConfsDelay = 10 +batchFulfillmentEnabled = true +publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" +coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) }, @@ -400,34 +237,18 @@ decode_log->vrf->encode_tx->submit_tx { name: "batch fulfillment enabled, batch coordinator address provided", toml: ` - type = "vrf" - schemaVersion = 1 - minIncomingConfirmations = 10 - requestedConfsDelay = 10 - batchFulfillmentEnabled = true - batchCoordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" - coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" - externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" - observationSource = """ - decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] - vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] - encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] - submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] - decode_log->vrf->encode_tx->submit_tx - """ - `, +type = "vrf" +schemaVersion = 1 +minIncomingConfirmations = 10 +requestedConfsDelay = 10 +batchFulfillmentEnabled = true +batchCoordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" +coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +externalJobID = "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" +`, assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) require.Equal(t, "0xB3b7874F13387D44a3398D298B075B7A3505D8d4", os.VRFSpec.BatchCoordinatorAddress.String()) @@ -445,24 +266,8 @@ requestTimeout = "168h" # 7 days chunkSize = 25 backoffInitialDelay = "1h" backoffMaxDelay = "30m" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) @@ -481,24 +286,8 @@ chunkSize = 25 backoffInitialDelay = "1m" backoffMaxDelay = "2h" gasLanePrice = "200 gwei" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.NoError(t, err) @@ -526,24 +315,8 @@ chunkSize = 25 backoffInitialDelay = "1m" backoffMaxDelay = "2h" gasLanePrice = "-200" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) @@ -562,24 +335,8 @@ chunkSize = 25 backoffInitialDelay = "1m" backoffMaxDelay = "2h" gasLanePrice = "0 gwei" -observationSource = """ -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx -""" +fromAddresses = ["0x1111111111111111111111111111111111111111"] +observationSource = """` + vrfV2Observation + `""" `, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) diff --git a/core/services/vrf/vrftesthelpers/helpers.go b/core/services/vrf/vrftesthelpers/helpers.go index a80455159e5..c785a98de83 100644 --- a/core/services/vrf/vrftesthelpers/helpers.go +++ b/core/services/vrf/vrftesthelpers/helpers.go @@ -1,32 +1,14 @@ package vrftesthelpers import ( - "math/big" - "strings" "testing" "time" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/google/uuid" "github.com/shopspring/decimal" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/ethkey" "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/blockhash_store" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/link_token_interface" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_consumer_interface" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_consumer_interface_v08" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_request_id" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_request_id_v08" - "github.com/smartcontractkit/chainlink-evm/pkg/assets" - evmtestutils "github.com/smartcontractkit/chainlink-evm/pkg/testutils" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/blockhashstore" @@ -49,7 +31,7 @@ func CreateAndStartBHSJob( t *testing.T, fromAddresses []string, app *cltest.TestApplication, - bhsAddress, coordinatorV1Address, coordinatorV2Address, coordinatorV2PlusAddress string, + bhsAddress, coordinatorV2Address, coordinatorV2PlusAddress string, trustedBlockhashStoreAddress string, trustedBlockhashStoreBatchSize int32, lookback int, heartbeatPeriod time.Duration, waitBlocks int, ) job.Job { @@ -57,7 +39,6 @@ func CreateAndStartBHSJob( s := testspecs.GenerateBlockhashStoreSpec(testspecs.BlockhashStoreSpecParams{ JobID: jid.String(), Name: "blockhash-store", - CoordinatorV1Address: coordinatorV1Address, CoordinatorV2Address: coordinatorV2Address, CoordinatorV2PlusAddress: coordinatorV2PlusAddress, WaitBlocks: waitBlocks, @@ -93,13 +74,12 @@ func CreateAndStartBlockHeaderFeederJob( t *testing.T, fromAddresses []string, app *cltest.TestApplication, - bhsAddress, batchBHSAddress, coordinatorV1Address, coordinatorV2Address, coordinatorV2PlusAddress string, + bhsAddress, batchBHSAddress, coordinatorV2Address, coordinatorV2PlusAddress string, ) job.Job { jid := uuid.New() s := testspecs.GenerateBlockHeaderFeederSpec(testspecs.BlockHeaderFeederSpecParams{ JobID: jid.String(), Name: "block-header-feeder", - CoordinatorV1Address: coordinatorV1Address, CoordinatorV2Address: coordinatorV2Address, CoordinatorV2PlusAddress: coordinatorV2PlusAddress, WaitBlocks: 256, @@ -130,131 +110,3 @@ func CreateAndStartBlockHeaderFeederJob( return jb } - -// CoordinatorUniverse represents the universe in which a randomness request occurs and -// is fulfilled. -type CoordinatorUniverse struct { - // Golang wrappers ofr solidity contracts - RootContract *solidity_vrf_coordinator_interface.VRFCoordinator - LinkContract *link_token_interface.LinkToken - BHSContract *blockhash_store.BlockhashStore - ConsumerContract *solidity_vrf_consumer_interface.VRFConsumer - RequestIDBase *solidity_vrf_request_id.VRFRequestIDBaseTestHelper - ConsumerContractV08 *solidity_vrf_consumer_interface_v08.VRFConsumer - RequestIDBaseV08 *solidity_vrf_request_id_v08.VRFRequestIDBaseTestHelper - RootContractAddress common.Address - ConsumerContractAddress common.Address - ConsumerContractAddressV08 common.Address - LinkContractAddress common.Address - BHSContractAddress common.Address - - // Abstraction representation of the ethereum blockchain - Backend evmtypes.Backend - CoordinatorABI *abi.ABI - ConsumerABI *abi.ABI - // Cast of participants - Sergey *bind.TransactOpts // Owns all the LINK initially - Neil *bind.TransactOpts // Node operator running VRF service - Ned *bind.TransactOpts // Secondary node operator - Carol *bind.TransactOpts // Author of consuming contract which requests randomness -} - -var oneEth = big.NewInt(1000000000000000000) // 1e18 wei - -func NewVRFCoordinatorUniverseWithV08Consumer(t *testing.T, key ethkey.KeyV2) CoordinatorUniverse { - cu := NewVRFCoordinatorUniverse(t, key) - consumerContractAddress, _, consumerContract, err := - solidity_vrf_consumer_interface_v08.DeployVRFConsumer( - cu.Carol, cu.Backend.Client(), cu.RootContractAddress, cu.LinkContractAddress) - require.NoError(t, err, "failed to deploy v08 VRFConsumer contract to simulated ethereum blockchain") - _, _, requestIDBase, err := - solidity_vrf_request_id_v08.DeployVRFRequestIDBaseTestHelper(cu.Neil, cu.Backend.Client()) - require.NoError(t, err, "failed to deploy v08 VRFRequestIDBaseTestHelper contract to simulated ethereum blockchain") - cu.ConsumerContractAddressV08 = consumerContractAddress - cu.RequestIDBaseV08 = requestIDBase - cu.ConsumerContractV08 = consumerContract - _, err = cu.LinkContract.Transfer(cu.Sergey, consumerContractAddress, oneEth) // Actually, LINK - require.NoError(t, err, "failed to send LINK to VRFConsumer contract on simulated ethereum blockchain") - cu.Backend.Commit() - return cu -} - -// newVRFCoordinatorUniverse sets up all identities and contracts associated with -// testing the solidity VRF contracts involved in randomness request workflow -func NewVRFCoordinatorUniverse(t *testing.T, keys ...ethkey.KeyV2) CoordinatorUniverse { - var oracleTransactors []*bind.TransactOpts - for _, key := range keys { - oracleTransactor := &bind.TransactOpts{ - From: key.Address, - Signer: key.SignerFn(testutils.SimulatedChainID), - } - oracleTransactors = append(oracleTransactors, oracleTransactor) - } - - var ( - sergey = evmtestutils.MustNewSimTransactor(t) - neil = evmtestutils.MustNewSimTransactor(t) - ned = evmtestutils.MustNewSimTransactor(t) - carol = evmtestutils.MustNewSimTransactor(t) - ) - genesisData := gethtypes.GenesisAlloc{ - sergey.From: {Balance: assets.Ether(1000).ToInt()}, - neil.From: {Balance: assets.Ether(1000).ToInt()}, - ned.From: {Balance: assets.Ether(1000).ToInt()}, - carol.From: {Balance: assets.Ether(1000).ToInt()}, - } - - for _, t := range oracleTransactors { - genesisData[t.From] = gethtypes.Account{Balance: assets.Ether(1000).ToInt()} - } - - consumerABI, err := abi.JSON(strings.NewReader( - solidity_vrf_consumer_interface.VRFConsumerABI)) - require.NoError(t, err) - coordinatorABI, err := abi.JSON(strings.NewReader( - solidity_vrf_coordinator_interface.VRFCoordinatorABI)) - require.NoError(t, err) - backend := cltest.NewSimulatedBackend(t, genesisData, ethconfig.Defaults.Miner.GasCeil) - linkAddress, _, linkContract, err := link_token_interface.DeployLinkToken( - sergey, backend.Client()) - require.NoError(t, err, "failed to deploy link contract to simulated ethereum blockchain") - backend.Commit() - bhsAddress, _, bhsContract, err := blockhash_store.DeployBlockhashStore(neil, backend.Client()) - require.NoError(t, err, "failed to deploy BlockhashStore contract to simulated ethereum blockchain") - backend.Commit() - coordinatorAddress, _, coordinatorContract, err := - solidity_vrf_coordinator_interface.DeployVRFCoordinator( - neil, backend.Client(), linkAddress, bhsAddress) - require.NoError(t, err, "failed to deploy VRFCoordinator contract to simulated ethereum blockchain") - backend.Commit() - consumerContractAddress, _, consumerContract, err := - solidity_vrf_consumer_interface.DeployVRFConsumer( - carol, backend.Client(), coordinatorAddress, linkAddress) - require.NoError(t, err, "failed to deploy VRFConsumer contract to simulated ethereum blockchain") - backend.Commit() - _, _, requestIDBase, err := - solidity_vrf_request_id.DeployVRFRequestIDBaseTestHelper(neil, backend.Client()) - require.NoError(t, err, "failed to deploy VRFRequestIDBaseTestHelper contract to simulated ethereum blockchain") - backend.Commit() - _, err = linkContract.Transfer(sergey, consumerContractAddress, oneEth) // Actually, LINK - require.NoError(t, err, "failed to send LINK to VRFConsumer contract on simulated ethereum blockchain") - backend.Commit() - return CoordinatorUniverse{ - RootContract: coordinatorContract, - RootContractAddress: coordinatorAddress, - LinkContract: linkContract, - LinkContractAddress: linkAddress, - BHSContract: bhsContract, - BHSContractAddress: bhsAddress, - ConsumerContract: consumerContract, - RequestIDBase: requestIDBase, - ConsumerContractAddress: consumerContractAddress, - Backend: backend, - CoordinatorABI: &coordinatorABI, - ConsumerABI: &consumerABI, - Sergey: sergey, - Neil: neil, - Ned: ned, - Carol: carol, - } -} diff --git a/core/testdata/testspecs/v2_specs.go b/core/testdata/testspecs/v2_specs.go index f68aa30bf3e..23af690ffa2 100644 --- a/core/testdata/testspecs/v2_specs.go +++ b/core/testdata/testspecs/v2_specs.go @@ -313,7 +313,11 @@ type VRFSpecParams struct { BackoffInitialDelay time.Duration BackoffMaxDelay time.Duration GasLanePrice *assets.Wei - PollPeriod time.Duration + // OmitGasLanePrice omits the gasLanePrice field from the generated TOML, resulting in a + // nil GasLanePrice on the parsed spec. Useful for testing code paths that only run when + // a gas lane price is explicitly configured. + OmitGasLanePrice bool + PollPeriod time.Duration } type VRFSpec struct { @@ -378,54 +382,7 @@ func GenerateVRFSpec(params VRFSpecParams) VRFSpec { if params.ChunkSize != 0 { chunkSize = params.ChunkSize } - observationSource := fmt.Sprintf(` -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="%s" - data="$(encode_tx)" - minConfirmations="0" - from="$(jobSpec.from)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}" - transmitChecker="{\\"CheckerType\\": \\"vrf_v1\\", \\"VRFCoordinatorAddress\\": \\"%s\\"}"] -decode_log->vrf->encode_tx->submit_tx -`, coordinatorAddress, coordinatorAddress) - if params.V2 { - observationSource = fmt.Sprintf(` -decode_log [type=ethabidecodelog - abi="RandomWordsRequested(bytes32 indexed keyHash,uint256 requestId,uint256 preSeed,uint64 indexed subId,uint16 minimumRequestConfirmations,uint32 callbackGasLimit,uint32 numWords,address indexed sender)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrfv2 - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -estimate_gas [type=estimategaslimit - to="%s" - multiplier="1.1" - data="$(vrf.output)" -] -simulate [type=ethcall - to="%s" - gas="$(estimate_gas)" - gasPrice="$(jobSpec.maxGasPrice)" - extractRevertReason=true - contract="%s" - data="$(vrf.output)" -] -decode_log->vrf->estimate_gas->simulate -`, coordinatorAddress, coordinatorAddress, coordinatorAddress) - } + var observationSource string if vrfVersion == vrfcommon.V2Plus { observationSource = fmt.Sprintf(` decode_log [type=ethabidecodelog @@ -453,6 +410,32 @@ simulate_fulfillment [type=ethcall block="latest" ] decode_log->generate_proof->estimate_gas->simulate_fulfillment +`, coordinatorAddress, coordinatorAddress, coordinatorAddress) + } else { + observationSource = fmt.Sprintf(` +decode_log [type=ethabidecodelog + abi="RandomWordsRequested(bytes32 indexed keyHash,uint256 requestId,uint256 preSeed,uint64 indexed subId,uint16 minimumRequestConfirmations,uint32 callbackGasLimit,uint32 numWords,address indexed sender)" + data="$(jobRun.logData)" + topics="$(jobRun.logTopics)"] +vrf [type=vrfv2 + publicKey="$(jobSpec.publicKey)" + requestBlockHash="$(jobRun.logBlockHash)" + requestBlockNumber="$(jobRun.logBlockNumber)" + topics="$(jobRun.logTopics)"] +estimate_gas [type=estimategaslimit + to="%s" + multiplier="1.1" + data="$(vrf.output)" +] +simulate [type=ethcall + to="%s" + gas="$(estimate_gas)" + gasPrice="$(jobSpec.maxGasPrice)" + extractRevertReason=true + contract="%s" + data="$(vrf.output)" +] +decode_log->vrf->estimate_gas->simulate `, coordinatorAddress, coordinatorAddress, coordinatorAddress) } if params.ObservationSource != "" { @@ -479,8 +462,11 @@ publicKey = "%s" chunkSize = %d backoffInitialDelay = "%s" backoffMaxDelay = "%s" -gasLanePrice = "%s" -pollPeriod = "%s" +` + if !params.OmitGasLanePrice { + template += `gasLanePrice = "` + gasLanePrice.String() + `"` + "\n" + } + template += `pollPeriod = "%s" observationSource = """ %s """ @@ -490,15 +476,17 @@ observationSource = """ params.BatchFulfillmentEnabled, strconv.FormatFloat(batchFulfillmentGasMultiplier, 'f', 2, 64), params.CustomRevertsPipelineEnabled, confirmations, params.RequestedConfsDelay, requestTimeout.String(), publicKey, chunkSize, - params.BackoffInitialDelay.String(), params.BackoffMaxDelay.String(), gasLanePrice.String(), + params.BackoffInitialDelay.String(), params.BackoffMaxDelay.String(), pollPeriod.String(), observationSource) - if len(params.FromAddresses) != 0 { - var addresses []string - for _, address := range params.FromAddresses { - addresses = append(addresses, fmt.Sprintf("%q", address)) - } - toml = toml + "\n" + fmt.Sprintf(`fromAddresses = [%s]`, strings.Join(addresses, ", ")) + fromAddrs := params.FromAddresses + if len(fromAddrs) == 0 { + fromAddrs = []string{"0x1111111111111111111111111111111111111111"} + } + var addresses []string + for _, address := range fromAddrs { + addresses = append(addresses, fmt.Sprintf("%q", address)) } + toml = toml + "\n" + fmt.Sprintf(`fromAddresses = [%s]`, strings.Join(addresses, ", ")) if vrfVersion == vrfcommon.V2 { toml = toml + "\n" + fmt.Sprintf(`vrfOwnerAddress = "%s"`, vrfOwnerAddress) } @@ -686,7 +674,6 @@ ds -> ds_parse -> ds_multiply; type BlockhashStoreSpecParams struct { JobID string Name string - CoordinatorV1Address string CoordinatorV2Address string CoordinatorV2PlusAddress string WaitBlocks int @@ -768,9 +755,6 @@ func GenerateBlockhashStoreSpec(params BlockhashStoreSpecParams) BlockhashStoreS `schemaVersion = 1`, fmt.Sprintf(`name = "%s"`, params.Name), ) - if params.CoordinatorV1Address != "" { - lines = append(lines, fmt.Sprintf(`coordinatorV1Address = "%s"`, params.CoordinatorV1Address)) - } if params.CoordinatorV2Address != "" { lines = append(lines, fmt.Sprintf(`coordinatorV2Address = "%s"`, params.CoordinatorV2Address)) } @@ -799,7 +783,6 @@ func GenerateBlockhashStoreSpec(params BlockhashStoreSpecParams) BlockhashStoreS type BlockHeaderFeederSpecParams struct { JobID string Name string - CoordinatorV1Address string CoordinatorV2Address string CoordinatorV2PlusAddress string WaitBlocks int @@ -885,9 +868,6 @@ func GenerateBlockHeaderFeederSpec(params BlockHeaderFeederSpecParams) BlockHead `schemaVersion = 1`, fmt.Sprintf(`name = "%s"`, params.Name), ) - if params.CoordinatorV1Address != "" { - lines = append(lines, fmt.Sprintf(`coordinatorV1Address = "%s"`, params.CoordinatorV1Address)) - } if params.CoordinatorV2Address != "" { lines = append(lines, fmt.Sprintf(`coordinatorV2Address = "%s"`, params.CoordinatorV2Address)) } diff --git a/core/web/jobs_controller_test.go b/core/web/jobs_controller_test.go index c1192b1bfd2..355589b2cd3 100644 --- a/core/web/jobs_controller_test.go +++ b/core/web/jobs_controller_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" "github.com/pelletier/go-toml" @@ -777,7 +778,14 @@ func setupJobsControllerTests(t *testing.T) (ta *cltest.TestApplication, cc clte func setupEthClientForControllerTests(t *testing.T) *clienttest.Client { ec := cltest.NewEthMocksWithStartupAssertions(t) ec.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(100), nil).Maybe() - ec.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Once().Return(big.NewInt(0), nil).Maybe() + ec.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(0), nil).Maybe() + // Return a valid ABI-encoded zero address for calls to the VRF coordinator so that + // the LINKETHFEED lookup succeeds and the VRF v2 service can start. All other + // contract calls (e.g. FluxMonitor) still receive nil and will trigger ErrNoCode. + vrfCoordAddr := common.HexToAddress("0xABA5eDc1a551E55b1A570c0e1f1055e5BE11eca7") + ec.On("CallContract", mock.Anything, mock.MatchedBy(func(msg ethereum.CallMsg) bool { + return msg.To != nil && *msg.To == vrfCoordAddr + }), mock.Anything).Return(make([]byte, 32), nil).Maybe() ec.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Maybe() return ec } diff --git a/core/web/presenters/job_test.go b/core/web/presenters/job_test.go index 2d02582c3a2..36fa7d8e0d3 100644 --- a/core/web/presenters/job_test.go +++ b/core/web/presenters/job_test.go @@ -42,9 +42,6 @@ func TestJob(t *testing.T) { require.NoError(t, err) // Used in blockhashstore test - v1CoordAddress, err := types.NewEIP55Address("0x16988483b46e695f6c8D58e6e1461DC703e008e1") - require.NoError(t, err) - v2CoordAddress, err := types.NewEIP55Address("0x2C409DD6D4eBDdA190B5174Cc19616DD13884262") require.NoError(t, err) @@ -532,7 +529,6 @@ func TestJob(t *testing.T) { ID: 1, BlockhashStoreSpec: &job.BlockhashStoreSpec{ ID: 1, - CoordinatorV1Address: &v1CoordAddress, CoordinatorV2Address: &v2CoordAddress, CoordinatorV2PlusAddress: &v2PlusCoordAddress, WaitBlocks: 123, @@ -577,7 +573,7 @@ func TestJob(t *testing.T) { "webhookSpec": null, "workflowSpec": null, "blockhashStoreSpec": { - "coordinatorV1Address": "0x16988483b46e695f6c8D58e6e1461DC703e008e1", + "coordinatorV1Address": null, "coordinatorV2Address": "0x2C409DD6D4eBDdA190B5174Cc19616DD13884262", "coordinatorV2PlusAddress": "0x92B5e28Ac583812874e4271380c7d070C5FB6E6b", "waitBlocks": 123, @@ -617,7 +613,6 @@ func TestJob(t *testing.T) { ID: 1, BlockHeaderFeederSpec: &job.BlockHeaderFeederSpec{ ID: 1, - CoordinatorV1Address: &v1CoordAddress, CoordinatorV2Address: &v2CoordAddress, CoordinatorV2PlusAddress: &v2PlusCoordAddress, WaitBlocks: 123, @@ -663,7 +658,7 @@ func TestJob(t *testing.T) { "workflowSpec": null, "blockhashStoreSpec": null, "blockHeaderFeederSpec": { - "coordinatorV1Address": "0x16988483b46e695f6c8D58e6e1461DC703e008e1", + "coordinatorV1Address": null, "coordinatorV2Address": "0x2C409DD6D4eBDdA190B5174Cc19616DD13884262", "coordinatorV2PlusAddress": "0x92B5e28Ac583812874e4271380c7d070C5FB6E6b", "waitBlocks": 123, diff --git a/core/web/resolver/spec.go b/core/web/resolver/spec.go index ad84e88a1ab..661ce36a9cb 100644 --- a/core/web/resolver/spec.go +++ b/core/web/resolver/spec.go @@ -725,7 +725,7 @@ type BlockhashStoreSpecResolver struct { spec job.BlockhashStoreSpec } -// CoordinatorV1Address returns the address of the V1 Coordinator, if any. +// CoordinatorV1Address returns the legacy V1 coordinator address from persisted jobs, if any. func (b *BlockhashStoreSpecResolver) CoordinatorV1Address() *string { if b.spec.CoordinatorV1Address == nil { return nil @@ -824,7 +824,7 @@ type BlockHeaderFeederSpecResolver struct { spec job.BlockHeaderFeederSpec } -// CoordinatorV1Address returns the address of the V1 Coordinator, if any. +// CoordinatorV1Address returns the legacy V1 coordinator address from persisted jobs, if any. func (b *BlockHeaderFeederSpecResolver) CoordinatorV1Address() *string { if b.spec.CoordinatorV1Address == nil { return nil diff --git a/core/web/resolver/spec_test.go b/core/web/resolver/spec_test.go index cc85daf95a6..8c0d7ba08f6 100644 --- a/core/web/resolver/spec_test.go +++ b/core/web/resolver/spec_test.go @@ -677,8 +677,6 @@ func TestResolver_BlockhashStoreSpec(t *testing.T) { var ( id = int32(1) ) - coordinatorV1Address, err := evmtypes.NewEIP55Address("0x613a38AC1659769640aaE063C651F48E0250454C") - require.NoError(t, err) coordinatorV2Address, err := evmtypes.NewEIP55Address("0x2fcA960AF066cAc46085588a66dA2D614c7Cd337") require.NoError(t, err) @@ -708,7 +706,6 @@ func TestResolver_BlockhashStoreSpec(t *testing.T) { f.Mocks.jobORM.On("FindJobWithoutSpecErrors", mock.Anything, id).Return(job.Job{ Type: job.BlockhashStore, BlockhashStoreSpec: &job.BlockhashStoreSpec{ - CoordinatorV1Address: &coordinatorV1Address, CoordinatorV2Address: &coordinatorV2Address, CoordinatorV2PlusAddress: &coordinatorV2PlusAddress, CreatedAt: f.Timestamp(), @@ -757,7 +754,7 @@ func TestResolver_BlockhashStoreSpec(t *testing.T) { "job": { "spec": { "__typename": "BlockhashStoreSpec", - "coordinatorV1Address": "0x613a38AC1659769640aaE063C651F48E0250454C", + "coordinatorV1Address": null, "coordinatorV2Address": "0x2fcA960AF066cAc46085588a66dA2D614c7Cd337", "coordinatorV2PlusAddress": "0x92B5e28Ac583812874e4271380c7d070C5FB6E6b", "createdAt": "2021-01-01T00:00:00Z", @@ -785,8 +782,6 @@ func TestResolver_BlockHeaderFeederSpec(t *testing.T) { var ( id = int32(1) ) - coordinatorV1Address, err := evmtypes.NewEIP55Address("0x613a38AC1659769640aaE063C651F48E0250454C") - require.NoError(t, err) coordinatorV2Address, err := evmtypes.NewEIP55Address("0x2fcA960AF066cAc46085588a66dA2D614c7Cd337") require.NoError(t, err) @@ -812,7 +807,6 @@ func TestResolver_BlockHeaderFeederSpec(t *testing.T) { f.Mocks.jobORM.On("FindJobWithoutSpecErrors", mock.Anything, id).Return(job.Job{ Type: job.BlockHeaderFeeder, BlockHeaderFeederSpec: &job.BlockHeaderFeederSpec{ - CoordinatorV1Address: &coordinatorV1Address, CoordinatorV2Address: &coordinatorV2Address, CoordinatorV2PlusAddress: &coordinatorV2PlusAddress, CreatedAt: f.Timestamp(), @@ -861,7 +855,7 @@ func TestResolver_BlockHeaderFeederSpec(t *testing.T) { "job": { "spec": { "__typename": "BlockHeaderFeederSpec", - "coordinatorV1Address": "0x613a38AC1659769640aaE063C651F48E0250454C", + "coordinatorV1Address": null, "coordinatorV2Address": "0x2fcA960AF066cAc46085588a66dA2D614c7Cd337", "coordinatorV2PlusAddress": "0x92B5e28Ac583812874e4271380c7d070C5FB6E6b", "createdAt": "2021-01-01T00:00:00Z", diff --git a/deployment/environment/nodeclient/chainlink_models.go b/deployment/environment/nodeclient/chainlink_models.go index 9ee979c4efb..5830c67e98e 100644 --- a/deployment/environment/nodeclient/chainlink_models.go +++ b/deployment/environment/nodeclient/chainlink_models.go @@ -779,38 +779,6 @@ decode_log->vrf->estimate_gas->simulate` return MarshallTemplate(d, "VRFV2 pipeline template", sourceString) } -// VRFTxPipelineSpec VRF request with tx callback -type VRFTxPipelineSpec struct { - Address string -} - -// Type returns the type of the pipeline -func (d *VRFTxPipelineSpec) Type() string { - return "vrf_pipeline" -} - -// String representation of the pipeline -func (d *VRFTxPipelineSpec) String() (string, error) { - sourceString := ` -decode_log [type=ethabidecodelog - abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" - data="$(jobRun.logData)" - topics="$(jobRun.logTopics)"] -vrf [type=vrf - publicKey="$(jobSpec.publicKey)" - requestBlockHash="$(jobRun.logBlockHash)" - requestBlockNumber="$(jobRun.logBlockNumber)" - topics="$(jobRun.logTopics)"] -encode_tx [type=ethabiencode - abi="fulfillRandomnessRequest(bytes proof)" - data="{\\"proof\\": $(vrf)}"] -submit_tx [type=ethtx to="{{.Address}}" - data="$(encode_tx)" - txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}"] -decode_log->vrf->encode_tx->submit_tx` - return MarshallTemplate(d, "VRF pipeline template", sourceString) -} - // DirectRequestTxPipelineSpec oracle request with tx callback type DirectRequestTxPipelineSpec struct { BridgeTypeAttributes BridgeTypeAttributes diff --git a/devenv/MIGRATION_GUIDE.md b/devenv/MIGRATION_GUIDE.md index 6e8496426a7..0fe26c40744 100644 --- a/devenv/MIGRATION_GUIDE.md +++ b/devenv/MIGRATION_GUIDE.md @@ -44,7 +44,6 @@ Create `devenv/products//configuration.go`. Reference files: - [products/directrequest/configuration.go](products/directrequest/configuration.go) -- single-node product with contracts, bridge, and job -- [products/vrf/configuration.go](products/vrf/configuration.go) -- single-node product with multiple contracts and local helper functions - [products/flux/configuration.go](products/flux/configuration.go) -- multi-node product ### Struct definitions @@ -76,7 +75,6 @@ This is where the real migration work happens. Port the setup logic from the old When old tests use functions from `integration-tests/actions`: - Read the source of the helper function - Reimplement it locally as an unexported function in your `configuration.go` -- Example: VRF migration reimplemented `EncodeOnChainVRFProvingKey` and `EncodeOnChainExternalJobID` as local helpers (see [products/vrf/configuration.go](products/vrf/configuration.go)) ### Contract wrappers @@ -99,7 +97,7 @@ Create `devenv/tests//smoke_test.go`. Reference files: - [tests/cron/smoke_test.go](tests/cron/smoke_test.go) -- simplest test (no contracts, just job run polling) -- [tests/vrf/smoke_test.go](tests/vrf/smoke_test.go) -- test with contract interaction, key hash decoding, and job replacement +- [tests/vrfv2/smoke_test.go](tests/vrfv2/smoke_test.go) -- test with contract interaction and async polling Every test must: @@ -153,9 +151,9 @@ Delete `integration-tests/smoke/_test.go`. If this was the last test in ## Common Pitfalls 1. **Forbidden imports** -- The most common failure. Grep for `chainlink/v2`, `integration-tests`, and `deployment` before committing. The `depguard` linter in `devenv/.golangci.yml` enforces this. -2. **TOML key mismatch** -- The `toml` struct tag on `Configurator.Config` must exactly match the TOML section name (e.g. `toml:"vrf"` matches `[[vrf]]`). A mismatch means the config silently loads as empty. +2. **TOML key mismatch** -- The `toml` struct tag on `Configurator.Config` must exactly match the TOML section name (e.g. `toml:"cron"` matches `[[cron]]`). A mismatch means the config silently loads as empty. 3. **Missing `toml` tags on `Out` fields** -- Every field in the `Out` struct needs a `toml` tag or it won't be persisted/loaded from `env-out.toml`. 4. **`WaitMinedFast` vs `bind.WaitDeployed`** -- Use `bind.WaitDeployed` for contract deployment transactions (returns the deployed address). Use `products.WaitMinedFast` for all other transactions (state changes, transfers, registrations). 5. **Test imports product package** -- The test in `tests//` imports the product package from `products//` only for the `Configurator` type. All contract interaction in tests uses gethwrappers directly. 6. **Output file path** -- Tests run from `devenv/tests//`, so the output file is `../../env-out.toml` (two levels up to `devenv/`). -7. **Package name** -- The test file's `package` declaration should match the directory name (e.g. `package vrf` in `tests/vrf/`). +7. **Package name** -- The test file's `package` declaration should match the directory name (e.g. `package cron` in `tests/cron/`). diff --git a/devenv/README.md b/devenv/README.md index 8ab9c3b3974..fc760653117 100644 --- a/devenv/README.md +++ b/devenv/README.md @@ -64,7 +64,6 @@ Each row maps to a CI matrix entry in [devenv-nightly.yml](../.github/workflows/ | Cron | `cl u env.toml,products/cron/basic.toml` | `go test -v -run TestSmoke` | `cron` | | Direct Request | `cl u env.toml,products/directrequest/basic.toml` | `go test -v -run TestSmoke` | `directrequest` | | Flux | `cl u env.toml,products/flux/basic.toml` | `go test -v -run TestSmoke` | `flux` | -| VRF | `cl u env.toml,products/vrf/basic.toml` | `go test -v -timeout 10m -run TestVRFBasic\|TestVRFJobReplacement` | `vrf` | | Automation 2.0 | `cl u env.toml,products/automation/basic.toml` | `go test -v -timeout 30m -run TestRegistry_2_0` | `automation` | | Automation 2.1 | `cl u env.toml,products/automation/basic.toml` | `go test -v -timeout 30m -run TestRegistry_2_1` | `automation` | | OCR2 Smoke | `cl u env.toml,products/ocr2/basic.toml` | `go test -v -run TestSmoke` | `ocr2` | @@ -82,7 +81,7 @@ Then inside the shell: ```sh up # start with default OCR2 config -up env.toml,products/vrf/basic.toml # start with a specific product +up env.toml,products/cron/basic.toml # start with a specific product obs up -f # start full observability stack test ocr2 TestSmoke # run a test down # tear down everything diff --git a/devenv/design.md b/devenv/design.md index 6611961d7e1..477aed6c8a4 100644 --- a/devenv/design.md +++ b/devenv/design.md @@ -166,7 +166,6 @@ sequenceDiagram | Flux Monitor | `flux` | `products/flux/` | 5 | LINK, FluxAggregator | | OCR2 | `ocr2` | `products/ocr2/` | 5 | LINK, OCR2Aggregator | | Automation | `automation` | `products/automation/` | 5 | LINK, Registry (2.0-2.3), Registrar, Upkeeps | -| VRF | `vrf` | `products/vrf/` | 1 | LINK, BlockHashStore, VRFCoordinator, VRFConsumer | ### Adding a New Product @@ -246,12 +245,12 @@ sequenceDiagram participant Test as go test (Terminal 2) participant EnvOut as env-out.toml - Dev->>CLI: cl u env.toml,products/vrf/basic.toml + Dev->>CLI: cl u env.toml,products/cron/basic.toml CLI->>Docker: Start Anvil, Fake Server, CL Nodes CLI->>Docker: Deploy contracts, create jobs CLI->>EnvOut: Write deployed state - Dev->>Test: go test -v -run TestVRFBasic + Dev->>Test: go test -v -run TestSmoke Test->>EnvOut: Load config + product output Test->>Docker: Interact with contracts (gethwrappers) Test->>Docker: Query CL node API (clclient) @@ -287,7 +286,8 @@ cls, err := clclient.New(in.NodeSets[0].Out.CLNodes) 4. **Interact with contracts** -- use gethwrappers directly (never through `chainlink/v2` wrappers) ```go -consumer, err := solidity_vrf_consumer_interface.NewVRFConsumer(addr, c) +// e.g. bind to a deployed contract address from env-out / product output +// consumer, err := mypackage.NewMyConsumer(addr, c) ``` 5. **Assert with polling** -- use `require.EventuallyWithT` to poll until expected state diff --git a/devenv/environment.go b/devenv/environment.go index 0436a090e63..83a3b3b96e4 100644 --- a/devenv/environment.go +++ b/devenv/environment.go @@ -18,7 +18,6 @@ import ( "github.com/smartcontractkit/chainlink/devenv/products/directrequest" "github.com/smartcontractkit/chainlink/devenv/products/flux" "github.com/smartcontractkit/chainlink/devenv/products/ocr2" - "github.com/smartcontractkit/chainlink/devenv/products/vrf" "github.com/smartcontractkit/chainlink/devenv/products/vrfv2" "github.com/smartcontractkit/chainlink/devenv/products/vrfv2plus" ) @@ -48,8 +47,6 @@ func newProduct(name string) (Product, error) { return ocr2.NewConfigurator(), nil case "automation": return automation.NewConfigurator(), nil - case "vrf": - return vrf.NewConfigurator(), nil case "vrfv2_plus": return vrfv2plus.NewConfigurator(), nil case "vrfv2": diff --git a/devenv/products/vrf/basic.toml b/devenv/products/vrf/basic.toml deleted file mode 100644 index dfeb343ed97..00000000000 --- a/devenv/products/vrf/basic.toml +++ /dev/null @@ -1,20 +0,0 @@ -[[products]] -name = "vrf" -instances = 1 - -[[nodesets]] - name = "don" - nodes = 1 - override_mode = "each" - - [nodesets.db] - image = "postgres:15.0" - - [[nodesets.node_specs]] - [nodesets.node_specs.node] - image = "public.ecr.aws/chainlink/chainlink:2.30.0" - -[[vrf]] - [vrf.gas_settings] - fee_cap_multiplier = 2 - tip_cap_multiplier = 2 diff --git a/devenv/products/vrf/configuration.go b/devenv/products/vrf/configuration.go deleted file mode 100644 index f1c96d458de..00000000000 --- a/devenv/products/vrf/configuration.go +++ /dev/null @@ -1,290 +0,0 @@ -package vrf - -import ( - "context" - "encoding/hex" - "errors" - "fmt" - "math/big" - "os" - "strings" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/google/uuid" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/blockhash_store" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_consumer_interface" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_coordinator_interface" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_wrapper" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/link_token" - "github.com/smartcontractkit/chainlink-testing-framework/framework/clclient" - "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" - "github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake" - nodeset "github.com/smartcontractkit/chainlink-testing-framework/framework/components/simple_node_set" - - "github.com/smartcontractkit/chainlink/devenv/products" -) - -var L = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.DebugLevel).With().Fields(map[string]any{"component": "vrf"}).Logger() - -type Configurator struct { - Config []*VRF `toml:"vrf"` -} - -type VRF struct { - GasSettings *products.GasSettings `toml:"gas_settings"` - Out *Out `toml:"out"` -} - -type Out struct { - ConsumerAddress string `toml:"consumer_address"` - CoordinatorAddress string `toml:"coordinator_address"` - KeyHash string `toml:"key_hash"` - JobID string `toml:"job_id"` - PublicKeyCompressed string `toml:"public_key_compressed"` - ExternalJobID string `toml:"external_job_id"` - ChainID string `toml:"chain_id"` -} - -func NewConfigurator() *Configurator { - return &Configurator{} -} - -func (m *Configurator) Load() error { - cfg, err := products.Load[Configurator]() - if err != nil { - return fmt.Errorf("failed to load product config: %w", err) - } - m.Config = cfg.Config - return nil -} - -func (m *Configurator) Store(path string, instanceIdx int) error { - if err := products.Store(".", m); err != nil { - return fmt.Errorf("failed to store product config: %w", err) - } - return nil -} - -func (m *Configurator) GenerateNodesConfig( - ctx context.Context, - fs *fake.Input, - bc []*blockchain.Input, - ns []*nodeset.Input, -) (string, error) { - return products.DefaultLegacyCLNodeConfig(bc) -} - -func (m *Configurator) GenerateNodesSecrets( - _ context.Context, - _ *fake.Input, - _ []*blockchain.Input, - _ []*nodeset.Input, -) (string, error) { - return "", nil -} - -func (m *Configurator) ConfigureJobsAndContracts( - ctx context.Context, - instanceIdx int, - fs *fake.Input, - bc []*blockchain.Input, - ns []*nodeset.Input, -) error { - L.Info().Msg("Connecting to CL nodes") - cls, err := clclient.New(ns[0].Out.CLNodes) - if err != nil { - return fmt.Errorf("failed to connect to CL nodes: %w", err) - } - - c, auth, rootAddr, err := products.ETHClient(ctx, bc[0].Out.Nodes[0].ExternalWSUrl, m.Config[0].GasSettings.FeeCapMultiplier, m.Config[0].GasSettings.TipCapMultiplier) - if err != nil { - return fmt.Errorf("failed to connect to blockchain: %w", err) - } - - // Deploy Link Token - linkAddr, linkTx, lt, err := link_token.DeployLinkToken(auth, c) - if err != nil { - return fmt.Errorf("could not deploy link token contract: %w", err) - } - _, err = bind.WaitDeployed(ctx, c, linkTx) - if err != nil { - return err - } - L.Info().Str("Address", linkAddr.Hex()).Msg("Deployed link token contract") - - tx, err := lt.GrantMintRole(auth, common.HexToAddress(rootAddr)) - if err != nil { - return fmt.Errorf("could not grant mint role: %w", err) - } - _, err = products.WaitMinedFast(ctx, c, tx.Hash()) - if err != nil { - return err - } - - // Deploy BlockHashStore - bhsAddr, bhsTx, _, err := blockhash_store.DeployBlockhashStore(auth, c) - if err != nil { - return fmt.Errorf("could not deploy blockhash store: %w", err) - } - _, err = bind.WaitDeployed(ctx, c, bhsTx) - if err != nil { - return err - } - L.Info().Str("Address", bhsAddr.Hex()).Msg("Deployed blockhash store") - - // Deploy VRF Coordinator - coordAddr, coordTx, coordinator, err := solidity_vrf_coordinator_interface.DeployVRFCoordinator(auth, c, linkAddr, bhsAddr) - if err != nil { - return fmt.Errorf("could not deploy VRF coordinator: %w", err) - } - _, err = bind.WaitDeployed(ctx, c, coordTx) - if err != nil { - return err - } - L.Info().Str("Address", coordAddr.Hex()).Msg("Deployed VRF coordinator") - - // Deploy VRF Consumer - consumerAddr, consumerTx, _, err := solidity_vrf_consumer_interface.DeployVRFConsumer(auth, c, coordAddr, linkAddr) - if err != nil { - return fmt.Errorf("could not deploy VRF consumer: %w", err) - } - _, err = bind.WaitDeployed(ctx, c, consumerTx) - if err != nil { - return err - } - L.Info().Str("Address", consumerAddr.Hex()).Msg("Deployed VRF consumer") - - // Deploy VRF v1 library - _, vrfLibTx, _, err := solidity_vrf_wrapper.DeployVRF(auth, c) - if err != nil { - return fmt.Errorf("could not deploy VRF library: %w", err) - } - _, err = bind.WaitDeployed(ctx, c, vrfLibTx) - if err != nil { - return err - } - L.Info().Msg("Deployed VRF v1 library") - - // Mint LINK to consumer - L.Info().Msgf("Minting LINK for consumer address: %s", consumerAddr) - tx, err = lt.Mint(auth, consumerAddr, big.NewInt(2e18)) - if err != nil { - return fmt.Errorf("could not mint link to consumer: %w", err) - } - _, err = products.WaitMinedFast(ctx, c, tx.Hash()) - if err != nil { - return err - } - - // Fund CL node transmitter - transmitters := make([]common.Address, 0) - for _, nc := range cls { - addr, cErr := nc.ReadPrimaryETHKey(bc[0].Out.ChainID) - if cErr != nil { - return cErr - } - transmitters = append(transmitters, common.HexToAddress(addr.Attributes.Address)) - } - pkey := products.NetworkPrivateKey() - if pkey == "" { - return errors.New("PRIVATE_KEY environment variable not set") - } - for _, addr := range transmitters { - if cErr := products.FundAddressEIP1559(ctx, c, pkey, addr.String(), 10); cErr != nil { - return cErr - } - } - - // Create VRF key on node - vrfKey, err := cls[0].MustCreateVRFKey() - if err != nil { - return fmt.Errorf("could not create VRF key: %w", err) - } - L.Info().Interface("Key", vrfKey).Msg("Created VRF proving key") - pubKeyCompressed := vrfKey.Data.ID - - // Create VRF job - jobUUID := uuid.New() - pipelineSpec := &clclient.VRFTxPipelineSpec{ - Address: coordAddr.String(), - } - observationSource, err := pipelineSpec.String() - if err != nil { - return fmt.Errorf("could not build VRF pipeline spec: %w", err) - } - - job, err := cls[0].MustCreateJob(&clclient.VRFJobSpec{ - Name: fmt.Sprintf("vrf-%s", jobUUID), - CoordinatorAddress: coordAddr.String(), - MinIncomingConfirmations: 1, - PublicKey: pubKeyCompressed, - ExternalJobID: jobUUID.String(), - EVMChainID: bc[0].ChainID, - ObservationSource: observationSource, - }) - if err != nil { - return fmt.Errorf("could not create VRF job: %w", err) - } - L.Info().Str("JobID", job.Data.ID).Msg("Created VRF job") - - // Register proving key on coordinator - oracleAddr := transmitters[0] - provingKey, err := encodeOnChainVRFProvingKey(vrfKey) - if err != nil { - return fmt.Errorf("could not encode VRF proving key: %w", err) - } - jobIDBytes := encodeOnChainExternalJobID(jobUUID) - - tx, err = coordinator.RegisterProvingKey(auth, big.NewInt(1), oracleAddr, provingKey, jobIDBytes) - if err != nil { - return fmt.Errorf("could not register proving key: %w", err) - } - _, err = products.WaitMinedFast(ctx, c, tx.Hash()) - if err != nil { - return err - } - L.Info().Msg("Registered VRF proving key on coordinator") - - // Compute key hash - keyHash, err := coordinator.HashOfKey(&bind.CallOpts{Context: ctx}, provingKey) - if err != nil { - return fmt.Errorf("could not compute key hash: %w", err) - } - L.Info().Str("KeyHash", hex.EncodeToString(keyHash[:])).Msg("Computed key hash") - - m.Config[0].Out = &Out{ - ConsumerAddress: consumerAddr.String(), - CoordinatorAddress: coordAddr.String(), - KeyHash: hex.EncodeToString(keyHash[:]), - JobID: job.Data.ID, - PublicKeyCompressed: pubKeyCompressed, - ExternalJobID: jobUUID.String(), - ChainID: bc[0].ChainID, - } - return nil -} - -func encodeOnChainVRFProvingKey(vrfKey *clclient.VRFKey) ([2]*big.Int, error) { - uncompressed := vrfKey.Data.Attributes.Uncompressed - provingKey := [2]*big.Int{} - var ok bool - provingKey[0], ok = new(big.Int).SetString(uncompressed[2:66], 16) - if !ok { - return [2]*big.Int{}, errors.New("cannot convert first half of VRF key to *big.Int") - } - provingKey[1], ok = new(big.Int).SetString(uncompressed[66:], 16) - if !ok { - return [2]*big.Int{}, errors.New("cannot convert second half of VRF key to *big.Int") - } - return provingKey, nil -} - -func encodeOnChainExternalJobID(jobID uuid.UUID) [32]byte { - var ji [32]byte - copy(ji[:], strings.Replace(jobID.String(), "-", "", 4)) - return ji -} diff --git a/devenv/tests/vrf/smoke_test.go b/devenv/tests/vrf/smoke_test.go deleted file mode 100644 index acedae6fb2a..00000000000 --- a/devenv/tests/vrf/smoke_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package vrf - -import ( - "encoding/hex" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/solidity_vrf_consumer_interface" - "github.com/smartcontractkit/chainlink-testing-framework/framework/clclient" - de "github.com/smartcontractkit/chainlink/devenv" - "github.com/smartcontractkit/chainlink/devenv/products" - "github.com/smartcontractkit/chainlink/devenv/products/vrf" -) - -func TestVRFBasic(t *testing.T) { - ctx := t.Context() - outputFile := "../../env-out.toml" - in, err := de.LoadOutput[de.Cfg](outputFile) - require.NoError(t, err) - productCfg, err := products.LoadOutput[vrf.Configurator](outputFile) - require.NoError(t, err) - t.Cleanup(func() { - cleanupErr := products.CleanupContainerLogs(products.DefaultSettings()) - require.NoError(t, cleanupErr, "failed to process cleanup container logs") - }) - - c, auth, _, err := products.ETHClient( - ctx, - in.Blockchains[0].Out.Nodes[0].ExternalWSUrl, - productCfg.Config[0].GasSettings.FeeCapMultiplier, - productCfg.Config[0].GasSettings.TipCapMultiplier, - ) - require.NoError(t, err) - - consumer, err := solidity_vrf_consumer_interface.NewVRFConsumer( - common.HexToAddress(productCfg.Config[0].Out.ConsumerAddress), c, - ) - require.NoError(t, err) - - keyHash := decodeKeyHash(t, productCfg.Config[0].Out.KeyHash) - - tx, err := consumer.TestRequestRandomness(auth, keyHash, big.NewInt(1)) - require.NoError(t, err) - _, err = products.WaitMinedFast(ctx, c, tx.Hash()) - require.NoError(t, err) - - cls, err := clclient.New(in.NodeSets[0].Out.CLNodes) - require.NoError(t, err) - - require.EventuallyWithT(t, func(ct *assert.CollectT) { - jobRuns, err := cls[0].MustReadRunsByJob(productCfg.Config[0].Out.JobID) - assert.NoError(ct, err) - assert.NotEmpty(ct, jobRuns.Data, "Expected the VRF job to have run at least once") - - out, err := consumer.RandomnessOutput(&bind.CallOpts{Context: ctx}) - assert.NoError(ct, err) - assert.NotZero(ct, out.Uint64(), "Expected the VRF job to produce a non-zero randomness output") - }, 2*time.Minute, 2*time.Second) -} - -func TestVRFJobReplacement(t *testing.T) { - ctx := t.Context() - outputFile := "../../env-out.toml" - in, err := de.LoadOutput[de.Cfg](outputFile) - require.NoError(t, err) - productCfg, err := products.LoadOutput[vrf.Configurator](outputFile) - require.NoError(t, err) - t.Cleanup(func() { - cleanupErr := products.CleanupContainerLogs(products.DefaultSettings()) - require.NoError(t, cleanupErr, "failed to process cleanup container logs") - }) - - c, auth, _, err := products.ETHClient( - ctx, - in.Blockchains[0].Out.Nodes[0].ExternalWSUrl, - productCfg.Config[0].GasSettings.FeeCapMultiplier, - productCfg.Config[0].GasSettings.TipCapMultiplier, - ) - require.NoError(t, err) - - consumer, err := solidity_vrf_consumer_interface.NewVRFConsumer( - common.HexToAddress(productCfg.Config[0].Out.ConsumerAddress), c, - ) - require.NoError(t, err) - - keyHash := decodeKeyHash(t, productCfg.Config[0].Out.KeyHash) - - cls, err := clclient.New(in.NodeSets[0].Out.CLNodes) - require.NoError(t, err) - - // First randomness request - tx, err := consumer.TestRequestRandomness(auth, keyHash, big.NewInt(1)) - require.NoError(t, err) - _, err = products.WaitMinedFast(ctx, c, tx.Hash()) - require.NoError(t, err) - - jobID := productCfg.Config[0].Out.JobID - require.EventuallyWithT(t, func(ct *assert.CollectT) { - jobRuns, err := cls[0].MustReadRunsByJob(jobID) - assert.NoError(ct, err) - assert.NotEmpty(ct, jobRuns.Data, "Expected the VRF job to have run at least once") - - out, err := consumer.RandomnessOutput(&bind.CallOpts{Context: ctx}) - assert.NoError(ct, err) - assert.NotZero(ct, out.Uint64(), "Expected the VRF job to produce a non-zero randomness output") - }, 2*time.Minute, 2*time.Second) - - // Delete the job and recreate it - err = cls[0].MustDeleteJob(jobID) - require.NoError(t, err) - - cfg := productCfg.Config[0].Out - pipelineSpec := &clclient.VRFTxPipelineSpec{ - Address: cfg.CoordinatorAddress, - } - observationSource, err := pipelineSpec.String() - require.NoError(t, err) - - newJob, err := cls[0].MustCreateJob(&clclient.VRFJobSpec{ - Name: "vrf-" + cfg.ExternalJobID, - CoordinatorAddress: cfg.CoordinatorAddress, - MinIncomingConfirmations: 1, - PublicKey: cfg.PublicKeyCompressed, - ExternalJobID: cfg.ExternalJobID, - EVMChainID: cfg.ChainID, - ObservationSource: observationSource, - }) - require.NoError(t, err) - - require.EventuallyWithT(t, func(ct *assert.CollectT) { - jobRuns, err := cls[0].MustReadRunsByJob(newJob.Data.ID) - assert.NoError(ct, err) - assert.NotEmpty(ct, jobRuns.Data, "Expected the recreated VRF job to have run at least once") - - out, err := consumer.RandomnessOutput(&bind.CallOpts{Context: ctx}) - assert.NoError(ct, err) - assert.NotZero(ct, out.Uint64(), "Expected the VRF job to produce a non-zero randomness output") - }, 2*time.Minute, 2*time.Second) -} - -func decodeKeyHash(t *testing.T, keyHashHex string) [32]byte { - t.Helper() - b, err := hex.DecodeString(keyHashHex) - require.NoError(t, err, "Failed to decode key hash hex") - var kh [32]byte - copy(kh[:], b) - return kh -} diff --git a/docs/core/DATA_FLOW.md b/docs/core/DATA_FLOW.md index a2a1a4df1da..3e9de861c8f 100644 --- a/docs/core/DATA_FLOW.md +++ b/docs/core/DATA_FLOW.md @@ -162,13 +162,6 @@ flowchart TB end end subgraph vrf [vrf] - subgraph listenerV1 [listenerV1] - direction TB - vrfv1-reqLogs[[reqLogs]] - vrfv1-HandleLog(["HandleLog()"]) -- "Deliver()" --> vrfv1-reqLogs - vrfv1-reqLogs -- "Notify()" --> vrfv1-runLogListener(["runLogListener()"]) - vrfv1-runLogListener -- "Retrieve()" --> vrfv1-reqLogs - end subgraph listenerV2 [listenerV2] direction TB vrfv2-reqLogs[[reqLogs]] @@ -189,7 +182,6 @@ flowchart TB Listener --> FunctionsListener Listener --> RegistrySynchronizer Listener --> OCRContractTracker - Listener --> listenerV1 Listener --> listenerV2