Skip to content

feat(ip-utils): add ipFaultDomain for peer network-block bucketing#805

Closed
vilenarios wants to merge 1 commit into
developfrom
feat/ip-fault-domain
Closed

feat(ip-utils): add ipFaultDomain for peer network-block bucketing#805
vilenarios wants to merge 1 commit into
developfrom
feat/ip-fault-domain

Conversation

@vilenarios

Copy link
Copy Markdown
Contributor

What

Pure, unwired additions to src/lib/ip-utils.ts:

  • ipFaultDomain(host, { v4Bits=24, v6Bits=48 }) — reduces a peer host to a stable "fault domain" bucket key (the network block it belongs to):
    • IPv4 (incl. IPv4-mapped IPv6) → masked to /24"a.b.c.0/24"
    • IPv6 → expanded + truncated to /48"2001:db8:abcd::/48"
    • non-IP (unresolved hostname) → returned verbatim (counts as its own domain)
  • expandIpv6(ip) — expands to 8 zero-padded hex groups (handles ::); the minimum IPv6 parsing needed for prefix bucketing (the existing isIpInCidr is IPv4-only).

Why

PR B1 of the chunk-fanout SPOF fix (design: architect-handoff/chunk-seeding-durability-plan.md §17). The five tip-*.arweave.xyz preferred chunk-POST nodes all resolve into one 38.29.227.0/24 — so today's hard CHUNK_POST_MIN_PREFERRED_SUCCESS_COUNT=2 quorum is a single-fault-domain SPOF: a tips outage hard-fails every chunk POST even when independent discovered peers accepted it.

ipFaultDomain is the primitive the upcoming B2 (soft-preferred quorum + distinct-domain counting) uses to detect that condition and route around it. The chunk-POST peer list is IP-literal after the peer manager's DNS resolution, so bucketing is in-process with no hot-path DNS.

Behavior change

None. Pure helper additions, not yet called anywhere. Ships first so B2 is a smaller, focused review.

Tests

yarn test:file src/lib/ip-utils.test.ts → 50/50 pass. New cases: the five tips collapse to one /24; distinct /24s stay distinct; IPv6 /48 bucketing (same /48 ⇒ same bucket, different ⇒ different); IPv4-mapped normalization; custom prefix widths; expandIpv6 full/compressed/malformed; unresolved-hostname fallback.

🤖 Generated with Claude Code

Adds a pure helper that reduces a peer host to a stable "fault domain" key —
the network block it belongs to — for chunk-seeding diversity accounting:

- IPv4 (incl. IPv4-mapped IPv6): masked to /24 by default -> "a.b.c.0/24"
- IPv6: expanded + truncated to /48 by default -> "2001:db8:abcd::/48"
- non-IP (unresolved hostname): returned verbatim (its own domain)

Also exports `expandIpv6` (8 zero-padded groups, handles `::`), the minimum
IPv6 parsing needed for prefix bucketing — ip-utils' existing `isIpInCidr` is
IPv4-only.

No behavior change: pure additions, not yet wired. This is PR B1 of the
chunk-fanout SPOF fix (see architect-handoff/chunk-seeding-durability-plan.md
§17): the five tip-*.arweave.xyz preferred nodes all collapse into one
38.29.227.0/24, which the upcoming soft-preferred quorum (B2) uses to detect
and route around a single-fault-domain tips outage.

Tested: the five tips collapse to one /24; distinct /24s stay distinct; IPv6
/48 bucketing; IPv4-mapped normalization; custom widths; hostname fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQPK4TXcVoXoyFp6sNW2Lr
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Two new exported functions are added to src/lib/ip-utils.ts: expandIpv6 expands an IPv6 address into eight zero-padded hex groups, and ipFaultDomain buckets IPv4, IPv6, and hostname inputs into stable network prefix keys. Tests for both functions are added to src/lib/ip-utils.test.ts.

IP Utility Additions

Layer / File(s) Summary
expandIpv6 and ipFaultDomain implementation and tests
src/lib/ip-utils.ts, src/lib/ip-utils.test.ts
expandIpv6 parses IPv6 including :: compression into 8 zero-padded groups, returning undefined on failure. ipFaultDomain normalizes IPv4-mapped IPv6, then masks IPv4 to /v4Bits (default 24) or IPv6 to /v6Bits (default 48), falling back to trimmed input for non-IP strings. Tests cover expansion edge cases, IPv4/IPv6 bucketing, custom prefix widths, and hostname passthrough.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main addition: fault-domain bucketing in ip-utils.
Description check ✅ Passed The description is directly related to the changeset and accurately describes the new helpers and tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ip-fault-domain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/lib/ip-utils.ts (1)

285-288: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Hoist the IPv4 regex to module scope.

The TSDoc describes this function as "Pure and hot-path cheap," but ipv4Segment/ipv4Regex are reconstructed via new RegExp(...) on every call. Move them to module-level constants to avoid the per-invocation allocation and recompile.

This also addresses the static-analysis ReDoS flag: the pattern is built from a fixed constant (not caller input) and is anchored with no catastrophic backtracking, so it is a false positive — hoisting makes that intent explicit.

♻️ Proposed refactor

Add module-level constants (outside the function):

+const IPV4_SEGMENT = '(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)';
+const IPV4_REGEX = new RegExp(
+  `^${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}$`,
+);

Then simplify the function body:

   const normalized = normalizeIpv4MappedIpv6(host.trim());

   // IPv4
-  const ipv4Segment = '(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)';
-  const ipv4Regex = new RegExp(
-    `^${ipv4Segment}\\.${ipv4Segment}\\.${ipv4Segment}\\.${ipv4Segment}$`,
-  );
-  if (ipv4Regex.test(normalized)) {
+  if (IPV4_REGEX.test(normalized)) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ip-utils.ts` around lines 285 - 288, The IPv4 matching logic in the
hot-path function is rebuilding the same regex on every call, which adds
avoidable allocation and compilation overhead. Hoist the fixed IPv4 pattern used
by ipv4Segment and ipv4Regex to module scope as constants, then have the
function reuse those shared constants instead of creating a new RegExp inside
the function body. This keeps the intent of the IPv4 utility clear and preserves
the anchored, fixed-pattern behavior referenced by the function’s TSDoc.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/ip-utils.ts`:
- Around line 285-288: The IPv4 matching logic in the hot-path function is
rebuilding the same regex on every call, which adds avoidable allocation and
compilation overhead. Hoist the fixed IPv4 pattern used by ipv4Segment and
ipv4Regex to module scope as constants, then have the function reuse those
shared constants instead of creating a new RegExp inside the function body. This
keeps the intent of the IPv4 utility clear and preserves the anchored,
fixed-pattern behavior referenced by the function’s TSDoc.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 561d8a7d-1571-4866-9f0d-76e25dc1eeae

📥 Commits

Reviewing files that changed from the base of the PR and between 55ef923 and c07d6db.

📒 Files selected for processing (2)
  • src/lib/ip-utils.test.ts
  • src/lib/ip-utils.ts

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.62500% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.79%. Comparing base (96ede32) to head (c07d6db).
⚠️ Report is 17 commits behind head on develop.

Files with missing lines Patch % Lines
src/lib/ip-utils.ts 90.62% 8 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #805      +/-   ##
===========================================
+ Coverage    78.65%   78.79%   +0.14%     
===========================================
  Files          132      132              
  Lines        49980    50189     +209     
  Branches      3763     3803      +40     
===========================================
+ Hits         39312    39548     +236     
+ Misses       10620    10592      -28     
- Partials        48       49       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

vilenarios added a commit that referenced this pull request Jun 30, 2026
Makes chunk fanout fault-domain-aware and removes the single-fault-domain
tips SPOF (all five tip-*.arweave.xyz resolve into one 38.29.227.0/24, so a
tips outage hard-fails every POST today). Both behaviors are opt-in and
default to the legacy quorum exactly.

- Distinct-domain counting: buckets each successful peer by IP /24 (v4) /48
  (v6) via ipFaultDomain; CHUNK_POST_MIN_DISTINCT_DOMAINS (default 0=off) adds
  a soft target among successes that degrades gracefully when the eligible
  peer set can't supply N (never hard-fails on scarcity).
- Soft-preferred fallback: CHUNK_POST_PREFERRED_SOFT_FALLBACK (default false)
  lets a strong distinct-domain discovered quorum satisfy a POST when NO tip
  was eligible (tips down/over-queue) — but never when tips merely failed,
  so tips stay the hard primary on-ranp on a real outage only.
- Centralized verdict: evaluateChunkBroadcastVerdict is the single source of
  truth for "seeded sufficiently?"; the handler reads result.succeeded instead
  of recomputing the thresholds (removes latent drift). With both flags at
  defaults it reduces EXACTLY to the legacy rule (regression test).
- Placement-evidence contract: BroadcastChunkResult now carries
  distinctDomainCount/preferredEligibleCount/succeeded, and POST /chunk emits
  X-AR-IO-Chunk-Placement-Domains — so the bundler can consume where a chunk
  landed instead of collapsing the result to pass/fail (see plan §18).
- Metrics: chunk_post_distinct_domains (histogram),
  chunk_post_preferred_shortfall_total{reason}, chunk_post_domain_shortfall_total{reason}.
  Preferred shortfall is emitted even when the fallback makes the POST succeed,
  so a tips outage is never silently masked.

Depends on #805 (ipFaultDomain). Default-off; soak before any default flip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQPK4TXcVoXoyFp6sNW2Lr
@vilenarios

Copy link
Copy Markdown
Contributor Author

Superseded by #812 — consolidated into the feature/chunk-fanout integration branch (F + B1 + B2 + dashboard as one coherent PR). All commits from this branch are included there; closing to review as a unit.

@vilenarios vilenarios closed this Jul 1, 2026
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.

1 participant