diff --git a/packages/block-brokers/src/trustless-gateway/broker.ts b/packages/block-brokers/src/trustless-gateway/broker.ts index 433a690d0..ce2330cd0 100644 --- a/packages/block-brokers/src/trustless-gateway/broker.ts +++ b/packages/block-brokers/src/trustless-gateway/broker.ts @@ -19,8 +19,13 @@ ProgressOptions 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 }) }) } diff --git a/packages/block-brokers/src/trustless-gateway/index.ts b/packages/block-brokers/src/trustless-gateway/index.ts index 91dcab15c..e24e1e997 100644 --- a/packages/block-brokers/src/trustless-gateway/index.ts +++ b/packages/block-brokers/src/trustless-gateway/index.ts @@ -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 + gateways?: Array } export interface TrustlessGatewayComponents { diff --git a/packages/block-brokers/src/trustless-gateway/trustless-gateway.ts b/packages/block-brokers/src/trustless-gateway/trustless-gateway.ts index 352d6ea38..601fdfc75 100644 --- a/packages/block-brokers/src/trustless-gateway/trustless-gateway.ts +++ b/packages/block-brokers/src/trustless-gateway/trustless-gateway.ts @@ -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 @@ -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 @@ -36,8 +51,9 @@ 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 } /** @@ -45,8 +61,7 @@ export class TrustlessGateway { * https://specs.ipfs.tech/http-gateways/trustless-gateway/ */ async getRawBlock (cid: CID, signal?: AbortSignal): Promise { - 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 @@ -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' @@ -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()}` + } + 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 @@ -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) } /** diff --git a/packages/block-brokers/test/trustless-gateway.spec.ts b/packages/block-brokers/test/trustless-gateway.spec.ts index 481ac6df8..1f1626dd7 100644 --- a/packages/block-brokers/test/trustless-gateway.spec.ts +++ b/packages/block-brokers/test/trustless-gateway.spec.ts @@ -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') ] @@ -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/`) + }) })