Skip to content
This repository has been archived by the owner on Mar 10, 2020. It is now read-only.

feat: ipns working locally #327

Merged
merged 5 commits into from
Aug 10, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exports.files = require('./files')
exports.key = require('./key')
exports.ls = require('./ls')
exports.miscellaneous = require('./miscellaneous')
exports.name = require('./name')
exports.object = require('./object')
exports.pin = require('./pin')
exports.ping = require('./ping')
Expand Down
9 changes: 9 additions & 0 deletions js/src/name/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'
const { createSuite } = require('../utils/suite')

const tests = {
publish: require('./publish'),
resolve: require('./resolve')
}

module.exports = createSuite(tests)
103 changes: 103 additions & 0 deletions js/src/name/publish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* eslint-env mocha */
'use strict'

const hat = require('hat')

const { fixture } = require('./utils')
const { spawnNodeWithId } = require('../utils/spawn')
const { getDescribe, getIt, expect } = require('../utils/mocha')

module.exports = (createCommon, options) => {
const describe = getDescribe(options)
const it = getIt(options)
const common = createCommon()

describe('.name.publish', function () {
const keyName = hat()
let ipfs
let nodeId

before(function (done) {
// CI takes longer to instantiate the daemon, so we need to increase the
// timeout for the before step
this.timeout(60 * 1000)

common.setup((err, factory) => {
expect(err).to.not.exist()

spawnNodeWithId(factory, (err, node) => {
expect(err).to.not.exist()

ipfs = node
nodeId = node.peerId.id

ipfs.files.add(fixture.data, { pin: false }, done)
})
})
})

after((done) => common.teardown(done))

it('should publish an IPNS record with the default params', (done) => {
this.timeout(50 * 1000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies I've just noticed that a bunch of these tests are using this.timeout in an arrow function - you'll need to change it to function (done) {/*...*/} for it to be referring to the correct this (otherwise you're referring to this from the describe block)


const value = fixture.cid

ipfs.name.publish(value, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()
expect(res.Name).to.equal(nodeId)
expect(res.Value).to.equal(`/ipfs/${value}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTTP API in js-ipfs sends Capitalised property names to maintain compatibility with the go-ipfs HTTP API.

js-ipfs core should respond with camelCase property names and the js-ipfs HTTP API should convert them to Capitalised form e.g.

https://github.com/ipfs/js-ipfs/blob/f4344b0e397639df6f26d904be9a015b763e4338/src/http/api/resources/files.js#L273-L277

js-ipfs-api then takes the response from the HTTP API and converts to camelCase. e.g.

https://github.com/ipfs/js-ipfs-api/blob/8536ee42ba59185b71723ae1abde4b722e9fa254/src/utils/file-result-stream-converter.js#L37-L39

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! And also updated on js-ipfs#1400


done()
})
})

it('should publish correctly when the file was not added but resolve is disabled', (done) => {
this.timeout(50 * 1000)

const value = 'QmPFVLPmp9zv5Z5KUqLhe2EivAGccQW2r7M7jhVJGLZoZU'

const options = {
resolve: false,
lifetime: '1m',
ttl: '10s',
key: 'self'
}

ipfs.name.publish(value, options, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()
expect(res.Name).to.equal(nodeId)
expect(res.Value).to.equal(`/ipfs/${value}`)

done()
})
})

it('should recursively resolve to an IPFS hash', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test might need to be renamed or rewritten. Was the intention to ensure that publishing /ipfs/rootHash/path/to/file will actually result in /ipfs/fileHash being published as the value? There doesn't seem to be anything recursive going on here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recursive: Resolve until the result is not an IPNS name. Default: “false”. Required: no. (according to the docs at https://ipfs.io/docs/api/#apiv0namepublish)

This test aims to do the following:

  1. Publish an ipfs path with the ID of the peer as the key
    Result: /ipns/{PeerID} => /ipfs/{CID}

  2. Publish the previously ipns path with the ID of a new key created
    Result: /ipns/{KeyID} => /ipns/{PeerID}

  3. Resolve the last recorder IPNS entry recursively:
    Result: /ipfs/{CID}

In this test, the resolve happens recursively until the stop condition (obtaining an ipfs path or reaching a maximum number of tries constant). This way, the first resolve of /ipns/{KeyID} will result in /ipns/{PeerID}, which is not an ipfs path. Accordingly, the resolve continues, and resolving /ipns/{PeerID} results in /ipfs/{CID}. As we got an ipfs path, this is returned.

Maybe the test description is not the most understandable, but for the people who know what the recursive parameter aims to, will understand the logic. If you have any suggestion for renaming it, I will happily change it 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong test name in fact, this was for name/resolve

this.timeout(90 * 1000)

const value = fixture.cid
const options = {
resolve: false,
lifetime: '24h',
ttl: '10s',
key: keyName
}

ipfs.key.gen(keyName, { type: 'rsa', size: 2048 }, (err, key) => {
expect(err).to.not.exist()

ipfs.name.publish(value, options, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()
expect(res.Name).to.equal(key.id)
expect(res.Value).to.equal(`/ipfs/${value}`)

done()
})
})
})
})
}
135 changes: 135 additions & 0 deletions js/src/name/resolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* eslint max-nested-callbacks: ["error", 6] */
/* eslint-env mocha */
'use strict'

const hat = require('hat')

const { fixture } = require('./utils')
const { spawnNodeWithId } = require('../utils/spawn')
const { getDescribe, getIt, expect } = require('../utils/mocha')

module.exports = (createCommon, options) => {
const describe = getDescribe(options)
const it = getIt(options)
const common = createCommon()

describe('.name.resolve', function () {
const keyName = hat()
let ipfs
let nodeId
let keyId

before(function (done) {
// CI takes longer to instantiate the daemon, so we need to increase the
// timeout for the before step
this.timeout(60 * 1000)

common.setup((err, factory) => {
expect(err).to.not.exist()

spawnNodeWithId(factory, (err, node) => {
expect(err).to.not.exist()

ipfs = node
nodeId = node.peerId.id

ipfs.files.add(fixture.data, { pin: false }, done)
})
})
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comments for the before block in name.publish - I think they mostly all apply here too.


after((done) => common.teardown(done))

it('should resolve a record with the default params after a publish', (done) => {
this.timeout(50 * 1000)

const value = fixture.cid

ipfs.name.publish(value, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()

ipfs.name.resolve(nodeId, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()
expect(res.Path).to.equal(`/ipfs/${value}`)

done()
})
})
})

it('should not get the entry if its validity time expired', (done) => {
this.timeout(50 * 1000)

const value = fixture.cid
const publishOptions = {
resolve: true,
lifetime: '1ms',
ttl: '10s',
key: 'self'
}

ipfs.name.publish(value, publishOptions, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()

setTimeout(function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required? There might be a bug if we can't immediately resolve a value after publishing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setTimeout here aims to guarantee that the record has an expired validity. This way, when we try to resolve it, its validity has expired and we will receive that error, instead of the value.

ipfs.name.resolve(nodeId, (err, res) => {
expect(err).to.exist()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an error message we can assert on here (would have to be the same for go-ipfs and js-ipfs)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I am using the same message as go-ipfs and I will add an assertion for it.

expect(res).to.not.exist()

done()
})
}, 1)
})
})

it('should recursively resolve to an IPFS hash', (done) => {
this.timeout(100 * 1000)

const value = fixture.cid
const publishOptions = {
resolve: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be false for this test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be the same having it enabled or disabled. In fact, resolve is true by default. Accordingly, when a name publish is performed, it is verified that the ipfs path / ipns path that we want to publish already was added. Otherwise, we may create a ipns path for a unavailable file.

However, as I have other tests with resolve: true I will switch it for false here, which will result in decreasing the time of the test.

lifetime: '24h',
ttl: '10s',
key: 'self'
}

// Generate new key
ipfs.key.gen(keyName, { type: 'rsa', size: 2048 }, (err, key) => {
expect(err).to.not.exist()

keyId = key.id

// publish ipfs
ipfs.name.publish(value, publishOptions, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()

publishOptions.key = keyName

// publish ipns with the generated key
ipfs.name.publish(`/ipns/${nodeId}`, publishOptions, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()

const resolveOptions = {
nocache: false,
recursive: true
}

// recursive resolve (will get ipns first, and will resolve again to find the ipfs)
ipfs.name.resolve(keyId, resolveOptions, (err, res) => {
expect(err).to.not.exist()
expect(res).to.exist()
expect(res.Path).to.equal(`/ipfs/${value}`)

done()
})
})
})
})
})
})
}
8 changes: 8 additions & 0 deletions js/src/name/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict'

const loadFixture = require('aegir/fixtures')

exports.fixture = Object.freeze({
data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core'),
cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP'
})