Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add explicit support for subdomain gateways #439

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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 @@
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, false)
}

Check warning on line 26 in packages/block-brokers/src/trustless-gateway/broker.ts

View check run for this annotation

Codecov / codecov/patch

packages/block-brokers/src/trustless-gateway/broker.ts#L24-L26

Added lines #L24 - L26 were not covered by tests

return new TrustlessGateway(gw.url, gw.supportsSubdomains)
})
}

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', supportsSubdomains: 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', supportsSubdomains: 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', supportsSubdomains: true }
]

interface TrustlessGatewayUrl {
url: string | URL
supportsSubdomains: 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,3 +1,4 @@
import { base32 } from 'multiformats/bases/base32'
import type { CID } from 'multiformats/cid'

/**
Expand All @@ -8,6 +9,12 @@
*/
export class TrustlessGateway {
public readonly url: URL

/**
* Whether this gateway is a subdomain resolution style gateway
*/
public supportsSubdomains: 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 +43,17 @@
*/
#successes = 0

constructor (url: URL | string) {
constructor (url: URL | string, supportsSubdomains: boolean = false) {
2color marked this conversation as resolved.
Show resolved Hide resolved
this.url = url instanceof URL ? url : new URL(url)
this.supportsSubdomains = supportsSubdomains
}

/**
* 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)

Check warning on line 56 in packages/block-brokers/src/trustless-gateway/trustless-gateway.ts

View check run for this annotation

Codecov / codecov/patch

packages/block-brokers/src/trustless-gateway/trustless-gateway.ts#L56

Added line #L56 was not covered by tests

// necessary as not every gateway supports dag-cbor, but every should support
// sending raw block as-is
Expand All @@ -61,8 +68,8 @@
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

Check warning on line 72 in packages/block-brokers/src/trustless-gateway/trustless-gateway.ts

View check run for this annotation

Codecov / codecov/patch

packages/block-brokers/src/trustless-gateway/trustless-gateway.ts#L71-L72

Added lines #L71 - L72 were not covered by tests
Accept: 'application/vnd.ipld.raw'
},
cache: 'force-cache'
Expand All @@ -84,6 +91,20 @@
}
}

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

if (this.supportsSubdomains) {
gwUrl.hostname = `${cid.toString(base32)}.ipfs.${gwUrl.hostname}`
} else {
gwUrl.pathname = `/ipfs/${cid.toString()}`
}
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 +135,7 @@
*
* 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)

Check warning on line 138 in packages/block-brokers/src/trustless-gateway/trustless-gateway.ts

View check run for this annotation

Codecov / codecov/patch

packages/block-brokers/src/trustless-gateway/trustless-gateway.ts#L138

Added line #L138 was not covered by tests
}

/**
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', 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', 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/`)
})
})
Loading