From 4a0091e02bb3c17240ef90823e306fb76c16128e Mon Sep 17 00:00:00 2001 From: "Abdelrahman Soliman (Boda)" <2677789+asoliman92@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:45:25 +0400 Subject: [PATCH] [CCIP-2942] OCR2 plugins merge fixes [CCIP-2942] (#14026) * Add status checker original commit: https://github.com/smartcontractkit/ccip/commit/451984ad0469c7d685e305dba0a0d94eb0c9a053 * Add CCIP to types.go * Add Unsafe_SetConnectionsManager to feeds service for testing * Add CCIP feature to core.toml * Rebuilding config * Wiring up relayer and ocr2 delegates - this commit touches shared code ! * Adding mockery configuration for ccip specific code * Setting CCIP feature flag to true - it's no longer used anywhere --------- Co-authored-by: Mateusz Sekara --- .mockery.yaml | 93 +++- core/config/docs/core.toml | 2 + core/config/toml/types.go | 4 + core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 +- core/services/chainlink/config_test.go | 2 + .../testdata/config-empty-effective.toml | 1 + .../chainlink/testdata/config-full.toml | 1 + .../config-multi-chain-effective.toml | 1 + core/services/feeds/mocks/service.go | 33 ++ core/services/feeds/service.go | 14 + core/services/ocr2/delegate.go | 401 +++++++++++++++++- .../ccip/testhelpers/integration/chainlink.go | 9 +- .../testhelpers_1_4_0/chainlink.go | 9 +- core/services/ocr2/validate/validate.go | 59 ++- core/services/relay/evm/ccip.go | 205 +++++++++ core/services/relay/evm/ccip_test.go | 18 + core/services/relay/evm/commit_provider.go | 309 ++++++++++++++ core/services/relay/evm/evm.go | 227 +++++++++- core/services/relay/evm/exec_provider.go | 391 +++++++++++++++++ .../mocks/ccip_transaction_status_checker.go | 104 +++++ .../evm/statuschecker/txm_status_checker.go | 54 +++ .../statuschecker/txm_status_checker_test.go | 103 +++++ core/services/synchronization/common.go | 2 + .../testdata/config-empty-effective.toml | 1 + core/web/resolver/testdata/config-full.toml | 1 + .../config-multi-chain-effective.toml | 1 + docs/CONFIG.md | 7 + go.mod | 8 +- go.sum | 9 +- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 +- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 +- testdata/scripts/node/validate/default.txtar | 1 + .../disk-based-logging-disabled.txtar | 1 + .../validate/disk-based-logging-no-dir.txtar | 1 + .../node/validate/disk-based-logging.txtar | 1 + .../node/validate/invalid-ocr-p2p.txtar | 1 + testdata/scripts/node/validate/invalid.txtar | 1 + testdata/scripts/node/validate/valid.txtar | 1 + testdata/scripts/node/validate/warnings.txtar | 1 + 42 files changed, 2062 insertions(+), 33 deletions(-) create mode 100644 core/services/relay/evm/ccip.go create mode 100644 core/services/relay/evm/ccip_test.go create mode 100644 core/services/relay/evm/commit_provider.go create mode 100644 core/services/relay/evm/exec_provider.go create mode 100644 core/services/relay/evm/statuschecker/mocks/ccip_transaction_status_checker.go create mode 100644 core/services/relay/evm/statuschecker/txm_status_checker.go create mode 100644 core/services/relay/evm/statuschecker/txm_status_checker_test.go diff --git a/.mockery.yaml b/.mockery.yaml index 77d2145a461..8fab61a5b9d 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -457,4 +457,95 @@ packages: filename: optimism_dispute_game_factory_interface.go outpkg: mock_optimism_dispute_game_factory interfaces: - OptimismDisputeGameFactoryInterface: + OptimismDisputeGameFactoryInterface: + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/cache: + config: + filename: chain_health_mock.go + interfaces: + ChainHealthcheck: + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata: + interfaces: + CommitStoreReader: + config: + filename: commit_store_reader_mock.go + OffRampReader: + config: + filename: offramp_reader_mock.go + OnRampReader: + config: + filename: onramp_reader_mock.go + PriceRegistryReader: + config: + filename: price_registry_reader_mock.go + TokenPoolReader: + config: + filename: token_pool_reader_mock.go + USDCReader: + config: + filename: usdc_reader_mock.go + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/batchreader: + config: + filename: token_pool_batched_reader_mock.go + interfaces: + TokenPoolBatchedReader: + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/ccipdataprovider: + config: + filename: price_registry_mock.go + interfaces: + PriceRegistry: + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdb: + config: + filename: price_service_mock.go + interfaces: + PriceService: + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/pricegetter: + config: + filename: mock.go + dir: "{{ .InterfaceDir }}/" + outpkg: pricegetter + interfaces: + PriceGetter: + config: + mockname: "Mock{{ .InterfaceName }}" + github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/statuschecker: + interfaces: + CCIPTransactionStatusChecker: + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/rpclib: + config: + outpkg: rpclibmocks + interfaces: + BatchCaller: + config: + filename: batch_caller.go + dir: core/services/relay/evm/rpclibmocks + EvmBatchCaller: + config: + filename: evm_mock.go + dir: "{{ .InterfaceDir }}/rpclibmocks" + + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices: + config: + dir: "{{ .InterfaceDir }}/" + outpkg: prices + interfaces: + GasPriceEstimatorCommit: + config: + mockname: "Mock{{ .InterfaceName }}" + filename: gas_price_estimator_commit_mock.go + GasPriceEstimatorExec: + config: + mockname: "Mock{{ .InterfaceName }}" + filename: gas_price_estimator_exec_mock.go + GasPriceEstimator: + config: + mockname: "Mock{{ .InterfaceName }}" + filename: gas_price_estimator_mock.go + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata: + config: + filename: reader_mock.go + dir: "{{ .InterfaceDir }}/" + outpkg: tokendata + interfaces: + Reader: + config: + mockname: "Mock{{ .InterfaceName }}" \ No newline at end of file diff --git a/core/config/docs/core.toml b/core/config/docs/core.toml index d1b922cf291..d0960779c6c 100644 --- a/core/config/docs/core.toml +++ b/core/config/docs/core.toml @@ -13,6 +13,8 @@ FeedsManager = true # Default LogPoller = false # Default # UICSAKeys enables CSA Keys in the UI. UICSAKeys = false # Default +# CCIP enables the CCIP service. +CCIP = true # Default [Database] # DefaultIdleInTxSessionTimeout is the maximum time allowed for a transaction to be open and idle before timing out. See Postgres `idle_in_transaction_session_timeout` for more details. diff --git a/core/config/toml/types.go b/core/config/toml/types.go index f827f086225..0c91ddd81a9 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -303,6 +303,7 @@ type Feature struct { FeedsManager *bool LogPoller *bool UICSAKeys *bool + CCIP *bool } func (f *Feature) setFrom(f2 *Feature) { @@ -315,6 +316,9 @@ func (f *Feature) setFrom(f2 *Feature) { if v := f2.UICSAKeys; v != nil { f.UICSAKeys = v } + if v := f2.CCIP; v != nil { + f.CCIP = v + } } type Database struct { diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 4ee443d46f8..d70770502b4 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -60,7 +60,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/XSAM/otelsql v0.27.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/avast/retry-go/v4 v4.5.1 // indirect + github.com/avast/retry-go/v4 v4.6.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 3ae26beb633..bab53448982 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -147,8 +147,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= -github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= +github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= +github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 9b40e4dfce4..0038be8a979 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -261,6 +261,7 @@ func TestConfig_Marshal(t *testing.T) { FeedsManager: ptr(true), LogPoller: ptr(true), UICSAKeys: ptr(true), + CCIP: ptr(true), } full.Database = toml.Database{ DefaultIdleInTxSessionTimeout: commoncfg.MustNewDuration(time.Minute), @@ -771,6 +772,7 @@ Headers = ['Authorization: token', 'X-SomeOther-Header: value with spaces | and FeedsManager = true LogPoller = true UICSAKeys = true +CCIP = true `}, {"Database", Config{Core: toml.Core{Database: full.Database}}, `[Database] DefaultIdleInTxSessionTimeout = '1m0s' diff --git a/core/services/chainlink/testdata/config-empty-effective.toml b/core/services/chainlink/testdata/config-empty-effective.toml index 1bad3fd91c6..f1325d824ea 100644 --- a/core/services/chainlink/testdata/config-empty-effective.toml +++ b/core/services/chainlink/testdata/config-empty-effective.toml @@ -6,6 +6,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 21d68c23ada..d752398f039 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -6,6 +6,7 @@ ShutdownGracePeriod = '10s' FeedsManager = true LogPoller = true UICSAKeys = true +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1m0s' diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index c56e755d360..12427650f42 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -6,6 +6,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/core/services/feeds/mocks/service.go b/core/services/feeds/mocks/service.go index a660420759e..d37c327850d 100644 --- a/core/services/feeds/mocks/service.go +++ b/core/services/feeds/mocks/service.go @@ -1403,6 +1403,39 @@ func (_c *Service_SyncNodeInfo_Call) RunAndReturn(run func(context.Context, int6 return _c } +// Unsafe_SetConnectionsManager provides a mock function with given fields: _a0 +func (_m *Service) Unsafe_SetConnectionsManager(_a0 feeds.ConnectionsManager) { + _m.Called(_a0) +} + +// Service_Unsafe_SetConnectionsManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unsafe_SetConnectionsManager' +type Service_Unsafe_SetConnectionsManager_Call struct { + *mock.Call +} + +// Unsafe_SetConnectionsManager is a helper method to define mock.On call +// - _a0 feeds.ConnectionsManager +func (_e *Service_Expecter) Unsafe_SetConnectionsManager(_a0 interface{}) *Service_Unsafe_SetConnectionsManager_Call { + return &Service_Unsafe_SetConnectionsManager_Call{Call: _e.mock.On("Unsafe_SetConnectionsManager", _a0)} +} + +func (_c *Service_Unsafe_SetConnectionsManager_Call) Run(run func(_a0 feeds.ConnectionsManager)) *Service_Unsafe_SetConnectionsManager_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(feeds.ConnectionsManager)) + }) + return _c +} + +func (_c *Service_Unsafe_SetConnectionsManager_Call) Return() *Service_Unsafe_SetConnectionsManager_Call { + _c.Call.Return() + return _c +} + +func (_c *Service_Unsafe_SetConnectionsManager_Call) RunAndReturn(run func(feeds.ConnectionsManager)) *Service_Unsafe_SetConnectionsManager_Call { + _c.Call.Return(run) + return _c +} + // UpdateChainConfig provides a mock function with given fields: ctx, cfg func (_m *Service) UpdateChainConfig(ctx context.Context, cfg feeds.ChainConfig) (int64, error) { ret := _m.Called(ctx, cfg) diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index b11b2b0167a..1733d4a7582 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -107,6 +107,9 @@ type Service interface { ListSpecsByJobProposalIDs(ctx context.Context, ids []int64) ([]JobProposalSpec, error) RejectSpec(ctx context.Context, id int64) error UpdateSpecDefinition(ctx context.Context, id int64, spec string) error + + // Unsafe_SetConnectionsManager Only for testing + Unsafe_SetConnectionsManager(ConnectionsManager) } type service struct { @@ -1105,6 +1108,16 @@ func (s *service) observeJobProposalCounts(ctx context.Context) error { return nil } +// Unsafe_SetConnectionsManager sets the ConnectionsManager on the service. +// +// We need to be able to inject a mock for the client to facilitate integration +// tests. +// +// ONLY TO BE USED FOR TESTING. +func (s *service) Unsafe_SetConnectionsManager(connMgr ConnectionsManager) { + s.connMgr = connMgr +} + // findExistingJobForOCR2 looks for existing job for OCR2 func findExistingJobForOCR2(ctx context.Context, j *job.Job, tx job.ORM) (int32, error) { var contractID string @@ -1501,5 +1514,6 @@ func (ns NullService) IsJobManaged(ctx context.Context, jobID int64) (bool, erro func (ns NullService) UpdateSpecDefinition(ctx context.Context, id int64, spec string) error { return ErrFeedsManagerDisabled } +func (ns NullService) Unsafe_SetConnectionsManager(_ ConnectionsManager) {} //revive:enable diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index db0f4e9725e..5c44825ca2a 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -6,18 +6,25 @@ import ( "encoding/json" "fmt" "log" + "strconv" "time" + "gopkg.in/guregu/null.v4" + + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" - "google.golang.org/grpc" - "gopkg.in/guregu/null.v4" - + chainselectors "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/libocr/commontypes" libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "google.golang.org/grpc" ocr2keepers20 "github.com/smartcontractkit/chainlink-automation/pkg/v2" ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" @@ -26,13 +33,11 @@ import ( ocr2keepers20runner "github.com/smartcontractkit/chainlink-automation/pkg/v2/runner" ocr2keepers21config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" ocr2keepers21 "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" - "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins" "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins/ocr3" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/core" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" @@ -47,12 +52,15 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" "github.com/smartcontractkit/chainlink/v2/core/services/llo" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipcommit" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipexec" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/generic" lloconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/llo/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/median" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21" ocr2keeper21core "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" @@ -68,6 +76,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" "github.com/smartcontractkit/chainlink/v2/plugins" + + ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" ) type ErrJobSpecNoRelayer struct { @@ -284,6 +294,7 @@ func (d *Delegate) cleanupEVM(ctx context.Context, jb job.Job, relayID types.Rel // an inconsistent state. This assumes UnregisterFilter will return nil if the filter wasn't found // at all (no rows deleted). spec := jb.OCR2OracleSpec + transmitterID := spec.TransmitterID.String chain, err := d.legacyChains.Get(relayID.ChainID) if err != nil { d.lggr.Errorw("cleanupEVM: failed to get chain id", "chainId", relayID.ChainID, "err", err) @@ -305,6 +316,51 @@ func (d *Delegate) cleanupEVM(ctx context.Context, jb job.Job, relayID types.Rel d.lggr.Errorw("failed to derive ocr2keeper filter names from spec", "err", err, "spec", spec) } filters = append(filters, filters21...) + case types.CCIPCommit: + // Write PluginConfig bytes to send source/dest relayer provider + info outside of top level rargs/pargs over the wire + var pluginJobSpecConfig ccipconfig.CommitPluginJobSpecConfig + err = json.Unmarshal(spec.PluginConfig.Bytes(), &pluginJobSpecConfig) + if err != nil { + return err + } + + dstProvider, err2 := d.ccipCommitGetDstProvider(ctx, jb, pluginJobSpecConfig, transmitterID) + if err2 != nil { + return err + } + + srcProvider, _, err2 := d.ccipCommitGetSrcProvider(ctx, jb, pluginJobSpecConfig, transmitterID, dstProvider) + if err2 != nil { + return err + } + err2 = ccipcommit.UnregisterCommitPluginLpFilters(srcProvider, dstProvider) + if err2 != nil { + d.lggr.Errorw("failed to unregister ccip commit plugin filters", "err", err2, "spec", spec) + } + return nil + case types.CCIPExecution: + // PROVIDER BASED ARG CONSTRUCTION + // Write PluginConfig bytes to send source/dest relayer provider + info outside of top level rargs/pargs over the wire + var pluginJobSpecConfig ccipconfig.ExecPluginJobSpecConfig + err = json.Unmarshal(spec.PluginConfig.Bytes(), &pluginJobSpecConfig) + if err != nil { + return err + } + + dstProvider, err2 := d.ccipExecGetDstProvider(ctx, jb, pluginJobSpecConfig, transmitterID) + if err2 != nil { + return err + } + + srcProvider, _, err2 := d.ccipExecGetSrcProvider(ctx, jb, pluginJobSpecConfig, transmitterID, dstProvider) + if err2 != nil { + return err + } + err2 = ccipexec.UnregisterExecPluginLpFilters(srcProvider, dstProvider) + if err2 != nil { + d.lggr.Errorw("failed to unregister ccip exec plugin filters", "err", err2, "spec", spec) + } + return nil default: return nil } @@ -448,6 +504,10 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) ([]job.Servi return d.newServicesGenericPlugin(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, d.capabilitiesRegistry, kvStore) + case types.CCIPCommit: + return d.newServicesCCIPCommit(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, transmitterID) + case types.CCIPExecution: + return d.newServicesCCIPExecution(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, transmitterID) default: return nil, errors.Errorf("plugin type %s not supported", spec.PluginType) } @@ -1498,6 +1558,337 @@ func (d *Delegate) newServicesOCR2Functions( return append([]job.ServiceCtx{functionsProvider, thresholdProvider, s4Provider, ocrLogger}, functionsServices...), nil } +func (d *Delegate) newServicesCCIPCommit(ctx context.Context, lggr logger.SugaredLogger, jb job.Job, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, lc ocrtypes.LocalConfig, transmitterID string) ([]job.ServiceCtx, error) { + spec := jb.OCR2OracleSpec + if spec.Relay != relay.NetworkEVM { + return nil, fmt.Errorf("non evm chains are not supported for CCIP commit") + } + dstRid, err := spec.RelayID() + if err != nil { + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: string(spec.PluginType)} + } + + logError := func(msg string) { + lggr.ErrorIf(d.jobORM.RecordError(context.Background(), jb.ID, msg), "unable to record error") + } + + // Write PluginConfig bytes to send source/dest relayer provider + info outside of top level rargs/pargs over the wire + var pluginJobSpecConfig ccipconfig.CommitPluginJobSpecConfig + err = json.Unmarshal(spec.PluginConfig.Bytes(), &pluginJobSpecConfig) + if err != nil { + return nil, err + } + + dstChainID, err := strconv.ParseInt(dstRid.ChainID, 10, 64) + if err != nil { + return nil, err + } + + dstProvider, err := d.ccipCommitGetDstProvider(ctx, jb, pluginJobSpecConfig, transmitterID) + if err != nil { + return nil, err + } + + srcProvider, srcChainID, err := d.ccipCommitGetSrcProvider(ctx, jb, pluginJobSpecConfig, transmitterID, dstProvider) + if err != nil { + return nil, err + } + + oracleArgsNoPlugin := libocr2.OCR2OracleArgs{ + BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, + V2Bootstrappers: bootstrapPeers, + ContractTransmitter: dstProvider.ContractTransmitter(), + ContractConfigTracker: dstProvider.ContractConfigTracker(), + Database: ocrDB, + LocalConfig: lc, + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint( + dstRid.Network, + dstRid.ChainID, + spec.ContractID, + synchronization.OCR2CCIPCommit, + ), + OffchainConfigDigester: dstProvider.OffchainConfigDigester(), + OffchainKeyring: kb, + OnchainKeyring: kb, + MetricsRegisterer: prometheus.WrapRegistererWith(map[string]string{"job_name": jb.Name.ValueOrZero()}, prometheus.DefaultRegisterer), + } + + return ccipcommit.NewCommitServices(ctx, d.ds, srcProvider, dstProvider, d.legacyChains, jb, lggr, d.pipelineRunner, oracleArgsNoPlugin, d.isNewlyCreatedJob, int64(srcChainID), dstChainID, logError) +} + +func newCCIPCommitPluginBytes(isSourceProvider bool, sourceStartBlock uint64, destStartBlock uint64) config.CommitPluginConfig { + return config.CommitPluginConfig{ + IsSourceProvider: isSourceProvider, + SourceStartBlock: sourceStartBlock, + DestStartBlock: destStartBlock, + } +} + +func (d *Delegate) ccipCommitGetDstProvider(ctx context.Context, jb job.Job, pluginJobSpecConfig ccipconfig.CommitPluginJobSpecConfig, transmitterID string) (types.CCIPCommitProvider, error) { + spec := jb.OCR2OracleSpec + if spec.Relay != relay.NetworkEVM { + return nil, fmt.Errorf("non evm chains are not supported for CCIP commit") + } + + dstRid, err := spec.RelayID() + if err != nil { + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: string(spec.PluginType)} + } + + // Write PluginConfig bytes to send source/dest relayer provider + info outside of top level rargs/pargs over the wire + dstConfigBytes, err := newCCIPCommitPluginBytes(false, pluginJobSpecConfig.SourceStartBlock, pluginJobSpecConfig.DestStartBlock).Encode() + if err != nil { + return nil, err + } + + // Get provider from dest chain + dstRelayer, err := d.RelayGetter.Get(dstRid) + if err != nil { + return nil, err + } + + provider, err := dstRelayer.NewPluginProvider(ctx, + types.RelayArgs{ + ContractID: spec.ContractID, + RelayConfig: spec.RelayConfig.Bytes(), + ProviderType: string(types.CCIPCommit), + }, + types.PluginArgs{ + TransmitterID: transmitterID, + PluginConfig: dstConfigBytes, + }) + if err != nil { + return nil, fmt.Errorf("unable to create ccip commit provider: %w", err) + } + dstProvider, ok := provider.(types.CCIPCommitProvider) + if !ok { + return nil, fmt.Errorf("could not coerce PluginProvider to CCIPCommitProvider") + } + + return dstProvider, nil +} + +func (d *Delegate) ccipCommitGetSrcProvider(ctx context.Context, jb job.Job, pluginJobSpecConfig ccipconfig.CommitPluginJobSpecConfig, transmitterID string, dstProvider types.CCIPCommitProvider) (srcProvider types.CCIPCommitProvider, srcChainID uint64, err error) { + spec := jb.OCR2OracleSpec + srcConfigBytes, err := newCCIPCommitPluginBytes(true, pluginJobSpecConfig.SourceStartBlock, pluginJobSpecConfig.DestStartBlock).Encode() + if err != nil { + return nil, 0, err + } + // Use OffRampReader to get src chain ID and fetch the src relayer + + var pluginConfig ccipconfig.CommitPluginJobSpecConfig + err = json.Unmarshal(spec.PluginConfig.Bytes(), &pluginConfig) + if err != nil { + return nil, 0, err + } + offRampAddress := pluginConfig.OffRamp + offRampReader, err := dstProvider.NewOffRampReader(ctx, offRampAddress) + if err != nil { + return nil, 0, fmt.Errorf("create offRampReader: %w", err) + } + + offRampConfig, err := offRampReader.GetStaticConfig(ctx) + if err != nil { + return nil, 0, fmt.Errorf("get offRamp static config: %w", err) + } + + srcChainID, err = chainselectors.ChainIdFromSelector(offRampConfig.SourceChainSelector) + if err != nil { + return nil, 0, err + } + srcChainIDstr := strconv.FormatUint(srcChainID, 10) + + // Get provider from source chain + srcRelayer, err := d.RelayGetter.Get(types.RelayID{Network: spec.Relay, ChainID: srcChainIDstr}) + if err != nil { + return nil, 0, err + } + provider, err := srcRelayer.NewPluginProvider(ctx, + types.RelayArgs{ + ContractID: "", // Contract address only valid for dst chain + RelayConfig: spec.RelayConfig.Bytes(), + ProviderType: string(types.CCIPCommit), + }, + types.PluginArgs{ + TransmitterID: transmitterID, + PluginConfig: srcConfigBytes, + }) + if err != nil { + return nil, 0, fmt.Errorf("srcRelayer.NewPluginProvider: %w", err) + } + srcProvider, ok := provider.(types.CCIPCommitProvider) + if !ok { + return nil, 0, fmt.Errorf("could not coerce PluginProvider to CCIPCommitProvider") + } + + return +} + +func (d *Delegate) newServicesCCIPExecution(ctx context.Context, lggr logger.SugaredLogger, jb job.Job, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, lc ocrtypes.LocalConfig, transmitterID string) ([]job.ServiceCtx, error) { + spec := jb.OCR2OracleSpec + if spec.Relay != relay.NetworkEVM { + return nil, fmt.Errorf("non evm chains are not supported for CCIP execution") + } + dstRid, err := spec.RelayID() + + if err != nil { + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: string(spec.PluginType)} + } + + logError := func(msg string) { + lggr.ErrorIf(d.jobORM.RecordError(context.Background(), jb.ID, msg), "unable to record error") + } + + // PROVIDER BASED ARG CONSTRUCTION + // Write PluginConfig bytes to send source/dest relayer provider + info outside of top level rargs/pargs over the wire + var pluginJobSpecConfig ccipconfig.ExecPluginJobSpecConfig + err = json.Unmarshal(spec.PluginConfig.Bytes(), &pluginJobSpecConfig) + if err != nil { + return nil, err + } + + dstChainID, err := strconv.ParseInt(dstRid.ChainID, 10, 64) + if err != nil { + return nil, err + } + + dstProvider, err := d.ccipExecGetDstProvider(ctx, jb, pluginJobSpecConfig, transmitterID) + if err != nil { + return nil, err + } + + srcProvider, srcChainID, err := d.ccipExecGetSrcProvider(ctx, jb, pluginJobSpecConfig, transmitterID, dstProvider) + if err != nil { + return nil, err + } + + oracleArgsNoPlugin2 := libocr2.OCR2OracleArgs{ + BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, + V2Bootstrappers: bootstrapPeers, + ContractTransmitter: dstProvider.ContractTransmitter(), + ContractConfigTracker: dstProvider.ContractConfigTracker(), + Database: ocrDB, + LocalConfig: lc, + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint( + dstRid.Network, + dstRid.ChainID, + spec.ContractID, + synchronization.OCR2CCIPExec, + ), + OffchainConfigDigester: dstProvider.OffchainConfigDigester(), + OffchainKeyring: kb, + OnchainKeyring: kb, + MetricsRegisterer: prometheus.WrapRegistererWith(map[string]string{"job_name": jb.Name.ValueOrZero()}, prometheus.DefaultRegisterer), + } + + return ccipexec.NewExecServices(ctx, lggr, jb, srcProvider, dstProvider, int64(srcChainID), dstChainID, d.isNewlyCreatedJob, oracleArgsNoPlugin2, logError) +} + +func (d *Delegate) ccipExecGetDstProvider(ctx context.Context, jb job.Job, pluginJobSpecConfig ccipconfig.ExecPluginJobSpecConfig, transmitterID string) (types.CCIPExecProvider, error) { + spec := jb.OCR2OracleSpec + if spec.Relay != relay.NetworkEVM { + return nil, fmt.Errorf("non evm chains are not supported for CCIP execution") + } + dstRid, err := spec.RelayID() + + if err != nil { + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: string(spec.PluginType)} + } + + // PROVIDER BASED ARG CONSTRUCTION + // Write PluginConfig bytes to send source/dest relayer provider + info outside of top level rargs/pargs over the wire + dstConfigBytes, err := newExecPluginConfig(false, pluginJobSpecConfig.SourceStartBlock, pluginJobSpecConfig.DestStartBlock, pluginJobSpecConfig.USDCConfig, string(jb.ID)).Encode() + if err != nil { + return nil, err + } + + // Get provider from dest chain + dstRelayer, err := d.RelayGetter.Get(dstRid) + if err != nil { + return nil, err + } + provider, err := dstRelayer.NewPluginProvider(ctx, + types.RelayArgs{ + ContractID: spec.ContractID, + RelayConfig: spec.RelayConfig.Bytes(), + ProviderType: string(types.CCIPExecution), + }, + types.PluginArgs{ + TransmitterID: transmitterID, + PluginConfig: dstConfigBytes, + }) + if err != nil { + return nil, fmt.Errorf("NewPluginProvider failed on dstRelayer: %w", err) + } + dstProvider, ok := provider.(types.CCIPExecProvider) + if !ok { + return nil, fmt.Errorf("could not coerce PluginProvider to CCIPExecProvider") + } + + return dstProvider, nil +} + +func (d *Delegate) ccipExecGetSrcProvider(ctx context.Context, jb job.Job, pluginJobSpecConfig ccipconfig.ExecPluginJobSpecConfig, transmitterID string, dstProvider types.CCIPExecProvider) (srcProvider types.CCIPExecProvider, srcChainID uint64, err error) { + spec := jb.OCR2OracleSpec + srcConfigBytes, err := newExecPluginConfig(true, pluginJobSpecConfig.SourceStartBlock, pluginJobSpecConfig.DestStartBlock, pluginJobSpecConfig.USDCConfig, string(jb.ID)).Encode() + if err != nil { + return nil, 0, err + } + + // Use OffRampReader to get src chain ID and fetch the src relayer + offRampAddress := cciptypes.Address(common.HexToAddress(spec.ContractID).String()) + offRampReader, err := dstProvider.NewOffRampReader(ctx, offRampAddress) + if err != nil { + return nil, 0, fmt.Errorf("create offRampReader: %w", err) + } + + offRampConfig, err := offRampReader.GetStaticConfig(ctx) + if err != nil { + return nil, 0, fmt.Errorf("get offRamp static config: %w", err) + } + + srcChainID, err = chainselectors.ChainIdFromSelector(offRampConfig.SourceChainSelector) + if err != nil { + return nil, 0, err + } + srcChainIDstr := strconv.FormatUint(srcChainID, 10) + + // Get provider from source chain + srcRelayer, err := d.RelayGetter.Get(types.RelayID{Network: spec.Relay, ChainID: srcChainIDstr}) + if err != nil { + return nil, 0, fmt.Errorf("failed to get relayer: %w", err) + } + provider, err := srcRelayer.NewPluginProvider(ctx, + types.RelayArgs{ + ContractID: "", + RelayConfig: spec.RelayConfig.Bytes(), + ProviderType: string(types.CCIPExecution), + }, + types.PluginArgs{ + TransmitterID: transmitterID, + PluginConfig: srcConfigBytes, + }) + if err != nil { + return nil, 0, err + } + srcProvider, ok := provider.(types.CCIPExecProvider) + if !ok { + return nil, 0, fmt.Errorf("could not coerce PluginProvider to CCIPExecProvider: %w", err) + } + + return +} + +func newExecPluginConfig(isSourceProvider bool, srcStartBlock uint64, dstStartBlock uint64, usdcConfig ccipconfig.USDCConfig, jobID string) config.ExecPluginConfig { + return config.ExecPluginConfig{ + IsSourceProvider: isSourceProvider, + SourceStartBlock: srcStartBlock, + DestStartBlock: dstStartBlock, + USDCConfig: usdcConfig, + JobID: jobID, + } +} + // errorLog implements [loop.ErrorLog] type errorLog struct { jobID int32 diff --git a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go index ad42078248a..177ccf323b7 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go @@ -36,6 +36,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" v2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" @@ -378,7 +379,6 @@ func setupNodeCCIP( fmt.Sprintf("127.0.0.1:%d", port), } c.Log.Level = &loglevel - c.Feature.CCIP = &trueRef c.Feature.UICSAKeys = &trueRef c.Feature.FeedsManager = &trueRef c.OCR.Enabled = &falseRef @@ -458,9 +458,10 @@ func setupNodeCCIP( } loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing()) relayerFactory := chainlink.RelayerFactory{ - Logger: lggr, - LoopRegistry: loopRegistry, - GRPCOpts: loop.GRPCOpts{}, + Logger: lggr, + LoopRegistry: loopRegistry, + GRPCOpts: loop.GRPCOpts{}, + CapabilitiesRegistry: coretypes.NewCapabilitiesRegistry(t), } testCtx := testutils.Context(t) // evm alway enabled for backward compatibility diff --git a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go index f406ea0e94a..25be1c2a9a9 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go @@ -36,6 +36,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" v2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" @@ -378,7 +379,6 @@ func setupNodeCCIP( fmt.Sprintf("127.0.0.1:%d", port), } c.Log.Level = &loglevel - c.Feature.CCIP = &trueRef c.Feature.UICSAKeys = &trueRef c.Feature.FeedsManager = &trueRef c.OCR.Enabled = &falseRef @@ -457,9 +457,10 @@ func setupNodeCCIP( } loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing()) relayerFactory := chainlink.RelayerFactory{ - Logger: lggr, - LoopRegistry: loopRegistry, - GRPCOpts: loop.GRPCOpts{}, + Logger: lggr, + LoopRegistry: loopRegistry, + GRPCOpts: loop.GRPCOpts{}, + CapabilitiesRegistry: coretypes.NewCapabilitiesRegistry(t), } testCtx := testutils.Context(t) // evm alway enabled for backward compatibility diff --git a/core/services/ocr2/validate/validate.go b/core/services/ocr2/validate/validate.go index 2993a67114e..8d98a282674 100644 --- a/core/services/ocr2/validate/validate.go +++ b/core/services/ocr2/validate/validate.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os/exec" + "strings" "github.com/lib/pq" "github.com/pelletier/go-toml" @@ -19,9 +20,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" lloconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/llo/config" mercuryconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -115,6 +118,10 @@ func validateSpec(ctx context.Context, tree *toml.Tree, spec job.Job, rc plugins return nil case types.Mercury: return validateOCR2MercurySpec(spec.OCR2OracleSpec.PluginConfig, *spec.OCR2OracleSpec.FeedID) + case types.CCIPExecution: + return validateOCR2CCIPExecutionSpec(spec.OCR2OracleSpec.PluginConfig) + case types.CCIPCommit: + return validateOCR2CCIPCommitSpec(spec.OCR2OracleSpec.PluginConfig) case types.LLO: return validateOCR2LLOSpec(spec.OCR2OracleSpec.PluginConfig) case types.GenericPlugin: @@ -313,11 +320,61 @@ func validateOCR2MercurySpec(jsonConfig job.JSONConfig, feedId [32]byte) error { var pluginConfig mercuryconfig.PluginConfig err := json.Unmarshal(jsonConfig.Bytes(), &pluginConfig) if err != nil { - return pkgerrors.Wrap(err, "error while unmarshaling plugin config") + return pkgerrors.Wrap(err, "error while unmarshalling plugin config") } return pkgerrors.Wrap(mercuryconfig.ValidatePluginConfig(pluginConfig, feedId), "Mercury PluginConfig is invalid") } +func validateOCR2CCIPExecutionSpec(jsonConfig job.JSONConfig) error { + if jsonConfig == nil { + return errors.New("pluginConfig is empty") + } + var cfg config.ExecPluginJobSpecConfig + err := json.Unmarshal(jsonConfig.Bytes(), &cfg) + if err != nil { + return pkgerrors.Wrap(err, "error while unmarshalling plugin config") + } + if cfg.USDCConfig != (config.USDCConfig{}) { + return cfg.USDCConfig.ValidateUSDCConfig() + } + return nil +} + +func validateOCR2CCIPCommitSpec(jsonConfig job.JSONConfig) error { + if jsonConfig == nil { + return errors.New("pluginConfig is empty") + } + var cfg config.CommitPluginJobSpecConfig + err := json.Unmarshal(jsonConfig.Bytes(), &cfg) + if err != nil { + return pkgerrors.Wrap(err, "error while unmarshalling plugin config") + } + + // Ensure that either the tokenPricesUSDPipeline or the priceGetterConfig is set, but not both. + emptyPipeline := strings.Trim(cfg.TokenPricesUSDPipeline, "\n\t ") == "" + emptyPriceGetter := cfg.PriceGetterConfig == nil + if emptyPipeline && emptyPriceGetter { + return fmt.Errorf("either tokenPricesUSDPipeline or priceGetterConfig must be set") + } + if !emptyPipeline && !emptyPriceGetter { + return fmt.Errorf("only one of tokenPricesUSDPipeline or priceGetterConfig must be set: %s and %v", cfg.TokenPricesUSDPipeline, cfg.PriceGetterConfig) + } + + if !emptyPipeline { + _, err = pipeline.Parse(cfg.TokenPricesUSDPipeline) + if err != nil { + return pkgerrors.Wrap(err, "invalid token prices pipeline") + } + } else { + // Validate prices config (like it was done for the pipeline). + if emptyPriceGetter { + return pkgerrors.New("priceGetterConfig is empty") + } + } + + return nil +} + func validateOCR2LLOSpec(jsonConfig job.JSONConfig) error { var pluginConfig lloconfig.PluginConfig err := json.Unmarshal(jsonConfig.Bytes(), &pluginConfig) diff --git a/core/services/relay/evm/ccip.go b/core/services/relay/evm/ccip.go new file mode 100644 index 00000000000..34a732e1454 --- /dev/null +++ b/core/services/relay/evm/ccip.go @@ -0,0 +1,205 @@ +package evm + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices" + + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" +) + +var _ cciptypes.CommitStoreReader = (*IncompleteSourceCommitStoreReader)(nil) +var _ cciptypes.CommitStoreReader = (*IncompleteDestCommitStoreReader)(nil) + +// IncompleteSourceCommitStoreReader is an implementation of CommitStoreReader with the only valid methods being +// GasPriceEstimator, ChangeConfig, and OffchainConfig +type IncompleteSourceCommitStoreReader struct { + estimator gas.EvmFeeEstimator + gasPriceEstimator *prices.DAGasPriceEstimator + sourceMaxGasPrice *big.Int + offchainConfig cciptypes.CommitOffchainConfig +} + +func NewIncompleteSourceCommitStoreReader(estimator gas.EvmFeeEstimator, sourceMaxGasPrice *big.Int) *IncompleteSourceCommitStoreReader { + return &IncompleteSourceCommitStoreReader{ + estimator: estimator, + sourceMaxGasPrice: sourceMaxGasPrice, + } +} + +func (i *IncompleteSourceCommitStoreReader) ChangeConfig(ctx context.Context, onchainConfig []byte, offchainConfig []byte) (cciptypes.Address, error) { + onchainConfigParsed, err := abihelpers.DecodeAbiStruct[ccip.CommitOnchainConfig](onchainConfig) + if err != nil { + return "", err + } + + offchainConfigParsed, err := ccipconfig.DecodeOffchainConfig[ccip.JSONCommitOffchainConfigV1_2_0](offchainConfig) + if err != nil { + return "", err + } + + i.gasPriceEstimator = prices.NewDAGasPriceEstimator( + i.estimator, + i.sourceMaxGasPrice, + int64(offchainConfigParsed.ExecGasPriceDeviationPPB), + int64(offchainConfigParsed.DAGasPriceDeviationPPB), + ) + i.offchainConfig = ccip.NewCommitOffchainConfig( + offchainConfigParsed.ExecGasPriceDeviationPPB, + offchainConfigParsed.GasPriceHeartBeat.Duration(), + offchainConfigParsed.TokenPriceDeviationPPB, + offchainConfigParsed.TokenPriceHeartBeat.Duration(), + offchainConfigParsed.InflightCacheExpiry.Duration(), + offchainConfigParsed.PriceReportingDisabled, + ) + + return cciptypes.Address(onchainConfigParsed.PriceRegistry.String()), nil +} + +func (i *IncompleteSourceCommitStoreReader) DecodeCommitReport(ctx context.Context, report []byte) (cciptypes.CommitStoreReport, error) { + return cciptypes.CommitStoreReport{}, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) EncodeCommitReport(ctx context.Context, report cciptypes.CommitStoreReport) ([]byte, error) { + return []byte{}, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +// GasPriceEstimator returns an ExecGasPriceEstimator to satisfy the GasPriceEstimatorCommit interface, +// with deviationPPB values hardcoded to 0 when this implementation is first constructed. +// When ChangeConfig is called, another call to this method must be made to fetch a GasPriceEstimator with updated values +func (i *IncompleteSourceCommitStoreReader) GasPriceEstimator(ctx context.Context) (cciptypes.GasPriceEstimatorCommit, error) { + return i.gasPriceEstimator, nil +} + +func (i *IncompleteSourceCommitStoreReader) GetAcceptedCommitReportsGteTimestamp(ctx context.Context, ts time.Time, confirmations int) ([]cciptypes.CommitStoreReportWithTxMeta, error) { + return nil, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) GetCommitReportMatchingSeqNum(ctx context.Context, seqNum uint64, confirmations int) ([]cciptypes.CommitStoreReportWithTxMeta, error) { + return nil, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) GetCommitStoreStaticConfig(ctx context.Context) (cciptypes.CommitStoreStaticConfig, error) { + return cciptypes.CommitStoreStaticConfig{}, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) GetExpectedNextSequenceNumber(ctx context.Context) (uint64, error) { + return 0, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) GetLatestPriceEpochAndRound(ctx context.Context) (uint64, error) { + return 0, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) IsBlessed(ctx context.Context, root [32]byte) (bool, error) { + return false, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) IsDestChainHealthy(ctx context.Context) (bool, error) { + return false, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) IsDown(ctx context.Context) (bool, error) { + return false, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) OffchainConfig(ctx context.Context) (cciptypes.CommitOffchainConfig, error) { + return i.offchainConfig, nil +} + +func (i *IncompleteSourceCommitStoreReader) VerifyExecutionReport(ctx context.Context, report cciptypes.ExecReport) (bool, error) { + return false, fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +func (i *IncompleteSourceCommitStoreReader) Close() error { + return fmt.Errorf("invalid usage of IncompleteSourceCommitStoreReader") +} + +// IncompleteDestCommitStoreReader is an implementation of CommitStoreReader with all valid methods except +// GasPriceEstimator, ChangeConfig, and OffchainConfig. +type IncompleteDestCommitStoreReader struct { + cs cciptypes.CommitStoreReader +} + +func NewIncompleteDestCommitStoreReader(lggr logger.Logger, versionFinder ccip.VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller) (*IncompleteDestCommitStoreReader, error) { + cs, err := ccip.NewCommitStoreReader(lggr, versionFinder, address, ec, lp) + if err != nil { + return nil, err + } + + return &IncompleteDestCommitStoreReader{ + cs: cs, + }, nil +} + +func (i *IncompleteDestCommitStoreReader) ChangeConfig(ctx context.Context, onchainConfig []byte, offchainConfig []byte) (cciptypes.Address, error) { + return "", fmt.Errorf("invalid usage of IncompleteDestCommitStoreReader") +} + +func (i *IncompleteDestCommitStoreReader) DecodeCommitReport(ctx context.Context, report []byte) (cciptypes.CommitStoreReport, error) { + return i.cs.DecodeCommitReport(ctx, report) +} + +func (i *IncompleteDestCommitStoreReader) EncodeCommitReport(ctx context.Context, report cciptypes.CommitStoreReport) ([]byte, error) { + return i.cs.EncodeCommitReport(ctx, report) +} + +func (i *IncompleteDestCommitStoreReader) GasPriceEstimator(ctx context.Context) (cciptypes.GasPriceEstimatorCommit, error) { + return nil, fmt.Errorf("invalid usage of IncompleteDestCommitStoreReader") +} + +func (i *IncompleteDestCommitStoreReader) GetAcceptedCommitReportsGteTimestamp(ctx context.Context, ts time.Time, confirmations int) ([]cciptypes.CommitStoreReportWithTxMeta, error) { + return i.cs.GetAcceptedCommitReportsGteTimestamp(ctx, ts, confirmations) +} + +func (i *IncompleteDestCommitStoreReader) GetCommitReportMatchingSeqNum(ctx context.Context, seqNum uint64, confirmations int) ([]cciptypes.CommitStoreReportWithTxMeta, error) { + return i.cs.GetCommitReportMatchingSeqNum(ctx, seqNum, confirmations) +} + +func (i *IncompleteDestCommitStoreReader) GetCommitStoreStaticConfig(ctx context.Context) (cciptypes.CommitStoreStaticConfig, error) { + return i.cs.GetCommitStoreStaticConfig(ctx) +} + +func (i *IncompleteDestCommitStoreReader) GetExpectedNextSequenceNumber(ctx context.Context) (uint64, error) { + return i.cs.GetExpectedNextSequenceNumber(ctx) +} + +func (i *IncompleteDestCommitStoreReader) GetLatestPriceEpochAndRound(ctx context.Context) (uint64, error) { + return i.cs.GetLatestPriceEpochAndRound(ctx) +} + +func (i *IncompleteDestCommitStoreReader) IsBlessed(ctx context.Context, root [32]byte) (bool, error) { + return i.cs.IsBlessed(ctx, root) +} + +func (i *IncompleteDestCommitStoreReader) IsDestChainHealthy(ctx context.Context) (bool, error) { + return i.cs.IsDestChainHealthy(ctx) +} + +func (i *IncompleteDestCommitStoreReader) IsDown(ctx context.Context) (bool, error) { + return i.cs.IsDown(ctx) +} + +func (i *IncompleteDestCommitStoreReader) OffchainConfig(ctx context.Context) (cciptypes.CommitOffchainConfig, error) { + return cciptypes.CommitOffchainConfig{}, fmt.Errorf("invalid usage of IncompleteDestCommitStoreReader") +} + +func (i *IncompleteDestCommitStoreReader) VerifyExecutionReport(ctx context.Context, report cciptypes.ExecReport) (bool, error) { + return i.cs.VerifyExecutionReport(ctx, report) +} + +func (i *IncompleteDestCommitStoreReader) Close() error { + return i.cs.Close() +} diff --git a/core/services/relay/evm/ccip_test.go b/core/services/relay/evm/ccip_test.go new file mode 100644 index 00000000000..8c0bfe182e1 --- /dev/null +++ b/core/services/relay/evm/ccip_test.go @@ -0,0 +1,18 @@ +package evm + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_CCIPSubjectUUID(t *testing.T) { + // We want the function to be + // (1) an actual function (i.e., deterministic) + assert.Equal(t, chainToUUID(big.NewInt(1)), chainToUUID(big.NewInt(1))) + // (2) injective (produce different results for different inputs) + assert.NotEqual(t, chainToUUID(big.NewInt(1)), chainToUUID(big.NewInt(2))) + // (3) stable across runs + assert.Equal(t, "c980e777-c95c-577b-83f6-ceb26a1a982d", chainToUUID(big.NewInt(1)).String()) +} diff --git a/core/services/relay/evm/commit_provider.go b/core/services/relay/evm/commit_provider.go new file mode 100644 index 00000000000..fe3e327c7f2 --- /dev/null +++ b/core/services/relay/evm/commit_provider.go @@ -0,0 +1,309 @@ +package evm + +import ( + "context" + "fmt" + "math/big" + + "go.uber.org/multierr" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" +) + +var _ commontypes.CCIPCommitProvider = (*SrcCommitProvider)(nil) +var _ commontypes.CCIPCommitProvider = (*DstCommitProvider)(nil) + +type SrcCommitProvider struct { + lggr logger.Logger + startBlock uint64 + client client.Client + lp logpoller.LogPoller + estimator gas.EvmFeeEstimator + maxGasPrice *big.Int + + // these values will be lazily initialized + seenOnRampAddress *cciptypes.Address + seenSourceChainSelector *uint64 + seenDestChainSelector *uint64 +} + +func NewSrcCommitProvider( + lggr logger.Logger, + startBlock uint64, + client client.Client, + lp logpoller.LogPoller, + srcEstimator gas.EvmFeeEstimator, + maxGasPrice *big.Int, +) commontypes.CCIPCommitProvider { + return &SrcCommitProvider{ + lggr: lggr, + startBlock: startBlock, + client: client, + lp: lp, + estimator: srcEstimator, + maxGasPrice: maxGasPrice, + } +} + +type DstCommitProvider struct { + lggr logger.Logger + versionFinder ccip.VersionFinder + startBlock uint64 + client client.Client + lp logpoller.LogPoller + contractTransmitter *contractTransmitter + configWatcher *configWatcher + gasEstimator gas.EvmFeeEstimator + maxGasPrice big.Int + + // these values will be lazily initialized + seenCommitStoreAddress *cciptypes.Address + seenOffRampAddress *cciptypes.Address +} + +func NewDstCommitProvider( + lggr logger.Logger, + versionFinder ccip.VersionFinder, + startBlock uint64, + client client.Client, + lp logpoller.LogPoller, + gasEstimator gas.EvmFeeEstimator, + maxGasPrice big.Int, + contractTransmitter contractTransmitter, + configWatcher *configWatcher, +) commontypes.CCIPCommitProvider { + return &DstCommitProvider{ + lggr: lggr, + versionFinder: versionFinder, + startBlock: startBlock, + client: client, + lp: lp, + contractTransmitter: &contractTransmitter, + configWatcher: configWatcher, + gasEstimator: gasEstimator, + maxGasPrice: maxGasPrice, + } +} + +func (P *SrcCommitProvider) Name() string { + return "CCIPCommitProvider.SrcRelayerProvider" +} + +// Close is called when the job that created this provider is deleted. +// At this time, any of the methods on the provider may or may not have been called. +// If NewOnRampReader has not been called, their corresponding +// Close methods will be expected to error. +func (P *SrcCommitProvider) Close() error { + versionFinder := ccip.NewEvmVersionFinder() + + unregisterFuncs := make([]func() error, 0, 2) + unregisterFuncs = append(unregisterFuncs, func() error { + // avoid panic in the case NewOnRampReader wasn't called + if P.seenOnRampAddress == nil { + return nil + } + return ccip.CloseOnRampReader(P.lggr, versionFinder, *P.seenSourceChainSelector, *P.seenDestChainSelector, *P.seenOnRampAddress, P.lp, P.client) + }) + + var multiErr error + for _, fn := range unregisterFuncs { + if err := fn(); err != nil { + multiErr = multierr.Append(multiErr, err) + } + } + return multiErr +} + +func (P *SrcCommitProvider) Ready() error { + return nil +} + +func (P *SrcCommitProvider) HealthReport() map[string]error { + return make(map[string]error) +} + +func (P *SrcCommitProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + // TODO CCIP-2494 + // "OffchainConfigDigester called on SrcCommitProvider. Valid on DstCommitProvider." + return UnimplementedOffchainConfigDigester{} +} + +func (P *SrcCommitProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + // // TODO CCIP-2494 + // "ContractConfigTracker called on SrcCommitProvider. Valid on DstCommitProvider.") + return UnimplementedContractConfigTracker{} +} + +func (P *SrcCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { + // // TODO CCIP-2494 + // "ContractTransmitter called on SrcCommitProvider. Valid on DstCommitProvider." + return UnimplementedContractTransmitter{} +} + +func (P *SrcCommitProvider) ChainReader() commontypes.ContractReader { + return nil +} + +func (P *SrcCommitProvider) Codec() commontypes.Codec { + return nil +} + +func (P *DstCommitProvider) Name() string { + return "CCIPCommitProvider.DstRelayerProvider" +} + +func (P *DstCommitProvider) Close() error { + versionFinder := ccip.NewEvmVersionFinder() + + unregisterFuncs := make([]func() error, 0, 2) + unregisterFuncs = append(unregisterFuncs, func() error { + if P.seenCommitStoreAddress == nil { + return nil + } + return ccip.CloseCommitStoreReader(P.lggr, versionFinder, *P.seenCommitStoreAddress, P.client, P.lp) + }) + unregisterFuncs = append(unregisterFuncs, func() error { + if P.seenOffRampAddress == nil { + return nil + } + return ccip.CloseOffRampReader(P.lggr, versionFinder, *P.seenOffRampAddress, P.client, P.lp, nil, big.NewInt(0)) + }) + + var multiErr error + for _, fn := range unregisterFuncs { + if err := fn(); err != nil { + multiErr = multierr.Append(multiErr, err) + } + } + return multiErr +} + +func (P *DstCommitProvider) Ready() error { + return nil +} + +func (P *DstCommitProvider) HealthReport() map[string]error { + return make(map[string]error) +} + +func (P *DstCommitProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + return P.configWatcher.OffchainConfigDigester() +} + +func (P *DstCommitProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + return P.configWatcher.ContractConfigTracker() +} + +func (P *DstCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { + return P.contractTransmitter +} + +func (P *DstCommitProvider) ChainReader() commontypes.ContractReader { + return nil +} + +func (P *DstCommitProvider) Codec() commontypes.Codec { + return nil +} + +func (P *SrcCommitProvider) Start(ctx context.Context) error { + if P.startBlock != 0 { + P.lggr.Infow("start replaying src chain", "fromBlock", P.startBlock) + return P.lp.Replay(ctx, int64(P.startBlock)) + } + return nil +} + +func (P *DstCommitProvider) Start(ctx context.Context) error { + if P.startBlock != 0 { + P.lggr.Infow("start replaying dst chain", "fromBlock", P.startBlock) + return P.lp.Replay(ctx, int64(P.startBlock)) + } + return nil +} + +func (P *SrcCommitProvider) NewPriceGetter(ctx context.Context) (priceGetter cciptypes.PriceGetter, err error) { + return nil, fmt.Errorf("can't construct a price getter from one relayer") +} + +func (P *DstCommitProvider) NewPriceGetter(ctx context.Context) (priceGetter cciptypes.PriceGetter, err error) { + return nil, fmt.Errorf("can't construct a price getter from one relayer") +} + +func (P *SrcCommitProvider) NewCommitStoreReader(ctx context.Context, commitStoreAddress cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { + commitStoreReader = NewIncompleteSourceCommitStoreReader(P.estimator, P.maxGasPrice) + return +} + +func (P *DstCommitProvider) NewCommitStoreReader(ctx context.Context, commitStoreAddress cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { + P.seenCommitStoreAddress = &commitStoreAddress + + versionFinder := ccip.NewEvmVersionFinder() + commitStoreReader, err = NewIncompleteDestCommitStoreReader(P.lggr, versionFinder, commitStoreAddress, P.client, P.lp) + return +} + +func (P *SrcCommitProvider) NewOnRampReader(ctx context.Context, onRampAddress cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (onRampReader cciptypes.OnRampReader, err error) { + P.seenOnRampAddress = &onRampAddress + P.seenSourceChainSelector = &sourceChainSelector + P.seenDestChainSelector = &destChainSelector + + versionFinder := ccip.NewEvmVersionFinder() + onRampReader, err = ccip.NewOnRampReader(P.lggr, versionFinder, sourceChainSelector, destChainSelector, onRampAddress, P.lp, P.client) + return +} + +func (P *DstCommitProvider) NewOnRampReader(ctx context.Context, onRampAddress cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (onRampReader cciptypes.OnRampReader, err error) { + return nil, fmt.Errorf("invalid: NewOnRampReader called for DstCommitProvider.NewOnRampReader should be called on SrcCommitProvider") +} + +func (P *SrcCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { + return nil, fmt.Errorf("invalid: NewOffRampReader called for SrcCommitProvider. NewOffRampReader should be called on DstCommitProvider") +} + +func (P *DstCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { + offRampReader, err = ccip.NewOffRampReader(P.lggr, P.versionFinder, offRampAddr, P.client, P.lp, P.gasEstimator, &P.maxGasPrice, true) + return +} + +func (P *SrcCommitProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { + return nil, fmt.Errorf("invalid: NewPriceRegistryReader called for SrcCommitProvider. NewOffRampReader should be called on DstCommitProvider") +} + +func (P *DstCommitProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { + destPriceRegistry := ccip.NewEvmPriceRegistry(P.lp, P.client, P.lggr, ccip.CommitPluginLabel) + priceRegistryReader, err = destPriceRegistry.NewPriceRegistryReader(ctx, addr) + return +} + +func (P *SrcCommitProvider) SourceNativeToken(ctx context.Context, sourceRouterAddr cciptypes.Address) (cciptypes.Address, error) { + sourceRouterAddrHex, err := ccip.GenericAddrToEvm(sourceRouterAddr) + if err != nil { + return "", err + } + sourceRouter, err := router.NewRouter(sourceRouterAddrHex, P.client) + if err != nil { + return "", err + } + sourceNative, err := sourceRouter.GetWrappedNative(&bind.CallOpts{Context: ctx}) + if err != nil { + return "", err + } + + return ccip.EvmAddrToGeneric(sourceNative), nil +} + +func (P *DstCommitProvider) SourceNativeToken(ctx context.Context, sourceRouterAddr cciptypes.Address) (cciptypes.Address, error) { + return "", fmt.Errorf("invalid: SourceNativeToken called for DstCommitProvider. SourceNativeToken should be called on SrcCommitProvider") +} diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index a0782380b5b..e310464a556 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -1,13 +1,24 @@ package evm import ( + "bytes" "context" + "crypto/sha256" "encoding/json" "errors" "fmt" + "math/big" "strings" "sync" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipcommit" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipexec" + ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + cciptransmitter "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/transmitter" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" @@ -72,6 +83,57 @@ func init() { var _ commontypes.Relayer = &Relayer{} //nolint:staticcheck +// The current PluginProvider interface does not support an error return. This was fine up until CCIP. +// CCIP is the first product to introduce the idea of incomplete implementations of a provider based on +// what chain (for CCIP, src or dest) the provider is created for. The Unimplemented* implementations below allow us to return +// a non nil value, which is hopefully a better developer experience should you find yourself using the right methods +// but on the *wrong* provider. + +// [UnimplementedOffchainConfigDigester] satisfies the OCR OffchainConfigDigester interface +type UnimplementedOffchainConfigDigester struct{} + +func (e UnimplementedOffchainConfigDigester) ConfigDigest(config ocrtypes.ContractConfig) (ocrtypes.ConfigDigest, error) { + return ocrtypes.ConfigDigest{}, fmt.Errorf("unimplemented for this relayer") +} + +func (e UnimplementedOffchainConfigDigester) ConfigDigestPrefix() (ocrtypes.ConfigDigestPrefix, error) { + return 0, fmt.Errorf("unimplemented for this relayer") +} + +// [UnimplementedContractConfigTracker] satisfies the OCR ContractConfigTracker interface +type UnimplementedContractConfigTracker struct{} + +func (u UnimplementedContractConfigTracker) Notify() <-chan struct{} { + return nil +} + +func (u UnimplementedContractConfigTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest ocrtypes.ConfigDigest, err error) { + return 0, ocrtypes.ConfigDigest{}, fmt.Errorf("unimplemented for this relayer") +} + +func (u UnimplementedContractConfigTracker) LatestConfig(ctx context.Context, changedInBlock uint64) (ocrtypes.ContractConfig, error) { + return ocrtypes.ContractConfig{}, fmt.Errorf("unimplemented for this relayer") +} + +func (u UnimplementedContractConfigTracker) LatestBlockHeight(ctx context.Context) (blockHeight uint64, err error) { + return 0, fmt.Errorf("unimplemented for this relayer") +} + +// [UnimplementedContractTransmitter] satisfies the OCR ContractTransmitter interface +type UnimplementedContractTransmitter struct{} + +func (u UnimplementedContractTransmitter) Transmit(context.Context, ocrtypes.ReportContext, ocrtypes.Report, []ocrtypes.AttributedOnchainSignature) error { + return fmt.Errorf("unimplemented for this relayer") +} + +func (u UnimplementedContractTransmitter) FromAccount() (ocrtypes.Account, error) { + return "", fmt.Errorf("unimplemented for this relayer") +} + +func (u UnimplementedContractTransmitter) LatestConfigDigestAndEpoch(ctx context.Context) (configDigest ocrtypes.ConfigDigest, epoch uint32, err error) { + return ocrtypes.ConfigDigest{}, 0, fmt.Errorf("unimplemented for this relayer") +} + type Relayer struct { ds sqlutil.DataSource chain legacyevm.Chain @@ -618,6 +680,17 @@ func generateTransmitterFrom(ctx context.Context, rargs commontypes.RelayArgs, e configWatcher.chain.ID(), ethKeystore, ) + case commontypes.CCIPExecution: + transmitter, err = cciptransmitter.NewTransmitterWithStatusChecker( + configWatcher.chain.TxManager(), + fromAddresses, + gasLimit, + effectiveTransmitterAddress, + strategy, + checker, + configWatcher.chain.ID(), + ethKeystore, + ) default: transmitter, err = ocrcommon.NewTransmitter( configWatcher.chain.TxManager(), @@ -734,12 +807,158 @@ func (r *Relayer) NewAutomationProvider(rargs commontypes.RelayArgs, pargs commo return ocr2keeperRelayer.NewOCR2KeeperProvider(rargs, pargs) } -func (r *Relayer) NewCCIPCommitProvider(_ commontypes.RelayArgs, _ commontypes.PluginArgs) (commontypes.CCIPCommitProvider, error) { - return nil, errors.New("ccip.commit is not supported for evm") +func chainToUUID(chainID *big.Int) uuid.UUID { + // See https://www.rfc-editor.org/rfc/rfc4122.html#section-4.1.3 for the list of supported versions. + const VersionSHA1 = 5 + var buf bytes.Buffer + buf.WriteString("CCIP:") + buf.Write(chainID.Bytes()) + // We use SHA-256 instead of SHA-1 because the former has better collision resistance. + // The UUID will contain only the first 16 bytes of the hash. + // You can't say which algorithms was used just by looking at the UUID bytes. + return uuid.NewHash(sha256.New(), uuid.NameSpaceOID, buf.Bytes(), VersionSHA1) } -func (r *Relayer) NewCCIPExecProvider(_ commontypes.RelayArgs, _ commontypes.PluginArgs) (commontypes.CCIPExecProvider, error) { - return nil, errors.New("ccip.exec is not supported for evm") +// NewCCIPCommitProvider constructs a provider of type CCIPCommitProvider. Since this is happening in the Relayer, +// which lives in a separate process from delegate which is requesting a provider, we need to wire in through pargs +// which *type* (impl) of CCIPCommitProvider should be created. CCIP is currently a special case where the provider has a +// subset of implementations of the complete interface as certain contracts in a CCIP lane are only deployed on the src +// chain or on the dst chain. This results in the two implementations of providers: a src and dst implementation. +func (r *Relayer) NewCCIPCommitProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.CCIPCommitProvider, error) { + // TODO https://smartcontract-it.atlassian.net/browse/BCF-2887 + ctx := context.Background() + + versionFinder := ccip.NewEvmVersionFinder() + + var commitPluginConfig ccipconfig.CommitPluginConfig + err := json.Unmarshal(pargs.PluginConfig, &commitPluginConfig) + if err != nil { + return nil, err + } + sourceStartBlock := commitPluginConfig.SourceStartBlock + destStartBlock := commitPluginConfig.DestStartBlock + + // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; + // bail early. + if commitPluginConfig.IsSourceProvider { + return NewSrcCommitProvider( + r.lggr, + sourceStartBlock, + r.chain.Client(), + r.chain.LogPoller(), + r.chain.GasEstimator(), + r.chain.Config().EVM().GasEstimator().PriceMax().ToInt(), + ), nil + } + + relayOpts := types.NewRelayOpts(rargs) + configWatcher, err := newStandardConfigProvider(ctx, r.lggr, r.chain, relayOpts) + if err != nil { + return nil, err + } + address := common.HexToAddress(relayOpts.ContractID) + typ, ver, err := ccipconfig.TypeAndVersion(address, r.chain.Client()) + if err != nil { + return nil, err + } + fn, err := ccipcommit.CommitReportToEthTxMeta(typ, ver) + if err != nil { + return nil, err + } + subjectID := chainToUUID(configWatcher.chain.ID()) + contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, r.ks.Eth(), configWatcher, configTransmitterOpts{ + subjectID: &subjectID, + }, OCR2AggregatorTransmissionContractABI, WithReportToEthMetadata(fn), WithRetention(0)) + if err != nil { + return nil, err + } + + return NewDstCommitProvider( + r.lggr, + versionFinder, + destStartBlock, + r.chain.Client(), + r.chain.LogPoller(), + r.chain.GasEstimator(), + *r.chain.Config().EVM().GasEstimator().PriceMax().ToInt(), + *contractTransmitter, + configWatcher, + ), nil +} + +// NewCCIPExecProvider constructs a provider of type CCIPExecProvider. Since this is happening in the Relayer, +// which lives in a separate process from delegate which is requesting a provider, we need to wire in through pargs +// which *type* (impl) of CCIPExecProvider should be created. CCIP is currently a special case where the provider has a +// subset of implementations of the complete interface as certain contracts in a CCIP lane are only deployed on the src +// chain or on the dst chain. This results in the two implementations of providers: a src and dst implementation. +func (r *Relayer) NewCCIPExecProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.CCIPExecProvider, error) { + // TODO https://smartcontract-it.atlassian.net/browse/BCF-2887 + ctx := context.Background() + + versionFinder := ccip.NewEvmVersionFinder() + + var execPluginConfig ccipconfig.ExecPluginConfig + err := json.Unmarshal(pargs.PluginConfig, &execPluginConfig) + if err != nil { + return nil, err + } + + usdcConfig := execPluginConfig.USDCConfig + + // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; + // bail early. + if execPluginConfig.IsSourceProvider { + return NewSrcExecProvider( + r.lggr, + versionFinder, + r.chain.Client(), + r.chain.GasEstimator(), + r.chain.Config().EVM().GasEstimator().PriceMax().ToInt(), + r.chain.LogPoller(), + execPluginConfig.SourceStartBlock, + execPluginConfig.JobID, + usdcConfig.AttestationAPI, + int(usdcConfig.AttestationAPITimeoutSeconds), + usdcConfig.AttestationAPIIntervalMilliseconds, + usdcConfig.SourceMessageTransmitterAddress, + ) + } + + relayOpts := types.NewRelayOpts(rargs) + configWatcher, err := newStandardConfigProvider(ctx, r.lggr, r.chain, relayOpts) + if err != nil { + return nil, err + } + address := common.HexToAddress(relayOpts.ContractID) + typ, ver, err := ccipconfig.TypeAndVersion(address, r.chain.Client()) + if err != nil { + return nil, err + } + fn, err := ccipexec.ExecReportToEthTxMeta(ctx, typ, ver) + if err != nil { + return nil, err + } + subjectID := chainToUUID(configWatcher.chain.ID()) + contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, r.ks.Eth(), configWatcher, configTransmitterOpts{ + subjectID: &subjectID, + }, OCR2AggregatorTransmissionContractABI, WithReportToEthMetadata(fn), WithRetention(0)) + if err != nil { + return nil, err + } + + return NewDstExecProvider( + r.lggr, + versionFinder, + r.chain.Client(), + r.chain.LogPoller(), + execPluginConfig.DestStartBlock, + contractTransmitter, + configWatcher, + r.chain.GasEstimator(), + *r.chain.Config().EVM().GasEstimator().PriceMax().ToInt(), + r.chain.TxManager(), + cciptypes.Address(rargs.ContractID), + ) } var _ commontypes.MedianProvider = (*medianProvider)(nil) diff --git a/core/services/relay/evm/exec_provider.go b/core/services/relay/evm/exec_provider.go new file mode 100644 index 00000000000..ae3ce56532a --- /dev/null +++ b/core/services/relay/evm/exec_provider.go @@ -0,0 +1,391 @@ +package evm + +import ( + "context" + "fmt" + "math/big" + "net/url" + "time" + + "go.uber.org/multierr" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata/usdc" +) + +type SrcExecProvider struct { + lggr logger.Logger + versionFinder ccip.VersionFinder + client client.Client + lp logpoller.LogPoller + startBlock uint64 + estimator gas.EvmFeeEstimator + maxGasPrice *big.Int + usdcReader *ccip.USDCReaderImpl + usdcAttestationAPI string + usdcAttestationAPITimeoutSeconds int + usdcAttestationAPIIntervalMilliseconds int + usdcSrcMsgTransmitterAddr common.Address + + // these values are nil and are updated for Close() + seenOnRampAddress *cciptypes.Address + seenSourceChainSelector *uint64 + seenDestChainSelector *uint64 +} + +func NewSrcExecProvider( + lggr logger.Logger, + versionFinder ccip.VersionFinder, + client client.Client, + estimator gas.EvmFeeEstimator, + maxGasPrice *big.Int, + lp logpoller.LogPoller, + startBlock uint64, + jobID string, + usdcAttestationAPI string, + usdcAttestationAPITimeoutSeconds int, + usdcAttestationAPIIntervalMilliseconds int, + usdcSrcMsgTransmitterAddr common.Address, +) (commontypes.CCIPExecProvider, error) { + var usdcReader *ccip.USDCReaderImpl + var err error + if usdcAttestationAPI != "" { + usdcReader, err = ccip.NewUSDCReader(lggr, jobID, usdcSrcMsgTransmitterAddr, lp, true) + if err != nil { + return nil, fmt.Errorf("new usdc reader: %w", err) + } + } + + return &SrcExecProvider{ + lggr: lggr, + versionFinder: versionFinder, + client: client, + estimator: estimator, + maxGasPrice: maxGasPrice, + lp: lp, + startBlock: startBlock, + usdcReader: usdcReader, + usdcAttestationAPI: usdcAttestationAPI, + usdcAttestationAPITimeoutSeconds: usdcAttestationAPITimeoutSeconds, + usdcAttestationAPIIntervalMilliseconds: usdcAttestationAPIIntervalMilliseconds, + usdcSrcMsgTransmitterAddr: usdcSrcMsgTransmitterAddr, + }, nil +} + +func (s *SrcExecProvider) Name() string { + return "CCIP.SrcExecProvider" +} + +func (s *SrcExecProvider) Start(ctx context.Context) error { + if s.startBlock != 0 { + s.lggr.Infow("start replaying src chain", "fromBlock", s.startBlock) + return s.lp.Replay(ctx, int64(s.startBlock)) + } + return nil +} + +// Close is called when the job that created this provider is closed. +func (s *SrcExecProvider) Close() error { + versionFinder := ccip.NewEvmVersionFinder() + + unregisterFuncs := make([]func() error, 0, 2) + unregisterFuncs = append(unregisterFuncs, func() error { + // avoid panic in the case NewOnRampReader wasn't called + if s.seenOnRampAddress == nil { + return nil + } + return ccip.CloseOnRampReader(s.lggr, versionFinder, *s.seenSourceChainSelector, *s.seenDestChainSelector, *s.seenOnRampAddress, s.lp, s.client) + }) + unregisterFuncs = append(unregisterFuncs, func() error { + if s.usdcAttestationAPI == "" { + return nil + } + return ccip.CloseUSDCReader(s.lggr, s.lggr.Name(), s.usdcSrcMsgTransmitterAddr, s.lp) + }) + var multiErr error + for _, fn := range unregisterFuncs { + if err := fn(); err != nil { + multiErr = multierr.Append(multiErr, err) + } + } + return multiErr +} + +func (s *SrcExecProvider) Ready() error { + return nil +} + +func (s *SrcExecProvider) HealthReport() map[string]error { + return make(map[string]error) +} + +func (s *SrcExecProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + // TODO CCIP-2494 + // OffchainConfigDigester called on SrcExecProvider. It should only be called on DstExecProvider + return UnimplementedOffchainConfigDigester{} +} + +func (s *SrcExecProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + // TODO CCIP-2494 + // "ContractConfigTracker called on SrcExecProvider. It should only be called on DstExecProvider + return UnimplementedContractConfigTracker{} +} + +func (s *SrcExecProvider) ContractTransmitter() ocrtypes.ContractTransmitter { + // TODO CCIP-2494 + // "ContractTransmitter called on SrcExecProvider. It should only be called on DstExecProvider + return UnimplementedContractTransmitter{} +} + +func (s *SrcExecProvider) ChainReader() commontypes.ContractReader { + return nil +} + +func (s *SrcExecProvider) Codec() commontypes.Codec { + return nil +} + +func (s *SrcExecProvider) GetTransactionStatus(ctx context.Context, transactionID string) (types.TransactionStatus, error) { + return 0, fmt.Errorf("invalid: GetTransactionStatus called on SrcExecProvider. It should only be called on DstExecProvider") +} + +func (s *SrcExecProvider) NewCommitStoreReader(ctx context.Context, addr cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { + commitStoreReader = NewIncompleteSourceCommitStoreReader(s.estimator, s.maxGasPrice) + return +} + +func (s *SrcExecProvider) NewOffRampReader(ctx context.Context, addr cciptypes.Address) (cciptypes.OffRampReader, error) { + return nil, fmt.Errorf("invalid: NewOffRampReader called on SrcExecProvider. Valid on DstExecProvider") +} + +func (s *SrcExecProvider) NewOnRampReader(ctx context.Context, onRampAddress cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (onRampReader cciptypes.OnRampReader, err error) { + s.seenOnRampAddress = &onRampAddress + + versionFinder := ccip.NewEvmVersionFinder() + onRampReader, err = ccip.NewOnRampReader(s.lggr, versionFinder, sourceChainSelector, destChainSelector, onRampAddress, s.lp, s.client) + return +} + +func (s *SrcExecProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { + srcPriceRegistry := ccip.NewEvmPriceRegistry(s.lp, s.client, s.lggr, ccip.ExecPluginLabel) + priceRegistryReader, err = srcPriceRegistry.NewPriceRegistryReader(ctx, addr) + return +} + +func (s *SrcExecProvider) NewTokenDataReader(ctx context.Context, tokenAddress cciptypes.Address) (tokenDataReader cciptypes.TokenDataReader, err error) { + attestationURI, err2 := url.ParseRequestURI(s.usdcAttestationAPI) + if err2 != nil { + return nil, fmt.Errorf("failed to parse USDC attestation API: %w", err2) + } + tokenAddr, err2 := ccip.GenericAddrToEvm(tokenAddress) + if err2 != nil { + return nil, fmt.Errorf("failed to parse token address: %w", err2) + } + tokenDataReader = usdc.NewUSDCTokenDataReader( + s.lggr, + s.usdcReader, + attestationURI, + s.usdcAttestationAPITimeoutSeconds, + tokenAddr, + time.Duration(s.usdcAttestationAPIIntervalMilliseconds)*time.Millisecond, + ) + return +} + +func (s *SrcExecProvider) NewTokenPoolBatchedReader(ctx context.Context, offRampAddr cciptypes.Address, sourceChainSelector uint64) (cciptypes.TokenPoolBatchedReader, error) { + return nil, fmt.Errorf("invalid: NewTokenPoolBatchedReader called on SrcExecProvider. It should only be called on DstExecProvdier") +} + +func (s *SrcExecProvider) SourceNativeToken(ctx context.Context, sourceRouterAddr cciptypes.Address) (cciptypes.Address, error) { + sourceRouterAddrHex, err := ccip.GenericAddrToEvm(sourceRouterAddr) + if err != nil { + return "", err + } + sourceRouter, err := router.NewRouter(sourceRouterAddrHex, s.client) + if err != nil { + return "", err + } + sourceNative, err := sourceRouter.GetWrappedNative(&bind.CallOpts{Context: ctx}) + if err != nil { + return "", err + } + + return ccip.EvmAddrToGeneric(sourceNative), nil +} + +type DstExecProvider struct { + lggr logger.Logger + versionFinder ccip.VersionFinder + client client.Client + lp logpoller.LogPoller + startBlock uint64 + contractTransmitter *contractTransmitter + configWatcher *configWatcher + gasEstimator gas.EvmFeeEstimator + maxGasPrice big.Int + txm txmgr.TxManager + offRampAddress cciptypes.Address + + // these values are nil and are updated for Close() + seenCommitStoreAddr *cciptypes.Address +} + +func NewDstExecProvider( + lggr logger.Logger, + versionFinder ccip.VersionFinder, + client client.Client, + lp logpoller.LogPoller, + startBlock uint64, + contractTransmitter *contractTransmitter, + configWatcher *configWatcher, + gasEstimator gas.EvmFeeEstimator, + maxGasPrice big.Int, + txm txmgr.TxManager, + offRampAddress cciptypes.Address, +) (commontypes.CCIPExecProvider, error) { + return &DstExecProvider{ + lggr: lggr, + versionFinder: versionFinder, + client: client, + lp: lp, + startBlock: startBlock, + contractTransmitter: contractTransmitter, + configWatcher: configWatcher, + gasEstimator: gasEstimator, + maxGasPrice: maxGasPrice, + txm: txm, + offRampAddress: offRampAddress, + }, nil +} + +func (d *DstExecProvider) Name() string { + return "CCIP.DestRelayerExecProvider" +} + +func (d *DstExecProvider) Start(ctx context.Context) error { + if d.startBlock != 0 { + d.lggr.Infow("start replaying dst chain", "fromBlock", d.startBlock) + return d.lp.Replay(ctx, int64(d.startBlock)) + } + return nil +} + +// Close is called when the job that created this provider is deleted +// At this time, any of the methods on the provider may or may not have been called. +// If NewOnRampReader and NewCommitStoreReader have not been called, their corresponding +// Close methods will be expected to error. +func (d *DstExecProvider) Close() error { + versionFinder := ccip.NewEvmVersionFinder() + + unregisterFuncs := make([]func() error, 0, 2) + unregisterFuncs = append(unregisterFuncs, func() error { + if d.seenCommitStoreAddr == nil { + return nil + } + return ccip.CloseCommitStoreReader(d.lggr, versionFinder, *d.seenCommitStoreAddr, d.client, d.lp) + }) + unregisterFuncs = append(unregisterFuncs, func() error { + return ccip.CloseOffRampReader(d.lggr, versionFinder, d.offRampAddress, d.client, d.lp, nil, big.NewInt(0)) + }) + + var multiErr error + for _, fn := range unregisterFuncs { + if err := fn(); err != nil { + multiErr = multierr.Append(multiErr, err) + } + } + return multiErr +} + +func (d *DstExecProvider) Ready() error { + return nil +} + +func (d *DstExecProvider) HealthReport() map[string]error { + return make(map[string]error) +} + +func (d *DstExecProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + return d.configWatcher.OffchainConfigDigester() +} + +func (d *DstExecProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + return d.configWatcher.ContractConfigTracker() +} + +func (d *DstExecProvider) ContractTransmitter() ocrtypes.ContractTransmitter { + return d.contractTransmitter +} + +func (d *DstExecProvider) ChainReader() commontypes.ContractReader { + return nil +} + +func (d *DstExecProvider) Codec() commontypes.Codec { + return nil +} + +func (d *DstExecProvider) GetTransactionStatus(ctx context.Context, transactionID string) (types.TransactionStatus, error) { + return d.txm.GetTransactionStatus(ctx, transactionID) +} + +func (d *DstExecProvider) NewCommitStoreReader(ctx context.Context, addr cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { + d.seenCommitStoreAddr = &addr + + versionFinder := ccip.NewEvmVersionFinder() + commitStoreReader, err = NewIncompleteDestCommitStoreReader(d.lggr, versionFinder, addr, d.client, d.lp) + return +} + +func (d *DstExecProvider) NewOffRampReader(ctx context.Context, offRampAddress cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { + offRampReader, err = ccip.NewOffRampReader(d.lggr, d.versionFinder, offRampAddress, d.client, d.lp, d.gasEstimator, &d.maxGasPrice, true) + return +} + +func (d *DstExecProvider) NewOnRampReader(ctx context.Context, addr cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (cciptypes.OnRampReader, error) { + return nil, fmt.Errorf("invalid: NewOnRampReader called on DstExecProvider. It should only be called on SrcExecProvider") +} + +func (d *DstExecProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { + destPriceRegistry := ccip.NewEvmPriceRegistry(d.lp, d.client, d.lggr, ccip.ExecPluginLabel) + priceRegistryReader, err = destPriceRegistry.NewPriceRegistryReader(ctx, addr) + return +} + +func (d *DstExecProvider) NewTokenDataReader(ctx context.Context, tokenAddress cciptypes.Address) (cciptypes.TokenDataReader, error) { + return nil, fmt.Errorf("invalid: NewTokenDataReader called on DstExecProvider. It should only be called on SrcExecProvider") +} + +func (d *DstExecProvider) NewTokenPoolBatchedReader(ctx context.Context, offRampAddress cciptypes.Address, sourceChainSelector uint64) (tokenPoolBatchedReader cciptypes.TokenPoolBatchedReader, err error) { + batchCaller := ccip.NewDynamicLimitedBatchCaller( + d.lggr, + d.client, + uint(ccip.DefaultRpcBatchSizeLimit), + uint(ccip.DefaultRpcBatchBackOffMultiplier), + uint(ccip.DefaultMaxParallelRpcCalls), + ) + + tokenPoolBatchedReader, err = ccip.NewEVMTokenPoolBatchedReader(d.lggr, sourceChainSelector, offRampAddress, batchCaller) + if err != nil { + return nil, fmt.Errorf("new token pool batched reader: %w", err) + } + return +} + +func (d *DstExecProvider) SourceNativeToken(ctx context.Context, addr cciptypes.Address) (cciptypes.Address, error) { + return "", fmt.Errorf("invalid: SourceNativeToken called on DstExecProvider. It should only be called on SrcExecProvider") +} diff --git a/core/services/relay/evm/statuschecker/mocks/ccip_transaction_status_checker.go b/core/services/relay/evm/statuschecker/mocks/ccip_transaction_status_checker.go new file mode 100644 index 00000000000..9bd59ccf4ef --- /dev/null +++ b/core/services/relay/evm/statuschecker/mocks/ccip_transaction_status_checker.go @@ -0,0 +1,104 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// CCIPTransactionStatusChecker is an autogenerated mock type for the CCIPTransactionStatusChecker type +type CCIPTransactionStatusChecker struct { + mock.Mock +} + +type CCIPTransactionStatusChecker_Expecter struct { + mock *mock.Mock +} + +func (_m *CCIPTransactionStatusChecker) EXPECT() *CCIPTransactionStatusChecker_Expecter { + return &CCIPTransactionStatusChecker_Expecter{mock: &_m.Mock} +} + +// CheckMessageStatus provides a mock function with given fields: ctx, msgID +func (_m *CCIPTransactionStatusChecker) CheckMessageStatus(ctx context.Context, msgID string) ([]types.TransactionStatus, int, error) { + ret := _m.Called(ctx, msgID) + + if len(ret) == 0 { + panic("no return value specified for CheckMessageStatus") + } + + var r0 []types.TransactionStatus + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]types.TransactionStatus, int, error)); ok { + return rf(ctx, msgID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []types.TransactionStatus); ok { + r0 = rf(ctx, msgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.TransactionStatus) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) int); ok { + r1 = rf(ctx, msgID) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { + r2 = rf(ctx, msgID) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// CCIPTransactionStatusChecker_CheckMessageStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckMessageStatus' +type CCIPTransactionStatusChecker_CheckMessageStatus_Call struct { + *mock.Call +} + +// CheckMessageStatus is a helper method to define mock.On call +// - ctx context.Context +// - msgID string +func (_e *CCIPTransactionStatusChecker_Expecter) CheckMessageStatus(ctx interface{}, msgID interface{}) *CCIPTransactionStatusChecker_CheckMessageStatus_Call { + return &CCIPTransactionStatusChecker_CheckMessageStatus_Call{Call: _e.mock.On("CheckMessageStatus", ctx, msgID)} +} + +func (_c *CCIPTransactionStatusChecker_CheckMessageStatus_Call) Run(run func(ctx context.Context, msgID string)) *CCIPTransactionStatusChecker_CheckMessageStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *CCIPTransactionStatusChecker_CheckMessageStatus_Call) Return(transactionStatuses []types.TransactionStatus, retryCounter int, err error) *CCIPTransactionStatusChecker_CheckMessageStatus_Call { + _c.Call.Return(transactionStatuses, retryCounter, err) + return _c +} + +func (_c *CCIPTransactionStatusChecker_CheckMessageStatus_Call) RunAndReturn(run func(context.Context, string) ([]types.TransactionStatus, int, error)) *CCIPTransactionStatusChecker_CheckMessageStatus_Call { + _c.Call.Return(run) + return _c +} + +// NewCCIPTransactionStatusChecker creates a new instance of CCIPTransactionStatusChecker. 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 NewCCIPTransactionStatusChecker(t interface { + mock.TestingT + Cleanup(func()) +}) *CCIPTransactionStatusChecker { + mock := &CCIPTransactionStatusChecker{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/relay/evm/statuschecker/txm_status_checker.go b/core/services/relay/evm/statuschecker/txm_status_checker.go new file mode 100644 index 00000000000..f22e6d78b9f --- /dev/null +++ b/core/services/relay/evm/statuschecker/txm_status_checker.go @@ -0,0 +1,54 @@ +package statuschecker + +import ( + "context" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// CCIPTransactionStatusChecker is an interface that defines the method for checking the status of a transaction. +// CheckMessageStatus checks the status of a transaction for a given message ID. +// It returns a list of transaction statuses, the retry counter, and an error if any occurred during the process. +// + +type CCIPTransactionStatusChecker interface { + CheckMessageStatus(ctx context.Context, msgID string) (transactionStatuses []types.TransactionStatus, retryCounter int, err error) +} + +type TxmStatusChecker struct { + getTransactionStatus func(ctx context.Context, transactionID string) (types.TransactionStatus, error) +} + +func NewTxmStatusChecker(getTransactionStatus func(ctx context.Context, transactionID string) (types.TransactionStatus, error)) *TxmStatusChecker { + return &TxmStatusChecker{getTransactionStatus: getTransactionStatus} +} + +// CheckMessageStatus checks the status of a message by checking the status of all transactions associated with the message ID. +// It returns a slice of all statuses and the number of transactions found (-1 if none). +// The key will follow the format: -. TXM will be queried for each key until a NotFound error is returned. +// The goal is to find all transactions associated with a message ID and snooze messages if they are fatal in the Execution Plugin. +func (tsc *TxmStatusChecker) CheckMessageStatus(ctx context.Context, msgID string) ([]types.TransactionStatus, int, error) { + var counter int + const maxStatuses = 1000 // Cap the number of statuses to avoid infinite loop + + allStatuses := make([]types.TransactionStatus, 0) + + for { + transactionID := fmt.Sprintf("%s-%d", msgID, counter) + status, err := tsc.getTransactionStatus(ctx, transactionID) + if err != nil && status == types.Unknown { + // If the status is unknown and err not nil, it means the transaction was not found + break + } + allStatuses = append(allStatuses, status) + counter++ + + // Break the loop if the cap is reached + if counter >= maxStatuses { + return allStatuses, counter - 1, fmt.Errorf("maximum number of statuses reached, possible infinite loop") + } + } + + return allStatuses, counter - 1, nil +} diff --git a/core/services/relay/evm/statuschecker/txm_status_checker_test.go b/core/services/relay/evm/statuschecker/txm_status_checker_test.go new file mode 100644 index 00000000000..456d07e7a7d --- /dev/null +++ b/core/services/relay/evm/statuschecker/txm_status_checker_test.go @@ -0,0 +1,103 @@ +package statuschecker + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" +) + +func Test_CheckMessageStatus(t *testing.T) { + testutils.SkipShort(t, "") + ctx := context.Background() + mockTxManager := mocks.NewMockEvmTxManager(t) + checker := NewTxmStatusChecker(mockTxManager.GetTransactionStatus) + + msgID := "test-message-id" + + // Define test cases + testCases := []struct { + name string + setupMock func() + expectedStatus []types.TransactionStatus + expectedCounter int + expectedError error + }{ + { + name: "No transactions found", + setupMock: func() { + mockTxManager.Mock = mock.Mock{} + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-0").Return(types.Unknown, errors.New("failed to find transaction with IdempotencyKey test-message-id-0")) + }, + expectedStatus: []types.TransactionStatus{}, + expectedCounter: -1, + expectedError: nil, + }, + { + name: "Single transaction found", + setupMock: func() { + mockTxManager.Mock = mock.Mock{} + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-0").Return(types.Finalized, nil) + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-1").Return(types.Unknown, errors.New("failed to find transaction with IdempotencyKey test-message-id-1")) + }, + expectedStatus: []types.TransactionStatus{types.Finalized}, + expectedCounter: 0, + expectedError: nil, + }, + { + name: "Multiple transactions found", + setupMock: func() { + mockTxManager.Mock = mock.Mock{} + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-0").Return(types.Finalized, nil) + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-1").Return(types.Failed, nil) + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-2").Return(types.Unknown, errors.New("failed to find transaction with IdempotencyKey test-message-id-2")) + }, + expectedStatus: []types.TransactionStatus{types.Finalized, types.Failed}, + expectedCounter: 1, + expectedError: nil, + }, + { + name: "Unknown status without nil (in progress)", + setupMock: func() { + mockTxManager.Mock = mock.Mock{} + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-0").Return(types.Unknown, nil) + mockTxManager.On("GetTransactionStatus", ctx, "test-message-id-1").Return(types.Unknown, errors.New("failed to find transaction with IdempotencyKey test-message-id-1")) + }, + expectedStatus: []types.TransactionStatus{types.Unknown}, + expectedCounter: 0, + expectedError: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.setupMock() + statuses, counter, err := checker.CheckMessageStatus(ctx, msgID) + assert.Equal(t, tc.expectedStatus, statuses) + assert.Equal(t, tc.expectedCounter, counter) + assert.Equal(t, tc.expectedError, err) + mockTxManager.AssertExpectations(t) + }) + } +} + +func Test_FailForMoreThan1000Retries(t *testing.T) { + ctx := context.Background() + mockTxManager := mocks.NewMockEvmTxManager(t) + checker := NewTxmStatusChecker(mockTxManager.GetTransactionStatus) + + for i := 0; i < 1000; i++ { + mockTxManager.On("GetTransactionStatus", ctx, fmt.Sprintf("test-message-id-%d", i)).Return(types.Finalized, nil) + } + + msgID := "test-message-id" + _, _, err := checker.CheckMessageStatus(ctx, msgID) + assert.EqualError(t, err, "maximum number of statuses reached, possible infinite loop") +} diff --git a/core/services/synchronization/common.go b/core/services/synchronization/common.go index 5f469c055d4..bfb9fba6de6 100644 --- a/core/services/synchronization/common.go +++ b/core/services/synchronization/common.go @@ -16,6 +16,8 @@ const ( OCR TelemetryType = "ocr" OCR2Automation TelemetryType = "ocr2-automation" OCR2Functions TelemetryType = "ocr2-functions" + OCR2CCIPCommit TelemetryType = "ocr2-ccip-commit" + OCR2CCIPExec TelemetryType = "ocr2-ccip-exec" OCR2Threshold TelemetryType = "ocr2-threshold" OCR2S4 TelemetryType = "ocr2-s4" OCR2Median TelemetryType = "ocr2-median" diff --git a/core/web/resolver/testdata/config-empty-effective.toml b/core/web/resolver/testdata/config-empty-effective.toml index 1bad3fd91c6..f1325d824ea 100644 --- a/core/web/resolver/testdata/config-empty-effective.toml +++ b/core/web/resolver/testdata/config-empty-effective.toml @@ -6,6 +6,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 1672eb1b41d..9421e6198ee 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -6,6 +6,7 @@ ShutdownGracePeriod = '10s' FeedsManager = true LogPoller = true UICSAKeys = true +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1m0s' diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 0e12af9a7e4..1c4093cbfca 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -6,6 +6,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 5caab7614e8..47935390ce8 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -51,6 +51,7 @@ ShutdownGracePeriod is the maximum time allowed to shut down gracefully. If exce FeedsManager = true # Default LogPoller = false # Default UICSAKeys = false # Default +CCIP = true # Default ``` @@ -72,6 +73,12 @@ UICSAKeys = false # Default ``` UICSAKeys enables CSA Keys in the UI. +### CCIP +```toml +CCIP = true # Default +``` +CCIP enables the CCIP service. + ## Database ```toml [Database] diff --git a/go.mod b/go.mod index 8e2103eb246..501fe6a36b8 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,12 @@ require ( github.com/NethermindEth/juno v0.3.1 github.com/NethermindEth/starknet.go v0.7.1-0.20240401080518-34a506f3cfdb github.com/XSAM/otelsql v0.27.0 - github.com/avast/retry-go/v4 v4.5.1 + github.com/avast/retry-go/v4 v4.6.0 github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/cometbft/cometbft v0.37.2 github.com/cosmos/cosmos-sdk v0.47.4 github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e + github.com/deckarep/golang-set/v2 v2.3.0 github.com/dominikbraun/graph v0.23.0 github.com/esote/minmaxheap v1.0.0 github.com/ethereum/go-ethereum v1.13.8 @@ -67,6 +68,7 @@ require ( github.com/prometheus/prometheus v0.48.1 github.com/robfig/cron/v3 v3.0.1 github.com/rogpeppe/go-internal v1.12.0 + github.com/rs/zerolog v1.30.0 github.com/scylladb/go-reflectx v1.0.1 github.com/shirou/gopsutil/v3 v3.24.3 github.com/shopspring/decimal v1.4.0 @@ -92,6 +94,7 @@ require ( github.com/umbracle/ethgo v0.1.3 github.com/unrolled/secure v1.13.0 github.com/urfave/cli v1.22.14 + github.com/wk8/go-ordered-map/v2 v2.1.8 go.dedis.ch/fixbuf v1.0.3 go.dedis.ch/kyber/v3 v3.1.0 go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0 @@ -112,6 +115,7 @@ require ( google.golang.org/protobuf v1.34.2 gopkg.in/guregu/null.v4 v4.0.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 + k8s.io/utils v0.0.0-20230711102312-30195339c3c7 ) require ( @@ -174,7 +178,6 @@ require ( github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckarep/golang-set/v2 v2.3.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect @@ -312,7 +315,6 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 // indirect github.com/valyala/fastjson v1.4.1 // indirect - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/hid v0.9.1 // indirect diff --git a/go.sum b/go.sum index 73d6d5b227a..2b936444ad8 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= -github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= +github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= +github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= @@ -264,6 +264,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= @@ -491,6 +492,7 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -1093,6 +1095,7 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= @@ -1911,6 +1914,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +k8s.io/utils v0.0.0-20230711102312-30195339c3c7 h1:ZgnF1KZsYxWIifwSNZFZgNtWE89WI5yiP5WwlfDoIyc= +k8s.io/utils v0.0.0-20230711102312-30195339c3c7/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index ed693f4fccc..824e15cf337 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -6,7 +6,7 @@ go 1.22.5 replace github.com/smartcontractkit/chainlink/v2 => ../ require ( - github.com/avast/retry-go/v4 v4.5.1 + github.com/avast/retry-go/v4 v4.6.0 github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240709130330-9f4feec7553f github.com/cli/go-gh/v2 v2.0.0 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index ca3ce8d903e..9258da195c8 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -207,8 +207,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= -github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= +github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= +github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 3d1ae6c7a98..42071bbf8b4 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -76,7 +76,7 @@ require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect - github.com/avast/retry-go/v4 v4.5.1 // indirect + github.com/avast/retry-go/v4 v4.6.0 // indirect github.com/aws/aws-sdk-go v1.45.25 // indirect github.com/aws/constructs-go/constructs/v10 v10.1.255 // indirect github.com/aws/jsii-runtime-go v1.75.0 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 2a54ec9254f..15bfa047fbe 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -207,8 +207,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= -github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= +github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= +github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= diff --git a/testdata/scripts/node/validate/default.txtar b/testdata/scripts/node/validate/default.txtar index 1063d9c2a5a..ff8b4889c49 100644 --- a/testdata/scripts/node/validate/default.txtar +++ b/testdata/scripts/node/validate/default.txtar @@ -18,6 +18,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 56ce1ea7ba8..f4dd43cb900 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -62,6 +62,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar index e534c67a2f3..75a6ae36418 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -62,6 +62,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index 29bc189e561..97bae5a84b6 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -62,6 +62,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/testdata/scripts/node/validate/invalid-ocr-p2p.txtar b/testdata/scripts/node/validate/invalid-ocr-p2p.txtar index 6a09dd06c47..0cdf001eccd 100644 --- a/testdata/scripts/node/validate/invalid-ocr-p2p.txtar +++ b/testdata/scripts/node/validate/invalid-ocr-p2p.txtar @@ -47,6 +47,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 60c42c7c399..ab6860ec790 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -52,6 +52,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index 719bb8bcc47..603fdaada66 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -59,6 +59,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' diff --git a/testdata/scripts/node/validate/warnings.txtar b/testdata/scripts/node/validate/warnings.txtar index a652943e26b..dea40ec8da0 100644 --- a/testdata/scripts/node/validate/warnings.txtar +++ b/testdata/scripts/node/validate/warnings.txtar @@ -41,6 +41,7 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false +CCIP = true [Database] DefaultIdleInTxSessionTimeout = '1h0m0s'