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 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 @@
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 })
}

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, { 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 @@
*/
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 @@
*/
#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)

Check warning on line 64 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#L64

Added line #L64 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 +76,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 80 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#L79-L80

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

/**
* 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 @@
*
* 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 146 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#L146

Added line #L146 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', { 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/`)
})
})
Loading