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
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 committed Jan 18, 2024
1 parent 19bf9ce commit 702a06c
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": "^14.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()
}

Check warning on line 48 in packages/interop/src/ipns-http.spec.ts

View check run for this annotation

Codecov / codecov/patch

packages/interop/src/ipns-http.spec.ts#L46-L48

Added lines #L46 - L48 were not covered by tests

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

Check warning on line 460 in packages/ipns/src/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/ipns/src/index.ts#L457-L460

Added lines #L457 - L460 were not covered by tests
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 702a06c

Please sign in to comment.