@streamr/dht 100.0.0-testnet-three.5 → 100.0.0-testnet-three.6
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 +7 -6
- package/dist/src/connection/ConnectionManager.d.ts +2 -1
- package/dist/src/connection/ConnectionManager.js +8 -1
- package/dist/src/connection/ConnectionManager.js.map +1 -1
- package/dist/src/connection/Handshaker.d.ts +2 -0
- package/dist/src/connection/Handshaker.js +6 -1
- package/dist/src/connection/Handshaker.js.map +1 -1
- package/dist/src/connection/ManagedConnection.js +1 -0
- package/dist/src/connection/ManagedConnection.js.map +1 -1
- package/dist/src/dht/DhtNode.d.ts +2 -1
- package/dist/src/dht/DhtNode.js +13 -6
- package/dist/src/dht/DhtNode.js.map +1 -1
- package/dist/src/dht/routing/Router.d.ts +5 -2
- package/dist/src/dht/routing/Router.js +17 -3
- package/dist/src/dht/routing/Router.js.map +1 -1
- package/dist/src/dht/routing/RouterRpcLocal.d.ts +2 -3
- package/dist/src/dht/routing/RouterRpcLocal.js +2 -2
- package/dist/src/dht/routing/RouterRpcLocal.js.map +1 -1
- package/dist/src/dht/routing/RoutingSession.d.ts +8 -6
- package/dist/src/dht/routing/RoutingSession.js +42 -40
- package/dist/src/dht/routing/RoutingSession.js.map +1 -1
- package/dist/src/dht/routing/RoutingTablesCache.d.ts +24 -0
- package/dist/src/dht/routing/RoutingTablesCache.js +46 -0
- package/dist/src/dht/routing/RoutingTablesCache.js.map +1 -0
- package/package.json +7 -6
- package/src/connection/ConnectionManager.ts +10 -2
- package/src/connection/Handshaker.ts +7 -2
- package/src/connection/ManagedConnection.ts +1 -0
- package/src/dht/DhtNode.ts +13 -6
- package/src/dht/routing/Router.ts +22 -6
- package/src/dht/routing/RouterRpcLocal.ts +4 -5
- package/src/dht/routing/RoutingSession.ts +50 -44
- package/src/dht/routing/RoutingTablesCache.ts +58 -0
- package/test/unit/Router.test.ts +2 -1
- package/test/unit/RoutingSession.test.ts +7 -1
- package/test/utils/mock/Router.ts +9 -0
|
@@ -13,29 +13,30 @@ import { EXISTING_CONNECTION_TIMEOUT } from '../contact/RpcRemote'
|
|
|
13
13
|
import { getPreviousPeer } from './getPreviousPeer'
|
|
14
14
|
import { DhtAddress, areEqualPeerDescriptors, getDhtAddressFromRaw, getNodeIdFromPeerDescriptor } from '../../identifiers'
|
|
15
15
|
import { pull } from 'lodash'
|
|
16
|
+
import { RoutingTable, RoutingTablesCache } from './RoutingTablesCache'
|
|
16
17
|
|
|
17
18
|
const logger = new Logger(module)
|
|
18
19
|
|
|
19
20
|
const MAX_FAILED_HOPS = 2
|
|
20
|
-
const
|
|
21
|
+
const ROUTING_TABLE_MAX_SIZE = 20
|
|
21
22
|
|
|
22
|
-
class
|
|
23
|
+
export class RoutingRemoteContact extends Contact {
|
|
23
24
|
|
|
24
25
|
private routerRpcRemote: RouterRpcRemote
|
|
25
26
|
private recursiveOperationRpcRemote: RecursiveOperationRpcRemote
|
|
26
27
|
|
|
27
|
-
constructor(peer:
|
|
28
|
-
super(peer
|
|
28
|
+
constructor(peer: PeerDescriptor, localPeerDescriptor: PeerDescriptor, rpcCommunicator: RoutingRpcCommunicator) {
|
|
29
|
+
super(peer)
|
|
29
30
|
this.routerRpcRemote = new RouterRpcRemote(
|
|
30
31
|
localPeerDescriptor,
|
|
31
|
-
peer
|
|
32
|
+
peer,
|
|
32
33
|
rpcCommunicator,
|
|
33
34
|
RouterRpcClient,
|
|
34
35
|
EXISTING_CONNECTION_TIMEOUT
|
|
35
36
|
)
|
|
36
37
|
this.recursiveOperationRpcRemote = new RecursiveOperationRpcRemote(
|
|
37
38
|
localPeerDescriptor,
|
|
38
|
-
peer
|
|
39
|
+
peer,
|
|
39
40
|
rpcCommunicator,
|
|
40
41
|
RecursiveOperationRpcClient,
|
|
41
42
|
EXISTING_CONNECTION_TIMEOUT
|
|
@@ -71,14 +72,15 @@ interface RoutingSessionConfig {
|
|
|
71
72
|
connections: Map<DhtAddress, DhtNodeRpcRemote>
|
|
72
73
|
parallelism: number
|
|
73
74
|
mode: RoutingMode
|
|
74
|
-
excludedNodeIds
|
|
75
|
+
excludedNodeIds: Set<DhtAddress>
|
|
76
|
+
routingTablesCache: RoutingTablesCache
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
|
|
78
80
|
|
|
79
81
|
public readonly sessionId = v4()
|
|
80
82
|
private ongoingRequests: Set<DhtAddress> = new Set()
|
|
81
|
-
private
|
|
83
|
+
private contactedPeers: Set<DhtAddress> = new Set()
|
|
82
84
|
private failedHopCounter = 0
|
|
83
85
|
private successfulHopCounter = 0
|
|
84
86
|
private stopped = false
|
|
@@ -87,16 +89,6 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
|
|
|
87
89
|
constructor(config: RoutingSessionConfig) {
|
|
88
90
|
super()
|
|
89
91
|
this.config = config
|
|
90
|
-
const previousPeer = getPreviousPeer(config.routedMessage)
|
|
91
|
-
const previousId = previousPeer ? getNodeIdFromPeerDescriptor(previousPeer) : undefined
|
|
92
|
-
this.contactList = new SortedContactList({
|
|
93
|
-
referenceId: getDhtAddressFromRaw(config.routedMessage.target),
|
|
94
|
-
maxSize: CONTACT_LIST_MAX_SIZE,
|
|
95
|
-
allowToContainReferenceId: true,
|
|
96
|
-
nodeIdDistanceLimit: previousId,
|
|
97
|
-
excludedNodeIds: config.excludedNodeIds,
|
|
98
|
-
emitEvents: false
|
|
99
|
-
})
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
private onRequestFailed(nodeId: DhtAddress) {
|
|
@@ -108,14 +100,17 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
|
|
|
108
100
|
this.ongoingRequests.delete(nodeId)
|
|
109
101
|
}
|
|
110
102
|
this.deleteParallelRootIfSource(nodeId)
|
|
103
|
+
this.failedHopCounter += 1
|
|
104
|
+
if (this.failedHopCounter >= MAX_FAILED_HOPS) {
|
|
105
|
+
logger.trace(`Stopping routing after ${MAX_FAILED_HOPS} failed attempts for sessionId: ${this.sessionId}`)
|
|
106
|
+
this.emitFailure()
|
|
107
|
+
return
|
|
108
|
+
}
|
|
111
109
|
const contacts = this.updateAndGetRoutablePeers()
|
|
112
110
|
if (contacts.length === 0 && this.ongoingRequests.size === 0) {
|
|
113
111
|
logger.trace('routing failed, emitting routingFailed sessionId: ' + this.sessionId)
|
|
114
|
-
// TODO should call this.stop() so that we do cleanup? (after the emitFailure call)
|
|
115
|
-
this.stopped = true
|
|
116
112
|
this.emitFailure()
|
|
117
113
|
} else {
|
|
118
|
-
this.failedHopCounter += 1
|
|
119
114
|
logger.trace('routing failed, retrying to route sessionId: ' + this.sessionId + ' failedHopCounter: ' + this.failedHopCounter)
|
|
120
115
|
this.sendMoreRequests(contacts)
|
|
121
116
|
}
|
|
@@ -135,17 +130,19 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
|
|
|
135
130
|
return
|
|
136
131
|
}
|
|
137
132
|
this.successfulHopCounter += 1
|
|
133
|
+
if (this.successfulHopCounter >= this.config.parallelism) {
|
|
134
|
+
this.emit('routingSucceeded')
|
|
135
|
+
return
|
|
136
|
+
}
|
|
138
137
|
const contacts = this.updateAndGetRoutablePeers()
|
|
139
|
-
if (
|
|
140
|
-
// TODO should call this.stop() so that we do cleanup? (after the routingSucceeded call)
|
|
141
|
-
this.stopped = true
|
|
138
|
+
if (contacts.length === 0) {
|
|
142
139
|
this.emit('routingSucceeded')
|
|
143
140
|
} else if (contacts.length > 0 && this.ongoingRequests.size === 0) {
|
|
144
141
|
this.sendMoreRequests(contacts)
|
|
145
142
|
}
|
|
146
143
|
}
|
|
147
144
|
|
|
148
|
-
private async sendRouteMessageRequest(contact:
|
|
145
|
+
private async sendRouteMessageRequest(contact: RoutingRemoteContact): Promise<boolean> {
|
|
149
146
|
if (this.stopped) {
|
|
150
147
|
return false
|
|
151
148
|
}
|
|
@@ -162,21 +159,36 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
|
|
|
162
159
|
}
|
|
163
160
|
}
|
|
164
161
|
|
|
165
|
-
updateAndGetRoutablePeers():
|
|
162
|
+
updateAndGetRoutablePeers(): RoutingRemoteContact[] {
|
|
166
163
|
logger.trace('getRoutablePeers() sessionId: ' + this.sessionId)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
164
|
+
const previousPeer = getPreviousPeer(this.config.routedMessage)
|
|
165
|
+
const previousId = previousPeer ? getNodeIdFromPeerDescriptor(previousPeer) : undefined
|
|
166
|
+
const targetId = getDhtAddressFromRaw(this.config.routedMessage.target)
|
|
167
|
+
let routingTable: RoutingTable
|
|
168
|
+
if (this.config.routingTablesCache.has(targetId, previousId)) {
|
|
169
|
+
routingTable = this.config.routingTablesCache.get(targetId, previousId)!
|
|
170
|
+
} else {
|
|
171
|
+
routingTable = new SortedContactList<RoutingRemoteContact>({
|
|
172
|
+
referenceId: getDhtAddressFromRaw(this.config.routedMessage.target),
|
|
173
|
+
maxSize: ROUTING_TABLE_MAX_SIZE,
|
|
174
|
+
allowToContainReferenceId: true,
|
|
175
|
+
nodeIdDistanceLimit: previousId,
|
|
176
|
+
emitEvents: false
|
|
177
|
+
})
|
|
178
|
+
const contacts = Array.from(this.config.connections.values())
|
|
179
|
+
.map((peer) => new RoutingRemoteContact(
|
|
180
|
+
peer.getPeerDescriptor(),
|
|
181
|
+
this.config.localPeerDescriptor,
|
|
182
|
+
this.config.rpcCommunicator
|
|
183
|
+
))
|
|
184
|
+
routingTable.addContacts(contacts)
|
|
185
|
+
this.config.routingTablesCache.set(targetId, routingTable, previousId)
|
|
186
|
+
}
|
|
187
|
+
return routingTable.getAllContacts()
|
|
188
|
+
.filter((contact) => !this.contactedPeers.has(contact.getNodeId()) && !this.config.excludedNodeIds.has(contact.getNodeId()))
|
|
177
189
|
}
|
|
178
190
|
|
|
179
|
-
sendMoreRequests(uncontacted:
|
|
191
|
+
sendMoreRequests(uncontacted: RoutingRemoteContact[]): void {
|
|
180
192
|
logger.trace('sendMoreRequests() sessionId: ' + this.sessionId)
|
|
181
193
|
if (this.stopped) {
|
|
182
194
|
return
|
|
@@ -185,16 +197,11 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
|
|
|
185
197
|
this.emitFailure()
|
|
186
198
|
return
|
|
187
199
|
}
|
|
188
|
-
if (this.failedHopCounter >= MAX_FAILED_HOPS) {
|
|
189
|
-
logger.trace(`Stopping routing after ${MAX_FAILED_HOPS} failed attempts for sessionId: ${this.sessionId}`)
|
|
190
|
-
this.emitFailure()
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
200
|
while ((this.ongoingRequests.size < this.config.parallelism) && (uncontacted.length > 0) && !this.stopped) {
|
|
194
201
|
const nextPeer = uncontacted.shift()
|
|
195
202
|
// eslint-disable-next-line max-len
|
|
196
203
|
logger.trace(`Sending routeMessage request to contact: ${getNodeIdFromPeerDescriptor(nextPeer!.getPeerDescriptor())} (sessionId=${this.sessionId})`)
|
|
197
|
-
this.
|
|
204
|
+
this.contactedPeers.add(nextPeer!.getNodeId())
|
|
198
205
|
this.ongoingRequests.add(nextPeer!.getNodeId())
|
|
199
206
|
this.addParallelRootIfSource(nextPeer!.getNodeId())
|
|
200
207
|
setImmediate(async () => {
|
|
@@ -234,7 +241,6 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
|
|
|
234
241
|
|
|
235
242
|
public stop(): void {
|
|
236
243
|
this.stopped = true
|
|
237
|
-
this.contactList.stop()
|
|
238
244
|
this.emit('stopped')
|
|
239
245
|
this.removeAllListeners()
|
|
240
246
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DhtAddress } from '../../identifiers'
|
|
2
|
+
import { SortedContactList } from '../contact/SortedContactList'
|
|
3
|
+
import { RoutingRemoteContact } from './RoutingSession'
|
|
4
|
+
import { LRUCache } from 'lru-cache'
|
|
5
|
+
|
|
6
|
+
type RoutingTableID = string
|
|
7
|
+
export type RoutingTable = Pick<SortedContactList<RoutingRemoteContact>, 'getAllContacts' | 'addContacts' | 'addContact' | 'removeContact' | 'stop'>
|
|
8
|
+
|
|
9
|
+
const createRoutingTableId = (targetId: DhtAddress, previousId?: DhtAddress): RoutingTableID => {
|
|
10
|
+
return targetId + (previousId ? previousId : '')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DEFAULT_LRU_OPTIONS = {
|
|
14
|
+
max: 1000,
|
|
15
|
+
maxAge: 15 * 1000
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* RoutingTablesCache is a cache for routing tables.
|
|
20
|
+
* It is used to store the routing tables for a specific targetId and previousId.
|
|
21
|
+
* Storing the previousId is important as it is used as a minimum distance for the contacts in the table.
|
|
22
|
+
* Calculating a RoutingTable from scratch is an O(n log n) operation (n = number of connections of a node)
|
|
23
|
+
* However,
|
|
24
|
+
* - Adding a contact to a RoutingTable is an O(log n) operation.
|
|
25
|
+
* - Deleting a contact from a RoutingTable is an O(1) operation.
|
|
26
|
+
* Thus, holding the most frequently used routing tables in memory to be updated on
|
|
27
|
+
* connections and disconnections is hugely beneficial in terms of performance.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export class RoutingTablesCache {
|
|
31
|
+
|
|
32
|
+
private readonly tables: LRUCache<RoutingTableID, RoutingTable> = new LRUCache(DEFAULT_LRU_OPTIONS)
|
|
33
|
+
|
|
34
|
+
get(targetId: DhtAddress, previousId?: DhtAddress): RoutingTable | undefined {
|
|
35
|
+
return this.tables.get(createRoutingTableId(targetId, previousId))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set(targetId: DhtAddress, table: RoutingTable, previousId?: DhtAddress): void {
|
|
39
|
+
this.tables.set(createRoutingTableId(targetId, previousId), table)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
has(targetId: DhtAddress, previousId?: DhtAddress): boolean {
|
|
43
|
+
return this.tables.has(createRoutingTableId(targetId, previousId))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onNodeDisconnected(nodeId: DhtAddress): void {
|
|
47
|
+
this.tables.forEach((table) => table.removeContact(nodeId))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onNodeConnected(remote: RoutingRemoteContact): void {
|
|
51
|
+
this.tables.forEach((table) => table.addContact(remote))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
reset(): void {
|
|
55
|
+
this.tables.forEach((table) => table.stop())
|
|
56
|
+
this.tables.clear()
|
|
57
|
+
}
|
|
58
|
+
}
|
package/test/unit/Router.test.ts
CHANGED
|
@@ -6,12 +6,14 @@ import { DhtNodeRpcRemote } from '../../src/dht/DhtNodeRpcRemote'
|
|
|
6
6
|
import { RoutingRpcCommunicator } from '../../src/transport/RoutingRpcCommunicator'
|
|
7
7
|
import { DhtAddress, getNodeIdFromPeerDescriptor } from '../../src/identifiers'
|
|
8
8
|
import { MockRpcCommunicator } from '../utils/mock/MockRpcCommunicator'
|
|
9
|
+
import { RoutingTablesCache } from '../../src/dht/routing/RoutingTablesCache'
|
|
9
10
|
|
|
10
11
|
describe('RoutingSession', () => {
|
|
11
12
|
|
|
12
13
|
let session: RoutingSession
|
|
13
14
|
let connections: Map<DhtAddress, DhtNodeRpcRemote>
|
|
14
15
|
let rpcCommunicator: RoutingRpcCommunicator
|
|
16
|
+
let routingTablesCache: RoutingTablesCache
|
|
15
17
|
const mockPeerDescriptor1 = createMockPeerDescriptor()
|
|
16
18
|
const mockPeerDescriptor2 = createMockPeerDescriptor()
|
|
17
19
|
const rpcWrapper = createWrappedClosestPeersRequest(mockPeerDescriptor1)
|
|
@@ -43,13 +45,16 @@ describe('RoutingSession', () => {
|
|
|
43
45
|
beforeEach(() => {
|
|
44
46
|
rpcCommunicator = new MockRpcCommunicator()
|
|
45
47
|
connections = new Map()
|
|
48
|
+
routingTablesCache = new RoutingTablesCache()
|
|
46
49
|
session = new RoutingSession({
|
|
47
50
|
rpcCommunicator: rpcCommunicator,
|
|
48
51
|
localPeerDescriptor: mockPeerDescriptor1,
|
|
49
52
|
routedMessage,
|
|
50
53
|
connections,
|
|
51
54
|
parallelism: 2,
|
|
52
|
-
mode: RoutingMode.ROUTE
|
|
55
|
+
mode: RoutingMode.ROUTE,
|
|
56
|
+
excludedNodeIds: new Set(),
|
|
57
|
+
routingTablesCache
|
|
53
58
|
})
|
|
54
59
|
})
|
|
55
60
|
|
|
@@ -68,6 +73,7 @@ describe('RoutingSession', () => {
|
|
|
68
73
|
connections.set(getNodeIdFromPeerDescriptor(mockPeerDescriptor2), createMockDhtNodeRpcRemote(mockPeerDescriptor2))
|
|
69
74
|
expect(session.updateAndGetRoutablePeers().length).toBe(1)
|
|
70
75
|
connections.delete(getNodeIdFromPeerDescriptor(mockPeerDescriptor2))
|
|
76
|
+
routingTablesCache.onNodeDisconnected(getNodeIdFromPeerDescriptor(mockPeerDescriptor2))
|
|
71
77
|
expect(session.updateAndGetRoutablePeers().length).toBe(0)
|
|
72
78
|
})
|
|
73
79
|
|
|
@@ -49,4 +49,13 @@ export class MockRouter implements Methods<Router> {
|
|
|
49
49
|
return RouteMessageAck.create()
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// eslint-disable-next-line class-methods-use-this
|
|
53
|
+
onNodeConnected(): void {}
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line class-methods-use-this
|
|
56
|
+
onNodeDisconnected(): void {}
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line class-methods-use-this
|
|
59
|
+
resetCache(): void {}
|
|
60
|
+
|
|
52
61
|
}
|