Skip to content

Commit

Permalink
fix: update ipns module to v9 and fix double verification of records (#…
Browse files Browse the repository at this point in the history
…396)

Updates IPNS module to version that is capable of round-tripping records
created with non-nano-second validity times.

Updates `@helia/delegated-routing-v1-http-api-client` to version that
can skip verification of incoming records.

Fixes #394 and #395
  • Loading branch information
achingbrain authored Jan 18, 2024
1 parent 4943c5b commit f2853f8
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 16 deletions.
4 changes: 2 additions & 2 deletions packages/helia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"@chainsafe/libp2p-noise": "^15.0.0",
"@chainsafe/libp2p-yamux": "^6.0.1",
"@helia/block-brokers": "^1.0.0",
"@helia/delegated-routing-v1-http-api-client": "^2.0.2",
"@helia/delegated-routing-v1-http-api-client": "^3.0.0",
"@helia/interface": "^3.0.1",
"@helia/routers": "^0.0.0",
"@helia/utils": "^0.0.0",
Expand All @@ -83,7 +83,7 @@
"datastore-core": "^9.2.6",
"interface-blockstore": "^5.2.7",
"interface-datastore": "^8.2.9",
"ipns": "^8.0.3",
"ipns": "^9.0.0",
"libp2p": "^1.1.1",
"multiformats": "^13.0.0"
},
Expand Down
7 changes: 7 additions & 0 deletions packages/interface/src/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export interface RoutingOptions extends AbortOptions, ProgressOptions {
* @default true
*/
useCache?: boolean

/**
* Pass `false` to not perform validation
*
* @default true
*/
validate?: boolean
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/interop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@
"@helia/car": "^2.0.1",
"@helia/dag-cbor": "^2.0.1",
"@helia/dag-json": "^2.0.1",
"@helia/http": "^0.9.0",
"@helia/interface": "^3.0.1",
"@helia/ipns": "^4.0.0",
"@helia/json": "^2.0.1",
"@helia/mfs": "^2.0.1",
"@helia/routers": "^0.0.0",
"@helia/strings": "^2.0.1",
"@helia/unixfs": "^2.0.1",
"@ipld/car": "^5.2.5",
Expand Down
8 changes: 8 additions & 0 deletions packages/interop/src/fixtures/create-helia-http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createHeliaHTTP as createHelia, type HeliaHTTPInit } from '@helia/http'
import type { Helia } from '@helia/interface'

export async function createHeliaHTTP (init: Partial<HeliaHTTPInit> = {}): Promise<Helia> {
return createHelia({
...init
})
}
11 changes: 10 additions & 1 deletion packages/interop/src/fixtures/create-kubo.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ export async function createKuboNode (): Promise<Controller> {
Swarm: [
'/ip4/0.0.0.0/tcp/0',
'/ip4/0.0.0.0/tcp/0/ws'
]
],
Gateway: '/ip4/127.0.0.1/tcp/8180'
},
Gateway: {
NoFetch: true,
ExposeRoutingAPI: true,
HTTPHeaders: {
'Access-Control-Allow-Origin': ['*'],
'Access-Control-Allow-Methods': ['GET', 'POST', 'PUT', 'OPTIONS']
}
}
}
},
Expand Down
11 changes: 10 additions & 1 deletion packages/interop/src/fixtures/create-kubo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ export async function createKuboNode (): Promise<Controller> {
Swarm: [
'/ip4/0.0.0.0/tcp/4001',
'/ip4/0.0.0.0/tcp/4002/ws'
]
],
Gateway: '/ip4/127.0.0.1/tcp/8180'
},
Gateway: {
NoFetch: true,
ExposeRoutingAPI: true,
HTTPHeaders: {
'Access-Control-Allow-Origin': ['*'],
'Access-Control-Allow-Methods': ['GET', 'POST', 'PUT', 'OPTIONS']
}
}
}
},
Expand Down
68 changes: 68 additions & 0 deletions packages/interop/src/ipns-http.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* eslint-env mocha */

import { ipns } from '@helia/ipns'
import { delegatedHTTPRouting } from '@helia/routers'
import { peerIdFromString } from '@libp2p/peer-id'
import { expect } from 'aegir/chai'
import { isNode } from 'wherearewe'
import { createHeliaHTTP } from './fixtures/create-helia-http.js'
import { createKuboNode } from './fixtures/create-kubo.js'
import type { Helia } from '@helia/interface'
import type { IPNS } from '@helia/ipns'
import type { Controller } from 'ipfsd-ctl'

describe('@helia/ipns - http', () => {
let helia: Helia
let kubo: Controller
let name: IPNS

/**
* Ensure that for the CID we are going to publish, the resolver has a peer ID that
* is KAD-closer to the routing key so we can predict the the resolver will receive
* the DHT record containing the IPNS record
*/
beforeEach(async () => {
kubo = await createKuboNode()
helia = await createHeliaHTTP({
routers: [
delegatedHTTPRouting('http://127.0.0.1:8180')
]
})
name = ipns(helia)
})

afterEach(async () => {
if (helia != null) {
await helia.stop()
}

if (kubo != null) {
await kubo.stop()
}
})

it('should publish on kubo and resolve on helia', async function () {
if (!isNode) {
// https://github.com/protocol/bifrost-community/issues/4#issuecomment-1898417008
return this.skip()
}

const keyName = 'my-ipns-key'
const { cid } = await kubo.api.add(Uint8Array.from([0, 1, 2, 3, 4]))

await kubo.api.key.gen(keyName, {
// @ts-expect-error the types say upper-case E, Kubo errors unless it's a
// lower case e
type: 'ed25519'
})

const res = await kubo.api.name.publish(cid, {
key: keyName
})

const key = peerIdFromString(res.name)

const resolvedCid = await name.resolve(key)
expect(resolvedCid.toString()).to.equal(cid.toString())
})
})
9 changes: 9 additions & 0 deletions packages/interop/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
{
"path": "../helia"
},
{
"path": "../http"
},
{
"path": "../interface"
},
{
"path": "../ipns"
},
Expand All @@ -32,6 +38,9 @@
{
"path": "../mfs"
},
{
"path": "../routers"
},
{
"path": "../strings"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ipns/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
"dns-packet": "^5.6.0",
"hashlru": "^2.3.0",
"interface-datastore": "^8.2.9",
"ipns": "^8.0.3",
"ipns": "^9.0.0",
"is-ipfs": "^8.0.1",
"multiformats": "^13.0.0",
"p-queue": "^8.0.1",
Expand Down
25 changes: 24 additions & 1 deletion packages/ipns/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,21 +420,44 @@ class DefaultIPNS implements IPNS {
}

const records: Uint8Array[] = []
let foundInvalid = 0

await Promise.all(
routers.map(async (router) => {
let record: Uint8Array

try {
record = await router.get(routingKey, {
...options,
validate: false
})
} catch (err: any) {
if (router === this.localStore && err.code === 'ERR_NOT_FOUND') {
log('did not have record locally')
} else {
log.error('error finding IPNS record', err)
}

return
}

try {
const record = await router.get(routingKey, options)
await ipnsValidator(routingKey, record)

records.push(record)
} catch (err) {
// we found a record, but the validator rejected it
foundInvalid++
log.error('error finding IPNS record', err)
}
})
)

if (records.length === 0) {
if (foundInvalid > 0) {
throw new CodeError(`${foundInvalid > 1 ? `${foundInvalid} records` : 'Record'} found for routing key ${foundInvalid > 1 ? 'were' : 'was'} invalid`, 'ERR_RECORDS_FAILED_VALIDATION')
}

throw new CodeError('Could not find record for routing key', 'ERR_NOT_FOUND')
}

Expand Down
7 changes: 6 additions & 1 deletion packages/ipns/src/routing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ export interface PutOptions extends AbortOptions, ProgressOptions {
}

export interface GetOptions extends AbortOptions, ProgressOptions {

/**
* Pass false to not perform validation actions
*
* @default true
*/
validate?: boolean
}

export interface IPNSRouting {
Expand Down
6 changes: 3 additions & 3 deletions packages/routers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,17 @@
"test:electron-main": "aegir test -t electron-main"
},
"dependencies": {
"@helia/delegated-routing-v1-http-api-client": "^2.0.2",
"@helia/delegated-routing-v1-http-api-client": "^3.0.0",
"@helia/interface": "^3.0.1",
"@libp2p/interface": "^1.1.1",
"@libp2p/peer-id": "^4.0.5",
"ipns": "^8.0.3",
"ipns": "^9.0.0",
"it-first": "^3.0.4",
"it-map": "^3.0.5",
"multiformats": "^13.0.0",
"uint8arrays": "^5.0.1"
},
"devDependencies": {
"@libp2p/peer-id": "^4.0.5",
"@libp2p/peer-id-factory": "^4.0.5",
"aegir": "^42.1.0",
"it-drain": "^3.0.5",
Expand Down
7 changes: 1 addition & 6 deletions packages/routers/src/delegated-http-routing.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createDelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
import { CodeError } from '@libp2p/interface'
import { peerIdFromBytes } from '@libp2p/peer-id'
import { marshal, unmarshal } from 'ipns'
import { marshal, unmarshal, peerIdFromRoutingKey } from 'ipns'
import first from 'it-first'
import map from 'it-map'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
Expand All @@ -17,10 +16,6 @@ function isIPNSKey (key: Uint8Array): boolean {
return uint8ArrayEquals(key.subarray(0, IPNS_PREFIX.byteLength), IPNS_PREFIX)
}

const peerIdFromRoutingKey = (key: Uint8Array): PeerId => {
return peerIdFromBytes(key.slice(IPNS_PREFIX.length))
}

class DelegatedHTTPRouter implements Routing {
private readonly client: DelegatedRoutingV1HttpApiClient

Expand Down

0 comments on commit f2853f8

Please sign in to comment.