@streamr/trackerless-network 102.0.0-beta.1 → 102.0.0-beta.2
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/package.json +5 -5
- package/package.json +5 -5
- package/src/NetworkNode.ts +0 -142
- package/src/NetworkStack.ts +0 -197
- package/src/exports.ts +0 -18
- package/src/logic/ContentDeliveryLayerNode.ts +0 -424
- package/src/logic/ContentDeliveryManager.ts +0 -401
- package/src/logic/ContentDeliveryRpcLocal.ts +0 -48
- package/src/logic/ContentDeliveryRpcRemote.ts +0 -44
- package/src/logic/ControlLayerNode.ts +0 -17
- package/src/logic/DiscoveryLayerNode.ts +0 -30
- package/src/logic/DuplicateMessageDetector.ts +0 -167
- package/src/logic/ExternalNetworkRpc.ts +0 -42
- package/src/logic/NodeList.ts +0 -114
- package/src/logic/PeerDescriptorStoreManager.ts +0 -96
- package/src/logic/StreamPartNetworkSplitAvoidance.ts +0 -90
- package/src/logic/StreamPartReconnect.ts +0 -38
- package/src/logic/createContentDeliveryLayerNode.ts +0 -130
- package/src/logic/formStreamPartDeliveryServiceId.ts +0 -7
- package/src/logic/inspect/InspectSession.ts +0 -55
- package/src/logic/inspect/Inspector.ts +0 -100
- package/src/logic/neighbor-discovery/HandshakeRpcLocal.ts +0 -138
- package/src/logic/neighbor-discovery/HandshakeRpcRemote.ts +0 -66
- package/src/logic/neighbor-discovery/Handshaker.ts +0 -215
- package/src/logic/neighbor-discovery/NeighborFinder.ts +0 -77
- package/src/logic/neighbor-discovery/NeighborUpdateManager.ts +0 -69
- package/src/logic/neighbor-discovery/NeighborUpdateRpcLocal.ts +0 -75
- package/src/logic/neighbor-discovery/NeighborUpdateRpcRemote.ts +0 -35
- package/src/logic/node-info/NodeInfoClient.ts +0 -23
- package/src/logic/node-info/NodeInfoRpcLocal.ts +0 -28
- package/src/logic/node-info/NodeInfoRpcRemote.ts +0 -11
- package/src/logic/propagation/FifoMapWithTTL.ts +0 -116
- package/src/logic/propagation/Propagation.ts +0 -84
- package/src/logic/propagation/PropagationTaskStore.ts +0 -41
- package/src/logic/proxy/ProxyClient.ts +0 -286
- package/src/logic/proxy/ProxyConnectionRpcLocal.ts +0 -106
- package/src/logic/proxy/ProxyConnectionRpcRemote.ts +0 -26
- package/src/logic/temporary-connection/TemporaryConnectionRpcLocal.ts +0 -73
- package/src/logic/temporary-connection/TemporaryConnectionRpcRemote.ts +0 -29
- package/src/logic/utils.ts +0 -18
- package/src/types.ts +0 -13
- package/test/benchmark/StreamPartIdDataKeyDistribution.test.ts +0 -60
- package/test/benchmark/first-message.ts +0 -171
- package/test/end-to-end/content-delivery-layer-node-with-real-connections.test.ts +0 -165
- package/test/end-to-end/external-network-rpc.test.ts +0 -67
- package/test/end-to-end/inspect.test.ts +0 -124
- package/test/end-to-end/proxy-and-full-node.test.ts +0 -143
- package/test/end-to-end/proxy-connections.test.ts +0 -226
- package/test/end-to-end/proxy-key-exchange.test.ts +0 -126
- package/test/end-to-end/webrtc-full-node-network.test.ts +0 -83
- package/test/end-to-end/websocket-full-node-network.test.ts +0 -82
- package/test/integration/ContentDeliveryLayerNode-Layer1Node-Latencies.test.ts +0 -139
- package/test/integration/ContentDeliveryLayerNode-Layer1Node.test.ts +0 -162
- package/test/integration/ContentDeliveryManager.test.ts +0 -160
- package/test/integration/ContentDeliveryRpcRemote.test.ts +0 -100
- package/test/integration/HandshakeRpcRemote.test.ts +0 -79
- package/test/integration/Handshakes.test.ts +0 -141
- package/test/integration/Inspect.test.ts +0 -89
- package/test/integration/NeighborUpdateRpcRemote.test.ts +0 -82
- package/test/integration/NetworkNode.test.ts +0 -115
- package/test/integration/NetworkRpc.test.ts +0 -52
- package/test/integration/NetworkStack.test.ts +0 -72
- package/test/integration/NodeInfoRpc.test.ts +0 -109
- package/test/integration/Propagation.test.ts +0 -76
- package/test/integration/joining-streams-on-offline-peers.test.ts +0 -82
- package/test/integration/stream-without-default-entrypoints.test.ts +0 -128
- package/test/integration/streamEntryPointReplacing.test.ts +0 -97
- package/test/types/global.d.ts +0 -1
- package/test/unit/ContentDeliveryLayerNode.test.ts +0 -112
- package/test/unit/ContentDeliveryManager.test.ts +0 -96
- package/test/unit/ContentDeliveryRpcLocal.test.ts +0 -60
- package/test/unit/DuplicateMessageDetector.test.ts +0 -192
- package/test/unit/ExternalNetworkRpc.test.ts +0 -48
- package/test/unit/FifoMapWithTtl.test.ts +0 -253
- package/test/unit/HandshakeRpcLocal.test.ts +0 -155
- package/test/unit/Handshaker.test.ts +0 -69
- package/test/unit/InspectSession.test.ts +0 -83
- package/test/unit/Inspector.test.ts +0 -51
- package/test/unit/NeighborFinder.test.ts +0 -51
- package/test/unit/NeighborUpdateRpcLocal.test.ts +0 -139
- package/test/unit/NetworkNode.test.ts +0 -42
- package/test/unit/NodeList.test.ts +0 -164
- package/test/unit/NumberPair.test.ts +0 -22
- package/test/unit/PeerDescriptorStoreManager.test.ts +0 -103
- package/test/unit/Propagation.test.ts +0 -151
- package/test/unit/ProxyConnectionRpcRemote.test.ts +0 -39
- package/test/unit/StreamPartIDDataKey.test.ts +0 -12
- package/test/unit/StreamPartNetworkSplitAvoidance.test.ts +0 -31
- package/test/unit/StreamPartReconnect.test.ts +0 -30
- package/test/unit/TemporaryConnectionRpcLocal.test.ts +0 -38
- package/test/utils/fake/FakePeerDescriptorStoreManager.ts +0 -29
- package/test/utils/mock/MockConnectionsView.ts +0 -18
- package/test/utils/mock/MockControlLayerNode.ts +0 -78
- package/test/utils/mock/MockDiscoveryLayerNode.ts +0 -60
- package/test/utils/mock/MockHandshaker.ts +0 -17
- package/test/utils/mock/MockNeighborFinder.ts +0 -20
- package/test/utils/mock/MockNeighborUpdateManager.ts +0 -21
- package/test/utils/mock/MockTransport.ts +0 -30
- package/test/utils/utils.ts +0 -144
- package/tsconfig.browser.json +0 -13
- package/tsconfig.jest.json +0 -17
- package/tsconfig.json +0 -3
- package/tsconfig.node.json +0 -17
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ConnectionLocker,
|
|
3
|
-
DhtAddress,
|
|
4
|
-
ITransport,
|
|
5
|
-
ListeningRpcCommunicator,
|
|
6
|
-
PeerDescriptor,
|
|
7
|
-
toNodeId
|
|
8
|
-
} from '@streamr/dht'
|
|
9
|
-
import { Logger, StreamPartID, UserID, addManagedEventListener, wait } from '@streamr/utils'
|
|
10
|
-
import { EventEmitter } from 'eventemitter3'
|
|
11
|
-
import { sampleSize } from 'lodash'
|
|
12
|
-
import {
|
|
13
|
-
LeaveStreamPartNotice,
|
|
14
|
-
MessageID,
|
|
15
|
-
MessageRef,
|
|
16
|
-
ProxyDirection,
|
|
17
|
-
StreamMessage
|
|
18
|
-
} from '../../../generated/packages/trackerless-network/protos/NetworkRpc'
|
|
19
|
-
import { ContentDeliveryRpcClient, ProxyConnectionRpcClient } from '../../../generated/packages/trackerless-network/protos/NetworkRpc.client'
|
|
20
|
-
import { ContentDeliveryRpcLocal } from '../ContentDeliveryRpcLocal'
|
|
21
|
-
import { ContentDeliveryRpcRemote } from '../ContentDeliveryRpcRemote'
|
|
22
|
-
import { DuplicateMessageDetector } from '../DuplicateMessageDetector'
|
|
23
|
-
import { NodeList } from '../NodeList'
|
|
24
|
-
import { formStreamPartContentDeliveryServiceId } from '../formStreamPartDeliveryServiceId'
|
|
25
|
-
import { Propagation } from '../propagation/Propagation'
|
|
26
|
-
import { markAndCheckDuplicate } from '../utils'
|
|
27
|
-
import { ProxyConnectionRpcRemote } from './ProxyConnectionRpcRemote'
|
|
28
|
-
|
|
29
|
-
// TODO use options option or named constant?
|
|
30
|
-
export const retry = async <T>(task: () => Promise<T>, description: string, abortSignal: AbortSignal, delay = 10000): Promise<T> => {
|
|
31
|
-
while (true) {
|
|
32
|
-
try {
|
|
33
|
-
const result = await task()
|
|
34
|
-
return result
|
|
35
|
-
} catch {
|
|
36
|
-
logger.warn(`Failed ${description} (retrying after delay)`, {
|
|
37
|
-
delayInMs: delay
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
await wait(delay, abortSignal)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface ProxyClientOptions {
|
|
45
|
-
transport: ITransport
|
|
46
|
-
localPeerDescriptor: PeerDescriptor
|
|
47
|
-
streamPartId: StreamPartID
|
|
48
|
-
connectionLocker: ConnectionLocker
|
|
49
|
-
minPropagationTargets?: number // TODO could be required option if we apply all defaults somewhere at higher level
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface ProxyDefinition {
|
|
53
|
-
nodes: Map<DhtAddress, PeerDescriptor>
|
|
54
|
-
connectionCount: number
|
|
55
|
-
direction: ProxyDirection
|
|
56
|
-
userId: UserID
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface ProxyConnection {
|
|
60
|
-
peerDescriptor: PeerDescriptor
|
|
61
|
-
direction: ProxyDirection
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
interface Events {
|
|
65
|
-
message: (message: StreamMessage) => void
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const logger = new Logger(module)
|
|
69
|
-
|
|
70
|
-
const SERVICE_ID = 'system/proxy-client'
|
|
71
|
-
|
|
72
|
-
export class ProxyClient extends EventEmitter<Events> {
|
|
73
|
-
|
|
74
|
-
private readonly rpcCommunicator: ListeningRpcCommunicator
|
|
75
|
-
private readonly contentDeliveryRpcLocal: ContentDeliveryRpcLocal
|
|
76
|
-
private readonly options: ProxyClientOptions
|
|
77
|
-
private readonly duplicateDetectors: Map<string, DuplicateMessageDetector> = new Map()
|
|
78
|
-
private definition?: ProxyDefinition
|
|
79
|
-
private readonly connections: Map<DhtAddress, ProxyConnection> = new Map()
|
|
80
|
-
private readonly propagation: Propagation
|
|
81
|
-
private readonly neighbors: NodeList
|
|
82
|
-
private readonly abortController: AbortController
|
|
83
|
-
|
|
84
|
-
constructor(options: ProxyClientOptions) {
|
|
85
|
-
super()
|
|
86
|
-
this.options = options
|
|
87
|
-
this.rpcCommunicator = new ListeningRpcCommunicator(formStreamPartContentDeliveryServiceId(options.streamPartId), options.transport)
|
|
88
|
-
// TODO use options option or named constant?
|
|
89
|
-
this.neighbors = new NodeList(toNodeId(this.options.localPeerDescriptor), 1000)
|
|
90
|
-
this.contentDeliveryRpcLocal = new ContentDeliveryRpcLocal({
|
|
91
|
-
localPeerDescriptor: this.options.localPeerDescriptor,
|
|
92
|
-
streamPartId: this.options.streamPartId,
|
|
93
|
-
markAndCheckDuplicate: (msg: MessageID, prev?: MessageRef) => markAndCheckDuplicate(this.duplicateDetectors, msg, prev),
|
|
94
|
-
broadcast: (message: StreamMessage, previousNode?: DhtAddress) => this.broadcast(message, previousNode),
|
|
95
|
-
onLeaveNotice: (remoteNodeId: DhtAddress) => {
|
|
96
|
-
const contact = this.neighbors.get(remoteNodeId)
|
|
97
|
-
if (contact) {
|
|
98
|
-
// TODO should we catch possible promise rejection?
|
|
99
|
-
setImmediate(() => this.onNodeDisconnected(contact.getPeerDescriptor()))
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
rpcCommunicator: this.rpcCommunicator,
|
|
103
|
-
markForInspection: () => {}
|
|
104
|
-
})
|
|
105
|
-
this.propagation = new Propagation({
|
|
106
|
-
// TODO use options option or named constant?
|
|
107
|
-
minPropagationTargets: options.minPropagationTargets ?? 2,
|
|
108
|
-
sendToNeighbor: async (neighborId: DhtAddress, msg: StreamMessage): Promise<void> => {
|
|
109
|
-
const remote = this.neighbors.get(neighborId)
|
|
110
|
-
if (remote) {
|
|
111
|
-
await remote.sendStreamMessage(msg)
|
|
112
|
-
} else {
|
|
113
|
-
throw new Error('Propagation target not found')
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
this.abortController = new AbortController()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private registerDefaultServerMethods(): void {
|
|
121
|
-
this.rpcCommunicator.registerRpcNotification(StreamMessage, 'sendStreamMessage',
|
|
122
|
-
(msg: StreamMessage, context) => this.contentDeliveryRpcLocal.sendStreamMessage(msg, context))
|
|
123
|
-
this.rpcCommunicator.registerRpcNotification(LeaveStreamPartNotice, 'leaveStreamPartNotice',
|
|
124
|
-
(req: LeaveStreamPartNotice, context) => this.contentDeliveryRpcLocal.leaveStreamPartNotice(req, context))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async setProxies(
|
|
128
|
-
nodes: PeerDescriptor[],
|
|
129
|
-
direction: ProxyDirection,
|
|
130
|
-
userId: UserID,
|
|
131
|
-
connectionCount?: number
|
|
132
|
-
): Promise<void> {
|
|
133
|
-
logger.trace('Setting proxies', { streamPartId: this.options.streamPartId, peerDescriptors: nodes, direction, userId, connectionCount })
|
|
134
|
-
if (connectionCount !== undefined && connectionCount > nodes.length) {
|
|
135
|
-
throw new Error('Cannot set connectionCount above the size of the configured array of nodes')
|
|
136
|
-
}
|
|
137
|
-
const nodesIds = new Map<DhtAddress, PeerDescriptor>()
|
|
138
|
-
nodes.forEach((peerDescriptor) => {
|
|
139
|
-
nodesIds.set(toNodeId(peerDescriptor), peerDescriptor)
|
|
140
|
-
})
|
|
141
|
-
this.definition = {
|
|
142
|
-
nodes: nodesIds,
|
|
143
|
-
userId,
|
|
144
|
-
direction,
|
|
145
|
-
connectionCount: connectionCount ?? nodes.length
|
|
146
|
-
}
|
|
147
|
-
await this.updateConnections()
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private async updateConnections(): Promise<void> {
|
|
151
|
-
await Promise.all(this.getInvalidConnections().map(async (id) => {
|
|
152
|
-
await this.closeConnection(id)
|
|
153
|
-
}))
|
|
154
|
-
const connectionCountDiff = this.definition!.connectionCount - this.connections.size
|
|
155
|
-
if (connectionCountDiff > 0) {
|
|
156
|
-
await this.openRandomConnections(connectionCountDiff)
|
|
157
|
-
} else if (connectionCountDiff < 0) {
|
|
158
|
-
await this.closeRandomConnections(-connectionCountDiff)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private getInvalidConnections(): DhtAddress[] {
|
|
163
|
-
return Array.from(this.connections.keys()).filter((id) => {
|
|
164
|
-
return !this.definition!.nodes.has(id)
|
|
165
|
-
|| this.definition!.direction !== this.connections.get(id)!.direction
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
private async openRandomConnections(connectionCount: number): Promise<void> {
|
|
170
|
-
const proxiesToAttempt = sampleSize(Array.from(this.definition!.nodes.keys()).filter((id) =>
|
|
171
|
-
!this.connections.has(id)
|
|
172
|
-
), connectionCount)
|
|
173
|
-
await Promise.all(proxiesToAttempt.map((id) =>
|
|
174
|
-
this.attemptConnection(id, this.definition!.direction, this.definition!.userId)
|
|
175
|
-
))
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
private async attemptConnection(nodeId: DhtAddress, direction: ProxyDirection, userId: UserID): Promise<void> {
|
|
179
|
-
const peerDescriptor = this.definition!.nodes.get(nodeId)!
|
|
180
|
-
const rpcRemote = new ProxyConnectionRpcRemote(
|
|
181
|
-
this.options.localPeerDescriptor,
|
|
182
|
-
peerDescriptor,
|
|
183
|
-
this.rpcCommunicator,
|
|
184
|
-
ProxyConnectionRpcClient
|
|
185
|
-
)
|
|
186
|
-
const accepted = await rpcRemote.requestConnection(direction, userId)
|
|
187
|
-
if (accepted) {
|
|
188
|
-
this.options.connectionLocker.lockConnection(peerDescriptor, SERVICE_ID)
|
|
189
|
-
this.connections.set(nodeId, { peerDescriptor, direction })
|
|
190
|
-
const remote = new ContentDeliveryRpcRemote(
|
|
191
|
-
this.options.localPeerDescriptor,
|
|
192
|
-
peerDescriptor,
|
|
193
|
-
this.rpcCommunicator,
|
|
194
|
-
ContentDeliveryRpcClient
|
|
195
|
-
)
|
|
196
|
-
this.neighbors.add(remote)
|
|
197
|
-
this.propagation.onNeighborJoined(nodeId)
|
|
198
|
-
logger.info('Open proxy connection', {
|
|
199
|
-
nodeId,
|
|
200
|
-
streamPartId: this.options.streamPartId
|
|
201
|
-
})
|
|
202
|
-
} else {
|
|
203
|
-
logger.warn('Unable to open proxy connection', {
|
|
204
|
-
nodeId,
|
|
205
|
-
streamPartId: this.options.streamPartId
|
|
206
|
-
})
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private async closeRandomConnections(connectionCount: number): Promise<void> {
|
|
211
|
-
const proxiesToDisconnect = sampleSize(Array.from(this.connections.keys()), connectionCount)
|
|
212
|
-
await Promise.allSettled(proxiesToDisconnect.map((node) => this.closeConnection(node)))
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private async closeConnection(nodeId: DhtAddress): Promise<void> {
|
|
216
|
-
if (this.connections.has(nodeId)) {
|
|
217
|
-
logger.info('Close proxy connection', {
|
|
218
|
-
nodeId
|
|
219
|
-
})
|
|
220
|
-
const server = this.neighbors.get(nodeId)
|
|
221
|
-
server?.leaveStreamPartNotice(this.options.streamPartId, false)
|
|
222
|
-
this.removeConnection(this.connections.get(nodeId)!.peerDescriptor)
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private removeConnection(peerDescriptor: PeerDescriptor): void {
|
|
227
|
-
const nodeId = toNodeId(peerDescriptor)
|
|
228
|
-
this.connections.delete(nodeId)
|
|
229
|
-
this.neighbors.remove(nodeId)
|
|
230
|
-
this.options.connectionLocker.unlockConnection(peerDescriptor, SERVICE_ID)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
broadcast(msg: StreamMessage, previousNode?: DhtAddress): void {
|
|
234
|
-
if (!previousNode) {
|
|
235
|
-
markAndCheckDuplicate(this.duplicateDetectors, msg.messageId!, msg.previousMessageRef)
|
|
236
|
-
}
|
|
237
|
-
this.emit('message', msg)
|
|
238
|
-
this.propagation.feedUnseenMessage(msg, this.neighbors.getIds(), previousNode ?? null)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
hasConnection(nodeId: DhtAddress, direction: ProxyDirection): boolean {
|
|
242
|
-
return this.connections.has(nodeId) && this.connections.get(nodeId)!.direction === direction
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
getDirection(): ProxyDirection {
|
|
246
|
-
return this.definition!.direction
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private async onNodeDisconnected(peerDescriptor: PeerDescriptor): Promise<void> {
|
|
250
|
-
const nodeId = toNodeId(peerDescriptor)
|
|
251
|
-
if (this.connections.has(nodeId)) {
|
|
252
|
-
this.options.connectionLocker.unlockConnection(peerDescriptor, SERVICE_ID)
|
|
253
|
-
this.removeConnection(peerDescriptor)
|
|
254
|
-
await retry(() => this.updateConnections(), 'updating proxy connections', this.abortController.signal)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async start(): Promise<void> {
|
|
259
|
-
this.registerDefaultServerMethods()
|
|
260
|
-
addManagedEventListener(
|
|
261
|
-
this.options.transport,
|
|
262
|
-
'disconnected',
|
|
263
|
-
// TODO should we catch possible promise rejection?
|
|
264
|
-
(peerDescriptor: PeerDescriptor) => this.onNodeDisconnected(peerDescriptor),
|
|
265
|
-
this.abortController.signal
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
public getDiagnosticInfo(): Record<string, unknown> {
|
|
270
|
-
return {
|
|
271
|
-
neighbors: this.neighbors.getAll().map((neighbor) => neighbor.getPeerDescriptor()),
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
stop(): void {
|
|
276
|
-
this.neighbors.getAll().forEach((remote) => {
|
|
277
|
-
this.options.connectionLocker.unlockConnection(remote.getPeerDescriptor(), SERVICE_ID)
|
|
278
|
-
remote.leaveStreamPartNotice(this.options.streamPartId, false)
|
|
279
|
-
})
|
|
280
|
-
this.neighbors.stop()
|
|
281
|
-
this.rpcCommunicator.destroy()
|
|
282
|
-
this.connections.clear()
|
|
283
|
-
this.abortController.abort()
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { ServerCallContext } from '@protobuf-ts/runtime-rpc'
|
|
2
|
-
import { DhtAddress, DhtCallContext, ListeningRpcCommunicator, PeerDescriptor, toNodeId } from '@streamr/dht'
|
|
3
|
-
import { Logger, StreamPartID, toUserId, UserID } from '@streamr/utils'
|
|
4
|
-
import { EventEmitter } from 'eventemitter3'
|
|
5
|
-
import {
|
|
6
|
-
ProxyConnectionRequest,
|
|
7
|
-
ProxyConnectionResponse,
|
|
8
|
-
ProxyDirection,
|
|
9
|
-
StreamMessage
|
|
10
|
-
} from '../../../generated/packages/trackerless-network/protos/NetworkRpc'
|
|
11
|
-
import { ContentDeliveryRpcClient } from '../../../generated/packages/trackerless-network/protos/NetworkRpc.client'
|
|
12
|
-
import { IProxyConnectionRpc } from '../../../generated/packages/trackerless-network/protos/NetworkRpc.server'
|
|
13
|
-
import { ContentDeliveryRpcRemote } from '../ContentDeliveryRpcRemote'
|
|
14
|
-
|
|
15
|
-
const logger = new Logger(module)
|
|
16
|
-
|
|
17
|
-
interface ProxyConnection {
|
|
18
|
-
direction: ProxyDirection // Direction is from the client's point of view
|
|
19
|
-
userId: UserID
|
|
20
|
-
remote: ContentDeliveryRpcRemote
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface ProxyConnectionRpcLocalOptions {
|
|
24
|
-
localPeerDescriptor: PeerDescriptor
|
|
25
|
-
streamPartId: StreamPartID
|
|
26
|
-
rpcCommunicator: ListeningRpcCommunicator
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface Events {
|
|
30
|
-
newConnection: (nodeId: DhtAddress) => void
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class ProxyConnectionRpcLocal extends EventEmitter<Events> implements IProxyConnectionRpc {
|
|
34
|
-
|
|
35
|
-
private readonly options: ProxyConnectionRpcLocalOptions
|
|
36
|
-
private readonly connections: Map<DhtAddress, ProxyConnection> = new Map()
|
|
37
|
-
|
|
38
|
-
constructor(options: ProxyConnectionRpcLocalOptions) {
|
|
39
|
-
super()
|
|
40
|
-
this.options = options
|
|
41
|
-
this.options.rpcCommunicator.registerRpcMethod(ProxyConnectionRequest, ProxyConnectionResponse, 'requestConnection',
|
|
42
|
-
(msg: ProxyConnectionRequest, context) => this.requestConnection(msg, context))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getConnection(nodeId: DhtAddress): ProxyConnection | undefined {
|
|
46
|
-
return this.connections.get(nodeId)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
hasConnection(nodeId: DhtAddress): boolean {
|
|
50
|
-
return this.connections.has(nodeId)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
removeConnection(nodeId: DhtAddress): void {
|
|
54
|
-
this.connections.delete(nodeId)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
stop(): void {
|
|
58
|
-
this.connections.forEach((connection) => connection.remote.leaveStreamPartNotice(this.options.streamPartId, false))
|
|
59
|
-
this.connections.clear()
|
|
60
|
-
this.removeAllListeners()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
getPropagationTargets(msg: StreamMessage): DhtAddress[] {
|
|
64
|
-
if (msg.body.oneofKind === 'groupKeyRequest') {
|
|
65
|
-
try {
|
|
66
|
-
const recipientId = msg.body.groupKeyRequest.recipientId
|
|
67
|
-
return this.getNodeIdsForUserId(toUserId(recipientId))
|
|
68
|
-
} catch (err) {
|
|
69
|
-
logger.trace(`Could not parse GroupKeyRequest`, { err })
|
|
70
|
-
return []
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
return this.getSubscribers()
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private getNodeIdsForUserId(userId: UserID): DhtAddress[] {
|
|
78
|
-
return Array.from(this.connections.keys()).filter((nodeId) => this.connections.get(nodeId)!.userId === userId)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private getSubscribers(): DhtAddress[] {
|
|
82
|
-
return Array.from(this.connections.keys()).filter((key) => this.connections.get(key)!.direction === ProxyDirection.SUBSCRIBE)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// IProxyConnectionRpc server method
|
|
86
|
-
async requestConnection(request: ProxyConnectionRequest, context: ServerCallContext): Promise<ProxyConnectionResponse> {
|
|
87
|
-
const senderPeerDescriptor = (context as DhtCallContext).incomingSourceDescriptor!
|
|
88
|
-
const remoteNodeId = toNodeId(senderPeerDescriptor)
|
|
89
|
-
this.connections.set(remoteNodeId, {
|
|
90
|
-
direction: request.direction,
|
|
91
|
-
userId: toUserId(request.userId),
|
|
92
|
-
remote: new ContentDeliveryRpcRemote(
|
|
93
|
-
this.options.localPeerDescriptor,
|
|
94
|
-
senderPeerDescriptor,
|
|
95
|
-
this.options.rpcCommunicator,
|
|
96
|
-
ContentDeliveryRpcClient
|
|
97
|
-
)
|
|
98
|
-
})
|
|
99
|
-
const response: ProxyConnectionResponse = {
|
|
100
|
-
accepted: true
|
|
101
|
-
}
|
|
102
|
-
logger.trace(`Accepted connection request from ${remoteNodeId} to ${this.options.streamPartId}`)
|
|
103
|
-
this.emit('newConnection', remoteNodeId)
|
|
104
|
-
return response
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { EXISTING_CONNECTION_TIMEOUT, RpcRemote } from '@streamr/dht'
|
|
2
|
-
import { Logger, UserID, toUserIdRaw } from '@streamr/utils'
|
|
3
|
-
import { ProxyConnectionRequest, ProxyDirection } from '../../../generated/packages/trackerless-network/protos/NetworkRpc'
|
|
4
|
-
import { ProxyConnectionRpcClient } from '../../../generated/packages/trackerless-network/protos/NetworkRpc.client'
|
|
5
|
-
|
|
6
|
-
const logger = new Logger(module)
|
|
7
|
-
|
|
8
|
-
export class ProxyConnectionRpcRemote extends RpcRemote<ProxyConnectionRpcClient> {
|
|
9
|
-
|
|
10
|
-
async requestConnection(direction: ProxyDirection, userId: UserID): Promise<boolean> {
|
|
11
|
-
const request: ProxyConnectionRequest = {
|
|
12
|
-
direction,
|
|
13
|
-
userId: toUserIdRaw(userId)
|
|
14
|
-
}
|
|
15
|
-
const options = this.formDhtRpcOptions({
|
|
16
|
-
timeout: EXISTING_CONNECTION_TIMEOUT
|
|
17
|
-
})
|
|
18
|
-
try {
|
|
19
|
-
const res = await this.getClient().requestConnection(request, options)
|
|
20
|
-
return res.accepted
|
|
21
|
-
} catch (err) {
|
|
22
|
-
logger.debug(`ProxyConnectionRequest failed with error`, { err })
|
|
23
|
-
return false
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { ServerCallContext } from '@protobuf-ts/runtime-rpc'
|
|
2
|
-
import { ConnectionLocker, DhtAddress, DhtCallContext, ListeningRpcCommunicator, toNodeId } from '@streamr/dht'
|
|
3
|
-
import { StreamPartID } from '@streamr/utils'
|
|
4
|
-
import { Empty } from '../../../generated/google/protobuf/empty'
|
|
5
|
-
import { PeerDescriptor } from '../../../generated/packages/dht/protos/DhtRpc'
|
|
6
|
-
import {
|
|
7
|
-
CloseTemporaryConnection,
|
|
8
|
-
TemporaryConnectionRequest,
|
|
9
|
-
TemporaryConnectionResponse
|
|
10
|
-
} from '../../../generated/packages/trackerless-network/protos/NetworkRpc'
|
|
11
|
-
import { ContentDeliveryRpcClient } from '../../../generated/packages/trackerless-network/protos/NetworkRpc.client'
|
|
12
|
-
import { ITemporaryConnectionRpc } from '../../../generated/packages/trackerless-network/protos/NetworkRpc.server'
|
|
13
|
-
import { ContentDeliveryRpcRemote } from '../ContentDeliveryRpcRemote'
|
|
14
|
-
import { NodeList } from '../NodeList'
|
|
15
|
-
|
|
16
|
-
interface TemporaryConnectionRpcLocalOptions {
|
|
17
|
-
rpcCommunicator: ListeningRpcCommunicator
|
|
18
|
-
localPeerDescriptor: PeerDescriptor
|
|
19
|
-
streamPartId: StreamPartID
|
|
20
|
-
connectionLocker: ConnectionLocker
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const LOCK_ID_BASE = 'system/content-delivery/temporary-connection/'
|
|
24
|
-
|
|
25
|
-
export class TemporaryConnectionRpcLocal implements ITemporaryConnectionRpc {
|
|
26
|
-
|
|
27
|
-
private readonly options: TemporaryConnectionRpcLocalOptions
|
|
28
|
-
private readonly temporaryNodes: NodeList
|
|
29
|
-
private readonly lockId: string
|
|
30
|
-
constructor(options: TemporaryConnectionRpcLocalOptions) {
|
|
31
|
-
this.options = options
|
|
32
|
-
// TODO use options option or named constant?
|
|
33
|
-
this.temporaryNodes = new NodeList(toNodeId(options.localPeerDescriptor), 10)
|
|
34
|
-
this.lockId = LOCK_ID_BASE + options.streamPartId
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getNodes(): NodeList {
|
|
38
|
-
return this.temporaryNodes
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
hasNode(node: DhtAddress): boolean {
|
|
42
|
-
return this.temporaryNodes.has(node)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
removeNode(nodeId: DhtAddress): void {
|
|
46
|
-
this.temporaryNodes.remove(nodeId)
|
|
47
|
-
this.options.connectionLocker.weakUnlockConnection(nodeId, this.lockId)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async openConnection(
|
|
51
|
-
_request: TemporaryConnectionRequest,
|
|
52
|
-
context: ServerCallContext
|
|
53
|
-
): Promise<TemporaryConnectionResponse> {
|
|
54
|
-
const sender = (context as DhtCallContext).incomingSourceDescriptor!
|
|
55
|
-
const remote = new ContentDeliveryRpcRemote(
|
|
56
|
-
this.options.localPeerDescriptor,
|
|
57
|
-
sender,
|
|
58
|
-
this.options.rpcCommunicator,
|
|
59
|
-
ContentDeliveryRpcClient
|
|
60
|
-
)
|
|
61
|
-
this.temporaryNodes.add(remote)
|
|
62
|
-
this.options.connectionLocker.weakLockConnection(toNodeId(sender), this.lockId)
|
|
63
|
-
return {
|
|
64
|
-
accepted: true
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async closeConnection(_request: CloseTemporaryConnection, context: ServerCallContext): Promise<Empty> {
|
|
69
|
-
const remoteNodeId = toNodeId((context as DhtCallContext).incomingSourceDescriptor!)
|
|
70
|
-
this.removeNode(remoteNodeId)
|
|
71
|
-
return {}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { RpcRemote, toNodeId } from '@streamr/dht'
|
|
2
|
-
import { Logger } from '@streamr/utils'
|
|
3
|
-
import { TemporaryConnectionRpcClient } from '../../../generated/packages/trackerless-network/protos/NetworkRpc.client'
|
|
4
|
-
|
|
5
|
-
const logger = new Logger(module)
|
|
6
|
-
|
|
7
|
-
export class TemporaryConnectionRpcRemote extends RpcRemote<TemporaryConnectionRpcClient> {
|
|
8
|
-
|
|
9
|
-
async openConnection(): Promise<boolean> {
|
|
10
|
-
try {
|
|
11
|
-
const response = await this.getClient().openConnection({}, this.formDhtRpcOptions())
|
|
12
|
-
return response.accepted
|
|
13
|
-
} catch (err: any) {
|
|
14
|
-
logger.debug(`temporaryConnection to ${toNodeId(this.getPeerDescriptor())} failed`, { err })
|
|
15
|
-
return false
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async closeConnection(): Promise<void> {
|
|
20
|
-
try {
|
|
21
|
-
await this.getClient().closeConnection({}, this.formDhtRpcOptions({
|
|
22
|
-
connect: false,
|
|
23
|
-
notification: true
|
|
24
|
-
}))
|
|
25
|
-
} catch (err) {
|
|
26
|
-
logger.trace(`closeConnection to ${toNodeId(this.getPeerDescriptor())} failed`, { err })
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
package/src/logic/utils.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { toUserId } from '@streamr/utils'
|
|
2
|
-
import { MessageID, MessageRef } from '../../generated/packages/trackerless-network/protos/NetworkRpc'
|
|
3
|
-
import { DuplicateMessageDetector, NumberPair } from './DuplicateMessageDetector'
|
|
4
|
-
|
|
5
|
-
export const markAndCheckDuplicate = (
|
|
6
|
-
duplicateDetectors: Map<string, DuplicateMessageDetector>,
|
|
7
|
-
currentMessage: MessageID,
|
|
8
|
-
previousMessageRef?: MessageRef
|
|
9
|
-
): boolean => {
|
|
10
|
-
const detectorKey = `${toUserId(currentMessage.publisherId)}-${currentMessage.messageChainId}`
|
|
11
|
-
const previousNumberPair = previousMessageRef ?
|
|
12
|
-
new NumberPair(Number(previousMessageRef.timestamp), previousMessageRef.sequenceNumber) : null
|
|
13
|
-
const currentNumberPair = new NumberPair(Number(currentMessage.timestamp), currentMessage.sequenceNumber)
|
|
14
|
-
if (!duplicateDetectors.has(detectorKey)) {
|
|
15
|
-
duplicateDetectors.set(detectorKey, new DuplicateMessageDetector())
|
|
16
|
-
}
|
|
17
|
-
return duplicateDetectors.get(detectorKey)!.markAndCheck(previousNumberPair, currentNumberPair)
|
|
18
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ChangeFieldType } from '@streamr/utils'
|
|
2
|
-
import { MarkRequired } from 'ts-essentials'
|
|
3
|
-
import {
|
|
4
|
-
ContentDeliveryLayerNeighborInfo as ContentDeliveryLayerNeighborInfo_,
|
|
5
|
-
NodeInfoResponse,
|
|
6
|
-
StreamPartitionInfo as StreamPartitionInfo_
|
|
7
|
-
} from '../generated/packages/trackerless-network/protos/NetworkRpc'
|
|
8
|
-
|
|
9
|
-
// These types are part of trackerless-network's public API. Therefore removing optionality from fields which are
|
|
10
|
-
// actually required. TODO: could do the same thing for other generated interfaces which are part of the public API.
|
|
11
|
-
export type ContentDeliveryLayerNeighborInfo = MarkRequired<ContentDeliveryLayerNeighborInfo_, 'peerDescriptor'>
|
|
12
|
-
export type StreamPartitionInfo = ChangeFieldType<Required<StreamPartitionInfo_>, 'contentDeliveryLayerNeighbors', ContentDeliveryLayerNeighborInfo[]>
|
|
13
|
-
export type NodeInfo = ChangeFieldType<Required<NodeInfoResponse>, 'streamPartitions', StreamPartitionInfo[]>
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
import { DhtAddress } from '@streamr/dht'
|
|
4
|
-
import { StreamPartIDUtils } from '@streamr/utils'
|
|
5
|
-
import { groupBy, range } from 'lodash'
|
|
6
|
-
import { streamPartIdToDataKey } from '../../src/logic/ContentDeliveryManager'
|
|
7
|
-
|
|
8
|
-
describe('StreamPartIdDataKeyDistribution', () => {
|
|
9
|
-
|
|
10
|
-
it('partitions are well distributed', () => {
|
|
11
|
-
|
|
12
|
-
const streamId = 'stream'
|
|
13
|
-
const dataKeys = range(100).map((i) => {
|
|
14
|
-
const streamPartId = StreamPartIDUtils.parse(streamId + '#' + i)
|
|
15
|
-
return streamPartIdToDataKey(streamPartId)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const byInitials = groupBy(dataKeys, (dataKey: DhtAddress) => dataKey[0])
|
|
19
|
-
expect(Object.keys(byInitials).length).toEqual(16)
|
|
20
|
-
console.log(Object.values(byInitials).map((a) => a.length))
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('streamIds are well distributed', () => {
|
|
24
|
-
const dataKeys = range(10000).map(() => {
|
|
25
|
-
const streamPartId = StreamPartIDUtils.parse(Math.random().toString(32).substr(2, 32) + '#0')
|
|
26
|
-
return streamPartIdToDataKey(streamPartId)
|
|
27
|
-
})
|
|
28
|
-
const byInitials = groupBy(dataKeys, (dataKey: DhtAddress) => dataKey[0])
|
|
29
|
-
expect(Object.keys(byInitials).length).toEqual(16)
|
|
30
|
-
console.log(Object.values(byInitials).map((a) => a.length))
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('streamPartIds are well distributed', () => {
|
|
34
|
-
const streamIds = range(10000).map(() => Math.random().toString(32).substr(2, 32))
|
|
35
|
-
const dataKeys: DhtAddress[] = []
|
|
36
|
-
streamIds.forEach((streamId) => {
|
|
37
|
-
range(100).forEach((i) => {
|
|
38
|
-
const streamPartId = StreamPartIDUtils.parse(streamId + '#' + i)
|
|
39
|
-
dataKeys.push(streamPartIdToDataKey(streamPartId))
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const byInitials = groupBy(dataKeys, (dataKey: DhtAddress) => dataKey[0])
|
|
44
|
-
expect(Object.keys(byInitials).length).toEqual(16)
|
|
45
|
-
console.log(Object.values(byInitials).map((a) => a.length))
|
|
46
|
-
|
|
47
|
-
const byTwoInitials = groupBy(dataKeys, (dataKey: DhtAddress) => dataKey[0] + dataKey[1])
|
|
48
|
-
expect(Object.keys(byTwoInitials).length).toEqual(16 * 16)
|
|
49
|
-
console.log(Object.values(byTwoInitials).map((a) => a.length))
|
|
50
|
-
|
|
51
|
-
const byThreeInitials = groupBy(dataKeys, (dataKey: DhtAddress) => dataKey[0] + dataKey[1] + dataKey[2])
|
|
52
|
-
expect(Object.keys(byThreeInitials).length).toEqual(16 * 16 * 16)
|
|
53
|
-
console.log(Object.values(byThreeInitials).map((a) => a.length))
|
|
54
|
-
|
|
55
|
-
const byFourInitials = groupBy(dataKeys, (dataKey: DhtAddress) => dataKey[0] + dataKey[1] + dataKey[2] + dataKey[3])
|
|
56
|
-
expect(Object.keys(byFourInitials).length).toEqual(16 * 16 * 16 * 16)
|
|
57
|
-
console.log(Object.values(byFourInitials).map((a) => a.length))
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
})
|