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 44 commits
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
24 changes: 21 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,30 @@ 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]

alloy-json-rpc = { path = "crates/json-rpc" }
alloy-transports = { path = "crates/transports" }
alloy-networks = { path = "crates/networks" }

alloy-primitives = { version = "0.2.0", features = ["serde"] }
alloy-rlp = "0.3.0"

# futures
futures-channel = "0.3"
futures-util = "0.3"

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

# thiserror
thiserror = "1.0"
16 changes: 16 additions & 0 deletions crates/json-rpc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "alloy-json-rpc"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
exclude.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json = { version = "1.0.103", features = ["raw_value"] }
15 changes: 15 additions & 0 deletions crates/json-rpc/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum Id {
Number(u64),
String(String),
None,
}

impl Id {
pub fn is_none(&self) -> bool {
matches!(self, Id::None)
}
}
24 changes: 24 additions & 0 deletions crates/json-rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use serde::{de::DeserializeOwned, Serialize};

mod request;
pub use request::JsonRpcRequest;

mod response;
pub use response::{ErrorPayload, JsonRpcResponse, ResponsePayload};

mod common;
pub use common::Id;

mod result;
pub use result::RpcResult;

pub trait RpcParam: Serialize + Clone + Send + Sync + Unpin {}
impl<T> RpcParam for T where T: Serialize + Clone + Send + Sync + Unpin {}

// Note: we add `'static` here to indicate that the Resp is wholly owned. it
// may not borrow.
pub trait RpcReturn: DeserializeOwned + Send + Sync + Unpin + 'static {}
impl<T> RpcReturn for T where T: DeserializeOwned + Send + Sync + Unpin + 'static {}

pub trait RpcObject: RpcParam + RpcReturn {}
impl<T> RpcObject for T where T: RpcParam + RpcReturn {}
27 changes: 27 additions & 0 deletions crates/json-rpc/src/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::{common::Id, RpcParam};

use serde::{ser::SerializeMap, Deserialize, Serialize};

#[derive(Debug, Deserialize, Clone)]
pub struct JsonRpcRequest<Params> {
pub method: &'static str,
pub params: Params,
pub id: Id,
}

impl<Params> Serialize for JsonRpcRequest<Params>
where
Params: RpcParam,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(4))?;
map.serialize_entry("method", self.method)?;
map.serialize_entry("params", &self.params)?;
map.serialize_entry("id", &self.id)?;
map.serialize_entry("jsonrpc", "2.0")?;
map.end()
}
}
216 changes: 216 additions & 0 deletions crates/json-rpc/src/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
use std::fmt;

use serde::{
de::{MapAccess, Visitor},
Deserialize, Deserializer, Serialize,
};
use serde_json::value::RawValue;

use crate::common::Id;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ErrorPayload {
pub code: i64,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<Box<RawValue>>,
}

#[derive(Debug, Clone)]
pub enum ResponsePayload {
Success(Box<RawValue>),
Error(ErrorPayload),
}

pub struct JsonRpcResponse {
pub id: Id,
pub payload: ResponsePayload,
}

impl<'de> Deserialize<'de> for JsonRpcResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
enum Field {
Result,
Error,
Id,
Unknown,
}

impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;

impl<'de> serde::de::Visitor<'de> for FieldVisitor {
type Value = Field;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`result`, `error` and `id`")
}

fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: serde::de::Error,
{
match value {
"result" => Ok(Field::Result),
"error" => Ok(Field::Error),
"id" => Ok(Field::Id),
_ => Ok(Field::Unknown),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}

struct JsonRpcResponseVisitor;

impl<'de> Visitor<'de> for JsonRpcResponseVisitor {
type Value = JsonRpcResponse;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(
"a JSON-RPC response object, consisting of either a result or an error",
)
}

fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut result = None;
let mut error = None;
let mut id: Option<Id> = None;

while let Some(key) = map.next_key()? {
match key {
Field::Result => {
if result.is_some() {
return Err(serde::de::Error::duplicate_field("result"));
}
result = Some(map.next_value()?);
}
Field::Error => {
if error.is_some() {
return Err(serde::de::Error::duplicate_field("error"));
}
error = Some(map.next_value()?);
}
Field::Id => {
if id.is_some() {
return Err(serde::de::Error::duplicate_field("id"));
}
id = Some(map.next_value()?);
}
Field::Unknown => {
let _: serde::de::IgnoredAny = map.next_value()?; // ignore
}
}
}
let id = id.unwrap_or(Id::None);

match (result, error) {
(Some(result), None) => Ok(JsonRpcResponse {
id,
payload: ResponsePayload::Success(result),
}),
(None, Some(error)) => Ok(JsonRpcResponse {
id,
payload: ResponsePayload::Error(error),
}),
(None, None) => Err(serde::de::Error::missing_field("result or error")),
(Some(_), Some(_)) => Err(serde::de::Error::custom(
"result and error are mutually exclusive",
)),
}
}
}

deserializer.deserialize_map(JsonRpcResponseVisitor)
}
}

#[cfg(test)]
mod test {
#[test]
pub fn deser_success() {
let response = r#"{
"jsonrpc": "2.0",
"result": "california",
"id": 1
}"#;
let response: super::JsonRpcResponse = serde_json::from_str(response).unwrap();
assert_eq!(response.id, super::Id::Number(1));
assert!(matches!(
response.payload,
super::ResponsePayload::Success(_)
));
}

#[test]
pub fn deser_err() {
let response = r#"{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Invalid Request"
},
"id": null
}"#;
let response: super::JsonRpcResponse = serde_json::from_str(response).unwrap();
assert_eq!(response.id, super::Id::None);
assert!(matches!(response.payload, super::ResponsePayload::Error(_)));
}

#[test]
pub fn deser_complex_success() {
let response = r#"{
"result": {
"name": "california",
"population": 39250000,
"cities": [
"los angeles",
"san francisco"
]
}
}"#;
let response: super::JsonRpcResponse = serde_json::from_str(response).unwrap();
assert_eq!(response.id, super::Id::None);
assert!(matches!(
response.payload,
super::ResponsePayload::Success(_)
));
}
}

// Copyright 2019-2021 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
Loading