@streamr/trackerless-network 103.3.1 → 103.6.0-rc.0

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/dist/exports.cjs CHANGED
@@ -29,7 +29,7 @@ class ExternalNetworkRpc {
29
29
  }
30
30
  }
31
31
 
32
- var version = "103.3.1";
32
+ var version = "103.6.0-rc.0";
33
33
 
34
34
  // @generated message type with reflection information, may provide speed optimized methods
35
35
  class Any$Type extends runtime.MessageType {
@@ -1676,6 +1676,18 @@ class PauseNeighborRequest$Type extends runtime.MessageType {
1676
1676
  */
1677
1677
  const PauseNeighborRequest = new PauseNeighborRequest$Type();
1678
1678
  // @generated message type with reflection information, may provide speed optimized methods
1679
+ class PauseNeighborResponse$Type extends runtime.MessageType {
1680
+ constructor() {
1681
+ super("PauseNeighborResponse", [
1682
+ { no: 1, name: "accepted", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
1683
+ ]);
1684
+ }
1685
+ }
1686
+ /**
1687
+ * @generated MessageType for protobuf message PauseNeighborResponse
1688
+ */
1689
+ const PauseNeighborResponse = new PauseNeighborResponse$Type();
1690
+ // @generated message type with reflection information, may provide speed optimized methods
1679
1691
  class ResumeNeighborRequest$Type extends runtime.MessageType {
1680
1692
  constructor() {
1681
1693
  super("ResumeNeighborRequest", [
@@ -1731,7 +1743,7 @@ const NodeInfoRpc = new runtimeRpc.ServiceType("NodeInfoRpc", [
1731
1743
  * @generated ServiceType for protobuf service PlumtreeRpc
1732
1744
  */
1733
1745
  const PlumtreeRpc = new runtimeRpc.ServiceType("PlumtreeRpc", [
1734
- { name: "pauseNeighbor", options: {}, I: PauseNeighborRequest, O: Empty },
1746
+ { name: "pauseNeighbor", options: {}, I: PauseNeighborRequest, O: PauseNeighborResponse },
1735
1747
  { name: "resumeNeighbor", options: {}, I: ResumeNeighborRequest, O: Empty },
1736
1748
  { name: "sendMetadata", options: {}, I: MessageID, O: Empty }
1737
1749
  ]);
@@ -3333,14 +3345,17 @@ class PlumtreeRpcLocal {
3333
3345
  async pauseNeighbor(request, context) {
3334
3346
  const sender = dht.toNodeId(context.incomingSourceDescriptor);
3335
3347
  if (this.neighbors.has(sender)) {
3336
- this.pausedNodes.add(sender, request.messageChainId);
3348
+ const accepted = this.pausedNodes.add(sender, request.messageChainId);
3349
+ return { accepted };
3337
3350
  }
3338
- return Empty;
3351
+ return { accepted: false };
3339
3352
  }
3340
3353
  async resumeNeighbor(request, context) {
3341
3354
  const sender = context.incomingSourceDescriptor;
3342
- this.pausedNodes.delete(dht.toNodeId(sender), request.messageChainId);
3343
- await this.sendBuffer(request.fromTimestamp, request.messageChainId, sender);
3355
+ if (this.neighbors.has(dht.toNodeId(sender))) {
3356
+ this.pausedNodes.delete(dht.toNodeId(sender), request.messageChainId);
3357
+ await this.sendBuffer(request.fromTimestamp, request.messageChainId, sender);
3358
+ }
3344
3359
  return Empty;
3345
3360
  }
3346
3361
  }
@@ -3353,10 +3368,9 @@ class PlumtreeRpcRemote extends dht.RpcRemote {
3353
3368
  await this.getClient().sendMetadata(msg, options);
3354
3369
  }
3355
3370
  async pauseNeighbor(messageChainId) {
3356
- const options = this.formDhtRpcOptions({
3357
- notification: true
3358
- });
3359
- await this.getClient().pauseNeighbor({ messageChainId }, options);
3371
+ const options = this.formDhtRpcOptions();
3372
+ const response = await this.getClient().pauseNeighbor({ messageChainId }, options);
3373
+ return response.accepted;
3360
3374
  }
3361
3375
  async resumeNeighbor(fromTimestamp, messageChainId) {
3362
3376
  const options = this.formDhtRpcOptions({
@@ -3378,9 +3392,10 @@ class PausedNeighbors {
3378
3392
  this.pausedNeighbors.set(msgChainId, new Set());
3379
3393
  }
3380
3394
  if (this.pausedNeighbors.get(msgChainId).size >= this.limit) {
3381
- return;
3395
+ return false;
3382
3396
  }
3383
3397
  this.pausedNeighbors.get(msgChainId).add(node);
3398
+ return true;
3384
3399
  }
3385
3400
  delete(node, msgChainId) {
3386
3401
  this.pausedNeighbors.get(msgChainId)?.delete(node);
@@ -3413,19 +3428,24 @@ class PausedNeighbors {
3413
3428
  }
3414
3429
 
3415
3430
  const MAX_PAUSED_NEIGHBORS_DEFAULT = 3;
3431
+ const DEFAULT_RECOVERY_TIMEOUT = 500;
3432
+ const DEFAULT_RECOVERY_CHECK_INTERVAL = 200;
3433
+ const DEFAULT_RECOVERY_COOLDOWN = 2500;
3416
3434
  const logger$4 = new utils.Logger('PlumtreeManager');
3417
3435
  class PlumtreeManager extends eventemitter3.EventEmitter {
3418
3436
  neighbors;
3419
3437
  localPeerDescriptor;
3420
- // We have paused sending real data to these neighbrs and only send metadata
3421
3438
  localPausedNeighbors;
3422
- // We have asked these nodes to pause sending real data to us, used to limit sending of pausing and resuming requests
3423
3439
  remotePausedNeighbors;
3424
3440
  rpcLocal;
3425
3441
  latestMessages = new Map();
3426
3442
  rpcCommunicator;
3427
- metadataTimestampsAheadOfRealData = new Map();
3428
3443
  maxPausedNeighbors;
3444
+ recoveryState = new Map();
3445
+ recoveryCooldownUntil = new Map();
3446
+ recoveryTimeout;
3447
+ recoveryCooldown;
3448
+ abortController = new AbortController();
3429
3449
  constructor(options) {
3430
3450
  super();
3431
3451
  this.neighbors = options.neighbors;
@@ -3433,12 +3453,22 @@ class PlumtreeManager extends eventemitter3.EventEmitter {
3433
3453
  this.localPeerDescriptor = options.localPeerDescriptor;
3434
3454
  this.localPausedNeighbors = new PausedNeighbors(options.maxPausedNeighbors ?? MAX_PAUSED_NEIGHBORS_DEFAULT);
3435
3455
  this.remotePausedNeighbors = new PausedNeighbors(options.maxPausedNeighbors ?? MAX_PAUSED_NEIGHBORS_DEFAULT);
3456
+ this.recoveryTimeout = options.recoveryTimeout ?? DEFAULT_RECOVERY_TIMEOUT;
3457
+ this.recoveryCooldown = options.recoveryCooldown ?? DEFAULT_RECOVERY_COOLDOWN;
3436
3458
  this.rpcLocal = new PlumtreeRpcLocal(this.neighbors, this.localPausedNeighbors, (metadata, previousNode) => this.onMetadata(metadata, previousNode), (fromTimestamp, msgChainId, remotePeerDescriptor) => this.sendBuffer(fromTimestamp, msgChainId, remotePeerDescriptor));
3437
- this.neighbors.on('nodeRemoved', (nodeId) => this.onNeighborRemoved(nodeId));
3459
+ this.neighbors.on('nodeRemoved', this.onNeighborRemoved);
3438
3460
  this.rpcCommunicator = options.rpcCommunicator;
3439
3461
  this.rpcCommunicator.registerRpcNotification(MessageID, 'sendMetadata', (msg, context) => this.rpcLocal.sendMetadata(msg, context));
3440
- this.rpcCommunicator.registerRpcNotification(PauseNeighborRequest, 'pauseNeighbor', (msg, context) => this.rpcLocal.pauseNeighbor(msg, context));
3462
+ this.rpcCommunicator.registerRpcMethod(PauseNeighborRequest, PauseNeighborResponse, 'pauseNeighbor', (msg, context) => this.rpcLocal.pauseNeighbor(msg, context));
3441
3463
  this.rpcCommunicator.registerRpcNotification(ResumeNeighborRequest, 'resumeNeighbor', (msg, context) => this.rpcLocal.resumeNeighbor(msg, context));
3464
+ utils.setAbortableInterval(() => {
3465
+ const now = performance.now();
3466
+ for (const [chainId, state] of this.recoveryState) {
3467
+ if (now - state.metadataAheadSince >= this.recoveryTimeout && !state.resumeInProgress) {
3468
+ this.attemptRecovery(chainId, state, this.getLatestMessageTimestamp(chainId));
3469
+ }
3470
+ }
3471
+ }, options.recoveryCheckInterval ?? DEFAULT_RECOVERY_CHECK_INTERVAL, this.abortController.signal);
3442
3472
  }
3443
3473
  async pauseNeighbor(node, msgChainId) {
3444
3474
  if (this.neighbors.has(dht.toNodeId(node))
@@ -3446,8 +3476,16 @@ class PlumtreeManager extends eventemitter3.EventEmitter {
3446
3476
  && this.remotePausedNeighbors.size(msgChainId) < this.maxPausedNeighbors) {
3447
3477
  logger$4.debug(`Pausing neighbor ${dht.toNodeId(node)}`);
3448
3478
  this.remotePausedNeighbors.add(dht.toNodeId(node), msgChainId);
3449
- const remote = this.createRemote(node);
3450
- await remote.pauseNeighbor(msgChainId);
3479
+ try {
3480
+ const remote = this.createRemote(node);
3481
+ const accepted = await remote.pauseNeighbor(msgChainId);
3482
+ if (!accepted) {
3483
+ this.remotePausedNeighbors.delete(dht.toNodeId(node), msgChainId);
3484
+ }
3485
+ }
3486
+ catch (_e) {
3487
+ this.remotePausedNeighbors.delete(dht.toNodeId(node), msgChainId);
3488
+ }
3451
3489
  }
3452
3490
  }
3453
3491
  async resumeNeighbor(node, msgChainId, fromTimestamp) {
@@ -3458,9 +3496,15 @@ class PlumtreeManager extends eventemitter3.EventEmitter {
3458
3496
  await remote.resumeNeighbor(fromTimestamp, msgChainId);
3459
3497
  }
3460
3498
  }
3461
- onNeighborRemoved(nodeId) {
3499
+ onNeighborRemoved = (nodeId) => {
3462
3500
  this.localPausedNeighbors.deleteAll(nodeId);
3463
3501
  this.remotePausedNeighbors.deleteAll(nodeId);
3502
+ for (const [_chainId, state] of this.recoveryState) {
3503
+ state.candidates = state.candidates.filter((c) => dht.toNodeId(c) !== nodeId);
3504
+ if (state.lastAttemptedNode !== null && dht.toNodeId(state.lastAttemptedNode) === nodeId) {
3505
+ state.lastAttemptedNode = null;
3506
+ }
3507
+ }
3464
3508
  if (this.neighbors.size() > 0) {
3465
3509
  this.remotePausedNeighbors.forEach((pausedNeighbors, msgChainId) => {
3466
3510
  if (pausedNeighbors.size >= this.neighbors.size()) {
@@ -3470,7 +3514,7 @@ class PlumtreeManager extends eventemitter3.EventEmitter {
3470
3514
  }
3471
3515
  });
3472
3516
  }
3473
- }
3517
+ };
3474
3518
  getLatestMessageTimestamp(msgChainId) {
3475
3519
  if (!this.latestMessages.has(msgChainId) || this.latestMessages.get(msgChainId).length === 0) {
3476
3520
  return 0;
@@ -3480,22 +3524,61 @@ class PlumtreeManager extends eventemitter3.EventEmitter {
3480
3524
  async sendBuffer(fromTimestamp, msgChainId, neighbor) {
3481
3525
  const remote = new ContentDeliveryRpcRemote(this.localPeerDescriptor, neighbor, this.rpcCommunicator, ContentDeliveryRpcClient);
3482
3526
  const messages = this.latestMessages.get(msgChainId)?.filter((msg) => msg.messageId.timestamp > fromTimestamp) ?? [];
3483
- await Promise.all(messages.map((msg) => remote.sendStreamMessage(msg)));
3527
+ for (const msg of messages) {
3528
+ await remote.sendStreamMessage(msg);
3529
+ }
3484
3530
  }
3485
3531
  async onMetadata(msg, previousNode) {
3486
- // If we receive newer metadata than messages in the buffer, resume the sending neighbor
3487
- const latestMessageTimestamp = this.getLatestMessageTimestamp(msg.messageChainId);
3488
- if (latestMessageTimestamp < msg.timestamp) {
3489
- if (!this.metadataTimestampsAheadOfRealData.has(msg.messageChainId)) {
3490
- this.metadataTimestampsAheadOfRealData.set(msg.messageChainId, new Set());
3491
- }
3492
- this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).add(msg.timestamp);
3493
- if (this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).size > 1) {
3494
- await this.resumeNeighbor(previousNode, msg.messageChainId, this.getLatestMessageTimestamp(msg.messageChainId));
3495
- this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).forEach((timestamp) => {
3496
- this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).delete(timestamp);
3497
- });
3498
- }
3532
+ const latestTs = this.getLatestMessageTimestamp(msg.messageChainId);
3533
+ if (latestTs >= msg.timestamp) {
3534
+ return;
3535
+ }
3536
+ const chainId = msg.messageChainId;
3537
+ const cooldownUntil = this.recoveryCooldownUntil.get(chainId);
3538
+ if (cooldownUntil !== undefined && performance.now() < cooldownUntil) {
3539
+ return;
3540
+ }
3541
+ let state = this.recoveryState.get(chainId);
3542
+ if (!state) {
3543
+ state = {
3544
+ timestampsAhead: new Set(),
3545
+ metadataAheadSince: performance.now(),
3546
+ candidates: [],
3547
+ lastAttemptedNode: null,
3548
+ resumeInProgress: false
3549
+ };
3550
+ this.recoveryState.set(chainId, state);
3551
+ }
3552
+ state.timestampsAhead.add(msg.timestamp);
3553
+ const nodeId = dht.toNodeId(previousNode);
3554
+ const isLastAttempted = state.lastAttemptedNode !== null && dht.toNodeId(state.lastAttemptedNode) === nodeId;
3555
+ if (!isLastAttempted && !state.candidates.some((c) => dht.toNodeId(c) === nodeId)) {
3556
+ state.candidates.push(previousNode);
3557
+ }
3558
+ if (state.timestampsAhead.size > 1 && !state.resumeInProgress) {
3559
+ await this.attemptRecovery(chainId, state, latestTs);
3560
+ }
3561
+ }
3562
+ async attemptRecovery(chainId, state, latestTs) {
3563
+ const candidate = state.candidates.shift();
3564
+ if (!candidate) {
3565
+ state.metadataAheadSince = performance.now();
3566
+ return;
3567
+ }
3568
+ state.resumeInProgress = true;
3569
+ state.lastAttemptedNode = candidate;
3570
+ state.candidates = [];
3571
+ state.timestampsAhead.clear();
3572
+ state.metadataAheadSince = performance.now();
3573
+ try {
3574
+ const remote = this.createRemote(candidate);
3575
+ await remote.resumeNeighbor(latestTs, chainId);
3576
+ }
3577
+ catch (_e) {
3578
+ logger$4.debug('Recovery resume failed, will retry with next candidate');
3579
+ }
3580
+ finally {
3581
+ state.resumeInProgress = false;
3499
3582
  }
3500
3583
  }
3501
3584
  createRemote(neighbor) {
@@ -3513,8 +3596,13 @@ class PlumtreeManager extends eventemitter3.EventEmitter {
3513
3596
  this.latestMessages.get(messageChainId).shift();
3514
3597
  this.latestMessages.get(messageChainId).push(msg);
3515
3598
  }
3516
- if (this.metadataTimestampsAheadOfRealData.has(msg.messageId.messageChainId)) {
3517
- this.metadataTimestampsAheadOfRealData.get(msg.messageId.messageChainId).delete(msg.messageId.timestamp);
3599
+ const state = this.recoveryState.get(messageChainId);
3600
+ if (state) {
3601
+ if (state.lastAttemptedNode) {
3602
+ this.remotePausedNeighbors.delete(dht.toNodeId(state.lastAttemptedNode), messageChainId);
3603
+ }
3604
+ this.recoveryState.delete(messageChainId);
3605
+ this.recoveryCooldownUntil.set(messageChainId, performance.now() + this.recoveryCooldown);
3518
3606
  }
3519
3607
  this.emit('message', msg);
3520
3608
  const neighbors = this.neighbors.getAll().filter((neighbor) => dht.toNodeId(neighbor.getPeerDescriptor()) !== previousNode);
@@ -3532,7 +3620,14 @@ class PlumtreeManager extends eventemitter3.EventEmitter {
3532
3620
  return this.localPausedNeighbors.isPaused(dht.toNodeId(node), msgChainId)
3533
3621
  || this.remotePausedNeighbors.isPaused(dht.toNodeId(node), msgChainId);
3534
3622
  }
3623
+ getLocalPausedNeighbors() {
3624
+ return this.localPausedNeighbors;
3625
+ }
3626
+ getRemotePausedNeighbors() {
3627
+ return this.remotePausedNeighbors;
3628
+ }
3535
3629
  stop() {
3630
+ this.abortController.abort();
3536
3631
  this.neighbors.off('nodeRemoved', this.onNeighborRemoved);
3537
3632
  }
3538
3633
  }