Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

feat: ipns locally #1400

Merged
merged 34 commits into from
Aug 29, 2018
Merged

feat: ipns locally #1400

merged 34 commits into from
Aug 29, 2018

Conversation

vasco-santos
Copy link
Member

@vasco-santos vasco-santos commented Jun 19, 2018

A working version of IPNS working locally is here! 🚀 😎 💪

Steps:

  • Create a new repo (js-ipns) like it was made with go in the last week (go-ipns) and port the related code from this PR to there
  • Resolve IPNS names in publish, in order to verify if they exist (as it is being done for regular files) before being published
  • Handle remaining parameters in publish and resolve (except ttl).
  • Test interface core spec interface-ipfs-core#327
  • Test interoperability with go. interop#26
  • Integrate logging
  • Write unit tests
  • Add support for the lifetime with nanoseconds precision
  • Add Cache
  • Add initializeKeyspace
  • Republish

Some notes, regarding the previous steps:

  • There is an optional parameter not implemented in this PR, which is ttl, since it is still experimental, we can add it in a separate PR.

Finally, thanks @Stebalien for your help and time answering all my questions regarding the IPNS implementation in GO.

Moreover, since there are no specs, and not that much documentation, I have been writing a document with all the IPNS workflow. It is a WIP by now, but it is currently available here.

Related PRs:

return ipnsEntryProto.decode(marsheled)
},
validityType: ipnsEntryProto.ValidityType
}
Copy link
Member

Choose a reason for hiding this comment

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

module.exports = {
	create,
	marshal : ipnsEntryProto.encode,
	unmarshal : ipnsEntryProto.decode,
	validityType: ipnsEntryProto.ValidityType
}

const human = require('human-to-milliseconds')
const path = require('../ipns/path')

const OFFLINE_ERROR = require('../utils').OFFLINE_ERROR
Copy link
Member

Choose a reason for hiding this comment

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

const { OFFLINE_ERROR } = require('../utils')

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I think it's better to:

const ERRORS = require('../utils')

so when you use it bellow is more meaningful:

return callback(new Error(ERROR.OFFLINE_ERROR))

Copy link
Member Author

Choose a reason for hiding this comment

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

It is really strange to me that the errors are in a utils.js file, making those imports really strange. In the future, all errors should be easily accessible from all the project, and we should access them in the same way as @fsdiogo suggested.

@@ -0,0 +1,54 @@
'use strict'

const peerId = require('peer-id')
Copy link
Member

Choose a reason for hiding this comment

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

const { createFromPrivKey } = require('peer-id')

}

// Validate EOL
const validityDate = Date.parse(validity.toString())
Copy link
Member

Choose a reason for hiding this comment

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

consider using https://date-fns.org/

Copy link
Member

Choose a reason for hiding this comment

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

FYI, the only valid time format is RFC3339 with nanosecond precision. That is:

2006-01-02T15:04:05.999999999Z07:00

Copy link
Member

Choose a reason for hiding this comment

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

Note: It's extremely important that you only except strings of this exact form. Not validating this has unfortunate security implications.

This also needs to check the validity type.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will support milliseconds precision at first here and I added a task to improve the precision to nanosecond later (in the list above).

Basically, JS does not support nanoseconds precision natively and all the libs that I managed to find don't support summing date times with nanosecond precision.

Copy link
Member Author

Choose a reason for hiding this comment

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

When you say that I must validate if the string has this exact form, you say that it should be compliant with RFC3339, right?

const human = require('human-to-milliseconds')
const path = require('../ipns/path')

const OFFLINE_ERROR = require('../utils').OFFLINE_ERROR
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I think it's better to:

const ERRORS = require('../utils')

so when you use it bellow is more meaningful:

return callback(new Error(ERROR.OFFLINE_ERROR))


// Parse ipfs path value
try {
value = path.parsePath(value)
Copy link
Member

Choose a reason for hiding this comment

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

Note: the value can be any arbitrary path.

}

// https://github.com/ipfs/go-ipfs-routing/blob/master/offline/offline.go
resolveLocal (name, publicKey, callback) {
Copy link
Member

Choose a reason for hiding this comment

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

Eventually, this should be pushed into a non-IPNS specific routing layer.

Copy link
Member Author

Choose a reason for hiding this comment

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

I had been thinking about the go-ipfs implementation for the resolver.

For one side, I agree with your approach. That is, you decide which router the IPNS resolver should use, and this router is completely transparent for the IPNS resolver. However, as we are fetching from an offline and local source (datastore), it also seems strange to me to have the OfflineRouter.

Anyway, I am not sure about where this code should live on. @diasdavid any opinion here?

Copy link
Member

Choose a reason for hiding this comment

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

Basically, it means that we can swap out the router at will. However, given the fact that you're only implementing offline routing, it doesn't really make a difference.

Copy link
Member Author

Choose a reason for hiding this comment

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

Being a pluggable router is a considerable advantage. But yeah, that's what I thought while putting the resolveLocal in there.

However, when I integrate the routing I will have to understand if it still makes sense to have the resolveLocal inside the IPNS layer.

}

// Validate EOL
const validityDate = Date.parse(validity.toString())
Copy link
Member

Choose a reason for hiding this comment

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

Note: It's extremely important that you only except strings of this exact form. Not validating this has unfortunate security implications.

This also needs to check the validity type.

@alanshaw alanshaw changed the title WIP: ipns locally [WIP] feat: ipns locally Jun 21, 2018
@@ -33,10 +35,12 @@ const verify = (publicKey, entry, callback) => {
}

// Validate EOL
const validityDate = Date.parse(validity.toString())
if (validityType === IpnsEntry.validityType.EOL) {
Copy link
Member

Choose a reason for hiding this comment

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

We should fail by default if we can't validate the record.

Copy link
Member Author

Choose a reason for hiding this comment

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

already done in js-ipns, more precisely here

@@ -33,10 +35,12 @@ const verify = (publicKey, entry, callback) => {
}

// Validate EOL
const validityDate = Date.parse(validity.toString())
if (validityType === IpnsEntry.validityType.EOL) {
const validityDate = Date.parse(validity.toString())
Copy link
Member

Choose a reason for hiding this comment

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

So it doesn't get lost in the buried review comments, this needs to explicitly check the date format.

Copy link
Member Author

Choose a reason for hiding this comment

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

Once js-ipns initial implementation PR is merged, I will create an issue in there for tackling this with priority. I have been thinking a lot earlier and reading some stuff about bigint for js and I think that I reached a way to add the nanoseconds precision here. I also need to find a way to explicitly check the date format according to the RFC 3339

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't feel comfortable merging this until this is fixed. Unfortunately, due to some mistakes in how we make signatures in IPNS, we rely on precisely validating the date for security.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Stebalien just added it to js-ipns PR!

@daviddias
Copy link
Member

@Stebalien why go-ipns and not go-libp2p-ipns? Did I miss any important convo?

@Stebalien
Copy link
Member

why go-ipns and not go-libp2p-ipns? Did I miss any important convo?

As far as I know, IPNS falls under the umbrella of IPFS, not libp2p.

@vasco-santos
Copy link
Member Author

vasco-santos commented Jun 22, 2018

why go-ipns and not go-libp2p-ipns? Did I miss any important convo?

As far as I know, IPNS falls under the umbrella of IPFS, not libp2p.

I see IPNS as a different piece than IPFS and libp2p. But as we don't have an IPNS umbrella, I don't know which would suit best here.

By the way @Stebalien and @diasdavid , if one day we decide to completely move IPNS (publish, resolve) to a new repo, this repo should be called js-ipns / go-ipns. This way, I think the current go-ipns and js-ipns should be ipns-record. What do you think?

@daviddias
Copy link
Member

daviddias commented Jun 22, 2018

I know that there is no spec, but IPNS has been libp2p all along -- https://github.com/libp2p/specs/blob/master/4-architecture.md#46-naming

Anyway. @vasco-santos, don't forget about the interop tests! You should be able to publish stuff with a go daemon and then read it from the repo with a js-ipfs daemon.

@vasco-santos
Copy link
Member Author

@diasdavid I will change to the libp2porganization then. Do you think it should be js-libp2p-ipns or js-libp2p-ipns-record? I think that the second one suits better because this module does not have the IPNS core code (publish and resolve), but utilities for creating, parsing, and validating IPNS records, which are used for the publish and resolve. The go-ipfs implementation has the IPNS core code, but I think that we could have the js-libp2p-ipns with the core implementation of publish and resolve. Unless @Stebalien have other ideas in here.

Yeah @diasdavid ! I have been testing everything to guarantee the interoperability between both. There are two fields which I am not sure right now, which are the record signature and the validity.
The signature is really difficult to test now, since it is stored in bytes, and I believe that I don't have a clean way to test this, while not having IPNS routing (I tried to copy the go-ipfs repo content and use it in js for validating the signature, but it seems that the repos are not compatible). Anyway, I have the interoperability task written in the first comment of this PR and once the PR initial implementation of the "js-libp2p-ipns-record" gets merged, I will create an issue to confirm the interoperabillity in that repo.

@daviddias
Copy link
Member

@vasco-santos mind doing a drawing of the IPNS architecture first as you see it so that we clearly sync what are the pieces and their respective names?

@vasco-santos
Copy link
Member Author

vasco-santos commented Jun 22, 2018

Here I have a small diagram with the interactions that I was describing. Or do you want a more low-level diagram with something like "how ipns works?"

ipns 2x 1

@Stebalien
Copy link
Member

I know that there is no spec, but IPNS has been libp2p all along

That's a bit odd. I guess one could, e.g., make an IPNS record point to a multiaddr but I still don't feel like it fits in libp2p. It really does feel like a separate thing. I can move it, I just don't want to shuffle things back and forth.


As for lang-ipns versus lang-ipns-record: ipfs/go-ipns#1

However, I held off on that as IPNS is record-store independent. That is, I'd almost expect:

  • {go,js}-ipns-interface: the publisher/resolver interfaces.
  • {go,js}-ipns-value-store: an implementation of the publisher/resolver interfaces that, internally, uses a valuestore (or a record store, depending on what you want to call it).
  • {go,js}-ipns-record: the actual IPNS record logic (this package).

@vasco-santos
Copy link
Member Author

@Stebalien I would go with that package naming proposal!

@daviddias
Copy link
Member

My claim (and others) is that IPNS is just a specialized version of IPRS and that naming is essential piece to Networking and therefore it should be part of libp2p primitives. But you know what, I'll let you paint the bikeshed the color you want as long as we keep making progress here and we having IPNS working locally, over PubSub and DHT as soon as possible :)

Copy link
Member

@alanshaw alanshaw left a comment

Choose a reason for hiding this comment

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

All green apart from commitlint! 💚

@alanshaw alanshaw merged commit 1110e96 into master Aug 29, 2018
@ghost ghost removed the status/in-progress In progress label Aug 29, 2018
@alanshaw alanshaw deleted the feat/ipns-locally branch August 29, 2018 17:02
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants