From 03bbcef33a7bfcae91697051f7a8c8c51e79f928 Mon Sep 17 00:00:00 2001 From: anirudhwarrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:57:35 +0400 Subject: [PATCH 1/2] remove automation pkg --- go.mod | 3 - go.sum | 6 - pkg/automation/cbor/cbor.go | 118 -- pkg/automation/cbor/cbor_test.go | 385 ----- pkg/automation/v21/active_list.go | 125 -- pkg/automation/v21/active_list_test.go | 106 -- pkg/automation/v21/block_subscriber.go | 299 ---- pkg/automation/v21/block_subscriber_test.go | 458 ------ pkg/automation/v21/core/abi.go | 14 - pkg/automation/v21/core/interfaces.go | 12 - .../v21/core/mocks/upkeep_state_reader.go | 111 -- pkg/automation/v21/core/payload.go | 40 - pkg/automation/v21/core/payload_test.go | 151 -- pkg/automation/v21/core/testutil.go | 32 - pkg/automation/v21/core/trigger.go | 102 -- pkg/automation/v21/core/trigger_test.go | 97 -- pkg/automation/v21/core/type.go | 36 - pkg/automation/v21/core/type_test.go | 57 - pkg/automation/v21/core/utils.go | 26 - pkg/automation/v21/core/utils_test.go | 71 - pkg/automation/v21/encoding/encoder.go | 129 -- pkg/automation/v21/encoding/encoder_test.go | 165 --- pkg/automation/v21/encoding/interface.go | 97 -- pkg/automation/v21/encoding/packer.go | 149 -- pkg/automation/v21/encoding/packer_test.go | 291 ---- .../v21/fixtures/expected_encoded_report.txt | 1 - pkg/automation/v21/gasprice/gasprice.go | 74 - pkg/automation/v21/gasprice/gasprice_test.go | 127 -- pkg/automation/v21/keyring.go | 50 - pkg/automation/v21/keyring_test.go | 110 -- pkg/automation/v21/logprovider/block_time.go | 61 - .../v21/logprovider/block_time_test.go | 81 -- pkg/automation/v21/logprovider/buffer_v1.go | 551 -------- .../v21/logprovider/buffer_v1_test.go | 543 -------- pkg/automation/v21/logprovider/factory.go | 91 -- pkg/automation/v21/logprovider/filter.go | 81 -- .../v21/logprovider/filter_store.go | 158 --- .../v21/logprovider/filter_store_test.go | 89 -- pkg/automation/v21/logprovider/filter_test.go | 196 --- pkg/automation/v21/logprovider/log.go | 58 - pkg/automation/v21/logprovider/log_packer.go | 44 - pkg/automation/v21/logprovider/log_test.go | 133 -- pkg/automation/v21/logprovider/provider.go | 495 ------- .../v21/logprovider/provider_life_cycle.go | 188 --- .../logprovider/provider_life_cycle_test.go | 236 ---- .../v21/logprovider/provider_test.go | 646 --------- pkg/automation/v21/logprovider/recoverer.go | 731 ---------- .../v21/logprovider/recoverer_test.go | 1239 ----------------- pkg/automation/v21/mercury/mercury.go | 201 --- pkg/automation/v21/mercury/mercury_test.go | 371 ----- pkg/automation/v21/mercury/streams/streams.go | 385 ----- .../v21/mercury/streams/streams_test.go | 970 ------------- pkg/automation/v21/mercury/testutils.go | 72 - pkg/automation/v21/mercury/v02/request.go | 293 ---- .../v21/mercury/v02/v02_request_test.go | 868 ------------ pkg/automation/v21/mercury/v03/request.go | 305 ---- .../v21/mercury/v03/v03_request_test.go | 899 ------------ pkg/automation/v21/mocks/http_client.go | 94 -- pkg/automation/v21/mocks/registry.go | 449 ------ pkg/automation/v21/payload_builder.go | 63 - pkg/automation/v21/payload_builder_test.go | 210 --- pkg/automation/v21/prommetrics/metrics.go | 108 -- pkg/automation/v21/registry.go | 634 --------- pkg/automation/v21/registry_check_pipeline.go | 403 ------ .../v21/registry_check_pipeline_test.go | 708 ---------- pkg/automation/v21/registry_test.go | 611 -------- pkg/automation/v21/services.go | 30 - pkg/automation/v21/testdata/btc-usd.json | 3 - pkg/automation/v21/testdata/eth-usd.json | 3 - .../v21/threadcontrol/thread_control.go | 55 - .../v21/threadcontrol/thread_control_test.go | 57 - pkg/automation/v21/transmit/cache.go | 72 - pkg/automation/v21/transmit/cache_test.go | 102 -- pkg/automation/v21/transmit/encoding.go | 113 -- pkg/automation/v21/transmit/encoding_test.go | 102 -- pkg/automation/v21/transmit/event_provider.go | 228 --- .../v21/transmit/event_provider_test.go | 230 --- pkg/automation/v21/upkeep_provider.go | 51 - pkg/automation/v21/upkeep_provider_test.go | 114 -- pkg/automation/v21/upkeepstate/orm.go | 89 -- pkg/automation/v21/upkeepstate/orm_test.go | 49 - pkg/automation/v21/upkeepstate/scanner.go | 106 -- .../v21/upkeepstate/scanner_test.go | 169 --- pkg/automation/v21/upkeepstate/store.go | 337 ----- pkg/automation/v21/upkeepstate/store_test.go | 599 -------- pkg/relay/evm.go | 7 - pkg/relay/ocr2keeper.go | 282 ---- 87 files changed, 19505 deletions(-) delete mode 100644 pkg/automation/cbor/cbor.go delete mode 100644 pkg/automation/cbor/cbor_test.go delete mode 100644 pkg/automation/v21/active_list.go delete mode 100644 pkg/automation/v21/active_list_test.go delete mode 100644 pkg/automation/v21/block_subscriber.go delete mode 100644 pkg/automation/v21/block_subscriber_test.go delete mode 100644 pkg/automation/v21/core/abi.go delete mode 100644 pkg/automation/v21/core/interfaces.go delete mode 100644 pkg/automation/v21/core/mocks/upkeep_state_reader.go delete mode 100644 pkg/automation/v21/core/payload.go delete mode 100644 pkg/automation/v21/core/payload_test.go delete mode 100644 pkg/automation/v21/core/testutil.go delete mode 100644 pkg/automation/v21/core/trigger.go delete mode 100644 pkg/automation/v21/core/trigger_test.go delete mode 100644 pkg/automation/v21/core/type.go delete mode 100644 pkg/automation/v21/core/type_test.go delete mode 100644 pkg/automation/v21/core/utils.go delete mode 100644 pkg/automation/v21/core/utils_test.go delete mode 100644 pkg/automation/v21/encoding/encoder.go delete mode 100644 pkg/automation/v21/encoding/encoder_test.go delete mode 100644 pkg/automation/v21/encoding/interface.go delete mode 100644 pkg/automation/v21/encoding/packer.go delete mode 100644 pkg/automation/v21/encoding/packer_test.go delete mode 100644 pkg/automation/v21/fixtures/expected_encoded_report.txt delete mode 100644 pkg/automation/v21/gasprice/gasprice.go delete mode 100644 pkg/automation/v21/gasprice/gasprice_test.go delete mode 100644 pkg/automation/v21/keyring.go delete mode 100644 pkg/automation/v21/keyring_test.go delete mode 100644 pkg/automation/v21/logprovider/block_time.go delete mode 100644 pkg/automation/v21/logprovider/block_time_test.go delete mode 100644 pkg/automation/v21/logprovider/buffer_v1.go delete mode 100644 pkg/automation/v21/logprovider/buffer_v1_test.go delete mode 100644 pkg/automation/v21/logprovider/factory.go delete mode 100644 pkg/automation/v21/logprovider/filter.go delete mode 100644 pkg/automation/v21/logprovider/filter_store.go delete mode 100644 pkg/automation/v21/logprovider/filter_store_test.go delete mode 100644 pkg/automation/v21/logprovider/filter_test.go delete mode 100644 pkg/automation/v21/logprovider/log.go delete mode 100644 pkg/automation/v21/logprovider/log_packer.go delete mode 100644 pkg/automation/v21/logprovider/log_test.go delete mode 100644 pkg/automation/v21/logprovider/provider.go delete mode 100644 pkg/automation/v21/logprovider/provider_life_cycle.go delete mode 100644 pkg/automation/v21/logprovider/provider_life_cycle_test.go delete mode 100644 pkg/automation/v21/logprovider/provider_test.go delete mode 100644 pkg/automation/v21/logprovider/recoverer.go delete mode 100644 pkg/automation/v21/logprovider/recoverer_test.go delete mode 100644 pkg/automation/v21/mercury/mercury.go delete mode 100644 pkg/automation/v21/mercury/mercury_test.go delete mode 100644 pkg/automation/v21/mercury/streams/streams.go delete mode 100644 pkg/automation/v21/mercury/streams/streams_test.go delete mode 100644 pkg/automation/v21/mercury/testutils.go delete mode 100644 pkg/automation/v21/mercury/v02/request.go delete mode 100644 pkg/automation/v21/mercury/v02/v02_request_test.go delete mode 100644 pkg/automation/v21/mercury/v03/request.go delete mode 100644 pkg/automation/v21/mercury/v03/v03_request_test.go delete mode 100644 pkg/automation/v21/mocks/http_client.go delete mode 100644 pkg/automation/v21/mocks/registry.go delete mode 100644 pkg/automation/v21/payload_builder.go delete mode 100644 pkg/automation/v21/payload_builder_test.go delete mode 100644 pkg/automation/v21/prommetrics/metrics.go delete mode 100644 pkg/automation/v21/registry.go delete mode 100644 pkg/automation/v21/registry_check_pipeline.go delete mode 100644 pkg/automation/v21/registry_check_pipeline_test.go delete mode 100644 pkg/automation/v21/registry_test.go delete mode 100644 pkg/automation/v21/services.go delete mode 100644 pkg/automation/v21/testdata/btc-usd.json delete mode 100644 pkg/automation/v21/testdata/eth-usd.json delete mode 100644 pkg/automation/v21/threadcontrol/thread_control.go delete mode 100644 pkg/automation/v21/threadcontrol/thread_control_test.go delete mode 100644 pkg/automation/v21/transmit/cache.go delete mode 100644 pkg/automation/v21/transmit/cache_test.go delete mode 100644 pkg/automation/v21/transmit/encoding.go delete mode 100644 pkg/automation/v21/transmit/encoding_test.go delete mode 100644 pkg/automation/v21/transmit/event_provider.go delete mode 100644 pkg/automation/v21/transmit/event_provider_test.go delete mode 100644 pkg/automation/v21/upkeep_provider.go delete mode 100644 pkg/automation/v21/upkeep_provider_test.go delete mode 100644 pkg/automation/v21/upkeepstate/orm.go delete mode 100644 pkg/automation/v21/upkeepstate/orm_test.go delete mode 100644 pkg/automation/v21/upkeepstate/scanner.go delete mode 100644 pkg/automation/v21/upkeepstate/scanner_test.go delete mode 100644 pkg/automation/v21/upkeepstate/store.go delete mode 100644 pkg/automation/v21/upkeepstate/store_test.go delete mode 100644 pkg/relay/ocr2keeper.go diff --git a/go.mod b/go.mod index ac9c343740..fa5df806c5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/smartcontractkit/chainlink-evm go 1.26.6 require ( - github.com/avast/retry-go/v4 v4.7.0 github.com/ethereum/go-ethereum v1.17.3 github.com/fbsobreira/gotron-sdk v0.0.0-20250403083053-2943ce8c759b github.com/fxamacker/cbor/v2 v2.9.2 @@ -20,7 +19,6 @@ require ( github.com/lib/pq v1.12.3 github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 github.com/onsi/gomega v1.38.2 - github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml/v2 v2.3.1 github.com/pkg/errors v0.9.1 @@ -29,7 +27,6 @@ require ( github.com/prometheus/common v0.67.5 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.100 - github.com/smartcontractkit/chainlink-automation v0.8.1 github.com/smartcontractkit/chainlink-common v0.11.2-0.20260729194708-6738a99f5706 github.com/smartcontractkit/chainlink-common/keystore v1.3.0 github.com/smartcontractkit/chainlink-data-streams v1.1.0 diff --git a/go.sum b/go.sum index b104ccf06e..c7cd001b7a 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,6 @@ github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJe github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/avast/retry-go/v4 v4.7.0 h1:yjDs35SlGvKwRNSykujfjdMxMhMQQM0TnIjJaHB+Zio= -github.com/avast/retry-go/v4 v4.7.0/go.mod h1:ZMPDa3sY2bKgpLtap9JRUgk2yTAba7cgiFhqxY2Sg6Q= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -567,8 +565,6 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -652,8 +648,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartcontractkit/chain-selectors v1.0.100 h1:wpiSpmI/eFjY+wx/nPr5VuNF4hki0prIBMKEaQWn3g4= github.com/smartcontractkit/chain-selectors v1.0.100/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= -github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260729194708-6738a99f5706 h1:ztPU+UfAEi/j+iVUZCh9gzU0PhuW3wN8mDBq//SeGD0= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260729194708-6738a99f5706/go.mod h1:euYQ2WxVYGU2ivHXPBRpbU0Z3/SkekfIK5YPf7hdDY8= github.com/smartcontractkit/chainlink-common/keystore v1.3.0 h1:V05Rp9/dTc4Wyipsk1EYnW3riZyjDVKmhz3769JpnHU= diff --git a/pkg/automation/cbor/cbor.go b/pkg/automation/cbor/cbor.go deleted file mode 100644 index 9b92bb46b8..0000000000 --- a/pkg/automation/cbor/cbor.go +++ /dev/null @@ -1,118 +0,0 @@ -package cbor - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/fxamacker/cbor/v2" - pkgerrors "github.com/pkg/errors" -) - -// ParseDietCBOR attempts to coerce the input byte array into valid CBOR. -// Assumes the input is "diet" CBOR which is like CBOR, except: -// 1. It is guaranteed to always be a map -// 2. It may or may not include the opening and closing markers "{}" -func ParseDietCBOR(b []byte) (map[string]any, error) { - b = autoAddMapDelimiters(b) - - var m map[any]any - if _, err := cbor.UnmarshalFirst(b, &m); err != nil { - return nil, err - } - - coerced, err := CoerceInterfaceMapToStringMap(m) - if err != nil { - return nil, err - } - - output, ok := coerced.(map[string]any) - if !ok { - return nil, pkgerrors.New("cbor data cannot be coerced to map") - } - - return output, nil -} - -// Similar to ParseDietCBOR but outputs to a concrete struct, which meets the -// "top-level map" requirement of "diet" CBOR. -func ParseDietCBORToStruct(b []byte, v any) error { - b = autoAddMapDelimiters(b) - _, err := cbor.UnmarshalFirst(b, v) - return err -} - -// ParseStandardCBOR parses CBOR in "standards compliant" mode. -// Literal values are passed through "as-is". -// The input is not assumed to be a map. -// Empty inputs will return nil. -func ParseStandardCBOR(b []byte) (a any, err error) { - if len(b) == 0 { - return nil, nil - } - if _, err = cbor.UnmarshalFirst(b, &a); err != nil { - return nil, err - } - return -} - -// Automatically add missing start map and end map to a CBOR encoded buffer -func autoAddMapDelimiters(b []byte) []byte { - if len(b) == 0 || (len(b) > 1 && (b[0]>>5) != 5) { - var buffer bytes.Buffer - buffer.Write([]byte{0xbf}) - buffer.Write(b) - buffer.Write([]byte{0xff}) - return buffer.Bytes() - } - - return b -} - -// CoerceInterfaceMapToStringMap converts map[interface{}]interface{} (interface maps) to -// map[string]interface{} (string maps) and []interface{} with interface maps to string maps. -// Relevant when serializing between CBOR and JSON. -// -// It also handles the CBOR 'bignum' type as documented here: https://tools.ietf.org/html/rfc7049#section-2.4.2 -func CoerceInterfaceMapToStringMap(in any) (any, error) { - switch typed := in.(type) { - case map[string]any: - for k, v := range typed { - coerced, err := CoerceInterfaceMapToStringMap(v) - if err != nil { - return nil, err - } - typed[k] = coerced - } - return typed, nil - case map[any]any: - m := map[string]any{} - for k, v := range typed { - coercedKey, ok := k.(string) - if !ok { - return nil, fmt.Errorf("unable to coerce key %T %v to a string", k, k) - } - coerced, err := CoerceInterfaceMapToStringMap(v) - if err != nil { - return nil, err - } - m[coercedKey] = coerced - } - return m, nil - case []any: - r := make([]any, len(typed)) - for i, v := range typed { - coerced, err := CoerceInterfaceMapToStringMap(v) - if err != nil { - return nil, err - } - r[i] = coerced - } - return r, nil - case big.Int: - value, _ := (in).(big.Int) - return &value, nil - default: - return in, nil - } -} diff --git a/pkg/automation/cbor/cbor_test.go b/pkg/automation/cbor/cbor_test.go deleted file mode 100644 index 4940209ab2..0000000000 --- a/pkg/automation/cbor/cbor_test.go +++ /dev/null @@ -1,385 +0,0 @@ -package cbor - -import ( - "encoding/json" - "fmt" - "math/big" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/fxamacker/cbor/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/utils/hex" -) - -func Test_ParseCBOR(t *testing.T) { - t.Parallel() - - address, err := hex.DecodeString("0x8bd112d3f8f92e41c861939545ad387307af9703") - require.NoError(t, err) - - tests := []struct { - name string - in string - want any - wantErrored bool - }{ - { - "hello world", - `0xbf6375726c781a68747470733a2f2f657468657270726963652e636f6d2f61706964706174689f66726563656e7463757364ffff`, - jsonMustUnmarshal(t, `{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), - false, - }, - { - "trailing empty bytes", - `0xbf6375726c781a68747470733a2f2f657468657270726963652e636f6d2f61706964706174689f66726563656e7463757364ffff000000`, - jsonMustUnmarshal(t, `{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), - false, - }, - { - "nested maps", - `0xbf657461736b739f6868747470706f7374ff66706172616d73bf636d73676f68656c6c6f5f636861696e6c696e6b6375726c75687474703a2f2f6c6f63616c686f73743a36363930ffff`, - jsonMustUnmarshal(t, `{"params":{"msg":"hello_chainlink","url":"http://localhost:6690"},"tasks":["httppost"]}`), - false, - }, - { - "missing initial start map marker", - `0x636B65796576616C7565ff`, - jsonMustUnmarshal(t, `{"key":"value"}`), - false, - }, - { - "with address encoded", - `0x6d72656d6f7465436861696e4964186a6e6c69627261727956657273696f6e016f636f6e747261637441646472657373548bd112d3f8f92e41c861939545ad387307af97036d636f6e6669726d6174696f6e730a68626c6f636b4e756d69307831336261626264`, - map[string]any{ - "blockNum": "0x13babbd", - "confirmations": uint64(10), - "contractAddress": address, - "libraryVersion": uint64(1), - "remoteChainId": uint64(106), - }, - false, - }, - { - "bignums", - "0x" + - "bf" + // map(*) - "67" + // text(7) - "6269676e756d73" + // "bignums" - "9f" + // array(*) - "c2" + // tag(2) == unsigned bignum - "5820" + // bytes(32) - "0000000000000000000000000000000000000000000000010000000000000000" + - // int(18446744073709551616) - "c2" + // tag(2) == unsigned bignum - "5820" + // bytes(32) - "4000000000000000000000000000000000000000000000000000000000000000" + - // int(28948022309329048855892746252171976963317496166410141009864396001978282409984) - "c3" + // tag(3) == signed bignum - "5820" + // bytes(32) - "0000000000000000000000000000000000000000000000010000000000000000" + - // int(18446744073709551616) - "c3" + // tag(3) == signed bignum - "5820" + // bytes(32) - "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + - // int(28948022309329048855892746252171976963317496166410141009864396001978282409983) - "ff" + // primitive(*) - "ff", // primitive(*) - map[string]any{ - "bignums": []any{ - mustParseBigInt(t, "18446744073709551616"), - mustParseBigInt(t, "28948022309329048855892746252171976963317496166410141009864396001978282409984"), - mustParseBigInt(t, "-18446744073709551617"), - mustParseBigInt(t, "-28948022309329048855892746252171976963317496166410141009864396001978282409984"), - }, - }, - false, - }, - { - "bignums", - "0x" + - "67" + // text(7) - "6269676e756d73" + // "bignums" - "9f" + // array(*) - "c2" + // tag(2) == unsigned bignum - "5820" + // bytes(32) - "0000000000000000000000000000000000000000000000010000000000000000" + - // int(18446744073709551616) - "c2" + // tag(2) == unsigned bignum - "5820" + // bytes(32) - "4000000000000000000000000000000000000000000000000000000000000000" + - // int(28948022309329048855892746252171976963317496166410141009864396001978282409984) - "c3" + // tag(3) == signed bignum - "5820" + // bytes(32) - "0000000000000000000000000000000000000000000000010000000000000000" + - // int(18446744073709551616) - "c3" + // tag(3) == signed bignum - "5820" + // bytes(32) - "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + - // int(28948022309329048855892746252171976963317496166410141009864396001978282409983) - "ff", // primitive(*) - map[string]any{ - "bignums": []any{ - mustParseBigInt(t, "18446744073709551616"), - mustParseBigInt(t, "28948022309329048855892746252171976963317496166410141009864396001978282409984"), - mustParseBigInt(t, "-18446744073709551617"), - mustParseBigInt(t, "-28948022309329048855892746252171976963317496166410141009864396001978282409984"), - }, - }, - false, - }, - {"empty object", `0xa0`, jsonMustUnmarshal(t, `{}`), false}, - {"empty string", `0x`, jsonMustUnmarshal(t, `{}`), false}, - {"invalid CBOR", `0xff`, jsonMustUnmarshal(t, `{}`), true}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - b, err := hexutil.Decode(test.in) - assert.NoError(t, err) - - json, err := ParseDietCBOR(b) - if test.wantErrored { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, test.want, json) - } - }) - } -} - -func Test_ParseCBORToStruct_Success(t *testing.T) { - t.Parallel() - - hexCBOR := `0xbf6375726c781a68747470733a2f2f657468657270726963652e636f6d2f61706964706174689f66726563656e7463757364ffff000000` - bytesCBOR, err := hexutil.Decode(hexCBOR) - assert.NoError(t, err) - - parsed := struct { - Url string `cbor:"url"` - Path []string `cbor:"path"` - }{} - err = ParseDietCBORToStruct(bytesCBOR, &parsed) - - require.NoError(t, err) - require.Equal(t, "https://etherprice.com/api", parsed.Url) - require.Equal(t, []string{"recent", "usd"}, parsed.Path) -} - -func Test_ParseCBORToStruct_WrongFieldType(t *testing.T) { - t.Parallel() - - hexCBOR := `0xbf6375726c781a68747470733a2f2f657468657270726963652e636f6d2f61706964706174689f66726563656e7463757364ffff000000` - bytesCBOR, err := hexutil.Decode(hexCBOR) - assert.NoError(t, err) - - parsed := struct { - Url string `cbor:"url"` - Path []int `cbor:"path"` // exect int but get string - }{} - err = ParseDietCBORToStruct(bytesCBOR, &parsed) - - require.Error(t, err) -} - -func Test_ParseCBORToStruct_BinaryStringOfWrongType(t *testing.T) { - t.Parallel() - - // {"key":"value"} but with last byte replaced with invalid unicode (0x88) - hexCBOR := `0x636B65796576616C7588` - bytesCBOR, err := hexutil.Decode(hexCBOR) - assert.NoError(t, err) - - parsed := struct { - Key string `cbor:"key"` - }{} - err = ParseDietCBORToStruct(bytesCBOR, &parsed) - require.Error(t, err) -} - -func Test_autoAddMapDelimiters(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - in []byte - want []byte - }{ - { - "map(0)", - hexutil.MustDecode("0xA0"), - hexutil.MustDecode("0xA0"), - }, - { - `map(1) {"key":"value"}`, - hexutil.MustDecode("0xA1636B65796576616C7565"), - hexutil.MustDecode("0xA1636B65796576616C7565"), - }, - { - "array(0)", - hexutil.MustDecode("0x80"), - hexutil.MustDecode("0x80"), - }, - { - `map(*) {"key":"value"}`, - hexutil.MustDecode("0xbf636B65796576616C7565ff"), - hexutil.MustDecode("0xbf636B65796576616C7565ff"), - }, - { - `map(*) {"key":"value"} missing open delimiter`, - hexutil.MustDecode("0x636B65796576616C7565ff"), - hexutil.MustDecode("0xbf636B65796576616C7565ffff"), - }, - { - `map(*) {"key":"value"} missing closing delimiter`, - hexutil.MustDecode("0xbf636B65796576616C7565"), - hexutil.MustDecode("0xbf636B65796576616C7565"), - }, - { - `map(*) {"key":"value"} missing both delimiters`, - hexutil.MustDecode("0x636B65796576616C7565"), - hexutil.MustDecode("0xbf636B65796576616C7565ff"), - }, - { - "empty input adds delimiters", - []byte{}, - []byte{0xbf, 0xff}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.want, autoAddMapDelimiters(test.in)) - }) - } -} - -func jsonMustUnmarshal(t *testing.T, in string) any { - var j any - err := json.Unmarshal([]byte(in), &j) - require.NoError(t, err) - return j -} - -func TestCoerceInterfaceMapToStringMap(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input any - want any - }{ - {"empty map", map[any]any{}, map[string]any{}}, - {"simple map", map[any]any{"key": "value"}, map[string]any{"key": "value"}}, - {"int map", map[int]any{1: "value"}, map[int]any{1: "value"}}, - { - "nested string map map", - map[string]any{"key": map[any]any{"nk": "nv"}}, - map[string]any{"key": map[string]any{"nk": "nv"}}, - }, - { - "nested map map", - map[any]any{"key": map[any]any{"nk": "nv"}}, - map[string]any{"key": map[string]any{"nk": "nv"}}, - }, - { - "nested map array", - map[any]any{"key": []any{1, "value"}}, - map[string]any{"key": []any{1, "value"}}, - }, - {"empty array", []any{}, []any{}}, - {"simple array", []any{1, "value"}, []any{1, "value"}}, - { - "nested array map", - []any{map[any]any{"key": map[any]any{"nk": "nv"}}}, - []any{map[string]any{"key": map[string]any{"nk": "nv"}}}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - decoded, err := CoerceInterfaceMapToStringMap(test.input) - require.NoError(t, err) - assert.True(t, reflect.DeepEqual(test.want, decoded)) - }) - } -} - -func TestCoerceInterfaceMapToStringMap_BadInputs(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input any - }{ - {"error map", map[any]any{1: "value"}}, - {"error array", []any{map[any]any{1: "value"}}}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - _, err := CoerceInterfaceMapToStringMap(test.input) - assert.Error(t, err) - }) - } -} - -func TestJSON_CBOR(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - in any - }{ - {"empty object", jsonMustUnmarshal(t, `{}`)}, - {"array", jsonMustUnmarshal(t, `[1,2,3,4]`)}, - { - "basic object", - jsonMustUnmarshal(t, `{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), - }, - { - "complex object", - jsonMustUnmarshal(t, `{"a":{"1":[{"b":"free"},{"c":"more"},{"d":["less", {"nesting":{"4":"life"}}]}]}}`), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - encoded := mustMarshal(t, test.in) - - var decoded any - err := cbor.Unmarshal(encoded, &decoded) - require.NoError(t, err) - - decoded, err = CoerceInterfaceMapToStringMap(decoded) - require.NoError(t, err) - assert.True(t, reflect.DeepEqual(test.in, decoded)) - }) - } -} - -// mustMarshal returns a bytes array of the JSON map or array encoded to CBOR. -func mustMarshal(t *testing.T, j any) []byte { - switch v := j.(type) { - case map[string]any, []any, nil: - b, err := cbor.Marshal(v) - if err != nil { - t.Fatalf("failed to marshal CBOR: %v", err) - } - return b - default: - t.Fatalf("unable to coerce JSON to CBOR for type %T", v) - return nil - } -} - -func mustParseBigInt(t *testing.T, input string) *big.Int { - i := new(big.Int) - _, err := fmt.Sscan(input, i) - require.NoError(t, err) - return i -} diff --git a/pkg/automation/v21/active_list.go b/pkg/automation/v21/active_list.go deleted file mode 100644 index 4e404990c2..0000000000 --- a/pkg/automation/v21/active_list.go +++ /dev/null @@ -1,125 +0,0 @@ -package evm - -import ( - "math/big" - "slices" - "sync" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/prommetrics" -) - -// ActiveUpkeepList is a list to manage active upkeep IDs -type ActiveUpkeepList interface { - // Reset resets the list to the given IDs - Reset(ids ...*big.Int) - // Add adds new entries to the list - Add(id ...*big.Int) int - // Remove removes entries from the list - Remove(id ...*big.Int) int - // View returns the list of IDs of the given type - View(...types.UpkeepType) []*big.Int - // IsActive returns true if the given ID is of an active upkeep - IsActive(id *big.Int) bool - Size() int -} - -type activeList struct { - items map[string]bool - lock sync.RWMutex -} - -var _ ActiveUpkeepList = &activeList{} - -// NewActiveList creates a new ActiveList -func NewActiveUpkeepList() ActiveUpkeepList { - return &activeList{ - items: make(map[string]bool), - } -} - -// Reset resets the list to the given IDs -func (al *activeList) Reset(ids ...*big.Int) { - al.lock.Lock() - defer al.lock.Unlock() - - al.items = make(map[string]bool) - for _, id := range ids { - al.items[id.String()] = true - } - prommetrics.AutomationActiveUpkeeps.Set(float64(len(al.items))) -} - -// Add adds new entries to the list. Returns the number of items added -func (al *activeList) Add(ids ...*big.Int) int { - al.lock.Lock() - defer al.lock.Unlock() - - count := 0 - for _, id := range ids { - if key := id.String(); !al.items[key] { - count++ - al.items[key] = true - } - } - prommetrics.AutomationActiveUpkeeps.Set(float64(len(al.items))) - return count -} - -// Remove removes entries from the list. Returns the number of items removed -func (al *activeList) Remove(ids ...*big.Int) int { - al.lock.Lock() - defer al.lock.Unlock() - - count := 0 - for _, id := range ids { - key := id.String() - if al.items[key] { - count++ - delete(al.items, key) - } - } - prommetrics.AutomationActiveUpkeeps.Set(float64(len(al.items))) - return count -} - -// View returns the list of IDs of the given type -func (al *activeList) View(upkeepTypes ...types.UpkeepType) []*big.Int { - al.lock.RLock() - defer al.lock.RUnlock() - - var keys []*big.Int - for key := range al.items { - id := &ocr2keepers.UpkeepIdentifier{} - bint, ok := big.NewInt(0).SetString(key, 10) - if !ok { - continue - } - if !id.FromBigInt(bint) { - continue - } - currentType := core.GetUpkeepType(*id) - if slices.Contains(upkeepTypes, currentType) { - keys = append(keys, bint) - } - } - return keys -} - -func (al *activeList) IsActive(id *big.Int) bool { - al.lock.RLock() - defer al.lock.RUnlock() - - return al.items[id.String()] -} - -func (al *activeList) Size() int { - al.lock.RLock() - defer al.lock.RUnlock() - - return len(al.items) -} diff --git a/pkg/automation/v21/active_list_test.go b/pkg/automation/v21/active_list_test.go deleted file mode 100644 index d46b279c1b..0000000000 --- a/pkg/automation/v21/active_list_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package evm - -import ( - "math/big" - "sort" - "testing" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/stretchr/testify/require" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" -) - -func TestActiveUpkeepList(t *testing.T) { - logIDs := []ocr2keepers.UpkeepIdentifier{ - core.GenUpkeepID(types.LogTrigger, "0"), - core.GenUpkeepID(types.LogTrigger, "1"), - core.GenUpkeepID(types.LogTrigger, "2"), - core.GenUpkeepID(types.LogTrigger, "3"), - core.GenUpkeepID(types.LogTrigger, "4"), - } - conditionalIDs := []ocr2keepers.UpkeepIdentifier{ - core.GenUpkeepID(types.ConditionTrigger, "0"), - core.GenUpkeepID(types.ConditionTrigger, "1"), - core.GenUpkeepID(types.ConditionTrigger, "2"), - core.GenUpkeepID(types.ConditionTrigger, "3"), - core.GenUpkeepID(types.ConditionTrigger, "4"), - } - - tests := []struct { - name string - initial []*big.Int - add []*big.Int - remove []*big.Int - expectedLogIds []*big.Int - expectedConditionalIds []*big.Int - }{ - { - name: "happy flow", - initial: []*big.Int{logIDs[0].BigInt(), logIDs[1].BigInt(), conditionalIDs[0].BigInt(), conditionalIDs[1].BigInt()}, - add: []*big.Int{logIDs[2].BigInt(), logIDs[3].BigInt(), conditionalIDs[2].BigInt(), conditionalIDs[3].BigInt()}, - remove: []*big.Int{logIDs[3].BigInt(), conditionalIDs[3].BigInt()}, - expectedLogIds: []*big.Int{logIDs[0].BigInt(), logIDs[1].BigInt(), logIDs[2].BigInt()}, - expectedConditionalIds: []*big.Int{conditionalIDs[0].BigInt(), conditionalIDs[1].BigInt(), conditionalIDs[2].BigInt()}, - }, - { - name: "empty", - initial: []*big.Int{}, - add: []*big.Int{}, - remove: []*big.Int{}, - expectedLogIds: []*big.Int{}, - expectedConditionalIds: []*big.Int{}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - al := NewActiveUpkeepList() - al.Reset(tc.initial...) - require.Equal(t, len(tc.initial), al.Size()) - for _, id := range tc.initial { - require.True(t, al.IsActive(id)) - } - al.Add(tc.add...) - for _, id := range tc.add { - require.True(t, al.IsActive(id)) - } - al.Remove(tc.remove...) - for _, id := range tc.remove { - require.False(t, al.IsActive(id)) - } - logIds := al.View(types.LogTrigger) - require.Len(t, logIds, len(tc.expectedLogIds)) - sort.Slice(logIds, func(i, j int) bool { - return logIds[i].Cmp(logIds[j]) < 0 - }) - for i := range logIds { - require.Equal(t, tc.expectedLogIds[i], logIds[i]) - } - conditionalIds := al.View(types.ConditionTrigger) - require.Len(t, conditionalIds, len(tc.expectedConditionalIds)) - sort.Slice(conditionalIds, func(i, j int) bool { - return conditionalIds[i].Cmp(conditionalIds[j]) < 0 - }) - for i := range conditionalIds { - require.Equal(t, tc.expectedConditionalIds[i], conditionalIds[i]) - } - }) - } -} - -func TestActiveUpkeepList_error(t *testing.T) { - t.Run("if invalid or negative numbers are in the store, they are excluded from the view operation", func(t *testing.T) { - al := &activeList{} - al.items = make(map[string]bool) - al.items["not a number"] = true - al.items["-1"] = true - al.items["100"] = true - - keys := al.View(types.ConditionTrigger) - require.Equal(t, []*big.Int{big.NewInt(100)}, keys) - }) -} diff --git a/pkg/automation/v21/block_subscriber.go b/pkg/automation/v21/block_subscriber.go deleted file mode 100644 index 1150ef80e4..0000000000 --- a/pkg/automation/v21/block_subscriber.go +++ /dev/null @@ -1,299 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/heads" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - "github.com/smartcontractkit/chainlink-evm/pkg/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - // cleanUpInterval is the interval for cleaning up block maps - cleanUpInterval = 15 * time.Minute - // channelSize represents the channel size for head broadcaster - channelSize = 100 - // lookbackDepth decides valid trigger block lookback range - lookbackDepth = 1024 - // blockHistorySize decides the block history size sent to subscribers - blockHistorySize = int64(256) -) - -var ( - BlockSubscriberServiceName = "BlockSubscriber" -) - -type BlockSubscriber struct { - services.StateMachine - threadCtrl threadcontrol.ThreadControl - - mu sync.RWMutex - hb heads.Broadcaster - lp logpoller.LogPoller - headC chan *types.Head - unsubscribe func() - subscribers map[int]chan ocr2keepers.BlockHistory - blocks map[int64]string - maxSubId int - lastClearedBlock int64 - lastSentBlock int64 - latestBlock atomic.Pointer[ocr2keepers.BlockKey] - blockHistorySize int64 - blockSize int64 - finalityDepth uint32 - lggr logger.Logger -} - -func (bs *BlockSubscriber) LatestBlock() *ocr2keepers.BlockKey { - return bs.latestBlock.Load() -} - -var _ ocr2keepers.BlockSubscriber = &BlockSubscriber{} - -func NewBlockSubscriber(hb heads.Broadcaster, lp logpoller.LogPoller, finalityDepth uint32, lggr logger.Logger) *BlockSubscriber { - return &BlockSubscriber{ - threadCtrl: threadcontrol.NewThreadControl(), - hb: hb, - lp: lp, - headC: make(chan *types.Head, channelSize), - subscribers: map[int]chan ocr2keepers.BlockHistory{}, - blocks: map[int64]string{}, - blockHistorySize: blockHistorySize, - blockSize: lookbackDepth, - finalityDepth: finalityDepth, - latestBlock: atomic.Pointer[ocr2keepers.BlockKey]{}, - lggr: logger.Named(lggr, "BlockSubscriber"), - } -} - -func (bs *BlockSubscriber) getBlockRange(ctx context.Context) ([]uint64, error) { - h, err := bs.lp.LatestBlock(ctx) - if err != nil { - return nil, err - } - latestBlockNumber := h.BlockNumber - bs.lggr.Infof("latest block from log poller is %d", latestBlockNumber) - - var blocks []uint64 - for i := bs.blockSize - 1; i >= 0; i-- { - if latestBlockNumber-i > 0 { - blocks = append(blocks, uint64(latestBlockNumber-i)) - } - } - return blocks, nil -} - -func (bs *BlockSubscriber) initializeBlocks(ctx context.Context, blocks []uint64) error { - logpollerBlocks, err := bs.lp.GetBlocksRange(ctx, blocks) - if err != nil { - return err - } - for i, b := range logpollerBlocks { - if i == 0 { - bs.lastClearedBlock = b.BlockNumber - 1 - bs.lggr.Infof("lastClearedBlock is %d", bs.lastClearedBlock) - } - bs.blocks[b.BlockNumber] = b.BlockHash.Hex() - } - bs.lggr.Infof("initialize with %d blocks", len(logpollerBlocks)) - return nil -} - -func (bs *BlockSubscriber) buildHistory(block int64) ocr2keepers.BlockHistory { - var keys []ocr2keepers.BlockKey - // populate keys slice in block DES order - for i := int64(0); i < bs.blockHistorySize; i++ { - if block-i > 0 { - if h, ok := bs.blocks[block-i]; ok { - keys = append(keys, ocr2keepers.BlockKey{ - Number: ocr2keepers.BlockNumber(block - i), - Hash: common.HexToHash(h), - }) - } else { - bs.lggr.Debugf("block %d is missing", block-i) - } - } - } - return keys -} - -func (bs *BlockSubscriber) cleanup() { - bs.mu.Lock() - defer bs.mu.Unlock() - - bs.lggr.Debugf("start clearing blocks from %d to %d", bs.lastClearedBlock+1, bs.lastSentBlock-bs.blockSize) - for i := bs.lastClearedBlock + 1; i <= bs.lastSentBlock-bs.blockSize; i++ { - delete(bs.blocks, i) - } - bs.lastClearedBlock = bs.lastSentBlock - bs.blockSize - bs.lggr.Infof("lastClearedBlock is set to %d", bs.lastClearedBlock) -} - -func (bs *BlockSubscriber) initialize(ctx context.Context) { - bs.mu.Lock() - defer bs.mu.Unlock() - // initialize the blocks map with the recent blockSize blocks - blocks, err := bs.getBlockRange(ctx) - if err != nil { - bs.lggr.Errorf("failed to get block range; error %v", err) - } - err = bs.initializeBlocks(ctx, blocks) - if err != nil { - bs.lggr.Errorf("failed to get log poller blocks; error %v", err) - } - _, bs.unsubscribe = bs.hb.Subscribe(&headWrapper{headC: bs.headC, lggr: bs.lggr}) -} - -func (bs *BlockSubscriber) Start(ctx context.Context) error { - return bs.StartOnce(BlockSubscriberServiceName, func() error { - bs.lggr.Info("block subscriber started.") - bs.initialize(ctx) - // poll from head broadcaster channel and push to subscribers - bs.threadCtrl.Go(func(ctx context.Context) { - for { - select { - case h := <-bs.headC: - if h != nil { - bs.processHead(h) - } - case <-ctx.Done(): - return - } - } - }) - // cleanup old blocks - bs.threadCtrl.Go(func(ctx context.Context) { - ticker := time.NewTicker(cleanUpInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - bs.cleanup() - case <-ctx.Done(): - return - } - } - }) - - return nil - }) -} - -func (bs *BlockSubscriber) Close() error { - return bs.StopOnce(BlockSubscriberServiceName, func() error { - bs.lggr.Info("stop block subscriber") - bs.threadCtrl.Close() - bs.unsubscribe() - return nil - }) -} - -func (bs *BlockSubscriber) Subscribe() (int, chan ocr2keepers.BlockHistory, error) { - bs.mu.Lock() - defer bs.mu.Unlock() - - bs.maxSubId++ - subId := bs.maxSubId - newC := make(chan ocr2keepers.BlockHistory, channelSize) - bs.subscribers[subId] = newC - bs.lggr.Infof("new subscriber %d", subId) - - return subId, newC, nil -} - -func (bs *BlockSubscriber) Unsubscribe(subId int) error { - bs.mu.Lock() - defer bs.mu.Unlock() - - c, ok := bs.subscribers[subId] - if !ok { - return fmt.Errorf("subscriber %d does not exist", subId) - } - - close(c) - delete(bs.subscribers, subId) - bs.lggr.Infof("subscriber %d unsubscribed", subId) - return nil -} - -func (bs *BlockSubscriber) processHead(h *types.Head) { - bs.mu.Lock() - defer bs.mu.Unlock() - // head parent is a linked list with EVM finality depth - // when re-org happens, new heads will have pointers to the new blocks - i := int64(0) - for cp := h; cp != nil; cp = cp.Parent.Load() { - // we don't stop when a matching (block number/hash) entry is seen in the map because parent linked list may be - // cut short during a re-org if head broadcaster backfill is not complete. This can cause some re-orged blocks - // left in the map. for example, re-org happens for block 98, 99, 100. next head 101 from broadcaster has parent list - // of 100, so block 100 and 101 are updated. when next head 102 arrives, it has full parent history of finality depth. - // if we stop when we see a block number/hash match, we won't look back and correct block 98 and 99. - // hence, we make a compromise here and check previous max(finality depth, blockSize) blocks and update the map. - existingHash, ok := bs.blocks[cp.Number] - if !ok { - bs.lggr.Debugf("filling block %d with new hash %s", cp.Number, cp.Hash.Hex()) - } else if existingHash != cp.Hash.Hex() { - bs.lggr.Warnf("overriding block %d old hash %s with new hash %s due to re-org", cp.Number, existingHash, cp.Hash.Hex()) - } - bs.blocks[cp.Number] = cp.Hash.Hex() - i++ - if i > int64(bs.finalityDepth) || i > bs.blockSize { - break - } - } - bs.lggr.Debugf("blocks block %d hash is %s", h.Number, h.Hash.Hex()) - - history := bs.buildHistory(h.Number) - block := &ocr2keepers.BlockKey{ - Number: ocr2keepers.BlockNumber(h.Number), - } - copy(block.Hash[:], h.Hash[:]) - bs.latestBlock.Store(block) - bs.lastSentBlock = h.Number - // send history to all subscribers - for _, subC := range bs.subscribers { - // wrapped in a select to not get blocked by certain subscribers - select { - case subC <- history: - default: - bs.lggr.Warnf("subscriber channel is full, dropping block history with length %d", len(history)) - } - } - - bs.lggr.Debugf("published block history with length %d and latestBlock %d to %d subscriber(s)", len(history), bs.latestBlock.Load(), len(bs.subscribers)) -} - -func (bs *BlockSubscriber) queryBlocksMap(bn int64) (string, bool) { - bs.mu.RLock() - defer bs.mu.RUnlock() - v, ok := bs.blocks[bn] - return v, ok -} - -type headWrapper struct { - headC chan *types.Head - lggr logger.Logger -} - -func (w *headWrapper) OnNewLongestChain(_ context.Context, head *types.Head) { - if head != nil { - select { - case w.headC <- head: - default: - w.lggr.Debugf("head channel is full, discarding head %+v", head) - } - } -} diff --git a/pkg/automation/v21/block_subscriber_test.go b/pkg/automation/v21/block_subscriber_test.go deleted file mode 100644 index 6c517ffa54..0000000000 --- a/pkg/automation/v21/block_subscriber_test.go +++ /dev/null @@ -1,458 +0,0 @@ -package evm - -import ( - "errors" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-evm/pkg/heads" - "github.com/smartcontractkit/chainlink-evm/pkg/heads/headstest" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" -) - -const historySize = 4 -const blockSize = int64(4) -const finality = uint32(4) - -func TestBlockSubscriber_Subscribe(t *testing.T) { - lggr := logger.Test(t) - var hb heads.Broadcaster - var lp logpoller.LogPoller - - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - subId, _, err := bs.Subscribe() - assert.NoError(t, err) - assert.Equal(t, 1, subId) - subId, _, err = bs.Subscribe() - assert.NoError(t, err) - assert.Equal(t, 2, subId) - subId, _, err = bs.Subscribe() - assert.NoError(t, err) - assert.Equal(t, 3, subId) -} - -func TestBlockSubscriber_Unsubscribe(t *testing.T) { - lggr := logger.Test(t) - var hb heads.Broadcaster - var lp logpoller.LogPoller - - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - subId, _, err := bs.Subscribe() - assert.NoError(t, err) - assert.Equal(t, 1, subId) - subId, _, err = bs.Subscribe() - assert.NoError(t, err) - assert.Equal(t, 2, subId) - err = bs.Unsubscribe(1) - assert.NoError(t, err) -} - -func TestBlockSubscriber_Unsubscribe_Failure(t *testing.T) { - lggr := logger.Test(t) - var hb heads.Broadcaster - var lp logpoller.LogPoller - - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - err := bs.Unsubscribe(2) - assert.Equal(t, "subscriber 2 does not exist", err.Error()) -} - -func TestBlockSubscriber_GetBlockRange(t *testing.T) { - lggr := logger.Test(t) - var hb heads.Broadcaster - - tests := []struct { - Name string - LatestBlock int64 - LatestBlockErr error - ExpectedBlocks []uint64 - }{ - { - Name: "failed to get latest block", - LatestBlockErr: errors.New("failed to get latest block"), - }, - { - Name: "get block range", - LatestBlock: 100, - ExpectedBlocks: []uint64{97, 98, 99, 100}, - }, - } - - for _, tc := range tests { - t.Run(tc.Name, func(t *testing.T) { - lp := mocks.NewLogPoller(t) - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: tc.LatestBlock}, tc.LatestBlockErr) - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - blocks, err := bs.getBlockRange(t.Context()) - - if tc.LatestBlockErr != nil { - assert.Equal(t, tc.LatestBlockErr.Error(), err.Error()) - } else { - assert.Equal(t, tc.ExpectedBlocks, blocks) - } - }) - } -} - -func TestBlockSubscriber_InitializeBlocks(t *testing.T) { - lggr := logger.Test(t) - var hb heads.Broadcaster - - tests := []struct { - Name string - Blocks []uint64 - PollerBlocks []logpoller.Block - LastClearedBlock int64 - Error error - }{ - { - Name: "failed to get latest block", - Error: errors.New("failed to get log poller blocks"), - }, - { - Name: "get block range", - Blocks: []uint64{97, 98, 99, 100}, - PollerBlocks: []logpoller.Block{ - { - BlockNumber: 97, - BlockHash: common.HexToHash("0x5e7fadfc14e1cfa9c05a91128c16a20c6cbc3be38b4723c3d482d44bf9c0e07b"), - }, - { - BlockNumber: 98, - BlockHash: common.HexToHash("0xaf3f8b36a27837e9f1ea3b4da7cdbf2ce0bdf7ef4e87d23add83b19438a2fcba"), - }, - { - BlockNumber: 99, - BlockHash: common.HexToHash("0xa7ac5bbc905b81f3a2ad9fb8ef1fe45f4a95768df456736952e4ec6c21296abe"), - }, - { - BlockNumber: 100, - BlockHash: common.HexToHash("0xa7ac5bbc905b81f3a2ad9fb8ef1fe45f4a95768df456736952e4ec6c21296abe"), - }, - }, - LastClearedBlock: 96, - }, - } - - for _, tc := range tests { - t.Run(tc.Name, func(t *testing.T) { - lp := mocks.NewLogPoller(t) - lp.On("GetBlocksRange", mock.Anything, tc.Blocks).Return(tc.PollerBlocks, tc.Error) - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - err := bs.initializeBlocks(t.Context(), tc.Blocks) - - if tc.Error != nil { - assert.Equal(t, tc.Error.Error(), err.Error()) - } else { - for _, b := range tc.PollerBlocks { - h, ok := bs.blocks[b.BlockNumber] - assert.True(t, ok) - assert.Equal(t, b.BlockHash.Hex(), h) - } - assert.Equal(t, tc.LastClearedBlock, bs.lastClearedBlock) - } - }) - } -} - -func TestBlockSubscriber_BuildHistory(t *testing.T) { - lggr := logger.Test(t) - var hb heads.Broadcaster - lp := mocks.NewLogPoller(t) - - tests := []struct { - Name string - Blocks map[int64]string - Block int64 - ExpectedHistory ocr2keepers.BlockHistory - }{ - { - Name: "build history", - Blocks: map[int64]string{ - 100: "0xaf3f8b36a27837e9f1ea3b4da7cdbf2ce0bdf7ef4e87d23add83b19438a2fcba", - 98: "0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0", - 97: "0xa7ac5bbc905b81f3a2ad9fb8ef1fe45f4a95768df456736952e4ec6c21296abe", - 95: "0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0", - }, - Block: 100, - ExpectedHistory: ocr2keepers.BlockHistory{ - ocr2keepers.BlockKey{ - Number: 100, - Hash: common.HexToHash("0xaf3f8b36a27837e9f1ea3b4da7cdbf2ce0bdf7ef4e87d23add83b19438a2fcba"), - }, - ocr2keepers.BlockKey{ - Number: 98, - Hash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - }, - ocr2keepers.BlockKey{ - Number: 97, - Hash: common.HexToHash("0xa7ac5bbc905b81f3a2ad9fb8ef1fe45f4a95768df456736952e4ec6c21296abe"), - }, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.Name, func(t *testing.T) { - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - bs.blocks = tc.Blocks - - history := bs.buildHistory(tc.Block) - assert.Equal(t, history, tc.ExpectedHistory) - }) - } -} - -func TestBlockSubscriber_Cleanup(t *testing.T) { - lggr := logger.Test(t) - var hb heads.Broadcaster - lp := mocks.NewLogPoller(t) - - tests := []struct { - Name string - Blocks map[int64]string - LastClearedBlock int64 - LastSentBlock int64 - ExpectedLastClearedBlock int64 - ExpectedBlocks map[int64]string - }{ - { - Name: "build history", - Blocks: map[int64]string{ - 102: "0xaf3f8b36a27837e9f1ea3b4da7cdbf2ce0bdf7ef4e87d23add83b19438a2fcba", - 100: "0xaf3f8b36a27837e9f1ea3b4da7cdbf2ce0bdf7ef4e87d23add83b19438a2fcba", - 98: "0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0", - 95: "0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0", - }, - LastClearedBlock: 94, - LastSentBlock: 101, - ExpectedLastClearedBlock: 97, - ExpectedBlocks: map[int64]string{ - 102: "0xaf3f8b36a27837e9f1ea3b4da7cdbf2ce0bdf7ef4e87d23add83b19438a2fcba", - 100: "0xaf3f8b36a27837e9f1ea3b4da7cdbf2ce0bdf7ef4e87d23add83b19438a2fcba", - 98: "0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.Name, func(t *testing.T) { - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - bs.blocks = tc.Blocks - bs.lastClearedBlock = tc.LastClearedBlock - bs.lastSentBlock = tc.LastSentBlock - bs.cleanup() - - assert.Equal(t, tc.ExpectedLastClearedBlock, bs.lastClearedBlock) - assert.Equal(t, tc.ExpectedBlocks, bs.blocks) - }) - } -} - -func TestBlockSubscriber_Start(t *testing.T) { - lggr := logger.Test(t) - hb := headstest.NewBroadcaster[*evmtypes.Head, common.Hash](t) - hb.On("Subscribe", mock.Anything).Return(&evmtypes.Head{Number: 42}, func() {}) - lp := mocks.NewLogPoller(t) - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: 100}, nil) - blocks := []uint64{97, 98, 99, 100} - pollerBlocks := []logpoller.Block{ - { - BlockNumber: 97, - BlockHash: common.HexToHash("0xda2f9d1359eadd7b93338703adc07d942021a78195564038321ef53f23f87333"), - }, - { - BlockNumber: 98, - BlockHash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - }, - { - BlockNumber: 99, - BlockHash: common.HexToHash("0x9bc2b51e147f9cad05f1614b7f1d8181cb24c544cbcf841f3155e54e752a3b44"), - }, - { - BlockNumber: 100, - BlockHash: common.HexToHash("0x5e7fadfc14e1cfa9c05a91128c16a20c6cbc3be38b4723c3d482d44bf9c0e07b"), - }, - } - - lp.On("GetBlocksRange", mock.Anything, blocks).Return(pollerBlocks, nil) - - bs := NewBlockSubscriber(hb, lp, finality, lggr) - bs.blockHistorySize = historySize - bs.blockSize = blockSize - err := bs.Start(t.Context()) - assert.NoError(t, err) - - h97 := evmtypes.Head{ - Number: 97, - Hash: common.HexToHash("0xda2f9d1359eadd7b93338703adc07d942021a78195564038321ef53f23f87333"), - } - h98 := evmtypes.Head{ - Number: 98, - Hash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - } - h98.Parent.Store(&h97) - h99 := evmtypes.Head{ - Number: 99, - Hash: common.HexToHash("0x9bc2b51e147f9cad05f1614b7f1d8181cb24c544cbcf841f3155e54e752a3b44"), - } - h99.Parent.Store(&h98) - h100 := evmtypes.Head{ - Number: 100, - Hash: common.HexToHash("0x5e7fadfc14e1cfa9c05a91128c16a20c6cbc3be38b4723c3d482d44bf9c0e07b"), - } - h100.Parent.Store(&h99) - - // no subscribers yet - bs.headC <- &h100 - - expectedBlocks := map[int64]string{ - 97: "0xda2f9d1359eadd7b93338703adc07d942021a78195564038321ef53f23f87333", - 98: "0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0", - 99: "0x9bc2b51e147f9cad05f1614b7f1d8181cb24c544cbcf841f3155e54e752a3b44", - 100: "0x5e7fadfc14e1cfa9c05a91128c16a20c6cbc3be38b4723c3d482d44bf9c0e07b", - } - - // sleep 100 milli to wait for the go routine to finish - time.Sleep(100 * time.Millisecond) - assert.Equal(t, int64(historySize), bs.blockHistorySize) - assert.Equal(t, int64(96), bs.lastClearedBlock) - assert.Equal(t, int64(100), bs.lastSentBlock) - assert.Equal(t, expectedBlocks, bs.blocks) - - // add 1 subscriber - subId1, c1, err := bs.Subscribe() - assert.NoError(t, err) - assert.Equal(t, 1, subId1) - - h101 := &evmtypes.Head{ - Number: 101, - Hash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - } - h101.Parent.Store(&h100) - bs.headC <- h101 - - time.Sleep(100 * time.Millisecond) - bk1 := <-c1 - assert.Equal(t, ocr2keepers.BlockHistory{ - ocr2keepers.BlockKey{ - Number: 101, - Hash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - }, - ocr2keepers.BlockKey{ - Number: 100, - Hash: common.HexToHash("0x5e7fadfc14e1cfa9c05a91128c16a20c6cbc3be38b4723c3d482d44bf9c0e07b"), - }, - ocr2keepers.BlockKey{ - Number: 99, - Hash: common.HexToHash("0x9bc2b51e147f9cad05f1614b7f1d8181cb24c544cbcf841f3155e54e752a3b44"), - }, - ocr2keepers.BlockKey{ - Number: 98, - Hash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - }, - }, bk1) - - // add 2nd subscriber - subId2, c2, err := bs.Subscribe() - assert.NoError(t, err) - assert.Equal(t, 2, subId2) - - // re-org happens - new99 := &evmtypes.Head{ - Number: 99, - Hash: common.HexToHash("0x70c03acc4ddbfb253ba41a25dc13fb21b25da8b63bcd1aa7fb55713d33a36c71"), - } - new99.Parent.Store(&h98) - new100 := &evmtypes.Head{ - Number: 100, - Hash: common.HexToHash("0x8a876b62d252e63e16cf3487db3486c0a7c0a8e06bc3792a3b116c5ca480503f"), - } - new100.Parent.Store(new99) - new101 := &evmtypes.Head{ - Number: 101, - Hash: common.HexToHash("0x41b5842b8847dcf834e39556d2ac51cc7d960a7de9471ec504673d0038fd6c8e"), - } - new101.Parent.Store(new100) - - new102 := &evmtypes.Head{ - Number: 102, - Hash: common.HexToHash("0x9ac1ebc307554cf1bcfcc2a49462278e89d6878d613a33df38a64d0aeac971b5"), - } - new102.Parent.Store(new101) - - bs.headC <- new102 - - time.Sleep(100 * time.Millisecond) - bk1 = <-c1 - assert.Equal(t, - ocr2keepers.BlockHistory{ - ocr2keepers.BlockKey{ - Number: 102, - Hash: common.HexToHash("0x9ac1ebc307554cf1bcfcc2a49462278e89d6878d613a33df38a64d0aeac971b5"), - }, - ocr2keepers.BlockKey{ - Number: 101, - Hash: common.HexToHash("0x41b5842b8847dcf834e39556d2ac51cc7d960a7de9471ec504673d0038fd6c8e"), - }, - ocr2keepers.BlockKey{ - Number: 100, - Hash: common.HexToHash("0x8a876b62d252e63e16cf3487db3486c0a7c0a8e06bc3792a3b116c5ca480503f"), - }, - ocr2keepers.BlockKey{ - Number: 99, - Hash: common.HexToHash("0x70c03acc4ddbfb253ba41a25dc13fb21b25da8b63bcd1aa7fb55713d33a36c71"), - }, - }, - bk1, - ) - - bk2 := <-c2 - assert.Equal(t, - ocr2keepers.BlockHistory{ - ocr2keepers.BlockKey{ - Number: 102, - Hash: common.HexToHash("0x9ac1ebc307554cf1bcfcc2a49462278e89d6878d613a33df38a64d0aeac971b5"), - }, - ocr2keepers.BlockKey{ - Number: 101, - Hash: common.HexToHash("0x41b5842b8847dcf834e39556d2ac51cc7d960a7de9471ec504673d0038fd6c8e"), - }, - ocr2keepers.BlockKey{ - Number: 100, - Hash: common.HexToHash("0x8a876b62d252e63e16cf3487db3486c0a7c0a8e06bc3792a3b116c5ca480503f"), - }, - ocr2keepers.BlockKey{ - Number: 99, - Hash: common.HexToHash("0x70c03acc4ddbfb253ba41a25dc13fb21b25da8b63bcd1aa7fb55713d33a36c71"), - }, - }, - bk2, - ) - - assert.Equal(t, int64(102), bs.lastSentBlock) - assert.Equal(t, int64(96), bs.lastClearedBlock) -} diff --git a/pkg/automation/v21/core/abi.go b/pkg/automation/v21/core/abi.go deleted file mode 100644 index df6201828f..0000000000 --- a/pkg/automation/v21/core/abi.go +++ /dev/null @@ -1,14 +0,0 @@ -package core - -import ( - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - autov2common "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_log_automation" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/streams_lookup_compatible_interface" - "github.com/smartcontractkit/chainlink-evm/pkg/types" -) - -var CompatibleUtilsABI = types.MustGetABI(ac.AutomationCompatibleUtilsABI) -var AutoV2CommonABI = types.MustGetABI(autov2common.IAutomationV21PlusCommonABI) -var StreamsCompatibleABI = types.MustGetABI(streams_lookup_compatible_interface.StreamsLookupCompatibleInterfaceABI) -var ILogAutomationABI = types.MustGetABI(i_log_automation.ILogAutomationABI) diff --git a/pkg/automation/v21/core/interfaces.go b/pkg/automation/v21/core/interfaces.go deleted file mode 100644 index 32d6a389f7..0000000000 --- a/pkg/automation/v21/core/interfaces.go +++ /dev/null @@ -1,12 +0,0 @@ -package core - -import ( - "context" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -// UpkeepStateReader is the interface for reading the current state of upkeeps. -type UpkeepStateReader interface { - SelectByWorkIDs(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) -} diff --git a/pkg/automation/v21/core/mocks/upkeep_state_reader.go b/pkg/automation/v21/core/mocks/upkeep_state_reader.go deleted file mode 100644 index 160a758da3..0000000000 --- a/pkg/automation/v21/core/mocks/upkeep_state_reader.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package mocks - -import ( - context "context" - - automation "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - mock "github.com/stretchr/testify/mock" -) - -// UpkeepStateReader is an autogenerated mock type for the UpkeepStateReader type -type UpkeepStateReader struct { - mock.Mock -} - -type UpkeepStateReader_Expecter struct { - mock *mock.Mock -} - -func (_m *UpkeepStateReader) EXPECT() *UpkeepStateReader_Expecter { - return &UpkeepStateReader_Expecter{mock: &_m.Mock} -} - -// SelectByWorkIDs provides a mock function with given fields: ctx, workIDs -func (_m *UpkeepStateReader) SelectByWorkIDs(ctx context.Context, workIDs ...string) ([]automation.UpkeepState, error) { - _va := make([]interface{}, len(workIDs)) - for _i := range workIDs { - _va[_i] = workIDs[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for SelectByWorkIDs") - } - - var r0 []automation.UpkeepState - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, ...string) ([]automation.UpkeepState, error)); ok { - return rf(ctx, workIDs...) - } - if rf, ok := ret.Get(0).(func(context.Context, ...string) []automation.UpkeepState); ok { - r0 = rf(ctx, workIDs...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]automation.UpkeepState) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, ...string) error); ok { - r1 = rf(ctx, workIDs...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpkeepStateReader_SelectByWorkIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectByWorkIDs' -type UpkeepStateReader_SelectByWorkIDs_Call struct { - *mock.Call -} - -// SelectByWorkIDs is a helper method to define mock.On call -// - ctx context.Context -// - workIDs ...string -func (_e *UpkeepStateReader_Expecter) SelectByWorkIDs(ctx interface{}, workIDs ...interface{}) *UpkeepStateReader_SelectByWorkIDs_Call { - return &UpkeepStateReader_SelectByWorkIDs_Call{Call: _e.mock.On("SelectByWorkIDs", - append([]interface{}{ctx}, workIDs...)...)} -} - -func (_c *UpkeepStateReader_SelectByWorkIDs_Call) Run(run func(ctx context.Context, workIDs ...string)) *UpkeepStateReader_SelectByWorkIDs_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(string) - } - } - run(args[0].(context.Context), variadicArgs...) - }) - return _c -} - -func (_c *UpkeepStateReader_SelectByWorkIDs_Call) Return(_a0 []automation.UpkeepState, _a1 error) *UpkeepStateReader_SelectByWorkIDs_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *UpkeepStateReader_SelectByWorkIDs_Call) RunAndReturn(run func(context.Context, ...string) ([]automation.UpkeepState, error)) *UpkeepStateReader_SelectByWorkIDs_Call { - _c.Call.Return(run) - return _c -} - -// NewUpkeepStateReader creates a new instance of UpkeepStateReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewUpkeepStateReader(t interface { - mock.TestingT - Cleanup(func()) -}) *UpkeepStateReader { - mock := &UpkeepStateReader{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/automation/v21/core/payload.go b/pkg/automation/v21/core/payload.go deleted file mode 100644 index 78dcb1b58d..0000000000 --- a/pkg/automation/v21/core/payload.go +++ /dev/null @@ -1,40 +0,0 @@ -package core - -import ( - "encoding/hex" - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/crypto" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -var ( - ErrInvalidUpkeepID = errors.New("invalid upkeepID") -) - -func UpkeepWorkID(uid ocr2keepers.UpkeepIdentifier, trigger ocr2keepers.Trigger) string { - var triggerExtBytes []byte - if trigger.LogTriggerExtension != nil { - triggerExtBytes = trigger.LogTriggerExtension.LogIdentifier() - } - hash := crypto.Keccak256(append(uid[:], triggerExtBytes...)) - return hex.EncodeToString(hash) -} - -func NewUpkeepPayload(id *big.Int, trigger ocr2keepers.Trigger, checkData []byte) (ocr2keepers.UpkeepPayload, error) { - uid := &ocr2keepers.UpkeepIdentifier{} - ok := uid.FromBigInt(id) - if !ok { - return ocr2keepers.UpkeepPayload{}, ErrInvalidUpkeepID - } - p := ocr2keepers.UpkeepPayload{ - UpkeepID: *uid, - Trigger: trigger, - CheckData: checkData, - } - // set work id based on upkeep id and trigger - p.WorkID = UpkeepWorkID(*uid, trigger) - return p, nil -} diff --git a/pkg/automation/v21/core/payload_test.go b/pkg/automation/v21/core/payload_test.go deleted file mode 100644 index 63de1f25ec..0000000000 --- a/pkg/automation/v21/core/payload_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package core - -import ( - "math/big" - "testing" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -func TestWorkID(t *testing.T) { - tests := []struct { - name string - upkeepID string - trigger ocr2keepers.Trigger - expected string - }{ - { - name: "happy flow no extension", - upkeepID: "12345", - trigger: ocr2keepers.Trigger{ - BlockNumber: 123, - BlockHash: common.HexToHash("0xabcdef"), - }, - expected: "e546b0a52c2879744f6def0fb483d581dc6d205de83af8440456804dd8b62380", - }, - { - name: "empty trigger", - upkeepID: "12345", - trigger: ocr2keepers.Trigger{}, - // same as with no extension - expected: "e546b0a52c2879744f6def0fb483d581dc6d205de83af8440456804dd8b62380", - }, - { - name: "happy flow with extension", - upkeepID: GenUpkeepID(types.LogTrigger, "12345").String(), - trigger: ocr2keepers.Trigger{ - BlockNumber: 123, - BlockHash: common.HexToHash("0xabcdef"), - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - Index: 1, - TxHash: common.HexToHash("0x12345"), - BlockHash: common.HexToHash("0xabcdef"), - }, - }, - expected: "aaa208331dfafff7a681e3358d082a2e78633dd05c8fb2817c391888cadb2912", - }, - { - name: "happy path example from an actual tx", - upkeepID: "57755329819103678328139927896464733492677608573736038892412245689671711489918", - trigger: ocr2keepers.Trigger{ - BlockNumber: 39344455, - BlockHash: common.HexToHash("0xb41258d18cd44ebf7a0d70de011f2bc4a67c9b68e8b6dada864045d8543bb020"), - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - Index: 41, - TxHash: common.HexToHash("0x44079b1b33aff337dbf17b9e12c5724ecab979c50c8201a9814a488ff3e22384"), - BlockHash: common.HexToHash("0xb41258d18cd44ebf7a0d70de011f2bc4a67c9b68e8b6dada864045d8543bb020"), - }, - }, - expected: "ef1b6acac8aa3682a8a08f666a13cfa165f7e811a16ea9fa0817f437fc4d110d", - }, - { - name: "empty upkeepID", - upkeepID: "0", - trigger: ocr2keepers.Trigger{ - BlockNumber: 123, - BlockHash: common.HexToHash("0xabcdef"), - }, - expected: "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // Convert the string to a big.Int - var id big.Int - _, success := id.SetString(tc.upkeepID, 10) - if !success { - t.Fatal("Invalid big integer value") - } - uid := &ocr2keepers.UpkeepIdentifier{} - ok := uid.FromBigInt(&id) - if !ok { - t.Fatal("Invalid upkeep identifier") - } - - res := UpkeepWorkID(*uid, tc.trigger) - assert.Equal(t, tc.expected, res, "UpkeepWorkID mismatch") - }) - } -} - -func TestNewUpkeepPayload(t *testing.T) { - tests := []struct { - name string - upkeepID *big.Int - upkeepType types.UpkeepType - trigger ocr2keepers.Trigger - check []byte - errored bool - workID string - }{ - { - name: "happy flow no extension", - upkeepID: big.NewInt(111), - upkeepType: types.ConditionTrigger, - trigger: ocr2keepers.Trigger{ - BlockNumber: 11, - BlockHash: common.HexToHash("0x11111"), - }, - check: []byte("check-data-111"), - workID: "39f2babe526038520877fc7c33d81accf578af4a06c5fa6b0d038cae36e12711", - }, - { - name: "happy flow with extension", - upkeepID: big.NewInt(111), - upkeepType: types.LogTrigger, - trigger: ocr2keepers.Trigger{ - BlockNumber: 11, - BlockHash: common.HexToHash("0x11111"), - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - Index: 1, - TxHash: common.HexToHash("0x11111"), - }, - }, - check: []byte("check-data-111"), - workID: "d8e7c8907a0b60b637ce71ff4f757edf076e270d52c51f6e4d46a3b0696e0a39", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - payload, err := NewUpkeepPayload( - tc.upkeepID, - tc.trigger, - tc.check, - ) - if tc.errored { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - assert.Equal(t, tc.workID, payload.WorkID) - }) - } -} diff --git a/pkg/automation/v21/core/testutil.go b/pkg/automation/v21/core/testutil.go deleted file mode 100644 index 18f4a73de0..0000000000 --- a/pkg/automation/v21/core/testutil.go +++ /dev/null @@ -1,32 +0,0 @@ -package core - -import ( - "math/big" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/ethereum/go-ethereum/common" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -// GenUpkeepID generates an ocr2keepers.UpkeepIdentifier with a specific UpkeepType and some random string -func GenUpkeepID(uType types.UpkeepType, rand string) ocr2keepers.UpkeepIdentifier { - b := append([]byte{1}, common.LeftPadBytes([]byte{uint8(uType)}, 15)...) - b = append(b, []byte(rand)...) - b = common.RightPadBytes(b, 32-len(b)) - if len(b) > 32 { - b = b[:32] - } - var id [32]byte - copy(id[:], b) - return ocr2keepers.UpkeepIdentifier(id) -} - -// UpkeepIDFromInt converts an int string to ocr2keepers.UpkeepIdentifier -func UpkeepIDFromInt(id string) ocr2keepers.UpkeepIdentifier { - uid := &ocr2keepers.UpkeepIdentifier{} - idInt, _ := big.NewInt(0).SetString(id, 10) - uid.FromBigInt(idInt) - return *uid -} diff --git a/pkg/automation/v21/core/trigger.go b/pkg/automation/v21/core/trigger.go deleted file mode 100644 index db5dda399a..0000000000 --- a/pkg/automation/v21/core/trigger.go +++ /dev/null @@ -1,102 +0,0 @@ -package core - -import ( - "errors" - "fmt" - "math/big" - "strings" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/ethereum/go-ethereum/accounts/abi" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" -) - -type triggerWrapper = ac.IAutomationV21PlusCommonLogTrigger - -var ErrABINotParsable = errors.New("error parsing abi") - -// PackTrigger packs the trigger data according to the upkeep type of the given id. it will remove the first 4 bytes of function selector. -func PackTrigger(id *big.Int, trig triggerWrapper) ([]byte, error) { - var trigger []byte - var err error - - // construct utils abi - utilsABI, err := abi.JSON(strings.NewReader(ac.AutomationCompatibleUtilsABI)) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrABINotParsable, err) - } - - // pack trigger based on upkeep type - upkeepType, ok := getUpkeepTypeFromBigInt(id) - if !ok { - return nil, ErrInvalidUpkeepID - } - switch upkeepType { - case types.ConditionTrigger: - trig := ac.IAutomationV21PlusCommonConditionalTrigger{ - BlockNum: trig.BlockNum, - BlockHash: trig.BlockHash, - } - trigger, err = utilsABI.Pack("_conditionalTrigger", &trig) - case types.LogTrigger: - logTrig := trig - trigger, err = utilsABI.Pack("_logTrigger", &logTrig) - default: - err = fmt.Errorf("unknown trigger type: %d", upkeepType) - } - if err != nil { - return nil, err - } - return trigger[4:], nil -} - -// UnpackTrigger unpacks the trigger from the given raw data, according to the upkeep type of the given id. -func UnpackTrigger(id *big.Int, raw []byte) (triggerWrapper, error) { - // construct utils abi - utilsABI, err := abi.JSON(strings.NewReader(ac.AutomationCompatibleUtilsABI)) - if err != nil { - return triggerWrapper{}, fmt.Errorf("%w: %w", ErrABINotParsable, err) - } - - upkeepType, ok := getUpkeepTypeFromBigInt(id) - if !ok { - return triggerWrapper{}, ErrInvalidUpkeepID - } - switch upkeepType { - case types.ConditionTrigger: - unpacked, err := utilsABI.Methods["_conditionalTrigger"].Inputs.Unpack(raw) - if err != nil { - return triggerWrapper{}, fmt.Errorf("%w: failed to unpack conditional trigger", err) - } - converted, ok := abi.ConvertType(unpacked[0], new(ac.IAutomationV21PlusCommonConditionalTrigger)).(*ac.IAutomationV21PlusCommonConditionalTrigger) - if !ok { - return triggerWrapper{}, errors.New("failed to convert type") - } - triggerW := triggerWrapper{ - BlockNum: converted.BlockNum, - } - copy(triggerW.BlockHash[:], converted.BlockHash[:]) - return triggerW, nil - case types.LogTrigger: - unpacked, err := utilsABI.Methods["_logTrigger"].Inputs.Unpack(raw) - if err != nil { - return triggerWrapper{}, fmt.Errorf("%w: failed to unpack log trigger", err) - } - converted, ok := abi.ConvertType(unpacked[0], new(ac.IAutomationV21PlusCommonLogTrigger)).(*ac.IAutomationV21PlusCommonLogTrigger) - if !ok { - return triggerWrapper{}, errors.New("failed to convert type") - } - triggerW := triggerWrapper{ - BlockNum: converted.BlockNum, - LogIndex: converted.LogIndex, - } - copy(triggerW.BlockHash[:], converted.BlockHash[:]) - copy(triggerW.TxHash[:], converted.TxHash[:]) - copy(triggerW.LogBlockHash[:], converted.LogBlockHash[:]) - return triggerW, nil - default: - return triggerWrapper{}, fmt.Errorf("unknown trigger type: %d", upkeepType) - } -} diff --git a/pkg/automation/v21/core/trigger_test.go b/pkg/automation/v21/core/trigger_test.go deleted file mode 100644 index 0a5d3fba4a..0000000000 --- a/pkg/automation/v21/core/trigger_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package core - -import ( - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/stretchr/testify/assert" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -func TestPackUnpackTrigger(t *testing.T) { - tests := []struct { - name string - id []byte - trigger triggerWrapper - encoded []byte - err error - }{ - { - "happy flow log trigger", - append([]byte{1}, common.LeftPadBytes([]byte{1}, 15)...), - triggerWrapper{ - BlockNum: 1, - BlockHash: common.HexToHash("0x01111111"), - LogIndex: 1, - TxHash: common.HexToHash("0x01111111"), - LogBlockHash: common.HexToHash("0x01111abc"), - }, - func() []byte { - b, _ := hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000001111abc0000000000000000000000000000000000000000000000000000000001111111000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") - return b - }(), - nil, - }, - { - "happy flow conditional trigger", - append([]byte{1}, common.LeftPadBytes([]byte{0}, 15)...), - triggerWrapper{ - BlockNum: 1, - BlockHash: common.HexToHash("0x01111111"), - }, - func() []byte { - b, _ := hexutil.Decode("0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") - return b - }(), - nil, - }, - { - "invalid type", - append([]byte{1}, common.LeftPadBytes([]byte{8}, 15)...), - triggerWrapper{ - BlockNum: 1, - BlockHash: common.HexToHash("0x01111111"), - }, - []byte{}, - fmt.Errorf("unknown trigger type: %d", 8), - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - var idBytes [32]byte - copy(idBytes[:], tc.id) - id := ocr2keepers.UpkeepIdentifier(idBytes) - - encoded, err := PackTrigger(id.BigInt(), tc.trigger) - if tc.err != nil { - assert.EqualError(t, err, tc.err.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.encoded, encoded) - decoded, err := UnpackTrigger(id.BigInt(), encoded) - assert.NoError(t, err) - assert.Equal(t, tc.trigger.BlockNum, decoded.BlockNum) - } - }) - } - - t.Run("unpacking invalid trigger", func(t *testing.T) { - _, err := UnpackTrigger(big.NewInt(0), []byte{1, 2, 3}) - assert.Error(t, err) - }) - - t.Run("unpacking unknown type", func(t *testing.T) { - uid := append([]byte{1}, common.LeftPadBytes([]byte{8}, 15)...) - var idBytes [32]byte - copy(idBytes[:], uid) - id := ocr2keepers.UpkeepIdentifier(idBytes) - decoded, _ := hexutil.Decode("0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") - _, err := UnpackTrigger(id.BigInt(), decoded) - assert.EqualError(t, err, "unknown trigger type: 8") - }) -} diff --git a/pkg/automation/v21/core/type.go b/pkg/automation/v21/core/type.go deleted file mode 100644 index b285db4d8a..0000000000 --- a/pkg/automation/v21/core/type.go +++ /dev/null @@ -1,36 +0,0 @@ -package core - -import ( - "math/big" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -const ( - // upkeepTypeStartIndex is the index where the upkeep type bytes start. - // for 2.1 we use 11 zeros (reserved bytes for future use) - // and 1 byte to represent the type, with index equal upkeepTypeByteIndex - upkeepTypeStartIndex = 4 - // upkeepTypeByteIndex is the index of the byte that holds the upkeep type. - upkeepTypeByteIndex = 15 -) - -// GetUpkeepType returns the upkeep type from the given ID. -// it follows the same logic as the contract, but performs it locally. -func GetUpkeepType(id ocr2keepers.UpkeepIdentifier) types.UpkeepType { - for i := upkeepTypeStartIndex; i < upkeepTypeByteIndex; i++ { - if id[i] != 0 { // old id - return types.ConditionTrigger - } - } - typeByte := id[upkeepTypeByteIndex] - return types.UpkeepType(typeByte) -} - -func getUpkeepTypeFromBigInt(id *big.Int) (types.UpkeepType, bool) { - uid := &ocr2keepers.UpkeepIdentifier{} - ok := uid.FromBigInt(id) - return GetUpkeepType(*uid), ok -} diff --git a/pkg/automation/v21/core/type_test.go b/pkg/automation/v21/core/type_test.go deleted file mode 100644 index 1e4ec201b4..0000000000 --- a/pkg/automation/v21/core/type_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package core - -import ( - "math/big" - "testing" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/stretchr/testify/assert" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -func TestGetUpkeepType(t *testing.T) { - tests := []struct { - name string - upkeepID []byte - upkeepType types.UpkeepType - }{ - { - "zeroed id", - big.NewInt(0).Bytes(), - types.ConditionTrigger, - }, - { - "old id", - []byte("5820911532554020907796191562093071158274499580927271776163559390280294438608"), - types.ConditionTrigger, - }, - { - "condition trigger", - GenUpkeepID(types.ConditionTrigger, "").BigInt().Bytes(), - types.ConditionTrigger, - }, - { - "log trigger", - GenUpkeepID(types.LogTrigger, "111").BigInt().Bytes(), - types.LogTrigger, - }, - { - "log trigger id", - func() []byte { - id, _ := big.NewInt(0).SetString("32329108151019397958065800113404894502874153543356521479058624064899121404671", 10) - return id.Bytes() - }(), - types.LogTrigger, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - uid := ocr2keepers.UpkeepIdentifier{} - copy(uid[:], tc.upkeepID) - assert.Equal(t, tc.upkeepType, GetUpkeepType(uid)) - }) - } -} diff --git a/pkg/automation/v21/core/utils.go b/pkg/automation/v21/core/utils.go deleted file mode 100644 index 81c7160406..0000000000 --- a/pkg/automation/v21/core/utils.go +++ /dev/null @@ -1,26 +0,0 @@ -package core - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-evm/pkg/client" - "github.com/smartcontractkit/chainlink-evm/pkg/types" -) - -// GetTxBlock calls eth_getTransactionReceipt on the eth client to obtain a tx receipt -func GetTxBlock(ctx context.Context, client client.Client, txHash common.Hash) (*big.Int, common.Hash, error) { - receipt := types.Receipt{} - - if err := client.CallContext(ctx, &receipt, "eth_getTransactionReceipt", txHash); err != nil { - return nil, common.Hash{}, err - } - - if receipt.Status != 1 { - return nil, common.Hash{}, nil - } - - return receipt.GetBlockNumber(), receipt.GetBlockHash(), nil -} diff --git a/pkg/automation/v21/core/utils_test.go b/pkg/automation/v21/core/utils_test.go deleted file mode 100644 index da12bc3e4b..0000000000 --- a/pkg/automation/v21/core/utils_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package core - -import ( - "errors" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/smartcontractkit/chainlink-evm/pkg/client/clienttest" - "github.com/smartcontractkit/chainlink-evm/pkg/types" -) - -func TestUtils_GetTxBlock(t *testing.T) { - tests := []struct { - name string - txHash common.Hash - ethCallError error - receipt *types.Receipt - status uint64 - }{ - { - name: "success", - txHash: common.HexToHash("0xc48fbf05edaf18f6aaa7de24de28528546b874bb03728d624ca407b8fed582a3"), - receipt: &types.Receipt{ - Status: 1, - BlockNumber: big.NewInt(2000), - }, - status: 1, - }, - { - name: "failure - eth call error", - txHash: common.HexToHash("0xc48fbf05edaf18f6aaa7de24de28528546b874bb03728d624ca407b8fed582a3"), - ethCallError: errors.New("eth call failed"), - }, - { - name: "failure - tx does not exist", - txHash: common.HexToHash("0xc48fbf05edaf18f6aaa7de24de28528546b874bb03728d624ca407b8fed582a3"), - receipt: &types.Receipt{ - Status: 0, - }, - status: 0, - }, - } - - for _, tt := range tests { - client := new(clienttest.Client) - client.On("CallContext", mock.Anything, mock.Anything, "eth_getTransactionReceipt", tt.txHash). - Return(tt.ethCallError).Run(func(args mock.Arguments) { - receipt := tt.receipt - if receipt != nil { - res := args.Get(1).(*types.Receipt) - res.Status = receipt.Status - res.TxHash = receipt.TxHash - res.BlockNumber = receipt.BlockNumber - res.BlockHash = receipt.BlockHash - } - }) - - bn, bh, err := GetTxBlock(t.Context(), client, tt.txHash) - if tt.ethCallError != nil { - assert.Equal(t, tt.ethCallError, err) - } else { - assert.Equal(t, tt.status, tt.receipt.Status) - assert.Equal(t, tt.receipt.BlockNumber, bn) - assert.Equal(t, tt.receipt.BlockHash, bh) - } - } -} diff --git a/pkg/automation/v21/encoding/encoder.go b/pkg/automation/v21/encoding/encoder.go deleted file mode 100644 index 2d8ebf5648..0000000000 --- a/pkg/automation/v21/encoding/encoder.go +++ /dev/null @@ -1,129 +0,0 @@ -package encoding - -import ( - "errors" - "fmt" - "math/big" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" -) - -var ( - ErrEmptyResults = errors.New("empty results; cannot encode") -) - -type reportEncoder struct { - packer Packer -} - -var _ ocr2keepers.Encoder = (*reportEncoder)(nil) - -func NewReportEncoder(p Packer) ocr2keepers.Encoder { - return &reportEncoder{ - packer: p, - } -} - -func (e reportEncoder) Encode(results ...ocr2keepers.CheckResult) ([]byte, error) { - if len(results) == 0 { - return nil, ErrEmptyResults - } - - report := ac.IAutomationV21PlusCommonReport{ - FastGasWei: big.NewInt(0), - LinkNative: big.NewInt(0), - UpkeepIds: make([]*big.Int, len(results)), - GasLimits: make([]*big.Int, len(results)), - Triggers: make([][]byte, len(results)), - PerformDatas: make([][]byte, len(results)), - } - - encoded := 0 - highestCheckBlock := big.NewInt(0) - - for i, result := range results { - checkBlock := big.NewInt(int64(result.Trigger.BlockNumber)) - - if checkBlock.Cmp(highestCheckBlock) == 1 { - highestCheckBlock = checkBlock - if result.FastGasWei != nil { - report.FastGasWei = result.FastGasWei - } - if result.LinkNative != nil { - report.LinkNative = result.LinkNative - } - } - - id := result.UpkeepID.BigInt() - report.UpkeepIds[i] = id - report.GasLimits[i] = big.NewInt(0).SetUint64(result.GasAllocated) - - triggerW := triggerWrapper{ - BlockNum: uint32(result.Trigger.BlockNumber), - BlockHash: result.Trigger.BlockHash, - } - switch core.GetUpkeepType(result.UpkeepID) { - case types.LogTrigger: - triggerW.TxHash = result.Trigger.LogTriggerExtension.TxHash - triggerW.LogIndex = result.Trigger.LogTriggerExtension.Index - triggerW.LogBlockHash = result.Trigger.LogTriggerExtension.BlockHash - default: - // no special handling here for conditional triggers - } - - trigger, err := core.PackTrigger(id, triggerW) - if err != nil { - return nil, fmt.Errorf("%w: failed to pack trigger", err) - } - - report.Triggers[i] = trigger - report.PerformDatas[i] = result.PerformData - - encoded++ - } - - return e.packer.PackReport(report) -} - -// Extract extracts a slice of reported upkeeps (upkeep id, trigger, and work id) from raw bytes. the plugin will call this function to accept/transmit reports. -func (e reportEncoder) Extract(raw []byte) ([]ocr2keepers.ReportedUpkeep, error) { - report, err := e.packer.UnpackReport(raw) - if err != nil { - return nil, fmt.Errorf("%w: failed to unpack report", err) - } - reportedUpkeeps := make([]ocr2keepers.ReportedUpkeep, len(report.UpkeepIds)) - for i, upkeepId := range report.UpkeepIds { - triggerW, err := core.UnpackTrigger(upkeepId, report.Triggers[i]) - if err != nil { - return nil, fmt.Errorf("%w: failed to unpack trigger", err) - } - id := &ocr2keepers.UpkeepIdentifier{} - id.FromBigInt(upkeepId) - - trigger := ocr2keepers.NewTrigger( - ocr2keepers.BlockNumber(triggerW.BlockNum), - triggerW.BlockHash, - ) - switch core.GetUpkeepType(*id) { - case types.LogTrigger: - trigger.LogTriggerExtension = &ocr2keepers.LogTriggerExtension{} - trigger.LogTriggerExtension.TxHash = triggerW.TxHash - trigger.LogTriggerExtension.Index = triggerW.LogIndex - trigger.LogTriggerExtension.BlockHash = triggerW.LogBlockHash - default: - } - workID := core.UpkeepWorkID(*id, trigger) - reportedUpkeeps[i] = ocr2keepers.ReportedUpkeep{ - WorkID: workID, - UpkeepID: *id, - Trigger: trigger, - } - } - - return reportedUpkeeps, nil -} diff --git a/pkg/automation/v21/encoding/encoder_test.go b/pkg/automation/v21/encoding/encoder_test.go deleted file mode 100644 index acde185668..0000000000 --- a/pkg/automation/v21/encoding/encoder_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package encoding - -import ( - "bytes" - "encoding/hex" - "math/big" - "os" - "testing" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" -) - -var expectedEncodedReport []byte - -func init() { - b, err := os.ReadFile("../fixtures/expected_encoded_report.txt") - if err != nil { - panic(err) - } - expectedEncodedReport, err = hex.DecodeString(string(b)) - if err != nil { - panic(err) - } -} - -func TestReportEncoder_EncodeExtract(t *testing.T) { - encoder := reportEncoder{ - packer: NewAbiPacker(), - } - - tests := []struct { - name string - results []ocr2keepers.CheckResult - reportSize int - expectedFastGasWei int64 - expectedLinkNative int64 - expectedErr error - }{ - { - "happy flow single", - []ocr2keepers.CheckResult{ - newResult(1, 1, core.GenUpkeepID(types.LogTrigger, "123"), 1, 1), - }, - 736, - 1, - 1, - nil, - }, - { - "happy flow multiple", - []ocr2keepers.CheckResult{ - newResult(1, 1, core.GenUpkeepID(types.LogTrigger, "10"), 1, 1), - newResult(1, 1, core.GenUpkeepID(types.ConditionTrigger, "20"), 1, 1), - newResult(1, 1, core.GenUpkeepID(types.ConditionTrigger, "30"), 1, 1), - }, - 1312, - 3, - 3, - nil, - }, - { - "happy flow highest block number first", - []ocr2keepers.CheckResult{ - newResult(1, 1, core.GenUpkeepID(types.ConditionTrigger, "30"), 1, 1), - newResult(1, 1, core.GenUpkeepID(types.ConditionTrigger, "20"), 1, 1), - newResult(1, 1, core.GenUpkeepID(types.LogTrigger, "10"), 1, 1), - }, - 1312, - 1000, - 2000, - nil, - }, - { - "empty results", - []ocr2keepers.CheckResult{}, - 0, - 0, - 0, - ErrEmptyResults, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - b, err := encoder.Encode(tc.results...) - if tc.expectedErr != nil { - assert.Equal(t, tc.expectedErr, err) - return - } - - assert.NoError(t, err) - assert.Len(t, b, tc.reportSize) - - results, err := encoder.Extract(b) - assert.NoError(t, err) - assert.Len(t, results, len(tc.results)) - - for i, r := range results { - assert.Equal(t, r.UpkeepID, tc.results[i].UpkeepID) - assert.Equal(t, r.WorkID, tc.results[i].WorkID) - assert.Equal(t, r.Trigger, tc.results[i].Trigger) - } - }) - } -} - -func TestReportEncoder_BackwardsCompatibility(t *testing.T) { - encoder := reportEncoder{ - packer: NewAbiPacker(), - } - results := []ocr2keepers.CheckResult{ - newResult(1, 2, core.GenUpkeepID(types.LogTrigger, "10"), 5, 6), - newResult(3, 4, core.GenUpkeepID(types.ConditionTrigger, "20"), 7, 8), - } - encoded, err := encoder.Encode(results...) - assert.NoError(t, err) - if !bytes.Equal(encoded, expectedEncodedReport) { - assert.Fail(t, - "encoded report does not match expected encoded report; "+ - "this means a breaking change has been made to the report encoding function; "+ - "only update this test if non-backwards-compatible changes are necessary", - ) - } -} - -func newResult(block int64, checkBlock ocr2keepers.BlockNumber, id ocr2keepers.UpkeepIdentifier, fastGasWei, linkNative int64) ocr2keepers.CheckResult { - tp := core.GetUpkeepType(id) - - trig := ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(block), - BlockHash: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, - } - - if tp == types.LogTrigger { - trig.LogTriggerExtension = &ocr2keepers.LogTriggerExtension{ - Index: 1, - TxHash: common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234"), - BlockHash: common.HexToHash("0xaaaaaaaa90123456789012345678901234567890123456789012345678901234"), - } - } - - payload, _ := core.NewUpkeepPayload( - id.BigInt(), - trig, - []byte{}, - ) - - return ocr2keepers.CheckResult{ - UpkeepID: id, - Trigger: payload.Trigger, - WorkID: payload.WorkID, - Eligible: true, - GasAllocated: 100, - PerformData: []byte("data0"), - FastGasWei: big.NewInt(fastGasWei), - LinkNative: big.NewInt(linkNative), - } -} diff --git a/pkg/automation/v21/encoding/interface.go b/pkg/automation/v21/encoding/interface.go deleted file mode 100644 index 6a10e9ddca..0000000000 --- a/pkg/automation/v21/encoding/interface.go +++ /dev/null @@ -1,97 +0,0 @@ -package encoding - -import ( - "net/http" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - autov2common "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" -) - -type UpkeepFailureReason uint8 -type PipelineExecutionState uint8 - -const ( - // upkeep failure onchain reasons - UpkeepFailureReasonNone UpkeepFailureReason = 0 - UpkeepFailureReasonUpkeepCancelled UpkeepFailureReason = 1 - UpkeepFailureReasonUpkeepPaused UpkeepFailureReason = 2 - UpkeepFailureReasonTargetCheckReverted UpkeepFailureReason = 3 - UpkeepFailureReasonUpkeepNotNeeded UpkeepFailureReason = 4 - UpkeepFailureReasonPerformDataExceedsLimit UpkeepFailureReason = 5 - UpkeepFailureReasonInsufficientBalance UpkeepFailureReason = 6 - UpkeepFailureReasonMercuryCallbackReverted UpkeepFailureReason = 7 - UpkeepFailureReasonRevertDataExceedsLimit UpkeepFailureReason = 8 - UpkeepFailureReasonRegistryPaused UpkeepFailureReason = 9 - // leaving a gap here for more onchain failure reasons in the future - // upkeep failure offchain reasons - UpkeepFailureReasonMercuryAccessNotAllowed UpkeepFailureReason = 32 - UpkeepFailureReasonTxHashNoLongerExists UpkeepFailureReason = 33 - UpkeepFailureReasonInvalidRevertDataInput UpkeepFailureReason = 34 - UpkeepFailureReasonSimulationFailed UpkeepFailureReason = 35 - UpkeepFailureReasonTxHashReorged UpkeepFailureReason = 36 - UpkeepFailureReasonGasPriceTooHigh UpkeepFailureReason = 37 - - // pipeline execution error - NoPipelineError PipelineExecutionState = 0 - CheckBlockTooOld PipelineExecutionState = 1 - CheckBlockInvalid PipelineExecutionState = 2 - RpcFlakyFailure PipelineExecutionState = 3 - MercuryFlakyFailure PipelineExecutionState = 4 - PackUnpackDecodeFailed PipelineExecutionState = 5 - PrivilegeConfigUnmarshalError PipelineExecutionState = 6 -) - -// ErrCode is used for invoking an error handler with a specific error code. -type ErrCode uint32 - -const ( - ErrCodeNil ErrCode = 0 - ErrCodeStreamsPartialContent ErrCode = 808206 - ErrCodeStreamsBadRequest ErrCode = 808400 - ErrCodeStreamsUnauthorized ErrCode = 808401 - ErrCodeStreamsNotFound ErrCode = 808404 - ErrCodeStreamsInternalError ErrCode = 808500 - ErrCodeStreamsBadGateway ErrCode = 808502 - ErrCodeStreamsServiceUnavailable ErrCode = 808503 - ErrCodeStreamsStatusGatewayTimeout ErrCode = 808504 - ErrCodeStreamsBadResponse ErrCode = 808600 - ErrCodeStreamsTimeout ErrCode = 808601 - ErrCodeStreamsUnknownError ErrCode = 808700 -) - -func HttpToStreamsErrCode(statusCode int) ErrCode { - switch statusCode { - case http.StatusOK: - return ErrCodeNil - case http.StatusPartialContent: - return ErrCodeStreamsPartialContent - case http.StatusBadRequest: - return ErrCodeStreamsBadRequest - case http.StatusUnauthorized: - return ErrCodeStreamsUnauthorized - case http.StatusNotFound: - return ErrCodeStreamsNotFound - case http.StatusInternalServerError: - return ErrCodeStreamsInternalError - case http.StatusBadGateway: - return ErrCodeStreamsBadGateway - case http.StatusServiceUnavailable: - return ErrCodeStreamsServiceUnavailable - case http.StatusGatewayTimeout: - return ErrCodeStreamsStatusGatewayTimeout - default: - return ErrCodeStreamsUnknownError - } -} - -type UpkeepInfo = autov2common.IAutomationV21PlusCommonUpkeepInfoLegacy - -type Packer interface { - UnpackCheckResult(payload ocr2keepers.UpkeepPayload, raw string) (ocr2keepers.CheckResult, error) - UnpackPerformResult(raw string) (PipelineExecutionState, bool, error) - UnpackLogTriggerConfig(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) - PackReport(report ac.IAutomationV21PlusCommonReport) ([]byte, error) - UnpackReport(raw []byte) (ac.IAutomationV21PlusCommonReport, error) -} diff --git a/pkg/automation/v21/encoding/packer.go b/pkg/automation/v21/encoding/packer.go deleted file mode 100644 index 373e9f1bdc..0000000000 --- a/pkg/automation/v21/encoding/packer.go +++ /dev/null @@ -1,149 +0,0 @@ -package encoding - -import ( - "errors" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common/hexutil" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" -) - -// triggerWrapper is a wrapper for the different trigger types (log and condition triggers). -// NOTE: we use log trigger because it extends condition trigger, -type triggerWrapper = ac.IAutomationV21PlusCommonLogTrigger - -type abiPacker struct { - autoV2CommonABI abi.ABI - utilsABI abi.ABI - streamsABI abi.ABI -} - -var _ Packer = (*abiPacker)(nil) - -func NewAbiPacker() *abiPacker { - return &abiPacker{autoV2CommonABI: core.AutoV2CommonABI, utilsABI: core.CompatibleUtilsABI, streamsABI: core.StreamsCompatibleABI} -} - -func (p *abiPacker) UnpackCheckResult(payload ocr2keepers.UpkeepPayload, raw string) (ocr2keepers.CheckResult, error) { - b, err := hexutil.Decode(raw) - if err != nil { - // decode failed, not retryable - return GetIneligibleCheckResultWithoutPerformData(payload, UpkeepFailureReasonNone, PackUnpackDecodeFailed, false), - fmt.Errorf("upkeepId %s failed to decode checkUpkeep result %s: %w", payload.UpkeepID.String(), raw, err) - } - - out, err := p.autoV2CommonABI.Methods["checkUpkeep"].Outputs.UnpackValues(b) - if err != nil { - // unpack failed, not retryable - return GetIneligibleCheckResultWithoutPerformData(payload, UpkeepFailureReasonNone, PackUnpackDecodeFailed, false), - fmt.Errorf("upkeepId %s failed to unpack checkUpkeep result %s: %w", payload.UpkeepID.String(), raw, err) - } - - result := ocr2keepers.CheckResult{ - Eligible: *abi.ConvertType(out[0], new(bool)).(*bool), - Retryable: false, - GasAllocated: uint64((*abi.ConvertType(out[4], new(*big.Int)).(**big.Int)).Int64()), - UpkeepID: payload.UpkeepID, - Trigger: payload.Trigger, - WorkID: payload.WorkID, - FastGasWei: *abi.ConvertType(out[5], new(*big.Int)).(**big.Int), - LinkNative: *abi.ConvertType(out[6], new(*big.Int)).(**big.Int), - IneligibilityReason: *abi.ConvertType(out[2], new(uint8)).(*uint8), - } - - rawPerformData := *abi.ConvertType(out[1], new([]byte)).(*[]byte) - - // if NONE we expect the perform data. if TARGET_CHECK_REVERTED we will have the error data in the perform data used for off chain lookup - if result.IneligibilityReason == uint8(UpkeepFailureReasonNone) || (result.IneligibilityReason == uint8(UpkeepFailureReasonTargetCheckReverted) && len(rawPerformData) > 0) { - result.PerformData = rawPerformData - } - - return result, nil -} - -func (p *abiPacker) UnpackPerformResult(raw string) (PipelineExecutionState, bool, error) { - b, err := hexutil.Decode(raw) - if err != nil { - return PackUnpackDecodeFailed, false, err - } - - out, err := p.autoV2CommonABI.Methods["simulatePerformUpkeep"].Outputs.UnpackValues(b) - if err != nil { - return PackUnpackDecodeFailed, false, err - } - - return NoPipelineError, *abi.ConvertType(out[0], new(bool)).(*bool), nil -} - -// UnpackLogTriggerConfig unpacks the log trigger config from the given raw data -func (p *abiPacker) UnpackLogTriggerConfig(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) { - var cfg ac.IAutomationV21PlusCommonLogTriggerConfig - - out, err := core.CompatibleUtilsABI.Methods["_logTriggerConfig"].Inputs.UnpackValues(raw) - if err != nil { - return cfg, fmt.Errorf("%w: unpack _logTriggerConfig return: %s", err, raw) - } - - converted, ok := abi.ConvertType(out[0], new(ac.IAutomationV21PlusCommonLogTriggerConfig)).(*ac.IAutomationV21PlusCommonLogTriggerConfig) - if !ok { - return cfg, errors.New("failed to convert type during UnpackLogTriggerConfig") - } - return *converted, nil -} - -// PackReport packs the report with abi definitions from the contract. -func (p *abiPacker) PackReport(report ac.IAutomationV21PlusCommonReport) ([]byte, error) { - bts, err := p.utilsABI.Methods["_report"].Inputs.Pack(&report) - if err != nil { - return nil, fmt.Errorf("%w: failed to pack report", err) - } - return bts, nil -} - -// UnpackReport unpacks the report from the given raw data. -func (p *abiPacker) UnpackReport(raw []byte) (ac.IAutomationV21PlusCommonReport, error) { - unpacked, err := p.utilsABI.Methods["_report"].Inputs.Unpack(raw) - if err != nil { - return ac.IAutomationV21PlusCommonReport{}, fmt.Errorf("%w: failed to unpack report", err) - } - converted, ok := abi.ConvertType(unpacked[0], new(ac.IAutomationV21PlusCommonReport)).(*ac.IAutomationV21PlusCommonReport) - if !ok { - return ac.IAutomationV21PlusCommonReport{}, errors.New("failed to convert type") - } - report := ac.IAutomationV21PlusCommonReport{ - FastGasWei: converted.FastGasWei, - LinkNative: converted.LinkNative, - UpkeepIds: make([]*big.Int, len(converted.UpkeepIds)), - GasLimits: make([]*big.Int, len(converted.GasLimits)), - Triggers: make([][]byte, len(converted.Triggers)), - PerformDatas: make([][]byte, len(converted.PerformDatas)), - } - if len(report.UpkeepIds) > 0 { - copy(report.UpkeepIds, converted.UpkeepIds) - copy(report.GasLimits, converted.GasLimits) - copy(report.Triggers, converted.Triggers) - copy(report.PerformDatas, converted.PerformDatas) - } - - return report, nil -} - -// GetIneligibleCheckResultWithoutPerformData returns an ineligible check result with ineligibility reason and pipeline execution state but without perform data -func GetIneligibleCheckResultWithoutPerformData(p ocr2keepers.UpkeepPayload, reason UpkeepFailureReason, state PipelineExecutionState, retryable bool) ocr2keepers.CheckResult { - return ocr2keepers.CheckResult{ - IneligibilityReason: uint8(reason), - PipelineExecutionState: uint8(state), - Retryable: retryable, - UpkeepID: p.UpkeepID, - Trigger: p.Trigger, - WorkID: p.WorkID, - FastGasWei: big.NewInt(0), - LinkNative: big.NewInt(0), - } -} diff --git a/pkg/automation/v21/encoding/packer_test.go b/pkg/automation/v21/encoding/packer_test.go deleted file mode 100644 index 50286c0485..0000000000 --- a/pkg/automation/v21/encoding/packer_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package encoding - -import ( - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" -) - -func TestPacker_PackReport(t *testing.T) { - for _, tc := range []struct { - name string - report ac.IAutomationV21PlusCommonReport - expectsErr bool - wantErr error - wantBytes int - }{ - { - name: "all non-nil values get encoded to a byte array of a specific length", - report: ac.IAutomationV21PlusCommonReport{ - FastGasWei: big.NewInt(0), - LinkNative: big.NewInt(0), - UpkeepIds: []*big.Int{big.NewInt(3)}, - GasLimits: []*big.Int{big.NewInt(4)}, - Triggers: [][]byte{ - {5}, - }, - PerformDatas: [][]byte{ - {6}, - }, - }, - wantBytes: 608, - }, - { - name: "if upkeep IDs are nil, the packed report is smaller", - report: ac.IAutomationV21PlusCommonReport{ - FastGasWei: big.NewInt(1), - LinkNative: big.NewInt(2), - UpkeepIds: nil, - GasLimits: []*big.Int{big.NewInt(4)}, - Triggers: [][]byte{ - {5}, - }, - PerformDatas: [][]byte{ - {6}, - }, - }, - wantBytes: 576, - }, - { - name: "if gas limits are nil, the packed report is smaller", - report: ac.IAutomationV21PlusCommonReport{ - FastGasWei: big.NewInt(1), - LinkNative: big.NewInt(2), - UpkeepIds: []*big.Int{big.NewInt(3)}, - GasLimits: nil, - Triggers: [][]byte{ - {5}, - }, - PerformDatas: [][]byte{ - {6}, - }, - }, - wantBytes: 576, - }, - { - name: "if perform datas are nil, the packed report is smaller", - report: ac.IAutomationV21PlusCommonReport{ - FastGasWei: big.NewInt(1), - LinkNative: big.NewInt(2), - UpkeepIds: []*big.Int{big.NewInt(3)}, - GasLimits: []*big.Int{big.NewInt(4)}, - Triggers: [][]byte{ - {5}, - }, - PerformDatas: nil, - }, - wantBytes: 512, - }, - { - name: "if triggers are nil, the packed report is smaller", - report: ac.IAutomationV21PlusCommonReport{ - FastGasWei: big.NewInt(1), - LinkNative: big.NewInt(2), - UpkeepIds: []*big.Int{big.NewInt(3)}, - GasLimits: []*big.Int{big.NewInt(4)}, - Triggers: nil, - PerformDatas: [][]byte{ - {6}, - }, - }, - wantBytes: 512, - }, - } { - t.Run(tc.name, func(t *testing.T) { - packer := NewAbiPacker() - bytes, err := packer.PackReport(tc.report) - if tc.expectsErr { - assert.Error(t, err) - assert.Equal(t, tc.wantErr.Error(), err.Error()) - } else { - assert.NoError(t, err) - assert.Len(t, bytes, tc.wantBytes) - } - }) - } -} - -func TestPacker_UnpackCheckResults(t *testing.T) { - uid, _ := new(big.Int).SetString("1843548457736589226156809205796175506139185429616502850435279853710366065936", 10) - upkeepId := ocr2keepers.UpkeepIdentifier{} - upkeepId.FromBigInt(uid) - p1, _ := core.NewUpkeepPayload(uid, ocr2keepers.NewTrigger(19447615, common.HexToHash("0x0")), []byte{}) - - p2, _ := core.NewUpkeepPayload(uid, ocr2keepers.NewTrigger(19448272, common.HexToHash("0x0")), []byte{}) - - tests := []struct { - Name string - Payload ocr2keepers.UpkeepPayload - RawData string - ExpectedResult ocr2keepers.CheckResult - ExpectedError error - }{ - { - Name: "upkeep not needed", - Payload: p1, - RawData: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000421c000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000c8caf37f3b3890000000000000000000000000000000000000000000000000000000000000000", - ExpectedResult: ocr2keepers.CheckResult{ - UpkeepID: upkeepId, - Eligible: false, - IneligibilityReason: uint8(UpkeepFailureReasonUpkeepNotNeeded), - Trigger: ocr2keepers.NewLogTrigger(ocr2keepers.BlockNumber(19447615), [32]byte{}, nil), - WorkID: "e54c524132d9c8d87b7e43b76f6d769face19ffd2ff93fc24f123dd745d3ce1e", - PerformData: nil, - FastGasWei: big.NewInt(1000000000), - LinkNative: big.NewInt(3532383906411401), - }, - }, - { - Name: "target check reverted", - Payload: p2, - RawData: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000007531000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000c8caf37f3b3890000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000008914039bf676e20aad43a5642485e666575ed0d927a4b5679745e947e7d125ee2687c10000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000024462e8a50d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000128c1d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000009666565644944537472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000184554482d5553442d415242495452554d2d544553544e4554000000000000000000000000000000000000000000000000000000000000000000000000000000184254432d5553442d415242495452554d2d544553544e45540000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d6265720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000", - ExpectedResult: ocr2keepers.CheckResult{ - UpkeepID: upkeepId, - Eligible: false, - IneligibilityReason: uint8(UpkeepFailureReasonTargetCheckReverted), - Trigger: ocr2keepers.NewLogTrigger(ocr2keepers.BlockNumber(19448272), [32]byte{}, nil), - WorkID: "e54c524132d9c8d87b7e43b76f6d769face19ffd2ff93fc24f123dd745d3ce1e", - PerformData: []byte{98, 232, 165, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 40, 193, 208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 102, 101, 101, 100, 73, 68, 83, 116, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 69, 84, 72, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 66, 84, 67, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 98, 108, 111, 99, 107, 78, 117, 109, 98, 101, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - FastGasWei: big.NewInt(1000000000), - LinkNative: big.NewInt(3532383906411401), - }, - }, - { - Name: "decode failed", - Payload: p2, - RawData: "invalid_raw_data", - ExpectedResult: ocr2keepers.CheckResult{ - UpkeepID: upkeepId, - PipelineExecutionState: uint8(PackUnpackDecodeFailed), - Trigger: p2.Trigger, - WorkID: p2.WorkID, - }, - ExpectedError: fmt.Errorf("upkeepId %s failed to decode checkUpkeep result invalid_raw_data: hex string without 0x prefix", p2.UpkeepID.String()), - }, - { - Name: "unpack failed", - Payload: p2, - RawData: "0x123123", - ExpectedResult: ocr2keepers.CheckResult{ - UpkeepID: upkeepId, - PipelineExecutionState: uint8(PackUnpackDecodeFailed), - Trigger: p2.Trigger, - WorkID: p2.WorkID, - }, - ExpectedError: fmt.Errorf("upkeepId %s failed to unpack checkUpkeep result 0x123123: abi: cannot marshal in to go type: length insufficient 3 require 32", p2.UpkeepID.String()), - }, - } - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - packer := NewAbiPacker() - rs, err := packer.UnpackCheckResult(test.Payload, test.RawData) - if test.ExpectedError != nil { - assert.Equal(t, test.ExpectedError.Error(), err.Error()) - } else { - assert.NoError(t, err) - } - assert.Equal(t, test.ExpectedResult.UpkeepID, rs.UpkeepID) - assert.Equal(t, test.ExpectedResult.Eligible, rs.Eligible) - assert.Equal(t, test.ExpectedResult.Trigger, rs.Trigger) - assert.Equal(t, test.ExpectedResult.WorkID, rs.WorkID) - assert.Equal(t, test.ExpectedResult.IneligibilityReason, rs.IneligibilityReason) - assert.Equal(t, test.ExpectedResult.PipelineExecutionState, rs.PipelineExecutionState) - }) - } -} - -func TestPacker_UnpackPerformResult(t *testing.T) { - tests := []struct { - Name string - RawData string - State PipelineExecutionState - }{ - { - Name: "unpack success", - RawData: "0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000a52d", - State: NoPipelineError, - }, - } - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - packer := NewAbiPacker() - state, rs, err := packer.UnpackPerformResult(test.RawData) - assert.NoError(t, err) - assert.True(t, rs) - assert.Equal(t, test.State, state) - }) - } -} - -func TestPacker_UnpackLogTriggerConfig(t *testing.T) { - tests := []struct { - name string - raw []byte - res ac.IAutomationV21PlusCommonLogTriggerConfig - errored bool - }{ - { - "happy flow", - func() []byte { - b, _ := hexutil.Decode("0x0000000000000000000000007456fadf415b7c34b1182bd20b0537977e945e3e00000000000000000000000000000000000000000000000000000000000000003d53a39550e04688065827f3bb86584cb007ab9ebca7ebd528e7301c9c31eb5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - return b - }(), - ac.IAutomationV21PlusCommonLogTriggerConfig{ - ContractAddress: common.HexToAddress("0x7456FadF415b7c34B1182Bd20B0537977e945e3E"), - Topic0: [32]uint8{0x3d, 0x53, 0xa3, 0x95, 0x50, 0xe0, 0x46, 0x88, 0x6, 0x58, 0x27, 0xf3, 0xbb, 0x86, 0x58, 0x4c, 0xb0, 0x7, 0xab, 0x9e, 0xbc, 0xa7, 0xeb, 0xd5, 0x28, 0xe7, 0x30, 0x1c, 0x9c, 0x31, 0xeb, 0x5d}, - }, - false, - }, - { - "invalid", - func() []byte { - b, _ := hexutil.Decode("0x000000000000000000000000b1182bd20b0537977e945e3e00000000000000000000000000000000000000000000000000000000000000003d53a39550e04688065827f3bb86584cb007ab9ebca7ebd528e7301c9c31eb5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - return b - }(), - ac.IAutomationV21PlusCommonLogTriggerConfig{}, - true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - packer := NewAbiPacker() - res, err := packer.UnpackLogTriggerConfig(tc.raw) - if tc.errored { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.res, res) - } - }) - } -} - -func TestPacker_PackReport_UnpackReport(t *testing.T) { - report := ac.IAutomationV21PlusCommonReport{ - FastGasWei: big.NewInt(1), - LinkNative: big.NewInt(1), - UpkeepIds: []*big.Int{big.NewInt(1), big.NewInt(2)}, - GasLimits: []*big.Int{big.NewInt(100), big.NewInt(200)}, - Triggers: [][]byte{{1, 2, 3, 4}, {5, 6, 7, 8}}, - PerformDatas: [][]byte{{5, 6, 7, 8}, {1, 2, 3, 4}}, - } - packer := NewAbiPacker() - res, err := packer.PackReport(report) - require.NoError(t, err) - report2, err := packer.UnpackReport(res) - require.NoError(t, err) - assert.Equal(t, report, report2) - expected := "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405060708000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004050607080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000" - assert.Equal(t, expected, hexutil.Encode(res)) -} diff --git a/pkg/automation/v21/fixtures/expected_encoded_report.txt b/pkg/automation/v21/fixtures/expected_encoded_report.txt deleted file mode 100644 index 3fb42146c8..0000000000 --- a/pkg/automation/v21/fixtures/expected_encoded_report.txt +++ /dev/null @@ -1 +0,0 @@ -00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000020100000000000000000000000000000131300000000000000000000000000000010000000000000000000000000000003230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a0aaaaaaaa9012345678901234567890123456789012345678901234567890123412345678901234567890123456789012345678901234567890123456789012340000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000101020304050607080102030405060708010203040506070801020304050607080000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000301020304050607080102030405060708010203040506070801020304050607080000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005646174613000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056461746130000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/pkg/automation/v21/gasprice/gasprice.go b/pkg/automation/v21/gasprice/gasprice.go deleted file mode 100644 index d87ad1455f..0000000000 --- a/pkg/automation/v21/gasprice/gasprice.go +++ /dev/null @@ -1,74 +0,0 @@ -package gasprice - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - "github.com/smartcontractkit/chainlink-evm/pkg/assets" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/cbor" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/gas" -) - -const ( - // feeLimit is a placeholder when getting current price from gas estimator. it does not impact gas price calculation - feeLimit = uint64(1_000_000) - // maxFeePrice is a placeholder when getting current price from gas estimator. it caps the returned gas price from - // the estimator. it's set to a very high value because the gas price will be compared with user-defined gas price - // later. - maxFeePrice = 1_000_000_000_000_000 -) - -type UpkeepOffchainConfig struct { - MaxGasPrice *big.Int `json:"maxGasPrice" cbor:"maxGasPrice"` -} - -// CheckGasPrice retrieves the current gas price and compare against the max gas price configured in upkeep's offchain config -// any errors in offchain config decoding will result in max gas price check disabled -func CheckGasPrice(ctx context.Context, upkeepId *big.Int, offchainConfigBytes []byte, ge gas.EvmFeeEstimator, lggr logger.Logger) encoding.UpkeepFailureReason { - // check for empty offchain config - if len(offchainConfigBytes) == 0 { - return encoding.UpkeepFailureReasonNone - } - - var offchainConfig UpkeepOffchainConfig - if err := cbor.ParseDietCBORToStruct(offchainConfigBytes, &offchainConfig); err != nil { - lggr.Warnw("failed to parse upkeep offchain config, gas price check is disabled", "offchainconfig", hexutil.Encode(offchainConfigBytes), "upkeepId", upkeepId.String(), "err", err) - return encoding.UpkeepFailureReasonNone - } - if offchainConfig.MaxGasPrice == nil || offchainConfig.MaxGasPrice.Int64() <= 0 { - lggr.Debugw("maxGasPrice is not configured or incorrectly configured in upkeep offchain config, gas price check is disabled", "offchainconfig", hexutil.Encode(offchainConfigBytes), "upkeepId", upkeepId.String()) - return encoding.UpkeepFailureReasonNone - } - lggr.Debugf("successfully decode offchain config for %s, max gas price is %s", upkeepId.String(), offchainConfig.MaxGasPrice.String()) - - fee, _, err := ge.GetFee(ctx, []byte{}, feeLimit, assets.NewWei(big.NewInt(maxFeePrice)), nil, nil) - if err != nil { - lggr.Errorw("failed to get fee, gas price check is disabled", "upkeepId", upkeepId.String(), "err", err) - return encoding.UpkeepFailureReasonNone - } - - if fee.ValidDynamic() { - lggr.Debugf("current gas price EIP-1559 is fee cap %s, tip cap %s", fee.GasFeeCap.String(), fee.GasTipCap.String()) - if fee.GasFeeCap.Cmp(assets.NewWei(offchainConfig.MaxGasPrice)) > 0 { - // current gas price is higher than max gas price - lggr.Warnf("maxGasPrice %s for %s is LOWER than current gas price %d", offchainConfig.MaxGasPrice.String(), upkeepId.String(), fee.GasFeeCap.Int64()) - return encoding.UpkeepFailureReasonGasPriceTooHigh - } - lggr.Debugf("maxGasPrice %s for %s is HIGHER than current gas price %d", offchainConfig.MaxGasPrice.String(), upkeepId.String(), fee.GasFeeCap.Int64()) - } else { - lggr.Debugf("current gas price legacy is %s", fee.GasPrice.String()) - if fee.GasPrice.Cmp(assets.NewWei(offchainConfig.MaxGasPrice)) > 0 { - // current gas price is higher than max gas price - lggr.Warnf("maxGasPrice %s for %s is LOWER than current gas price %d", offchainConfig.MaxGasPrice.String(), upkeepId.String(), fee.GasPrice.Int64()) - return encoding.UpkeepFailureReasonGasPriceTooHigh - } - lggr.Debugf("maxGasPrice %s for %s is HIGHER than current gas price %d", offchainConfig.MaxGasPrice.String(), upkeepId.String(), fee.GasPrice.Int64()) - } - - return encoding.UpkeepFailureReasonNone -} diff --git a/pkg/automation/v21/gasprice/gasprice_test.go b/pkg/automation/v21/gasprice/gasprice_test.go deleted file mode 100644 index da75a9d581..0000000000 --- a/pkg/automation/v21/gasprice/gasprice_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package gasprice - -import ( - "math/big" - "testing" - - "github.com/fxamacker/cbor/v2" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-evm/pkg/assets" - "github.com/smartcontractkit/chainlink-evm/pkg/gas" - gasMocks "github.com/smartcontractkit/chainlink-evm/pkg/gas/mocks" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" -) - -type WrongOffchainConfig struct { - MaxGasPrice1 []int `json:"maxGasPrice1" cbor:"maxGasPrice1"` -} - -func TestGasPrice_Check(t *testing.T) { - lggr := logger.Test(t) - uid, _ := new(big.Int).SetString("1843548457736589226156809205796175506139185429616502850435279853710366065936", 10) - - tests := []struct { - Name string - MaxGasPrice *big.Int - CurrentLegacyGasPrice *big.Int - CurrentDynamicGasPrice *big.Int - ExpectedResult encoding.UpkeepFailureReason - FailedToGetFee bool - NotConfigured bool - ParsingFailed bool - }{ - { - Name: "no offchain config", - ExpectedResult: encoding.UpkeepFailureReasonNone, - }, - { - Name: "maxGasPrice not configured in offchain config", - NotConfigured: true, - ExpectedResult: encoding.UpkeepFailureReasonNone, - }, - { - Name: "fail to parse offchain config", - ParsingFailed: true, - MaxGasPrice: big.NewInt(10_000_000_000), - ExpectedResult: encoding.UpkeepFailureReasonNone, - }, - { - Name: "fail to retrieve current gas price", - MaxGasPrice: big.NewInt(8_000_000_000), - FailedToGetFee: true, - ExpectedResult: encoding.UpkeepFailureReasonNone, - }, - { - Name: "current gas price is too high - legacy", - MaxGasPrice: big.NewInt(10_000_000_000), - CurrentLegacyGasPrice: big.NewInt(18_000_000_000), - ExpectedResult: encoding.UpkeepFailureReasonGasPriceTooHigh, - }, - { - Name: "current gas price is too high - dynamic", - MaxGasPrice: big.NewInt(10_000_000_000), - CurrentDynamicGasPrice: big.NewInt(15_000_000_000), - ExpectedResult: encoding.UpkeepFailureReasonGasPriceTooHigh, - }, - { - Name: "current gas price is less than user's max gas price - legacy", - MaxGasPrice: big.NewInt(8_000_000_000), - CurrentLegacyGasPrice: big.NewInt(5_000_000_000), - ExpectedResult: encoding.UpkeepFailureReasonNone, - }, - { - Name: "current gas price is less than user's max gas price - dynamic", - MaxGasPrice: big.NewInt(10_000_000_000), - CurrentDynamicGasPrice: big.NewInt(8_000_000_000), - ExpectedResult: encoding.UpkeepFailureReasonNone, - }, - } - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - ctx := t.Context() - ge := gasMocks.NewEvmFeeEstimator(t) - if test.FailedToGetFee { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - gas.EvmFee{}, - feeLimit, - errors.New("failed to retrieve gas price"), - ) - } else if test.CurrentLegacyGasPrice != nil { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - gas.EvmFee{ - GasPrice: assets.NewWei(test.CurrentLegacyGasPrice), - }, - feeLimit, - nil, - ) - } else if test.CurrentDynamicGasPrice != nil { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - gas.EvmFee{ - DynamicFee: gas.DynamicFee{GasFeeCap: assets.NewWei(test.CurrentDynamicGasPrice), GasTipCap: assets.NewWei(big.NewInt(1_000_000_000))}, - }, - feeLimit, - nil, - ) - } - - var oc []byte - if test.ParsingFailed { - oc, _ = cbor.Marshal(WrongOffchainConfig{MaxGasPrice1: []int{1, 2, 3}}) - if len(oc) > 0 { - oc[len(oc)-1] = 0x99 - } - } else if test.NotConfigured { - oc = []byte{1, 2, 3, 4} // parsing this will set maxGasPrice field to nil - } else if test.MaxGasPrice != nil { - oc, _ = cbor.Marshal(UpkeepOffchainConfig{MaxGasPrice: test.MaxGasPrice}) - } - fr := CheckGasPrice(ctx, uid, oc, ge, lggr) - assert.Equal(t, test.ExpectedResult, fr) - }) - } -} diff --git a/pkg/automation/v21/keyring.go b/pkg/automation/v21/keyring.go deleted file mode 100644 index 2d84f09637..0000000000 --- a/pkg/automation/v21/keyring.go +++ /dev/null @@ -1,50 +0,0 @@ -package evm - -import ( - "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" -) - -var _ ocr3types.OnchainKeyring[plugin.AutomationReportInfo] = &onchainKeyringV3Wrapper{} - -type onchainKeyringV3Wrapper struct { - core types.OnchainKeyring -} - -func NewOnchainKeyringV3Wrapper(keyring types.OnchainKeyring) *onchainKeyringV3Wrapper { - return &onchainKeyringV3Wrapper{ - core: keyring, - } -} - -func (w *onchainKeyringV3Wrapper) PublicKey() types.OnchainPublicKey { - return w.core.PublicKey() -} - -func (w *onchainKeyringV3Wrapper) MaxSignatureLength() int { - return w.core.MaxSignatureLength() -} - -func (w *onchainKeyringV3Wrapper) Sign(digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[plugin.AutomationReportInfo]) (signature []byte, err error) { - rCtx := types.ReportContext{ - ReportTimestamp: types.ReportTimestamp{ - ConfigDigest: digest, - Epoch: uint32(seqNr), - }, - } - - return w.core.Sign(rCtx, r.Report) -} - -func (w *onchainKeyringV3Wrapper) Verify(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[plugin.AutomationReportInfo], signature []byte) bool { - rCtx := types.ReportContext{ - ReportTimestamp: types.ReportTimestamp{ - ConfigDigest: digest, - Epoch: uint32(seqNr), - }, - } - - return w.core.Verify(key, rCtx, r.Report, signature) -} diff --git a/pkg/automation/v21/keyring_test.go b/pkg/automation/v21/keyring_test.go deleted file mode 100644 index 92a05c2d76..0000000000 --- a/pkg/automation/v21/keyring_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package evm - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" -) - -func TestNewOnchainKeyringV3Wrapper(t *testing.T) { - t.Run("the on chain keyring wrapper gets the public key and max signature length from the wrapped keyring", func(t *testing.T) { - onchainKeyring := &mockOnchainKeyring{ - MaxSignatureLengthFn: func() int { - return 123 - }, - PublicKeyFn: func() types.OnchainPublicKey { - return types.OnchainPublicKey([]byte("abcdef")) - }, - } - keyring := NewOnchainKeyringV3Wrapper(onchainKeyring) - assert.Equal(t, 123, keyring.MaxSignatureLength()) - assert.Equal(t, types.OnchainPublicKey([]byte("abcdef")), keyring.PublicKey()) - }) - - t.Run("a report context is created and the wrapped keyring signs the report", func(t *testing.T) { - onchainKeyring := &mockOnchainKeyring{ - SignFn: func(context types.ReportContext, report types.Report) (signature []byte, err error) { - assert.Equal(t, types.ReportContext{ - ReportTimestamp: types.ReportTimestamp{ - ConfigDigest: types.ConfigDigest([32]byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}), - Epoch: 101, - Round: 0, - }, - ExtraHash: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, context) - assert.Equal(t, types.Report([]byte("a report to sign")), report) - return []byte("signature"), err - }, - } - keyring := NewOnchainKeyringV3Wrapper(onchainKeyring) - signed, err := keyring.Sign( - types.ConfigDigest([32]byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}), - 101, - ocr3types.ReportWithInfo[plugin.AutomationReportInfo]{ - Report: []byte("a report to sign"), - Info: plugin.AutomationReportInfo{}, - }, - ) - assert.NoError(t, err) - assert.Equal(t, []byte("signature"), signed) - }) - - t.Run("a report context is created and the wrapped keyring verifies the report", func(t *testing.T) { - onchainKeyring := &mockOnchainKeyring{ - VerifyFn: func(pk types.OnchainPublicKey, rc types.ReportContext, r types.Report, signature []byte) bool { - assert.Equal(t, types.OnchainPublicKey([]byte("key")), pk) - assert.Equal(t, types.ReportContext{ - ReportTimestamp: types.ReportTimestamp{ - ConfigDigest: types.ConfigDigest([32]byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}), - Epoch: 999, - Round: 0, - }, - ExtraHash: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, rc) - assert.Equal(t, types.Report([]byte("a report to sign")), r) - assert.Equal(t, []byte("signature"), signature) - return true - }, - } - keyring := NewOnchainKeyringV3Wrapper(onchainKeyring) - valid := keyring.Verify( - types.OnchainPublicKey([]byte("key")), - types.ConfigDigest([32]byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}), - 999, - ocr3types.ReportWithInfo[plugin.AutomationReportInfo]{ - Report: []byte("a report to sign"), - Info: plugin.AutomationReportInfo{}, - }, - []byte("signature"), - ) - assert.True(t, valid) - }) -} - -type mockOnchainKeyring struct { - PublicKeyFn func() types.OnchainPublicKey - SignFn func(types.ReportContext, types.Report) (signature []byte, err error) - VerifyFn func(_ types.OnchainPublicKey, _ types.ReportContext, _ types.Report, signature []byte) bool - MaxSignatureLengthFn func() int -} - -func (k *mockOnchainKeyring) PublicKey() types.OnchainPublicKey { - return k.PublicKeyFn() -} - -func (k *mockOnchainKeyring) Sign(c types.ReportContext, r types.Report) (signature []byte, err error) { - return k.SignFn(c, r) -} - -func (k *mockOnchainKeyring) Verify(pk types.OnchainPublicKey, c types.ReportContext, r types.Report, signature []byte) bool { - return k.VerifyFn(pk, c, r, signature) -} - -func (k *mockOnchainKeyring) MaxSignatureLength() int { - return k.MaxSignatureLengthFn() -} diff --git a/pkg/automation/v21/logprovider/block_time.go b/pkg/automation/v21/logprovider/block_time.go deleted file mode 100644 index 988e0c8f6e..0000000000 --- a/pkg/automation/v21/logprovider/block_time.go +++ /dev/null @@ -1,61 +0,0 @@ -package logprovider - -import ( - "context" - "fmt" - "sort" - "time" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -var ( - defaultSampleSize = int64(10000) - defaultBlockTime = time.Second * 1 -) - -type blockTimeResolver struct { - poller logpoller.LogPoller -} - -func newBlockTimeResolver(poller logpoller.LogPoller) *blockTimeResolver { - return &blockTimeResolver{ - poller: poller, - } -} - -func (r *blockTimeResolver) BlockTime(ctx context.Context, blockSampleSize int64) (time.Duration, error) { - if blockSampleSize < 2 { // min 2 blocks range - blockSampleSize = defaultSampleSize - } - - latest, err := r.poller.LatestBlock(ctx) - if err != nil { - return 0, fmt.Errorf("failed to get latest block from poller: %w", err) - } - latestBlockNumber := latest.BlockNumber - if latestBlockNumber <= blockSampleSize { - return defaultBlockTime, nil - } - start, end := latestBlockNumber-blockSampleSize, latestBlockNumber - startTime, endTime, err := r.getSampleTimestamps(ctx, uint64(start), uint64(end)) - if err != nil { - return 0, err - } - - return endTime.Sub(startTime) / time.Duration(blockSampleSize), nil -} - -func (r *blockTimeResolver) getSampleTimestamps(ctx context.Context, start, end uint64) (time.Time, time.Time, error) { - blocks, err := r.poller.GetBlocksRange(ctx, []uint64{start, end}) - if err != nil { - return time.Time{}, time.Time{}, fmt.Errorf("failed to get block range from poller: %w", err) - } - sort.Slice(blocks, func(i, j int) bool { - return blocks[i].BlockNumber < blocks[j].BlockNumber - }) - if len(blocks) < 2 { - return time.Time{}, time.Time{}, fmt.Errorf("failed to fetch blocks %d, %d from log poller", start, end) - } - return blocks[0].BlockTimestamp, blocks[1].BlockTimestamp, nil -} diff --git a/pkg/automation/v21/logprovider/block_time_test.go b/pkg/automation/v21/logprovider/block_time_test.go deleted file mode 100644 index 3de819eecb..0000000000 --- a/pkg/automation/v21/logprovider/block_time_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package logprovider - -import ( - "errors" - "testing" - "time" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - lpmocks "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" -) - -func TestBlockTimeResolver_BlockTime(t *testing.T) { - now := time.Now() - - tests := []struct { - name string - blockSampleSize int64 - latestBlock int64 - latestBlockErr error - blocksRange []logpoller.Block - blocksRangeErr error - blockTime time.Duration - blockTimeErr error - }{ - { - "latest block err", - 10, - 0, - errors.New("test err"), - nil, - nil, - 0, - errors.New("test err"), - }, - { - "block range err", - 10, - 20, - nil, - nil, - errors.New("test err"), - 0, - errors.New("test err"), - }, - { - "2 sec block time", - 4, - 20, - nil, - []logpoller.Block{ - {BlockTimestamp: now.Add(-time.Second * (2 * 4)), BlockNumber: 16}, - {BlockTimestamp: now, BlockNumber: 20}, - }, - nil, - 2 * time.Second, - nil, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := t.Context() - - lp := new(lpmocks.LogPoller) - resolver := newBlockTimeResolver(lp) - - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: tc.latestBlock}, tc.latestBlockErr) - lp.On("GetBlocksRange", mock.Anything, mock.Anything).Return(tc.blocksRange, tc.blocksRangeErr) - - blockTime, err := resolver.BlockTime(ctx, tc.blockSampleSize) - if tc.blockTimeErr != nil { - require.Error(t, err) - return - } - require.Equal(t, tc.blockTime, blockTime) - }) - } -} diff --git a/pkg/automation/v21/logprovider/buffer_v1.go b/pkg/automation/v21/logprovider/buffer_v1.go deleted file mode 100644 index 9f45da4b9b..0000000000 --- a/pkg/automation/v21/logprovider/buffer_v1.go +++ /dev/null @@ -1,551 +0,0 @@ -package logprovider - -import ( - "math/big" - "slices" - "sort" - "sync" - "sync/atomic" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/prommetrics" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -type BufferedLog struct { - ID *big.Int - Log logpoller.Log -} - -type LogBuffer interface { - // Enqueue adds logs to the buffer and might also drop logs if the limit for the - // given upkeep was exceeded. Returns the number of logs that were added and number of logs that were dropped. - Enqueue(id *big.Int, logs ...logpoller.Log) (added int, dropped int) - // Dequeue pulls logs from the buffer that are within the given block window, - // with a maximum number of logs to return. - // It also accepts a boolean to identify if we are operating under minimum dequeue. - // Returns logs (associated to upkeeps) and the number of remaining - // logs in that window for the involved upkeeps. - Dequeue(startWindowBlock int64, maxResults int, minimumDequeue bool) ([]BufferedLog, int) - // SetConfig sets the buffer size and the maximum number of logs to keep for each upkeep. - SetConfig(lookback, blockRate, logLimit uint32) - // NumOfUpkeeps returns the number of upkeeps that are being tracked by the buffer. - NumOfUpkeeps() int - // SyncFilters removes upkeeps that are not in the filter store. - SyncFilters(filterStore UpkeepFilterStore) error -} - -type logBufferOptions struct { - // number of blocks to keep in the buffer - lookback *atomic.Uint32 - // blockRate is the number of blocks per window - blockRate *atomic.Uint32 - // max number of logs to keep in the buffer for each upkeep per window (LogLimit*10) - windowLimit *atomic.Uint32 - // number of logs we need to dequeue per upkeep per block window at a minimum - logLimit *atomic.Uint32 -} - -func newLogBufferOptions(lookback, blockRate, logLimit uint32) *logBufferOptions { - opts := &logBufferOptions{ - windowLimit: new(atomic.Uint32), - lookback: new(atomic.Uint32), - blockRate: new(atomic.Uint32), - logLimit: new(atomic.Uint32), - } - opts.override(lookback, blockRate, logLimit) - - return opts -} - -func (o *logBufferOptions) override(lookback, blockRate, logLimit uint32) { - o.windowLimit.Store(logLimit * 10) - o.lookback.Store(lookback) - o.blockRate.Store(blockRate) - o.logLimit.Store(logLimit) -} - -type logBuffer struct { - lggr logger.Logger - opts *logBufferOptions - // last block number seen by the buffer - lastBlockSeen *atomic.Int64 - // map of upkeep id to its queue - queues map[string]*upkeepLogQueue - queueIDs []string - blockHashes map[int64]string - - lock sync.RWMutex -} - -func NewLogBuffer(lggr logger.Logger, lookback, blockRate, logLimit uint32) LogBuffer { - return &logBuffer{ - lggr: logger.Sugared(lggr).Named("KeepersRegistry").Named("LogEventBufferV1"), - opts: newLogBufferOptions(lookback, blockRate, logLimit), - lastBlockSeen: new(atomic.Int64), - queueIDs: []string{}, - blockHashes: map[int64]string{}, - queues: make(map[string]*upkeepLogQueue), - } -} - -// Enqueue adds logs to the buffer and might also drop logs if the limit for the -// given upkeep was exceeded. It will create a new buffer if it does not exist. -// Logs are expected to be enqueued in increasing order of block number. -// All logs for an upkeep on a particular block will be enqueued in a single Enqueue call. -// Returns the number of logs that were added and number of logs that were dropped. -func (b *logBuffer) Enqueue(uid *big.Int, logs ...logpoller.Log) (int, int) { - b.lock.Lock() - defer b.lock.Unlock() - - buf, ok := b.getUpkeepQueue(uid) - if !ok || buf == nil { - buf = newUpkeepLogQueue(b.lggr, uid, b.opts) - b.setUpkeepQueue(uid, buf) - } - - latestLogBlock, reorgBlocks := b.blockStatistics(logs...) - - if len(reorgBlocks) > 0 { - b.evictReorgdLogs(reorgBlocks) - } - - if lastBlockSeen := b.lastBlockSeen.Load(); lastBlockSeen < latestLogBlock { - b.lastBlockSeen.Store(latestLogBlock) - } else if latestLogBlock < lastBlockSeen { - b.lggr.Debugw("enqueuing logs with a latest block older older than latest seen block", "logBlock", latestLogBlock, "lastBlockSeen", lastBlockSeen) - } - - blockThreshold := b.lastBlockSeen.Load() - int64(b.opts.lookback.Load()) - blockThreshold, _ = getBlockWindow(blockThreshold, int(b.opts.blockRate.Load())) - if blockThreshold <= 0 { - blockThreshold = 1 - } - - return buf.enqueue(blockThreshold, logs...) -} - -// blockStatistics returns the latest block number from the given logs, and updates any blocks that have been reorgd -func (b *logBuffer) blockStatistics(logs ...logpoller.Log) (int64, map[int64]bool) { - var latest int64 - reorgBlocks := map[int64]bool{} - - for _, l := range logs { - if l.BlockNumber > latest { - latest = l.BlockNumber - } - if hash, ok := b.blockHashes[l.BlockNumber]; ok { - if hash != l.BlockHash.String() { - reorgBlocks[l.BlockNumber] = true - b.lggr.Debugw("encountered reorgd block", "blockNumber", l.BlockNumber) - } - } - b.blockHashes[l.BlockNumber] = l.BlockHash.String() - } - - return latest, reorgBlocks -} - -func (b *logBuffer) evictReorgdLogs(reorgBlocks map[int64]bool) { - for blockNumber := range reorgBlocks { - start, _ := getBlockWindow(blockNumber, int(b.opts.blockRate.Load())) - for _, queue := range b.queues { - if _, ok := queue.logs[blockNumber]; ok { - queue.logs[blockNumber] = []logpoller.Log{} - queue.dequeued[start] = 0 - } - } - } -} - -// Dequeue greedly pulls logs from the buffers. -// Returns logs and the number of remaining logs in the buffer. -func (b *logBuffer) Dequeue(startWindowBlock int64, maxResults int, bestEffort bool) ([]BufferedLog, int) { - b.lock.RLock() - defer b.lock.RUnlock() - - return b.dequeue(startWindowBlock, maxResults, bestEffort) -} - -// dequeue pulls logs from the buffers, in block range [start,end] with minimum number -// of results per upkeep (upkeepLimit) and the maximum number of results (capacity). -// If operating under minimum dequeue, upkeeps are skipped when the minimum number -// of logs have been dequeued for that upkeep. -// Returns logs and the number of remaining logs in the buffer for the given range and selector. -// NOTE: this method is not thread safe and should be called within a lock. -func (b *logBuffer) dequeue(start int64, capacity int, minimumDequeue bool) ([]BufferedLog, int) { - var result []BufferedLog - var remainingLogs int - minimumDequeueMet := 0 - - logLimit := int(b.opts.logLimit.Load()) - end := start + int64(b.opts.blockRate.Load()) - - for _, qid := range b.queueIDs { - q := b.queues[qid] - - if minimumDequeue && q.getDequeued(start) >= logLimit { - // if we have already dequeued the minimum commitment for this window, skip it - minimumDequeueMet++ - continue - } - - logsInRange := q.sizeOfRange(start, end) - if logsInRange == 0 { - // if there are no logs in the range, skip the upkeep - continue - } - if capacity == 0 { - // if there is no more capacity for results, just count the remaining logs - remainingLogs += logsInRange - continue - } - - var logs []logpoller.Log - remaining := 0 - - if minimumDequeue { - logs, remaining = q.dequeue(start, end, min(capacity, logLimit-q.getDequeued(start))) - } else { - logs, remaining = q.dequeue(start, end, capacity) - } - - for _, l := range logs { - result = append(result, BufferedLog{ID: q.id, Log: l}) - capacity-- - } - remainingLogs += remaining - - // update the buffer with how many logs we have dequeued for this window - q.addDequeued(start, len(logs)) - } - b.lggr.Debugw("minimum commitment logs dequeued", "start", start, "end", end, "numUpkeeps", len(b.queues), "minimumDequeueMet", minimumDequeueMet) - return result, remainingLogs -} - -func (b *logBuffer) SetConfig(lookback, blockRate, logLimit uint32) { - b.lock.Lock() - defer b.lock.Unlock() - - b.opts.override(lookback, blockRate, logLimit) -} - -func (b *logBuffer) NumOfUpkeeps() int { - b.lock.RLock() - defer b.lock.RUnlock() - - return len(b.queues) -} - -func (b *logBuffer) SyncFilters(filterStore UpkeepFilterStore) error { - b.lock.Lock() - defer b.lock.Unlock() - - var newQueueIDs []string - - for _, upkeepID := range b.queueIDs { - uid := new(big.Int) - _, ok := uid.SetString(upkeepID, 10) - if ok && !filterStore.Has(uid) { - // remove upkeep that is not in the filter store - delete(b.queues, upkeepID) - } else { - newQueueIDs = append(newQueueIDs, upkeepID) - } - } - - b.queueIDs = newQueueIDs - - return nil -} - -func (b *logBuffer) getUpkeepQueue(uid *big.Int) (*upkeepLogQueue, bool) { - ub, ok := b.queues[uid.String()] - return ub, ok -} - -func (b *logBuffer) setUpkeepQueue(uid *big.Int, buf *upkeepLogQueue) { - if _, ok := b.queues[uid.String()]; !ok { - b.queueIDs = append(b.queueIDs, uid.String()) - slices.Sort(b.queueIDs) - } - b.queues[uid.String()] = buf -} - -// TODO (AUTO-9256) separate files - -// logTriggerState represents the state of a log in the buffer. -type logTriggerState uint8 - -const ( - // the log was dropped due to buffer limits - logTriggerStateDropped logTriggerState = iota - // the log was enqueued by the buffer - logTriggerStateEnqueued - // the log was visited/dequeued from the buffer - logTriggerStateDequeued -) - -// logTriggerStateEntry represents the state of a log in the buffer and the block number of the log. -// TODO (AUTO-10013) handling of reorgs might require to store the block hash as well. -type logTriggerStateEntry struct { - state logTriggerState - block int64 -} - -// upkeepLogQueue is a priority queue for logs associated to a specific upkeep. -// It keeps track of the logs that were already visited and the capacity of the queue. -type upkeepLogQueue struct { - lggr logger.Logger - - id *big.Int - opts *logBufferOptions - - // logs is the buffer of logs for the upkeep - logs map[int64][]logpoller.Log - blockNumbers []int64 - - // states keeps track of the state of the logs that are known to the queue - // and the block number they were seen at - states map[string]logTriggerStateEntry - dequeued map[int64]int - lock sync.RWMutex -} - -func newUpkeepLogQueue(lggr logger.Logger, id *big.Int, opts *logBufferOptions) *upkeepLogQueue { - return &upkeepLogQueue{ - lggr: logger.With(lggr, "upkeepID", id.String()), - id: id, - opts: opts, - logs: map[int64][]logpoller.Log{}, - blockNumbers: make([]int64, 0), - states: make(map[string]logTriggerStateEntry), - dequeued: map[int64]int{}, - } -} - -// sizeOfRange returns the number of logs in the buffer that are within the given block range. -func (q *upkeepLogQueue) sizeOfRange(start, end int64) int { - q.lock.RLock() - defer q.lock.RUnlock() - - size := 0 - for blockNumber, logs := range q.logs { - if blockNumber >= start && blockNumber <= end { - size += len(logs) - } - } - return size -} - -func (q *upkeepLogQueue) getDequeued(k int64) int { - q.lock.RLock() - defer q.lock.RUnlock() - return q.dequeued[k] -} - -func (q *upkeepLogQueue) addDequeued(k int64, add int) { - q.lock.Lock() - defer q.lock.Unlock() - q.dequeued[k] += add -} - -// dequeue pulls logs from the buffer that are within the given block range, -// with a limit of logs to pull. Returns logs and the number of remaining logs in the buffer. -func (q *upkeepLogQueue) dequeue(start, end int64, limit int) ([]logpoller.Log, int) { - q.lock.Lock() - defer q.lock.Unlock() - - if len(q.logs) == 0 { - return nil, 0 - } - - var results []logpoller.Log - var remaining int - - for blockNumber := start; blockNumber <= end; blockNumber++ { - updatedLogs := make([]logpoller.Log, 0) - blockResults := 0 - for _, l := range q.logs[blockNumber] { - if len(results) < limit { - results = append(results, l) - lid := logID(l) - if s, ok := q.states[lid]; ok { - s.state = logTriggerStateDequeued - q.states[lid] = s - } - blockResults++ - } else { - remaining++ - updatedLogs = append(updatedLogs, l) - } - } - if blockResults > 0 { - q.logs[blockNumber] = updatedLogs - } - } - - if len(results) > 0 { - q.lggr.Debugw("Dequeued logs", "start", start, "end", end, "limit", limit, "results", len(results), "remaining", remaining) - } - - prommetrics.AutomationLogBufferFlow.WithLabelValues(prommetrics.LogBufferFlowDirectionEgress).Add(float64(len(results))) - - return results, remaining -} - -// enqueue adds logs to the buffer and might also drop logs if the limit for the -// given upkeep was exceeded. Additionally, it will drop logs that are older than blockThreshold. -// Returns the number of logs that were added and number of logs that were dropped. -func (q *upkeepLogQueue) enqueue(blockThreshold int64, logsToAdd ...logpoller.Log) (int, int) { - var added int - for _, log := range logsToAdd { - if log.BlockNumber < blockThreshold { - // q.lggr.Debugw("Skipping log from old block", "blockThreshold", blockThreshold, "logBlock", log.BlockNumber, "logIndex", log.LogIndex) - continue - } - lid := logID(log) - if _, ok := q.states[lid]; ok { - // q.lggr.Debugw("Skipping known log", "blockThreshold", blockThreshold, "logBlock", log.BlockNumber, "logIndex", log.LogIndex) - continue - } - q.states[lid] = logTriggerStateEntry{state: logTriggerStateEnqueued, block: log.BlockNumber} - added++ - if logList, ok := q.logs[log.BlockNumber]; ok { - logList = append(logList, log) - q.logs[log.BlockNumber] = logList - } else { - q.logs[log.BlockNumber] = []logpoller.Log{log} - q.blockNumbers = append(q.blockNumbers, log.BlockNumber) - slices.Sort(q.blockNumbers) - } - } - - var dropped int - if added > 0 { - q.orderLogs() - dropped = q.clean(blockThreshold) - q.lggr.Debugw("Enqueued logs", "added", added, "dropped", dropped, "blockThreshold", blockThreshold, "q size", len(q.logs), "visited size", len(q.states)) - } - - prommetrics.AutomationLogBufferFlow.WithLabelValues(prommetrics.LogBufferFlowDirectionIngress).Add(float64(added)) - prommetrics.AutomationLogBufferFlow.WithLabelValues(prommetrics.LogBufferFlowDirectionDropped).Add(float64(dropped)) - - return added, dropped -} - -// orderLogs sorts the logs in the buffer. -// NOTE: this method is not thread safe and should be called within a lock. -func (q *upkeepLogQueue) orderLogs() { - // sort logs by block number, tx hash and log index - // to keep the q sorted and to ensure that logs can be - // grouped by block windows for the cleanup - for _, blockNumber := range q.blockNumbers { - toSort := q.logs[blockNumber] - sort.SliceStable(toSort, func(i, j int) bool { - return LogSorter(toSort[i], toSort[j]) - }) - q.logs[blockNumber] = toSort - } -} - -// clean removes logs that are older than blockThreshold and drops logs if the limit for the -// given upkeep was exceeded. Returns the number of logs that were dropped. -// NOTE: this method is not thread safe and should be called within a lock. -func (q *upkeepLogQueue) clean(blockThreshold int64) int { - var totalDropped int - - blockRate := int(q.opts.blockRate.Load()) - windowLimit := int(q.opts.windowLimit.Load()) - // helper variables to keep track of the current window capacity - currentWindowCapacity, currentWindowStart := 0, int64(0) - oldBlockNumbers := make([]int64, 0) - blockNumbers := make([]int64, 0) - - for _, blockNumber := range q.blockNumbers { - var dropped, expired int - - logs := q.logs[blockNumber] - updated := make([]logpoller.Log, 0) - - if blockThreshold > blockNumber { - oldBlockNumbers = append(oldBlockNumbers, blockNumber) - } else { - blockNumbers = append(blockNumbers, blockNumber) - } - - for _, l := range logs { - if blockThreshold > l.BlockNumber { // old log, removed - prommetrics.AutomationLogBufferFlow.WithLabelValues(prommetrics.LogBufferFlowDirectionExpired).Inc() - // q.lggr.Debugw("Expiring old log", "blockNumber", l.BlockNumber, "blockThreshold", blockThreshold, "logIndex", l.LogIndex) - logid := logID(l) - delete(q.states, logid) - expired++ - continue - } - start, _ := getBlockWindow(l.BlockNumber, blockRate) - if start != currentWindowStart { - // new window, reset capacity - currentWindowStart = start - currentWindowCapacity = 0 - } - currentWindowCapacity++ - // if capacity has been reached, drop the log - if currentWindowCapacity > windowLimit { - lid := logID(l) - if s, ok := q.states[lid]; ok { - s.state = logTriggerStateDropped - q.states[lid] = s - } - dropped++ - prommetrics.AutomationLogBufferFlow.WithLabelValues(prommetrics.LogBufferFlowDirectionDropped).Inc() - q.lggr.Debugw("Reached log buffer limits, dropping log", "blockNumber", l.BlockNumber, - "blockHash", l.BlockHash, "txHash", l.TxHash, "logIndex", l.LogIndex, "len updated", len(updated), - "currentWindowStart", currentWindowStart, "currentWindowCapacity", currentWindowCapacity, - "maxLogsPerWindow", windowLimit, "blockRate", blockRate) - continue - } - updated = append(updated, l) - } - - if dropped > 0 || expired > 0 { - totalDropped += dropped - q.logs[blockNumber] = updated - q.lggr.Debugw("Cleaned logs", "dropped", dropped, "expired", expired, "blockThreshold", blockThreshold, "len updated", len(updated), "len before", len(q.logs)) - continue - } - } - - for _, blockNumber := range oldBlockNumbers { - delete(q.logs, blockNumber) - startWindow, _ := getBlockWindow(blockNumber, int(q.opts.blockRate.Load())) - delete(q.dequeued, startWindow) - } - q.blockNumbers = blockNumbers - - q.cleanStates(blockThreshold) - - return totalDropped -} - -// cleanStates removes states that are older than blockThreshold. -// NOTE: this method is not thread safe and should be called within a lock. -func (q *upkeepLogQueue) cleanStates(blockThreshold int64) { - for lid, s := range q.states { - if s.block < blockThreshold { - delete(q.states, lid) - } - } -} - -// getBlockWindow returns the start and end block of the window for the given block. -func getBlockWindow(block int64, blockRate int) (start int64, end int64) { - windowSize := int64(blockRate) - if windowSize == 0 { - return block, block - } - start = block - (block % windowSize) - end = start + windowSize - 1 - return -} diff --git a/pkg/automation/v21/logprovider/buffer_v1_test.go b/pkg/automation/v21/logprovider/buffer_v1_test.go deleted file mode 100644 index b2664e9c5b..0000000000 --- a/pkg/automation/v21/logprovider/buffer_v1_test.go +++ /dev/null @@ -1,543 +0,0 @@ -package logprovider - -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/pkg/logger" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -func TestLogEventBufferV1(t *testing.T) { - buf := NewLogBuffer(logger.Test(t), 10, 20, 1) - - buf.Enqueue(big.NewInt(1), - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 0}, - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 1}, - ) - buf.Enqueue(big.NewInt(2), - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x2"), LogIndex: 0}, - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 2}, - ) - results, remaining := buf.Dequeue(int64(1), 2, true) - require.Len(t, results, 2) - require.Equal(t, 2, remaining) - require.NotEqual(t, 0, results[0].ID.Cmp(results[1].ID)) - results, remaining = buf.Dequeue(int64(1), 2, true) - require.Empty(t, results) - require.Equal(t, 0, remaining) -} - -func TestLogEventBufferV1_SyncFilters(t *testing.T) { - buf := NewLogBuffer(logger.Test(t), 10, 20, 1) - - buf.Enqueue(big.NewInt(1), - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 0}, - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 1}, - ) - buf.Enqueue(big.NewInt(2), - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x2"), LogIndex: 0}, - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 2}, - ) - filterStore := NewUpkeepFilterStore() - filterStore.AddActiveUpkeeps(upkeepFilter{upkeepID: big.NewInt(1)}) - - require.Equal(t, 2, buf.NumOfUpkeeps()) - require.NoError(t, buf.SyncFilters(filterStore)) - require.Equal(t, 1, buf.NumOfUpkeeps()) -} - -type readableLogger struct { - logger.Logger - DebugwFn func(msg string, keysAndValues ...any) -} - -func (l *readableLogger) Debugw(msg string, keysAndValues ...any) { - l.DebugwFn(msg, keysAndValues...) -} - -func (l *readableLogger) Named(name string) logger.Logger { - return l -} - -func (l *readableLogger) With(args ...any) logger.Logger { - return l -} - -func TestLogEventBufferV1_EnqueueViolations(t *testing.T) { - t.Run("enqueuing logs for a block older than latest seen logs a message", func(t *testing.T) { - logReceived := false - readableLogger := &readableLogger{ - Logger: logger.Test(t), - DebugwFn: func(msg string, keysAndValues ...any) { - if msg == "enqueuing logs from a block older than latest seen block" { - logReceived = true - assert.Equal(t, "logBlock", keysAndValues[0]) - assert.Equal(t, int64(1), keysAndValues[1]) - assert.Equal(t, "lastBlockSeen", keysAndValues[2]) - assert.Equal(t, int64(2), keysAndValues[3]) - } - }, - } - - logBufferV1 := NewLogBuffer(readableLogger, 10, 20, 1) - - buf := logBufferV1.(*logBuffer) - - buf.Enqueue(big.NewInt(1), - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 0}, - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 1}, - ) - buf.Enqueue(big.NewInt(2), - logpoller.Log{BlockNumber: 1, TxHash: common.HexToHash("0x2"), LogIndex: 0}, - ) - - assert.True(t, true, logReceived) - }) - - t.Run("enqueuing logs for the same block over multiple calls logs a message", func(t *testing.T) { - logReceived := false - readableLogger := &readableLogger{ - Logger: logger.Test(t), - DebugwFn: func(msg string, keysAndValues ...any) { - if msg == "enqueuing logs again for a previously seen block" { - logReceived = true - assert.Equal(t, "blockNumber", keysAndValues[0]) - assert.Equal(t, int64(3), keysAndValues[1]) - assert.Equal(t, "numberOfEnqueues", keysAndValues[2]) - assert.Equal(t, 2, keysAndValues[3]) - } - }, - } - - logBufferV1 := NewLogBuffer(readableLogger, 10, 20, 1) - - buf := logBufferV1.(*logBuffer) - - buf.Enqueue(big.NewInt(1), - logpoller.Log{BlockNumber: 1, TxHash: common.HexToHash("0x1"), LogIndex: 0}, - logpoller.Log{BlockNumber: 1, TxHash: common.HexToHash("0x1"), LogIndex: 1}, - ) - buf.Enqueue(big.NewInt(2), - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x2"), LogIndex: 0}, - ) - buf.Enqueue(big.NewInt(3), - logpoller.Log{BlockNumber: 3, TxHash: common.HexToHash("0x3a"), LogIndex: 0}, - ) - buf.Enqueue(big.NewInt(3), - logpoller.Log{BlockNumber: 3, TxHash: common.HexToHash("0x3b"), LogIndex: 0}, - ) - - assert.True(t, true, logReceived) - }) -} - -func TestLogEventBufferV1_Dequeue(t *testing.T) { - tests := []struct { - name string - logsInBuffer map[*big.Int][]logpoller.Log - args dequeueArgs - lookback int - results []logpoller.Log - remaining int - }{ - { - name: "empty", - logsInBuffer: map[*big.Int][]logpoller.Log{}, - args: newDequeueArgs(10, 1, 1, 10), - lookback: 20, - results: []logpoller.Log{}, - }, - { - name: "happy path", - logsInBuffer: map[*big.Int][]logpoller.Log{ - big.NewInt(1): { - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 0}, - {BlockNumber: 14, TxHash: common.HexToHash("0x15"), LogIndex: 1}, - }, - }, - args: newDequeueArgs(10, 5, 3, 10), - lookback: 20, - results: []logpoller.Log{ - {}, {}, - }, - }, - { - name: "with upkeep limits", - logsInBuffer: map[*big.Int][]logpoller.Log{ - big.NewInt(1): { - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 1}, - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 0}, - {BlockNumber: 13, TxHash: common.HexToHash("0x13"), LogIndex: 0}, - {BlockNumber: 13, TxHash: common.HexToHash("0x13"), LogIndex: 1}, - {BlockNumber: 14, TxHash: common.HexToHash("0x14"), LogIndex: 1}, - {BlockNumber: 14, TxHash: common.HexToHash("0x14"), LogIndex: 2}, - }, - big.NewInt(2): { - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 11}, - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 10}, - {BlockNumber: 13, TxHash: common.HexToHash("0x13"), LogIndex: 10}, - {BlockNumber: 13, TxHash: common.HexToHash("0x13"), LogIndex: 11}, - {BlockNumber: 14, TxHash: common.HexToHash("0x14"), LogIndex: 11}, - {BlockNumber: 14, TxHash: common.HexToHash("0x14"), LogIndex: 12}, - }, - }, - args: newDequeueArgs(10, 5, 2, 10), - lookback: 20, - results: []logpoller.Log{ - {}, {}, {}, {}, - }, - remaining: 8, - }, - { - name: "with max results", - logsInBuffer: map[*big.Int][]logpoller.Log{ - big.NewInt(1): append(createDummyLogSequence(2, 0, 12, common.HexToHash("0x12")), createDummyLogSequence(2, 0, 13, common.HexToHash("0x13"))...), - big.NewInt(2): append(createDummyLogSequence(2, 10, 12, common.HexToHash("0x12")), createDummyLogSequence(2, 10, 13, common.HexToHash("0x13"))...), - }, - args: newDequeueArgs(10, 5, 3, 4), - lookback: 20, - results: []logpoller.Log{ - {}, {}, {}, {}, - }, - remaining: 4, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - buf := NewLogBuffer(logger.Test(t), uint32(tc.lookback), uint32(tc.args.blockRate), uint32(tc.args.upkeepLimit)) //nolint:gosec // G115 - for id, logs := range tc.logsInBuffer { - added, dropped := buf.Enqueue(id, logs...) - require.Equal(t, len(logs), added+dropped) - } - start, _ := getBlockWindow(tc.args.block, tc.args.blockRate) - - results, remaining := buf.Dequeue(start, tc.args.maxResults, true) - require.Len(t, results, len(tc.results)) - require.Equal(t, tc.remaining, remaining) - }) - } -} - -func TestLogEventBufferV1_Enqueue(t *testing.T) { - tests := []struct { - name string - logsToAdd map[*big.Int][]logpoller.Log - added, dropped map[string]int - sizeOfRange map[*big.Int]int - rangeStart, rangeEnd int64 - lookback, blockRate, upkeepLimit uint32 - }{ - { - name: "empty", - logsToAdd: map[*big.Int][]logpoller.Log{}, - added: map[string]int{}, - dropped: map[string]int{}, - sizeOfRange: map[*big.Int]int{}, - rangeStart: 0, - rangeEnd: 10, - blockRate: 1, - upkeepLimit: 1, - lookback: 20, - }, - { - name: "happy path", - logsToAdd: map[*big.Int][]logpoller.Log{ - big.NewInt(1): { - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 0}, - {BlockNumber: 14, TxHash: common.HexToHash("0x15"), LogIndex: 1}, - }, - big.NewInt(2): { - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 11}, - }, - }, - added: map[string]int{ - big.NewInt(1).String(): 2, - big.NewInt(2).String(): 1, - }, - dropped: map[string]int{ - big.NewInt(1).String(): 0, - big.NewInt(2).String(): 0, - }, - sizeOfRange: map[*big.Int]int{ - big.NewInt(1): 2, - big.NewInt(2): 1, - }, - rangeStart: 10, - rangeEnd: 20, - blockRate: 5, - upkeepLimit: 1, - lookback: 20, - }, - { - name: "above limits", - logsToAdd: map[*big.Int][]logpoller.Log{ - big.NewInt(1): createDummyLogSequence(11, 0, 12, common.HexToHash("0x12")), - big.NewInt(2): { - {BlockNumber: 12, TxHash: common.HexToHash("0x12"), LogIndex: 11}, - }, - }, - added: map[string]int{ - big.NewInt(1).String(): 11, - big.NewInt(2).String(): 1, - }, - dropped: map[string]int{ - big.NewInt(1).String(): 1, - big.NewInt(2).String(): 0, - }, - sizeOfRange: map[*big.Int]int{ - big.NewInt(1): 10, - big.NewInt(2): 1, - }, - rangeStart: 10, - rangeEnd: 20, - blockRate: 10, - upkeepLimit: 1, - lookback: 20, - }, - { - name: "out of block range", - logsToAdd: map[*big.Int][]logpoller.Log{ - big.NewInt(1): append(createDummyLogSequence(2, 0, 1, common.HexToHash("0x1")), createDummyLogSequence(2, 0, 100, common.HexToHash("0x1"))...), - }, - added: map[string]int{ - big.NewInt(1).String(): 2, - }, - dropped: map[string]int{ - big.NewInt(1).String(): 0, - }, - sizeOfRange: map[*big.Int]int{ - big.NewInt(1): 2, - }, - rangeStart: 1, - rangeEnd: 101, - blockRate: 10, - upkeepLimit: 10, - lookback: 20, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - buf := NewLogBuffer(logger.Test(t), tc.lookback, tc.blockRate, tc.upkeepLimit) - for id, logs := range tc.logsToAdd { - added, dropped := buf.Enqueue(id, logs...) - sid := id.String() - if _, ok := tc.added[sid]; !ok { - tc.added[sid] = 0 - } - if _, ok := tc.dropped[sid]; !ok { - tc.dropped[sid] = 0 - } - require.Equal(t, tc.added[sid], added) - require.Equal(t, tc.dropped[sid], dropped) - } - for id, size := range tc.sizeOfRange { - q, ok := buf.(*logBuffer).getUpkeepQueue(id) - require.True(t, ok) - require.Equal(t, size, q.sizeOfRange(tc.rangeStart, tc.rangeEnd)) - } - }) - } -} - -func TestLogEventBufferV1_UpkeepQueue(t *testing.T) { - t.Run("enqueue dequeue", func(t *testing.T) { - q := newUpkeepLogQueue(logger.Test(t), big.NewInt(1), newLogBufferOptions(10, 1, 1)) - - added, dropped := q.enqueue(10, logpoller.Log{BlockNumber: 20, TxHash: common.HexToHash("0x1"), LogIndex: 0}) - require.Equal(t, 0, dropped) - require.Equal(t, 1, added) - require.Equal(t, 1, q.sizeOfRange(1, 20)) - logs, remaining := q.dequeue(19, 21, 10) - require.Len(t, logs, 1) - require.Equal(t, 0, remaining) - }) - - t.Run("enqueue with limits", func(t *testing.T) { - q := newUpkeepLogQueue(logger.Test(t), big.NewInt(1), newLogBufferOptions(10, 1, 1)) - - added, dropped := q.enqueue(10, - createDummyLogSequence(15, 0, 20, common.HexToHash("0x20"))..., - ) - require.Equal(t, 5, dropped) - require.Equal(t, 15, added) - }) - - t.Run("dequeue with limits", func(t *testing.T) { - q := newUpkeepLogQueue(logger.Test(t), big.NewInt(1), newLogBufferOptions(10, 1, 3)) - - added, dropped := q.enqueue(10, - logpoller.Log{BlockNumber: 20, TxHash: common.HexToHash("0x1"), LogIndex: 0}, - logpoller.Log{BlockNumber: 20, TxHash: common.HexToHash("0x1"), LogIndex: 1}, - logpoller.Log{BlockNumber: 20, TxHash: common.HexToHash("0x1"), LogIndex: 10}, - ) - require.Equal(t, 0, dropped) - require.Equal(t, 3, added) - - logs, remaining := q.dequeue(19, 21, 2) - require.Len(t, logs, 2) - require.Equal(t, 1, remaining) - }) -} - -func TestLogEventBufferV1_UpkeepQueue_sizeOfRange(t *testing.T) { - t.Run("empty", func(t *testing.T) { - q := newUpkeepLogQueue(logger.Test(t), big.NewInt(1), newLogBufferOptions(10, 1, 1)) - - require.Equal(t, 0, q.sizeOfRange(1, 10)) - }) - - t.Run("happy path", func(t *testing.T) { - q := newUpkeepLogQueue(logger.Test(t), big.NewInt(1), newLogBufferOptions(10, 1, 1)) - - added, dropped := q.enqueue(10, logpoller.Log{BlockNumber: 20, TxHash: common.HexToHash("0x1"), LogIndex: 0}) - require.Equal(t, 0, dropped) - require.Equal(t, 1, added) - require.Equal(t, 0, q.sizeOfRange(1, 10)) - require.Equal(t, 1, q.sizeOfRange(1, 20)) - }) -} - -func TestLogEventBufferV1_UpkeepQueue_clean(t *testing.T) { - t.Run("empty", func(t *testing.T) { - q := newUpkeepLogQueue(logger.Test(t), big.NewInt(1), newLogBufferOptions(10, 1, 1)) - - q.clean(10) - }) - - t.Run("happy path", func(t *testing.T) { - buf := NewLogBuffer(logger.Test(t), 10, 5, 1) - - buf.Enqueue(big.NewInt(1), - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 0}, - logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x1"), LogIndex: 1}, - ) - buf.Enqueue(big.NewInt(1), - logpoller.Log{BlockNumber: 11, TxHash: common.HexToHash("0x111"), LogIndex: 0}, - logpoller.Log{BlockNumber: 11, TxHash: common.HexToHash("0x111"), LogIndex: 1}, - ) - - q, ok := buf.(*logBuffer).getUpkeepQueue(big.NewInt(1)) - require.True(t, ok) - require.Equal(t, 4, q.sizeOfRange(1, 11)) - - buf.Enqueue(big.NewInt(1), - logpoller.Log{BlockNumber: 17, TxHash: common.HexToHash("0x171"), LogIndex: 0}, - logpoller.Log{BlockNumber: 17, TxHash: common.HexToHash("0x171"), LogIndex: 1}, - ) - - require.Equal(t, 4, q.sizeOfRange(1, 18)) - require.Equal(t, 0, q.clean(12)) - require.Equal(t, 2, q.sizeOfRange(1, 18)) - q.lock.Lock() - defer q.lock.Unlock() - require.Len(t, q.states, 2) - }) -} - -func TestLogEventBufferV1_BlockWindow(t *testing.T) { - tests := []struct { - name string - block int64 - blockRate int - wantStart int64 - wantEnd int64 - }{ - { - name: "block 0, blockRate 1", - block: 0, - blockRate: 1, - wantStart: 0, - wantEnd: 0, - }, - { - name: "block 81, blockRate 1", - block: 81, - blockRate: 1, - wantStart: 81, - wantEnd: 81, - }, - { - name: "block 0, blockRate 4", - block: 0, - blockRate: 4, - wantStart: 0, - wantEnd: 3, - }, - { - name: "block 81, blockRate 4", - block: 81, - blockRate: 4, - wantStart: 80, - wantEnd: 83, - }, - { - name: "block 83, blockRate 4", - block: 83, - blockRate: 4, - wantStart: 80, - wantEnd: 83, - }, - { - name: "block 84, blockRate 4", - block: 84, - blockRate: 4, - wantStart: 84, - wantEnd: 87, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - start, end := getBlockWindow(tc.block, tc.blockRate) - require.Equal(t, tc.wantStart, start) - require.Equal(t, tc.wantEnd, end) - }) - } -} - -type dequeueArgs struct { - block int64 - blockRate int - upkeepLimit int - maxResults int -} - -func newDequeueArgs(block int64, blockRate int, upkeepLimit int, maxResults int) dequeueArgs { - args := dequeueArgs{ - block: block, - blockRate: blockRate, - upkeepLimit: upkeepLimit, - maxResults: maxResults, - } - - if blockRate == 0 { - args.blockRate = 1 - } - if maxResults == 0 { - args.maxResults = 10 - } - if upkeepLimit == 0 { - args.upkeepLimit = 1 - } - - return args -} - -func createDummyLogSequence(n, startIndex int, block int64, tx common.Hash) []logpoller.Log { - logs := make([]logpoller.Log, n) - for i := range n { - logs[i] = logpoller.Log{ - BlockNumber: block, - TxHash: tx, - LogIndex: int64(i + startIndex), - } - } - return logs -} diff --git a/pkg/automation/v21/logprovider/factory.go b/pkg/automation/v21/logprovider/factory.go deleted file mode 100644 index 6afe5e21e3..0000000000 --- a/pkg/automation/v21/logprovider/factory.go +++ /dev/null @@ -1,91 +0,0 @@ -package logprovider - -import ( - "math/big" - "time" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/client" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -// New creates a new log event provider and recoverer. -// using default values for the options. -func New(lggr logger.Logger, poller logpoller.LogPoller, c client.Client, stateStore core.UpkeepStateReader, finalityDepth uint32, chainID *big.Int) (LogEventProvider, LogRecoverer) { - filterStore := NewUpkeepFilterStore() - packer := NewLogEventsPacker() - opts := NewOptions(int64(finalityDepth), chainID) - - provider := NewLogProvider(lggr, poller, chainID, packer, filterStore, opts) - recoverer := NewLogRecoverer(lggr, poller, c, stateStore, packer, filterStore, opts) - - return provider, recoverer -} - -// LogTriggersOptions holds the options for the log trigger components. -type LogTriggersOptions struct { - chainID *big.Int - // LookbackBlocks is the number of blocks the provider will look back for logs. - // The recoverer will scan for logs up to this depth. - // NOTE: MUST be set to a greater-or-equal to the chain's finality depth. - LookbackBlocks int64 - // ReadInterval is the interval to fetch logs in the background. - ReadInterval time.Duration - // Finality depth is the number of blocks to wait before considering a block final. - FinalityDepth int64 - - // LogLimit is the minimum number of logs to process in a single block window. - LogLimit uint32 - // BlockRate determines the block window for log processing. - BlockRate uint32 -} - -func NewOptions(finalityDepth int64, chainID *big.Int) LogTriggersOptions { - opts := new(LogTriggersOptions) - opts.chainID = chainID - opts.Defaults(finalityDepth) - return *opts -} - -// Defaults sets the default values for the options. -// NOTE: o.LookbackBlocks should be set only from within tests -func (o *LogTriggersOptions) Defaults(finalityDepth int64) { - if o.LookbackBlocks == 0 { - lookbackBlocks := max(int64(100), finalityDepth) - o.LookbackBlocks = lookbackBlocks - } - if o.ReadInterval == 0 { - o.ReadInterval = time.Second - } - if o.FinalityDepth == 0 { - o.FinalityDepth = finalityDepth - } - if o.BlockRate == 0 { - o.BlockRate = o.defaultBlockRate() - } - if o.LogLimit == 0 { - o.LogLimit = o.defaultLogLimit() - } -} - -func (o *LogTriggersOptions) defaultBlockRate() uint32 { - switch o.chainID.Int64() { - case 42161, 421614: // Arbitrum, Arb Sepolia - return 2 - default: - return 1 - } -} - -func (o *LogTriggersOptions) defaultLogLimit() uint32 { - switch o.chainID.Int64() { - case 1, 11155111: // Eth, Sepolia - return 20 - case 10, 11155420, 56, 97, 137, 80002, 43114, 43113, 8453, 84532: // Optimism, OP Sepolia, BSC, BSC Test, Polygon, Amoy, Avax, Avax Fuji, Base, Base Sepolia - return 4 - default: - return 1 - } -} diff --git a/pkg/automation/v21/logprovider/filter.go b/pkg/automation/v21/logprovider/filter.go deleted file mode 100644 index 7ee0493084..0000000000 --- a/pkg/automation/v21/logprovider/filter.go +++ /dev/null @@ -1,81 +0,0 @@ -package logprovider - -import ( - "bytes" - "math/big" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -type upkeepFilter struct { - addr []byte - // selector is the filter selector in log trigger config - selector uint8 - topics []common.Hash - upkeepID *big.Int - // configUpdateBlock is the block number the filter was last updated at - configUpdateBlock uint64 - // lastPollBlock is the last block number the logs were fetched for this upkeep - // used by log event provider. - lastPollBlock int64 - // lastRePollBlock is the last block number the logs were recovered for this upkeep - // used by log recoverer. - lastRePollBlock int64 -} - -func (f upkeepFilter) Clone() upkeepFilter { - topics := make([]common.Hash, len(f.topics)) - copy(topics, f.topics) - addr := make([]byte, len(f.addr)) - copy(addr, f.addr) - return upkeepFilter{ - upkeepID: f.upkeepID, - selector: f.selector, - topics: topics, - addr: addr, - configUpdateBlock: f.configUpdateBlock, - lastPollBlock: f.lastPollBlock, - lastRePollBlock: f.lastRePollBlock, - } -} - -// Select returns a slice of logs which match the upkeep filter. -func (f upkeepFilter) Select(logs ...logpoller.Log) []logpoller.Log { - var selected []logpoller.Log - for _, log := range logs { - if f.match(log) { - selected = append(selected, log) - } - } - return selected -} - -// match returns a bool indicating if the log's topics data matches selector and indexed topics in upkeep filter. -func (f upkeepFilter) match(log logpoller.Log) bool { - filters := f.topics[1:] - selector := f.selector - - if selector == 0 { - // no filters - return true - } - - for i, filter := range filters { - // bitwise AND the selector with the index to check - // if the filter is needed - mask := uint8(1 << uint8(i)) - if selector&mask == uint8(0) { - continue - } - if len(log.Topics) <= i+1 { - // log doesn't have enough topics - return false - } - if !bytes.Equal(filter.Bytes(), log.Topics[i+1]) { - return false - } - } - return true -} diff --git a/pkg/automation/v21/logprovider/filter_store.go b/pkg/automation/v21/logprovider/filter_store.go deleted file mode 100644 index 07173ac707..0000000000 --- a/pkg/automation/v21/logprovider/filter_store.go +++ /dev/null @@ -1,158 +0,0 @@ -package logprovider - -import ( - "math/big" - "sync" -) - -type UpkeepFilterStore interface { - GetIDs(selector func(upkeepFilter) bool) []*big.Int - UpdateFilters(updater func(upkeepFilter, upkeepFilter) upkeepFilter, filters ...upkeepFilter) - Has(id *big.Int) bool - Get(id *big.Int) *upkeepFilter - RangeFiltersByIDs(iterator func(int, upkeepFilter), ids ...*big.Int) - GetFilters(selector func(upkeepFilter) bool) []upkeepFilter - AddActiveUpkeeps(filters ...upkeepFilter) - RemoveActiveUpkeeps(filters ...upkeepFilter) - Size() int -} - -var _ UpkeepFilterStore = &upkeepFilterStore{} - -type upkeepFilterStore struct { - lock *sync.RWMutex - // filters is a map of upkeepID to upkeepFilter - filters map[string]upkeepFilter -} - -func NewUpkeepFilterStore() *upkeepFilterStore { - return &upkeepFilterStore{ - lock: &sync.RWMutex{}, - filters: make(map[string]upkeepFilter), - } -} - -func (s *upkeepFilterStore) GetIDs(selector func(upkeepFilter) bool) []*big.Int { - s.lock.RLock() - defer s.lock.RUnlock() - - if selector == nil { - // noop selector returns true for all filters - selector = func(upkeepFilter) bool { return true } - } - - var ids []*big.Int - for _, f := range s.filters { - if selector(f) { - ids = append(ids, f.upkeepID) - } - } - - return ids -} - -func (s *upkeepFilterStore) UpdateFilters(resolveUpdated func(upkeepFilter, upkeepFilter) upkeepFilter, filters ...upkeepFilter) { - s.lock.Lock() - defer s.lock.Unlock() - - if resolveUpdated == nil { - // noop resolveUpdated will use the newer filter - resolveUpdated = func(_ upkeepFilter, f upkeepFilter) upkeepFilter { return f } - } - - for _, f := range filters { - uid := f.upkeepID.String() - orig, ok := s.filters[uid] - if !ok { - // not found, turned inactive probably - continue - } - updated := resolveUpdated(orig, f) - s.filters[uid] = updated - } -} - -func (s *upkeepFilterStore) Has(id *big.Int) bool { - s.lock.RLock() - defer s.lock.RUnlock() - - _, ok := s.filters[id.String()] - return ok -} - -func (s *upkeepFilterStore) Get(id *big.Int) *upkeepFilter { - s.lock.RLock() - defer s.lock.RUnlock() - - f, ok := s.filters[id.String()] - if !ok { - return nil - } - fp := f.Clone() - return &fp -} - -func (s *upkeepFilterStore) RangeFiltersByIDs(iterator func(int, upkeepFilter), ids ...*big.Int) { - s.lock.RLock() - defer s.lock.RUnlock() - - if iterator == nil { - // noop iterator does nothing - iterator = func(int, upkeepFilter) {} - } - - for i, id := range ids { - f, ok := s.filters[id.String()] - if !ok { - // in case the filter is not found, we still want to call the iterator - // with an empty filter, so - iterator(i, upkeepFilter{upkeepID: id}) - } else { - iterator(i, f) - } - } -} - -func (s *upkeepFilterStore) GetFilters(selector func(upkeepFilter) bool) []upkeepFilter { - s.lock.RLock() - defer s.lock.RUnlock() - - if selector == nil { - // noop selector returns true for all filters - selector = func(upkeepFilter) bool { return true } - } - - var filters []upkeepFilter - for _, f := range s.filters { - if selector(f) { - filters = append(filters, f.Clone()) - } - } - return filters -} - -func (s *upkeepFilterStore) AddActiveUpkeeps(filters ...upkeepFilter) { - s.lock.Lock() - defer s.lock.Unlock() - - for _, f := range filters { - s.filters[f.upkeepID.String()] = f - } -} - -func (s *upkeepFilterStore) RemoveActiveUpkeeps(filters ...upkeepFilter) { - s.lock.Lock() - defer s.lock.Unlock() - - for _, f := range filters { - uid := f.upkeepID.String() - delete(s.filters, uid) - } -} - -func (s *upkeepFilterStore) Size() int { - s.lock.RLock() - defer s.lock.RUnlock() - - return len(s.filters) -} diff --git a/pkg/automation/v21/logprovider/filter_store_test.go b/pkg/automation/v21/logprovider/filter_store_test.go deleted file mode 100644 index 015fa28aff..0000000000 --- a/pkg/automation/v21/logprovider/filter_store_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package logprovider - -import ( - "math/big" - "sort" - "sync" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFilterStore_CRUD(t *testing.T) { - tests := []struct { - name string - initial []upkeepFilter - toAdd []upkeepFilter - expectedPostAdd []upkeepFilter - toRemove []upkeepFilter - expectedPostRemove []upkeepFilter - }{ - { - "empty", - []upkeepFilter{}, - []upkeepFilter{}, - []upkeepFilter{}, - []upkeepFilter{}, - []upkeepFilter{}, - }, - { - "add rm one", - []upkeepFilter{}, - []upkeepFilter{{upkeepID: big.NewInt(1)}}, - []upkeepFilter{{upkeepID: big.NewInt(1)}}, - []upkeepFilter{{upkeepID: big.NewInt(1)}}, - []upkeepFilter{}, - }, - { - "add rm multiple", - []upkeepFilter{}, - []upkeepFilter{{upkeepID: big.NewInt(1)}, {upkeepID: big.NewInt(2)}}, - []upkeepFilter{{upkeepID: big.NewInt(1)}, {upkeepID: big.NewInt(2)}}, - []upkeepFilter{{upkeepID: big.NewInt(1)}}, - []upkeepFilter{{upkeepID: big.NewInt(2)}}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - s := NewUpkeepFilterStore() - s.AddActiveUpkeeps(tc.initial...) - require.Len(t, s.GetIDs(nil), len(tc.initial)) - s.AddActiveUpkeeps(tc.toAdd...) - require.Equal(t, len(tc.expectedPostAdd), s.Size()) - filters := s.GetFilters(func(f upkeepFilter) bool { return true }) - require.Len(t, filters, len(tc.expectedPostAdd)) - if len(filters) > 0 { - sort.Slice(filters, func(i, j int) bool { - return filters[i].upkeepID.Cmp(filters[j].upkeepID) < 0 - }) - for i, f := range filters { - require.Equal(t, tc.expectedPostAdd[i].upkeepID, f.upkeepID) - } - } - s.RemoveActiveUpkeeps(tc.toRemove...) - require.Len(t, s.GetIDs(func(upkeepFilter) bool { return true }), len(tc.expectedPostRemove)) - }) - } -} - -func TestFilterStore_Concurrency(t *testing.T) { - s := NewUpkeepFilterStore() - var wg sync.WaitGroup - - wg.Go(func() { - s.AddActiveUpkeeps(upkeepFilter{upkeepID: big.NewInt(1)}) - s.AddActiveUpkeeps(upkeepFilter{upkeepID: big.NewInt(2)}) - }) - wg.Go(func() { - s.AddActiveUpkeeps(upkeepFilter{upkeepID: big.NewInt(2)}) - }) - - go func() { - _ = s.GetIDs(nil) - }() - - wg.Wait() - - require.Equal(t, 2, s.Size()) -} diff --git a/pkg/automation/v21/logprovider/filter_test.go b/pkg/automation/v21/logprovider/filter_test.go deleted file mode 100644 index 53c3bf3b39..0000000000 --- a/pkg/automation/v21/logprovider/filter_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package logprovider - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/stretchr/testify/assert" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -func TestUpkeepFilter_Select(t *testing.T) { - var zeroBytes [32]byte - emptyTopic := common.BytesToHash(zeroBytes[:]) - contractAddress := common.HexToAddress("0xB9F3af0c2CbfE108efd0E23F7b0a151Ea42f764E") - uid := big.NewInt(123456) - topic10 := "0x000000000000000000000000000000000000000000000000000000000000007b" // decimal 123 encoded - topic20 := "0x0000000000000000000000000000000000000000000000000000000000000001" // bool true encoded - topic30 := "0x00000000000000000000000082b8b466f4be252e56af8a00aa28838866686062" // address encoded - topic11 := "0x000000000000000000000000000000000000000000000000000000000000007a" // decimal 122 encoded - topic21 := "0x0000000000000000000000000000000000000000000000000000000000000000" // bool false encoded - topic31 := "0x000000000000000000000000f91a27d2f37a36f1e6acc681b07b1dd2e288aebc" // address encoded - - log1 := logpoller.Log{ - Topics: [][]byte{ - contractAddress.Bytes(), - hexutil.MustDecode(topic10), - hexutil.MustDecode(topic20), - hexutil.MustDecode(topic30), - }, - } - log2 := logpoller.Log{ - Topics: [][]byte{ - contractAddress.Bytes(), - hexutil.MustDecode(topic11), - hexutil.MustDecode(topic21), - hexutil.MustDecode(topic31), - }, - } - log3 := logpoller.Log{ - Topics: [][]byte{ - contractAddress.Bytes(), - hexutil.MustDecode(topic11), - hexutil.MustDecode(topic20), - hexutil.MustDecode(topic31), - }, - } - log4 := logpoller.Log{ - Topics: [][]byte{ - contractAddress.Bytes(), - hexutil.MustDecode(topic10), - hexutil.MustDecode(topic21), - hexutil.MustDecode(topic31), - }, - } - log5 := logpoller.Log{ - Topics: [][]byte{ - contractAddress.Bytes(), - hexutil.MustDecode(topic10), - hexutil.MustDecode(topic20), - hexutil.MustDecode(topic30), - }, - } - - tests := []struct { - name string - filter upkeepFilter - logs []logpoller.Log - expectedLogs []logpoller.Log - }{ - { - "no selector configured - all logs are returned", - upkeepFilter{ - selector: 0, - topics: []common.Hash{common.BytesToHash(contractAddress.Bytes()), emptyTopic, emptyTopic, emptyTopic}, - upkeepID: uid, - }, - []logpoller.Log{ - log1, - log2, - }, - []logpoller.Log{ - log1, - log2, - }, - }, - { - "selector is 1 - topics 1 is used to filter logs", - upkeepFilter{ - selector: 1, - topics: []common.Hash{common.BytesToHash(contractAddress.Bytes()), common.HexToHash(topic10), emptyTopic, emptyTopic}, - upkeepID: uid, - }, - []logpoller.Log{ - log1, - log2, - }, - []logpoller.Log{ - log1, - }, - }, - { - "selector is 2 - topic 2 is used to filter logs", - upkeepFilter{ - selector: 2, - topics: []common.Hash{common.BytesToHash(contractAddress.Bytes()), emptyTopic, common.HexToHash(topic21), emptyTopic}, - upkeepID: uid, - }, - []logpoller.Log{ - log1, - log2, - }, - []logpoller.Log{ - log2, - }, - }, - { - "selector is 3 - topics 1 2 are used to filter logs", - upkeepFilter{ - selector: 3, - topics: []common.Hash{common.BytesToHash(contractAddress.Bytes()), common.HexToHash(topic10), common.HexToHash(topic21), emptyTopic}, - upkeepID: uid, - }, - []logpoller.Log{ - log1, - log2, - log3, - log4, - }, - []logpoller.Log{ - log4, - }, - }, - { - "selector is 4 - topic 3 is used to filter logs", - upkeepFilter{ - selector: 4, - topics: []common.Hash{common.BytesToHash(contractAddress.Bytes()), emptyTopic, emptyTopic, common.HexToHash(topic31)}, - upkeepID: uid, - }, - []logpoller.Log{ - log1, - log2, - }, - []logpoller.Log{ - log2, - }, - }, - { - "selector is 5 - topics 1 3 are used to filter logs", - upkeepFilter{ - selector: 5, - topics: []common.Hash{common.BytesToHash(contractAddress.Bytes()), common.HexToHash(topic11), emptyTopic, common.HexToHash(topic31)}, - upkeepID: uid, - }, - []logpoller.Log{ - log1, - log2, - log3, - log4, - }, - []logpoller.Log{ - log2, - log3, - }, - }, - { - "selector is 7 - topics 1 2 3 are used to filter logs", - upkeepFilter{ - selector: 7, - topics: []common.Hash{common.BytesToHash(contractAddress.Bytes()), common.HexToHash(topic10), common.HexToHash(topic20), common.HexToHash(topic30)}, - upkeepID: uid, - }, - []logpoller.Log{ - log1, - log2, - log3, - log4, - log5, - }, - []logpoller.Log{ - log1, - log5, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - filteredLogs := tc.filter.Select(tc.logs...) - assert.Equal(t, tc.expectedLogs, filteredLogs) - }) - } -} diff --git a/pkg/automation/v21/logprovider/log.go b/pkg/automation/v21/logprovider/log.go deleted file mode 100644 index f8bb0a5b89..0000000000 --- a/pkg/automation/v21/logprovider/log.go +++ /dev/null @@ -1,58 +0,0 @@ -package logprovider - -import ( - "encoding/hex" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -// LogSorter sorts the logs primarily by block number, then by log index, and finally by tx hash. -// returns true if b should come before a. -func LogSorter(a, b logpoller.Log) bool { - return LogComparator(a, b) > 0 -} - -// LogComparator compares the logs based on block number, log index. -// tx hash is also checked in case the log index is not unique within a block. -// -// Returns: -// -// -1 if a < b -// 0 if a == b -// +1 if a > b -func LogComparator(a, b logpoller.Log) int { - blockDiff := int(a.BlockNumber - b.BlockNumber) - if blockDiff != 0 { - return normalizeCompareResult(blockDiff) - } - logIndexDiff := int(a.LogIndex - b.LogIndex) - if logIndexDiff != 0 { - return normalizeCompareResult(logIndexDiff) - } - return a.TxHash.Big().Cmp(b.TxHash.Big()) -} - -// normalizeCompareResult normalizes the result of a comparison to -1, 0, 1 -func normalizeCompareResult(res int) int { - switch { - case res < 0: - return -1 - case res > 0: - return 1 - default: - return 0 - } -} - -// logID returns a unique identifier for a log, which is an hex string -// of ocr2keepers.LogTriggerExtension.LogIdentifier() -func logID(l logpoller.Log) string { - ext := ocr2keepers.LogTriggerExtension{ - Index: uint32(l.LogIndex), - } - copy(ext.TxHash[:], l.TxHash[:]) - copy(ext.BlockHash[:], l.BlockHash[:]) - return hex.EncodeToString(ext.LogIdentifier()) -} diff --git a/pkg/automation/v21/logprovider/log_packer.go b/pkg/automation/v21/logprovider/log_packer.go deleted file mode 100644 index 96c72ab8f7..0000000000 --- a/pkg/automation/v21/logprovider/log_packer.go +++ /dev/null @@ -1,44 +0,0 @@ -package logprovider - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -type LogDataPacker interface { - PackLogData(log logpoller.Log) ([]byte, error) -} - -type logEventsPacker struct { - abi abi.ABI -} - -func NewLogEventsPacker() *logEventsPacker { - return &logEventsPacker{abi: core.CompatibleUtilsABI} -} - -func (p *logEventsPacker) PackLogData(log logpoller.Log) ([]byte, error) { - var topics [][32]byte - for _, topic := range log.GetTopics() { - topics = append(topics, topic) - } - b, err := p.abi.Methods["_log"].Inputs.Pack(&ac.Log{ - Index: big.NewInt(log.LogIndex), - Timestamp: big.NewInt(log.BlockTimestamp.Unix()), - TxHash: log.TxHash, - BlockNumber: big.NewInt(log.BlockNumber), - BlockHash: log.BlockHash, - Source: log.Address, - Topics: topics, - Data: log.Data, - }) - if err != nil { - return nil, err - } - return b, nil -} diff --git a/pkg/automation/v21/logprovider/log_test.go b/pkg/automation/v21/logprovider/log_test.go deleted file mode 100644 index 919457de74..0000000000 --- a/pkg/automation/v21/logprovider/log_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package logprovider - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -func TestLogComparatorSorter(t *testing.T) { - tests := []struct { - name string - a logpoller.Log - b logpoller.Log - wantCmp int - wantSort bool - }{ - { - name: "a == b", - a: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - }, - b: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - }, - wantCmp: 0, - wantSort: false, - }, - { - name: "a < b: block number", - a: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - }, - b: logpoller.Log{ - BlockNumber: 4, - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - }, - wantCmp: -1, - wantSort: false, - }, - { - name: "a < b: log index", - a: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - }, - b: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 2, - }, - wantCmp: -1, - wantSort: false, - }, - { - name: "a > b: block number", - a: logpoller.Log{ - BlockNumber: 3, - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - }, - b: logpoller.Log{ - BlockNumber: 2, - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - }, - wantCmp: 1, - wantSort: true, - }, - { - name: "a > b: log index", - a: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 4, - }, - b: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 2, - }, - wantCmp: 1, - wantSort: true, - }, - { - name: "a > b: tx hash", - a: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x21"), - LogIndex: 2, - }, - b: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 2, - }, - wantCmp: 1, - wantSort: true, - }, - { - name: "a < b: tx hash", - a: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - LogIndex: 2, - }, - b: logpoller.Log{ - BlockNumber: 1, - TxHash: common.HexToHash("0x4"), - LogIndex: 2, - }, - wantCmp: -1, - wantSort: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.wantCmp, LogComparator(tc.a, tc.b)) - require.Equal(t, tc.wantSort, LogSorter(tc.a, tc.b)) - }) - } -} diff --git a/pkg/automation/v21/logprovider/provider.go b/pkg/automation/v21/logprovider/provider.go deleted file mode 100644 index 9e362a5198..0000000000 --- a/pkg/automation/v21/logprovider/provider.go +++ /dev/null @@ -1,495 +0,0 @@ -package logprovider - -import ( - "context" - "crypto/sha256" - "errors" - "fmt" - "hash" - "io" - "math/big" - "runtime" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/prommetrics" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -var ( - LogProviderServiceName = "LogEventProvider" - - ErrHeadNotAvailable = errors.New("head not available") - ErrBlockLimitExceeded = errors.New("block limit exceeded") - - // AllowedLogsPerUpkeep is the maximum number of logs allowed per upkeep every single call. - AllowedLogsPerUpkeep = 5 - // MaxPayloads is the maximum number of payloads to return per call. - MaxPayloads = 100 - - readJobQueueSize = 64 - readLogsTimeout = 10 * time.Second - - readMaxBatchSize = 56 - // reorgBuffer is the number of blocks to add as a buffer to the block range when reading logs. - reorgBuffer = int64(32) - readerThreads = 4 - - bufferSyncInterval = 10 * time.Minute -) - -// LogTriggerConfig is an alias for log trigger config. -type LogTriggerConfig ac.IAutomationV21PlusCommonLogTriggerConfig - -type FilterOptions struct { - UpkeepID *big.Int - TriggerConfig LogTriggerConfig - UpdateBlock uint64 -} - -type LogTriggersLifeCycle interface { - // RegisterFilter registers the filter (if valid) for the given upkeepID. - RegisterFilter(ctx context.Context, opts FilterOptions) error - // UnregisterFilter removes the filter for the given upkeepID. - UnregisterFilter(ctx context.Context, upkeepID *big.Int) error -} -type LogEventProvider interface { - ocr2keepers.LogEventProvider - LogTriggersLifeCycle - - RefreshActiveUpkeeps(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) - - Start(context.Context) error - io.Closer -} - -type LogEventProviderTest interface { - LogEventProvider - ReadLogs(ctx context.Context, ids ...*big.Int) error - CurrentPartitionIdx() uint64 -} - -var _ LogEventProvider = &logEventProvider{} -var _ LogEventProviderTest = &logEventProvider{} - -// logEventProvider manages log filters for upkeeps and enables to read the log events. -type logEventProvider struct { - services.StateMachine - threadCtrl threadcontrol.ThreadControl - - lggr logger.Logger - - poller logpoller.LogPoller - - packer LogDataPacker - - lock sync.RWMutex - registerLock sync.Mutex - - filterStore UpkeepFilterStore - buffer LogBuffer - - opts LogTriggersOptions - - currentPartitionIdx uint64 - - chainID *big.Int -} - -func NewLogProvider(lggr logger.Logger, poller logpoller.LogPoller, chainID *big.Int, packer LogDataPacker, filterStore UpkeepFilterStore, opts LogTriggersOptions) *logEventProvider { - return &logEventProvider{ - threadCtrl: threadcontrol.NewThreadControl(), - lggr: logger.Named(lggr, "KeepersRegistry.LogEventProvider"), - packer: packer, - buffer: NewLogBuffer(lggr, uint32(opts.LookbackBlocks), opts.BlockRate, opts.LogLimit), - poller: poller, - opts: opts, - filterStore: filterStore, - chainID: chainID, - } -} - -func (p *logEventProvider) SetConfig(cfg ocr2keepers.LogEventProviderConfig) { - p.lock.Lock() - defer p.lock.Unlock() - - blockRate := cfg.BlockRate - logLimit := cfg.LogLimit - - if blockRate == 0 { - blockRate = p.opts.defaultBlockRate() - } - if logLimit == 0 { - logLimit = p.opts.defaultLogLimit() - } - - p.lggr.Infow("setting config", "where", "setConfig", "bockRate", blockRate, "logLimit", logLimit) - - atomic.StoreUint32(&p.opts.BlockRate, blockRate) - atomic.StoreUint32(&p.opts.LogLimit, logLimit) - - p.buffer.SetConfig(uint32(p.opts.LookbackBlocks), blockRate, logLimit) -} - -func (p *logEventProvider) Start(context.Context) error { - return p.StartOnce(LogProviderServiceName, func() error { - readQ := make(chan []*big.Int, readJobQueueSize) - - p.lggr.Infow("starting log event provider", "readInterval", p.opts.ReadInterval, "readMaxBatchSize", readMaxBatchSize, "readers", readerThreads) - - for range readerThreads { - p.threadCtrl.Go(func(ctx context.Context) { - p.startReader(ctx, readQ) - }) - } - - p.threadCtrl.Go(func(ctx context.Context) { - lggr := logger.With(p.lggr, "where", "scheduler") - - p.scheduleReadJobs(ctx, func(ids []*big.Int) { - select { - case readQ <- ids: - case <-ctx.Done(): - default: - lggr.Warnw("readQ is full, dropping ids", "ids", ids) - } - }) - }) - - p.threadCtrl.Go(func(ctx context.Context) { - // sync filters with buffer periodically, - // to ensure that inactive upkeeps won't waste capacity. - ticker := time.NewTicker(bufferSyncInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if err := p.buffer.SyncFilters(p.filterStore); err != nil { - p.lggr.Warnw("failed to sync buffer filters", "err", err) - } - case <-ctx.Done(): - return - } - } - }) - - return nil - }) -} - -func (p *logEventProvider) Close() error { - return p.StopOnce(LogProviderServiceName, func() error { - p.threadCtrl.Close() - return nil - }) -} - -func (p *logEventProvider) HealthReport() map[string]error { - return map[string]error{LogProviderServiceName: p.Healthy()} -} - -func (p *logEventProvider) GetLatestPayloads(ctx context.Context) ([]ocr2keepers.UpkeepPayload, error) { - latest, err := p.poller.LatestBlock(ctx) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrHeadNotAvailable, err) - } - prommetrics.AutomationLogProviderLatestBlock.Set(float64(latest.BlockNumber)) - payloads := p.getLogsFromBuffer(latest.BlockNumber) - - if len(payloads) > 0 { - p.lggr.Debugw("Fetched payloads from buffer", "latestBlock", latest.BlockNumber, "payloads", len(payloads)) - } - - return payloads, nil -} - -func (p *logEventProvider) createPayload(id *big.Int, log logpoller.Log) (ocr2keepers.UpkeepPayload, error) { - trig := logToTrigger(log) - checkData, err := p.packer.PackLogData(log) - if err != nil { - p.lggr.Warnw("failed to pack log data", "err", err, "log", log, "id", id) - return ocr2keepers.UpkeepPayload{}, err - } - payload, err := core.NewUpkeepPayload(id, trig, checkData) - if err != nil { - p.lggr.Warnw("failed to create upkeep payload", "err", err, "id", id, "trigger", trig, "checkData", checkData) - return ocr2keepers.UpkeepPayload{}, err - } - return payload, nil -} - -func (p *logEventProvider) getLogsFromBuffer(latestBlock int64) []ocr2keepers.UpkeepPayload { - var payloads []ocr2keepers.UpkeepPayload - - start := latestBlock - p.opts.LookbackBlocks - if start <= 0 { // edge case when the chain is new (e.g. tests) - start = 1 - } - - payloads = p.minimumCommitmentDequeue(latestBlock, start) - - // if we have remaining capacity following minimum commitment dequeue, perform a best effort dequeue - if len(payloads) < MaxPayloads { - payloads = p.bestEffortDequeue(latestBlock, start, payloads) - } - - return payloads -} - -// minimumCommitmentDequeue dequeues the minimum number of logs per upkeep per block window, when available. -func (p *logEventProvider) minimumCommitmentDequeue(latestBlock, start int64) []ocr2keepers.UpkeepPayload { - var payloads []ocr2keepers.UpkeepPayload - - blockRate := int(p.opts.BlockRate) - - for len(payloads) < MaxPayloads && start <= latestBlock { - startWindow, _ := getBlockWindow(start, blockRate) - - // dequeue the minimum number logs (log limit, varies by chain) per upkeep for this block window - logs, remaining := p.buffer.Dequeue(startWindow, MaxPayloads-len(payloads), true) - if len(logs) > 0 { - p.lggr.Debugw("minimum commitment dequeue", "start", start, "latestBlock", latestBlock, "logs", len(logs), "remaining", remaining) - } - for _, l := range logs { - payload, err := p.createPayload(l.ID, l.Log) - if err == nil { - payloads = append(payloads, payload) - } - } - - start += int64(blockRate) - } - - return payloads -} - -// bestEffortDequeue dequeues the remaining logs from the buffer, after the minimum number of logs -// have been dequeued for every upkeep in every block window. -func (p *logEventProvider) bestEffortDequeue(latestBlock, start int64, payloads []ocr2keepers.UpkeepPayload) []ocr2keepers.UpkeepPayload { - blockRate := int(p.opts.BlockRate) - - for len(payloads) < MaxPayloads && start <= latestBlock { - startWindow, _ := getBlockWindow(start, blockRate) - - // dequeue as many logs as we can, based on remaining capacity, for this block window - logs, remaining := p.buffer.Dequeue(startWindow, MaxPayloads-len(payloads), false) - if len(logs) > 0 { - p.lggr.Debugw("best effort dequeue", "start", start, "latestBlock", latestBlock, "logs", len(logs), "remaining", remaining) - } - for _, l := range logs { - payload, err := p.createPayload(l.ID, l.Log) - if err == nil { - payloads = append(payloads, payload) - } - } - - start += int64(blockRate) - } - - return payloads -} - -// ReadLogs fetches the logs for the given upkeeps. -func (p *logEventProvider) ReadLogs(pctx context.Context, ids ...*big.Int) error { - ctx, cancel := context.WithTimeout(pctx, readLogsTimeout) - defer cancel() - - latest, err := p.poller.LatestBlock(ctx) - if err != nil { - return fmt.Errorf("%w: %w", ErrHeadNotAvailable, err) - } - if latest.BlockNumber == 0 { - return fmt.Errorf("%w: %s", ErrHeadNotAvailable, "latest block is 0") - } - filters := p.getFilters(latest.BlockNumber, ids...) - - err = p.readLogs(ctx, latest.BlockNumber, filters) - p.updateFiltersLastPoll(filters) - // p.lggr.Debugw("read logs for entries", "latestBlock", latest, "entries", len(entries), "err", err) - if err != nil { - return fmt.Errorf("fetched logs with errors: %w", err) - } - - return nil -} - -func (p *logEventProvider) CurrentPartitionIdx() uint64 { - return atomic.LoadUint64(&p.currentPartitionIdx) -} - -// scheduleReadJobs starts a scheduler that pushed ids to readQ for reading logs in the background. -func (p *logEventProvider) scheduleReadJobs(pctx context.Context, execute func([]*big.Int)) { - ctx, cancel := context.WithCancel(pctx) - defer cancel() - - ticker := time.NewTicker(p.opts.ReadInterval) - defer ticker.Stop() - - h := sha256.New() - - partitionIdx := p.CurrentPartitionIdx() - - for { - select { - case <-ticker.C: - ids := p.getPartitionIds(h, int(partitionIdx)) - if len(ids) > 0 { - maxBatchSize := readMaxBatchSize - for len(ids) > maxBatchSize { - batch := ids[:maxBatchSize] - execute(batch) - ids = ids[maxBatchSize:] - runtime.Gosched() - } - execute(ids) - } - partitionIdx++ - atomic.StoreUint64(&p.currentPartitionIdx, partitionIdx) - case <-ctx.Done(): - return - } - } -} - -// startReader starts a reader that reads logs from the ids coming from readQ. -func (p *logEventProvider) startReader(pctx context.Context, readQ <-chan []*big.Int) { - ctx, cancel := context.WithCancel(pctx) - defer cancel() - - lggr := logger.With(p.lggr, "where", "reader") - - for { - select { - case batch := <-readQ: - if err := p.ReadLogs(ctx, batch...); err != nil { - if ctx.Err() != nil { - return - } - lggr.Warnw("failed to read logs", "err", err) - } - case <-ctx.Done(): - return - } - } -} - -// getPartitionIds returns the upkeepIDs for the given partition and the number of partitions. -// Partitioning is done by hashing the upkeepID and taking the modulus of the number of partitions. -func (p *logEventProvider) getPartitionIds(hashFn hash.Hash, partition int) []*big.Int { - numOfPartitions := max(p.filterStore.Size()/readMaxBatchSize, 1) - partition = partition % numOfPartitions - - ids := p.filterStore.GetIDs(func(f upkeepFilter) bool { - if len(f.addr) == 0 { - return false - } - n, err := hashFn.Write(f.addr) - if err != nil || n == 0 { - p.lggr.Warnw("failed to hash upkeep address", "err", err, "addr", hexutil.Encode(f.addr)) - return false - } - h := hashFn.Sum(nil) - defer hashFn.Reset() - // taking only 6 bytes to avoid working with big numbers - i := big.NewInt(0).SetBytes(h[len(h)-6:]) - return int(i.Int64())%numOfPartitions == partition - }) - - return ids -} - -func (p *logEventProvider) updateFiltersLastPoll(entries []upkeepFilter) { - p.filterStore.UpdateFilters(func(orig, f upkeepFilter) upkeepFilter { - if f.lastPollBlock > orig.lastPollBlock { - orig.lastPollBlock = f.lastPollBlock - if f.lastPollBlock%10 == 0 { - // print log occasionally to avoid spamming logs - p.lggr.Debugw("Updated lastPollBlock", "lastPollBlock", f.lastPollBlock, "upkeepID", f.upkeepID) - } - } - return orig - }, entries...) -} - -// getFilters returns the filters for the given upkeepIDs, -// returns empty filter for inactive upkeeps. -func (p *logEventProvider) getFilters(latestBlock int64, ids ...*big.Int) []upkeepFilter { - var filters []upkeepFilter - p.filterStore.RangeFiltersByIDs(func(i int, f upkeepFilter) { - if len(f.addr) == 0 { // not found - p.lggr.Debugw("upkeep filter not found", "upkeep", f.upkeepID.String()) - filters = append(filters, f) - return - } - if f.configUpdateBlock > uint64(latestBlock) { - p.lggr.Debugw("upkeep config update block was created after latestBlock", "upkeep", f.upkeepID.String(), "configUpdateBlock", f.configUpdateBlock, "latestBlock", latestBlock) - filters = append(filters, upkeepFilter{upkeepID: f.upkeepID}) - return - } - if f.lastPollBlock > latestBlock { - p.lggr.Debugw("already polled latest block", "entry.lastPollBlock", f.lastPollBlock, "latestBlock", latestBlock, "upkeep", f.upkeepID.String()) - filters = append(filters, upkeepFilter{upkeepID: f.upkeepID}) - return - } - filters = append(filters, f.Clone()) - }, ids...) - - return filters -} - -// readLogs calls log poller to get the logs for the given upkeep entries. -// -// Exploratory: batch filters by contract address and call log poller once per contract address -// NOTE: the filters are already grouped by contract address -func (p *logEventProvider) readLogs(ctx context.Context, latest int64, filters []upkeepFilter) (merr error) { - lookbackBlocks := p.opts.LookbackBlocks - if latest < lookbackBlocks { - // special case of a new blockchain (e.g. simulated chain) - lookbackBlocks = latest - 1 - } - - for i, filter := range filters { - if len(filter.addr) == 0 { - continue - } - // range should not exceed [lookbackBlocks, latest] - start := max(filter.lastPollBlock, latest-lookbackBlocks) - // adding a buffer to check for reorged logs. - start = start - reorgBuffer - // make sure start of the range is not before the config update block - if configUpdateBlock := int64(filter.configUpdateBlock); start < configUpdateBlock { - start = configUpdateBlock - } - // query logs based on contract address, event sig, and blocks - logs, err := p.poller.LogsWithSigs(ctx, start, latest, []common.Hash{filter.topics[0]}, common.BytesToAddress(filter.addr)) - if err != nil { - if ctx.Err() != nil { - // exit if the context was canceled - return merr - } - merr = errors.Join(merr, fmt.Errorf("failed to get logs for upkeep %s: %w", filter.upkeepID.String(), err)) - continue - } - filteredLogs := filter.Select(logs...) - - p.buffer.Enqueue(filter.upkeepID, filteredLogs...) - - // Update the lastPollBlock for filter in slice this is then - // updated into filter store in updateFiltersLastPoll - filters[i].lastPollBlock = latest - } - - return merr -} diff --git a/pkg/automation/v21/logprovider/provider_life_cycle.go b/pkg/automation/v21/logprovider/provider_life_cycle.go deleted file mode 100644 index ecaf47e6c4..0000000000 --- a/pkg/automation/v21/logprovider/provider_life_cycle.go +++ /dev/null @@ -1,188 +0,0 @@ -package logprovider - -import ( - "bytes" - "context" - "errors" - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -var ( - // LogRetention is the amount of time to retain logs for. - LogRetention = 24 * time.Hour - // LogBackfillBuffer is the number of blocks from the latest block for which backfill is done when adding a filter in log poller - LogBackfillBuffer = 100 -) - -func (p *logEventProvider) RefreshActiveUpkeeps(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - // Exploratory: investigate how we can batch the refresh - if len(ids) == 0 { - return nil, nil - } - p.lggr.Debugw("Refreshing active upkeeps", "upkeeps", len(ids)) - visited := make(map[string]bool, len(ids)) - for _, id := range ids { - visited[id.String()] = false - } - inactiveIDs := p.filterStore.GetIDs(func(f upkeepFilter) bool { - uid := f.upkeepID.String() - _, ok := visited[uid] - visited[uid] = true - return !ok - }) - var merr error - if len(inactiveIDs) > 0 { - p.lggr.Debugw("Removing inactive upkeeps", "upkeeps", len(inactiveIDs)) - for _, id := range inactiveIDs { - if err := p.UnregisterFilter(ctx, id); err != nil { - merr = errors.Join(merr, fmt.Errorf("failed to unregister filter: %s", id.String())) - } - } - } - var newIDs []*big.Int - for id, ok := range visited { - if !ok { - uid, _ := new(big.Int).SetString(id, 10) - newIDs = append(newIDs, uid) - } - } - - return newIDs, merr -} - -func (p *logEventProvider) RegisterFilter(ctx context.Context, opts FilterOptions) error { - upkeepID, cfg := opts.UpkeepID, opts.TriggerConfig - if err := p.validateLogTriggerConfig(cfg); err != nil { - return fmt.Errorf("invalid log trigger config: %w", err) - } - lpFilter := p.newLogFilter(upkeepID, cfg) - - // using lock to facilitate multiple events causing filter registration - // at the same time. - // Exploratory: consider using a q to handle registration requests - p.registerLock.Lock() - defer p.registerLock.Unlock() - - var filter upkeepFilter - currentFilter := p.filterStore.Get(upkeepID) - if currentFilter != nil { - if currentFilter.configUpdateBlock > opts.UpdateBlock { - // already registered with a config from a higher block number - return fmt.Errorf("filter for upkeep with id %s already registered with newer config", upkeepID.String()) - } else if currentFilter.configUpdateBlock == opts.UpdateBlock { - // already registered with the same config - p.lggr.Debugf("filter for upkeep with id %s already registered with the same config", upkeepID.String()) - return nil - } - filter = *currentFilter - } else { // new filter - filter = upkeepFilter{ - upkeepID: upkeepID, - } - } - filter.lastPollBlock = 0 - filter.lastRePollBlock = 0 - filter.configUpdateBlock = opts.UpdateBlock - filter.selector = cfg.FilterSelector - filter.addr = cfg.ContractAddress.Bytes() - filter.topics = []common.Hash{cfg.Topic0, cfg.Topic1, cfg.Topic2, cfg.Topic3} - - if err := p.register(ctx, lpFilter, filter); err != nil { - return fmt.Errorf("failed to register upkeep filter %s: %w", filter.upkeepID.String(), err) - } - - return nil -} - -// register registers the upkeep filter with the log poller and adds it to the filter store. -func (p *logEventProvider) register(ctx context.Context, lpFilter logpoller.Filter, ufilter upkeepFilter) error { - latest, err := p.poller.LatestBlock(ctx) - if err != nil { - return fmt.Errorf("failed to get latest block while registering filter: %w", err) - } - lggr := logger.With(p.lggr, "upkeepID", ufilter.upkeepID.String()) - logPollerHasFilter := p.poller.HasFilter(lpFilter.Name) - filterStoreHasFilter := p.filterStore.Has(ufilter.upkeepID) - if filterStoreHasFilter { - // removing filter in case of an update so we can recreate it with updated values - lggr.Debugw("Upserting upkeep filter") - err := p.poller.UnregisterFilter(ctx, lpFilter.Name) - if err != nil { - return fmt.Errorf("failed to upsert (unregister) upkeep filter %s: %w", ufilter.upkeepID.String(), err) - } - } - if err := p.poller.RegisterFilter(ctx, lpFilter); err != nil { - return err - } - p.filterStore.AddActiveUpkeeps(ufilter) - if logPollerHasFilter { - // already registered in DB before, no need to backfill - return nil - } - backfillBlock := max( - // New chain, backfill from start - int64(ufilter.configUpdateBlock), - // backfill from config update block in case it is not too old - max(latest.BlockNumber-int64(LogBackfillBuffer), - - 1)) - // NOTE: replys are planned to be done as part of RegisterFilter within logpoller - lggr.Debugw("Backfilling logs for new upkeep filter", "backfillBlock", backfillBlock) - p.poller.ReplayAsync(backfillBlock) - - return nil -} - -func (p *logEventProvider) UnregisterFilter(ctx context.Context, upkeepID *big.Int) error { - // Filter might have been unregistered already, only try to unregister if it exists - if p.poller.HasFilter(p.filterName(upkeepID)) { - if err := p.poller.UnregisterFilter(ctx, p.filterName(upkeepID)); err != nil { - return fmt.Errorf("failed to unregister upkeep filter %s: %w", upkeepID.String(), err) - } - } - p.filterStore.RemoveActiveUpkeeps(upkeepFilter{ - upkeepID: upkeepID, - }) - return nil -} - -// newLogFilter creates logpoller.Filter from the given upkeep config -func (p *logEventProvider) newLogFilter(upkeepID *big.Int, cfg LogTriggerConfig) logpoller.Filter { - return logpoller.Filter{ - Name: p.filterName(upkeepID), - // log poller filter treats this event sigs slice as an array of topic0 - // since we don't support multiple events right now, only put one topic0 here - EventSigs: []common.Hash{common.BytesToHash(cfg.Topic0[:])}, - Addresses: []common.Address{cfg.ContractAddress}, - Retention: LogRetention, - } -} - -func (p *logEventProvider) validateLogTriggerConfig(cfg LogTriggerConfig) error { - var zeroAddr common.Address - var zeroBytes [32]byte - if bytes.Equal(cfg.ContractAddress[:], zeroAddr[:]) { - return errors.New("invalid contract address: zeroed") - } - if bytes.Equal(cfg.Topic0[:], zeroBytes[:]) { - return errors.New("invalid topic0: zeroed") - } - s := cfg.FilterSelector - if s >= 8 { - p.lggr.Error("filter selector %d is invalid", s) - return errors.New("invalid filter selector: larger or equal to 8") - } - return nil -} - -func (p *logEventProvider) filterName(upkeepID *big.Int) string { - return logpoller.FilterName("KeepersRegistry LogUpkeep", upkeepID.String()) -} diff --git a/pkg/automation/v21/logprovider/provider_life_cycle_test.go b/pkg/automation/v21/logprovider/provider_life_cycle_test.go deleted file mode 100644 index a7b61bf432..0000000000 --- a/pkg/automation/v21/logprovider/provider_life_cycle_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package logprovider - -import ( - "errors" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" -) - -func TestLogEventProvider_LifeCycle(t *testing.T) { - tests := []struct { - name string - errored bool - upkeepID *big.Int - upkeepCfg LogTriggerConfig - hasFilter bool - replyed bool - cfgUpdateBlock uint64 - mockPoller bool - unregister bool - }{ - { - "new upkeep", - false, - big.NewInt(111), - LogTriggerConfig{ - ContractAddress: common.BytesToAddress(common.LeftPadBytes([]byte{1, 2, 3, 4}, 20)), - Topic0: common.BytesToHash(common.LeftPadBytes([]byte{1, 2, 3, 4}, 32)), - }, - false, - true, - uint64(1), - true, - false, - }, - { - "empty config", - true, - big.NewInt(111), - LogTriggerConfig{}, - false, - false, - uint64(0), - false, - false, - }, - { - "invalid config", - true, - big.NewInt(111), - LogTriggerConfig{ - ContractAddress: common.BytesToAddress(common.LeftPadBytes([]byte{}, 20)), - Topic0: common.BytesToHash(common.LeftPadBytes([]byte{}, 32)), - }, - false, - false, - uint64(2), - false, - false, - }, - { - "existing config with old block", - true, - big.NewInt(111), - LogTriggerConfig{ - ContractAddress: common.BytesToAddress(common.LeftPadBytes([]byte{1, 2, 3, 4}, 20)), - Topic0: common.BytesToHash(common.LeftPadBytes([]byte{1, 2, 3, 4}, 32)), - }, - true, - false, - uint64(0), - true, - false, - }, - { - "existing config with newer block", - false, - big.NewInt(111), - LogTriggerConfig{ - ContractAddress: common.BytesToAddress(common.LeftPadBytes([]byte{1, 2, 3, 4}, 20)), - Topic0: common.BytesToHash(common.LeftPadBytes([]byte{1, 2, 3, 4}, 32)), - }, - true, - false, - uint64(2), - true, - true, - }, - } - - p := NewLogProvider(logger.Test(t), nil, big.NewInt(1), &mockedPacker{}, NewUpkeepFilterStore(), NewOptions(200, big.NewInt(1))) - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := t.Context() - - if tc.mockPoller { - lp := mocks.NewLogPoller(t) - servicetest.SetupNoOpMock(lp) - lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil).Maybe() - lp.On("UnregisterFilter", mock.Anything, mock.Anything).Return(nil).Maybe() - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{}, nil).Maybe() - lp.On("HasFilter", p.filterName(tc.upkeepID)).Return(tc.hasFilter).Maybe() - if tc.replyed { - lp.On("ReplayAsync", mock.Anything).Return(nil).Times(1) - } - p.lock.Lock() - p.poller = lp - p.lock.Unlock() - } - - err := p.RegisterFilter(ctx, FilterOptions{ - UpkeepID: tc.upkeepID, - TriggerConfig: tc.upkeepCfg, - UpdateBlock: tc.cfgUpdateBlock, - }) - if tc.errored { - require.Error(t, err) - } else { - require.NoError(t, err) - if tc.unregister { - require.NoError(t, p.UnregisterFilter(ctx, tc.upkeepID)) - } - } - }) - } -} - -func TestEventLogProvider_RefreshActiveUpkeeps(t *testing.T) { - ctx := t.Context() - mp := mocks.NewLogPoller(t) - mp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil) - mp.On("HasFilter", mock.Anything).Return(false) - mp.On("LatestBlock", mock.Anything).Return(logpoller.Block{}, nil) - mp.On("ReplayAsync", mock.Anything).Return(nil) - - p := NewLogProvider(logger.Test(t), mp, big.NewInt(1), &mockedPacker{}, NewUpkeepFilterStore(), NewOptions(200, big.NewInt(1))) - - require.NoError(t, p.RegisterFilter(ctx, FilterOptions{ - UpkeepID: core.GenUpkeepID(types.LogTrigger, "1111").BigInt(), - TriggerConfig: LogTriggerConfig{ - ContractAddress: common.BytesToAddress(common.LeftPadBytes([]byte{1, 2, 3, 4}, 20)), - Topic0: common.BytesToHash(common.LeftPadBytes([]byte{1, 2, 3, 4}, 32)), - }, - UpdateBlock: uint64(0), - })) - require.NoError(t, p.RegisterFilter(ctx, FilterOptions{ - UpkeepID: core.GenUpkeepID(types.LogTrigger, "2222").BigInt(), - TriggerConfig: LogTriggerConfig{ - ContractAddress: common.BytesToAddress(common.LeftPadBytes([]byte{1, 2, 3, 4}, 20)), - Topic0: common.BytesToHash(common.LeftPadBytes([]byte{1, 2, 3, 4}, 32)), - }, - UpdateBlock: uint64(0), - })) - require.Equal(t, 2, p.filterStore.Size()) - - newIds, err := p.RefreshActiveUpkeeps(ctx) - require.NoError(t, err) - require.Empty(t, newIds) - mp.On("HasFilter", p.filterName(core.GenUpkeepID(types.LogTrigger, "2222").BigInt())).Return(true) - newIds, err = p.RefreshActiveUpkeeps( - ctx, - core.GenUpkeepID(types.LogTrigger, "2222").BigInt(), - core.GenUpkeepID(types.LogTrigger, "1234").BigInt(), - core.GenUpkeepID(types.LogTrigger, "123").BigInt()) - require.NoError(t, err) - require.Len(t, newIds, 2) - require.Equal(t, 1, p.filterStore.Size()) -} - -func TestLogEventProvider_ValidateLogTriggerConfig(t *testing.T) { - contractAddress := common.HexToAddress("0xB9F3af0c2CbfE108efd0E23F7b0a151Ea42f764E") - eventSig := common.HexToHash("0x3bdab8bffae631cfee411525ebae27f3fb61b10c662c09ec2a7dbb5854c87e8c") - tests := []struct { - name string - cfg LogTriggerConfig - expectedErr error - }{ - { - "success", - LogTriggerConfig{ - ContractAddress: contractAddress, - FilterSelector: 0, - Topic0: eventSig, - }, - nil, - }, - { - "invalid contract address", - LogTriggerConfig{ - ContractAddress: common.Address{}, - FilterSelector: 0, - Topic0: eventSig, - }, - errors.New("invalid contract address: zeroed"), - }, - { - "invalid topic0", - LogTriggerConfig{ - ContractAddress: contractAddress, - FilterSelector: 0, - }, - errors.New("invalid topic0: zeroed"), - }, - { - "success", - LogTriggerConfig{ - ContractAddress: contractAddress, - FilterSelector: 8, - Topic0: eventSig, - }, - errors.New("invalid filter selector: larger or equal to 8"), - }, - } - - p := NewLogProvider(logger.Test(t), nil, big.NewInt(1), &mockedPacker{}, NewUpkeepFilterStore(), NewOptions(200, big.NewInt(1))) - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - err := p.validateLogTriggerConfig(tc.cfg) - assert.Equal(t, tc.expectedErr, err) - }) - } -} diff --git a/pkg/automation/v21/logprovider/provider_test.go b/pkg/automation/v21/logprovider/provider_test.go deleted file mode 100644 index 1ec859b82d..0000000000 --- a/pkg/automation/v21/logprovider/provider_test.go +++ /dev/null @@ -1,646 +0,0 @@ -package logprovider - -import ( - "context" - "fmt" - "math/big" - "runtime" - "strconv" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" -) - -func TestLogEventProvider_GetFilters(t *testing.T) { - p := NewLogProvider(logger.Test(t), nil, big.NewInt(1), &mockedPacker{}, NewUpkeepFilterStore(), NewOptions(200, big.NewInt(1))) - - _, f := newEntry(p, 1) - p.filterStore.AddActiveUpkeeps(f) - - t.Run("no filters", func(t *testing.T) { - filters := p.getFilters(0, big.NewInt(0)) - require.Len(t, filters, 1) - require.Empty(t, filters[0].addr) - }) - - t.Run("has filter with lower lastPollBlock", func(t *testing.T) { - filters := p.getFilters(0, f.upkeepID) - require.Len(t, filters, 1) - require.NotEmpty(t, filters[0].addr) - filters = p.getFilters(10, f.upkeepID) - require.Len(t, filters, 1) - require.NotEmpty(t, filters[0].addr) - }) - - t.Run("has filter with higher lastPollBlock", func(t *testing.T) { - _, f := newEntry(p, 2) - f.lastPollBlock = 3 - p.filterStore.AddActiveUpkeeps(f) - - filters := p.getFilters(1, f.upkeepID) - require.Len(t, filters, 1) - require.Empty(t, filters[0].addr) - }) - - t.Run("has filter with higher configUpdateBlock", func(t *testing.T) { - _, f := newEntry(p, 2) - f.configUpdateBlock = 3 - p.filterStore.AddActiveUpkeeps(f) - - filters := p.getFilters(1, f.upkeepID) - require.Len(t, filters, 1) - require.Empty(t, filters[0].addr) - }) -} - -func TestLogEventProvider_UpdateEntriesLastPoll(t *testing.T) { - p := NewLogProvider(logger.Test(t), nil, big.NewInt(1), &mockedPacker{}, NewUpkeepFilterStore(), NewOptions(200, big.NewInt(1))) - - n := 10 - - // entries := map[string]upkeepFilter{} - for i := range n { - _, f := newEntry(p, i+1) - p.filterStore.AddActiveUpkeeps(f) - } - - t.Run("no entries", func(t *testing.T) { - _, f := newEntry(p, n*2) - f.lastPollBlock = 10 - p.updateFiltersLastPoll([]upkeepFilter{f}) - - filters := p.filterStore.GetFilters(nil) - for _, f := range filters { - require.Equal(t, int64(0), f.lastPollBlock) - } - }) - - t.Run("update entries", func(t *testing.T) { - _, f2 := newEntry(p, n-2) - f2.lastPollBlock = 10 - _, f1 := newEntry(p, n-1) - f1.lastPollBlock = 10 - p.updateFiltersLastPoll([]upkeepFilter{f1, f2}) - - p.filterStore.RangeFiltersByIDs(func(_ int, f upkeepFilter) { - require.Equal(t, int64(10), f.lastPollBlock) - }, f1.upkeepID, f2.upkeepID) - - // update with same block - p.updateFiltersLastPoll([]upkeepFilter{f1}) - - // checking other entries are not updated - _, f := newEntry(p, 1) - p.filterStore.RangeFiltersByIDs(func(_ int, f upkeepFilter) { - require.Equal(t, int64(0), f.lastPollBlock) - }, f.upkeepID) - }) -} - -func TestLogEventProvider_ScheduleReadJobs(t *testing.T) { - mp := mocks.NewLogPoller(t) - - tests := []struct { - name string - maxBatchSize int - ids []int - addrs []string - }{ - { - "no entries", - 3, - []int{}, - []string{}, - }, - { - "single entry", - 3, - []int{1}, - []string{"0x1111111"}, - }, - { - "happy flow", - 3, - []int{1, 2, 3}, - []string{"0x1111111", "0x2222222", "0x3333333"}, - }, - { - "batching", - 3, - []int{ - 1, 2, 3, - 4, 5, 6, - 7, 8, 9, - 10, 11, 12, - 13, 14, 15, - 16, 17, 18, - 19, 20, 21, - }, - []string{ - "0x11111111", - "0x22222222", - "0x33333333", - "0x111111111", - "0x122222222", - "0x133333333", - "0x1111111111", - "0x1122222222", - "0x1133333333", - "0x11111111111", - "0x11122222222", - "0x11133333333", - "0x111111111111", - "0x111122222222", - "0x111133333333", - "0x1111111111111", - "0x1111122222222", - "0x1111133333333", - "0x11111111111111", - "0x11111122222222", - "0x11111133333333", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := t.Context() - - readInterval := 10 * time.Millisecond - opts := NewOptions(200, big.NewInt(1)) - opts.ReadInterval = readInterval - - p := NewLogProvider(logger.Test(t), mp, big.NewInt(1), &mockedPacker{}, NewUpkeepFilterStore(), opts) - - var ids []*big.Int - for i, id := range tc.ids { - _, f := newEntry(p, id, tc.addrs[i]) - p.filterStore.AddActiveUpkeeps(f) - ids = append(ids, f.upkeepID) - } - - reads := make(chan []*big.Int, 100) - - go func(ctx context.Context) { - p.scheduleReadJobs(ctx, func(ids []*big.Int) { - select { - case reads <- ids: - default: - t.Log("dropped ids") - } - }) - }(ctx) - - batches := (len(tc.ids) / tc.maxBatchSize) + 1 - - timeoutTicker := time.NewTicker(readInterval * time.Duration(batches*10)) - defer timeoutTicker.Stop() - - got := map[string]int{} - - readLoop: - for { - select { - case <-timeoutTicker.C: - break readLoop - case batch := <-reads: - for _, id := range batch { - got[id.String()]++ - } - case <-ctx.Done(): - break readLoop - default: - if p.CurrentPartitionIdx() > uint64(batches+1) { - break readLoop - } - } - runtime.Gosched() - } - - require.Len(t, got, len(ids)) - for _, id := range ids { - _, ok := got[id.String()] - require.True(t, ok, "id not found %s", id.String()) - require.GreaterOrEqual(t, got[id.String()], 1, "id don't have schdueled job %s", id.String()) - } - }) - } -} - -func TestLogEventProvider_ReadLogs(t *testing.T) { - ctx := t.Context() - - mp := mocks.NewLogPoller(t) - - mp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil) - mp.On("ReplayAsync", mock.Anything).Return() - mp.On("HasFilter", mock.Anything).Return(false) - mp.On("UnregisterFilter", mock.Anything, mock.Anything).Return(nil).Maybe() - mp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: int64(1)}, nil) - mp.On("LogsWithSigs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]logpoller.Log{ - { - BlockNumber: 1, - TxHash: common.HexToHash("0x1"), - }, - }, nil).Maybe() - - filterStore := NewUpkeepFilterStore() - p := NewLogProvider(logger.Test(t), mp, big.NewInt(1), &mockedPacker{}, filterStore, NewOptions(200, big.NewInt(1))) - - for i := range 10 { - cfg, f := newEntry(p, i+1) - require.NoError(t, p.RegisterFilter(ctx, FilterOptions{ - UpkeepID: f.upkeepID, - TriggerConfig: cfg, - })) - } - - // TODO: test rate limiting -} - -func newEntry(p *logEventProvider, i int, args ...string) (LogTriggerConfig, upkeepFilter) { - idBytes := append(common.LeftPadBytes([]byte{1}, 16), []byte(strconv.Itoa(i))...) - id := ocr2keepers.UpkeepIdentifier{} - copy(id[:], idBytes) - uid := id.BigInt() - for len(args) < 2 { - args = append(args, "0x3d53a39550e04688065827f3bb86584cb007ab9ebca7ebd528e7301c9c31eb5d") - } - addr, topic0 := args[0], args[1] - cfg := LogTriggerConfig{ - ContractAddress: common.HexToAddress(addr), - FilterSelector: 0, - Topic0: common.HexToHash(topic0), - } - filter := p.newLogFilter(uid, cfg) - topics := make([]common.Hash, len(filter.EventSigs)) - copy(topics, filter.EventSigs) - f := upkeepFilter{ - upkeepID: uid, - addr: filter.Addresses[0].Bytes(), - topics: topics, - } - return cfg, f -} - -func countRemainingLogs(logs map[int64][]logpoller.Log) int { - count := 0 - for _, logList := range logs { - count += len(logList) - } - return count -} - -func remainingBlockWindowCounts(queues map[string]*upkeepLogQueue, blockRate int) map[int64]int { - blockWindowCounts := map[int64]int{} - - for _, q := range queues { - for blockNumber, logs := range q.logs { - start, _ := getBlockWindow(blockNumber, blockRate) - - blockWindowCounts[start] += len(logs) - } - } - - return blockWindowCounts -} - -func TestLogEventProvider_GetLatestPayloads(t *testing.T) { - t.Run("dequeuing from an empty buffer returns 0 logs", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Empty(t, payloads) - }) - - t.Run("a single log for a single upkeep gets dequeued", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - buffer := provider.buffer - - buffer.Enqueue(big.NewInt(1), logpoller.Log{BlockNumber: 1, TxHash: common.HexToHash("0x1"), LogIndex: 0}) - - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 1) - }) - - t.Run("a log per upkeep for 4 upkeeps across 4 blocks (2 separate block windows) is dequeued, for a total of 4 payloads", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - buffer := provider.buffer - - buffer.Enqueue(big.NewInt(1), logpoller.Log{BlockNumber: 1, TxHash: common.HexToHash("0x1"), LogIndex: 0}) - buffer.Enqueue(big.NewInt(2), logpoller.Log{BlockNumber: 2, TxHash: common.HexToHash("0x2"), LogIndex: 0}) - buffer.Enqueue(big.NewInt(3), logpoller.Log{BlockNumber: 3, TxHash: common.HexToHash("0x3"), LogIndex: 0}) - buffer.Enqueue(big.NewInt(4), logpoller.Log{BlockNumber: 4, TxHash: common.HexToHash("0x4"), LogIndex: 0}) - - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 4) - }) - - t.Run("100 logs are dequeued for a single upkeep, 1 log for every block window across 100 blocks followed by best effort", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 101, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - buffer := provider.buffer.(*logBuffer) - - for i := range 100 { - buffer.Enqueue(big.NewInt(1), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x%d", i+1)), LogIndex: 0}) - } - - assert.Equal(t, 100, countRemainingLogs(buffer.getQueue("1").logs)) - - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - assert.Equal(t, 0, countRemainingLogs(buffer.getQueue("1").logs)) - }) - - t.Run("100 logs are dequeued for two upkeeps, 25 logs each as min commitment (50 logs total best effort), followed by best effort", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 101, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - buffer := provider.buffer.(*logBuffer) - - for i := range 100 { - buffer.Enqueue(big.NewInt(1), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x1%d", i+1)), LogIndex: 0}) - buffer.Enqueue(big.NewInt(2), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x2%d", i+1)), LogIndex: 0}) - } - - assert.Equal(t, 100, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 100, countRemainingLogs(buffer.getQueue("2").logs)) - - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - assert.Equal(t, 50, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 50, countRemainingLogs(buffer.getQueue("2").logs)) - - windowCount := remainingBlockWindowCounts(buffer.queues, 4) - - assert.Equal(t, 2, windowCount[0]) - assert.Equal(t, 4, windowCount[48]) - assert.Equal(t, 4, windowCount[96]) - - // the second dequeue call will retrieve the remaining 100 logs and exhaust the queues - payloads, err = provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - assert.Equal(t, 0, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 0, countRemainingLogs(buffer.getQueue("2").logs)) - - windowCount = remainingBlockWindowCounts(buffer.queues, 4) - - assert.Equal(t, 0, windowCount[0]) - assert.Equal(t, 0, windowCount[48]) - assert.Equal(t, 0, windowCount[96]) - }) - - t.Run("minimum guaranteed for all windows including an incomplete window followed by best effort", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 102, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - buffer := provider.buffer.(*logBuffer) - - for i := range 102 { - buffer.Enqueue(big.NewInt(1), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x1%d", i+1)), LogIndex: 0}) - buffer.Enqueue(big.NewInt(2), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x2%d", i+1)), LogIndex: 0}) - } - - assert.Equal(t, 102, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 102, countRemainingLogs(buffer.getQueue("2").logs)) - - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - windowCount := remainingBlockWindowCounts(buffer.queues, 4) - - assert.Equal(t, 6, windowCount[100]) - - payloads, err = provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - // upkeep 1 has had the minimum number of logs dequeued on the latest (incomplete) window - assert.Equal(t, 1, buffer.getQueue("1").getDequeued(100)) - // upkeep 2 has had the minimum number of logs dequeued on the latest (incomplete) window - assert.Equal(t, 1, buffer.getQueue("2").getDequeued(100)) - - // the third dequeue call will retrieve the remaining 100 logs and exhaust the queues - payloads, err = provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 4) - - assert.Equal(t, 0, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 0, countRemainingLogs(buffer.getQueue("2").logs)) - - windowCount = remainingBlockWindowCounts(buffer.queues, 4) - - assert.Equal(t, 0, windowCount[0]) - assert.Equal(t, 0, windowCount[28]) - assert.Equal(t, 0, windowCount[32]) - assert.Equal(t, 0, windowCount[36]) - assert.Equal(t, 0, windowCount[48]) - assert.Equal(t, 0, windowCount[96]) - assert.Equal(t, 0, windowCount[100]) - }) - - t.Run("min dequeue followed by best effort followed by reorg followed by best effort", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 101, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - buffer := provider.buffer.(*logBuffer) - - for i := range 100 { - buffer.Enqueue(big.NewInt(1), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x1%d", i+1)), LogIndex: 0}) - buffer.Enqueue(big.NewInt(2), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x2%d", i+1)), LogIndex: 0}) - } - - assert.Equal(t, 100, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 100, countRemainingLogs(buffer.getQueue("2").logs)) - - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - windowCount := remainingBlockWindowCounts(buffer.queues, 4) - - assert.Equal(t, 4, windowCount[28]) - - // reorg block 28 - buffer.Enqueue(big.NewInt(1), logpoller.Log{BlockNumber: int64(28), TxHash: common.HexToHash(fmt.Sprintf("0xreorg1%d", 28)), LogIndex: 0, BlockHash: common.BytesToHash([]byte("reorg"))}) - buffer.Enqueue(big.NewInt(2), logpoller.Log{BlockNumber: int64(28), TxHash: common.HexToHash(fmt.Sprintf("0xreorg2%d", 28)), LogIndex: 0, BlockHash: common.BytesToHash([]byte("reorg"))}) - - windowCount = remainingBlockWindowCounts(buffer.queues, 4) - - assert.Equal(t, 6, windowCount[28]) - - // the second dequeue call will retrieve the remaining 100 logs and exhaust the queues - payloads, err = provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - windowCount = remainingBlockWindowCounts(buffer.queues, 4) - - assert.Equal(t, 0, windowCount[0]) - assert.Equal(t, 0, windowCount[28]) - assert.Equal(t, 0, windowCount[32]) - assert.Equal(t, 0, windowCount[36]) - assert.Equal(t, 0, windowCount[48]) - assert.Equal(t, 2, windowCount[96]) // these 2 remaining logs are because of the 2 re orgd logs taking up dequeue space - }) - - t.Run("sparsely populated blocks", func(t *testing.T) { - opts := NewOptions(200, big.NewInt(42161)) - - logPoller := &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - } - - provider := NewLogProvider(logger.Test(t), logPoller, big.NewInt(42161), &mockedPacker{}, nil, opts) - - ctx := t.Context() - - buffer := provider.buffer.(*logBuffer) - - upkeepOmittedOnBlocks := map[int64][]int{ - 1: {5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100}, // upkeep 1 won't have logs on 20 blocks - 2: {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100}, // upkeep 2 won't have logs on 50 blocks - 3: {3, 13, 23, 33, 43, 53, 63, 73, 83, 93}, // upkeep 3 won't appear on 10 blocks - 4: {1, 25, 50, 75, 100}, // upkeep 4 won't appear on 5 blocks - 5: {}, // upkeep 5 appears on all blocks - } - - for upkeep, skipBlocks := range upkeepOmittedOnBlocks { - blockLoop: - for i := range 100 { - for _, block := range skipBlocks { - if block == i+1 { - continue blockLoop - } - } - buffer.Enqueue(big.NewInt(upkeep), logpoller.Log{BlockNumber: int64(i + 1), TxHash: common.HexToHash(fmt.Sprintf("0x1%d", i+1)), LogIndex: 0}) - } - } - - assert.Equal(t, 80, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 50, countRemainingLogs(buffer.getQueue("2").logs)) - assert.Equal(t, 90, countRemainingLogs(buffer.getQueue("3").logs)) - assert.Equal(t, 95, countRemainingLogs(buffer.getQueue("4").logs)) - assert.Equal(t, 100, countRemainingLogs(buffer.getQueue("5").logs)) - - // perform two dequeues - payloads, err := provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - payloads, err = provider.GetLatestPayloads(ctx) - assert.NoError(t, err) - assert.Len(t, payloads, 100) - - assert.Equal(t, 40, countRemainingLogs(buffer.getQueue("1").logs)) - assert.Equal(t, 10, countRemainingLogs(buffer.getQueue("2").logs)) - assert.Equal(t, 50, countRemainingLogs(buffer.getQueue("3").logs)) - assert.Equal(t, 55, countRemainingLogs(buffer.getQueue("4").logs)) - assert.Equal(t, 60, countRemainingLogs(buffer.getQueue("5").logs)) - }) -} - -type mockedPacker struct { -} - -func (p *mockedPacker) PackLogData(log logpoller.Log) ([]byte, error) { - return log.Data, nil -} - -func (l *logBuffer) getQueue(k string) *upkeepLogQueue { - l.lock.RLock() - defer l.lock.RUnlock() - return l.queues[k] -} diff --git a/pkg/automation/v21/logprovider/recoverer.go b/pkg/automation/v21/logprovider/recoverer.go deleted file mode 100644 index bf49f6b8f4..0000000000 --- a/pkg/automation/v21/logprovider/recoverer.go +++ /dev/null @@ -1,731 +0,0 @@ -package logprovider - -import ( - "bytes" - "context" - "crypto/rand" - "errors" - "fmt" - "io" - "math" - "math/big" - "slices" - "sort" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/random" - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-common/pkg/timeutil" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/prommetrics" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" - "github.com/smartcontractkit/chainlink-evm/pkg/client" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -var ( - LogRecovererServiceName = "LogRecoverer" - - // RecoveryInterval is the interval at which the recovery scanning processing is triggered - RecoveryInterval = 5 * time.Second - // RecoveryCacheTTL is the time to live for the recovery cache - RecoveryCacheTTL = 10 * time.Minute - // GCInterval is the interval at which the recovery cache is cleaned up - GCInterval = RecoveryCacheTTL - time.Second - // MaxProposals is the maximum number of proposals that can be returned by GetRecoveryProposals - MaxProposals = 20 - // recoveryBatchSize is the number of filters to recover in a single batch - recoveryBatchSize = 10 - // recoveryLogsBuffer is the number of blocks to be used as a safety buffer when reading logs - recoveryLogsBuffer = int64(200) - recoveryLogsBurst = int64(500) - // blockTimeUpdateCadence is the cadence at which the chain's blocktime is re-calculated - blockTimeUpdateCadence = 10 * time.Minute - // maxPendingPayloadsPerUpkeep is the number of logs we can have pending for a single upkeep - // at any given time - maxPendingPayloadsPerUpkeep = 500 -) - -type LogRecoverer interface { - ocr2keepers.RecoverableProvider - GetProposalData(context.Context, ocr2keepers.CoordinatedBlockProposal) ([]byte, error) - - Start(context.Context) error - io.Closer -} - -type visitedRecord struct { - visitedAt time.Time - payload ocr2keepers.UpkeepPayload -} - -type logRecoverer struct { - services.StateMachine - threadCtrl threadcontrol.ThreadControl - - lggr logger.SugaredLogger - - lookbackBlocks *atomic.Int64 - blockTime *atomic.Int64 - - interval time.Duration - lock sync.RWMutex - - pending []ocr2keepers.UpkeepPayload - visited map[string]visitedRecord - - filterStore UpkeepFilterStore - states core.UpkeepStateReader - packer LogDataPacker - poller logpoller.LogPoller - client client.Client - blockTimeResolver *blockTimeResolver - - finalityDepth int64 -} - -var _ LogRecoverer = &logRecoverer{} - -func NewLogRecoverer(lggr logger.Logger, poller logpoller.LogPoller, client client.Client, stateStore core.UpkeepStateReader, packer LogDataPacker, filterStore UpkeepFilterStore, opts LogTriggersOptions) *logRecoverer { - rec := &logRecoverer{ - lggr: logger.Sugared(lggr).Named(LogRecovererServiceName), - - threadCtrl: threadcontrol.NewThreadControl(), - - blockTime: new(atomic.Int64), - lookbackBlocks: new(atomic.Int64), - interval: opts.ReadInterval * 5, - - pending: make([]ocr2keepers.UpkeepPayload, 0), - visited: make(map[string]visitedRecord), - poller: poller, - filterStore: filterStore, - states: stateStore, - packer: packer, - client: client, - blockTimeResolver: newBlockTimeResolver(poller), - - finalityDepth: opts.FinalityDepth, - } - - rec.lookbackBlocks.Store(opts.LookbackBlocks) - rec.blockTime.Store(int64(defaultBlockTime)) - - return rec -} - -// Start starts the log recoverer, which runs 3 threads in the background: -// 1. Recovery thread: scans for logs that were missed by the log poller -// 2. Cleanup thread: cleans up the cache of logs that were already processed -// 3. Block time thread: updates the block time of the chain -func (r *logRecoverer) Start(ctx context.Context) error { - return r.StartOnce(LogRecovererServiceName, func() error { - r.updateBlockTime(ctx) - - r.lggr.Infow("starting log recoverer", "blockTime", r.blockTime.Load(), "lookbackBlocks", r.lookbackBlocks.Load(), "interval", r.interval) - - r.threadCtrl.Go(func(ctx context.Context) { - recoveryTicker := time.NewTicker(r.interval) - defer recoveryTicker.Stop() - - for { - select { - case <-recoveryTicker.C: - if err := r.recover(ctx); err != nil { - r.lggr.Warnw("failed to recover logs", "err", err) - } - case <-ctx.Done(): - return - } - } - }) - - r.threadCtrl.Go(func(ctx context.Context) { - cleanupTicker := services.NewTicker(GCInterval) - defer cleanupTicker.Stop() - - for { - select { - case <-cleanupTicker.C: - r.clean(ctx) - cleanupTicker.Reset() - case <-ctx.Done(): - return - } - } - }) - - r.threadCtrl.Go(func(ctx context.Context) { - blockTimeTicker := time.NewTicker(blockTimeUpdateCadence) - defer blockTimeTicker.Stop() - - for { - select { - case <-blockTimeTicker.C: - r.updateBlockTime(ctx) - blockTimeTicker.Reset(timeutil.JitterPct(0.1).Apply(blockTimeUpdateCadence)) - case <-ctx.Done(): - return - } - } - }) - - return nil - }) -} - -func (r *logRecoverer) Close() error { - return r.StopOnce(LogRecovererServiceName, func() error { - r.threadCtrl.Close() - return nil - }) -} - -func (r *logRecoverer) HealthReport() map[string]error { - return map[string]error{LogRecovererServiceName: r.Healthy()} -} - -func (r *logRecoverer) GetProposalData(ctx context.Context, proposal ocr2keepers.CoordinatedBlockProposal) ([]byte, error) { - switch core.GetUpkeepType(proposal.UpkeepID) { - case types.LogTrigger: - return r.getLogTriggerCheckData(ctx, proposal) - default: - return []byte{}, errors.New("not a log trigger upkeep ID") - } -} - -func (r *logRecoverer) getLogTriggerCheckData(ctx context.Context, proposal ocr2keepers.CoordinatedBlockProposal) ([]byte, error) { - if !r.filterStore.Has(proposal.UpkeepID.BigInt()) { - return nil, fmt.Errorf("filter not found for upkeep %v", proposal.UpkeepID) - } - latest, err := r.poller.LatestBlock(ctx) - if err != nil { - return nil, err - } - - start, offsetBlock := r.getRecoveryWindow(latest.BlockNumber) - if proposal.Trigger.LogTriggerExtension == nil { - return nil, errors.New("missing log trigger extension") - } - - // Verify the log is still present on chain, not reorged and is within recoverable range - // Do not trust the logBlockNumber from proposal since it's not included in workID - logBlockHash := common.BytesToHash(proposal.Trigger.LogTriggerExtension.BlockHash[:]) - bn, bh, err := core.GetTxBlock(ctx, r.client, proposal.Trigger.LogTriggerExtension.TxHash) - if err != nil { - return nil, err - } - if bn == nil { - return nil, errors.New("failed to get tx block") - } - if bh.Hex() != logBlockHash.Hex() { - return nil, errors.New("log tx reorged") - } - logBlock := bn.Int64() - if isRecoverable := logBlock < offsetBlock && logBlock > start; !isRecoverable { - return nil, errors.New("log block is not recoverable") - } - - // Check if the log was already performed or ineligible - upkeepStates, err := r.states.SelectByWorkIDs(ctx, proposal.WorkID) - if err != nil { - return nil, err - } - for _, upkeepState := range upkeepStates { - switch upkeepState { - case ocr2keepers.Performed, ocr2keepers.Ineligible: - return nil, errors.New("upkeep state is not recoverable") - default: - // we can proceed - } - } - - var filter upkeepFilter - r.filterStore.RangeFiltersByIDs(func(i int, f upkeepFilter) { - filter = f - }, proposal.UpkeepID.BigInt()) - - if len(filter.addr) == 0 { - return nil, fmt.Errorf("invalid filter found for upkeepID %s", proposal.UpkeepID.String()) - } - if filter.configUpdateBlock > uint64(logBlock) { - return nil, fmt.Errorf("log block %d is before the filter configUpdateBlock %d for upkeepID %s", logBlock, filter.configUpdateBlock, proposal.UpkeepID.String()) - } - - logs, err := r.poller.LogsWithSigs(ctx, logBlock-1, logBlock+1, filter.topics, common.BytesToAddress(filter.addr)) - if err != nil { - return nil, fmt.Errorf("could not read logs: %w", err) - } - logs = filter.Select(logs...) - - for _, log := range logs { - trigger := logToTrigger(log) - // use coordinated proposal block number as checkblock/hash - trigger.BlockHash = proposal.Trigger.BlockHash - trigger.BlockNumber = proposal.Trigger.BlockNumber - wid := core.UpkeepWorkID(proposal.UpkeepID, trigger) - if wid == proposal.WorkID { - r.lggr.Debugw("found log for proposal", "upkeepId", proposal.UpkeepID, "trigger.ext", trigger.LogTriggerExtension) - checkData, err := r.packer.PackLogData(log) - if err != nil { - return nil, fmt.Errorf("failed to pack log data: %w", err) - } - return checkData, nil - } - } - return nil, fmt.Errorf("no log found for upkeepID %v and trigger %+v", proposal.UpkeepID, proposal.Trigger) -} - -func (r *logRecoverer) GetRecoveryProposals(ctx context.Context) ([]ocr2keepers.UpkeepPayload, error) { - latestBlock, err := r.poller.LatestBlock(ctx) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrHeadNotAvailable, err) - } - - r.lock.Lock() - defer r.lock.Unlock() - - if len(r.pending) == 0 { - return nil, nil - } - - allLogsCounter := 0 - logsCount := map[string]int{} - - r.sortPending(uint64(latestBlock.BlockNumber)) - - var results, pending []ocr2keepers.UpkeepPayload - for _, payload := range r.pending { - if allLogsCounter >= MaxProposals { - // we have enough proposals, the rest are pushed back to pending - pending = append(pending, payload) - continue - } - uid := payload.UpkeepID.String() - if logsCount[uid] >= AllowedLogsPerUpkeep { - // we have enough proposals for this upkeep, the rest are pushed back to pending - pending = append(pending, payload) - continue - } - results = append(results, payload) - logsCount[uid]++ - allLogsCounter++ - } - - r.pending = pending - prommetrics.AutomationRecovererPendingPayloads.Set(float64(len(r.pending))) - - r.lggr.Debugf("found %d recoverable payloads", len(results)) - - return results, nil -} - -func (r *logRecoverer) recover(ctx context.Context) error { - latest, err := r.poller.LatestBlock(ctx) - if err != nil { - return fmt.Errorf("%w: %w", ErrHeadNotAvailable, err) - } - - start, offsetBlock := r.getRecoveryWindow(latest.BlockNumber) - if offsetBlock < 0 { - // too soon to recover, we don't have enough blocks - return nil - } - if start < 0 { - start = 0 - } - - filters := r.getFilterBatch(offsetBlock) - if len(filters) == 0 { - return nil - } - - r.lggr.Debugw("recovering logs", "filters", filters, "startBlock", start, "offsetBlock", offsetBlock, "latestBlock", latest) - - var wg sync.WaitGroup - for _, f := range filters { - wg.Add(1) - go func(f upkeepFilter) { - defer wg.Done() - if err := r.recoverFilter(ctx, f, start, offsetBlock); err != nil { - r.lggr.Debugw("error recovering filter", "err", err.Error()) - } - }(f) - } - wg.Wait() - - return nil -} - -// recoverFilter recovers logs for a single upkeep filter. -func (r *logRecoverer) recoverFilter(ctx context.Context, f upkeepFilter, startBlock, offsetBlock int64) error { - start := f.lastRePollBlock + 1 // NOTE: we expect f.lastRePollBlock + 1 <= offsetBlock, as others would have been filtered out - // ensure we don't recover logs from before the filter was created - if configUpdateBlock := int64(f.configUpdateBlock); start < configUpdateBlock { - // NOTE: we expect that configUpdateBlock <= offsetBlock, as others would have been filtered out - start = configUpdateBlock - } - if start < startBlock { - start = startBlock - } - end := start + recoveryLogsBuffer - if offsetBlock-end > 100*recoveryLogsBuffer { - // If recoverer is lagging by a lot (more than 100x recoveryLogsBuffer), allow - // a range of recoveryLogsBurst - // Exploratory: Store lastRePollBlock in DB to prevent bursts during restarts - // (while also taking into account existing pending payloads) - end = start + recoveryLogsBurst - } - if end > offsetBlock { - end = offsetBlock - } - // we expect start to be > offsetBlock in any case - logs, err := r.poller.LogsWithSigs(ctx, start, end, f.topics, common.BytesToAddress(f.addr)) - if err != nil { - return fmt.Errorf("could not read logs: %w", err) - } - logs = f.Select(logs...) - - workIDs := make([]string, 0) - for _, log := range logs { - trigger := logToTrigger(log) - upkeepId := &ocr2keepers.UpkeepIdentifier{} - ok := upkeepId.FromBigInt(f.upkeepID) - if !ok { - r.lggr.Warnw("failed to convert upkeepID to UpkeepIdentifier", "upkeepID", f.upkeepID) - continue - } - workIDs = append(workIDs, core.UpkeepWorkID(*upkeepId, trigger)) - } - - states, err := r.states.SelectByWorkIDs(ctx, workIDs...) - if err != nil { - return fmt.Errorf("could not read states: %w", err) - } - if len(logs) != len(states) { - return fmt.Errorf("log and state count mismatch: %d != %d", len(logs), len(states)) - } - filteredLogs := r.filterFinalizedStates(f, logs, states) - - added, alreadyPending, ok := r.populatePending(f, filteredLogs) - if added > 0 { - r.lggr.Debugw("found missed logs", "added", added, "alreadyPending", alreadyPending, "upkeepID", f.upkeepID) - prommetrics.AutomationRecovererMissedLogs.Add(float64(added)) - } - if !ok { - r.lggr.Debugw("failed to add all logs to pending", "upkeepID", f.upkeepID) - return nil - } - r.filterStore.UpdateFilters(func(uf1, uf2 upkeepFilter) upkeepFilter { - uf1.lastRePollBlock = end - r.lggr.Debugw("Updated lastRePollBlock", "lastRePollBlock", end, "upkeepID", uf1.upkeepID) - return uf1 - }, f) - - return nil -} - -// populatePending adds the logs to the pending list if they are not already pending. -// returns the number of logs added, the number of logs that were already pending, -// and a flag that indicates whether some errors happened while we are trying to add to pending q. -func (r *logRecoverer) populatePending(f upkeepFilter, filteredLogs []logpoller.Log) (int, int, bool) { - r.lock.Lock() - defer r.lock.Unlock() - - pendingSizeBefore := len(r.pending) - alreadyPending := 0 - errs := make([]error, 0) - for _, log := range filteredLogs { - trigger := logToTrigger(log) - // Set the checkBlock and Hash to zero so that the checkPipeline uses the latest block - trigger.BlockHash = [32]byte{} - trigger.BlockNumber = 0 - upkeepId := &ocr2keepers.UpkeepIdentifier{} - ok := upkeepId.FromBigInt(f.upkeepID) - if !ok { - r.lggr.Warnw("failed to convert upkeepID to UpkeepIdentifier", "upkeepID", f.upkeepID) - continue - } - wid := core.UpkeepWorkID(*upkeepId, trigger) - if _, ok := r.visited[wid]; ok { - alreadyPending++ - continue - } - checkData, err := r.packer.PackLogData(log) - if err != nil { - r.lggr.Warnw("failed to pack log data", "err", err, "log", log) - continue - } - payload, err := core.NewUpkeepPayload(f.upkeepID, trigger, checkData) - if err != nil { - r.lggr.Warnw("failed to create payload", "err", err, "log", log) - continue - } - // r.lggr.Debugw("adding a payload to pending", "payload", payload) - if err := r.addPending(payload); err != nil { - errs = append(errs, err) - } else { - r.visited[wid] = visitedRecord{ - visitedAt: time.Now(), - payload: payload, - } - } - } - return len(r.pending) - pendingSizeBefore, alreadyPending, len(errs) == 0 -} - -// filterFinalizedStates filters out the log upkeeps that have already been completed (performed or ineligible). -func (r *logRecoverer) filterFinalizedStates(_ upkeepFilter, logs []logpoller.Log, states []ocr2keepers.UpkeepState) []logpoller.Log { - filtered := make([]logpoller.Log, 0) - - for i, log := range logs { - state := states[i] - if state != ocr2keepers.UnknownState { - continue - } - filtered = append(filtered, log) - } - - return filtered -} - -// getRecoveryWindow returns the block range of which the recoverer will try work on -func (r *logRecoverer) getRecoveryWindow(latest int64) (int64, int64) { - lookbackBlocks := r.lookbackBlocks.Load() - blockTime := r.blockTime.Load() - blocksInDay := int64(24*time.Hour) / blockTime - start := latest - blocksInDay - // Exploratory: Instead of subtracting finality depth to account for finalized performs - // keep two pointers of lastRePollBlock for soft and hard finalization, i.e. manage - // unfinalized perform logs better - end := latest - lookbackBlocks - r.finalityDepth - if start > end { - // In this case, allow starting from more than a day behind - start = end - } - return start, end -} - -// getFilterBatch returns a batch of filters that are ready to be recovered. -func (r *logRecoverer) getFilterBatch(offsetBlock int64) []upkeepFilter { - filters := r.filterStore.GetFilters(func(f upkeepFilter) bool { - // ensure we work only on filters that are ready to be recovered - // no need to recover in case f.configUpdateBlock is after offsetBlock - return f.lastRePollBlock < offsetBlock && int64(f.configUpdateBlock) <= offsetBlock - }) - - sort.Slice(filters, func(i, j int) bool { - return filters[i].lastRePollBlock < filters[j].lastRePollBlock - }) - - return r.selectFilterBatch(filters) -} - -// selectFilterBatch selects a batch of filters to be recovered. -// Half of the batch is selected randomly, the other half is selected -// in order of the oldest lastRePollBlock. -func (r *logRecoverer) selectFilterBatch(filters []upkeepFilter) []upkeepFilter { - batchSize := recoveryBatchSize - - if len(filters) < batchSize { - return filters - } - results := filters[:batchSize/2] - filters = filters[batchSize/2:] - - for len(results) < batchSize && len(filters) != 0 { - i, err := r.randIntn(len(filters)) - if err != nil { - r.lggr.Debugw("error generating random number", "err", err.Error()) - continue - } - results = append(results, filters[i]) - if i == 0 { - filters = filters[1:] - } else if i == len(filters)-1 { - filters = filters[:i] - } else { - filters = append(filters[:i], filters[i+1:]...) - } - } - - return results -} - -func (r *logRecoverer) randIntn(limit int) (int, error) { - n, err := rand.Int(rand.Reader, big.NewInt(int64(limit))) - if err != nil { - return 0, err - } - - return int(n.Int64()), nil -} - -func logToTrigger(log logpoller.Log) ocr2keepers.Trigger { - t := ocr2keepers.NewTrigger( - ocr2keepers.BlockNumber(log.BlockNumber), - log.BlockHash, - ) - t.LogTriggerExtension = &ocr2keepers.LogTriggerExtension{ - TxHash: log.TxHash, - Index: uint32(log.LogIndex), - BlockHash: log.BlockHash, - BlockNumber: ocr2keepers.BlockNumber(log.BlockNumber), - } - return t -} - -func (r *logRecoverer) clean(ctx context.Context) { - r.lock.RLock() - var expired []string - for id, t := range r.visited { - if time.Since(t.visitedAt) > RecoveryCacheTTL { - expired = append(expired, id) - } - } - r.lock.RUnlock() - lggr := r.lggr.With("where", "clean") - if len(expired) == 0 { - lggr.Debug("no expired upkeeps") - return - } - err := r.tryExpire(ctx, expired...) - if err != nil { - lggr.Warnw("failed to clean visited upkeeps", "err", err) - } -} - -func (r *logRecoverer) tryExpire(ctx context.Context, ids ...string) error { - latestBlock, err := r.poller.LatestBlock(ctx) - if err != nil { - return fmt.Errorf("failed to get latest block: %w", err) - } - slices.Sort(ids) - states, err := r.states.SelectByWorkIDs(ctx, ids...) - if err != nil { - return fmt.Errorf("failed to get states: %w", err) - } - lggr := r.lggr.With("where", "clean") - start, _ := r.getRecoveryWindow(latestBlock.BlockNumber) - r.lock.Lock() - defer r.lock.Unlock() - var removed int - for i, state := range states { - switch state { - case ocr2keepers.UnknownState: - // in case the state is unknown, we can't be sure if the upkeep was performed or not - // so we push it back to the pending list - rec, ok := r.visited[ids[i]] - if !ok { - // in case it was removed by another thread - continue - } - if logBlock := rec.payload.Trigger.LogTriggerExtension.BlockNumber; int64(logBlock) < start { - // we can't recover this log anymore, so we remove it from the visited list - lggr.Debugw("removing expired log: old block", "upkeepID", rec.payload.UpkeepID, - "latestBlock", latestBlock, "logBlock", logBlock, "start", start) - r.removePending(rec.payload.WorkID) - delete(r.visited, ids[i]) - removed++ - continue - } - if err := r.addPending(rec.payload); err == nil { - rec.visitedAt = time.Now() - r.visited[ids[i]] = rec - } - default: - delete(r.visited, ids[i]) - removed++ - } - } - - if removed > 0 { - lggr.Debugw("expired upkeeps", "expired", len(ids), "cleaned", removed) - } - - return nil -} - -// addPending adds a payload to the pending list if it's not already there. -// NOTE: the lock must be held before calling this function. -func (r *logRecoverer) addPending(payload ocr2keepers.UpkeepPayload) error { - var exist bool - pending := r.pending - upkeepPayloads := 0 - for _, p := range pending { - if bytes.Equal(p.UpkeepID[:], payload.UpkeepID[:]) { - upkeepPayloads++ - } - if p.WorkID == payload.WorkID { - exist = true - } - } - if upkeepPayloads >= maxPendingPayloadsPerUpkeep { - return fmt.Errorf("upkeep %v has too many payloads in pending queue", payload.UpkeepID) - } - if !exist { - r.pending = append(pending, payload) - prommetrics.AutomationRecovererPendingPayloads.Inc() - } - return nil -} - -// removePending removes a payload from the pending list. -// NOTE: the lock must be held before calling this function. -func (r *logRecoverer) removePending(workID string) { - updated := make([]ocr2keepers.UpkeepPayload, 0, len(r.pending)) - for _, p := range r.pending { - if p.WorkID != workID { - updated = append(updated, p) - } else { - prommetrics.AutomationRecovererPendingPayloads.Dec() - } - } - r.pending = updated -} - -// sortPending sorts the pending list by a random order based on the normalized latest block number. -// Divided by 10 to ensure that nodes with similar block numbers won't end up with different order. -// NOTE: the lock must be held before calling this function. -func (r *logRecoverer) sortPending(latestBlock uint64) { - normalized := latestBlock / 100 - if normalized == 0 { - normalized = 1 - } - randSeed := random.GetRandomKeySource(nil, normalized) - - shuffledIDs := make(map[string]string, len(r.pending)) - for _, p := range r.pending { - shuffledIDs[p.WorkID] = random.ShuffleString(p.WorkID, randSeed) - } - - sort.SliceStable(r.pending, func(i, j int) bool { - return shuffledIDs[r.pending[i].WorkID] < shuffledIDs[r.pending[j].WorkID] - }) -} - -func (r *logRecoverer) updateBlockTime(ctx context.Context) { - blockTime, err := r.blockTimeResolver.BlockTime(ctx, defaultSampleSize) - if err != nil { - r.lggr.Warnw("failed to compute block time", "err", err) - return - } - if blockTime > 0 { - currentBlockTime := r.blockTime.Load() - newBlockTime := int64(blockTime) - if currentBlockTime > 0 && (int64(math.Abs(float64(currentBlockTime-newBlockTime)))*100/currentBlockTime) > 20 { - r.lggr.Warnf("updating blocktime from %d to %d, this change is larger than 20%%", currentBlockTime, newBlockTime) - } else { - r.lggr.Debugf("updating blocktime from %d to %d", currentBlockTime, newBlockTime) - } - r.blockTime.Store(newBlockTime) - } -} diff --git a/pkg/automation/v21/logprovider/recoverer_test.go b/pkg/automation/v21/logprovider/recoverer_test.go deleted file mode 100644 index 7bc830597d..0000000000 --- a/pkg/automation/v21/logprovider/recoverer_test.go +++ /dev/null @@ -1,1239 +0,0 @@ -package logprovider - -import ( - "context" - "math" - "math/big" - "sort" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - autotypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-evm/pkg/client" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - "github.com/smartcontractkit/chainlink-evm/pkg/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core/mocks" - lpmocks "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" -) - -func TestLogRecoverer_GetRecoverables(t *testing.T) { - ctx := t.Context() - lp := &lpmocks.LogPoller{} - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: 100}, nil) - r := NewLogRecoverer(logger.Test(t), lp, nil, nil, nil, nil, NewOptions(200, big.NewInt(1))) - - tests := []struct { - name string - pending []ocr2keepers.UpkeepPayload - want []ocr2keepers.UpkeepPayload - wantErr bool - }{ - { - "empty", - []ocr2keepers.UpkeepPayload{}, - []ocr2keepers.UpkeepPayload{}, - false, - }, - { - "happy flow", - []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - }, - []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - }, - false, - }, - { - "rate limiting", - []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "3", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "4", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "5", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "6", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - }, - []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "3", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "4", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "5", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - }, - false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - r.lock.Lock() - r.pending = tc.pending - r.lock.Unlock() - - got, err := r.GetRecoveryProposals(ctx) - if tc.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - require.Len(t, got, len(tc.want)) - }) - } -} - -func TestLogRecoverer_Clean(t *testing.T) { - oldLogsOffset := int64(20) - - tests := []struct { - name string - pending []ocr2keepers.UpkeepPayload - visited map[string]visitedRecord - states []ocr2keepers.UpkeepState - wantPending []ocr2keepers.UpkeepPayload - wantVisited []string - }{ - { - "empty", - []ocr2keepers.UpkeepPayload{}, - map[string]visitedRecord{}, - []ocr2keepers.UpkeepState{}, - []ocr2keepers.UpkeepPayload{}, - []string{}, - }, - { - "clean expired", - []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - {WorkID: "3", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "3")}, - }, - map[string]visitedRecord{ - "1": visitedRecord{time.Now(), ocr2keepers.UpkeepPayload{ - WorkID: "1", - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: ocr2keepers.BlockNumber(oldLogsOffset * 2), - }, - }, - }}, - "2": visitedRecord{time.Now(), ocr2keepers.UpkeepPayload{ - WorkID: "2", - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: ocr2keepers.BlockNumber(oldLogsOffset * 2), - }, - }, - }}, - "3": visitedRecord{time.Now().Add(-time.Hour), ocr2keepers.UpkeepPayload{ - WorkID: "3", - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: ocr2keepers.BlockNumber(oldLogsOffset - 10), - }, - }, - }}, - "4": visitedRecord{time.Now().Add(-time.Hour), ocr2keepers.UpkeepPayload{ - WorkID: "4", - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: ocr2keepers.BlockNumber(oldLogsOffset + 10), - }, - }, - }}, - }, - []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - ocr2keepers.UnknownState, - }, - []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - {WorkID: "4", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "4")}, - }, - []string{"1", "2", "4"}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithCancel(t.Context()) - defer cancel() - - lookbackBlocks := int64(100) - r, _, lp, statesReader := setupTestRecoverer(t, time.Millisecond*50, lookbackBlocks) - start, _ := r.getRecoveryWindow(0) - block24h := int64(math.Abs(float64(start))) - - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: block24h + oldLogsOffset}, nil) - statesReader.On("SelectByWorkIDs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.states, nil) - - r.lock.Lock() - r.pending = tc.pending - r.visited = tc.visited - r.lock.Unlock() - - r.clean(ctx) - - r.lock.RLock() - defer r.lock.RUnlock() - - pending := r.pending - require.Len(t, pending, len(tc.wantPending)) - sort.Slice(pending, func(i, j int) bool { - return pending[i].WorkID < pending[j].WorkID - }) - for i := range pending { - require.Equal(t, tc.wantPending[i].WorkID, pending[i].WorkID) - } - require.Len(t, r.visited, len(tc.wantVisited)) - for _, id := range tc.wantVisited { - _, ok := r.visited[id] - require.True(t, ok) - } - }) - } -} - -func TestLogRecoverer_Recover(t *testing.T) { - ctx := t.Context() - - tests := []struct { - name string - lookbackBlocks int64 - latestBlock int64 - latestBlockErr error - active []upkeepFilter - states []ocr2keepers.UpkeepState - statesErr error - logs []logpoller.Log - logsErr error - recoverErr error - proposalsWorkIDs []string - lastRePollBlocks []int64 - }{ - { - "no filters", - 200, - 300, - nil, - []upkeepFilter{}, - []ocr2keepers.UpkeepState{}, - nil, - []logpoller.Log{}, - nil, - nil, - []string{}, - []int64{}, - }, - { - "latest block error", - 200, - 0, - errors.New("test error"), - []upkeepFilter{}, - []ocr2keepers.UpkeepState{}, - nil, - []logpoller.Log{}, - nil, - errors.New("test error"), - []string{}, - []int64{}, - }, - { - "states error", - 100, - 200, - nil, - []upkeepFilter{ - { - upkeepID: big.NewInt(1), - addr: common.HexToAddress("0x1").Bytes(), - topics: []common.Hash{ - common.HexToHash("0x1"), - }, - }, - }, - nil, - errors.New("test error"), - []logpoller.Log{ - { - BlockNumber: 2, - TxHash: common.HexToHash("0x111"), - LogIndex: 1, - BlockHash: common.HexToHash("0x2"), - }, - }, - nil, - nil, - []string{}, - []int64{0}, - }, - { - "get logs error", - 200, - 300, - nil, - []upkeepFilter{ - { - upkeepID: big.NewInt(1), - addr: common.HexToAddress("0x1").Bytes(), - topics: []common.Hash{ - common.HexToHash("0x1"), - }, - }, - }, - []ocr2keepers.UpkeepState{}, - nil, - []logpoller.Log{}, - errors.New("test error"), - nil, - []string{}, - []int64{0}, - }, - { - "happy flow", - 100, - 500, - nil, - []upkeepFilter{ - { - upkeepID: big.NewInt(1), - addr: common.HexToAddress("0x1").Bytes(), - topics: []common.Hash{ - common.HexToHash("0x1"), - }, - }, - { - upkeepID: big.NewInt(2), - addr: common.HexToAddress("0x2").Bytes(), - topics: []common.Hash{ - common.HexToHash("0x2"), - }, - configUpdateBlock: 450, // should be filtered out - }, - { - upkeepID: big.NewInt(3), - addr: common.HexToAddress("0x2").Bytes(), - topics: []common.Hash{ - common.HexToHash("0x2"), - }, - lastRePollBlock: 450, // should be filtered out, as its higher than latest-lookback - }, - }, - []ocr2keepers.UpkeepState{ocr2keepers.UnknownState}, - nil, - []logpoller.Log{ - { - BlockNumber: 2, - TxHash: common.HexToHash("0x111"), - LogIndex: 1, - BlockHash: common.HexToHash("0x2"), - }, - }, - nil, - nil, - []string{"c207451fa897f9bb13b09d54d8655edf0644e027c53521b4a92eafbb64ba4d14"}, - []int64{201, 0, 450}, - }, - { - "lastRePollBlock updated with burst when lagging behind", - 100, - 50000, - nil, - []upkeepFilter{ - { - upkeepID: big.NewInt(1), - addr: common.HexToAddress("0x1").Bytes(), - topics: []common.Hash{ - common.HexToHash("0x1"), - }, - lastRePollBlock: 99, // Should be updated with burst - }, - }, - []ocr2keepers.UpkeepState{ocr2keepers.UnknownState}, - nil, - []logpoller.Log{ - { - BlockNumber: 2, - TxHash: common.HexToHash("0x111"), - LogIndex: 1, - BlockHash: common.HexToHash("0x2"), - }, - }, - nil, - nil, - []string{"c207451fa897f9bb13b09d54d8655edf0644e027c53521b4a92eafbb64ba4d14"}, - []int64{600}, - }, - { - "recovery starts at configUpdateBlock if higher than lastRePollBlock", - 100, - 5000, - nil, - []upkeepFilter{ - { - upkeepID: big.NewInt(1), - addr: common.HexToAddress("0x1").Bytes(), - topics: []common.Hash{ - common.HexToHash("0x1"), - }, - lastRePollBlock: 100, - configUpdateBlock: 500, - }, - }, - []ocr2keepers.UpkeepState{ocr2keepers.UnknownState}, - nil, - []logpoller.Log{ - { - BlockNumber: 2, - TxHash: common.HexToHash("0x111"), - LogIndex: 1, - BlockHash: common.HexToHash("0x2"), - }, - }, - nil, - nil, - []string{"c207451fa897f9bb13b09d54d8655edf0644e027c53521b4a92eafbb64ba4d14"}, - []int64{700}, // should be configUpdateBlock + recoveryLogsBuffer - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - lookbackBlocks := int64(100) - recoverer, filterStore, lp, statesReader := setupTestRecoverer(t, time.Millisecond*50, lookbackBlocks) - - filterStore.AddActiveUpkeeps(tc.active...) - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: tc.latestBlock}, tc.latestBlockErr) - lp.On("LogsWithSigs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.logs, tc.logsErr) - statesReader.On("SelectByWorkIDs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.states, tc.statesErr) - - err := recoverer.recover(ctx) - if tc.recoverErr != nil { - require.Error(t, err) - return - } - require.NoError(t, err) - for i, active := range tc.active { - filters := filterStore.GetFilters(func(f upkeepFilter) bool { - return f.upkeepID.String() == active.upkeepID.String() - }) - require.Len(t, filters, 1) - require.Equal(t, tc.lastRePollBlocks[i], filters[0].lastRePollBlock) - } - - proposals, err := recoverer.GetRecoveryProposals(ctx) - require.NoError(t, err) - require.Len(t, proposals, len(tc.proposalsWorkIDs)) - if len(proposals) > 0 { - sort.Slice(proposals, func(i, j int) bool { - return proposals[i].WorkID < proposals[j].WorkID - }) - } - for i := range proposals { - require.Equal(t, tc.proposalsWorkIDs[i], proposals[i].WorkID) - } - }) - } -} - -func TestLogRecoverer_SelectFilterBatch(t *testing.T) { - n := recoveryBatchSize*2 + 2 - filters := []upkeepFilter{} - for i := range n { - filters = append(filters, upkeepFilter{ - upkeepID: big.NewInt(int64(i)), - }) - } - recoverer, _, _, _ := setupTestRecoverer(t, time.Millisecond*50, int64(100)) - - batch := recoverer.selectFilterBatch(filters) - require.Len(t, batch, recoveryBatchSize) - - batch = recoverer.selectFilterBatch(filters[:recoveryBatchSize/2]) - require.Len(t, batch, recoveryBatchSize/2) -} - -func TestLogRecoverer_getFilterBatch(t *testing.T) { - tests := []struct { - name string - offsetBlock int64 - filters []upkeepFilter - want int - }{ - { - "empty", - 2, - []upkeepFilter{}, - 0, - }, - { - "filter out of range", - 100, - []upkeepFilter{ - { - upkeepID: big.NewInt(1), - addr: common.HexToAddress("0x1").Bytes(), - lastRePollBlock: 50, - }, - { - upkeepID: big.NewInt(2), - addr: common.HexToAddress("0x2").Bytes(), - lastRePollBlock: 50, - configUpdateBlock: 101, // out of range - }, - { - upkeepID: big.NewInt(3), - addr: common.HexToAddress("0x3").Bytes(), - configUpdateBlock: 99, - }, - }, - 2, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - recoverer, filterStore, _, _ := setupTestRecoverer(t, time.Millisecond*50, int64(100)) - filterStore.AddActiveUpkeeps(tc.filters...) - batch := recoverer.getFilterBatch(tc.offsetBlock) - require.Len(t, batch, tc.want) - }) - } -} - -func TestLogRecoverer_FilterFinalizedStates(t *testing.T) { - tests := []struct { - name string - logs []logpoller.Log - states []ocr2keepers.UpkeepState - want []logpoller.Log - }{ - { - "empty", - []logpoller.Log{}, - []ocr2keepers.UpkeepState{}, - []logpoller.Log{}, - }, - { - "happy flow", - []logpoller.Log{ - {LogIndex: 0}, {LogIndex: 2}, {LogIndex: 2}, - }, - []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - ocr2keepers.Performed, - ocr2keepers.Ineligible, - }, - []logpoller.Log{ - {LogIndex: 0}, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - recoverer, _, _, _ := setupTestRecoverer(t, time.Millisecond*50, int64(100)) - state := recoverer.filterFinalizedStates(upkeepFilter{}, tc.logs, tc.states) - require.Len(t, state, len(tc.want)) - for i := range state { - require.Equal(t, tc.want[i].LogIndex, state[i].LogIndex) - } - }) - } -} - -func TestLogRecoverer_GetProposalData(t *testing.T) { - for _, tc := range []struct { - name string - proposal ocr2keepers.CoordinatedBlockProposal - skipFilter bool - filterStore UpkeepFilterStore - logPoller logpoller.LogPoller - client client.Client - stateReader core.UpkeepStateReader - wantBytes []byte - expectErr bool - wantErr error - }{ - { - name: "passing an empty proposal with an empty upkeep ID returns an error", - proposal: ocr2keepers.CoordinatedBlockProposal{}, - expectErr: true, - wantErr: errors.New("not a log trigger upkeep ID"), - }, - { - name: "if a filter is not found for the upkeep ID, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - }, - skipFilter: true, - expectErr: true, - wantErr: errors.New("filter not found for upkeep 452312848583266388373324160190187140457511065560374322131410487042692349952"), - }, - { - name: "if an error is encountered fetching the latest block, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 0, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 0, errors.New("latest block boom") - }, - }, - expectErr: true, - wantErr: errors.New("latest block boom"), - }, - { - name: "if an error is encountered fetching the tx receipt, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 0, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - return errors.New("tx receipt boom") - }, - }, - expectErr: true, - wantErr: errors.New("tx receipt boom"), - }, - { - name: "if the tx block is nil, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 0, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - return nil - }, - }, - expectErr: true, - wantErr: errors.New("failed to get tx block"), - }, - { - name: "if a log trigger extension block number is 0, and the block number on the tx receipt is not recoverable, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 0, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(200) - return nil - }, - }, - expectErr: true, - wantErr: errors.New("log block is not recoverable"), - }, - { - name: "if a log block is not recoverable, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 200, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(200) - return nil - }, - }, - expectErr: true, - wantErr: errors.New("log block is not recoverable"), - }, - { - name: "if a log block has does not match, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 200, - BlockHash: common.HexToHash("0x2"), - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 100, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(200) - receipt.BlockHash = common.HexToHash("0x1") - return nil - }, - }, - expectErr: true, - wantErr: errors.New("log tx reorged"), - }, - { - name: "if a log block is recoverable, when the upkeep state reader errors, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 80, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 300, nil - }, - }, - stateReader: &mockStateReader{ - SelectByWorkIDsFn: func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return nil, errors.New("upkeep state boom") - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(80) - return nil - }, - }, - expectErr: true, - wantErr: errors.New("upkeep state boom"), - }, - { - name: "if a log block is recoverable, when the upkeep state reader returns a non recoverable state, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 80, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 300, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(80) - return nil - }, - }, - stateReader: &mockStateReader{ - SelectByWorkIDsFn: func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return []ocr2keepers.UpkeepState{ - ocr2keepers.Ineligible, - }, nil - }, - }, - expectErr: true, - wantErr: errors.New("upkeep state is not recoverable"), - }, - { - name: "if a log block is recoverable, when the filter address is empty, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 80, - }, - }, - }, - filterStore: &mockFilterStore{ - HasFn: func(id *big.Int) bool { - return true - }, - RangeFiltersByIDsFn: func(iterator func(int, upkeepFilter), ids ...*big.Int) { - - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 300, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(80) - return nil - }, - }, - stateReader: &mockStateReader{ - SelectByWorkIDsFn: func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - }, nil - }, - }, - expectErr: true, - wantErr: errors.New("invalid filter found for upkeepID 452312848583266388373324160190187140457511065560374322131410487042692349952"), - }, - { - name: "if a log block is recoverable, when the log poller returns an error fetching logs, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 80, - }, - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 300, nil - }, - LogsWithSigsFn: func(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { - return nil, errors.New("logs with sigs boom") - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(80) - return nil - }, - }, - stateReader: &mockStateReader{ - SelectByWorkIDsFn: func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - }, nil - }, - }, - expectErr: true, - wantErr: errors.New("could not read logs: logs with sigs boom"), - }, - { - name: "if a log block is recoverable, when logs cannot be found for an upkeep ID, an error is returned", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: ocr2keepers.Trigger{ - LogTriggerExtension: &ocr2keepers.LogTriggerExtension{ - BlockNumber: 80, - }, - }, - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 300, nil - }, - LogsWithSigsFn: func(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { - return []logpoller.Log{ - { - BlockNumber: 80, - }, - }, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(80) - return nil - }, - }, - stateReader: &mockStateReader{ - SelectByWorkIDsFn: func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - }, nil - }, - }, - expectErr: true, - wantErr: errors.New(`no log found for upkeepID 452312848583266388373324160190187140457511065560374322131410487042692349952 and trigger {"BlockNumber":0,"BlockHash":"0000000000000000000000000000000000000000000000000000000000000000","LogTriggerExtension":{"BlockHash":"0000000000000000000000000000000000000000000000000000000000000000","BlockNumber":80,"Index":0,"TxHash":"0000000000000000000000000000000000000000000000000000000000000000"}}`), - }, - { - name: "happy path with empty check data", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: func() ocr2keepers.Trigger { - t := ocr2keepers.NewTrigger( - ocr2keepers.BlockNumber(80), - [32]byte{1}, - ) - t.LogTriggerExtension = &ocr2keepers.LogTriggerExtension{ - TxHash: [32]byte{2}, - Index: uint32(3), - BlockHash: [32]byte{1}, - BlockNumber: ocr2keepers.BlockNumber(80), - } - return t - }(), - WorkID: "7f775793422d178c90e99c3bbdf05181bc6bb6ce13170e87c92ac396bb7ddda0", - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 300, nil - }, - LogsWithSigsFn: func(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { - return []logpoller.Log{ - { - BlockNumber: 80, - BlockHash: [32]byte{1}, - TxHash: [32]byte{2}, - LogIndex: 3, - }, - }, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(80) - receipt.BlockHash = [32]byte{1} - return nil - }, - }, - stateReader: &mockStateReader{ - SelectByWorkIDsFn: func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - }, nil - }, - }, - wantBytes: []byte(nil), - }, - { - name: "happy path with check data", - proposal: ocr2keepers.CoordinatedBlockProposal{ - UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123"), - Trigger: func() ocr2keepers.Trigger { - t := ocr2keepers.NewTrigger( - ocr2keepers.BlockNumber(80), - [32]byte{1}, - ) - t.LogTriggerExtension = &ocr2keepers.LogTriggerExtension{ - TxHash: [32]byte{2}, - Index: uint32(3), - BlockHash: [32]byte{1}, - BlockNumber: ocr2keepers.BlockNumber(80), - } - return t - }(), - WorkID: "7f775793422d178c90e99c3bbdf05181bc6bb6ce13170e87c92ac396bb7ddda0", - }, - logPoller: &mockLogPoller{ - LatestBlockFn: func(ctx context.Context) (int64, error) { - return 300, nil - }, - LogsWithSigsFn: func(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { - return []logpoller.Log{ - { - EVMChainID: sqlutil.New(big.NewInt(1)), - LogIndex: 3, - BlockHash: [32]byte{1}, - BlockNumber: 80, - BlockTimestamp: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), - EventSig: common.HexToHash("abc"), - TxHash: [32]byte{2}, - Data: []byte{1, 2, 3}, - CreatedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), - }, - }, nil - }, - }, - client: &mockClient{ - CallContextFn: func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error { - receipt.Status = 1 - receipt.BlockNumber = big.NewInt(80) - receipt.BlockHash = [32]byte{1} - return nil - }, - }, - stateReader: &mockStateReader{ - SelectByWorkIDsFn: func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - }, nil - }, - }, - wantBytes: []byte{1, 2, 3}, - }, - } { - t.Run(tc.name, func(t *testing.T) { - recoverer, filterStore, _, _ := setupTestRecoverer(t, time.Second, 10) - - if !tc.skipFilter { - filterStore.AddActiveUpkeeps(upkeepFilter{ - addr: []byte("test"), - topics: []common.Hash{common.HexToHash("0x1"), common.HexToHash("0x2"), common.HexToHash("0x3"), common.HexToHash("0x4")}, - upkeepID: core.GenUpkeepID(autotypes.LogTrigger, "123").BigInt(), - }) - } - - if tc.filterStore != nil { - recoverer.filterStore = tc.filterStore - } - if tc.logPoller != nil { - recoverer.poller = tc.logPoller - } - if tc.client != nil { - recoverer.client = tc.client - } - if tc.stateReader != nil { - recoverer.states = tc.stateReader - } - - b, err := recoverer.GetProposalData(t.Context(), tc.proposal) - if tc.expectErr { - assert.Error(t, err) - assert.Equal(t, tc.wantErr.Error(), err.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.wantBytes, b) - } - }) - } -} - -func TestLogRecoverer_pending(t *testing.T) { - tests := []struct { - name string - maxPerUpkeep int - exist []ocr2keepers.UpkeepPayload - new []ocr2keepers.UpkeepPayload - errored []bool - want []ocr2keepers.UpkeepPayload - }{ - { - name: "empty", - maxPerUpkeep: 10, - exist: []ocr2keepers.UpkeepPayload{}, - new: []ocr2keepers.UpkeepPayload{}, - errored: []bool{}, - want: []ocr2keepers.UpkeepPayload{}, - }, - { - name: "add new and existing", - maxPerUpkeep: 10, - exist: []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - }, - new: []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - }, - errored: []bool{false, false}, - want: []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "2")}, - }, - }, - { - name: "exceed limits for upkeep", - maxPerUpkeep: 3, - exist: []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "3", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - }, - new: []ocr2keepers.UpkeepPayload{ - {WorkID: "4", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - }, - errored: []bool{true}, - want: []ocr2keepers.UpkeepPayload{ - {WorkID: "1", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "2", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - {WorkID: "3", UpkeepID: core.GenUpkeepID(autotypes.LogTrigger, "1")}, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - origMaxPendingPayloadsPerUpkeep := maxPendingPayloadsPerUpkeep - maxPendingPayloadsPerUpkeep = tc.maxPerUpkeep - defer func() { - maxPendingPayloadsPerUpkeep = origMaxPendingPayloadsPerUpkeep - }() - - r := NewLogRecoverer(logger.Test(t), nil, nil, nil, nil, nil, NewOptions(200, big.NewInt(1))) - r.lock.Lock() - r.pending = tc.exist - for i, p := range tc.new { - err := r.addPending(p) - if tc.errored[i] { - require.Error(t, err) - continue - } - require.NoError(t, err) - } - pending := r.pending - require.GreaterOrEqual(t, len(pending), len(tc.new)) - require.Len(t, pending, len(tc.want)) - sort.Slice(pending, func(i, j int) bool { - return pending[i].WorkID < pending[j].WorkID - }) - for i := range pending { - require.Equal(t, tc.want[i].WorkID, pending[i].WorkID) - } - r.lock.Unlock() - for _, p := range tc.want { - r.removePending(p.WorkID) - } - r.lock.Lock() - defer r.lock.Unlock() - require.Empty(t, r.pending) - }) - } -} - -type mockFilterStore struct { - UpkeepFilterStore - HasFn func(id *big.Int) bool - RangeFiltersByIDsFn func(iterator func(int, upkeepFilter), ids ...*big.Int) -} - -func (s *mockFilterStore) RangeFiltersByIDs(iterator func(int, upkeepFilter), ids ...*big.Int) { - s.RangeFiltersByIDsFn(iterator, ids...) -} - -func (s *mockFilterStore) Has(id *big.Int) bool { - return s.HasFn(id) -} - -type mockLogPoller struct { - logpoller.LogPoller - LatestBlockFn func(ctx context.Context) (int64, error) - LogsWithSigsFn func(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) -} - -func (p *mockLogPoller) LogsWithSigs(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { - return p.LogsWithSigsFn(ctx, start, end, eventSigs, address) -} -func (p *mockLogPoller) LatestBlock(ctx context.Context) (logpoller.Block, error) { - block, err := p.LatestBlockFn(ctx) - return logpoller.Block{BlockNumber: block}, err -} - -type mockClient struct { - client.Client - CallContextFn func(ctx context.Context, receipt *types.Receipt, method string, args ...any) error -} - -func (c *mockClient) CallContext(ctx context.Context, r any, method string, args ...any) error { - receipt := r.(*types.Receipt) - return c.CallContextFn(ctx, receipt, method, args) -} - -type mockStateReader struct { - SelectByWorkIDsFn func(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) -} - -func (r *mockStateReader) SelectByWorkIDs(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - return r.SelectByWorkIDsFn(ctx, workIDs...) -} - -func setupTestRecoverer(t *testing.T, interval time.Duration, lookbackBlocks int64) (*logRecoverer, UpkeepFilterStore, *lpmocks.LogPoller, *mocks.UpkeepStateReader) { - lp := new(lpmocks.LogPoller) - statesReader := new(mocks.UpkeepStateReader) - filterStore := NewUpkeepFilterStore() - opts := NewOptions(lookbackBlocks, big.NewInt(1)) - opts.ReadInterval = interval / 5 - opts.LookbackBlocks = lookbackBlocks - recoverer := NewLogRecoverer(logger.Test(t), lp, nil, statesReader, &mockedPacker{}, filterStore, opts) - return recoverer, filterStore, lp, statesReader -} diff --git a/pkg/automation/v21/mercury/mercury.go b/pkg/automation/v21/mercury/mercury.go deleted file mode 100644 index 342bed3664..0000000000 --- a/pkg/automation/v21/mercury/mercury.go +++ /dev/null @@ -1,201 +0,0 @@ -package mercury - -import ( - "context" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "fmt" - "math/big" - "net/http" - "time" - - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/patrickmn/go-cache" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" -) - -const ( - FeedIDs = "feedIDs" // valid for v0.3 - FeedIdHex = "feedIdHex" // valid for v0.2 - BlockNumber = "blockNumber" // valid for v0.2 - Timestamp = "timestamp" // valid for v0.3 - totalFastPluginRetries = 5 - totalMediumPluginRetries = totalFastPluginRetries + 1 - RetryIntervalTimeout = time.Duration(-1) - RequestTimeout = 10 * time.Second -) - -var GenerateHMACFn = func(method string, path string, body []byte, clientId string, secret string, ts int64) string { - bodyHash := sha256.New() - bodyHash.Write(body) - hashString := fmt.Sprintf("%s %s %s %s %d", - method, - path, - hex.EncodeToString(bodyHash.Sum(nil)), - clientId, - ts) - signedMessage := hmac.New(sha256.New, []byte(secret)) - signedMessage.Write([]byte(hashString)) - userHmac := hex.EncodeToString(signedMessage.Sum(nil)) - return userHmac -} - -// CalculateStreamsRetryConfig returns plugin retry interval based on how many times plugin has retried this work -var CalculateStreamsRetryConfigFn = func(upkeepType automationTypes.UpkeepType, prk string, mercuryConfig MercuryConfigProvider) (retryInterval time.Duration) { - var retries int - totalAttempts, ok := mercuryConfig.GetPluginRetry(prk) - if ok { - retries = totalAttempts.(int) - if retries < totalFastPluginRetries { - retryInterval = 1 * time.Second - } else if retries < totalMediumPluginRetries { - retryInterval = 5 * time.Second - } else { - retryInterval = RetryIntervalTimeout - } - } else { - retryInterval = 1 * time.Second - } - if upkeepType == automationTypes.ConditionTrigger { - // Conditional Upkees don't have any pipeline retries as they automatically get checked on a new block - retryInterval = RetryIntervalTimeout - } - mercuryConfig.SetPluginRetry(prk, retries+1, cache.DefaultExpiration) - return retryInterval -} - -type MercuryData struct { - Index int - Bytes [][]byte // Mercury values if request is successful - ErrCode encoding.ErrCode // Error code if mercury gives an error - State encoding.PipelineExecutionState // NoPipelineError if no error during execution, otherwise appropriate error - Retryable bool // Applicable if State != NoPipelineError - Error error // non nil if State != NoPipelineError -} - -type MercuryConfigProvider interface { - Credentials() *types.MercuryCredentials - IsUpkeepAllowed(string) (any, bool) - SetUpkeepAllowed(string, any, time.Duration) - GetPluginRetry(string) (any, bool) - SetPluginRetry(string, any, time.Duration) -} - -type HttpClient interface { - Do(req *http.Request) (*http.Response, error) -} - -type MercuryClient interface { - // DoRequest makes a data stream request, it manages retries and returns the following: - // state: the state of the pipeline execution. This field is coupled with err. If state is NoPipelineError then err is nil, otherwise err is non nil and appropriate state is returned - // data: the data returned from the data stream if there's NoPipelineError - // errCode: the errorCode of streams request if data is nil and there's NoPipelineError - // retryable: whether the request is retryable. Only applicable for errored states. - // retryInterval: the interval to wait before retrying the request. Only applicable for errored states. - // err: non nil if some internal error occurs in pipeline - DoRequest(ctx context.Context, streamsLookup *StreamsLookup, upkeepType automationTypes.UpkeepType, pluginRetryKey string) (encoding.PipelineExecutionState, [][]byte, encoding.ErrCode, bool, time.Duration, error) -} - -type StreamsLookupError struct { - FeedParamKey string - Feeds []string - TimeParamKey string - Time *big.Int - ExtraData []byte -} - -type StreamsLookup struct { - *StreamsLookupError - UpkeepId *big.Int - Block uint64 -} - -func (l *StreamsLookup) IsMercuryV02() bool { - return l.FeedParamKey == FeedIdHex && l.TimeParamKey == BlockNumber -} - -func (l *StreamsLookup) IsMercuryV03() bool { - return l.FeedParamKey == FeedIDs -} - -// IsMercuryV03UsingBlockNumber is used to distinguish the batch path. It is used for Mercury V03 only -func (l *StreamsLookup) IsMercuryV03UsingBlockNumber() bool { - return l.TimeParamKey == BlockNumber -} - -type Packer interface { - UnpackCheckCallbackResult(callbackResp []byte) (encoding.PipelineExecutionState, bool, []byte, encoding.UpkeepFailureReason, *big.Int, error) - PackGetUpkeepPrivilegeConfig(upkeepId *big.Int) ([]byte, error) - UnpackGetUpkeepPrivilegeConfig(resp []byte) ([]byte, error) - DecodeStreamsLookupRequest(data []byte) (*StreamsLookupError, error) - PackUserCheckErrorHandler(errCode encoding.ErrCode, extraData []byte) ([]byte, error) -} - -type abiPacker struct { - registryABI abi.ABI - streamsABI abi.ABI -} - -func NewAbiPacker() *abiPacker { - return &abiPacker{registryABI: core.AutoV2CommonABI, streamsABI: core.StreamsCompatibleABI} -} - -// DecodeStreamsLookupRequest decodes the revert error StreamsLookup(string feedParamKey, string[] feeds, string feedParamKey, uint256 time, byte[] extraData) -func (p *abiPacker) DecodeStreamsLookupRequest(data []byte) (*StreamsLookupError, error) { - e := p.streamsABI.Errors["StreamsLookup"] - unpack, err := e.Unpack(data) - if err != nil { - return nil, fmt.Errorf("unpack error: %w", err) - } - errorParameters := unpack.([]any) - - return &StreamsLookupError{ - FeedParamKey: *abi.ConvertType(errorParameters[0], new(string)).(*string), - Feeds: *abi.ConvertType(errorParameters[1], new([]string)).(*[]string), - TimeParamKey: *abi.ConvertType(errorParameters[2], new(string)).(*string), - Time: *abi.ConvertType(errorParameters[3], new(*big.Int)).(**big.Int), - ExtraData: *abi.ConvertType(errorParameters[4], new([]byte)).(*[]byte), - }, nil -} - -func (p *abiPacker) UnpackCheckCallbackResult(callbackResp []byte) (encoding.PipelineExecutionState, bool, []byte, encoding.UpkeepFailureReason, *big.Int, error) { - out, err := p.registryABI.Methods["checkCallback"].Outputs.UnpackValues(callbackResp) - if err != nil { - return encoding.PackUnpackDecodeFailed, false, nil, 0, nil, fmt.Errorf("%w: unpack checkUpkeep return: %s", err, hexutil.Encode(callbackResp)) - } - - upkeepNeeded := *abi.ConvertType(out[0], new(bool)).(*bool) - rawPerformData := *abi.ConvertType(out[1], new([]byte)).(*[]byte) - failureReason := encoding.UpkeepFailureReason(*abi.ConvertType(out[2], new(uint8)).(*uint8)) - gasUsed := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) - - return encoding.NoPipelineError, upkeepNeeded, rawPerformData, failureReason, gasUsed, nil -} - -func (p *abiPacker) UnpackGetUpkeepPrivilegeConfig(resp []byte) ([]byte, error) { - out, err := p.registryABI.Methods["getUpkeepPrivilegeConfig"].Outputs.UnpackValues(resp) - if err != nil { - return nil, fmt.Errorf("%w: unpack getUpkeepPrivilegeConfig return", err) - } - - bts := *abi.ConvertType(out[0], new([]byte)).(*[]byte) - - return bts, nil -} - -func (p *abiPacker) PackGetUpkeepPrivilegeConfig(upkeepId *big.Int) ([]byte, error) { - return p.registryABI.Pack("getUpkeepPrivilegeConfig", upkeepId) -} - -func (p *abiPacker) PackUserCheckErrorHandler(errCode encoding.ErrCode, extraData []byte) ([]byte, error) { - // Convert errCode to bigInt as contract expects uint256 - errCodeBigInt := new(big.Int).SetUint64(uint64(errCode)) - return p.streamsABI.Pack("checkErrorHandler", errCodeBigInt, extraData) -} diff --git a/pkg/automation/v21/mercury/mercury_test.go b/pkg/automation/v21/mercury/mercury_test.go deleted file mode 100644 index 58789f22fd..0000000000 --- a/pkg/automation/v21/mercury/mercury_test.go +++ /dev/null @@ -1,371 +0,0 @@ -package mercury - -import ( - "encoding/json" - "errors" - "math/big" - "testing" - "time" - - "github.com/patrickmn/go-cache" - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/stretchr/testify/assert" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" -) - -func TestGenerateHMACFn(t *testing.T) { - testCases := []struct { - name string - method string - path string - body []byte - clientId string - secret string - ts int64 - expected string - }{ - { - name: "generate hmac function", - method: "GET", - path: "/example", - body: []byte(""), - clientId: "yourClientId", - secret: "yourSecret", - ts: 1234567890, - expected: "17b0bb6b14f7b48ef9d24f941ff8f33ad2d5e94ac343380be02c2f1ca32fdbd8", - }, - { - name: "generate hmac function with non-empty body", - method: "POST", - path: "/api", - body: []byte("request body"), - clientId: "anotherClientId", - secret: "anotherSecret", - ts: 1597534567, - expected: "d326c168c50c996e271d6b3b4c97944db01163994090f73fcf4fd42f23f06bbb", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result := GenerateHMACFn(tc.method, tc.path, tc.body, tc.clientId, tc.secret, tc.ts) - - if result != tc.expected { - t.Errorf("Expected: %s, Got: %s", tc.expected, result) - } - }) - } -} - -func TestPacker_DecodeStreamsLookupRequest(t *testing.T) { - tests := []struct { - name string - data []byte - expected *StreamsLookupError - state uint8 - err error - }{ - { - name: "success - decode to streams lookup", - data: hexutil.MustDecode("0xf055e4a200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000002435eb50000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000966656564496448657800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000423078343535343438326435353533343432643431353234323439353435323535346432643534343535333534346534353534303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307834323534343332643535353334343264343135323432343935343532353534643264353434353533353434653435353430303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d62657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - expected: &StreamsLookupError{ - FeedParamKey: "feedIdHex", - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: "blockNumber", - Time: big.NewInt(37969589), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - }, - { - name: "failure - unpack error", - data: []byte{1, 2, 3, 4}, - err: errors.New("unpack error: invalid identifier, have 0x01020304 want 0xf055e4a2"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - packer := NewAbiPacker() - fl, err := packer.DecodeStreamsLookupRequest(tt.data) - assert.Equal(t, tt.expected, fl) - if tt.err != nil { - assert.Equal(t, tt.err.Error(), err.Error()) - } - }) - } -} - -func TestPacker_UnpackGetUpkeepPrivilegeConfig(t *testing.T) { - tests := []struct { - name string - raw []byte - errored bool - }{ - { - name: "happy path", - raw: func() []byte { - b, _ := hexutil.Decode("0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000177b226d657263757279456e61626c6564223a747275657d000000000000000000") - - return b - }(), - errored: false, - }, - { - name: "error empty config", - raw: func() []byte { - b, _ := hexutil.Decode("0x") - - return b - }(), - errored: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - packer := NewAbiPacker() - - b, err := packer.UnpackGetUpkeepPrivilegeConfig(test.raw) - - if !test.errored { - require.NoError(t, err, "should unpack bytes from abi encoded value") - - // the actual struct to unmarshal into is not available to this - // package so basic json encoding is the limit of the following test - var data map[string]any - err = json.Unmarshal(b, &data) - - assert.NoError(t, err, "packed data should unmarshal using json encoding") - assert.Equal(t, []byte(`{"mercuryEnabled":true}`), b) - } else { - assert.Error(t, err, "error expected from unpack function") - } - }) - } -} - -func TestPacker_PackGetUpkeepPrivilegeConfig(t *testing.T) { - tests := []struct { - name string - upkeepId *big.Int - raw []byte - errored bool - }{ - { - name: "happy path", - upkeepId: func() *big.Int { - id, _ := new(big.Int).SetString("52236098515066839510538748191966098678939830769967377496848891145101407612976", 10) - - return id - }(), - raw: func() []byte { - b, _ := hexutil.Decode("0x19d97a94737c9583000000000000000000000001ea8ed6d0617dd5b3b87374020efaf030") - - return b - }(), - errored: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - packer := NewAbiPacker() - - b, err := packer.PackGetUpkeepPrivilegeConfig(test.upkeepId) - - if !test.errored { - require.NoError(t, err, "no error expected from packing") - - assert.Equal(t, test.raw, b, "raw bytes for output should match expected") - } else { - assert.Error(t, err, "error expected from packing function") - } - }) - } -} - -func TestPacker_UnpackCheckCallbackResult(t *testing.T) { - tests := []struct { - Name string - CallbackResp []byte - UpkeepNeeded bool - PerformData []byte - FailureReason encoding.UpkeepFailureReason - GasUsed *big.Int - ErrorString string - State encoding.PipelineExecutionState - }{ - { - Name: "unpack upkeep needed", - CallbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 10, 11, 21, 31, 41, 15, 16, 17, 18, 19, 13, 14, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 120, 111, 101, 122, 90, 54, 44, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - UpkeepNeeded: true, - PerformData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 10, 11, 21, 31, 41, 15, 16, 17, 18, 19, 13, 14, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 120, 111, 101, 122, 90, 54, 44, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - FailureReason: encoding.UpkeepFailureReasonNone, - GasUsed: big.NewInt(11796), - }, - { - Name: "unpack upkeep not needed", - CallbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 10, 11, 21, 31, 41, 15, 16, 17, 18, 19, 13, 14, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 120, 111, 101, 122, 90, 54, 44, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - UpkeepNeeded: false, - PerformData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 10, 11, 21, 31, 41, 15, 16, 17, 18, 19, 13, 14, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 120, 111, 101, 122, 90, 54, 44, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - FailureReason: encoding.UpkeepFailureReasonUpkeepNotNeeded, - GasUsed: big.NewInt(13008), - }, - { - Name: "unpack malformed data", - CallbackResp: []byte{0, 0, 0, 23, 4, 163, 66, 91, 228, 102, 200, 84, 144, 233, 218, 44, 168, 192, 191, 253, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - UpkeepNeeded: false, - PerformData: nil, - ErrorString: "abi: improperly encoded boolean value: unpack checkUpkeep return: ", - State: encoding.PackUnpackDecodeFailed, - }, - } - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - packer := NewAbiPacker() - - state, needed, pd, failureReason, gasUsed, err := packer.UnpackCheckCallbackResult(test.CallbackResp) - - if test.ErrorString != "" { - assert.EqualError(t, err, test.ErrorString+hexutil.Encode(test.CallbackResp)) - } else { - assert.NoError(t, err) - } - assert.Equal(t, test.UpkeepNeeded, needed) - assert.Equal(t, test.PerformData, pd) - assert.Equal(t, test.FailureReason, failureReason) - assert.Equal(t, test.GasUsed, gasUsed) - assert.Equal(t, test.State, state) - }) - } -} - -func TestPacker_PackUserCheckErrorHandler(t *testing.T) { - tests := []struct { - name string - errCode encoding.ErrCode - extraData []byte - rawOutput []byte - errored bool - }{ - { - name: "happy path", - errCode: encoding.ErrCodeStreamsBadRequest, - extraData: func() []byte { - b, _ := hexutil.Decode("0x19d97a94737c9583000000000000000000000001ea8ed6d0617dd5b3b87374020efaf030") - - return b - }(), - rawOutput: []byte{0xf, 0xb1, 0x72, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x55, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x19, 0xd9, 0x7a, 0x94, 0x73, 0x7c, 0x95, 0x83, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xea, 0x8e, 0xd6, 0xd0, 0x61, 0x7d, 0xd5, 0xb3, 0xb8, 0x73, 0x74, 0x2, 0xe, 0xfa, 0xf0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - errored: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - packer := NewAbiPacker() - - b, err := packer.PackUserCheckErrorHandler(test.errCode, test.extraData) - - if !test.errored { - require.NoError(t, err, "no error expected from packing") - - assert.Equal(t, test.rawOutput, b, "raw bytes for output should match expected") - } else { - assert.Error(t, err, "error expected from packing function") - } - }) - } -} - -func Test_CalculateRetryConfigFn(t *testing.T) { - tests := []struct { - name string - times int - upkeepType automationTypes.UpkeepType - expected time.Duration - }{ - { - name: "first retry", - times: 1, - upkeepType: automationTypes.LogTrigger, - expected: 1 * time.Second, - }, - { - name: "second retry", - times: 2, - upkeepType: automationTypes.LogTrigger, - expected: 1 * time.Second, - }, - { - name: "fifth retry", - times: 5, - upkeepType: automationTypes.LogTrigger, - expected: 1 * time.Second, - }, - { - name: "sixth retry", - times: 6, - upkeepType: automationTypes.LogTrigger, - expected: 5 * time.Second, - }, - { - name: "timeout", - times: totalMediumPluginRetries + 1, - upkeepType: automationTypes.LogTrigger, - expected: RetryIntervalTimeout, - }, - { - name: "conditional first timeout", - times: 1, - upkeepType: automationTypes.ConditionTrigger, - expected: RetryIntervalTimeout, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - cfg := newMercuryConfigMock() - var result time.Duration - for i := 0; i < tc.times; i++ { - result = CalculateStreamsRetryConfigFn(tc.upkeepType, "prk", cfg) - } - assert.Equal(t, tc.expected, result) - }) - } -} - -type mercuryConfigMock struct { - pluginRetryCache *cache.Cache -} - -func newMercuryConfigMock() *mercuryConfigMock { - return &mercuryConfigMock{ - pluginRetryCache: cache.New(10*time.Second, time.Minute), - } -} - -func (c *mercuryConfigMock) Credentials() *types.MercuryCredentials { - return nil -} - -func (c *mercuryConfigMock) IsUpkeepAllowed(k string) (any, bool) { - return nil, false -} - -func (c *mercuryConfigMock) SetUpkeepAllowed(k string, v any, d time.Duration) { -} - -func (c *mercuryConfigMock) GetPluginRetry(k string) (any, bool) { - return c.pluginRetryCache.Get(k) -} - -func (c *mercuryConfigMock) SetPluginRetry(k string, v any, d time.Duration) { - c.pluginRetryCache.Set(k, v, d) -} diff --git a/pkg/automation/v21/mercury/streams/streams.go b/pkg/automation/v21/mercury/streams/streams.go deleted file mode 100644 index b7ced12fb6..0000000000 --- a/pkg/automation/v21/mercury/streams/streams.go +++ /dev/null @@ -1,385 +0,0 @@ -package streams - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "math/big" - "net/http" - "strconv" - "sync" - "time" - - "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/common/hexutil" - "github.com/patrickmn/go-cache" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - autov2common "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury" - v02 "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury/v02" - v03 "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury/v03" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/prommetrics" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - zeroAddress = "0x0000000000000000000000000000000000000000" -) - -type Lookup interface { - Lookup(ctx context.Context, checkResults []ocr2keepers.CheckResult) []ocr2keepers.CheckResult -} - -type latestBlockProvider interface { - LatestBlock() *ocr2keepers.BlockKey -} - -type streamRegistry interface { - GetUpkeepPrivilegeConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) - CheckCallback(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) - Address() common.Address -} - -type contextCaller interface { - CallContext(ctx context.Context, result any, method string, args ...any) error -} - -type streams struct { - services.StateMachine - packer mercury.Packer - mercuryConfig mercury.MercuryConfigProvider - abi abi.ABI - blockSubscriber latestBlockProvider - registry streamRegistry - client contextCaller - lggr logger.Logger - threadCtrl threadcontrol.ThreadControl - v02Client mercury.MercuryClient - v03Client mercury.MercuryClient -} - -// UpkeepPrivilegeConfig represents the administrative offchain config for each upkeep. It can be set by s_upkeepPrivilegeManager -// role on the registry. Upkeeps allowed to use Mercury server will have this set to true. -type UpkeepPrivilegeConfig struct { - MercuryEnabled bool `json:"mercuryEnabled"` -} - -func NewStreamsLookup( - mercuryConfig mercury.MercuryConfigProvider, - blockSubscriber latestBlockProvider, - client contextCaller, - registry streamRegistry, - lggr logger.Logger) *streams { - httpClient := http.DefaultClient - threadCtrl := threadcontrol.NewThreadControl() - packer := mercury.NewAbiPacker() - - return &streams{ - packer: packer, - mercuryConfig: mercuryConfig, - abi: core.AutoV2CommonABI, - blockSubscriber: blockSubscriber, - registry: registry, - client: client, - lggr: lggr, - threadCtrl: threadCtrl, - v02Client: v02.NewClient(mercuryConfig, httpClient, threadCtrl, lggr), - v03Client: v03.NewClient(mercuryConfig, httpClient, threadCtrl, lggr), - } -} - -// Lookup looks through check upkeep results to find any that needs off chain lookup -func (s *streams) Lookup(ctx context.Context, checkResults []ocr2keepers.CheckResult) []ocr2keepers.CheckResult { - lookups := map[int]*mercury.StreamsLookup{} - for i, checkResult := range checkResults { - s.buildResult(ctx, i, checkResult, checkResults, lookups) - } - - var wg sync.WaitGroup - for i, lookup := range lookups { - wg.Add(1) - func(i int, lookup *mercury.StreamsLookup) { - s.threadCtrl.GoCtx(ctx, func(ctx context.Context) { - s.doLookup(ctx, &wg, lookup, i, checkResults) - }) - }(i, lookup) - } - wg.Wait() - - // don't surface error to plugin bc StreamsLookup process should be self-contained. - return checkResults -} - -// buildResult checks if the upkeep is allowed by Mercury and builds a streams lookup request from the check result -func (s *streams) buildResult(ctx context.Context, i int, checkResult ocr2keepers.CheckResult, checkResults []ocr2keepers.CheckResult, lookups map[int]*mercury.StreamsLookup) { - lookupLggr := logger.Sugared(s.lggr).With("where", "StreamsLookup") - if checkResult.IneligibilityReason != uint8(encoding.UpkeepFailureReasonTargetCheckReverted) { - // Streams Lookup only works when upkeep target check reverts - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorReasonNotReverted).Inc() - return - } - - block := big.NewInt(int64(checkResult.Trigger.BlockNumber)) - upkeepId := checkResult.UpkeepID - - // Try to decode the revert error into streams lookup format. User upkeeps can revert with any reason, see if they - // tried to call mercury - lookupLggr.Infof("at block %d upkeep %s trying to DecodeStreamsLookupRequest performData=%s", block, upkeepId, hexutil.Encode(checkResults[i].PerformData)) - streamsLookupErr, err := s.packer.DecodeStreamsLookupRequest(checkResult.PerformData) - if err != nil { - lookupLggr.Debugf("at block %d upkeep %s DecodeStreamsLookupRequest failed: %v", block, upkeepId, err) - // user contract did not revert with StreamsLookup error - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorDecodeRequestFailed).Inc() - return - } - streamsLookupResponse := &mercury.StreamsLookup{StreamsLookupError: streamsLookupErr} - if s.mercuryConfig.Credentials() == nil { - lookupLggr.Errorf("at block %d upkeep %s tries to access mercury server but mercury credential is not configured", block, upkeepId) - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorCredentialsNotConfigured).Inc() - return - } - - // mercury permission checking for v0.3 is done by mercury server, so no need to check here - if streamsLookupResponse.IsMercuryV02() { - // check permission on the registry for mercury v0.2 - opts := s.buildCallOpts(ctx, block) - if state, reason, retryable, allowed, err := s.AllowedToUseMercury(opts, upkeepId.BigInt()); err != nil { - lookupLggr.Warnf("at block %s upkeep %s failed to query mercury allow list: %s", block, upkeepId, err) - checkResults[i].PipelineExecutionState = uint8(state) - checkResults[i].IneligibilityReason = uint8(reason) - checkResults[i].Retryable = retryable - return - } else if !allowed { - lookupLggr.Debugf("at block %d upkeep %s NOT allowed to query Mercury server", block, upkeepId) - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonMercuryAccessNotAllowed) - return - } - } else if !streamsLookupResponse.IsMercuryV03() { - // if mercury version is not v02 or v03, set failure reason - lookupLggr.Debugf("at block %d upkeep %s NOT allowed to query Mercury server", block, upkeepId) - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonInvalidRevertDataInput) - return - } - - streamsLookupResponse.UpkeepId = upkeepId.BigInt() - // the block here is exclusively used to call checkCallback at this block, not to be confused with the block number - // in the revert for mercury v0.2, which is denoted by time in the struct bc starting from v0.3, only timestamp will be supported - streamsLookupResponse.Block = uint64(block.Int64()) - lookupLggr.Infof("at block %d upkeep %s DecodeStreamsLookupRequest feedKey=%s timeKey=%s feeds=%v time=%s extraData=%s", block, upkeepId, streamsLookupResponse.FeedParamKey, streamsLookupResponse.TimeParamKey, streamsLookupResponse.Feeds, streamsLookupResponse.Time, hexutil.Encode(streamsLookupResponse.ExtraData)) - lookups[i] = streamsLookupResponse -} - -// Does the requested lookup and sets appropriate fields on checkResult[i] -func (s *streams) doLookup(ctx context.Context, wg *sync.WaitGroup, lookup *mercury.StreamsLookup, i int, checkResults []ocr2keepers.CheckResult) { - defer wg.Done() - - values, errCode, err := s.DoMercuryRequest(ctx, lookup, checkResults, i) - if err != nil { - s.lggr.Errorf("at block %d upkeep %s requested time %s DoMercuryRequest err: %s", lookup.Block, lookup.UpkeepId, lookup.Time, err.Error()) - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorDoMercuryRequest).Inc() - return - } - - if errCode != encoding.ErrCodeNil { - err = s.CheckErrorHandler(ctx, errCode, lookup, checkResults, i) - if err != nil { - s.lggr.Errorf("at block %d upkeep %s requested time %s CheckErrorHandler err: %s", lookup.Block, lookup.UpkeepId, lookup.Time, err.Error()) - } - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorCodeNotNil).Inc() - return - } - - // Mercury request returned values or user's checkErrorhandler didn't return error, call checkCallback - err = s.CheckCallback(ctx, values, lookup, checkResults, i) - if err != nil { - s.lggr.Errorf("at block %d upkeep %s requested time %s CheckCallback err: %s", lookup.Block, lookup.UpkeepId, lookup.Time, err.Error()) - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorCheckCallback).Inc() - } -} - -func (s *streams) CheckCallback(ctx context.Context, values [][]byte, lookup *mercury.StreamsLookup, checkResults []ocr2keepers.CheckResult, i int) error { - prommetrics.AutomationStreamsLookupStep.WithLabelValues(prommetrics.StreamsLookupStepCheckCallback).Inc() - payload, err := s.abi.Pack("checkCallback", lookup.UpkeepId, values, lookup.ExtraData) - if err != nil { - checkResults[i].Retryable = false - checkResults[i].PipelineExecutionState = uint8(encoding.PackUnpackDecodeFailed) - return err - } - - return s.makeCallbackEthCall(ctx, payload, lookup, checkResults, i) -} - -// eth_call to checkCallback and checkErrorHandler and update checkResults[i] accordingly -func (s *streams) makeCallbackEthCall(ctx context.Context, payload []byte, lookup *mercury.StreamsLookup, checkResults []ocr2keepers.CheckResult, i int) error { - var responseBytes hexutil.Bytes - args := map[string]any{ - "from": zeroAddress, - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - - if err := s.client.CallContext(ctx, &responseBytes, "eth_call", args, hexutil.EncodeUint64(lookup.Block)); err != nil { - checkResults[i].Retryable = true - checkResults[i].PipelineExecutionState = uint8(encoding.RpcFlakyFailure) - return err - } - - s.lggr.Infof("at block %d upkeep %s requested time %s responseBytes: %s", lookup.Block, lookup.UpkeepId, lookup.Time, hexutil.Encode(responseBytes)) - - unpackCallBackState, needed, performData, failureReason, _, err := s.packer.UnpackCheckCallbackResult(responseBytes) - if err != nil { - checkResults[i].PipelineExecutionState = uint8(unpackCallBackState) - return err - } - - s.lggr.Infof("at block %d upkeep %s requested time %s returns needed: %v, failure reason: %d, perform data: %s", lookup.Block, lookup.UpkeepId, lookup.Time, needed, failureReason, hexutil.Encode(performData)) - - checkResults[i].IneligibilityReason = uint8(failureReason) - checkResults[i].Eligible = needed - checkResults[i].PerformData = performData - - return nil -} - -// Does the mercury request for the checkResult. Returns either the looked up values or an error code if something is wrong with mercury -// In case of any pipeline processing issues, returns an error and also sets approriate state on the checkResult itself -func (s *streams) DoMercuryRequest(ctx context.Context, lookup *mercury.StreamsLookup, checkResults []ocr2keepers.CheckResult, i int) ([][]byte, encoding.ErrCode, error) { - prommetrics.AutomationStreamsLookupStep.WithLabelValues(prommetrics.StreamsLookupStepDoMercuryRequest).Inc() - var state, values, errCode, retryable, retryInterval = encoding.NoPipelineError, [][]byte{}, encoding.ErrCodeNil, false, 0 * time.Second - var err error - pluginRetryKey := generatePluginRetryKey(checkResults[i].WorkID, lookup.Block) - upkeepType := core.GetUpkeepType(checkResults[i].UpkeepID) - - if lookup.IsMercuryV02() { - state, values, errCode, retryable, retryInterval, err = s.v02Client.DoRequest(ctx, lookup, upkeepType, pluginRetryKey) - } else if lookup.IsMercuryV03() { - state, values, errCode, retryable, retryInterval, err = s.v03Client.DoRequest(ctx, lookup, upkeepType, pluginRetryKey) - } - - if err != nil { - // Something went wrong in the pipeline processing, set the state, retry reason and return - checkResults[i].Retryable = retryable - checkResults[i].RetryInterval = retryInterval - checkResults[i].PipelineExecutionState = uint8(state) - s.lggr.Debugf("at block %d upkeep %s requested time %s doMercuryRequest err: %s", lookup.Block, lookup.UpkeepId, lookup.Time, err.Error()) - return nil, encoding.ErrCodeNil, err - } - - if errCode != encoding.ErrCodeNil { - s.lggr.Infof("at block %d upkeep %s requested time %s doMercuryRequest error code: %d", lookup.Block, lookup.UpkeepId, lookup.Time, errCode) - return nil, errCode, nil - } - - for j, v := range values { - s.lggr.Infof("at block %d upkeep %s requested time %s doMercuryRequest values[%d]: %s", lookup.Block, lookup.UpkeepId, lookup.Time, j, hexutil.Encode(v)) - } - return values, encoding.ErrCodeNil, nil -} - -func (s *streams) CheckErrorHandler(ctx context.Context, errCode encoding.ErrCode, lookup *mercury.StreamsLookup, checkResults []ocr2keepers.CheckResult, i int) error { - s.lggr.Debugf("at block %d upkeep %s requested time %s CheckErrorHandler error code: %d", lookup.Block, lookup.UpkeepId, lookup.Time, errCode) - prommetrics.AutomationStreamsLookupStep.WithLabelValues(prommetrics.StreamsLookupStepCheckErrorHandler).Inc() - - userPayload, err := s.packer.PackUserCheckErrorHandler(errCode, lookup.ExtraData) - if err != nil { - checkResults[i].Retryable = false - checkResults[i].PipelineExecutionState = uint8(encoding.PackUnpackDecodeFailed) - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorPackUserCheckErrorHandler).Inc() - return err - } - - payload, err := s.abi.Pack("executeCallback", lookup.UpkeepId, userPayload) - if err != nil { - checkResults[i].Retryable = false - checkResults[i].PipelineExecutionState = uint8(encoding.PackUnpackDecodeFailed) - prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorPackExecuteCallback).Inc() - return err - } - - return s.makeCallbackEthCall(ctx, payload, lookup, checkResults, i) -} - -// AllowedToUseMercury retrieves upkeep's administrative offchain config and decode a mercuryEnabled bool to indicate if -// this upkeep is allowed to use Mercury service. -func (s *streams) AllowedToUseMercury(opts *bind.CallOpts, upkeepId *big.Int) (state encoding.PipelineExecutionState, reason encoding.UpkeepFailureReason, retryable bool, allow bool, err error) { - allowed, ok := s.mercuryConfig.IsUpkeepAllowed(upkeepId.String()) - if ok { - return encoding.NoPipelineError, encoding.UpkeepFailureReasonNone, false, allowed.(bool), nil - } - - payload, err := s.packer.PackGetUpkeepPrivilegeConfig(upkeepId) - if err != nil { - // pack error, no retryable - s.lggr.Warnf("failed to pack getUpkeepPrivilegeConfig data for upkeepId %s: %s", upkeepId, err) - - return encoding.PackUnpackDecodeFailed, encoding.UpkeepFailureReasonNone, false, false, fmt.Errorf("failed to pack upkeepId: %w", err) - } - - var resultBytes hexutil.Bytes - args := map[string]any{ - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - - if err = s.client.CallContext(opts.Context, &resultBytes, "eth_call", args, hexutil.EncodeBig(opts.BlockNumber)); err != nil { - return encoding.RpcFlakyFailure, encoding.UpkeepFailureReasonNone, true, false, fmt.Errorf("failed to get upkeep privilege config: %w", err) - } - - var upkeepPrivilegeConfigBytes []byte - upkeepPrivilegeConfigBytes, err = s.packer.UnpackGetUpkeepPrivilegeConfig(resultBytes) - - if err != nil { - return encoding.PackUnpackDecodeFailed, encoding.UpkeepFailureReasonNone, false, false, fmt.Errorf("failed to get upkeep privilege config: %w", err) - } - - if len(upkeepPrivilegeConfigBytes) == 0 { - s.mercuryConfig.SetUpkeepAllowed(upkeepId.String(), false, cache.DefaultExpiration) - return encoding.NoPipelineError, encoding.UpkeepFailureReasonMercuryAccessNotAllowed, false, false, errors.New("upkeep privilege config is empty") - } - - var privilegeConfig UpkeepPrivilegeConfig - if err = json.Unmarshal(upkeepPrivilegeConfigBytes, &privilegeConfig); err != nil { - return encoding.PrivilegeConfigUnmarshalError, encoding.UpkeepFailureReasonNone, false, false, fmt.Errorf("failed to unmarshal privilege config: %w", err) - } - - s.mercuryConfig.SetUpkeepAllowed(upkeepId.String(), privilegeConfig.MercuryEnabled, cache.DefaultExpiration) - - return encoding.NoPipelineError, encoding.UpkeepFailureReasonNone, false, privilegeConfig.MercuryEnabled, nil -} - -func (s *streams) buildCallOpts(ctx context.Context, block *big.Int) *bind.CallOpts { - opts := bind.CallOpts{ - Context: ctx, - } - - if block == nil || block.Int64() == 0 { - if latestBlock := s.blockSubscriber.LatestBlock(); latestBlock != nil && latestBlock.Number != 0 { - opts.BlockNumber = big.NewInt(int64(latestBlock.Number)) - } - } else { - opts.BlockNumber = block - } - - return &opts -} - -// generatePluginRetryKey returns a plugin retry cache key -func generatePluginRetryKey(workID string, block uint64) string { - return workID + "|" + strconv.FormatUint(block, 10) -} - -func (s *streams) Close() error { - return s.StopOnce("streams_lookup", func() error { - s.threadCtrl.Close() - return nil - }) -} diff --git a/pkg/automation/v21/mercury/streams/streams_test.go b/pkg/automation/v21/mercury/streams/streams_test.go deleted file mode 100644 index d5db1ca049..0000000000 --- a/pkg/automation/v21/mercury/streams/streams_test.go +++ /dev/null @@ -1,970 +0,0 @@ -package streams - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "math/big" - "net/http" - "strings" - "testing" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - autov2common "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/client/clienttest" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury" - v02 "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury/v02" - v03 "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury/v03" -) - -type MockMercuryConfigProvider struct { - mock.Mock -} - -func (m *MockMercuryConfigProvider) Credentials() *types.MercuryCredentials { - mc := &types.MercuryCredentials{ - LegacyURL: "https://google.old.com", - URL: "https://google.com", - Username: "FakeClientID", - Password: "FakeClientKey", - } - return mc -} - -func (m *MockMercuryConfigProvider) IsUpkeepAllowed(s string) (any, bool) { - args := m.Called(s) - return args.Get(0), args.Bool(1) -} - -func (m *MockMercuryConfigProvider) SetUpkeepAllowed(s string, i any, d time.Duration) { - m.Called(s, i, d) -} - -func (m *MockMercuryConfigProvider) GetPluginRetry(s string) (any, bool) { - args := m.Called(s) - return args.Get(0), args.Bool(1) -} - -func (m *MockMercuryConfigProvider) SetPluginRetry(s string, i any, d time.Duration) { - m.Called(s, i, d) -} - -type MockBlockSubscriber struct { - mock.Mock -} - -func (b *MockBlockSubscriber) LatestBlock() *ocr2keepers.BlockKey { - return nil -} - -type MockHttpClient struct { - mock.Mock -} - -func (mock *MockHttpClient) Do(req *http.Request) (*http.Response, error) { - args := mock.Called(req) - return args.Get(0).(*http.Response), args.Error(1) -} - -type mockRegistry struct { - GetUpkeepPrivilegeConfigFn func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) - CheckCallbackFn func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) -} - -func (r *mockRegistry) Address() common.Address { - return common.HexToAddress("0x6cA639822c6C241Fa9A7A6b5032F6F7F1C513CAD") -} - -func (r *mockRegistry) GetUpkeepPrivilegeConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return r.GetUpkeepPrivilegeConfigFn(opts, upkeepId) -} - -func (r *mockRegistry) CheckCallback(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return r.CheckCallbackFn(opts, id, values, extraData) -} - -// setups up a streams object for tests. -func setupStreams(t *testing.T) *streams { - lggr := logger.Test(t) - mercuryConfig := new(MockMercuryConfigProvider) - blockSubscriber := new(MockBlockSubscriber) - registry := &mockRegistry{} - client := clienttest.NewClient(t) - - streams := NewStreamsLookup( - mercuryConfig, - blockSubscriber, - client, - registry, - lggr, - ) - return streams -} - -func TestStreams_CheckErrorHandler(t *testing.T) { - upkeepId := big.NewInt(123456789) - blockNumber := uint64(999) - tests := []struct { - name string - lookup *mercury.StreamsLookup - checkResults []ocr2keepers.CheckResult - errCode encoding.ErrCode - - callbackResp []byte - callbackErr error - - upkeepNeeded bool - performData []byte - wantErr assert.ErrorAssertionFunc - - state encoding.PipelineExecutionState - retryable bool - }{ - { - name: "success - empty extra data", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"ETD-USD", "BTC-ETH"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(100), - ExtraData: []byte{48, 120, 48, 48}, - }, - UpkeepId: upkeepId, - Block: blockNumber, - }, - checkResults: []ocr2keepers.CheckResult{ - {}, - }, - errCode: encoding.ErrCodeNil, - callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - upkeepNeeded: true, - performData: []byte{48, 120, 48, 48}, - wantErr: assert.NoError, - }, - { - name: "success - with extra data", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(18952430), - // this is the address of precompile contract ArbSys(0x0000000000000000000000000000000000000064) - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - Block: blockNumber, - }, - checkResults: []ocr2keepers.CheckResult{ - {}, - }, - errCode: encoding.ErrCodeNil, - callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - upkeepNeeded: true, - performData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - wantErr: assert.NoError, - }, - { - name: "failure - checkCallback eth_call failure", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"ETD-USD", "BTC-ETH"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(100), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - UpkeepId: upkeepId, - Block: blockNumber, - }, - checkResults: []ocr2keepers.CheckResult{ - {}, - }, - errCode: encoding.ErrCodeNil, - callbackResp: []byte{}, - callbackErr: errors.New("bad response"), - wantErr: assert.Error, - state: encoding.RpcFlakyFailure, - retryable: true, - upkeepNeeded: false, - }, - { - name: "failure - unpack error because of invalid response bytes from checkCallback eth_call", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"ETD-USD", "BTC-ETH"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(100), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - UpkeepId: upkeepId, - Block: blockNumber, - }, - checkResults: []ocr2keepers.CheckResult{ - {}, - }, - errCode: encoding.ErrCodeNil, - callbackResp: []byte{0x01, 0xAB, 0x45, 0xFF, 0x32}, // invalid response bytes - wantErr: assert.Error, - state: encoding.PackUnpackDecodeFailed, - retryable: false, - upkeepNeeded: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client := new(clienttest.Client) - s := setupStreams(t) - defer s.Close() - - userPayload, err := s.packer.PackUserCheckErrorHandler(tt.errCode, tt.lookup.ExtraData) - require.NoError(t, err) - payload, err := s.abi.Pack("executeCallback", tt.lookup.UpkeepId, userPayload) - require.NoError(t, err) - - args := map[string]any{ - "from": zeroAddress, - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, hexutil.EncodeUint64(tt.lookup.Block)).Return(tt.callbackErr). - Run(func(args mock.Arguments) { - by := args.Get(1).(*hexutil.Bytes) - *by = tt.callbackResp - }).Once() - s.client = client - - err = s.CheckErrorHandler(t.Context(), tt.errCode, tt.lookup, tt.checkResults, 0) - tt.wantErr(t, err, fmt.Sprintf("Error assertion failed: %v", tt.name)) - assert.Equal(t, uint8(tt.state), tt.checkResults[0].PipelineExecutionState) - assert.Equal(t, tt.retryable, tt.checkResults[0].Retryable) - assert.Equal(t, tt.upkeepNeeded, tt.checkResults[0].Eligible) - assert.Equal(t, tt.performData, tt.checkResults[0].PerformData) - }) - } -} - -func TestStreams_CheckCallback(t *testing.T) { - upkeepId := big.NewInt(123456789) - bn := uint64(999) - bs := []byte{183, 114, 215, 10, 0, 0, 0, 0, 0, 0} - values := [][]byte{bs} - tests := []struct { - name string - lookup *mercury.StreamsLookup - input []ocr2keepers.CheckResult - values [][]byte - statusCode int - - callbackResp []byte - callbackErr error - - upkeepNeeded bool - performData []byte - wantErr assert.ErrorAssertionFunc - - state encoding.PipelineExecutionState - retryable bool - registry streamRegistry - }{ - { - name: "success - empty extra data", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"ETD-USD", "BTC-ETH"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(100), - ExtraData: []byte{48, 120, 48, 48}, - }, - UpkeepId: upkeepId, - Block: bn, - }, - input: []ocr2keepers.CheckResult{ - {}, - }, - values: values, - statusCode: http.StatusOK, - callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - upkeepNeeded: true, - performData: []byte{48, 120, 48, 48}, - wantErr: assert.NoError, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{ - UpkeepNeeded: true, - PerformData: []byte{48, 120, 48, 48}, - }, nil - }, - }, - }, - { - name: "success - with extra data", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(18952430), - // this is the address of precompile contract ArbSys(0x0000000000000000000000000000000000000064) - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - Block: bn, - }, - input: []ocr2keepers.CheckResult{ - {}, - }, - values: values, - statusCode: http.StatusOK, - callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - upkeepNeeded: true, - performData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - wantErr: assert.NoError, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{ - UpkeepNeeded: true, - PerformData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, nil - }, - }, - }, - { - name: "failure - bad response", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"ETD-USD", "BTC-ETH"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(100), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - UpkeepId: upkeepId, - Block: bn, - }, - input: []ocr2keepers.CheckResult{ - {}, - }, - values: values, - statusCode: http.StatusOK, - callbackResp: []byte{}, - callbackErr: errors.New("bad response"), - wantErr: assert.Error, - state: encoding.RpcFlakyFailure, - retryable: true, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, errors.New("bad response") - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client := new(clienttest.Client) - s := setupStreams(t) - defer s.Close() - payload, err := s.abi.Pack("checkCallback", tt.lookup.UpkeepId, values, tt.lookup.ExtraData) - require.NoError(t, err) - args := map[string]any{ - "from": zeroAddress, - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, hexutil.EncodeUint64(tt.lookup.Block)).Return(tt.callbackErr). - Run(func(args mock.Arguments) { - by := args.Get(1).(*hexutil.Bytes) - *by = tt.callbackResp - }).Once() - s.client = client - - err = s.CheckCallback(t.Context(), tt.values, tt.lookup, tt.input, 0) - tt.wantErr(t, err, fmt.Sprintf("Error assertion failed: %v", tt.name)) - assert.Equal(t, uint8(tt.state), tt.input[0].PipelineExecutionState) - assert.Equal(t, tt.retryable, tt.input[0].Retryable) - assert.Equal(t, tt.upkeepNeeded, tt.input[0].Eligible) - }) - } -} - -func TestStreams_AllowedToUseMercury(t *testing.T) { - upkeepId, ok := new(big.Int).SetString("71022726777042968814359024671382968091267501884371696415772139504780367423725", 10) - assert.True(t, ok, t.Name()) - tests := []struct { - name string - cached bool - allowed bool - ethCallErr error - err error - state encoding.PipelineExecutionState - reason encoding.UpkeepFailureReason - registry streamRegistry - retryable bool - config []byte - }{ - { - name: "success - allowed via cache", - cached: true, - allowed: true, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return nil, nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, nil - }, - }, - }, - { - name: "success - allowed via fetching privilege config", - allowed: true, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, nil - }, - }, - }, - { - name: "success - not allowed via cache", - cached: true, - allowed: false, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return nil, nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, nil - }, - }, - }, - { - name: "success - not allowed via fetching privilege config", - allowed: false, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":false}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, nil - }, - }, - }, - { - name: "failure - cannot unmarshal privilege config", - err: errors.New("failed to unmarshal privilege config: invalid character '\\x00' looking for beginning of value"), - state: encoding.PrivilegeConfigUnmarshalError, - config: []byte{0, 1}, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, nil - }, - }, - }, - { - name: "failure - flaky RPC", - retryable: true, - err: errors.New("failed to get upkeep privilege config: flaky RPC"), - state: encoding.RpcFlakyFailure, - ethCallErr: errors.New("flaky RPC"), - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return nil, errors.New("flaky RPC") - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, nil - }, - }, - }, - { - name: "failure - empty upkeep privilege config", - err: errors.New("upkeep privilege config is empty"), - reason: encoding.UpkeepFailureReasonMercuryAccessNotAllowed, - config: []byte{}, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(``), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{}, nil - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := setupStreams(t) - defer s.Close() - s.registry = tt.registry - - client := new(clienttest.Client) - s.client = client - - mc := new(MockMercuryConfigProvider) - mc.On("IsUpkeepAllowed", mock.Anything).Return(tt.allowed, tt.cached).Once() - mc.On("SetUpkeepAllowed", mock.Anything, mock.Anything, mock.Anything).Return().Once() - s.mercuryConfig = mc - - if !tt.cached { - if tt.err != nil { - bContractCfg, err := s.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]any{tt.config}) - require.NoError(t, err) - - payload, err := s.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) - require.NoError(t, err) - - args := map[string]any{ - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, mock.AnythingOfType("string")). - Return(tt.ethCallErr). - Run(func(args mock.Arguments) { - b := args.Get(1).(*hexutil.Bytes) - *b = bContractCfg - }).Once() - } else { - cfg := UpkeepPrivilegeConfig{MercuryEnabled: tt.allowed} - bCfg, err := json.Marshal(cfg) - require.NoError(t, err) - - bContractCfg, err := s.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]any{bCfg}) - require.NoError(t, err) - - payload, err := s.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) - require.NoError(t, err) - - args := map[string]any{ - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, mock.AnythingOfType("string")).Return(nil). - Run(func(args mock.Arguments) { - b := args.Get(1).(*hexutil.Bytes) - *b = bContractCfg - }).Once() - } - } - - opts := &bind.CallOpts{ - BlockNumber: big.NewInt(10), - } - - state, reason, retryable, allowed, err := s.AllowedToUseMercury(opts, upkeepId) - if tt.err == nil { - assert.NoError(t, err) - } else { - assert.ErrorContains(t, err, tt.err.Error()) - } - assert.Equal(t, tt.allowed, allowed) - assert.Equal(t, tt.state, state) - assert.Equal(t, tt.reason, reason) - assert.Equal(t, tt.retryable, retryable) - }) - } -} - -func TestStreams_StreamsLookup(t *testing.T) { - upkeepId, ok := new(big.Int).SetString("71022726777042968814359024671382968091267501884371696415772139504780367423725", 10) - var upkeepIdentifier [32]byte - copy(upkeepIdentifier[:], upkeepId.Bytes()) - assert.True(t, ok, t.Name()) - blockNum := ocr2keepers.BlockNumber(37974374) - tests := []struct { - name string - input []ocr2keepers.CheckResult - blobs map[string]string - callbackResp []byte - expectedResults []ocr2keepers.CheckResult - callbackNeeded bool - extraData []byte - checkCallbackResp []byte - values [][]byte - cachedAdminCfg bool - hasError bool - hasPermission bool - v3 bool - registry streamRegistry - }{ - { - name: "success - happy path no cache", - input: []ocr2keepers.CheckResult{ - { - PerformData: hexutil.MustDecode("0xf055e4a200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000966656564496448657800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000423078343535343438326435353533343432643431353234323439353435323535346432643534343535333534346534353534303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307834323534343332643535353334343264343135323432343935343532353534643264353434353533353434653435353430303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d62657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonTargetCheckReverted), - }, - }, - blobs: map[string]string{ - "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000": "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa3", - "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000": "0x0006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d", - }, - cachedAdminCfg: false, - extraData: hexutil.MustDecode("0x0000000000000000000000000000000000000064"), - callbackNeeded: true, - checkCallbackResp: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063a400000000000000000000000000000000000000000000000000000000000006e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - values: [][]byte{hexutil.MustDecode("0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa3"), hexutil.MustDecode("0x0006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d")}, - expectedResults: []ocr2keepers.CheckResult{ - { - Eligible: true, - PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonNone), - }, - }, - hasPermission: true, - v3: false, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{ - UpkeepNeeded: true, - PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - }, nil - }, - }, - }, - { - name: "two CheckResults: Mercury success E2E and No Mercury because of insufficient balance", - input: []ocr2keepers.CheckResult{ - { - PerformData: []byte{}, - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: 26046145, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonInsufficientBalance), - }, - { - PerformData: hexutil.MustDecode("0xf055e4a200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000966656564496448657800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000423078343535343438326435353533343432643431353234323439353435323535346432643534343535333534346534353534303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307834323534343332643535353334343264343135323432343935343532353534643264353434353533353434653435353430303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d62657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonTargetCheckReverted), - }, - }, - blobs: map[string]string{ - "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000": "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa3", - "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000": "0x0006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d", - }, - cachedAdminCfg: false, - extraData: hexutil.MustDecode("0x0000000000000000000000000000000000000064"), - callbackNeeded: true, - checkCallbackResp: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063a400000000000000000000000000000000000000000000000000000000000006e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - values: [][]byte{hexutil.MustDecode("0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa3"), hexutil.MustDecode("0x0006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d")}, - expectedResults: []ocr2keepers.CheckResult{ - { - Eligible: false, - PerformData: []byte{}, - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: 26046145, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonInsufficientBalance), - }, - { - Eligible: true, - PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonNone), - }, - }, - hasPermission: true, - v3: false, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{ - UpkeepNeeded: true, - PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - }, nil - }, - }, - }, - { - name: "success - happy path no cache - v0.3", - input: []ocr2keepers.CheckResult{ - { - PerformData: hexutil.MustDecode("0xf055e4a200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000766656564494473000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000423078343535343438326435353533343432643431353234323439353435323535346432643534343535333534346534353534303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307834323534343332643535353334343264343135323432343935343532353534643264353434353533353434653435353430303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d62657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonTargetCheckReverted), - }, - }, - blobs: map[string]string{ - "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000": "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa3", - "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000": "0x0006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d", - }, - cachedAdminCfg: false, - extraData: hexutil.MustDecode("0x0000000000000000000000000000000000000064"), - callbackNeeded: true, - checkCallbackResp: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063a400000000000000000000000000000000000000000000000000000000000006e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - values: [][]byte{hexutil.MustDecode("0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa3"), hexutil.MustDecode("0x0006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d")}, - expectedResults: []ocr2keepers.CheckResult{ - { - Eligible: true, - PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonNone), - }, - }, - hasPermission: true, - v3: true, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{ - UpkeepNeeded: true, - PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - }, nil - }, - }, - }, - { - name: "skip - failure reason is insufficient balance", - input: []ocr2keepers.CheckResult{ - { - PerformData: []byte{}, - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: 26046145, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonInsufficientBalance), - }, - }, - expectedResults: []ocr2keepers.CheckResult{ - { - Eligible: false, - PerformData: []byte{}, - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: 26046145, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonInsufficientBalance), - }, - }, - hasError: true, - registry: &mockRegistry{ - GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return []byte(`{"mercuryEnabled":true}`), nil - }, - CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (autov2common.CheckCallback, error) { - return autov2common.CheckCallback{ - UpkeepNeeded: true, - PerformData: []byte{}, - }, nil - }, - }, - }, - { - name: "skip - invalid revert data", - input: []ocr2keepers.CheckResult{ - { - PerformData: []byte{}, - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: 26046145, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonTargetCheckReverted), - }, - }, - expectedResults: []ocr2keepers.CheckResult{ - { - Eligible: false, - PerformData: []byte{}, - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: 26046145, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonTargetCheckReverted), - }, - }, - hasError: true, - }, - { - name: "failure - invalid mercury version", - input: []ocr2keepers.CheckResult{ - { - // This Perform data contains invalid FeedParamKey: {feedIdHex:RandomString [ETD-USD BTC-ETH] blockNumber 100 [48 120 48 48]} - PerformData: hexutil.MustDecode("0xf055e4a200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000166665656449644865783a52616e646f6d537472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000074554442d5553440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074254432d45544800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d62657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043078303000000000000000000000000000000000000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonTargetCheckReverted), - }, - }, - expectedResults: []ocr2keepers.CheckResult{ - { - PerformData: hexutil.MustDecode("0xf055e4a200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000166665656449644865783a52616e646f6d537472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000074554442d5553440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074254432d45544800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d62657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043078303000000000000000000000000000000000000000000000000000000000"), - UpkeepID: upkeepIdentifier, - Trigger: ocr2keepers.Trigger{ - BlockNumber: blockNum, - }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonInvalidRevertDataInput), - }, - }, - hasError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := setupStreams(t) - defer s.Close() - s.registry = tt.registry - - client := new(clienttest.Client) - s.client = client - - mc := new(MockMercuryConfigProvider) - mc.On("IsUpkeepAllowed", mock.Anything).Return(tt.hasPermission, tt.cachedAdminCfg).Once() - mc.On("SetUpkeepAllowed", mock.Anything, mock.Anything, mock.Anything).Return().Once() - s.mercuryConfig = mc - - if !tt.cachedAdminCfg && !tt.hasError { - cfg := UpkeepPrivilegeConfig{MercuryEnabled: tt.hasPermission} - bCfg, err := json.Marshal(cfg) - require.NoError(t, err) - - bContractCfg, err := s.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]any{bCfg}) - require.NoError(t, err) - - payload, err := s.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) - require.NoError(t, err) - - args := map[string]any{ - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, mock.AnythingOfType("string")).Return(nil). - Run(func(args mock.Arguments) { - b := args.Get(1).(*hexutil.Bytes) - *b = bContractCfg - }).Once() - } - - if len(tt.blobs) > 0 { - if tt.v3 { - hc03 := new(MockHttpClient) - v03HttpClient := v03.NewClient(s.mercuryConfig, hc03, s.threadCtrl, s.lggr) - s.v03Client = v03HttpClient - - mr1 := v03.MercuryV03Response{ - Reports: []v03.MercuryV03Report{{FullReport: tt.blobs["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"]}, {FullReport: tt.blobs["0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"]}}} - b1, err := json.Marshal(mr1) - assert.NoError(t, err) - resp1 := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b1)), - } - hc03.On("Do", mock.Anything).Return(resp1, nil).Once() - } else { - hc02 := new(MockHttpClient) - v02HttpClient := v02.NewClient(s.mercuryConfig, hc02, s.threadCtrl, s.lggr) - s.v02Client = v02HttpClient - - mr1 := v02.MercuryV02Response{ChainlinkBlob: tt.blobs["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"]} - b1, err := json.Marshal(mr1) - assert.NoError(t, err) - resp1 := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b1)), - } - mr2 := v02.MercuryV02Response{ChainlinkBlob: tt.blobs["0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"]} - b2, err := json.Marshal(mr2) - assert.NoError(t, err) - resp2 := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b2)), - } - - hc02.On("Do", mock.MatchedBy(func(req *http.Request) bool { - return strings.Contains(req.URL.String(), "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000") - })).Return(resp2, nil).Once() - - hc02.On("Do", mock.MatchedBy(func(req *http.Request) bool { - return strings.Contains(req.URL.String(), "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000") - })).Return(resp1, nil).Once() - } - } - - if tt.callbackNeeded { - payload, err := s.abi.Pack("checkCallback", upkeepId, tt.values, tt.extraData) - require.NoError(t, err) - args := map[string]any{ - "from": zeroAddress, - "to": s.registry.Address().Hex(), - "data": hexutil.Bytes(payload), - } - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, hexutil.EncodeUint64(uint64(blockNum))).Return(nil). - Run(func(args mock.Arguments) { - b := args.Get(1).(*hexutil.Bytes) - *b = tt.checkCallbackResp - }).Once() - } - - got := s.Lookup(t.Context(), tt.input) - assert.Equal(t, tt.expectedResults, got, tt.name) - }) - } -} diff --git a/pkg/automation/v21/mercury/testutils.go b/pkg/automation/v21/mercury/testutils.go deleted file mode 100644 index df64cf3bce..0000000000 --- a/pkg/automation/v21/mercury/testutils.go +++ /dev/null @@ -1,72 +0,0 @@ -package mercury - -import ( - "net/http" - "net/http/httptest" - "sync/atomic" -) - -type MercuryEndpointMock interface { - URL() string - Username() string - Password() string - CallCount() int - RegisterHandler(http.HandlerFunc) -} - -var _ MercuryEndpointMock = &SimulatedMercuryServer{} - -var notFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) -}) - -type SimulatedMercuryServer struct { - server *httptest.Server - handler http.HandlerFunc - - callCounter atomic.Int32 -} - -func NewSimulatedMercuryServer() *SimulatedMercuryServer { - srv := &SimulatedMercuryServer{ - handler: notFoundHandler, - callCounter: atomic.Int32{}, - } - - srv.server = httptest.NewUnstartedServer(srv) - - return srv -} - -func (ms *SimulatedMercuryServer) URL() string { - return ms.server.URL -} - -func (ms *SimulatedMercuryServer) Username() string { - return "username1" -} - -func (ms *SimulatedMercuryServer) Password() string { - return "password1" -} - -func (ms *SimulatedMercuryServer) CallCount() int { - return int(ms.callCounter.Load()) -} - -func (ms *SimulatedMercuryServer) RegisterHandler(h http.HandlerFunc) { - ms.handler = h -} - -func (ms *SimulatedMercuryServer) Start() { - ms.server.Start() -} - -func (ms *SimulatedMercuryServer) Stop() { - ms.server.Close() -} - -func (ms *SimulatedMercuryServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ms.callCounter.Add(1) - ms.handler.ServeHTTP(w, r) -} diff --git a/pkg/automation/v21/mercury/v02/request.go b/pkg/automation/v21/mercury/v02/request.go deleted file mode 100644 index ec973344e3..0000000000 --- a/pkg/automation/v21/mercury/v02/request.go +++ /dev/null @@ -1,293 +0,0 @@ -package v02 - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "time" - - "github.com/avast/retry-go/v4" - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/prommetrics" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - mercuryPathV02 = "/client?" // only used to access mercury v0.2 server - retryDelay = 500 * time.Millisecond - totalAttempt = 3 - contentTypeHeader = "Content-Type" - authorizationHeader = "Authorization" - timestampHeader = "X-Authorization-Timestamp" - signatureHeader = "X-Authorization-Signature-SHA256" -) - -type MercuryV02Response struct { - ChainlinkBlob string `json:"chainlinkBlob"` -} - -type client struct { - services.StateMachine - mercuryConfig mercury.MercuryConfigProvider - httpClient mercury.HttpClient - threadCtrl threadcontrol.ThreadControl - lggr logger.Logger -} - -func NewClient(mercuryConfig mercury.MercuryConfigProvider, httpClient mercury.HttpClient, threadCtrl threadcontrol.ThreadControl, lggr logger.Logger) *client { - return &client{ - mercuryConfig: mercuryConfig, - httpClient: httpClient, - threadCtrl: threadCtrl, - lggr: lggr, - } -} - -func (c *client) DoRequest(ctx context.Context, streamsLookup *mercury.StreamsLookup, upkeepType automationTypes.UpkeepType, pluginRetryKey string) (encoding.PipelineExecutionState, [][]byte, encoding.ErrCode, bool, time.Duration, error) { - if len(streamsLookup.Feeds) == 0 { - return encoding.NoPipelineError, nil, encoding.ErrCodeStreamsBadRequest, false, 0 * time.Second, nil - } - resultLen := len(streamsLookup.Feeds) - ch := make(chan mercury.MercuryData, resultLen) - for i := range streamsLookup.Feeds { - // TODO (AUTO-7209): limit the number of concurrent requests - c.threadCtrl.GoCtx(ctx, func(ctx context.Context) { - c.singleFeedRequest(ctx, ch, i, streamsLookup) - }) - } - - ctx, cancel := context.WithTimeout(ctx, mercury.RequestTimeout) - defer cancel() - - state := encoding.NoPipelineError - var reqErr error - retryable := true - allFeedsPipelineSuccess := true - allFeedsReturnedValues := true - var errCode encoding.ErrCode - results := make([][]byte, len(streamsLookup.Feeds)) - // in v0.2, when combining results for multiple feed requests - // if any request resulted in pipeline execution error then use the last execution error as the state - // if no execution errors, then check if any feed returned an error code, if so use the last error code - for range resultLen { - select { - case <-ctx.Done(): - // Request Timed out, return timeout error - c.lggr.Errorf("at block %s upkeep %s, streams lookup v0.2 timed out", streamsLookup.Time.String(), streamsLookup.UpkeepId.String()) - return encoding.NoPipelineError, nil, encoding.ErrCodeStreamsTimeout, false, 0 * time.Second, nil - case m := <-ch: - if m.Error != nil { - state = m.State - reqErr = errors.Join(reqErr, m.Error) - retryable = retryable && m.Retryable - if m.ErrCode != encoding.ErrCodeNil { - // Some pipeline errors can get converted to error codes if retries are exhausted - errCode = m.ErrCode - } - allFeedsPipelineSuccess = false - continue - } - if m.ErrCode != encoding.ErrCodeNil { - errCode = m.ErrCode - allFeedsReturnedValues = false - continue - } - // Feed request didn't face a pipeline error and didn't return an error code - results[m.Index] = m.Bytes[0] - } - } - - if !allFeedsPipelineSuccess { - // Some feeds faced a pipeline error during execution - // If any error was non retryable then just return the state and error - if !retryable { - return state, nil, errCode, retryable, 0 * time.Second, reqErr - } - // If errors were retryable then calculate retry interval - retryInterval := mercury.CalculateStreamsRetryConfigFn(upkeepType, pluginRetryKey, c.mercuryConfig) - if retryInterval != mercury.RetryIntervalTimeout { - // Return the retyrable state with appropriate retry interval - return state, nil, errCode, retryable, retryInterval, reqErr - } - - // Now we have exhausted all our retries. We treat it as not a pipeline error - // and expose error code to the user - return encoding.NoPipelineError, nil, errCode, false, 0 * time.Second, nil - } - - // All feeds faced no pipeline error - // If any feed request returned an error code, return the error code with empty values, else return the values - if !allFeedsReturnedValues { - return encoding.NoPipelineError, nil, errCode, false, 0 * time.Second, nil - } - - // All success, return the results - return encoding.NoPipelineError, results, encoding.ErrCodeNil, false, 0 * time.Second, nil -} - -func (c *client) singleFeedRequest(ctx context.Context, ch chan<- mercury.MercuryData, index int, sl *mercury.StreamsLookup) { - var httpRequest *http.Request - var err error - - q := url.Values{ - sl.FeedParamKey: {sl.Feeds[index]}, - sl.TimeParamKey: {sl.Time.String()}, - } - mercuryURL := c.mercuryConfig.Credentials().LegacyURL - reqUrl := fmt.Sprintf("%s%s%s", mercuryURL, mercuryPathV02, q.Encode()) - c.lggr.Debugf("request URL for upkeep %s feed %s: %s", sl.UpkeepId.String(), sl.Feeds[index], reqUrl) - - httpRequest, err = http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, nil) - if err != nil { - // Not a pipeline error, a bad streams request - ch <- mercury.MercuryData{Index: index, ErrCode: encoding.ErrCodeStreamsBadRequest, Bytes: nil, State: encoding.NoPipelineError} - return - } - - ts := time.Now().UTC().UnixMilli() - signature := mercury.GenerateHMACFn(http.MethodGet, mercuryPathV02+q.Encode(), []byte{}, c.mercuryConfig.Credentials().Username, c.mercuryConfig.Credentials().Password, ts) - httpRequest.Header.Set(contentTypeHeader, "application/json") - httpRequest.Header.Set(authorizationHeader, c.mercuryConfig.Credentials().Username) - httpRequest.Header.Set(timestampHeader, strconv.FormatInt(ts, 10)) - httpRequest.Header.Set(signatureHeader, signature) - - // in the case of multiple retries here, use the last attempt's data - errCode := encoding.ErrCodeNil - state := encoding.NoPipelineError - retryable := false - sent := false - retryErr := retry.Do( - func() error { - prommetrics.AutomationStreamsRetries.WithLabelValues(prommetrics.StreamsVersion02).Inc() - var httpResponse *http.Response - var responseBody []byte - var blobBytes []byte - - retryable = false - if httpResponse, err = c.httpClient.Do(httpRequest); err != nil { - c.lggr.Errorf("at block %s upkeep %s GET request fails for feed %s: %v", sl.Time.String(), sl.UpkeepId.String(), sl.Feeds[index], err) - errCode = encoding.ErrCodeStreamsUnknownError - if ctx.Err() != nil { - errCode = encoding.ErrCodeStreamsTimeout - } - ch <- mercury.MercuryData{ - Index: index, - Bytes: nil, - ErrCode: errCode, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - defer httpResponse.Body.Close() - - if responseBody, err = io.ReadAll(httpResponse.Body); err != nil { - // Not a pipeline error, a bad streams response, send back error code - ch <- mercury.MercuryData{ - Index: index, - Bytes: nil, - ErrCode: encoding.ErrCodeStreamsBadResponse, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - - prommetrics.AutomationStreamsResponses.WithLabelValues(prommetrics.StreamsVersion02, strconv.Itoa(httpResponse.StatusCode)).Inc() - switch httpResponse.StatusCode { - case http.StatusNotFound, http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: - // Considered as pipeline error, but if retry attempts go over threshold, is changed upstream to ErrCode - c.lggr.Warnf("at block %s upkeep %s received status code %d for feed %s", sl.Time.String(), sl.UpkeepId.String(), httpResponse.StatusCode, sl.Feeds[index]) - state = encoding.MercuryFlakyFailure - retryable = true - errCode = encoding.HttpToStreamsErrCode(httpResponse.StatusCode) - return errors.New(strconv.FormatInt(int64(httpResponse.StatusCode), 10)) - case http.StatusOK: - // continue - default: - // Not considered as a pipeline error, a bad streams response with unknown status code. Send back to user as error code - c.lggr.Errorf("at block %s upkeep %s received unhandled status code %d for feed %s", sl.Time.String(), sl.UpkeepId.String(), httpResponse.StatusCode, sl.Feeds[index]) - ch <- mercury.MercuryData{ - Index: index, - Bytes: nil, - ErrCode: encoding.HttpToStreamsErrCode(httpResponse.StatusCode), - State: encoding.NoPipelineError, - } - sent = true - return nil - } - - c.lggr.Debugf("at block %s upkeep %s received status code %d from mercury v0.2 with BODY=%s", sl.Time.String(), sl.UpkeepId.String(), httpResponse.StatusCode, hexutil.Encode(responseBody)) - - var m MercuryV02Response - if err = json.Unmarshal(responseBody, &m); err != nil { - c.lggr.Warnf("at block %s upkeep %s failed to unmarshal body to MercuryV02Response for feed %s: %v", sl.Time.String(), sl.UpkeepId.String(), sl.Feeds[index], err) - ch <- mercury.MercuryData{ - Index: index, - Bytes: nil, - ErrCode: encoding.ErrCodeStreamsBadResponse, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - if blobBytes, err = hexutil.Decode(m.ChainlinkBlob); err != nil { - c.lggr.Warnf("at block %s upkeep %s failed to decode chainlinkBlob %s for feed %s: %v", sl.Time.String(), sl.UpkeepId.String(), m.ChainlinkBlob, sl.Feeds[index], err) - ch <- mercury.MercuryData{ - Index: index, - Bytes: nil, - ErrCode: encoding.ErrCodeStreamsBadResponse, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - ch <- mercury.MercuryData{ - Index: index, - Bytes: [][]byte{blobBytes}, - Retryable: false, - State: encoding.NoPipelineError, - } - sent = true - return nil - }, - // only retry when the error is 404 Not Found, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout - retry.RetryIf(func(err error) bool { - return err.Error() == strconv.Itoa(http.StatusNotFound) || err.Error() == strconv.Itoa(http.StatusInternalServerError) || err.Error() == strconv.Itoa(http.StatusBadGateway) || err.Error() == strconv.Itoa(http.StatusServiceUnavailable) || err.Error() == strconv.Itoa(http.StatusGatewayTimeout) - }), - retry.Context(ctx), - retry.Delay(retryDelay), - retry.Attempts(totalAttempt), - ) - - if !sent { - ch <- mercury.MercuryData{ - Index: index, - Bytes: nil, - ErrCode: errCode, - State: state, - Retryable: retryable, - Error: fmt.Errorf("failed to request feed for %s: %w", sl.Feeds[index], retryErr), - } - } -} - -func (c *client) Close() error { - return c.StopOnce("v02_request", func() error { - c.threadCtrl.Close() - return nil - }) -} diff --git a/pkg/automation/v21/mercury/v02/v02_request_test.go b/pkg/automation/v21/mercury/v02/v02_request_test.go deleted file mode 100644 index 8d8643c543..0000000000 --- a/pkg/automation/v21/mercury/v02/v02_request_test.go +++ /dev/null @@ -1,868 +0,0 @@ -package v02 - -import ( - "bytes" - "encoding/json" - "errors" - "io" - "math/big" - "net/http" - "strings" - "testing" - "time" - - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/patrickmn/go-cache" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - defaultPluginRetryExpiration = 30 * time.Minute - cleanupInterval = 5 * time.Minute -) - -type MockMercuryConfigProvider struct { - cache *cache.Cache - mock.Mock -} - -func NewMockMercuryConfigProvider() *MockMercuryConfigProvider { - return &MockMercuryConfigProvider{ - cache: cache.New(defaultPluginRetryExpiration, cleanupInterval), - } -} - -func (m *MockMercuryConfigProvider) Credentials() *types.MercuryCredentials { - mc := &types.MercuryCredentials{ - LegacyURL: "https://google.old.com", - URL: "https://google.com", - Username: "FakeClientID", - Password: "FakeClientKey", - } - return mc -} - -func (m *MockMercuryConfigProvider) IsUpkeepAllowed(s string) (any, bool) { - args := m.Called(s) - return args.Get(0), args.Bool(1) -} - -func (m *MockMercuryConfigProvider) SetUpkeepAllowed(s string, i any, d time.Duration) { - m.Called(s, i, d) -} - -func (m *MockMercuryConfigProvider) GetPluginRetry(s string) (any, bool) { - if value, found := m.cache.Get(s); found { - return value, true - } - - return nil, false -} - -func (m *MockMercuryConfigProvider) SetPluginRetry(s string, i any, d time.Duration) { - m.cache.Set(s, i, d) -} - -type MockHttpClient struct { - mock.Mock -} - -func (mock *MockHttpClient) Do(req *http.Request) (*http.Response, error) { - args := mock.Called(req) - return args.Get(0).(*http.Response), args.Error(1) -} - -// setups up a client object for tests. -func setupClient(t *testing.T) *client { - lggr := logger.Test(t) - mockHttpClient := new(MockHttpClient) - mercuryConfig := NewMockMercuryConfigProvider() - threadCtl := threadcontrol.NewThreadControl() - - client := NewClient( - mercuryConfig, - mockHttpClient, - threadCtl, - lggr, - ) - return client -} - -func TestV02_SingleFeedRequest(t *testing.T) { - t.Parallel() - upkeepId := big.NewInt(123456789) - tests := []struct { - name string - index int - lookup *mercury.StreamsLookup - blob string - responseBytes string - statusCode int - lastStatusCode int - retryNumber int - retryable bool - errorMessage string - streamsErrCode encoding.ErrCode - state encoding.PipelineExecutionState - }{ - { - name: "success - mercury responds in the first try", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dc00000012", - }, - { - name: "success - retry for 404", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dcbabbad", - retryNumber: 1, - statusCode: http.StatusNotFound, - lastStatusCode: http.StatusOK, - }, - { - name: "success - retry for 500", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dcbbabad", - retryNumber: 2, - statusCode: http.StatusInternalServerError, - lastStatusCode: http.StatusOK, - }, - { - name: "failure - returns retryable", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dc", - retryNumber: totalAttempt, - statusCode: http.StatusNotFound, - state: encoding.MercuryFlakyFailure, - retryable: true, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 404\n#2: 404\n#3: 404", - }, - { - name: "failure StatusGatewayTimeout - returns retryable", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dc", - retryNumber: totalAttempt, - statusCode: http.StatusGatewayTimeout, - state: encoding.MercuryFlakyFailure, - retryable: true, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 504\n#2: 504\n#3: 504", - }, - { - name: "failure StatusServiceUnavailable - returns retryable", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dc", - retryNumber: totalAttempt, - statusCode: http.StatusServiceUnavailable, - streamsErrCode: encoding.ErrCodeStreamsServiceUnavailable, - state: encoding.MercuryFlakyFailure, - retryable: true, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 503\n#2: 503\n#3: 503", - }, - { - name: "failure StatusBadGateway - returns retryable", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dc", - retryNumber: totalAttempt, - statusCode: http.StatusBadGateway, - streamsErrCode: encoding.ErrCodeStreamsBadGateway, - state: encoding.MercuryFlakyFailure, - retryable: true, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 502\n#2: 502\n#3: 502", - }, - { - name: "failure - returns retryable and then non-retryable", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dc", - retryNumber: 1, - statusCode: http.StatusNotFound, - lastStatusCode: http.StatusTooManyRequests, - streamsErrCode: encoding.ErrCodeStreamsUnknownError, - }, - { - name: "failure - returns not retryable", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xab2123dc", - statusCode: http.StatusConflict, - streamsErrCode: encoding.ErrCodeStreamsUnknownError, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: at block 123456 upkeep 123456789 received status code 409 for feed 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - }, - { - name: "failure invalid json response - returns err code", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - responseBytes: "invalid response", - streamsErrCode: encoding.ErrCodeStreamsBadResponse, - }, - { - name: "failure invalid blob - returns err code", - index: 0, - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - blob: "0xgz", // Invalid hex - streamsErrCode: encoding.ErrCodeStreamsBadResponse, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - c := setupClient(t) - defer c.Close() - hc := new(MockHttpClient) - - mr := MercuryV02Response{ChainlinkBlob: tt.blob} - b, err := json.Marshal(mr) - assert.NoError(t, err) - - if tt.responseBytes != "" { - b = []byte(tt.responseBytes) - } - - if tt.retryNumber == 0 { - if tt.errorMessage != "" { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } else { - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - } else if tt.retryNumber > 0 && tt.retryNumber < totalAttempt { - retryResp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(retryResp, nil).Times(tt.retryNumber) - - resp := &http.Response{ - StatusCode: tt.lastStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } else { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Times(tt.retryNumber) - } - c.httpClient = hc - - ch := make(chan mercury.MercuryData, 1) - c.singleFeedRequest(t.Context(), ch, tt.index, tt.lookup) - - m := <-ch - assert.Equal(t, tt.index, m.Index) - assert.Equal(t, tt.retryable, m.Retryable) - assert.Equal(t, tt.state, m.State) - if tt.streamsErrCode != encoding.ErrCodeNil { - assert.Equal(t, tt.streamsErrCode, m.ErrCode) - assert.Equal(t, [][]byte(nil), m.Bytes) - } else if tt.retryNumber >= totalAttempt || tt.errorMessage != "" { - assert.Equal(t, tt.errorMessage, m.Error.Error()) - assert.Equal(t, [][]byte(nil), m.Bytes) - } else { - blobBytes, err := hexutil.Decode(tt.blob) - assert.NoError(t, err) - assert.NoError(t, m.Error) - assert.Equal(t, [][]byte{blobBytes}, m.Bytes) - } - }) - } -} - -func TestV02_DoMercuryRequestV02(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - tests := []struct { - name string - lookup *mercury.StreamsLookup - mockHttpStatusCode int - mockChainlinkBlobs []string - pluginRetries int - upkeepType automationTypes.UpkeepType - expectedState encoding.PipelineExecutionState - expectedValues [][]byte - expectedErrCode encoding.ErrCode - expectedRetryable bool - expectedRetryInterval time.Duration - expectedError error - }{ - { - name: "success", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - mockHttpStatusCode: http.StatusOK, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte{{0, 6, 109, 252, 209, 237, 45, 149, 177, 140, 148, 141, 188, 91, 214, 76, 104, 122, 254, 147, 228, 202, 125, 102, 61, 222, 193, 76, 32, 9, 10, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 69, 84, 72, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 216, 211, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 207, 11, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 155, 61, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 206, 116, 217, 250, 37, 42, 137, 131, 151, 110, 171, 96, 13, 199, 89, 12, 119, 141, 4, 129, 52, 48, 132, 27, 198, 231, 101, 195, 76, 216, 26, 22, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 65, 43, 148, 229, 37, 202, 108, 237, 201, 245, 68, 253, 134, 247, 118, 6, 213, 47, 231, 49, 165, 208, 105, 219, 232, 54, 168, 191, 192, 251, 140, 145, 25, 99, 176, 174, 122, 20, 151, 31, 59, 70, 33, 191, 251, 128, 46, 240, 96, 83, 146, 185, 166, 200, 156, 127, 171, 29, 248, 99, 58, 90, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 69, 0, 194, 245, 33, 248, 63, 186, 94, 252, 43, 243, 239, 250, 174, 221, 228, 61, 10, 74, 223, 247, 133, 193, 33, 59, 113, 42, 58, 237, 13, 129, 87, 100, 42, 132, 50, 77, 176, 207, 150, 149, 235, 210, 119, 8, 212, 96, 142, 176, 51, 126, 13, 216, 123, 14, 67, 240, 250, 112, 199, 0, 217, 17}}, - expectedRetryable: false, - expectedError: nil, - }, - { - name: "failure - retryable but out of retries for conditional", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - upkeepType: automationTypes.ConditionTrigger, - mockHttpStatusCode: http.StatusInternalServerError, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte(nil), - expectedRetryable: false, - pluginRetries: 0, - expectedRetryInterval: 0 * time.Second, - expectedErrCode: encoding.ErrCodeStreamsInternalError, - expectedState: encoding.NoPipelineError, - }, - { - name: "failure - retryable but out of retries for conditional 404 error code", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - upkeepType: automationTypes.ConditionTrigger, - mockHttpStatusCode: http.StatusNotFound, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte(nil), - expectedRetryable: false, - pluginRetries: 0, - expectedRetryInterval: 0 * time.Second, - expectedErrCode: encoding.ErrCodeStreamsNotFound, - expectedState: encoding.NoPipelineError, - }, - { - name: "failure - retryable and interval is 1s", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - upkeepType: automationTypes.LogTrigger, - pluginRetries: 1, - mockHttpStatusCode: http.StatusInternalServerError, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte(nil), - expectedRetryable: true, - expectedRetryInterval: 1 * time.Second, - expectedErrCode: encoding.ErrCodeStreamsInternalError, - expectedState: encoding.MercuryFlakyFailure, - expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 500\n#2: 500\n#3: 500"), - }, - { - name: "failure - retryable and interval is 5s", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - upkeepType: automationTypes.LogTrigger, - pluginRetries: 5, - mockHttpStatusCode: http.StatusInternalServerError, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte(nil), - expectedRetryable: true, - expectedRetryInterval: 5 * time.Second, - expectedErrCode: encoding.ErrCodeStreamsInternalError, - expectedState: encoding.MercuryFlakyFailure, - expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 500\n#2: 500\n#3: 500"), - }, - { - name: "failure - not retryable because there are many plugin retries already", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - upkeepType: automationTypes.LogTrigger, - pluginRetries: 6, - mockHttpStatusCode: http.StatusBadGateway, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte(nil), - expectedRetryInterval: 0 * time.Second, - expectedErrCode: encoding.ErrCodeStreamsBadGateway, - expectedRetryable: false, - expectedState: encoding.NoPipelineError, - }, - { - name: "failure - not retryable with an error code", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - mockHttpStatusCode: http.StatusTooManyRequests, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte(nil), - expectedErrCode: encoding.ErrCodeStreamsUnknownError, - expectedRetryable: false, - expectedState: encoding.NoPipelineError, - }, - { - name: "failure - no feeds", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - expectedValues: [][]byte(nil), - expectedErrCode: encoding.ErrCodeStreamsBadRequest, - }, - { - name: "failure - invalid revert data", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - expectedValues: [][]byte(nil), - expectedErrCode: encoding.ErrCodeStreamsBadRequest, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - c := setupClient(t) - defer c.Close() - if tt.pluginRetries != 0 { - c.mercuryConfig.SetPluginRetry(pluginRetryKey, tt.pluginRetries, cache.DefaultExpiration) - } - hc := new(MockHttpClient) - - for _, blob := range tt.mockChainlinkBlobs { - mr := MercuryV02Response{ChainlinkBlob: blob} - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: tt.mockHttpStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - if tt.expectedErrCode != encoding.ErrCodeNil { - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - } else { - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - } - c.httpClient = hc - - state, values, errCode, retryable, retryInterval, reqErr := c.DoRequest(t.Context(), tt.lookup, tt.upkeepType, pluginRetryKey) - assert.Equal(t, tt.expectedValues, values) - assert.Equal(t, tt.expectedRetryable, retryable) - if retryable { - newRetries, _ := c.mercuryConfig.GetPluginRetry(pluginRetryKey) - assert.Equal(t, tt.pluginRetries+1, newRetries.(int)) - } - assert.Equal(t, tt.expectedRetryInterval, retryInterval) - assert.Equal(t, tt.expectedErrCode, errCode) - assert.Equal(t, tt.expectedState, state) - - if tt.expectedError != nil { - assert.True(t, strings.HasPrefix(reqErr.Error(), "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000")) - } - }) - } -} - -func TestV02_DoMercuryRequestV02_MultipleFeedsSuccess(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - for i := 0; i <= 3; i++ { - mr := MercuryV02Response{ChainlinkBlob: "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"} - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - state, _, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.ConditionTrigger, pluginRetryKey) - assert.False(t, retryable) - assert.Equal(t, 0*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeNil, errCode) - assert.Equal(t, encoding.NoPipelineError, state) -} - -func TestV02_DoMercuryRequestV02_Timeout(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - mr := MercuryV02Response{ChainlinkBlob: "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"} - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() // First request sends result normally - - resp2 := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte{})), - } - // Second request has timeout - serverTimeout := 15 * time.Second // Server has delay of 15s, higher than mercury.RequestTimeout = 10s - hc.On("Do", mock.Anything).Run(func(args mock.Arguments) { - time.Sleep(serverTimeout) - }).Return(resp2, nil).Once() - - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - start := time.Now() - state, values, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.ConditionTrigger, pluginRetryKey) - elapsed := time.Since(start) - assert.Less(t, elapsed, serverTimeout) - assert.False(t, retryable) - assert.Equal(t, 0*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeStreamsTimeout, errCode) - assert.Equal(t, encoding.NoPipelineError, state) - assert.Equal(t, [][]byte(nil), values) -} - -func TestV02_DoMercuryRequestV02_OneFeedSuccessOneFeedPipelineError(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - // First request success - mr := MercuryV02Response{ChainlinkBlob: "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"} - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - // Second request returns MercuryFlakyError - resp = &http.Response{ - StatusCode: http.StatusServiceUnavailable, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - state, values, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.LogTrigger, pluginRetryKey) - assert.True(t, retryable) - assert.Equal(t, 1*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeStreamsServiceUnavailable, errCode) - assert.Equal(t, encoding.MercuryFlakyFailure, state) - assert.Equal(t, [][]byte(nil), values) -} - -func TestV02_DoMercuryRequestV02_OneFeedSuccessOneFeedErrCode(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - // First request success - mr := MercuryV02Response{ChainlinkBlob: "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"} - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - // Second request returns invalid response - resp = &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte{})), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - state, values, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.LogTrigger, pluginRetryKey) - assert.Equal(t, [][]byte(nil), values) - assert.False(t, retryable) - assert.Equal(t, 0*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeStreamsBadResponse, errCode) - assert.Equal(t, encoding.NoPipelineError, state) -} - -func TestV02_DoMercuryRequestV02_OneFeedSuccessOneFeedPipelineErrorConvertedError(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - // First request success - mr := MercuryV02Response{ChainlinkBlob: "0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"} - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - // Second request returns MercuryFlakyError - resp = &http.Response{ - StatusCode: http.StatusGatewayTimeout, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - state, values, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.ConditionTrigger, pluginRetryKey) - assert.False(t, retryable) - assert.Equal(t, 0*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeStreamsStatusGatewayTimeout, errCode) - assert.Equal(t, encoding.NoPipelineError, state) - assert.Equal(t, [][]byte(nil), values) -} diff --git a/pkg/automation/v21/mercury/v03/request.go b/pkg/automation/v21/mercury/v03/request.go deleted file mode 100644 index 1efcd32742..0000000000 --- a/pkg/automation/v21/mercury/v03/request.go +++ /dev/null @@ -1,305 +0,0 @@ -package v03 - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - "strings" - "time" - - "github.com/avast/retry-go/v4" - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/prommetrics" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - mercuryBatchPathV03 = "/api/v1/reports/bulk?" // only used to access mercury v0.3 server - mercuryBatchPathV03BlockNumber = "/api/v1gmx/reports/bulk?" // only used to access mercury v0.3 server with blockNumber - retryDelay = 500 * time.Millisecond - totalAttempt = 3 - contentTypeHeader = "Content-Type" - authorizationHeader = "Authorization" - timestampHeader = "X-Authorization-Timestamp" - signatureHeader = "X-Authorization-Signature-SHA256" - upkeepIDHeader = "X-Authorization-Upkeep-Id" -) - -type MercuryV03Response struct { - Reports []MercuryV03Report `json:"reports"` -} - -type MercuryV03Report struct { - FeedID string `json:"feedID"` // feed id in hex encoded - ValidFromTimestamp uint32 `json:"validFromTimestamp"` - ObservationsTimestamp uint32 `json:"observationsTimestamp"` - FullReport string `json:"fullReport"` // the actual hex encoded mercury report of this feed, can be sent to verifier -} - -type client struct { - services.StateMachine - mercuryConfig mercury.MercuryConfigProvider - httpClient mercury.HttpClient - threadCtrl threadcontrol.ThreadControl - lggr logger.Logger -} - -func NewClient(mercuryConfig mercury.MercuryConfigProvider, httpClient mercury.HttpClient, threadCtrl threadcontrol.ThreadControl, lggr logger.Logger) *client { - return &client{ - mercuryConfig: mercuryConfig, - httpClient: httpClient, - threadCtrl: threadCtrl, - lggr: lggr, - } -} - -func (c *client) DoRequest(ctx context.Context, streamsLookup *mercury.StreamsLookup, upkeepType automationTypes.UpkeepType, pluginRetryKey string) (encoding.PipelineExecutionState, [][]byte, encoding.ErrCode, bool, time.Duration, error) { - if len(streamsLookup.Feeds) == 0 { - return encoding.NoPipelineError, nil, encoding.ErrCodeStreamsBadRequest, false, 0 * time.Second, nil - } - resultLen := 1 // Only 1 multi-feed request is made for all feeds - ch := make(chan mercury.MercuryData, resultLen) - c.threadCtrl.GoCtx(ctx, func(ctx context.Context) { - c.multiFeedsRequest(ctx, ch, streamsLookup) - }) - - ctx, cancel := context.WithTimeout(ctx, mercury.RequestTimeout) - defer cancel() - select { - case <-ctx.Done(): - // Request Timed out, return timeout error - c.lggr.Errorf("at timestamp %s upkeep %s, streams lookup v0.3 timed out", streamsLookup.Time.String(), streamsLookup.UpkeepId.String()) - return encoding.NoPipelineError, nil, encoding.ErrCodeStreamsTimeout, false, 0 * time.Second, nil - case m := <-ch: - if m.Error != nil { - // There was a pipeline error during execution - // If error was non retryable then just return the state and error - if !m.Retryable { - return m.State, nil, m.ErrCode, m.Retryable, 0 * time.Second, m.Error - } - // If errors were retryable then calculate retry interval - retryInterval := mercury.CalculateStreamsRetryConfigFn(upkeepType, pluginRetryKey, c.mercuryConfig) - if retryInterval != mercury.RetryIntervalTimeout { - // Return the retyrable state with appropriate retry interval - return m.State, nil, m.ErrCode, m.Retryable, retryInterval, m.Error - } - - // Now we have exhausted all our retries. We treat it as not a pipeline error - // and expose error code to the user - return encoding.NoPipelineError, nil, m.ErrCode, false, 0 * time.Second, nil - } - - // No pipeline error, return bytes and error code out of which one should be null - return encoding.NoPipelineError, m.Bytes, m.ErrCode, false, 0 * time.Second, nil - } -} - -func (c *client) multiFeedsRequest(ctx context.Context, ch chan<- mercury.MercuryData, sl *mercury.StreamsLookup) { - // this won't work bc q.Encode() will encode commas as '%2C' but the server is strictly expecting a comma separated list - // q := url.Values{ - // feedIDs: {strings.Join(sl.Feeds, ",")}, - // timestamp: {sl.Time.String()}, - //} - - params := fmt.Sprintf("%s=%s&%s=%s", mercury.FeedIDs, strings.Join(sl.Feeds, ","), mercury.Timestamp, sl.Time.String()) - batchPathV03 := mercuryBatchPathV03 - if sl.IsMercuryV03UsingBlockNumber() { - batchPathV03 = mercuryBatchPathV03BlockNumber - } - reqUrl := fmt.Sprintf("%s%s%s", c.mercuryConfig.Credentials().URL, batchPathV03, params) - - c.lggr.Debugf("request URL for upkeep %s userId %s: %s", sl.UpkeepId.String(), c.mercuryConfig.Credentials().Username, reqUrl) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, nil) - if err != nil { - // Not a pipeline error, a bad streams request - ch <- mercury.MercuryData{Index: 0, ErrCode: encoding.ErrCodeStreamsBadRequest, State: encoding.NoPipelineError} - return - } - - ts := time.Now().UTC().UnixMilli() - signature := mercury.GenerateHMACFn(http.MethodGet, mercuryBatchPathV03+params, []byte{}, c.mercuryConfig.Credentials().Username, c.mercuryConfig.Credentials().Password, ts) - - req.Header.Set(contentTypeHeader, "application/json") - // username here is often referred to as user id - req.Header.Set(authorizationHeader, c.mercuryConfig.Credentials().Username) - req.Header.Set(timestampHeader, strconv.FormatInt(ts, 10)) - req.Header.Set(signatureHeader, signature) - // mercury will inspect authorization headers above to make sure this user (in automation's context, this node) is eligible to access mercury - // and if it has an automation role. it will then look at this upkeep id to check if it has access to all the requested feeds. - req.Header.Set(upkeepIDHeader, sl.UpkeepId.String()) - - // in the case of multiple retries here, use the last attempt's data - state := encoding.NoPipelineError - errCode := encoding.ErrCodeNil - retryable := false - sent := false - retryCtx, cancel := context.WithCancel(ctx) - defer cancel() - retryErr := retry.Do( - func() error { - prommetrics.AutomationStreamsRetries.WithLabelValues(prommetrics.StreamsVersion03).Inc() - retryable = false - resp, err := c.httpClient.Do(req) - if err != nil { - c.lggr.Errorf("at timestamp %s upkeep %s GET request fails from mercury v0.3: %v", sl.Time.String(), sl.UpkeepId.String(), err) - errCode = encoding.ErrCodeStreamsUnknownError - if ctx.Err() != nil { - errCode = encoding.ErrCodeStreamsTimeout - } - ch <- mercury.MercuryData{ - Index: 0, - ErrCode: errCode, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - // Not a pipeline error, a bad streams response, send back error code - ch <- mercury.MercuryData{ - Index: 0, - ErrCode: encoding.ErrCodeStreamsBadResponse, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - - c.lggr.Infof("at timestamp %s upkeep %s received status code %d from mercury v0.3", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) - prommetrics.AutomationStreamsResponses.WithLabelValues(prommetrics.StreamsVersion03, strconv.Itoa(resp.StatusCode)).Inc() - switch resp.StatusCode { - case http.StatusUnauthorized: - c.lggr.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3, most likely this is caused by unauthorized upkeep", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) - ch <- mercury.MercuryData{ - Index: 0, - ErrCode: encoding.HttpToStreamsErrCode(resp.StatusCode), - State: encoding.NoPipelineError, - } - sent = true - return nil - case http.StatusBadRequest: - c.lggr.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3, most likely this is caused by invalid format of timestamp", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) - ch <- mercury.MercuryData{ - Index: 0, - ErrCode: encoding.HttpToStreamsErrCode(resp.StatusCode), - State: encoding.NoPipelineError, - } - sent = true - return nil - case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: - retryable = true - state = encoding.MercuryFlakyFailure - errCode = encoding.HttpToStreamsErrCode(resp.StatusCode) - return fmt.Errorf("%d", resp.StatusCode) - case http.StatusPartialContent: - // TODO (AUTO-5044): handle response code 206 entirely with errors field parsing - c.lggr.Warnf("at timestamp %s upkeep %s requested [%s] feeds but mercury v0.3 server returned 206 status, treating it as 404 and retrying", sl.Time.String(), sl.UpkeepId.String(), sl.Feeds) - retryable = true - state = encoding.MercuryFlakyFailure - errCode = encoding.HttpToStreamsErrCode(resp.StatusCode) - return fmt.Errorf("%d", http.StatusPartialContent) - case http.StatusOK: - // continue - default: - // Not considered as a pipeline error, a bad streams response with unknown status code. Send back to user as error code - c.lggr.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) - ch <- mercury.MercuryData{ - Index: 0, - ErrCode: encoding.HttpToStreamsErrCode(resp.StatusCode), - State: encoding.NoPipelineError, - } - sent = true - return nil - } - c.lggr.Debugf("at block %s upkeep %s received status code %d from mercury v0.3 with BODY=%s", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode, hexutil.Encode(body)) - - var response MercuryV03Response - if err := json.Unmarshal(body, &response); err != nil { - c.lggr.Warnf("at timestamp %s upkeep %s failed to unmarshal body to MercuryV03Response from mercury v0.3: %v", sl.Time.String(), sl.UpkeepId.String(), err) - ch <- mercury.MercuryData{ - Index: 0, - ErrCode: encoding.ErrCodeStreamsBadResponse, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - - // in v0.3, if some feeds are not available, the server will only return available feeds, but we need to make sure ALL feeds are retrieved before calling user contract - // hence, retry in this case. retry will help when we send a very new timestamp and reports are not yet generated - if len(response.Reports) != len(sl.Feeds) { - var receivedFeeds []string - for _, f := range response.Reports { - receivedFeeds = append(receivedFeeds, f.FeedID) - } - c.lggr.Warnf("at timestamp %s upkeep %s mercury v0.3 server returned less reports [%s] while we requested [%s] feeds, retrying", sl.Time.String(), sl.UpkeepId.String(), receivedFeeds, sl.Feeds) - retryable = true - state = encoding.MercuryFlakyFailure - errCode = encoding.HttpToStreamsErrCode(http.StatusPartialContent) - return fmt.Errorf("%d", http.StatusPartialContent) - } - var reportBytes [][]byte - for _, rsp := range response.Reports { - b, err := hexutil.Decode(rsp.FullReport) - if err != nil { - c.lggr.Warnf("at timestamp %s upkeep %s failed to decode reportBlob %s: %v", sl.Time.String(), sl.UpkeepId.String(), rsp.FullReport, err) - ch <- mercury.MercuryData{ - Index: 0, - ErrCode: encoding.ErrCodeStreamsBadResponse, - State: encoding.NoPipelineError, - } - sent = true - return nil - } - reportBytes = append(reportBytes, b) - } - ch <- mercury.MercuryData{ - Index: 0, - Bytes: reportBytes, - State: encoding.NoPipelineError, - } - sent = true - return nil - }, - // only retry when the error is 206 Partial Content, 404 Not Found, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout - retry.RetryIf(func(err error) bool { - return err.Error() == strconv.Itoa(http.StatusPartialContent) || err.Error() == strconv.Itoa(http.StatusNotFound) || err.Error() == strconv.Itoa(http.StatusInternalServerError) || err.Error() == strconv.Itoa(http.StatusBadGateway) || err.Error() == strconv.Itoa(http.StatusServiceUnavailable) || err.Error() == strconv.Itoa(http.StatusGatewayTimeout) - }), - retry.Context(retryCtx), - retry.Delay(retryDelay), - retry.Attempts(totalAttempt), - ) - - if !sent { - ch <- mercury.MercuryData{ - Index: 0, - Bytes: nil, - Retryable: retryable, - Error: retryErr, - ErrCode: errCode, - State: state, - } - } -} - -func (c *client) Close() error { - return c.StopOnce("v03_request", func() error { - c.threadCtrl.Close() - return nil - }) -} diff --git a/pkg/automation/v21/mercury/v03/v03_request_test.go b/pkg/automation/v21/mercury/v03/v03_request_test.go deleted file mode 100644 index 99fe9e390a..0000000000 --- a/pkg/automation/v21/mercury/v03/v03_request_test.go +++ /dev/null @@ -1,899 +0,0 @@ -package v03 - -import ( - "bytes" - "encoding/json" - "io" - "math/big" - "net/http" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/patrickmn/go-cache" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mocks" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - defaultPluginRetryExpiration = 30 * time.Minute - cleanupInterval = 5 * time.Minute -) - -type MockMercuryConfigProvider struct { - cache *cache.Cache - mock.Mock -} - -func NewMockMercuryConfigProvider() *MockMercuryConfigProvider { - return &MockMercuryConfigProvider{ - cache: cache.New(defaultPluginRetryExpiration, cleanupInterval), - } -} - -func (m *MockMercuryConfigProvider) Credentials() *types.MercuryCredentials { - mc := &types.MercuryCredentials{ - LegacyURL: "https://google.old.com", - URL: "https://google.com", - Username: "FakeClientID", - Password: "FakeClientKey", - } - return mc -} - -func (m *MockMercuryConfigProvider) IsUpkeepAllowed(s string) (any, bool) { - args := m.Called(s) - return args.Get(0), args.Bool(1) -} - -func (m *MockMercuryConfigProvider) SetUpkeepAllowed(s string, i any, d time.Duration) { - m.Called(s, i, d) -} - -func (m *MockMercuryConfigProvider) GetPluginRetry(s string) (any, bool) { - if value, found := m.cache.Get(s); found { - return value, true - } - - return nil, false -} - -func (m *MockMercuryConfigProvider) SetPluginRetry(s string, i any, d time.Duration) { - m.cache.Set(s, i, d) -} - -type MockHttpClient struct { - mock.Mock -} - -func (mock *MockHttpClient) Do(req *http.Request) (*http.Response, error) { - args := mock.Called(req) - return args.Get(0).(*http.Response), args.Error(1) -} - -// setups up a client object for tests. -func setupClient(t *testing.T) *client { - lggr := logger.Test(t) - mockHttpClient := new(MockHttpClient) - mercuryConfig := NewMockMercuryConfigProvider() - threadCtl := threadcontrol.NewThreadControl() - - client := NewClient( - mercuryConfig, - mockHttpClient, - threadCtl, - lggr, - ) - return client -} - -func TestV03_DoMercuryRequestV03(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - - tests := []struct { - name string - lookup *mercury.StreamsLookup - mockHttpStatusCode int - mockChainlinkBlobs []string - pluginRetryKey string - expectedValues [][]byte - expectedRetryable bool - expectedRetryInterval time.Duration - expectedErrCode encoding.ErrCode - expectedError error - state encoding.PipelineExecutionState - }{ - { - name: "success v0.3", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - }, - mockHttpStatusCode: http.StatusOK, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte{{0, 6, 109, 252, 209, 237, 45, 149, 177, 140, 148, 141, 188, 91, 214, 76, 104, 122, 254, 147, 228, 202, 125, 102, 61, 222, 193, 76, 32, 9, 10, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 69, 84, 72, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 216, 211, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 207, 11, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 155, 61, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 206, 116, 217, 250, 37, 42, 137, 131, 151, 110, 171, 96, 13, 199, 89, 12, 119, 141, 4, 129, 52, 48, 132, 27, 198, 231, 101, 195, 76, 216, 26, 22, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 65, 43, 148, 229, 37, 202, 108, 237, 201, 245, 68, 253, 134, 247, 118, 6, 213, 47, 231, 49, 165, 208, 105, 219, 232, 54, 168, 191, 192, 251, 140, 145, 25, 99, 176, 174, 122, 20, 151, 31, 59, 70, 33, 191, 251, 128, 46, 240, 96, 83, 146, 185, 166, 200, 156, 127, 171, 29, 248, 99, 58, 90, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 69, 0, 194, 245, 33, 248, 63, 186, 94, 252, 43, 243, 239, 250, 174, 221, 228, 61, 10, 74, 223, 247, 133, 193, 33, 59, 113, 42, 58, 237, 13, 129, 87, 100, 42, 132, 50, 77, 176, 207, 150, 149, 235, 210, 119, 8, 212, 96, 142, 176, 51, 126, 13, 216, 123, 14, 67, 240, 250, 112, 199, 0, 217, 17}}, - expectedRetryable: false, - expectedError: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := setupClient(t) - defer c.Close() - hc := mocks.NewHttpClient(t) - - mr := MercuryV03Response{} - for i, blob := range tt.mockChainlinkBlobs { - r := MercuryV03Report{ - FeedID: tt.lookup.Feeds[i], - ValidFromTimestamp: 0, - ObservationsTimestamp: 0, - FullReport: blob, - } - mr.Reports = append(mr.Reports, r) - } - - b, err := json.Marshal(mr) - assert.NoError(t, err) - resp := &http.Response{ - StatusCode: tt.mockHttpStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - if tt.expectedError != nil && tt.expectedRetryable { - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - } else { - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - c.httpClient = hc - - state, values, errCode, retryable, retryInterval, reqErr := c.DoRequest(t.Context(), tt.lookup, automationTypes.ConditionTrigger, tt.pluginRetryKey) - - assert.Equal(t, tt.expectedValues, values) - assert.Equal(t, tt.expectedRetryable, retryable) - assert.Equal(t, tt.expectedRetryInterval, retryInterval) - assert.Equal(t, tt.state, state) - assert.Equal(t, tt.expectedErrCode, errCode) - if tt.expectedError != nil { - assert.Equal(t, tt.expectedError.Error(), reqErr.Error()) - } - }) - } -} - -func TestV03_DoMercuryRequestV03_MultipleFeedsSuccess(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - for i := 0; i <= 3; i++ { - mr := MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - } - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - state, _, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.ConditionTrigger, pluginRetryKey) - assert.False(t, retryable) - assert.Equal(t, 0*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeNil, errCode) - assert.Equal(t, encoding.NoPipelineError, state) -} - -func TestV03_DoMercuryRequestV03_Timeout(t *testing.T) { - t.Skip("TODO: MERC-5965") - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - mr := MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - } - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - serverTimeout := 15 * time.Second // Server has delay of 15s, higher than mercury.RequestTimeout = 10s - hc.On("Do", mock.Anything).Run(func(args mock.Arguments) { - time.Sleep(serverTimeout) - }).Return(resp, nil).Once() - - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - start := time.Now() - state, values, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.ConditionTrigger, pluginRetryKey) - elapsed := time.Since(start) - assert.Less(t, elapsed, serverTimeout) - assert.False(t, retryable) - assert.Equal(t, 0*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeStreamsTimeout, errCode) - assert.Equal(t, encoding.NoPipelineError, state) - assert.Equal(t, [][]byte(nil), values) -} - -func TestV03_DoMercuryRequestV03_OneFeedSuccessOneFeedPipelineError(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - // First request success - mr := MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - } - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - // Second request returns MercuryFlakyError - resp = &http.Response{ - StatusCode: http.StatusBadGateway, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - state, values, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.LogTrigger, pluginRetryKey) - assert.True(t, retryable) - assert.Equal(t, 1*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeStreamsBadGateway, errCode) - assert.Equal(t, encoding.MercuryFlakyFailure, state) - assert.Equal(t, [][]byte(nil), values) -} - -func TestV03_DoMercuryRequestV03_OneFeedSuccessOneFeedErrCode(t *testing.T) { - t.Parallel() - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - pluginRetryKey := "88786950015966611018675766524283132478093844178961698330929478019253453382042|34" - - c := setupClient(t) - defer c.Close() - - c.mercuryConfig.SetPluginRetry(pluginRetryKey, 0, cache.DefaultExpiration) - hc := new(MockHttpClient) - - // First request success - mr := MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - } - b, err := json.Marshal(mr) - assert.NoError(t, err) - - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - - // Second request returns invalid response - invalidResponse := MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "random", // invalid hex - }, - }, - } - b, err = json.Marshal(invalidResponse) - assert.NoError(t, err) - - resp = &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - c.httpClient = hc - - lookup := &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - UpkeepId: upkeepId, - } - - state, values, errCode, retryable, retryInterval, _ := c.DoRequest(t.Context(), lookup, automationTypes.LogTrigger, pluginRetryKey) - assert.Equal(t, [][]byte(nil), values) - assert.False(t, retryable) - assert.Equal(t, 0*time.Second, retryInterval) - assert.Equal(t, encoding.ErrCodeStreamsBadResponse, errCode) - assert.Equal(t, encoding.NoPipelineError, state) -} - -func TestV03_MultiFeedRequest(t *testing.T) { - t.Parallel() - upkeepId := big.NewInt(123456789) - tests := []struct { - name string - lookup *mercury.StreamsLookup - statusCode int - lastStatusCode int - pluginRetries int - pluginRetryKey string - retryNumber int - retryable bool - errorMessage string - firstResponse *MercuryV03Response - response *MercuryV03Response - streamsErrCode encoding.ErrCode - state encoding.PipelineExecutionState - }{ - { - name: "success - mercury responds in the first try", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000016", - }, - }, - }, - statusCode: http.StatusOK, - }, - { - name: "success - mercury responds in the first try with blocknumber", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000016", - }, - }, - }, - statusCode: http.StatusOK, - }, - { - name: "success - retry 206", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - firstResponse: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000019", - }, - }, - }, - retryNumber: 1, - statusCode: http.StatusPartialContent, - lastStatusCode: http.StatusOK, - }, - { - name: "success - retry for 500", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - retryNumber: 2, - statusCode: http.StatusInternalServerError, - lastStatusCode: http.StatusOK, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000019", - }, - }, - }, - }, - { - name: "failure - invalid response and fail to decode reportBlob", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "qerwiu", // invalid hex blob - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000016", - }, - }, - }, - statusCode: http.StatusOK, - retryable: false, - errorMessage: "All attempts fail:\n#1: hex string without 0x prefix", - streamsErrCode: encoding.ErrCodeStreamsBadResponse, - state: encoding.NoPipelineError, - }, - { - name: "failure - returns retryable with 1s plugin retry interval", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - retryNumber: totalAttempt, - statusCode: http.StatusInternalServerError, - retryable: true, - errorMessage: "All attempts fail:\n#1: 500\n#2: 500\n#3: 500", - }, - { - name: "failure - returns retryable with 5s plugin retry interval", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - pluginRetries: 6, - retryNumber: totalAttempt, - statusCode: http.StatusInternalServerError, - retryable: true, - errorMessage: "All attempts fail:\n#1: 500\n#2: 500\n#3: 500", - }, - { - name: "failure - returns retryable and then non-retryable", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - retryNumber: 1, - statusCode: http.StatusInternalServerError, - lastStatusCode: http.StatusUnauthorized, - streamsErrCode: encoding.ErrCodeStreamsUnauthorized, - state: encoding.NoPipelineError, - }, - { - name: "failure - returns status code 422 not retryable", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - statusCode: http.StatusUnprocessableEntity, - streamsErrCode: encoding.ErrCodeStreamsUnknownError, - state: encoding.NoPipelineError, - }, - { - name: "failure - StatusGatewayTimeout - returns retryable", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - retryNumber: totalAttempt, - statusCode: http.StatusGatewayTimeout, - state: encoding.MercuryFlakyFailure, - retryable: true, - streamsErrCode: encoding.ErrCodeStreamsStatusGatewayTimeout, - errorMessage: "All attempts fail:\n#1: 504\n#2: 504\n#3: 504", - }, - { - name: "failure - StatusServiceUnavailable - returns retryable", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - retryNumber: totalAttempt, - statusCode: http.StatusServiceUnavailable, - state: encoding.MercuryFlakyFailure, - retryable: true, - streamsErrCode: encoding.ErrCodeStreamsServiceUnavailable, - errorMessage: "All attempts fail:\n#1: 503\n#2: 503\n#3: 503", - }, - { - name: "failure - StatusBadGateway - returns retryable", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - retryNumber: totalAttempt, - statusCode: http.StatusBadGateway, - streamsErrCode: encoding.ErrCodeStreamsBadGateway, - state: encoding.MercuryFlakyFailure, - retryable: true, - errorMessage: "All attempts fail:\n#1: 502\n#2: 502\n#3: 502", - }, - - { - name: "failure - partial content three times with status ok", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - }, - statusCode: http.StatusOK, - retryNumber: totalAttempt, - retryable: true, - streamsErrCode: encoding.ErrCodeStreamsPartialContent, - errorMessage: "All attempts fail:\n#1: 404\n#2: 404\n#3: 404", - state: encoding.MercuryFlakyFailure, - }, - { - name: "failure - partial content three times with status partial content", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.BlockNumber, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - }, - statusCode: http.StatusPartialContent, - retryNumber: totalAttempt, - retryable: true, - errorMessage: "All attempts fail:\n#1: 206\n#2: 206\n#3: 206", - state: encoding.MercuryFlakyFailure, - }, - { - name: "success - retry when reports length does not match feeds length", - lookup: &mercury.StreamsLookup{ - StreamsLookupError: &mercury.StreamsLookupError{ - FeedParamKey: mercury.FeedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: mercury.Timestamp, - Time: big.NewInt(123456), - }, - UpkeepId: upkeepId, - }, - firstResponse: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000019", - }, - }, - }, - retryNumber: 1, - statusCode: http.StatusOK, - lastStatusCode: http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - c := setupClient(t) - defer c.Close() - if tt.pluginRetries != 0 { - c.mercuryConfig.SetPluginRetry(tt.pluginRetryKey, tt.pluginRetries, cache.DefaultExpiration) - } - - hc := new(MockHttpClient) - b, err := json.Marshal(tt.response) - assert.NoError(t, err) - - if tt.retryNumber == 0 { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } else if tt.retryNumber < totalAttempt { - if tt.firstResponse != nil && tt.response != nil { - b0, err := json.Marshal(tt.firstResponse) - assert.NoError(t, err) - resp0 := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b0)), - } - b1, err := json.Marshal(tt.response) - assert.NoError(t, err) - resp1 := &http.Response{ - StatusCode: tt.lastStatusCode, - Body: io.NopCloser(bytes.NewReader(b1)), - } - hc.On("Do", mock.Anything).Return(resp0, nil).Once().On("Do", mock.Anything).Return(resp1, nil).Once() - } else { - retryResp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(retryResp, nil).Times(tt.retryNumber) - - resp := &http.Response{ - StatusCode: tt.lastStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - } else { - for i := 1; i <= tt.retryNumber; i++ { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - } - c.httpClient = hc - - ch := make(chan mercury.MercuryData, 1) - c.multiFeedsRequest(t.Context(), ch, tt.lookup) - - m := <-ch - assert.Equal(t, 0, m.Index) - assert.Equal(t, tt.retryable, m.Retryable) - if tt.streamsErrCode != encoding.ErrCodeNil { - assert.Equal(t, tt.streamsErrCode, m.ErrCode) - assert.Equal(t, tt.state, m.State) - assert.Equal(t, [][]byte(nil), m.Bytes) - } else if tt.retryNumber >= totalAttempt || tt.errorMessage != "" { - assert.Equal(t, tt.errorMessage, m.Error.Error()) - assert.Equal(t, [][]byte(nil), m.Bytes) - } else { - assert.NoError(t, m.Error) - var reports [][]byte - for _, rsp := range tt.response.Reports { - b, _ := hexutil.Decode(rsp.FullReport) - reports = append(reports, b) - } - assert.Equal(t, reports, m.Bytes) - } - }) - } -} diff --git a/pkg/automation/v21/mocks/http_client.go b/pkg/automation/v21/mocks/http_client.go deleted file mode 100644 index eae421a32e..0000000000 --- a/pkg/automation/v21/mocks/http_client.go +++ /dev/null @@ -1,94 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package mocks - -import ( - http "net/http" - - mock "github.com/stretchr/testify/mock" -) - -// HttpClient is an autogenerated mock type for the HttpClient type -type HttpClient struct { - mock.Mock -} - -type HttpClient_Expecter struct { - mock *mock.Mock -} - -func (_m *HttpClient) EXPECT() *HttpClient_Expecter { - return &HttpClient_Expecter{mock: &_m.Mock} -} - -// Do provides a mock function with given fields: req -func (_m *HttpClient) Do(req *http.Request) (*http.Response, error) { - ret := _m.Called(req) - - if len(ret) == 0 { - panic("no return value specified for Do") - } - - var r0 *http.Response - var r1 error - if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok { - return rf(req) - } - if rf, ok := ret.Get(0).(func(*http.Request) *http.Response); ok { - r0 = rf(req) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*http.Response) - } - } - - if rf, ok := ret.Get(1).(func(*http.Request) error); ok { - r1 = rf(req) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// HttpClient_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do' -type HttpClient_Do_Call struct { - *mock.Call -} - -// Do is a helper method to define mock.On call -// - req *http.Request -func (_e *HttpClient_Expecter) Do(req interface{}) *HttpClient_Do_Call { - return &HttpClient_Do_Call{Call: _e.mock.On("Do", req)} -} - -func (_c *HttpClient_Do_Call) Run(run func(req *http.Request)) *HttpClient_Do_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*http.Request)) - }) - return _c -} - -func (_c *HttpClient_Do_Call) Return(_a0 *http.Response, _a1 error) *HttpClient_Do_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *HttpClient_Do_Call) RunAndReturn(run func(*http.Request) (*http.Response, error)) *HttpClient_Do_Call { - _c.Call.Return(run) - return _c -} - -// NewHttpClient creates a new instance of HttpClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewHttpClient(t interface { - mock.TestingT - Cleanup(func()) -}) *HttpClient { - mock := &HttpClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/automation/v21/mocks/registry.go b/pkg/automation/v21/mocks/registry.go deleted file mode 100644 index 0a829ac54f..0000000000 --- a/pkg/automation/v21/mocks/registry.go +++ /dev/null @@ -1,449 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package mocks - -import ( - big "math/big" - - bind "github.com/ethereum/go-ethereum/accounts/abi/bind/v2" - - types "github.com/ethereum/go-ethereum/core/types" - generated "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated" - i_automation_v21_plus_common "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - mock "github.com/stretchr/testify/mock" -) - -// Registry is an autogenerated mock type for the Registry type -type Registry struct { - mock.Mock -} - -type Registry_Expecter struct { - mock *mock.Mock -} - -func (_m *Registry) EXPECT() *Registry_Expecter { - return &Registry_Expecter{mock: &_m.Mock} -} - -// CheckCallback provides a mock function with given fields: opts, id, values, extraData -func (_m *Registry) CheckCallback(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (i_automation_v21_plus_common.CheckCallback, error) { - ret := _m.Called(opts, id, values, extraData) - - if len(ret) == 0 { - panic("no return value specified for CheckCallback") - } - - var r0 i_automation_v21_plus_common.CheckCallback - var r1 error - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int, [][]byte, []byte) (i_automation_v21_plus_common.CheckCallback, error)); ok { - return rf(opts, id, values, extraData) - } - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int, [][]byte, []byte) i_automation_v21_plus_common.CheckCallback); ok { - r0 = rf(opts, id, values, extraData) - } else { - r0 = ret.Get(0).(i_automation_v21_plus_common.CheckCallback) - } - - if rf, ok := ret.Get(1).(func(*bind.CallOpts, *big.Int, [][]byte, []byte) error); ok { - r1 = rf(opts, id, values, extraData) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Registry_CheckCallback_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckCallback' -type Registry_CheckCallback_Call struct { - *mock.Call -} - -// CheckCallback is a helper method to define mock.On call -// - opts *bind.CallOpts -// - id *big.Int -// - values [][]byte -// - extraData []byte -func (_e *Registry_Expecter) CheckCallback(opts interface{}, id interface{}, values interface{}, extraData interface{}) *Registry_CheckCallback_Call { - return &Registry_CheckCallback_Call{Call: _e.mock.On("CheckCallback", opts, id, values, extraData)} -} - -func (_c *Registry_CheckCallback_Call) Run(run func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte)) *Registry_CheckCallback_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*bind.CallOpts), args[1].(*big.Int), args[2].([][]byte), args[3].([]byte)) - }) - return _c -} - -func (_c *Registry_CheckCallback_Call) Return(_a0 i_automation_v21_plus_common.CheckCallback, _a1 error) *Registry_CheckCallback_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Registry_CheckCallback_Call) RunAndReturn(run func(*bind.CallOpts, *big.Int, [][]byte, []byte) (i_automation_v21_plus_common.CheckCallback, error)) *Registry_CheckCallback_Call { - _c.Call.Return(run) - return _c -} - -// GetActiveUpkeepIDs provides a mock function with given fields: opts, startIndex, maxCount -func (_m *Registry) GetActiveUpkeepIDs(opts *bind.CallOpts, startIndex *big.Int, maxCount *big.Int) ([]*big.Int, error) { - ret := _m.Called(opts, startIndex, maxCount) - - if len(ret) == 0 { - panic("no return value specified for GetActiveUpkeepIDs") - } - - var r0 []*big.Int - var r1 error - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int, *big.Int) ([]*big.Int, error)); ok { - return rf(opts, startIndex, maxCount) - } - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int, *big.Int) []*big.Int); ok { - r0 = rf(opts, startIndex, maxCount) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*big.Int) - } - } - - if rf, ok := ret.Get(1).(func(*bind.CallOpts, *big.Int, *big.Int) error); ok { - r1 = rf(opts, startIndex, maxCount) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Registry_GetActiveUpkeepIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetActiveUpkeepIDs' -type Registry_GetActiveUpkeepIDs_Call struct { - *mock.Call -} - -// GetActiveUpkeepIDs is a helper method to define mock.On call -// - opts *bind.CallOpts -// - startIndex *big.Int -// - maxCount *big.Int -func (_e *Registry_Expecter) GetActiveUpkeepIDs(opts interface{}, startIndex interface{}, maxCount interface{}) *Registry_GetActiveUpkeepIDs_Call { - return &Registry_GetActiveUpkeepIDs_Call{Call: _e.mock.On("GetActiveUpkeepIDs", opts, startIndex, maxCount)} -} - -func (_c *Registry_GetActiveUpkeepIDs_Call) Run(run func(opts *bind.CallOpts, startIndex *big.Int, maxCount *big.Int)) *Registry_GetActiveUpkeepIDs_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*bind.CallOpts), args[1].(*big.Int), args[2].(*big.Int)) - }) - return _c -} - -func (_c *Registry_GetActiveUpkeepIDs_Call) Return(_a0 []*big.Int, _a1 error) *Registry_GetActiveUpkeepIDs_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Registry_GetActiveUpkeepIDs_Call) RunAndReturn(run func(*bind.CallOpts, *big.Int, *big.Int) ([]*big.Int, error)) *Registry_GetActiveUpkeepIDs_Call { - _c.Call.Return(run) - return _c -} - -// GetState provides a mock function with given fields: opts -func (_m *Registry) GetState(opts *bind.CallOpts) (i_automation_v21_plus_common.GetState, error) { - ret := _m.Called(opts) - - if len(ret) == 0 { - panic("no return value specified for GetState") - } - - var r0 i_automation_v21_plus_common.GetState - var r1 error - if rf, ok := ret.Get(0).(func(*bind.CallOpts) (i_automation_v21_plus_common.GetState, error)); ok { - return rf(opts) - } - if rf, ok := ret.Get(0).(func(*bind.CallOpts) i_automation_v21_plus_common.GetState); ok { - r0 = rf(opts) - } else { - r0 = ret.Get(0).(i_automation_v21_plus_common.GetState) - } - - if rf, ok := ret.Get(1).(func(*bind.CallOpts) error); ok { - r1 = rf(opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Registry_GetState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetState' -type Registry_GetState_Call struct { - *mock.Call -} - -// GetState is a helper method to define mock.On call -// - opts *bind.CallOpts -func (_e *Registry_Expecter) GetState(opts interface{}) *Registry_GetState_Call { - return &Registry_GetState_Call{Call: _e.mock.On("GetState", opts)} -} - -func (_c *Registry_GetState_Call) Run(run func(opts *bind.CallOpts)) *Registry_GetState_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*bind.CallOpts)) - }) - return _c -} - -func (_c *Registry_GetState_Call) Return(_a0 i_automation_v21_plus_common.GetState, _a1 error) *Registry_GetState_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Registry_GetState_Call) RunAndReturn(run func(*bind.CallOpts) (i_automation_v21_plus_common.GetState, error)) *Registry_GetState_Call { - _c.Call.Return(run) - return _c -} - -// GetUpkeep provides a mock function with given fields: opts, id -func (_m *Registry) GetUpkeep(opts *bind.CallOpts, id *big.Int) (i_automation_v21_plus_common.IAutomationV21PlusCommonUpkeepInfoLegacy, error) { - ret := _m.Called(opts, id) - - if len(ret) == 0 { - panic("no return value specified for GetUpkeep") - } - - var r0 i_automation_v21_plus_common.IAutomationV21PlusCommonUpkeepInfoLegacy - var r1 error - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int) (i_automation_v21_plus_common.IAutomationV21PlusCommonUpkeepInfoLegacy, error)); ok { - return rf(opts, id) - } - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int) i_automation_v21_plus_common.IAutomationV21PlusCommonUpkeepInfoLegacy); ok { - r0 = rf(opts, id) - } else { - r0 = ret.Get(0).(i_automation_v21_plus_common.IAutomationV21PlusCommonUpkeepInfoLegacy) - } - - if rf, ok := ret.Get(1).(func(*bind.CallOpts, *big.Int) error); ok { - r1 = rf(opts, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Registry_GetUpkeep_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUpkeep' -type Registry_GetUpkeep_Call struct { - *mock.Call -} - -// GetUpkeep is a helper method to define mock.On call -// - opts *bind.CallOpts -// - id *big.Int -func (_e *Registry_Expecter) GetUpkeep(opts interface{}, id interface{}) *Registry_GetUpkeep_Call { - return &Registry_GetUpkeep_Call{Call: _e.mock.On("GetUpkeep", opts, id)} -} - -func (_c *Registry_GetUpkeep_Call) Run(run func(opts *bind.CallOpts, id *big.Int)) *Registry_GetUpkeep_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*bind.CallOpts), args[1].(*big.Int)) - }) - return _c -} - -func (_c *Registry_GetUpkeep_Call) Return(_a0 i_automation_v21_plus_common.IAutomationV21PlusCommonUpkeepInfoLegacy, _a1 error) *Registry_GetUpkeep_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Registry_GetUpkeep_Call) RunAndReturn(run func(*bind.CallOpts, *big.Int) (i_automation_v21_plus_common.IAutomationV21PlusCommonUpkeepInfoLegacy, error)) *Registry_GetUpkeep_Call { - _c.Call.Return(run) - return _c -} - -// GetUpkeepPrivilegeConfig provides a mock function with given fields: opts, upkeepId -func (_m *Registry) GetUpkeepPrivilegeConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - ret := _m.Called(opts, upkeepId) - - if len(ret) == 0 { - panic("no return value specified for GetUpkeepPrivilegeConfig") - } - - var r0 []byte - var r1 error - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int) ([]byte, error)); ok { - return rf(opts, upkeepId) - } - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int) []byte); ok { - r0 = rf(opts, upkeepId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - if rf, ok := ret.Get(1).(func(*bind.CallOpts, *big.Int) error); ok { - r1 = rf(opts, upkeepId) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Registry_GetUpkeepPrivilegeConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUpkeepPrivilegeConfig' -type Registry_GetUpkeepPrivilegeConfig_Call struct { - *mock.Call -} - -// GetUpkeepPrivilegeConfig is a helper method to define mock.On call -// - opts *bind.CallOpts -// - upkeepId *big.Int -func (_e *Registry_Expecter) GetUpkeepPrivilegeConfig(opts interface{}, upkeepId interface{}) *Registry_GetUpkeepPrivilegeConfig_Call { - return &Registry_GetUpkeepPrivilegeConfig_Call{Call: _e.mock.On("GetUpkeepPrivilegeConfig", opts, upkeepId)} -} - -func (_c *Registry_GetUpkeepPrivilegeConfig_Call) Run(run func(opts *bind.CallOpts, upkeepId *big.Int)) *Registry_GetUpkeepPrivilegeConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*bind.CallOpts), args[1].(*big.Int)) - }) - return _c -} - -func (_c *Registry_GetUpkeepPrivilegeConfig_Call) Return(_a0 []byte, _a1 error) *Registry_GetUpkeepPrivilegeConfig_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Registry_GetUpkeepPrivilegeConfig_Call) RunAndReturn(run func(*bind.CallOpts, *big.Int) ([]byte, error)) *Registry_GetUpkeepPrivilegeConfig_Call { - _c.Call.Return(run) - return _c -} - -// GetUpkeepTriggerConfig provides a mock function with given fields: opts, upkeepId -func (_m *Registry) GetUpkeepTriggerConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - ret := _m.Called(opts, upkeepId) - - if len(ret) == 0 { - panic("no return value specified for GetUpkeepTriggerConfig") - } - - var r0 []byte - var r1 error - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int) ([]byte, error)); ok { - return rf(opts, upkeepId) - } - if rf, ok := ret.Get(0).(func(*bind.CallOpts, *big.Int) []byte); ok { - r0 = rf(opts, upkeepId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - if rf, ok := ret.Get(1).(func(*bind.CallOpts, *big.Int) error); ok { - r1 = rf(opts, upkeepId) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Registry_GetUpkeepTriggerConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUpkeepTriggerConfig' -type Registry_GetUpkeepTriggerConfig_Call struct { - *mock.Call -} - -// GetUpkeepTriggerConfig is a helper method to define mock.On call -// - opts *bind.CallOpts -// - upkeepId *big.Int -func (_e *Registry_Expecter) GetUpkeepTriggerConfig(opts interface{}, upkeepId interface{}) *Registry_GetUpkeepTriggerConfig_Call { - return &Registry_GetUpkeepTriggerConfig_Call{Call: _e.mock.On("GetUpkeepTriggerConfig", opts, upkeepId)} -} - -func (_c *Registry_GetUpkeepTriggerConfig_Call) Run(run func(opts *bind.CallOpts, upkeepId *big.Int)) *Registry_GetUpkeepTriggerConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*bind.CallOpts), args[1].(*big.Int)) - }) - return _c -} - -func (_c *Registry_GetUpkeepTriggerConfig_Call) Return(_a0 []byte, _a1 error) *Registry_GetUpkeepTriggerConfig_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Registry_GetUpkeepTriggerConfig_Call) RunAndReturn(run func(*bind.CallOpts, *big.Int) ([]byte, error)) *Registry_GetUpkeepTriggerConfig_Call { - _c.Call.Return(run) - return _c -} - -// ParseLog provides a mock function with given fields: log -func (_m *Registry) ParseLog(log types.Log) (generated.AbigenLog, error) { - ret := _m.Called(log) - - if len(ret) == 0 { - panic("no return value specified for ParseLog") - } - - var r0 generated.AbigenLog - var r1 error - if rf, ok := ret.Get(0).(func(types.Log) (generated.AbigenLog, error)); ok { - return rf(log) - } - if rf, ok := ret.Get(0).(func(types.Log) generated.AbigenLog); ok { - r0 = rf(log) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(generated.AbigenLog) - } - } - - if rf, ok := ret.Get(1).(func(types.Log) error); ok { - r1 = rf(log) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Registry_ParseLog_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ParseLog' -type Registry_ParseLog_Call struct { - *mock.Call -} - -// ParseLog is a helper method to define mock.On call -// - log types.Log -func (_e *Registry_Expecter) ParseLog(log interface{}) *Registry_ParseLog_Call { - return &Registry_ParseLog_Call{Call: _e.mock.On("ParseLog", log)} -} - -func (_c *Registry_ParseLog_Call) Run(run func(log types.Log)) *Registry_ParseLog_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(types.Log)) - }) - return _c -} - -func (_c *Registry_ParseLog_Call) Return(_a0 generated.AbigenLog, _a1 error) *Registry_ParseLog_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Registry_ParseLog_Call) RunAndReturn(run func(types.Log) (generated.AbigenLog, error)) *Registry_ParseLog_Call { - _c.Call.Return(run) - return _c -} - -// NewRegistry creates a new instance of Registry. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewRegistry(t interface { - mock.TestingT - Cleanup(func()) -}) *Registry { - mock := &Registry{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/automation/v21/payload_builder.go b/pkg/automation/v21/payload_builder.go deleted file mode 100644 index 3bf547469e..0000000000 --- a/pkg/automation/v21/payload_builder.go +++ /dev/null @@ -1,63 +0,0 @@ -package evm - -import ( - "context" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/logprovider" -) - -type payloadBuilder struct { - upkeepList ActiveUpkeepList - lggr logger.Logger - recoverer logprovider.LogRecoverer -} - -var _ ocr2keepers.PayloadBuilder = &payloadBuilder{} - -func NewPayloadBuilder(activeUpkeepList ActiveUpkeepList, recoverer logprovider.LogRecoverer, lggr logger.Logger) *payloadBuilder { - return &payloadBuilder{ - upkeepList: activeUpkeepList, - lggr: lggr, - recoverer: recoverer, - } -} - -func (b *payloadBuilder) BuildPayloads(ctx context.Context, proposals ...ocr2keepers.CoordinatedBlockProposal) ([]ocr2keepers.UpkeepPayload, error) { - payloads := make([]ocr2keepers.UpkeepPayload, len(proposals)) - - for i, proposal := range proposals { - var payload ocr2keepers.UpkeepPayload - if !b.upkeepList.IsActive(proposal.UpkeepID.BigInt()) { - b.lggr.Warnw("upkeep is not active, skipping", "upkeepID", proposal.UpkeepID) - continue - } - b.lggr.Debugf("building payload for coordinated block proposal %+v", proposal) - var checkData []byte - var err error - switch core.GetUpkeepType(proposal.UpkeepID) { - case types.LogTrigger: - checkData, err = b.recoverer.GetProposalData(ctx, proposal) - if err != nil { - b.lggr.Warnw("failed to get log proposal data", "err", err, "upkeepID", proposal.UpkeepID, "trigger", proposal.Trigger) - continue - } - case types.ConditionTrigger: - // Empty checkData for conditionals - } - payload, err = core.NewUpkeepPayload(proposal.UpkeepID.BigInt(), proposal.Trigger, checkData) - if err != nil { - b.lggr.Warnw("error building upkeep payload", "err", err, "upkeepID", proposal.UpkeepID) - continue - } - - payloads[i] = payload - } - - return payloads, nil -} diff --git a/pkg/automation/v21/payload_builder_test.go b/pkg/automation/v21/payload_builder_test.go deleted file mode 100644 index 75420442e9..0000000000 --- a/pkg/automation/v21/payload_builder_test.go +++ /dev/null @@ -1,210 +0,0 @@ -package evm - -import ( - "context" - "math/big" - "testing" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/logprovider" -) - -func TestNewPayloadBuilder(t *testing.T) { - for _, tc := range []struct { - name string - activeList ActiveUpkeepList - recoverer logprovider.LogRecoverer - proposals []automation.CoordinatedBlockProposal - wantPayloads []automation.UpkeepPayload - }{ - { - name: "for log trigger upkeeps, new payloads are created", - activeList: &mockActiveUpkeepList{ - IsActiveFn: func(id *big.Int) bool { - return true - }, - }, - proposals: []automation.CoordinatedBlockProposal{ - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "abc"), - WorkID: "workID1", - Trigger: automation.Trigger{ - BlockNumber: 1, - BlockHash: [32]byte{1}, - }, - }, - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "def"), - WorkID: "workID2", - Trigger: automation.Trigger{ - BlockNumber: 2, - BlockHash: [32]byte{2}, - }, - }, - }, - recoverer: &mockLogRecoverer{ - GetProposalDataFn: func(ctx context.Context, proposal automation.CoordinatedBlockProposal) ([]byte, error) { - return []byte{1, 2, 3}, nil - }, - }, - wantPayloads: []automation.UpkeepPayload{ - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "abc"), - WorkID: "714f83255c5b562823725748c4a75777c9b78ea8c5ba72ea819926a1fecd389e", - Trigger: automation.Trigger{ - BlockNumber: 1, - BlockHash: [32]byte{1}, - }, - CheckData: []byte{1, 2, 3}, - }, - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "def"), - WorkID: "3956daa0378d6a761fe972ee00fe98338f17fb6b7865c1d49a8a416cd85977b8", - Trigger: automation.Trigger{ - BlockNumber: 2, - BlockHash: [32]byte{2}, - }, - CheckData: []byte{1, 2, 3}, - }, - }, - }, - { - name: "for an inactive log trigger upkeep, an empty payload is added to the list of payloads", - activeList: &mockActiveUpkeepList{ - IsActiveFn: func(id *big.Int) bool { - return core.GenUpkeepID(types.LogTrigger, "ghi").BigInt().Cmp(id) != 0 - }, - }, - proposals: []automation.CoordinatedBlockProposal{ - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "abc"), - WorkID: "workID1", - Trigger: automation.Trigger{ - BlockNumber: 1, - BlockHash: [32]byte{1}, - }, - }, - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "def"), - WorkID: "workID2", - Trigger: automation.Trigger{ - BlockNumber: 2, - BlockHash: [32]byte{2}, - }, - }, - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "ghi"), - WorkID: "workID3", - Trigger: automation.Trigger{ - BlockNumber: 3, - BlockHash: [32]byte{3}, - }, - }, - }, - recoverer: &mockLogRecoverer{ - GetProposalDataFn: func(ctx context.Context, proposal automation.CoordinatedBlockProposal) ([]byte, error) { - return []byte{1, 2, 3}, nil - }, - }, - wantPayloads: []automation.UpkeepPayload{ - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "abc"), - WorkID: "714f83255c5b562823725748c4a75777c9b78ea8c5ba72ea819926a1fecd389e", - Trigger: automation.Trigger{ - BlockNumber: 1, - BlockHash: [32]byte{1}, - }, - CheckData: []byte{1, 2, 3}, - }, - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "def"), - WorkID: "3956daa0378d6a761fe972ee00fe98338f17fb6b7865c1d49a8a416cd85977b8", - Trigger: automation.Trigger{ - BlockNumber: 2, - BlockHash: [32]byte{2}, - }, - CheckData: []byte{1, 2, 3}, - }, - {}, - }, - }, - { - name: "when the recoverer errors, an empty payload is created but not added to the list of payloads", - activeList: &mockActiveUpkeepList{ - IsActiveFn: func(id *big.Int) bool { - return true - }, - }, - proposals: []automation.CoordinatedBlockProposal{ - { - UpkeepID: core.GenUpkeepID(types.LogTrigger, "abc"), - WorkID: "workID1", - Trigger: automation.Trigger{ - BlockNumber: 1, - BlockHash: [32]byte{1}, - }, - }, - }, - recoverer: &mockLogRecoverer{ - GetProposalDataFn: func(ctx context.Context, proposal automation.CoordinatedBlockProposal) ([]byte, error) { - return nil, errors.New("recoverer boom") - }, - }, - wantPayloads: []automation.UpkeepPayload{ - {}, - }, - }, - { - name: "for a conditional upkeep, a new payload with empty check data is added to the list of payloads", - activeList: &mockActiveUpkeepList{ - IsActiveFn: func(id *big.Int) bool { - return true - }, - }, - proposals: []automation.CoordinatedBlockProposal{ - { - UpkeepID: core.GenUpkeepID(types.ConditionTrigger, "def"), - WorkID: "workID1", - Trigger: automation.Trigger{ - BlockNumber: 1, - BlockHash: [32]byte{1}, - }, - }, - }, - wantPayloads: []automation.UpkeepPayload{ - { - UpkeepID: core.GenUpkeepID(types.ConditionTrigger, "def"), - WorkID: "58f2f231792448679a75bac6efc2af4ba731901f0cb93a44a366525751cbabfb", - Trigger: automation.Trigger{ - BlockNumber: 1, - BlockHash: [32]byte{1}, - }, - }, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - lggr := logger.Test(t) - builder := NewPayloadBuilder(tc.activeList, tc.recoverer, lggr) - payloads, err := builder.BuildPayloads(t.Context(), tc.proposals...) - assert.NoError(t, err) - assert.Equal(t, tc.wantPayloads, payloads) - }) - } -} - -type mockLogRecoverer struct { - logprovider.LogRecoverer - GetProposalDataFn func(context.Context, automation.CoordinatedBlockProposal) ([]byte, error) -} - -func (r *mockLogRecoverer) GetProposalData(ctx context.Context, p automation.CoordinatedBlockProposal) ([]byte, error) { - return r.GetProposalDataFn(ctx, p) -} diff --git a/pkg/automation/v21/prommetrics/metrics.go b/pkg/automation/v21/prommetrics/metrics.go deleted file mode 100644 index 682b8710c0..0000000000 --- a/pkg/automation/v21/prommetrics/metrics.go +++ /dev/null @@ -1,108 +0,0 @@ -package prommetrics - -import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" -) - -// Namespaces -const ( - NamespaceAutomationLogTrigger = "automation_log_trigger" - NamespaceAutomationStreams = "automation_streams" -) - -// Streams steps -const ( - StreamsLookupStepDoMercuryRequest = "do_mercury_request" - StreamsLookupStepCheckErrorHandler = "check_error_handler" - StreamsLookupStepCheckCallback = "check_callback" -) - -// Streams error labels -const ( - StreamsLookupErrorReasonNotReverted = "reason_not_target_check_reverted" - StreamsLookupErrorDecodeRequestFailed = "decode_request_failed" - StreamsLookupErrorCredentialsNotConfigured = "credentials_not_configured" - StreamsLookupErrorDoMercuryRequest = "do_mercury_request" - StreamsLookupErrorCodeNotNil = "err_code_not_nil" - StreamsLookupErrorCheckCallback = "check_callback" - StreamsLookupErrorPackUserCheckErrorHandler = "pack_user_check_error_handler" - StreamsLookupErrorPackExecuteCallback = "pack_execute_callback" -) - -// Streams versions -const ( - StreamsVersion02 = "v02" - StreamsVersion03 = "v03" -) - -// Metric labels -const ( - LogBufferFlowDirectionIngress = "ingress" - LogBufferFlowDirectionEgress = "egress" - LogBufferFlowDirectionDropped = "dropped" - LogBufferFlowDirectionExpired = "expired" -) - -// Automation metrics -var ( - // Log Trigger metrics - AutomationLogBufferFlow = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: NamespaceAutomationLogTrigger, - Name: "num_logs_in_log_buffer", - Help: "The total number of logs currently being stored in the log buffer", - }, []string{ - "direction", - }) - AutomationRecovererMissedLogs = promauto.NewCounter(prometheus.CounterOpts{ - Namespace: NamespaceAutomationLogTrigger, - Name: "num_recoverer_missed_logs", - Help: "How many valid log triggers were identified as being missed by the recoverer", - }) - AutomationRecovererPendingPayloads = promauto.NewGauge(prometheus.GaugeOpts{ - Namespace: NamespaceAutomationLogTrigger, - Name: "num_recoverer_pending_payloads", - Help: "How many log trigger payloads are currently pending in the recoverer", - }) - AutomationActiveUpkeeps = promauto.NewGauge(prometheus.GaugeOpts{ - Namespace: NamespaceAutomationLogTrigger, - Name: "num_active_upkeeps", - Help: "How many log trigger upkeeps are currently active", - }) - AutomationLogProviderLatestBlock = promauto.NewGauge(prometheus.GaugeOpts{ - Namespace: NamespaceAutomationLogTrigger, - Name: "log_provider_latest_block", - Help: "The latest block number the log provider has seen", - }) - - // Streams metrics - AutomationStreamsLookupStep = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: NamespaceAutomationStreams, - Name: "streams_lookup_step_count", - Help: "How many times individual steps of the streams lookup process run", - }, []string{ - "step", - }) - AutomationStreamsLookupError = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: NamespaceAutomationStreams, - Name: "streams_lookup_error_count", - Help: "Errors occurred during a streams lookup attempt", - }, []string{ - "error", - }) - AutomationStreamsRetries = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: NamespaceAutomationStreams, - Name: "streams_retries", - Help: "Count of the times a streams lookup was retried", - }, []string{ - "version", - }) - AutomationStreamsResponses = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: NamespaceAutomationStreams, - Name: "streams_responses", - Help: "Count of individual response codes from streams lookup", - }, []string{ - "version", - "status", - }) -) diff --git a/pkg/automation/v21/registry.go b/pkg/automation/v21/registry.go deleted file mode 100644 index 0f3502c342..0000000000 --- a/pkg/automation/v21/registry.go +++ /dev/null @@ -1,634 +0,0 @@ -package evm - -import ( - "context" - "errors" - "fmt" - "math/big" - "net/http" - "strconv" - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - coreTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/patrickmn/go-cache" - - autotypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-common/pkg/types" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated" - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" - "github.com/smartcontractkit/chainlink-evm/pkg/client" - "github.com/smartcontractkit/chainlink-evm/pkg/gas" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/logprovider" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mercury/streams" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - defaultPluginRetryExpiration = 30 * time.Minute - // defaultAllowListExpiration decides how long an upkeep's allow list info will be valid for. - defaultAllowListExpiration = 10 * time.Minute - // cleanupInterval decides when the expired items in cache will be deleted. - cleanupInterval = 5 * time.Minute - logTriggerRefreshBatchSize = 32 -) - -var ( - RegistryServiceName = "AutomationRegistry" - - ErrLogReadFailure = errors.New("failure reading logs") - ErrHeadNotAvailable = errors.New("head not available") - ErrInitializationFailure = errors.New("failed to initialize registry") - ErrContextCancelled = errors.New("context was cancelled") - ErrABINotParsable = errors.New("error parsing abi") - ActiveUpkeepIDBatchSize int64 = 1000 - // This is the interval at which active upkeep list is fully refreshed from chain - refreshInterval = 15 * time.Minute - // This is the lookback for polling upkeep state event logs from latest block - logEventLookback int64 = 250 -) - -type Registry interface { - GetUpkeep(opts *bind.CallOpts, id *big.Int) (encoding.UpkeepInfo, error) - GetState(opts *bind.CallOpts) (ac.GetState, error) - GetActiveUpkeepIDs(opts *bind.CallOpts, startIndex *big.Int, maxCount *big.Int) ([]*big.Int, error) - GetUpkeepPrivilegeConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) - GetUpkeepTriggerConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) - CheckCallback(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (ac.CheckCallback, error) - ParseLog(log coreTypes.Log) (generated.AbigenLog, error) -} - -type HttpClient interface { - Do(req *http.Request) (*http.Response, error) -} - -func NewEvmRegistry( - lggr logger.Logger, - addr common.Address, - client legacyevm.Chain, - registry *ac.IAutomationV21PlusCommon, - mc *types.MercuryCredentials, - al ActiveUpkeepList, - logEventProvider logprovider.LogEventProvider, - packer encoding.Packer, - blockSub *BlockSubscriber, - finalityDepth uint32, -) *EvmRegistry { - mercuryConfig := NewMercuryConfig(mc, core.StreamsCompatibleABI) - hc := http.DefaultClient - - return &EvmRegistry{ - stopCh: make(chan struct{}), - threadCtrl: threadcontrol.NewThreadControl(), - lggr: logger.Sugared(lggr).Named(RegistryServiceName), - poller: client.LogPoller(), - addr: addr, - client: client.Client(), - logProcessed: make(map[string]bool), - registry: registry, - abi: core.AutoV2CommonABI, - active: al, - packer: packer, - headFunc: func(ocr2keepers.BlockKey) {}, - chLog: make(chan logpoller.Log, 1000), - hc: hc, - logEventProvider: logEventProvider, - bs: blockSub, - finalityDepth: finalityDepth, - streams: streams.NewStreamsLookup(mercuryConfig, blockSub, client.Client(), registry, lggr), - ge: client.GasEstimator(), - } -} - -var upkeepStateEvents = []common.Hash{ - ac.IAutomationV21PlusCommonUpkeepRegistered{}.Topic(), // adds new upkeep id to registry - ac.IAutomationV21PlusCommonUpkeepReceived{}.Topic(), // adds new upkeep id to registry via migration - ac.IAutomationV21PlusCommonUpkeepUnpaused{}.Topic(), // unpauses an upkeep - ac.IAutomationV21PlusCommonUpkeepPaused{}.Topic(), // pauses an upkeep - ac.IAutomationV21PlusCommonUpkeepMigrated{}.Topic(), // migrated an upkeep, equivalent to cancel from this registry's perspective - ac.IAutomationV21PlusCommonUpkeepCanceled{}.Topic(), // cancels an upkeep - ac.IAutomationV21PlusCommonUpkeepTriggerConfigSet{}.Topic(), // trigger config was changed -} - -type MercuryConfig struct { - cred *types.MercuryCredentials - Abi abi.ABI - // AllowListCache stores the upkeeps privileges. In 2.1, this only includes a JSON bytes for allowed to use mercury - AllowListCache *cache.Cache - pluginRetryCache *cache.Cache -} - -func NewMercuryConfig(cred *types.MercuryCredentials, abi abi.ABI) *MercuryConfig { - c := &types.MercuryCredentials{} - if cred != nil { - c.Password = cred.Password - c.Username = cred.Username - c.URL = strings.TrimRight(cred.URL, "/") - c.LegacyURL = strings.TrimRight(cred.LegacyURL, "/") - } - return &MercuryConfig{ - cred: c, - Abi: abi, - AllowListCache: cache.New(defaultPluginRetryExpiration, cleanupInterval), - pluginRetryCache: cache.New(defaultPluginRetryExpiration, cleanupInterval), - } -} - -func (c *MercuryConfig) Credentials() *types.MercuryCredentials { - return c.cred -} - -func (c *MercuryConfig) IsUpkeepAllowed(k string) (any, bool) { - return c.AllowListCache.Get(k) -} - -func (c *MercuryConfig) SetUpkeepAllowed(k string, v any, d time.Duration) { - c.AllowListCache.Set(k, v, d) -} - -func (c *MercuryConfig) GetPluginRetry(k string) (any, bool) { - return c.pluginRetryCache.Get(k) -} - -func (c *MercuryConfig) SetPluginRetry(k string, v any, d time.Duration) { - c.pluginRetryCache.Set(k, v, d) -} - -type EvmRegistry struct { - services.StateMachine - threadCtrl threadcontrol.ThreadControl - lggr logger.SugaredLogger - poller logpoller.LogPoller - addr common.Address - client client.Client - chainID uint64 - registry Registry - abi abi.ABI - packer encoding.Packer - chLog chan logpoller.Log - mu sync.RWMutex - logProcessed map[string]bool - active ActiveUpkeepList - lastPollBlock int64 - stopCh services.StopChan - headFunc func(ocr2keepers.BlockKey) - mercury *MercuryConfig - hc HttpClient - bs *BlockSubscriber - logEventProvider logprovider.LogEventProvider - finalityDepth uint32 - streams streams.Lookup - ge gas.EvmFeeEstimator -} - -func (r *EvmRegistry) Name() string { - return r.lggr.Name() -} - -func (r *EvmRegistry) Start(ctx context.Context) error { - return r.StartOnce(RegistryServiceName, func() error { - if err := r.registerEvents(ctx, r.chainID, r.addr); err != nil { - return fmt.Errorf("logPoller error while registering automation events: %w", err) - } - - r.threadCtrl.Go(func(ctx context.Context) { - lggr := r.lggr.With("where", "upkeeps_referesh") - err := r.refreshActiveUpkeeps(ctx) - if err != nil { - lggr.Errorf("failed to initialize upkeeps; error %v", err) - } - - ticker := time.NewTicker(refreshInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - err = r.refreshActiveUpkeeps(ctx) - if err != nil { - lggr.Errorf("failed to refresh upkeeps; error %v", err) - } - case <-ctx.Done(): - return - } - } - }) - - r.threadCtrl.Go(func(ctx context.Context) { - lggr := r.lggr.With("where", "logs_polling") - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - err := r.pollUpkeepStateLogs(ctx) - if err != nil { - lggr.Errorf("failed to poll logs for upkeeps; error %v", err) - } - case <-ctx.Done(): - return - } - } - }) - - r.threadCtrl.Go(func(ctx context.Context) { - lggr := r.lggr.With("where", "logs_processing") - ch := r.chLog - - for { - select { - case l := <-ch: - err := r.processUpkeepStateLog(ctx, l) - if err != nil { - lggr.Errorf("failed to process log for upkeep; error %v", err) - } - case <-ctx.Done(): - return - } - } - }) - - return nil - }) -} - -func (r *EvmRegistry) Close() error { - return r.StopOnce(RegistryServiceName, func() error { - r.threadCtrl.Close() - return nil - }) -} - -func (r *EvmRegistry) HealthReport() map[string]error { - return map[string]error{RegistryServiceName: r.Healthy()} -} - -func (r *EvmRegistry) refreshActiveUpkeeps(ctx context.Context) error { - // Allow for max timeout of refreshInterval - ctx, cancel := context.WithTimeout(ctx, refreshInterval) - defer cancel() - - r.lggr.Debugf("Refreshing active upkeeps list") - // get active upkeep ids from contract - ids, err := r.getLatestIDsFromContract(ctx) - if err != nil { - return fmt.Errorf("failed to get active upkeep ids from contract during refresh: %w", err) - } - r.active.Reset(ids...) - - var logTriggerIDs []*big.Int - for _, id := range ids { - uid := &ocr2keepers.UpkeepIdentifier{} - if ok := uid.FromBigInt(id); !ok { - r.lggr.Warnf("failed to parse upkeep id %s", id.String()) - continue - } - switch core.GetUpkeepType(*uid) { - case autotypes.LogTrigger: - logTriggerIDs = append(logTriggerIDs, id) - default: - } - } - - _, err = r.logEventProvider.RefreshActiveUpkeeps(ctx, logTriggerIDs...) - if err != nil { - return fmt.Errorf("failed to refresh active upkeep ids in log event provider: %w", err) - } - - // Try to refersh log trigger config for all log upkeeps - return r.refreshLogTriggerUpkeeps(ctx, logTriggerIDs) -} - -// refreshLogTriggerUpkeeps refreshes the active upkeep ids for log trigger upkeeps -func (r *EvmRegistry) refreshLogTriggerUpkeeps(ctx context.Context, ids []*big.Int) error { - var err error - for i := 0; i < len(ids); i += logTriggerRefreshBatchSize { - end := min(i+logTriggerRefreshBatchSize, len(ids)) - idBatch := ids[i:end] - - if batchErr := r.refreshLogTriggerUpkeepsBatch(ctx, idBatch); batchErr != nil { - err = errors.Join(err, batchErr) - } - - time.Sleep(500 * time.Millisecond) - } - - return err -} - -func (r *EvmRegistry) refreshLogTriggerUpkeepsBatch(ctx context.Context, logTriggerIDs []*big.Int) error { - var logTriggerHashes []common.Hash - for _, id := range logTriggerIDs { - logTriggerHashes = append(logTriggerHashes, common.BigToHash(id)) - } - - unpausedLogs, err := r.poller.IndexedLogs(ctx, ac.IAutomationV21PlusCommonUpkeepUnpaused{}.Topic(), r.addr, 1, logTriggerHashes, evmtypes.Confirmations(r.finalityDepth)) - if err != nil { - return err - } - configSetLogs, err := r.poller.IndexedLogs(ctx, ac.IAutomationV21PlusCommonUpkeepTriggerConfigSet{}.Topic(), r.addr, 1, logTriggerHashes, evmtypes.Confirmations(r.finalityDepth)) - if err != nil { - return err - } - - logs := append(unpausedLogs, configSetLogs...) - - configSetBlockNumbers := map[string]uint64{} - unpausedBlockNumbers := map[string]uint64{} - perUpkeepConfig := map[string][]byte{} - - for _, log := range logs { - rawLog := log.ToGethLog() - abilog, err := r.registry.ParseLog(rawLog) - if err != nil { - return err - } - switch l := abilog.(type) { - case *ac.IAutomationV21PlusCommonUpkeepTriggerConfigSet: - if rawLog.BlockNumber > configSetBlockNumbers[l.Id.String()] { - configSetBlockNumbers[l.Id.String()] = rawLog.BlockNumber - perUpkeepConfig[l.Id.String()] = l.TriggerConfig - } - case *ac.IAutomationV21PlusCommonUpkeepUnpaused: - if rawLog.BlockNumber > unpausedBlockNumbers[l.Id.String()] { - unpausedBlockNumbers[l.Id.String()] = rawLog.BlockNumber - } - } - } - - var merr error - for _, id := range logTriggerIDs { - logBlock, ok := configSetBlockNumbers[id.String()] - if !ok { - r.lggr.Warnf("unable to find finalized config set block number for %s, using 0 as config start block", id.String()) - // Use zero as config update block so it can be updated if an actual event is found later - logBlock = 0 - } - - config, ok := perUpkeepConfig[id.String()] - if !ok { - r.lggr.Warnf("unable to find per finalized log config for %s, will fetch latest config from chain", id.String()) - // Set it to empty bytes so that latest config is fetched within r.updateTriggerConfig - config = []byte{} - } - - // In case an upkeep was paused then unpaused after a config set event, start the config from the unpaused block number - if unpausedBlockNumbers[id.String()] > logBlock { - logBlock = unpausedBlockNumbers[id.String()] - } - if err := r.updateTriggerConfig(ctx, id, config, logBlock); err != nil { - merr = errors.Join(merr, fmt.Errorf("failed to update trigger config for upkeep id %s: %w", id.String(), err)) - } - } - - return merr -} - -func (r *EvmRegistry) pollUpkeepStateLogs(ctx context.Context) error { - var latest int64 - var end logpoller.Block - var err error - - if end, err = r.poller.LatestBlock(ctx); err != nil { - return fmt.Errorf("%w: %w", ErrHeadNotAvailable, err) - } - - r.mu.Lock() - latest = r.lastPollBlock - r.lastPollBlock = end.BlockNumber - r.mu.Unlock() - - // if start and end are the same, no polling needs to be done - if latest == 0 || latest == end.BlockNumber { - return nil - } - - var logs []logpoller.Log - if logs, err = r.poller.LogsWithSigs( - ctx, - end.BlockNumber-logEventLookback, - end.BlockNumber, - upkeepStateEvents, - r.addr, - ); err != nil { - return fmt.Errorf("%w: %w", ErrLogReadFailure, err) - } - - for _, log := range logs { - r.chLog <- log - } - - return nil -} - -func (r *EvmRegistry) processUpkeepStateLog(ctx context.Context, l logpoller.Log) error { - lid := fmt.Sprintf("%s%d", l.TxHash.String(), l.LogIndex) - r.mu.Lock() - if _, ok := r.logProcessed[lid]; ok { - r.mu.Unlock() - return nil - } - r.logProcessed[lid] = true - r.mu.Unlock() - txHash := l.TxHash.String() - - rawLog := l.ToGethLog() - abilog, err := r.registry.ParseLog(rawLog) - if err != nil { - return err - } - - switch l := abilog.(type) { - case *ac.IAutomationV21PlusCommonUpkeepPaused: - r.lggr.Debugf("KeeperRegistryUpkeepPaused log detected for upkeep ID %s in transaction %s", l.Id, txHash) - r.removeFromActive(ctx, l.Id) - case *ac.IAutomationV21PlusCommonUpkeepCanceled: - r.lggr.Debugf("KeeperRegistryUpkeepCanceled log detected for upkeep ID %s in transaction %s", l.Id, txHash) - r.removeFromActive(ctx, l.Id) - case *ac.IAutomationV21PlusCommonUpkeepMigrated: - r.lggr.Debugf("AutomationV2CommonUpkeepMigrated log detected for upkeep ID %s in transaction %s", l.Id, txHash) - r.removeFromActive(ctx, l.Id) - case *ac.IAutomationV21PlusCommonUpkeepTriggerConfigSet: - r.lggr.Debugf("KeeperRegistryUpkeepTriggerConfigSet log detected for upkeep ID %s in transaction %s", l.Id, txHash) - if err := r.updateTriggerConfig(ctx, l.Id, l.TriggerConfig, rawLog.BlockNumber); err != nil { - r.lggr.Warnf("failed to update trigger config upon AutomationV2CommonUpkeepTriggerConfigSet for upkeep ID %s: %s", l.Id, err) - } - case *ac.IAutomationV21PlusCommonUpkeepRegistered: - uid := &ocr2keepers.UpkeepIdentifier{} - uid.FromBigInt(l.Id) - trigger := core.GetUpkeepType(*uid) - r.lggr.Debugf("KeeperRegistryUpkeepRegistered log detected for upkeep ID %s (trigger=%d) in transaction %s", l.Id, trigger, txHash) - r.active.Add(l.Id) - if err := r.updateTriggerConfig(ctx, l.Id, nil, rawLog.BlockNumber); err != nil { - r.lggr.Warnf("failed to update trigger config upon AutomationV2CommonUpkeepRegistered for upkeep ID %s: %s", l.Id, err) - } - case *ac.IAutomationV21PlusCommonUpkeepReceived: - r.lggr.Debugf("KeeperRegistryUpkeepReceived log detected for upkeep ID %s in transaction %s", l.Id, txHash) - r.active.Add(l.Id) - if err := r.updateTriggerConfig(ctx, l.Id, nil, rawLog.BlockNumber); err != nil { - r.lggr.Warnf("failed to update trigger config upon AutomationV2CommonUpkeepReceived for upkeep ID %s: %s", l.Id, err) - } - case *ac.IAutomationV21PlusCommonUpkeepUnpaused: - r.lggr.Debugf("KeeperRegistryUpkeepUnpaused log detected for upkeep ID %s in transaction %s", l.Id, txHash) - r.active.Add(l.Id) - if err := r.updateTriggerConfig(ctx, l.Id, nil, rawLog.BlockNumber); err != nil { - r.lggr.Warnf("failed to update trigger config upon AutomationV2CommonUpkeepUnpaused for upkeep ID %s: %s", l.Id, err) - } - default: - r.lggr.Debugf("Unknown log detected for log %+v in transaction %s", l, txHash) - } - - return nil -} - -func RegistryUpkeepFilterName(addr common.Address) string { - return logpoller.FilterName("KeeperRegistry Events", addr.String()) -} - -// registerEvents registers upkeep state events from keeper registry on log poller -func (r *EvmRegistry) registerEvents(ctx context.Context, _ uint64, addr common.Address) error { - // Add log filters for the log poller so that it can poll and find the logs that we need - return r.poller.RegisterFilter(ctx, logpoller.Filter{ - Name: RegistryUpkeepFilterName(addr), - EventSigs: upkeepStateEvents, - Addresses: []common.Address{addr}, - }) -} - -// removeFromActive removes an upkeepID from active list and unregisters the log filter for log upkeeps -func (r *EvmRegistry) removeFromActive(ctx context.Context, id *big.Int) { - r.active.Remove(id) - - uid := &ocr2keepers.UpkeepIdentifier{} - uid.FromBigInt(id) - trigger := core.GetUpkeepType(*uid) - switch trigger { - case autotypes.LogTrigger: - if err := r.logEventProvider.UnregisterFilter(ctx, id); err != nil { - r.lggr.Warnw("failed to unregister log filter", "upkeepID", id.String()) - } - r.lggr.Debugw("unregistered log filter", "upkeepID", id.String()) - default: - } -} - -func (r *EvmRegistry) buildCallOpts(ctx context.Context, block *big.Int) *bind.CallOpts { - opts := bind.CallOpts{ - Context: ctx, - } - - if block == nil || block.Int64() == 0 { - l := r.bs.latestBlock.Load() - if l != nil && l.Number != 0 { - opts.BlockNumber = big.NewInt(int64(l.Number)) - } - } else { - opts.BlockNumber = block - } - - return &opts -} - -func (r *EvmRegistry) getLatestIDsFromContract(ctx context.Context) ([]*big.Int, error) { - opts := r.buildCallOpts(ctx, nil) - - state, err := r.registry.GetState(opts) - if err != nil { - n := "latest" - if opts.BlockNumber != nil { - n = strconv.FormatInt(opts.BlockNumber.Int64(), 10) - } - - return nil, fmt.Errorf("%w: failed to get contract state at block number '%s'", err, n) - } - - ids := make([]*big.Int, 0, int(state.State.NumUpkeeps.Int64())) - for int64(len(ids)) < state.State.NumUpkeeps.Int64() { - startIndex := int64(len(ids)) - maxCount := state.State.NumUpkeeps.Int64() - startIndex - - if maxCount == 0 { - break - } - - if maxCount > ActiveUpkeepIDBatchSize { - maxCount = ActiveUpkeepIDBatchSize - } - - batchIDs, err := r.registry.GetActiveUpkeepIDs(opts, big.NewInt(startIndex), big.NewInt(maxCount)) - if err != nil { - return nil, fmt.Errorf("%w: failed to get active upkeep IDs from index %d to %d (both inclusive)", err, startIndex, startIndex+maxCount-1) - } - - ids = append(ids, batchIDs...) - } - - return ids, nil -} - -// updateTriggerConfig updates the trigger config for an upkeep. it will re-register a filter for this upkeep. -func (r *EvmRegistry) updateTriggerConfig(ctx context.Context, id *big.Int, cfg []byte, logBlock uint64) error { - uid := &ocr2keepers.UpkeepIdentifier{} - uid.FromBigInt(id) - switch core.GetUpkeepType(*uid) { - case autotypes.LogTrigger: - if len(cfg) == 0 { - fetched, err := r.fetchTriggerConfig(ctx, id) - if err != nil { - return fmt.Errorf("failed to fetch log upkeep config: %w", err) - } - cfg = fetched - } - parsed, err := r.packer.UnpackLogTriggerConfig(cfg) - if err != nil { - // Upkeep has been setup with improper config. Log a warning and ignore the upkeep. - r.lggr.Warnw("failed to unpack log upkeep config", "upkeepID", id.String(), "err", err) - return nil - } - if err := r.logEventProvider.RegisterFilter(ctx, logprovider.FilterOptions{ - TriggerConfig: logprovider.LogTriggerConfig(parsed), - UpkeepID: id, - UpdateBlock: logBlock, - }); err != nil { - return fmt.Errorf("failed to register log filter: %w", err) - } - r.lggr.Debugw("registered log filter", "upkeepID", id.String(), "cfg", parsed) - default: - } - return nil -} - -// fetchTriggerConfig fetches trigger config in raw bytes for an upkeep. -func (r *EvmRegistry) fetchTriggerConfig(ctx context.Context, id *big.Int) ([]byte, error) { - opts := r.buildCallOpts(ctx, nil) - cfg, err := r.registry.GetUpkeepTriggerConfig(opts, id) - if err != nil { - r.lggr.Warnw("failed to get trigger config", "err", err) - return nil, err - } - return cfg, nil -} - -// fetchUpkeepOffchainConfig fetches upkeep offchain config in raw bytes for an upkeep. -func (r *EvmRegistry) fetchUpkeepOffchainConfig(ctx context.Context, id *big.Int) ([]byte, error) { - opts := r.buildCallOpts(ctx, nil) - ui, err := r.registry.GetUpkeep(opts, id) - if err != nil { - return []byte{}, err - } - return ui.OffchainConfig, nil -} diff --git a/pkg/automation/v21/registry_check_pipeline.go b/pkg/automation/v21/registry_check_pipeline.go deleted file mode 100644 index 315d0328cb..0000000000 --- a/pkg/automation/v21/registry_check_pipeline.go +++ /dev/null @@ -1,403 +0,0 @@ -package evm - -import ( - "context" - "errors" - "fmt" - "math/big" - "strings" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rpc" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/gasprice" -) - -const ( - // checkBlockTooOldRange is the number of blocks that can be behind the latest block before - // we return a CheckBlockTooOld error - checkBlockTooOldRange = 128 - zeroAddress = "0x0000000000000000000000000000000000000000" -) - -type checkResult struct { - cr []ocr2keepers.CheckResult - err error -} - -func (r *EvmRegistry) CheckUpkeeps(ctx context.Context, keys ...ocr2keepers.UpkeepPayload) ([]ocr2keepers.CheckResult, error) { - r.lggr.Debugw("Checking upkeeps", "upkeeps", keys) - for i := range keys { - if keys[i].Trigger.BlockNumber == 0 { // check block was not populated, use latest - latest := r.bs.latestBlock.Load() - if latest == nil { - return nil, errors.New("no latest block available") - } - copy(keys[i].Trigger.BlockHash[:], latest.Hash[:]) - keys[i].Trigger.BlockNumber = latest.Number - r.lggr.Debugf("Check upkeep key had no trigger block number, using latest block %v", keys[i].Trigger.BlockNumber) - } - } - - chResult := make(chan checkResult, 1) - - r.threadCtrl.GoCtx(ctx, func(ctx context.Context) { - r.doCheck(ctx, keys, chResult) - }) - - select { - case rs := <-chResult: - result := make([]ocr2keepers.CheckResult, len(rs.cr)) - copy(result, rs.cr) - return result, rs.err - case <-ctx.Done(): - // safety on context done to provide an error on context cancellation - // contract calls through the geth wrappers are a bit of a black box - // so this safety net ensures contexts are fully respected and contract - // call functions have a more graceful closure outside the scope of - // CheckUpkeep needing to return immediately. - return nil, fmt.Errorf("%w: failed to check upkeep on registry", ErrContextCancelled) - } -} - -func (r *EvmRegistry) doCheck(ctx context.Context, keys []ocr2keepers.UpkeepPayload, chResult chan checkResult) { - upkeepResults, err := r.checkUpkeeps(ctx, keys) - if err != nil { - chResult <- checkResult{ - err: err, - } - return - } - - upkeepResults = r.streams.Lookup(ctx, upkeepResults) - - upkeepResults, err = r.simulatePerformUpkeeps(ctx, upkeepResults) - if err != nil { - chResult <- checkResult{ - err: err, - } - return - } - - chResult <- checkResult{ - cr: upkeepResults, - } -} - -// getBlockAndUpkeepId retrieves check block number, block hash from trigger and upkeep id -func (r *EvmRegistry) getBlockAndUpkeepId(upkeepID ocr2keepers.UpkeepIdentifier, trigger ocr2keepers.Trigger) (*big.Int, common.Hash, *big.Int) { - block := new(big.Int).SetInt64(int64(trigger.BlockNumber)) - return block, common.BytesToHash(trigger.BlockHash[:]), upkeepID.BigInt() -} - -func (r *EvmRegistry) getBlockHash(ctx context.Context, blockNumber *big.Int) (common.Hash, error) { - blocks, err := r.poller.GetBlocksRange(ctx, []uint64{blockNumber.Uint64()}) - if err != nil { - return [32]byte{}, err - } - if len(blocks) == 0 { - return [32]byte{}, fmt.Errorf("could not find block %d in log poller", blockNumber.Uint64()) - } - - return blocks[0].BlockHash, nil -} - -// verifyCheckBlock checks that the check block and hash are valid, returns the pipeline execution state and retryable -func (r *EvmRegistry) verifyCheckBlock(ctx context.Context, checkBlock, upkeepId *big.Int, checkHash common.Hash) (state encoding.PipelineExecutionState, retryable bool) { - // verify check block number and hash are valid - h, ok := r.bs.queryBlocksMap(checkBlock.Int64()) - // if this block number/hash combo exists in block subscriber, this check block and hash still exist on chain and are valid - // the block hash in block subscriber might be slightly outdated, if it doesn't match then we fetch the latest from RPC. - if ok && h == checkHash.Hex() { - r.lggr.Debugf("check block hash %s exists on chain at block number %d for upkeepId %s", checkHash.Hex(), checkBlock, upkeepId) - return encoding.NoPipelineError, false - } - r.lggr.Warnf("check block %s does not exist in block subscriber or hash does not match for upkeepId %s. this may be caused by block subscriber outdated due to re-org, querying eth client to confirm", checkBlock, upkeepId) - b, err := r.getBlockHash(ctx, checkBlock) - if err != nil { - r.lggr.Warnf("failed to query block %s: %s", checkBlock, err.Error()) - return encoding.RpcFlakyFailure, true - } - if checkHash.Hex() != b.Hex() { - r.lggr.Warnf("check block %s hash do not match. %s from block subscriber vs %s from trigger for upkeepId %s", checkBlock, h, checkHash.Hex(), upkeepId) - return encoding.CheckBlockInvalid, false - } - return encoding.NoPipelineError, false -} - -// verifyLogExists checks that the log still exists on chain, returns failure reason, pipeline error, and retryable -func (r *EvmRegistry) verifyLogExists(ctx context.Context, upkeepId *big.Int, p ocr2keepers.UpkeepPayload) (encoding.UpkeepFailureReason, encoding.PipelineExecutionState, bool) { - logBlockNumber := int64(p.Trigger.LogTriggerExtension.BlockNumber) - logBlockHash := common.BytesToHash(p.Trigger.LogTriggerExtension.BlockHash[:]) - checkBlockHash := common.BytesToHash(p.Trigger.BlockHash[:]) - if checkBlockHash.String() == logBlockHash.String() { - // log verification would be covered by checkBlock verification as they are the same. Return early from - // log verificaion. This also helps in preventing some racy conditions when rpc does not return the tx receipt - // for a very new log - return encoding.UpkeepFailureReasonNone, encoding.NoPipelineError, false - } - // if log block number is populated, check log block number and block hash - if logBlockNumber != 0 { - h, ok := r.bs.queryBlocksMap(logBlockNumber) - // if this block number/hash combo exists in block subscriber, this block and tx still exists on chain and is valid - // the block hash in block subscriber might be slightly outdated, if it doesn't match then we fetch the latest from RPC. - if ok && h == logBlockHash.Hex() { - r.lggr.Debugf("tx hash %s exists on chain at block number %d, block hash %s for upkeepId %s", hexutil.Encode(p.Trigger.LogTriggerExtension.TxHash[:]), logBlockNumber, logBlockHash.Hex(), upkeepId) - return encoding.UpkeepFailureReasonNone, encoding.NoPipelineError, false - } - // if this block does not exist in the block subscriber, the block which this log lived on was probably re-orged - // hence, check eth client for this log's tx hash to confirm - r.lggr.Debugf("log block %d does not exist in block subscriber or block hash does not match for upkeepId %s. this may be caused by block subscriber outdated due to re-org, querying eth client to confirm", logBlockNumber, upkeepId) - } else { - r.lggr.Debugf("log block not provided, querying eth client for tx hash %s for upkeepId %s", hexutil.Encode(p.Trigger.LogTriggerExtension.TxHash[:]), upkeepId) - } - // query eth client as a fallback - bn, bh, err := core.GetTxBlock(ctx, r.client, p.Trigger.LogTriggerExtension.TxHash) - if err != nil { - // primitive way of checking errors - if strings.Contains(err.Error(), "missing required field") || strings.Contains(err.Error(), "not found") { - return encoding.UpkeepFailureReasonTxHashNoLongerExists, encoding.NoPipelineError, false - } - r.lggr.Warnf("failed to query tx hash %s for upkeepId %s: %s", hexutil.Encode(p.Trigger.LogTriggerExtension.TxHash[:]), upkeepId, err.Error()) - return encoding.UpkeepFailureReasonNone, encoding.RpcFlakyFailure, true - } - if bn == nil { - r.lggr.Warnf("tx hash %s does not exist on chain for upkeepId %s.", hexutil.Encode(p.Trigger.LogTriggerExtension.TxHash[:]), upkeepId) - return encoding.UpkeepFailureReasonTxHashNoLongerExists, encoding.NoPipelineError, false - } - if bh.Hex() != logBlockHash.Hex() { - r.lggr.Warnf("tx hash %s reorged from expected blockhash %s to %s for upkeepId %s.", hexutil.Encode(p.Trigger.LogTriggerExtension.TxHash[:]), logBlockHash.Hex(), bh.Hex(), upkeepId) - return encoding.UpkeepFailureReasonTxHashReorged, encoding.NoPipelineError, false - } - r.lggr.Debugf("tx hash %s exists on chain for upkeepId %s", hexutil.Encode(p.Trigger.LogTriggerExtension.TxHash[:]), upkeepId) - return encoding.UpkeepFailureReasonNone, encoding.NoPipelineError, false -} - -func (r *EvmRegistry) checkUpkeeps(ctx context.Context, payloads []ocr2keepers.UpkeepPayload) ([]ocr2keepers.CheckResult, error) { - var ( - checkReqs []rpc.BatchElem - checkResults []*string - results = make([]ocr2keepers.CheckResult, len(payloads)) - ) - indices := map[int]int{} - - for i, p := range payloads { - if ctx.Err() != nil { - return nil, context.Cause(ctx) - } - block, checkHash, upkeepId := r.getBlockAndUpkeepId(p.UpkeepID, p.Trigger) - state, retryable := r.verifyCheckBlock(ctx, block, upkeepId, checkHash) - if state != encoding.NoPipelineError { - results[i] = encoding.GetIneligibleCheckResultWithoutPerformData(p, encoding.UpkeepFailureReasonNone, state, retryable) - continue - } - - opts := r.buildCallOpts(ctx, block) - var payload []byte - var err error - uid := &ocr2keepers.UpkeepIdentifier{} - uid.FromBigInt(upkeepId) - switch core.GetUpkeepType(*uid) { - case types.LogTrigger: - reason, state, retryable := r.verifyLogExists(ctx, upkeepId, p) - if reason != encoding.UpkeepFailureReasonNone || state != encoding.NoPipelineError { - results[i] = encoding.GetIneligibleCheckResultWithoutPerformData(p, reason, state, retryable) - continue - } - - // check data will include the log trigger config - payload, err = r.abi.Pack("checkUpkeep", upkeepId, p.CheckData) - if err != nil { - // pack error, no retryable - r.lggr.Warnf("failed to pack log trigger checkUpkeep data for upkeepId %s with check data %s: %s", upkeepId, hexutil.Encode(p.CheckData), err) - results[i] = encoding.GetIneligibleCheckResultWithoutPerformData(p, encoding.UpkeepFailureReasonNone, encoding.PackUnpackDecodeFailed, false) - continue - } - default: - // checkUpkeep is overloaded on the contract for conditionals and log upkeeps - // Need to use the first function (checkUpkeep0) for conditionals - payload, err = r.abi.Pack("checkUpkeep0", upkeepId) - if err != nil { - // pack error, no retryable - r.lggr.Warnf("failed to pack conditional checkUpkeep data for upkeepId %s with check data %s: %s", upkeepId, hexutil.Encode(p.CheckData), err) - results[i] = encoding.GetIneligibleCheckResultWithoutPerformData(p, encoding.UpkeepFailureReasonNone, encoding.PackUnpackDecodeFailed, false) - continue - } - } - indices[len(checkReqs)] = i - results[i] = encoding.GetIneligibleCheckResultWithoutPerformData(p, encoding.UpkeepFailureReasonNone, encoding.NoPipelineError, false) - - args := []any{ - map[string]any{ - "from": zeroAddress, - "to": r.addr.Hex(), - "data": hexutil.Bytes(payload), - }, - } - - if opts.BlockNumber != nil { - args = append(args, hexutil.EncodeBig(opts.BlockNumber)) - } - - var result string - checkReqs = append(checkReqs, rpc.BatchElem{ - Method: "eth_call", - Args: args, - Result: &result, - }) - - checkResults = append(checkResults, &result) - } - - if len(checkResults) > 0 { - // In contrast to CallContext, BatchCallContext only returns errors that have occurred - // while sending the request. Any error specific to a request is reported through the - // Error field of the corresponding BatchElem. - // hence, if BatchCallContext returns an error, it will be an error which will terminate the pipeline - if err := r.client.BatchCallContext(ctx, checkReqs); err != nil { - r.lggr.Errorf("failed to batch call for checkUpkeeps: %s", err) - return nil, err - } - } - - for i, req := range checkReqs { - index := indices[i] - if req.Error != nil { - latestBlockNumber := int64(0) - latestBlock := r.bs.latestBlock.Load() - if latestBlock != nil { - latestBlockNumber = int64(latestBlock.Number) - } - checkBlock, _, _ := r.getBlockAndUpkeepId(payloads[index].UpkeepID, payloads[index].Trigger) - // Exploratory: remove reliance on primitive way of checking errors - blockNotFound := (strings.Contains(req.Error.Error(), "header not found") || strings.Contains(req.Error.Error(), "missing trie node")) - if blockNotFound && latestBlockNumber-checkBlock.Int64() > checkBlockTooOldRange { - // Check block not found in RPC and it is too old, non-retryable error - r.lggr.Warnf("block not found error encountered in check result for upkeepId %s, check block %d, latest block %d: %s", results[index].UpkeepID.String(), checkBlock.Int64(), latestBlockNumber, req.Error) - results[index].Retryable = false - results[index].PipelineExecutionState = uint8(encoding.CheckBlockTooOld) - } else { - // individual upkeep failed in a batch call, likely a flay RPC error, consider retryable - r.lggr.Warnf("rpc error encountered in check result for upkeepId %s: %s", results[index].UpkeepID.String(), req.Error) - results[index].Retryable = true - results[index].PipelineExecutionState = uint8(encoding.RpcFlakyFailure) - } - } else { - var err error - results[index], err = r.packer.UnpackCheckResult(payloads[index], *checkResults[i]) - if err != nil { - r.lggr.Warnf("failed to unpack check result: %s", err) - } - } - } - - return results, nil -} - -func (r *EvmRegistry) simulatePerformUpkeeps(ctx context.Context, checkResults []ocr2keepers.CheckResult) ([]ocr2keepers.CheckResult, error) { - var ( - performReqs = make([]rpc.BatchElem, 0, len(checkResults)) - performResults = make([]*string, 0, len(checkResults)) - performToKeyIdx = make([]int, 0, len(checkResults)) - ) - - for i, cr := range checkResults { - if !cr.Eligible { - continue - } - - block, _, upkeepId := r.getBlockAndUpkeepId(cr.UpkeepID, cr.Trigger) - - oc, err := r.fetchUpkeepOffchainConfig(ctx, upkeepId) - if err != nil { - // this is mostly caused by RPC flakiness - r.lggr.Errorw("failed get offchain config, gas price check will be disabled", "err", err, "upkeepId", upkeepId, "block", block) - } - fr := gasprice.CheckGasPrice(ctx, upkeepId, oc, r.ge, r.lggr) - if uint8(fr) == uint8(encoding.UpkeepFailureReasonGasPriceTooHigh) { - r.lggr.Debugf("upkeep %s upkeep failure reason is %d", upkeepId, fr) - checkResults[i].Eligible = false - checkResults[i].Retryable = false - checkResults[i].IneligibilityReason = uint8(fr) - continue - } - - // Since checkUpkeep is true, simulate perform upkeep to ensure it doesn't revert - payload, err := r.abi.Pack("simulatePerformUpkeep", upkeepId, cr.PerformData) - if err != nil { - // pack failed, not retryable - r.lggr.Warnf("failed to pack perform data %s for %s: %s", hexutil.Encode(cr.PerformData), upkeepId, err) - checkResults[i].Eligible = false - checkResults[i].PipelineExecutionState = uint8(encoding.PackUnpackDecodeFailed) - continue - } - - opts := r.buildCallOpts(ctx, block) - - args := []any{ - map[string]any{ - "from": zeroAddress, - "to": r.addr.Hex(), - "data": hexutil.Bytes(payload), - }, - } - - if opts.BlockNumber != nil { - args = append(args, hexutil.EncodeBig(opts.BlockNumber)) - } - - var result string - performReqs = append(performReqs, rpc.BatchElem{ - Method: "eth_call", - Args: args, - Result: &result, - }) - - performResults = append(performResults, &result) - performToKeyIdx = append(performToKeyIdx, i) - } - - if len(performReqs) > 0 { - if err := r.client.BatchCallContext(ctx, performReqs); err != nil { - r.lggr.Errorf("failed to batch call for simulatePerformUpkeeps: %s", err) - return nil, err - } - } - - for i, req := range performReqs { - idx := performToKeyIdx[i] - if req.Error != nil { - // individual upkeep failed in a batch call, retryable - r.lggr.Warnf("failed to simulate upkeepId %s: %s", checkResults[idx].UpkeepID.String(), req.Error) - checkResults[idx].Retryable = true - checkResults[idx].Eligible = false - checkResults[idx].PipelineExecutionState = uint8(encoding.RpcFlakyFailure) - continue - } - - state, simulatePerformSuccess, err := r.packer.UnpackPerformResult(*performResults[i]) - if err != nil { - // unpack failed, not retryable - r.lggr.Warnf("failed to unpack simulate performUpkeep result for upkeepId %s for state %d: %s", checkResults[idx].UpkeepID.String(), state, req.Error) - checkResults[idx].Retryable = false - checkResults[idx].Eligible = false - checkResults[idx].PipelineExecutionState = uint8(state) - continue - } - - if !simulatePerformSuccess { - r.lggr.Warnf("upkeepId %s is not eligible after simulation of perform", checkResults[idx].UpkeepID.String()) - checkResults[performToKeyIdx[i]].Eligible = false - checkResults[performToKeyIdx[i]].IneligibilityReason = uint8(encoding.UpkeepFailureReasonSimulationFailed) - } - } - - return checkResults, nil -} diff --git a/pkg/automation/v21/registry_check_pipeline_test.go b/pkg/automation/v21/registry_check_pipeline_test.go deleted file mode 100644 index 0c3fa02c7f..0000000000 --- a/pkg/automation/v21/registry_check_pipeline_test.go +++ /dev/null @@ -1,708 +0,0 @@ -package evm - -import ( - "context" - "errors" - "math/big" - "strings" - "sync/atomic" - "testing" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc" - "github.com/patrickmn/go-cache" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - autotypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/streams_lookup_compatible_interface" - "github.com/smartcontractkit/chainlink-evm/pkg/client/clienttest" - gasMocks "github.com/smartcontractkit/chainlink-evm/pkg/gas/mocks" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/mocks" -) - -func TestRegistry_GetBlockAndUpkeepId(t *testing.T) { - r := &EvmRegistry{} - tests := []struct { - name string - input ocr2keepers.UpkeepPayload - wantBlock *big.Int - wantUpkeep *big.Int - }{ - { - "happy flow", - ocr2keepers.UpkeepPayload{ - UpkeepID: core.UpkeepIDFromInt("10"), - Trigger: ocr2keepers.Trigger{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x1"), - }, - }, - big.NewInt(1), - big.NewInt(10), - }, - { - "empty trigger", - ocr2keepers.UpkeepPayload{ - UpkeepID: core.UpkeepIDFromInt("10"), - }, - big.NewInt(0), - big.NewInt(10), - }, - { - "empty payload", - ocr2keepers.UpkeepPayload{}, - big.NewInt(0), - big.NewInt(0), - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - block, _, upkeep := r.getBlockAndUpkeepId(tc.input.UpkeepID, tc.input.Trigger) - assert.Equal(t, tc.wantBlock, block) - assert.Equal(t, tc.wantUpkeep.String(), upkeep.String()) - }) - } -} - -func TestRegistry_VerifyCheckBlock(t *testing.T) { - lggr := logger.Test(t) - upkeepId := ocr2keepers.UpkeepIdentifier{} - upkeepId.FromBigInt(big.NewInt(12345)) - tests := []struct { - name string - checkBlock *big.Int - latestBlock *ocr2keepers.BlockKey - upkeepId *big.Int - checkHash common.Hash - payload ocr2keepers.UpkeepPayload - blocks map[int64]string - poller logpoller.LogPoller - state encoding.PipelineExecutionState - retryable bool - makeEthCall bool - }{ - { - name: "for an invalid check block number, if hash does not match the check hash, return CheckBlockInvalid", - checkBlock: big.NewInt(500), - latestBlock: &ocr2keepers.BlockKey{Number: 560}, - upkeepId: big.NewInt(12345), - checkHash: common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewTrigger(500, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83")), - WorkID: "work", - }, - poller: &mockLogPoller{ - GetBlocksRangeFn: func(ctx context.Context, numbers []uint64) ([]logpoller.Block, error) { - return []logpoller.Block{ - { - BlockHash: common.HexToHash("abcdef"), - }, - }, nil - }, - }, - state: encoding.CheckBlockInvalid, - retryable: false, - makeEthCall: true, - }, - { - name: "for an invalid check block number, if hash does match the check hash, return NoPipelineError", - checkBlock: big.NewInt(500), - latestBlock: &ocr2keepers.BlockKey{Number: 560}, - upkeepId: big.NewInt(12345), - checkHash: common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewTrigger(500, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83")), - WorkID: "work", - }, - poller: &mockLogPoller{ - GetBlocksRangeFn: func(ctx context.Context, numbers []uint64) ([]logpoller.Block, error) { - return []logpoller.Block{ - { - BlockHash: common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), - }, - }, nil - }, - }, - state: encoding.NoPipelineError, - retryable: false, - makeEthCall: true, - }, - { - name: "check block hash does not match", - checkBlock: big.NewInt(500), - latestBlock: &ocr2keepers.BlockKey{Number: 560}, - upkeepId: big.NewInt(12345), - checkHash: common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewTrigger(500, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83")), - WorkID: "work", - }, - poller: &mockLogPoller{ - GetBlocksRangeFn: func(ctx context.Context, numbers []uint64) ([]logpoller.Block, error) { - return []logpoller.Block{ - { - BlockHash: common.HexToHash("0xcba5cf9e2bb32373c76015384e1098912d9510a72481c78057fcb088209167de"), - }, - }, nil - }, - }, - blocks: map[int64]string{ - 500: "0xa518faeadcc423338c62572da84dda35fe44b34f521ce88f6081b703b250cca4", - }, - state: encoding.CheckBlockInvalid, - }, - { - name: "check block is valid", - checkBlock: big.NewInt(500), - latestBlock: &ocr2keepers.BlockKey{Number: 560}, - upkeepId: big.NewInt(12345), - checkHash: common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewTrigger(500, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83")), - WorkID: "work", - }, - blocks: map[int64]string{ - 500: "0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83", - }, - state: encoding.NoPipelineError, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - bs := &BlockSubscriber{ - latestBlock: atomic.Pointer[ocr2keepers.BlockKey]{}, - blocks: tc.blocks, - } - bs.latestBlock.Store(tc.latestBlock) - e := &EvmRegistry{ - lggr: logger.Sugared(lggr), - bs: bs, - poller: tc.poller, - } - if tc.makeEthCall { - client := new(clienttest.Client) - client.On("BlockByNumber", mock.Anything, tc.checkBlock).Return(nil, errors.New("error")) - e.client = client - } - - state, retryable := e.verifyCheckBlock(t.Context(), tc.checkBlock, tc.upkeepId, tc.checkHash) - assert.Equal(t, tc.state, state) - assert.Equal(t, tc.retryable, retryable) - }) - } -} - -type mockLogPoller struct { - logpoller.LogPoller - GetBlocksRangeFn func(ctx context.Context, numbers []uint64) ([]logpoller.Block, error) - IndexedLogsFn func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) -} - -func (p *mockLogPoller) GetBlocksRange(ctx context.Context, numbers []uint64) ([]logpoller.Block, error) { - return p.GetBlocksRangeFn(ctx, numbers) -} - -func (p *mockLogPoller) IndexedLogs(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - return p.IndexedLogsFn(ctx, eventSig, address, topicIndex, topicValues, confs) -} - -func TestRegistry_VerifyLogExists(t *testing.T) { - lggr := logger.Test(t) - upkeepId := ocr2keepers.UpkeepIdentifier{} - upkeepId.FromBigInt(big.NewInt(12345)) - - extension := &ocr2keepers.LogTriggerExtension{ - TxHash: common.HexToHash("0xc8def8abdcf3a4eaaf6cc13bff3e4e2a7168d86ea41dbbf97451235aa76c3651"), - Index: 0, - BlockHash: common.HexToHash("0x3df0e926f3e21ec1195ffe007a2899214905eb02e768aa89ce0b94accd7f3d71"), - BlockNumber: 500, - } - extension1 := &ocr2keepers.LogTriggerExtension{ - TxHash: common.HexToHash("0xc8def8abdcf3a4eaaf6cc13bff3e4e2a7168d86ea41dbbf97451235aa76c3651"), - Index: 0, - BlockHash: common.HexToHash("0x3df0e926f3e21ec1195ffe007a2899214905eb02e768aa89ce0b94accd7f3d71"), - BlockNumber: 0, - } - - tests := []struct { - name string - upkeepId *big.Int - payload ocr2keepers.UpkeepPayload - blocks map[int64]string - makeEthCall bool - reason encoding.UpkeepFailureReason - state encoding.PipelineExecutionState - retryable bool - ethCallErr error - receipt *evmtypes.Receipt - }{ - { - name: "log block number invalid", - upkeepId: big.NewInt(12345), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewLogTrigger(550, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), extension), - WorkID: "work", - }, - reason: encoding.UpkeepFailureReasonNone, - state: encoding.RpcFlakyFailure, - retryable: true, - makeEthCall: true, - ethCallErr: errors.New("error"), - }, - { - name: "log block no longer exists", - upkeepId: big.NewInt(12345), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewLogTrigger(550, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), extension), - WorkID: "work", - }, - reason: encoding.UpkeepFailureReasonTxHashNoLongerExists, - retryable: false, - makeEthCall: true, - blocks: map[int64]string{ - 500: "0xb2173b4b75f23f56b7b2b6b2cc5fa9ed1079b9d1655b12b40fdb4dbf59006419", - }, - receipt: &evmtypes.Receipt{Status: 0}, - }, - { - name: "eth client returns a matching block but different hash", - upkeepId: big.NewInt(12345), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewLogTrigger(550, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), extension1), - WorkID: "work", - }, - reason: encoding.UpkeepFailureReasonTxHashReorged, - retryable: false, - blocks: map[int64]string{ - 500: "0xa518faeadcc423338c62572da84dda35fe44b34f521ce88f6081b703b250cca4", - }, - makeEthCall: true, - receipt: &evmtypes.Receipt{ - Status: 1, - BlockNumber: big.NewInt(550), - BlockHash: common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), - }, - }, - { - name: "eth client returns a matching block", - upkeepId: big.NewInt(12345), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewLogTrigger(550, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), extension1), - WorkID: "work", - }, - reason: encoding.UpkeepFailureReasonNone, - retryable: false, - blocks: map[int64]string{ - 500: "0xa518faeadcc423338c62572da84dda35fe44b34f521ce88f6081b703b250cca4", - }, - makeEthCall: true, - receipt: &evmtypes.Receipt{ - Status: 1, - BlockNumber: big.NewInt(550), - BlockHash: common.HexToHash("0x3df0e926f3e21ec1195ffe007a2899214905eb02e768aa89ce0b94accd7f3d71"), - }, - }, - { - name: "log block is valid", - upkeepId: big.NewInt(12345), - payload: ocr2keepers.UpkeepPayload{ - UpkeepID: upkeepId, - Trigger: ocr2keepers.NewLogTrigger(550, common.HexToHash("0x5bff03de234fe771ac0d685f9ee0fb0b757ea02ec9e6f10e8e2ee806db1b6b83"), extension), - WorkID: "work", - }, - reason: encoding.UpkeepFailureReasonNone, - retryable: false, - blocks: map[int64]string{ - 500: "0x3df0e926f3e21ec1195ffe007a2899214905eb02e768aa89ce0b94accd7f3d71", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := t.Context() - bs := &BlockSubscriber{ - blocks: tc.blocks, - } - e := &EvmRegistry{ - lggr: logger.Sugared(lggr), - bs: bs, - } - - if tc.makeEthCall { - client := new(clienttest.Client) - client.On("CallContext", mock.Anything, mock.Anything, "eth_getTransactionReceipt", common.BytesToHash(tc.payload.Trigger.LogTriggerExtension.TxHash[:])). - Return(tc.ethCallErr).Run(func(args mock.Arguments) { - if tc.receipt != nil { - res := args.Get(1).(*evmtypes.Receipt) - res.Status = tc.receipt.Status - res.TxHash = tc.receipt.TxHash - res.BlockNumber = tc.receipt.BlockNumber - res.BlockHash = tc.receipt.BlockHash - } - }) - e.client = client - } - - reason, state, retryable := e.verifyLogExists(ctx, tc.upkeepId, tc.payload) - assert.Equal(t, tc.reason, reason) - assert.Equal(t, tc.state, state) - assert.Equal(t, tc.retryable, retryable) - }) - } -} - -func TestRegistry_CheckUpkeeps(t *testing.T) { - lggr := logger.Test(t) - uid0 := core.GenUpkeepID(autotypes.UpkeepType(0), "p0") - uid1 := core.GenUpkeepID(autotypes.UpkeepType(1), "p1") - uid2 := core.GenUpkeepID(autotypes.UpkeepType(1), "p2") - - extension1 := &ocr2keepers.LogTriggerExtension{ - TxHash: common.HexToHash("0xc8def8abdcf3a4eaaf6cc13bff3e4e2a7168d86ea41dbbf97451235aa76c3651"), - Index: 0, - BlockHash: common.HexToHash("0x0919c83363b439ea634ce2b576cf3e30db26b340fb7a12058c2fcc401bd04ba0"), - BlockNumber: 550, - } - extension2 := &ocr2keepers.LogTriggerExtension{ - TxHash: common.HexToHash("0xc8def8abdcf3a4eaaf6cc13bff3e4e2a7168d86ea41dbbf97451235aa76c3651"), - Index: 0, - BlockHash: common.HexToHash("0x9840e5b709bfccf6a1b44f34c884bc39403f57923f3f5ead6243cc090546b857"), - BlockNumber: 550, - } - - trigger0 := ocr2keepers.NewTrigger(575, common.HexToHash("0x1c77db0abe32327cf3ea9de2aadf79876f9e6b6dfcee9d4719a8a2dc8ca289d0")) - trigger1 := ocr2keepers.NewLogTrigger(560, common.HexToHash("0x9840e5b709bfccf6a1b44f34c884bc39403f57923f3f5ead6243cc090546b857"), extension1) - trigger2 := ocr2keepers.NewLogTrigger(570, common.HexToHash("0x1222d75217e2dd461cc77e4091c37abe76277430d97f1963a822b4e94ebb83fc"), extension2) - - tests := []struct { - name string - inputs []ocr2keepers.UpkeepPayload - blocks map[int64]string - latestBlock *ocr2keepers.BlockKey - results []ocr2keepers.CheckResult - err error - ethCalls map[string]bool - receipts map[string]*evmtypes.Receipt - poller logpoller.LogPoller - ethCallErrors map[string]error - }{ - { - name: "check upkeeps with different upkeep types", - inputs: []ocr2keepers.UpkeepPayload{ - { - UpkeepID: uid0, - Trigger: trigger0, - WorkID: "work0", - }, - { - UpkeepID: uid1, - Trigger: trigger1, - WorkID: "work1", - }, - { - UpkeepID: uid2, - Trigger: trigger2, - WorkID: "work2", - // check data byte slice length cannot be odd number, abi pack error - CheckData: []byte{0, 0, 0, 0, 1}, - }, - }, - blocks: map[int64]string{ - 550: "0x9840e5b709bfccf6a1b44f34c884bc39403f57923f3f5ead6243cc090546b857", - 560: "0x9840e5b709bfccf6a1b44f34c884bc39403f57923f3f5ead6243cc090546b857", - 570: "0x1222d75217e2dd461cc77e4091c37abe76277430d97f1963a822b4e94ebb83fc", - 575: "0x9840e5b709bfccf6a1b44f34c884bc39403f57923f3f5ead6243cc090546b857", - }, - latestBlock: &ocr2keepers.BlockKey{Number: 580}, - results: []ocr2keepers.CheckResult{ - { - PipelineExecutionState: uint8(encoding.CheckBlockInvalid), - Retryable: false, - Eligible: false, - IneligibilityReason: 0, - UpkeepID: uid0, - Trigger: trigger0, - WorkID: "work0", - GasAllocated: 0, - PerformData: nil, - FastGasWei: big.NewInt(0), - LinkNative: big.NewInt(0), - }, - { - PipelineExecutionState: uint8(encoding.RpcFlakyFailure), - Retryable: true, - Eligible: false, - IneligibilityReason: 0, - UpkeepID: uid1, - Trigger: trigger1, - WorkID: "work1", - GasAllocated: 0, - PerformData: nil, - FastGasWei: big.NewInt(0), - LinkNative: big.NewInt(0), - }, - { - PipelineExecutionState: uint8(encoding.PackUnpackDecodeFailed), - Retryable: false, - Eligible: false, - IneligibilityReason: 0, - UpkeepID: uid2, - Trigger: trigger2, - WorkID: "work2", - GasAllocated: 0, - PerformData: nil, - FastGasWei: big.NewInt(0), - LinkNative: big.NewInt(0), - }, - }, - ethCalls: map[string]bool{ - uid1.String(): true, - }, - receipts: map[string]*evmtypes.Receipt{}, - poller: &mockLogPoller{ - GetBlocksRangeFn: func(ctx context.Context, numbers []uint64) ([]logpoller.Block, error) { - return []logpoller.Block{ - { - BlockHash: common.HexToHash("0xcba5cf9e2bb32373c76015384e1098912d9510a72481c78057fcb088209167de"), - }, - }, nil - }, - }, - ethCallErrors: map[string]error{ - uid1.String(): errors.New("error"), - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - bs := &BlockSubscriber{ - latestBlock: atomic.Pointer[ocr2keepers.BlockKey]{}, - blocks: tc.blocks, - } - bs.latestBlock.Store(tc.latestBlock) - e := &EvmRegistry{ - lggr: logger.Sugared(lggr), - bs: bs, - poller: tc.poller, - } - client := new(clienttest.Client) - for _, i := range tc.inputs { - uid := i.UpkeepID.String() - if tc.ethCalls[uid] { - client.On("CallContext", mock.Anything, mock.Anything, "eth_getTransactionReceipt", common.HexToHash("0xc8def8abdcf3a4eaaf6cc13bff3e4e2a7168d86ea41dbbf97451235aa76c3651")). - Return(tc.ethCallErrors[uid]).Run(func(args mock.Arguments) { - receipt := tc.receipts[uid] - if receipt != nil { - res := args.Get(1).(*evmtypes.Receipt) - res.Status = receipt.Status - res.TxHash = receipt.TxHash - res.BlockNumber = receipt.BlockNumber - res.BlockHash = receipt.BlockHash - } - }) - } - } - e.client = client - - results, err := e.checkUpkeeps(t.Context(), tc.inputs) - assert.Equal(t, tc.results, results) - assert.Equal(t, tc.err, err) - }) - } -} - -func TestRegistry_SimulatePerformUpkeeps(t *testing.T) { - uid0 := core.GenUpkeepID(autotypes.UpkeepType(0), "p0") - uid1 := core.GenUpkeepID(autotypes.UpkeepType(1), "p1") - uid2 := core.GenUpkeepID(autotypes.UpkeepType(1), "p2") - - extension1 := &ocr2keepers.LogTriggerExtension{ - TxHash: common.HexToHash("0xc8def8abdcf3a4eaaf6cc13bff3e4e2a7168d86ea41dbbf97451235aa76c3651"), - Index: 0, - BlockHash: common.HexToHash("0x9840e5b709bfccf6a1b44f34c884bc39403f57923f3f5ead6243cc090546b857"), - BlockNumber: 550, - } - - trigger0 := ocr2keepers.NewTrigger(150, common.HexToHash("0x1c77db0abe32327cf3ea9de2aadf79876f9e6b6dfcee9d4719a8a2dc8ca289d0")) - trigger1 := ocr2keepers.NewLogTrigger(570, common.HexToHash("0x1222d75217e2dd461cc77e4091c37abe76277430d97f1963a822b4e94ebb83fc"), extension1) - trigger2 := ocr2keepers.NewLogTrigger(570, common.HexToHash("0x1222d75217e2dd461cc77e4091c37abe76277430d97f1963a822b4e94ebb83fc"), extension1) - - cr0 := ocr2keepers.CheckResult{ - PipelineExecutionState: uint8(encoding.CheckBlockTooOld), - Retryable: false, - Eligible: false, - IneligibilityReason: 0, - UpkeepID: uid0, - Trigger: trigger0, - WorkID: "work0", - GasAllocated: 0, - PerformData: nil, - FastGasWei: big.NewInt(0), - LinkNative: big.NewInt(0), - } - - tests := []struct { - name string - inputs []ocr2keepers.CheckResult - results []ocr2keepers.CheckResult - err error - }{ - { - name: "simulate multiple upkeeps", - inputs: []ocr2keepers.CheckResult{ - cr0, - { - PipelineExecutionState: 0, - Retryable: false, - Eligible: true, - IneligibilityReason: 0, - UpkeepID: uid1, - Trigger: trigger1, - WorkID: "work1", - GasAllocated: 20000, - PerformData: []byte{0, 0, 0, 1, 2, 3}, - FastGasWei: big.NewInt(20000), - LinkNative: big.NewInt(20000), - }, - { - PipelineExecutionState: 0, - Retryable: false, - Eligible: true, - IneligibilityReason: 0, - UpkeepID: uid2, - Trigger: trigger2, - WorkID: "work2", - GasAllocated: 20000, - PerformData: []byte{0, 0, 0, 1, 2, 3}, - FastGasWei: big.NewInt(20000), - LinkNative: big.NewInt(20000), - }, - }, - results: []ocr2keepers.CheckResult{ - cr0, - { - PipelineExecutionState: uint8(encoding.RpcFlakyFailure), - Retryable: true, - Eligible: false, - IneligibilityReason: 0, - UpkeepID: uid1, - Trigger: trigger1, - WorkID: "work1", - GasAllocated: 20000, - PerformData: []byte{0, 0, 0, 1, 2, 3}, - FastGasWei: big.NewInt(20000), - LinkNative: big.NewInt(20000), - }, - { - PipelineExecutionState: uint8(encoding.PackUnpackDecodeFailed), - Retryable: false, - Eligible: false, - IneligibilityReason: 0, - UpkeepID: uid2, - Trigger: trigger2, - WorkID: "work2", - GasAllocated: 20000, - PerformData: []byte{0, 0, 0, 1, 2, 3}, - FastGasWei: big.NewInt(20000), - LinkNative: big.NewInt(20000), - }, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - e := setupEVMRegistry(t) - client := new(clienttest.Client) - client.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { - return len(b) == 2 && b[0].Method == "eth_call" && b[1].Method == "eth_call" - })).Return(nil). - Run(func(args mock.Arguments) { - be := args.Get(1).([]rpc.BatchElem) - be[0].Error = errors.New("error") - res := "0x0001" - be[1].Result = res - }).Once() - e.client = client - - mockReg := mocks.NewRegistry(t) - mockReg.On("GetUpkeep", mock.Anything, mock.Anything).Return( - encoding.UpkeepInfo{OffchainConfig: make([]byte, 0)}, - nil, - ).Times(2) - e.registry = mockReg - - results, err := e.simulatePerformUpkeeps(t.Context(), tc.inputs) - assert.Equal(t, tc.results, results) - assert.Equal(t, tc.err, err) - }) - } -} - -// setups up an evm registry for tests. -func setupEVMRegistry(t *testing.T) *EvmRegistry { - lggr := logger.Test(t) - addr := common.HexToAddress("0x6cA639822c6C241Fa9A7A6b5032F6F7F1C513CAD") - keeperRegistryABI, err := abi.JSON(strings.NewReader(ac.IAutomationV21PlusCommonABI)) - require.NoError(t, err, "need registry abi") - streamsLookupCompatibleABI, err := abi.JSON(strings.NewReader(streams_lookup_compatible_interface.StreamsLookupCompatibleInterfaceABI)) - require.NoError(t, err, "need mercury abi") - var logPoller logpoller.LogPoller - mockReg := mocks.NewRegistry(t) - mockHttpClient := mocks.NewHttpClient(t) - client := clienttest.NewClient(t) - ge := gasMocks.NewEvmFeeEstimator(t) - - r := &EvmRegistry{ - lggr: logger.Sugared(lggr), - poller: logPoller, - addr: addr, - client: client, - logProcessed: make(map[string]bool), - registry: mockReg, - abi: keeperRegistryABI, - active: NewActiveUpkeepList(), - packer: encoding.NewAbiPacker(), - headFunc: func(ocr2keepers.BlockKey) {}, - chLog: make(chan logpoller.Log, 1000), - mercury: &MercuryConfig{ - cred: &commontypes.MercuryCredentials{ - LegacyURL: "https://google.old.com", - URL: "https://google.com", - Username: "FakeClientID", - Password: "FakeClientKey", - }, - Abi: streamsLookupCompatibleABI, - AllowListCache: cache.New(defaultAllowListExpiration, cleanupInterval), - }, - hc: mockHttpClient, - bs: &BlockSubscriber{latestBlock: atomic.Pointer[ocr2keepers.BlockKey]{}}, - ge: ge, - } - return r -} diff --git a/pkg/automation/v21/registry_test.go b/pkg/automation/v21/registry_test.go deleted file mode 100644 index 66a90ca61c..0000000000 --- a/pkg/automation/v21/registry_test.go +++ /dev/null @@ -1,611 +0,0 @@ -package evm - -import ( - "context" - "errors" - "math/big" - "strconv" - "testing" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - coreTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - autotypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated" - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/automation_compatible_utils" - autov2common "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - evmheads "github.com/smartcontractkit/chainlink-evm/pkg/heads" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/logprovider" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" -) - -func TestMercuryConfig_RemoveTrailingSlash(t *testing.T) { - tests := []struct { - Name string - URL string - LegacyURL string - }{ - { - Name: "Both have trailing slashes", - URL: "http://example.com/", - LegacyURL: "http://legacy.example.com/", - }, - { - Name: "One has trailing slashes", - URL: "http://example.com", - LegacyURL: "http://legacy.example.com/", - }, - { - Name: "Neither has trailing slashes", - URL: "http://example.com", - LegacyURL: "http://legacy.example.com", - }, - } - - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - mockConfig := NewMercuryConfig(&types.MercuryCredentials{ - URL: test.URL, - LegacyURL: test.LegacyURL, - Username: "user", - Password: "pass", - }, core.StreamsCompatibleABI) - - result := mockConfig.Credentials() - - // Assert that trailing slashes are removed - assert.Equal(t, "http://example.com", result.URL) - assert.Equal(t, "http://legacy.example.com", result.LegacyURL) - assert.Equal(t, "user", result.Username) - assert.Equal(t, "pass", result.Password) - }) - } -} - -func TestPollLogs(t *testing.T) { - tests := []struct { - Name string - LastPoll int64 - Address common.Address - ExpectedLastPoll int64 - ExpectedErr error - LatestBlock *struct { - OutputBlock int64 - OutputErr error - } - LogsWithSigs *struct { - InputStart int64 - InputEnd int64 - OutputLogs []logpoller.Log - OutputErr error - } - }{ - { - Name: "LatestBlockError", - ExpectedErr: ErrHeadNotAvailable, - LatestBlock: &struct { - OutputBlock int64 - OutputErr error - }{ - OutputBlock: 0, - OutputErr: errors.New("test error output"), - }, - }, - { - Name: "LastHeadPollIsLatestHead", - LastPoll: 500, - ExpectedLastPoll: 500, - ExpectedErr: nil, - LatestBlock: &struct { - OutputBlock int64 - OutputErr error - }{ - OutputBlock: 500, - OutputErr: nil, - }, - }, - { - Name: "LastHeadPollNotInitialized", - LastPoll: 0, - ExpectedLastPoll: 500, - ExpectedErr: nil, - LatestBlock: &struct { - OutputBlock int64 - OutputErr error - }{ - OutputBlock: 500, - OutputErr: nil, - }, - }, - { - Name: "LogPollError", - LastPoll: 480, - Address: common.BigToAddress(big.NewInt(1)), - ExpectedLastPoll: 500, - ExpectedErr: ErrLogReadFailure, - LatestBlock: &struct { - OutputBlock int64 - OutputErr error - }{ - OutputBlock: 500, - OutputErr: nil, - }, - LogsWithSigs: &struct { - InputStart int64 - InputEnd int64 - OutputLogs []logpoller.Log - OutputErr error - }{ - InputStart: 250, - InputEnd: 500, - OutputLogs: []logpoller.Log{}, - OutputErr: errors.New("test output error"), - }, - }, - { - Name: "LogPollSuccess", - LastPoll: 480, - Address: common.BigToAddress(big.NewInt(1)), - ExpectedLastPoll: 500, - ExpectedErr: nil, - LatestBlock: &struct { - OutputBlock int64 - OutputErr error - }{ - OutputBlock: 500, - OutputErr: nil, - }, - LogsWithSigs: &struct { - InputStart int64 - InputEnd int64 - OutputLogs []logpoller.Log - OutputErr error - }{ - InputStart: 250, - InputEnd: 500, - OutputLogs: []logpoller.Log{ - {EVMChainID: sqlutil.New(big.NewInt(5)), LogIndex: 1}, - {EVMChainID: sqlutil.New(big.NewInt(6)), LogIndex: 2}, - }, - OutputErr: nil, - }, - }, - } - - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - ctx := t.Context() - mp := mocks.NewLogPoller(t) - - if test.LatestBlock != nil { - mp.On("LatestBlock", mock.Anything). - Return(logpoller.Block{BlockNumber: test.LatestBlock.OutputBlock}, test.LatestBlock.OutputErr) - } - - if test.LogsWithSigs != nil { - fc := test.LogsWithSigs - mp.On("LogsWithSigs", mock.Anything, fc.InputStart, fc.InputEnd, upkeepStateEvents, test.Address).Return(fc.OutputLogs, fc.OutputErr) - } - - rg := &EvmRegistry{ - addr: test.Address, - lastPollBlock: test.LastPoll, - poller: mp, - chLog: make(chan logpoller.Log, 10), - } - - err := rg.pollUpkeepStateLogs(ctx) - - assert.Equal(t, test.ExpectedLastPoll, rg.lastPollBlock) - if test.ExpectedErr != nil { - assert.ErrorIs(t, err, test.ExpectedErr) - } else { - assert.NoError(t, err) - } - - var outputLogCount int - - CheckLoop: - for { - chT := time.NewTimer(20 * time.Millisecond) - select { - case l := <-rg.chLog: - chT.Stop() - if test.LogsWithSigs == nil { - assert.FailNow(t, "logs detected but no logs were expected") - } - outputLogCount++ - assert.Contains(t, test.LogsWithSigs.OutputLogs, l) - case <-chT.C: - break CheckLoop - } - } - - if test.LogsWithSigs != nil { - assert.Equal(t, len(test.LogsWithSigs.OutputLogs), outputLogCount) - } - - mp.AssertExpectations(t) - }) - } -} - -func TestRegistry_refreshLogTriggerUpkeeps(t *testing.T) { - for _, tc := range []struct { - name string - ids []*big.Int - logEventProvider logprovider.LogEventProvider - poller logpoller.LogPoller - registry Registry - packer encoding.Packer - expectsErr bool - wantErrMsg string - }{ - { - name: "an error is returned when fetching indexed logs for IAutomationV21PlusCommonUpkeepUnpaused errors", - ids: []*big.Int{ - core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - }, - logEventProvider: &mockLogEventProvider{ - RefreshActiveUpkeepsFn: func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - // of the ids specified in the test, only one is a valid log trigger upkeep - assert.Len(t, ids, 1) - return ids, nil - }, - }, - poller: &mockLogPoller{ - IndexedLogsFn: func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - if eventSig == (autov2common.IAutomationV21PlusCommonUpkeepUnpaused{}.Topic()) { - return nil, errors.New("indexed logs boom") - } - return nil, nil - }, - }, - expectsErr: true, - wantErrMsg: "indexed logs boom", - }, - { - name: "an error is returned when fetching indexed logs for IAutomationV21PlusCommonUpkeepTriggerConfigSet errors", - ids: []*big.Int{ - core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - core.GenUpkeepID(autotypes.ConditionTrigger, "abc").BigInt(), - big.NewInt(-1), - }, - logEventProvider: &mockLogEventProvider{ - RefreshActiveUpkeepsFn: func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - // of the ids specified in the test, only one is a valid log trigger upkeep - assert.Len(t, ids, 1) - return ids, nil - }, - }, - poller: &mockLogPoller{ - IndexedLogsFn: func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - if eventSig == (autov2common.IAutomationV21PlusCommonUpkeepTriggerConfigSet{}.Topic()) { - return nil, errors.New("indexed logs boom") - } - return nil, nil - }, - }, - expectsErr: true, - wantErrMsg: "indexed logs boom", - }, - { - name: "an error is returned when parsing the logs using the registry errors", - ids: []*big.Int{ - core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - core.GenUpkeepID(autotypes.ConditionTrigger, "abc").BigInt(), - big.NewInt(-1), - }, - logEventProvider: &mockLogEventProvider{ - RefreshActiveUpkeepsFn: func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - // of the ids specified in the test, only one is a valid log trigger upkeep - assert.Len(t, ids, 1) - return ids, nil - }, - }, - poller: &mockLogPoller{ - IndexedLogsFn: func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - return []logpoller.Log{ - {}, - }, nil - }, - }, - registry: &mockRegistry{ - ParseLogFn: func(log coreTypes.Log) (generated.AbigenLog, error) { - return nil, errors.New("parse log boom") - }, - }, - expectsErr: true, - wantErrMsg: "parse log boom", - }, - { - name: "an error is returned when registering the filter errors", - ids: []*big.Int{ - core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - core.GenUpkeepID(autotypes.ConditionTrigger, "abc").BigInt(), - big.NewInt(-1), - }, - logEventProvider: &mockLogEventProvider{ - RefreshActiveUpkeepsFn: func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - // of the ids specified in the test, only one is a valid log trigger upkeep - assert.Len(t, ids, 1) - return ids, nil - }, - RegisterFilterFn: func(ctx context.Context, opts logprovider.FilterOptions) error { - return errors.New("register filter boom") - }, - }, - poller: &mockLogPoller{ - IndexedLogsFn: func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - return []logpoller.Log{ - { - BlockNumber: 1, - }, - { - BlockNumber: 2, - }, - }, nil - }, - }, - registry: &mockRegistry{ - ParseLogFn: func(log coreTypes.Log) (generated.AbigenLog, error) { - if log.BlockNumber == 1 { - return &autov2common.IAutomationV21PlusCommonUpkeepTriggerConfigSet{ - TriggerConfig: []byte{1, 2, 3}, - Id: core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - }, nil - } - return &autov2common.IAutomationV21PlusCommonUpkeepUnpaused{ - Id: core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - }, nil - }, - GetUpkeepTriggerConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return nil, nil - }, - }, - packer: &mockPacker{ - UnpackLogTriggerConfigFn: func(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) { - return ac.IAutomationV21PlusCommonLogTriggerConfig{}, nil - }, - }, - expectsErr: true, - wantErrMsg: "failed to update trigger config for upkeep id 452312848583266388373324160190187140521564213162920931037143039228013182976: failed to register log filter: register filter boom", - }, - { - name: "log trigger upkeeps are refreshed without error", - ids: []*big.Int{ - core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - core.GenUpkeepID(autotypes.LogTrigger, "def").BigInt(), - core.GenUpkeepID(autotypes.ConditionTrigger, "abc").BigInt(), - big.NewInt(-1), - }, - logEventProvider: &mockLogEventProvider{ - RefreshActiveUpkeepsFn: func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - // of the ids specified in the test, only two are a valid log trigger upkeep - assert.Len(t, ids, 2) - return ids, nil - }, - RegisterFilterFn: func(ctx context.Context, opts logprovider.FilterOptions) error { - return nil - }, - }, - poller: &mockLogPoller{ - IndexedLogsFn: func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - return []logpoller.Log{ - { - BlockNumber: 2, - }, - { - BlockNumber: 1, - }, - }, nil - }, - }, - registry: &mockRegistry{ - ParseLogFn: func(log coreTypes.Log) (generated.AbigenLog, error) { - if log.BlockNumber == 1 { - return &autov2common.IAutomationV21PlusCommonUpkeepTriggerConfigSet{ - Id: core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - TriggerConfig: []byte{1, 2, 3}, - }, nil - } - return &autov2common.IAutomationV21PlusCommonUpkeepUnpaused{ - Id: core.GenUpkeepID(autotypes.LogTrigger, "def").BigInt(), - }, nil - }, - GetUpkeepTriggerConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return nil, nil - }, - }, - packer: &mockPacker{ - UnpackLogTriggerConfigFn: func(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) { - return ac.IAutomationV21PlusCommonLogTriggerConfig{}, nil - }, - }, - }, - { - name: "log trigger upkeeps are refreshed in batch without error", - ids: func() []*big.Int { - res := []*big.Int{} - for i := range logTriggerRefreshBatchSize * 3 { - res = append(res, core.GenUpkeepID(autotypes.LogTrigger, strconv.Itoa(i)).BigInt()) - } - return res - }(), - logEventProvider: &mockLogEventProvider{ - RefreshActiveUpkeepsFn: func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - assert.Len(t, ids, logTriggerRefreshBatchSize) - return ids, nil - }, - RegisterFilterFn: func(ctx context.Context, opts logprovider.FilterOptions) error { - return nil - }, - }, - poller: &mockLogPoller{ - IndexedLogsFn: func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - return []logpoller.Log{ - { - BlockNumber: 2, - }, - { - BlockNumber: 1, - }, - }, nil - }, - }, - registry: &mockRegistry{ - ParseLogFn: func(log coreTypes.Log) (generated.AbigenLog, error) { - if log.BlockNumber == 1 { - return &autov2common.IAutomationV21PlusCommonUpkeepTriggerConfigSet{ - Id: core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - TriggerConfig: []byte{1, 2, 3}, - }, nil - } - return &autov2common.IAutomationV21PlusCommonUpkeepUnpaused{ - Id: core.GenUpkeepID(autotypes.LogTrigger, "def").BigInt(), - }, nil - }, - GetUpkeepTriggerConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return nil, nil - }, - }, - packer: &mockPacker{ - UnpackLogTriggerConfigFn: func(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) { - return ac.IAutomationV21PlusCommonLogTriggerConfig{}, nil - }, - }, - }, - { - name: "log trigger upkeeps are refreshed in batch, with a partial batch without error", - ids: func() []*big.Int { - res := []*big.Int{} - for i := range logTriggerRefreshBatchSize + 3 { - res = append(res, core.GenUpkeepID(autotypes.LogTrigger, strconv.Itoa(i)).BigInt()) - } - return res - }(), - logEventProvider: &mockLogEventProvider{ - RefreshActiveUpkeepsFn: func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - if len(ids) != logTriggerRefreshBatchSize { - assert.Len(t, ids, 3) - } - return ids, nil - }, - RegisterFilterFn: func(ctx context.Context, opts logprovider.FilterOptions) error { - return nil - }, - }, - poller: &mockLogPoller{ - IndexedLogsFn: func(ctx context.Context, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs evmtypes.Confirmations) ([]logpoller.Log, error) { - return []logpoller.Log{ - { - BlockNumber: 2, - }, - { - BlockNumber: 1, - }, - }, nil - }, - }, - registry: &mockRegistry{ - ParseLogFn: func(log coreTypes.Log) (generated.AbigenLog, error) { - if log.BlockNumber == 1 { - return &autov2common.IAutomationV21PlusCommonUpkeepTriggerConfigSet{ - Id: core.GenUpkeepID(autotypes.LogTrigger, "abc").BigInt(), - TriggerConfig: []byte{1, 2, 3}, - }, nil - } - return &autov2common.IAutomationV21PlusCommonUpkeepUnpaused{ - Id: core.GenUpkeepID(autotypes.LogTrigger, "def").BigInt(), - }, nil - }, - GetUpkeepTriggerConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return nil, nil - }, - }, - packer: &mockPacker{ - UnpackLogTriggerConfigFn: func(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) { - return ac.IAutomationV21PlusCommonLogTriggerConfig{}, nil - }, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - ctx := t.Context() - lggr := logger.Test(t) - var hb evmheads.Broadcaster - var lp logpoller.LogPoller - - bs := NewBlockSubscriber(hb, lp, 1000, lggr) - - registry := &EvmRegistry{ - addr: common.BigToAddress(big.NewInt(1)), - poller: tc.poller, - logEventProvider: tc.logEventProvider, - chLog: make(chan logpoller.Log, 10), - bs: bs, - registry: tc.registry, - packer: tc.packer, - lggr: logger.Sugared(lggr), - } - - err := registry.refreshLogTriggerUpkeeps(ctx, tc.ids) - if tc.expectsErr { - assert.Error(t, err) - assert.ErrorContains(t, err, tc.wantErrMsg) - } else { - assert.NoError(t, err) - } - }) - } -} - -type mockLogEventProvider struct { - logprovider.LogEventProvider - RefreshActiveUpkeepsFn func(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) - RegisterFilterFn func(ctx context.Context, opts logprovider.FilterOptions) error -} - -func (p *mockLogEventProvider) RefreshActiveUpkeeps(ctx context.Context, ids ...*big.Int) ([]*big.Int, error) { - return p.RefreshActiveUpkeepsFn(ctx, ids...) -} - -func (p *mockLogEventProvider) RegisterFilter(ctx context.Context, opts logprovider.FilterOptions) error { - return p.RegisterFilterFn(ctx, opts) -} - -type mockRegistry struct { - Registry - GetUpkeepTriggerConfigFn func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) - ParseLogFn func(log coreTypes.Log) (generated.AbigenLog, error) -} - -func (r *mockRegistry) ParseLog(log coreTypes.Log) (generated.AbigenLog, error) { - return r.ParseLogFn(log) -} - -func (r *mockRegistry) GetUpkeepTriggerConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { - return r.GetUpkeepTriggerConfigFn(opts, upkeepId) -} - -type mockPacker struct { - encoding.Packer - UnpackLogTriggerConfigFn func(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) -} - -func (p *mockPacker) UnpackLogTriggerConfig(raw []byte) (ac.IAutomationV21PlusCommonLogTriggerConfig, error) { - return p.UnpackLogTriggerConfigFn(raw) -} diff --git a/pkg/automation/v21/services.go b/pkg/automation/v21/services.go deleted file mode 100644 index 8cdcd53ade..0000000000 --- a/pkg/automation/v21/services.go +++ /dev/null @@ -1,30 +0,0 @@ -package evm - -import ( - "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" -) - -type AutomationServices interface { - Keyring() ocr3types.OnchainKeyring[plugin.AutomationReportInfo] -} - -func New(keyring ocrtypes.OnchainKeyring) (AutomationServices, error) { - services := new(automationServices) - - services.keyring = NewOnchainKeyringV3Wrapper(keyring) - - return services, nil -} - -type automationServices struct { - keyring *onchainKeyringV3Wrapper -} - -var _ AutomationServices = &automationServices{} - -func (f *automationServices) Keyring() ocr3types.OnchainKeyring[plugin.AutomationReportInfo] { - return f.keyring -} diff --git a/pkg/automation/v21/testdata/btc-usd.json b/pkg/automation/v21/testdata/btc-usd.json deleted file mode 100644 index c3c121fd2f..0000000000 --- a/pkg/automation/v21/testdata/btc-usd.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "chainlinkBlob": "0x0001c38d71fed6c320b90e84b6f559459814d068e2a1700adc931ca9717d4fe70000000000000000000000000000000000000000000000000000000001a80b52b4bf1233f9cb71144a253a1791b202113c4ab4a92fa1b176d684b4959666ff8200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001004254432d5553442d415242495452554d2d544553544e4554000000000000000000000000000000000000000000000000000000000000000000000000645570be000000000000000000000000000000000000000000000000000002af2b818dc5000000000000000000000000000000000000000000000000000002af2426faf3000000000000000000000000000000000000000000000000000002af32dc209700000000000000000000000000000000000000000000000000000000012130f8df0a9745bb6ad5e2df605e158ba8ad8a33ef8a0acf9851f0f01668a3a3f2b68600000000000000000000000000000000000000000000000000000000012130f60000000000000000000000000000000000000000000000000000000000000002c4a7958dce105089cf5edb68dad7dcfe8618d7784eb397f97d5a5fade78c11a58275aebda478968e545f7e3657aba9dcbe8d44605e4c6fde3e24edd5e22c94270000000000000000000000000000000000000000000000000000000000000002459c12d33986018a8959566d145225f0c4a4e61a9a3f50361ccff397899314f0018162cf10cd89897635a0bb62a822355bd199d09f4abe76e4d05261bb44733d" -} \ No newline at end of file diff --git a/pkg/automation/v21/testdata/eth-usd.json b/pkg/automation/v21/testdata/eth-usd.json deleted file mode 100644 index c5c1ae536b..0000000000 --- a/pkg/automation/v21/testdata/eth-usd.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "chainlinkBlob": "0x00013d0126ad1ad2e0e0ab3e6de9499910b44925f8c3ee02e242de5dbc9c7bdb0000000000000000000000000000000000000000000000000000000001a8cf43d1dae0445bfc7e4942ab6e109d1685f3c64e7c9308595d17aadefaa51d8afd0d00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000260010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001004554482d5553442d415242495452554d2d544553544e4554000000000000000000000000000000000000000000000000000000000000000000000000645570be0000000000000000000000000000000000000000000000000000002e5f7503c30000000000000000000000000000000000000000000000000000002e5eea51290000000000000000000000000000000000000000000000000000002e5fffb65c00000000000000000000000000000000000000000000000000000000012130f8df0a9745bb6ad5e2df605e158ba8ad8a33ef8a0acf9851f0f01668a3a3f2b68600000000000000000000000000000000000000000000000000000000012130f600000000000000000000000000000000000000000000000000000000000000021a53f0612f666ef65a2b91567c4b9cbac38e8d3dfd44b6d181518966a6c4c738151e1856cab0bcfc4688b67abb13228f9426f82161ec6265cf4edaa1e60bc0ce000000000000000000000000000000000000000000000000000000000000000218f3a503be5d4a40e9cd4024bac143e263a6d4ad80a8285d87241b8b0cd59d9446ec5662fc4956fbeefba7a7f052635dcb7fef649cbf08c37f7f196487560b30" -} diff --git a/pkg/automation/v21/threadcontrol/thread_control.go b/pkg/automation/v21/threadcontrol/thread_control.go deleted file mode 100644 index ed8b6155e6..0000000000 --- a/pkg/automation/v21/threadcontrol/thread_control.go +++ /dev/null @@ -1,55 +0,0 @@ -package threadcontrol - -import ( - "context" - "sync" - - "github.com/smartcontractkit/chainlink-common/pkg/services" -) - -var _ ThreadControl = &threadControl{} - -// ThreadControl is a helper for managing a group of goroutines. -type ThreadControl interface { - // Go starts a goroutine and tracks the lifetime of the goroutine. - Go(fn func(context.Context)) - // GoCtx starts a goroutine with a given context and tracks the lifetime of the goroutine. - GoCtx(ctx context.Context, fn func(context.Context)) - // Close cancels the goroutines and waits for all of them to exit. - Close() -} - -func NewThreadControl() *threadControl { - tc := &threadControl{ - stop: make(chan struct{}), - } - - return tc -} - -type threadControl struct { - threadsWG sync.WaitGroup - stop services.StopChan -} - -func (tc *threadControl) Go(fn func(context.Context)) { - tc.threadsWG.Go(func() { - ctx, cancel := tc.stop.NewCtx() - defer cancel() - fn(ctx) - }) -} - -func (tc *threadControl) GoCtx(ctx context.Context, fn func(context.Context)) { - tc.threadsWG.Go(func() { - // Create a new context that is cancelled when either parent context is cancelled or stop is closed. - ctx2, cancel := tc.stop.Ctx(ctx) - defer cancel() - fn(ctx2) - }) -} - -func (tc *threadControl) Close() { - close(tc.stop) - tc.threadsWG.Wait() -} diff --git a/pkg/automation/v21/threadcontrol/thread_control_test.go b/pkg/automation/v21/threadcontrol/thread_control_test.go deleted file mode 100644 index 53c39b26dd..0000000000 --- a/pkg/automation/v21/threadcontrol/thread_control_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package threadcontrol - -import ( - "context" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestThreadControl_Close(t *testing.T) { - n := 10 - tc := NewThreadControl() - - finished := atomic.Int32{} - - for range n { - tc.Go(func(ctx context.Context) { - <-ctx.Done() - finished.Add(1) - }) - } - - tc.Close() - - require.Equal(t, int32(n), finished.Load()) -} - -func TestThreadControl_GoCtx(t *testing.T) { - tc := NewThreadControl() - defer tc.Close() - - var wg sync.WaitGroup - finished := atomic.Int32{} - - timeout := 100 * time.Millisecond - - start := time.Now() - ctx, cancel := context.WithDeadline(t.Context(), start.Add(timeout)) - defer cancel() - - wg.Add(1) - tc.GoCtx(ctx, func(c context.Context) { - defer wg.Done() - <-c.Done() - finished.Add(1) - }) - - wg.Wait() - elapsed := time.Since(start) - assert.GreaterOrEqual(t, elapsed, timeout) - assert.Less(t, elapsed, 2*timeout) - require.Equal(t, int32(1), finished.Load()) -} diff --git a/pkg/automation/v21/transmit/cache.go b/pkg/automation/v21/transmit/cache.go deleted file mode 100644 index 814e86040b..0000000000 --- a/pkg/automation/v21/transmit/cache.go +++ /dev/null @@ -1,72 +0,0 @@ -package transmit - -import ( - "sync" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -// transmitEventCache holds a ring buffer of the last visited blocks (transmit block), -// and their corresponding logs (by log id). -// Using a ring buffer allows us to keep a cache of the last N blocks, -// without having to iterate over the entire buffer to clean it up. -type transmitEventCache struct { - lock sync.RWMutex - buffer []cacheBlock - - cap int64 -} - -func newTransmitEventCache(cap int64) transmitEventCache { - return transmitEventCache{ - buffer: make([]cacheBlock, cap), - cap: cap, - } -} - -func (c *transmitEventCache) get(block ocr2keepers.BlockNumber, logID string) (ocr2keepers.TransmitEvent, bool) { - c.lock.RLock() - defer c.lock.RUnlock() - - i := int64(block) % c.cap - b := c.buffer[i] - if b.block != block { - return ocr2keepers.TransmitEvent{}, false - } - if len(b.records) == 0 { - return ocr2keepers.TransmitEvent{}, false - } - e, ok := b.records[logID] - - return e, ok -} - -func (c *transmitEventCache) add(logID string, e ocr2keepers.TransmitEvent) { - c.lock.Lock() - defer c.lock.Unlock() - - i := int64(e.TransmitBlock) % c.cap - b := c.buffer[i] - isBlockEmpty := len(b.records) == 0 - isNewBlock := b.block < e.TransmitBlock - if isBlockEmpty || isNewBlock { - b = newCacheBlock(e.TransmitBlock) - } else if b.block > e.TransmitBlock { - // old log - return - } - b.records[logID] = e - c.buffer[i] = b -} - -type cacheBlock struct { - block ocr2keepers.BlockNumber - records map[string]ocr2keepers.TransmitEvent -} - -func newCacheBlock(block ocr2keepers.BlockNumber) cacheBlock { - return cacheBlock{ - block: block, - records: make(map[string]ocr2keepers.TransmitEvent), - } -} diff --git a/pkg/automation/v21/transmit/cache_test.go b/pkg/automation/v21/transmit/cache_test.go deleted file mode 100644 index 687dbfdbfe..0000000000 --- a/pkg/automation/v21/transmit/cache_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package transmit - -import ( - "testing" - - "github.com/stretchr/testify/require" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" -) - -func TestTransmitEventCache_Sanity(t *testing.T) { - tests := []struct { - name string - cap int64 - logIDsToAdd []string - eventsToAdd []ocr2keepers.TransmitEvent - toGet []string - blocksToGet []int64 - expected map[string]ocr2keepers.TransmitEvent - }{ - { - "empty cache", - 10, - []string{}, - []ocr2keepers.TransmitEvent{}, - []string{"1"}, - []int64{1}, - map[string]ocr2keepers.TransmitEvent{}, - }, - { - "happy path", - 10, - []string{"3", "2", "4", "1"}, - []ocr2keepers.TransmitEvent{ - {WorkID: "3", TransmitBlock: 3}, - {WorkID: "2", TransmitBlock: 2}, - {WorkID: "4", TransmitBlock: 4}, - {WorkID: "1", TransmitBlock: 1}, - }, - []string{"1", "3"}, - []int64{1, 3}, - map[string]ocr2keepers.TransmitEvent{ - "1": {WorkID: "1", TransmitBlock: 1}, - "3": {WorkID: "3", TransmitBlock: 3}, - }, - }, - { - "different blocks", - 10, - []string{"3", "1", "2", "4"}, - []ocr2keepers.TransmitEvent{ - {WorkID: "3", TransmitBlock: 3}, - {WorkID: "1", TransmitBlock: 1}, - {WorkID: "2", TransmitBlock: 2}, - {WorkID: "4", TransmitBlock: 4}, - }, - []string{"1", "3"}, - []int64{9, 9}, - map[string]ocr2keepers.TransmitEvent{}, - }, - { - "overflow", - 3, - []string{"4", "1", "3", "2", "5"}, - []ocr2keepers.TransmitEvent{ - {WorkID: "4", TransmitBlock: 4}, - {WorkID: "1", TransmitBlock: 1}, - {WorkID: "3", TransmitBlock: 3}, - {WorkID: "2", TransmitBlock: 2}, - {WorkID: "5", TransmitBlock: 5}, - }, - []string{"1", "4", "2", "3", "5"}, - []int64{1, 4, 2, 3, 5}, - map[string]ocr2keepers.TransmitEvent{ - "3": {WorkID: "3", TransmitBlock: 3}, - "4": {WorkID: "4", TransmitBlock: 4}, - "5": {WorkID: "5", TransmitBlock: 5}, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - c := newTransmitEventCache(tc.cap) - require.Len(t, tc.logIDsToAdd, len(tc.eventsToAdd)) - for i, e := range tc.eventsToAdd { - c.add(tc.logIDsToAdd[i], e) - } - require.Len(t, tc.blocksToGet, len(tc.toGet)) - for i, logID := range tc.toGet { - e, exist := c.get(ocr2keepers.BlockNumber(tc.blocksToGet[i]), logID) - expected, ok := tc.expected[logID] - if !ok { - require.False(t, exist, "expected not to find logID %s", logID) - continue - } - require.True(t, exist, "expected to find logID %s", logID) - require.Equal(t, expected.WorkID, e.WorkID) - } - }) - } -} diff --git a/pkg/automation/v21/transmit/encoding.go b/pkg/automation/v21/transmit/encoding.go deleted file mode 100644 index 0028924c12..0000000000 --- a/pkg/automation/v21/transmit/encoding.go +++ /dev/null @@ -1,113 +0,0 @@ -package transmit - -import ( - "errors" - "fmt" - "math/big" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -// defaultLogParser parses logs from the registry contract -func defaultLogParser(registry *ac.IAutomationV21PlusCommon, log logpoller.Log) (transmitEventLog, error) { - rawLog := log.ToGethLog() - abilog, err := registry.ParseLog(rawLog) - if err != nil { - return transmitEventLog{}, fmt.Errorf("%w: failed to parse log", err) - } - - switch l := abilog.(type) { - case *ac.IAutomationV21PlusCommonUpkeepPerformed: - if l == nil { - break - } - return transmitEventLog{ - Log: log, - Performed: l, - }, nil - case *ac.IAutomationV21PlusCommonReorgedUpkeepReport: - if l == nil { - break - } - return transmitEventLog{ - Log: log, - Reorged: l, - }, nil - case *ac.IAutomationV21PlusCommonStaleUpkeepReport: - if l == nil { - break - } - return transmitEventLog{ - Log: log, - Stale: l, - }, nil - case *ac.IAutomationV21PlusCommonInsufficientFundsUpkeepReport: - if l == nil { - break - } - return transmitEventLog{ - Log: log, - InsufficientFunds: l, - }, nil - default: - return transmitEventLog{}, fmt.Errorf("unknown log type: %v", l) - } - return transmitEventLog{}, errors.New("log with bad structure") -} - -// transmitEventLog is a wrapper around logpoller.Log and the parsed log -type transmitEventLog struct { - logpoller.Log - Performed *ac.IAutomationV21PlusCommonUpkeepPerformed - Stale *ac.IAutomationV21PlusCommonStaleUpkeepReport - Reorged *ac.IAutomationV21PlusCommonReorgedUpkeepReport - InsufficientFunds *ac.IAutomationV21PlusCommonInsufficientFundsUpkeepReport -} - -func (l transmitEventLog) Id() *big.Int { - switch { - case l.Performed != nil: - return l.Performed.Id - case l.Stale != nil: - return l.Stale.Id - case l.Reorged != nil: - return l.Reorged.Id - case l.InsufficientFunds != nil: - return l.InsufficientFunds.Id - default: - return nil - } -} - -func (l transmitEventLog) Trigger() []byte { - switch { - case l.Performed != nil: - return l.Performed.Trigger - case l.Stale != nil: - return l.Stale.Trigger - case l.Reorged != nil: - return l.Reorged.Trigger - case l.InsufficientFunds != nil: - return l.InsufficientFunds.Trigger - default: - return []byte{} - } -} - -func (l transmitEventLog) TransmitEventType() ocr2keepers.TransmitEventType { - switch { - case l.Performed != nil: - return ocr2keepers.PerformEvent - case l.Stale != nil: - return ocr2keepers.StaleReportEvent - case l.Reorged != nil: - return ocr2keepers.ReorgReportEvent - case l.InsufficientFunds != nil: - return ocr2keepers.InsufficientFundsReportEvent - default: - return ocr2keepers.UnknownEvent - } -} diff --git a/pkg/automation/v21/transmit/encoding_test.go b/pkg/automation/v21/transmit/encoding_test.go deleted file mode 100644 index 3f5d8ef8e8..0000000000 --- a/pkg/automation/v21/transmit/encoding_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package transmit - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -func TestTransmitEventLog(t *testing.T) { - uid := core.GenUpkeepID(types.ConditionTrigger, "111") - - tests := []struct { - name string - log transmitEventLog - etype ocr2keepers.TransmitEventType - }{ - { - "performed", - transmitEventLog{ - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x010203040"), - }, - Performed: &ac.IAutomationV21PlusCommonUpkeepPerformed{ - Id: uid.BigInt(), - Trigger: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - }, - ocr2keepers.PerformEvent, - }, - { - "stale", - transmitEventLog{ - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x010203040"), - }, - Stale: &ac.IAutomationV21PlusCommonStaleUpkeepReport{ - Id: uid.BigInt(), - Trigger: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - }, - ocr2keepers.StaleReportEvent, - }, - { - "insufficient funds", - transmitEventLog{ - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x010203040"), - }, - InsufficientFunds: &ac.IAutomationV21PlusCommonInsufficientFundsUpkeepReport{ - Id: uid.BigInt(), - Trigger: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - }, - ocr2keepers.InsufficientFundsReportEvent, - }, - { - "reorged", - transmitEventLog{ - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x010203040"), - }, - Reorged: &ac.IAutomationV21PlusCommonReorgedUpkeepReport{ - Id: uid.BigInt(), - Trigger: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - }, - ocr2keepers.ReorgReportEvent, - }, - { - "empty", - transmitEventLog{ - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x010203040"), - }, - }, - ocr2keepers.UnknownEvent, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - if tc.log.Id() != nil { - require.Equal(t, uid.BigInt().Int64(), tc.log.Id().Int64()) - require.Equal(t, []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, tc.log.Trigger()) - } - require.Equal(t, tc.etype, tc.log.TransmitEventType()) - }) - } -} diff --git a/pkg/automation/v21/transmit/event_provider.go b/pkg/automation/v21/transmit/event_provider.go deleted file mode 100644 index a8c96c861c..0000000000 --- a/pkg/automation/v21/transmit/event_provider.go +++ /dev/null @@ -1,228 +0,0 @@ -package transmit - -import ( - "context" - "encoding/hex" - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - evmclient "github.com/smartcontractkit/chainlink-evm/pkg/client" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -var _ types.TransmitEventProvider = &EventProvider{} - -type logParser func(registry *ac.IAutomationV21PlusCommon, log logpoller.Log) (transmitEventLog, error) - -type EventProvider struct { - sync services.StateMachine - mu sync.RWMutex - runState int - runError error - - logger logger.Logger - logPoller logpoller.LogPoller - registry *ac.IAutomationV21PlusCommon - client evmclient.Client - - registryAddress common.Address - lookbackBlocks int64 - - parseLog logParser - cache transmitEventCache -} - -func EventProviderFilterName(addr common.Address) string { - return logpoller.FilterName("KeepersRegistry TransmitEventProvider", addr) -} - -func NewTransmitEventProvider( - ctx context.Context, - logger logger.Logger, - logPoller logpoller.LogPoller, - registryAddress common.Address, - client evmclient.Client, - lookbackBlocks int64, -) (*EventProvider, error) { - var err error - - contract, err := ac.NewIAutomationV21PlusCommon(registryAddress, client) - if err != nil { - return nil, err - } - err = logPoller.RegisterFilter(ctx, logpoller.Filter{ - Name: EventProviderFilterName(contract.Address()), - EventSigs: []common.Hash{ - // These are the events that are emitted when a node transmits a report - ac.IAutomationV21PlusCommonUpkeepPerformed{}.Topic(), // Happy path: report performed the upkeep - ac.IAutomationV21PlusCommonReorgedUpkeepReport{}.Topic(), // Report checkBlockNumber was reorged - ac.IAutomationV21PlusCommonInsufficientFundsUpkeepReport{}.Topic(), // Upkeep didn't have sufficient funds when report reached chain, perform was aborted early - // Report was too old when it reached the chain. For conditionals upkeep was already performed on a higher block than checkBlockNum - // for logs upkeep was already performed for the particular log - ac.IAutomationV21PlusCommonStaleUpkeepReport{}.Topic(), - }, - Addresses: []common.Address{registryAddress}, - }) - if err != nil { - return nil, err - } - - return &EventProvider{ - logger: logger, - logPoller: logPoller, - registryAddress: registryAddress, - lookbackBlocks: lookbackBlocks, - registry: contract, - client: client, - parseLog: defaultLogParser, - cache: newTransmitEventCache(lookbackBlocks), - }, nil -} - -func (c *EventProvider) Name() string { - return c.logger.Name() -} - -func (c *EventProvider) Start(_ context.Context) error { - return c.sync.StartOnce("AutomationTransmitEventProvider", func() error { - c.mu.Lock() - defer c.mu.Unlock() - - c.runState = 1 - return nil - }) -} - -func (c *EventProvider) Close() error { - return c.sync.StopOnce("AutomationRegistry", func() error { - c.mu.Lock() - defer c.mu.Unlock() - - c.runState = 0 - c.runError = nil - return nil - }) -} - -func (c *EventProvider) Ready() error { - c.mu.RLock() - defer c.mu.RUnlock() - - if c.runState == 1 { - return nil - } - return c.sync.Ready() -} - -func (c *EventProvider) HealthReport() map[string]error { - c.mu.RLock() - defer c.mu.RUnlock() - - if c.runState > 1 { - c.sync.SvcErrBuffer.Append(fmt.Errorf("failed run state: %w", c.runError)) - } - return map[string]error{c.Name(): c.sync.Healthy()} -} - -func (c *EventProvider) GetLatestEvents(ctx context.Context) ([]ocr2keepers.TransmitEvent, error) { - end, err := c.logPoller.LatestBlock(ctx) - if err != nil { - return nil, fmt.Errorf("%w: failed to get latest block from log poller", err) - } - - // always check the last lookback number of blocks and rebroadcast - // this allows the plugin to make decisions based on event confirmations - logs, err := c.logPoller.LogsWithSigs( - ctx, - end.BlockNumber-c.lookbackBlocks, - end.BlockNumber, - []common.Hash{ - ac.IAutomationV21PlusCommonUpkeepPerformed{}.Topic(), - ac.IAutomationV21PlusCommonStaleUpkeepReport{}.Topic(), - ac.IAutomationV21PlusCommonReorgedUpkeepReport{}.Topic(), - ac.IAutomationV21PlusCommonInsufficientFundsUpkeepReport{}.Topic(), - }, - c.registryAddress, - ) - if err != nil { - return nil, fmt.Errorf("%w: failed to collect logs from log poller", err) - } - - return c.processLogs(end.BlockNumber, logs...) -} - -// processLogs will parse the unseen logs and return the corresponding transmit events. -func (c *EventProvider) processLogs(latestBlock int64, logs ...logpoller.Log) ([]ocr2keepers.TransmitEvent, error) { - var vals []ocr2keepers.TransmitEvent - - for _, log := range logs { - k := c.logKey(log) - - transmitEvent, ok := c.cache.get(ocr2keepers.BlockNumber(log.BlockNumber), k) - if !ok { - l, err := c.parseLog(c.registry, log) - if err != nil { - c.logger.Debugw("failed to parse log", "err", err) - continue - } - id := l.Id() - upkeepId := &ocr2keepers.UpkeepIdentifier{} - ok := upkeepId.FromBigInt(id) - if !ok { - return nil, core.ErrInvalidUpkeepID - } - triggerW, err := core.UnpackTrigger(id, l.Trigger()) - if err != nil { - return nil, fmt.Errorf("%w: failed to unpack trigger", err) - } - trigger := ocr2keepers.NewTrigger( - ocr2keepers.BlockNumber(triggerW.BlockNum), - triggerW.BlockHash, - ) - switch core.GetUpkeepType(*upkeepId) { - case types.LogTrigger: - trigger.LogTriggerExtension = &ocr2keepers.LogTriggerExtension{} - trigger.LogTriggerExtension.TxHash = triggerW.TxHash - trigger.LogTriggerExtension.Index = triggerW.LogIndex - trigger.LogTriggerExtension.BlockHash = triggerW.LogBlockHash - default: - } - workID := core.UpkeepWorkID(*upkeepId, trigger) - transmitEvent = ocr2keepers.TransmitEvent{ - Type: l.TransmitEventType(), - TransmitBlock: ocr2keepers.BlockNumber(l.BlockNumber), - TransactionHash: l.TxHash, - WorkID: workID, - UpkeepID: *upkeepId, - CheckBlock: trigger.BlockNumber, - } - c.cache.add(k, transmitEvent) - } - - transmitEvent.Confirmations = latestBlock - int64(transmitEvent.TransmitBlock) - - vals = append(vals, transmitEvent) - } - - return vals, nil -} - -func (c *EventProvider) logKey(log logpoller.Log) string { - logExt := ocr2keepers.LogTriggerExtension{ - TxHash: log.TxHash, - Index: uint32(log.LogIndex), - BlockHash: log.BlockHash, - } - logId := logExt.LogIdentifier() - return hex.EncodeToString(logId) -} diff --git a/pkg/automation/v21/transmit/event_provider_test.go b/pkg/automation/v21/transmit/event_provider_test.go deleted file mode 100644 index d3705ec24f..0000000000 --- a/pkg/automation/v21/transmit/event_provider_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package transmit - -import ( - "math/big" - "runtime" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/client" - "github.com/smartcontractkit/chainlink-evm/pkg/client/clienttest" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" -) - -func TestTransmitEventProvider_Sanity(t *testing.T) { - ctx := t.Context() - - lp := mocks.NewLogPoller(t) - - lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil) - - provider, err := NewTransmitEventProvider(ctx, logger.Test(t), lp, common.HexToAddress("0x"), client.NewNullClient(big.NewInt(1), logger.Test(t)), 32) - require.NoError(t, err) - require.NotNil(t, provider) - - go func() { - require.Error(t, provider.Ready()) - errs := provider.HealthReport() - require.Len(t, errs, 1) - require.Error(t, errs[provider.Name()]) - err := provider.Start(ctx) - if ctx.Err() == nil { - require.NoError(t, err) - } - }() - - for provider.Ready() != nil { - runtime.Gosched() - } - errs := provider.HealthReport() - require.Len(t, errs, 1) - require.NoError(t, errs[provider.Name()]) - - tests := []struct { - name string - latestBlock int64 - logs []logpoller.Log - errored bool - resultsLen int - }{ - { - "empty logs", - 100, - []logpoller.Log{}, - false, - 0, - }, - { - "invalid log", - 101, - []logpoller.Log{ - { - BlockNumber: 101, - BlockHash: common.HexToHash("0x1"), - TxHash: common.HexToHash("0x1"), - LogIndex: 1, - Address: common.HexToAddress("0x1"), - Topics: [][]byte{ - ac.IAutomationV21PlusCommonUpkeepPerformed{}.Topic().Bytes(), - }, - EventSig: ac.IAutomationV21PlusCommonUpkeepPerformed{}.Topic(), - Data: []byte{}, - }, - }, - false, - 0, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - lp.On("LatestBlock", mock.Anything).Return(logpoller.Block{BlockNumber: tc.latestBlock}, nil) - lp.On("LogsWithSigs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.logs, nil) - - res, err := provider.GetLatestEvents(ctx) - require.Equal(t, tc.errored, err != nil) - require.Len(t, res, tc.resultsLen) - }) - } -} - -func TestTransmitEventProvider_ProcessLogs(t *testing.T) { - lp := mocks.NewLogPoller(t) - lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil) - client := clienttest.NewClient(t) - ctx := t.Context() - - provider, err := NewTransmitEventProvider(ctx, logger.Test(t), lp, common.HexToAddress("0x"), client, 250) - require.NoError(t, err) - - id := core.GenUpkeepID(types.LogTrigger, "1111111111111111") - - tests := []struct { - name string - parsedPerformed []transmitEventLog - latestBlock int64 - want []ocr2keepers.TransmitEvent - errored bool - }{ - { - "happy flow", - []transmitEventLog{ - { - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x0102030405060708010203040506070801020304050607080102030405060708"), - }, - Performed: &ac.IAutomationV21PlusCommonUpkeepPerformed{ - Id: id.BigInt(), - Trigger: func() []byte { - b, _ := hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000001111abc0000000000000000000000000000000000000000000000000000000001111111000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") - return b - }(), - }, - }, - }, - 1, - []ocr2keepers.TransmitEvent{ - { - Type: ocr2keepers.PerformEvent, - UpkeepID: id, - CheckBlock: ocr2keepers.BlockNumber(1), - }, - }, - false, - }, - { - "empty events", - []transmitEventLog{}, - 1, - []ocr2keepers.TransmitEvent{}, - false, - }, - { - "same log twice", // shouldn't happen in practice as log poller should not return duplicate logs - []transmitEventLog{ - { - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x0102030405060708010203040506070801020304050607080102030405060708"), - }, - Performed: &ac.IAutomationV21PlusCommonUpkeepPerformed{ - Id: id.BigInt(), - Trigger: func() []byte { - b, _ := hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000001111abc0000000000000000000000000000000000000000000000000000000001111111000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") - return b - }(), - }, - }, - { - Log: logpoller.Log{ - BlockNumber: 1, - BlockHash: common.HexToHash("0x0102030405060708010203040506070801020304050607080102030405060708"), - }, - Performed: &ac.IAutomationV21PlusCommonUpkeepPerformed{ - Id: id.BigInt(), - Trigger: func() []byte { - b, _ := hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000001111abc0000000000000000000000000000000000000000000000000000000001111111000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") - return b - }(), - }, - }, - }, - 1, - []ocr2keepers.TransmitEvent{ - { - Type: ocr2keepers.PerformEvent, - UpkeepID: id, - CheckBlock: ocr2keepers.BlockNumber(1), - }, - { - Type: ocr2keepers.PerformEvent, - UpkeepID: id, - CheckBlock: ocr2keepers.BlockNumber(1), - }, - }, - false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - parseResults := make(map[string]transmitEventLog, len(tc.parsedPerformed)) - performedLogs := make([]logpoller.Log, len(tc.parsedPerformed)) - for i, l := range tc.parsedPerformed { - performedLogs[i] = l.Log - if _, ok := parseResults[provider.logKey(l.Log)]; ok { - continue - } - parseResults[provider.logKey(l.Log)] = l - } - provider.mu.Lock() - provider.cache = newTransmitEventCache(provider.cache.cap) - provider.parseLog = func(registry *ac.IAutomationV21PlusCommon, log logpoller.Log) (transmitEventLog, error) { - return parseResults[provider.logKey(log)], nil - } - provider.mu.Unlock() - - results, err := provider.processLogs(tc.latestBlock, performedLogs...) - require.Equal(t, tc.errored, err != nil) - require.Len(t, results, len(tc.want)) - for i, res := range results { - require.Equal(t, tc.want[i].Type, res.Type) - require.Equal(t, tc.want[i].UpkeepID, res.UpkeepID) - require.Equal(t, tc.want[i].CheckBlock, res.CheckBlock) - } - }) - } -} diff --git a/pkg/automation/v21/upkeep_provider.go b/pkg/automation/v21/upkeep_provider.go deleted file mode 100644 index 5b213795a3..0000000000 --- a/pkg/automation/v21/upkeep_provider.go +++ /dev/null @@ -1,51 +0,0 @@ -package evm - -import ( - "context" - "errors" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" -) - -var _ ocr2keepers.ConditionalUpkeepProvider = &upkeepProvider{} - -type upkeepProvider struct { - activeUpkeeps ActiveUpkeepList - bs *BlockSubscriber - lp logpoller.LogPoller -} - -func NewUpkeepProvider(activeUpkeeps ActiveUpkeepList, bs *BlockSubscriber, lp logpoller.LogPoller) *upkeepProvider { - return &upkeepProvider{ - activeUpkeeps: activeUpkeeps, - bs: bs, - lp: lp, - } -} - -func (p *upkeepProvider) GetActiveUpkeeps(_ context.Context) ([]ocr2keepers.UpkeepPayload, error) { - latestBlock := p.bs.latestBlock.Load() - if latestBlock == nil { - return nil, errors.New("no latest block found when fetching active upkeeps") - } - var payloads []ocr2keepers.UpkeepPayload - for _, uid := range p.activeUpkeeps.View(types.ConditionTrigger) { - payload, err := core.NewUpkeepPayload( - uid, - ocr2keepers.NewTrigger(latestBlock.Number, latestBlock.Hash), - nil, - ) - if err != nil { - // skip invalid payloads - continue - } - - payloads = append(payloads, payload) - } - - return payloads, nil -} diff --git a/pkg/automation/v21/upkeep_provider_test.go b/pkg/automation/v21/upkeep_provider_test.go deleted file mode 100644 index 03425a4b50..0000000000 --- a/pkg/automation/v21/upkeep_provider_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package evm - -import ( - "math/big" - "sync/atomic" - "testing" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/stretchr/testify/require" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" -) - -func TestUpkeepProvider_GetActiveUpkeeps(t *testing.T) { - ctx := t.Context() - - var lp logpoller.LogPoller - - tests := []struct { - name string - active ActiveUpkeepList - latestBlock *ocr2keepers.BlockKey - want []ocr2keepers.UpkeepPayload - wantErr bool - }{ - { - "empty", - &mockActiveUpkeepList{ - ViewFn: func(upkeepType ...types.UpkeepType) []*big.Int { - return []*big.Int{} - }, - }, - &ocr2keepers.BlockKey{Number: 1}, - nil, - false, - }, - { - "happy flow", - &mockActiveUpkeepList{ - ViewFn: func(upkeepType ...types.UpkeepType) []*big.Int { - return []*big.Int{ - big.NewInt(1), - big.NewInt(2), - } - }, - }, - &ocr2keepers.BlockKey{Number: 1}, - []ocr2keepers.UpkeepPayload{ - { - UpkeepID: core.UpkeepIDFromInt("1"), - Trigger: ocr2keepers.NewTrigger(ocr2keepers.BlockNumber(1), [32]byte{}), - WorkID: "b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", - }, - { - UpkeepID: core.UpkeepIDFromInt("2"), - Trigger: ocr2keepers.NewTrigger(ocr2keepers.BlockNumber(1), [32]byte{}), - WorkID: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", - }, - }, - false, - }, - { - "latest block not found", - &mockActiveUpkeepList{ - ViewFn: func(upkeepType ...types.UpkeepType) []*big.Int { - return []*big.Int{ - big.NewInt(1), - big.NewInt(2), - } - }, - }, - nil, - []ocr2keepers.UpkeepPayload{}, - true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - bs := &BlockSubscriber{ - latestBlock: atomic.Pointer[ocr2keepers.BlockKey]{}, - } - bs.latestBlock.Store(tc.latestBlock) - p := NewUpkeepProvider(tc.active, bs, lp) - - got, err := p.GetActiveUpkeeps(ctx) - if tc.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Len(t, got, len(tc.want)) - require.Equal(t, tc.want, got) - }) - } -} - -type mockActiveUpkeepList struct { - ActiveUpkeepList - ViewFn func(...types.UpkeepType) []*big.Int - IsActiveFn func(id *big.Int) bool -} - -func (l *mockActiveUpkeepList) View(u ...types.UpkeepType) []*big.Int { - return l.ViewFn(u...) -} - -func (l *mockActiveUpkeepList) IsActive(id *big.Int) bool { - return l.IsActiveFn(id) -} diff --git a/pkg/automation/v21/upkeepstate/orm.go b/pkg/automation/v21/upkeepstate/orm.go deleted file mode 100644 index d7a639bbcf..0000000000 --- a/pkg/automation/v21/upkeepstate/orm.go +++ /dev/null @@ -1,89 +0,0 @@ -package upkeepstate - -import ( - "context" - "math/big" - "time" - - "github.com/lib/pq" - - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" -) - -type orm struct { - chainID *sqlutil.Big - ds sqlutil.DataSource -} - -type persistedStateRecord struct { - UpkeepID *sqlutil.Big - WorkID string - CompletionState uint8 - BlockNumber int64 - IneligibilityReason uint8 - InsertedAt time.Time -} - -// NewORM creates an ORM scoped to chainID. -func NewORM(chainID *big.Int, ds sqlutil.DataSource) *orm { - return &orm{ - chainID: sqlutil.New(chainID), - ds: ds, - } -} - -// BatchInsertRecords is idempotent and sets upkeep state values in db -func (o *orm) BatchInsertRecords(ctx context.Context, state []persistedStateRecord) error { - if len(state) == 0 { - return nil - } - - type row struct { - EvmChainId *sqlutil.Big - WorkId string - CompletionState uint8 - BlockNumber int64 - InsertedAt time.Time - UpkeepId *sqlutil.Big - IneligibilityReason uint8 - } - - var rows []row - for _, record := range state { - rows = append(rows, row{ - EvmChainId: o.chainID, - WorkId: record.WorkID, - CompletionState: record.CompletionState, - BlockNumber: record.BlockNumber, - InsertedAt: record.InsertedAt, - UpkeepId: record.UpkeepID, - IneligibilityReason: record.IneligibilityReason, - }) - } - - _, err := o.ds.NamedExecContext(ctx, `INSERT INTO evm.upkeep_states -(evm_chain_id, work_id, completion_state, block_number, inserted_at, upkeep_id, ineligibility_reason) VALUES -(:evm_chain_id, :work_id, :completion_state, :block_number, :inserted_at, :upkeep_id, :ineligibility_reason) ON CONFLICT (evm_chain_id, work_id) DO NOTHING`, rows) - return err -} - -// SelectStatesByWorkIDs searches the data store for stored states for the -// provided work ids and configured chain id -func (o *orm) SelectStatesByWorkIDs(ctx context.Context, workIDs []string) (states []persistedStateRecord, err error) { - err = o.ds.SelectContext(ctx, &states, `SELECT upkeep_id, work_id, completion_state, block_number, ineligibility_reason, inserted_at - FROM evm.upkeep_states - WHERE work_id = ANY($1) AND evm_chain_id = $2::NUMERIC`, pq.Array(workIDs), o.chainID) - - if err != nil { - return nil, err - } - - return states, err -} - -// DeleteExpired prunes stored states older than to the provided time -func (o *orm) DeleteExpired(ctx context.Context, expired time.Time) error { - _, err := o.ds.ExecContext(ctx, `DELETE FROM evm.upkeep_states WHERE inserted_at <= $1 AND evm_chain_id::NUMERIC = $2`, expired, o.chainID) - - return err -} diff --git a/pkg/automation/v21/upkeepstate/orm_test.go b/pkg/automation/v21/upkeepstate/orm_test.go deleted file mode 100644 index d0d5aae2c2..0000000000 --- a/pkg/automation/v21/upkeepstate/orm_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package upkeepstate - -import ( - "math/big" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink-evm/pkg/testutils" -) - -func TestInsertSelectDelete(t *testing.T) { - ctx := t.Context() - chainID := testutils.FixtureChainID - db := testutils.NewSqlxDB(t) - orm := NewORM(chainID, db) - - inserted := []persistedStateRecord{ - { - UpkeepID: sqlutil.New(big.NewInt(2)), - WorkID: "0x1", - CompletionState: 100, - BlockNumber: 2, - IneligibilityReason: 2, - InsertedAt: time.Now(), - }, - } - - err := orm.BatchInsertRecords(ctx, inserted) - - require.NoError(t, err, "no error expected from insert") - - states, err := orm.SelectStatesByWorkIDs(ctx, []string{"0x1"}) - - require.NoError(t, err, "no error expected from select") - require.Len(t, states, 1, "records return should equal records inserted") - - err = orm.DeleteExpired(ctx, time.Now()) - - assert.NoError(t, err, "no error expected from delete") - - states, err = orm.SelectStatesByWorkIDs(ctx, []string{"0x1"}) - - require.NoError(t, err, "no error expected from select") - require.Empty(t, states, "records return should be empty since records were deleted") -} diff --git a/pkg/automation/v21/upkeepstate/scanner.go b/pkg/automation/v21/upkeepstate/scanner.go deleted file mode 100644 index b817c449a2..0000000000 --- a/pkg/automation/v21/upkeepstate/scanner.go +++ /dev/null @@ -1,106 +0,0 @@ -package upkeepstate - -import ( - "context" - "encoding/hex" - "fmt" - "io" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/logprovider" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" -) - -var ( - _ PerformedLogsScanner = &performedEventsScanner{} - - workIDsBatchSize = 25 -) - -type PerformedLogsScanner interface { - ScanWorkIDs(ctx context.Context, workIDs ...string) ([]string, error) - - Start(context.Context) error - io.Closer -} - -type performedEventsScanner struct { - lggr logger.Logger - poller logpoller.LogPoller - registryAddress common.Address - - finalityDepth uint32 -} - -func NewPerformedEventsScanner( - lggr logger.Logger, - poller logpoller.LogPoller, - registryAddress common.Address, - finalityDepth uint32, -) *performedEventsScanner { - return &performedEventsScanner{ - lggr: logger.Named(lggr, "EventsScanner"), - poller: poller, - registryAddress: registryAddress, - finalityDepth: finalityDepth, - } -} - -func (s *performedEventsScanner) Start(ctx context.Context) error { - return s.poller.RegisterFilter(ctx, logpoller.Filter{ - Name: dedupFilterName(s.registryAddress), - EventSigs: []common.Hash{ - // listening to dedup key added event - ac.IAutomationV21PlusCommonDedupKeyAdded{}.Topic(), - }, - Addresses: []common.Address{s.registryAddress}, - Retention: logprovider.LogRetention, - }) -} - -// Close implements io.Closer and does nothing -func (s *performedEventsScanner) Close() error { - return nil -} - -func (s *performedEventsScanner) ScanWorkIDs(ctx context.Context, workID ...string) ([]string, error) { - var ids []common.Hash - for _, id := range workID { - ids = append(ids, common.HexToHash(id)) - } - logs := make([]logpoller.Log, 0) - for i := 0; i < len(ids); i += workIDsBatchSize { - end := min(i+workIDsBatchSize, len(ids)) - batch := ids[i:end] - - batchLogs, err := s.poller.IndexedLogs(ctx, ac.IAutomationV21PlusCommonDedupKeyAdded{}.Topic(), s.registryAddress, 1, batch, evmtypes.Confirmations(s.finalityDepth)) - if err != nil { - return nil, fmt.Errorf("error fetching logs: %w", err) - } - logs = append(logs, batchLogs...) - } - - return s.logsToWorkIDs(logs), nil -} - -func (s *performedEventsScanner) logsToWorkIDs(logs []logpoller.Log) []string { - workIDs := make([]string, 0) - for _, log := range logs { - topics := log.GetTopics() - if len(topics) < 2 { - s.lggr.Debugw("unexpected log topics", "topics", topics) - continue - } - workIDs = append(workIDs, hex.EncodeToString(topics[1].Bytes())) - } - return workIDs -} - -func dedupFilterName(addr common.Address) string { - return logpoller.FilterName("KeepersRegistry UpkeepStates Deduped", addr) -} diff --git a/pkg/automation/v21/upkeepstate/scanner_test.go b/pkg/automation/v21/upkeepstate/scanner_test.go deleted file mode 100644 index e6cb866f17..0000000000 --- a/pkg/automation/v21/upkeepstate/scanner_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package upkeepstate - -import ( - "errors" - "slices" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" - - "github.com/smartcontractkit/chainlink-evm/pkg/logpoller/mocks" -) - -func TestPerformedEventsScanner(t *testing.T) { - ctx := t.Context() - registryAddr := common.HexToAddress("0x12345") - lggr := logger.Test(t) - - tests := []struct { - name string - workIDs []string - pollerResults []logpoller.Log - scannerResults []string - pollerErr error - errored bool - }{ - { - "empty", - []string{}, - []logpoller.Log{}, - []string{}, - nil, - false, - }, - { - "log poller error", - []string{"111"}, - []logpoller.Log{}, - []string{}, - errors.New("test-error"), - true, - }, - { - "one result", - []string{"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"}, - []logpoller.Log{ - { - BlockNumber: 1, - Address: registryAddr, - Topics: convertTopics([]common.Hash{ - ac.IAutomationV21PlusCommonDedupKeyAdded{}.Topic(), - common.HexToHash("0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), - }), - }, - }, - []string{"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"}, - nil, - false, - }, - { - "missing workID", - []string{"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"}, - []logpoller.Log{ - { - BlockNumber: 1, - Address: registryAddr, - Topics: convertTopics([]common.Hash{ - ac.IAutomationV21PlusCommonDedupKeyAdded{}.Topic(), - }), - }, - }, - []string{}, - nil, - false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - mp := mocks.NewLogPoller(t) - mp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil).Maybe() - mp.On("UnregisterFilter", mock.Anything, mock.Anything).Return(nil).Maybe() - scanner := NewPerformedEventsScanner(lggr, mp, registryAddr, 100) - - go func() { - _ = scanner.Start(ctx) - }() - defer func() { - _ = scanner.Close() - }() - - mp.On("IndexedLogs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.pollerResults, tc.pollerErr).Maybe() - - results, err := scanner.ScanWorkIDs(ctx, tc.workIDs...) - if tc.errored { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Len(t, results, len(tc.scannerResults)) - - for _, result := range results { - require.Contains(t, tc.scannerResults, result) - } - }) - } -} - -func TestPerformedEventsScanner_Batch(t *testing.T) { - ctx := t.Context() - registryAddr := common.HexToAddress("0x12345") - lggr := logger.Test(t) - lp := mocks.NewLogPoller(t) - scanner := NewPerformedEventsScanner(lggr, lp, registryAddr, 100) - - lp.On("IndexedLogs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]logpoller.Log{ - { - BlockNumber: 1, - Address: registryAddr, - Topics: convertTopics([]common.Hash{ - ac.IAutomationV21PlusCommonDedupKeyAdded{}.Topic(), - common.HexToHash("0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), - }), - }, - }, nil).Times(1) - lp.On("IndexedLogs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]logpoller.Log{ - { - BlockNumber: 3, - Address: registryAddr, - Topics: convertTopics([]common.Hash{ - ac.IAutomationV21PlusCommonDedupKeyAdded{}.Topic(), - common.HexToHash("0x331decd9548b62a8d603457658386fc84ba6bc95888008f6362f93160ef3b663"), - }), - }, - }, nil).Times(1) - - origWorkIDsBatchSize := workIDsBatchSize - workIDsBatchSize = 8 - defer func() { - workIDsBatchSize = origWorkIDsBatchSize - }() - - ids, err := scanner.ScanWorkIDs(ctx, - "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", - "1111", "2222", "3333", "4444", "5555", "6666", "7777", "8888", "9999", - "331decd9548b62a8d603457658386fc84ba6bc95888008f6362f93160ef3b663", - ) - - require.NoError(t, err) - require.Len(t, ids, 2) - slices.Sort(ids) - require.Equal(t, "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", ids[0]) - require.Equal(t, "331decd9548b62a8d603457658386fc84ba6bc95888008f6362f93160ef3b663", ids[1]) - - lp.AssertExpectations(t) -} - -func convertTopics(topics []common.Hash) [][]byte { - var topicsForDB [][]byte - for _, t := range topics { - topicsForDB = append(topicsForDB, t.Bytes()) - } - return topicsForDB -} diff --git a/pkg/automation/v21/upkeepstate/store.go b/pkg/automation/v21/upkeepstate/store.go deleted file mode 100644 index 9f3b3a8cb3..0000000000 --- a/pkg/automation/v21/upkeepstate/store.go +++ /dev/null @@ -1,337 +0,0 @@ -package upkeepstate - -import ( - "context" - "errors" - "io" - "math/big" - "sync" - "time" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink-common/pkg/timeutil" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/core" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/threadcontrol" -) - -const ( - UpkeepStateStoreServiceName = "UpkeepStateStore" - // CacheExpiration is the amount of time that we keep a record in the cache. - CacheExpiration = 24 * time.Hour - // GCInterval is the amount of time between cache cleanups. - GCInterval = 2 * time.Hour - // flushCadence is the amount of time between flushes to the DB. - flushCadence = 30 * time.Second - concurrentBatchCalls = 10 -) - -type ORM interface { - BatchInsertRecords(context.Context, []persistedStateRecord) error - SelectStatesByWorkIDs(context.Context, []string) ([]persistedStateRecord, error) - DeleteExpired(context.Context, time.Time) error -} - -// UpkeepStateStore is the interface for managing upkeeps final state in a local store. -type UpkeepStateStore interface { - ocr2keepers.UpkeepStateUpdater - core.UpkeepStateReader - Start(context.Context) error - io.Closer -} - -var ( - _ UpkeepStateStore = &upkeepStateStore{} - newTickerFn = time.NewTicker - batchSize = 1000 -) - -// upkeepStateRecord is a record that we save in a local cache. -type upkeepStateRecord struct { - workID string - state ocr2keepers.UpkeepState - - addedAt time.Time -} - -// upkeepStateStore implements UpkeepStateStore. -// It stores the state of ineligible upkeeps in a local, in-memory cache. -// In addition, performed events are fetched by the scanner on demand. -type upkeepStateStore struct { - services.StateMachine - threadCtrl threadcontrol.ThreadControl - - orm ORM - lggr logger.Logger - scanner PerformedLogsScanner - - retention time.Duration - cleanCadence time.Duration - - mu sync.RWMutex - cache map[string]*upkeepStateRecord - - pendingRecords []persistedStateRecord - sem chan struct{} - batchSize int -} - -// NewUpkeepStateStore creates a new state store -func NewUpkeepStateStore(orm ORM, lggr logger.Logger, scanner PerformedLogsScanner) *upkeepStateStore { - return &upkeepStateStore{ - orm: orm, - lggr: logger.Named(lggr, UpkeepStateStoreServiceName), - cache: map[string]*upkeepStateRecord{}, - scanner: scanner, - retention: CacheExpiration, - cleanCadence: GCInterval, - threadCtrl: threadcontrol.NewThreadControl(), - pendingRecords: []persistedStateRecord{}, - sem: make(chan struct{}, concurrentBatchCalls), - batchSize: batchSize, - } -} - -// Start starts the upkeep state store. -// it does background cleanup of the cache every GCInterval, -// and flush records to DB every flushCadence. -func (u *upkeepStateStore) Start(pctx context.Context) error { - return u.StartOnce(UpkeepStateStoreServiceName, func() error { - if err := u.scanner.Start(pctx); err != nil { - return errors.New("failed to start scanner") - } - - u.lggr.Debug("Starting upkeep state store") - - u.threadCtrl.Go(func(ctx context.Context) { - ticker := services.NewTicker(u.cleanCadence) - defer ticker.Stop() - - flushTicker := newTickerFn(timeutil.JitterPct(0.1).Apply(flushCadence)) - defer flushTicker.Stop() - - for { - select { - case <-ticker.C: - if err := u.cleanup(ctx); err != nil { - u.lggr.Errorw("unable to clean old state values", "err", err) - } - ticker.Reset() - case <-flushTicker.C: - u.flush(ctx) - flushTicker.Reset(timeutil.JitterPct(0.1).Apply(flushCadence)) - case <-ctx.Done(): - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(context.WithoutCancel(ctx), time.Second) - defer cancel() - u.flush(ctx) - return - } - } - }) - return nil - }) -} - -func (u *upkeepStateStore) flush(ctx context.Context) { - u.mu.Lock() - cloneRecords := make([]persistedStateRecord, len(u.pendingRecords)) - copy(cloneRecords, u.pendingRecords) - u.pendingRecords = []persistedStateRecord{} - u.mu.Unlock() - - for i := 0; i < len(cloneRecords); i += u.batchSize { - end := min(i+u.batchSize, len(cloneRecords)) - - batch := cloneRecords[i:end] - - u.sem <- struct{}{} - - go func() { - if err := u.orm.BatchInsertRecords(ctx, batch); err != nil { - u.lggr.Errorw("error inserting records", "err", err) - } - <-u.sem - }() - } -} - -// Close stops the service of pruning stale data; implements io.Closer -func (u *upkeepStateStore) Close() error { - return u.StopOnce(UpkeepStateStoreServiceName, func() error { - u.threadCtrl.Close() - return nil - }) -} - -func (u *upkeepStateStore) HealthReport() map[string]error { - return map[string]error{UpkeepStateStoreServiceName: u.Healthy()} -} - -// SelectByWorkIDs returns the current state of the upkeep for the provided ids. -// If an id is not found, the state is returned as StateUnknown. -// We first check the cache, and if any ids are missing, we fetch them from the scanner and DB. -func (u *upkeepStateStore) SelectByWorkIDs(ctx context.Context, workIDs ...string) ([]ocr2keepers.UpkeepState, error) { - states, missing := u.selectFromCache(workIDs...) - if len(missing) == 0 { - // all ids were found in the cache - return states, nil - } - if err := u.fetchPerformed(ctx, missing...); err != nil { - return nil, err - } - if err := u.fetchFromDB(ctx, missing...); err != nil { - return nil, err - } - - // at this point all values should be in the cache. if values are missing - // their state is indicated as unknown - states, _ = u.selectFromCache(workIDs...) - - return states, nil -} - -// SetUpkeepState updates the state of the upkeep. -// Currently we only store the state if the upkeep is ineligible. -// Performed events will be fetched on demand. -func (u *upkeepStateStore) SetUpkeepState(ctx context.Context, result ocr2keepers.CheckResult, _ ocr2keepers.UpkeepState) error { - if result.Eligible { - return nil - } - - return u.upsertStateRecord(ctx, result.WorkID, ocr2keepers.Ineligible, uint64(result.Trigger.BlockNumber), result.UpkeepID.BigInt(), result.IneligibilityReason) -} - -// upsertStateRecord inserts or updates a record for the provided -// check result. If an item already exists in the data store, the state and -// block are updated. -func (u *upkeepStateStore) upsertStateRecord(ctx context.Context, workID string, s ocr2keepers.UpkeepState, b uint64, upkeepID *big.Int, reason uint8) error { - u.mu.Lock() - defer u.mu.Unlock() - - record, ok := u.cache[workID] - if !ok { - record = &upkeepStateRecord{ - workID: workID, - addedAt: time.Now(), - } - } - - record.state = s - - u.cache[workID] = record - - u.pendingRecords = append(u.pendingRecords, persistedStateRecord{ - UpkeepID: sqlutil.New(upkeepID), - WorkID: record.workID, - CompletionState: uint8(record.state), - IneligibilityReason: reason, - InsertedAt: record.addedAt, - }) - - return nil -} - -// fetchPerformed fetches all performed logs from the scanner to populate the cache. -func (u *upkeepStateStore) fetchPerformed(ctx context.Context, workIDs ...string) error { - performed, err := u.scanner.ScanWorkIDs(ctx, workIDs...) - if err != nil { - return err - } - - if len(performed) > 0 { - u.lggr.Debugw("Fetched performed logs", "performed", len(performed)) - } - - u.mu.Lock() - defer u.mu.Unlock() - - for _, workID := range performed { - if _, ok := u.cache[workID]; !ok { - s := &upkeepStateRecord{ - workID: workID, - state: ocr2keepers.Performed, - addedAt: time.Now(), - } - - u.cache[workID] = s - } - } - - return nil -} - -// fetchFromDB fetches all upkeeps indicated as ineligible from the db to -// populate the cache. -func (u *upkeepStateStore) fetchFromDB(ctx context.Context, workIDs ...string) error { - states, err := u.orm.SelectStatesByWorkIDs(ctx, workIDs) - if err != nil { - return err - } - - u.mu.Lock() - defer u.mu.Unlock() - - for _, state := range states { - if _, ok := u.cache[state.WorkID]; !ok { - u.cache[state.WorkID] = &upkeepStateRecord{ - workID: state.WorkID, - state: ocr2keepers.UpkeepState(state.CompletionState), - addedAt: state.InsertedAt, - } - } - } - - return nil -} - -// selectFromCache returns all saved state values for the provided ids, -// returning stateNotFound for any ids that are not found. -// the second return value is true if all ids were found in the cache. -func (u *upkeepStateStore) selectFromCache(workIDs ...string) ([]ocr2keepers.UpkeepState, []string) { - u.mu.RLock() - defer u.mu.RUnlock() - - var missing []string - states := make([]ocr2keepers.UpkeepState, len(workIDs)) - for i, workID := range workIDs { - if state, ok := u.cache[workID]; ok { - states[i] = state.state - } else { - missing = append(missing, workID) - } - } - - return states, missing -} - -// cleanup removes any records that are older than the TTL from both cache and DB. -func (u *upkeepStateStore) cleanup(ctx context.Context) error { - u.cleanCache() - - return u.cleanDB(ctx) -} - -// cleanDB cleans up records in the DB that are older than the TTL. -func (u *upkeepStateStore) cleanDB(ctx context.Context) error { - tm := time.Now().Add(-1 * u.retention) - - ctx, cancel := context.WithTimeout(sqlutil.WithoutDefaultTimeout(ctx), time.Minute) - defer cancel() - return u.orm.DeleteExpired(ctx, tm) -} - -// cleanupCache removes any records from the cache that are older than the TTL. -func (u *upkeepStateStore) cleanCache() { - u.mu.Lock() - defer u.mu.Unlock() - - for id, state := range u.cache { - if time.Since(state.addedAt) > u.retention { - delete(u.cache, id) - } - } -} diff --git a/pkg/automation/v21/upkeepstate/store_test.go b/pkg/automation/v21/upkeepstate/store_test.go deleted file mode 100644 index a2db004cdc..0000000000 --- a/pkg/automation/v21/upkeepstate/store_test.go +++ /dev/null @@ -1,599 +0,0 @@ -package upkeepstate - -import ( - "context" - "errors" - "math/big" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" - "github.com/smartcontractkit/chainlink-evm/pkg/testutils" -) - -func TestUpkeepStateStore(t *testing.T) { - tests := []struct { - name string - inserts []ocr2keepers.CheckResult - workIDsSelect []string - workIDsFromScanner []string - errScanner error - recordsFromDB []persistedStateRecord - errDB error - expected []ocr2keepers.UpkeepState - errored bool - }{ - { - name: "empty store", - }, - { - name: "save only ineligible states", - inserts: []ocr2keepers.CheckResult{ - { - UpkeepID: createUpkeepIDForTest(1), - WorkID: "0x1", - Eligible: false, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - }, - { - UpkeepID: createUpkeepIDForTest(2), - WorkID: "ox2", - Eligible: true, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(2), - }, - }, - }, - workIDsSelect: []string{"0x1", "0x2"}, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.Ineligible, - ocr2keepers.UnknownState, - }, - }, - { - name: "fetch results from scanner", - inserts: []ocr2keepers.CheckResult{ - { - UpkeepID: createUpkeepIDForTest(1), - WorkID: "0x1", - Eligible: false, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - }, - }, - workIDsSelect: []string{"0x1", "0x2"}, - workIDsFromScanner: []string{"0x2", "0x222"}, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.Ineligible, - ocr2keepers.Performed, - }, - }, - { - name: "fetch results from db", - inserts: []ocr2keepers.CheckResult{ - { - UpkeepID: createUpkeepIDForTest(1), - WorkID: "0x1", - Eligible: false, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - }, - }, - workIDsSelect: []string{"0x1", "0x2", "0x3"}, - workIDsFromScanner: []string{"0x2", "0x222"}, - recordsFromDB: []persistedStateRecord{ - { - WorkID: "0x3", - CompletionState: 2, - BlockNumber: 2, - InsertedAt: time.Now(), - }, - }, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.Ineligible, - ocr2keepers.Performed, - ocr2keepers.Ineligible, - }, - }, - { - name: "unknown states", - inserts: []ocr2keepers.CheckResult{ - { - UpkeepID: createUpkeepIDForTest(1), - WorkID: "0x1", - Eligible: false, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - }, - }, - workIDsSelect: []string{"0x2"}, - workIDsFromScanner: []string{}, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - }, - }, - { - name: "scanner error", - inserts: []ocr2keepers.CheckResult{ - { - UpkeepID: createUpkeepIDForTest(1), - WorkID: "0x1", - Eligible: false, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - }, - }, - workIDsSelect: []string{"0x1", "0x2"}, - workIDsFromScanner: []string{"0x2", "0x222"}, - errScanner: errors.New("test error"), - errored: true, - }, - { - name: "db error", - inserts: []ocr2keepers.CheckResult{ - { - UpkeepID: createUpkeepIDForTest(1), - WorkID: "0x1", - Eligible: false, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - }, - }, - workIDsSelect: []string{"0x1", "0x2"}, - workIDsFromScanner: []string{"0x2", "0x222"}, - errDB: errors.New("test error"), - errored: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := t.Context() - lggr := logger.Test(t) - - scanner := &mockScanner{} - scanner.addWorkID(tc.workIDsFromScanner...) - scanner.setErr(tc.errScanner) - - orm := &mockORM{} - orm.addRecords(tc.recordsFromDB...) - orm.setErr(tc.errDB) - - store := NewUpkeepStateStore(orm, lggr, scanner) - - for _, insert := range tc.inserts { - assert.NoError(t, store.SetUpkeepState(ctx, insert, ocr2keepers.Performed)) - } - - states, err := store.SelectByWorkIDs(ctx, tc.workIDsSelect...) - if tc.errored { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - assert.Len(t, states, len(tc.expected)) - for i, state := range states { - assert.Equal(t, tc.expected[i], state) - } - }) - } -} - -func TestUpkeepStateStore_SetSelectIntegration(t *testing.T) { - if testing.Short() { - t.Skip("database required for upkeep state store integration test") - } - - makeTestResult := func(id int64, workID string, eligible bool, block uint64) ocr2keepers.CheckResult { - uid := &ocr2keepers.UpkeepIdentifier{} - _ = uid.FromBigInt(big.NewInt(id)) - - return ocr2keepers.CheckResult{ - UpkeepID: *uid, - WorkID: workID, - Eligible: eligible, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(block), - }, - } - } - - type storedValue struct { - result ocr2keepers.CheckResult - state ocr2keepers.UpkeepState - } - - tests := []struct { - name string - flushSize int - expectedWrites int - queryIDs []string - storedValues []storedValue - expected []ocr2keepers.UpkeepState - }{ - { - name: "querying non-stored workIDs on db with values returns unknown state results", - queryIDs: []string{"0x1", "0x2", "0x3", "0x4"}, - flushSize: 10, - expectedWrites: 1, - storedValues: []storedValue{ - {result: makeTestResult(1, "0x11", false, 1), state: ocr2keepers.Performed}, - {result: makeTestResult(2, "0x22", false, 1), state: ocr2keepers.Performed}, - {result: makeTestResult(3, "0x33", false, 1), state: ocr2keepers.Performed}, - {result: makeTestResult(4, "0x44", false, 1), state: ocr2keepers.Performed}, - }, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - ocr2keepers.UnknownState, - ocr2keepers.UnknownState, - ocr2keepers.UnknownState, - }, - }, - { - name: "storing eligible values is a noop", - queryIDs: []string{"0x1", "0x2", "0x3", "0x4"}, - flushSize: 4, - expectedWrites: 1, - storedValues: []storedValue{ - {result: makeTestResult(9, "0x1", false, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(10, "0x2", false, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(11, "0x3", false, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(12, "0x4", true, 1), state: ocr2keepers.Performed}, // gets inserted - }, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.Ineligible, - ocr2keepers.Ineligible, - ocr2keepers.Ineligible, - ocr2keepers.UnknownState, - }, - }, - { - name: "provided state on setupkeepstate is currently ignored for eligible check results", - queryIDs: []string{"0x1", "0x2"}, - flushSize: 1, - expectedWrites: 1, - storedValues: []storedValue{ - {result: makeTestResult(13, "0x1", true, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(14, "0x2", false, 1), state: ocr2keepers.Performed}, // gets inserted - }, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - ocr2keepers.Ineligible, - }, - }, - { - name: "provided state outside the flush batch isn't registered in the db", - queryIDs: []string{"0x1", "0x2", "0x3", "0x4", "0x5", "0x6", "0x7", "0x8"}, - flushSize: 3, - expectedWrites: 2, - storedValues: []storedValue{ - {result: makeTestResult(13, "0x1", true, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(14, "0x2", false, 1), state: ocr2keepers.Performed}, // gets inserted - {result: makeTestResult(15, "0x3", true, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(16, "0x4", false, 1), state: ocr2keepers.Performed}, // gets inserted - {result: makeTestResult(17, "0x5", true, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(18, "0x6", false, 1), state: ocr2keepers.Performed}, // gets inserted - {result: makeTestResult(19, "0x7", true, 1), state: ocr2keepers.Ineligible}, - {result: makeTestResult(20, "0x8", false, 1), state: ocr2keepers.Performed}, // gets inserted - }, - expected: []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - ocr2keepers.Ineligible, - ocr2keepers.UnknownState, - ocr2keepers.Ineligible, - ocr2keepers.UnknownState, - ocr2keepers.Ineligible, - ocr2keepers.UnknownState, - ocr2keepers.Ineligible, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ctx := t.Context() - - tickerCh := make(chan time.Time) - - oldNewTickerFn := newTickerFn - oldFlushSize := batchSize - newTickerFn = func(d time.Duration) *time.Ticker { - t := time.NewTicker(d) - t.C = tickerCh - return t - } - batchSize = test.flushSize - defer func() { - newTickerFn = oldNewTickerFn - batchSize = oldFlushSize - }() - - lggr, observedLogs := logger.TestObserved(t, zapcore.ErrorLevel) - chainID := testutils.FixtureChainID - db := testutils.NewSqlxDB(t) - realORM := NewORM(chainID, db) - insertFinished := make(chan struct{}, 1) - orm := &wrappedORM{ - BatchInsertRecordsFn: func(ctx context.Context, records []persistedStateRecord) error { - err := realORM.BatchInsertRecords(ctx, records) - insertFinished <- struct{}{} - return err - }, - SelectStatesByWorkIDsFn: realORM.SelectStatesByWorkIDs, - DeleteExpiredFn: realORM.DeleteExpired, - } - scanner := &mockScanner{} - store := NewUpkeepStateStore(orm, lggr, scanner) - - servicetest.Run(t, store) - - t.Cleanup(func() { - t.Log("cleaning up database") - - if _, err := db.Exec(`DELETE FROM evm.upkeep_states`); err != nil { - t.Logf("error in cleanup: %s", err) - } - }) - - for _, insert := range test.storedValues { - require.NoError(t, store.SetUpkeepState(ctx, insert.result, insert.state), "storing states should not produce an error") - } - - tickerCh <- time.Now() - - // if this test inserts data, wait for the insert to complete before proceeding - for i := 0; i < test.expectedWrites; i++ { - <-insertFinished - } - - // empty the cache before doing selects to force a db lookup - store.clearCache() - - states, err := store.SelectByWorkIDs(ctx, test.queryIDs...) - - require.NoError(t, err, "no error expected from selecting states") - - assert.Equal(t, test.expected, states, "upkeep state values should match expected") - - observedLogs.TakeAll() - - require.Equal(t, 0, observedLogs.Len()) - }) - } -} - -func (u *upkeepStateStore) clearCache() { - u.mu.Lock() - defer u.mu.Unlock() - u.cache = make(map[string]*upkeepStateRecord) -} - -func TestUpkeepStateStore_emptyDB(t *testing.T) { - t.Run("querying non-stored workIDs on empty db returns unknown state results", func(t *testing.T) { - lggr, observedLogs := logger.TestObserved(t, zapcore.ErrorLevel) - chainID := testutils.FixtureChainID - db := testutils.NewSqlxDB(t) - realORM := NewORM(chainID, db) - insertFinished := make(chan struct{}, 1) - orm := &wrappedORM{ - BatchInsertRecordsFn: func(ctx context.Context, records []persistedStateRecord) error { - err := realORM.BatchInsertRecords(ctx, records) - insertFinished <- struct{}{} - return err - }, - SelectStatesByWorkIDsFn: realORM.SelectStatesByWorkIDs, - DeleteExpiredFn: realORM.DeleteExpired, - } - scanner := &mockScanner{} - store := NewUpkeepStateStore(orm, lggr, scanner) - - states, err := store.SelectByWorkIDs(t.Context(), []string{"0x1", "0x2", "0x3", "0x4"}...) - assert.NoError(t, err) - assert.Equal(t, []ocr2keepers.UpkeepState{ - ocr2keepers.UnknownState, - ocr2keepers.UnknownState, - ocr2keepers.UnknownState, - ocr2keepers.UnknownState, - }, states) - - observedLogs.TakeAll() - - require.Equal(t, 0, observedLogs.Len()) - }) -} - -func TestUpkeepStateStore_Upsert(t *testing.T) { - db := testutils.NewSqlxDB(t) - ctx := t.Context() - lggr := logger.Test(t) - chainID := testutils.FixtureChainID - orm := NewORM(chainID, db) - - store := NewUpkeepStateStore(orm, lggr, &mockScanner{}) - - res := ocr2keepers.CheckResult{ - UpkeepID: createUpkeepIDForTest(1), - WorkID: "0x1", - Eligible: false, - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - } - require.NoError(t, store.SetUpkeepState(ctx, res, ocr2keepers.Performed)) - <-time.After(10 * time.Millisecond) - res.Trigger.BlockNumber = ocr2keepers.BlockNumber(2) - now := time.Now() - require.NoError(t, store.SetUpkeepState(ctx, res, ocr2keepers.Performed)) - - store.mu.Lock() - addedAt := store.cache["0x1"].addedAt - store.mu.Unlock() - - require.True(t, now.After(addedAt)) -} - -func TestUpkeepStateStore_Service(t *testing.T) { - ctx := t.Context() - orm := &mockORM{ - onDelete: func(tm time.Time) { - - }, - } - scanner := &mockScanner{} - - store := NewUpkeepStateStore(orm, logger.Test(t), scanner) - - store.retention = 500 * time.Millisecond - store.cleanCadence = 100 * time.Millisecond - - servicetest.Run(t, store) - - // add a value to set up the test - require.NoError(t, store.SetUpkeepState(ctx, ocr2keepers.CheckResult{ - Eligible: false, - WorkID: "0x2", - Trigger: ocr2keepers.Trigger{ - BlockNumber: ocr2keepers.BlockNumber(1), - }, - }, ocr2keepers.Ineligible)) - - // allow one cycle of cleaning the cache - time.Sleep(110 * time.Millisecond) - - // select from store to ensure values still exist - values, err := store.SelectByWorkIDs(ctx, "0x2") - require.NoError(t, err, "no error from selecting states") - require.Equal(t, []ocr2keepers.UpkeepState{ocr2keepers.Ineligible}, values, "selected values should match expected") - - // wait longer than cache timeout - time.Sleep(700 * time.Millisecond) - - // select from store to ensure cached values were removed - values, err = store.SelectByWorkIDs(ctx, "0x2") - require.NoError(t, err, "no error from selecting states") - require.Equal(t, []ocr2keepers.UpkeepState{ocr2keepers.UnknownState}, values, "selected values should match expected") -} - -func createUpkeepIDForTest(v int64) ocr2keepers.UpkeepIdentifier { - uid := &ocr2keepers.UpkeepIdentifier{} - _ = uid.FromBigInt(big.NewInt(v)) - - return *uid -} - -type mockScanner struct { - lock sync.Mutex - workIDs []string - err error -} - -func (s *mockScanner) addWorkID(workIDs ...string) { - s.lock.Lock() - defer s.lock.Unlock() - - s.workIDs = append(s.workIDs, workIDs...) -} - -func (s *mockScanner) setErr(err error) { - s.lock.Lock() - defer s.lock.Unlock() - - s.err = err -} - -func (s *mockScanner) ScanWorkIDs(context.Context, ...string) ([]string, error) { - s.lock.Lock() - defer s.lock.Unlock() - - res := s.workIDs - s.workIDs = nil - return res, s.err -} - -func (s *mockScanner) Start(context.Context) error { - return nil -} - -func (s *mockScanner) Close() error { - return nil -} - -type mockORM struct { - lock sync.Mutex - records []persistedStateRecord - lastPruneDepth time.Time - onDelete func(tm time.Time) - err error -} - -func (_m *mockORM) addRecords(records ...persistedStateRecord) { - _m.lock.Lock() - defer _m.lock.Unlock() - - _m.records = append(_m.records, records...) -} - -func (_m *mockORM) setErr(err error) { - _m.lock.Lock() - defer _m.lock.Unlock() - - _m.err = err -} - -func (_m *mockORM) BatchInsertRecords(ctx context.Context, state []persistedStateRecord) error { - return nil -} - -func (_m *mockORM) SelectStatesByWorkIDs(ctx context.Context, workIDs []string) ([]persistedStateRecord, error) { - _m.lock.Lock() - defer _m.lock.Unlock() - - res := _m.records - _m.records = nil - - return res, _m.err -} - -func (_m *mockORM) DeleteExpired(ctx context.Context, tm time.Time) error { - _m.lock.Lock() - defer _m.lock.Unlock() - - _m.lastPruneDepth = tm - _m.onDelete(tm) - - return _m.err -} - -type wrappedORM struct { - BatchInsertRecordsFn func(context.Context, []persistedStateRecord) error - SelectStatesByWorkIDsFn func(context.Context, []string) ([]persistedStateRecord, error) - DeleteExpiredFn func(context.Context, time.Time) error -} - -func (o *wrappedORM) BatchInsertRecords(ctx context.Context, r []persistedStateRecord) error { - return o.BatchInsertRecordsFn(ctx, r) -} - -func (o *wrappedORM) SelectStatesByWorkIDs(ctx context.Context, ids []string) ([]persistedStateRecord, error) { - return o.SelectStatesByWorkIDsFn(ctx, ids) -} - -func (o *wrappedORM) DeleteExpired(ctx context.Context, t time.Time) error { - return o.DeleteExpiredFn(ctx, t) -} diff --git a/pkg/relay/evm.go b/pkg/relay/evm.go index 6300745bf9..5be8da8062 100644 --- a/pkg/relay/evm.go +++ b/pkg/relay/evm.go @@ -761,13 +761,6 @@ func (r *Relayer) NewMedianProvider(ctx context.Context, rargs commontypes.Relay return &medianProvider, nil } -func (r *Relayer) NewAutomationProvider(ctx context.Context, rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.AutomationProvider, error) { - lggr := logger.Sugared(r.lggr).Named(rargs.ExternalJobID.String()).Named("AutomationProvider") - ocr2keeperRelayer := NewOCR2KeeperRelayer(r.ds, r.chain, lggr.Named("OCR2KeeperRelayer"), r.evmKeystore) - - return ocr2keeperRelayer.NewOCR2KeeperProvider(ctx, rargs, pargs) -} - var _ commontypes.MedianProvider = (*medianProvider)(nil) type medianProvider struct { diff --git a/pkg/relay/ocr2keeper.go b/pkg/relay/ocr2keeper.go deleted file mode 100644 index fc606f82f4..0000000000 --- a/pkg/relay/ocr2keeper.go +++ /dev/null @@ -1,282 +0,0 @@ -package relay - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - - "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" - "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - ac "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/i_automation_v21_plus_common" - evm "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/encoding" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/logprovider" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/transmit" - "github.com/smartcontractkit/chainlink-evm/pkg/automation/v21/upkeepstate" - "github.com/smartcontractkit/chainlink-evm/pkg/chains/legacyevm" - "github.com/smartcontractkit/chainlink-evm/pkg/config" - "github.com/smartcontractkit/chainlink-evm/pkg/keys" - "github.com/smartcontractkit/chainlink-evm/pkg/transmitter" - evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" -) - -var ( - _ OCR2KeeperRelayer = (*ocr2keeperRelayer)(nil) - _ OCR2KeeperProvider = (*ocr2keeperProvider)(nil) - ErrInitializationFailure = errors.New("failed to initialize registry") -) - -// OCR2KeeperProviderOpts is the custom options to create a keeper provider -type OCR2KeeperProviderOpts struct { - RArgs commontypes.RelayArgs - PArgs commontypes.PluginArgs - InstanceID int -} - -// OCR2KeeperProvider provides all components needed for a OCR2Keeper plugin. -type OCR2KeeperProvider interface { - commontypes.PluginProvider - Registry() automation.Registry - Encoder() automation.Encoder - TransmitEventProvider() automation.EventProvider - BlockSubscriber() automation.BlockSubscriber - PayloadBuilder() automation.PayloadBuilder - UpkeepStateStore() automation.UpkeepStateStore - LogEventProvider() automation.LogEventProvider - LogRecoverer() automation.LogRecoverer - UpkeepProvider() automation.ConditionalUpkeepProvider -} - -// OCR2KeeperRelayer contains the relayer and instantiating functions for OCR2Keeper providers. -type OCR2KeeperRelayer interface { - NewOCR2KeeperProvider(ctx context.Context, rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (OCR2KeeperProvider, error) -} - -// ocr2keeperRelayer is the relayer with added DKG and OCR2Keeper provider functions. -type ocr2keeperRelayer struct { - ds sqlutil.DataSource - chain legacyevm.Chain - lggr logger.Logger - ethKeystore keys.Store -} - -// NewOCR2KeeperRelayer is the constructor of ocr2keeperRelayer -func NewOCR2KeeperRelayer(ds sqlutil.DataSource, chain legacyevm.Chain, lggr logger.Logger, ethKeystore keys.Store) OCR2KeeperRelayer { - return &ocr2keeperRelayer{ - ds: ds, - chain: chain, - lggr: lggr, - ethKeystore: ethKeystore, - } -} - -func (r *ocr2keeperRelayer) NewOCR2KeeperProvider(ctx context.Context, rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (OCR2KeeperProvider, error) { - cfgWatcher, err := newOCR2KeeperConfigProvider(ctx, r.lggr, r.chain, rargs) - if err != nil { - return nil, err - } - - gasLimit := cfgWatcher.chain.Config().EVM().OCR2().Automation().GasLimit() - contractTransmitter, err := transmitter.NewContractTransmitter(ctx, r.lggr, rargs, r.ethKeystore, cfgWatcher.chain, cfgWatcher.contractAddress, transmitter.ConfigTransmitterOpts{PluginGasLimit: &gasLimit}, OCR2AggregatorTransmissionContractABI, false) - if err != nil { - return nil, err - } - - client := r.chain - - services := new(ocr2keeperProvider) - services.configWatcher = cfgWatcher - services.contractTransmitter = contractTransmitter - - addr := evmtypes.MustEIP55Address(rargs.ContractID).Address() - - registryContract, err := ac.NewIAutomationV21PlusCommon(addr, client.Client()) - if err != nil { - return nil, fmt.Errorf("%w: failed to create caller for address and backend", ErrInitializationFailure) - } - // lookback blocks for transmit event is hard coded and should provide ample time for logs - // to be detected in most cases - var transmitLookbackBlocks int64 = 250 - transmitEventProvider, err := transmit.NewTransmitEventProvider(ctx, r.lggr, client.LogPoller(), addr, client.Client(), transmitLookbackBlocks) - if err != nil { - return nil, err - } - - services.transmitEventProvider = transmitEventProvider - - packer := encoding.NewAbiPacker() - services.encoder = encoding.NewReportEncoder(packer) - - finalityDepth := client.Config().EVM().FinalityDepth() - - orm := upkeepstate.NewORM(client.ID(), r.ds) - scanner := upkeepstate.NewPerformedEventsScanner(r.lggr, client.LogPoller(), addr, finalityDepth) - services.upkeepStateStore = upkeepstate.NewUpkeepStateStore(orm, r.lggr, scanner) - - logProvider, logRecoverer := logprovider.New(r.lggr, client.LogPoller(), client.Client(), services.upkeepStateStore, finalityDepth, client.ID()) - services.logEventProvider = logProvider - services.logRecoverer = logRecoverer - blockSubscriber := evm.NewBlockSubscriber(client.HeadBroadcaster(), client.LogPoller(), finalityDepth, r.lggr) - services.blockSubscriber = blockSubscriber - - al := evm.NewActiveUpkeepList() - services.payloadBuilder = evm.NewPayloadBuilder(al, logRecoverer, r.lggr) - - services.registry = evm.NewEvmRegistry(r.lggr, addr, client, - registryContract, rargs.MercuryCredentials, al, logProvider, - packer, blockSubscriber, finalityDepth) - - services.conditionalUpkeepProvider = evm.NewUpkeepProvider(al, blockSubscriber, client.LogPoller()) - - return services, nil -} - -type ocr3keeperProviderContractTransmitter struct { - contractTransmitter ocrtypes.ContractTransmitter -} - -var _ ocr3types.ContractTransmitter[plugin.AutomationReportInfo] = &ocr3keeperProviderContractTransmitter{} - -func NewKeepersOCR3ContractTransmitter(ocr2ContractTransmitter ocrtypes.ContractTransmitter) *ocr3keeperProviderContractTransmitter { - return &ocr3keeperProviderContractTransmitter{ocr2ContractTransmitter} -} - -func (t *ocr3keeperProviderContractTransmitter) Transmit( - ctx context.Context, - digest ocrtypes.ConfigDigest, - seqNr uint64, - reportWithInfo ocr3types.ReportWithInfo[plugin.AutomationReportInfo], - aoss []ocrtypes.AttributedOnchainSignature, -) error { - return t.contractTransmitter.Transmit( - ctx, - ocrtypes.ReportContext{ - ReportTimestamp: ocrtypes.ReportTimestamp{ - ConfigDigest: digest, - Epoch: uint32(seqNr), - }, - }, - reportWithInfo.Report, - aoss, - ) -} - -func (t *ocr3keeperProviderContractTransmitter) FromAccount(ctx context.Context) (ocrtypes.Account, error) { - return t.contractTransmitter.FromAccount(ctx) -} - -type ocr2keeperProvider struct { - *configWatcher - contractTransmitter transmitter.ContractTransmitter - registry automation.Registry - encoder automation.Encoder - transmitEventProvider automation.EventProvider - blockSubscriber automation.BlockSubscriber - payloadBuilder automation.PayloadBuilder - upkeepStateStore automation.UpkeepStateStore - logEventProvider automation.LogEventProvider - logRecoverer automation.LogRecoverer - conditionalUpkeepProvider automation.ConditionalUpkeepProvider -} - -func (c *ocr2keeperProvider) ContractTransmitter() ocrtypes.ContractTransmitter { - return c.contractTransmitter -} - -func (c *ocr2keeperProvider) ContractReader() commontypes.ContractReader { - return nil -} - -func (c *ocr2keeperProvider) Codec() commontypes.Codec { - return nil -} - -func newOCR2KeeperConfigProvider(ctx context.Context, lggr logger.Logger, chain legacyevm.Chain, rargs commontypes.RelayArgs) (*configWatcher, error) { - var relayConfig config.RelayConfig - err := json.Unmarshal(rargs.RelayConfig, &relayConfig) - if err != nil { - return nil, err - } - if !common.IsHexAddress(rargs.ContractID) { - return nil, fmt.Errorf("invalid contract address '%s'", rargs.ContractID) - } - - contractAddress := common.HexToAddress(rargs.ContractID) - - configPoller, err := NewConfigPoller( - ctx, - logger.With(lggr, "contractID", rargs.ContractID), - CPConfig{ - chain.Client(), - chain.LogPoller(), - contractAddress, - // TODO: Does ocr2keeper need to support config contract? DF-19182 - nil, - OCR2AggregatorLogDecoder, - }, - ) - if err != nil { - return nil, errors.Wrap(err, "failed to create config poller") - } - - offchainConfigDigester := evmutil.EVMOffchainConfigDigester{ - ChainID: chain.Config().EVM().ChainID().Uint64(), - ContractAddress: contractAddress, - } - - return newConfigWatcher( - lggr, - contractAddress, - offchainConfigDigester, - configPoller, - chain, - relayConfig.FromBlock, - rargs.New, - ), nil -} - -func (c *ocr2keeperProvider) Registry() automation.Registry { - return c.registry -} - -func (c *ocr2keeperProvider) Encoder() automation.Encoder { - return c.encoder -} - -func (c *ocr2keeperProvider) TransmitEventProvider() automation.EventProvider { - return c.transmitEventProvider -} - -func (c *ocr2keeperProvider) BlockSubscriber() automation.BlockSubscriber { - return c.blockSubscriber -} - -func (c *ocr2keeperProvider) PayloadBuilder() automation.PayloadBuilder { - return c.payloadBuilder -} - -func (c *ocr2keeperProvider) UpkeepStateStore() automation.UpkeepStateStore { - return c.upkeepStateStore -} - -func (c *ocr2keeperProvider) LogEventProvider() automation.LogEventProvider { - return c.logEventProvider -} - -func (c *ocr2keeperProvider) LogRecoverer() automation.LogRecoverer { - return c.logRecoverer -} - -func (c *ocr2keeperProvider) UpkeepProvider() automation.ConditionalUpkeepProvider { - return c.conditionalUpkeepProvider -} From dc9da9e8cb1c372cb3e167687d23f4c46f431d82 Mon Sep 17 00:00:00 2001 From: anirudhwarrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:30:31 +0400 Subject: [PATCH 2/2] cleanup residual config structs --- CONFIG.md | 25 +--------- go.md | 3 -- pkg/config/chain_scoped.go | 4 -- pkg/config/chain_scoped_gas_estimator.go | 4 -- pkg/config/chain_scoped_ocr2.go | 25 ---------- pkg/config/chain_scoped_ocr2_test.go | 14 ------ pkg/config/config.go | 10 ---- pkg/config/config_test.go | 1 - pkg/config/mocks/evm.go | 47 ------------------- pkg/config/mocks/limit_job_type.go | 47 ------------------- pkg/config/toml/config.go | 23 --------- pkg/config/toml/config_test.go | 8 ---- pkg/config/toml/defaults.go | 1 - .../toml/defaults/Arbitrum_Mainnet.toml | 3 -- .../toml/defaults/Arbitrum_Sepolia.toml | 3 -- pkg/config/toml/defaults/Base_Mainnet.toml | 3 -- pkg/config/toml/defaults/Base_Sepolia.toml | 3 -- .../toml/defaults/Ethereum_Mainnet.toml | 3 -- .../toml/defaults/Ethereum_Sepolia.toml | 3 -- pkg/config/toml/defaults/Fantom_Mainnet.toml | 5 +- pkg/config/toml/defaults/Fantom_Testnet.toml | 3 -- .../toml/defaults/Optimism_Mainnet.toml | 3 -- .../toml/defaults/Optimism_Sepolia.toml | 3 -- pkg/config/toml/defaults/Soneium_Mainnet.toml | 3 -- pkg/config/toml/defaults/Soneium_Sepolia.toml | 3 -- pkg/config/toml/defaults/ZKsync_Mainnet.toml | 3 -- pkg/config/toml/defaults/ZKsync_Sepolia.toml | 3 -- pkg/config/toml/defaults/Zircuit_Mainnet.toml | 3 -- pkg/config/toml/defaults/Zircuit_Sepolia.toml | 3 -- pkg/config/toml/defaults/fallback.toml | 3 -- pkg/config/toml/docs.toml | 11 +---- pkg/config/toml/testdata/config-full.toml | 5 -- 32 files changed, 5 insertions(+), 276 deletions(-) delete mode 100644 pkg/config/chain_scoped_ocr2.go delete mode 100644 pkg/config/chain_scoped_ocr2_test.go diff --git a/CONFIG.md b/CONFIG.md index 3058987a92..994904625a 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -263,7 +263,7 @@ For example, suppose RPC returns block 100 as the latest finalized. In that case With `EnforceRepeatableRead = true,` RPC is considered healthy only if its most recent finalized block is larger or equal to the highest finalized block observed by the CL Node minus `FinalizedBlockOffset.` Higher values of `FinalizedBlockOffset` with `EnforceRepeatableRead = true` reduce the number of false `FinalizedBlockOutOfSync` declarations on healthy RPCs that are slightly lagging behind due to network delays. This may increase the number of healthy RPCs and reduce the probability that the CL Node will not have any healthy alternatives to the active RPC. -CAUTION: Setting this to values higher than 0 may delay transaction creation in products (e.g., CCIP, Automation) that base their decision on finalized on-chain events. +CAUTION: Setting this to values higher than 0 may delay transaction creation in products (e.g., CCIP) that base their decision on finalized on-chain events. PoS chains with `FinalityTagEnabled=true` and batched (epochs) blocks finalization (e.g., Ethereum Mainnet) must be treated with special care as a minor increase in the `FinalizedBlockOffset` may lead to significant delays. For example, let's say that `FinalizedBlockOffset = 1` and blocks are finalized in batches of 32. The latest finalized block on chain is 64, so block 63 is the latest finalized for CL Node. @@ -558,7 +558,7 @@ Mode = 'FixedPrice' LimitDefault = 500_000 # Default ``` LimitDefault sets default gas limit for outgoing transactions. This should not need to be changed in most cases. -Some job types, such as Keeper jobs, might set their own gas limit unrelated to this value. +Some job types, might set their own gas limit unrelated to this value. ### LimitMax ```toml @@ -665,7 +665,6 @@ In EIP-1559 mode, the following changes occur to how configuration works: - `FixedPriceEstimator` will use `GasFeeCapDefault` instaed of `GasPriceDefault` for the fee cap - `PriceMin` is ignored for new transactions and `GasTipCapMinimum` is used instead (default 0) - `PriceMax` still represents that absolute upper limit that Chainlink will ever spend (total) on a single tx -- `Keeper.GasTipCapBufferPercent` is ignored in EIP-1559 mode and `Keeper.GasTipCapBufferPercent` is used instead ### FeeCapDefault ```toml @@ -724,7 +723,6 @@ OCR2 = 100_000 # Example DR = 100_000 # Example VRF = 100_000 # Example FM = 100_000 # Example -Keeper = 100_000 # Example ``` @@ -758,12 +756,6 @@ FM = 100_000 # Example ``` FM overrides LimitDefault for Flux Monitor jobs. -### Keeper -```toml -Keeper = 100_000 # Example -``` -Keeper overrides LimitDefault for Keeper jobs. - ## GasEstimator.BlockHistory ```toml [GasEstimator.BlockHistory] @@ -1343,19 +1335,6 @@ IsLoadBalancedRPC indicates whether the http/ws url above has multiple rpc's beh If true, we should try reconnecting to the node even when its the only node in the Nodes list. If false and its the only node in the nodes list, we will mark it alive even when its out of sync, because it might still be able to send txs. -## OCR2.Automation -```toml -[OCR2.Automation] -GasLimit = 5400000 # Default -``` - - -### GasLimit -```toml -GasLimit = 5400000 # Default -``` -GasLimit controls the gas limit for transmit transactions from ocr2automation job. - ## Workflow ```toml [Workflow] diff --git a/go.md b/go.md index 654bc8ae77..8a27aea035 100644 --- a/go.md +++ b/go.md @@ -4,8 +4,6 @@ flowchart LR chain-selectors click chain-selectors href "https://github.com/smartcontractkit/chain-selectors" - chainlink-automation --> chainlink-common - click chainlink-automation href "https://github.com/smartcontractkit/chainlink-automation" chainlink-common --> chainlink-common/pkg/chipingress chainlink-common --> chainlink-protos/billing/go chainlink-common --> chainlink-protos/cre/go @@ -28,7 +26,6 @@ flowchart LR chainlink-data-streams --> chainlink-common/keystore chainlink-data-streams --> chainlink-evm/gethwrappers click chainlink-data-streams href "https://github.com/smartcontractkit/chainlink-data-streams" - chainlink-evm --> chainlink-automation chainlink-evm --> chainlink-data-streams chainlink-evm --> chainlink-framework/capabilities chainlink-evm --> chainlink-framework/chains diff --git a/pkg/config/chain_scoped.go b/pkg/config/chain_scoped.go index d8705e3258..1fae6a231e 100644 --- a/pkg/config/chain_scoped.go +++ b/pkg/config/chain_scoped.go @@ -63,10 +63,6 @@ func (e *EVMConfig) OCR() OCR { return &ocrConfig{c: e.C.OCR} } -func (e *EVMConfig) OCR2() OCR2 { - return &ocr2Config{c: e.C.OCR2} -} - func (e *EVMConfig) Workflow() Workflow { return &workflowConfig{c: e.C.Workflow} } diff --git a/pkg/config/chain_scoped_gas_estimator.go b/pkg/config/chain_scoped_gas_estimator.go index 1523fca663..3abdad551f 100644 --- a/pkg/config/chain_scoped_gas_estimator.go +++ b/pkg/config/chain_scoped_gas_estimator.go @@ -172,10 +172,6 @@ func (l *limitJobTypeConfig) FM() *uint32 { return l.c.FM } -func (l *limitJobTypeConfig) Keeper() *uint32 { - return l.c.Keeper -} - func (l *limitJobTypeConfig) VRF() *uint32 { return l.c.VRF } diff --git a/pkg/config/chain_scoped_ocr2.go b/pkg/config/chain_scoped_ocr2.go deleted file mode 100644 index 186bf0bb1f..0000000000 --- a/pkg/config/chain_scoped_ocr2.go +++ /dev/null @@ -1,25 +0,0 @@ -package config - -import ( - "github.com/smartcontractkit/chainlink-evm/pkg/config/toml" -) - -type ocr2Automation struct { - c toml.Automation -} - -func (o *ocr2Automation) GasLimit() uint32 { - return *o.c.GasLimit -} - -type ocr2Config struct { - c toml.OCR2 -} - -func (o *ocr2Config) Automation() OCR2Automation { - return &ocr2Automation{c: o.c.Automation} -} - -func (o *ocr2Config) ContractConfirmations() uint16 { - return uint16(*o.c.Automation.GasLimit) -} diff --git a/pkg/config/chain_scoped_ocr2_test.go b/pkg/config/chain_scoped_ocr2_test.go deleted file mode 100644 index 4ec623b306..0000000000 --- a/pkg/config/chain_scoped_ocr2_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package config_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-evm/pkg/config/configtest" -) - -func Test_ocr2Config(t *testing.T) { - cfg := configtest.NewChainScopedConfig(t, nil) // fallback.toml values - require.Equal(t, uint32(5400000), cfg.EVM().OCR2().Automation().GasLimit()) -} diff --git a/pkg/config/config.go b/pkg/config/config.go index 8750b21319..5c21a3990b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -22,7 +22,6 @@ type EVM interface { Transactions() Transactions GasEstimator() GasEstimator OCR() OCR - OCR2() OCR2 Workflow() Workflow NodePool() NodePool @@ -69,14 +68,6 @@ type OCR interface { DeltaCJitterOverride() time.Duration } -type OCR2 interface { - Automation() OCR2Automation -} - -type OCR2Automation interface { - GasLimit() uint32 -} - type HeadTracker interface { HistoryDepth() uint32 MaxBufferSize() uint32 @@ -173,7 +164,6 @@ type LimitJobType interface { OCR2() *uint32 DR() *uint32 FM() *uint32 - Keeper() *uint32 VRF() *uint32 } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 05caba17e6..548d0a68e5 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -336,7 +336,6 @@ func TestChainScopedConfig_Profiles(t *testing.T) { assert.Nil(t, config.EVM().GasEstimator().LimitJobType().DR()) assert.Nil(t, config.EVM().GasEstimator().LimitJobType().VRF()) assert.Nil(t, config.EVM().GasEstimator().LimitJobType().FM()) - assert.Nil(t, config.EVM().GasEstimator().LimitJobType().Keeper()) assert.Equal(t, tt.expectedMinimumContractPayment, strings.TrimRight(config.EVM().MinContractPayment().Link(), "0")) }) } diff --git a/pkg/config/mocks/evm.go b/pkg/config/mocks/evm.go index 9ffc245124..1afd7a699c 100644 --- a/pkg/config/mocks/evm.go +++ b/pkg/config/mocks/evm.go @@ -1389,53 +1389,6 @@ func (_c *EVM_OCR_Call) RunAndReturn(run func() config.OCR) *EVM_OCR_Call { return _c } -// OCR2 provides a mock function with no fields -func (_m *EVM) OCR2() config.OCR2 { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for OCR2") - } - - var r0 config.OCR2 - if rf, ok := ret.Get(0).(func() config.OCR2); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(config.OCR2) - } - } - - return r0 -} - -// EVM_OCR2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OCR2' -type EVM_OCR2_Call struct { - *mock.Call -} - -// OCR2 is a helper method to define mock.On call -func (_e *EVM_Expecter) OCR2() *EVM_OCR2_Call { - return &EVM_OCR2_Call{Call: _e.mock.On("OCR2")} -} - -func (_c *EVM_OCR2_Call) Run(run func()) *EVM_OCR2_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *EVM_OCR2_Call) Return(_a0 config.OCR2) *EVM_OCR2_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *EVM_OCR2_Call) RunAndReturn(run func() config.OCR2) *EVM_OCR2_Call { - _c.Call.Return(run) - return _c -} - // OperatorFactoryAddress provides a mock function with no fields func (_m *EVM) OperatorFactoryAddress() string { ret := _m.Called() diff --git a/pkg/config/mocks/limit_job_type.go b/pkg/config/mocks/limit_job_type.go index 8ec8c096b0..46c2aa092c 100644 --- a/pkg/config/mocks/limit_job_type.go +++ b/pkg/config/mocks/limit_job_type.go @@ -111,53 +111,6 @@ func (_c *LimitJobType_FM_Call) RunAndReturn(run func() *uint32) *LimitJobType_F return _c } -// Keeper provides a mock function with no fields -func (_m *LimitJobType) Keeper() *uint32 { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Keeper") - } - - var r0 *uint32 - if rf, ok := ret.Get(0).(func() *uint32); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*uint32) - } - } - - return r0 -} - -// LimitJobType_Keeper_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Keeper' -type LimitJobType_Keeper_Call struct { - *mock.Call -} - -// Keeper is a helper method to define mock.On call -func (_e *LimitJobType_Expecter) Keeper() *LimitJobType_Keeper_Call { - return &LimitJobType_Keeper_Call{Call: _e.mock.On("Keeper")} -} - -func (_c *LimitJobType_Keeper_Call) Run(run func()) *LimitJobType_Keeper_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *LimitJobType_Keeper_Call) Return(_a0 *uint32) *LimitJobType_Keeper_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *LimitJobType_Keeper_Call) RunAndReturn(run func() *uint32) *LimitJobType_Keeper_Call { - _c.Call.Return(run) - return _c -} - // OCR provides a mock function with no fields func (_m *LimitJobType) OCR() *uint32 { ret := _m.Called() diff --git a/pkg/config/toml/config.go b/pkg/config/toml/config.go index 3900cb7e58..d149005745 100644 --- a/pkg/config/toml/config.go +++ b/pkg/config/toml/config.go @@ -423,7 +423,6 @@ type Chain struct { KeySpecific KeySpecificConfig `toml:",omitempty"` NodePool NodePool `toml:",omitempty"` OCR OCR `toml:",omitempty"` - OCR2 OCR2 `toml:",omitempty"` Workflow Workflow `toml:",omitempty"` } @@ -652,24 +651,6 @@ func (t *TransactionManagerV2Config) ValidateConfig() (err error) { return } -type OCR2 struct { - Automation Automation `toml:",omitempty"` -} - -func (o *OCR2) setFrom(f *OCR2) { - o.Automation.setFrom(&f.Automation) -} - -type Automation struct { - GasLimit *uint32 -} - -func (a *Automation) setFrom(f *Automation) { - if v := f.GasLimit; v != nil { - a.GasLimit = v - } -} - type Workflow struct { FromAddress *types.EIP55Address `toml:",omitempty"` ForwarderAddress *types.EIP55Address `toml:",omitempty"` @@ -847,7 +828,6 @@ type GasLimitJobType struct { DR *uint32 `toml:",inline"` VRF *uint32 `toml:",inline"` FM *uint32 `toml:",inline"` - Keeper *uint32 `toml:",inline"` } func (t *GasLimitJobType) setFrom(f *GasLimitJobType) { @@ -866,9 +846,6 @@ func (t *GasLimitJobType) setFrom(f *GasLimitJobType) { if f.FM != nil { t.FM = f.FM } - if f.Keeper != nil { - t.Keeper = f.Keeper - } } type BlockHistoryEstimator struct { diff --git a/pkg/config/toml/config_test.go b/pkg/config/toml/config_test.go index a2b4e3c790..e230613f9f 100644 --- a/pkg/config/toml/config_test.go +++ b/pkg/config/toml/config_test.go @@ -129,7 +129,6 @@ func TestDefaults_fieldsNotNil(t *testing.T) { DR: new(uint32(25)), VRF: new(uint32(37)), FM: new(uint32(42)), - Keeper: new(uint32(51)), } unknown.GasEstimator.BumpTxDepth = new(uint32(15)) unknown.NodePool.Errors = ClientErrors{ @@ -175,7 +174,6 @@ func TestDocs(t *testing.T) { require.Zero(t, *docDefaults.GasEstimator.LimitJobType.OCR) require.Zero(t, *docDefaults.GasEstimator.LimitJobType.OCR2) require.Zero(t, *docDefaults.GasEstimator.LimitJobType.DR) - require.Zero(t, *docDefaults.GasEstimator.LimitJobType.Keeper) require.Zero(t, *docDefaults.GasEstimator.LimitJobType.VRF) require.Zero(t, *docDefaults.GasEstimator.LimitJobType.FM) docDefaults.GasEstimator.LimitJobType = GasLimitJobType{} @@ -285,7 +283,6 @@ var fullConfig = EVMConfig{ DR: new(uint32(1002)), VRF: new(uint32(1003)), FM: new(uint32(1004)), - Keeper: new(uint32(1005)), OCR2: new(uint32(1006)), }, @@ -410,11 +407,6 @@ var fullConfig = EVMConfig{ DeltaCJitterOverride: config.MustNewDuration(time.Second), ObservationGracePeriod: config.MustNewDuration(time.Second), }, - OCR2: OCR2{ - Automation: Automation{ - GasLimit: new(uint32(540)), - }, - }, Workflow: Workflow{ FromAddress: new(types.MustEIP55Address("0x627306090abaB3A6e1400e9345bC60c78a8BEf57")), ForwarderAddress: new(types.MustEIP55Address("0x9FBDa871d559710256a2502A2517b794B482Db40")), diff --git a/pkg/config/toml/defaults.go b/pkg/config/toml/defaults.go index 835b1b4a6f..fc0f8dee3c 100644 --- a/pkg/config/toml/defaults.go +++ b/pkg/config/toml/defaults.go @@ -290,6 +290,5 @@ func (c *Chain) SetFrom(f *Chain) { c.HeadTracker.setFrom(&f.HeadTracker) c.NodePool.setFrom(&f.NodePool) c.OCR.setFrom(&f.OCR) - c.OCR2.setFrom(&f.OCR2) c.Workflow.setFrom(&f.Workflow) } diff --git a/pkg/config/toml/defaults/Arbitrum_Mainnet.toml b/pkg/config/toml/defaults/Arbitrum_Mainnet.toml index 9d8a4dc5d7..66b62d643b 100644 --- a/pkg/config/toml/defaults/Arbitrum_Mainnet.toml +++ b/pkg/config/toml/defaults/Arbitrum_Mainnet.toml @@ -27,6 +27,3 @@ OracleType = 'arbitrum' [NodePool] SyncThreshold = 10 - -[OCR2.Automation] -GasLimit = 14500000 diff --git a/pkg/config/toml/defaults/Arbitrum_Sepolia.toml b/pkg/config/toml/defaults/Arbitrum_Sepolia.toml index bcac6443b5..04f9dd9250 100644 --- a/pkg/config/toml/defaults/Arbitrum_Sepolia.toml +++ b/pkg/config/toml/defaults/Arbitrum_Sepolia.toml @@ -25,6 +25,3 @@ OracleType = 'arbitrum' [NodePool] SyncThreshold = 10 - -[OCR2.Automation] -GasLimit = 14500000 diff --git a/pkg/config/toml/defaults/Base_Mainnet.toml b/pkg/config/toml/defaults/Base_Mainnet.toml index ace960771a..8d8cfebff6 100644 --- a/pkg/config/toml/defaults/Base_Mainnet.toml +++ b/pkg/config/toml/defaults/Base_Mainnet.toml @@ -32,6 +32,3 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 - -[OCR2.Automation] -GasLimit = 6500000 diff --git a/pkg/config/toml/defaults/Base_Sepolia.toml b/pkg/config/toml/defaults/Base_Sepolia.toml index 682acb8f4e..a9c67c1c50 100644 --- a/pkg/config/toml/defaults/Base_Sepolia.toml +++ b/pkg/config/toml/defaults/Base_Sepolia.toml @@ -32,6 +32,3 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 - -[OCR2.Automation] -GasLimit = 6500000 diff --git a/pkg/config/toml/defaults/Ethereum_Mainnet.toml b/pkg/config/toml/defaults/Ethereum_Mainnet.toml index 05c705ba22..59b6ffdb9f 100644 --- a/pkg/config/toml/defaults/Ethereum_Mainnet.toml +++ b/pkg/config/toml/defaults/Ethereum_Mainnet.toml @@ -16,8 +16,5 @@ EIP1559DynamicFees = true BlockHistorySize = 4 TransactionPercentile = 50 -[OCR2.Automation] -GasLimit = 10500000 - [HeadTracker] PersistenceEnabled = false \ No newline at end of file diff --git a/pkg/config/toml/defaults/Ethereum_Sepolia.toml b/pkg/config/toml/defaults/Ethereum_Sepolia.toml index eb3622f5f6..46eeaf289e 100644 --- a/pkg/config/toml/defaults/Ethereum_Sepolia.toml +++ b/pkg/config/toml/defaults/Ethereum_Sepolia.toml @@ -10,8 +10,5 @@ EIP1559DynamicFees = true BlockHistorySize = 4 TransactionPercentile = 50 -[OCR2.Automation] -GasLimit = 10500000 - [HeadTracker] PersistenceEnabled = false \ No newline at end of file diff --git a/pkg/config/toml/defaults/Fantom_Mainnet.toml b/pkg/config/toml/defaults/Fantom_Mainnet.toml index 7e76d94278..acb9d130db 100644 --- a/pkg/config/toml/defaults/Fantom_Mainnet.toml +++ b/pkg/config/toml/defaults/Fantom_Mainnet.toml @@ -6,7 +6,4 @@ RPCBlockQueryDelay = 2 [GasEstimator] # Fantom network has been slow to include txs at times when using the BlockHistory estimator, and the recommendation is to use SuggestedPrice mode. -Mode = 'SuggestedPrice' - -[OCR2.Automation] -GasLimit = 3800000 \ No newline at end of file +Mode = 'SuggestedPrice' \ No newline at end of file diff --git a/pkg/config/toml/defaults/Fantom_Testnet.toml b/pkg/config/toml/defaults/Fantom_Testnet.toml index 5f24a76c2e..3bb911ddaf 100644 --- a/pkg/config/toml/defaults/Fantom_Testnet.toml +++ b/pkg/config/toml/defaults/Fantom_Testnet.toml @@ -7,6 +7,3 @@ RPCBlockQueryDelay = 2 [GasEstimator] Mode = 'SuggestedPrice' - -[OCR2.Automation] -GasLimit = 3800000 \ No newline at end of file diff --git a/pkg/config/toml/defaults/Optimism_Mainnet.toml b/pkg/config/toml/defaults/Optimism_Mainnet.toml index cea0f9afc7..30f5eff0f5 100644 --- a/pkg/config/toml/defaults/Optimism_Mainnet.toml +++ b/pkg/config/toml/defaults/Optimism_Mainnet.toml @@ -32,6 +32,3 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 - -[OCR2.Automation] -GasLimit = 6500000 diff --git a/pkg/config/toml/defaults/Optimism_Sepolia.toml b/pkg/config/toml/defaults/Optimism_Sepolia.toml index 35c44ede40..a0086b662b 100644 --- a/pkg/config/toml/defaults/Optimism_Sepolia.toml +++ b/pkg/config/toml/defaults/Optimism_Sepolia.toml @@ -32,6 +32,3 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 - -[OCR2.Automation] -GasLimit = 6500000 diff --git a/pkg/config/toml/defaults/Soneium_Mainnet.toml b/pkg/config/toml/defaults/Soneium_Mainnet.toml index e1a28009e2..231f4bda19 100644 --- a/pkg/config/toml/defaults/Soneium_Mainnet.toml +++ b/pkg/config/toml/defaults/Soneium_Mainnet.toml @@ -31,6 +31,3 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 - -[OCR2.Automation] -GasLimit = 6500000 diff --git a/pkg/config/toml/defaults/Soneium_Sepolia.toml b/pkg/config/toml/defaults/Soneium_Sepolia.toml index 7ab92f04e5..0d892cb099 100644 --- a/pkg/config/toml/defaults/Soneium_Sepolia.toml +++ b/pkg/config/toml/defaults/Soneium_Sepolia.toml @@ -31,6 +31,3 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 - -[OCR2.Automation] -GasLimit = 6500000 diff --git a/pkg/config/toml/defaults/ZKsync_Mainnet.toml b/pkg/config/toml/defaults/ZKsync_Mainnet.toml index 320ea57f72..08e9c4b455 100644 --- a/pkg/config/toml/defaults/ZKsync_Mainnet.toml +++ b/pkg/config/toml/defaults/ZKsync_Mainnet.toml @@ -17,6 +17,3 @@ OracleType = 'zksync' [HeadTracker] HistoryDepth = 50 - -[OCR2.Automation] -GasLimit = 11_000_000 diff --git a/pkg/config/toml/defaults/ZKsync_Sepolia.toml b/pkg/config/toml/defaults/ZKsync_Sepolia.toml index 8702d28d7e..aa26993e65 100644 --- a/pkg/config/toml/defaults/ZKsync_Sepolia.toml +++ b/pkg/config/toml/defaults/ZKsync_Sepolia.toml @@ -17,6 +17,3 @@ OracleType = 'zksync' [HeadTracker] HistoryDepth = 50 - -[OCR2.Automation] -GasLimit = 11_000_000 diff --git a/pkg/config/toml/defaults/Zircuit_Mainnet.toml b/pkg/config/toml/defaults/Zircuit_Mainnet.toml index bce4bee24b..f902e260fd 100644 --- a/pkg/config/toml/defaults/Zircuit_Mainnet.toml +++ b/pkg/config/toml/defaults/Zircuit_Mainnet.toml @@ -31,9 +31,6 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 -[OCR2.Automation] -GasLimit = 6500000 - [Transactions.AutoPurge] Enabled = true Threshold = 90 # 90 blocks at 2s block time ~3 minutes diff --git a/pkg/config/toml/defaults/Zircuit_Sepolia.toml b/pkg/config/toml/defaults/Zircuit_Sepolia.toml index 00e0b9cae5..ac2efadeb7 100644 --- a/pkg/config/toml/defaults/Zircuit_Sepolia.toml +++ b/pkg/config/toml/defaults/Zircuit_Sepolia.toml @@ -31,9 +31,6 @@ SyncThreshold = 10 [OCR] ContractConfirmations = 1 -[OCR2.Automation] -GasLimit = 6500000 - [Transactions.AutoPurge] Enabled = true Threshold = 90 # 90 blocks at 2s block time ~3 minutes diff --git a/pkg/config/toml/defaults/fallback.toml b/pkg/config/toml/defaults/fallback.toml index ddc23e85ed..e730ea877c 100644 --- a/pkg/config/toml/defaults/fallback.toml +++ b/pkg/config/toml/defaults/fallback.toml @@ -105,9 +105,6 @@ DeltaCOverride = '168h' DeltaCJitterOverride = '1h' ObservationGracePeriod = '1s' -[OCR2.Automation] -GasLimit = 5400000 - [Workflow] GasLimitDefault = 400_000 TxAcceptanceState = 2 diff --git a/pkg/config/toml/docs.toml b/pkg/config/toml/docs.toml index ee8ae0b953..cbfd903669 100644 --- a/pkg/config/toml/docs.toml +++ b/pkg/config/toml/docs.toml @@ -112,7 +112,7 @@ RPCBlockQueryDelay = 1 # Default # With `EnforceRepeatableRead = true,` RPC is considered healthy only if its most recent finalized block is larger or equal to the highest finalized block observed by the CL Node minus `FinalizedBlockOffset.` # Higher values of `FinalizedBlockOffset` with `EnforceRepeatableRead = true` reduce the number of false `FinalizedBlockOutOfSync` declarations on healthy RPCs that are slightly lagging behind due to network delays. # This may increase the number of healthy RPCs and reduce the probability that the CL Node will not have any healthy alternatives to the active RPC. -# CAUTION: Setting this to values higher than 0 may delay transaction creation in products (e.g., CCIP, Automation) that base their decision on finalized on-chain events. +# CAUTION: Setting this to values higher than 0 may delay transaction creation in products (e.g., CCIP) that base their decision on finalized on-chain events. # PoS chains with `FinalityTagEnabled=true` and batched (epochs) blocks finalization (e.g., Ethereum Mainnet) must be treated with special care as a minor increase in the `FinalizedBlockOffset` may lead to significant delays. # For example, let's say that `FinalizedBlockOffset = 1` and blocks are finalized in batches of 32. # The latest finalized block on chain is 64, so block 63 is the latest finalized for CL Node. @@ -231,7 +231,7 @@ PriceMax = '115792089237316195423570985008687907853269984665.6405640394575840079 # ``` PriceMin = '1 gwei' # Default # LimitDefault sets default gas limit for outgoing transactions. This should not need to be changed in most cases. -# Some job types, such as Keeper jobs, might set their own gas limit unrelated to this value. +# Some job types, might set their own gas limit unrelated to this value. LimitDefault = 500_000 # Default # LimitMax sets a maximum for _estimated_ gas limits. This currently only applies to `Arbitrum` `GasEstimatorMode`. LimitMax = 500_000 # Default @@ -298,7 +298,6 @@ BumpTxDepth = 16 # Example # - `FixedPriceEstimator` will use `GasFeeCapDefault` instaed of `GasPriceDefault` for the fee cap # - `PriceMin` is ignored for new transactions and `GasTipCapMinimum` is used instead (default 0) # - `PriceMax` still represents that absolute upper limit that Chainlink will ever spend (total) on a single tx -# - `Keeper.GasTipCapBufferPercent` is ignored in EIP-1559 mode and `Keeper.GasTipCapBufferPercent` is used instead EIP1559DynamicFees = false # Default # FeeCapDefault controls the fixed initial fee cap, if EIP1559 mode is enabled and `FixedPrice` gas estimator is used. FeeCapDefault = '100 gwei' # Default @@ -330,8 +329,6 @@ DR = 100_000 # Example VRF = 100_000 # Example # FM overrides LimitDefault for Flux Monitor jobs. FM = 100_000 # Example -# Keeper overrides LimitDefault for Keeper jobs. -Keeper = 100_000 # Example # These settings allow you to configure how your node calculates gas prices when using the block history estimator. @@ -581,10 +578,6 @@ Order = 100 # Default IsLoadBalancedRPC = false # Example -[OCR2.Automation] -# GasLimit controls the gas limit for transmit transactions from ocr2automation job. -GasLimit = 5400000 # Default - [Workflow] # FromAddress is Address of the transmitter key to use for workflow writes. FromAddress = '0x2a3e23c6f242F5345320814aC8a1b4E58707D292' # Example diff --git a/pkg/config/toml/testdata/config-full.toml b/pkg/config/toml/testdata/config-full.toml index 20854c4ed4..6a94dc2e4b 100644 --- a/pkg/config/toml/testdata/config-full.toml +++ b/pkg/config/toml/testdata/config-full.toml @@ -83,7 +83,6 @@ OCR2 = 1006 DR = 1002 VRF = 1003 FM = 1004 -Keeper = 1005 [GasEstimator.BlockHistory] BatchSize = 17 @@ -160,10 +159,6 @@ DeltaCOverride = '1h0m0s' DeltaCJitterOverride = '1s' ObservationGracePeriod = '1s' -[OCR2] -[OCR2.Automation] -GasLimit = 540 - [Workflow] FromAddress = '0x627306090abaB3A6e1400e9345bC60c78a8BEf57' ForwarderAddress = '0x9FBDa871d559710256a2502A2517b794B482Db40'