@helia/block-brokers 2.0.2 → 2.0.3-329652a
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 +1 -1
- package/dist/src/bitswap.d.ts +5 -3
- package/dist/src/bitswap.d.ts.map +1 -1
- package/dist/src/bitswap.js +17 -6
- package/dist/src/bitswap.js.map +1 -1
- package/dist/src/trustless-gateway/broker.d.ts +30 -4
- package/dist/src/trustless-gateway/broker.d.ts.map +1 -1
- package/dist/src/trustless-gateway/broker.js +100 -4
- package/dist/src/trustless-gateway/broker.js.map +1 -1
- package/dist/src/trustless-gateway/index.d.ts +3 -2
- package/dist/src/trustless-gateway/index.d.ts.map +1 -1
- package/dist/src/trustless-gateway/index.js.map +1 -1
- package/dist/src/trustless-gateway/trustless-gateway.d.ts +3 -1
- package/dist/src/trustless-gateway/trustless-gateway.d.ts.map +1 -1
- package/dist/src/trustless-gateway/trustless-gateway.js +4 -1
- package/dist/src/trustless-gateway/trustless-gateway.js.map +1 -1
- package/package.json +12 -3
- package/src/bitswap.ts +26 -13
- package/src/trustless-gateway/broker.ts +147 -10
- package/src/trustless-gateway/index.ts +3 -2
- package/src/trustless-gateway/trustless-gateway.ts +8 -1
- package/dist/typedoc-urls.json +0 -4
|
@@ -1,30 +1,67 @@
|
|
|
1
|
+
import { DEFAULT_SESSION_MIN_PROVIDERS, DEFAULT_SESSION_MAX_PROVIDERS, DEFAULT_SESSION_PROVIDER_QUERY_CONCURRENCY, DEFAULT_SESSION_PROVIDER_QUERY_TIMEOUT } from '@helia/interface'
|
|
2
|
+
import { PeerQueue } from '@libp2p/utils/peer-queue'
|
|
3
|
+
import { isPrivateIp } from '@libp2p/utils/private-ip'
|
|
4
|
+
import { DNS, HTTP, HTTPS } from '@multiformats/multiaddr-matcher'
|
|
5
|
+
import { multiaddrToUri } from '@multiformats/multiaddr-to-uri'
|
|
6
|
+
import pDefer from 'p-defer'
|
|
1
7
|
import { TrustlessGateway } from './trustless-gateway.js'
|
|
2
8
|
import { DEFAULT_TRUSTLESS_GATEWAYS } from './index.js'
|
|
3
9
|
import type { TrustlessGatewayBlockBrokerInit, TrustlessGatewayComponents, TrustlessGatewayGetBlockProgressEvents } from './index.js'
|
|
4
|
-
import type { BlockRetrievalOptions,
|
|
10
|
+
import type { Routing, BlockRetrievalOptions, BlockBroker, CreateSessionOptions } from '@helia/interface'
|
|
5
11
|
import type { Logger } from '@libp2p/interface'
|
|
6
12
|
import type { CID } from 'multiformats/cid'
|
|
7
|
-
|
|
13
|
+
|
|
14
|
+
export interface CreateTrustlessGatewaySessionOptions extends CreateSessionOptions<TrustlessGatewayGetBlockProgressEvents> {
|
|
15
|
+
/**
|
|
16
|
+
* Specify the cache control header to send to the remote. 'only-if-cached'
|
|
17
|
+
* will prevent the gateway from fetching the content if they don't have it.
|
|
18
|
+
*
|
|
19
|
+
* @default only-if-cached
|
|
20
|
+
*/
|
|
21
|
+
cacheControl?: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* By default we will only connect to peers with HTTPS addresses, pass true
|
|
25
|
+
* to also connect to HTTP addresses.
|
|
26
|
+
*
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
allowInsecure?: boolean
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* By default we will only connect to peers with public or DNS addresses, pass
|
|
33
|
+
* true to also connect to private addresses.
|
|
34
|
+
*
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
allowLocal?: boolean
|
|
38
|
+
}
|
|
8
39
|
|
|
9
40
|
/**
|
|
10
41
|
* A class that accepts a list of trustless gateways that are queried
|
|
11
42
|
* for blocks.
|
|
12
43
|
*/
|
|
13
|
-
export class TrustlessGatewayBlockBroker implements
|
|
14
|
-
|
|
15
|
-
> {
|
|
44
|
+
export class TrustlessGatewayBlockBroker implements BlockBroker<TrustlessGatewayGetBlockProgressEvents> {
|
|
45
|
+
private readonly components: TrustlessGatewayComponents
|
|
16
46
|
private readonly gateways: TrustlessGateway[]
|
|
47
|
+
private readonly routing: Routing
|
|
17
48
|
private readonly log: Logger
|
|
18
49
|
|
|
19
50
|
constructor (components: TrustlessGatewayComponents, init: TrustlessGatewayBlockBrokerInit = {}) {
|
|
51
|
+
this.components = components
|
|
20
52
|
this.log = components.logger.forComponent('helia:trustless-gateway-block-broker')
|
|
53
|
+
this.routing = components.routing
|
|
21
54
|
this.gateways = (init.gateways ?? DEFAULT_TRUSTLESS_GATEWAYS)
|
|
22
55
|
.map((gatewayOrUrl) => {
|
|
23
|
-
return new TrustlessGateway(gatewayOrUrl)
|
|
56
|
+
return new TrustlessGateway(gatewayOrUrl, components.logger)
|
|
24
57
|
})
|
|
25
58
|
}
|
|
26
59
|
|
|
27
|
-
|
|
60
|
+
addGateway (gatewayOrUrl: string): void {
|
|
61
|
+
this.gateways.push(new TrustlessGateway(gatewayOrUrl, this.components.logger))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async retrieve (cid: CID, options: BlockRetrievalOptions<TrustlessGatewayGetBlockProgressEvents> = {}): Promise<Uint8Array> {
|
|
28
65
|
// Loop through the gateways until we get a block or run out of gateways
|
|
29
66
|
// TODO: switch to toSorted when support is better
|
|
30
67
|
const sortedGateways = this.gateways.sort((a, b) => b.reliability() - a.reliability())
|
|
@@ -41,7 +78,7 @@ ProgressOptions<TrustlessGatewayGetBlockProgressEvents>
|
|
|
41
78
|
this.log.error('failed to validate block for %c from %s', cid, gateway.url, err)
|
|
42
79
|
gateway.incrementInvalidBlocks()
|
|
43
80
|
|
|
44
|
-
throw new Error(`
|
|
81
|
+
throw new Error(`Block for CID ${cid} from gateway ${gateway.url} failed validation`)
|
|
45
82
|
}
|
|
46
83
|
|
|
47
84
|
return block
|
|
@@ -50,7 +87,7 @@ ProgressOptions<TrustlessGatewayGetBlockProgressEvents>
|
|
|
50
87
|
if (err instanceof Error) {
|
|
51
88
|
aggregateErrors.push(err)
|
|
52
89
|
} else {
|
|
53
|
-
aggregateErrors.push(new Error(`
|
|
90
|
+
aggregateErrors.push(new Error(`Unable to fetch raw block for CID ${cid} from gateway ${gateway.url}`))
|
|
54
91
|
}
|
|
55
92
|
// if signal was aborted, exit the loop
|
|
56
93
|
if (options.signal?.aborted === true) {
|
|
@@ -60,6 +97,106 @@ ProgressOptions<TrustlessGatewayGetBlockProgressEvents>
|
|
|
60
97
|
}
|
|
61
98
|
}
|
|
62
99
|
|
|
63
|
-
|
|
100
|
+
if (aggregateErrors.length > 0) {
|
|
101
|
+
throw new AggregateError(aggregateErrors, `Unable to fetch raw block for CID ${cid} from any gateway`)
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(`Unable to fetch raw block for CID ${cid} from any gateway`)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async createSession (root: CID, options: CreateTrustlessGatewaySessionOptions = {}): Promise<BlockBroker<TrustlessGatewayGetBlockProgressEvents>> {
|
|
108
|
+
const gateways: string[] = []
|
|
109
|
+
const minProviders = options.minProviders ?? DEFAULT_SESSION_MIN_PROVIDERS
|
|
110
|
+
const maxProviders = options.minProviders ?? DEFAULT_SESSION_MAX_PROVIDERS
|
|
111
|
+
const deferred = pDefer<BlockBroker<TrustlessGatewayGetBlockProgressEvents>>()
|
|
112
|
+
const broker = new TrustlessGatewayBlockBroker(this.components, {
|
|
113
|
+
gateways
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
this.log('finding transport-ipfs-gateway-http providers for cid %c', root)
|
|
117
|
+
|
|
118
|
+
const queue = new PeerQueue({
|
|
119
|
+
concurrency: options.providerQueryConcurrency ?? DEFAULT_SESSION_PROVIDER_QUERY_CONCURRENCY
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
Promise.resolve().then(async () => {
|
|
123
|
+
for await (const provider of this.routing.findProviders(root, options)) {
|
|
124
|
+
const httpAddresses = provider.multiaddrs.filter(ma => {
|
|
125
|
+
if (HTTPS.matches(ma) || (options.allowInsecure === true && HTTP.matches(ma))) {
|
|
126
|
+
if (options.allowLocal === true) {
|
|
127
|
+
return true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (DNS.matches(ma)) {
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return isPrivateIp(ma.toOptions().host) === false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return false
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (httpAddresses.length === 0) {
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.log('found transport-ipfs-gateway-http provider %p for cid %c', provider.id, root)
|
|
145
|
+
|
|
146
|
+
void queue.add(async () => {
|
|
147
|
+
for (const ma of httpAddresses) {
|
|
148
|
+
let uri: string | undefined
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// /ip4/x.x.x.x/tcp/31337/http
|
|
152
|
+
// /ip4/x.x.x.x/tcp/31337/https
|
|
153
|
+
// etc
|
|
154
|
+
uri = multiaddrToUri(ma)
|
|
155
|
+
|
|
156
|
+
const resource = `${uri}/ipfs/${root.toString()}?format=raw`
|
|
157
|
+
|
|
158
|
+
// make sure the peer is available - HEAD support doesn't seem to
|
|
159
|
+
// be very widely implemented so as long as the remote responds
|
|
160
|
+
// we are happy they are valid
|
|
161
|
+
// https://specs.ipfs.tech/http-gateways/trustless-gateway/#head-ipfs-cid-path-params
|
|
162
|
+
|
|
163
|
+
// in the future we should be able to request `${uri}/.well-known/libp2p-http
|
|
164
|
+
// and discover an IPFS gateway from $.protocols['/ipfs/gateway'].path
|
|
165
|
+
// in the response
|
|
166
|
+
// https://github.com/libp2p/specs/pull/508/files
|
|
167
|
+
const response = await fetch(resource, {
|
|
168
|
+
method: 'HEAD',
|
|
169
|
+
headers: {
|
|
170
|
+
Accept: 'application/vnd.ipld.raw',
|
|
171
|
+
'Cache-Control': options.cacheControl ?? 'only-if-cached'
|
|
172
|
+
},
|
|
173
|
+
signal: AbortSignal.timeout(options.providerQueryTimeout ?? DEFAULT_SESSION_PROVIDER_QUERY_TIMEOUT)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
this.log('HEAD %s %d', resource, response.status)
|
|
177
|
+
gateways.push(uri)
|
|
178
|
+
broker.addGateway(uri)
|
|
179
|
+
|
|
180
|
+
this.log('found %d transport-ipfs-gateway-http providers for cid %c', gateways.length, root)
|
|
181
|
+
|
|
182
|
+
if (gateways.length === minProviders) {
|
|
183
|
+
deferred.resolve(broker)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (gateways.length === maxProviders) {
|
|
187
|
+
queue.clear()
|
|
188
|
+
}
|
|
189
|
+
} catch (err: any) {
|
|
190
|
+
this.log.error('could not fetch %c from %a', root, uri ?? ma, err)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
.catch(err => {
|
|
197
|
+
this.log.error('error creating session for %c', root, err)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return deferred.promise
|
|
64
201
|
}
|
|
65
202
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TrustlessGatewayBlockBroker } from './broker.js'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Routing, BlockBroker } from '@helia/interface'
|
|
3
3
|
import type { ComponentLogger } from '@libp2p/interface'
|
|
4
4
|
import type { ProgressEvent } from 'progress-events'
|
|
5
5
|
|
|
@@ -22,9 +22,10 @@ export interface TrustlessGatewayBlockBrokerInit {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface TrustlessGatewayComponents {
|
|
25
|
+
routing: Routing
|
|
25
26
|
logger: ComponentLogger
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function trustlessGateway (init: TrustlessGatewayBlockBrokerInit = {}): (components: TrustlessGatewayComponents) =>
|
|
29
|
+
export function trustlessGateway (init: TrustlessGatewayBlockBrokerInit = {}): (components: TrustlessGatewayComponents) => BlockBroker<TrustlessGatewayGetBlockProgressEvents> {
|
|
29
30
|
return (components) => new TrustlessGatewayBlockBroker(components, init)
|
|
30
31
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ComponentLogger, Logger } from '@libp2p/interface'
|
|
1
2
|
import type { CID } from 'multiformats/cid'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -36,8 +37,11 @@ export class TrustlessGateway {
|
|
|
36
37
|
*/
|
|
37
38
|
#successes = 0
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
private readonly log: Logger
|
|
41
|
+
|
|
42
|
+
constructor (url: URL | string, logger: ComponentLogger) {
|
|
40
43
|
this.url = url instanceof URL ? url : new URL(url)
|
|
44
|
+
this.log = logger.forComponent(`helia:trustless-gateway-block-broker:${this.url.hostname}`)
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
/**
|
|
@@ -67,6 +71,9 @@ export class TrustlessGateway {
|
|
|
67
71
|
},
|
|
68
72
|
cache: 'force-cache'
|
|
69
73
|
})
|
|
74
|
+
|
|
75
|
+
this.log('GET %s %d', gwUrl, res.status)
|
|
76
|
+
|
|
70
77
|
if (!res.ok) {
|
|
71
78
|
this.#errors++
|
|
72
79
|
throw new Error(`unable to fetch raw block for CID ${cid} from gateway ${this.url}`)
|
package/dist/typedoc-urls.json
DELETED