@decentnetwork/peer 0.1.117 → 0.1.119

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 +92 -17
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -32,7 +32,9 @@ const TURN_RELAY_SERVERS = [
32
32
  { host: "tokyo.fi.chat", port: 3478, username: "allcom", password: "allcompass" }
33
33
  ];
34
34
  const ANNOUNCE_WAIT_TIMEOUT_MS = readEnvInt("DECENT_ANNOUNCE_WAIT_TIMEOUT_MS", 4000);
35
- const MAX_FRIEND_ROUTE_ATTEMPTS = readEnvInt("DECENT_FRIEND_ROUTE_MAX_ATTEMPTS", 12);
35
+ // 24 (was 12): with distance-ordered walking a lookup needs headroom to
36
+ // recurse past a first wave of dead close-in-XOR-space entries.
37
+ const MAX_FRIEND_ROUTE_ATTEMPTS = readEnvInt("DECENT_FRIEND_ROUTE_MAX_ATTEMPTS", 24);
36
38
  // How many in-flight announce step1 requests to fan out at once. Toxcore
37
39
  // pipelines all 12 of its onion clients in parallel; we batch up to this
38
40
  // many concurrent #sendAnnounceAndWait calls so the worst-case walk drops
@@ -2886,9 +2888,53 @@ export class Peer {
2886
2888
  const searchKey = createEphemeralKeyPair();
2887
2889
  const routes = [];
2888
2890
  const routeSeen = new Set();
2891
+ // The friend's announce lives on the ~8 nodes XOR-CLOSEST to their key
2892
+ // (toxcore onion_client). The old queue order (nodeScore) never converged
2893
+ // toward those nodes in our mostly-NAT'd table — 12 attempts burned on
2894
+ // responsive-but-far nodes, so lookups for native friends returned 0
2895
+ // routes and DHTPK could never be delivered. Sort by XOR distance to the
2896
+ // target; per-wave we still guarantee a couple of bootstraps so at least
2897
+ // one responder returns closer nodes to recurse on.
2898
+ const pkCache = new Map();
2899
+ const nodePkBytes = (node) => {
2900
+ if (!node.pk)
2901
+ return null;
2902
+ let v = pkCache.get(node.pk);
2903
+ if (v === undefined) {
2904
+ try {
2905
+ const b = base58ToBytes(node.pk);
2906
+ v = b.length === 32 ? b : null;
2907
+ }
2908
+ catch {
2909
+ v = null;
2910
+ }
2911
+ pkCache.set(node.pk, v);
2912
+ }
2913
+ return v;
2914
+ };
2915
+ const xorCloser = (a, b) => {
2916
+ for (let i = 0; i < 32; i++) {
2917
+ const da = a[i] ^ friendPublicKey[i];
2918
+ const db = b[i] ^ friendPublicKey[i];
2919
+ if (da !== db)
2920
+ return da - db;
2921
+ }
2922
+ return 0;
2923
+ };
2924
+ const byDistance = (x, y) => {
2925
+ const px = nodePkBytes(x);
2926
+ const py = nodePkBytes(y);
2927
+ if (!px || !py)
2928
+ return px ? -1 : py ? 1 : 0;
2929
+ const c = xorCloser(px, py);
2930
+ if (c !== 0)
2931
+ return c;
2932
+ return this.#nodeScore(`${y.host}:${y.port}`) - this.#nodeScore(`${x.host}:${x.port}`);
2933
+ };
2934
+ const bootstrapIdSet = new Set(this.#opts.bootstrapNodes.map((n) => `${n.host}:${n.port}`));
2889
2935
  const queue = dedupeNodes(this.#knownNodes.length > 0 ? this.#knownNodes : this.#opts.bootstrapNodes)
2890
2936
  .filter((n) => !this.#isNodeBlacklisted(`${n.host}:${n.port}`))
2891
- .sort((a, b) => this.#nodeScore(`${b.host}:${b.port}`) - this.#nodeScore(`${a.host}:${a.port}`));
2937
+ .sort(byDistance);
2892
2938
  const friendId = carrierIdFromPublicKey(friendPublicKey);
2893
2939
  const visited = new Set();
2894
2940
  let attempts = 0;
@@ -2903,25 +2949,32 @@ export class Peer {
2903
2949
  // 4-10s parallel waves).
2904
2950
  while (queue.length > 0 && attempts < maxAttempts && routes.length < 8) {
2905
2951
  const batch = [];
2906
- while (queue.length > 0 && batch.length < FRIEND_ROUTE_BATCH_SIZE && attempts < maxAttempts) {
2907
- const node = queue.shift();
2952
+ const takeInto = (node) => {
2908
2953
  const nodeId = `${node.host}:${node.port}`;
2909
- if (visited.has(nodeId) || !node.pk) {
2910
- continue;
2954
+ if (visited.has(nodeId))
2955
+ return false;
2956
+ const nodePk = nodePkBytes(node);
2957
+ if (!nodePk) {
2958
+ visited.add(nodeId);
2959
+ return false;
2911
2960
  }
2912
2961
  visited.add(nodeId);
2913
- let nodePk;
2914
- try {
2915
- nodePk = base58ToBytes(node.pk);
2916
- }
2917
- catch {
2918
- continue;
2919
- }
2920
- if (nodePk.length !== 32) {
2921
- continue;
2922
- }
2923
2962
  attempts += 1;
2924
2963
  batch.push({ node, nodePk, sendBack: randomBytes(8) });
2964
+ return true;
2965
+ };
2966
+ if (attempts === 0) {
2967
+ let bootstrapsTaken = 0;
2968
+ for (const node of queue) {
2969
+ if (bootstrapsTaken >= 2 || attempts >= maxAttempts)
2970
+ break;
2971
+ if (bootstrapIdSet.has(`${node.host}:${node.port}`) && takeInto(node))
2972
+ bootstrapsTaken++;
2973
+ }
2974
+ }
2975
+ queue.sort(byDistance);
2976
+ while (queue.length > 0 && batch.length < FRIEND_ROUTE_BATCH_SIZE && attempts < maxAttempts) {
2977
+ takeInto(queue.shift());
2925
2978
  }
2926
2979
  if (batch.length === 0) {
2927
2980
  break;
@@ -3110,8 +3163,30 @@ export class Peer {
3110
3163
  return storedNodes;
3111
3164
  }
3112
3165
  waves += 1;
3113
- // Pull the closest unvisited candidates for this wave.
3166
+ // Pull the closest unvisited candidates for this wave. Wave 1 reserves
3167
+ // slots for the BOOTSTRAP nodes: XOR-closest entries in our table are
3168
+ // mostly dead NAT'd peers, so a purely distance-ordered walk burned its
3169
+ // whole deadline before ever asking the only guaranteed-public,
3170
+ // always-on nodes. Announce storage capacity is large (toxcore keeps
3171
+ // ~160 entries, evicting furthest), so in this small network the fleet
3172
+ // WILL store us when asked — and every peer's lookup walk reaches the
3173
+ // fleet, so an announce stored there is universally findable.
3114
3174
  const wave = [];
3175
+ if (waves === 1) {
3176
+ const bootstrapIdSet = new Set(this.#opts.bootstrapNodes.map((n) => `${n.host}:${n.port}`));
3177
+ for (let qi = 0; qi < queue.length && wave.length < 4;) {
3178
+ const c = queue[qi];
3179
+ const id = `${c.node.host}:${c.node.port}`;
3180
+ if (bootstrapIdSet.has(id) && !visited.has(id)) {
3181
+ visited.add(id);
3182
+ wave.push(c);
3183
+ queue.splice(qi, 1);
3184
+ }
3185
+ else {
3186
+ qi++;
3187
+ }
3188
+ }
3189
+ }
3115
3190
  while (queue.length > 0 && wave.length < SELF_ANNOUNCE_BATCH_SIZE) {
3116
3191
  const c = queue.shift();
3117
3192
  const id = `${c.node.host}:${c.node.port}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.117",
3
+ "version": "0.1.119",
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",