-
Notifications
You must be signed in to change notification settings - Fork 249
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
[POC] generalized requests to support sync and async #212
Conversation
client/src/client.rs
Outdated
|
||
fn converter_hex<T: bitcoin::consensus::encode::Decodable>(raw: &RawValue) -> Result<T> { | ||
let hex: &str = serde_json::from_str(raw.get())?; | ||
let bytes = Vec::<u8>::from_hex(&hex)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
once bitcoin_hashes is released we can skip the Vec allocation with rust-bitcoin/bitcoin_hashes#135
once concept would be to have only async API, but provide a blocking client executor that uses non-async internally. This would keep the core API pure (and always async), but allow using the async code from non async functions with the sync client. |
One advantage of sync is that it requires a lot less dependency, using async+blocking client would prevent that |
Not quite true; for example you can build a simple executor https://rust-lang.github.io/async-book/02_execution/04_executor.html and not pull in e..g tokio |
client/src/client.rs
Outdated
pub struct Request<'c, T: 'static> { | ||
method: &'static str, | ||
args: Vec<serde_json::Value>, //TODO(stevenroose) something more efficient? | ||
converter: &'static dyn Fn(&RawValue) -> Result<T>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you can build this into the method signatures instead of explicitly calling a converter (e.g., https://github.com/lightningdevkit/rust-lightning/blob/5e86bbf97030c69b0118d1bef39554febfb1cbef/lightning-block-sync/src/rpc.rs#L40-L41).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
the only exception being if we need >1 "TryFrom" for a given type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the RPC example is assuming JSON responses, which could be examined and converted as needed (e.g. https://github.com/lightningdevkit/rust-lightning/blob/b89f8815c3d40d8c5bf9aa60af5b3fbb2ee78f25/lightning-block-sync/src/convert.rs#L42-L72).
But in the following REST example, the response could be either JSON or binary depending on the parameters, which I think is what you are alluding to.
There the response format needs to be explicit at the call site. For example:
So basically, you define conversion from bytes to some response format:
And then any conversions from those formats to the desired types.
client/src/client.rs
Outdated
conv(&raw) | ||
} | ||
|
||
fn async(self, client: &dyn RpcClient) -> Result<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also be an async
method?
client/src/client.rs
Outdated
let res: Response = self.call("getnetworkinfo", &[])?; | ||
Ok(res.version) | ||
} | ||
#[cfg(not(feature = "async"))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally the API should support both, no? That way two dependencies setting different feature flags wouldn't cause compile failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spoke with @stevenroose about this, we think there is a very clean approach for this by impl future for the request type directly. he's prototyping it :)
@stevenroose any update on this? We're quite interested in having async support - in the past @gregdhill made #144 but unfortunately that never went anywhere |
in theory if you need something right away, you can use https://crates.io/crates/bitcoincore-rpc-json-async note that this uses the sapio flavoured bitcoin crates, though, not the standard ones. I would accept a patch that let's you choose via a flag which dep you take. |
Big update on this. I did a WIP/POC refactor to rust-jsonrpc and to this branch. It's not finished, I will have to add two traits that will have all the actual client's methods I went kind of in a rabbit hole to reduce allocations, but the result is quite an ugly construction that requires double references There's still some things I didn't figure out, like wrappers around argument inputs, but I think we might be able to create the wrappers on the trait level, so that would in the end be possible. Right now some of those arguments that need internal conversion are just re-allocated, but those could be optimized using such wrapper types lateron. For now, I'll be working on the traits that should just be grinding through all the calls twice (I might be tempted to write a Rust macro for this, but maybe I'll just do a vim macro to manually convert them) to build the traits. The end result should be two traits ( |
1c315a2
to
dcc6c7d
Compare
This is almost finished, but blocked on rust-bitcoin/rust-bitcoin#1785 since this create bumped the rust-bitcoin dep to 0.30. |
Is there any chance of having generic HTTP implementation so that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only reviewed this lightly.
client/src/lib.rs
Outdated
let bytes = bitcoin::consensus::encode::serialize(self); | ||
HexParam::write_hex(&bytes[..], f) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bitcoin
0.30 has direct support for this which is also significantly more performant. See bitcoin::consensus::serde
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah I focussed on optimizing all the parsing stuff but didn't really look too much at the input stuff. This is indeed quite a silly impl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the consensus::serde stuff is interesting, I'll take a look.
into_json_from!(u64); | ||
into_json_from!(i32); | ||
into_json_from!(i64); | ||
into_json_from!(f64); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what these do but it looks like maybe you're trying to do what erased-serde
should be used for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't think it's relased to the erased crate. But arguably it's just a way to limit the Into<serde_json::Value>
to anything not-string.
@@ -1,6 +1,6 @@ | |||
[package] | |||
name = "bitcoincore-rpc-json" | |||
version = "0.16.0" | |||
version = "1.0.0-rc0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks quite bold considering crazy-new change and the amount of breaking changes at Core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah version is a draft. Can be anything, but I'd like to be able to release rc's while also still being able to patch the current branch. So either it would be 0.20 or something or 1.0. Doesn't have to be too bold, we can go until 2^64-1 probably with versions ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1.0+ means there should be minimal if any breaking changes.
pub fr_75th: Amount, | ||
#[serde(with = "bitcoin::util::amount::serde::as_sat")] | ||
pub fr_90th: Amount, | ||
#[serde(with = "serde_feerate::sat_per_vb")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, we should add this to bitcoin
.
json/src/lib.rs
Outdated
|
||
/// Type to represent feerates. | ||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
pub struct FeeRate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have this in bitcoin
.
I'm planning a compile feature to support hyper, yeah. IIRC there is already code for that in the jsonrpc crate, in this crate it would be simply having a feature-gated constructor, I guess that uses that one. |
c1e7e7b
to
029e778
Compare
I'm going to close this and open a new one. |
So this is just a proof-of-concept of an idea I've had to finally have a way to support both sync and async in this crate.
It could go hand in hand with supporting batching and deprecating old-hyper in favor of the custom HTTP transport from LDK.
Basically, it introduces a
Request
struct that basically holds everything you need to perform an RPC and then it has some methodssync
(perform the request sync),async
(perform the request async) andbatch
(add the request to a batch).In principle, this construction could even be pulled upstream the
jsonrpc
crate. Based on this construction we can decide how to change the API to support sync and async. Options are (1) use features to have the same method name have different signatures for sync/async, (2) support both and have_sync
and_async
method or (3) some kind of hybrid like I did in this POC.As you see, inside the
RpcApi
impl, there is one actual impl method_request
and the others are basically templated and could easily be made into a macro. However I think this crate previously moved away from having macros, so I'm not against just writing it all out in full.Everything is open for discussion, the new construction compiles if you comment out all the other method not yet converted in the
RpcApi
trait..