From 41befc79a3612a681b02b5d08b03f065fec08168 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Tue, 12 Mar 2024 13:04:39 +0100 Subject: [PATCH] chore: upgrade libp2p --- packages/helia/package.json | 2 +- packages/helia/src/index.ts | 1 + .../src/utils/libp2p-defaults.browser.ts | 1 + packages/helia/src/utils/libp2p-defaults.ts | 1 + packages/helia/src/utils/libp2p.ts | 2 + packages/interop/src/ipns-dnslink.spec.ts | 28 ++++++ packages/ipns/src/dnslink.ts | 85 +++++++++++++++++++ packages/utils/package.json | 1 + 8 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 packages/interop/src/ipns-dnslink.spec.ts create mode 100644 packages/ipns/src/dnslink.ts diff --git a/packages/helia/package.json b/packages/helia/package.json index 64d56532..8c22b4d4 100644 --- a/packages/helia/package.json +++ b/packages/helia/package.json @@ -83,7 +83,7 @@ "interface-blockstore": "^5.2.10", "interface-datastore": "^8.2.11", "ipns": "^9.0.0", - "libp2p": "^1.2.4", + "libp2p": "^1.3.0", "multiformats": "^13.1.0" }, "devDependencies": { diff --git a/packages/helia/src/index.ts b/packages/helia/src/index.ts index e0377960..cb4ed8b7 100644 --- a/packages/helia/src/index.ts +++ b/packages/helia/src/index.ts @@ -100,6 +100,7 @@ export async function createHelia (init: Partial = {}): Promise({ ...init, libp2p: { + dns: init.dns, ...init.libp2p, // ignore the libp2p start parameter as it should be on the main init diff --git a/packages/helia/src/utils/libp2p-defaults.browser.ts b/packages/helia/src/utils/libp2p-defaults.browser.ts index cf7cfe57..0e9b3f2e 100644 --- a/packages/helia/src/utils/libp2p-defaults.browser.ts +++ b/packages/helia/src/utils/libp2p-defaults.browser.ts @@ -34,6 +34,7 @@ export interface DefaultLibp2pServices extends Record { export function libp2pDefaults (options: Libp2pDefaultsOptions = {}): Libp2pOptions { return { peerId: options.peerId, + dns: options.dns, addresses: { listen: [ '/webrtc' diff --git a/packages/helia/src/utils/libp2p-defaults.ts b/packages/helia/src/utils/libp2p-defaults.ts index 3f4de600..83bb03b3 100644 --- a/packages/helia/src/utils/libp2p-defaults.ts +++ b/packages/helia/src/utils/libp2p-defaults.ts @@ -38,6 +38,7 @@ export interface DefaultLibp2pServices extends Record { export function libp2pDefaults (options: Libp2pDefaultsOptions = {}): Libp2pOptions { return { peerId: options.peerId, + dns: options.dns, addresses: { listen: [ '/ip4/0.0.0.0/tcp/0', diff --git a/packages/helia/src/utils/libp2p.ts b/packages/helia/src/utils/libp2p.ts index 64a3b56e..f5597e08 100644 --- a/packages/helia/src/utils/libp2p.ts +++ b/packages/helia/src/utils/libp2p.ts @@ -6,6 +6,7 @@ import { libp2pDefaults } from './libp2p-defaults.js' import type { DefaultLibp2pServices } from './libp2p-defaults.js' import type { ComponentLogger, Libp2p, PeerId } from '@libp2p/interface' import type { Keychain, KeychainInit } from '@libp2p/keychain' +import type { DNS } from '@multiformats/dns' import type { Datastore } from 'interface-datastore' import type { Libp2pOptions } from 'libp2p' @@ -20,6 +21,7 @@ export interface CreateLibp2pOptions> { export interface Libp2pDefaultsOptions { peerId?: PeerId keychain?: KeychainInit + dns?: DNS } export async function createLibp2p = DefaultLibp2pServices> (options: CreateLibp2pOptions): Promise> { diff --git a/packages/interop/src/ipns-dnslink.spec.ts b/packages/interop/src/ipns-dnslink.spec.ts new file mode 100644 index 00000000..82db389d --- /dev/null +++ b/packages/interop/src/ipns-dnslink.spec.ts @@ -0,0 +1,28 @@ +/* eslint-env mocha */ + +import { ipns } from '@helia/ipns' +import { createHeliaNode } from './fixtures/create-helia.js' +import type { IPNS } from '@helia/ipns' +import type { HeliaLibp2p } from 'helia' + +describe.only('@helia/ipns - dnslink', () => { + let helia: HeliaLibp2p + let name: IPNS + + beforeEach(async () => { + helia = await createHeliaNode() + name = ipns(helia) + }) + + afterEach(async () => { + if (helia != null) { + await helia.stop() + } + }) + + it('should resolve ipfs.io', async () => { + const result = await name.resolveDns('ipfs.io') + + console.info(result) + }) +}) diff --git a/packages/ipns/src/dnslink.ts b/packages/ipns/src/dnslink.ts new file mode 100644 index 00000000..e8d7dcb7 --- /dev/null +++ b/packages/ipns/src/dnslink.ts @@ -0,0 +1,85 @@ +import { CodeError, type Logger } from '@libp2p/interface' +import { peerIdFromString } from '@libp2p/peer-id' +import { RecordType } from '@multiformats/dns' +import { CID } from 'multiformats/cid' +import type { ResolveDNSOptions } from './index.js' +import type { DNS } from '@multiformats/dns' + +const MAX_RECURSIVE_DEPTH = 32 + +export const recursiveResolveDnslink = async (domain: string, depth: number, dns: DNS, log: Logger, options: ResolveDNSOptions = {}): Promise => { + if (depth === 0) { + throw new Error('recursion limit exceeded') + } + + const response = await dns.query(domain, { + ...options, + types: [ + RecordType.TXT + ] + }) + + // TODO: support multiple dnslink records + for (const answer of response.Answer) { + try { + let result = answer.data + + if (!result.startsWith('dnslink=')) { + // invalid record? + continue + } + + result = result.replace('dnslink=', '') + // result is now a `/ipfs/` or `/ipns/` string + const [, protocol, domainOrCID, ...rest] = result.split('/') // e.g. ["", "ipfs", ""] + + if (protocol === 'ipfs') { + try { + const cid = CID.parse(domainOrCID) + + // if the result is a CID, we've reached the end of the recursion + return `/ipfs/${cid}${rest.length > 0 ? `/${rest.join('/')}` : ''}` + } catch {} + } else if (protocol === 'ipns') { + try { + const peerId = peerIdFromString(domainOrCID) + + // if the result is a PeerId, we've reached the end of the recursion + return `/ipns/${peerId}${rest.length > 0 ? `/${rest.join('/')}` : ''}` + } catch {} + + // if the result was another IPNS domain, try to follow it + return await recursiveResolveDnslink(domainOrCID, depth - 1, dns, log, options) + } else { + throw new CodeError(`Unknown protocol in DNSLink record for domain: ${domain}`, 'ERR_DNSLINK_NOT_FOUND') + } + } catch (err: any) { + log.error('could not parse DNS link record for domain %s, %s', domain, answer.data, err) + } + } + + throw new CodeError(`No DNSLink records found for domain: ${domain}`, 'ERR_DNSLINK_NOT_FOUND') +} + +export async function resolveDNSLink (domain: string, dns: DNS, log: Logger, options: ResolveDNSOptions = {}): Promise { + try { + return await recursiveResolveDnslink(domain, options.maxRecursiveDepth ?? MAX_RECURSIVE_DEPTH, dns, log, options) + } catch (err: any) { + // If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error + if (err.code !== 'ENOTFOUND' && err.code !== 'ERR_DNSLINK_NOT_FOUND' && err.code !== 'ENODATA') { + throw err + } + + if (domain.startsWith('_dnslink.')) { + // The supplied domain contains a _dnslink component + // Check the non-_dnslink domain + domain = domain.replace('_dnslink.', '') + } else { + // Check the _dnslink subdomain + domain = `_dnslink.${domain}` + } + + // If this throws then we propagate the error + return await recursiveResolveDnslink(domain, options.maxRecursiveDepth ?? MAX_RECURSIVE_DEPTH, dns, log, options) + } +} diff --git a/packages/utils/package.json b/packages/utils/package.json index 58627f2f..817d042c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -61,6 +61,7 @@ "@libp2p/logger": "^4.0.7", "@libp2p/peer-collections": "^5.1.7", "@libp2p/utils": "^5.2.6", + "@multiformats/dns": "^1.0.1", "any-signal": "^4.1.1", "blockstore-core": "^4.4.0", "cborg": "^4.0.9",