-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Consolidate configs for the EVM chain client for easier external use (#…
…12294) * Consolidated configs for easier external use and moved the evm client out of legacyevm * Reused existing config types and created centralized models for client types * Removed deprecated client constructor * Renamed the client config builder file * Added evm client and config builder tests * Fixed linting * Reverted client type aliases
- Loading branch information
1 parent
608ea0a
commit 594a7f0
Showing
9 changed files
with
380 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package client | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
"time" | ||
|
||
"go.uber.org/multierr" | ||
|
||
commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" | ||
"github.com/smartcontractkit/chainlink/v2/common/config" | ||
evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" | ||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" | ||
) | ||
|
||
type nodeConfig struct { | ||
Name *string | ||
WSURL *string | ||
HTTPURL *string | ||
SendOnly *bool | ||
Order *int32 | ||
} | ||
|
||
// Build the configs needed to initialize the chain client | ||
// Parameters should only be basic go types to make it accessible for external users | ||
// Configs can be stored in a variety of ways | ||
func NewClientConfigs( | ||
selectionMode *string, | ||
leaseDuration time.Duration, | ||
chainType string, | ||
nodeCfgs []nodeConfig, | ||
pollFailureThreshold *uint32, | ||
pollInterval time.Duration, | ||
syncThreshold *uint32, | ||
nodeIsSyncingEnabled *bool, | ||
) (evmconfig.NodePool, []*toml.Node, config.ChainType, error) { | ||
nodes, err := parseNodeConfigs(nodeCfgs) | ||
if err != nil { | ||
return nil, nil, "", err | ||
} | ||
nodePool := toml.NodePool{ | ||
SelectionMode: selectionMode, | ||
LeaseDuration: commonconfig.MustNewDuration(leaseDuration), | ||
PollFailureThreshold: pollFailureThreshold, | ||
PollInterval: commonconfig.MustNewDuration(pollInterval), | ||
SyncThreshold: syncThreshold, | ||
NodeIsSyncingEnabled: nodeIsSyncingEnabled, | ||
} | ||
nodePoolCfg := &evmconfig.NodePoolConfig{C: nodePool} | ||
return nodePoolCfg, nodes, config.ChainType(chainType), nil | ||
} | ||
|
||
func parseNodeConfigs(nodeCfgs []nodeConfig) ([]*toml.Node, error) { | ||
nodes := make([]*toml.Node, len(nodeCfgs)) | ||
for i, nodeCfg := range nodeCfgs { | ||
if nodeCfg.WSURL == nil || nodeCfg.HTTPURL == nil { | ||
return nil, fmt.Errorf("node config [%d]: missing WS or HTTP URL", i) | ||
} | ||
wsUrl := commonconfig.MustParseURL(*nodeCfg.WSURL) | ||
httpUrl := commonconfig.MustParseURL(*nodeCfg.HTTPURL) | ||
node := &toml.Node{ | ||
Name: nodeCfg.Name, | ||
WSURL: wsUrl, | ||
HTTPURL: httpUrl, | ||
SendOnly: nodeCfg.SendOnly, | ||
Order: nodeCfg.Order, | ||
} | ||
nodes[i] = node | ||
} | ||
|
||
if err := validateNodeConfigs(nodes); err != nil { | ||
return nil, err | ||
} | ||
|
||
return nodes, nil | ||
} | ||
|
||
func validateNodeConfigs(nodes []*toml.Node) (err error) { | ||
names := commonconfig.UniqueStrings{} | ||
wsURLs := commonconfig.UniqueStrings{} | ||
httpURLs := commonconfig.UniqueStrings{} | ||
for i, node := range nodes { | ||
if nodeErr := node.ValidateConfig(); nodeErr != nil { | ||
err = multierr.Append(err, nodeErr) | ||
} | ||
if names.IsDupe(node.Name) { | ||
err = multierr.Append(err, commonconfig.NewErrDuplicate(fmt.Sprintf("Nodes.%d.Name", i), *node.Name)) | ||
} | ||
u := (*url.URL)(node.WSURL) | ||
if wsURLs.IsDupeFmt(u) { | ||
err = multierr.Append(err, commonconfig.NewErrDuplicate(fmt.Sprintf("Nodes.%d.WSURL", i), u.String())) | ||
} | ||
u = (*url.URL)(node.HTTPURL) | ||
if httpURLs.IsDupeFmt(u) { | ||
err = multierr.Append(err, commonconfig.NewErrDuplicate(fmt.Sprintf("Nodes.%d.HTTPURL", i), u.String())) | ||
} | ||
} | ||
|
||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package client_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" | ||
) | ||
|
||
func TestClientConfigBuilder(t *testing.T) { | ||
t.Parallel() | ||
|
||
selectionMode := ptr("HighestHead") | ||
leaseDuration := 0 * time.Second | ||
pollFailureThreshold := ptr(uint32(5)) | ||
pollInterval := 10 * time.Second | ||
syncThreshold := ptr(uint32(5)) | ||
nodeIsSyncingEnabled := ptr(false) | ||
chainTypeStr := "" | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo"), | ||
WSURL: ptr("ws://foo.test"), | ||
HTTPURL: ptr("http://foo.test"), | ||
}, | ||
} | ||
nodePool, nodes, chainType, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs, pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled) | ||
require.NoError(t, err) | ||
|
||
// Validate node pool configs | ||
require.Equal(t, *selectionMode, nodePool.SelectionMode()) | ||
require.Equal(t, leaseDuration, nodePool.LeaseDuration()) | ||
require.Equal(t, *pollFailureThreshold, nodePool.PollFailureThreshold()) | ||
require.Equal(t, pollInterval, nodePool.PollInterval()) | ||
require.Equal(t, *syncThreshold, nodePool.SyncThreshold()) | ||
require.Equal(t, *nodeIsSyncingEnabled, nodePool.NodeIsSyncingEnabled()) | ||
|
||
// Validate node configs | ||
require.Equal(t, *nodeConfigs[0].Name, *nodes[0].Name) | ||
require.Equal(t, *nodeConfigs[0].WSURL, (*nodes[0].WSURL).String()) | ||
require.Equal(t, *nodeConfigs[0].HTTPURL, (*nodes[0].HTTPURL).String()) | ||
|
||
// Validate chain type | ||
require.Equal(t, chainTypeStr, string(chainType)) | ||
} | ||
|
||
func TestNodeConfigs(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("parsing unique node configs succeeds", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo1.test"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
}, | ||
{ | ||
Name: ptr("foo2"), | ||
WSURL: ptr("ws://foo2.test"), | ||
HTTPURL: ptr("http://foo2.test"), | ||
}, | ||
} | ||
tomlNodes, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.NoError(t, err) | ||
require.Len(t, tomlNodes, len(nodeConfigs)) | ||
}) | ||
|
||
t.Run("parsing missing ws url fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("parsing missing http url fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo1.test"), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("parsing invalid ws url fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("http://foo1.test"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("parsing duplicate http url fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo1.test"), | ||
HTTPURL: ptr("ws://foo1.test"), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("parsing duplicate node names fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo1.test"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
}, | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo2.test"), | ||
HTTPURL: ptr("http://foo2.test"), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("parsing duplicate node ws urls fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo1.test"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
}, | ||
{ | ||
Name: ptr("foo2"), | ||
WSURL: ptr("ws://foo2.test"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("parsing duplicate node http urls fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo1.test"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
}, | ||
{ | ||
Name: ptr("foo2"), | ||
WSURL: ptr("ws://foo1.test"), | ||
HTTPURL: ptr("http://foo2.test"), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("parsing order too large fails", func(t *testing.T) { | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo1"), | ||
WSURL: ptr("ws://foo1.test"), | ||
HTTPURL: ptr("http://foo1.test"), | ||
Order: ptr(int32(101)), | ||
}, | ||
} | ||
_, err := client.ParseTestNodeConfigs(nodeConfigs) | ||
require.Error(t, err) | ||
}) | ||
} | ||
|
||
func ptr[T any](t T) *T { return &t } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package client | ||
|
||
import ( | ||
"math/big" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
commonclient "github.com/smartcontractkit/chainlink/v2/common/client" | ||
"github.com/smartcontractkit/chainlink/v2/common/config" | ||
evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" | ||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" | ||
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" | ||
) | ||
|
||
func NewEvmClient(cfg evmconfig.NodePool, noNewHeadsThreshold time.Duration, lggr logger.Logger, chainID *big.Int, chainType config.ChainType, nodes []*toml.Node) Client { | ||
var empty url.URL | ||
var primaries []commonclient.Node[*big.Int, *evmtypes.Head, RPCClient] | ||
var sendonlys []commonclient.SendOnlyNode[*big.Int, RPCClient] | ||
for i, node := range nodes { | ||
if node.SendOnly != nil && *node.SendOnly { | ||
rpc := NewRPCClient(lggr, empty, (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, | ||
commonclient.Secondary) | ||
sendonly := commonclient.NewSendOnlyNode(lggr, (url.URL)(*node.HTTPURL), | ||
*node.Name, chainID, rpc) | ||
sendonlys = append(sendonlys, sendonly) | ||
} else { | ||
rpc := NewRPCClient(lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), | ||
chainID, commonclient.Primary) | ||
primaryNode := commonclient.NewNode(cfg, noNewHeadsThreshold, | ||
lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, *node.Order, | ||
rpc, "EVM") | ||
primaries = append(primaries, primaryNode) | ||
} | ||
} | ||
return NewChainClient(lggr, cfg.SelectionMode(), cfg.LeaseDuration(), noNewHeadsThreshold, primaries, sendonlys, chainID, chainType) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package client_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" | ||
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils" | ||
"github.com/smartcontractkit/chainlink/v2/core/logger" | ||
) | ||
|
||
func TestNewEvmClient(t *testing.T) { | ||
t.Parallel() | ||
|
||
noNewHeadsThreshold := 3 * time.Minute | ||
selectionMode := ptr("HighestHead") | ||
leaseDuration := 0 * time.Second | ||
pollFailureThreshold := ptr(uint32(5)) | ||
pollInterval := 10 * time.Second | ||
syncThreshold := ptr(uint32(5)) | ||
nodeIsSyncingEnabled := ptr(false) | ||
chainTypeStr := "" | ||
nodeConfigs := []client.TestNodeConfig{ | ||
{ | ||
Name: ptr("foo"), | ||
WSURL: ptr("ws://foo.test"), | ||
HTTPURL: ptr("http://foo.test"), | ||
}, | ||
} | ||
nodePool, nodes, chainType, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs, pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled) | ||
require.NoError(t, err) | ||
|
||
client := client.NewEvmClient(nodePool, noNewHeadsThreshold, logger.TestLogger(t), testutils.FixtureChainID, chainType, nodes) | ||
require.NotNil(t, client) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.