mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Read the wall clock directly for Nostr traversal timestamps
The traversal clock cached a Unix millisecond value and an Instant at first use, then served every later call by advancing the cached value with the monotonic elapsed time. A monotonic clock does not tick while the host is suspended, so once a machine had slept the returned value trailed real time by the sleep duration for the rest of the process lifetime, and never re-synced. Almost everything that clock feeds is an absolute timestamp. The NIP-40 expiration tags on adverts and traversal signals, and the issuedAt and expiresAt fields of offers and answers, are all computed as now plus a TTL; the freshness and cache-pruning paths compare it against a peer-authored, signed created_at. Once the host had slept longer than signal_ttl_secs, every offer was published already expired, relays dropped it, and the initiator timed out waiting for an answer with traversal broken until a restart. Read the wall clock on every call instead. This also removes a mismatch inside the traversal failure-state map, which was written here from the cached clock but written and read from the node lifecycle with the real one. Not platform-specific: monotonic clocks exclude suspended time on Linux and Windows as well, so this affected any host that suspends. A laptop is simply where a process lives long enough across a sleep to notice. The interval-shaped consumers hold up under a clock step. A forward step, which is what a resume produces, saturates the punch start delay to zero, and the attempt's own bounds are monotonic deadlines that are unaffected. A backward step lengthens that delay and can cost one punch attempt, which retries. Early eviction from the replay window cannot admit a replay under the shipped defaults, because the freshness window a replayed offer must also satisfy is strictly narrower than the replay window itself. The added test pins the contract and fires on a host that has genuinely suspended, but it is not a regression guard for this defect: nothing reachable from a unit test can simulate a suspend, so on a machine that has not slept the old implementation passes it too. Its comment says so rather than leaving a false sense of coverage. Reported in https://github.com/jmcorgan/fips/issues/128
This commit is contained in:
@@ -13,6 +13,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nostr NAT traversal no longer breaks after the host suspends. The traversal
|
||||
clock cached a Unix timestamp once at startup and advanced it with a
|
||||
monotonic `Instant`, which does not tick while a machine is asleep, so after
|
||||
a suspend the daemon's idea of the time trailed real time by the suspend
|
||||
duration for the rest of the process lifetime. Every NIP-40 expiration it
|
||||
computed was therefore published already in the past: relays dropped the
|
||||
offers as expired, the initiator logged a signal timeout waiting for an
|
||||
answer, and traversal stayed broken until the daemon was restarted. The
|
||||
clock now reads the wall clock on every call. This is not macOS-specific,
|
||||
though a laptop that sleeps is where it is easiest to hit; any host that
|
||||
suspends or hibernates was affected. Reported in
|
||||
[#128](https://github.com/jmcorgan/fips/issues/128).
|
||||
|
||||
- `SessionDatagram` hop-limit handling now follows IP semantics. Delivery to
|
||||
the addressed node is no longer TTL-gated, and a forwarder decrements before
|
||||
deciding rather than after, so a datagram that would leave with a TTL of zero
|
||||
|
||||
Reference in New Issue
Block a user