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

Add minimal blocking ABCI crate #794

Merged
merged 28 commits into from
Feb 19, 2021
Merged

Add minimal blocking ABCI crate #794

merged 28 commits into from
Feb 19, 2021

Conversation

thanethomson
Copy link
Contributor

@thanethomson thanethomson commented Jan 28, 2021

Closes #29

In order to reduce the amount of code for review, I've extracted portions of #750 and expanded on them, but purely for the blocking case (using the Rust standard library's networking functionality). I've now included the full range of ABCI requests/responses, but purely using their Protobuf representations (also to reduce the sheer quantity of code). Now that the investigative work in #750 has been done, adding async compatibility should be a relatively simple (but probably tedious) task - TBD in separate PRs.

There's a minimal kvstore-rs application that can be built into a binary, and the basic functionality works with a fresh Tendermint node. See the ABCI crate README for details:

📖 README rendered

  • 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

thanethomson and others added 4 commits February 8, 2021 09:35
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
* Add integration testing utility for ABCI key/value store

Signed-off-by: Thane Thomson <[email protected]>

* Add hacky bash script to demonstrate parallel execution

Signed-off-by: Thane Thomson <[email protected]>

* Created abci test harness (#800)

* Created abci test harness

* cargo make additions and docs

Co-authored-by: Greg Szabo <[email protected]>
@thanethomson thanethomson marked this pull request as ready for review February 9, 2021 15:19
Copy link
Member

@romac romac left a comment

Choose a reason for hiding this comment

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

Great stuff, very clean and minimal, <3 the test suite :)

Left a few minor comments but otherwise it looks really good to me!

abci/src/lib.rs Show resolved Hide resolved
abci/Cargo.toml Outdated Show resolved Hide resolved

fn channel_send<T>(tx: &Sender<T>, value: T) -> Result<()> {
tx.send(value)
.map_err(|e| Error::ChannelSend(e.to_string()))
Copy link
Member

Choose a reason for hiding this comment

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

Can we hold on to the original error here rather than its string representation?

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 might be able to do that with eyre - I'll try it out 👍 Originally I had trouble with this because SendError is generic over T, which would imply that the error needs to be generic over T too. But if the error can be wrapped as a Box<dyn std::error::Error> this shouldn't be a problem, I think.

Copy link
Member

Choose a reason for hiding this comment

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

Oh I see. Since it's just an example app it does not matter too much, just a nice-to-have (probably not worth it if we have to pull in eyre).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's the conundrum with eyre, as per https://docs.rs/eyre/0.6.5/eyre/struct.Report.html:

Report requires that the error is Send, Sync, and 'static.

This would imply that T must be Send, Sync and 'static too. In this case I'm trying to send a variant of the Command enum:

enum Command {
    /// Get the height of the last commit.
    GetInfo { result_tx: Sender<(i64, Vec<u8>)> },
    /// Get the key associated with `key`.
    Get {
        key: String,
        result_tx: Sender<(i64, Option<String>)>,
    },
    /// Set the value of `key` to to `value`.
    Set {
        key: String,
        value: String,
        result_tx: Sender<Option<String>>,
    },
    /// Commit the current state of the application, which involves recomputing
    /// the application's hash.
    Commit { result_tx: Sender<(i64, Vec<u8>)> },
}

std::sync::mpsc::Sender is !Sync, and I ideally need to be able to send it across the channel in order to get a dedicated response asynchronously.

Perhaps this is a hint that a better overall design is possible? I suppose falling back to Arc<Mutex<...>> instead of the handle/driver architecture would solve this. Or simply "flattening" std::sync::mpsc::SendError<T>, like I'm doing in the original code here. I can't think why the SendError would need to be generic over T in the first place?

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 current solution and architecture are fine.

Just wondering, have you tried just casting SendError to Box<dyn std::error::Error> and wrapping that one in ChannelSend (without using eyre at all)?


Regarding SendError being generic over T: that's because it's defined as

pub struct SendError<T>(pub T);

which allows you to get back the value that failed to be sent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just wondering, have you tried just casting SendError to Box and wrapping that one in ChannelSend (without using eyre at all)?

T would still need to implement Sync, afaik 😞

abci/src/application/kvstore.rs Outdated Show resolved Hide resolved
abci/src/codec.rs Outdated Show resolved Hide resolved
abci/src/codec.rs Outdated Show resolved Hide resolved

/// Called once upon genesis.
fn init_chain(&self, _request: RequestInitChain) -> ResponseInitChain {
Default::default()
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you expand why the default pattern in here is preferred over not having a default implementation for these methods?

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 was modelling it on what the BaseApplication does in the Go version: https://github.com/tendermint/tendermint/blob/master/abci/types/application.go#L34

tools/docker/tendermint-0.34.0/.gitignore Outdated Show resolved Hide resolved
abci/src/codec.rs Outdated Show resolved Hide resolved
melekes
melekes previously approved these changes Feb 12, 2021
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.

👍

/// Attempt to retrieve the value associated with the given key.
pub fn get<K: AsRef<str>>(&self, key: K) -> Result<(i64, Option<String>)> {
let (result_tx, result_rx) = channel();
channel_send(
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you explain why you've chosen this concurrency model (communication via channels) over something like a mutex & hashmap? As someone who's learning Rust it will be useful to me.

Copy link
Contributor Author

@thanethomson thanethomson Feb 16, 2021

Choose a reason for hiding this comment

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

This may be a personal preference/readability thing 😁 I'd say there are pros and cons to each approach. I've always tended to prefer an actor-style model for managing state in concurrent systems (explicit-lock-free state encapsulation with message passing and pattern matching on messages), which I've tried to implement here in a lightweight way. It's easier for me to reason about such state machines in general (like this).

When it comes to contention, I'd imagine using mutexes would push bottlenecks down the stack towards the networking layer, outside of the app's direct control. With the actor-based model it'd be able to leverage memory (in the unbounded channel) to avoid pushing the bottleneck towards the networking layer. On more constrained devices, this channel-based approach wouldn't provide any benefit for the additional complexity cost, so mutexes would probably be the way to go.

Practically both models would probably work the same for this specific application, although I'm not sure which one would provide better performance (I'd have to benchmark to find out). I'd imagine that the mutex-based approach would probably provide better performance, speed-wise, as long as the app can keep up with incoming requests.

abci/src/codec.rs Show resolved Hide resolved
@thanethomson thanethomson mentioned this pull request Feb 16, 2021
11 tasks
Copy link
Contributor

@brapse brapse left a comment

Choose a reason for hiding this comment

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

Good stuff & discussion 👍

@brapse brapse merged commit a2596d5 into master Feb 19, 2021
@brapse brapse deleted the thane/minimal-blocking-abci branch February 19, 2021 01:00
@tomtau
Copy link
Contributor

tomtau commented Feb 19, 2021

I don't have admin rights on https://github.com/tendermint/rust-abci but after the ABCI crate here gets published, perhaps it'll be good to archive the old repo and point to here?
@xla and @liamsi should have rights on https://crates.io/crates/abci

@@ -0,0 +1,33 @@
[package]
name = "tendermint-abci"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
name = "tendermint-abci"
name = "abci"

it can be called just this and published as a new breaking version of https://crates.io/crates/abci -- @xla @liamsi can help with that

lklimek referenced this pull request in lklimek/tenderdash-abci-rs Feb 28, 2023
* Add minimal blocking ABCI library

Signed-off-by: Thane Thomson <[email protected]>

* Expand API to implement in-memory key/value store app

Signed-off-by: Thane Thomson <[email protected]>

* Add kvstore-rs ABCI app

Signed-off-by: Thane Thomson <[email protected]>

* Add rudimentary README

Signed-off-by: Thane Thomson <[email protected]>

* Bump proto version dependency to v0.18.0

Signed-off-by: Thane Thomson <[email protected]>

* Replace manual default structs with Default::default()

Signed-off-by: Thane Thomson <[email protected]>

* Enable debug logging for all incoming ABCI requests

Signed-off-by: Thane Thomson <[email protected]>

* Improve CLI UX

Signed-off-by: Thane Thomson <[email protected]>

* Allow for read buffer size customization

Signed-off-by: Thane Thomson <[email protected]>

* Add crate description

Signed-off-by: Thane Thomson <[email protected]>

* Update README for ABCI crate

Signed-off-by: Thane Thomson <[email protected]>

* Add ABCI integration test for minimal ABCI crate (#797)

* Add integration testing utility for ABCI key/value store

Signed-off-by: Thane Thomson <[email protected]>

* Add hacky bash script to demonstrate parallel execution

Signed-off-by: Thane Thomson <[email protected]>

* Created abci test harness (#800)

* Created abci test harness

* cargo make additions and docs

Co-authored-by: Greg Szabo <[email protected]>

* Update abci/src/codec.rs

Co-authored-by: Romain Ruetschi <[email protected]>

* Apply suggestion from https://github.com/informalsystems/tendermint-rs/pull/794\#discussion_r573100911

Signed-off-by: Thane Thomson <[email protected]>

* Refactor error handing and expose eyre::Result as crate default Result type

Signed-off-by: Thane Thomson <[email protected]>

* Refactor to use tracing instead of log

Signed-off-by: Thane Thomson <[email protected]>

* Add newline

Signed-off-by: Thane Thomson <[email protected]>

* Remove comment relating to constraints on Codec struct params

Signed-off-by: Thane Thomson <[email protected]>

* Version tendermint-abci crate in line with other tendermint-rs crates

Signed-off-by: Thane Thomson <[email protected]>

* Update CHANGELOG

Signed-off-by: Thane Thomson <[email protected]>

* Expand crate documentation

Signed-off-by: Thane Thomson <[email protected]>

* Extract request dispatch functionality from Application trait

Signed-off-by: Thane Thomson <[email protected]>

* Move ABCI server example to crate root

Signed-off-by: Thane Thomson <[email protected]>

* Fix broken link in docs

Signed-off-by: Thane Thomson <[email protected]>

* Replace EchoApp example with KeyValueStoreApp example

Signed-off-by: Thane Thomson <[email protected]>

Co-authored-by: Greg Szabo <[email protected]>
Co-authored-by: Romain Ruetschi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Absorb rust-abci
6 participants