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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions