Skip to content

Node aborts (panic=abort) on Windows when started within 60 s of boot: overflow when subtracting duration from instant in saorsa-transport NAT-traversal cleanup #167

Description

@jordglob

Summary

ant-node 0.14.2 panics and aborts with overflow when subtracting duration from instant (std time.rs:445) when launched shortly after Windows boots — i.e. the recommended auto-start-service setup. Operators without SCM recovery configured lose the node on every reboot; with recovery, the second start succeeds because uptime by then exceeds the offending window (which is what we observed).

We traced the reachable call site to saorsa-transport 0.35.1 (the exact version in this repo's Cargo.lock). The same bug class hit x0xd on the same host via its parallel fork of the same code — canonical analysis and fix patterns in the companion issue: saorsa-labs/x0x#190.

Mechanism (short — full version in companion issue)

On Windows std::time::Instant counts from boot (QPC). Instant::now() - duration panics whenever duration > uptime. ant-node builds with panic = "abort", so one underflow kills the node (Windows exception 0xC0000409 / __fastfail).

Observed failure

Host Windows 10 Pro N for Workstations 19045
Binary ant-node 0.14.2 (release), PE link timestamp 2026-07-01, WinSW auto-start service, SYSTEM
Config production network, --port 12003, storage on a dedicated HDD volume
Boot 2026-07-06 17:25:11 UTC
ant-node start 17:25:28 UTC (uptime 17 s)
Abort 17:25:35 UTC (uptime 24 s)

Service stderr (the panic printed twice — two threads hit it):

The application panicked (crashed).
Message:  overflow when subtracting duration from instant
Location: /rustc/31fca3adb283cc9dfd56b49cdee9a96eb9c96ffd/library\std\src\time.rs:445

WER recorded exception 0xc0000409 at module offset 0x9e0675, fault bucket 2300527752312831529 (BEX64); WinSW logged exit code -1073740791. SCM recovery restarted the node 48 s later and that instance ran fine — consistent with the underflow window having passed. Crash minidumps exist and can be shared privately (we'd rather not attach process memory of a rewards-earning node publicly).

Call site (verified in saorsa-transport 0.35.1, as pinned by ant-node 0.14.2's Cargo.lock via saorsa-core 0.26.2)

src/connection/nat_traversal.rs:766 and :777:

fn cleanup_rate_tracker(&mut self, now: Instant) {
    let cutoff = now - self.rate_window;          // rate_window = 60 s  → panics at uptime < 60 s
    ...
fn cleanup_coordination_tracker(&mut self, now: Instant) {
    let cutoff = now - self.rate_window;

Reached via add_remote_candidate / observe_peer_address when the first NAT-traversal or OBSERVED_ADDRESS frame arrives — for a bootstrap-connected node that happens within seconds of start. This is the only reachable Instant - Duration with a >20 s window in the ant-node + saorsa-core + saorsa-transport tree (we grepped all three at the pinned versions; everything else is test code or millisecond-scale).

Worth noting: saorsa-core's own test suite contains an explicit comment about exactly this platform hazard (src/dht/core_engine.rs:3596), and upstream quinn fixed the same pattern in its loss detector via quinn PR #2436 — the codebase family knows the pattern; these two functions just missed it.

Suggested fix

// invert the comparison — only Instant − Instant, saturating since Rust 1.60 (quinn PR #2436's approach)
while let Some(&front_time) = self.candidate_rate_tracker.front() {
    if now.saturating_duration_since(front_time) > self.rate_window {
        self.candidate_rate_tracker.pop_front();
    } else { break; }
}
// or: let Some(cutoff) = now.checked_sub(self.rate_window) else { return; };

Reproduction

  1. Install ant-node as an auto-start Windows service (WinSW/sc.exe), connected to the production network.
  2. Reboot.
  3. Node aborts within ~30 s of boot (2/2 boots on our host). Configure SCM recovery and the second start succeeds — which also means the bug hides from operators who look a minute later.

We can run a boot cycle with RUST_BACKTRACE=full to pin the exact trace, and can test a patched build on the reproducing host.

Related but separate reports from the same 24 h on this node: a deterministic JoinHandle polled after completion abort in saorsa-transport's MASQUE relay teardown, and a silent whole-node stall at the UTC-midnight log rotation — filed separately: WithAutonomi/saorsa-transport#108 and #166.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions