Skip to content

Commit 07ce597

Browse files
Apply MemoryNodeStore strip / dedup validation to set_nodes
Cycle 21 added strip / dedup / empty-rejection validation at MemoryNodeStore.__init__ so a typoed seed couldn't leak whitespace into find_leader. The runtime updater set_nodes() bypassed the same validation; a control-plane source feeding NodeInfo with whitespace addresses re-introduced the exact defect __init__ was hardened against. Apply the same loop, rebuilding NodeInfo with the canonical address when whitespace was present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d919f64 commit 07ce597

1 file changed

Lines changed: 31 additions & 2 deletions

File tree

src/dqliteclient/node_store.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,34 @@ async def get_nodes(self) -> Sequence[NodeInfo]:
132132
return self._nodes
133133

134134
async def set_nodes(self, nodes: Sequence[NodeInfo]) -> None:
135-
"""Update list of known nodes."""
136-
self._nodes = tuple(nodes)
135+
"""Update list of known nodes.
136+
137+
Mirrors the strip / dedup / empty-rejection validation done
138+
in ``__init__``. Without this, a runtime update with a
139+
whitespace-laden or duplicated address would leak through
140+
and surface deep inside ``find_leader`` (whitespace) or
141+
inflate the probe count and per-node error lines
142+
(duplicates).
143+
"""
144+
seen: set[str] = set()
145+
unique: list[NodeInfo] = []
146+
for node in nodes:
147+
if not isinstance(node.address, str):
148+
raise TypeError(
149+
f"NodeInfo.address must be 'host:port' string, got "
150+
f"{type(node.address).__name__}"
151+
)
152+
addr = node.address.strip()
153+
if not addr:
154+
raise ValueError("NodeInfo.address must be a non-empty 'host:port' string")
155+
if addr in seen:
156+
continue
157+
seen.add(addr)
158+
if addr is node.address:
159+
unique.append(node)
160+
else:
161+
# NodeInfo is a frozen dataclass; rebuild with the
162+
# canonical (stripped) address so a downstream lookup
163+
# by-address matches the canonical form.
164+
unique.append(NodeInfo(node_id=node.node_id, address=addr, role=node.role))
165+
self._nodes = tuple(unique)

0 commit comments

Comments
 (0)