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 a transports crate & initial Network abstraction #2

Merged
merged 53 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
83a5c8a
feature: transports crate
prestwich Jul 10, 2023
0203bad
review: address comments
prestwich Jul 12, 2023
7528c5e
refactor: RpcObject trait
prestwich Jul 12, 2023
fe6142d
refactor: RpcResult type
prestwich Jul 12, 2023
b2e3705
refactor: small code quality
prestwich Jul 12, 2023
f1010c6
deps: bump Cargo.toml
prestwich Jul 12, 2023
04f3c49
chore: delete unused src
prestwich Jul 12, 2023
366a5ac
refactor: more stuff
prestwich Jul 12, 2023
4de4a8e
refactor: remove Params type from RpcCall
prestwich Jul 12, 2023
27a6c0f
fix: remove extra to_json_raw_value
prestwich Jul 12, 2023
e2703df
refactor: minor legibility
prestwich Jul 12, 2023
53128fd
feat: unwrap variants
prestwich Jul 12, 2023
76bef7f
fix: add debug bounds
prestwich Jul 12, 2023
d654407
feature: some cool combinators on rpccall
prestwich Jul 12, 2023
1d691ec
fix: hide __ENFORCE_ZST
prestwich Jul 12, 2023
0073a12
feature: DummyNetwork compile check
prestwich Jul 12, 2023
9d736b5
test: dummynet compile checks
prestwich Jul 13, 2023
268412b
doc: fix comment
prestwich Jul 13, 2023
3cf3f2d
wip: mware and combinator stuff
prestwich Jul 14, 2023
5c53aa8
fuck jsonrpsee
prestwich Jul 16, 2023
efb5cdc
feature: blanket
prestwich Jul 16, 2023
cf00429
test: http impls transport
prestwich Jul 16, 2023
ed0a5e8
feature: send batch request
prestwich Jul 16, 2023
1e9e873
feature: separate rpc type crate
prestwich Jul 16, 2023
21fda6e
feat: RpcObject
prestwich Jul 17, 2023
12f78fa
refactor: transport future aliases
prestwich Jul 22, 2023
2be245a
refactor: transport requires type-erased futures. improved batch ergo
prestwich Jul 25, 2023
38a63ab
wip: some middleware noodling
prestwich Jul 30, 2023
dcdb4cf
feature: manual future for json rpc to avoid higher-ranked lifetime
prestwich Aug 1, 2023
d45a19d
cleanup: some clippy and stuff
prestwich Aug 2, 2023
b8dbb9a
feature: client builder
prestwich Aug 2, 2023
763a5c6
refactor: move is_local to transport
prestwich Aug 2, 2023
9d1f491
chore: clippy
prestwich Aug 2, 2023
0f3fb8f
feature: generic request
prestwich Aug 2, 2023
843009b
feature: allow type-erased rpc client
prestwich Aug 7, 2023
2b83171
chore: cleanup in transports mod
prestwich Aug 7, 2023
562e539
chore: misc cleanup
prestwich Aug 7, 2023
ceda9f7
refactor: more crate
prestwich Aug 9, 2023
b7a8d1e
chore: clippy cleanup
prestwich Aug 9, 2023
36513b2
fix: lifetimes for rpc calls
prestwich Aug 9, 2023
e204269
feature: lifetime on rpccall
prestwich Aug 9, 2023
7a9726e
feature: BoxTransport
prestwich Aug 12, 2023
ba8fd02
chore: clippy
prestwich Aug 12, 2023
5cd4826
refactor: cow for jsonrpc params
prestwich Aug 14, 2023
7f8923b
fix: lint
prestwich Aug 19, 2023
047ba1d
refactor: rename to boxed
prestwich Aug 19, 2023
c5ce15b
docs and misc convenience
prestwich Aug 21, 2023
b7fbff4
refactor: docs and cleanup
prestwich Aug 21, 2023
7173fcc
docs: more of em
prestwich Aug 21, 2023
ee69008
refactor: seal transport
prestwich Aug 21, 2023
5770e67
feature: seal transport
prestwich Aug 21, 2023
0c6b2c9
rename middleware to provider
prestwich Aug 21, 2023
8a6a838
chore: remove dead code
prestwich Aug 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ edition = "2021"
rust-version = "1.65"
authors = ["Alloy Contributors"]
license = "MIT OR Apache-2.0"
homepage = "https://github.com/ethers-rs/next"
repository = "https://github.com/ethers-rs/next"
homepage = "https://github.com/alloy-rs/next"
repository = "https://github.com/alloy-rs/next"
exclude = ["benches/", "tests/"]

[workspace.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[workspace.dependencies]
[workspace.dependencies]

# futures
futures-channel = "0.3"

# jsonrpsee-types
jsonrpsee-types = "0.16"

# serde
serde = { version = "1.0", default-features = false, features = ["alloc"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }

# thiserror
thiserror = "1.0"
9 changes: 9 additions & 0 deletions crates/transports/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
base64 = "0.21.0"
reqwest = { version = "0.11.18", features = ["serde_json", "json"] }

futures-channel.workspace = true
jsonrpsee-types.workspace = true
serde.workspace = true
serde_json = { workspace = true, features = ["raw_value"] }
thiserror.workspace = true

165 changes: 165 additions & 0 deletions crates/transports/src/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use std::{
Copy link
Member

Choose a reason for hiding this comment

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

Feels like we'll need to fill this thing with docs given how much thinking went into the type separations

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah I'll work on it a bit. but right now i'm trying to iterate without docs until it gets really firm

borrow::Borrow,
future::Future,
marker::PhantomData,
pin::Pin,
task::{ready, Context, Poll},
};

use jsonrpsee_types::ErrorObjectOwned;
use serde::{Deserialize, Serialize};

use crate::{
common::{Id, Request, RpcFuture, RpcOutcome},
utils::{from_json, to_json_raw_value},
Connection, TransportError,
};

pub(crate) enum CallState<B, T, Params> {
Prepared {
connection: B,
method: &'static str,
params: Params,
id: Id<'static>,
_pd: PhantomData<T>,
},
AwaitingResponse {
fut: RpcFuture,
},
Complete,
Running,
}
impl<B, T, Params> std::fmt::Debug for CallState<B, T, Params> {
prestwich marked this conversation as resolved.
Show resolved Hide resolved
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Prepared { method, id, .. } => f
.debug_struct("Prepared")
.field("method", method)
.field("id", id)
.finish(),
Self::AwaitingResponse { .. } => f.debug_struct("AwaitingResponse").finish(),
Self::Complete => write!(f, "Complete"),
Self::Running => write!(f, "Running"),
}
}
}

impl<B, T, Params> CallState<B, T, Params> {
pub(crate) fn new(
connection: B,
method: &'static str,
params: Params,
id: Id<'static>,
) -> CallState<B, T, Params> {
Self::Prepared {
connection,
method,
params,
id,
_pd: PhantomData,
}
}
}

impl<B, T, Params> CallState<B, T, Params>
where
B: Borrow<T> + Unpin,
T: Connection + Unpin,
Params: Serialize + Unpin,
{
fn poll_prepared(&mut self, cx: &mut Context<'_>) -> Poll<RpcOutcome> {
let this = std::mem::replace(self, CallState::Running);

match this {
CallState::Prepared {
connection,
method,
params,
id,
..
} => {
let params = to_json_raw_value(&params);
prestwich marked this conversation as resolved.
Show resolved Hide resolved
if let Err(err) = params {
*self = CallState::Complete;
return Poll::Ready(Err(err));
}
let params = params.unwrap();
gakonst marked this conversation as resolved.
Show resolved Hide resolved
let req = Request::owned(id, method, Some(params));
let fut = connection.borrow().json_rpc_request(&req);
*self = CallState::AwaitingResponse { fut };
cx.waker().wake_by_ref();
Poll::Pending
}
_ => panic!(""),
prestwich marked this conversation as resolved.
Show resolved Hide resolved
}
}

fn poll_awaiting(&mut self, cx: &mut Context<'_>) -> Poll<RpcOutcome> {
let this = std::mem::replace(self, CallState::Running);
match this {
CallState::AwaitingResponse { mut fut } => {
if let Poll::Ready(val) = fut.as_mut().poll(cx) {
*self = CallState::Complete;
return Poll::Ready(val);
}
*self = CallState::AwaitingResponse { fut };
Poll::Pending
}
_ => panic!(""),
prestwich marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

impl<B, T, Params> Future for CallState<B, T, Params>
where
B: Borrow<T> + Unpin,
T: Connection + Unpin,
Params: Serialize + Unpin,
{
type Output = RpcOutcome;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let state = self.get_mut();
match state {
CallState::Prepared { .. } => state.poll_prepared(cx),
CallState::AwaitingResponse { .. } => state.poll_awaiting(cx),
_ => panic!("Polled in bad state"),
}
}
}

#[derive(Debug)]
pub struct RpcCall<B, T, Params, Resp> {
state: CallState<B, T, Params>,
resp: PhantomData<fn() -> Resp>,
prestwich marked this conversation as resolved.
Show resolved Hide resolved
}

impl<B, T, Params, Resp> RpcCall<B, T, Params, Resp> {
pub fn new(connection: B, method: &'static str, params: Params, id: Id<'static>) -> Self {
Self {
state: CallState::new(connection, method, params, id),
resp: PhantomData,
}
}
}

impl<B, T, Params, Resp> Future for RpcCall<B, T, Params, Resp>
where
B: Borrow<T> + Unpin,
T: Connection + Unpin,
Params: Serialize + Unpin,
Resp: for<'de> Deserialize<'de> + Unpin,
{
type Output = Result<Result<Resp, ErrorObjectOwned>, TransportError>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let state = Pin::new(&mut self.get_mut().state);
let res = ready!(state.poll(cx));

match res {
Ok(Ok(val)) => Poll::Ready(from_json(val.get()).map(Result::Ok)),
Ok(Err(err)) => Poll::Ready(Ok(Err(err))),
Err(e) => Poll::Ready(Err(e)),
}
}
}
56 changes: 56 additions & 0 deletions crates/transports/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use base64::{engine::general_purpose, Engine};
use serde_json::value::RawValue;
use std::{borrow::Cow, fmt, future::Future, pin::Pin};

pub use jsonrpsee_types::{ErrorObject, ErrorResponse, Id, RequestSer as Request, Response};

use crate::TransportError;

#[cfg(target_arch = "wasm32")]
pub(crate) type DynFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) type DynFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

pub type JsonRpcResult<'a> = Result<Cow<'a, RawValue>, ErrorObject<'a>>;
pub type JsonRpcResultOwned = JsonRpcResult<'static>;

pub type RpcOutcome = Result<JsonRpcResultOwned, TransportError>;
pub type BatchRpcOutcome = Result<Vec<JsonRpcResultOwned>, TransportError>;

pub type RpcFuture = DynFuture<'static, RpcOutcome>;
pub type BatchRpcFuture = DynFuture<'static, BatchRpcOutcome>;

/// Basic or bearer authentication in http or websocket transport
///
/// Use to inject username and password or an auth token into requests
#[derive(Clone, Debug)]
pub enum Authorization {
/// HTTP Basic Auth
Basic(String),
/// Bearer Auth
Bearer(String),
}

impl Authorization {
/// Make a new basic auth
pub fn basic(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
let username = username.as_ref();
let password = password.as_ref();
let auth_secret = general_purpose::STANDARD.encode(format!("{username}:{password}"));
Self::Basic(auth_secret)
}

/// Make a new bearer auth
pub fn bearer(token: impl Into<String>) -> Self {
Self::Bearer(token.into())
}
}

impl fmt::Display for Authorization {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Authorization::Basic(auth_secret) => write!(f, "Basic {auth_secret}"),
Authorization::Bearer(token) => write!(f, "Bearer {token}"),
}
}
}
31 changes: 31 additions & 0 deletions crates/transports/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum TransportError {
/// SerdeJson (de)ser
#[error("{err}")]
gakonst marked this conversation as resolved.
Show resolved Hide resolved
SerdeJson {
err: serde_json::Error,
text: String,
},

/// Http transport
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
}

impl TransportError {
pub fn ser_err(err: serde_json::Error) -> Self {
prestwich marked this conversation as resolved.
Show resolved Hide resolved
Self::SerdeJson {
err,
text: "".to_string(),
}
}

pub fn deser_err(err: serde_json::Error, text: impl AsRef<str>) -> Self {
prestwich marked this conversation as resolved.
Show resolved Hide resolved
Self::SerdeJson {
err,
text: text.as_ref().to_string(),
}
}
}
39 changes: 25 additions & 14 deletions crates/transports/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
#![warn(
missing_debug_implementations,
// missing_docs,
unreachable_pub,
// unused_crate_dependencies
)]
#![deny(unused_must_use, rust_2018_idioms)]
#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
))]

pub mod common;
pub(crate) mod utils;

mod error;
pub use error::TransportError;

mod call;

mod transport;
pub use transport::{Connection, PubSubConnection};

pub mod transports;
pub use transports::Http;
57 changes: 57 additions & 0 deletions crates/transports/src/transport.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use futures_channel::mpsc::UnboundedReceiver;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;

use std::{borrow::Cow, fmt::Debug};

use crate::{call::RpcCall, common::*, TransportError};

pub trait Connection: Debug + Send + Sync {
fn is_local(&self) -> bool;

fn increment_id(&self) -> u64;

fn next_id(&self) -> Id<'static> {
Id::Number(self.increment_id())
}

fn json_rpc_request(&self, req: &Request<'_>) -> RpcFuture;

fn batch_request(&self, reqs: &[Request<'_>]) -> BatchRpcFuture;

fn request<Params, Resp>(
&self,
method: &'static str,
params: Params,
) -> RpcCall<&Self, Self, Params, Resp>
where
Self: Sized,
Params: Serialize,
Resp: for<'de> Deserialize<'de>,
{
RpcCall::new(self, method, params, self.next_id())
}
}

pub trait PubSubConnection: Connection {
#[doc(hidden)]
fn uninstall_listener(&self, id: [u8; 32]) -> Result<(), TransportError>;

#[doc(hidden)]
fn install_listener(
&self,
id: [u8; 32],
) -> Result<UnboundedReceiver<Cow<'_, RawValue>>, TransportError>;
}

#[cfg(test)]
mod test {
use crate::{Connection, PubSubConnection};

fn __compile_check() -> Box<dyn Connection> {
todo!()
}
fn __compile_check_pubsub() -> Box<dyn PubSubConnection> {
todo!()
}
}
Loading