Skip to content

fix(chainlink): deterministic ordering for /nodes pagination#22459

Open
ozpool wants to merge 2 commits into
smartcontractkit:developfrom
ozpool:fix/predictable-node-list-order-18263
Open

fix(chainlink): deterministic ordering for /nodes pagination#22459
ozpool wants to merge 2 commits into
smartcontractkit:developfrom
ozpool:fix/predictable-node-list-order-18263

Conversation

@ozpool
Copy link
Copy Markdown

@ozpool ozpool commented May 14, 2026

Problem

The Nodes tab in the operator UI reorders on every render. With more than 100 nodes configured (now routine for CCIP), the same chain's nodes flip across pages between refreshes, making the page unusable for locating a specific node by sight.

The root cause is in (*CoreRelayerChainInteroperators).NodeStatuses: per-relayer node ordering is whatever the underlying chain implementation returns, and several relayer implementations build the list from map iteration. The outer relayer keys are already sorted by (Network, ChainID), but the aggregated []types.NodeStatus slice has no stable order, so pagination is non-deterministic.

Fix

Extract a package-private sortNodeStatuses helper and apply it in NodeStatuses right after aggregation, before the offset/size slicing happens. The sort key is (ChainID, Name) using slices.SortStableFunc so equal keys (e.g. dev fixtures or multi-relayer dummy chains) keep their input order and the result is fully reproducible.

Test

relayer_chain_interoperators_internal_test.go covers:

  • Shuffled input within a single chain comes out lexicographic by Name.
  • Nodes are grouped across chains, with chain IDs in lexicographic order.
  • Stability: pre-sorted equal-key pairs keep their original order.

The test lives in an internal _test.go so it can exercise the helper without standing up the full loop.Relayer interface.

Fixes #18263

ozpool added 2 commits May 13, 2026 13:47
Fixes smartcontractkit#18263. The Nodes tab reorders on every render because the
aggregated NodeStatuses() slice inherits whatever per-relayer order
the underlying chain returned, which is map-iteration order for
several implementations. With more than 100 nodes (now the case for
CCIP) the same chain's nodes flip across pages between refreshes,
making the page unusable for finding a specific node.

The outer relayer keys are already sorted by (Network, ChainID), so
the missing layer is a stable sort of the aggregated NodeStatus
slice by (ChainID, Name). slices.SortStableFunc preserves input
order on equal keys so dev-fixture duplicates or multi-relayer
dummy chains stay reproducible too.

Extract the sort to a small `sortNodeStatuses` helper and add unit
coverage that asserts both the ordering (shuffled within a chain,
grouped across chains, lexicographic chain ids) and the stability
property. The helper is package-private and the test lives in a
new internal `_test.go` file so the full loop.Relayer interface
does not need to be stubbed just to verify the sort.
@ozpool ozpool requested review from a team as code owners May 14, 2026 06:27
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.

[FEAT] Predictable ordering of nodes tab

1 participant