@streamr/dht 100.2.2 → 100.2.4-beta.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/README.md +1 -1
- package/dist/package.json +6 -6
- package/dist/src/connection/ConnectionLockStates.js +4 -6
- package/dist/src/connection/ConnectionLockStates.js.map +1 -1
- package/dist/src/connection/ConnectionManager.d.ts +1 -0
- package/dist/src/connection/ConnectionManager.js +8 -1
- package/dist/src/connection/ConnectionManager.js.map +1 -1
- package/dist/src/connection/ManagedConnection.js +7 -9
- package/dist/src/connection/ManagedConnection.js.map +1 -1
- package/dist/src/connection/websocket/WebsocketConnector.js +1 -0
- package/dist/src/connection/websocket/WebsocketConnector.js.map +1 -1
- package/dist/src/dht/DhtNode.d.ts +10 -7
- package/dist/src/dht/DhtNode.js +51 -32
- package/dist/src/dht/DhtNode.js.map +1 -1
- package/dist/src/dht/DhtNodeRpcLocal.d.ts +2 -2
- package/dist/src/dht/DhtNodeRpcLocal.js +5 -4
- package/dist/src/dht/DhtNodeRpcLocal.js.map +1 -1
- package/dist/src/dht/DhtNodeRpcRemote.js +1 -0
- package/dist/src/dht/DhtNodeRpcRemote.js.map +1 -1
- package/dist/src/dht/PeerManager.d.ts +14 -14
- package/dist/src/dht/PeerManager.js +23 -58
- package/dist/src/dht/PeerManager.js.map +1 -1
- package/dist/src/dht/contact/ContactList.d.ts +2 -2
- package/dist/src/dht/contact/RandomContactList.js +2 -2
- package/dist/src/dht/contact/RandomContactList.js.map +1 -1
- package/dist/src/dht/contact/RingContactList.d.ts +2 -5
- package/dist/src/dht/contact/RingContactList.js +3 -11
- package/dist/src/dht/contact/RingContactList.js.map +1 -1
- package/dist/src/dht/contact/SortedContactList.d.ts +4 -1
- package/dist/src/dht/contact/SortedContactList.js +4 -5
- package/dist/src/dht/contact/SortedContactList.js.map +1 -1
- package/dist/src/dht/contact/getClosestContacts.d.ts +7 -0
- package/dist/src/dht/contact/getClosestContacts.js +18 -0
- package/dist/src/dht/contact/getClosestContacts.js.map +1 -0
- package/dist/src/dht/discovery/DiscoverySession.d.ts +8 -8
- package/dist/src/dht/discovery/DiscoverySession.js +30 -35
- package/dist/src/dht/discovery/DiscoverySession.js.map +1 -1
- package/dist/src/dht/discovery/PeerDiscovery.d.ts +1 -2
- package/dist/src/dht/discovery/PeerDiscovery.js +6 -8
- package/dist/src/dht/discovery/PeerDiscovery.js.map +1 -1
- package/dist/src/dht/discovery/RingDiscoverySession.d.ts +4 -4
- package/dist/src/dht/discovery/RingDiscoverySession.js +13 -13
- package/dist/src/dht/discovery/RingDiscoverySession.js.map +1 -1
- package/dist/src/dht/recursive-operation/RecursiveOperationManager.d.ts +2 -2
- package/dist/src/dht/recursive-operation/RecursiveOperationManager.js +15 -15
- package/dist/src/dht/recursive-operation/RecursiveOperationManager.js.map +1 -1
- package/dist/src/dht/recursive-operation/RecursiveOperationSession.d.ts +1 -1
- package/dist/src/dht/recursive-operation/RecursiveOperationSession.js +11 -13
- package/dist/src/dht/recursive-operation/RecursiveOperationSession.js.map +1 -1
- package/dist/src/dht/recursive-operation/RecursiveOperationSessionRpcLocal.js +1 -1
- package/dist/src/dht/recursive-operation/RecursiveOperationSessionRpcRemote.d.ts +1 -1
- package/dist/src/dht/recursive-operation/RecursiveOperationSessionRpcRemote.js +2 -2
- package/dist/src/dht/recursive-operation/RecursiveOperationSessionRpcRemote.js.map +1 -1
- package/dist/src/dht/routing/Router.d.ts +1 -2
- package/dist/src/dht/routing/Router.js +2 -3
- package/dist/src/dht/routing/Router.js.map +1 -1
- package/dist/src/dht/routing/RoutingSession.d.ts +1 -2
- package/dist/src/dht/routing/RoutingSession.js +2 -2
- package/dist/src/dht/routing/RoutingSession.js.map +1 -1
- package/dist/src/dht/store/StoreRpcLocal.js +3 -3
- package/dist/src/dht/store/StoreRpcLocal.js.map +1 -1
- package/dist/src/proto/packages/dht/protos/DhtRpc.client.d.ts +8 -0
- package/dist/src/proto/packages/dht/protos/DhtRpc.client.js +4 -0
- package/dist/src/proto/packages/dht/protos/DhtRpc.client.js.map +1 -1
- package/dist/src/proto/packages/dht/protos/DhtRpc.d.ts +10 -2
- package/dist/src/proto/packages/dht/protos/DhtRpc.js +1 -1
- package/dist/src/proto/packages/dht/protos/DhtRpc.js.map +1 -1
- package/dist/src/proto/packages/dht/protos/DhtRpc.server.d.ts +4 -0
- package/dist/src/transport/ITransport.d.ts +3 -0
- package/dist/src/transport/ITransport.js.map +1 -1
- package/package.json +6 -6
- package/protos/DhtRpc.proto +7 -1
- package/src/connection/ConnectionLockStates.ts +3 -5
- package/src/connection/ConnectionManager.ts +9 -1
- package/src/connection/ManagedConnection.ts +11 -14
- package/src/connection/websocket/WebsocketConnector.ts +1 -0
- package/src/dht/DhtNode.ts +63 -44
- package/src/dht/DhtNodeRpcLocal.ts +7 -6
- package/src/dht/DhtNodeRpcRemote.ts +1 -0
- package/src/dht/PeerManager.ts +38 -72
- package/src/dht/contact/ContactList.ts +2 -2
- package/src/dht/contact/RandomContactList.ts +2 -3
- package/src/dht/contact/RingContactList.ts +6 -16
- package/src/dht/contact/SortedContactList.ts +9 -10
- package/src/dht/contact/getClosestContacts.ts +22 -0
- package/src/dht/discovery/DiscoverySession.ts +35 -45
- package/src/dht/discovery/PeerDiscovery.ts +6 -9
- package/src/dht/discovery/RingDiscoverySession.ts +13 -13
- package/src/dht/recursive-operation/RecursiveOperationManager.ts +16 -16
- package/src/dht/recursive-operation/RecursiveOperationSession.ts +11 -13
- package/src/dht/recursive-operation/RecursiveOperationSessionRpcLocal.ts +1 -1
- package/src/dht/recursive-operation/RecursiveOperationSessionRpcRemote.ts +2 -2
- package/src/dht/routing/Router.ts +3 -5
- package/src/dht/routing/RoutingSession.ts +3 -4
- package/src/dht/store/StoreRpcLocal.ts +3 -3
- package/src/proto/packages/dht/protos/DhtRpc.client.ts +8 -0
- package/src/proto/packages/dht/protos/DhtRpc.server.ts +4 -0
- package/src/proto/packages/dht/protos/DhtRpc.ts +11 -3
- package/src/transport/ITransport.ts +3 -0
- package/test/end-to-end/Layer0MixedConnectionTypes.test.ts +3 -5
- package/test/integration/DhtNode.test.ts +81 -0
- package/test/integration/Layer1-scale.test.ts +1 -1
- package/test/integration/ScaleDownDht.test.ts +7 -4
- package/test/unit/DiscoverySession.test.ts +85 -0
- package/test/unit/PeerManager.test.ts +3 -17
- package/test/unit/RecursiveOperationManager.test.ts +5 -3
- package/test/unit/Router.test.ts +2 -2
- package/test/unit/RoutingSession.test.ts +2 -2
- package/test/unit/SortedContactList.test.ts +4 -4
- package/test/unit/getClosestContacts.test.ts +28 -0
- package/test/utils/FakeTransport.ts +34 -7
- package/test/utils/mock/Transport.ts +10 -0
- package/test/utils/topology.ts +80 -0
- package/test/RandomGraphSimulation.ts +0 -53
package/protos/DhtRpc.proto
CHANGED
|
@@ -9,7 +9,9 @@ import "google/protobuf/timestamp.proto";
|
|
|
9
9
|
import "packages/proto-rpc/protos/ProtoRpc.proto";
|
|
10
10
|
|
|
11
11
|
service DhtNodeRpc {
|
|
12
|
+
// TODO rename to getClosestNeighbors (breaking change)
|
|
12
13
|
rpc getClosestPeers (ClosestPeersRequest) returns (ClosestPeersResponse);
|
|
14
|
+
// TODO rename to getClosestRingContacts (breaking change)
|
|
13
15
|
rpc getClosestRingPeers (ClosestRingPeersRequest) returns (ClosestRingPeersResponse);
|
|
14
16
|
rpc ping (PingRequest) returns (PingResponse);
|
|
15
17
|
rpc leaveNotice (LeaveNotice) returns (google.protobuf.Empty);
|
|
@@ -92,21 +94,25 @@ message DataEntry {
|
|
|
92
94
|
bool deleted = 8;
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
// TODO rename to ClosestNeighborsRequest
|
|
95
98
|
message ClosestPeersRequest {
|
|
96
99
|
bytes nodeId = 1;
|
|
97
100
|
string requestId = 2;
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
// TODO rename to ClosestNeighborsResponse
|
|
100
104
|
message ClosestPeersResponse {
|
|
101
105
|
repeated PeerDescriptor peers = 1;
|
|
102
106
|
string requestId = 2;
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
// TODO rename to ClosestRingContactsRequest
|
|
105
110
|
message ClosestRingPeersRequest {
|
|
106
111
|
bytes ringId = 1;
|
|
107
112
|
string requestId = 2;
|
|
108
113
|
}
|
|
109
114
|
|
|
115
|
+
// TODO rename to ClosestRingContactsResponse
|
|
110
116
|
message ClosestRingPeersResponse {
|
|
111
117
|
repeated PeerDescriptor leftPeers = 1;
|
|
112
118
|
repeated PeerDescriptor rightPeers = 2;
|
|
@@ -125,7 +131,7 @@ enum RecursiveOperation {
|
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
message RecursiveOperationResponse {
|
|
128
|
-
repeated PeerDescriptor
|
|
134
|
+
repeated PeerDescriptor closestConnectedNodes = 1;
|
|
129
135
|
repeated DataEntry dataEntries = 2;
|
|
130
136
|
bool noCloserNodesFound = 3;
|
|
131
137
|
repeated PeerDescriptor routingPath = 4;
|
|
@@ -36,12 +36,10 @@ export class ConnectionLockStates {
|
|
|
36
36
|
public isRemoteLocked(id: DhtAddress, lockId?: LockID): boolean {
|
|
37
37
|
if (lockId === undefined) {
|
|
38
38
|
return this.remoteLocks.has(id)
|
|
39
|
+
} else if (this.remoteLocks.has(id) && this.remoteLocks.get(id)!.has(lockId)) {
|
|
40
|
+
return true
|
|
39
41
|
} else {
|
|
40
|
-
|
|
41
|
-
return true
|
|
42
|
-
} else {
|
|
43
|
-
return false
|
|
44
|
-
}
|
|
42
|
+
return false
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
@@ -304,7 +304,13 @@ export class ConnectionManager extends EventEmitter<TransportEvents> implements
|
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
public hasConnection(nodeId: DhtAddress): boolean {
|
|
307
|
-
|
|
307
|
+
// TODO if we remove filtering in getConnections, this can just be this.connection.has(nodeId)
|
|
308
|
+
return this.getConnections().some((c) => getNodeIdFromPeerDescriptor(c) == nodeId)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public getConnectionCount(): number {
|
|
312
|
+
// TODO if we remove filtering in getConnections, this can just be this.connection.length
|
|
313
|
+
return this.getConnections().length
|
|
308
314
|
}
|
|
309
315
|
|
|
310
316
|
public hasLocalLockedConnection(nodeId: DhtAddress): boolean {
|
|
@@ -545,6 +551,8 @@ export class ConnectionManager extends EventEmitter<TransportEvents> implements
|
|
|
545
551
|
|
|
546
552
|
public getConnections(): PeerDescriptor[] {
|
|
547
553
|
return Array.from(this.connections.values())
|
|
554
|
+
// TODO is this filtering needed? (if it is, should we do the same filtering e.g.
|
|
555
|
+
// in getConnection() or in other methods which access this.connections directly?)
|
|
548
556
|
.filter((managedConnection: ManagedConnection) => managedConnection.isHandshakeCompleted())
|
|
549
557
|
.map((managedConnection: ManagedConnection) => managedConnection.getPeerDescriptor()!)
|
|
550
558
|
}
|
|
@@ -96,21 +96,18 @@ export class ManagedConnection extends EventEmitter<Events> {
|
|
|
96
96
|
})
|
|
97
97
|
outgoingConnection.once('disconnected', this.onDisconnected)
|
|
98
98
|
|
|
99
|
-
} else {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
incomingConnection.on('disconnected', this.onDisconnected)
|
|
99
|
+
} else if (incomingConnection) {
|
|
100
|
+
this.handshaker = new Handshaker(this.localPeerDescriptor, incomingConnection)
|
|
101
|
+
this.handshaker.on('handshakeRequest', (
|
|
102
|
+
sourcePeerDescriptor: PeerDescriptor,
|
|
103
|
+
version: string,
|
|
104
|
+
targetPeerDescriptor?: PeerDescriptor
|
|
105
|
+
) => {
|
|
106
|
+
this.setRemotePeerDescriptor(sourcePeerDescriptor)
|
|
107
|
+
this.emit('handshakeRequest', sourcePeerDescriptor, version, targetPeerDescriptor)
|
|
108
|
+
})
|
|
112
109
|
|
|
113
|
-
|
|
110
|
+
incomingConnection.on('disconnected', this.onDisconnected)
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
113
|
|
|
@@ -154,6 +154,7 @@ export class WebsocketConnector {
|
|
|
154
154
|
// The localPeerDescriptor can be undefined here as the WS server is used for connectivity checks
|
|
155
155
|
// before the localPeerDescriptor is set during start.
|
|
156
156
|
// Handshaked connections should be rejected before the localPeerDescriptor is set.
|
|
157
|
+
// eslint-disable-next-line no-lonely-if
|
|
157
158
|
if (this.localPeerDescriptor !== undefined) {
|
|
158
159
|
this.attachHandshaker(connection)
|
|
159
160
|
} else {
|
package/src/dht/DhtNode.ts
CHANGED
|
@@ -53,12 +53,12 @@ import { getLocalRegion } from '@streamr/cdn-location'
|
|
|
53
53
|
import { RingContacts } from './contact/RingContactList'
|
|
54
54
|
|
|
55
55
|
export interface DhtNodeEvents {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
randomContactAdded: (peerDescriptor: PeerDescriptor
|
|
59
|
-
randomContactRemoved: (peerDescriptor: PeerDescriptor
|
|
60
|
-
ringContactAdded: (peerDescriptor: PeerDescriptor
|
|
61
|
-
ringContactRemoved: (peerDescriptor: PeerDescriptor
|
|
56
|
+
closestContactAdded: (peerDescriptor: PeerDescriptor) => void
|
|
57
|
+
closestContactRemoved: (peerDescriptor: PeerDescriptor) => void
|
|
58
|
+
randomContactAdded: (peerDescriptor: PeerDescriptor) => void
|
|
59
|
+
randomContactRemoved: (peerDescriptor: PeerDescriptor) => void
|
|
60
|
+
ringContactAdded: (peerDescriptor: PeerDescriptor) => void
|
|
61
|
+
ringContactRemoved: (peerDescriptor: PeerDescriptor) => void
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export interface DhtNodeOptions {
|
|
@@ -241,7 +241,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
241
241
|
|
|
242
242
|
this.rpcCommunicator = new RoutingRpcCommunicator(
|
|
243
243
|
this.config.serviceId,
|
|
244
|
-
this.transport
|
|
244
|
+
(msg, opts) => this.transport!.send(msg, opts),
|
|
245
245
|
{ rpcRequestTimeout: this.config.rpcRequestTimeout }
|
|
246
246
|
)
|
|
247
247
|
|
|
@@ -252,7 +252,6 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
252
252
|
this.peerDiscovery = new PeerDiscovery({
|
|
253
253
|
localPeerDescriptor: this.localPeerDescriptor!,
|
|
254
254
|
joinNoProgressLimit: this.config.joinNoProgressLimit,
|
|
255
|
-
peerDiscoveryQueryBatchSize: this.config.peerDiscoveryQueryBatchSize,
|
|
256
255
|
joinTimeout: this.config.dhtJoinTimeout,
|
|
257
256
|
serviceId: this.config.serviceId,
|
|
258
257
|
parallelism: this.config.joinParallelism,
|
|
@@ -261,19 +260,19 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
261
260
|
})
|
|
262
261
|
this.router = new Router({
|
|
263
262
|
rpcCommunicator: this.rpcCommunicator,
|
|
264
|
-
connections: this.peerManager!.connections,
|
|
265
263
|
localPeerDescriptor: this.localPeerDescriptor!,
|
|
266
264
|
handleMessage: (message: Message) => this.handleMessageFromRouter(message),
|
|
265
|
+
getConnections: () => this.getConnections()
|
|
267
266
|
})
|
|
268
267
|
this.recursiveOperationManager = new RecursiveOperationManager({
|
|
269
268
|
rpcCommunicator: this.rpcCommunicator,
|
|
270
269
|
router: this.router,
|
|
271
270
|
sessionTransport: this,
|
|
272
|
-
connections: this.peerManager!.connections,
|
|
273
271
|
localPeerDescriptor: this.localPeerDescriptor!,
|
|
274
272
|
serviceId: this.config.serviceId,
|
|
273
|
+
localDataStore: this.localDataStore,
|
|
275
274
|
addContact: (contact: PeerDescriptor) => this.peerManager!.addContact(contact),
|
|
276
|
-
|
|
275
|
+
createDhtNodeRpcRemote: (peerDescriptor: PeerDescriptor) => this.createDhtNodeRpcRemote(peerDescriptor),
|
|
277
276
|
})
|
|
278
277
|
this.storeManager = new StoreManager({
|
|
279
278
|
rpcCommunicator: this.rpcCommunicator,
|
|
@@ -296,7 +295,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
296
295
|
)
|
|
297
296
|
}
|
|
298
297
|
})
|
|
299
|
-
this.on('
|
|
298
|
+
this.on('closestContactAdded', (peerDescriptor: PeerDescriptor) => {
|
|
300
299
|
this.storeManager!.onContactAdded(peerDescriptor)
|
|
301
300
|
})
|
|
302
301
|
this.bindRpcLocalMethods()
|
|
@@ -309,28 +308,27 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
309
308
|
localNodeId: this.getNodeId(),
|
|
310
309
|
localPeerDescriptor: this.localPeerDescriptor!,
|
|
311
310
|
connectionLocker: this.connectionLocker,
|
|
312
|
-
|
|
313
|
-
isLayer0: (this.connectionLocker !== undefined),
|
|
311
|
+
lockId: this.config.serviceId,
|
|
314
312
|
createDhtNodeRpcRemote: (peerDescriptor: PeerDescriptor) => this.createDhtNodeRpcRemote(peerDescriptor),
|
|
315
|
-
|
|
313
|
+
hasConnection: (nodeId: DhtAddress) => this.transport!.hasConnection(nodeId)
|
|
316
314
|
})
|
|
317
|
-
this.peerManager.on('
|
|
318
|
-
this.emit('
|
|
315
|
+
this.peerManager.on('closestContactRemoved', (peerDescriptor: PeerDescriptor) => {
|
|
316
|
+
this.emit('closestContactRemoved', peerDescriptor)
|
|
319
317
|
})
|
|
320
|
-
this.peerManager.on('
|
|
321
|
-
this.emit('
|
|
318
|
+
this.peerManager.on('closestContactAdded', (peerDescriptor: PeerDescriptor) =>
|
|
319
|
+
this.emit('closestContactAdded', peerDescriptor)
|
|
322
320
|
)
|
|
323
|
-
this.peerManager.on('randomContactRemoved', (peerDescriptor: PeerDescriptor
|
|
324
|
-
this.emit('randomContactRemoved', peerDescriptor
|
|
321
|
+
this.peerManager.on('randomContactRemoved', (peerDescriptor: PeerDescriptor) =>
|
|
322
|
+
this.emit('randomContactRemoved', peerDescriptor)
|
|
325
323
|
)
|
|
326
|
-
this.peerManager.on('randomContactAdded', (peerDescriptor: PeerDescriptor
|
|
327
|
-
this.emit('randomContactAdded', peerDescriptor
|
|
324
|
+
this.peerManager.on('randomContactAdded', (peerDescriptor: PeerDescriptor) =>
|
|
325
|
+
this.emit('randomContactAdded', peerDescriptor)
|
|
328
326
|
)
|
|
329
|
-
this.peerManager.on('ringContactRemoved', (peerDescriptor: PeerDescriptor
|
|
330
|
-
this.emit('ringContactRemoved', peerDescriptor
|
|
327
|
+
this.peerManager.on('ringContactRemoved', (peerDescriptor: PeerDescriptor) => {
|
|
328
|
+
this.emit('ringContactRemoved', peerDescriptor)
|
|
331
329
|
})
|
|
332
|
-
this.peerManager.on('ringContactAdded', (peerDescriptor: PeerDescriptor
|
|
333
|
-
this.emit('ringContactAdded', peerDescriptor
|
|
330
|
+
this.peerManager.on('ringContactAdded', (peerDescriptor: PeerDescriptor) => {
|
|
331
|
+
this.emit('ringContactAdded', peerDescriptor)
|
|
334
332
|
})
|
|
335
333
|
this.peerManager.on('kBucketEmpty', () => {
|
|
336
334
|
if (!this.peerDiscovery!.isJoinOngoing()
|
|
@@ -348,18 +346,22 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
348
346
|
}
|
|
349
347
|
})
|
|
350
348
|
this.transport!.on('connected', (peerDescriptor: PeerDescriptor) => {
|
|
351
|
-
this.peerManager!.onContactConnected(peerDescriptor)
|
|
352
349
|
this.router!.onNodeConnected(peerDescriptor)
|
|
353
350
|
this.emit('connected', peerDescriptor)
|
|
354
351
|
})
|
|
355
352
|
this.transport!.on('disconnected', (peerDescriptor: PeerDescriptor, gracefulLeave: boolean) => {
|
|
356
|
-
this.
|
|
353
|
+
const isLayer0 = (this.connectionLocker !== undefined)
|
|
354
|
+
if (isLayer0) {
|
|
355
|
+
const nodeId = getNodeIdFromPeerDescriptor(peerDescriptor)
|
|
356
|
+
if (gracefulLeave) {
|
|
357
|
+
this.peerManager!.removeContact(nodeId)
|
|
358
|
+
} else {
|
|
359
|
+
this.peerManager!.removeNeighbor(nodeId)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
357
362
|
this.router!.onNodeDisconnected(peerDescriptor)
|
|
358
363
|
this.emit('disconnected', peerDescriptor, gracefulLeave)
|
|
359
364
|
})
|
|
360
|
-
this.transport!.getConnections().forEach((peer) => {
|
|
361
|
-
this.peerManager!.onContactConnected(peer)
|
|
362
|
-
})
|
|
363
365
|
}
|
|
364
366
|
|
|
365
367
|
private bindRpcLocalMethods(): void {
|
|
@@ -368,11 +370,11 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
368
370
|
}
|
|
369
371
|
const dhtNodeRpcLocal = new DhtNodeRpcLocal({
|
|
370
372
|
peerDiscoveryQueryBatchSize: this.config.peerDiscoveryQueryBatchSize,
|
|
371
|
-
|
|
373
|
+
getClosestNeighborsTo: (nodeId: DhtAddress, limit: number) => {
|
|
372
374
|
return this.peerManager!.getClosestNeighborsTo(nodeId, limit)
|
|
373
375
|
.map((dhtPeer: DhtNodeRpcRemote) => dhtPeer.getPeerDescriptor())
|
|
374
376
|
},
|
|
375
|
-
|
|
377
|
+
getClosestRingContactsTo: (ringIdRaw: RingIdRaw, limit: number) => {
|
|
376
378
|
return this.getClosestRingContactsTo(ringIdRaw, limit)
|
|
377
379
|
},
|
|
378
380
|
addContact: (contact: PeerDescriptor) => this.peerManager!.addContact(contact),
|
|
@@ -432,9 +434,22 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
432
434
|
}
|
|
433
435
|
|
|
434
436
|
public getClosestContacts(limit?: number): PeerDescriptor[] {
|
|
435
|
-
return this.peerManager!.
|
|
436
|
-
|
|
437
|
-
|
|
437
|
+
return this.peerManager!.getClosestContacts()
|
|
438
|
+
.getClosestContacts(limit)
|
|
439
|
+
.map((peer) => peer.getPeerDescriptor())
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// TODO remove defaultContactQueryLimit parameter from RandomContactList#getContacts and use explicit value here?
|
|
443
|
+
getRandomContacts(): PeerDescriptor[] {
|
|
444
|
+
return this.peerManager!.getRandomContacts().getContacts().map((c) => c.getPeerDescriptor())
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
getRingContacts(): RingContacts {
|
|
448
|
+
const contacts = this.peerManager!.getRingContacts().getClosestContacts()
|
|
449
|
+
return {
|
|
450
|
+
left: contacts.left.map((c) => c.getPeerDescriptor()),
|
|
451
|
+
right: contacts.right.map((c) => c.getPeerDescriptor())
|
|
452
|
+
}
|
|
438
453
|
}
|
|
439
454
|
|
|
440
455
|
public getClosestRingContactsTo(ringIdRaw: RingIdRaw, limit?: number): RingContacts {
|
|
@@ -470,7 +485,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
470
485
|
|
|
471
486
|
private getConnectedEntryPoints(): PeerDescriptor[] {
|
|
472
487
|
return this.config.entryPoints !== undefined ? this.config.entryPoints.filter((entryPoint) =>
|
|
473
|
-
this.
|
|
488
|
+
this.transport!.hasConnection(getNodeIdFromPeerDescriptor(entryPoint))
|
|
474
489
|
) : []
|
|
475
490
|
}
|
|
476
491
|
|
|
@@ -544,16 +559,20 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
544
559
|
return this.localPeerDescriptor!
|
|
545
560
|
}
|
|
546
561
|
|
|
547
|
-
public getConnections(): PeerDescriptor[] {
|
|
548
|
-
return Array.from(this.peerManager!.connections.values()).map((peer) => peer.getPeerDescriptor())
|
|
549
|
-
}
|
|
550
|
-
|
|
551
562
|
public getNeighbors(): PeerDescriptor[] {
|
|
552
563
|
return this.started ? this.peerManager!.getNeighbors() : []
|
|
553
564
|
}
|
|
554
565
|
|
|
566
|
+
public getConnections(): PeerDescriptor[] {
|
|
567
|
+
return this.transport!.getConnections()
|
|
568
|
+
}
|
|
569
|
+
|
|
555
570
|
public getConnectionCount(): number {
|
|
556
|
-
return this.
|
|
571
|
+
return this.transport!.getConnectionCount()
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
public hasConnection(nodeId: DhtAddress): boolean {
|
|
575
|
+
return this.transport!.hasConnection(nodeId)
|
|
557
576
|
}
|
|
558
577
|
|
|
559
578
|
public getLocalLockedConnectionCount(): number {
|
|
@@ -573,7 +592,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
|
|
|
573
592
|
if (!this.peerManager) {
|
|
574
593
|
return false
|
|
575
594
|
} else {
|
|
576
|
-
return (this.
|
|
595
|
+
return (this.getConnectionCount() > 0)
|
|
577
596
|
}
|
|
578
597
|
}, this.config.networkConnectivityTimeout, 100, this.abortController.signal)
|
|
579
598
|
}
|
|
@@ -18,8 +18,8 @@ import { RingContacts } from './contact/RingContactList'
|
|
|
18
18
|
|
|
19
19
|
interface DhtNodeRpcLocalConfig {
|
|
20
20
|
peerDiscoveryQueryBatchSize: number
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
getClosestNeighborsTo: (nodeId: DhtAddress, limit: number) => PeerDescriptor[]
|
|
22
|
+
getClosestRingContactsTo: (id: RingIdRaw, limit: number) => RingContacts
|
|
23
23
|
addContact: (contact: PeerDescriptor) => void
|
|
24
24
|
removeContact: (nodeId: DhtAddress) => void
|
|
25
25
|
}
|
|
@@ -37,18 +37,19 @@ export class DhtNodeRpcLocal implements IDhtNodeRpc {
|
|
|
37
37
|
async getClosestPeers(request: ClosestPeersRequest, context: ServerCallContext): Promise<ClosestPeersResponse> {
|
|
38
38
|
this.config.addContact((context as DhtCallContext).incomingSourceDescriptor!)
|
|
39
39
|
const response = {
|
|
40
|
-
peers: this.config.
|
|
40
|
+
peers: this.config.getClosestNeighborsTo(getDhtAddressFromRaw(request.nodeId), this.config.peerDiscoveryQueryBatchSize),
|
|
41
41
|
requestId: request.requestId
|
|
42
42
|
}
|
|
43
43
|
return response
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// TODO rename to getClosestRingContacts
|
|
46
47
|
async getClosestRingPeers(request: ClosestRingPeersRequest, context: ServerCallContext): Promise<ClosestRingPeersResponse> {
|
|
47
48
|
this.config.addContact((context as DhtCallContext).incomingSourceDescriptor!)
|
|
48
|
-
const
|
|
49
|
+
const closestContacts = this.config.getClosestRingContactsTo(request.ringId as RingIdRaw, this.config.peerDiscoveryQueryBatchSize)
|
|
49
50
|
const response = {
|
|
50
|
-
leftPeers:
|
|
51
|
-
rightPeers:
|
|
51
|
+
leftPeers: closestContacts.left,
|
|
52
|
+
rightPeers: closestContacts.right,
|
|
52
53
|
requestId: request.requestId
|
|
53
54
|
}
|
|
54
55
|
return response
|
|
@@ -58,6 +58,7 @@ export class DhtNodeRpcRemote extends RpcRemote<DhtNodeRpcClient> implements KBu
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// TODO rename to getClosestRingContacts (breaking change)
|
|
61
62
|
async getClosestRingPeers(ringIdRaw: RingIdRaw): Promise<RingContacts> {
|
|
62
63
|
logger.trace(`Requesting getClosestRingPeers on ${this.serviceId} from ${this.getNodeId()}`)
|
|
63
64
|
const request: ClosestRingPeersRequest = {
|
package/src/dht/PeerManager.ts
CHANGED
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
} from '../proto/packages/dht/protos/DhtRpc'
|
|
8
8
|
import { DhtNodeRpcRemote } from './DhtNodeRpcRemote'
|
|
9
9
|
import { RandomContactList } from './contact/RandomContactList'
|
|
10
|
-
import { SortedContactList } from './contact/SortedContactList'
|
|
10
|
+
import { ReadonlySortedContactList, SortedContactList } from './contact/SortedContactList'
|
|
11
11
|
import { ConnectionLocker } from '../connection/ConnectionManager'
|
|
12
12
|
import EventEmitter from 'eventemitter3'
|
|
13
13
|
import { DhtAddress, DhtAddressRaw, getNodeIdFromPeerDescriptor, getRawFromDhtAddress } from '../identifiers'
|
|
14
|
-
import { RingContactList
|
|
14
|
+
import { RingContactList } from './contact/RingContactList'
|
|
15
15
|
import { RingIdRaw, getRingIdRawFromPeerDescriptor } from './contact/ringIdentifiers'
|
|
16
16
|
import { LockID } from '../connection/ConnectionLockStates'
|
|
17
17
|
|
|
@@ -20,22 +20,21 @@ const logger = new Logger(module)
|
|
|
20
20
|
interface PeerManagerConfig {
|
|
21
21
|
numberOfNodesPerKBucket: number
|
|
22
22
|
maxContactListSize: number
|
|
23
|
-
peerDiscoveryQueryBatchSize: number
|
|
24
23
|
localNodeId: DhtAddress
|
|
25
24
|
localPeerDescriptor: PeerDescriptor
|
|
26
25
|
connectionLocker?: ConnectionLocker
|
|
27
|
-
isLayer0: boolean
|
|
28
26
|
lockId: LockID
|
|
29
27
|
createDhtNodeRpcRemote: (peerDescriptor: PeerDescriptor) => DhtNodeRpcRemote
|
|
28
|
+
hasConnection: (nodeId: DhtAddress) => boolean
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export interface PeerManagerEvents {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
randomContactAdded: (peerDescriptor: PeerDescriptor
|
|
36
|
-
randomContactRemoved: (peerDescriptor: PeerDescriptor
|
|
37
|
-
ringContactAdded: (peerDescriptor: PeerDescriptor
|
|
38
|
-
ringContactRemoved: (peerDescriptor: PeerDescriptor
|
|
32
|
+
closestContactAdded: (peerDescriptor: PeerDescriptor) => void
|
|
33
|
+
closestContactRemoved: (peerDescriptor: PeerDescriptor) => void
|
|
34
|
+
randomContactAdded: (peerDescriptor: PeerDescriptor) => void
|
|
35
|
+
randomContactRemoved: (peerDescriptor: PeerDescriptor) => void
|
|
36
|
+
ringContactAdded: (peerDescriptor: PeerDescriptor) => void
|
|
37
|
+
ringContactRemoved: (peerDescriptor: PeerDescriptor) => void
|
|
39
38
|
kBucketEmpty: () => void
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -52,8 +51,6 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
52
51
|
// * 'contacts' are all non-unresponsive nodes that we know about
|
|
53
52
|
|
|
54
53
|
private neighbors: KBucket<DhtNodeRpcRemote>
|
|
55
|
-
// Nodes that are connected to this node on Layer0
|
|
56
|
-
public readonly connections: Map<DhtAddress, DhtNodeRpcRemote> = new Map()
|
|
57
54
|
private closestContacts: SortedContactList<DhtNodeRpcRemote>
|
|
58
55
|
private activeContacts: Set<DhtAddress>
|
|
59
56
|
private ringContacts: RingContactList<DhtNodeRpcRemote>
|
|
@@ -70,11 +67,11 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
70
67
|
numberOfNodesToPing: this.config.numberOfNodesPerKBucket
|
|
71
68
|
})
|
|
72
69
|
this.ringContacts = new RingContactList<DhtNodeRpcRemote>(getRingIdRawFromPeerDescriptor(this.config.localPeerDescriptor))
|
|
73
|
-
this.ringContacts.on('
|
|
74
|
-
this.emit('ringContactAdded',
|
|
70
|
+
this.ringContacts.on('contactAdded', (contact: DhtNodeRpcRemote) => {
|
|
71
|
+
this.emit('ringContactAdded', contact.getPeerDescriptor())
|
|
75
72
|
})
|
|
76
|
-
this.ringContacts.on('
|
|
77
|
-
this.emit('ringContactRemoved',
|
|
73
|
+
this.ringContacts.on('contactRemoved', (contact: DhtNodeRpcRemote) => {
|
|
74
|
+
this.emit('ringContactRemoved', contact.getPeerDescriptor())
|
|
78
75
|
})
|
|
79
76
|
this.neighbors.on('ping', (oldContacts: DhtNodeRpcRemote[], newContact: DhtNodeRpcRemote) => this.onKBucketPing(oldContacts, newContact))
|
|
80
77
|
this.neighbors.on('removed', (contact: DhtNodeRpcRemote) => this.onKBucketRemoved(getNodeIdFromPeerDescriptor(contact.getPeerDescriptor())))
|
|
@@ -87,23 +84,23 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
87
84
|
maxSize: this.config.maxContactListSize,
|
|
88
85
|
allowToContainReferenceId: false
|
|
89
86
|
})
|
|
90
|
-
this.closestContacts.on('contactRemoved', (
|
|
87
|
+
this.closestContacts.on('contactRemoved', (contact: DhtNodeRpcRemote) => {
|
|
91
88
|
if (this.stopped) {
|
|
92
89
|
return
|
|
93
90
|
}
|
|
94
|
-
this.emit('
|
|
95
|
-
this.randomContacts.addContact(this.config.createDhtNodeRpcRemote(
|
|
91
|
+
this.emit('closestContactRemoved', contact.getPeerDescriptor())
|
|
92
|
+
this.randomContacts.addContact(this.config.createDhtNodeRpcRemote(contact.getPeerDescriptor()))
|
|
96
93
|
})
|
|
97
|
-
this.closestContacts.on('contactAdded', (
|
|
98
|
-
this.emit('
|
|
94
|
+
this.closestContacts.on('contactAdded', (contact: DhtNodeRpcRemote) =>
|
|
95
|
+
this.emit('closestContactAdded', contact.getPeerDescriptor())
|
|
99
96
|
)
|
|
100
97
|
this.activeContacts = new Set()
|
|
101
98
|
this.randomContacts = new RandomContactList(this.config.localNodeId, this.config.maxContactListSize)
|
|
102
|
-
this.randomContacts.on('contactRemoved', (removedContact: DhtNodeRpcRemote
|
|
103
|
-
this.emit('randomContactRemoved', removedContact.getPeerDescriptor()
|
|
99
|
+
this.randomContacts.on('contactRemoved', (removedContact: DhtNodeRpcRemote) =>
|
|
100
|
+
this.emit('randomContactRemoved', removedContact.getPeerDescriptor())
|
|
104
101
|
)
|
|
105
|
-
this.randomContacts.on('contactAdded', (contactAdded: DhtNodeRpcRemote
|
|
106
|
-
this.emit('randomContactAdded', contactAdded.getPeerDescriptor()
|
|
102
|
+
this.randomContacts.on('contactAdded', (contactAdded: DhtNodeRpcRemote) =>
|
|
103
|
+
this.emit('randomContactAdded', contactAdded.getPeerDescriptor())
|
|
107
104
|
)
|
|
108
105
|
}
|
|
109
106
|
|
|
@@ -143,7 +140,7 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
143
140
|
const nodeId = getNodeIdFromPeerDescriptor(peerDescriptor)
|
|
144
141
|
// Important to lock here, before the ping result is known
|
|
145
142
|
this.config.connectionLocker?.weakLockConnection(nodeId, this.config.lockId)
|
|
146
|
-
if (this.
|
|
143
|
+
if (this.config.hasConnection(contact.getNodeId())) {
|
|
147
144
|
logger.trace(`Added new contact ${nodeId}`)
|
|
148
145
|
} else { // open connection by pinging
|
|
149
146
|
logger.trace('starting ping ' + nodeId)
|
|
@@ -184,35 +181,6 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
184
181
|
return undefined
|
|
185
182
|
}
|
|
186
183
|
|
|
187
|
-
onContactConnected(peerDescriptor: PeerDescriptor): void {
|
|
188
|
-
const nodeId = getNodeIdFromPeerDescriptor(peerDescriptor)
|
|
189
|
-
if (nodeId === this.config.localNodeId) {
|
|
190
|
-
logger.error('handleConnected() to self')
|
|
191
|
-
}
|
|
192
|
-
const rpcRemote = this.config.createDhtNodeRpcRemote(peerDescriptor)
|
|
193
|
-
if (!this.connections.has(nodeId)) {
|
|
194
|
-
this.connections.set(nodeId, rpcRemote)
|
|
195
|
-
logger.trace('connectionschange add ' + this.connections.size)
|
|
196
|
-
} else {
|
|
197
|
-
logger.trace('new connection not set to connections, there is already a connection with the peer ID')
|
|
198
|
-
}
|
|
199
|
-
logger.trace('connected: ' + nodeId + ' ' + this.connections.size)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
onContactDisconnected(nodeId: DhtAddress, gracefulLeave: boolean): void {
|
|
203
|
-
logger.trace('disconnected: ' + nodeId)
|
|
204
|
-
this.connections.delete(nodeId)
|
|
205
|
-
if (this.config.isLayer0) {
|
|
206
|
-
this.neighbors.remove(getRawFromDhtAddress(nodeId))
|
|
207
|
-
if (gracefulLeave === true) {
|
|
208
|
-
logger.trace(nodeId + ' ' + 'onTransportDisconnected with gracefulLeave ' + gracefulLeave)
|
|
209
|
-
this.removeContact(nodeId)
|
|
210
|
-
} else {
|
|
211
|
-
logger.trace(nodeId + ' ' + 'onTransportDisconnected with gracefulLeave ' + gracefulLeave)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
184
|
removeContact(nodeId: DhtAddress): void {
|
|
217
185
|
if (this.stopped) {
|
|
218
186
|
return
|
|
@@ -225,6 +193,10 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
225
193
|
this.randomContacts.removeContact(nodeId)
|
|
226
194
|
}
|
|
227
195
|
|
|
196
|
+
removeNeighbor(nodeId: DhtAddress): void {
|
|
197
|
+
this.neighbors.remove(getRawFromDhtAddress(nodeId))
|
|
198
|
+
}
|
|
199
|
+
|
|
228
200
|
stop(): void {
|
|
229
201
|
this.stopped = true
|
|
230
202
|
this.neighbors.toArray().forEach((rpcRemote: DhtNodeRpcRemote) => {
|
|
@@ -238,7 +210,6 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
238
210
|
})
|
|
239
211
|
this.closestContacts.stop()
|
|
240
212
|
this.randomContacts.stop()
|
|
241
|
-
this.connections.clear()
|
|
242
213
|
}
|
|
243
214
|
|
|
244
215
|
getClosestNeighborsTo(referenceId: DhtAddress, limit?: number, excludedNodeIds?: Set<DhtAddress>): DhtNodeRpcRemote[] {
|
|
@@ -252,18 +223,8 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
252
223
|
return closest.getClosestContacts()
|
|
253
224
|
}
|
|
254
225
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const closest = new SortedContactList<DhtNodeRpcRemote>({
|
|
258
|
-
referenceId,
|
|
259
|
-
allowToContainReferenceId: true,
|
|
260
|
-
excludedNodeIds,
|
|
261
|
-
maxSize: limit
|
|
262
|
-
})
|
|
263
|
-
for (const contact of this.closestContacts.getAllContactsInUndefinedOrder()) {
|
|
264
|
-
closest.addContact(contact)
|
|
265
|
-
}
|
|
266
|
-
return closest.getClosestContacts()
|
|
226
|
+
getClosestContacts(): ReadonlySortedContactList<DhtNodeRpcRemote> {
|
|
227
|
+
return this.closestContacts
|
|
267
228
|
}
|
|
268
229
|
|
|
269
230
|
getClosestRingContactsTo(
|
|
@@ -278,15 +239,20 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
|
|
|
278
239
|
this.ringContacts.getAllContacts().map((contact) => closest.addContact(contact))
|
|
279
240
|
return closest.getClosestContacts(limit ?? 8)
|
|
280
241
|
}
|
|
242
|
+
|
|
243
|
+
getRandomContacts(): RandomContactList<DhtNodeRpcRemote> {
|
|
244
|
+
return this.randomContacts
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
getRingContacts(): RingContactList<DhtNodeRpcRemote> {
|
|
248
|
+
return this.ringContacts
|
|
249
|
+
}
|
|
281
250
|
|
|
251
|
+
// TODO rename getNearbyContactsCount
|
|
282
252
|
getContactCount(excludedNodeIds?: Set<DhtAddress>): number {
|
|
283
253
|
return this.closestContacts.getSize(excludedNodeIds)
|
|
284
254
|
}
|
|
285
255
|
|
|
286
|
-
getConnectionCount(): number {
|
|
287
|
-
return this.connections.size
|
|
288
|
-
}
|
|
289
|
-
|
|
290
256
|
getNeighborCount(): number {
|
|
291
257
|
return this.neighbors.count()
|
|
292
258
|
}
|
|
@@ -2,8 +2,8 @@ import EventEmitter from 'eventemitter3'
|
|
|
2
2
|
import { DhtAddress } from '../../identifiers'
|
|
3
3
|
|
|
4
4
|
export interface Events<C> {
|
|
5
|
-
contactRemoved: (removedContact: C
|
|
6
|
-
contactAdded: (contactAdded: C
|
|
5
|
+
contactRemoved: (removedContact: C) => void
|
|
6
|
+
contactAdded: (contactAdded: C) => void
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export class ContactList<C extends { getNodeId: () => DhtAddress }> extends EventEmitter<Events<C>> {
|
|
@@ -30,8 +30,7 @@ export class RandomContactList<C extends { getNodeId: () => DhtAddress }> extend
|
|
|
30
30
|
this.contactsById.set(contact.getNodeId(), contact)
|
|
31
31
|
this.emit(
|
|
32
32
|
'contactAdded',
|
|
33
|
-
contact
|
|
34
|
-
this.getContacts()
|
|
33
|
+
contact
|
|
35
34
|
)
|
|
36
35
|
}
|
|
37
36
|
}
|
|
@@ -43,7 +42,7 @@ export class RandomContactList<C extends { getNodeId: () => DhtAddress }> extend
|
|
|
43
42
|
const index = this.contactIds.findIndex((nodeId) => (nodeId === id))
|
|
44
43
|
this.contactIds.splice(index, 1)
|
|
45
44
|
this.contactsById.delete(id)
|
|
46
|
-
this.emit('contactRemoved', removed
|
|
45
|
+
this.emit('contactRemoved', removed)
|
|
47
46
|
return true
|
|
48
47
|
}
|
|
49
48
|
return false
|