diff --git a/.changeset/five-chicken-talk.md b/.changeset/five-chicken-talk.md new file mode 100644 index 00000000000..5f93b29364b --- /dev/null +++ b/.changeset/five-chicken-talk.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +#updated introduce network field on chain resolver diff --git a/common/types/chain.go b/common/types/chain.go index 800f0d9fdc0..bf4654142a8 100644 --- a/common/types/chain.go +++ b/common/types/chain.go @@ -1,6 +1,10 @@ package types -import "fmt" +import ( + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/types" +) // Sequence represents the base type, for any chain's sequence object. // It should be convertible to a string @@ -12,3 +16,10 @@ type Sequence interface { // ID represents the base type, for any chain's ID. // It should be convertible to a string, that can uniquely identify this chain type ID fmt.Stringer + +// ChainStatusWithID compose of ChainStatus and RelayID. This is useful for +// storing the Network associated with the ChainStatus. +type ChainStatusWithID struct { + types.ChainStatus + types.RelayID +} diff --git a/core/chains/config.go b/core/chains/config.go index 3556c33a785..73e1e87f146 100644 --- a/core/chains/config.go +++ b/core/chains/config.go @@ -6,8 +6,9 @@ import ( var ( // ErrChainIDEmpty is returned when chain is required but was empty. - ErrChainIDEmpty = errors.New("chain id empty") - ErrNotFound = errors.New("not found") + ErrChainIDEmpty = errors.New("chain id empty") + ErrNotFound = errors.New("not found") + ErrMultipleChainFound = errors.New("multiple chains found with the same chain ID") ) // ChainOpts holds options for configuring a Chain diff --git a/core/services/chainlink/mocks/relayer_chain_interoperators.go b/core/services/chainlink/mocks/relayer_chain_interoperators.go index 5b0815b6569..13fef0e3180 100644 --- a/core/services/chainlink/mocks/relayer_chain_interoperators.go +++ b/core/services/chainlink/mocks/relayer_chain_interoperators.go @@ -17,7 +17,7 @@ import ( // FakeRelayerChainInteroperators is a fake chainlink.RelayerChainInteroperators. // This exists because mockery generation doesn't understand how to produce an alias instead of the underlying type (which is not exported in this case). type FakeRelayerChainInteroperators struct { - Relayers []loop.Relayer + Relayers map[types.RelayID]loop.Relayer EVMChains legacyevm.LegacyChainContainer Nodes []types.NodeStatus NodesErr error @@ -44,11 +44,17 @@ func (f *FakeRelayerChainInteroperators) Get(id types.RelayID) (loop.Relayer, er } func (f *FakeRelayerChainInteroperators) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { - panic("unimplemented") + return f.Relayers, nil } func (f *FakeRelayerChainInteroperators) Slice() []loop.Relayer { - return f.Relayers + var relayers []loop.Relayer + + for _, value := range f.Relayers { + relayers = append(relayers, value) + } + + return relayers } func (f *FakeRelayerChainInteroperators) LegacyCosmosChains() chainlink.LegacyCosmosContainer { diff --git a/core/web/loader/chain.go b/core/web/loader/chain.go index 215c643f3c5..54fccf9cf9e 100644 --- a/core/web/loader/chain.go +++ b/core/web/loader/chain.go @@ -5,8 +5,7 @@ import ( "slices" "github.com/graph-gophers/dataloader" - - "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) @@ -25,20 +24,32 @@ func (b *chainBatcher) loadByIDs(ctx context.Context, keys dataloader.Keys) []*d keyOrder[key.String()] = ix } - var cs []types.ChainStatus - relayers := b.app.GetRelayers().Slice() + var cs []types.ChainStatusWithID + relayersMap, err := b.app.GetRelayers().GetIDToRelayerMap() + if err != nil { + return []*dataloader.Result{{Data: nil, Error: err}} + } - for _, r := range relayers { - s, err := r.GetChainStatus(ctx) + for k, v := range relayersMap { + s, err := v.GetChainStatus(ctx) if err != nil { return []*dataloader.Result{{Data: nil, Error: err}} } if slices.Contains(chainIDs, s.ID) { - cs = append(cs, s) + cs = append(cs, types.ChainStatusWithID{ + ChainStatus: s, + RelayID: k, + }) } } + // todo: future improvements to handle multiple chains with same id + if len(cs) > len(keys) { + b.app.GetLogger().Warn("Found multiple chain with same id") + return []*dataloader.Result{{Data: nil, Error: chains.ErrMultipleChainFound}} + } + results := make([]*dataloader.Result, len(keys)) for _, c := range cs { ix, ok := keyOrder[c.ID] diff --git a/core/web/loader/getters.go b/core/web/loader/getters.go index 27a39181ff8..33aba17db36 100644 --- a/core/web/loader/getters.go +++ b/core/web/loader/getters.go @@ -7,8 +7,7 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services/feeds" @@ -21,7 +20,7 @@ import ( var ErrInvalidType = errors.New("invalid type") // GetChainByID fetches the chain by it's id. -func GetChainByID(ctx context.Context, id string) (*commontypes.ChainStatus, error) { +func GetChainByID(ctx context.Context, id string) (*commonTypes.ChainStatusWithID, error) { ldr := For(ctx) thunk := ldr.ChainsByIDLoader.Load(ctx, dataloader.StringKey(id)) @@ -30,7 +29,7 @@ func GetChainByID(ctx context.Context, id string) (*commontypes.ChainStatus, err return nil, err } - chain, ok := result.(commontypes.ChainStatus) + chain, ok := result.(commonTypes.ChainStatusWithID) if !ok { return nil, ErrInvalidType } diff --git a/core/web/loader/loader_test.go b/core/web/loader/loader_test.go index 5e22c9afef6..0e88f67c444 100644 --- a/core/web/loader/loader_test.go +++ b/core/web/loader/loader_test.go @@ -13,11 +13,13 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtxmgrmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" evmutils "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" coremocks "github.com/smartcontractkit/chainlink/v2/core/internal/mocks" @@ -46,12 +48,18 @@ func TestLoader_Chains(t *testing.T) { config2, err := chain2.TOMLString() require.NoError(t, err) - app.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: []loop.Relayer{ - testutils2.MockRelayer{ChainStatus: commontypes.ChainStatus{ + app.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[commontypes.RelayID]loop.Relayer{ + commontypes.RelayID{ + Network: relay.NetworkEVM, + ChainID: "1", + }: testutils2.MockRelayer{ChainStatus: commontypes.ChainStatus{ ID: "1", Enabled: true, Config: config1, - }}, testutils2.MockRelayer{ChainStatus: commontypes.ChainStatus{ + }}, commontypes.RelayID{ + Network: relay.NetworkEVM, + ChainID: "2", + }: testutils2.MockRelayer{ChainStatus: commontypes.ChainStatus{ ID: "2", Enabled: true, Config: config2, @@ -65,11 +73,17 @@ func TestLoader_Chains(t *testing.T) { assert.Len(t, results, 3) require.NoError(t, err) - want2 := commontypes.ChainStatus{ID: "2", Enabled: true, Config: config2} - assert.Equal(t, want2, results[0].Data.(commontypes.ChainStatus)) + want2 := types.ChainStatusWithID{ + ChainStatus: commontypes.ChainStatus{ID: "2", Enabled: true, Config: config2}, + RelayID: commontypes.RelayID{Network: relay.NetworkEVM, ChainID: "2"}, + } + assert.Equal(t, want2, results[0].Data.(types.ChainStatusWithID)) - want1 := commontypes.ChainStatus{ID: "1", Enabled: true, Config: config1} - assert.Equal(t, want1, results[1].Data.(commontypes.ChainStatus)) + want1 := types.ChainStatusWithID{ + ChainStatus: commontypes.ChainStatus{ID: "1", Enabled: true, Config: config1}, + RelayID: commontypes.RelayID{Network: relay.NetworkEVM, ChainID: "1"}, + } + assert.Equal(t, want1, results[1].Data.(types.ChainStatusWithID)) assert.Nil(t, results[2].Data) assert.Error(t, results[2].Error) assert.ErrorIs(t, results[2].Error, chains.ErrNotFound) diff --git a/core/web/resolver/chain.go b/core/web/resolver/chain.go index 32e9a8caac3..22dfcf39e92 100644 --- a/core/web/resolver/chain.go +++ b/core/web/resolver/chain.go @@ -2,20 +2,19 @@ package resolver import ( "github.com/graph-gophers/graphql-go" - - "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/common/types" ) // ChainResolver resolves the Chain type. type ChainResolver struct { - chain types.ChainStatus + chain types.ChainStatusWithID } -func NewChain(chain types.ChainStatus) *ChainResolver { +func NewChain(chain types.ChainStatusWithID) *ChainResolver { return &ChainResolver{chain: chain} } -func NewChains(chains []types.ChainStatus) []*ChainResolver { +func NewChains(chains []types.ChainStatusWithID) []*ChainResolver { var resolvers []*ChainResolver for _, c := range chains { resolvers = append(resolvers, NewChain(c)) @@ -39,12 +38,17 @@ func (r *ChainResolver) Config() string { return r.chain.Config } +// Network resolves the chain's network field +func (r *ChainResolver) Network() string { + return r.chain.Network +} + type ChainPayloadResolver struct { - chain types.ChainStatus + chain types.ChainStatusWithID NotFoundErrorUnionType } -func NewChainPayload(chain types.ChainStatus, err error) *ChainPayloadResolver { +func NewChainPayload(chain types.ChainStatusWithID, err error) *ChainPayloadResolver { e := NotFoundErrorUnionType{err: err, message: "chain not found", isExpectedErrorFn: nil} return &ChainPayloadResolver{chain: chain, NotFoundErrorUnionType: e} @@ -59,11 +63,11 @@ func (r *ChainPayloadResolver) ToChain() (*ChainResolver, bool) { } type ChainsPayloadResolver struct { - chains []types.ChainStatus + chains []types.ChainStatusWithID total int32 } -func NewChainsPayload(chains []types.ChainStatus, total int32) *ChainsPayloadResolver { +func NewChainsPayload(chains []types.ChainStatusWithID, total int32) *ChainsPayloadResolver { return &ChainsPayloadResolver{chains: chains, total: total} } diff --git a/core/web/resolver/chain_test.go b/core/web/resolver/chain_test.go index 75d7e36a5b5..fed817df456 100644 --- a/core/web/resolver/chain_test.go +++ b/core/web/resolver/chain_test.go @@ -3,9 +3,11 @@ package resolver import ( "context" "encoding/json" + "errors" "fmt" "testing" + gqlerrors "github.com/graph-gophers/graphql-go/errors" "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/require" @@ -15,6 +17,7 @@ import ( evmtoml "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" chainlinkmocks "github.com/smartcontractkit/chainlink/v2/core/services/chainlink/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/web/testutils" ) @@ -28,6 +31,7 @@ func TestResolver_Chains(t *testing.T) { id enabled config + network } metadata { total @@ -87,8 +91,11 @@ ResendAfterThreshold = '1h0m0s' chainConfToml, err2 := chainConf.TOMLString() require.NoError(t, err2) - f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: []loop.Relayer{ - testutils.MockRelayer{ChainStatus: commontypes.ChainStatus{ + f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[commontypes.RelayID]loop.Relayer{ + commontypes.RelayID{ + Network: relay.NetworkEVM, + ChainID: chainID.String(), + }: testutils.MockRelayer{ChainStatus: commontypes.ChainStatus{ ID: chainID.String(), Enabled: *chain.Enabled, Config: chainConfToml, @@ -102,7 +109,8 @@ ResendAfterThreshold = '1h0m0s' "results": [{ "id": "1", "enabled": true, - "config": %s + "config": %s, + "network": "evm" }], "metadata": { "total": 1 @@ -115,7 +123,7 @@ ResendAfterThreshold = '1h0m0s' name: "no chains", authenticated: true, before: func(ctx context.Context, f *gqlTestFramework) { - f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: []loop.Relayer{}}) + f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[commontypes.RelayID]loop.Relayer{}}) }, query: query, result: ` @@ -143,6 +151,7 @@ func TestResolver_Chain(t *testing.T) { id enabled config + network } ... on NotFoundError { code @@ -188,17 +197,31 @@ ResendAfterThreshold = '1h0m0s' configTOMLEscaped, err := json.Marshal(configTOML) require.NoError(t, err) + multipleChainError := errors.New("multiple chains found with the same chain ID") testCases := []GQLTestCase{ unauthorizedTestCase(GQLTestCase{query: query}, "chain"), { name: "success", authenticated: true, before: func(ctx context.Context, f *gqlTestFramework) { - f.App.On("EVMORM").Return(f.Mocks.evmORM) - f.Mocks.evmORM.PutChains(evmtoml.EVMConfig{ - ChainID: &chainID, + chainConf := evmtoml.EVMConfig{ Chain: chain, - }) + ChainID: &chainID, + } + + chainConfToml, err2 := chainConf.TOMLString() + require.NoError(t, err2) + + f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[commontypes.RelayID]loop.Relayer{ + commontypes.RelayID{ + Network: relay.NetworkEVM, + ChainID: chainID.String(), + }: testutils.MockRelayer{ChainStatus: commontypes.ChainStatus{ + ID: chainID.String(), + Enabled: chainConf.IsEnabled(), + Config: chainConfToml, + }}, + }}) }, query: query, result: fmt.Sprintf(` @@ -206,7 +229,8 @@ ResendAfterThreshold = '1h0m0s' "chain": { "id": "1", "enabled": true, - "config": %s + "config": %s, + "network": "evm" } }`, configTOMLEscaped), }, @@ -214,7 +238,7 @@ ResendAfterThreshold = '1h0m0s' name: "not found error", authenticated: true, before: func(ctx context.Context, f *gqlTestFramework) { - f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[commontypes.RelayID]loop.Relayer{}}) }, query: query, result: ` @@ -225,6 +249,48 @@ ResendAfterThreshold = '1h0m0s' } }`, }, + { + name: "multiple chain with same chainID found error", + authenticated: true, + before: func(ctx context.Context, f *gqlTestFramework) { + chainConf := evmtoml.EVMConfig{ + Chain: chain, + ChainID: &chainID, + } + + chainConfToml, err2 := chainConf.TOMLString() + require.NoError(t, err2) + + f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[commontypes.RelayID]loop.Relayer{ + commontypes.RelayID{ + Network: relay.NetworkEVM, + ChainID: chainID.String(), + }: testutils.MockRelayer{ChainStatus: commontypes.ChainStatus{ + ID: chainID.String(), + Enabled: chainConf.IsEnabled(), + Config: chainConfToml, + }}, + commontypes.RelayID{ + Network: relay.NetworkAptos, + ChainID: chainID.String(), + }: testutils.MockRelayer{ChainStatus: commontypes.ChainStatus{ + ID: chainID.String(), + Enabled: chainConf.IsEnabled(), + Config: chainConfToml, + }}, + }}) + }, + query: query, + result: "null", + errors: []*gqlerrors.QueryError{ + { + Extensions: nil, + ResolverError: multipleChainError, + Path: []interface{}{"chain"}, + Message: multipleChainError.Error(), + }, + }, + }, } RunGQLTests(t, testCases) diff --git a/core/web/resolver/eth_key_test.go b/core/web/resolver/eth_key_test.go index 55cdc230bd2..9002f17fce6 100644 --- a/core/web/resolver/eth_key_test.go +++ b/core/web/resolver/eth_key_test.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/web/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" @@ -107,8 +108,11 @@ func TestResolver_ETHKeys(t *testing.T) { f.Mocks.chain.On("BalanceMonitor").Return(f.Mocks.balM) f.Mocks.chain.On("Config").Return(f.Mocks.scfg) f.Mocks.relayerChainInterops.EVMChains = legacyEVMChains - f.Mocks.relayerChainInterops.Relayers = []loop.Relayer{ - testutils.MockRelayer{ + f.Mocks.relayerChainInterops.Relayers = map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "12", + }: testutils.MockRelayer{ ChainStatus: types.ChainStatus{ ID: "12", Enabled: true, @@ -165,8 +169,11 @@ func TestResolver_ETHKeys(t *testing.T) { f.Mocks.ethKs.On("GetAll", mock.Anything).Return(keys, nil) f.Mocks.relayerChainInterops.EVMChains = f.Mocks.legacyEVMChains f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) - f.Mocks.relayerChainInterops.Relayers = []loop.Relayer{ - testutils.MockRelayer{ + f.Mocks.relayerChainInterops.Relayers = map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "12", + }: testutils.MockRelayer{ ChainStatus: types.ChainStatus{ ID: "12", Enabled: true, @@ -325,8 +332,11 @@ func TestResolver_ETHKeys(t *testing.T) { f.Mocks.ethClient.On("LINKBalance", mock.Anything, address, linkAddr).Return(commonassets.NewLinkFromJuels(12), gError) f.Mocks.legacyEVMChains.On("Get", states[0].EVMChainID.String()).Return(f.Mocks.chain, nil) f.Mocks.relayerChainInterops.EVMChains = f.Mocks.legacyEVMChains - f.Mocks.relayerChainInterops.Relayers = []loop.Relayer{ - testutils.MockRelayer{ + f.Mocks.relayerChainInterops.Relayers = map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "12", + }: testutils.MockRelayer{ ChainStatus: types.ChainStatus{ ID: "12", Enabled: true, @@ -390,8 +400,11 @@ func TestResolver_ETHKeys(t *testing.T) { f.Mocks.legacyEVMChains.On("Get", states[0].EVMChainID.String()).Return(f.Mocks.chain, nil) f.Mocks.relayerChainInterops.EVMChains = f.Mocks.legacyEVMChains f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) - f.Mocks.relayerChainInterops.Relayers = []loop.Relayer{ - testutils.MockRelayer{ + f.Mocks.relayerChainInterops.Relayers = map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "12", + }: testutils.MockRelayer{ ChainStatus: types.ChainStatus{ ID: "12", Enabled: true, diff --git a/core/web/resolver/eth_transaction_test.go b/core/web/resolver/eth_transaction_test.go index 690eea30b50..5844c50fde3 100644 --- a/core/web/resolver/eth_transaction_test.go +++ b/core/web/resolver/eth_transaction_test.go @@ -22,6 +22,7 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" chainlinkmocks "github.com/smartcontractkit/chainlink/v2/core/services/chainlink/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/web/testutils" ) @@ -93,8 +94,11 @@ func TestResolver_EthTransaction(t *testing.T) { f.App.On("TxmStorageService").Return(f.Mocks.txmStore) f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{ - Relayers: []loop.Relayer{ - testutils.MockRelayer{ChainStatus: types.ChainStatus{ + Relayers: map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "22", + }: testutils.MockRelayer{ChainStatus: types.ChainStatus{ ID: "22", Enabled: true, Config: "", @@ -158,8 +162,11 @@ func TestResolver_EthTransaction(t *testing.T) { f.App.On("TxmStorageService").Return(f.Mocks.txmStore) f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{ - Relayers: []loop.Relayer{ - testutils.MockRelayer{ChainStatus: types.ChainStatus{ + Relayers: map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "22", + }: testutils.MockRelayer{ChainStatus: types.ChainStatus{ ID: "22", Enabled: true, Config: "", diff --git a/core/web/resolver/node_test.go b/core/web/resolver/node_test.go index 870f694990f..735c2cb7867 100644 --- a/core/web/resolver/node_test.go +++ b/core/web/resolver/node_test.go @@ -10,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/types" chainlinkmocks "github.com/smartcontractkit/chainlink/v2/core/services/chainlink/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/web/testutils" ) @@ -50,8 +51,11 @@ func TestResolver_Nodes(t *testing.T) { State: "alive", }, }, - Relayers: []loop.Relayer{ - testutils.MockRelayer{ChainStatus: types.ChainStatus{ + Relayers: map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "1", + }: testutils.MockRelayer{ChainStatus: types.ChainStatus{ ID: "1", Enabled: true, Config: "", @@ -124,8 +128,11 @@ func Test_NodeQuery(t *testing.T) { name: "success", authenticated: true, before: func(ctx context.Context, f *gqlTestFramework) { - f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: []loop.Relayer{ - testutils.MockRelayer{NodeStatuses: []types.NodeStatus{ + f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[types.RelayID]loop.Relayer{ + types.RelayID{ + Network: relay.NetworkEVM, + ChainID: "1", + }: testutils.MockRelayer{NodeStatuses: []types.NodeStatus{ { Name: "node-name", Config: "Name='node-name'\nOrder=11\nHTTPURL='http://some-url'\nWSURL='ws://some-url'", @@ -148,7 +155,7 @@ func Test_NodeQuery(t *testing.T) { name: "not found error", authenticated: true, before: func(ctx context.Context, f *gqlTestFramework) { - f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: []loop.Relayer{}}) + f.App.On("GetRelayers").Return(&chainlinkmocks.FakeRelayerChainInteroperators{Relayers: map[types.RelayID]loop.Relayer{}}) }, query: query, result: ` diff --git a/core/web/resolver/query.go b/core/web/resolver/query.go index b44ac751db9..c8f20d81dc8 100644 --- a/core/web/resolver/query.go +++ b/core/web/resolver/query.go @@ -10,13 +10,14 @@ import ( "github.com/graph-gophers/graphql-go" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-common/pkg/types" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/utils/stringutils" + "github.com/smartcontractkit/chainlink/v2/core/web/loader" ) // Bridge retrieves a bridges by name. @@ -68,18 +69,15 @@ func (r *Resolver) Chain(ctx context.Context, args struct{ ID graphql.ID }) (*Ch return nil, err } - cs, _, err := r.App.EVMORM().Chains(string(args.ID)) + id, err := loader.GetChainByID(ctx, string(args.ID)) if err != nil { + if errors.Is(err, chains.ErrNotFound) { + return NewChainPayload(commonTypes.ChainStatusWithID{}, chains.ErrNotFound), nil + } return nil, err } - l := len(cs) - if l == 0 { - return NewChainPayload(types.ChainStatus{}, chains.ErrNotFound), nil - } - if l > 1 { - return nil, fmt.Errorf("multiple chains found: %d", len(cs)) - } - return NewChainPayload(cs[0], nil), nil + + return NewChainPayload(*id, nil), nil } // Chains retrieves a paginated list of chains. @@ -94,16 +92,25 @@ func (r *Resolver) Chains(ctx context.Context, args struct { offset := pageOffset(args.Offset) limit := pageLimit(args.Limit) - var chains []types.ChainStatus - for _, rel := range r.App.GetRelayers().Slice() { - status, err := rel.GetChainStatus(ctx) + var chains []commonTypes.ChainStatusWithID + relayersMap, err := r.App.GetRelayers().GetIDToRelayerMap() + if err != nil { + return nil, err + } + + for k, v := range relayersMap { + s, err := v.GetChainStatus(ctx) if err != nil { return nil, err } - chains = append(chains, status) + + chains = append(chains, commonTypes.ChainStatusWithID{ + ChainStatus: s, + RelayID: k, + }) } - count := len(chains) + count := len(chains) if count == 0 { //No chains are configured, return an empty ChainsPayload, so we don't break the UI return NewChainsPayload(nil, 0), nil @@ -118,9 +125,19 @@ func (r *Resolver) Chains(ctx context.Context, args struct { end = offset + limit } + sortByNetworkAndID(chains) return NewChainsPayload(chains[offset:end], int32(count)), nil } +func sortByNetworkAndID(chains []commonTypes.ChainStatusWithID) { + sort.SliceStable(chains, func(i, j int) bool { + if chains[i].Network == chains[j].Network { + return chains[i].ID < chains[j].ID + } + return chains[i].Network < chains[j].Network + }) +} + // FeedsManager retrieves a feeds manager by id. func (r *Resolver) FeedsManager(ctx context.Context, args struct{ ID graphql.ID }) (*FeedsManagerPayloadResolver, error) { if err := authenticateUser(ctx); err != nil { diff --git a/core/web/schema/type/chain.graphql b/core/web/schema/type/chain.graphql index dcfac1f41d9..1bacaee4a48 100644 --- a/core/web/schema/type/chain.graphql +++ b/core/web/schema/type/chain.graphql @@ -2,6 +2,7 @@ type Chain { id: ID! enabled: Boolean! config: String! + network: String! } union ChainPayload = Chain | NotFoundError diff --git a/integration-tests/web/sdk/internal/schema.graphql b/integration-tests/web/sdk/internal/schema.graphql index 2dabf2c774c..71aa67598da 100644 --- a/integration-tests/web/sdk/internal/schema.graphql +++ b/integration-tests/web/sdk/internal/schema.graphql @@ -182,6 +182,7 @@ type Chain { id: ID! enabled: Boolean! config: String! + network: String! } union ChainPayload = Chain | NotFoundError