Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/block-brokers/src/trustless-gateway/broker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ ProgressOptions<TrustlessGatewayGetBlockProgressEvents>
constructor (components: TrustlessGatewayComponents, init: TrustlessGatewayBlockBrokerInit = {}) {
this.log = components.logger.forComponent('helia:trustless-gateway-block-broker')
this.gateways = (init.gateways ?? DEFAULT_TRUSTLESS_GATEWAYS)
.map((gatewayOrUrl) => {
return new TrustlessGateway(gatewayOrUrl)
.map((gw) => {
if (typeof gw === 'string' || gw instanceof URL) {
// backward compatibility defaults to path gateway
return new TrustlessGateway(gw, { subdomainResolution: false })
}

return new TrustlessGateway(gw.url, { subdomainResolution: gw.subdomainResolution })
})
}

Expand Down
21 changes: 13 additions & 8 deletions packages/block-brokers/src/trustless-gateway/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ import type { BlockRetriever } from '@helia/interface/src/blocks.js'
import type { ComponentLogger } from '@libp2p/interface'
import type { ProgressEvent } from 'progress-events'

export const DEFAULT_TRUSTLESS_GATEWAYS = [
// 2023-10-03: IPNS, Origin, and Block/CAR support from https://ipfs-public-gateway-checker.on.fleek.co/
'https://trustless-gateway.link',
export const DEFAULT_TRUSTLESS_GATEWAYS: TrustlessGatewayUrl[] = [
// 2024-02-20: IPNS and Block/CAR support from https://ipfs.github.io/public-gateway-checker/
{ url: 'https://trustless-gateway.link', subdomainResolution: false },

// 2023-10-03: IPNS, Origin, and Block/CAR support from https://ipfs-public-gateway-checker.on.fleek.co/
'https://cloudflare-ipfs.com',
// 2024-02-20: IPNS and Block/CAR support from https://ipfs.github.io/public-gateway-checker/
{ url: 'https://cloudflare-ipfs.com', subdomainResolution: false },

// 2023-10-03: IPNS, Origin, and Block/CAR support from https://ipfs-public-gateway-checker.on.fleek.co/
'https://4everland.io'
// 2024-02-20: IPNS, Origin, and Block/CAR support from https://ipfs.github.io/public-gateway-checker/
{ url: 'https://4everland.io', subdomainResolution: true }
]

interface TrustlessGatewayUrl {
url: string | URL
subdomainResolution: boolean
}

export type TrustlessGatewayGetBlockProgressEvents =
ProgressEvent<'trustless-gateway:get-block:fetch', URL>

export interface TrustlessGatewayBlockBrokerInit {
gateways?: Array<string | URL>
gateways?: Array<string | URL | TrustlessGatewayUrl>
}

export interface TrustlessGatewayComponents {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { base32 } from 'multiformats/bases/base32'
import type { CID } from 'multiformats/cid'

interface TrustlessGatewayOpts {

/**
* Determins whether the gateway supports subdomain resolution
*/
subdomainResolution: boolean
}

/**
* A `TrustlessGateway` keeps track of the number of attempts, errors, and
* successes for a given gateway url so that we can prioritize gateways that
Expand All @@ -8,6 +17,12 @@ import type { CID } from 'multiformats/cid'
*/
export class TrustlessGateway {
public readonly url: URL

/**
* Whether this gateway is a subdomain resolution style gateway
*/
public subdomainResolution: boolean

/**
* The number of times this gateway has been attempted to be used to fetch a
* block. This includes successful, errored, and aborted attempts. By counting
Expand Down Expand Up @@ -36,17 +51,17 @@ export class TrustlessGateway {
*/
#successes = 0

constructor (url: URL | string) {
constructor (url: URL | string, { subdomainResolution }: TrustlessGatewayOpts = { subdomainResolution: false }) {
this.url = url instanceof URL ? url : new URL(url)
this.subdomainResolution = subdomainResolution
}

/**
* Fetch a raw block from `this.url` following the specification defined at
* https://specs.ipfs.tech/http-gateways/trustless-gateway/
*/
async getRawBlock (cid: CID, signal?: AbortSignal): Promise<Uint8Array> {
const gwUrl = this.url
gwUrl.pathname = `/ipfs/${cid.toString()}`
const gwUrl = this.getGwUrl(cid)

// necessary as not every gateway supports dag-cbor, but every should support
// sending raw block as-is
Expand All @@ -61,8 +76,8 @@ export class TrustlessGateway {
const res = await fetch(gwUrl.toString(), {
signal,
headers: {
// also set header, just in case ?format= is filtered out by some
// reverse proxy
// also set header, just in case ?format= is filtered out by some
// reverse proxy
Accept: 'application/vnd.ipld.raw'
},
cache: 'force-cache'
Expand All @@ -84,6 +99,20 @@ export class TrustlessGateway {
}
}

/**
* Construct the Gateway URL for a CID
*/
getGwUrl (cid: CID): URL {
const gwUrl = new URL(this.url)

if (this.subdomainResolution) {
gwUrl.hostname = `${cid.toString(base32)}.ipfs.${gwUrl.hostname}`
} else {
gwUrl.pathname = `/ipfs/${cid.toString()}`
}
Comment on lines +108 to +112
Copy link
Member

@achingbrain achingbrain Mar 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check here that the CID can actually be encoded as base32 (v0 will throw, I think?), and that it's not too long to be used as a DNS label.

If it's v0 we could convert to v1 first to be able to encode it in a case insensitive way? We'll get the same block data back but I don't know if this will cause other problems.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's v0 we could convert to v1 first to be able to encode it in a case insensitive way? We'll get the same block data back but I don't know if this will cause other problems.

Right. What other problems that it may cause did you have in mind?

return gwUrl
}

/**
* Encapsulate the logic for determining whether a gateway is considered
* reliable, for prioritization. This is based on the number of successful attempts made
Expand Down Expand Up @@ -114,7 +143,7 @@ export class TrustlessGateway {
*
* Play around with the below reliability function at https://www.desmos.com/calculator/d6hfhf5ukm
*/
return this.#successes / (this.#attempts + (this.#errors * 3))
return this.#successes / (this.#attempts + this.#errors * 3)
}

/**
Expand Down
15 changes: 14 additions & 1 deletion packages/block-brokers/test/trustless-gateway.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('trustless-gateway-block-broker', () => {

gateways = [
stubConstructor(TrustlessGateway, 'http://localhost:8080'),
stubConstructor(TrustlessGateway, 'http://localhost:8081'),
stubConstructor(TrustlessGateway, 'http://localhost:8081', { subdomainResolution: true }),
stubConstructor(TrustlessGateway, 'http://localhost:8082'),
stubConstructor(TrustlessGateway, 'http://localhost:8083')
]
Expand Down Expand Up @@ -150,4 +150,17 @@ describe('trustless-gateway-block-broker', () => {
expect(gateways[1].getRawBlock.calledWith(cid1, Sinon.match.any)).to.be.false()
expect(gateways[2].getRawBlock.calledWith(cid1, Sinon.match.any)).to.be.false()
})

it('constructs the gateway url for the cid for both path and subdomain gateways', async () => {
const pathGw = new TrustlessGateway('http://localhost:8080')
const subdomainGw = new TrustlessGateway('https://dweb.link', { subdomainResolution: true })

expect(pathGw.getGwUrl(blocks[0].cid).hostname).to.equal('localhost')
expect(pathGw.getGwUrl(blocks[0].cid).toString()).to.equal('http://localhost:8080/ipfs/bafkreiefnkxuhnq3536qo2i2w3tazvifek4mbbzb6zlq3ouhprjce5c3aq')
expect(pathGw.getGwUrl(blocks[1].cid).toString()).to.equal(`http://localhost:8080/ipfs/${blocks[1].cid.toString()}`)

expect(subdomainGw.getGwUrl(blocks[0].cid).hostname).to.equal('bafkreiefnkxuhnq3536qo2i2w3tazvifek4mbbzb6zlq3ouhprjce5c3aq.ipfs.dweb.link')
expect(subdomainGw.getGwUrl(blocks[0].cid).toString()).to.equal('https://bafkreiefnkxuhnq3536qo2i2w3tazvifek4mbbzb6zlq3ouhprjce5c3aq.ipfs.dweb.link/')
expect(subdomainGw.getGwUrl(blocks[1].cid).toString()).to.equal(`https://${blocks[1].cid.toString()}.ipfs.dweb.link/`)
})
})