@decentnetwork/peer 0.1.100 → 0.1.103

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.
@@ -96,6 +96,14 @@ const PACE_CYCLE_GAINS = [1.25, 0.8, 1, 1, 1, 1, 1, 1]; // steady PROBE_BW cycle
96
96
  const PACE_BW_WINDOW = 10; // rounds of delivered-rate history for the max-filter
97
97
  const PACE_SAMPLE_MS = 200; // aggregate ACKs so one scheduler-jittered packet is not a "round"
98
98
  const PACE_INIT_BPS = 120_000; // initial pace rate (~120 KB/s) before any bandwidth estimate
99
+ // A subnet-validated endpoint is not an unknown Internet/relay path. Starting
100
+ // it at 120 KB/s made a Windows ACK-scheduling wobble terminate STARTUP before
101
+ // the controller had ever offered meaningful LAN load; transfers then stayed
102
+ // around 200-500 KB/s despite a multi-MB/s physical path. 2 MB/s is a starting
103
+ // probe, not a cap: the normal feedback loop can grow above it or immediately
104
+ // drain below it on real loss/queueing. The 20 ms pacer limits the initial burst
105
+ // to ~40 KB, so this does not recreate the old 1 MB window blast.
106
+ const LAN_PACE_INIT_BPS = 2_000_000;
99
107
  const PACE_BURST_MS = 20; // token-bucket burst allowance (keeps sends smooth, not bunched)
100
108
  // A LAN receiver can coalesce a backlog of ACKs after its event loop wakes. The
101
109
  // resulting delivery sample is ACK rate, not link capacity: in practice a
@@ -111,7 +119,11 @@ const LAN_STARTUP_QUEUE_MS = 500; // unmistakable raw queue spike; smaller app-l
111
119
  const LAN_STARTUP_HARD_QUEUE_MS = 2000; // one multi-second queue sample is unsafe
112
120
  const LAN_STARTUP_QUEUE_ROUNDS = 2; // tolerate one scheduler/session-handover outlier on LAN
113
121
  const LAN_STARTUP_KEEPUP = 0.70; // delivered goodput below 70% of pace means the receiver is no longer keeping up
114
- const LAN_STARTUP_SLOW_ROUNDS = 5; // require sustained saturation across Windows scheduler/ACK jitter
122
+ // Five 200 ms samples was still only about one Windows ACK scheduling cycle in
123
+ // practice: a clean LAN (0% loss) exited STARTUP after ~3 MB and threw away a
124
+ // healthy 2.3 MB/s probe. Require a few seconds of sustained under-delivery;
125
+ // the independent queue/loss brakes still react immediately to a real overload.
126
+ const LAN_STARTUP_SLOW_ROUNDS = 15;
115
127
  // FILE ACKs are coalesced and handled by the remote JS event loop. On Windows
116
128
  // the effective ACK clock is commonly 20–120 ms even when an occasional probe
117
129
  // reports a 7 ms network RTT. Sizing cwnd from that lucky minimum made a
@@ -119,6 +131,7 @@ const LAN_STARTUP_SLOW_ROUNDS = 5; // require sustained saturation across Window
119
131
  // and falsely ended startup. Pacing remains the primary throttle, so this floor
120
132
  // only prevents the safety window from starving a validated LAN flow.
121
133
  const LAN_BDP_RTT_FLOOR_MS = 50;
134
+ const LAN_CWND_INIT_CHUNKS = Math.ceil((LAN_PACE_INIT_BPS * (LAN_BDP_RTT_FLOOR_MS / 1000) * 2) / MAX_FILE_DATA_SIZE);
122
135
  const WATCHDOG_MS = 150; // stall poll cadence / base no-progress patience
123
136
  const WATCHDOG_MAX_MS = 8000; // cap the RTT-adaptive stall patience (must exceed a slow path's RTT so acks aren't mistaken for a stall)
124
137
  const FAST_RT_MIN_MS = 40; // min gap between fast-retransmits of the same hole (≈1 RTT)
@@ -245,16 +258,19 @@ export class FileTransferManager {
245
258
  ? new Uint8Array(createHash("sha256").update(data).digest()).slice(0, FILE_ID_LENGTH)
246
259
  : randomBytes(FILE_ID_LENGTH);
247
260
  const req = encodeFileSendRequest(fileNumber, opts.kind ?? FILEKIND.DATA, BigInt(data.length), fileId, opts.name);
261
+ const lanPath = this.isLanPath(friendId);
248
262
  const st = {
249
263
  fileNumber, fileId, name: opts.name, data, size: data.length,
250
264
  acked: 0, nextSend: 0, pumping: false, started: false, req, startMs: nowMs(),
251
265
  lastProgressAcked: 0, lastAckAdvanceMs: nowMs(), lastResendMs: 0,
252
- cwnd: CWND_INIT_CHUNKS, stalls: 0, parityHighBlock: -1, lastLossMs: 0, lossEwma: 0,
266
+ cwnd: lanPath ? LAN_CWND_INIT_CHUNKS : CWND_INIT_CHUNKS,
267
+ stalls: 0, parityHighBlock: -1, lastLossMs: 0, lossEwma: 0,
253
268
  minRttMs: Infinity, srttMs: 0, probeOffset: -1, probeSentMs: 0,
254
- paceRateBps: PACE_INIT_BPS, paceTokens: 0, lastPaceMs: nowMs(),
269
+ paceRateBps: lanPath ? LAN_PACE_INIT_BPS : PACE_INIT_BPS,
270
+ paceTokens: 0, lastPaceMs: nowMs(),
255
271
  btlBwBps: 0, bwSamples: [], fullBwRounds: 0, startupQueueRounds: 0, bbrPhase: 0, roundCount: 0,
256
272
  rateMarkMs: nowMs(), rateMarkAcked: 0, lowRttCount: 0, lowRttMinMs: Infinity, lastCcLogMs: 0,
257
- lanPath: this.isLanPath(friendId),
273
+ lanPath,
258
274
  };
259
275
  this.#map(this.#sending, friendId).set(fileNumber, st);
260
276
  void this.send(friendId, PACKET_ID_FILE_SENDREQUEST, req).catch(() => undefined);
@@ -786,10 +802,15 @@ export class FileTransferManager {
786
802
  const lossMult = lanNow ? 1 : 1 / (1 - Math.min(0.5, st.lossEwma));
787
803
  if (lanNow && !st.lanPath) {
788
804
  // A transfer can begin on relay and then promote to a validated
789
- // physical-LAN endpoint. Restart STARTUP from the measured rate.
805
+ // physical-LAN endpoint. Relay samples must not pin the new LAN
806
+ // path near 120 KB/s: discard them and begin a safe LAN probe.
790
807
  st.bbrPhase = 0;
791
808
  st.fullBwRounds = 0;
792
809
  st.startupQueueRounds = 0;
810
+ st.bwSamples = [];
811
+ st.btlBwBps = 0;
812
+ st.paceRateBps = Math.max(st.paceRateBps, LAN_PACE_INIT_BPS);
813
+ st.cwnd = Math.max(st.cwnd, LAN_CWND_INIT_CHUNKS);
793
814
  }
794
815
  st.lanPath = lanNow;
795
816
  if (!lanNow) {
package/dist/peer.js CHANGED
@@ -430,7 +430,7 @@ export class Peer {
430
430
  });
431
431
  }
432
432
  this.#udp.on("datagram", this.#onDatagram);
433
- await this.#udp.start();
433
+ await this.#udp.start({ port: this.#opts.udpPort });
434
434
  // Spin up the TCP relay pool. We pass the same bootstrap node list
435
435
  // we already have — every Carrier bootstrap is also a TCP relay
436
436
  // server on the same pubkey. The pool opens up to 3 connections in
@@ -25,6 +25,12 @@ export type NetworkNode = {
25
25
  };
26
26
  export type PeerOptions = {
27
27
  keyFile: string;
28
+ /**
29
+ * Stable UDP listen port. Long-lived service peers (Dora registries,
30
+ * relays/exits) should set this so friends' cached DHT endpoints remain
31
+ * usable across process restarts. Omit or set 0 for an ephemeral port.
32
+ */
33
+ udpPort?: number;
28
34
  friendStoreFile?: string;
29
35
  bootstrapNodes: NetworkNode[];
30
36
  expressNodes?: NetworkNode[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.100",
3
+ "version": "0.1.103",
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",