Skip to content

Bugfix/tmosley/network scanner fixes 2 - #66

Merged
tmosleyIII merged 8 commits into
mainfrom
bugfix/tmosley/network-scanner-fixes-2
Nov 10, 2026
Merged

tmosleyIII merged 8 commits into
mainfrom
bugfix/tmosley/network-scanner-fixes-2

Conversation

@tmosleyIII

Copy link
Copy Markdown
Contributor

Previous network scanner updates pushed the scan time above our normal 5 minute threshold. This PR is to address those issues.

tmosleyIII and others added 5 commits October 3, 2026 08:27
recordConnectionAttempts persisted an observation and a candidate update
per (attempt x candidate-at-that-address), each in its own transaction.
That is fine against the data the feature was designed for -- a validator
identity plus a couple of DNS-resolved children -- but migration
1785360000000 backfilled one candidate per node ever seen, and recycled
hosting IPs accumulated thousands of dead identities each. One address
carried 31,894 candidates. A pubnet crawl reports ~1400 attempts, so the
inner loop ran ~48,000 times and issued ~96,000 serial round trips,
taking roughly fifteen minutes. Production scans went from a steady 5.0
minutes to 20+ the hour that migration ran.

The loop now only reads and mutates in memory; the writes happen
afterwards in bulk. Observations are append-only, so they go through
insert() rather than save(), which would first SELECT to decide
insert-vs-update. Candidates go through a single save() so TypeORM loads
the existing rows in one SELECT and writes inside one transaction.

A gossiped address that fails validation now skips that attempt instead
of throwing; previously one bad address cost the whole crawl's
diagnostics, since the caller only logs the error.

Pruning the backfilled rows is what fixes the current numbers (23.6 ->
6.7 min in production). This keeps a future backfill, or a genuinely
busy address, from recreating the collapse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjRxs5zPEq6Q7GatFf17VQ
SatoshiPay stopped validating in June, but their three archive hostnames
no longer resolve and every scan logged three ENOTFOUND failures against
them. Read as archive defects, they are really just a departed operator.

getHistoryArchiveUrls now requires isActive() alongside a historyUrl.

The nodes themselves stay in the trust graph deliberately.
InactiveNodesArchiver declines to archive a node still trusted by active
nodes -- 42 active validators still list each SatoshiPay node in their
quorum sets -- so archival is working as designed and will not retire
them until those operators update. The archive poller is the right place
to draw the line; the trust graph still needs the nodes to exist.

One trade-off: a node unreachable during a single crawl drops out of
archive polling for that scan, so its archive status goes stale rather
than refreshing. Stale-but-known beats a reachability error misattributed
to the archive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjRxs5zPEq6Q7GatFf17VQ
An archive Radar cannot read was reported the same way as one that is
genuinely lagging: isFullValidator went false and the UI said the archive
was behind. MoneyGram's three archives are the clearest case -- their
CloudFront WAF returns 403 to the production egress IP (159.223.128.81),
verified from inside the network-scanner container, while the identical
request with the identical User-Agent returns 200 from elsewhere. Nothing
is behind; we are blocked. Same for the SatoshiPay hostnames that no
longer resolve and the LOBSTR archives returning 410.

historyArchiveUnreachable and historyArchiveCacheMaxAge are now carried
on the node DTO, and the warning detector reports unreachable ahead of
the cache-misconfigured and behind branches. Both fields are optional in
NodeV1Schema so older payloads still validate.

This only changes how the condition is displayed. A WAF block is not
something Radar can fix in code; getting the egress IP allowlisted is the
actual remedy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjRxs5zPEq6Q7GatFf17VQ
undici reports connection-level failures as a bare "fetch failed" and
hides the reason on error.cause. Production logged the fallback to the
Rust scanner with no way to tell a refused connection from a DNS failure
from a timeout.

Flatten the cause chain into the message, with each link's code where it
has one, so the log says what actually happened.

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

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

DNS refresh races, incorrect archive-check handling, and retained cache entries remain unresolved.

Review effort: Balanced
Findings: 4 Medium severity

Open (4)
What changed in this PR

This PR aims to bring network scans back below five minutes while preserving validator measurements and improving archive diagnostics.

Changes:

  • Batch endpoint persistence, parallelize network work, cache TOML responses, and suppress recently failed gossip addresses.
  • Preserve crawl validation evidence during endpoint probes and distinguish unreachable archives.
  • Improve scan timing logs and Python analysis handling.
File Description
python-fbas-service/​app.py Run synchronous handlers outside the event loop.
packages/​shared/​test/​node.test.ts Update serialized-node expectations.
packages/​shared/​src/​node.ts Expose archive reachability status.
packages/​shared/​src/​network.ts Distinguish archive warning types.
packages/​shared/​src/​dto/​node-v1.ts Extend the node response schema.
packages/​crawler/​src/​crawler.ts Exclude suppressed gossip addresses.
packages/​crawler/​src/​crawl.ts Store suppressed addresses.
packages/​crawler/​src/​crawl-factory.ts Pass suppression into crawls.
packages/​crawler/​src/​__tests__/​crawler.test.ts Test gossip suppression.
apps/​frontend/​src/​services/​NodeWarningDetector.ts Display archive reachability warnings.
apps/​backend/​src/​network-scan/​mappers/​NodeV1DTOMapper.ts Map archive reachability.
apps/​backend/​src/​network-scan/​mappers/​__tests__/​NodeV1DTOMapper.test.ts Update mapping expectations.
apps/​backend/​src/​network-scan/​infrastructure/​database/​repositories/​TypeOrmEndpointCandidateRepository.ts Add bulk persistence operations.
apps/​backend/​src/​network-scan/​domain/​Scanner.ts Log scan-stage durations.
apps/​backend/​src/​network-scan/​domain/​node/​scan/​NodeScannerTomlStep.ts Merge probes without replacing crawl measurements.
apps/​backend/​src/​network-scan/​domain/​node/​scan/​NodeScannerCrawlStep.ts Prepare suppression and log crawl timings.
apps/​backend/​src/​network-scan/​domain/​node/​scan/​NodeScan.ts Merge probes and filter archive checks.
apps/​backend/​src/​network-scan/​domain/​node/​scan/​node-crawl/​PeerNodeToNodeMapper.ts Separate identity updates from measurements.
apps/​backend/​src/​network-scan/​domain/​node/​scan/​node-crawl/​CrawlerService.ts Forward suppressed addresses.
apps/​backend/​src/​network-scan/​domain/​node/​scan/​__tests__/​NodeScannerTomlStep.test.ts Verify probe-specific merging.
apps/​backend/​src/​network-scan/​domain/​node/​scan/​__tests__/​NodeScan.test.ts Test measurement preservation and archive filtering.
apps/​backend/​src/​network-scan/​domain/​node/​endpoint/​ValidatorEndpointCandidate.ts Assign UUIDs before persistence.
apps/​backend/​src/​network-scan/​domain/​node/​endpoint/​EndpointCandidateRepository.ts Define bulk repository methods.
apps/​backend/​src/​network-scan/​domain/​node/​endpoint/​EndpointCandidateManager.ts Batch writes and parallelize endpoint work.
apps/​backend/​src/​network-scan/​domain/​node/​endpoint/​__tests__/​EndpointCandidateManager.test.ts Test suppression and batch persistence.
apps/​backend/​src/​network-scan/​domain/​network/​scan/​TomlService.ts Cache TOML requests temporarily.
apps/​backend/​src/​network-scan/​domain/​network/​scan/​python-fbas/​PythonFbasHttpClient.ts Include underlying connection errors.
apps/​backend/​src/​network-scan/​domain/​network/​scan/​python-fbas/​PythonFbasAdapter.ts Trigger fallback when aggregated analysis fails.
apps/​backend/​src/​network-scan/​domain/​network/​scan/​__tests__/​TomlService.test.ts Test cached response reuse.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/shared/src/network.ts Resolved
@tmosleyIII
tmosleyIII temporarily deployed to obsrvr-radar-staging October 5, 2026 00:28 — with GitHub Actions Inactive
@tmosleyIII
tmosleyIII had a problem deploying to obsrvr-radar-production October 5, 2026 00:28 — with GitHub Actions Failure
@tmosleyIII
tmosleyIII deployed to obsrvr-radar-production October 5, 2026 00:44 — with GitHub Actions Active
@tmosleyIII
tmosleyIII temporarily deployed to obsrvr-radar-staging October 5, 2026 00:44 — with GitHub Actions Inactive
@tmosleyIII
tmosleyIII temporarily deployed to obsrvr-radar-staging October 5, 2026 00:44 — with GitHub Actions Inactive
@tmosleyIII
tmosleyIII deployed to obsrvr-radar-production October 5, 2026 12:50 — with GitHub Actions Active
@tmosleyIII
tmosleyIII temporarily deployed to obsrvr-radar-staging October 5, 2026 12:50 — with GitHub Actions Inactive
@tmosleyIII
tmosleyIII temporarily deployed to obsrvr-radar-staging October 5, 2026 12:51 — with GitHub Actions Inactive
@tmosleyIII
tmosleyIII merged commit 9a6416e into main Nov 10, 2026
12 checks passed
@tmosleyIII
tmosleyIII temporarily deployed to obsrvr-radar-staging November 10, 2026 10:31 — with GitHub Actions Inactive
@tmosleyIII
tmosleyIII temporarily deployed to obsrvr-radar-staging November 10, 2026 10:32 — with GitHub Actions Inactive

This branch was successfully deployed

1 active and 1 inactive deployments
obsrvr-radar-staging — e8b3a5ee Deployed Nov 10, 2026 by tmosleyIII via Destroy Staging Environment #8
obsrvr-radar-production — e8b3a5ee Deployed Oct 5, 2026 by tmosleyIII via Plan Production Environment #289
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.

2 participants