@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.d.ts CHANGED
@@ -1084,11 +1084,26 @@ type ContentDeliveryLayerNeighborInfo = MarkRequired<ContentDeliveryLayerNeighbo
1084
1084
  type StreamPartitionInfo = ChangeFieldType<Required<StreamPartitionInfo$1>, 'contentDeliveryLayerNeighbors', ContentDeliveryLayerNeighborInfo[]>;
1085
1085
  type NodeInfo = ChangeFieldType<Required<NodeInfoResponse>, 'streamPartitions', StreamPartitionInfo[]>;
1086
1086
 
1087
+ declare class PausedNeighbors {
1088
+ private readonly pausedNeighbors;
1089
+ private readonly limit;
1090
+ constructor(limit: number);
1091
+ add(node: DhtAddress, msgChainId: string): boolean;
1092
+ delete(node: DhtAddress, msgChainId: string): void;
1093
+ deleteAll(node: DhtAddress): void;
1094
+ isPaused(node: DhtAddress, msgChainId: string): boolean;
1095
+ forEach(fn: (neighbors: Set<DhtAddress>, msgChainId: string) => void): void;
1096
+ size(msgChainId: string): number;
1097
+ }
1098
+
1087
1099
  interface Options {
1088
1100
  neighbors: NodeList;
1089
1101
  localPeerDescriptor: PeerDescriptor$1;
1090
1102
  rpcCommunicator: ListeningRpcCommunicator;
1091
1103
  maxPausedNeighbors?: number;
1104
+ recoveryTimeout?: number;
1105
+ recoveryCheckInterval?: number;
1106
+ recoveryCooldown?: number;
1092
1107
  }
1093
1108
  interface Events$3 {
1094
1109
  message: (msg: StreamMessage) => void;
@@ -1101,8 +1116,12 @@ declare class PlumtreeManager extends EventEmitter<Events$3> {
1101
1116
  private readonly rpcLocal;
1102
1117
  private readonly latestMessages;
1103
1118
  private readonly rpcCommunicator;
1104
- private readonly metadataTimestampsAheadOfRealData;
1105
1119
  private readonly maxPausedNeighbors;
1120
+ private readonly recoveryState;
1121
+ private readonly recoveryCooldownUntil;
1122
+ private readonly recoveryTimeout;
1123
+ private readonly recoveryCooldown;
1124
+ private readonly abortController;
1106
1125
  constructor(options: Options);
1107
1126
  pauseNeighbor(node: PeerDescriptor$1, msgChainId: string): Promise<void>;
1108
1127
  resumeNeighbor(node: PeerDescriptor$1, msgChainId: string, fromTimestamp: number): Promise<void>;
@@ -1110,9 +1129,12 @@ declare class PlumtreeManager extends EventEmitter<Events$3> {
1110
1129
  getLatestMessageTimestamp(msgChainId: string): number;
1111
1130
  private sendBuffer;
1112
1131
  private onMetadata;
1132
+ private attemptRecovery;
1113
1133
  private createRemote;
1114
1134
  broadcast(msg: StreamMessage, previousNode: DhtAddress): void;
1115
1135
  isNeighborPaused(node: PeerDescriptor$1, msgChainId: string): boolean;
1136
+ getLocalPausedNeighbors(): PausedNeighbors;
1137
+ getRemotePausedNeighbors(): PausedNeighbors;
1116
1138
  stop(): void;
1117
1139
  }
1118
1140
 
package/dist/exports.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ListeningRpcCommunicator, PeerDescriptor as PeerDescriptor$1, areEqualPeerDescriptors, toNodeId, RpcRemote, toDhtAddress, toDhtAddressRaw, EXISTING_CONNECTION_TIMEOUT, DhtNode } from '@streamr/dht';
2
2
  import { toProtoRpcClient } from '@streamr/proto-rpc';
3
- import { Logger, scheduleAtInterval, wait, setAbortableTimeout, toUserId, addManagedEventListener, waitForEvent, toUserIdRaw, computeSha1, MetricsContext, RateMetric, toStreamPartID, StreamPartIDUtils, until } from '@streamr/utils';
3
+ import { Logger, scheduleAtInterval, wait, setAbortableTimeout, toUserId, addManagedEventListener, waitForEvent, setAbortableInterval, toUserIdRaw, computeSha1, MetricsContext, RateMetric, toStreamPartID, StreamPartIDUtils, until } from '@streamr/utils';
4
4
  import pull from 'lodash/pull';
5
5
  import { EventEmitter } from 'eventemitter3';
6
6
  import sampleSize from 'lodash/sampleSize';
@@ -27,7 +27,7 @@ class ExternalNetworkRpc {
27
27
  }
28
28
  }
29
29
 
30
- var version = "103.3.1";
30
+ var version = "103.6.0-rc.0";
31
31
 
32
32
  // @generated message type with reflection information, may provide speed optimized methods
33
33
  class Any$Type extends MessageType {
@@ -1674,6 +1674,18 @@ class PauseNeighborRequest$Type extends MessageType {
1674
1674
  */
1675
1675
  const PauseNeighborRequest = new PauseNeighborRequest$Type();
1676
1676
  // @generated message type with reflection information, may provide speed optimized methods
1677
+ class PauseNeighborResponse$Type extends MessageType {
1678
+ constructor() {
1679
+ super("PauseNeighborResponse", [
1680
+ { no: 1, name: "accepted", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
1681
+ ]);
1682
+ }
1683
+ }
1684
+ /**
1685
+ * @generated MessageType for protobuf message PauseNeighborResponse
1686
+ */
1687
+ const PauseNeighborResponse = new PauseNeighborResponse$Type();
1688
+ // @generated message type with reflection information, may provide speed optimized methods
1677
1689
  class ResumeNeighborRequest$Type extends MessageType {
1678
1690
  constructor() {
1679
1691
  super("ResumeNeighborRequest", [
@@ -1729,7 +1741,7 @@ const NodeInfoRpc = new ServiceType("NodeInfoRpc", [
1729
1741
  * @generated ServiceType for protobuf service PlumtreeRpc
1730
1742
  */
1731
1743
  const PlumtreeRpc = new ServiceType("PlumtreeRpc", [
1732
- { name: "pauseNeighbor", options: {}, I: PauseNeighborRequest, O: Empty },
1744
+ { name: "pauseNeighbor", options: {}, I: PauseNeighborRequest, O: PauseNeighborResponse },
1733
1745
  { name: "resumeNeighbor", options: {}, I: ResumeNeighborRequest, O: Empty },
1734
1746
  { name: "sendMetadata", options: {}, I: MessageID, O: Empty }
1735
1747
  ]);
@@ -3331,14 +3343,17 @@ class PlumtreeRpcLocal {
3331
3343
  async pauseNeighbor(request, context) {
3332
3344
  const sender = toNodeId(context.incomingSourceDescriptor);
3333
3345
  if (this.neighbors.has(sender)) {
3334
- this.pausedNodes.add(sender, request.messageChainId);
3346
+ const accepted = this.pausedNodes.add(sender, request.messageChainId);
3347
+ return { accepted };
3335
3348
  }
3336
- return Empty;
3349
+ return { accepted: false };
3337
3350
  }
3338
3351
  async resumeNeighbor(request, context) {
3339
3352
  const sender = context.incomingSourceDescriptor;
3340
- this.pausedNodes.delete(toNodeId(sender), request.messageChainId);
3341
- await this.sendBuffer(request.fromTimestamp, request.messageChainId, sender);
3353
+ if (this.neighbors.has(toNodeId(sender))) {
3354
+ this.pausedNodes.delete(toNodeId(sender), request.messageChainId);
3355
+ await this.sendBuffer(request.fromTimestamp, request.messageChainId, sender);
3356
+ }
3342
3357
  return Empty;
3343
3358
  }
3344
3359
  }
@@ -3351,10 +3366,9 @@ class PlumtreeRpcRemote extends RpcRemote {
3351
3366
  await this.getClient().sendMetadata(msg, options);
3352
3367
  }
3353
3368
  async pauseNeighbor(messageChainId) {
3354
- const options = this.formDhtRpcOptions({
3355
- notification: true
3356
- });
3357
- await this.getClient().pauseNeighbor({ messageChainId }, options);
3369
+ const options = this.formDhtRpcOptions();
3370
+ const response = await this.getClient().pauseNeighbor({ messageChainId }, options);
3371
+ return response.accepted;
3358
3372
  }
3359
3373
  async resumeNeighbor(fromTimestamp, messageChainId) {
3360
3374
  const options = this.formDhtRpcOptions({
@@ -3376,9 +3390,10 @@ class PausedNeighbors {
3376
3390
  this.pausedNeighbors.set(msgChainId, new Set());
3377
3391
  }
3378
3392
  if (this.pausedNeighbors.get(msgChainId).size >= this.limit) {
3379
- return;
3393
+ return false;
3380
3394
  }
3381
3395
  this.pausedNeighbors.get(msgChainId).add(node);
3396
+ return true;
3382
3397
  }
3383
3398
  delete(node, msgChainId) {
3384
3399
  this.pausedNeighbors.get(msgChainId)?.delete(node);
@@ -3411,19 +3426,24 @@ class PausedNeighbors {
3411
3426
  }
3412
3427
 
3413
3428
  const MAX_PAUSED_NEIGHBORS_DEFAULT = 3;
3429
+ const DEFAULT_RECOVERY_TIMEOUT = 500;
3430
+ const DEFAULT_RECOVERY_CHECK_INTERVAL = 200;
3431
+ const DEFAULT_RECOVERY_COOLDOWN = 2500;
3414
3432
  const logger$4 = new Logger('PlumtreeManager');
3415
3433
  class PlumtreeManager extends EventEmitter {
3416
3434
  neighbors;
3417
3435
  localPeerDescriptor;
3418
- // We have paused sending real data to these neighbrs and only send metadata
3419
3436
  localPausedNeighbors;
3420
- // We have asked these nodes to pause sending real data to us, used to limit sending of pausing and resuming requests
3421
3437
  remotePausedNeighbors;
3422
3438
  rpcLocal;
3423
3439
  latestMessages = new Map();
3424
3440
  rpcCommunicator;
3425
- metadataTimestampsAheadOfRealData = new Map();
3426
3441
  maxPausedNeighbors;
3442
+ recoveryState = new Map();
3443
+ recoveryCooldownUntil = new Map();
3444
+ recoveryTimeout;
3445
+ recoveryCooldown;
3446
+ abortController = new AbortController();
3427
3447
  constructor(options) {
3428
3448
  super();
3429
3449
  this.neighbors = options.neighbors;
@@ -3431,12 +3451,22 @@ class PlumtreeManager extends EventEmitter {
3431
3451
  this.localPeerDescriptor = options.localPeerDescriptor;
3432
3452
  this.localPausedNeighbors = new PausedNeighbors(options.maxPausedNeighbors ?? MAX_PAUSED_NEIGHBORS_DEFAULT);
3433
3453
  this.remotePausedNeighbors = new PausedNeighbors(options.maxPausedNeighbors ?? MAX_PAUSED_NEIGHBORS_DEFAULT);
3454
+ this.recoveryTimeout = options.recoveryTimeout ?? DEFAULT_RECOVERY_TIMEOUT;
3455
+ this.recoveryCooldown = options.recoveryCooldown ?? DEFAULT_RECOVERY_COOLDOWN;
3434
3456
  this.rpcLocal = new PlumtreeRpcLocal(this.neighbors, this.localPausedNeighbors, (metadata, previousNode) => this.onMetadata(metadata, previousNode), (fromTimestamp, msgChainId, remotePeerDescriptor) => this.sendBuffer(fromTimestamp, msgChainId, remotePeerDescriptor));
3435
- this.neighbors.on('nodeRemoved', (nodeId) => this.onNeighborRemoved(nodeId));
3457
+ this.neighbors.on('nodeRemoved', this.onNeighborRemoved);
3436
3458
  this.rpcCommunicator = options.rpcCommunicator;
3437
3459
  this.rpcCommunicator.registerRpcNotification(MessageID, 'sendMetadata', (msg, context) => this.rpcLocal.sendMetadata(msg, context));
3438
- this.rpcCommunicator.registerRpcNotification(PauseNeighborRequest, 'pauseNeighbor', (msg, context) => this.rpcLocal.pauseNeighbor(msg, context));
3460
+ this.rpcCommunicator.registerRpcMethod(PauseNeighborRequest, PauseNeighborResponse, 'pauseNeighbor', (msg, context) => this.rpcLocal.pauseNeighbor(msg, context));
3439
3461
  this.rpcCommunicator.registerRpcNotification(ResumeNeighborRequest, 'resumeNeighbor', (msg, context) => this.rpcLocal.resumeNeighbor(msg, context));
3462
+ setAbortableInterval(() => {
3463
+ const now = performance.now();
3464
+ for (const [chainId, state] of this.recoveryState) {
3465
+ if (now - state.metadataAheadSince >= this.recoveryTimeout && !state.resumeInProgress) {
3466
+ this.attemptRecovery(chainId, state, this.getLatestMessageTimestamp(chainId));
3467
+ }
3468
+ }
3469
+ }, options.recoveryCheckInterval ?? DEFAULT_RECOVERY_CHECK_INTERVAL, this.abortController.signal);
3440
3470
  }
3441
3471
  async pauseNeighbor(node, msgChainId) {
3442
3472
  if (this.neighbors.has(toNodeId(node))
@@ -3444,8 +3474,16 @@ class PlumtreeManager extends EventEmitter {
3444
3474
  && this.remotePausedNeighbors.size(msgChainId) < this.maxPausedNeighbors) {
3445
3475
  logger$4.debug(`Pausing neighbor ${toNodeId(node)}`);
3446
3476
  this.remotePausedNeighbors.add(toNodeId(node), msgChainId);
3447
- const remote = this.createRemote(node);
3448
- await remote.pauseNeighbor(msgChainId);
3477
+ try {
3478
+ const remote = this.createRemote(node);
3479
+ const accepted = await remote.pauseNeighbor(msgChainId);
3480
+ if (!accepted) {
3481
+ this.remotePausedNeighbors.delete(toNodeId(node), msgChainId);
3482
+ }
3483
+ }
3484
+ catch (_e) {
3485
+ this.remotePausedNeighbors.delete(toNodeId(node), msgChainId);
3486
+ }
3449
3487
  }
3450
3488
  }
3451
3489
  async resumeNeighbor(node, msgChainId, fromTimestamp) {
@@ -3456,9 +3494,15 @@ class PlumtreeManager extends EventEmitter {
3456
3494
  await remote.resumeNeighbor(fromTimestamp, msgChainId);
3457
3495
  }
3458
3496
  }
3459
- onNeighborRemoved(nodeId) {
3497
+ onNeighborRemoved = (nodeId) => {
3460
3498
  this.localPausedNeighbors.deleteAll(nodeId);
3461
3499
  this.remotePausedNeighbors.deleteAll(nodeId);
3500
+ for (const [_chainId, state] of this.recoveryState) {
3501
+ state.candidates = state.candidates.filter((c) => toNodeId(c) !== nodeId);
3502
+ if (state.lastAttemptedNode !== null && toNodeId(state.lastAttemptedNode) === nodeId) {
3503
+ state.lastAttemptedNode = null;
3504
+ }
3505
+ }
3462
3506
  if (this.neighbors.size() > 0) {
3463
3507
  this.remotePausedNeighbors.forEach((pausedNeighbors, msgChainId) => {
3464
3508
  if (pausedNeighbors.size >= this.neighbors.size()) {
@@ -3468,7 +3512,7 @@ class PlumtreeManager extends EventEmitter {
3468
3512
  }
3469
3513
  });
3470
3514
  }
3471
- }
3515
+ };
3472
3516
  getLatestMessageTimestamp(msgChainId) {
3473
3517
  if (!this.latestMessages.has(msgChainId) || this.latestMessages.get(msgChainId).length === 0) {
3474
3518
  return 0;
@@ -3478,22 +3522,61 @@ class PlumtreeManager extends EventEmitter {
3478
3522
  async sendBuffer(fromTimestamp, msgChainId, neighbor) {
3479
3523
  const remote = new ContentDeliveryRpcRemote(this.localPeerDescriptor, neighbor, this.rpcCommunicator, ContentDeliveryRpcClient);
3480
3524
  const messages = this.latestMessages.get(msgChainId)?.filter((msg) => msg.messageId.timestamp > fromTimestamp) ?? [];
3481
- await Promise.all(messages.map((msg) => remote.sendStreamMessage(msg)));
3525
+ for (const msg of messages) {
3526
+ await remote.sendStreamMessage(msg);
3527
+ }
3482
3528
  }
3483
3529
  async onMetadata(msg, previousNode) {
3484
- // If we receive newer metadata than messages in the buffer, resume the sending neighbor
3485
- const latestMessageTimestamp = this.getLatestMessageTimestamp(msg.messageChainId);
3486
- if (latestMessageTimestamp < msg.timestamp) {
3487
- if (!this.metadataTimestampsAheadOfRealData.has(msg.messageChainId)) {
3488
- this.metadataTimestampsAheadOfRealData.set(msg.messageChainId, new Set());
3489
- }
3490
- this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).add(msg.timestamp);
3491
- if (this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).size > 1) {
3492
- await this.resumeNeighbor(previousNode, msg.messageChainId, this.getLatestMessageTimestamp(msg.messageChainId));
3493
- this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).forEach((timestamp) => {
3494
- this.metadataTimestampsAheadOfRealData.get(msg.messageChainId).delete(timestamp);
3495
- });
3496
- }
3530
+ const latestTs = this.getLatestMessageTimestamp(msg.messageChainId);
3531
+ if (latestTs >= msg.timestamp) {
3532
+ return;
3533
+ }
3534
+ const chainId = msg.messageChainId;
3535
+ const cooldownUntil = this.recoveryCooldownUntil.get(chainId);
3536
+ if (cooldownUntil !== undefined && performance.now() < cooldownUntil) {
3537
+ return;
3538
+ }
3539
+ let state = this.recoveryState.get(chainId);
3540
+ if (!state) {
3541
+ state = {
3542
+ timestampsAhead: new Set(),
3543
+ metadataAheadSince: performance.now(),
3544
+ candidates: [],
3545
+ lastAttemptedNode: null,
3546
+ resumeInProgress: false
3547
+ };
3548
+ this.recoveryState.set(chainId, state);
3549
+ }
3550
+ state.timestampsAhead.add(msg.timestamp);
3551
+ const nodeId = toNodeId(previousNode);
3552
+ const isLastAttempted = state.lastAttemptedNode !== null && toNodeId(state.lastAttemptedNode) === nodeId;
3553
+ if (!isLastAttempted && !state.candidates.some((c) => toNodeId(c) === nodeId)) {
3554
+ state.candidates.push(previousNode);
3555
+ }
3556
+ if (state.timestampsAhead.size > 1 && !state.resumeInProgress) {
3557
+ await this.attemptRecovery(chainId, state, latestTs);
3558
+ }
3559
+ }
3560
+ async attemptRecovery(chainId, state, latestTs) {
3561
+ const candidate = state.candidates.shift();
3562
+ if (!candidate) {
3563
+ state.metadataAheadSince = performance.now();
3564
+ return;
3565
+ }
3566
+ state.resumeInProgress = true;
3567
+ state.lastAttemptedNode = candidate;
3568
+ state.candidates = [];
3569
+ state.timestampsAhead.clear();
3570
+ state.metadataAheadSince = performance.now();
3571
+ try {
3572
+ const remote = this.createRemote(candidate);
3573
+ await remote.resumeNeighbor(latestTs, chainId);
3574
+ }
3575
+ catch (_e) {
3576
+ logger$4.debug('Recovery resume failed, will retry with next candidate');
3577
+ }
3578
+ finally {
3579
+ state.resumeInProgress = false;
3497
3580
  }
3498
3581
  }
3499
3582
  createRemote(neighbor) {
@@ -3511,8 +3594,13 @@ class PlumtreeManager extends EventEmitter {
3511
3594
  this.latestMessages.get(messageChainId).shift();
3512
3595
  this.latestMessages.get(messageChainId).push(msg);
3513
3596
  }
3514
- if (this.metadataTimestampsAheadOfRealData.has(msg.messageId.messageChainId)) {
3515
- this.metadataTimestampsAheadOfRealData.get(msg.messageId.messageChainId).delete(msg.messageId.timestamp);
3597
+ const state = this.recoveryState.get(messageChainId);
3598
+ if (state) {
3599
+ if (state.lastAttemptedNode) {
3600
+ this.remotePausedNeighbors.delete(toNodeId(state.lastAttemptedNode), messageChainId);
3601
+ }
3602
+ this.recoveryState.delete(messageChainId);
3603
+ this.recoveryCooldownUntil.set(messageChainId, performance.now() + this.recoveryCooldown);
3516
3604
  }
3517
3605
  this.emit('message', msg);
3518
3606
  const neighbors = this.neighbors.getAll().filter((neighbor) => toNodeId(neighbor.getPeerDescriptor()) !== previousNode);
@@ -3530,7 +3618,14 @@ class PlumtreeManager extends EventEmitter {
3530
3618
  return this.localPausedNeighbors.isPaused(toNodeId(node), msgChainId)
3531
3619
  || this.remotePausedNeighbors.isPaused(toNodeId(node), msgChainId);
3532
3620
  }
3621
+ getLocalPausedNeighbors() {
3622
+ return this.localPausedNeighbors;
3623
+ }
3624
+ getRemotePausedNeighbors() {
3625
+ return this.remotePausedNeighbors;
3626
+ }
3533
3627
  stop() {
3628
+ this.abortController.abort();
3534
3629
  this.neighbors.off('nodeRemoved', this.onNeighborRemoved);
3535
3630
  }
3536
3631
  }