@decentnetwork/peer 0.1.128 → 0.1.129

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.
package/README.md CHANGED
@@ -95,3 +95,47 @@ is your identity — back it up.
95
95
  ## License
96
96
 
97
97
  GPL-3.0-or-later. Same as upstream toxcore.
98
+
99
+ ## Tuning peer discovery
100
+
101
+ Discovery is the loop that finds a friend's current UDP endpoint when there is
102
+ no live session. It costs real CPU — every onion request is three X25519
103
+ scalar multiplications, one per layer — so the defaults are a compromise, and
104
+ a node with unusual friend counts or hardware may want to move them.
105
+
106
+ All are environment variables read at startup.
107
+
108
+ | Axis | Variable | Default | What it controls |
109
+ |---|---|---|---|
110
+ | **Interval** | `DECENT_DHT_PK_ANNOUNCE_COOLDOWN_MS` | `25000` | Minimum gap between lookups for the same friend |
111
+ | **Backoff cap** | `DECENT_ONION_LOOKUP_MAX_BACKOFF_MS` | `120000` | Ceiling once a friend keeps failing to resolve |
112
+ | **Breadth** | `DECENT_FRIEND_ROUTE_MAX_ATTEMPTS` | `24` | Nodes queried per sweep |
113
+ | **Depth** | `DECENT_ONION_DATA_ATTEMPTS` | `5` | Retries per onion data packet |
114
+ | Self-announce interval | `DECENT_SELF_ANNOUNCE_INTERVAL_MS` | `20000` | How often we re-announce ourselves |
115
+ | Self-announce breadth | `DECENT_SELF_ANNOUNCE_TARGETS` | `16` | Nodes we announce to |
116
+
117
+ ### Which knob to turn
118
+
119
+ **Turn the interval, not the breadth.** Cost is breadth x frequency, so both
120
+ reduce CPU — but they fail differently. Narrowing breadth was tried and
121
+ reverted: iOS peers appear in short windows and are only caught by a wide
122
+ sweep, so a narrow one loses them outright rather than finding them late.
123
+ Lengthening the interval degrades gracefully — the worst case is simply
124
+ `ONION_LOOKUP_MAX_BACKOFF_MS`, the longest you can go without noticing a peer
125
+ that came back.
126
+
127
+ Read the backoff cap as exactly that: **the worst-case time to notice a friend
128
+ returning.** Raising it to save CPU is a direct trade against how quickly the
129
+ node reacts.
130
+
131
+ ### Symptoms and responses
132
+
133
+ - **High steady CPU with offline friends.** Expected: each unreachable friend
134
+ is swept on its own schedule, so cost scales with how many friends are *not*
135
+ there. Raise `DECENT_ONION_LOOKUP_MAX_BACKOFF_MS`. Measured on a 2-core box
136
+ with ~5 unreachable friends, a flat 25s interval held the daemon at ~22% of
137
+ a core purely on lookups.
138
+ - **A peer is online but never seen.** Lower the interval, or raise breadth if
139
+ it was lowered. Do not lower breadth further.
140
+ - **Small or battery-powered device.** Raise the interval and the backoff cap
141
+ together; leave breadth alone.
@@ -70,12 +70,31 @@ export declare class TcpRelayPool extends EventEmitter {
70
70
  /** Diagnostics. */
71
71
  connectedCount(): number;
72
72
  /**
73
- * Send an onion request (createOnionRequest0Tcp packet) through up to `fanout`
74
- * connected relays. Each relay forwards it to node B over UDP and routes the
75
- * onion response back over TCP (surfaced via the pool's "onionResponse"
76
- * event). Returns the number of relays the request was handed to (0 = none
77
- * connected). Fanning out across a few relays covers the case where one relay
78
- * can't reach node B.
73
+ * Send an onion request through up to `fanout` connected relays.
74
+ *
75
+ * Each relay forwards the packet to node B over UDP and routes the onion
76
+ * response back over TCP (surfaced via the pool's "onionResponse" event).
77
+ * Returns how many relays accepted it (0 = none connected).
78
+ *
79
+ * **What fanout buys.** The SAME request is handed to several relays, so it
80
+ * is redundancy, not extra reach: if one relay is unreachable, overloaded, or
81
+ * simply cannot get to node B, another copy still arrives. The peer sees one
82
+ * request either way — duplicates are dropped by the onion layer.
83
+ *
84
+ * **What it costs.** Linear in every resource this path spends: N relays
85
+ * means N times the TCP traffic and N times the work at the relay end. It
86
+ * does NOT multiply local crypto — the onion packet is built once and reused,
87
+ * so the X25519 cost is paid once regardless of fanout.
88
+ *
89
+ * **Choosing a value.** `1` is cheapest and correct on a stable link where
90
+ * the first relay is reliable; a lost request just waits for the next sweep.
91
+ * `2` (the default) is the useful middle: it covers a single dead relay,
92
+ * which is the common failure, at double the traffic on a path that is
93
+ * already small. Above 3 the added redundancy is mostly wasted — you are
94
+ * paying for the case where three independent relays fail at once, which in
95
+ * practice means the network is down, not the relays.
96
+ *
97
+ * Override with `DECENT_ONION_RELAY_FANOUT`.
79
98
  */
80
99
  sendOnionRequest(packet: Uint8Array, fanout?: number): number;
81
100
  /**
@@ -22,6 +22,14 @@
22
22
  import { EventEmitter } from "node:events";
23
23
  import { base58ToBytes } from "../utils/base58.js";
24
24
  import { TcpRelayClient } from "./tcp-relay.js";
25
+ /** How many connected relays each onion request is duplicated across.
26
+ *
27
+ * Redundancy against a single dead relay, at a linear cost in TCP traffic.
28
+ * See {@link TcpRelayPool.sendOnionRequest} for how to choose a value. */
29
+ const ONION_RELAY_FANOUT = (() => {
30
+ const raw = Number(process.env.DECENT_ONION_RELAY_FANOUT);
31
+ return Number.isInteger(raw) && raw >= 1 && raw <= 8 ? raw : 2;
32
+ })();
25
33
  const KEY_SIZE = 32;
26
34
  // Connect to (up to) this many bootstrap nodes as persistent TCP relays. It was
27
35
  // 3 — but the pool picks the FIRST N in order, so a peer only met friends whose
@@ -212,14 +220,33 @@ export class TcpRelayPool extends EventEmitter {
212
220
  return this.#relays.filter((r) => r.client?.state() === "connected").length;
213
221
  }
214
222
  /**
215
- * Send an onion request (createOnionRequest0Tcp packet) through up to `fanout`
216
- * connected relays. Each relay forwards it to node B over UDP and routes the
217
- * onion response back over TCP (surfaced via the pool's "onionResponse"
218
- * event). Returns the number of relays the request was handed to (0 = none
219
- * connected). Fanning out across a few relays covers the case where one relay
220
- * can't reach node B.
223
+ * Send an onion request through up to `fanout` connected relays.
224
+ *
225
+ * Each relay forwards the packet to node B over UDP and routes the onion
226
+ * response back over TCP (surfaced via the pool's "onionResponse" event).
227
+ * Returns how many relays accepted it (0 = none connected).
228
+ *
229
+ * **What fanout buys.** The SAME request is handed to several relays, so it
230
+ * is redundancy, not extra reach: if one relay is unreachable, overloaded, or
231
+ * simply cannot get to node B, another copy still arrives. The peer sees one
232
+ * request either way — duplicates are dropped by the onion layer.
233
+ *
234
+ * **What it costs.** Linear in every resource this path spends: N relays
235
+ * means N times the TCP traffic and N times the work at the relay end. It
236
+ * does NOT multiply local crypto — the onion packet is built once and reused,
237
+ * so the X25519 cost is paid once regardless of fanout.
238
+ *
239
+ * **Choosing a value.** `1` is cheapest and correct on a stable link where
240
+ * the first relay is reliable; a lost request just waits for the next sweep.
241
+ * `2` (the default) is the useful middle: it covers a single dead relay,
242
+ * which is the common failure, at double the traffic on a path that is
243
+ * already small. Above 3 the added redundancy is mostly wasted — you are
244
+ * paying for the case where three independent relays fail at once, which in
245
+ * practice means the network is down, not the relays.
246
+ *
247
+ * Override with `DECENT_ONION_RELAY_FANOUT`.
221
248
  */
222
- sendOnionRequest(packet, fanout = 2) {
249
+ sendOnionRequest(packet, fanout = ONION_RELAY_FANOUT) {
223
250
  let sent = 0;
224
251
  for (const r of this.#relays) {
225
252
  if (sent >= fanout)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.128",
3
+ "version": "0.1.129",
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",