1
1
import { CodeError , TypedEventEmitter , setMaxListeners } from '@libp2p/interface'
2
2
import { PeerQueue , type PeerQueueJobOptions } from '@libp2p/utils/peer-queue'
3
- import { anySignal } from 'any-signal'
4
- import debug from 'debug'
5
3
import drain from 'it-drain'
6
4
import * as lp from 'it-length-prefixed'
7
- import { lpStream } from 'it-length-prefixed-stream'
8
5
import map from 'it-map'
9
6
import { pipe } from 'it-pipe'
10
7
import take from 'it-take'
11
- import { base64 } from 'multiformats/bases/base64'
12
- import { CID } from 'multiformats/cid'
13
8
import { CustomProgressEvent } from 'progress-events'
14
9
import { raceEvent } from 'race-event'
15
- import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
16
- import { BITSWAP_120 , DEFAULT_MAX_INBOUND_STREAMS , DEFAULT_MAX_OUTBOUND_STREAMS , DEFAULT_MAX_PROVIDERS_PER_REQUEST , DEFAULT_MESSAGE_RECEIVE_TIMEOUT , DEFAULT_MESSAGE_SEND_CONCURRENCY , DEFAULT_MESSAGE_SEND_TIMEOUT , DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js'
10
+ import { BITSWAP_120 , DEFAULT_MAX_INBOUND_STREAMS , DEFAULT_MAX_INCOMING_MESSAGE_SIZE , DEFAULT_MAX_OUTBOUND_STREAMS , DEFAULT_MAX_OUTGOING_MESSAGE_SIZE , DEFAULT_MAX_PROVIDERS_PER_REQUEST , DEFAULT_MESSAGE_RECEIVE_TIMEOUT , DEFAULT_MESSAGE_SEND_CONCURRENCY , DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js'
17
11
import { BitswapMessage } from './pb/message.js'
12
+ import { mergeMessages } from './utils/merge-messages.js'
13
+ import { splitMessage } from './utils/split-message.js'
18
14
import type { WantOptions } from './bitswap.js'
19
15
import type { MultihashHasherLoader } from './index.js'
20
- import type { Block , BlockPresence , WantlistEntry } from './pb/message.js'
16
+ import type { Block } from './pb/message.js'
21
17
import type { Provider , Routing } from '@helia/interface/routing'
22
18
import type { Libp2p , AbortOptions , Connection , PeerId , IncomingStreamData , Topology , ComponentLogger , IdentifyResult , Counter } from '@libp2p/interface'
23
19
import type { Logger } from '@libp2p/logger'
20
+ import type { CID } from 'multiformats/cid'
24
21
import type { ProgressEvent , ProgressOptions } from 'progress-events'
25
22
26
- // Add a formatter for a bitswap message
27
- debug . formatters . B = ( b ?: BitswapMessage ) : string => {
28
- if ( b == null ) {
29
- return 'undefined'
30
- }
31
-
32
- return JSON . stringify ( {
33
- blocks : b . blocks ?. map ( b => ( {
34
- data : `${ uint8ArrayToString ( b . data , 'base64' ) . substring ( 0 , 10 ) } ...` ,
35
- prefix : uint8ArrayToString ( b . prefix , 'base64' )
36
- } ) ) ,
37
- blockPresences : b . blockPresences ?. map ( p => ( {
38
- ...p ,
39
- cid : CID . decode ( p . cid ) . toString ( )
40
- } ) ) ,
41
- wantlist : b . wantlist == null
42
- ? undefined
43
- : {
44
- full : b . wantlist . full ,
45
- entries : b . wantlist . entries . map ( e => ( {
46
- ...e ,
47
- cid : CID . decode ( e . cid ) . toString ( )
48
- } ) )
49
- }
50
- } , null , 2 )
51
- }
52
-
53
23
export type BitswapNetworkProgressEvents =
54
24
ProgressEvent < 'bitswap:network:dial' , PeerId >
55
25
@@ -68,10 +38,11 @@ export interface NetworkInit {
68
38
maxInboundStreams ?: number
69
39
maxOutboundStreams ?: number
70
40
messageReceiveTimeout ?: number
71
- messageSendTimeout ?: number
72
41
messageSendConcurrency ?: number
73
42
protocols ?: string [ ]
74
43
runOnTransientConnections ?: boolean
44
+ maxOutgoingMessageSize ?: number
45
+ maxIncomingMessageSize ?: number
75
46
}
76
47
77
48
export interface NetworkComponents {
@@ -107,8 +78,9 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
107
78
private registrarIds : string [ ]
108
79
private readonly metrics : { blocksSent ?: Counter , dataSent ?: Counter }
109
80
private readonly sendQueue : PeerQueue < void , SendMessageJobOptions >
110
- private readonly messageSendTimeout : number
111
81
private readonly runOnTransientConnections : boolean
82
+ private readonly maxOutgoingMessageSize : number
83
+ private readonly maxIncomingMessageSize : number
112
84
113
85
constructor ( components : NetworkComponents , init : NetworkInit = { } ) {
114
86
super ( )
@@ -125,8 +97,9 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
125
97
this . maxInboundStreams = init . maxInboundStreams ?? DEFAULT_MAX_INBOUND_STREAMS
126
98
this . maxOutboundStreams = init . maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS
127
99
this . messageReceiveTimeout = init . messageReceiveTimeout ?? DEFAULT_MESSAGE_RECEIVE_TIMEOUT
128
- this . messageSendTimeout = init . messageSendTimeout ?? DEFAULT_MESSAGE_SEND_TIMEOUT
129
100
this . runOnTransientConnections = init . runOnTransientConnections ?? DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS
101
+ this . maxIncomingMessageSize = init . maxIncomingMessageSize ?? DEFAULT_MAX_OUTGOING_MESSAGE_SIZE
102
+ this . maxOutgoingMessageSize = init . maxOutgoingMessageSize ?? init . maxIncomingMessageSize ?? DEFAULT_MAX_INCOMING_MESSAGE_SIZE
130
103
this . metrics = {
131
104
blocksSent : components . libp2p . metrics ?. registerCounter ( 'helia_bitswap_sent_blocks_total' ) ,
132
105
dataSent : components . libp2p . metrics ?. registerCounter ( 'helia_bitswap_sent_data_bytes_total' )
@@ -212,21 +185,29 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
212
185
Promise . resolve ( ) . then ( async ( ) => {
213
186
this . log ( 'incoming new bitswap %s stream from %p' , stream . protocol , connection . remotePeer )
214
187
const abortListener = ( ) : void => {
215
- stream . abort ( new CodeError ( 'Incoming Bitswap stream timed out' , 'ERR_TIMEOUT' ) )
188
+ if ( stream . status === 'open' ) {
189
+ stream . abort ( new CodeError ( 'Incoming Bitswap stream timed out' , 'ERR_TIMEOUT' ) )
190
+ } else {
191
+ this . log ( 'stream aborted with status' , stream . status )
192
+ }
216
193
}
217
194
218
195
let signal = AbortSignal . timeout ( this . messageReceiveTimeout )
219
196
setMaxListeners ( Infinity , signal )
220
197
signal . addEventListener ( 'abort' , abortListener )
221
198
199
+ await stream . closeWrite ( )
200
+
222
201
await pipe (
223
202
stream ,
224
- ( source ) => lp . decode ( source ) ,
203
+ ( source ) => lp . decode ( source , {
204
+ maxDataLength : this . maxIncomingMessageSize
205
+ } ) ,
225
206
async ( source ) => {
226
207
for await ( const data of source ) {
227
208
try {
228
209
const message = BitswapMessage . decode ( data )
229
- this . log ( 'incoming new bitswap %s message from %p %B ' , stream . protocol , connection . remotePeer , message )
210
+ this . log ( 'incoming new bitswap %s message from %p on stream ' , stream . protocol , connection . remotePeer , stream . id )
230
211
231
212
this . safeDispatchEvent ( 'bitswap:message' , {
232
213
detail : {
@@ -241,7 +222,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
241
222
setMaxListeners ( Infinity , signal )
242
223
signal . addEventListener ( 'abort' , abortListener )
243
224
} catch ( err : any ) {
244
- this . log . error ( 'error reading incoming bitswap message from %p' , connection . remotePeer , err )
225
+ this . log . error ( 'error reading incoming bitswap message from %p on stream ' , connection . remotePeer , stream . id , err )
245
226
stream . abort ( err )
246
227
break
247
228
}
@@ -309,58 +290,55 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
309
290
pendingBytes : msg . pendingBytes ?? 0
310
291
}
311
292
312
- const timeoutSignal = AbortSignal . timeout ( this . messageSendTimeout )
313
- const signal = anySignal ( [ timeoutSignal , options ?. signal ] )
314
- setMaxListeners ( Infinity , timeoutSignal , signal )
293
+ const existingJob = this . sendQueue . queue . find ( job => {
294
+ return peerId . equals ( job . options . peerId ) && job . status === 'queued'
295
+ } )
315
296
316
- try {
317
- const existingJob = this . sendQueue . queue . find ( job => {
318
- return peerId . equals ( job . options . peerId ) && job . status === 'queued'
297
+ if ( existingJob != null ) {
298
+ // merge messages instead of adding new job
299
+ existingJob . options . message = mergeMessages ( existingJob . options . message , message )
300
+
301
+ await existingJob . join ( {
302
+ signal : options ?. signal
319
303
} )
320
304
321
- if ( existingJob != null ) {
322
- // merge messages instead of adding new job
323
- existingJob . options . message = mergeMessages ( existingJob . options . message , message )
305
+ return
306
+ }
324
307
325
- await existingJob . join ( {
326
- signal
327
- } )
308
+ await this . sendQueue . add ( async ( options ) => {
309
+ const message = options ?. message
328
310
329
- return
311
+ if ( message == null ) {
312
+ throw new CodeError ( 'No message to send' , 'ERR_NO_MESSAGE' )
330
313
}
331
314
332
- await this . sendQueue . add ( async ( options ) => {
333
- const message = options ?. message
315
+ this . log ( 'sendMessage to %p' , peerId )
334
316
335
- if ( message == null ) {
336
- throw new CodeError ( 'No message to send' , 'ERR_NO_MESSAGE' )
337
- }
317
+ options ?. onProgress ?.( new CustomProgressEvent < PeerId > ( 'bitswap:network:send-wantlist' , peerId ) )
338
318
339
- this . log ( 'sendMessage to %p %B' , peerId , message )
319
+ const stream = await this . libp2p . dialProtocol ( peerId , BITSWAP_120 , options )
320
+ await stream . closeRead ( )
340
321
341
- options ?. onProgress ?.( new CustomProgressEvent < PeerId > ( 'bitswap:network:send-wantlist' , peerId ) )
322
+ try {
323
+ await pipe (
324
+ splitMessage ( message , this . maxOutgoingMessageSize ) ,
325
+ ( source ) => lp . encode ( source ) ,
326
+ stream
327
+ )
342
328
343
- const stream = await this . libp2p . dialProtocol ( peerId , BITSWAP_120 , options )
344
-
345
- try {
346
- const lp = lpStream ( stream )
347
- await lp . write ( BitswapMessage . encode ( message ) , options )
348
- await lp . unwrap ( ) . close ( options )
349
- } catch ( err : any ) {
350
- options ?. onProgress ?.( new CustomProgressEvent < { peer : PeerId , error : Error } > ( 'bitswap:network:send-wantlist:error' , { peer : peerId , error : err } ) )
351
- this . log . error ( 'error sending message to %p' , peerId , err )
352
- stream . abort ( err )
353
- }
329
+ await stream . close ( options )
330
+ } catch ( err : any ) {
331
+ options ?. onProgress ?.( new CustomProgressEvent < { peer : PeerId , error : Error } > ( 'bitswap:network:send-wantlist:error' , { peer : peerId , error : err } ) )
332
+ this . log . error ( 'error sending message to %p' , peerId , err )
333
+ stream . abort ( err )
334
+ }
354
335
355
- this . _updateSentStats ( message . blocks )
356
- } , {
357
- peerId,
358
- signal,
359
- message
360
- } )
361
- } finally {
362
- signal . clear ( )
363
- }
336
+ this . _updateSentStats ( message . blocks )
337
+ } , {
338
+ peerId,
339
+ signal : options ?. signal ,
340
+ message
341
+ } )
364
342
}
365
343
366
344
/**
@@ -409,71 +387,3 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
409
387
this . metrics . blocksSent ?. increment ( blocks . length )
410
388
}
411
389
}
412
-
413
- function mergeMessages ( messageA : BitswapMessage , messageB : BitswapMessage ) : BitswapMessage {
414
- const wantListEntries = new Map < string , WantlistEntry > (
415
- ( messageA . wantlist ?. entries ?? [ ] ) . map ( entry => ( [
416
- base64 . encode ( entry . cid ) ,
417
- entry
418
- ] ) )
419
- )
420
-
421
- for ( const entry of messageB . wantlist ?. entries ?? [ ] ) {
422
- const key = base64 . encode ( entry . cid )
423
- const existingEntry = wantListEntries . get ( key )
424
-
425
- if ( existingEntry != null ) {
426
- // take highest priority
427
- if ( existingEntry . priority > entry . priority ) {
428
- entry . priority = existingEntry . priority
429
- }
430
-
431
- // take later values if passed, otherwise use earlier ones
432
- entry . cancel = entry . cancel ?? existingEntry . cancel
433
- entry . wantType = entry . wantType ?? existingEntry . wantType
434
- entry . sendDontHave = entry . sendDontHave ?? existingEntry . sendDontHave
435
- }
436
-
437
- wantListEntries . set ( key , entry )
438
- }
439
-
440
- const blockPresences = new Map < string , BlockPresence > (
441
- messageA . blockPresences . map ( presence => ( [
442
- base64 . encode ( presence . cid ) ,
443
- presence
444
- ] ) )
445
- )
446
-
447
- for ( const blockPresence of messageB . blockPresences ) {
448
- const key = base64 . encode ( blockPresence . cid )
449
-
450
- // override earlier block presence with later one as if duplicated it is
451
- // likely to be more accurate since it is more recent
452
- blockPresences . set ( key , blockPresence )
453
- }
454
-
455
- const blocks = new Map < string , Block > (
456
- messageA . blocks . map ( block => ( [
457
- base64 . encode ( block . data ) ,
458
- block
459
- ] ) )
460
- )
461
-
462
- for ( const block of messageB . blocks ) {
463
- const key = base64 . encode ( block . data )
464
-
465
- blocks . set ( key , block )
466
- }
467
-
468
- const output : BitswapMessage = {
469
- wantlist : {
470
- full : messageA . wantlist ?. full ?? messageB . wantlist ?. full ?? false ,
471
- entries : [ ...wantListEntries . values ( ) ]
472
- } ,
473
- blockPresences : [ ...blockPresences . values ( ) ] ,
474
- blocks : [ ...blocks . values ( ) ] ,
475
- pendingBytes : messageA . pendingBytes + messageB . pendingBytes
476
- }
477
-
478
- return output
479
- }
0 commit comments