Skip to content
This repository was archived by the owner on Mar 14, 2025. It is now read-only.

Commit 96b7ec5

Browse files
Feature/optional ws url (#1527)
Optional WS URL for RPCs to unblock new chain integration. Charry-picks from: smartcontractkit/chainlink#14354 smartcontractkit/chainlink#14373 smartcontractkit/chainlink#14534 smartcontractkit/chainlink#14364 smartcontractkit/chainlink#14929 --------- Co-authored-by: Joe Huang <[email protected]>
1 parent 31925b8 commit 96b7ec5

36 files changed

+695
-103
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink": patch
3+
---
4+
5+
Add config validation so it requires ws url when http polling disabled #bugfix

.changeset/happy-feet-rhyme.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"chainlink": minor
3+
---
4+
5+
This PR introduce few changes:
6+
- Add a new config option `EVM.NodePool.NewHeadsPollInterval` (0 by default indicate disabled), which is an interval for polling new block periodically using http client rather than subscribe to ws feed.
7+
- Updated new head handler for polling new head over http, and register the subscription in node lifecycle logic.
8+
- If the polling new heads is enabled, WS new heads subscription will be replaced with the new http based polling.
9+
10+
Note: There will be another PR for making WS URL optional with some extra condition.
11+
#added

.changeset/kind-numbers-melt.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"chainlink": minor
3+
---
4+
5+
Adding feature flag for `LogBroadcaster` called `LogBroadcasterEnabled`, which is `true` by default to support backwards compatibility.
6+
Adding `LogBroadcasterEnabled` allows certain chains to completely disable the `LogBroadcaster` feature, which is an old feature (getting replaced by logPoller) that only few products are using it:
7+
* OCR1 Median
8+
* *OCR2 Median when ChainReader is disabled
9+
* *pre-OCR2 Keeper
10+
* Flux Monitor
11+
* Direct RequestOCR1 Median
12+
13+
#added

.changeset/moody-rules-agree.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"chainlink": patch
3+
---
4+
5+
- register polling subscription to avoid subscription leaking when rpc client gets closed.
6+
- add a temporary special treatment for SubscribeNewHead before we replace it with SubscribeToHeads. Add a goroutine that forwards new head from poller to caller channel.
7+
- fix a deadlock in poller, by using a new lock for subs slice in rpc client.
8+
#bugfix

.changeset/silly-lies-boil.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"chainlink": minor
3+
---
4+
5+
Make websocket URL `WSURL` for `EVM.Nodes` optional, and apply logic so that:
6+
* If WS URL was not provided, SubscribeFilterLogs should fail with an explicit error
7+
* If WS URL was not provided LogBroadcaster should be disabled
8+
#nops

.github/workflows/integration-tests.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ jobs:
170170
only-new-issues: false # disabled for PRs due to unreliability
171171
args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml
172172
working-directory: ${{ matrix.project.path }}
173+
continue-on-error: true
173174

174175
build-chainlink:
175176
environment: integration
@@ -584,7 +585,7 @@ jobs:
584585
test_config_chainlink_version: ${{ inputs.evm-ref || github.sha }}
585586
test_config_selected_networks: ${{ env.SELECTED_NETWORKS }}
586587
test_config_logging_run_id: ${{ github.run_id }}
587-
test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }}
588+
test_config_logstream_log_targets: "file"
588589
test_config_test_log_collect: ${{ vars.TEST_LOG_COLLECT }}
589590
cl_repo: ${{ env.CHAINLINK_IMAGE }}
590591
cl_image_tag: ${{ inputs.evm-ref || github.sha }}${{ matrix.product.tag_suffix }}
@@ -606,7 +607,7 @@ jobs:
606607
go_coverage_src_dir: /var/tmp/go-coverage
607608
go_coverage_dest_dir: ${{ github.workspace }}/.covdata
608609
DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }}
609-
DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }}
610+
DEFAULT_LOKI_TENANT_ID: "promtail"
610611
DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push
611612
DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }}
612613
DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary"

common/client/node.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type NodeConfig interface {
4545
FinalizedBlockPollInterval() time.Duration
4646
EnforceRepeatableRead() bool
4747
DeathDeclarationDelay() time.Duration
48+
NewHeadsPollInterval() time.Duration
4849
}
4950

5051
type ChainConfig interface {
@@ -90,14 +91,14 @@ type node[
9091
services.StateMachine
9192
lfcLog logger.Logger
9293
name string
93-
id int32
94+
id int
9495
chainID CHAIN_ID
9596
nodePoolCfg NodeConfig
9697
chainCfg ChainConfig
9798
order int32
9899
chainFamily string
99100

100-
ws url.URL
101+
ws *url.URL
101102
http *url.URL
102103

103104
rpc RPC
@@ -120,10 +121,10 @@ func NewNode[
120121
nodeCfg NodeConfig,
121122
chainCfg ChainConfig,
122123
lggr logger.Logger,
123-
wsuri url.URL,
124+
wsuri *url.URL,
124125
httpuri *url.URL,
125126
name string,
126-
id int32,
127+
id int,
127128
chainID CHAIN_ID,
128129
nodeOrder int32,
129130
rpc RPC,
@@ -135,8 +136,10 @@ func NewNode[
135136
n.chainID = chainID
136137
n.nodePoolCfg = nodeCfg
137138
n.chainCfg = chainCfg
138-
n.ws = wsuri
139139
n.order = nodeOrder
140+
if wsuri != nil {
141+
n.ws = wsuri
142+
}
140143
if httpuri != nil {
141144
n.http = httpuri
142145
}
@@ -156,7 +159,10 @@ func NewNode[
156159
}
157160

158161
func (n *node[CHAIN_ID, HEAD, RPC]) String() string {
159-
s := fmt.Sprintf("(%s)%s:%s", Primary.String(), n.name, n.ws.String())
162+
s := fmt.Sprintf("(%s)%s", Primary.String(), n.name)
163+
if n.ws != nil {
164+
s = s + fmt.Sprintf(":%s", n.ws.String())
165+
}
160166
if n.http != nil {
161167
s = s + fmt.Sprintf(":%s", n.http.String())
162168
}

common/client/node_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ type testNodeConfig struct {
2020
enforceRepeatableRead bool
2121
finalizedBlockPollInterval time.Duration
2222
deathDeclarationDelay time.Duration
23+
newHeadsPollInterval time.Duration
24+
}
25+
26+
func (n testNodeConfig) NewHeadsPollInterval() time.Duration {
27+
return n.newHeadsPollInterval
2328
}
2429

2530
func (n testNodeConfig) PollFailureThreshold() uint32 {
@@ -62,10 +67,10 @@ type testNodeOpts struct {
6267
config testNodeConfig
6368
chainConfig clientMocks.ChainConfig
6469
lggr logger.Logger
65-
wsuri url.URL
70+
wsuri *url.URL
6671
httpuri *url.URL
6772
name string
68-
id int32
73+
id int
6974
chainID types.ID
7075
nodeOrder int32
7176
rpc *mockNodeClient[types.ID, Head]

core/chains/evm/client/config_builder.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func NewClientConfigs(
4343
deathDeclarationDelay time.Duration,
4444
noNewFinalizedHeadsThreshold time.Duration,
4545
finalizedBlockPollInterval time.Duration,
46-
46+
newHeadsPollInterval time.Duration,
4747
) (commonclient.ChainConfig, evmconfig.NodePool, []*toml.Node, error) {
4848
nodes, err := parseNodeConfigs(nodeCfgs)
4949
if err != nil {
@@ -59,6 +59,7 @@ func NewClientConfigs(
5959
EnforceRepeatableRead: enforceRepeatableRead,
6060
DeathDeclarationDelay: commonconfig.MustNewDuration(deathDeclarationDelay),
6161
FinalizedBlockPollInterval: commonconfig.MustNewDuration(finalizedBlockPollInterval),
62+
NewHeadsPollInterval: commonconfig.MustNewDuration(newHeadsPollInterval),
6263
}
6364
nodePoolCfg := &evmconfig.NodePoolConfig{C: nodePool}
6465
chainConfig := &evmconfig.EVMConfig{
@@ -79,15 +80,21 @@ func NewClientConfigs(
7980
func parseNodeConfigs(nodeCfgs []NodeConfig) ([]*toml.Node, error) {
8081
nodes := make([]*toml.Node, len(nodeCfgs))
8182
for i, nodeCfg := range nodeCfgs {
82-
if nodeCfg.WSURL == nil || nodeCfg.HTTPURL == nil {
83-
return nil, fmt.Errorf("node config [%d]: missing WS or HTTP URL", i)
83+
var wsURL, httpURL *commonconfig.URL
84+
// wsUrl requirement will be checked in EVMConfig validation
85+
if nodeCfg.WSURL != nil {
86+
wsURL = commonconfig.MustParseURL(*nodeCfg.WSURL)
87+
}
88+
89+
if nodeCfg.HTTPURL == nil {
90+
return nil, fmt.Errorf("node config [%d]: missing HTTP URL", i)
8491
}
85-
wsUrl := commonconfig.MustParseURL(*nodeCfg.WSURL)
86-
httpUrl := commonconfig.MustParseURL(*nodeCfg.HTTPURL)
92+
93+
httpURL = commonconfig.MustParseURL(*nodeCfg.HTTPURL)
8794
node := &toml.Node{
8895
Name: nodeCfg.Name,
89-
WSURL: wsUrl,
90-
HTTPURL: httpUrl,
96+
WSURL: wsURL,
97+
HTTPURL: httpURL,
9198
SendOnly: nodeCfg.SendOnly,
9299
Order: nodeCfg.Order,
93100
}

core/chains/evm/client/config_builder_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ func TestClientConfigBuilder(t *testing.T) {
3737
finalityDepth := ptr(uint32(10))
3838
finalityTagEnabled := ptr(true)
3939
noNewHeadsThreshold := time.Second
40+
newHeadsPollInterval := 0 * time.Second
4041
chainCfg, nodePool, nodes, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs,
4142
pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth,
42-
finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold, pollInterval)
43+
finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold,
44+
pollInterval, newHeadsPollInterval)
4345
require.NoError(t, err)
4446

4547
// Validate node pool configs
@@ -52,6 +54,7 @@ func TestClientConfigBuilder(t *testing.T) {
5254
require.Equal(t, *enforceRepeatableRead, nodePool.EnforceRepeatableRead())
5355
require.Equal(t, deathDeclarationDelay, nodePool.DeathDeclarationDelay())
5456
require.Equal(t, pollInterval, nodePool.FinalizedBlockPollInterval())
57+
require.Equal(t, newHeadsPollInterval, nodePool.NewHeadsPollInterval())
5558

5659
// Validate node configs
5760
require.Equal(t, *nodeConfigs[0].Name, *nodes[0].Name)
@@ -90,15 +93,15 @@ func TestNodeConfigs(t *testing.T) {
9093
require.Len(t, tomlNodes, len(nodeConfigs))
9194
})
9295

93-
t.Run("parsing missing ws url fails", func(t *testing.T) {
96+
t.Run("ws can be optional", func(t *testing.T) {
9497
nodeConfigs := []client.NodeConfig{
9598
{
9699
Name: ptr("foo1"),
97100
HTTPURL: ptr("http://foo1.test"),
98101
},
99102
}
100103
_, err := client.ParseTestNodeConfigs(nodeConfigs)
101-
require.Error(t, err)
104+
require.Nil(t, err)
102105
})
103106

104107
t.Run("parsing missing http url fails", func(t *testing.T) {

0 commit comments

Comments
 (0)