Skip to content
This repository was archived by the owner on Jan 8, 2024. It is now read-only.
Merged
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
2 changes: 1 addition & 1 deletion packages/interop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"go-ipfs": "^0.22.0",
"helia": "^2.0.1",
"ipfsd-ctl": "^13.0.0",
"ipns": "^6.0.0",
"ipns": "^7.0.1",
"it-all": "^3.0.2",
"it-last": "^3.0.1",
"it-map": "^3.0.3",
Expand Down
6 changes: 4 additions & 2 deletions packages/interop/test/fixtures/create-helia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { tcp } from '@libp2p/tcp'
import { MemoryBlockstore } from 'blockstore-core'
import { MemoryDatastore } from 'datastore-core'
import { createHelia } from 'helia'
import { createLibp2p, type Libp2p, type Libp2pOptions } from 'libp2p'
import { createLibp2p, type Libp2pOptions } from 'libp2p'
import type { Helia } from '@helia/interface'
import type { Libp2p } from '@libp2p/interface'
import type { IdentifyService } from 'libp2p/identify'

export async function createHeliaNode <T extends { identify: any }> (config: Libp2pOptions<T> = {}): Promise<Helia<Libp2p<T>>> {
export async function createHeliaNode <T extends { identify: IdentifyService }> (config: Libp2pOptions<T> = {}): Promise<Helia<Libp2p<T>>> {
const blockstore = new MemoryBlockstore()
const datastore = new MemoryDatastore()

Expand Down
2 changes: 1 addition & 1 deletion packages/interop/test/pubsub.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import { keyTypes } from './fixtures/key-types.js'
import { waitFor } from './fixtures/wait-for.js'
import type { Helia } from '@helia/interface'
import type { IPNS } from '@helia/ipns'
import type { Libp2p } from '@libp2p/interface'
import type { PubSub } from '@libp2p/interface/pubsub'
import type { Controller } from 'ipfsd-ctl'
import type { Libp2p } from 'libp2p'

const LIBP2P_KEY_CODEC = 0x72

Expand Down
2 changes: 1 addition & 1 deletion packages/ipns/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
"@libp2p/record": "^3.0.0",
"hashlru": "^2.3.0",
"interface-datastore": "^8.0.0",
"ipns": "^6.0.0",
"ipns": "^7.0.1",
"is-ipfs": "^8.0.1",
"multiformats": "^12.0.1",
"p-queue": "^7.3.0",
Expand Down
62 changes: 18 additions & 44 deletions packages/ipns/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,11 @@
* @example
*
* ```typescript
* import { gossipsub } from '@chainsafe/libp2p-gossipsub'
* import { kadDHT } from '@libp2p/kad-dht'
* import { createLibp2p } from 'libp2p'
* import { createHelia } from 'helia'
* import { ipns, ipnsValidator, ipnsSelector } from '@helia/ipns'
* import { dht, pubsub } from '@helia/ipns/routing'
* import { unixfs } from '@helia/unixfs'
*
* const libp2p = await createLibp2p({
* dht: kadDHT({
* validators: {
* ipns: ipnsValidator
* },
* selectors: {
* ipns: ipnsSelector
* }
* }),
* pubsub: gossipsub()
* })
*
* const helia = await createHelia({
* libp2p,
* //.. other options
* })
* const helia = await createHelia()
* const name = ipns(helia, [
* dht(helia),
* pubsub(helia)
Expand Down Expand Up @@ -63,22 +44,20 @@
*/

import { CodeError } from '@libp2p/interface/errors'
import { isPeerId, type PeerId } from '@libp2p/interface/peer-id'
import { logger } from '@libp2p/logger'
import { peerIdFromString } from '@libp2p/peer-id'
import { create, marshal, peerIdToRoutingKey, unmarshal } from 'ipns'
import { ipnsSelector } from 'ipns/selector'
import { ipnsValidator } from 'ipns/validator'
import { CID } from 'multiformats/cid'
import { CustomProgressEvent } from 'progress-events'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { localStore, type LocalStore } from './routing/local-store.js'
import { resolveDnslink } from './utils/resolve-dns-link.js'
import type { IPNSRouting, IPNSRoutingEvents } from './routing/index.js'
import type { AbortOptions } from '@libp2p/interface'
import type { PeerId } from '@libp2p/interface/peer-id'
import type { Datastore } from 'interface-datastore'
import type { IPNSEntry } from 'ipns'
import type { IPNSRecord } from 'ipns'
import type { ProgressEvent, ProgressOptions } from 'progress-events'

const log = logger('helia:ipns')
Expand All @@ -91,18 +70,18 @@ const DEFAULT_REPUBLISH_INTERVAL_MS = 23 * HOUR

export type PublishProgressEvents =
ProgressEvent<'ipns:publish:start'> |
ProgressEvent<'ipns:publish:success', IPNSEntry> |
ProgressEvent<'ipns:publish:success', IPNSRecord> |
ProgressEvent<'ipns:publish:error', Error>

export type ResolveProgressEvents =
ProgressEvent<'ipns:resolve:start', unknown> |
ProgressEvent<'ipns:resolve:success', IPNSEntry> |
ProgressEvent<'ipns:resolve:success', IPNSRecord> |
ProgressEvent<'ipns:resolve:error', Error>

export type RepublishProgressEvents =
ProgressEvent<'ipns:republish:start', unknown> |
ProgressEvent<'ipns:republish:success', IPNSEntry> |
ProgressEvent<'ipns:republish:error', { record: IPNSEntry, err: Error }>
ProgressEvent<'ipns:republish:success', IPNSRecord> |
ProgressEvent<'ipns:republish:error', { record: IPNSRecord, err: Error }>

export interface PublishOptions extends AbortOptions, ProgressOptions<PublishProgressEvents | IPNSRoutingEvents> {
/**
Expand All @@ -114,6 +93,12 @@ export interface PublishOptions extends AbortOptions, ProgressOptions<PublishPro
* Only publish to a local datastore (default: false)
*/
offline?: boolean

/**
* By default a IPNS V1 and a V2 signature is added to every record. Pass
* false here to only add a V2 signature. (default: true)
*/
v1Compatible?: boolean
}

export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolveProgressEvents | IPNSRoutingEvents> {
Expand Down Expand Up @@ -143,7 +128,7 @@ export interface IPNS {
*
* If the valid is a PeerId, a recursive IPNS record will be created.
*/
publish: (key: PeerId, value: CID | PeerId, options?: PublishOptions) => Promise<IPNSEntry>
publish: (key: PeerId, value: CID | PeerId, options?: PublishOptions) => Promise<IPNSRecord>

/**
* Accepts a public key formatted as a libp2p PeerID and resolves the IPNS record
Expand Down Expand Up @@ -178,7 +163,7 @@ class DefaultIPNS implements IPNS {
this.localStore = localStore(components.datastore)
}

async publish (key: PeerId, value: CID | PeerId, options: PublishOptions = {}): Promise<IPNSEntry> {
async publish (key: PeerId, value: CID | PeerId, options: PublishOptions = {}): Promise<IPNSRecord> {
try {
let sequenceNumber = 1n
const routingKey = peerIdToRoutingKey(key)
Expand All @@ -190,18 +175,8 @@ class DefaultIPNS implements IPNS {
sequenceNumber = existingRecord.sequence + 1n
}

let str

if (isPeerId(value)) {
str = `/ipns/${value.toString()}`
} else {
str = `/ipfs/${value.toString()}`
}

const bytes = uint8ArrayFromString(str)

// create record
const record = await create(key, bytes, sequenceNumber, options.lifetime ?? DEFAULT_LIFETIME_MS)
const record = await create(key, value, sequenceNumber, options.lifetime ?? DEFAULT_LIFETIME_MS, options)
const marshaledRecord = marshal(record)

await this.localStore.put(routingKey, marshaledRecord, options)
Expand All @@ -221,9 +196,8 @@ class DefaultIPNS implements IPNS {
async resolve (key: PeerId, options: ResolveOptions = {}): Promise<CID> {
const routingKey = peerIdToRoutingKey(key)
const record = await this.#findIpnsRecord(routingKey, options)
const str = uint8ArrayToString(record.value)

return this.#resolve(str, options)
return this.#resolve(record.value, options)
}

async resolveDns (domain: string, options: ResolveDNSOptions = {}): Promise<CID> {
Expand Down Expand Up @@ -285,7 +259,7 @@ class DefaultIPNS implements IPNS {
throw new Error('Invalid value')
}

async #findIpnsRecord (routingKey: Uint8Array, options: ResolveOptions = {}): Promise<IPNSEntry> {
async #findIpnsRecord (routingKey: Uint8Array, options: ResolveOptions = {}): Promise<IPNSRecord> {
let routers = [
this.localStore,
...this.routers
Expand Down
18 changes: 8 additions & 10 deletions packages/ipns/test/resolve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { create, marshal, peerIdToRoutingKey } from 'ipns'
import { CID } from 'multiformats/cid'
import Sinon from 'sinon'
import { type StubbedInstance, stubInterface } from 'sinon-ts'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { ipns } from '../src/index.js'
import type { IPNS, IPNSRouting } from '../src/index.js'
Expand Down Expand Up @@ -39,7 +38,7 @@ describe('resolve', () => {
throw new Error('Did not resolve entry')
}

expect(resolvedValue.toString()).to.equal(cid.toString())
expect(resolvedValue.toString()).to.equal(cid.toV1().toString())
})

it('should resolve a record offline', async () => {
Expand All @@ -58,7 +57,7 @@ describe('resolve', () => {
throw new Error('Did not resolve entry')
}

expect(resolvedValue.toString()).to.equal(cid.toString())
expect(resolvedValue.toString()).to.equal(cid.toV1().toString())
})

it('should resolve a recursive record', async () => {
Expand All @@ -73,7 +72,7 @@ describe('resolve', () => {
throw new Error('Did not resolve entry')
}

expect(resolvedValue.toString()).to.equal(cid.toString())
expect(resolvedValue.toString()).to.equal(cid.toV1().toString())
})

it('should resolve /ipns/tableflip.io', async function () {
Expand Down Expand Up @@ -112,14 +111,13 @@ describe('resolve', () => {

expect(datastore.has(dhtKey)).to.be.false('already had record')

const bytes = uint8ArrayFromString(`/ipfs/${cid.toString()}`)
const record = await create(peerId, bytes, 0n, 60000)
const record = await create(peerId, cid, 0n, 60000)
const marshalledRecord = marshal(record)

routing.get.withArgs(routingKey).resolves(marshalledRecord)

const result = await name.resolve(peerId)
expect(result.toString()).to.equal(cid.toString(), 'incorrect record resolved')
expect(result.toString()).to.equal(cid.toV1().toString(), 'incorrect record resolved')

expect(datastore.has(dhtKey)).to.be.true('did not cache record locally')
})
Expand All @@ -129,8 +127,8 @@ describe('resolve', () => {
const routingKey = peerIdToRoutingKey(peerId)
const dhtKey = new Key('/dht/record/' + uint8ArrayToString(routingKey, 'base32'), false)

const marshalledRecordA = marshal(await create(peerId, uint8ArrayFromString(`/ipfs/${cid.toString()}`), 0n, 60000))
const marshalledRecordB = marshal(await create(peerId, uint8ArrayFromString(`/ipfs/${cid.toString()}`), 10n, 60000))
const marshalledRecordA = marshal(await create(peerId, cid, 0n, 60000))
const marshalledRecordB = marshal(await create(peerId, cid, 10n, 60000))

// records should not match
expect(marshalledRecordA).to.not.equalBytes(marshalledRecordB)
Expand All @@ -140,7 +138,7 @@ describe('resolve', () => {
routing.get.withArgs(routingKey).resolves(marshalledRecordB)

const result = await name.resolve(peerId)
expect(result.toString()).to.equal(cid.toString(), 'incorrect record resolved')
expect(result.toString()).to.equal(cid.toV1().toString(), 'incorrect record resolved')

const cached = await datastore.get(dhtKey)
const record = Libp2pRecord.deserialize(cached)
Expand Down