From cefb1c6bb77c775e5faa9c5046e7d88aae47eeb2 Mon Sep 17 00:00:00 2001 From: Sri Kidambi <1702865+kidambisrinivas@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:34:17 +0000 Subject: [PATCH] Test VRF LogPoller functionality- initializeLastProcessedBlock, updateLastProcessedBlock and getUnfulfilled (#11472) * Test VRF LogPoller functionality- initializeLastProcessedBlock, updateLastProcessedBlock and getUnfulfilled * Addressed PR comments * Minor change * Add comments to explain replay * Addressed PR comments * Prettier --- .../scripts/native_solc_compile_all_logpoller | 3 +- contracts/src/v0.8/tests/VRFLogEmitter.sol | 42 + .../vrf_log_emitter/vrf_log_emitter.go | 526 +++++++++ ...rapper-dependency-versions-do-not-edit.txt | 1 + core/gethwrappers/go_generate_logpoller.go | 7 + .../vrf/v2/listener_v2_log_listener_test.go | 1039 +++++++++++++++++ 6 files changed, 1617 insertions(+), 1 deletion(-) create mode 100644 contracts/src/v0.8/tests/VRFLogEmitter.sol create mode 100644 core/gethwrappers/generated/vrf_log_emitter/vrf_log_emitter.go create mode 100644 core/gethwrappers/go_generate_logpoller.go create mode 100644 core/services/vrf/v2/listener_v2_log_listener_test.go diff --git a/contracts/scripts/native_solc_compile_all_logpoller b/contracts/scripts/native_solc_compile_all_logpoller index b6ac51ecedb..e8ea2a2be80 100755 --- a/contracts/scripts/native_solc_compile_all_logpoller +++ b/contracts/scripts/native_solc_compile_all_logpoller @@ -29,4 +29,5 @@ compileContract () { } -compileContract tests/LogEmitter.sol \ No newline at end of file +compileContract tests/LogEmitter.sol +compileContract tests/VRFLogEmitter.sol \ No newline at end of file diff --git a/contracts/src/v0.8/tests/VRFLogEmitter.sol b/contracts/src/v0.8/tests/VRFLogEmitter.sol new file mode 100644 index 00000000000..18b99605ac9 --- /dev/null +++ b/contracts/src/v0.8/tests/VRFLogEmitter.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract VRFLogEmitter { + event RandomWordsRequested( + bytes32 indexed keyHash, + uint256 requestId, + uint256 preSeed, + uint64 indexed subId, + uint16 minimumRequestConfirmations, + uint32 callbackGasLimit, + uint32 numWords, + address indexed sender + ); + event RandomWordsFulfilled(uint256 indexed requestId, uint256 outputSeed, uint96 payment, bool success); + + function emitRandomWordsRequested( + bytes32 keyHash, + uint256 requestId, + uint256 preSeed, + uint64 subId, + uint16 minimumRequestConfirmations, + uint32 callbackGasLimit, + uint32 numWords, + address sender + ) public { + emit RandomWordsRequested( + keyHash, + requestId, + preSeed, + subId, + minimumRequestConfirmations, + callbackGasLimit, + numWords, + sender + ); + } + + function emitRandomWordsFulfilled(uint256 requestId, uint256 outputSeed, uint96 payment, bool success) public { + emit RandomWordsFulfilled(requestId, outputSeed, payment, success); + } +} diff --git a/core/gethwrappers/generated/vrf_log_emitter/vrf_log_emitter.go b/core/gethwrappers/generated/vrf_log_emitter/vrf_log_emitter.go new file mode 100644 index 00000000000..2cdeaa6c3a8 --- /dev/null +++ b/core/gethwrappers/generated/vrf_log_emitter/vrf_log_emitter.go @@ -0,0 +1,526 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package vrf_log_emitter + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "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/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +var VRFLogEmitterMetaData = &bind.MetaData{ + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"outputSeed\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"payment\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"RandomWordsFulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"preSeed\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RandomWordsRequested\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"outputSeed\",\"type\":\"uint256\"},{\"internalType\":\"uint96\",\"name\":\"payment\",\"type\":\"uint96\"},{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"emitRandomWordsFulfilled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preSeed\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"emitRandomWordsRequested\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5061027f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063ca920adb1461003b578063fe62d3e914610050575b600080fd5b61004e61004936600461015b565b610063565b005b61004e61005e366004610212565b6100eb565b604080518881526020810188905261ffff86168183015263ffffffff858116606083015284166080820152905173ffffffffffffffffffffffffffffffffffffffff83169167ffffffffffffffff8816918b917f63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772919081900360a00190a45050505050505050565b604080518481526bffffffffffffffffffffffff8416602082015282151581830152905185917f7dffc5ae5ee4e2e4df1651cf6ad329a73cebdb728f37ea0187b9b17e036756e4919081900360600190a250505050565b803563ffffffff8116811461015657600080fd5b919050565b600080600080600080600080610100898b03121561017857600080fd5b883597506020890135965060408901359550606089013567ffffffffffffffff811681146101a557600080fd5b9450608089013561ffff811681146101bc57600080fd5b93506101ca60a08a01610142565b92506101d860c08a01610142565b915060e089013573ffffffffffffffffffffffffffffffffffffffff8116811461020157600080fd5b809150509295985092959890939650565b6000806000806080858703121561022857600080fd5b843593506020850135925060408501356bffffffffffffffffffffffff8116811461025257600080fd5b91506060850135801515811461026757600080fd5b93969295509093505056fea164736f6c6343000813000a", +} + +var VRFLogEmitterABI = VRFLogEmitterMetaData.ABI + +var VRFLogEmitterBin = VRFLogEmitterMetaData.Bin + +func DeployVRFLogEmitter(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *VRFLogEmitter, error) { + parsed, err := VRFLogEmitterMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(VRFLogEmitterBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &VRFLogEmitter{address: address, abi: *parsed, VRFLogEmitterCaller: VRFLogEmitterCaller{contract: contract}, VRFLogEmitterTransactor: VRFLogEmitterTransactor{contract: contract}, VRFLogEmitterFilterer: VRFLogEmitterFilterer{contract: contract}}, nil +} + +type VRFLogEmitter struct { + address common.Address + abi abi.ABI + VRFLogEmitterCaller + VRFLogEmitterTransactor + VRFLogEmitterFilterer +} + +type VRFLogEmitterCaller struct { + contract *bind.BoundContract +} + +type VRFLogEmitterTransactor struct { + contract *bind.BoundContract +} + +type VRFLogEmitterFilterer struct { + contract *bind.BoundContract +} + +type VRFLogEmitterSession struct { + Contract *VRFLogEmitter + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type VRFLogEmitterCallerSession struct { + Contract *VRFLogEmitterCaller + CallOpts bind.CallOpts +} + +type VRFLogEmitterTransactorSession struct { + Contract *VRFLogEmitterTransactor + TransactOpts bind.TransactOpts +} + +type VRFLogEmitterRaw struct { + Contract *VRFLogEmitter +} + +type VRFLogEmitterCallerRaw struct { + Contract *VRFLogEmitterCaller +} + +type VRFLogEmitterTransactorRaw struct { + Contract *VRFLogEmitterTransactor +} + +func NewVRFLogEmitter(address common.Address, backend bind.ContractBackend) (*VRFLogEmitter, error) { + abi, err := abi.JSON(strings.NewReader(VRFLogEmitterABI)) + if err != nil { + return nil, err + } + contract, err := bindVRFLogEmitter(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &VRFLogEmitter{address: address, abi: abi, VRFLogEmitterCaller: VRFLogEmitterCaller{contract: contract}, VRFLogEmitterTransactor: VRFLogEmitterTransactor{contract: contract}, VRFLogEmitterFilterer: VRFLogEmitterFilterer{contract: contract}}, nil +} + +func NewVRFLogEmitterCaller(address common.Address, caller bind.ContractCaller) (*VRFLogEmitterCaller, error) { + contract, err := bindVRFLogEmitter(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &VRFLogEmitterCaller{contract: contract}, nil +} + +func NewVRFLogEmitterTransactor(address common.Address, transactor bind.ContractTransactor) (*VRFLogEmitterTransactor, error) { + contract, err := bindVRFLogEmitter(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &VRFLogEmitterTransactor{contract: contract}, nil +} + +func NewVRFLogEmitterFilterer(address common.Address, filterer bind.ContractFilterer) (*VRFLogEmitterFilterer, error) { + contract, err := bindVRFLogEmitter(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &VRFLogEmitterFilterer{contract: contract}, nil +} + +func bindVRFLogEmitter(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := VRFLogEmitterMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_VRFLogEmitter *VRFLogEmitterRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFLogEmitter.Contract.VRFLogEmitterCaller.contract.Call(opts, result, method, params...) +} + +func (_VRFLogEmitter *VRFLogEmitterRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.VRFLogEmitterTransactor.contract.Transfer(opts) +} + +func (_VRFLogEmitter *VRFLogEmitterRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.VRFLogEmitterTransactor.contract.Transact(opts, method, params...) +} + +func (_VRFLogEmitter *VRFLogEmitterCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFLogEmitter.Contract.contract.Call(opts, result, method, params...) +} + +func (_VRFLogEmitter *VRFLogEmitterTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.contract.Transfer(opts) +} + +func (_VRFLogEmitter *VRFLogEmitterTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.contract.Transact(opts, method, params...) +} + +func (_VRFLogEmitter *VRFLogEmitterTransactor) EmitRandomWordsFulfilled(opts *bind.TransactOpts, requestId *big.Int, outputSeed *big.Int, payment *big.Int, success bool) (*types.Transaction, error) { + return _VRFLogEmitter.contract.Transact(opts, "emitRandomWordsFulfilled", requestId, outputSeed, payment, success) +} + +func (_VRFLogEmitter *VRFLogEmitterSession) EmitRandomWordsFulfilled(requestId *big.Int, outputSeed *big.Int, payment *big.Int, success bool) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.EmitRandomWordsFulfilled(&_VRFLogEmitter.TransactOpts, requestId, outputSeed, payment, success) +} + +func (_VRFLogEmitter *VRFLogEmitterTransactorSession) EmitRandomWordsFulfilled(requestId *big.Int, outputSeed *big.Int, payment *big.Int, success bool) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.EmitRandomWordsFulfilled(&_VRFLogEmitter.TransactOpts, requestId, outputSeed, payment, success) +} + +func (_VRFLogEmitter *VRFLogEmitterTransactor) EmitRandomWordsRequested(opts *bind.TransactOpts, keyHash [32]byte, requestId *big.Int, preSeed *big.Int, subId uint64, minimumRequestConfirmations uint16, callbackGasLimit uint32, numWords uint32, sender common.Address) (*types.Transaction, error) { + return _VRFLogEmitter.contract.Transact(opts, "emitRandomWordsRequested", keyHash, requestId, preSeed, subId, minimumRequestConfirmations, callbackGasLimit, numWords, sender) +} + +func (_VRFLogEmitter *VRFLogEmitterSession) EmitRandomWordsRequested(keyHash [32]byte, requestId *big.Int, preSeed *big.Int, subId uint64, minimumRequestConfirmations uint16, callbackGasLimit uint32, numWords uint32, sender common.Address) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.EmitRandomWordsRequested(&_VRFLogEmitter.TransactOpts, keyHash, requestId, preSeed, subId, minimumRequestConfirmations, callbackGasLimit, numWords, sender) +} + +func (_VRFLogEmitter *VRFLogEmitterTransactorSession) EmitRandomWordsRequested(keyHash [32]byte, requestId *big.Int, preSeed *big.Int, subId uint64, minimumRequestConfirmations uint16, callbackGasLimit uint32, numWords uint32, sender common.Address) (*types.Transaction, error) { + return _VRFLogEmitter.Contract.EmitRandomWordsRequested(&_VRFLogEmitter.TransactOpts, keyHash, requestId, preSeed, subId, minimumRequestConfirmations, callbackGasLimit, numWords, sender) +} + +type VRFLogEmitterRandomWordsFulfilledIterator struct { + Event *VRFLogEmitterRandomWordsFulfilled + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFLogEmitterRandomWordsFulfilledIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFLogEmitterRandomWordsFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(VRFLogEmitterRandomWordsFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *VRFLogEmitterRandomWordsFulfilledIterator) Error() error { + return it.fail +} + +func (it *VRFLogEmitterRandomWordsFulfilledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFLogEmitterRandomWordsFulfilled struct { + RequestId *big.Int + OutputSeed *big.Int + Payment *big.Int + Success bool + Raw types.Log +} + +func (_VRFLogEmitter *VRFLogEmitterFilterer) FilterRandomWordsFulfilled(opts *bind.FilterOpts, requestId []*big.Int) (*VRFLogEmitterRandomWordsFulfilledIterator, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _VRFLogEmitter.contract.FilterLogs(opts, "RandomWordsFulfilled", requestIdRule) + if err != nil { + return nil, err + } + return &VRFLogEmitterRandomWordsFulfilledIterator{contract: _VRFLogEmitter.contract, event: "RandomWordsFulfilled", logs: logs, sub: sub}, nil +} + +func (_VRFLogEmitter *VRFLogEmitterFilterer) WatchRandomWordsFulfilled(opts *bind.WatchOpts, sink chan<- *VRFLogEmitterRandomWordsFulfilled, requestId []*big.Int) (event.Subscription, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _VRFLogEmitter.contract.WatchLogs(opts, "RandomWordsFulfilled", requestIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFLogEmitterRandomWordsFulfilled) + if err := _VRFLogEmitter.contract.UnpackLog(event, "RandomWordsFulfilled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_VRFLogEmitter *VRFLogEmitterFilterer) ParseRandomWordsFulfilled(log types.Log) (*VRFLogEmitterRandomWordsFulfilled, error) { + event := new(VRFLogEmitterRandomWordsFulfilled) + if err := _VRFLogEmitter.contract.UnpackLog(event, "RandomWordsFulfilled", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFLogEmitterRandomWordsRequestedIterator struct { + Event *VRFLogEmitterRandomWordsRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFLogEmitterRandomWordsRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFLogEmitterRandomWordsRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(VRFLogEmitterRandomWordsRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *VRFLogEmitterRandomWordsRequestedIterator) Error() error { + return it.fail +} + +func (it *VRFLogEmitterRandomWordsRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFLogEmitterRandomWordsRequested struct { + KeyHash [32]byte + RequestId *big.Int + PreSeed *big.Int + SubId uint64 + MinimumRequestConfirmations uint16 + CallbackGasLimit uint32 + NumWords uint32 + Sender common.Address + Raw types.Log +} + +func (_VRFLogEmitter *VRFLogEmitterFilterer) FilterRandomWordsRequested(opts *bind.FilterOpts, keyHash [][32]byte, subId []uint64, sender []common.Address) (*VRFLogEmitterRandomWordsRequestedIterator, error) { + + var keyHashRule []interface{} + for _, keyHashItem := range keyHash { + keyHashRule = append(keyHashRule, keyHashItem) + } + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _VRFLogEmitter.contract.FilterLogs(opts, "RandomWordsRequested", keyHashRule, subIdRule, senderRule) + if err != nil { + return nil, err + } + return &VRFLogEmitterRandomWordsRequestedIterator{contract: _VRFLogEmitter.contract, event: "RandomWordsRequested", logs: logs, sub: sub}, nil +} + +func (_VRFLogEmitter *VRFLogEmitterFilterer) WatchRandomWordsRequested(opts *bind.WatchOpts, sink chan<- *VRFLogEmitterRandomWordsRequested, keyHash [][32]byte, subId []uint64, sender []common.Address) (event.Subscription, error) { + + var keyHashRule []interface{} + for _, keyHashItem := range keyHash { + keyHashRule = append(keyHashRule, keyHashItem) + } + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _VRFLogEmitter.contract.WatchLogs(opts, "RandomWordsRequested", keyHashRule, subIdRule, senderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFLogEmitterRandomWordsRequested) + if err := _VRFLogEmitter.contract.UnpackLog(event, "RandomWordsRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_VRFLogEmitter *VRFLogEmitterFilterer) ParseRandomWordsRequested(log types.Log) (*VRFLogEmitterRandomWordsRequested, error) { + event := new(VRFLogEmitterRandomWordsRequested) + if err := _VRFLogEmitter.contract.UnpackLog(event, "RandomWordsRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_VRFLogEmitter *VRFLogEmitter) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _VRFLogEmitter.abi.Events["RandomWordsFulfilled"].ID: + return _VRFLogEmitter.ParseRandomWordsFulfilled(log) + case _VRFLogEmitter.abi.Events["RandomWordsRequested"].ID: + return _VRFLogEmitter.ParseRandomWordsRequested(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (VRFLogEmitterRandomWordsFulfilled) Topic() common.Hash { + return common.HexToHash("0x7dffc5ae5ee4e2e4df1651cf6ad329a73cebdb728f37ea0187b9b17e036756e4") +} + +func (VRFLogEmitterRandomWordsRequested) Topic() common.Hash { + return common.HexToHash("0x63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772") +} + +func (_VRFLogEmitter *VRFLogEmitter) Address() common.Address { + return _VRFLogEmitter.address +} + +type VRFLogEmitterInterface interface { + EmitRandomWordsFulfilled(opts *bind.TransactOpts, requestId *big.Int, outputSeed *big.Int, payment *big.Int, success bool) (*types.Transaction, error) + + EmitRandomWordsRequested(opts *bind.TransactOpts, keyHash [32]byte, requestId *big.Int, preSeed *big.Int, subId uint64, minimumRequestConfirmations uint16, callbackGasLimit uint32, numWords uint32, sender common.Address) (*types.Transaction, error) + + FilterRandomWordsFulfilled(opts *bind.FilterOpts, requestId []*big.Int) (*VRFLogEmitterRandomWordsFulfilledIterator, error) + + WatchRandomWordsFulfilled(opts *bind.WatchOpts, sink chan<- *VRFLogEmitterRandomWordsFulfilled, requestId []*big.Int) (event.Subscription, error) + + ParseRandomWordsFulfilled(log types.Log) (*VRFLogEmitterRandomWordsFulfilled, error) + + FilterRandomWordsRequested(opts *bind.FilterOpts, keyHash [][32]byte, subId []uint64, sender []common.Address) (*VRFLogEmitterRandomWordsRequestedIterator, error) + + WatchRandomWordsRequested(opts *bind.WatchOpts, sink chan<- *VRFLogEmitterRandomWordsRequested, keyHash [][32]byte, subId []uint64, sender []common.Address) (event.Subscription, error) + + ParseRandomWordsRequested(log types.Log) (*VRFLogEmitterRandomWordsRequested, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 1db18d8366d..5e9dcfc3326 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -82,6 +82,7 @@ vrf_external_sub_owner_example: ../../contracts/solc/v0.8.6/VRFExternalSubOwnerE vrf_load_test_external_sub_owner: ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner/VRFLoadTestExternalSubOwner.abi ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner/VRFLoadTestExternalSubOwner.bin 2097faa70265e420036cc8a3efb1f1e0836ad2d7323b295b9a26a125dbbe6c7d vrf_load_test_ownerless_consumer: ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer/VRFLoadTestOwnerlessConsumer.abi ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer/VRFLoadTestOwnerlessConsumer.bin 74f914843cbc70b9c3079c3e1c709382ce415225e8bb40113e7ac018bfcb0f5c vrf_load_test_with_metrics: ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics/VRFV2LoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics/VRFV2LoadTestWithMetrics.bin 8ab9de5816fbdf93a2865e2711b85a39a6fc9c413a4b336578c485be1158d430 +vrf_log_emitter: ../../contracts/solc/v0.8.19/VRFLogEmitter/VRFLogEmitter.abi ../../contracts/solc/v0.8.19/VRFLogEmitter/VRFLogEmitter.bin 15f491d445ac4d0c712d1cbe4e5054c759b080bf20de7d54bfe2a82cde4dcf06 vrf_malicious_consumer_v2: ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2/VRFMaliciousConsumerV2.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2/VRFMaliciousConsumerV2.bin 9755fa8ffc7f5f0b337d5d413d77b0c9f6cd6f68c31727d49acdf9d4a51bc522 vrf_malicious_consumer_v2_plus: ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus/VRFMaliciousConsumerV2Plus.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus/VRFMaliciousConsumerV2Plus.bin e2a72638e11da807b6533d037e7e5aaeed695efd5035777b8e20d2f8973a574c vrf_owner: ../../contracts/solc/v0.8.6/VRFOwner/VRFOwner.abi ../../contracts/solc/v0.8.6/VRFOwner/VRFOwner.bin eccfae5ee295b5850e22f61240c469f79752b8d9a3bac5d64aec7ac8def2f6cb diff --git a/core/gethwrappers/go_generate_logpoller.go b/core/gethwrappers/go_generate_logpoller.go new file mode 100644 index 00000000000..b28b8205830 --- /dev/null +++ b/core/gethwrappers/go_generate_logpoller.go @@ -0,0 +1,7 @@ +// Package gethwrappers provides tools for wrapping solidity contracts with +// golang packages, using abigen. +package gethwrappers + +// Log tester +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.abi ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.bin LogEmitter log_emitter +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/VRFLogEmitter/VRFLogEmitter.abi ../../contracts/solc/v0.8.19/VRFLogEmitter/VRFLogEmitter.bin VRFLogEmitter vrf_log_emitter diff --git a/core/services/vrf/v2/listener_v2_log_listener_test.go b/core/services/vrf/v2/listener_v2_log_listener_test.go new file mode 100644 index 00000000000..11b299abc40 --- /dev/null +++ b/core/services/vrf/v2/listener_v2_log_listener_test.go @@ -0,0 +1,1039 @@ +package v2 + +import ( + "context" + "fmt" + "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/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + evmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_log_emitter" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" + "github.com/smartcontractkit/chainlink/v2/core/testdata/testspecs" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +var ( + emitterABI, _ = abi.JSON(strings.NewReader(log_emitter.LogEmitterABI)) + vrfEmitterABI, _ = abi.JSON(strings.NewReader(vrf_log_emitter.VRFLogEmitterABI)) +) + +type vrfLogPollerListenerTH struct { + Lggr logger.Logger + ChainID *big.Int + ORM *logpoller.DbORM + LogPoller logpoller.LogPollerTest + Client *backends.SimulatedBackend + Emitter *log_emitter.LogEmitter + EmitterAddress common.Address + VRFLogEmitter *vrf_log_emitter.VRFLogEmitter + VRFEmitterAddress common.Address + Owner *bind.TransactOpts + EthDB ethdb.Database + Db *sqlx.DB + Listener *listenerV2 + Ctx context.Context +} + +func setupVRFLogPollerListenerTH(t *testing.T, + useFinalityTag bool, + finalityDepth, backfillBatchSize, + rpcBatchSize, keepFinalizedBlocksDepth int64, + mockChainUpdateFn func(*evmmocks.Chain, *vrfLogPollerListenerTH)) *vrfLogPollerListenerTH { + + lggr := logger.TestLogger(t) + chainID := testutils.NewRandomEVMChainID() + db := pgtest.NewSqlxDB(t) + + o := logpoller.NewORM(chainID, db, lggr, pgtest.NewQConfig(true)) + owner := testutils.MustNewSimTransactor(t) + ethDB := rawdb.NewMemoryDatabase() + ec := backends.NewSimulatedBackendWithDatabase(ethDB, map[common.Address]core.GenesisAccount{ + owner.From: { + Balance: big.NewInt(0).Mul(big.NewInt(10), big.NewInt(1e18)), + }, + }, 10e6) + // VRF Listener relies on block timestamps, but SimulatedBackend uses by default clock starting from 1970-01-01 + // This trick is used to move the clock closer to the current time. We set first block to be X hours ago. + // FirstBlockAge is used to compute first block's timestamp in SimulatedBackend (time.Now() - FirstBlockAge) + const FirstBlockAge = 24 * time.Hour + blockTime := time.UnixMilli(int64(ec.Blockchain().CurrentHeader().Time)) + err := ec.AdjustTime(time.Since(blockTime) - FirstBlockAge) + require.NoError(t, err) + ec.Commit() + + esc := client.NewSimulatedBackendClient(t, ec, chainID) + // Mark genesis block as finalized to avoid any nulls in the tests + head := esc.Backend().Blockchain().CurrentHeader() + esc.Backend().Blockchain().SetFinalized(head) + + // Poll period doesn't matter, we intend to call poll and save logs directly in the test. + // Set it to some insanely high value to not interfere with any tests. + lp := logpoller.NewLogPoller(o, esc, lggr, 1*time.Hour, useFinalityTag, finalityDepth, backfillBatchSize, rpcBatchSize, keepFinalizedBlocksDepth) + + emitterAddress1, _, emitter1, err := log_emitter.DeployLogEmitter(owner, ec) + require.NoError(t, err) + vrfLogEmitterAddress, _, vrfLogEmitter, err := vrf_log_emitter.DeployVRFLogEmitter(owner, ec) + require.NoError(t, err) + ec.Commit() + + // Log Poller Listener + cfg := pgtest.NewQConfig(false) + ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, cfg) + require.NoError(t, ks.Unlock("blah")) + j, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ + RequestedConfsDelay: 10, + EVMChainID: chainID.String(), + }).Toml()) + require.NoError(t, err) + + coordinatorV2, err := vrf_coordinator_v2.NewVRFCoordinatorV2(vrfLogEmitter.Address(), ec) + require.Nil(t, err) + coordinator := NewCoordinatorV2(coordinatorV2) + + chain := evmmocks.NewChain(t) + listener := &listenerV2{ + respCount: map[string]uint64{}, + job: j, + chain: chain, + l: logger.Sugared(lggr), + coordinator: coordinator, + } + ctx := testutils.Context(t) + + // Filter registration is idempotent, so we can just call it every time + // and retry on errors using the ticker. + err = lp.RegisterFilter(logpoller.Filter{ + Name: fmt.Sprintf("vrf_%s_keyhash_%s_job_%d", "v2", listener.job.VRFSpec.PublicKey.MustHash().String(), listener.job.ID), + EventSigs: evmtypes.HashArray{ + vrf_log_emitter.VRFLogEmitterRandomWordsRequested{}.Topic(), + vrf_log_emitter.VRFLogEmitterRandomWordsFulfilled{}.Topic(), + }, + Addresses: evmtypes.AddressArray{ + vrfLogEmitter.Address(), + // listener.job.VRFSpec.CoordinatorAddress.Address(), + }, + }) + require.Nil(t, err) + require.NoError(t, lp.RegisterFilter(logpoller.Filter{ + Name: "Integration test", + EventSigs: []common.Hash{emitterABI.Events["Log1"].ID}, + Addresses: []common.Address{emitterAddress1}, + Retention: 0})) + require.Nil(t, err) + require.Len(t, lp.Filter(nil, nil, nil).Addresses, 2) + require.Len(t, lp.Filter(nil, nil, nil).Topics, 1) + require.Len(t, lp.Filter(nil, nil, nil).Topics[0], 3) + + th := &vrfLogPollerListenerTH{ + Lggr: lggr, + ChainID: chainID, + ORM: o, + LogPoller: lp, + Emitter: emitter1, + EmitterAddress: emitterAddress1, + VRFLogEmitter: vrfLogEmitter, + VRFEmitterAddress: vrfLogEmitterAddress, + Client: ec, + Owner: owner, + EthDB: ethDB, + Db: db, + Listener: listener, + Ctx: ctx, + } + mockChainUpdateFn(chain, th) + return th +} + +/* Tests for initializeLastProcessedBlock: BEGIN + * TestInitProcessedBlock_NoVRFReqs + * TestInitProcessedBlock_NoUnfulfilledVRFReqs + * TestInitProcessedBlock_OneUnfulfilledVRFReq + * TestInitProcessedBlock_SomeUnfulfilledVRFReqs + * TestInitProcessedBlock_UnfulfilledNFulfilledVRFReqs + */ + +func TestInitProcessedBlock_NoVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, th *vrfLogPollerListenerTH) { + mockChain.On("ID").Return(th.ChainID) + mockChain.On("LogPoller").Return(th.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Emit some logs from block 5 to 9 (Inclusive) + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + } + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 5 (EmitLog blocks) = 9 + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(testutils.Context(t))) + + // The poller starts on a new chain at latest-finality (finalityDepth + 5 in this case), + // Replaying from block 4 should guarantee we have block 4 immediately. (We will also get + // block 3 once the backup poller runs, since it always starts 100 blocks behind.) + require.NoError(t, th.LogPoller.Replay(testutils.Context(t), 4)) + + // Should return logs from block 5 to 7 (inclusive) + logs, err := th.LogPoller.Logs(4, 7, emitterABI.Events["Log1"].ID, th.EmitterAddress, + pg.WithParentCtx(testutils.Context(t))) + require.NoError(t, err) + require.Equal(t, 3, len(logs)) + + lastProcessedBlock, err := th.Listener.initializeLastProcessedBlock(th.Ctx) + require.Nil(t, err) + require.Equal(t, int64(6), lastProcessedBlock) +} + +func TestInitProcessedBlock_NoUnfulfilledVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("ID").Return(curTH.ChainID) + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Create VRF request block and a fulfillment block + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID := big.NewInt(1) + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + _, err2 = th.VRFLogEmitter.EmitRandomWordsFulfilled(th.Owner, reqID, preSeed, big.NewInt(10), true) + require.NoError(t, err2) + th.Client.Commit() + + // Emit some logs in blocks to make the VRF req and fulfillment older than finalityDepth from latestBlock + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + } + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 2 (VRF req/resp block) + 5 (EmitLog blocks) = 11 + latestBlock := int64(2 + 2 + 2 + 5) + + // A replay is needed so that log poller has a latest block + // Replay from block 11 (latest) onwards, so that log poller has a latest block + // Then test if log poller is able to replay from finalizedBlockNumber (8 --> onwards) + // since there are no pending VRF requests + // Blocks: 1 2 3 4 [5;Request] [6;Fulfilment] 7 8 9 10 11 + require.NoError(t, th.LogPoller.Replay(th.Ctx, latestBlock)) + + // initializeLastProcessedBlock must return the finalizedBlockNumber (8) instead of + // VRF request block number (5), since all VRF requests are fulfilled + lastProcessedBlock, err := th.Listener.initializeLastProcessedBlock(th.Ctx) + require.Nil(t, err) + require.Equal(t, int64(8), lastProcessedBlock) +} + +func TestInitProcessedBlock_OneUnfulfilledVRFReq(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("ID").Return(curTH.ChainID) + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Make a VRF request without fulfilling it + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID := big.NewInt(1) + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + // Emit some logs in blocks to make the VRF req and fulfillment older than finalityDepth from latestBlock + n := 5 + th.Client.Commit() + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + } + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 1 (VRF req block) + 5 (EmitLog blocks) = 10 + latestBlock := int64(2 + 2 + 1 + 5) + + // A replay is needed so that log poller has a latest block + // Replay from block 10 (latest) onwards, so that log poller has a latest block + // Then test if log poller is able to replay from earliestUnprocessedBlock (5 --> onwards) + // Blocks: 1 2 3 4 [5;Request] 6 7 8 9 10 + require.NoError(t, th.LogPoller.Replay(th.Ctx, latestBlock)) + + // initializeLastProcessedBlock must return the unfulfilled VRF + // request block number (5) instead of finalizedBlockNumber (8) + lastProcessedBlock, err := th.Listener.initializeLastProcessedBlock(th.Ctx) + require.Nil(t, err) + require.Equal(t, int64(5), lastProcessedBlock) +} + +func TestInitProcessedBlock_SomeUnfulfilledVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("ID").Return(curTH.ChainID) + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Emit some logs in blocks with VRF reqs interspersed + // No fulfillment for any VRF requests + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + + // Create 2 blocks with VRF requests in each iteration + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID1 := big.NewInt(int64(2 * i)) + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID1, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + reqID2 := big.NewInt(int64(2*i + 1)) + _, err2 = th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID2, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + } + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 3*5 (EmitLog + VRF req/resp blocks) = 19 + latestBlock := int64(2 + 2 + 3*5) + + // A replay is needed so that log poller has a latest block + // Replay from block 19 (latest) onwards, so that log poller has a latest block + // Then test if log poller is able to replay from earliestUnprocessedBlock (6 --> onwards) + // Blocks: 1 2 3 4 5 [6;Request] [7;Request] 8 [9;Request] [10;Request] + // 11 [12;Request] [13;Request] 14 [15;Request] [16;Request] + // 17 [18;Request] [19;Request] + require.NoError(t, th.LogPoller.Replay(th.Ctx, latestBlock)) + + // initializeLastProcessedBlock must return the earliest unfulfilled VRF request block + // number instead of finalizedBlockNumber + lastProcessedBlock, err := th.Listener.initializeLastProcessedBlock(th.Ctx) + require.Nil(t, err) + require.Equal(t, int64(6), lastProcessedBlock) +} + +func TestInitProcessedBlock_UnfulfilledNFulfilledVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("ID").Return(curTH.ChainID) + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Emit some logs in blocks with VRF reqs interspersed + // One VRF request in each iteration is fulfilled to imitate mixed workload + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + + // Create 2 blocks with VRF requests in each iteration and fulfill one + // of them. This creates a mixed workload of fulfilled and unfulfilled + // VRF requests for testing the VRF listener + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID1 := big.NewInt(int64(2 * i)) + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID1, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + reqID2 := big.NewInt(int64(2*i + 1)) + _, err2 = th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID2, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + + _, err2 = th.VRFLogEmitter.EmitRandomWordsFulfilled(th.Owner, reqID1, preSeed, big.NewInt(10), true) + require.NoError(t, err2) + th.Client.Commit() + } + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 3*5 (EmitLog + VRF req/resp blocks) = 19 + latestBlock := int64(2 + 2 + 3*5) + // A replay is needed so that log poller has a latest block + // Replay from block 19 (latest) onwards, so that log poller has a latest block + // Then test if log poller is able to replay from earliestUnprocessedBlock (7 --> onwards) + // Blocks: 1 2 3 4 5 [6;Request] [7;Request;6-Fulfilment] 8 [9;Request] [10;Request;9-Fulfilment] + // 11 [12;Request] [13;Request;12-Fulfilment] 14 [15;Request] [16;Request;15-Fulfilment] + // 17 [18;Request] [19;Request;18-Fulfilment] + require.NoError(t, th.LogPoller.Replay(th.Ctx, latestBlock)) + + // initializeLastProcessedBlock must return the earliest unfulfilled VRF request block + // number instead of finalizedBlockNumber + lastProcessedBlock, err := th.Listener.initializeLastProcessedBlock(th.Ctx) + require.Nil(t, err) + require.Equal(t, int64(7), lastProcessedBlock) +} + +/* Tests for initializeLastProcessedBlock: END */ + +/* Tests for updateLastProcessedBlock: BEGIN + * TestUpdateLastProcessedBlock_NoVRFReqs + * TestUpdateLastProcessedBlock_NoUnfulfilledVRFReqs + * TestUpdateLastProcessedBlock_OneUnfulfilledVRFReq + * TestUpdateLastProcessedBlock_SomeUnfulfilledVRFReqs + * TestUpdateLastProcessedBlock_UnfulfilledNFulfilledVRFReqs + */ + +func TestUpdateLastProcessedBlock_NoVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Create VRF request logs + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID1 := big.NewInt(int64(1)) + + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID1, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + reqID2 := big.NewInt(int64(2)) + _, err2 = th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID2, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + // Emit some logs in blocks to make the VRF req and fulfillment older than finalityDepth from latestBlock + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + } + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 2 (VRF req blocks) + 5 (EmitLog blocks) = 11 + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // We've to replay from before VRF request log, since updateLastProcessedBlock + // does not internally call LogPoller.Replay + require.NoError(t, th.LogPoller.Replay(th.Ctx, 4)) + + // updateLastProcessedBlock must return the finalizedBlockNumber as there are + // no VRF requests, after currLastProcessedBlock (block 6). The VRF requests + // made above are before the currLastProcessedBlock (7) passed in below + lastProcessedBlock, err := th.Listener.updateLastProcessedBlock(th.Ctx, 7) + require.Nil(t, err) + require.Equal(t, int64(8), lastProcessedBlock) +} + +func TestUpdateLastProcessedBlock_NoUnfulfilledVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Create VRF request log block with a fulfillment log block + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID1 := big.NewInt(int64(1)) + + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID1, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + _, err2 = th.VRFLogEmitter.EmitRandomWordsFulfilled(th.Owner, reqID1, preSeed, big.NewInt(10), true) + require.NoError(t, err2) + th.Client.Commit() + + // Emit some logs in blocks to make the VRF req and fulfillment older than finalityDepth from latestBlock + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + } + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 2 (VRF req/resp blocks) + 5 (EmitLog blocks) = 11 + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // We've to replay from before VRF request log, since updateLastProcessedBlock + // does not internally call LogPoller.Replay + require.NoError(t, th.LogPoller.Replay(th.Ctx, 4)) + + // updateLastProcessedBlock must return the finalizedBlockNumber (8) though we have + // a VRF req at block (5) after currLastProcessedBlock (4) passed below, because + // the VRF request is fulfilled + lastProcessedBlock, err := th.Listener.updateLastProcessedBlock(th.Ctx, 4) + require.Nil(t, err) + require.Equal(t, int64(8), lastProcessedBlock) +} + +func TestUpdateLastProcessedBlock_OneUnfulfilledVRFReq(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Create VRF request logs without a fulfillment log block + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID1 := big.NewInt(int64(1)) + + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID1, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + // Emit some logs in blocks to make the VRF req and fulfillment older than finalityDepth from latestBlock + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + } + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 1 (VRF req block) + 5 (EmitLog blocks) = 10 + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // We've to replay from before VRF request log, since updateLastProcessedBlock + // does not internally call LogPoller.Replay + require.NoError(t, th.LogPoller.Replay(th.Ctx, 4)) + + // updateLastProcessedBlock must return the VRF req at block (5) instead of + // finalizedBlockNumber (8) after currLastProcessedBlock (4) passed below, + // because the VRF request is unfulfilled + lastProcessedBlock, err := th.Listener.updateLastProcessedBlock(th.Ctx, 4) + require.Nil(t, err) + require.Equal(t, int64(5), lastProcessedBlock) +} + +func TestUpdateLastProcessedBlock_SomeUnfulfilledVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Emit some logs in blocks to make the VRF req and fulfillment older than finalityDepth from latestBlock + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + + // Create 2 blocks with VRF requests in each iteration + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID1 := big.NewInt(int64(2 * i)) + + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID1, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + reqID2 := big.NewInt(int64(2*i + 1)) + _, err2 = th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID2, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + } + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 3*5 (EmitLog + VRF req blocks) = 19 + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // We've to replay from before VRF request log, since updateLastProcessedBlock + // does not internally call LogPoller.Replay + require.NoError(t, th.LogPoller.Replay(th.Ctx, 4)) + + // updateLastProcessedBlock must return the VRF req at block (6) instead of + // finalizedBlockNumber (16) after currLastProcessedBlock (4) passed below, + // as block 6 contains the earliest unfulfilled VRF request + lastProcessedBlock, err := th.Listener.updateLastProcessedBlock(th.Ctx, 4) + require.Nil(t, err) + require.Equal(t, int64(6), lastProcessedBlock) +} + +func TestUpdateLastProcessedBlock_UnfulfilledNFulfilledVRFReqs(t *testing.T) { + t.Parallel() + + finalityDepth := int64(3) + th := setupVRFLogPollerListenerTH(t, false, finalityDepth, 3, 2, 1000, func(mockChain *evmmocks.Chain, curTH *vrfLogPollerListenerTH) { + mockChain.On("LogPoller").Return(curTH.LogPoller) + }) + + // Block 3 to finalityDepth. Ensure we have finality number of blocks + for i := 1; i < int(finalityDepth); i++ { + th.Client.Commit() + } + + // Emit some logs in blocks to make the VRF req and fulfillment older than finalityDepth from latestBlock + n := 5 + for i := 0; i < n; i++ { + _, err1 := th.Emitter.EmitLog1(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + _, err1 = th.Emitter.EmitLog2(th.Owner, []*big.Int{big.NewInt(int64(i))}) + require.NoError(t, err1) + th.Client.Commit() + + // Create 2 blocks with VRF requests in each iteration and fulfill one + // of them. This creates a mixed workload of fulfilled and unfulfilled + // VRF requests for testing the VRF listener + keyHash := [32]byte(th.Listener.job.VRFSpec.PublicKey.MustHash().Bytes()) + preSeed := big.NewInt(105) + subID := uint64(1) + reqID1 := big.NewInt(int64(2 * i)) + + _, err2 := th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID1, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + th.Client.Commit() + + reqID2 := big.NewInt(int64(2*i + 1)) + _, err2 = th.VRFLogEmitter.EmitRandomWordsRequested(th.Owner, + keyHash, reqID2, preSeed, subID, 10, 10000, 2, th.Owner.From) + require.NoError(t, err2) + _, err2 = th.VRFLogEmitter.EmitRandomWordsFulfilled(th.Owner, reqID1, preSeed, big.NewInt(10), true) + require.NoError(t, err2) + th.Client.Commit() + } + + // Blocks till now: 2 (in SetupTH) + 2 (empty blocks) + 3*5 (EmitLog + VRF req blocks) = 19 + + // Calling Start() after RegisterFilter() simulates a node restart after job creation, should reload Filter from db. + require.NoError(t, th.LogPoller.Start(th.Ctx)) + + // We've to replay from before VRF request log, since updateLastProcessedBlock + // does not internally call LogPoller.Replay + require.NoError(t, th.LogPoller.Replay(th.Ctx, 4)) + + // updateLastProcessedBlock must return the VRF req at block (7) instead of + // finalizedBlockNumber (16) after currLastProcessedBlock (4) passed below, + // as block 7 contains the earliest unfulfilled VRF request. VRF request + // in block 6 has been fulfilled in block 7. + lastProcessedBlock, err := th.Listener.updateLastProcessedBlock(th.Ctx, 4) + require.Nil(t, err) + require.Equal(t, int64(7), lastProcessedBlock) +} + +/* Tests for updateLastProcessedBlock: END */ + +/* Tests for getUnfulfilled: BEGIN + * TestGetUnfulfilled_NoVRFReqs + * TestGetUnfulfilled_NoUnfulfilledVRFReqs + * TestGetUnfulfilled_OneUnfulfilledVRFReq + * TestGetUnfulfilled_SomeUnfulfilledVRFReqs + * TestGetUnfulfilled_UnfulfilledNFulfilledVRFReqs + */ + +func SetupGetUnfulfilledTH(t *testing.T) (*listenerV2, *utils.Big) { + chainID := utils.NewBig(big.NewInt(12345)) + lggr := logger.TestLogger(t) + j, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ + RequestedConfsDelay: 10, + EVMChainID: chainID.String(), + }).Toml()) + require.NoError(t, err) + chain := evmmocks.NewChain(t) + + // Construct CoordinatorV2_X object for VRF listener + owner := testutils.MustNewSimTransactor(t) + ethDB := rawdb.NewMemoryDatabase() + ec := backends.NewSimulatedBackendWithDatabase(ethDB, map[common.Address]core.GenesisAccount{ + owner.From: { + Balance: big.NewInt(0).Mul(big.NewInt(10), big.NewInt(1e18)), + }, + }, 10e6) + _, _, vrfLogEmitter, err := vrf_log_emitter.DeployVRFLogEmitter(owner, ec) + require.NoError(t, err) + ec.Commit() + coordinatorV2, err := vrf_coordinator_v2.NewVRFCoordinatorV2(vrfLogEmitter.Address(), ec) + require.Nil(t, err) + coordinator := NewCoordinatorV2(coordinatorV2) + + listener := &listenerV2{ + respCount: map[string]uint64{}, + job: j, + chain: chain, + l: logger.Sugared(lggr), + coordinator: coordinator, + } + return listener, chainID +} + +func TestGetUnfulfilled_NoVRFReqs(t *testing.T) { + t.Parallel() + + listener, chainID := SetupGetUnfulfilledTH(t) + + logs := []logpoller.Log{} + for i := 0; i < 10; i++ { + logs = append(logs, logpoller.Log{ + EvmChainId: chainID, + LogIndex: 0, + BlockHash: common.BigToHash(big.NewInt(int64(i))), + BlockNumber: int64(i), + BlockTimestamp: time.Now(), + Topics: [][]byte{ + []byte("0x46692c0e59ca9cd1ad8f984a9d11715ec83424398b7eed4e05c8ce84662415a8"), + }, + EventSig: emitterABI.Events["Log1"].ID, + Address: common.Address{}, + TxHash: common.BigToHash(big.NewInt(int64(i))), + Data: nil, + CreatedAt: time.Now(), + }) + } + + unfulfilled, _, fulfilled := listener.getUnfulfilled(logs, listener.l) + require.Empty(t, unfulfilled) + require.Empty(t, fulfilled) +} + +func TestGetUnfulfilled_NoUnfulfilledVRFReqs(t *testing.T) { + t.Parallel() + + listener, chainID := SetupGetUnfulfilledTH(t) + + logs := []logpoller.Log{} + for i := 0; i < 10; i++ { + eventSig := emitterABI.Events["Log1"].ID + topics := [][]byte{ + common.FromHex("0x46692c0e59ca9cd1ad8f984a9d11715ec83424398b7eed4e05c8ce84662415a8"), + } + if i%2 == 0 { + eventSig = vrfEmitterABI.Events["RandomWordsRequested"].ID + topics = [][]byte{ + common.FromHex("0x63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772"), + common.FromHex("0xc0a6c424ac7157ae408398df7e5f4552091a69125d5dfcb7b8c2659029395bdf"), + common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000001"), + common.FromHex("0x0000000000000000000000005ee3b50502b5c4c9184dcb281471a0614d4b2ef9"), + } + } + logs = append(logs, logpoller.Log{ + EvmChainId: chainID, + LogIndex: 0, + BlockHash: common.BigToHash(big.NewInt(int64(2 * i))), + BlockNumber: int64(2 * i), + BlockTimestamp: time.Now(), + Topics: topics, + EventSig: eventSig, + Address: common.Address{}, + TxHash: common.BigToHash(big.NewInt(int64(2 * i))), + Data: common.FromHex("0x000000000000000000000000000000000000000000000000000000000000000" + fmt.Sprintf("%d", i) + "000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000002"), + CreatedAt: time.Now(), + }) + if i%2 == 0 { + logs = append(logs, logpoller.Log{ + EvmChainId: chainID, + LogIndex: 0, + BlockHash: common.BigToHash(big.NewInt(int64(2*i + 1))), + BlockNumber: int64(2*i + 1), + BlockTimestamp: time.Now(), + Topics: [][]byte{ + common.FromHex("0x7dffc5ae5ee4e2e4df1651cf6ad329a73cebdb728f37ea0187b9b17e036756e4"), + common.FromHex("0x000000000000000000000000000000000000000000000000000000000000000" + fmt.Sprintf("%d", i)), + }, + EventSig: vrfEmitterABI.Events["RandomWordsFulfilled"].ID, + Address: common.Address{}, + TxHash: common.BigToHash(big.NewInt(int64(2*i + 1))), + Data: common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000069000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001"), + CreatedAt: time.Now(), + }) + } + } + + unfulfilled, _, fulfilled := listener.getUnfulfilled(logs, listener.l) + require.Empty(t, unfulfilled) + require.Len(t, fulfilled, 5) +} + +func TestGetUnfulfilled_OneUnfulfilledVRFReq(t *testing.T) { + t.Parallel() + + listener, chainID := SetupGetUnfulfilledTH(t) + + logs := []logpoller.Log{} + for i := 0; i < 10; i++ { + eventSig := emitterABI.Events["Log1"].ID + topics := [][]byte{ + common.FromHex("0x46692c0e59ca9cd1ad8f984a9d11715ec83424398b7eed4e05c8ce84662415a8"), + } + if i == 4 { + eventSig = vrfEmitterABI.Events["RandomWordsRequested"].ID + topics = [][]byte{ + common.FromHex("0x63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772"), + common.FromHex("0xc0a6c424ac7157ae408398df7e5f4552091a69125d5dfcb7b8c2659029395bdf"), + common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000001"), + common.FromHex("0x0000000000000000000000005ee3b50502b5c4c9184dcb281471a0614d4b2ef9"), + } + } + logs = append(logs, logpoller.Log{ + EvmChainId: chainID, + LogIndex: 0, + BlockHash: common.BigToHash(big.NewInt(int64(2 * i))), + BlockNumber: int64(2 * i), + BlockTimestamp: time.Now(), + Topics: topics, + EventSig: eventSig, + Address: common.Address{}, + TxHash: common.BigToHash(big.NewInt(int64(2 * i))), + Data: common.FromHex("0x000000000000000000000000000000000000000000000000000000000000000" + fmt.Sprintf("%d", i) + "000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000002"), + CreatedAt: time.Now(), + }) + } + + unfulfilled, _, fulfilled := listener.getUnfulfilled(logs, listener.l) + require.Equal(t, unfulfilled[0].RequestID().Int64(), big.NewInt(4).Int64()) + require.Len(t, unfulfilled, 1) + require.Empty(t, fulfilled) +} + +func TestGetUnfulfilled_SomeUnfulfilledVRFReq(t *testing.T) { + t.Parallel() + + listener, chainID := SetupGetUnfulfilledTH(t) + + logs := []logpoller.Log{} + for i := 0; i < 10; i++ { + eventSig := emitterABI.Events["Log1"].ID + topics := [][]byte{ + common.FromHex("0x46692c0e59ca9cd1ad8f984a9d11715ec83424398b7eed4e05c8ce84662415a8"), + } + if i%2 == 0 { + eventSig = vrfEmitterABI.Events["RandomWordsRequested"].ID + topics = [][]byte{ + common.FromHex("0x63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772"), + common.FromHex("0xc0a6c424ac7157ae408398df7e5f4552091a69125d5dfcb7b8c2659029395bdf"), + common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000001"), + common.FromHex("0x0000000000000000000000005ee3b50502b5c4c9184dcb281471a0614d4b2ef9"), + } + } + logs = append(logs, logpoller.Log{ + EvmChainId: chainID, + LogIndex: 0, + BlockHash: common.BigToHash(big.NewInt(int64(2 * i))), + BlockNumber: int64(2 * i), + BlockTimestamp: time.Now(), + Topics: topics, + EventSig: eventSig, + Address: common.Address{}, + TxHash: common.BigToHash(big.NewInt(int64(2 * i))), + Data: common.FromHex("0x000000000000000000000000000000000000000000000000000000000000000" + fmt.Sprintf("%d", i) + "000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000002"), + CreatedAt: time.Now(), + }) + } + + unfulfilled, _, fulfilled := listener.getUnfulfilled(logs, listener.l) + require.Len(t, unfulfilled, 5) + require.Len(t, fulfilled, 0) + expected := map[int64]bool{0: true, 2: true, 4: true, 6: true, 8: true} + for _, u := range unfulfilled { + v, ok := expected[u.RequestID().Int64()] + require.Equal(t, ok, true) + require.Equal(t, v, true) + } + require.Equal(t, len(expected), len(unfulfilled)) +} + +func TestGetUnfulfilled_UnfulfilledNFulfilledVRFReqs(t *testing.T) { + t.Parallel() + + listener, chainID := SetupGetUnfulfilledTH(t) + + logs := []logpoller.Log{} + for i := 0; i < 10; i++ { + eventSig := emitterABI.Events["Log1"].ID + topics := [][]byte{ + common.FromHex("0x46692c0e59ca9cd1ad8f984a9d11715ec83424398b7eed4e05c8ce84662415a8"), + } + if i%2 == 0 { + eventSig = vrfEmitterABI.Events["RandomWordsRequested"].ID + topics = [][]byte{ + common.FromHex("0x63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772"), + common.FromHex("0xc0a6c424ac7157ae408398df7e5f4552091a69125d5dfcb7b8c2659029395bdf"), + common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000001"), + common.FromHex("0x0000000000000000000000005ee3b50502b5c4c9184dcb281471a0614d4b2ef9"), + } + } + logs = append(logs, logpoller.Log{ + EvmChainId: chainID, + LogIndex: 0, + BlockHash: common.BigToHash(big.NewInt(int64(2 * i))), + BlockNumber: int64(2 * i), + BlockTimestamp: time.Now(), + Topics: topics, + EventSig: eventSig, + Address: common.Address{}, + TxHash: common.BigToHash(big.NewInt(int64(2 * i))), + Data: common.FromHex("0x000000000000000000000000000000000000000000000000000000000000000" + fmt.Sprintf("%d", i) + "000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000002"), + CreatedAt: time.Now(), + }) + if i%2 == 0 && i < 6 { + logs = append(logs, logpoller.Log{ + EvmChainId: chainID, + LogIndex: 0, + BlockHash: common.BigToHash(big.NewInt(int64(2*i + 1))), + BlockNumber: int64(2*i + 1), + BlockTimestamp: time.Now(), + Topics: [][]byte{ + common.FromHex("0x7dffc5ae5ee4e2e4df1651cf6ad329a73cebdb728f37ea0187b9b17e036756e4"), + common.FromHex("0x000000000000000000000000000000000000000000000000000000000000000" + fmt.Sprintf("%d", i)), + }, + EventSig: vrfEmitterABI.Events["RandomWordsFulfilled"].ID, + Address: common.Address{}, + TxHash: common.BigToHash(big.NewInt(int64(2*i + 1))), + Data: common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000069000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001"), + CreatedAt: time.Now(), + }) + } + } + + unfulfilled, _, fulfilled := listener.getUnfulfilled(logs, listener.l) + require.Len(t, unfulfilled, 2) + require.Len(t, fulfilled, 3) + expected := map[int64]bool{6: true, 8: true} + for _, u := range unfulfilled { + v, ok := expected[u.RequestID().Int64()] + require.Equal(t, ok, true) + require.Equal(t, v, true) + } + require.Equal(t, len(expected), len(unfulfilled)) +} + +/* Tests for getUnfulfilled: END */