@olane/o-node 0.7.40 → 0.7.41
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/src/connection/index.d.ts +1 -1
- package/dist/src/connection/index.d.ts.map +1 -1
- package/dist/src/connection/index.js +1 -1
- package/dist/src/connection/interfaces/o-node-connection-stream.config.d.ts +8 -0
- package/dist/src/connection/interfaces/o-node-connection-stream.config.d.ts.map +1 -0
- package/dist/src/connection/interfaces/o-node-connection-stream.config.js +1 -0
- package/dist/src/connection/o-node-connection-stream.d.ts +34 -0
- package/dist/src/connection/o-node-connection-stream.d.ts.map +1 -0
- package/dist/src/connection/o-node-connection-stream.js +68 -0
- package/dist/src/connection/o-node-connection.d.ts +10 -4
- package/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +51 -34
- package/dist/src/connection/o-node-connection.manager.d.ts +4 -0
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +24 -0
- package/dist/src/connection/stream-handler.d.ts +1 -59
- package/dist/src/connection/stream-handler.d.ts.map +1 -1
- package/dist/src/connection/stream-handler.js +104 -179
- package/dist/src/o-node.tool.d.ts +3 -1
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +44 -11
- package/dist/src/router/o-node.address.d.ts +1 -0
- package/dist/src/router/o-node.address.d.ts.map +1 -1
- package/dist/src/router/o-node.address.js +4 -0
- package/dist/src/utils/connection.utils.d.ts +9 -0
- package/dist/src/utils/connection.utils.d.ts.map +1 -0
- package/dist/src/utils/connection.utils.js +26 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -0
- package/dist/test/astream-reuse.spec.d.ts +2 -0
- package/dist/test/astream-reuse.spec.d.ts.map +1 -0
- package/dist/test/astream-reuse.spec.js +107 -0
- package/dist/test/connection-management.spec.js +1 -0
- package/dist/test/helpers/network-builder.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/src/connection/o-managed-stream.d.ts +0 -57
- package/dist/src/connection/o-managed-stream.d.ts.map +0 -1
- package/dist/src/connection/o-managed-stream.js +0 -76
- package/dist/test/o-managed-stream.spec.d.ts +0 -2
- package/dist/test/o-managed-stream.spec.d.ts.map +0 -1
- package/dist/test/o-managed-stream.spec.js +0 -122
- package/dist/test/stream-handler-caching.spec.d.ts +0 -2
- package/dist/test/stream-handler-caching.spec.d.ts.map +0 -1
- package/dist/test/stream-handler-caching.spec.js +0 -261
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { oRequest, oResponse, CoreUtils, oError, oErrorCodes, Logger, ResponseBuilder, } from '@olane/o-core';
|
|
2
2
|
import { lpStream } from '@olane/o-config';
|
|
3
3
|
import JSON5 from 'json5';
|
|
4
|
-
import { oManagedStream } from './o-managed-stream.js';
|
|
5
4
|
/**
|
|
6
5
|
* StreamHandler centralizes all stream-related functionality including:
|
|
7
6
|
* - Message type detection (request vs response)
|
|
@@ -12,7 +11,6 @@ import { oManagedStream } from './o-managed-stream.js';
|
|
|
12
11
|
*/
|
|
13
12
|
export class StreamHandler {
|
|
14
13
|
constructor(logger) {
|
|
15
|
-
this.streamCache = new Map();
|
|
16
14
|
this.logger = logger ?? new Logger('StreamHandler');
|
|
17
15
|
}
|
|
18
16
|
/**
|
|
@@ -85,161 +83,110 @@ export class StreamHandler {
|
|
|
85
83
|
const addresses = [callerAddress.value, receiverAddress.value].sort();
|
|
86
84
|
return `${addresses[0]}↔${addresses[1]}`;
|
|
87
85
|
}
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (cachedStream?.isReusable) {
|
|
193
|
-
this.logger.debug('Reusing cached stream by address', {
|
|
194
|
-
cacheKey,
|
|
195
|
-
streamId: cachedStream.stream.id,
|
|
196
|
-
direction: cachedStream.direction,
|
|
197
|
-
});
|
|
198
|
-
cachedStream.updateLastUsed();
|
|
199
|
-
return cachedStream.stream;
|
|
200
|
-
}
|
|
201
|
-
else if (cachedStream) {
|
|
202
|
-
// Stream exists but not reusable, remove from cache
|
|
203
|
-
this.logger.debug('Removing non-reusable stream from cache', {
|
|
204
|
-
cacheKey,
|
|
205
|
-
status: cachedStream.stream.status,
|
|
206
|
-
});
|
|
207
|
-
this.streamCache.delete(cacheKey);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
this.logger.debug('No reusable cached stream found, checking connection streams', connection.streams.map((s) => ({
|
|
211
|
-
status: s.status,
|
|
212
|
-
protocol: s.protocol,
|
|
213
|
-
writeStatus: s.writeStatus,
|
|
214
|
-
remoteReadStatus: s.remoteReadStatus,
|
|
215
|
-
id: s.id,
|
|
216
|
-
})));
|
|
217
|
-
// Fallback to protocol-based check (legacy behavior)
|
|
218
|
-
if (reusePolicy === 'reuse' && !streamAddresses) {
|
|
219
|
-
const existingStream = connection.streams.find((stream) => stream.status === 'open' &&
|
|
220
|
-
stream.protocol === protocol &&
|
|
221
|
-
stream.writeStatus === 'writable' &&
|
|
222
|
-
stream.remoteReadStatus === 'readable');
|
|
223
|
-
if (existingStream) {
|
|
224
|
-
this.logger.debug('Reusing existing stream by protocol (legacy)', existingStream.id, existingStream.direction);
|
|
225
|
-
return existingStream;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Create new stream
|
|
229
|
-
this.logger.debug('Creating new stream', { protocol });
|
|
230
|
-
const stream = await connection.newStream(protocol, {
|
|
231
|
-
signal: config.signal,
|
|
232
|
-
maxOutboundStreams: config.maxOutboundStreams ?? 1000,
|
|
233
|
-
runOnLimitedConnection: config.runOnLimitedConnection ?? false,
|
|
234
|
-
});
|
|
235
|
-
// Cache the stream if reuse is enabled and addresses are provided
|
|
236
|
-
if (reusePolicy === 'reuse' && streamAddresses) {
|
|
237
|
-
const managedStream = new oManagedStream(stream, streamAddresses.callerAddress, streamAddresses.receiverAddress, streamAddresses.direction);
|
|
238
|
-
this.cacheStream(managedStream);
|
|
239
|
-
this.setupStreamCleanup(stream);
|
|
240
|
-
}
|
|
241
|
-
return stream;
|
|
242
|
-
}
|
|
86
|
+
// /**
|
|
87
|
+
// * Gets an existing open stream or creates a new one based on reuse policy
|
|
88
|
+
// *
|
|
89
|
+
// * @param connection - The libp2p connection
|
|
90
|
+
// * @param protocol - The protocol to use for the stream
|
|
91
|
+
// * @param config - Stream handler configuration
|
|
92
|
+
// * @param streamAddresses - Optional addresses for address-based stream reuse
|
|
93
|
+
// */
|
|
94
|
+
// async getOrCreateStream(
|
|
95
|
+
// connection: Connection,
|
|
96
|
+
// protocol: string,
|
|
97
|
+
// config: StreamHandlerConfig = {},
|
|
98
|
+
// streamAddresses?: {
|
|
99
|
+
// callerAddress: oAddress;
|
|
100
|
+
// receiverAddress: oAddress;
|
|
101
|
+
// direction: 'inbound' | 'outbound';
|
|
102
|
+
// },
|
|
103
|
+
// ): Promise<Stream> {
|
|
104
|
+
// this.logger.debug(
|
|
105
|
+
// `Getting or creating stream for protocol: ${protocol}, connection`,
|
|
106
|
+
// {
|
|
107
|
+
// status: connection.status,
|
|
108
|
+
// remoteAddr: connection.remoteAddr.toString(),
|
|
109
|
+
// streamCount: connection.streams.length,
|
|
110
|
+
// reusePolicy: config.reusePolicy ?? 'none',
|
|
111
|
+
// hasAddresses: !!streamAddresses,
|
|
112
|
+
// },
|
|
113
|
+
// );
|
|
114
|
+
// if (connection.status !== 'open') {
|
|
115
|
+
// throw new oError(oErrorCodes.INVALID_STATE, 'Connection not open');
|
|
116
|
+
// }
|
|
117
|
+
// const reusePolicy = config.reusePolicy ?? 'none';
|
|
118
|
+
// // Check address-based cache if reuse is enabled and addresses provided
|
|
119
|
+
// if (reusePolicy === 'reuse' && streamAddresses) {
|
|
120
|
+
// const cacheKey = this.buildCacheKey(
|
|
121
|
+
// streamAddresses.callerAddress,
|
|
122
|
+
// streamAddresses.receiverAddress,
|
|
123
|
+
// );
|
|
124
|
+
// const cachedStream = this.streamCache.get(cacheKey);
|
|
125
|
+
// if (cachedStream?.isReusable) {
|
|
126
|
+
// this.logger.debug('Reusing cached stream by address', {
|
|
127
|
+
// cacheKey,
|
|
128
|
+
// streamId: cachedStream.stream.id,
|
|
129
|
+
// direction: cachedStream.direction,
|
|
130
|
+
// });
|
|
131
|
+
// cachedStream.updateLastUsed();
|
|
132
|
+
// return cachedStream.stream;
|
|
133
|
+
// } else if (cachedStream) {
|
|
134
|
+
// // Stream exists but not reusable, remove from cache
|
|
135
|
+
// this.logger.debug('Removing non-reusable stream from cache', {
|
|
136
|
+
// cacheKey,
|
|
137
|
+
// status: cachedStream.stream.status,
|
|
138
|
+
// });
|
|
139
|
+
// this.streamCache.delete(cacheKey);
|
|
140
|
+
// }
|
|
141
|
+
// }
|
|
142
|
+
// this.logger.debug(
|
|
143
|
+
// 'No reusable cached stream found, checking connection streams',
|
|
144
|
+
// connection.streams.map((s) => ({
|
|
145
|
+
// status: s.status,
|
|
146
|
+
// protocol: s.protocol,
|
|
147
|
+
// writeStatus: s.writeStatus,
|
|
148
|
+
// remoteReadStatus: s.remoteReadStatus,
|
|
149
|
+
// id: s.id,
|
|
150
|
+
// })),
|
|
151
|
+
// );
|
|
152
|
+
// // Fallback to protocol-based check (legacy behavior)
|
|
153
|
+
// if (reusePolicy === 'reuse' && !streamAddresses) {
|
|
154
|
+
// const existingStream = connection.streams.find(
|
|
155
|
+
// (stream) =>
|
|
156
|
+
// stream.status === 'open' &&
|
|
157
|
+
// stream.protocol === protocol &&
|
|
158
|
+
// stream.writeStatus === 'writable' &&
|
|
159
|
+
// stream.remoteReadStatus === 'readable',
|
|
160
|
+
// );
|
|
161
|
+
// if (existingStream) {
|
|
162
|
+
// this.logger.debug(
|
|
163
|
+
// 'Reusing existing stream by protocol (legacy)',
|
|
164
|
+
// existingStream.id,
|
|
165
|
+
// existingStream.direction,
|
|
166
|
+
// );
|
|
167
|
+
// return existingStream;
|
|
168
|
+
// }
|
|
169
|
+
// }
|
|
170
|
+
// // Create new stream
|
|
171
|
+
// this.logger.debug('Creating new stream', { protocol });
|
|
172
|
+
// const stream = await connection.newStream(protocol, {
|
|
173
|
+
// signal: config.signal,
|
|
174
|
+
// maxOutboundStreams: config.maxOutboundStreams ?? 1000,
|
|
175
|
+
// runOnLimitedConnection: config.runOnLimitedConnection ?? false,
|
|
176
|
+
// });
|
|
177
|
+
// // Cache the stream if reuse is enabled and addresses are provided
|
|
178
|
+
// if (reusePolicy === 'reuse' && streamAddresses) {
|
|
179
|
+
// const managedStream = new oNodeConnectionStream(
|
|
180
|
+
// stream,
|
|
181
|
+
// streamAddresses.callerAddress,
|
|
182
|
+
// streamAddresses.receiverAddress,
|
|
183
|
+
// streamAddresses.direction,
|
|
184
|
+
// );
|
|
185
|
+
// this.cacheStream(managedStream);
|
|
186
|
+
// this.setupStreamCleanup(stream);
|
|
187
|
+
// }
|
|
188
|
+
// return stream;
|
|
189
|
+
// }
|
|
243
190
|
/**
|
|
244
191
|
* Sends data through a stream using length-prefixed encoding (libp2p v3 best practice)
|
|
245
192
|
* Each message is automatically prefixed with a varint indicating the message length
|
|
@@ -253,28 +200,6 @@ export class StreamHandler {
|
|
|
253
200
|
const lp = lpStream(stream);
|
|
254
201
|
await lp.write(data, { signal: config.signal });
|
|
255
202
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Closes a stream safely with error handling
|
|
258
|
-
*
|
|
259
|
-
* @param stream - The stream to close
|
|
260
|
-
* @param config - Configuration including reuse policy
|
|
261
|
-
*/
|
|
262
|
-
async close(stream, config = {}) {
|
|
263
|
-
// Don't close if reuse policy is enabled
|
|
264
|
-
if (config.reusePolicy === 'reuse') {
|
|
265
|
-
this.logger.debug('Stream reuse enabled, not closing stream');
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (stream.status === 'open') {
|
|
269
|
-
try {
|
|
270
|
-
// force the close for now until we can implement a proper close
|
|
271
|
-
await stream.abort(new Error('Stream closed'));
|
|
272
|
-
}
|
|
273
|
-
catch (error) {
|
|
274
|
-
this.logger.debug('Error closing stream:', error.message);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
203
|
/**
|
|
279
204
|
* Handles an incoming stream on the server side using length-prefixed protocol
|
|
280
205
|
* Uses async read loops instead of event listeners (libp2p v3 best practice)
|
|
@@ -9,9 +9,11 @@ declare const oNodeTool_base: typeof oServerNode;
|
|
|
9
9
|
*/
|
|
10
10
|
export declare class oNodeTool extends oNodeTool_base {
|
|
11
11
|
private streamHandler;
|
|
12
|
+
handleProtocolReuse(address: oAddress): Promise<void>;
|
|
12
13
|
handleProtocol(address: oAddress): Promise<void>;
|
|
13
14
|
initialize(): Promise<void>;
|
|
14
|
-
|
|
15
|
+
handleStreamReuse(stream: Stream, connection: Connection): Promise<void>;
|
|
16
|
+
handleStream(stream: Stream, connection: Connection, reuse?: boolean): Promise<void>;
|
|
15
17
|
_tool_identify(): Promise<any>;
|
|
16
18
|
_tool_child_register(request: oRequest): Promise<any>;
|
|
17
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;
|
|
1
|
+
{"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAMrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAgBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAoBhC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAwCV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAgC5D"}
|
package/dist/src/o-node.tool.js
CHANGED
|
@@ -4,25 +4,46 @@ import { oServerNode } from './nodes/server.node.js';
|
|
|
4
4
|
import { oNodeTransport } from './router/o-node.transport.js';
|
|
5
5
|
import { oNodeAddress } from './router/o-node.address.js';
|
|
6
6
|
import { StreamHandler } from './connection/stream-handler.js';
|
|
7
|
+
import { ConnectionUtils } from './utils/connection.utils.js';
|
|
7
8
|
/**
|
|
8
9
|
* oTool is a mixin that extends the base class and implements the oTool interface
|
|
9
10
|
* @param Base - The base class to extend
|
|
10
11
|
* @returns A new class that extends the base class and implements the oTool interface
|
|
11
12
|
*/
|
|
12
13
|
export class oNodeTool extends oTool(oServerNode) {
|
|
14
|
+
async handleProtocolReuse(address) {
|
|
15
|
+
const reuseProtocol = address.protocol + '/reuse';
|
|
16
|
+
const protocols = this.p2pNode.getProtocols();
|
|
17
|
+
if (protocols.find((p) => p === reuseProtocol)) {
|
|
18
|
+
// already handling
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const maxOutboundsStreams = process.env.MAX_OUTBOUND_STREAMS
|
|
22
|
+
? parseInt(process.env.MAX_OUTBOUND_STREAMS)
|
|
23
|
+
: 1000;
|
|
24
|
+
await this.p2pNode.handle(reuseProtocol, this.handleStream.bind(this), {
|
|
25
|
+
maxInboundStreams: 10000,
|
|
26
|
+
maxOutboundStreams: maxOutboundsStreams,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
13
29
|
async handleProtocol(address) {
|
|
14
30
|
const protocols = this.p2pNode.getProtocols();
|
|
15
31
|
if (protocols.find((p) => p === address.protocol)) {
|
|
16
32
|
// already handling
|
|
17
33
|
return;
|
|
18
34
|
}
|
|
19
|
-
|
|
35
|
+
const maxOutboundsStreams = process.env.MAX_OUTBOUND_STREAMS
|
|
36
|
+
? parseInt(process.env.MAX_OUTBOUND_STREAMS)
|
|
37
|
+
: 1000;
|
|
38
|
+
this.logger.debug('Handling protocol: ' + address.protocol, {
|
|
39
|
+
maxInboundStreams: 10000,
|
|
40
|
+
maxOutboundStreams: maxOutboundsStreams,
|
|
41
|
+
});
|
|
20
42
|
await this.p2pNode.handle(address.protocol, this.handleStream.bind(this), {
|
|
21
43
|
maxInboundStreams: 10000,
|
|
22
|
-
maxOutboundStreams:
|
|
23
|
-
? parseInt(process.env.MAX_OUTBOUND_STREAMS)
|
|
24
|
-
: 1000,
|
|
44
|
+
maxOutboundStreams: maxOutboundsStreams,
|
|
25
45
|
});
|
|
46
|
+
await this.handleProtocolReuse(address);
|
|
26
47
|
}
|
|
27
48
|
async initialize() {
|
|
28
49
|
await super.initialize();
|
|
@@ -33,12 +54,25 @@ export class oNodeTool extends oTool(oServerNode) {
|
|
|
33
54
|
await this.handleProtocol(this.staticAddress);
|
|
34
55
|
}
|
|
35
56
|
}
|
|
36
|
-
async
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
async handleStreamReuse(stream, connection) {
|
|
58
|
+
return this.handleStream(stream, connection, true);
|
|
59
|
+
}
|
|
60
|
+
async handleStream(stream, connection, reuse) {
|
|
61
|
+
if (reuse) {
|
|
62
|
+
this.logger.debug('Handle stream with reuse = true');
|
|
63
|
+
}
|
|
64
|
+
// record inbound connection to manager
|
|
65
|
+
const remoteAddress = await ConnectionUtils.addressFromConnection({
|
|
66
|
+
currentNode: this,
|
|
67
|
+
connection: connection,
|
|
68
|
+
});
|
|
69
|
+
this.connectionManager.answer({
|
|
70
|
+
nextHopAddress: remoteAddress,
|
|
71
|
+
address: remoteAddress,
|
|
72
|
+
callerAddress: this.address,
|
|
73
|
+
p2pConnection: connection,
|
|
74
|
+
reuse,
|
|
75
|
+
});
|
|
42
76
|
// Use StreamHandler for consistent stream handling
|
|
43
77
|
// This follows libp2p v3 best practices for length-prefixed streaming
|
|
44
78
|
await this.streamHandler.handleIncomingStream(stream, connection, async (request, stream) => {
|
|
@@ -61,7 +95,6 @@ export class oNodeTool extends oTool(oServerNode) {
|
|
|
61
95
|
};
|
|
62
96
|
}
|
|
63
97
|
async _tool_child_register(request) {
|
|
64
|
-
this.logger.debug('Child register: ', request.params);
|
|
65
98
|
const { address, transports } = request.params;
|
|
66
99
|
const childAddress = new oNodeAddress(address, transports.map((t) => new oNodeTransport(t)));
|
|
67
100
|
// Add child to hierarchy
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.address.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node.address.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AAExD,qBAAa,YAAa,SAAQ,QAAQ;aAItB,KAAK,EAAE,MAAM;IAHxB,UAAU,EAAE,cAAc,EAAE,CAAM;gBAGvB,KAAK,EAAE,MAAM,EAC7B,UAAU,GAAE,cAAc,EAAO;IAMnC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;KACpC,GAAG,YAAY;IAShB,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAEvC;IAED,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAEvC;IAED,QAAQ,IAAI,MAAM;IAIlB,WAAW,IAAI,SAAS;IAIxB,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,QAAQ;IAI7C,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ;CAIhD"}
|
|
@@ -26,4 +26,8 @@ export class oNodeAddress extends oAddress {
|
|
|
26
26
|
static fromMultiaddr(ma) {
|
|
27
27
|
return new oAddress(ma.toString().replace('/o/', 'o://'));
|
|
28
28
|
}
|
|
29
|
+
static fromProtocol(protocol) {
|
|
30
|
+
const addressInput = protocol.replace('/o/', 'o://');
|
|
31
|
+
return new oAddress(addressInput);
|
|
32
|
+
}
|
|
29
33
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Connection } from '@olane/o-config';
|
|
2
|
+
import { oObject } from '@olane/o-core';
|
|
3
|
+
export declare class ConnectionUtils extends oObject {
|
|
4
|
+
static addressFromConnection(options: {
|
|
5
|
+
currentNode: any;
|
|
6
|
+
connection: Connection;
|
|
7
|
+
}): Promise<import("@olane/o-core").oAddress>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=connection.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,qBAAa,eAAgB,SAAQ,OAAO;WACtB,qBAAqB,CAAC,OAAO,EAAE;QACjD,WAAW,EAAE,GAAG,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;KACxB;CAsCF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { oObject } from '@olane/o-core';
|
|
2
|
+
import { oNodeAddress } from '../router/o-node.address.js';
|
|
3
|
+
export class ConnectionUtils extends oObject {
|
|
4
|
+
static async addressFromConnection(options) {
|
|
5
|
+
const { currentNode, connection } = options;
|
|
6
|
+
const p2pNode = currentNode.p2pNode;
|
|
7
|
+
// Extract the actual olane address from the peer store
|
|
8
|
+
const peers = await p2pNode.peerStore.all();
|
|
9
|
+
const remotePeer = peers.find((peer) => peer.id.toString() === connection.remotePeer.toString());
|
|
10
|
+
if (!remotePeer) {
|
|
11
|
+
console.log('Failed to find peer:', remotePeer);
|
|
12
|
+
throw new Error(`Failed to extract remote address, peer ${connection.remotePeer.toString()} not found in peer store.`);
|
|
13
|
+
}
|
|
14
|
+
// Get origin address for comparison
|
|
15
|
+
const originAddress = currentNode.address?.value;
|
|
16
|
+
if (!originAddress) {
|
|
17
|
+
throw new Error('Origin address is not configured');
|
|
18
|
+
}
|
|
19
|
+
const originProtocol = originAddress.toString();
|
|
20
|
+
const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/') && p.startsWith(originProtocol) === false);
|
|
21
|
+
if (!oProtocol) {
|
|
22
|
+
throw new Error('Failed to extract remote address, could not find o-protocol in peer protocols.');
|
|
23
|
+
}
|
|
24
|
+
return oNodeAddress.fromProtocol(oProtocol);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC"}
|
package/dist/src/utils/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astream-reuse.spec.d.ts","sourceRoot":"","sources":["../../test/astream-reuse.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { TestEnvironment } from './helpers/index.js';
|
|
3
|
+
import { NetworkTopologies, } from './helpers/network-builder.js';
|
|
4
|
+
import { oNodeAddress } from '../src/router/o-node.address.js';
|
|
5
|
+
describe('Stream Reuse', () => {
|
|
6
|
+
const env = new TestEnvironment();
|
|
7
|
+
let builder;
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
if (builder) {
|
|
10
|
+
await builder.cleanup();
|
|
11
|
+
}
|
|
12
|
+
await env.cleanup();
|
|
13
|
+
});
|
|
14
|
+
describe('Stream Reuse with reusePolicy="reuse"', () => {
|
|
15
|
+
it('should reuse the same stream across multiple requests', async () => {
|
|
16
|
+
builder = await NetworkTopologies.twoNode();
|
|
17
|
+
const leader = builder.getNode('o://leader');
|
|
18
|
+
const child = builder.getNode('o://child');
|
|
19
|
+
// Track stream IDs
|
|
20
|
+
const streamIds = [];
|
|
21
|
+
// Make first request
|
|
22
|
+
const response1 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
23
|
+
method: 'echo',
|
|
24
|
+
params: { message: 'first' },
|
|
25
|
+
});
|
|
26
|
+
expect(response1.result.success).to.be.true;
|
|
27
|
+
// Get active connection to inspect streams
|
|
28
|
+
const connectionManager = leader.connectionManager;
|
|
29
|
+
const connection = connectionManager.getCachedLibp2pConnection(child.address);
|
|
30
|
+
connection.reusePolicy;
|
|
31
|
+
if (connection) {
|
|
32
|
+
// Store initial stream count and first stream ID
|
|
33
|
+
const initialStreamCount = connection.streams.length;
|
|
34
|
+
if (connection.streams.length > 0) {
|
|
35
|
+
streamIds.push(connection.streams[0].id);
|
|
36
|
+
}
|
|
37
|
+
// Make second request
|
|
38
|
+
const response2 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
39
|
+
method: 'echo',
|
|
40
|
+
params: { message: 'second' },
|
|
41
|
+
});
|
|
42
|
+
expect(response2.result.success).to.be.true;
|
|
43
|
+
// Get stream count after second request
|
|
44
|
+
const finalStreamCount = connection.streams.length;
|
|
45
|
+
if (connection.streams.length > 0) {
|
|
46
|
+
streamIds.push(connection.streams[0].id);
|
|
47
|
+
}
|
|
48
|
+
// With default behavior (reusePolicy='none'), new streams are created
|
|
49
|
+
// This test verifies current behavior
|
|
50
|
+
expect(streamIds.length).to.be.greaterThan(0);
|
|
51
|
+
expect(finalStreamCount).to.be.greaterThan(0);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('Stream Creation with reusePolicy="none"', () => {
|
|
56
|
+
it('should create new streams for each request', async () => {
|
|
57
|
+
builder = await NetworkTopologies.twoNode();
|
|
58
|
+
const leader = builder.getNode('o://leader');
|
|
59
|
+
const child = builder.getNode('o://child');
|
|
60
|
+
// Make multiple requests
|
|
61
|
+
for (let i = 0; i < 3; i++) {
|
|
62
|
+
const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
63
|
+
method: 'echo',
|
|
64
|
+
params: { message: `request-${i}` },
|
|
65
|
+
});
|
|
66
|
+
expect(response.result.success).to.be.true;
|
|
67
|
+
}
|
|
68
|
+
// Get connection to verify stream behavior
|
|
69
|
+
const connectionManager = leader.connectionManager;
|
|
70
|
+
const connection = connectionManager.getCachedLibp2pConnection(child.address);
|
|
71
|
+
// With default reusePolicy='none', streams are closed after each request
|
|
72
|
+
// So we expect minimal open streams
|
|
73
|
+
expect(connection).to.exist;
|
|
74
|
+
if (connection) {
|
|
75
|
+
// The number of open streams should be limited since old ones are closed
|
|
76
|
+
expect(connection.streams.length).to.be.lessThan(10);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('Stream Persistence After Transmission', () => {
|
|
81
|
+
it('should close streams after transmission with reusePolicy="none"', async () => {
|
|
82
|
+
builder = await NetworkTopologies.twoNode();
|
|
83
|
+
const leader = builder.getNode('o://leader');
|
|
84
|
+
const child = builder.getNode('o://child');
|
|
85
|
+
// Make a request
|
|
86
|
+
const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
87
|
+
method: 'echo',
|
|
88
|
+
params: { message: 'test' },
|
|
89
|
+
});
|
|
90
|
+
expect(response.result.success).to.be.true;
|
|
91
|
+
// Get connection
|
|
92
|
+
const connectionManager = leader.connectionManager;
|
|
93
|
+
const connection = connectionManager.getCachedLibp2pConnection(child.address);
|
|
94
|
+
expect(connection).to.exist;
|
|
95
|
+
if (connection) {
|
|
96
|
+
// Make another request to verify new stream can be created
|
|
97
|
+
const response2 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
98
|
+
method: 'echo',
|
|
99
|
+
params: { message: 'test2' },
|
|
100
|
+
});
|
|
101
|
+
expect(response2.result.success).to.be.true;
|
|
102
|
+
// Verify connection is still functional
|
|
103
|
+
expect(connection.status).to.equal('open');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -16,6 +16,7 @@ describe('Connection Management', () => {
|
|
|
16
16
|
describe('Connection Pooling', () => {
|
|
17
17
|
it('should cache and reuse connections', async () => {
|
|
18
18
|
builder = await NetworkTopologies.twoNode();
|
|
19
|
+
console.log('Built the 2 node network, starting test');
|
|
19
20
|
const leader = builder.getNode('o://leader');
|
|
20
21
|
const child = builder.getNode('o://child');
|
|
21
22
|
const spy = createConnectionSpy(leader);
|