Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@ GasEstimator.PriceMax overrides the maximum gas price for this key. See EVM.GasE
```toml
[NodePool]
PollFailureThreshold = 5 # Default
PollSuccessThreshold = 0 # Default
PollInterval = '10s' # Default
SelectionMode = 'HighestHead' # Default
SyncThreshold = 5 # Default
Expand All @@ -958,6 +959,14 @@ PollFailureThreshold indicates how many consecutive polls must fail in order to

Set to zero to disable poll checking.

### PollSuccessThreshold
```toml
PollSuccessThreshold = 0 # Default
```
PollSuccessThreshold indicates how many consecutive polls must succeed in order to mark a node as alive once it has been marked as unreachable.

Set to zero to require no successful polls (previous behavior).
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The PollSuccessThreshold docs here have the same ambiguity as docs.toml: “Set to zero to require no successful polls” can be interpreted as immediately marking a node alive. Consider clarifying what 0 actually does (e.g., “mark alive after the first successful poll / disable the consecutive-success requirement”) so operators don’t accidentally choose an unsafe value.

Suggested change
Set to zero to require no successful polls (previous behavior).
Set to zero to disable the consecutive-success requirement, so a node marked as unreachable will be marked alive again after the first successful poll (previous behavior).

Copilot uses AI. Check for mistakes.

### PollInterval
```toml
PollInterval = '10s' # Default
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ require (
github.com/smartcontractkit/chainlink-common/keystore v1.0.2
github.com/smartcontractkit/chainlink-data-streams v0.1.12-0.20260227110503-42b236799872
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1
github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563
github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57
github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260401162955-be2bc6b5264b
github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512-ca02ad6ed16a
github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20260423135514-5b1a7565a99c
github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260423135514-5b1a7565a99c
github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260423135514-5b1a7565a99c
github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260423135514-5b1a7565a99c
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260326111235-8c09d1a4491f
github.com/smartcontractkit/chainlink-protos/svr v1.1.1-0.20260203131522-bb8bc5c423b3
github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335
Expand Down
17 changes: 8 additions & 9 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/client/config_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func NewClientConfigs(
chainType string,
nodeCfgs []NodeConfig,
pollFailureThreshold *uint32,
pollSuccessThreshold *uint32,
pollInterval time.Duration,
syncThreshold *uint32,
nodeIsSyncingEnabled *bool,
Expand All @@ -56,6 +57,7 @@ func NewClientConfigs(
SelectionMode: selectionMode,
LeaseDuration: commonconfig.MustNewDuration(leaseDuration),
PollFailureThreshold: pollFailureThreshold,
PollSuccessThreshold: pollSuccessThreshold,
PollInterval: commonconfig.MustNewDuration(pollInterval),
SyncThreshold: syncThreshold,
NodeIsSyncingEnabled: nodeIsSyncingEnabled,
Expand Down
4 changes: 3 additions & 1 deletion pkg/client/config_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestClientConfigBuilder(t *testing.T) {
selectionMode := ptr("HighestHead")
leaseDuration := 0 * time.Second
pollFailureThreshold := ptr(uint32(5))
pollSuccessThreshold := ptr(uint32(3))
pollInterval := 10 * time.Second
syncThreshold := ptr(uint32(5))
nodeIsSyncingEnabled := ptr(false)
Expand All @@ -42,7 +43,7 @@ func TestClientConfigBuilder(t *testing.T) {
noNewHeadsThreshold := time.Second
newHeadsPollInterval := 0 * time.Second
chainCfg, nodePool, nodes, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs,
pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth,
pollFailureThreshold, pollSuccessThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth,
finalityTagEnabled, SafeTagSupported, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold,
pollInterval, newHeadsPollInterval, confirmationTimeout, safeDepth)
require.NoError(t, err)
Expand All @@ -51,6 +52,7 @@ func TestClientConfigBuilder(t *testing.T) {
require.Equal(t, *selectionMode, nodePool.SelectionMode())
require.Equal(t, leaseDuration, nodePool.LeaseDuration())
require.Equal(t, *pollFailureThreshold, nodePool.PollFailureThreshold())
require.Equal(t, *pollSuccessThreshold, nodePool.PollSuccessThreshold())
require.Equal(t, pollInterval, nodePool.PollInterval())
require.Equal(t, *syncThreshold, nodePool.SyncThreshold())
require.Equal(t, *nodeIsSyncingEnabled, nodePool.NodeIsSyncingEnabled())
Expand Down
5 changes: 3 additions & 2 deletions pkg/client/evm_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func TestNewEvmClient(t *testing.T) {
selectionMode := ptr("HighestHead")
leaseDuration := 0 * time.Second
pollFailureThreshold := ptr(uint32(5))
pollSuccessThreshold := ptr(uint32(0))
pollInterval := 10 * time.Second
syncThreshold := ptr(uint32(5))
nodeIsSyncingEnabled := ptr(false)
Expand All @@ -52,7 +53,7 @@ func TestNewEvmClient(t *testing.T) {
finalityTagEnabled := ptr(true)
SafeTagSupported := ptr(true)
chainCfg, nodePool, nodes, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs,
pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth,
pollFailureThreshold, pollSuccessThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth,
finalityTagEnabled, SafeTagSupported, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold,
finalizedBlockPollInterval, newHeadsPollInterval, confirmationTimeout, safeDepth)
require.NoError(t, err)
Expand All @@ -79,7 +80,7 @@ func TestChainClientMetrics(t *testing.T) {
},
}
chainCfg, nodePool, nodes, err := client.NewClientConfigs(ptr("HighestHead"), time.Duration(0), "", nodeConfigs,
ptr[uint32](5), 10*time.Second, ptr[uint32](5), ptr(false), time.Minute, ptr[uint32](5), ptr(false), ptr(false),
ptr[uint32](5), ptr[uint32](0), 10*time.Second, ptr[uint32](5), ptr(false), time.Minute, ptr[uint32](5), ptr(false), ptr(false),
ptr[uint32](5), ptr(false), 10*time.Second, 10*time.Second, 10*time.Second, 10*time.Second, 60*time.Second, ptr[uint32](10))
require.NoError(t, err)

Expand Down
2 changes: 2 additions & 0 deletions pkg/client/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func (c *TestClientErrors) MissingBlocks() string { return c.missingBl

type TestNodePoolConfig struct {
NodePollFailureThreshold uint32
NodePollSuccessThreshold uint32
NodePollInterval time.Duration
NodeSelectionMode string
NodeSyncThreshold uint32
Expand All @@ -103,6 +104,7 @@ type TestNodePoolConfig struct {
}

func (tc TestNodePoolConfig) PollFailureThreshold() uint32 { return tc.NodePollFailureThreshold }
func (tc TestNodePoolConfig) PollSuccessThreshold() uint32 { return tc.NodePollSuccessThreshold }
func (tc TestNodePoolConfig) PollInterval() time.Duration { return tc.NodePollInterval }
func (tc TestNodePoolConfig) SelectionMode() string { return tc.NodeSelectionMode }
func (tc TestNodePoolConfig) SyncThreshold() uint32 { return tc.NodeSyncThreshold }
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,12 +834,12 @@ func (r *RPCClient) SendTransaction(ctx context.Context, tx *types.Transaction)
lggr := r.newRqLggr().With("tx", tx)

lggr.Debug("RPC call: evmclient.Client#SendTransaction")
start := time.Now()
if r.isChainType(chaintype.ChainTron) {
err := errors.New("SendTransaction not implemented for Tron, this should never be called")
return struct{}{}, multinode.Fatal, err
}

start := time.Now()
err := r.wrapRPCClientError(client.geth.SendTransaction(ctx, tx))
duration := time.Since(start)

Expand Down
4 changes: 4 additions & 0 deletions pkg/config/chain_scoped_node_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ func (n *NodePoolConfig) PollFailureThreshold() uint32 {
return *n.C.PollFailureThreshold
}

func (n *NodePoolConfig) PollSuccessThreshold() uint32 {
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

PollSuccessThreshold() blindly dereferences n.C.PollSuccessThreshold. Because this is a newly introduced optional config field, older in-memory configs (or external constructors) may leave it nil, causing a panic. If the intended default is “previous behavior”, consider returning 0 when the pointer is nil (similar to ExternalRequestMaxResponseSize()), or ensure validation guarantees the pointer is always set before this config is used.

Suggested change
func (n *NodePoolConfig) PollSuccessThreshold() uint32 {
func (n *NodePoolConfig) PollSuccessThreshold() uint32 {
if n.C.PollSuccessThreshold == nil {
return 0
}

Copilot uses AI. Check for mistakes.
return *n.C.PollSuccessThreshold
}

func (n *NodePoolConfig) PollInterval() time.Duration {
return n.C.PollInterval.Duration()
}
Expand Down
1 change: 1 addition & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ type Workflow interface {

type NodePool interface {
PollFailureThreshold() uint32
PollSuccessThreshold() uint32
PollInterval() time.Duration
SelectionMode() string
SyncThreshold() uint32
Expand Down
1 change: 1 addition & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ func TestNodePoolConfig(t *testing.T) {
require.Equal(t, uint32(5), cfg.EVM().NodePool().SyncThreshold())
require.Equal(t, time.Duration(10000000000), cfg.EVM().NodePool().PollInterval())
require.Equal(t, uint32(5), cfg.EVM().NodePool().PollFailureThreshold())
require.Equal(t, uint32(0), cfg.EVM().NodePool().PollSuccessThreshold())
require.False(t, cfg.EVM().NodePool().NodeIsSyncingEnabled())
require.True(t, cfg.EVM().NodePool().EnforceRepeatableRead())
require.Equal(t, time.Minute, cfg.EVM().NodePool().DeathDeclarationDelay())
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/toml/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,7 @@ func (r *ClientErrors) setFrom(f *ClientErrors) bool {

type NodePool struct {
PollFailureThreshold *uint32
PollSuccessThreshold *uint32
PollInterval *commonconfig.Duration
SelectionMode *string
SyncThreshold *uint32
Expand All @@ -1114,6 +1115,9 @@ func (p *NodePool) setFrom(f *NodePool) {
if v := f.PollFailureThreshold; v != nil {
p.PollFailureThreshold = v
}
if v := f.PollSuccessThreshold; v != nil {
p.PollSuccessThreshold = v
}
if v := f.PollInterval; v != nil {
p.PollInterval = v
}
Expand Down
1 change: 1 addition & 0 deletions pkg/config/toml/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ var fullConfig = EVMConfig{

NodePool: NodePool{
PollFailureThreshold: ptr[uint32](5),
PollSuccessThreshold: ptr[uint32](0),
PollInterval: config.MustNewDuration(time.Minute),
SelectionMode: ptr(multinode.NodeSelectionModeHighestHead),
SyncThreshold: ptr[uint32](13),
Expand Down
1 change: 1 addition & 0 deletions pkg/config/toml/defaults/Blast_Mainnet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ HistoryDepth = 300
[NodePool]
# 4 block sync time between nodes to ensure they aren't labelled unreachable too soon
PollFailureThreshold = 4
PollSuccessThreshold = 0
# polls every 4sec to check if there is a block produced, since blockRate is ~3sec
PollInterval = '4s'

Expand Down
1 change: 1 addition & 0 deletions pkg/config/toml/defaults/Blast_Sepolia.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ HistoryDepth = 300
[NodePool]
# 4 block sync time between nodes to ensure they aren't labelled unreachable too soon
PollFailureThreshold = 4
PollSuccessThreshold = 0
# polls every 4sec to check if there is a block produced, since blockRate is ~3sec
PollInterval = '4s'

Expand Down
1 change: 1 addition & 0 deletions pkg/config/toml/defaults/Ethereum_Holesky.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ FeeCapDefault = '1000 gwei'

[NodePool]
PollFailureThreshold = 2
PollSuccessThreshold = 0
PollInterval = '3s'
1 change: 1 addition & 0 deletions pkg/config/toml/defaults/Hashkey_Mainnet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ FeeCapDefault = '1000 gwei'

[NodePool]
PollFailureThreshold = 2
PollSuccessThreshold = 0
PollInterval = '8s'

[GasEstimator.DAOracle]
Expand Down
1 change: 1 addition & 0 deletions pkg/config/toml/defaults/Shibarium_Testnet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ SamplingInterval = '1s'

[NodePool]
PollFailureThreshold = 2
PollSuccessThreshold = 0
PollInterval = '3s'
SyncThreshold = 10
1 change: 1 addition & 0 deletions pkg/config/toml/defaults/fallback.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ PersistenceBatchSize = 100

[NodePool]
PollFailureThreshold = 5
PollSuccessThreshold = 0
PollInterval = '10s'
SelectionMode = 'HighestHead'
SyncThreshold = 5
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/toml/docs.toml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ GasEstimator.PriceMax = '79 gwei' # Example
#
# Set to zero to disable poll checking.
PollFailureThreshold = 5 # Default
# PollSuccessThreshold indicates how many consecutive polls must succeed in order to mark a node as alive once it has been marked as unreachable.
#
# Set to zero to require no successful polls (previous behavior).
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The description for PollSuccessThreshold is ambiguous: “Set to zero to require no successful polls” can be read as the node becoming alive immediately (without waiting for any successful poll), which likely isn’t the intent. Consider rewording to explicitly state the behavior for 0 (e.g., “mark alive after the first successful poll / disable the success-threshold check”) to avoid misconfiguration.

Suggested change
# Set to zero to require no successful polls (previous behavior).
# Set to zero to disable the success-threshold check; in this case, the node is marked alive again after the first successful poll (previous behavior).

Copilot uses AI. Check for mistakes.
PollSuccessThreshold = 0 # Default
# PollInterval controls how often to poll the node to check for liveness.
#
# Set to zero to disable poll checking.
Expand Down
1 change: 1 addition & 0 deletions pkg/config/toml/testdata/config-full.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ PriceMax = '79.228162514264337593543950335 gether'

[NodePool]
PollFailureThreshold = 5
PollSuccessThreshold = 0
PollInterval = '1m0s'
SelectionMode = 'HighestHead'
SyncThreshold = 13
Expand Down
Loading