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

fix: update ipns module to v9 and fix double verification of records #396

Merged
merged 1 commit into from
Jan 18, 2024
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
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 @@
}

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