diff --git a/core/cmd/app.go b/core/cmd/app.go index ad944f0d0a6..8128d578238 100644 --- a/core/cmd/app.go +++ b/core/cmd/app.go @@ -290,25 +290,14 @@ func NewApp(s *Shell) *cli.App { }, }, { - Name: "chains", - Usage: "Commands for handling chain configuration", - Subcommands: cli.Commands{ - chainCommand("EVM", EVMChainClient(s), cli.Int64Flag{Name: "id", Usage: "chain ID"}), - chainCommand("Cosmos", CosmosChainClient(s), cli.StringFlag{Name: "id", Usage: "chain ID"}), - chainCommand("Solana", SolanaChainClient(s), - cli.StringFlag{Name: "id", Usage: "chain ID, options: [mainnet, testnet, devnet, localnet]"}), - chainCommand("StarkNet", StarkNetChainClient(s), cli.StringFlag{Name: "id", Usage: "chain ID"}), - }, + Name: "chains", + Usage: "Commands for handling chain configuration", + Subcommands: initChainSubCmds(s), }, { - Name: "nodes", - Usage: "Commands for handling node configuration", - Subcommands: cli.Commands{ - initEVMNodeSubCmd(s), - initCosmosNodeSubCmd(s), - initSolanaNodeSubCmd(s), - initStarkNetNodeSubCmd(s), - }, + Name: "nodes", + Usage: "Commands for handling node configuration", + Subcommands: initNodeSubCmds(s), }, { Name: "forwarders", diff --git a/core/cmd/chains_commands.go b/core/cmd/chains_commands.go index 6edb5afc5ba..ffbff8ffdd3 100644 --- a/core/cmd/chains_commands.go +++ b/core/cmd/chains_commands.go @@ -2,9 +2,15 @@ package cmd import ( "fmt" + "maps" + "slices" + "strconv" "strings" "github.com/urfave/cli" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" ) var chainHeaders = []string{"ID", "Enabled", "Config"} @@ -39,12 +45,12 @@ type chainClient[P TableRenderer] struct { path string } -// newChainClient returns a new ChainClient for a particular type of chains.Config. +// NewChainClient returns a new ChainClient for a particular type of chains.Config. // P is a TableRenderer corresponding to R, and P2 is the slice variant (type P2 []P). -func newChainClient[P TableRenderer](s *Shell, name string) ChainClient { - return &chainClient[P]{ +func NewChainClient(s *Shell, network string) ChainClient { + return &chainClient[ChainPresenters]{ Shell: s, - path: "/v2/chains/" + name, + path: "/v2/chains/" + network, } } @@ -53,3 +59,51 @@ func (cli *chainClient[P]) IndexChains(c *cli.Context) (err error) { var p P return cli.getPage(cli.path, c.Int("page"), &p) } + +// ChainPresenter implements TableRenderer for a ChainResource +type ChainPresenter struct { + presenters.ChainResource +} + +// ToRow presents the ChainResource as a slice of strings. +func (p *ChainPresenter) ToRow() []string { + return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} +} + +// RenderTable implements TableRenderer +// Just renders a single row +func (p ChainPresenter) RenderTable(rt RendererTable) error { + rows := [][]string{} + rows = append(rows, p.ToRow()) + + renderList(chainHeaders, rows, rt.Writer) + + return nil +} + +// ChainPresenters implements TableRenderer for a slice of ChainPresenters. +type ChainPresenters []ChainPresenter + +// RenderTable implements TableRenderer +func (ps ChainPresenters) RenderTable(rt RendererTable) error { + rows := [][]string{} + + for _, p := range ps { + rows = append(rows, p.ToRow()) + } + + renderList(chainHeaders, rows, rt.Writer) + + return nil +} + +func initChainSubCmds(s *Shell) []cli.Command { + cmds := []cli.Command{} + for _, network := range slices.Sorted(maps.Keys(relay.SupportedNetworks)) { + if network == relay.NetworkDummy { + continue + } + cmds = append(cmds, chainCommand(network, NewChainClient(s, network), cli.StringFlag{Name: "id", Usage: "chain ID"})) + } + return cmds +} diff --git a/core/cmd/chains_commands_test.go b/core/cmd/chains_commands_test.go new file mode 100644 index 00000000000..d2c8a2e4744 --- /dev/null +++ b/core/cmd/chains_commands_test.go @@ -0,0 +1,81 @@ +package cmd_test + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + + client2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/cmd" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" +) + +func TestShell_IndexCosmosChains(t *testing.T) { + t.Parallel() + + chainID := cosmostest.RandomChainID() + chain := coscfg.TOMLConfig{ + ChainID: ptr(chainID), + Enabled: ptr(true), + } + app := cosmosStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewChainClient(client, "cosmos").IndexChains(cltest.EmptyCLIContext())) + chains := *r.Renders[0].(*cmd.ChainPresenters) + require.Len(t, chains, 1) + c := chains[0] + assert.Equal(t, chainID, c.ID) + assertTableRenders(t, r) +} + +func newRandChainID() *big.Big { + return big.New(testutils.NewRandomEVMChainID()) +} + +func TestShell_IndexEVMChains(t *testing.T) { + t.Parallel() + + app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.EVM[0].Enabled = ptr(true) + c.EVM[0].NonceAutoSync = ptr(false) + c.EVM[0].BalanceMonitor.Enabled = ptr(false) + }) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewChainClient(client, "evm").IndexChains(cltest.EmptyCLIContext())) + chains := *r.Renders[0].(*cmd.ChainPresenters) + require.Len(t, chains, 1) + c := chains[0] + assert.Equal(t, strconv.Itoa(client2.NullClientChainID), c.ID) + assertTableRenders(t, r) +} + +func TestShell_IndexSolanaChains(t *testing.T) { + t.Parallel() + + id := solanatest.RandomChainID() + cfg := solcfg.TOMLConfig{ + ChainID: &id, + Enabled: ptr(true), + } + app := solanaStartNewApplication(t, &cfg) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewChainClient(client, "solana").IndexChains(cltest.EmptyCLIContext())) + chains := *r.Renders[0].(*cmd.ChainPresenters) + require.Len(t, chains, 1) + c := chains[0] + assert.Equal(t, id, c.ID) + assertTableRenders(t, r) +} diff --git a/core/cmd/cosmos_chains_commands.go b/core/cmd/cosmos_chains_commands.go deleted file mode 100644 index d58b1baa159..00000000000 --- a/core/cmd/cosmos_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// CosmosChainPresenter implements TableRenderer for a CosmosChainResource -type CosmosChainPresenter struct { - presenters.CosmosChainResource -} - -// ToRow presents the CosmosChainResource as a slice of strings. -func (p *CosmosChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p CosmosChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// CosmosChainPresenters implements TableRenderer for a slice of CosmosChainPresenters. -type CosmosChainPresenters []CosmosChainPresenter - -// RenderTable implements TableRenderer -func (ps CosmosChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func CosmosChainClient(s *Shell) ChainClient { - return newChainClient[CosmosChainPresenters](s, "cosmos") -} diff --git a/core/cmd/cosmos_chains_commands_test.go b/core/cmd/cosmos_chains_commands_test.go deleted file mode 100644 index a0d2052d836..00000000000 --- a/core/cmd/cosmos_chains_commands_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package cmd_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" -) - -func TestShell_IndexCosmosChains(t *testing.T) { - t.Parallel() - - chainID := cosmostest.RandomChainID() - chain := coscfg.TOMLConfig{ - ChainID: ptr(chainID), - Enabled: ptr(true), - } - app := cosmosStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.CosmosChainClient(client).IndexChains(cltest.EmptyCLIContext())) - chains := *r.Renders[0].(*cmd.CosmosChainPresenters) - require.Len(t, chains, 1) - c := chains[0] - assert.Equal(t, chainID, c.ID) - assertTableRenders(t, r) -} diff --git a/core/cmd/cosmos_node_commands.go b/core/cmd/cosmos_node_commands.go deleted file mode 100644 index 760691d9379..00000000000 --- a/core/cmd/cosmos_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// CosmosNodePresenter implements TableRenderer for a CosmosNodeResource. -type CosmosNodePresenter struct { - presenters.CosmosNodeResource -} - -// ToRow presents the CosmosNodeResource as a slice of strings. -func (p *CosmosNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p CosmosNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// CosmosNodePresenters implements TableRenderer for a slice of CosmosNodePresenter. -type CosmosNodePresenters []CosmosNodePresenter - -// RenderTable implements TableRenderer -func (ps CosmosNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewCosmosNodeClient(s *Shell) NodeClient { - return newNodeClient[CosmosNodePresenters](s, "cosmos") -} diff --git a/core/cmd/cosmos_node_commands_test.go b/core/cmd/cosmos_node_commands_test.go deleted file mode 100644 index f6fa367440c..00000000000 --- a/core/cmd/cosmos_node_commands_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/config" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func cosmosStartNewApplication(t *testing.T, cfgs ...*coscfg.TOMLConfig) *cltest.TestApplication { - for i := range cfgs { - cfgs[i].SetDefaults() - } - return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Cosmos = cfgs - c.EVM = nil - }) -} - -func TestShell_IndexCosmosNodes(t *testing.T) { - t.Parallel() - - chainID := cosmostest.RandomChainID() - node := coscfg.Node{ - Name: ptr("second"), - TendermintURL: config.MustParseURL("http://tender.mint.test/bombay-12"), - } - chain := coscfg.TOMLConfig{ - ChainID: ptr(chainID), - Enabled: ptr(true), - Nodes: coscfg.Nodes{&node}, - } - app := cosmosStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - require.Nil(t, cmd.NewCosmosNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.CosmosNodePresenters) - require.Len(t, nodes, 1) - n := nodes[0] - assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID, *node.Name), n.ID) - assert.Equal(t, chainID, n.ChainID) - assert.Equal(t, *node.Name, n.Name) - wantConfig, err := toml.Marshal(node) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 10, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n.State) -} diff --git a/core/cmd/evm_chains_commands.go b/core/cmd/evm_chains_commands.go deleted file mode 100644 index d4025cfca53..00000000000 --- a/core/cmd/evm_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// EVMChainPresenter implements TableRenderer for an EVMChainResource. -type EVMChainPresenter struct { - presenters.EVMChainResource -} - -// ToRow presents the EVMChainResource as a slice of strings. -func (p *EVMChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p EVMChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// EVMChainPresenters implements TableRenderer for a slice of EVMChainPresenters. -type EVMChainPresenters []EVMChainPresenter - -// RenderTable implements TableRenderer -func (ps EVMChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func EVMChainClient(s *Shell) ChainClient { - return newChainClient[EVMChainPresenters](s, "evm") -} diff --git a/core/cmd/evm_chains_commands_test.go b/core/cmd/evm_chains_commands_test.go deleted file mode 100644 index fa6d7bb519c..00000000000 --- a/core/cmd/evm_chains_commands_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package cmd_test - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - client2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func newRandChainID() *big.Big { - return big.New(testutils.NewRandomEVMChainID()) -} - -func TestShell_IndexEVMChains(t *testing.T) { - t.Parallel() - - app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].Enabled = ptr(true) - c.EVM[0].NonceAutoSync = ptr(false) - c.EVM[0].BalanceMonitor.Enabled = ptr(false) - }) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.EVMChainClient(client).IndexChains(cltest.EmptyCLIContext())) - chains := *r.Renders[0].(*cmd.EVMChainPresenters) - require.Len(t, chains, 1) - c := chains[0] - assert.Equal(t, strconv.Itoa(client2.NullClientChainID), c.ID) - assertTableRenders(t, r) -} diff --git a/core/cmd/evm_node_commands.go b/core/cmd/evm_node_commands.go deleted file mode 100644 index 515ece18a8e..00000000000 --- a/core/cmd/evm_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// EVMNodePresenter implements TableRenderer for an EVMNodeResource. -type EVMNodePresenter struct { - presenters.EVMNodeResource -} - -// ToRow presents the EVMNodeResource as a slice of strings. -func (p *EVMNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p EVMNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// EVMNodePresenters implements TableRenderer for a slice of EVMNodePresenter. -type EVMNodePresenters []EVMNodePresenter - -// RenderTable implements TableRenderer -func (ps EVMNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewEVMNodeClient(s *Shell) NodeClient { - return newNodeClient[EVMNodePresenters](s, "evm") -} diff --git a/core/cmd/evm_node_commands_test.go b/core/cmd/evm_node_commands_test.go deleted file mode 100644 index d743ee28e1b..00000000000 --- a/core/cmd/evm_node_commands_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func assertTableRenders(t *testing.T, r *cltest.RendererMock) { - // Should be no error rendering any of the responses as tables - b := bytes.NewBuffer([]byte{}) - tb := cmd.RendererTable{b} - for _, rn := range r.Renders { - require.NoError(t, tb.Render(rn)) - } -} - -func TestShell_IndexEVMNodes(t *testing.T) { - t.Parallel() - - chainID := newRandChainID() - node1 := evmcfg.Node{ - Name: ptr("Test node 1"), - WSURL: commonconfig.MustParseURL("ws://localhost:8546"), - HTTPURL: commonconfig.MustParseURL("http://localhost:8546"), - SendOnly: ptr(false), - Order: ptr(int32(15)), - } - node2 := evmcfg.Node{ - Name: ptr("Test node 2"), - WSURL: commonconfig.MustParseURL("ws://localhost:8547"), - HTTPURL: commonconfig.MustParseURL("http://localhost:8547"), - SendOnly: ptr(false), - Order: ptr(int32(36)), - } - chain := evmcfg.EVMConfig{ - ChainID: chainID, - Chain: evmcfg.Defaults(chainID), - Nodes: evmcfg.EVMNodes{&node1, &node2}, - } - app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM = evmcfg.EVMConfigs{&chain} - }) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.NewEVMNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.EVMNodePresenters) - require.Len(t, nodes, 2) - n1 := nodes[0] - n2 := nodes[1] - assert.Equal(t, chainID.String(), n1.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node1.Name), n1.ID) - assert.Equal(t, *node1.Name, n1.Name) - wantConfig, err := toml.Marshal(node1) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n1.Config) - assert.Equal(t, chainID.String(), n2.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node2.Name), n2.ID) - assert.Equal(t, *node2.Name, n2.Name) - wantConfig2, err := toml.Marshal(node2) - require.NoError(t, err) - assert.Equal(t, string(wantConfig2), n2.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 23, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n1.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n1.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n1.State) - assert.Contains(t, renderLines[12], "Name") - assert.Contains(t, renderLines[12], n2.Name) - assert.Contains(t, renderLines[13], "Chain ID") - assert.Contains(t, renderLines[13], n2.ChainID) - assert.Contains(t, renderLines[14], "State") - assert.Contains(t, renderLines[14], n2.State) -} diff --git a/core/cmd/node_commands_test.go b/core/cmd/node_commands_test.go new file mode 100644 index 00000000000..8a17bad719e --- /dev/null +++ b/core/cmd/node_commands_test.go @@ -0,0 +1,290 @@ +package cmd_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + starkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" + evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + + "github.com/smartcontractkit/chainlink/v2/core/cmd" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" +) + +func assertTableRenders(t *testing.T, r *cltest.RendererMock) { + // Should be no error rendering any of the responses as tables + b := bytes.NewBuffer([]byte{}) + tb := cmd.RendererTable{b} + for _, rn := range r.Renders { + require.NoError(t, tb.Render(rn)) + } +} + +func TestShell_IndexEVMNodes(t *testing.T) { + t.Parallel() + + chainID := newRandChainID() + node1 := evmcfg.Node{ + Name: ptr("Test node 1"), + WSURL: config.MustParseURL("ws://localhost:8546"), + HTTPURL: config.MustParseURL("http://localhost:8546"), + SendOnly: ptr(false), + Order: ptr(int32(15)), + } + node2 := evmcfg.Node{ + Name: ptr("Test node 2"), + WSURL: config.MustParseURL("ws://localhost:8547"), + HTTPURL: config.MustParseURL("http://localhost:8547"), + SendOnly: ptr(false), + Order: ptr(int32(36)), + } + chain := evmcfg.EVMConfig{ + ChainID: chainID, + Chain: evmcfg.Defaults(chainID), + Nodes: evmcfg.EVMNodes{&node1, &node2}, + } + app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.EVM = evmcfg.EVMConfigs{&chain} + }) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewNodeClient(client, "evm").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 2) + n1 := nodes[0] + n2 := nodes[1] + assert.Equal(t, chainID.String(), n1.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node1.Name), n1.ID) + assert.Equal(t, *node1.Name, n1.Name) + wantConfig, err := toml.Marshal(node1) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n1.Config) + assert.Equal(t, chainID.String(), n2.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node2.Name), n2.ID) + assert.Equal(t, *node2.Name, n2.Name) + wantConfig2, err := toml.Marshal(node2) + require.NoError(t, err) + assert.Equal(t, string(wantConfig2), n2.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 23) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n1.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n1.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n1.State) + assert.Contains(t, renderLines[12], "Name") + assert.Contains(t, renderLines[12], n2.Name) + assert.Contains(t, renderLines[13], "Chain ID") + assert.Contains(t, renderLines[13], n2.ChainID) + assert.Contains(t, renderLines[14], "State") + assert.Contains(t, renderLines[14], n2.State) +} + +func cosmosStartNewApplication(t *testing.T, cfgs ...*coscfg.TOMLConfig) *cltest.TestApplication { + for i := range cfgs { + cfgs[i].SetDefaults() + } + return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Cosmos = cfgs + c.EVM = nil + }) +} + +func TestShell_IndexCosmosNodes(t *testing.T) { + t.Parallel() + + chainID := cosmostest.RandomChainID() + node := coscfg.Node{ + Name: ptr("second"), + TendermintURL: config.MustParseURL("http://tender.mint.test/bombay-12"), + } + chain := coscfg.TOMLConfig{ + ChainID: ptr(chainID), + Enabled: ptr(true), + Nodes: coscfg.Nodes{&node}, + } + app := cosmosStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + require.NoError(t, cmd.NewNodeClient(client, "cosmos").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 1) + n := nodes[0] + assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID, *node.Name), n.ID) + assert.Equal(t, chainID, n.ChainID) + assert.Equal(t, *node.Name, n.Name) + wantConfig, err := toml.Marshal(node) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 10) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n.State) +} +func solanaStartNewApplication(t *testing.T, cfgs ...*solcfg.TOMLConfig) *cltest.TestApplication { + for i := range cfgs { + cfgs[i].Chain.SetDefaults() + } + return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Solana = cfgs + c.EVM = nil + }) +} + +func TestShell_IndexSolanaNodes(t *testing.T) { + t.Parallel() + + id := solanatest.RandomChainID() + node1 := solcfg.Node{ + Name: ptr("first"), + URL: config.MustParseURL("https://solana1.example"), + } + node2 := solcfg.Node{ + Name: ptr("second"), + URL: config.MustParseURL("https://solana2.example"), + } + chain := solcfg.TOMLConfig{ + ChainID: &id, + Nodes: solcfg.Nodes{&node1, &node2}, + } + app := solanaStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewNodeClient(client, "solana").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 2) + n1 := nodes[0] + n2 := nodes[1] + assert.Equal(t, id, n1.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) + assert.Equal(t, *node1.Name, n1.Name) + wantConfig, err := toml.Marshal(node1) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n1.Config) + assert.Equal(t, id, n2.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) + assert.Equal(t, *node2.Name, n2.Name) + wantConfig2, err := toml.Marshal(node2) + require.NoError(t, err) + assert.Equal(t, string(wantConfig2), n2.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 19) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n1.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n1.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n1.State) + assert.Contains(t, renderLines[10], "Name") + assert.Contains(t, renderLines[10], n2.Name) + assert.Contains(t, renderLines[11], "Chain ID") + assert.Contains(t, renderLines[11], n2.ChainID) + assert.Contains(t, renderLines[12], "State") + assert.Contains(t, renderLines[12], n2.State) +} + +func starknetStartNewApplication(t *testing.T, cfgs ...*starkcfg.TOMLConfig) *cltest.TestApplication { + for i := range cfgs { + cfgs[i].SetDefaults() + } + return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Starknet = cfgs + c.EVM = nil + c.Solana = nil + }) +} + +func TestShell_IndexStarkNetNodes(t *testing.T) { + t.Parallel() + + id := "starknet chain ID" + node1 := starkcfg.Node{ + Name: ptr("first"), + URL: config.MustParseURL("https://starknet1.example"), + } + node2 := starkcfg.Node{ + Name: ptr("second"), + URL: config.MustParseURL("https://starknet2.example"), + } + chain := starkcfg.TOMLConfig{ + ChainID: &id, + Nodes: starkcfg.Nodes{&node1, &node2}, + } + app := starknetStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewNodeClient(client, "starknet").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 2) + n1 := nodes[0] + n2 := nodes[1] + assert.Equal(t, id, n1.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) + assert.Equal(t, *node1.Name, n1.Name) + wantConfig, err := toml.Marshal(node1) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n1.Config) + assert.Equal(t, id, n2.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) + assert.Equal(t, *node2.Name, n2.Name) + wantConfig2, err := toml.Marshal(node2) + require.NoError(t, err) + assert.Equal(t, string(wantConfig2), n2.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 17) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n1.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n1.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n1.State) + assert.Contains(t, renderLines[9], "Name") + assert.Contains(t, renderLines[9], n2.Name) + assert.Contains(t, renderLines[10], "Chain ID") + assert.Contains(t, renderLines[10], n2.ChainID) + assert.Contains(t, renderLines[11], "State") + assert.Contains(t, renderLines[11], n2.State) +} diff --git a/core/cmd/nodes_commands.go b/core/cmd/nodes_commands.go index efee10bb156..2afff31ace0 100644 --- a/core/cmd/nodes_commands.go +++ b/core/cmd/nodes_commands.go @@ -2,25 +2,24 @@ package cmd import ( "fmt" + "maps" + "slices" "strings" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" "github.com/urfave/cli" ) -func initCosmosNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("Cosmos", NewCosmosNodeClient(s)) -} - -func initStarkNetNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("StarkNet", NewStarkNetNodeClient(s)) -} - -func initEVMNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("EVM", NewEVMNodeClient(s)) -} - -func initSolanaNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("Solana", NewSolanaNodeClient(s)) +func initNodeSubCmds(s *Shell) []cli.Command { + cmds := []cli.Command{} + for _, network := range slices.Sorted(maps.Keys(relay.SupportedNetworks)) { + if network == relay.NetworkDummy { + continue + } + cmds = append(cmds, nodeCommand(network, NewNodeClient(s, network))) + } + return cmds } // nodeCommand returns a cli.Command with subcommands for the given NodeClient. @@ -40,6 +39,41 @@ func nodeCommand(typ string, client NodeClient) cli.Command { } } +// EVMNodePresenter implements TableRenderer for an EVMNodeResource. +type NodePresenter struct { + presenters.NodeResource +} + +// ToRow presents the EVMNodeResource as a slice of strings. +func (p *NodePresenter) ToRow() []string { + return []string{p.Name, p.ChainID, p.State, p.Config} +} + +// RenderTable implements TableRenderer +func (p NodePresenter) RenderTable(rt RendererTable) error { + var rows [][]string + rows = append(rows, p.ToRow()) + renderList(nodeHeaders, rows, rt.Writer) + + return nil +} + +// NodePresenters implements TableRenderer for a slice of NodePresenter. +type NodePresenters []NodePresenter + +// RenderTable implements TableRenderer +func (ps NodePresenters) RenderTable(rt RendererTable) error { + rows := [][]string{} + + for _, p := range ps { + rows = append(rows, p.ToRow()) + } + + renderList(nodeHeaders, rows, rt.Writer) + + return nil +} + // NodeClient is a generic client interface for any of node. type NodeClient interface { IndexNodes(c *cli.Context) error @@ -52,10 +86,10 @@ type nodeClient[P TableRenderer] struct { // newNodeClient returns a new NodeClient for a particular type of NodeStatus. // P is a TableRenderer for []types.NodeStatus. -func newNodeClient[P TableRenderer](s *Shell, name string) NodeClient { - return &nodeClient[P]{ +func NewNodeClient(s *Shell, network string) NodeClient { + return &nodeClient[NodePresenters]{ Shell: s, - path: "/v2/nodes/" + name, + path: "/v2/nodes/" + network, } } diff --git a/core/cmd/shell.go b/core/cmd/shell.go index 53ba5e3f82f..94664a3cf3d 100644 --- a/core/cmd/shell.go +++ b/core/cmd/shell.go @@ -399,7 +399,7 @@ func takeBackupIfVersionUpgrade(dbUrl url.URL, rootDir string, cfg periodicbacku } // Because backups can take a long time we must start a "fake" health report to prevent - //node shutdown because of healthcheck fail/timeout + // node shutdown because of healthcheck fail/timeout err = databaseBackup.RunBackup(appv.String()) return err } diff --git a/core/cmd/solana_chains_commands.go b/core/cmd/solana_chains_commands.go deleted file mode 100644 index aa2a07c0f8c..00000000000 --- a/core/cmd/solana_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// SolanaChainPresenter implements TableRenderer for a SolanaChainResource -type SolanaChainPresenter struct { - presenters.SolanaChainResource -} - -// ToRow presents the SolanaChainResource as a slice of strings. -func (p *SolanaChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p SolanaChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// SolanaChainPresenters implements TableRenderer for a slice of SolanaChainPresenters. -type SolanaChainPresenters []SolanaChainPresenter - -// RenderTable implements TableRenderer -func (ps SolanaChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func SolanaChainClient(s *Shell) ChainClient { - return newChainClient[SolanaChainPresenters](s, "solana") -} diff --git a/core/cmd/solana_chains_commands_test.go b/core/cmd/solana_chains_commands_test.go deleted file mode 100644 index e374ba11c65..00000000000 --- a/core/cmd/solana_chains_commands_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package cmd_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" -) - -func TestShell_IndexSolanaChains(t *testing.T) { - t.Parallel() - - id := solanatest.RandomChainID() - cfg := solcfg.TOMLConfig{ - ChainID: &id, - Enabled: ptr(true), - } - app := solanaStartNewApplication(t, &cfg) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.SolanaChainClient(client).IndexChains(cltest.EmptyCLIContext())) - chains := *r.Renders[0].(*cmd.SolanaChainPresenters) - require.Len(t, chains, 1) - c := chains[0] - assert.Equal(t, id, c.ID) - assertTableRenders(t, r) -} diff --git a/core/cmd/solana_node_commands.go b/core/cmd/solana_node_commands.go deleted file mode 100644 index 6ccf9a5864c..00000000000 --- a/core/cmd/solana_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// SolanaNodePresenter implements TableRenderer for a SolanaNodeResource. -type SolanaNodePresenter struct { - presenters.SolanaNodeResource -} - -// ToRow presents the SolanaNodeResource as a slice of strings. -func (p *SolanaNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p SolanaNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// SolanaNodePresenters implements TableRenderer for a slice of SolanaNodePresenter. -type SolanaNodePresenters []SolanaNodePresenter - -// RenderTable implements TableRenderer -func (ps SolanaNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewSolanaNodeClient(s *Shell) NodeClient { - return newNodeClient[SolanaNodePresenters](s, "solana") -} diff --git a/core/cmd/solana_node_commands_test.go b/core/cmd/solana_node_commands_test.go deleted file mode 100644 index f08fcd7aaf0..00000000000 --- a/core/cmd/solana_node_commands_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/config" - solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func solanaStartNewApplication(t *testing.T, cfgs ...*solcfg.TOMLConfig) *cltest.TestApplication { - for i := range cfgs { - cfgs[i].Chain.SetDefaults() - } - return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Solana = cfgs - c.EVM = nil - }) -} - -func TestShell_IndexSolanaNodes(t *testing.T) { - t.Parallel() - - id := solanatest.RandomChainID() - node1 := solcfg.Node{ - Name: ptr("first"), - URL: config.MustParseURL("https://solana1.example"), - } - node2 := solcfg.Node{ - Name: ptr("second"), - URL: config.MustParseURL("https://solana2.example"), - } - chain := solcfg.TOMLConfig{ - ChainID: &id, - Nodes: solcfg.Nodes{&node1, &node2}, - } - app := solanaStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.NewSolanaNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.SolanaNodePresenters) - require.Len(t, nodes, 2) - n1 := nodes[0] - n2 := nodes[1] - assert.Equal(t, id, n1.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) - assert.Equal(t, *node1.Name, n1.Name) - wantConfig, err := toml.Marshal(node1) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n1.Config) - assert.Equal(t, id, n2.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) - assert.Equal(t, *node2.Name, n2.Name) - wantConfig2, err := toml.Marshal(node2) - require.NoError(t, err) - assert.Equal(t, string(wantConfig2), n2.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 19, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n1.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n1.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n1.State) - assert.Contains(t, renderLines[10], "Name") - assert.Contains(t, renderLines[10], n2.Name) - assert.Contains(t, renderLines[11], "Chain ID") - assert.Contains(t, renderLines[11], n2.ChainID) - assert.Contains(t, renderLines[12], "State") - assert.Contains(t, renderLines[12], n2.State) -} diff --git a/core/cmd/starknet_chains_commands.go b/core/cmd/starknet_chains_commands.go deleted file mode 100644 index 5b20b37ae22..00000000000 --- a/core/cmd/starknet_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// StarkNetChainPresenter implements TableRenderer for a StarkNetChainResource -type StarkNetChainPresenter struct { - presenters.StarkNetChainResource -} - -// ToRow presents the StarkNetChainResource as a slice of strings. -func (p *StarkNetChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p StarkNetChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// StarkNetChainPresenters implements TableRenderer for a slice of StarkNetChainPresenters. -type StarkNetChainPresenters []StarkNetChainPresenter - -// RenderTable implements TableRenderer -func (ps StarkNetChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func StarkNetChainClient(s *Shell) ChainClient { - return newChainClient[StarkNetChainPresenters](s, "starknet") -} diff --git a/core/cmd/starknet_node_commands.go b/core/cmd/starknet_node_commands.go deleted file mode 100644 index c26bb031145..00000000000 --- a/core/cmd/starknet_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// StarkNetNodePresenter implements TableRenderer for a StarkNetNodeResource. -type StarkNetNodePresenter struct { - presenters.StarkNetNodeResource -} - -// ToRow presents the StarkNetNodeResource as a slice of strings. -func (p *StarkNetNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p StarkNetNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// StarkNetNodePresenters implements TableRenderer for a slice of StarkNetNodePresenter. -type StarkNetNodePresenters []StarkNetNodePresenter - -// RenderTable implements TableRenderer -func (ps StarkNetNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewStarkNetNodeClient(s *Shell) NodeClient { - return newNodeClient[StarkNetNodePresenters](s, "starknet") -} diff --git a/core/cmd/starknet_node_commands_test.go b/core/cmd/starknet_node_commands_test.go deleted file mode 100644 index dae93682085..00000000000 --- a/core/cmd/starknet_node_commands_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func starknetStartNewApplication(t *testing.T, cfgs ...*config.TOMLConfig) *cltest.TestApplication { - for i := range cfgs { - cfgs[i].SetDefaults() - } - return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Starknet = cfgs - c.EVM = nil - c.Solana = nil - }) -} - -func TestShell_IndexStarkNetNodes(t *testing.T) { - t.Parallel() - - id := "starknet chain ID" - node1 := config.Node{ - Name: ptr("first"), - URL: commoncfg.MustParseURL("https://starknet1.example"), - } - node2 := config.Node{ - Name: ptr("second"), - URL: commoncfg.MustParseURL("https://starknet2.example"), - } - chain := config.TOMLConfig{ - ChainID: &id, - Nodes: config.Nodes{&node1, &node2}, - } - app := starknetStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.NewStarkNetNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.StarkNetNodePresenters) - require.Len(t, nodes, 2) - n1 := nodes[0] - n2 := nodes[1] - assert.Equal(t, id, n1.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) - assert.Equal(t, *node1.Name, n1.Name) - wantConfig, err := toml.Marshal(node1) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n1.Config) - assert.Equal(t, id, n2.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) - assert.Equal(t, *node2.Name, n2.Name) - wantConfig2, err := toml.Marshal(node2) - require.NoError(t, err) - assert.Equal(t, string(wantConfig2), n2.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 17, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n1.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n1.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n1.State) - assert.Contains(t, renderLines[9], "Name") - assert.Contains(t, renderLines[9], n2.Name) - assert.Contains(t, renderLines[10], "Chain ID") - assert.Contains(t, renderLines[10], n2.ChainID) - assert.Contains(t, renderLines[11], "State") - assert.Contains(t, renderLines[11], n2.State) -} diff --git a/core/services/chainlink/mocks/relayer_chain_interoperators.go b/core/services/chainlink/mocks/relayer_chain_interoperators.go index 3ebe1411c19..bc30ede77ee 100644 --- a/core/services/chainlink/mocks/relayer_chain_interoperators.go +++ b/core/services/chainlink/mocks/relayer_chain_interoperators.go @@ -4,6 +4,7 @@ import ( "context" "slices" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" services2 "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -69,6 +70,6 @@ func (f *FakeRelayerChainInteroperators) ChainStatus(ctx context.Context, id typ panic("unimplemented") } -func (f *FakeRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]types.ChainStatus, int, error) { +func (f *FakeRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]commonTypes.ChainStatusWithID, int, error) { panic("unimplemented") } diff --git a/core/services/chainlink/relayer_chain_interoperators.go b/core/services/chainlink/relayer_chain_interoperators.go index 1be6e9337d1..2fc671bfe6e 100644 --- a/core/services/chainlink/relayer_chain_interoperators.go +++ b/core/services/chainlink/relayer_chain_interoperators.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/relay" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services" @@ -53,7 +54,7 @@ type LegacyChainer interface { type ChainStatuser interface { ChainStatus(ctx context.Context, id types.RelayID) (types.ChainStatus, error) - ChainStatuses(ctx context.Context, offset, limit int) ([]types.ChainStatus, int, error) + ChainStatuses(ctx context.Context, offset, limit int) ([]commonTypes.ChainStatusWithID, int, error) } // NodesStatuser is an interface for node configuration and state. @@ -261,9 +262,9 @@ func (rs *CoreRelayerChainInteroperators) ChainStatus(ctx context.Context, id ty return lr.GetChainStatus(ctx) } -func (rs *CoreRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]types.ChainStatus, int, error) { +func (rs *CoreRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]commonTypes.ChainStatusWithID, int, error) { var ( - stats []types.ChainStatus + stats []commonTypes.ChainStatusWithID totalErr error ) rs.mu.Lock() @@ -283,7 +284,7 @@ func (rs *CoreRelayerChainInteroperators) ChainStatuses(ctx context.Context, off totalErr = errors.Join(totalErr, err) continue } - stats = append(stats, stat) + stats = append(stats, commonTypes.ChainStatusWithID{ChainStatus: stat, RelayID: rid}) } if totalErr != nil { diff --git a/core/web/chains_controller.go b/core/web/chains_controller.go index 6bc5ee4daa3..a99cbf4ca4b 100644 --- a/core/web/chains_controller.go +++ b/core/web/chains_controller.go @@ -5,13 +5,14 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/manyminds/api2go/jsonapi" "github.com/smartcontractkit/chainlink-common/pkg/types" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" ) type ChainsController interface { @@ -21,16 +22,6 @@ type ChainsController interface { Show(*gin.Context) } -type chainsController[R jsonapi.EntityNamer] struct { - network string - resourceName string - chainStats chainlink.ChainStatuser - errNotEnabled error - newResource func(types.ChainStatus) R - lggr logger.Logger - auditLogger audit.AuditLogger -} - type errChainDisabled struct { name string tomlKey string @@ -40,50 +31,51 @@ func (e errChainDisabled) Error() string { return fmt.Sprintf("%s is disabled: Set %s=true to enable", e.name, e.tomlKey) } -func newChainsController[R jsonapi.EntityNamer](network string, chainStats chainlink.ChainsNodesStatuser, errNotEnabled error, - newResource func(types.ChainStatus) R, lggr logger.Logger, auditLogger audit.AuditLogger) *chainsController[R] { - return &chainsController[R]{ - network: network, - resourceName: network + "_chain", - chainStats: chainStats, - errNotEnabled: errNotEnabled, - newResource: newResource, - lggr: lggr, - auditLogger: auditLogger, +type chainsController struct { + chainStats chainlink.RelayerChainInteroperators + newResource func(commonTypes.ChainStatusWithID) presenters.ChainResource + lggr logger.Logger + auditLogger audit.AuditLogger +} + +func NewChainsController(chainStats chainlink.RelayerChainInteroperators, lggr logger.Logger, auditLogger audit.AuditLogger) *chainsController { + return &chainsController{ + chainStats: chainStats, + newResource: presenters.NewChainResource, + lggr: lggr, + auditLogger: auditLogger, } } -func (cc *chainsController[R]) Index(c *gin.Context, size, page, offset int) { - if cc.chainStats == nil { - jsonAPIError(c, http.StatusBadRequest, cc.errNotEnabled) - return +func (cc *chainsController) Index(c *gin.Context, size, page, offset int) { + chainStats := cc.chainStats + if network := c.Param("network"); network != "" { + chainStats = chainStats.List(chainlink.FilterRelayersByType(network)) } - chains, count, err := cc.chainStats.ChainStatuses(c.Request.Context(), offset, size) + + chains, count, err := chainStats.ChainStatuses(c.Request.Context(), offset, size) if err != nil { jsonAPIError(c, http.StatusBadRequest, err) return } - var resources []R + resources := []presenters.ChainResource{} for _, chain := range chains { resources = append(resources, cc.newResource(chain)) } - paginatedResponse(c, cc.resourceName, size, page, resources, count, err) + paginatedResponse(c, "chain", size, page, resources, count, err) } -func (cc *chainsController[R]) Show(c *gin.Context) { - if cc.chainStats == nil { - jsonAPIError(c, http.StatusBadRequest, cc.errNotEnabled) - return - } - relayID := types.RelayID{Network: cc.network, ChainID: c.Param("ID")} +func (cc *chainsController) Show(c *gin.Context) { + relayID := types.RelayID{Network: c.Param("network"), ChainID: c.Param("ID")} chain, err := cc.chainStats.ChainStatus(c.Request.Context(), relayID) + status := commonTypes.ChainStatusWithID{ChainStatus: chain, RelayID: relayID} if err != nil { jsonAPIError(c, http.StatusBadRequest, err) return } - jsonAPIResponse(c, cc.newResource(chain), cc.resourceName) + jsonAPIResponse(c, cc.newResource(status), "chain") } diff --git a/core/web/chains_controller_test.go b/core/web/chains_controller_test.go new file mode 100644 index 00000000000..9e6c32a9637 --- /dev/null +++ b/core/web/chains_controller_test.go @@ -0,0 +1,585 @@ +package web_test + +import ( + "fmt" + "math/big" + "net/http" + "sort" + "testing" + "time" + + "github.com/manyminds/api2go/jsonapi" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/rand" + + commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" + commonTypes "github.com/smartcontractkit/chainlink-common/pkg/types" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + + evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/web" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" +) + +func Test_EVMChainsController_Show(t *testing.T) { + t.Parallel() + + validID := ubig.New(testutils.NewRandomEVMChainID()) + + testCases := []struct { + name string + inputID string + wantStatusCode int + want *evmcfg.EVMConfig + }{ + { + inputID: validID.String(), + name: "success", + want: &evmcfg.EVMConfig{ + ChainID: validID, + Enabled: ptr(true), + Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ + GasEstimator: evmcfg.GasEstimator{ + EIP1559DynamicFees: ptr(true), + BlockHistory: evmcfg.BlockHistoryEstimator{ + BlockHistorySize: ptr[uint16](50), + }, + }, + RPCBlockQueryDelay: ptr[uint16](23), + MinIncomingConfirmations: ptr[uint32](12), + LinkContractAddress: ptr(types.EIP55AddressFromAddress(testutils.NewAddress())), + }), + }, + wantStatusCode: http.StatusOK, + }, + { + inputID: "invalidid", + name: "invalid id", + want: nil, + wantStatusCode: http.StatusBadRequest, + }, + { + inputID: "234", + name: "not found", + want: nil, + wantStatusCode: http.StatusBadRequest, + }, + } + + for _, testCase := range testCases { + tc := testCase + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + if tc.want != nil { + c.EVM = evmcfg.EVMConfigs{tc.want} + } + })) + + wantedResult := tc.want + resp, cleanup := controller.client.Get( + "/v2/chains/evm/" + tc.inputID, + ) + t.Cleanup(cleanup) + require.Equal(t, tc.wantStatusCode, resp.StatusCode) + + if wantedResult != nil { + resource1 := presenters.ChainResource{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) + require.NoError(t, err) + + assert.Equal(t, resource1.ID, wantedResult.ChainID.String()) + toml, err := wantedResult.TOMLString() + require.NoError(t, err) + assert.Equal(t, toml, resource1.Config) + } + }) + } +} + +func Test_EVMChainsController_Index(t *testing.T) { + t.Parallel() + + // sort test chain ids to make expected comparison easy + chainIDs := []*big.Int{testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID()} + sort.Slice(chainIDs, func(i, j int) bool { + return chainIDs[i].String() < chainIDs[j].String() + }) + + configuredChains := evmcfg.EVMConfigs{ + {ChainID: ubig.New(chainIDs[0]), Chain: evmcfg.Defaults(nil)}, + { + ChainID: ubig.New(chainIDs[1]), + Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ + RPCBlockQueryDelay: ptr[uint16](13), + GasEstimator: evmcfg.GasEstimator{ + EIP1559DynamicFees: ptr(true), + BlockHistory: evmcfg.BlockHistoryEstimator{ + BlockHistorySize: ptr[uint16](1), + }, + }, + MinIncomingConfirmations: ptr[uint32](120), + }), + }, + { + ChainID: ubig.New(chainIDs[2]), + Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ + RPCBlockQueryDelay: ptr[uint16](5), + GasEstimator: evmcfg.GasEstimator{ + EIP1559DynamicFees: ptr(false), + BlockHistory: evmcfg.BlockHistoryEstimator{ + BlockHistorySize: ptr[uint16](2), + }, + }, + MinIncomingConfirmations: ptr[uint32](30), + }), + }, + } + + assert.Len(t, configuredChains, 3) + controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.EVM = append(c.EVM, configuredChains...) + })) + + badResp, cleanup := controller.client.Get("/v2/chains/evm?size=asd") + t.Cleanup(cleanup) + require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) + + resp, cleanup := controller.client.Get("/v2/chains/evm?size=3") + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body := cltest.ParseResponseBody(t, resp) + + metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) + require.NoError(t, err) + require.Equal(t, 1+len(configuredChains), metaCount) + + var links jsonapi.Links + + var gotChains []presenters.ChainResource + err = web.ParsePaginatedResponse(body, &gotChains, &links) + require.NoError(t, err) + assert.NotEmpty(t, links["next"].Href) + assert.Empty(t, links["prev"].Href) + + assert.Len(t, links, 1) + // the difference in index value here seems to be due to the fact + // that cltest always has a default EVM chain, which is the off-by-one + // in the indices + assert.Equal(t, gotChains[2].ID, configuredChains[1].ChainID.String()) + toml, err := configuredChains[1].TOMLString() + require.NoError(t, err) + assert.Equal(t, toml, gotChains[2].Config) + + resp, cleanup = controller.client.Get(links["next"].Href) + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + gotChains = []presenters.ChainResource{} + err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &gotChains, &links) + require.NoError(t, err) + assert.Empty(t, links["next"].Href) + assert.NotEmpty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, gotChains[0].ID, configuredChains[2].ChainID.String()) + toml, err = configuredChains[2].TOMLString() + require.NoError(t, err) + assert.Equal(t, toml, gotChains[0].Config) +} + +type TestEVMChainsController struct { + app *cltest.TestApplication + client cltest.HTTPClientCleaner +} + +func setupEVMChainsControllerTest(t *testing.T, cfg chainlink.GeneralConfig) *TestEVMChainsController { + // Using this instead of `NewApplicationEVMDisabled` since we need the chain set to be loaded in the app + // for the sake of the API endpoints to work properly + app := cltest.NewApplicationWithConfig(t, cfg) + ctx := testutils.Context(t) + require.NoError(t, app.Start(ctx)) + + client := app.NewHTTPClient(nil) + + return &TestEVMChainsController{ + app: app, + client: client, + } +} + +func ptr[T any](t T) *T { return &t } + +func Test_CosmosChainsController_Show(t *testing.T) { + t.Parallel() + + const validID = "Chainlink-12" + + testCases := []struct { + name string + inputID string + wantStatusCode int + want func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus + }{ + { + inputID: validID, + name: "success", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return &commonTypes.ChainStatus{ + ID: validID, + Enabled: true, + Config: `ChainID = 'Chainlink-12' +Enabled = true +Bech32Prefix = 'wasm' +BlockRate = '6s' +BlocksUntilTxTimeout = 30 +ConfirmPollPeriod = '1s' +FallbackGasPrice = '9.999' +GasToken = 'ucosm' +GasLimitMultiplier = '1.55555' +MaxMsgsPerBatch = 100 +OCR2CachePollPeriod = '4s' +OCR2CacheTTL = '1m0s' +TxMsgTimeout = '10m0s' +Nodes = [] +`, + } + }, + wantStatusCode: http.StatusOK, + }, + { + inputID: "234", + name: "not found", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return nil + }, + wantStatusCode: http.StatusBadRequest, + }, + } + + for _, testCase := range testCases { + tc := testCase + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + controller := setupCosmosChainsControllerTestV2(t, &coscfg.TOMLConfig{ + ChainID: ptr(validID), + Enabled: ptr(true), + Chain: coscfg.Chain{ + FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), + GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), + }}) + + wantedResult := tc.want(t, controller.app) + resp, cleanup := controller.client.Get( + "/v2/chains/cosmos/" + tc.inputID, + ) + t.Cleanup(cleanup) + require.Equal(t, tc.wantStatusCode, resp.StatusCode) + + if wantedResult != nil { + resource1 := presenters.ChainResource{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) + require.NoError(t, err) + + assert.Equal(t, wantedResult.ID, resource1.ID) + assert.Equal(t, wantedResult.Config, resource1.Config) + } + }) + } +} + +func Test_CosmosChainsController_Index(t *testing.T) { + t.Parallel() + + chainA := &coscfg.TOMLConfig{ + ChainID: ptr("a" + cosmostest.RandomChainID()), + Enabled: ptr(true), + Chain: coscfg.Chain{ + FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), + }, + } + + chainB := &coscfg.TOMLConfig{ + ChainID: ptr("b" + cosmostest.RandomChainID()), + Enabled: ptr(true), + Chain: coscfg.Chain{ + GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), + }, + } + controller := setupCosmosChainsControllerTestV2(t, chainA, chainB) + + badResp, cleanup := controller.client.Get("/v2/chains/cosmos?size=asd") + t.Cleanup(cleanup) + require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) + + resp, cleanup := controller.client.Get("/v2/chains/cosmos?size=1") + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body := cltest.ParseResponseBody(t, resp) + + metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) + require.NoError(t, err) + require.Equal(t, 2, metaCount) + + var links jsonapi.Links + + var chains []presenters.ChainResource + err = web.ParsePaginatedResponse(body, &chains, &links) + require.NoError(t, err) + assert.NotEmpty(t, links["next"].Href) + assert.Empty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainA.ChainID, chains[0].ID) + tomlA, err := chainA.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlA, chains[0].Config) + + resp, cleanup = controller.client.Get(links["next"].Href) + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + chains = []presenters.ChainResource{} + err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) + require.NoError(t, err) + assert.Empty(t, links["next"].Href) + assert.NotEmpty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainB.ChainID, chains[0].ID) + tomlB, err := chainB.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlB, chains[0].Config) +} + +type TestCosmosChainsController struct { + app *cltest.TestApplication + client cltest.HTTPClientCleaner +} + +func setupCosmosChainsControllerTestV2(t *testing.T, cfgs ...*coscfg.TOMLConfig) *TestCosmosChainsController { + for i := range cfgs { + cfgs[i].SetDefaults() + } + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Cosmos = cfgs + c.EVM = nil + }) + app := cltest.NewApplicationWithConfig(t, cfg) + ctx := testutils.Context(t) + require.NoError(t, app.Start(ctx)) + + client := app.NewHTTPClient(nil) + + return &TestCosmosChainsController{ + app: app, + client: client, + } +} +func Test_SolanaChainsController_Show(t *testing.T) { + t.Parallel() + + const validID = "Chainlink-12" + + testCases := []struct { + name string + inputID string + wantStatusCode int + want func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus + }{ + { + inputID: validID, + name: "success", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return &commonTypes.ChainStatus{ + ID: validID, + Enabled: true, + Config: `ChainID = 'Chainlink-12' +BalancePollPeriod = '5s' +ConfirmPollPeriod = '500ms' +OCR2CachePollPeriod = '1s' +OCR2CacheTTL = '1m0s' +TxTimeout = '1h0m0s' +TxRetryTimeout = '10s' +TxConfirmTimeout = '30s' +TxRetentionTimeout = '0s' +SkipPreflight = false +Commitment = 'confirmed' +MaxRetries = 0 +FeeEstimatorMode = 'fixed' +ComputeUnitPriceMax = 1000 +ComputeUnitPriceMin = 0 +ComputeUnitPriceDefault = 0 +FeeBumpPeriod = '3s' +BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 +ComputeUnitLimitDefault = 200000 +EstimateComputeUnitLimit = false +Nodes = [] + +[MultiNode] +Enabled = false +PollFailureThreshold = 5 +PollInterval = '15s' +SelectionMode = 'PriorityLevel' +SyncThreshold = 10 +NodeIsSyncingEnabled = false +LeaseDuration = '1m0s' +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = true +DeathDeclarationDelay = '20s' +NodeNoNewHeadsThreshold = '20s' +NoNewFinalizedHeadsThreshold = '20s' +FinalityDepth = 0 +FinalityTagEnabled = true +FinalizedBlockOffset = 50 +`, + } + }, + wantStatusCode: http.StatusOK, + }, + { + inputID: "234", + name: "not found", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return nil + }, + wantStatusCode: http.StatusBadRequest, + }, + } + + for _, testCase := range testCases { + tc := testCase + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + controller := setupSolanaChainsControllerTestV2(t, &config.TOMLConfig{ + ChainID: ptr(validID), + Chain: config.Chain{ + SkipPreflight: ptr(false), + TxTimeout: commoncfg.MustNewDuration(time.Hour), + }, + }) + + wantedResult := tc.want(t, controller.app) + resp, cleanup := controller.client.Get( + "/v2/chains/solana/" + tc.inputID, + ) + t.Cleanup(cleanup) + require.Equal(t, tc.wantStatusCode, resp.StatusCode) + + if wantedResult != nil { + resource1 := presenters.ChainResource{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) + require.NoError(t, err) + + assert.Equal(t, wantedResult.ID, resource1.ID) + assert.Equal(t, wantedResult.Enabled, resource1.Enabled) + assert.Equal(t, wantedResult.Config, resource1.Config) + } + }) + } +} + +func Test_SolanaChainsController_Index(t *testing.T) { + t.Parallel() + + chainA := &config.TOMLConfig{ + ChainID: ptr(fmt.Sprintf("ChainlinktestA-%d", rand.Int31n(999999))), + Chain: config.Chain{ + TxTimeout: commoncfg.MustNewDuration(time.Hour), + }, + } + chainB := &config.TOMLConfig{ + ChainID: ptr(fmt.Sprintf("ChainlinktestB-%d", rand.Int31n(999999))), + Chain: config.Chain{ + SkipPreflight: ptr(false), + }, + } + controller := setupSolanaChainsControllerTestV2(t, chainA, chainB) + + badResp, cleanup := controller.client.Get("/v2/chains/solana?size=asd") + t.Cleanup(cleanup) + require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) + + resp, cleanup := controller.client.Get("/v2/chains/solana?size=1") + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body := cltest.ParseResponseBody(t, resp) + + metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) + require.NoError(t, err) + require.Equal(t, 2, metaCount) + + var links jsonapi.Links + + chains := []presenters.ChainResource{} + err = web.ParsePaginatedResponse(body, &chains, &links) + require.NoError(t, err) + assert.NotEmpty(t, links["next"].Href) + assert.Empty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainA.ChainID, chains[0].ID) + tomlA, err := chainA.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlA, chains[0].Config) + + resp, cleanup = controller.client.Get(links["next"].Href) + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + chains = []presenters.ChainResource{} + err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) + require.NoError(t, err) + assert.Empty(t, links["next"].Href) + assert.NotEmpty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainB.ChainID, chains[0].ID) + tomlB, err := chainB.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlB, chains[0].Config) +} + +type TestSolanaChainsController struct { + app *cltest.TestApplication + client cltest.HTTPClientCleaner +} + +func setupSolanaChainsControllerTestV2(t *testing.T, cfgs ...*config.TOMLConfig) *TestSolanaChainsController { + for i := range cfgs { + cfgs[i].SetDefaults() + } + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Solana = cfgs + c.EVM = nil + }) + app := cltest.NewApplicationWithConfig(t, cfg) + require.NoError(t, app.Start(testutils.Context(t))) + + client := app.NewHTTPClient(nil) + + return &TestSolanaChainsController{ + app: app, + client: client, + } +} diff --git a/core/web/cosmos_chains_controller.go b/core/web/cosmos_chains_controller.go deleted file mode 100644 index 27c3976ce39..00000000000 --- a/core/web/cosmos_chains_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewCosmosChainsController(app chainlink.Application) ChainsController { - return newChainsController[presenters.CosmosChainResource]( - relay.NetworkCosmos, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkCosmos)), - ErrCosmosNotEnabled, - presenters.NewCosmosChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/cosmos_chains_controller_test.go b/core/web/cosmos_chains_controller_test.go deleted file mode 100644 index 2d5eb42515a..00000000000 --- a/core/web/cosmos_chains_controller_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package web_test - -import ( - "fmt" - "net/http" - "testing" - - "github.com/manyminds/api2go/jsonapi" - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/web" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func Test_CosmosChainsController_Show(t *testing.T) { - t.Parallel() - - const validId = "Chainlink-12" - - testCases := []struct { - name string - inputId string - wantStatusCode int - want func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus - }{ - { - inputId: validId, - name: "success", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return &types.ChainStatus{ - ID: validId, - Enabled: true, - Config: `ChainID = 'Chainlink-12' -Enabled = true -Bech32Prefix = 'wasm' -BlockRate = '6s' -BlocksUntilTxTimeout = 30 -ConfirmPollPeriod = '1s' -FallbackGasPrice = '9.999' -GasToken = 'ucosm' -GasLimitMultiplier = '1.55555' -MaxMsgsPerBatch = 100 -OCR2CachePollPeriod = '4s' -OCR2CacheTTL = '1m0s' -TxMsgTimeout = '10m0s' -Nodes = [] -`, - } - }, - wantStatusCode: http.StatusOK, - }, - { - inputId: "234", - name: "not found", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return nil - }, - wantStatusCode: http.StatusBadRequest, - }, - } - - for _, testCase := range testCases { - tc := testCase - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - controller := setupCosmosChainsControllerTestV2(t, &coscfg.TOMLConfig{ - ChainID: ptr(validId), - Enabled: ptr(true), - Chain: coscfg.Chain{ - FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), - GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), - }}) - - wantedResult := tc.want(t, controller.app) - resp, cleanup := controller.client.Get( - fmt.Sprintf("/v2/chains/cosmos/%s", tc.inputId), - ) - t.Cleanup(cleanup) - require.Equal(t, tc.wantStatusCode, resp.StatusCode) - - if wantedResult != nil { - resource1 := presenters.CosmosChainResource{} - err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) - require.NoError(t, err) - - assert.Equal(t, wantedResult.ID, resource1.ID) - assert.Equal(t, wantedResult.Config, resource1.Config) - } - }) - } -} - -func Test_CosmosChainsController_Index(t *testing.T) { - t.Parallel() - - chainA := &coscfg.TOMLConfig{ - ChainID: ptr("a" + cosmostest.RandomChainID()), - Enabled: ptr(true), - Chain: coscfg.Chain{ - FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), - }, - } - - chainB := &coscfg.TOMLConfig{ - ChainID: ptr("b" + cosmostest.RandomChainID()), - Enabled: ptr(true), - Chain: coscfg.Chain{ - GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), - }, - } - controller := setupCosmosChainsControllerTestV2(t, chainA, chainB) - - badResp, cleanup := controller.client.Get("/v2/chains/cosmos?size=asd") - t.Cleanup(cleanup) - require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) - - resp, cleanup := controller.client.Get("/v2/chains/cosmos?size=1") - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - body := cltest.ParseResponseBody(t, resp) - - metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) - require.NoError(t, err) - require.Equal(t, 2, metaCount) - - var links jsonapi.Links - - var chains []presenters.CosmosChainResource - err = web.ParsePaginatedResponse(body, &chains, &links) - assert.NoError(t, err) - assert.NotEmpty(t, links["next"].Href) - assert.Empty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainA.ChainID, chains[0].ID) - tomlA, err := chainA.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlA, chains[0].Config) - - resp, cleanup = controller.client.Get(links["next"].Href) - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - chains = []presenters.CosmosChainResource{} - err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) - assert.NoError(t, err) - assert.Empty(t, links["next"].Href) - assert.NotEmpty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainB.ChainID, chains[0].ID) - tomlB, err := chainB.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlB, chains[0].Config) -} - -type TestCosmosChainsController struct { - app *cltest.TestApplication - client cltest.HTTPClientCleaner -} - -func setupCosmosChainsControllerTestV2(t *testing.T, cfgs ...*coscfg.TOMLConfig) *TestCosmosChainsController { - for i := range cfgs { - cfgs[i].SetDefaults() - } - cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Cosmos = cfgs - c.EVM = nil - }) - app := cltest.NewApplicationWithConfig(t, cfg) - ctx := testutils.Context(t) - require.NoError(t, app.Start(ctx)) - - client := app.NewHTTPClient(nil) - - return &TestCosmosChainsController{ - app: app, - client: client, - } -} diff --git a/core/web/cosmos_nodes_controller.go b/core/web/cosmos_nodes_controller.go deleted file mode 100644 index f3a226721ca..00000000000 --- a/core/web/cosmos_nodes_controller.go +++ /dev/null @@ -1,18 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// ErrCosmosNotEnabled is returned when COSMOS_ENABLED is not true. -var ErrCosmosNotEnabled = errChainDisabled{name: "Cosmos", tomlKey: "Cosmos.Enabled"} - -func NewCosmosNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkCosmos) - - return newNodesController[presenters.CosmosNodeResource]( - scopedNodeStatuser, ErrCosmosNotEnabled, presenters.NewCosmosNodeResource, app.GetAuditLogger(), - ) -} diff --git a/core/web/cosmos_transfer_controller.go b/core/web/cosmos_transfer_controller.go index ab3d8c20f30..f80dda005eb 100644 --- a/core/web/cosmos_transfer_controller.go +++ b/core/web/cosmos_transfer_controller.go @@ -27,11 +27,13 @@ type CosmosTransfersController struct { App chainlink.Application } +var ErrCosmosNotEnabled = errChainDisabled{name: "Cosmos", tomlKey: "Cosmos.Enabled"} + // Create sends native coins from the Chainlink's account to a specified address. func (tc *CosmosTransfersController) Create(c *gin.Context) { relayers := tc.App.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkCosmos)) if relayers == nil { - jsonAPIError(c, http.StatusBadRequest, ErrSolanaNotEnabled) + jsonAPIError(c, http.StatusBadRequest, ErrCosmosNotEnabled) return } diff --git a/core/web/evm_chains_controller.go b/core/web/evm_chains_controller.go deleted file mode 100644 index 9c887fa409c..00000000000 --- a/core/web/evm_chains_controller.go +++ /dev/null @@ -1,19 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -var ErrEVMNotEnabled = errChainDisabled{name: "EVM", tomlKey: "EVM.Enabled"} - -func NewEVMChainsController(app chainlink.Application) ChainsController { - return newChainsController[presenters.EVMChainResource]( - relay.NetworkEVM, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkEVM)), - ErrEVMNotEnabled, - presenters.NewEVMChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/evm_chains_controller_test.go b/core/web/evm_chains_controller_test.go deleted file mode 100644 index ab8bf35e6cb..00000000000 --- a/core/web/evm_chains_controller_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package web_test - -import ( - "fmt" - "math/big" - "net/http" - "sort" - "testing" - - "github.com/manyminds/api2go/jsonapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/web" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func Test_EVMChainsController_Show(t *testing.T) { - t.Parallel() - - validId := ubig.New(testutils.NewRandomEVMChainID()) - - testCases := []struct { - name string - inputId string - wantStatusCode int - want *evmcfg.EVMConfig - }{ - { - inputId: validId.String(), - name: "success", - want: &evmcfg.EVMConfig{ - ChainID: validId, - Enabled: ptr(true), - Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ - GasEstimator: evmcfg.GasEstimator{ - EIP1559DynamicFees: ptr(true), - BlockHistory: evmcfg.BlockHistoryEstimator{ - BlockHistorySize: ptr[uint16](50), - }, - }, - RPCBlockQueryDelay: ptr[uint16](23), - MinIncomingConfirmations: ptr[uint32](12), - LinkContractAddress: ptr(types.EIP55AddressFromAddress(testutils.NewAddress())), - }), - }, - wantStatusCode: http.StatusOK, - }, - { - inputId: "invalidid", - name: "invalid id", - want: nil, - wantStatusCode: http.StatusBadRequest, - }, - { - inputId: "234", - name: "not found", - want: nil, - wantStatusCode: http.StatusBadRequest, - }, - } - - for _, testCase := range testCases { - tc := testCase - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - if tc.want != nil { - c.EVM = evmcfg.EVMConfigs{tc.want} - } - })) - - wantedResult := tc.want - resp, cleanup := controller.client.Get( - fmt.Sprintf("/v2/chains/evm/%s", tc.inputId), - ) - t.Cleanup(cleanup) - require.Equal(t, tc.wantStatusCode, resp.StatusCode) - - if wantedResult != nil { - resource1 := presenters.EVMChainResource{} - err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) - require.NoError(t, err) - - assert.Equal(t, resource1.ID, wantedResult.ChainID.String()) - toml, err := wantedResult.TOMLString() - require.NoError(t, err) - assert.Equal(t, toml, resource1.Config) - } - }) - } -} - -func Test_EVMChainsController_Index(t *testing.T) { - t.Parallel() - - // sort test chain ids to make expected comparison easy - chainIDs := []*big.Int{testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID()} - sort.Slice(chainIDs, func(i, j int) bool { - return chainIDs[i].String() < chainIDs[j].String() - }) - - configuredChains := evmcfg.EVMConfigs{ - {ChainID: ubig.New(chainIDs[0]), Chain: evmcfg.Defaults(nil)}, - { - ChainID: ubig.New(chainIDs[1]), - Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ - RPCBlockQueryDelay: ptr[uint16](13), - GasEstimator: evmcfg.GasEstimator{ - EIP1559DynamicFees: ptr(true), - BlockHistory: evmcfg.BlockHistoryEstimator{ - BlockHistorySize: ptr[uint16](1), - }, - }, - MinIncomingConfirmations: ptr[uint32](120), - }), - }, - { - ChainID: ubig.New(chainIDs[2]), - Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ - RPCBlockQueryDelay: ptr[uint16](5), - GasEstimator: evmcfg.GasEstimator{ - EIP1559DynamicFees: ptr(false), - BlockHistory: evmcfg.BlockHistoryEstimator{ - BlockHistorySize: ptr[uint16](2), - }, - }, - MinIncomingConfirmations: ptr[uint32](30), - }), - }, - } - - assert.Len(t, configuredChains, 3) - controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM = append(c.EVM, configuredChains...) - })) - - badResp, cleanup := controller.client.Get("/v2/chains/evm?size=asd") - t.Cleanup(cleanup) - require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) - - resp, cleanup := controller.client.Get("/v2/chains/evm?size=3") - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - body := cltest.ParseResponseBody(t, resp) - - metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) - require.NoError(t, err) - require.Equal(t, 1+len(configuredChains), metaCount) - - var links jsonapi.Links - - var gotChains []presenters.EVMChainResource - err = web.ParsePaginatedResponse(body, &gotChains, &links) - assert.NoError(t, err) - assert.NotEmpty(t, links["next"].Href) - assert.Empty(t, links["prev"].Href) - - assert.Len(t, links, 1) - // the difference in index value here seems to be due to the fact - // that cltest always has a default EVM chain, which is the off-by-one - // in the indices - assert.Equal(t, gotChains[2].ID, configuredChains[1].ChainID.String()) - toml, err := configuredChains[1].TOMLString() - require.NoError(t, err) - assert.Equal(t, toml, gotChains[2].Config) - - resp, cleanup = controller.client.Get(links["next"].Href) - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - gotChains = []presenters.EVMChainResource{} - err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &gotChains, &links) - assert.NoError(t, err) - assert.Empty(t, links["next"].Href) - assert.NotEmpty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, gotChains[0].ID, configuredChains[2].ChainID.String()) - toml, err = configuredChains[2].TOMLString() - require.NoError(t, err) - assert.Equal(t, toml, gotChains[0].Config) -} - -type TestEVMChainsController struct { - app *cltest.TestApplication - client cltest.HTTPClientCleaner -} - -func setupEVMChainsControllerTest(t *testing.T, cfg chainlink.GeneralConfig) *TestEVMChainsController { - // Using this instead of `NewApplicationEVMDisabled` since we need the chain set to be loaded in the app - // for the sake of the API endpoints to work properly - app := cltest.NewApplicationWithConfig(t, cfg) - ctx := testutils.Context(t) - require.NoError(t, app.Start(ctx)) - - client := app.NewHTTPClient(nil) - - return &TestEVMChainsController{ - app: app, - client: client, - } -} - -func ptr[T any](t T) *T { return &t } diff --git a/core/web/evm_nodes_controller.go b/core/web/evm_nodes_controller.go deleted file mode 100644 index 8872f51d7e3..00000000000 --- a/core/web/evm_nodes_controller.go +++ /dev/null @@ -1,14 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewEVMNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkEVM) - - return newNodesController[presenters.EVMNodeResource]( - scopedNodeStatuser, ErrEVMNotEnabled, presenters.NewEVMNodeResource, app.GetAuditLogger()) -} diff --git a/core/web/nodes_controller.go b/core/web/nodes_controller.go index 0e43316629a..ee40de9885c 100644 --- a/core/web/nodes_controller.go +++ b/core/web/nodes_controller.go @@ -2,7 +2,6 @@ package web import ( "context" - "net/http" "github.com/gin-gonic/gin" "github.com/manyminds/api2go/jsonapi" @@ -11,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" ) type NodesController interface { @@ -36,42 +36,38 @@ func (n *NetworkScopedNodeStatuser) NodeStatuses(ctx context.Context, offset, li } type nodesController[R jsonapi.EntityNamer] struct { - nodeSet *NetworkScopedNodeStatuser - errNotEnabled error - newResource func(status types.NodeStatus) R - auditLogger audit.AuditLogger + relayers chainlink.RelayerChainInteroperators + newResource func(status types.NodeStatus) R + auditLogger audit.AuditLogger } -func newNodesController[R jsonapi.EntityNamer]( - nodeSet *NetworkScopedNodeStatuser, - errNotEnabled error, - newResource func(status types.NodeStatus) R, - auditLogger audit.AuditLogger, +func NewNodesController( + relayers chainlink.RelayerChainInteroperators, auditLogger audit.AuditLogger, ) NodesController { - return &nodesController[R]{ - nodeSet: nodeSet, - errNotEnabled: errNotEnabled, - newResource: newResource, - auditLogger: auditLogger, + return &nodesController[presenters.NodeResource]{ + relayers: relayers, + newResource: presenters.NewNodeResource, + auditLogger: auditLogger, } } func (n *nodesController[R]) Index(c *gin.Context, size, page, offset int) { - if n.nodeSet == nil { - jsonAPIError(c, http.StatusBadRequest, n.errNotEnabled) - return - } - id := c.Param("ID") + network := c.Param("network") var nodes []types.NodeStatus var count int var err error + relayers := n.relayers + if network != "" { + relayers = relayers.List(chainlink.FilterRelayersByType(network)) + } + ctx := c.Request.Context() if id == "" { // fetch all nodes - nodes, count, err = n.nodeSet.NodeStatuses(ctx, offset, size) + nodes, count, err = relayers.NodeStatuses(ctx, offset, size) } else { // fetch nodes for chain ID // backward compatibility @@ -79,9 +75,9 @@ func (n *nodesController[R]) Index(c *gin.Context, size, page, offset int) { err = rid.UnmarshalString(id) if err != nil { rid.ChainID = id - rid.Network = n.nodeSet.network + rid.Network = network } - nodes, count, err = n.nodeSet.NodeStatuses(ctx, offset, size, rid) + nodes, count, err = relayers.NodeStatuses(ctx, offset, size, rid) } var resources []R diff --git a/core/web/presenters/chain.go b/core/web/presenters/chain.go index 99cf9a1d252..280f7a7f8d4 100644 --- a/core/web/presenters/chain.go +++ b/core/web/presenters/chain.go @@ -1,11 +1,32 @@ package presenters +import ( + "github.com/smartcontractkit/chainlink-common/pkg/types" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" +) + type ChainResource struct { JAID + Network string `json:"network"` Enabled bool `json:"enabled"` Config string `json:"config"` // TOML } +// GetName implements the api2go EntityNamer interface +func (r ChainResource) GetName() string { + return "chain" +} + +// NewChainResource returns a new ChainResource for chain. +func NewChainResource(chain commonTypes.ChainStatusWithID) ChainResource { + return ChainResource{ + JAID: NewJAID(chain.RelayID.ChainID), + Network: chain.RelayID.Network, + Config: chain.Config, + Enabled: chain.Enabled, + } +} + type NodeResource struct { JAID ChainID string `json:"chainID"` @@ -13,3 +34,19 @@ type NodeResource struct { Config string `json:"config"` // TOML State string `json:"state"` } + +// NewNodeResource returns a new NodeResource for node. +func NewNodeResource(node types.NodeStatus) NodeResource { + return NodeResource{ + JAID: NewPrefixedJAID(node.Name, node.ChainID), + ChainID: node.ChainID, + Name: node.Name, + State: node.State, + Config: node.Config, + } +} + +// GetName implements the api2go EntityNamer interface +func (r NodeResource) GetName() string { + return "node" +} diff --git a/core/web/presenters/cosmos_chain.go b/core/web/presenters/cosmos_chain.go deleted file mode 100644 index c2bc4b52b61..00000000000 --- a/core/web/presenters/cosmos_chain.go +++ /dev/null @@ -1,45 +0,0 @@ -package presenters - -import ( - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// CosmosChainResource is an Cosmos chain JSONAPI resource. -type CosmosChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r CosmosChainResource) GetName() string { - return "cosmos_chain" -} - -// NewCosmosChainResource returns a new CosmosChainResource for chain. -func NewCosmosChainResource(chain types.ChainStatus) CosmosChainResource { - return CosmosChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// CosmosNodeResource is a Cosmos node JSONAPI resource. -type CosmosNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r CosmosNodeResource) GetName() string { - return "cosmos_node" -} - -// NewCosmosNodeResource returns a new CosmosNodeResource for node. -func NewCosmosNodeResource(node types.NodeStatus) CosmosNodeResource { - return CosmosNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/presenters/evm_chain.go b/core/web/presenters/evm_chain.go deleted file mode 100644 index adf399d4b01..00000000000 --- a/core/web/presenters/evm_chain.go +++ /dev/null @@ -1,43 +0,0 @@ -package presenters - -import "github.com/smartcontractkit/chainlink-common/pkg/types" - -// EVMChainResource is an EVM chain JSONAPI resource. -type EVMChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r EVMChainResource) GetName() string { - return "evm_chain" -} - -// NewEVMChainResource returns a new EVMChainResource for chain. -func NewEVMChainResource(chain types.ChainStatus) EVMChainResource { - return EVMChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// EVMNodeResource is an EVM node JSONAPI resource. -type EVMNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r EVMNodeResource) GetName() string { - return "evm_node" -} - -// NewEVMNodeResource returns a new EVMNodeResource for node. -func NewEVMNodeResource(node types.NodeStatus) EVMNodeResource { - return EVMNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/presenters/job.go b/core/web/presenters/job.go index bb518650516..8b01eeb5005 100644 --- a/core/web/presenters/job.go +++ b/core/web/presenters/job.go @@ -176,6 +176,7 @@ type OffChainReporting2Spec struct { BlockchainTimeout models.Interval `json:"blockchainTimeout"` ContractConfigTrackerPollInterval models.Interval `json:"contractConfigTrackerPollInterval"` ContractConfigConfirmations uint16 `json:"contractConfigConfirmations"` + OnchainSigningStrategy map[string]interface{} `json:"onchainSigningStrategy"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` CollectTelemetry bool `json:"collectTelemetry"` @@ -194,6 +195,7 @@ func NewOffChainReporting2Spec(spec *job.OCR2OracleSpec) *OffChainReporting2Spec BlockchainTimeout: spec.BlockchainTimeout, ContractConfigTrackerPollInterval: spec.ContractConfigTrackerPollInterval, ContractConfigConfirmations: spec.ContractConfigConfirmations, + OnchainSigningStrategy: spec.OnchainSigningStrategy, CreatedAt: spec.CreatedAt, UpdatedAt: spec.UpdatedAt, CollectTelemetry: spec.CaptureEATelemetry, diff --git a/core/web/presenters/jsonapi.go b/core/web/presenters/jsonapi.go index d14e24a7455..fe3aee8393f 100644 --- a/core/web/presenters/jsonapi.go +++ b/core/web/presenters/jsonapi.go @@ -16,8 +16,8 @@ func NewJAID(id string) JAID { } // NewPrefixedJAID prefixes JAID with chain id in %s/%s format. -func NewPrefixedJAID(id string, chainID string) JAID { - return JAID{ID: fmt.Sprintf("%s/%s", chainID, id)} +func NewPrefixedJAID(id string, prefix string) JAID { + return JAID{ID: fmt.Sprintf("%s/%s", prefix, id)} } // NewJAIDInt32 converts an int32 into a JAID diff --git a/core/web/presenters/node_test.go b/core/web/presenters/node_test.go index 34210a52166..d2db83009d9 100644 --- a/core/web/presenters/node_test.go +++ b/core/web/presenters/node_test.go @@ -13,7 +13,6 @@ import ( func TestNodeResource(t *testing.T) { var nodeResource NodeResource - var r interface{} state := "test" cfg := "cfg" testCases := []string{"solana", "cosmos", "starknet"} @@ -21,62 +20,25 @@ func TestNodeResource(t *testing.T) { chainID := fmt.Sprintf("%s chain ID", tc) nodeName := fmt.Sprintf("%s_node", tc) - switch tc { - case "evm": - evmNodeResource := NewEVMNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = evmNodeResource - nodeResource = evmNodeResource.NodeResource - case "solana": - solanaNodeResource := NewSolanaNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = solanaNodeResource - nodeResource = solanaNodeResource.NodeResource - case "cosmos": - cosmosNodeResource := NewCosmosNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = cosmosNodeResource - nodeResource = cosmosNodeResource.NodeResource - case "starknet": - starknetNodeResource := NewStarkNetNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = starknetNodeResource - nodeResource = starknetNodeResource.NodeResource - default: - t.Fail() - } + nodeResource = NewNodeResource(types.NodeStatus{ + ChainID: chainID, + Name: nodeName, + Config: cfg, + State: state, + }) + assert.Equal(t, chainID, nodeResource.ChainID) assert.Equal(t, nodeName, nodeResource.Name) assert.Equal(t, cfg, nodeResource.Config) assert.Equal(t, state, nodeResource.State) - b, err := jsonapi.Marshal(r) + b, err := jsonapi.Marshal(nodeResource) require.NoError(t, err) expected := fmt.Sprintf(` { "data":{ - "type":"%s_node", + "type":"node", "id":"%s/%s", "attributes":{ "chainID":"%s", @@ -86,7 +48,7 @@ func TestNodeResource(t *testing.T) { } } } - `, tc, chainID, nodeName, chainID, nodeName, cfg, state) + `, chainID, nodeName, chainID, nodeName, cfg, state) assert.JSONEq(t, expected, string(b)) } } diff --git a/core/web/presenters/solana_chain.go b/core/web/presenters/solana_chain.go deleted file mode 100644 index 798d98124a5..00000000000 --- a/core/web/presenters/solana_chain.go +++ /dev/null @@ -1,45 +0,0 @@ -package presenters - -import ( - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// SolanaChainResource is an Solana chain JSONAPI resource. -type SolanaChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r SolanaChainResource) GetName() string { - return "solana_chain" -} - -// NewSolanaChainResource returns a new SolanaChainResource for chain. -func NewSolanaChainResource(chain types.ChainStatus) SolanaChainResource { - return SolanaChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// SolanaNodeResource is a Solana node JSONAPI resource. -type SolanaNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r SolanaNodeResource) GetName() string { - return "solana_node" -} - -// NewSolanaNodeResource returns a new SolanaNodeResource for node. -func NewSolanaNodeResource(node types.NodeStatus) SolanaNodeResource { - return SolanaNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/presenters/starknet_chain.go b/core/web/presenters/starknet_chain.go deleted file mode 100644 index addf798fe9f..00000000000 --- a/core/web/presenters/starknet_chain.go +++ /dev/null @@ -1,45 +0,0 @@ -package presenters - -import ( - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// StarkNetChainResource is an StarkNet chain JSONAPI resource. -type StarkNetChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r StarkNetChainResource) GetName() string { - return "starknet_chain" -} - -// NewStarkNetChainResource returns a new StarkNetChainResource for chain. -func NewStarkNetChainResource(chain types.ChainStatus) StarkNetChainResource { - return StarkNetChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// StarkNetNodeResource is a StarkNet node JSONAPI resource. -type StarkNetNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r StarkNetNodeResource) GetName() string { - return "starknet_node" -} - -// NewStarkNetNodeResource returns a new StarkNetNodeResource for node. -func NewStarkNetNodeResource(node types.NodeStatus) StarkNetNodeResource { - return StarkNetNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/resolver/spec.go b/core/web/resolver/spec.go index ce23df49264..9a62e55308b 100644 --- a/core/web/resolver/spec.go +++ b/core/web/resolver/spec.go @@ -987,6 +987,11 @@ func (r *BootstrapSpecResolver) ContractConfigConfirmations() *int32 { return &confirmations } +// RelayConfig resolves the spec's onchain signing strategy config +func (r *OCR2SpecResolver) OnchainSigningStrategy() gqlscalar.Map { + return gqlscalar.Map(r.spec.OnchainSigningStrategy) +} + // CreatedAt resolves the spec's created at timestamp. func (r *BootstrapSpecResolver) CreatedAt() graphql.Time { return graphql.Time{Time: r.spec.CreatedAt} diff --git a/core/web/resolver/spec_test.go b/core/web/resolver/spec_test.go index 69d6a56509c..e5a08faadcf 100644 --- a/core/web/resolver/spec_test.go +++ b/core/web/resolver/spec_test.go @@ -471,6 +471,12 @@ func TestResolver_OCR2Spec(t *testing.T) { pluginConfig := map[string]interface{}{ "juelsPerFeeCoinSource": 100000000, } + onchainSigningStrategy := map[string]interface{}{ + "strategyName": "multi-chain", + "config": map[string]any{ + "evm": "b3df4d8748b67731a1112e8b45a764941974f5590c93672eebbc4f3504dd10ed", + }, + } require.NoError(t, err) testCases := []GQLTestCase{ @@ -486,6 +492,7 @@ func TestResolver_OCR2Spec(t *testing.T) { ContractID: contractAddress.String(), ContractConfigConfirmations: 1, ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), + OnchainSigningStrategy: onchainSigningStrategy, CreatedAt: f.Timestamp(), OCRKeyBundleID: null.StringFrom(keyBundleID.String()), MonitoringEndpoint: null.StringFrom("https://monitor.endpoint"), @@ -509,6 +516,7 @@ func TestResolver_OCR2Spec(t *testing.T) { contractID contractConfigConfirmations contractConfigTrackerPollInterval + onchainSigningStrategy createdAt ocrKeyBundleID monitoringEndpoint @@ -533,6 +541,12 @@ func TestResolver_OCR2Spec(t *testing.T) { "contractID": "0x613a38AC1659769640aaE063C651F48E0250454C", "contractConfigConfirmations": 1, "contractConfigTrackerPollInterval": "1m0s", + "onchainSigningStrategy": { + "strategyName": "multi-chain", + "config": { + "evm": "b3df4d8748b67731a1112e8b45a764941974f5590c93672eebbc4f3504dd10ed" + } + }, "createdAt": "2021-01-01T00:00:00Z", "ocrKeyBundleID": "f5bf259689b26f1374efb3c9a9868796953a0f814bb2d39b968d0e61b58620a5", "monitoringEndpoint": "https://monitor.endpoint", diff --git a/core/web/router.go b/core/web/router.go index 6e96b47981b..c57bf3c8095 100644 --- a/core/web/router.go +++ b/core/web/router.go @@ -390,36 +390,23 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { authv2.PATCH("/log", auth.RequiresAdminRole(lgc.Patch)) chains := authv2.Group("chains") - for _, chain := range []struct { - path string - cc ChainsController - }{ - {"evm", NewEVMChainsController(app)}, - {"solana", NewSolanaChainsController(app)}, - {"starknet", NewStarkNetChainsController(app)}, - {"cosmos", NewCosmosChainsController(app)}, - } { - chains.GET(chain.path, paginatedRequest(chain.cc.Index)) - chains.GET(chain.path+"/:ID", chain.cc.Show) - } + chainController := NewChainsController( + app.GetRelayers(), + app.GetLogger(), + app.GetAuditLogger(), + ) + chains.GET("", paginatedRequest(chainController.Index)) + chains.GET("/:network", paginatedRequest(chainController.Index)) + chains.GET("/:network/:ID", chainController.Show) nodes := authv2.Group("nodes") - for _, chain := range []struct { - path string - nc NodesController - }{ - {"evm", NewEVMNodesController(app)}, - {"solana", NewSolanaNodesController(app)}, - {"starknet", NewStarkNetNodesController(app)}, - {"cosmos", NewCosmosNodesController(app)}, - } { - if chain.path == "evm" { - // TODO still EVM only . Archive ticket: story/26276/multi-chain-type-ui-node-chain-configuration - nodes.GET("", paginatedRequest(chain.nc.Index)) - } - nodes.GET(chain.path, paginatedRequest(chain.nc.Index)) - chains.GET(chain.path+"/:ID/nodes", paginatedRequest(chain.nc.Index)) - } + nodesController := NewNodesController( + app.GetRelayers(), + app.GetAuditLogger(), + ) + nodes.GET("", paginatedRequest(nodesController.Index)) + nodes.GET("/:network", paginatedRequest(nodesController.Index)) + chains.GET("/:network/:ID/nodes", paginatedRequest(nodesController.Index)) efc := EVMForwardersController{app} authv2.GET("/nodes/evm/forwarders", paginatedRequest(efc.Index)) diff --git a/core/web/schema/type/spec.graphql b/core/web/schema/type/spec.graphql index db81001543c..b979b9c799f 100644 --- a/core/web/schema/type/spec.graphql +++ b/core/web/schema/type/spec.graphql @@ -82,6 +82,7 @@ type OCR2Spec { p2pv2Bootstrappers: [String!] relay: String! relayConfig: Map! + onchainSigningStrategy: Map! transmitterID: String pluginType: String! pluginConfig: Map! diff --git a/core/web/solana_chains_controller.go b/core/web/solana_chains_controller.go deleted file mode 100644 index 56d44d600ca..00000000000 --- a/core/web/solana_chains_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewSolanaChainsController(app chainlink.Application) ChainsController { - return newChainsController( - relay.NetworkSolana, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkSolana)), - ErrSolanaNotEnabled, - presenters.NewSolanaChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/solana_chains_controller_test.go b/core/web/solana_chains_controller_test.go deleted file mode 100644 index fdc9bd16b9b..00000000000 --- a/core/web/solana_chains_controller_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package web_test - -import ( - "fmt" - "math/rand" - "net/http" - "testing" - "time" - - "github.com/manyminds/api2go/jsonapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/web" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func Test_SolanaChainsController_Show(t *testing.T) { - t.Parallel() - - const validId = "Chainlink-12" - - testCases := []struct { - name string - inputId string - wantStatusCode int - want func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus - }{ - { - inputId: validId, - name: "success", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return &types.ChainStatus{ - ID: validId, - Enabled: true, - Config: `ChainID = 'Chainlink-12' -BalancePollPeriod = '5s' -ConfirmPollPeriod = '500ms' -OCR2CachePollPeriod = '1s' -OCR2CacheTTL = '1m0s' -TxTimeout = '1h0m0s' -TxRetryTimeout = '10s' -TxConfirmTimeout = '30s' -TxRetentionTimeout = '0s' -SkipPreflight = false -Commitment = 'confirmed' -MaxRetries = 0 -FeeEstimatorMode = 'fixed' -ComputeUnitPriceMax = 1000 -ComputeUnitPriceMin = 0 -ComputeUnitPriceDefault = 0 -FeeBumpPeriod = '3s' -BlockHistoryPollPeriod = '5s' -BlockHistorySize = 1 -ComputeUnitLimitDefault = 200000 -EstimateComputeUnitLimit = false -Nodes = [] - -[MultiNode] -Enabled = false -PollFailureThreshold = 5 -PollInterval = '15s' -SelectionMode = 'PriorityLevel' -SyncThreshold = 10 -NodeIsSyncingEnabled = false -LeaseDuration = '1m0s' -FinalizedBlockPollInterval = '5s' -EnforceRepeatableRead = true -DeathDeclarationDelay = '20s' -NodeNoNewHeadsThreshold = '20s' -NoNewFinalizedHeadsThreshold = '20s' -FinalityDepth = 0 -FinalityTagEnabled = true -FinalizedBlockOffset = 50 -`, - } - }, - wantStatusCode: http.StatusOK, - }, - { - inputId: "234", - name: "not found", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return nil - }, - wantStatusCode: http.StatusBadRequest, - }, - } - - for _, testCase := range testCases { - tc := testCase - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - controller := setupSolanaChainsControllerTestV2(t, &config.TOMLConfig{ - ChainID: ptr(validId), - Chain: config.Chain{ - SkipPreflight: ptr(false), - TxTimeout: commoncfg.MustNewDuration(time.Hour), - }, - }) - - wantedResult := tc.want(t, controller.app) - resp, cleanup := controller.client.Get( - fmt.Sprintf("/v2/chains/solana/%s", tc.inputId), - ) - t.Cleanup(cleanup) - require.Equal(t, tc.wantStatusCode, resp.StatusCode) - - if wantedResult != nil { - resource1 := presenters.SolanaChainResource{} - err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) - require.NoError(t, err) - - assert.Equal(t, wantedResult.ID, resource1.ID) - assert.Equal(t, wantedResult.Enabled, resource1.Enabled) - assert.Equal(t, wantedResult.Config, resource1.Config) - } - }) - } -} - -func Test_SolanaChainsController_Index(t *testing.T) { - t.Parallel() - - chainA := &config.TOMLConfig{ - ChainID: ptr(fmt.Sprintf("ChainlinktestA-%d", rand.Int31n(999999))), - Chain: config.Chain{ - TxTimeout: commoncfg.MustNewDuration(time.Hour), - }, - } - chainB := &config.TOMLConfig{ - ChainID: ptr(fmt.Sprintf("ChainlinktestB-%d", rand.Int31n(999999))), - Chain: config.Chain{ - SkipPreflight: ptr(false), - }, - } - controller := setupSolanaChainsControllerTestV2(t, chainA, chainB) - - badResp, cleanup := controller.client.Get("/v2/chains/solana?size=asd") - t.Cleanup(cleanup) - require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) - - resp, cleanup := controller.client.Get("/v2/chains/solana?size=1") - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - body := cltest.ParseResponseBody(t, resp) - - metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) - require.NoError(t, err) - require.Equal(t, 2, metaCount) - - var links jsonapi.Links - - chains := []presenters.SolanaChainResource{} - err = web.ParsePaginatedResponse(body, &chains, &links) - assert.NoError(t, err) - assert.NotEmpty(t, links["next"].Href) - assert.Empty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainA.ChainID, chains[0].ID) - tomlA, err := chainA.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlA, chains[0].Config) - - resp, cleanup = controller.client.Get(links["next"].Href) - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - chains = []presenters.SolanaChainResource{} - err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) - assert.NoError(t, err) - assert.Empty(t, links["next"].Href) - assert.NotEmpty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainB.ChainID, chains[0].ID) - tomlB, err := chainB.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlB, chains[0].Config) -} - -type TestSolanaChainsController struct { - app *cltest.TestApplication - client cltest.HTTPClientCleaner -} - -func setupSolanaChainsControllerTestV2(t *testing.T, cfgs ...*config.TOMLConfig) *TestSolanaChainsController { - for i := range cfgs { - cfgs[i].SetDefaults() - } - cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Solana = cfgs - c.EVM = nil - }) - app := cltest.NewApplicationWithConfig(t, cfg) - require.NoError(t, app.Start(testutils.Context(t))) - - client := app.NewHTTPClient(nil) - - return &TestSolanaChainsController{ - app: app, - client: client, - } -} diff --git a/core/web/solana_nodes_controller.go b/core/web/solana_nodes_controller.go deleted file mode 100644 index 71b8f70c5ec..00000000000 --- a/core/web/solana_nodes_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// ErrSolanaNotEnabled is returned when Solana.Enabled is not true. -var ErrSolanaNotEnabled = errChainDisabled{name: "Solana", tomlKey: "Solana.Enabled"} - -func NewSolanaNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkSolana) - - return newNodesController[presenters.SolanaNodeResource]( - scopedNodeStatuser, ErrSolanaNotEnabled, presenters.NewSolanaNodeResource, app.GetAuditLogger()) -} diff --git a/core/web/solana_transfer_controller.go b/core/web/solana_transfer_controller.go index 07c629a7dd1..5a5f51bc9dd 100644 --- a/core/web/solana_transfer_controller.go +++ b/core/web/solana_transfer_controller.go @@ -22,6 +22,8 @@ type SolanaTransfersController struct { App chainlink.Application } +var ErrSolanaNotEnabled = errChainDisabled{name: "Solana", tomlKey: "Solana.Enabled"} + // Create sends SOL and other native coins from the Chainlink's account to a specified address. func (tc *SolanaTransfersController) Create(c *gin.Context) { relayers := tc.App.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkSolana)) diff --git a/core/web/starknet_chains_controller.go b/core/web/starknet_chains_controller.go deleted file mode 100644 index eb79ba3f962..00000000000 --- a/core/web/starknet_chains_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewStarkNetChainsController(app chainlink.Application) ChainsController { - return newChainsController( - relay.NetworkStarkNet, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkStarkNet)), - ErrStarkNetNotEnabled, - presenters.NewStarkNetChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/starknet_nodes_controller.go b/core/web/starknet_nodes_controller.go deleted file mode 100644 index 664b89d03ca..00000000000 --- a/core/web/starknet_nodes_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// ErrStarkNetNotEnabled is returned when Starknet.Enabled is not true. -var ErrStarkNetNotEnabled = errChainDisabled{name: "StarkNet", tomlKey: "Starknet.Enabled"} - -func NewStarkNetNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkStarkNet) - - return newNodesController[presenters.StarkNetNodeResource]( - scopedNodeStatuser, ErrStarkNetNotEnabled, presenters.NewStarkNetNodeResource, app.GetAuditLogger()) -} diff --git a/testdata/scripts/chains/cosmos/help.txtar b/testdata/scripts/chains/cosmos/help.txtar index edef6f7345c..4fe0a930ac0 100644 --- a/testdata/scripts/chains/cosmos/help.txtar +++ b/testdata/scripts/chains/cosmos/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains cosmos - Commands for handling Cosmos chains + chainlink chains cosmos - Commands for handling cosmos chains USAGE: chainlink chains cosmos command [command options] [arguments...] COMMANDS: - list List all existing Cosmos chains + list List all existing cosmos chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/cosmos/list/help.txtar b/testdata/scripts/chains/cosmos/list/help.txtar index 7e9be2efb00..d1f2d166374 100644 --- a/testdata/scripts/chains/cosmos/list/help.txtar +++ b/testdata/scripts/chains/cosmos/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains cosmos list - List all existing Cosmos chains + chainlink chains cosmos list - List all existing cosmos chains USAGE: chainlink chains cosmos list [arguments...] diff --git a/testdata/scripts/chains/evm/help.txtar b/testdata/scripts/chains/evm/help.txtar index e15dde5ecd2..b3e9e366810 100644 --- a/testdata/scripts/chains/evm/help.txtar +++ b/testdata/scripts/chains/evm/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains evm - Commands for handling EVM chains + chainlink chains evm - Commands for handling evm chains USAGE: chainlink chains evm command [command options] [arguments...] COMMANDS: - list List all existing EVM chains + list List all existing evm chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/evm/list/help.txtar b/testdata/scripts/chains/evm/list/help.txtar index 154ee110ad5..bb5eec199b7 100644 --- a/testdata/scripts/chains/evm/list/help.txtar +++ b/testdata/scripts/chains/evm/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains evm list - List all existing EVM chains + chainlink chains evm list - List all existing evm chains USAGE: chainlink chains evm list [arguments...] diff --git a/testdata/scripts/chains/help.txtar b/testdata/scripts/chains/help.txtar index 83a342925e1..ccfb54d2928 100644 --- a/testdata/scripts/chains/help.txtar +++ b/testdata/scripts/chains/help.txtar @@ -9,10 +9,11 @@ USAGE: chainlink chains command [command options] [arguments...] COMMANDS: - evm Commands for handling EVM chains - cosmos Commands for handling Cosmos chains - solana Commands for handling Solana chains - starknet Commands for handling StarkNet chains + aptos Commands for handling aptos chains + cosmos Commands for handling cosmos chains + evm Commands for handling evm chains + solana Commands for handling solana chains + starknet Commands for handling starknet chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/solana/help.txtar b/testdata/scripts/chains/solana/help.txtar index be3ab9b343b..ddc27fed18f 100644 --- a/testdata/scripts/chains/solana/help.txtar +++ b/testdata/scripts/chains/solana/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains solana - Commands for handling Solana chains + chainlink chains solana - Commands for handling solana chains USAGE: chainlink chains solana command [command options] [arguments...] COMMANDS: - list List all existing Solana chains + list List all existing solana chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/solana/list/help.txtar b/testdata/scripts/chains/solana/list/help.txtar index 794085d43d9..ed8a857529d 100644 --- a/testdata/scripts/chains/solana/list/help.txtar +++ b/testdata/scripts/chains/solana/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains solana list - List all existing Solana chains + chainlink chains solana list - List all existing solana chains USAGE: chainlink chains solana list [arguments...] diff --git a/testdata/scripts/chains/starknet/help.txtar b/testdata/scripts/chains/starknet/help.txtar index 992623d842c..5cb8fe93746 100644 --- a/testdata/scripts/chains/starknet/help.txtar +++ b/testdata/scripts/chains/starknet/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains starknet - Commands for handling StarkNet chains + chainlink chains starknet - Commands for handling starknet chains USAGE: chainlink chains starknet command [command options] [arguments...] COMMANDS: - list List all existing StarkNet chains + list List all existing starknet chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/starknet/list/help.txtar b/testdata/scripts/chains/starknet/list/help.txtar index 723318bf098..67315ce6c1e 100644 --- a/testdata/scripts/chains/starknet/list/help.txtar +++ b/testdata/scripts/chains/starknet/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains starknet list - List all existing StarkNet chains + chainlink chains starknet list - List all existing starknet chains USAGE: chainlink chains starknet list [arguments...] diff --git a/testdata/scripts/help-all/help-all.txtar b/testdata/scripts/help-all/help-all.txtar index 372b149bd19..078853ef6a5 100644 --- a/testdata/scripts/help-all/help-all.txtar +++ b/testdata/scripts/help-all/help-all.txtar @@ -24,14 +24,16 @@ bridges destroy # Destroys the Bridge for an External Adapter bridges list # List all Bridges to External Adapters bridges show # Show a Bridge's details chains # Commands for handling chain configuration -chains cosmos # Commands for handling Cosmos chains -chains cosmos list # List all existing Cosmos chains -chains evm # Commands for handling EVM chains -chains evm list # List all existing EVM chains -chains solana # Commands for handling Solana chains -chains solana list # List all existing Solana chains -chains starknet # Commands for handling StarkNet chains -chains starknet list # List all existing StarkNet chains +chains aptos # Commands for handling aptos chains +chains aptos list # List all existing aptos chains +chains cosmos # Commands for handling cosmos chains +chains cosmos list # List all existing cosmos chains +chains evm # Commands for handling evm chains +chains evm list # List all existing evm chains +chains solana # Commands for handling solana chains +chains solana list # List all existing solana chains +chains starknet # Commands for handling starknet chains +chains starknet list # List all existing starknet chains config # Commands for the node's configuration config loglevel # Set log level config logsql # Enable/disable SQL statement logging @@ -132,14 +134,16 @@ node start # Run the Chainlink node node status # Displays the health of various services running inside the node. node validate # Validate the TOML configuration and secrets that are passed as flags to the `node` command. Prints the full effective configuration, with defaults included nodes # Commands for handling node configuration -nodes cosmos # Commands for handling Cosmos node configuration -nodes cosmos list # List all existing Cosmos nodes -nodes evm # Commands for handling EVM node configuration -nodes evm list # List all existing EVM nodes -nodes solana # Commands for handling Solana node configuration -nodes solana list # List all existing Solana nodes -nodes starknet # Commands for handling StarkNet node configuration -nodes starknet list # List all existing StarkNet nodes +nodes aptos # Commands for handling aptos node configuration +nodes aptos list # List all existing aptos nodes +nodes cosmos # Commands for handling cosmos node configuration +nodes cosmos list # List all existing cosmos nodes +nodes evm # Commands for handling evm node configuration +nodes evm list # List all existing evm nodes +nodes solana # Commands for handling solana node configuration +nodes solana list # List all existing solana nodes +nodes starknet # Commands for handling starknet node configuration +nodes starknet list # List all existing starknet nodes txs # Commands for handling transactions txs cosmos # Commands for handling Cosmos transactions txs cosmos create # Send of from node Cosmos account to destination . diff --git a/testdata/scripts/nodes/cosmos/help.txtar b/testdata/scripts/nodes/cosmos/help.txtar index b605e676f8c..78939331b6b 100644 --- a/testdata/scripts/nodes/cosmos/help.txtar +++ b/testdata/scripts/nodes/cosmos/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes cosmos - Commands for handling Cosmos node configuration + chainlink nodes cosmos - Commands for handling cosmos node configuration USAGE: chainlink nodes cosmos command [command options] [arguments...] COMMANDS: - list List all existing Cosmos nodes + list List all existing cosmos nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/cosmos/list/help.txtar b/testdata/scripts/nodes/cosmos/list/help.txtar index c984b4f3e18..372693c704c 100644 --- a/testdata/scripts/nodes/cosmos/list/help.txtar +++ b/testdata/scripts/nodes/cosmos/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes cosmos list - List all existing Cosmos nodes + chainlink nodes cosmos list - List all existing cosmos nodes USAGE: chainlink nodes cosmos list [arguments...] diff --git a/testdata/scripts/nodes/evm/help.txtar b/testdata/scripts/nodes/evm/help.txtar index dfc13eba9aa..5e9d9d482ab 100644 --- a/testdata/scripts/nodes/evm/help.txtar +++ b/testdata/scripts/nodes/evm/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes evm - Commands for handling EVM node configuration + chainlink nodes evm - Commands for handling evm node configuration USAGE: chainlink nodes evm command [command options] [arguments...] COMMANDS: - list List all existing EVM nodes + list List all existing evm nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/evm/list/help.txtar b/testdata/scripts/nodes/evm/list/help.txtar index 62d3814823d..3ecfe32de40 100644 --- a/testdata/scripts/nodes/evm/list/help.txtar +++ b/testdata/scripts/nodes/evm/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes evm list - List all existing EVM nodes + chainlink nodes evm list - List all existing evm nodes USAGE: chainlink nodes evm list [arguments...] diff --git a/testdata/scripts/nodes/help.txtar b/testdata/scripts/nodes/help.txtar index 8a8f31f4166..f9132045d29 100644 --- a/testdata/scripts/nodes/help.txtar +++ b/testdata/scripts/nodes/help.txtar @@ -9,10 +9,11 @@ USAGE: chainlink nodes command [command options] [arguments...] COMMANDS: - evm Commands for handling EVM node configuration - cosmos Commands for handling Cosmos node configuration - solana Commands for handling Solana node configuration - starknet Commands for handling StarkNet node configuration + aptos Commands for handling aptos node configuration + cosmos Commands for handling cosmos node configuration + evm Commands for handling evm node configuration + solana Commands for handling solana node configuration + starknet Commands for handling starknet node configuration OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/solana/help.txtar b/testdata/scripts/nodes/solana/help.txtar index 8a0d3751ed7..3b10772e3e4 100644 --- a/testdata/scripts/nodes/solana/help.txtar +++ b/testdata/scripts/nodes/solana/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes solana - Commands for handling Solana node configuration + chainlink nodes solana - Commands for handling solana node configuration USAGE: chainlink nodes solana command [command options] [arguments...] COMMANDS: - list List all existing Solana nodes + list List all existing solana nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/solana/list/help.txtar b/testdata/scripts/nodes/solana/list/help.txtar index 9cb124a84d8..29787c54d94 100644 --- a/testdata/scripts/nodes/solana/list/help.txtar +++ b/testdata/scripts/nodes/solana/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes solana list - List all existing Solana nodes + chainlink nodes solana list - List all existing solana nodes USAGE: chainlink nodes solana list [arguments...] diff --git a/testdata/scripts/nodes/starknet/help.txtar b/testdata/scripts/nodes/starknet/help.txtar index bd0b11c6ec5..0ec3bc19730 100644 --- a/testdata/scripts/nodes/starknet/help.txtar +++ b/testdata/scripts/nodes/starknet/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes starknet - Commands for handling StarkNet node configuration + chainlink nodes starknet - Commands for handling starknet node configuration USAGE: chainlink nodes starknet command [command options] [arguments...] COMMANDS: - list List all existing StarkNet nodes + list List all existing starknet nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/starknet/list/help.txtar b/testdata/scripts/nodes/starknet/list/help.txtar index e38b2ff8867..b4bbdf5ae76 100644 --- a/testdata/scripts/nodes/starknet/list/help.txtar +++ b/testdata/scripts/nodes/starknet/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes starknet list - List all existing StarkNet nodes + chainlink nodes starknet list - List all existing starknet nodes USAGE: chainlink nodes starknet list [arguments...]