Skip to content

Commit

Permalink
feat(chain): return network data (#14718)
Browse files Browse the repository at this point in the history
Currently for graphql query chains and chain, they do not support returning network/chain type such as evm, solana, or aptos etc.

For `chain` and `chains` graphql query, this commit add support for returning the network associated with the chain.

This can then be used on the operator UI to show a list of supported chains instead of just hardcoded to EVM.

JIRA: https://smartcontract-it.atlassian.net/browse/DPA-1150

Co-authored-by: Blaž Hrastnik <[email protected]>
Co-authored-by: Giorgio Gambino <[email protected]>
  • Loading branch information
3 people authored Oct 14, 2024
1 parent 44daea3 commit 16499e5
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 75 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-chicken-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

#updated introduce network field on chain resolver
13 changes: 12 additions & 1 deletion common/types/chain.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
5 changes: 3 additions & 2 deletions core/chains/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions core/services/chainlink/mocks/relayer_chain_interoperators.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
25 changes: 18 additions & 7 deletions core/web/loader/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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]
Expand Down
7 changes: 3 additions & 4 deletions core/web/loader/getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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))
Expand All @@ -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
}
Expand Down
28 changes: 21 additions & 7 deletions core/web/loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down
22 changes: 13 additions & 9 deletions core/web/resolver/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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}
Expand All @@ -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}
}

Expand Down
Loading

0 comments on commit 16499e5

Please sign in to comment.