@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.
- package/dist/index.min.js +6 -1
- package/dist/src/bitswap.d.ts +3 -2
- package/dist/src/bitswap.d.ts.map +1 -1
- package/dist/src/bitswap.js +7 -14
- package/dist/src/bitswap.js.map +1 -1
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +22 -45
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/network.d.ts +8 -7
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +71 -169
- package/dist/src/network.js.map +1 -1
- package/dist/src/pb/message.d.ts +6 -6
- package/dist/src/pb/message.d.ts.map +1 -1
- package/dist/src/pb/message.js +37 -20
- package/dist/src/pb/message.js.map +1 -1
- package/dist/src/peer-want-lists/index.d.ts +3 -2
- package/dist/src/peer-want-lists/index.d.ts.map +1 -1
- package/dist/src/peer-want-lists/index.js +7 -3
- package/dist/src/peer-want-lists/index.js.map +1 -1
- package/dist/src/peer-want-lists/ledger.d.ts +3 -1
- package/dist/src/peer-want-lists/ledger.d.ts.map +1 -1
- package/dist/src/peer-want-lists/ledger.js +8 -0
- package/dist/src/peer-want-lists/ledger.js.map +1 -1
- package/dist/src/session.d.ts +13 -8
- package/dist/src/session.d.ts.map +1 -1
- package/dist/src/session.js +25 -88
- package/dist/src/session.js.map +1 -1
- package/dist/src/stats.d.ts +2 -2
- package/dist/src/stats.d.ts.map +1 -1
- package/dist/src/stats.js +4 -4
- package/dist/src/stats.js.map +1 -1
- package/dist/src/utils/merge-messages.d.ts +3 -0
- package/dist/src/utils/merge-messages.d.ts.map +1 -0
- package/dist/src/utils/merge-messages.js +51 -0
- package/dist/src/utils/merge-messages.js.map +1 -0
- package/dist/src/utils/split-message.d.ts +14 -0
- package/dist/src/utils/split-message.d.ts.map +1 -0
- package/dist/src/utils/split-message.js +99 -0
- package/dist/src/utils/split-message.js.map +1 -0
- package/dist/src/want-list.d.ts +20 -23
- package/dist/src/want-list.d.ts.map +1 -1
- package/dist/src/want-list.js +136 -133
- package/dist/src/want-list.js.map +1 -1
- package/package.json +6 -6
- package/src/bitswap.ts +9 -16
- package/src/constants.ts +2 -0
- package/src/index.ts +24 -51
- package/src/network.ts +83 -200
- package/src/pb/message.ts +40 -20
- package/src/peer-want-lists/index.ts +9 -5
- package/src/peer-want-lists/ledger.ts +11 -1
- package/src/session.ts +31 -120
- package/src/stats.ts +6 -6
- package/src/utils/merge-messages.ts +70 -0
- package/src/utils/split-message.ts +131 -0
- package/src/want-list.ts +205 -212
- 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,
|
|
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
|
-
|
|
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: '
|
|
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 {
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
55
|
-
|
|
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
|
|
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('
|
|
35
|
+
this.log('%p %s %c', provider, result.has ? 'has' : 'does not have', cid)
|
|
71
36
|
|
|
72
|
-
|
|
73
|
-
|
|
37
|
+
if (result.has && result.block != null) {
|
|
38
|
+
return result.block
|
|
39
|
+
}
|
|
74
40
|
|
|
75
|
-
|
|
41
|
+
throw new Error('Provider did not have block')
|
|
76
42
|
}
|
|
77
43
|
|
|
78
|
-
async findNewProviders (cid: CID,
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
50
|
+
toEvictionKey (provider: PeerId): Uint8Array | string {
|
|
51
|
+
return provider.toBytes()
|
|
52
|
+
}
|
|
137
53
|
|
|
138
|
-
|
|
54
|
+
equals (providerA: PeerId, providerB: PeerId): boolean {
|
|
55
|
+
return providerA.equals(providerB)
|
|
139
56
|
}
|
|
140
57
|
}
|
|
141
58
|
|
|
142
|
-
export
|
|
143
|
-
|
|
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 {
|
|
1
|
+
import type { Libp2p, MetricGroup, PeerId } from '@libp2p/interface'
|
|
2
2
|
|
|
3
3
|
export interface StatsComponents {
|
|
4
|
-
|
|
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('
|
|
15
|
-
this.duplicateBlocksReceived = components.metrics?.registerMetricGroup('
|
|
16
|
-
this.dataReceived = components.metrics?.registerMetricGroup('
|
|
17
|
-
this.duplicateDataReceived = components.metrics?.registerMetricGroup('
|
|
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
|
+
}
|