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

Websockets Support #49

Merged
merged 7 commits into from
Jan 15, 2018
Merged

Websockets Support #49

merged 7 commits into from
Jan 15, 2018

Conversation

benjamincburns
Copy link
Contributor

@benjamincburns benjamincburns commented Jan 8, 2018

This PR supersedes #14, as it is based on those changes. Thanks heaps to @perissology for his submission!

How this all works:

Subscription creation and notification:

  • In Add subscription subprovider to handle websocket pub/sub MetaMask/web3-provider-engine#207, I created a SubscriptionSubprovider which extends the existing FilterSubprovider
  • When the SubscriptionSubprovider gets an eth_subscribe request, it uses the underlying FilterSubprovider code to create a Filter (i.e. LogFilter, BlockFilter, or PendingBlockFilter) for the subscription, then returns the id of this filter.
  • I refactored the LogFilter, BlockFilter and PendingBlockFilter types to emit a data event on update
  • When SubscriptionSubprovider creates a filter, it subscribes to the filter's data event, and and emits its own data event when the Filter's event fires, clearing pending filter changes in the process.
  • Similarly, I refactored our Provider type to be an EventEmitter and to proxy data events from the SubscriptionSubprovider.

Routing notifications to clients:

  • As one might expect, we listen on a WebSocket, and proxy RPC calls over to the existing Provider type.
  • The component responsible for this proxying is the ConnectionManager.
  • The ConnectionManager snoops on the traffic it's proxying to maintain a forward and reverse mapping of subscription ID to websocket connection.
  • The ConnectionManager listens to data events from the Provider, and uses its mapping of subscription ID to connection to route the subscription notifications to the correct websocket connection.
  • When an eth_unsubscribe request is handled, ConnectionManager cleans up the relevant mapping.
  • When a client disconnects, ConnectionManager cleans up all mappings pertaining to that client.

TODO:

  • Fix intermittent forking test failure
  • Fix block tracker to avoid polling
  • Rebase on current develop
  • Squash a bunch of noisy commits (especially the one where I misspelled "concurrency")

@benjamincburns benjamincburns force-pushed the websockets-pr-redux branch 5 times, most recently from d758778 to 2dc89a1 Compare January 12, 2018 13:02
@benjamincburns benjamincburns changed the title WIP: Websockets Redux Websockets Support Jan 12, 2018
This was referenced Jan 12, 2018
Copy link
Contributor

@gnidan gnidan left a comment

Choose a reason for hiding this comment

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

Hey @benjamincburns - couple comments / thoughts from a first read.

Looks great! Lots of bits and pieces moving around, but generally seems pretty intuitive.

Hat tip 👒 to @perissology for getting this started!

self.emit('block', block);
}
callback(err, result);
});
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 risk of blocks being emitted out of order? Would that matter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There shouldn't be. pushBlock is only called when processBlock wants to commit a new block to the chain, when the genesis block is created, or on start with an existing db when latest is read from that db. None of those things should be possible to order incorrectly.


if (options.ws) {
wss = webSocketServer(server, provider, logger);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this get closed when the httpServer closes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought so at first as well, but after testing, I think this is the correct way to do it. webSocketServer has a reference to the httpServer, meaning it listens on the same socket, and its connections are tracked/managed by the httpServer anyway.

@@ -342,13 +336,12 @@ StateManager.prototype.queueTransaction = function(method, tx_params, callback)
self.action_queue.push({
method: method,
from: tx_params.from,
tx: tx,
tx: rawTx,
callback: callback
});

// We know there's work, so get started.
self.processNextAction();
Copy link
Contributor

Choose a reason for hiding this comment

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

Indent (L322-344)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch

@@ -319,12 +319,6 @@ StateManager.prototype.queueTransaction = function(method, tx_params, callback)
// Get the nonce for this address, taking account any transactions already queued.
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment is out of date if you're moving nonce stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch


var tx_hash = to.hex(tx.hash());
let tx = new FakeTransaction(rawTx);
Copy link
Contributor

Choose a reason for hiding this comment

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

Possible suggestion: this getQueuedNonce logic is now repeated 3 times - maybe worth abstracting into a prepareTx function, or somesuch. Might make processCall/processGasEstimate/processTransaction a lot easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

definitely.

self.connections[ connection.id ] = {
connection: connection,
subscriptions: [],
subscriptionCounter: 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this is unused

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll double check, I think you're correct. I think it's likely hold over from the initial set of refactored changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't get to this yesterday. Will fix this now.

after('shutdown', function(done) {
let provider = web3._provider;
web3.setProvider();
provider.close(done);
Copy link
Contributor

Choose a reason for hiding this comment

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

What does web3.setProvider() do when not passing in a provider as a parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

from memory it sets the provider to undefined (it doesn't check the arg). I think I was being overly pedantic here though. My thought process was that web3 has a bunch of stuff it does in the background, and I didn't want it trying to run a request during or after provider close.

Further, I think calling provider.close is a good idea in general when using the direct provider (probably need to make sure other tests do this), but unfortunately it's not a standard part of the interface, so I need to resort to something like provider.connection.close for the WebSocket provider. Maybe I'll raise an issue for that.

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.

4 participants