Skip to content
This repository has been archived by the owner on Jun 26, 2023. It is now read-only.

feat: interface pubsub #60

Merged
merged 18 commits into from
Aug 25, 2020
Merged

feat: interface pubsub #60

merged 18 commits into from
Aug 25, 2020

Conversation

vasco-santos
Copy link
Member

@vasco-santos vasco-santos commented Aug 16, 2020

This PR adds pubsub-interface, which will replace libp2p/js-libp2p-pubsub per #53 . This is also an important work in the context of dropping the pubsub abstraction layer in js-libp2p per libp2p/js-libp2p-pubsub#68

The interface is closely in line with the base pubsub spec and also got some inspiration from go-libp2p-pubsub during the gossipsub refactor in preparation for 1.1.

This PR includes:

  • move libp2p-pubsub content into src/pubsub and test/pubsub.
  • refactor of libp2p-pubsub codebase regarding constructor and to better track streams
  • test suite that pubsub routers should use for testing

TODO:

  • routers test suite
  • final refactor on pending libp2p-gossipsub
  • base implementation test suite refactor
  • improve docs on proper usage, what to override and what not to override

Unblocks:

@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch 5 times, most recently from db97b73 to b8cca2d Compare August 17, 2020 09:40
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch 3 times, most recently from 603aea4 to 1093890 Compare August 17, 2020 17:12
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch from 1093890 to 7d5b3c4 Compare August 17, 2020 17:25
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch 10 times, most recently from 7c975af to fe6329f Compare August 18, 2020 15:58
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch from fe6329f to d8671f9 Compare August 18, 2020 16:46
@vasco-santos
Copy link
Member Author

vasco-santos commented Aug 18, 2020

@wemeetagain can you give a pass through this PR?
It is gigantic, but it is mostly for moving pieces around, both from libp2p-pubsub and your refactor from gossipsub. The main changes I needed to do are in the constructor, subscription handlers and some improvements on the coordination of overrides.

I did these changes in parallel with the routers update libp2p/js-libp2p-floodsub#108 and ChainSafe/js-libp2p-gossipsub#122 which are successfully using this code now

The main things left for me to finish tomorrow are improving the docs for usage and the refactor of the tests to include more than we had in libp2p-pubsub

* @param {InMessage} msg
* @returns {void}
*/
_publishFrom (msg) {
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 need to remove this according to the gossipsub last PR merged!

| Name | Type | Description |
|------|------|-------------|
| topics | `Array<string>|string` | set of pubsub topics |
| [handler] | `function (msg)` | handler for messages received in the given topics |
Copy link
Member

Choose a reason for hiding this comment

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

I think the interface should only allow subscription/unsubscription of a single topic at a time.

Attaching a handler to multiple topics at once feels like an anti-pattern, or at least a less primary interface, in the same way we were previously attaching multiple topics to multiple messages at once in publish.

I think this will simplify some of the underlying implementations, too. The body of our subscription code looks roughly like: for (const topic of topics) { /* doSubscribe(topic) */ }. We would be able to remove this outer loop and better support extended behavior.

Also, go-libp2p-pubsub's interface for subscribing is for a single topic at a time, for what its worth.

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 agree that the API would look better with one topic. Taking into account that we are doing several breaking changes in pubsub, I think we should also change this.

The libp2p API doc also says a single topic, so we were supporting it below in the stack but we were not mentioning it. However, this should be pointed in the migration guide

Copy link
Member Author

Choose a reason for hiding this comment

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

What about publish? Should we also just accept one topic per API call? That's how we have it in libp2p main docs as well

Copy link
Member

Choose a reason for hiding this comment

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

👍 for publish also accepting a single topic per API call

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 update it

* @returns {Uint8Array}
*/
_encodeRpc (rpc) {
throw errcode(new Error('_encodeRpc must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED')
Copy link
Member

Choose a reason for hiding this comment

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

In the same way that _acceptFrom returns a default implementation, we should consider providing default encoding/decoding using the base protobuf.

seqno: utils.randomSeqno(),
topicIDs: topics
}

Copy link
Member

Choose a reason for hiding this comment

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

pls copy the latest edits from gossipsub here, so we can have the signature already attached in _publish

Copy link
Member

@wemeetagain wemeetagain Aug 19, 2020

Choose a reason for hiding this comment

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

It looks like this wasn't copied exactly, there was a reason it was as it was before:

  • we want to emit the message object with the optional signature (since other messages will be emitted with the signature and we shouldn't be treating self-published messages differently)
  • _publish currently expects an "InMessage" message transformed using utils.normalizeInRpcMessage. It should be standardized bc this function is called in different contexts: self-publish and peer-publish. Also, all overridable methods that operate on messages (_publish, _processRpcMessage, etc) receive an "InMessage". It's nice to rely on that and not require overriders to call 'utils.normalizeInRpcMessage'.

Copy link
Member Author

Choose a reason for hiding this comment

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

We had a test before stating: "should emit non normalized messages on publish" and that's why I put it like this.

I will revisit this in a bit

Copy link
Member

@wemeetagain wemeetagain Aug 19, 2020

Choose a reason for hiding this comment

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

Yeah, the code is a little screwy, I think a proper solution would be to allow an InMessage (plus private key) to be signed (and have signature/key attached).

What we were doing to get around all this was:

  • create msg: InMessage
  • normalize/sign outMsg = await this.buildMessage(msg)
  • set msg to the non-normalized msg, but including the signature (msg = utils.normalizeInRpcMsg(outMsg))
  • then emit msg
  • _publish(msg)

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 guess we can emit it normalized and signed for now. I don't see any reason against it. I changed to what you had before for now

* @param {function} [handler]
* @returns {void}
*/
subscribe (topics, handler) {
Copy link
Member

Choose a reason for hiding this comment

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

Previously, we had this broken into two levels subscribe and _subscribe.
Perhaps this can be better named, eg: subscribe and _newSubscription (or some other).
But the outer layer, subscribe normalized input and removed existing subscriptions, so that _newSubscription was able to be extended in a way that didn't need to renormalize + remove existing subscriptions, but rather acted on known new subscriptions.
Eg: In gossipsub, we want to call join only on new subscriptions, so overriding just using this method becomes somewhat more cumbersome, than overriding a lower level method for "subscribing to new, previously unsubscribed topic".

Copy link
Member Author

@vasco-santos vasco-santos Aug 19, 2020

Choose a reason for hiding this comment

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

I went with not decoupling it because all these operations seem mandatory to do. For the gossipsub example, I changed it to:

subscribe (topics: string[], handler: (msg: any) => void): void {
    super.subscribe(topics, handler)
    this.join(topics)
  }

We should only be extending its behaviour, not changing what it currently is implementing it.
Any reason you foresee to not simplify this?

Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine, especially considering we'll only be operating on a single topic.

@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch from e266b17 to 3173755 Compare August 19, 2020 08:13
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch from e905864 to ec5ae25 Compare August 19, 2020 16:19
@vasco-santos vasco-santos marked this pull request as ready for review August 19, 2020 17:02
@vasco-santos vasco-santos requested a review from jacobheun August 19, 2020 17:03
Copy link
Member

@wemeetagain wemeetagain left a comment

Choose a reason for hiding this comment

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

It would be nice for typescript router implementers to have a typescript interface

@vasco-santos
Copy link
Member Author

It would be nice for typescript router implementers to have a typescript interface

Ideally, we should move forward with the support via js doc + aegir soon

Copy link
Contributor

@jacobheun jacobheun left a comment

Choose a reason for hiding this comment

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

I still need to run through the tests but adding my initial comments on the source for now.

also, test/pubsub/utils/emit-self.spec.js is an empty file.

package.json Outdated Show resolved Hide resolved
src/pubsub/README.md Outdated Show resolved Hide resolved
src/pubsub/utils.js Outdated Show resolved Hide resolved
src/pubsub/utils.js Outdated Show resolved Hide resolved
const baseMessage = { ...message }
delete baseMessage.signature
delete baseMessage.key
if (typeof baseMessage.from === 'string') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't this always be a string as this takes an InMessage? The message is run through the normalizeIn function by this point. If this is not a string there's an issue.

src/pubsub/message/sign.js Outdated Show resolved Hide resolved
this.log('received message we didn\'t subscribe to. Dropping.')
return
}
const msg = utils.normalizeInRpcMessage(message, PeerId.createFromB58String(idB58Str))
Copy link
Contributor

Choose a reason for hiding this comment

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

The PeerId passed to normalizeInRpcMessage is converted to its string form for use, and I only see this called with a PeerId here. Perhaps this should just change to taking and using the string to avoid the double conversion.

src/pubsub/index.js Outdated Show resolved Hide resolved
src/pubsub/index.js Show resolved Hide resolved
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch from 9c7dd34 to bc8ed8b Compare August 21, 2020 09:10
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch from bc8ed8b to d5ffc49 Compare August 21, 2020 09:48
@vasco-santos
Copy link
Member Author

vasco-santos commented Aug 21, 2020

also, test/pubsub/utils/emit-self.spec.js is an empty file.

yeah lol, I created it inside utils by mistake and it moved away from my radar. Just added the base tests for it and the review is addressed

src/pubsub/utils.js Outdated Show resolved Hide resolved
Copy link
Contributor

@jacobheun jacobheun left a comment

Choose a reason for hiding this comment

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

Just a few minor things left

src/pubsub/tests/multiple-nodes.js Show resolved Hide resolved
test/pubsub/sign.spec.js Outdated Show resolved Hide resolved
test/pubsub/topic-validators.spec.js Outdated Show resolved Hide resolved
@vasco-santos vasco-santos force-pushed the feat/interface-pubsub branch from 00104ec to 2152105 Compare August 24, 2020 13:27
Copy link
Contributor

@jacobheun jacobheun left a comment

Choose a reason for hiding this comment

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

LGTM

@jacobheun jacobheun merged commit ba15a48 into master Aug 25, 2020
@jacobheun jacobheun deleted the feat/interface-pubsub branch August 25, 2020 11:06
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.

3 participants