@helia/bitswap 0.0.0 → 1.0.0-59de059

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.
Files changed (62) hide show
  1. package/dist/index.min.js +6 -1
  2. package/dist/src/bitswap.d.ts +3 -2
  3. package/dist/src/bitswap.d.ts.map +1 -1
  4. package/dist/src/bitswap.js +7 -14
  5. package/dist/src/bitswap.js.map +1 -1
  6. package/dist/src/constants.d.ts +2 -0
  7. package/dist/src/constants.d.ts.map +1 -1
  8. package/dist/src/constants.js +2 -0
  9. package/dist/src/constants.js.map +1 -1
  10. package/dist/src/index.d.ts +22 -45
  11. package/dist/src/index.d.ts.map +1 -1
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/network.d.ts +8 -7
  14. package/dist/src/network.d.ts.map +1 -1
  15. package/dist/src/network.js +71 -169
  16. package/dist/src/network.js.map +1 -1
  17. package/dist/src/pb/message.d.ts +6 -6
  18. package/dist/src/pb/message.d.ts.map +1 -1
  19. package/dist/src/pb/message.js +37 -20
  20. package/dist/src/pb/message.js.map +1 -1
  21. package/dist/src/peer-want-lists/index.d.ts +3 -2
  22. package/dist/src/peer-want-lists/index.d.ts.map +1 -1
  23. package/dist/src/peer-want-lists/index.js +7 -3
  24. package/dist/src/peer-want-lists/index.js.map +1 -1
  25. package/dist/src/peer-want-lists/ledger.d.ts +3 -1
  26. package/dist/src/peer-want-lists/ledger.d.ts.map +1 -1
  27. package/dist/src/peer-want-lists/ledger.js +8 -0
  28. package/dist/src/peer-want-lists/ledger.js.map +1 -1
  29. package/dist/src/session.d.ts +13 -8
  30. package/dist/src/session.d.ts.map +1 -1
  31. package/dist/src/session.js +25 -88
  32. package/dist/src/session.js.map +1 -1
  33. package/dist/src/stats.d.ts +2 -2
  34. package/dist/src/stats.d.ts.map +1 -1
  35. package/dist/src/stats.js +4 -4
  36. package/dist/src/stats.js.map +1 -1
  37. package/dist/src/utils/merge-messages.d.ts +3 -0
  38. package/dist/src/utils/merge-messages.d.ts.map +1 -0
  39. package/dist/src/utils/merge-messages.js +51 -0
  40. package/dist/src/utils/merge-messages.js.map +1 -0
  41. package/dist/src/utils/split-message.d.ts +14 -0
  42. package/dist/src/utils/split-message.d.ts.map +1 -0
  43. package/dist/src/utils/split-message.js +99 -0
  44. package/dist/src/utils/split-message.js.map +1 -0
  45. package/dist/src/want-list.d.ts +20 -23
  46. package/dist/src/want-list.d.ts.map +1 -1
  47. package/dist/src/want-list.js +136 -133
  48. package/dist/src/want-list.js.map +1 -1
  49. package/package.json +6 -6
  50. package/src/bitswap.ts +9 -16
  51. package/src/constants.ts +2 -0
  52. package/src/index.ts +24 -51
  53. package/src/network.ts +83 -200
  54. package/src/pb/message.ts +40 -20
  55. package/src/peer-want-lists/index.ts +9 -5
  56. package/src/peer-want-lists/ledger.ts +11 -1
  57. package/src/session.ts +31 -120
  58. package/src/stats.ts +6 -6
  59. package/src/utils/merge-messages.ts +70 -0
  60. package/src/utils/split-message.ts +131 -0
  61. package/src/want-list.ts +205 -212
  62. package/dist/typedoc-urls.json +0 -24
@@ -6,7 +6,7 @@ import { Ledger } from './ledger.js'
6
6
  import type { BitswapNotifyProgressEvents, WantListEntry } from '../index.js'
7
7
  import type { Network } from '../network.js'
8
8
  import type { BitswapMessage } from '../pb/message.js'
9
- import type { ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
9
+ import type { ComponentLogger, Libp2p, Logger, PeerId } from '@libp2p/interface'
10
10
  import type { PeerMap } from '@libp2p/peer-collections'
11
11
  import type { Blockstore } from 'interface-blockstore'
12
12
  import type { AbortOptions } from 'it-length-prefixed-stream'
@@ -19,7 +19,7 @@ export interface PeerWantListsInit {
19
19
  export interface PeerWantListsComponents {
20
20
  blockstore: Blockstore
21
21
  network: Network
22
- metrics?: Metrics
22
+ libp2p: Libp2p
23
23
  logger: ComponentLogger
24
24
  }
25
25
 
@@ -37,16 +37,18 @@ export class PeerWantLists {
37
37
  public readonly ledgerMap: PeerMap<Ledger>
38
38
  private readonly maxSizeReplaceHasWithBlock?: number
39
39
  private readonly log: Logger
40
+ private readonly logger: ComponentLogger
40
41
 
41
42
  constructor (components: PeerWantListsComponents, init: PeerWantListsInit = {}) {
42
43
  this.blockstore = components.blockstore
43
44
  this.network = components.network
44
45
  this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock
45
46
  this.log = components.logger.forComponent('helia:bitswap:peer-want-lists')
47
+ this.logger = components.logger
46
48
 
47
49
  this.ledgerMap = trackedPeerMap({
48
- name: 'ipfs_bitswap_ledger_map',
49
- metrics: components.metrics
50
+ name: 'helia_bitswap_ledger_map',
51
+ metrics: components.libp2p.metrics
50
52
  })
51
53
 
52
54
  this.network.addEventListener('bitswap:message', (evt) => {
@@ -100,7 +102,8 @@ export class PeerWantLists {
100
102
  ledger = new Ledger({
101
103
  peerId,
102
104
  blockstore: this.blockstore,
103
- network: this.network
105
+ network: this.network,
106
+ logger: this.logger
104
107
  }, {
105
108
  maxSizeReplaceHasWithBlock: this.maxSizeReplaceHasWithBlock
106
109
  })
@@ -141,6 +144,7 @@ export class PeerWantLists {
141
144
  }
142
145
  }
143
146
 
147
+ this.log('send blocks to peer')
144
148
  await ledger.sendBlocksToPeer()
145
149
  }
146
150
 
@@ -3,7 +3,7 @@ import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK } from '../constants.js'
3
3
  import { BlockPresenceType, type BitswapMessage, WantType } from '../pb/message.js'
4
4
  import { cidToPrefix } from '../utils/cid-prefix.js'
5
5
  import type { Network } from '../network.js'
6
- import type { PeerId } from '@libp2p/interface'
6
+ import type { ComponentLogger, Logger, PeerId } from '@libp2p/interface'
7
7
  import type { Blockstore } from 'interface-blockstore'
8
8
  import type { AbortOptions } from 'it-length-prefixed-stream'
9
9
  import type { CID } from 'multiformats/cid'
@@ -12,6 +12,7 @@ export interface LedgerComponents {
12
12
  peerId: PeerId
13
13
  blockstore: Blockstore
14
14
  network: Network
15
+ logger: ComponentLogger
15
16
  }
16
17
 
17
18
  export interface LedgerInit {
@@ -56,12 +57,14 @@ export class Ledger {
56
57
  public bytesReceived: number
57
58
  public lastExchange?: number
58
59
  private readonly maxSizeReplaceHasWithBlock: number
60
+ private readonly log: Logger
59
61
 
60
62
  constructor (components: LedgerComponents, init: LedgerInit) {
61
63
  this.peerId = components.peerId
62
64
  this.blockstore = components.blockstore
63
65
  this.network = components.network
64
66
  this.wants = new Map()
67
+ this.log = components.logger.forComponent(`helia:bitswap:ledger:${components.peerId}`)
65
68
 
66
69
  this.exchangeCount = 0
67
70
  this.bytesSent = 0
@@ -96,6 +99,8 @@ export class Ledger {
96
99
  const has = await this.blockstore.has(entry.cid, options)
97
100
 
98
101
  if (!has) {
102
+ this.log('do not have block for %c', entry.cid)
103
+
99
104
  // we don't have the requested block and the remote is not interested
100
105
  // in us telling them that
101
106
  if (!entry.sendDontHave) {
@@ -121,6 +126,7 @@ export class Ledger {
121
126
  // do they want the block or just us to tell them we have the block
122
127
  if (entry.wantType === WantType.WantHave) {
123
128
  if (block.byteLength < this.maxSizeReplaceHasWithBlock) {
129
+ this.log('sending have and block for %c', entry.cid)
124
130
  // if the block is small we just send it to them
125
131
  sentBlocks.add(key)
126
132
  message.blocks.push({
@@ -128,6 +134,7 @@ export class Ledger {
128
134
  prefix: cidToPrefix(entry.cid)
129
135
  })
130
136
  } else {
137
+ this.log('sending have for %c', entry.cid)
131
138
  // otherwise tell them we have the block
132
139
  message.blockPresences.push({
133
140
  cid: entry.cid.bytes,
@@ -135,6 +142,7 @@ export class Ledger {
135
142
  })
136
143
  }
137
144
  } else {
145
+ this.log('sending block for %c', entry.cid)
138
146
  // they want the block, send it to them
139
147
  sentBlocks.add(key)
140
148
  message.blocks.push({
@@ -146,7 +154,9 @@ export class Ledger {
146
154
 
147
155
  // only send the message if we actually have something to send
148
156
  if (message.blocks.length > 0 || message.blockPresences.length > 0) {
157
+ this.log('sending message')
149
158
  await this.network.sendMessage(this.peerId, message, options)
159
+ this.log('sent message')
150
160
 
151
161
  // update accounting
152
162
  this.sentBytes(message.blocks.reduce((acc, curr) => acc + curr.data.byteLength, 0))
package/src/session.ts CHANGED
@@ -1,16 +1,11 @@
1
- import { CodeError } from '@libp2p/interface'
2
- import { PeerSet } from '@libp2p/peer-collections'
3
- import { PeerQueue } from '@libp2p/utils/peer-queue'
4
- import map from 'it-map'
5
- import merge from 'it-merge'
6
- import pDefer, { type DeferredPromise } from 'p-defer'
7
- import type { BitswapWantProgressEvents, BitswapSession as BitswapSessionInterface } from './index.js'
1
+ import { AbstractSession } from '@helia/utils'
2
+ import type { BitswapWantProgressEvents } from './index.js'
8
3
  import type { Network } from './network.js'
9
4
  import type { WantList } from './want-list.js'
10
- import type { ComponentLogger, Logger, PeerId } from '@libp2p/interface'
5
+ import type { CreateSessionOptions } from '@helia/interface'
6
+ import type { ComponentLogger, PeerId } from '@libp2p/interface'
11
7
  import type { AbortOptions } from 'interface-store'
12
8
  import type { CID } from 'multiformats/cid'
13
- import type { ProgressOptions } from 'progress-events'
14
9
 
15
10
  export interface BitswapSessionComponents {
16
11
  network: Network
@@ -18,133 +13,49 @@ export interface BitswapSessionComponents {
18
13
  logger: ComponentLogger
19
14
  }
20
15
 
21
- export interface BitswapSessionInit extends AbortOptions {
22
- root: CID
23
- queryConcurrency: number
24
- minProviders: number
25
- maxProviders: number
26
- connectedPeers: PeerId[]
27
- }
28
-
29
- class BitswapSession implements BitswapSessionInterface {
30
- public readonly root: CID
31
- public readonly peers: PeerSet
32
- private readonly log: Logger
16
+ class BitswapSession extends AbstractSession<PeerId, BitswapWantProgressEvents> {
33
17
  private readonly wantList: WantList
34
18
  private readonly network: Network
35
- private readonly queue: PeerQueue
36
- private readonly maxProviders: number
37
19
 
38
- constructor (components: BitswapSessionComponents, init: BitswapSessionInit) {
39
- this.peers = new PeerSet()
40
- this.root = init.root
41
- this.maxProviders = init.maxProviders
42
- this.log = components.logger.forComponent(`helia:bitswap:session:${init.root}`)
20
+ constructor (components: BitswapSessionComponents, init: CreateSessionOptions) {
21
+ super(components, {
22
+ ...init,
23
+ name: 'helia:bitswap:session'
24
+ })
25
+
43
26
  this.wantList = components.wantList
44
27
  this.network = components.network
45
-
46
- this.queue = new PeerQueue({
47
- concurrency: init.queryConcurrency
48
- })
49
- this.queue.addEventListener('error', (evt) => {
50
- this.log.error('error querying peer for %c', this.root, evt.detail)
51
- })
52
28
  }
53
29
 
54
- async want (cid: CID, options: AbortOptions & ProgressOptions<BitswapWantProgressEvents> = {}): Promise<Uint8Array> {
55
- if (this.peers.size === 0) {
56
- throw new CodeError('Bitswap session had no peers', 'ERR_NO_SESSION_PEERS')
57
- }
58
-
59
- this.log('sending WANT-BLOCK for %c to', cid, this.peers)
30
+ async queryProvider (cid: CID, provider: PeerId, options: AbortOptions): Promise<Uint8Array> {
31
+ this.log('sending WANT-BLOCK for %c to %p', cid, provider)
60
32
 
61
- const result = await Promise.any(
62
- [...this.peers].map(async peerId => {
63
- return this.wantList.wantBlock(cid, {
64
- peerId,
65
- ...options
66
- })
67
- })
68
- )
33
+ const result = await this.wantList.wantSessionBlock(cid, provider, options)
69
34
 
70
- this.log('received block for %c from %p', cid, result.sender)
35
+ this.log('%p %s %c', provider, result.has ? 'has' : 'does not have', cid)
71
36
 
72
- // TODO findNewProviders when promise.any throws aggregate error and signal
73
- // is not aborted
37
+ if (result.has && result.block != null) {
38
+ return result.block
39
+ }
74
40
 
75
- return result.block
41
+ throw new Error('Provider did not have block')
76
42
  }
77
43
 
78
- async findNewProviders (cid: CID, count: number, options: AbortOptions = {}): Promise<void> {
79
- const deferred: DeferredPromise<void> = pDefer()
80
- let found = 0
81
-
82
- this.log('find %d-%d new provider(s) for %c', count, this.maxProviders, cid)
83
-
84
- const source = merge(
85
- [...this.wantList.peers.keys()],
86
- map(this.network.findProviders(cid, options), prov => prov.id)
87
- )
88
-
89
- void Promise.resolve()
90
- .then(async () => {
91
- for await (const peerId of source) {
92
- // eslint-disable-next-line no-loop-func
93
- await this.queue.add(async () => {
94
- try {
95
- this.log('asking potential session peer %p if they have %c', peerId, cid)
96
- const result = await this.wantList.wantPresence(cid, {
97
- peerId,
98
- ...options
99
- })
100
-
101
- if (!result.has) {
102
- this.log('potential session peer %p did not have %c', peerId, cid)
103
- return
104
- }
105
-
106
- this.log('potential session peer %p had %c', peerId, cid)
107
- found++
108
-
109
- // add to list
110
- this.peers.add(peerId)
111
-
112
- if (found === count) {
113
- this.log('found %d session peers', found)
114
-
115
- deferred.resolve()
116
- }
117
-
118
- if (found === this.maxProviders) {
119
- this.log('found max provider session peers', found)
120
-
121
- this.queue.clear()
122
- }
123
- } catch (err: any) {
124
- this.log.error('error querying potential session peer %p for %c', peerId, cid, err.errors ?? err)
125
- }
126
- }, {
127
- peerId
128
- })
129
- }
130
-
131
- this.log('found %d session peers total', found)
44
+ async * findNewProviders (cid: CID, options: AbortOptions = {}): AsyncGenerator<PeerId> {
45
+ for await (const provider of this.network.findProviders(cid, options)) {
46
+ yield provider.id
47
+ }
48
+ }
132
49
 
133
- if (count > 0) {
134
- deferred.reject(new CodeError(`Found ${found} of ${count} providers`, 'ERR_NO_PROVIDERS_FOUND'))
135
- }
136
- })
50
+ toEvictionKey (provider: PeerId): Uint8Array | string {
51
+ return provider.toBytes()
52
+ }
137
53
 
138
- return deferred.promise
54
+ equals (providerA: PeerId, providerB: PeerId): boolean {
55
+ return providerA.equals(providerB)
139
56
  }
140
57
  }
141
58
 
142
- export async function createBitswapSession (components: BitswapSessionComponents, init: BitswapSessionInit): Promise<BitswapSessionInterface> {
143
- const session = new BitswapSession(components, init)
144
-
145
- await session.findNewProviders(init.root, init.minProviders, {
146
- signal: init.signal
147
- })
148
-
149
- return session
59
+ export function createBitswapSession (components: BitswapSessionComponents, init: CreateSessionOptions): BitswapSession {
60
+ return new BitswapSession(components, init)
150
61
  }
package/src/stats.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { MetricGroup, Metrics, PeerId } from '@libp2p/interface'
1
+ import type { Libp2p, MetricGroup, PeerId } from '@libp2p/interface'
2
2
 
3
3
  export interface StatsComponents {
4
- metrics?: Metrics
4
+ libp2p: Libp2p
5
5
  }
6
6
 
7
7
  export class Stats {
@@ -11,10 +11,10 @@ export class Stats {
11
11
  private readonly duplicateDataReceived?: MetricGroup
12
12
 
13
13
  constructor (components: StatsComponents) {
14
- this.blocksReceived = components.metrics?.registerMetricGroup('ipfs_bitswap_received_blocks')
15
- this.duplicateBlocksReceived = components.metrics?.registerMetricGroup('ipfs_bitswap_duplicate_received_blocks')
16
- this.dataReceived = components.metrics?.registerMetricGroup('ipfs_bitswap_data_received_bytes')
17
- this.duplicateDataReceived = components.metrics?.registerMetricGroup('ipfs_bitswap_duplicate_data_received_bytes')
14
+ this.blocksReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_received_blocks')
15
+ this.duplicateBlocksReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_duplicate_received_blocks')
16
+ this.dataReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_data_received_bytes')
17
+ this.duplicateDataReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_duplicate_data_received_bytes')
18
18
  }
19
19
 
20
20
  updateBlocksReceived (count: number = 1, peerId?: PeerId): void {
@@ -0,0 +1,70 @@
1
+ import { base64 } from 'multiformats/bases/base64'
2
+ import type { BitswapMessage, Block, BlockPresence, WantlistEntry } from '../pb/message.js'
3
+
4
+ export function mergeMessages (messageA: BitswapMessage, messageB: BitswapMessage): BitswapMessage {
5
+ const wantListEntries = new Map<string, WantlistEntry>(
6
+ (messageA.wantlist?.entries ?? []).map(entry => ([
7
+ base64.encode(entry.cid),
8
+ entry
9
+ ]))
10
+ )
11
+
12
+ for (const entry of messageB.wantlist?.entries ?? []) {
13
+ const key = base64.encode(entry.cid)
14
+ const existingEntry = wantListEntries.get(key)
15
+
16
+ if (existingEntry != null) {
17
+ // take highest priority
18
+ if (existingEntry.priority > entry.priority) {
19
+ entry.priority = existingEntry.priority
20
+ }
21
+
22
+ // take later values if passed, otherwise use earlier ones
23
+ entry.cancel = entry.cancel ?? existingEntry.cancel
24
+ entry.wantType = entry.wantType ?? existingEntry.wantType
25
+ entry.sendDontHave = entry.sendDontHave ?? existingEntry.sendDontHave
26
+ }
27
+
28
+ wantListEntries.set(key, entry)
29
+ }
30
+
31
+ const blockPresences = new Map<string, BlockPresence>(
32
+ messageA.blockPresences.map(presence => ([
33
+ base64.encode(presence.cid),
34
+ presence
35
+ ]))
36
+ )
37
+
38
+ for (const blockPresence of messageB.blockPresences) {
39
+ const key = base64.encode(blockPresence.cid)
40
+
41
+ // override earlier block presence with later one as if duplicated it is
42
+ // likely to be more accurate since it is more recent
43
+ blockPresences.set(key, blockPresence)
44
+ }
45
+
46
+ const blocks = new Map<string, Block>(
47
+ messageA.blocks.map(block => ([
48
+ base64.encode(block.data),
49
+ block
50
+ ]))
51
+ )
52
+
53
+ for (const block of messageB.blocks) {
54
+ const key = base64.encode(block.data)
55
+
56
+ blocks.set(key, block)
57
+ }
58
+
59
+ const output: BitswapMessage = {
60
+ wantlist: {
61
+ full: messageA.wantlist?.full ?? messageB.wantlist?.full ?? false,
62
+ entries: [...wantListEntries.values()]
63
+ },
64
+ blockPresences: [...blockPresences.values()],
65
+ blocks: [...blocks.values()],
66
+ pendingBytes: messageA.pendingBytes + messageB.pendingBytes
67
+ }
68
+
69
+ return output
70
+ }
@@ -0,0 +1,131 @@
1
+ /* eslint-disable complexity */
2
+ import { CodeError } from '@libp2p/interface'
3
+ import { encodingLength } from 'uint8-varint'
4
+ import { BitswapMessage, Block, BlockPresence, WantlistEntry } from '../pb/message.js'
5
+
6
+ /**
7
+ * Split the passed Bitswap message into multiple smaller messages that when
8
+ * serialized will be under the maximum message size.
9
+ *
10
+ * Since blocks are the largest thing to send, we first try to fit as many
11
+ * blocks as possible into the message, then add (smaller) block presences and
12
+ * wants until the max size is reached.
13
+ *
14
+ * If a block is encountered that is larger than the max message size an error
15
+ * will be thrown.
16
+ */
17
+ export async function * splitMessage (message: BitswapMessage, maxSize: number): AsyncGenerator<Uint8Array> {
18
+ const wantListEntries = message.wantlist?.entries ?? []
19
+ const blockPresences = message.blockPresences
20
+ const blocks = message.blocks
21
+
22
+ let wantListIndex = 0
23
+ let blockPresencesIndex = 0
24
+ let blocksIndex = 0
25
+ let messagesSent = 0
26
+ let doneSending = false
27
+
28
+ while (true) {
29
+ const subMessage: Required<BitswapMessage> = {
30
+ wantlist: {
31
+ full: false,
32
+ entries: []
33
+ },
34
+ blockPresences: [],
35
+ blocks: [],
36
+ pendingBytes: 0
37
+ }
38
+
39
+ let size = 4
40
+
41
+ let { added, hasMore, newSize } = addToMessage(blocks, subMessage.blocks, blocksIndex, maxSize, size, calculateEncodedBlockSize)
42
+
43
+ blocksIndex += added
44
+ size = newSize
45
+ const haveMoreBlocks = hasMore
46
+
47
+ ;({ added, hasMore, newSize } = addToMessage(blockPresences, subMessage.blockPresences, blockPresencesIndex, maxSize, size, calculateEncodedBlockPresenceSize))
48
+
49
+ blockPresencesIndex += added
50
+ size = newSize
51
+ const haveMorePresences = hasMore
52
+
53
+ ;({ added, hasMore, newSize } = addToMessage(wantListEntries, subMessage.wantlist.entries, wantListIndex, maxSize, size, calculateEncodedWantlistEntrySize))
54
+
55
+ wantListIndex += added
56
+ size = newSize
57
+ const haveMoreWantlistEntries = hasMore
58
+
59
+ doneSending = !haveMoreBlocks && !haveMorePresences && !haveMoreWantlistEntries
60
+
61
+ // if we're only sending one message, and that message has the full wantlist
62
+ // make sure we let the remote know it's the full list
63
+ if (doneSending && messagesSent === 0 && message.wantlist?.full === true) {
64
+ subMessage.wantlist.full = true
65
+ }
66
+
67
+ yield BitswapMessage.encode(subMessage)
68
+
69
+ messagesSent++
70
+
71
+ if (doneSending) {
72
+ break
73
+ }
74
+ }
75
+ }
76
+
77
+ interface AddResult {
78
+ hasMore: boolean
79
+ added: number
80
+ newSize: number
81
+ }
82
+
83
+ function addToMessage <T> (input: T[], output: T[], start: number, maxSize: number, size: number, calculateSize: (arg: T) => number): AddResult {
84
+ let added = 0
85
+ let hasMore = false
86
+
87
+ // try to send as many blocks as possible
88
+ for (let i = start; i < input.length; i++) {
89
+ const item = input[i]
90
+ const itemSize = calculateSize(item)
91
+
92
+ if (itemSize > maxSize) {
93
+ throw new CodeError('Cannot send block as it is over the max message size', 'ERR_BLOCK_TOO_LARGE')
94
+ }
95
+
96
+ const newSize = size + itemSize
97
+
98
+ if (newSize >= maxSize) {
99
+ hasMore = true
100
+ break
101
+ }
102
+
103
+ output.push(item)
104
+ added++
105
+ size = newSize
106
+ }
107
+
108
+ return { hasMore, added, newSize: size }
109
+ }
110
+
111
+ function calculateEncodedBlockSize (block: Block): number {
112
+ // 3 is the "blocks" field number in message.proto
113
+ return calculateLength(3, Block.encode(block))
114
+ }
115
+
116
+ function calculateEncodedBlockPresenceSize (blockPresence: BlockPresence): number {
117
+ // 4 is the "blockPresences" field number in message.proto
118
+ return calculateLength(4, BlockPresence.encode(blockPresence))
119
+ }
120
+
121
+ function calculateEncodedWantlistEntrySize (entry: WantlistEntry): number {
122
+ // 1 is the "entries" field number in message.proto
123
+ return calculateLength(1, WantlistEntry.encode(entry))
124
+ }
125
+
126
+ function calculateLength (fieldNumber: number, data: Uint8Array): number {
127
+ const fieldNumberLength = encodingLength(fieldNumber)
128
+ const dataLengthLength = encodingLength(data.byteLength)
129
+
130
+ return fieldNumberLength + dataLengthLength + data.byteLength
131
+ }