@decentnetwork/peer 0.1.113 → 0.1.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/peer.js +56 -16
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -340,6 +340,14 @@ export class Peer {
340
340
  * When a DHT packet later arrives from one of these sources, its sender pk
341
341
  * is that friend's CURRENT DHT key — see #refreshFriendDhtKeyFromDht. */
342
342
  #lanProbeTargets = new Map();
343
+ /** Exclusive turn-taking for OWN-HOST probe targets. Own-host targets
344
+ * (our IPs + loopback at native ports) are shared by every stranded
345
+ * friend, so concurrent probes overwrite each other's #lanProbeTargets
346
+ * mapping and the reply gets credited to whoever probed last (observed
347
+ * live: the simulator's key landed on an unrelated friend). One friend
348
+ * holds the slot at a time; per-friend candidate probes are unaffected. */
349
+ #ownHostProbeFriendId;
350
+ #ownHostProbeUntilMs = 0;
343
351
  #dhtMaintenanceTimer;
344
352
  // Per-friend last DHT-PK send time, keyed by friendId, used even when no
345
353
  // session entry exists yet so the connection loop does not flood DHT-PK
@@ -5188,6 +5196,7 @@ export class Peer {
5188
5196
  };
5189
5197
  const tried = new Set();
5190
5198
  // (b) known candidates — refresh a rotated DHT key at a live endpoint.
5199
+ // Per-friend targets: no mapping ambiguity, always allowed.
5191
5200
  for (const candidate of connectCandidates) {
5192
5201
  const key = `${candidate.host}:${candidate.port}`;
5193
5202
  if (tried.has(key))
@@ -5196,17 +5205,25 @@ export class Peer {
5196
5205
  await probe(candidate.host, candidate.port, false); // cookie already sent above
5197
5206
  }
5198
5207
  // (a)+(b) own-host targets — find a same-host peer after an IP change.
5199
- for (const host of [...getLocalIpv4Addresses(), "127.0.0.1"]) {
5200
- if (isOwnVirtualAddress(host))
5201
- continue; // never probe into the overlay
5202
- for (const port of LAN_SWEEP_PORTS) {
5203
- if (port === ourLocalPort)
5204
- continue; // our own socket
5205
- const key = `${host}:${port}`;
5206
- if (tried.has(key))
5207
- continue;
5208
- tried.add(key);
5209
- await probe(host, port, true);
5208
+ // SHARED targets: only the current slot holder probes them, so the
5209
+ // reply maps to exactly one friend. Slot rotates by expiry; the
5210
+ // cookie backoff staggers contenders so every stranded friend
5211
+ // eventually gets a turn.
5212
+ if (this.#ownHostProbeFriendId === friendId || probeNow >= this.#ownHostProbeUntilMs) {
5213
+ this.#ownHostProbeFriendId = friendId;
5214
+ this.#ownHostProbeUntilMs = probeNow + 10_000;
5215
+ for (const host of [...getLocalIpv4Addresses(), "127.0.0.1"]) {
5216
+ if (isOwnVirtualAddress(host))
5217
+ continue; // never probe into the overlay
5218
+ for (const port of LAN_SWEEP_PORTS) {
5219
+ if (port === ourLocalPort)
5220
+ continue; // our own socket
5221
+ const key = `${host}:${port}`;
5222
+ if (tried.has(key))
5223
+ continue;
5224
+ tried.add(key);
5225
+ await probe(host, port, true);
5226
+ }
5210
5227
  }
5211
5228
  }
5212
5229
  }
@@ -5237,9 +5254,18 @@ export class Peer {
5237
5254
  }
5238
5255
  catch (error) {
5239
5256
  this.#debugLog(`cookie request send failed for ${friendId}: ${error.message}`);
5257
+ // A failed send is still a failed ATTEMPT: count it so (a) backoff
5258
+ // grows instead of hammering a dead address at base cadence forever,
5259
+ // and (b) the rescue probes (gated on retryCount >= 1) still fire for
5260
+ // a friend whose sole candidate errors at the syscall level — before
5261
+ // this, such a friend never probed and could never be rescued.
5262
+ this.#cookieRetryCount.set(friendId, (this.#cookieRetryCount.get(friendId) ?? 0) + 1);
5240
5263
  session.pendingEcho = undefined;
5241
5264
  session.pendingCookiePeerDhtPublicKey = undefined;
5242
- session.cookieRequestSentMs = undefined;
5265
+ // Keep cookieRequestSentMs (set at attempt start): it is the loop's
5266
+ // `lastAttempt`. Clearing it made lastAttempt 0, so the cooldown gate
5267
+ // always passed and a friend whose sends error retried every 250ms
5268
+ // tick forever — the "cookie request send failed" log flood.
5243
5269
  return false;
5244
5270
  }
5245
5271
  }
@@ -5486,11 +5512,25 @@ export class Peer {
5486
5512
  }
5487
5513
  if (!friendId)
5488
5514
  return;
5489
- // The sender identifies as an existing DIFFERENT friend (JS peers use
5490
- // their stable real key as DHT key) — don't cross-wire.
5515
+ // The sender pk currently maps to a DIFFERENT friend. If that friend's
5516
+ // session is ESTABLISHED, the key genuinely belongs to them — don't
5517
+ // cross-wire. If it is NOT established, the mapping itself may be a
5518
+ // mis-credited earlier probe reply (shared own-host targets), and
5519
+ // refusing here would permanently lock the true owner out — steal it
5520
+ // and scrub the other friend's stale copy so routing stops confusing
5521
+ // the two. A wrong steal self-corrects: the real-key handshake cannot
5522
+ // complete against the wrong node.
5491
5523
  const byDht = this.#friendByDhtPk(senderId);
5492
- if (byDht && byDht.friendId !== friendId)
5493
- return;
5524
+ if (byDht && byDht.friendId !== friendId) {
5525
+ const otherSession = this.#friendSessions.get(byDht.friendId);
5526
+ if (otherSession?.established)
5527
+ return;
5528
+ this.#debugLog(`dht_key_steal ${senderId} from=${byDht.friendId} to=${friendId} (other unestablished)`);
5529
+ if (otherSession?.friendDhtPublicKey && Buffer.from(otherSession.friendDhtPublicKey).equals(Buffer.from(senderDhtPk))) {
5530
+ otherSession.friendDhtPublicKey = undefined;
5531
+ }
5532
+ this.#friendDhtKeys.delete(byDht.friendId);
5533
+ }
5494
5534
  if (senderId === friendId)
5495
5535
  return; // already keyed by real pk — nothing to refresh
5496
5536
  const session = this.#friendSessions.get(friendId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.113",
3
+ "version": "0.1.114",
4
4
  "description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",