diff --git a/core/capabilities/ccip/delegate.go b/core/capabilities/ccip/delegate.go index 1b15180da0d..160fdd10961 100644 --- a/core/capabilities/ccip/delegate.go +++ b/core/capabilities/ccip/delegate.go @@ -28,6 +28,7 @@ import ( kcr "github.com/smartcontractkit/chainlink-evm/gethwrappers/keystone/generated/capabilities_registry_1_1_0" "github.com/smartcontractkit/chainlink-evm/pkg/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common" configsevm "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/launcher" @@ -51,7 +52,7 @@ import ( type RelayGetter interface { Get(types.RelayID) (loop.Relayer, error) - GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) + GetIDToRelayerMap() map[types.RelayID]loop.Relayer } type Keystore[K keystore.Key] interface { @@ -145,10 +146,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services return nil, err } - allRelayers, err := d.relayers.GetIDToRelayerMap() - if err != nil { - return nil, fmt.Errorf("could not fetch all relayers: %w", err) - } + allRelayers := d.relayers.GetIDToRelayerMap() transmitterKeys, err := d.getTransmitterKeys(ctx, maps.Keys(allRelayers)) if err != nil { return nil, err diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index f15274c9389..c5e65b09ac8 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -471,13 +471,15 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err ) srvcs = append(srvcs, workflowORM) - promReporter := headreporter.NewPrometheusReporter(opts.DS, legacyEVMChains) - chainIDs := make([]*big.Int, legacyEVMChains.Len()) + promReporter := headreporter.NewLegacyEVMPrometheusReporter(opts.DS, legacyEVMChains) + evmChainIDs := make([]*big.Int, legacyEVMChains.Len()) for i, chain := range legacyEVMChains.Slice() { - chainIDs[i] = chain.ID() + evmChainIDs[i] = chain.ID() } - telemReporter := headreporter.NewTelemetryReporter(telemetryManager, globalLogger, chainIDs...) - headReporter := headreporter.NewHeadReporterService(opts.DS, globalLogger, promReporter, telemReporter) + + legacyEVMTelemReporter := headreporter.NewLegacyEVMTelemetryReporter(telemetryManager, globalLogger, evmChainIDs...) + loopTelemReporter := headreporter.NewTelemetryReporter(telemetryManager, globalLogger, relayChainInterops.GetIDToRelayerMap()) + headReporter := headreporter.NewHeadReporterService(opts.DS, globalLogger, promReporter, legacyEVMTelemReporter, loopTelemReporter) srvcs = append(srvcs, headReporter) for _, chain := range legacyEVMChains.Slice() { chain.HeadBroadcaster().Subscribe(headReporter) diff --git a/core/services/chainlink/mocks/relayer_chain_interoperators.go b/core/services/chainlink/mocks/relayer_chain_interoperators.go index f15b1cc18c1..e71b183cb33 100644 --- a/core/services/chainlink/mocks/relayer_chain_interoperators.go +++ b/core/services/chainlink/mocks/relayer_chain_interoperators.go @@ -47,8 +47,8 @@ func (f *FakeRelayerChainInteroperators) Get(id types.RelayID) (loop.Relayer, er return r, nil } -func (f *FakeRelayerChainInteroperators) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { - return f.Relayers, nil +func (f *FakeRelayerChainInteroperators) GetIDToRelayerMap() map[types.RelayID]loop.Relayer { + return f.Relayers } func (f *FakeRelayerChainInteroperators) Slice() []loop.Relayer { diff --git a/core/services/chainlink/relayer_chain_interoperators.go b/core/services/chainlink/relayer_chain_interoperators.go index a7e6d2121a6..7728f9dccc0 100644 --- a/core/services/chainlink/relayer_chain_interoperators.go +++ b/core/services/chainlink/relayer_chain_interoperators.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services" @@ -244,7 +245,7 @@ func (rs *CoreRelayerChainInteroperators) Get(id types.RelayID) (loop.Relayer, e return lr, nil } -func (rs *CoreRelayerChainInteroperators) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { +func (rs *CoreRelayerChainInteroperators) GetIDToRelayerMap() map[types.RelayID]loop.Relayer { rs.mu.Lock() defer rs.mu.Unlock() result := make(map[types.RelayID]loop.Relayer) @@ -252,7 +253,7 @@ func (rs *CoreRelayerChainInteroperators) GetIDToRelayerMap() (map[types.RelayID result[id] = relayer } - return result, nil + return result } // LegacyEVMChains returns a container with all the evm chains @@ -380,7 +381,7 @@ func FilterRelayersByType(network string) func(id types.RelayID) bool { } // List returns all the [RelayerChainInteroperators] that match the [FilterFn]. -// A typical usage pattern to use [List] with [FilterByType] to obtain a set of [RelayerChainInteroperators] +// A typical usage pattern to use [List] with [FilterRelayersByType] to obtain a set of [RelayerChainInteroperators] // for a given chain func (rs *CoreRelayerChainInteroperators) List(filter FilterFn) RelayerChainInteroperators { matches := make(map[types.RelayID]loop.Relayer) diff --git a/core/services/headreporter/prometheus_reporter.go b/core/services/headreporter/prometheus_reporter.go index 0e58f266e93..f197cb3b77d 100644 --- a/core/services/headreporter/prometheus_reporter.go +++ b/core/services/headreporter/prometheus_reporter.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" txmgrcommon "github.com/smartcontractkit/chainlink-framework/chains/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" ) @@ -60,7 +61,7 @@ var ( }) ) -func NewPrometheusReporter(ds sqlutil.DataSource, chainContainer legacyevm.LegacyChainContainer) *prometheusReporter { +func NewLegacyEVMPrometheusReporter(ds sqlutil.DataSource, chainContainer legacyevm.LegacyChainContainer) *prometheusReporter { return &prometheusReporter{ ds: ds, chains: chainContainer, diff --git a/core/services/headreporter/prometheus_reporter_test.go b/core/services/headreporter/prometheus_reporter_test.go index c06c925d926..782130d447d 100644 --- a/core/services/headreporter/prometheus_reporter_test.go +++ b/core/services/headreporter/prometheus_reporter_test.go @@ -35,7 +35,7 @@ func Test_PrometheusReporter(t *testing.T) { backend.On("SetMaxUnconfirmedAge", big.NewInt(0), float64(0)).Return() backend.On("SetMaxUnconfirmedBlocks", big.NewInt(0), int64(0)).Return() - reporter := headreporter.NewPrometheusReporter(db, newLegacyChainContainer(t, db)) + reporter := headreporter.NewLegacyEVMPrometheusReporter(db, newLegacyChainContainer(t, db)) reporter.SetBackend(backend) head := headreporter.NewHead() @@ -52,7 +52,7 @@ func Test_PrometheusReporter(t *testing.T) { db := pgtest.NewSqlxDB(t) backend := headreporter.NewMockPrometheusBackend(t) - reporter := headreporter.NewPrometheusReporter(db, newLegacyChainContainerWithNullTxm(t)) + reporter := headreporter.NewLegacyEVMPrometheusReporter(db, newLegacyChainContainerWithNullTxm(t)) reporter.SetBackend(backend) head := headreporter.NewHead() @@ -78,7 +78,7 @@ func Test_PrometheusReporter(t *testing.T) { })).Return() backend.On("SetMaxUnconfirmedBlocks", big.NewInt(0), int64(35)).Return() - reporter := headreporter.NewPrometheusReporter(db, newLegacyChainContainer(t, db)) + reporter := headreporter.NewLegacyEVMPrometheusReporter(db, newLegacyChainContainer(t, db)) reporter.SetBackend(backend) head := headreporter.NewHead() @@ -105,7 +105,7 @@ func Test_PrometheusReporter(t *testing.T) { backend.On("SetMaxUnconfirmedAge", big.NewInt(0), float64(0)).Return() backend.On("SetMaxUnconfirmedBlocks", big.NewInt(0), int64(0)).Return() - reporter := headreporter.NewPrometheusReporter(db, newLegacyChainContainer(t, db)) + reporter := headreporter.NewLegacyEVMPrometheusReporter(db, newLegacyChainContainer(t, db)) reporter.SetBackend(backend) head := headreporter.NewHead() diff --git a/core/services/headreporter/telemetry_reporter.go b/core/services/headreporter/telemetry_reporter.go index b8798280625..9869cb18195 100644 --- a/core/services/headreporter/telemetry_reporter.go +++ b/core/services/headreporter/telemetry_reporter.go @@ -2,59 +2,65 @@ package headreporter import ( "context" + "encoding/hex" + "fmt" "math/big" + "strconv" - "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/libocr/commontypes" "google.golang.org/protobuf/proto" evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" + "github.com/smartcontractkit/chainlink/v2/core/utils" ) -type telemetryReporter struct { +type legacyEVMTelemetryReporter struct { lggr logger.Logger endpoints map[uint64]commontypes.MonitoringEndpoint } -func NewTelemetryReporter(monitoringEndpointGen telemetry.MonitoringEndpointGenerator, lggr logger.Logger, chainIDs ...*big.Int) HeadReporter { +func NewLegacyEVMTelemetryReporter(monitoringEndpointGen telemetry.MonitoringEndpointGenerator, lggr logger.Logger, chainIDs ...*big.Int) HeadReporter { endpoints := make(map[uint64]commontypes.MonitoringEndpoint) for _, chainID := range chainIDs { endpoints[chainID.Uint64()] = monitoringEndpointGen.GenMonitoringEndpoint("EVM", chainID.String(), "", synchronization.HeadReport) } - return &telemetryReporter{lggr: lggr.Named("TelemetryReporter"), endpoints: endpoints} + return &legacyEVMTelemetryReporter{lggr: lggr.Named("TelemetryReporter"), endpoints: endpoints} } -func (t *telemetryReporter) ReportNewHead(ctx context.Context, head *evmtypes.Head) error { +func (t *legacyEVMTelemetryReporter) ReportNewHead(ctx context.Context, head *evmtypes.Head) error { monitoringEndpoint := t.endpoints[head.EVMChainID.ToInt().Uint64()] if monitoringEndpoint == nil { - return errors.Errorf("No monitoring endpoint provided chain_id=%d", head.EVMChainID.Int64()) + return fmt.Errorf("No monitoring endpoint provided chain_id=%d", head.EVMChainID.Int64()) } var finalized *telem.Block latestFinalizedHead := head.LatestFinalizedHead() if latestFinalizedHead != nil { finalized = &telem.Block{ - Timestamp: uint64(latestFinalizedHead.GetTimestamp().UTC().Unix()), - Number: uint64(latestFinalizedHead.BlockNumber()), + Timestamp: utils.NonNegativeInt64ToUint64(latestFinalizedHead.GetTimestamp().UTC().Unix()), + Number: utils.NonNegativeInt64ToUint64(latestFinalizedHead.BlockNumber()), Hash: latestFinalizedHead.BlockHash().Hex(), } } request := &telem.HeadReportRequest{ ChainID: head.EVMChainID.String(), Latest: &telem.Block{ - Timestamp: uint64(head.Timestamp.UTC().Unix()), - Number: uint64(head.Number), + Timestamp: utils.NonNegativeInt64ToUint64(head.Timestamp.UTC().Unix()), + Number: utils.NonNegativeInt64ToUint64(head.Number), Hash: head.Hash.Hex(), }, Finalized: finalized, } bytes, err := proto.Marshal(request) if err != nil { - return errors.WithMessage(err, "telem.HeadReportRequest marshal error") + return fmt.Errorf("telem.HeadReportRequest marshal error: %w", err) } monitoringEndpoint.SendLog(bytes) if finalized == nil { @@ -64,6 +70,73 @@ func (t *telemetryReporter) ReportNewHead(ctx context.Context, head *evmtypes.He return nil } -func (t *telemetryReporter) ReportPeriodic(ctx context.Context) error { +func (t *legacyEVMTelemetryReporter) ReportPeriodic(_ context.Context) error { + return nil +} + +type loopTelemetryReporter struct { + lggr logger.Logger + endpoints map[types.RelayID]commontypes.MonitoringEndpoint + relays map[types.RelayID]loop.Relayer +} + +// NewTelemetryReporter creates a new telemetry reporter for each relayer +func NewTelemetryReporter(monitoringEndpointGen telemetry.MonitoringEndpointGenerator, lggr logger.Logger, relayers map[types.RelayID]loop.Relayer) HeadReporter { + if relayers == nil { + return nil + } + endpoints := make(map[types.RelayID]commontypes.MonitoringEndpoint) + for relayID := range relayers { + endpoints[relayID] = monitoringEndpointGen.GenMonitoringEndpoint(relayID.Network, relayID.ChainID, "", synchronization.HeadReport) + } + return &loopTelemetryReporter{lggr: lggr.Named("TelemetryReporter"), endpoints: endpoints, relays: relayers} +} + +// ReportNewHead is unimplemented on Solana because there is no Headtracker to subscribe to +func (t *loopTelemetryReporter) ReportNewHead(_ context.Context, _ *evmtypes.Head) error { + return nil +} + +// ReportPeriodic is used on Solana to report the latest head +func (t *loopTelemetryReporter) ReportPeriodic(ctx context.Context) error { + for relayID, endpoint := range t.endpoints { + relay, ok := t.relays[relayID] + if !ok { + return fmt.Errorf("no relay found for Solana chain_id=%s", relayID.ChainID) + } + err := reportLatestHead(ctx, endpoint, relayID.ChainID, relay) + if err != nil { + return err + } + } + + return nil +} + +func reportLatestHead(ctx context.Context, endpoint commontypes.MonitoringEndpoint, chainID string, relay loop.Relayer) error { + head, err := relay.LatestHead(ctx) + if err != nil { + return fmt.Errorf("failed getting Solana head for chainID %s: %w", chainID, err) + } + + blockNum, err := strconv.ParseUint(head.Height, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse Solana block height %s: %w", head.Height, err) + } + + request := &telem.HeadReportRequest{ + ChainID: chainID, + Latest: &telem.Block{ + Timestamp: head.Timestamp, + Number: blockNum, + Hash: hex.EncodeToString(head.Hash), + }, + Finalized: nil, // latest finalized head retrieval not supported by Solana relayer yet + } + bytes, err := proto.Marshal(request) + if err != nil { + return fmt.Errorf("telem.HeadReportRequest marshal error: %w", err) + } + endpoint.SendLog(bytes) return nil } diff --git a/core/services/headreporter/telemetry_reporter_test.go b/core/services/headreporter/telemetry_reporter_test.go index dcbe0032e73..f2c99bc51a5 100644 --- a/core/services/headreporter/telemetry_reporter_test.go +++ b/core/services/headreporter/telemetry_reporter_test.go @@ -1,16 +1,24 @@ package headreporter_test import ( + "context" + "encoding/hex" "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" + evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" ubig "github.com/smartcontractkit/chainlink-evm/pkg/utils/big" + mocks2 "github.com/smartcontractkit/chainlink/v2/common/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -18,9 +26,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" + utils2 "github.com/smartcontractkit/chainlink/v2/core/utils" + testutils2 "github.com/smartcontractkit/chainlink/v2/core/web/testutils" ) -func Test_TelemetryReporter_NewHead(t *testing.T) { +func Test_EVMTelemetryReporter_NewHead(t *testing.T) { head := evmtypes.Head{ Number: 42, EVMChainID: ubig.NewI(100), @@ -37,17 +47,17 @@ func Test_TelemetryReporter_NewHead(t *testing.T) { requestBytes, err := proto.Marshal(&telem.HeadReportRequest{ ChainID: "100", Latest: &telem.Block{ - Timestamp: uint64(head.Timestamp.UTC().Unix()), + Timestamp: utils2.NonNegativeInt64ToUint64(head.Timestamp.UTC().Unix()), Number: 42, Hash: head.Hash.Hex(), }, Finalized: &telem.Block{ - Timestamp: uint64(head.Parent.Load().Timestamp.UTC().Unix()), + Timestamp: utils2.NonNegativeInt64ToUint64(head.Parent.Load().Timestamp.UTC().Unix()), Number: 41, Hash: head.Parent.Load().Hash.Hex(), }, }) - assert.NoError(t, err) + require.NoError(t, err) monitoringEndpoint := mocks2.NewMonitoringEndpoint(t) monitoringEndpoint.On("SendLog", requestBytes).Return() @@ -56,13 +66,13 @@ func Test_TelemetryReporter_NewHead(t *testing.T) { monitoringEndpointGen. On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). Return(monitoringEndpoint) - reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) + reporter := headreporter.NewLegacyEVMTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) err = reporter.ReportNewHead(testutils.Context(t), &head) assert.NoError(t, err) } -func Test_TelemetryReporter_NewHeadMissingFinalized(t *testing.T) { +func Test_EVMTelemetryReporter_NewHeadMissingFinalized(t *testing.T) { head := evmtypes.Head{ Number: 42, EVMChainID: ubig.NewI(100), @@ -72,12 +82,12 @@ func Test_TelemetryReporter_NewHeadMissingFinalized(t *testing.T) { requestBytes, err := proto.Marshal(&telem.HeadReportRequest{ ChainID: "100", Latest: &telem.Block{ - Timestamp: uint64(head.Timestamp.UTC().Unix()), + Timestamp: utils2.NonNegativeInt64ToUint64(head.Timestamp.UTC().Unix()), Number: 42, Hash: head.Hash.Hex(), }, }) - assert.NoError(t, err) + require.NoError(t, err) monitoringEndpoint := mocks2.NewMonitoringEndpoint(t) monitoringEndpoint.On("SendLog", requestBytes).Return() @@ -86,22 +96,87 @@ func Test_TelemetryReporter_NewHeadMissingFinalized(t *testing.T) { monitoringEndpointGen. On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). Return(monitoringEndpoint) - reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) + reporter := headreporter.NewLegacyEVMTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) err = reporter.ReportNewHead(testutils.Context(t), &head) assert.NoError(t, err) } -func Test_TelemetryReporter_NewHead_MissingEndpoint(t *testing.T) { +func Test_EVMTelemetryReporter_NewHead_MissingEndpoint(t *testing.T) { monitoringEndpointGen := telemetry.NewMockMonitoringEndpointGenerator(t) monitoringEndpointGen. On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). Return(nil) - reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) + reporter := headreporter.NewLegacyEVMTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) head := evmtypes.Head{Number: 42, EVMChainID: ubig.NewI(100)} err := reporter.ReportNewHead(testutils.Context(t), &head) assert.Errorf(t, err, "No monitoring endpoint provided chain_id=100") } + +type mockRelayer struct { + testutils2.MockRelayer + latestHead types.Head +} + +func (m mockRelayer) LatestHead(_ context.Context) (types.Head, error) { + return m.latestHead, nil +} + +func Test_SolanaTelemetryReporter_ReportPeriodic(t *testing.T) { + blockHash := [32]byte(utils.GetRandomPubKey(t)) + + head := types.Head{ + Height: "42", + Hash: blockHash[:], + Timestamp: 1000, + } + relay := mockRelayer{latestHead: head} + solanaRelays := map[types.RelayID]loop.Relayer{ + types.RelayID{Network: "Solana", ChainID: "testchain"}: relay, + } + + request := telem.HeadReportRequest{ + ChainID: "testchain", + Latest: &telem.Block{ + Timestamp: head.Timestamp, + Number: 42, + Hash: hex.EncodeToString(head.Hash), + }, + } + requestBytes, err := proto.Marshal(&request) + require.NoError(t, err) + + monitoringEndpoint := mocks2.NewMonitoringEndpoint(t) + monitoringEndpoint.On("SendLog", requestBytes).Return() + + monitoringEndpointGen := telemetry.NewMockMonitoringEndpointGenerator(t) + monitoringEndpointGen. + On("GenMonitoringEndpoint", "Solana", "testchain", "", synchronization.HeadReport). + Return(monitoringEndpoint) + + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), solanaRelays) + + err = reporter.ReportPeriodic(testutils.Context(t)) + assert.NoError(t, err) +} + +func Test_SolanaTelemetryReporter_ReportPeriodic_MissingEndpoint(t *testing.T) { + monitoringEndpoint := mocks2.NewMonitoringEndpoint(t) + + monitoringEndpointGen := telemetry.NewMockMonitoringEndpointGenerator(t) + monitoringEndpointGen. + On("GenMonitoringEndpoint", "Solana", "testchain", "", synchronization.HeadReport). + Return(monitoringEndpoint) + + solanaRelays := map[types.RelayID]loop.Relayer{ + types.RelayID{Network: "Solana", ChainID: "testchain"}: testutils2.MockRelayer{}, + } + + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), solanaRelays) + + err := reporter.ReportPeriodic(testutils.Context(t)) + assert.Errorf(t, err, "No monitoring endpoint provided chain_id=testchain") +} diff --git a/core/services/job/spawner_test.go b/core/services/job/spawner_test.go index 8129934a57c..68bebcfd2a4 100644 --- a/core/services/job/spawner_test.go +++ b/core/services/job/spawner_test.go @@ -72,8 +72,8 @@ func (g *relayGetter) Get(id types.RelayID) (loop.Relayer, error) { return evmrelayer.NewLOOPRelayAdapter(g.r), nil } -func (g *relayGetter) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { - return map[types.RelayID]loop.Relayer{}, nil +func (g *relayGetter) GetIDToRelayerMap() map[types.RelayID]loop.Relayer { + return map[types.RelayID]loop.Relayer{} } func TestSpawner_CreateJobDeleteJob(t *testing.T) { diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index 9c7f4d8e7b6..bd7dd1b3a39 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -103,7 +103,7 @@ func (e ErrRelayNotEnabled) Error() string { type RelayGetter interface { Get(id types.RelayID) (loop.Relayer, error) - GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) + GetIDToRelayerMap() map[types.RelayID]loop.Relayer } type Delegate struct { ds sqlutil.DataSource diff --git a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go index 1bb660db3dd..dc531fb852c 100644 --- a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go +++ b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go @@ -372,5 +372,5 @@ func UnregisterCommitPluginLpFilters(srcProvider commontypes.CCIPCommitProvider, type RelayGetter interface { Get(id commontypes.RelayID) (loop.Relayer, error) - GetIDToRelayerMap() (map[commontypes.RelayID]loop.Relayer, error) + GetIDToRelayerMap() map[commontypes.RelayID]loop.Relayer } diff --git a/core/services/ocr2/plugins/generic/relayerset.go b/core/services/ocr2/plugins/generic/relayerset.go index 229166dd36a..c0570ea2f78 100644 --- a/core/services/ocr2/plugins/generic/relayerset.go +++ b/core/services/ocr2/plugins/generic/relayerset.go @@ -12,7 +12,7 @@ import ( ) type RelayGetter interface { - GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) + GetIDToRelayerMap() map[types.RelayID]loop.Relayer } type RelayerSet struct { @@ -22,10 +22,7 @@ type RelayerSet struct { func NewRelayerSet(relayGetter RelayGetter, externalJobID uuid.UUID, jobID int32, isNew bool) (*RelayerSet, error) { wrappedRelayers := map[types.RelayID]core.Relayer{} - relayers, err := relayGetter.GetIDToRelayerMap() - if err != nil { - return nil, fmt.Errorf("failed to get relayers: %w", err) - } + relayers := relayGetter.GetIDToRelayerMap() for id, relayer := range relayers { wrappedRelayers[id] = relayerWrapper{Relayer: relayer, ExternalJobID: externalJobID, JobID: jobID, New: isNew} diff --git a/core/services/ocr2/plugins/generic/relayerset_test.go b/core/services/ocr2/plugins/generic/relayerset_test.go index dcd9e7eb484..d338b050f1f 100644 --- a/core/services/ocr2/plugins/generic/relayerset_test.go +++ b/core/services/ocr2/plugins/generic/relayerset_test.go @@ -124,8 +124,8 @@ func (t TestRelayGetter) Get(id types.RelayID) (loop.Relayer, error) { return nil, fmt.Errorf("relayer with id %s not found", id) } -func (t TestRelayGetter) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { - return t.relayers, nil +func (t TestRelayGetter) GetIDToRelayerMap() map[types.RelayID]loop.Relayer { + return t.relayers } type TestRelayer struct { diff --git a/core/services/ocrbootstrap/delegate.go b/core/services/ocrbootstrap/delegate.go index ad3a602d0bb..88d152d7af3 100644 --- a/core/services/ocrbootstrap/delegate.go +++ b/core/services/ocrbootstrap/delegate.go @@ -21,7 +21,7 @@ import ( type RelayGetter interface { Get(types.RelayID) (loop.Relayer, error) - GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) + GetIDToRelayerMap() map[types.RelayID]loop.Relayer } // Delegate creates Bootstrap jobs diff --git a/core/services/standardcapabilities/delegate.go b/core/services/standardcapabilities/delegate.go index aa0097e0336..0cfe5ff6b21 100644 --- a/core/services/standardcapabilities/delegate.go +++ b/core/services/standardcapabilities/delegate.go @@ -33,7 +33,7 @@ import ( type RelayGetter interface { Get(id types.RelayID) (loop.Relayer, error) - GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) + GetIDToRelayerMap() map[types.RelayID]loop.Relayer } type Delegate struct { diff --git a/core/utils/safe_math.go b/core/utils/safe_math.go new file mode 100644 index 00000000000..13cb96fc189 --- /dev/null +++ b/core/utils/safe_math.go @@ -0,0 +1,9 @@ +package utils + +// safely cast int64 to uint64, treating negative values as 0 +func NonNegativeInt64ToUint64(i int64) uint64 { + if i < 0 { + i = 0 + } + return uint64(i) //nolint:gosec // already checked for negative +} diff --git a/core/utils/safe_math_test.go b/core/utils/safe_math_test.go new file mode 100644 index 00000000000..8295c81ff45 --- /dev/null +++ b/core/utils/safe_math_test.go @@ -0,0 +1,26 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +func Test_NonNegativeInt64ToUint64(t *testing.T) { + tests := []struct { + input int64 + expected uint64 + }{ + {input: 42, expected: 42}, + {input: -1, expected: 0}, + {input: 0, expected: 0}, + {input: 9223372036854775807, expected: 9223372036854775807}, // Max int64 + } + + for _, test := range tests { + result := utils.NonNegativeInt64ToUint64(test.input) + assert.Equal(t, test.expected, result) + } +} diff --git a/core/web/loader/chain.go b/core/web/loader/chain.go index 35b38f2c1ec..892109eca31 100644 --- a/core/web/loader/chain.go +++ b/core/web/loader/chain.go @@ -28,10 +28,7 @@ func (b *chainBatcher) loadByIDs(ctx context.Context, keys dataloader.Keys) []*d } var cs []chainlink.NetworkChainStatus - relayersMap, err := b.app.GetRelayers().GetIDToRelayerMap() - if err != nil { - return []*dataloader.Result{{Data: nil, Error: err}} - } + relayersMap := b.app.GetRelayers().GetIDToRelayerMap() for k, v := range relayersMap { s, err := v.GetChainStatus(ctx) diff --git a/core/web/resolver/query.go b/core/web/resolver/query.go index 19ecfa7548b..a4218a104af 100644 --- a/core/web/resolver/query.go +++ b/core/web/resolver/query.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/bridges" @@ -111,10 +112,7 @@ func (r *Resolver) Chains(ctx context.Context, args struct { offset := pageOffset(args.Offset) limit := pageLimit(args.Limit) - relayersMap, err := r.App.GetRelayers().GetIDToRelayerMap() - if err != nil { - return nil, err - } + relayersMap := r.App.GetRelayers().GetIDToRelayerMap() chains := make([]chainlink.NetworkChainStatus, 0, len(relayersMap)) for k, v := range relayersMap {