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

rpc: Upgrade WebSocketClient to support full client functionality #646

Merged
merged 21 commits into from
Nov 26, 2020

Conversation

thanethomson
Copy link
Member

@thanethomson thanethomson commented Oct 20, 2020

In partial fulfillment of #582.

This PR expands the WebSocketClient's capabilities to be able to handle other types of requests other than just subscriptions (i.e. it implements the Client trait).

It also drastically simplifies some of the internal code for the RPC crate.

📖 Rendered updated ADR (008)

  • Referenced an issue explaining the need for the change
  • Updated all relevant documentation in docs
  • Updated all code comments where relevant
  • Wrote tests
  • Updated CHANGELOG.md

This commit is multi-faceted. It:

1. Drastically simplifies much of the subscription-related
   functionality.
2. Expands the WebSocketClient's capabilities to be able to handle other
   types of requests other than just subscriptions (i.e. it implements the
   Client trait).

Signed-off-by: Thane Thomson <connect@thanethomson.com>
@greg-szabo greg-szabo added this to the v0.17.0 milestone Oct 21, 2020
Signed-off-by: Thane Thomson <connect@thanethomson.com>
@thanethomson thanethomson removed this from the v0.17.0 milestone Oct 21, 2020
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
@codecov-io
Copy link

codecov-io commented Nov 10, 2020

Codecov Report

Merging #646 (281a0a8) into master (b159940) will decrease coverage by 1.1%.
The diff coverage is 55.2%.

Impacted file tree graph

@@           Coverage Diff            @@
##           master    #646     +/-   ##
========================================
- Coverage    41.7%   40.6%   -1.2%     
========================================
  Files         191     192      +1     
  Lines       12595   12410    -185     
  Branches     3223    3137     -86     
========================================
- Hits         5262    5042    -220     
- Misses       6991    7048     +57     
+ Partials      342     320     -22     
Impacted Files Coverage Δ
rpc/src/client.rs 8.5% <ø> (ø)
rpc/src/client/transport/http.rs 0.0% <0.0%> (ø)
rpc/src/endpoint/abci_info.rs 54.8% <ø> (ø)
rpc/src/endpoint/abci_query.rs 22.2% <ø> (ø)
rpc/src/endpoint/block.rs 30.7% <ø> (ø)
rpc/src/endpoint/block_results.rs 0.0% <ø> (ø)
rpc/src/endpoint/blockchain.rs 0.0% <ø> (ø)
rpc/src/endpoint/broadcast/tx_async.rs 0.0% <ø> (ø)
rpc/src/endpoint/broadcast/tx_commit.rs 0.0% <ø> (ø)
rpc/src/endpoint/broadcast/tx_sync.rs 0.0% <ø> (ø)
... and 34 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b159940...281a0a8. Read the comment docs.

Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
@thanethomson thanethomson marked this pull request as ready for review November 10, 2020 22:51
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
rpc/src/client.rs Outdated Show resolved Hide resolved
@@ -39,6 +39,7 @@ pub struct ChannelRx<T>(mpsc::UnboundedReceiver<T>);
impl<T> ChannelRx<T> {
/// Wait indefinitely until we receive a value from the channel (or the
/// channel is closed).
#[allow(dead_code)]
Copy link
Member Author

Choose a reason for hiding this comment

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

This method is not used if only the http-client feature is enabled, but putting it behind a feature flag breaks things.

/// simple, singular response.
///
/// [`Subscription`]: struct.Subscription.html
pub trait SimpleRequest: Request {}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the simplest way I could find to configure the type system in such a way that I could restrict access to the Client::perform method to only those kinds of requests that expect a single response (i.e. non-subscription-related requests), while still preserving the existing overall type architecture.

Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
ChannelTx::send now no longer needs to be mutable because the underlying
channel has no mutability requirement. This leads to viral changes
throughout the interfaces and clients (positive ones).

ChannelTx::send also doesn't need to be async, because it relies on an
unbounded channel. This also has some viral implications for other
methods throughout the RPC client. If, in future, we want to support
bounded channels, then we can consider making it async again. But we
probably shouldn't make it async if we don't need it to be.

Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
@greg-szabo
Copy link
Member

The latest changes are awesome. When can we review and merge?

Copy link
Contributor

@melekes melekes left a comment

Choose a reason for hiding this comment

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

);
}
}
// Obtain a mutable reference because the previous reference was
Copy link
Contributor

Choose a reason for hiding this comment

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

interesting 🤔 i would expect Rust to figure out that the loop is over

Copy link
Member

Choose a reason for hiding this comment

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

for x in y { b } is syntactic sugar for let mut iter = y.into_iter(); while let Some(x) = iter.next() { b } and into_iter() consumes self, therefore one cannot access the collection being iterated over (ie. subs_for_query) after the loop is over.

On the other hand, for x in &y { b } would desugar to a call to iter() which does not not consume self but instead yields reference to the underlying elements. Same with for x in &mut y.

I wonder though if we could use for (id, event_tx) in &subs_for_query here instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

got it! thanks for explaining it @romac 🙏

Copy link
Member Author

Choose a reason for hiding this comment

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

So interestingly, the for (id, event_tx) in &subs_for_query didn't work previously, and I've checked again now, it results in the following error:

error[E0277]: `&&mut std::collections::HashMap<std::string::String, client::sync::ChannelTx<std::result::Result<event::Event, error::Error>>>` is not an iterator
  --> rpc/src/client/transport/router.rs:35:31
   |
35 |         for (id, event_tx) in &subs_for_query {
   |                               ^^^^^^^^^^^^^^^ `&&mut std::collections::HashMap<std::string::String, client::sync::ChannelTx<std::result::Result<event::Event, error::Error>>>` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `&&mut std::collections::HashMap<std::string::String, client::sync::ChannelTx<std::result::Result<event::Event, error::Error>>>`
   = note: required by `std::iter::IntoIterator::into_iter`

for (id, event_tx) in &mut subs_for_query also doesn't compile.

The following code, however, does work, so I'll update the code accordingly:

for (id, event_tx) in subs_for_query.borrow_mut() {
    // ...
}

I feel as though if I manually expanded all the syntactic sugar, I'd understand this better 😁

self.subscriptions.get_mut(&query).unwrap()
}
};
subs_for_query.insert(id.to_string(), tx);
Copy link
Contributor

Choose a reason for hiding this comment

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

what if the id already exists?

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'd be overwritten.

My assumption here is that our UUIDs that we're automatically generating for subscriptions won't ever collide. I suppose that leaves a very small, albeit non-zero, non-deterministic probability of having a subscription dropped arbitrarily, but I'd imagine that networking failures leading to the same outcome would be far higher probability than that of a UUIDv4 collision.

pub enum PublishResult {
Success,
NoSubscribers,
AllDisconnected,
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the intuition behind this type of result?

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 was one way of passing some additional information back to the WebSocketClient after it attempts to publish an event.

Since the WebSocketClient only sends an unsubscribe request once all subscribers for a particular query have dropped, I needed to be able to distinguish between the case where there were no subscribers to begin with (i.e. we somehow received a spurious event without having a subscription) and the case where we had subscribers, but since we published last to them they dropped their receiving ends. Both would publish the event to zero subscribers.

In the former case, we would consider this to be unexpected behaviour on the part of the node (I don't do anything about it right now though), and in the latter case it'd be a signal to send a single unsubscribe request.

}
}
},
async fn confirm_pending_command(
Copy link
Contributor

Choose a reason for hiding this comment

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

confirm here means sending response?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. I can rename it to something a bit clearer though.

…wice

Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
Signed-off-by: Thane Thomson <connect@thanethomson.com>
@cargo-dep-bot
Copy link

cargo-dep-bot bot commented Nov 25, 2020

This PR made the following dependency changes:

Added Packages (Duplicate versions in '()'):
	clap 2.33.3
	colored 1.9.3
	heck 0.3.1
	num-derive 0.3.3
	proc-macro-error 1.0.4
	proc-macro-error-attr 1.0.4
	simple_logger 1.11.0
	strsim 0.8.0 (0.9.3)
	structopt 0.3.20
	structopt-derive 0.4.13
	tendermint-rpc-probe 0.17.0-rc3
	textwrap 0.11.0
	unicode-segmentation 1.7.1
	unicode-width 0.1.8
	vec_map 0.8.2

Removed Packages (Remaining versions in '()'):
	abscissa_tokio 0.5.1

Updated Packages:
	async-tungstenite: 0.8.0 -> 0.9.3
	tendermint: 0.16.0 -> 0.17.0-rc3
	tendermint-light-client: 0.16.0 -> 0.17.0-rc3
	tendermint-light-node: 0.16.0 -> 0.17.0-rc3
	tendermint-proto: 0.1.0 -> 0.17.0-rc3
	tendermint-rpc: 0.16.0 -> 0.17.0-rc3
	tendermint-testgen: 0.1.0 -> 0.17.0-rc3

@thanethomson thanethomson merged commit 5105efe into master Nov 26, 2020
@thanethomson thanethomson deleted the rpc/upgrade-websocket-client branch November 26, 2020 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants