-
Notifications
You must be signed in to change notification settings - Fork 236
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
Conversation
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.
good start - light pass
making a few trait changes here |
crates/middleware/src/lib.rs
Outdated
fn send_transaction<'s: 'fut, 'a: 'fut, 'fut>( | ||
&'s self, | ||
tx: &'a N::TransactionRequest, | ||
) -> MwareFut<'fut, N::Receipt, TransportError> { |
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.
love the param over the network
{ | ||
fn set_gas(&mut self, gas: alloy_primitives::U256); |
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 seems to make sense to me - wdyt @mattsse
crates/middleware/src/lib.rs
Outdated
/// Middleware use a tower-like Layer abstraction | ||
pub trait MwareLayer<N: Network> { | ||
type Middleware<T: Transport>: Middleware<N, T>; | ||
|
||
fn layer<M, T>(&self, inner: M) -> Self::Middleware<T> | ||
where | ||
M: Middleware<N, T>, | ||
T: Transport; | ||
} |
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.
seems reasonable, curious to see how this ends up looking for various stacks
crates/middleware/src/lib.rs
Outdated
|
||
/// Middleware is parameterized with a network and a transport. The default | ||
/// transport is type-erased, but you can do `Middleware<N, Http>`. | ||
pub trait Middleware<N: Network, T: Transport = BoxTransport>: Send + Sync { |
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.
it might be worth doing a couple test-drives of this abstraction with an Ethereum mainnet and Celo transaction and having both providers side-by-side
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.
you mean ethereum and optimism?
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.
Yes or that, just recall the Celo txs having more differences, but actually we have the Optimism Deposit Tx which should be a good test too
crates/middleware/src/lib.rs
Outdated
&'s self, | ||
tx: &'a N::TransactionRequest, |
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.
ok so we de-asynctraited the trait, i guess it makes some things easier, but havent fully realized it yet
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'll re-async-trait it. just wanted to make sure i understood the inner workings of async-trait so i could make better decisions about it
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 was optimistic that I would be able to introduce a non-boxpinned future type that would cover mware usecases, but that results in some type complexity explosion and extra indirection for to erase types of boxed fns 😮💨
|
||
#[inline] | ||
fn call(&mut self, req: Box<RawValue>) -> Self::Future { | ||
let replacement = self.client.clone(); |
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.
guessing this is ok to do?
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 is the tower-recommended pattern. It's permissible for the clone to not return ready on poll_ready
, so you replace to ensure the original is the one filling the service
let json = resp.text().await?; | ||
|
||
RawValue::from_string(json).map_err(|err| TransportError::deser_err(err, "")) |
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.
Maybe bytes faster here?
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.
RawValue
is a wrapper for str
(not &str
), and should re-use the String
's allocation. so it ought to be equivalent
match to_json_raw_value(&req) { | ||
Ok(raw) => JsonRpcFuture { | ||
state: States::Pending { | ||
fut: client.call(raw), | ||
}, | ||
_resp: std::marker::PhantomData, | ||
}, |
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.
really hoping for some giga-unsafe way to get us around these manual conversions
impl Clone for BoxTransport { | ||
fn clone(&self) -> Self { | ||
Self { | ||
inner: self.inner.clone_box(), | ||
} | ||
} | ||
} | ||
|
||
trait CloneTransport: Transport { | ||
fn clone_box(&self) -> Box<dyn CloneTransport + Send + Sync>; | ||
} | ||
|
||
impl<T> CloneTransport for T | ||
where | ||
T: Transport + Clone + Send + Sync, | ||
{ | ||
fn clone_box(&self) -> Box<dyn CloneTransport + Send + Sync> { | ||
Box::new(self.clone()) | ||
} | ||
} |
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.
Can be simplified by doing trait CloneTransport: Transport + Clone + Send + Sync {}
and using that inside of BoxTransport
? Or do we need all intermediate traits/should we seal them
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.
traits with a Clone
supertrait are never object safe as Sized
is a supertrait of Clone
. The CloneTransport
trait here is private, and only used by the Clone
impl on BoxTransport
, so we don't need to seal it. it's just invisible to lib consumers
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 code is basically a copy of the structure of tower's BoxCloneService setup. which I wanted to use, but doesn't have + Send + Sync
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 should also check to see that error handling is "nice" even when stacking many layers.
As-is right now I mainly see that we have removed generics from the providers, but the middleware abstraction looks somewhat similar with before eg fn inner()
, beyond using the more Tower-esque functions for stacking?
/// `true` if the transport is local. | ||
pub(crate) is_local: bool, |
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.
What's the difference between a local and non-local transport?
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 might write code to slam local nodes and not slam remote nodes
Basically the only way to achieve nice error types AND stacking AND useful object-safety is to have a single error type and force all middleware to use it. so that's the current plan
We can't have an associated type or a generic type on the middleware trait, as that would break |
Transports crate handles JSON-RPC semantics and is based on past work in ethers-rs