Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WS URL can be optional when LogBroadcaster is disabled #14364

Open
wants to merge 37 commits into
base: develop
Choose a base branch
from

Conversation

huangzhen1997
Copy link
Contributor

@huangzhen1997 huangzhen1997 commented Sep 6, 2024

Description

WS URL can be optional (empty string) when LogBroadcaster is disabled. If WS URL was not provided, SubscribeFilterLogs should fail with an explicit error

Tickets:

BCFR-451

@huangzhen1997 huangzhen1997 marked this pull request as ready for review September 6, 2024 20:46
@huangzhen1997 huangzhen1997 requested review from a team as code owners September 6, 2024 20:46
@huangzhen1997 huangzhen1997 requested review from reductionista and dhaidashenko and removed request for a team September 6, 2024 20:46
core/chains/evm/config/toml/config.go Outdated Show resolved Hide resolved
@@ -20,7 +20,7 @@ func NewEvmClient(cfg evmconfig.NodePool, chainCfg commonclient.ChainConfig, cli
var sendonlys []commonclient.SendOnlyNode[*big.Int, RPCClient]
largePayloadRPCTimeout, defaultRPCTimeout := getRPCTimeouts(chainType)
for i, node := range nodes {
if node.SendOnly != nil && *node.SendOnly {
if node.WSURL == nil || (node.SendOnly != nil && *node.SendOnly) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A secondary or SendOnly node has limited functionality. It's only used to broadcast transactions (fire and forget), and we are not running health checks for it.
We should not change the type of the node. Instead, introduce changes to the RPC client.

Copy link
Contributor Author

@huangzhen1997 huangzhen1997 Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the enforcement check to Dial() at rpc_client.go

@@ -76,18 +85,37 @@ func NewClientConfigs(
return chainConfig, nodePoolCfg, nodes, nil
}

// verifyLogBroadcasterFlag checks node config and return error if LogBroadcaster enabled but some node missing WSURL
func verifyLogBroadcasterFlag(nodes []*toml.Node, LogBroadcasterEnabled bool) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config Builder is built for external applications like Atlas, where LogBroadcaster does not exist. The CL Node does not use it.
verifyLogBroadcasterFlag should be moved to CL Node config validation, which is located in toml pkg

Copy link
Contributor Author

@huangzhen1997 huangzhen1997 Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I moved the restriction (ws exist when logBroadcaster disabled + http exist when ws missing) to rpc_client.go.

Just making sure, we don't have to worry about the node config validation here (also here), because it's for external users right ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you've moved the config validation to the rpc_client.go, the link I've provided in my previous msg points to core/chains/evm/config/toml/config.go.
The validation must be part of the config validation step to ensure CL Node won't start with the invalid configs

@@ -174,6 +179,7 @@ func NewRPCClient(
r.tier = tier
r.ws.uri = wsuri
r.finalizedBlockPollInterval = finalizedBlockPollInterval
r.logBroadcasterEnabled = logBroadcasterEnabled
if httpuri != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RPC Client does not need to know about LogBroadcaster, this breaks abstraction

@@ -76,18 +85,37 @@ func NewClientConfigs(
return chainConfig, nodePoolCfg, nodes, nil
}

// verifyLogBroadcasterFlag checks node config and return error if LogBroadcaster enabled but some node missing WSURL
func verifyLogBroadcasterFlag(nodes []*toml.Node, LogBroadcasterEnabled bool) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you've moved the config validation to the rpc_client.go, the link I've provided in my previous msg points to core/chains/evm/config/toml/config.go.
The validation must be part of the config validation step to ensure CL Node won't start with the invalid configs

httpUrl := commonconfig.MustParseURL(*nodeCfg.HTTPURL)

if nodeCfg.HTTPURL == nil {
return nil, fmt.Errorf("node config [%d]: HTTP URL", i)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, fmt.Errorf("node config [%d]: HTTP URL", i)
return nil, fmt.Errorf("node config [%d]: missing HTTP URL", i)

Comment on lines 214 to 216
httpURL := url.URL{}
observedLggr, _ := logger.TestObserved(t, zap.DebugLevel)
rpcClient := client.NewRPCClient(observedLggr, url.URL{}, &httpURL, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could httpURL be omitted?

Suggested change
httpURL := url.URL{}
observedLggr, _ := logger.TestObserved(t, zap.DebugLevel)
rpcClient := client.NewRPCClient(observedLggr, url.URL{}, &httpURL, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
observedLggr, _ := logger.TestObserved(t, zap.DebugLevel)
rpcClient := client.NewRPCClient(observedLggr, url.URL{}, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")

Copy link
Contributor Author

@huangzhen1997 huangzhen1997 Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this specific test it can't, since Dial() will fail due to errWSAndHTTPBothMissing. I can add another test that covers when both WS and HTTPURL are empty

@@ -310,6 +313,12 @@ func (c *EVMConfig) ValidateConfig() (err error) {
if len(c.Nodes) == 0 {
err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "must have at least one node"})
} else {
if c.LogBroadcasterEnabled != nil {
if err = verifyLogBroadcasterFlag(c.Nodes, *c.LogBroadcasterEnabled); err != nil {
err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "must have at least one ws uri when LogBroadcaster is enabled"})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "must have at least one ws uri when LogBroadcaster is enabled"})
err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "must have at least one node with a WSURL when LogBroadcaster is enabled"})

Comment on lines 1335 to 1336
- logbroadcaster cannot be enabled unless all nodes have WSURL provided
- Nodes: missing: must have at least one ws uri when LogBroadcaster is enabled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem contradictory. Must all nodes have WSURL, or just one?

Copy link
Contributor Author

@huangzhen1997 huangzhen1997 Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching it. According to the requirement, I was intended to enforce Must all nodes have WSURL when LogBroadcaster is enabled, so the second error message is incorrect. But I will double check if the requirement is correct, as it might not backward compatible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the error message. We going to require all primary nodes to provide valid WS URL when LogBroadcaster is enabled.

Copy link
Collaborator

@dhaidashenko dhaidashenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you try starting the CL node locally without WS URL? It seems like in the current version, it should fail with nil pointer deference. Due to this

"chainlink": minor
---

Make websocket URL flag `WSURL` for `EVM.Nodes`, and apply logic so that:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Make websocket URL flag `WSURL` for `EVM.Nodes`, and apply logic so that:
Make websocket URL `WSURL` for `EVM.Nodes` optional, and apply logic so that:

if nodeCfg.WSURL == nil || nodeCfg.HTTPURL == nil {
return nil, fmt.Errorf("node config [%d]: missing WS or HTTP URL", i)
var wsURL, httpURL *commonconfig.URL
// wsUrl is optional, if LogBroadcaster is enabled, at least one wsURL is required and this will be checked in EVMConfig validation
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogBroadcaster is not relevant to the context of evm/client. It's one of many users, we should not reference it here.

lggr := r.rpcLog.With("wsuri", r.ws.uri.Redacted())
if r.http != nil {
lggr = lggr.With("httpuri", r.http.uri.Redacted())
if r.ws.uri.String() == "" && r.http == nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WS is optional. Can't we make it a *url.URL instead of url.URL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can, but it requires a lot refactor and adding nil ptr check and not sure if we want to do in this PR

r.ws.rpc = wsrpc
r.ws.geth = ethclient.NewClient(wsrpc)
} else {
lggr = lggr.With("wsuri", "empty WS URI")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to include empty was URI into the log

@@ -45,6 +45,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) {

chainId := big.NewInt(123456)
lggr := logger.Test(t)
var missingBothWSAndHTTPErr = errors.New("cannot dial rpc client when both ws and http info are missing")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try minimizing the lifespan of a variable. This one is only used in a single test case. There is no need to define it globally for the testing function.

require.Nil(t, rpcClient.Dial(ctx))

_, err := rpcClient.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log))
require.Error(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you ensure that we return proper error msg?

@@ -75,6 +75,8 @@ var (
float64(8 * time.Second),
},
}, []string{"evmChainID", "nodeName", "rpcHost", "isSendOnly", "success", "rpcCallName"})
errSubscribeFilterLogsNotAllowedWithoutWS = errors.New("SubscribeFilterLogs is not allowed without ws url")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we reusing these variables across multiple functions? If not, it might make sense to define them inline or make them public and use in tests

hasPrimary = true
break

if !hasPrimary {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if statement is redundant as it's fine to set hasPrimary = true if it's already true


// if the node is a primary node, then the WS URL is required when LogBroadcaster is enabled
if logBroadcasterEnabled && (n.WSURL == nil || n.WSURL.IsZero()) {
err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "all primary nodes must provide a valid WSURL when LogBroadcaster is enabled"})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to specify failed node index

@cl-sonarqube-production
Copy link

Quality Gate failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube

Catch issues before they fail your Quality Gate with our IDE extension SonarLint SonarLint

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants