Improved RPC handling. WIP
This commit is contained in:
parent
bb0e28b8e3
commit
4a84b2f7cc
@ -22,3 +22,7 @@ futures = "0.1.25"
|
||||
error-chain = "0.12.0"
|
||||
tokio-timer = "0.2.10"
|
||||
dirs = "2.0.1"
|
||||
tokio-io = "0.1.12"
|
||||
smallvec = "0.6.10"
|
||||
fnv = "1.0.6"
|
||||
unsigned-varint = "0.2.2"
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::discovery::Discovery;
|
||||
use crate::rpc::{RPCEvent, RPCMessage, Rpc};
|
||||
use crate::rpc::{RPCEvent, RPCMessage, RPC};
|
||||
use crate::{error, NetworkConfig};
|
||||
use crate::{Topic, TopicHash};
|
||||
use futures::prelude::*;
|
||||
@ -29,7 +29,7 @@ pub struct Behaviour<TSubstream: AsyncRead + AsyncWrite> {
|
||||
/// The routing pub-sub mechanism for eth2.
|
||||
gossipsub: Gossipsub<TSubstream>,
|
||||
/// The serenity RPC specified in the wire-0 protocol.
|
||||
serenity_rpc: Rpc<TSubstream>,
|
||||
serenity_rpc: RPC<TSubstream>,
|
||||
/// Keep regular connection to peers and disconnect if absent.
|
||||
ping: Ping<TSubstream>,
|
||||
/// Kademlia for peer discovery.
|
||||
@ -57,7 +57,7 @@ impl<TSubstream: AsyncRead + AsyncWrite> Behaviour<TSubstream> {
|
||||
.with_keep_alive(false);
|
||||
|
||||
Ok(Behaviour {
|
||||
serenity_rpc: Rpc::new(log),
|
||||
serenity_rpc: RPC::new(log),
|
||||
gossipsub: Gossipsub::new(local_peer_id.clone(), net_conf.gs_config.clone()),
|
||||
discovery: Discovery::new(local_key, net_conf, log)?,
|
||||
ping: Ping::new(ping_config),
|
||||
|
@ -1,37 +1,37 @@
|
||||
use libp2p::core::protocols_handler::{
|
||||
KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr,
|
||||
SubstreamProtocol
|
||||
};
|
||||
use libp2p::core::upgrade::{InboundUpgrade, OutboundUpgrade};
|
||||
use super::protocol::{ProtocolId, RPCError, RPCProtocol, RPCRequest};
|
||||
use super::RPCEvent;
|
||||
use fnv::FnvHashMap;
|
||||
use futures::prelude::*;
|
||||
use libp2p::core::protocols_handler::{
|
||||
KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol,
|
||||
};
|
||||
use libp2p::core::upgrade::{self, InboundUpgrade, OutboundUpgrade, WriteOne};
|
||||
use smallvec::SmallVec;
|
||||
use std::{error, marker::PhantomData, time::Duration};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use wasm_timer::Instant;
|
||||
|
||||
/// The time (in seconds) before a substream that is awaiting a response times out.
|
||||
pub const RESPONSE_TIMEOUT: u64 = 9;
|
||||
|
||||
/// Implementation of `ProtocolsHandler` for the RPC protocol.
|
||||
pub struct RPCHandler<TSubstream> {
|
||||
|
||||
/// The upgrade for inbound substreams.
|
||||
listen_protocol: SubstreamProtocol<RPCProtocol>,
|
||||
|
||||
/// If `Some`, something bad happened and we should shut down the handler with an error.
|
||||
pending_error: Option<ProtocolsHandlerUpgrErr<RPCRequest::Error>>,
|
||||
pending_error: Option<ProtocolsHandlerUpgrErr<RPCError>>,
|
||||
|
||||
/// Queue of events to produce in `poll()`.
|
||||
events_out: SmallVec<[TOutEvent; 4]>,
|
||||
events_out: SmallVec<[RPCEvent; 4]>,
|
||||
|
||||
/// Queue of outbound substreams to open.
|
||||
dial_queue: SmallVec<[(usize,TOutProto); 4]>,
|
||||
dial_queue: SmallVec<[(usize, RPCRequest); 4]>,
|
||||
|
||||
/// Current number of concurrent outbound substreams being opened.
|
||||
dial_negotiated: u32,
|
||||
|
||||
/// Map of current substreams awaiting a response to an RPC request.
|
||||
waiting_substreams: FnvHashMap<u64, SubstreamState<TSubstream>
|
||||
waiting_substreams: FnvHashMap<usize, SubstreamState<TSubstream>>,
|
||||
|
||||
/// Sequential Id for waiting substreams.
|
||||
current_substream_id: usize,
|
||||
@ -50,19 +50,21 @@ pub struct RPCHandler<TSubstream> {
|
||||
pub enum SubstreamState<TSubstream> {
|
||||
/// An outbound substream is waiting a response from the user.
|
||||
WaitingResponse {
|
||||
stream: <TSubstream>,
|
||||
timeout: Duration,
|
||||
}
|
||||
/// The negotiated substream.
|
||||
substream: upgrade::Negotiated<TSubstream>,
|
||||
/// The protocol that was negotiated.
|
||||
negotiated_protocol: ProtocolId,
|
||||
/// The time until we close the substream.
|
||||
timeout: Instant,
|
||||
},
|
||||
/// A response has been sent and we are waiting for the stream to close.
|
||||
ResponseSent(WriteOne<TSubstream, Vec<u8>)
|
||||
PendingWrite(WriteOne<upgrade::Negotiated<TSubstream>, Vec<u8>>),
|
||||
}
|
||||
|
||||
impl<TSubstream>
|
||||
RPCHandler<TSubstream>
|
||||
{
|
||||
impl<TSubstream> RPCHandler<TSubstream> {
|
||||
pub fn new(
|
||||
listen_protocol: SubstreamProtocol<RPCProtocol>,
|
||||
inactive_timeout: Duration
|
||||
inactive_timeout: Duration,
|
||||
) -> Self {
|
||||
RPCHandler {
|
||||
listen_protocol,
|
||||
@ -71,7 +73,7 @@ impl<TSubstream>
|
||||
dial_queue: SmallVec::new(),
|
||||
dial_negotiated: 0,
|
||||
waiting_substreams: FnvHashMap::default(),
|
||||
curent_substream_id: 0,
|
||||
current_substream_id: 0,
|
||||
max_dial_negotiated: 8,
|
||||
keep_alive: KeepAlive::Yes,
|
||||
inactive_timeout,
|
||||
@ -87,7 +89,7 @@ impl<TSubstream>
|
||||
///
|
||||
/// > **Note**: If you modify the protocol, modifications will only applies to future inbound
|
||||
/// > substreams, not the ones already being negotiated.
|
||||
pub fn listen_protocol_ref(&self) -> &SubstreamProtocol<TInProto> {
|
||||
pub fn listen_protocol_ref(&self) -> &SubstreamProtocol<RPCProtocol> {
|
||||
&self.listen_protocol
|
||||
}
|
||||
|
||||
@ -95,36 +97,35 @@ impl<TSubstream>
|
||||
///
|
||||
/// > **Note**: If you modify the protocol, modifications will only applies to future inbound
|
||||
/// > substreams, not the ones already being negotiated.
|
||||
pub fn listen_protocol_mut(&mut self) -> &mut SubstreamProtocol<TInProto> {
|
||||
pub fn listen_protocol_mut(&mut self) -> &mut SubstreamProtocol<RPCProtocol> {
|
||||
&mut self.listen_protocol
|
||||
}
|
||||
|
||||
/// Opens an outbound substream with `upgrade`.
|
||||
#[inline]
|
||||
pub fn send_request(&mut self, request_id, u64, upgrade: RPCRequest) {
|
||||
pub fn send_request(&mut self, request_id: usize, upgrade: RPCRequest) {
|
||||
self.keep_alive = KeepAlive::Yes;
|
||||
self.dial_queue.push((request_id, upgrade));
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSubstream> Default
|
||||
for RPCHandler<TSubstream>
|
||||
{
|
||||
impl<TSubstream> Default for RPCHandler<TSubstream> {
|
||||
fn default() -> Self {
|
||||
RPCHandler::new(SubstreamProtocol::new(RPCProtocol), Duration::from_secs(30))
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSubstream> ProtocolsHandler
|
||||
for RPCHandler<TSubstream>
|
||||
impl<TSubstream> ProtocolsHandler for RPCHandler<TSubstream>
|
||||
where
|
||||
TSubstream: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type InEvent = RPCEvent;
|
||||
type OutEvent = RPCEvent;
|
||||
type Error = ProtocolsHandlerUpgrErr<RPCRequest::Error>;
|
||||
type Error = ProtocolsHandlerUpgrErr<RPCError>;
|
||||
type Substream = TSubstream;
|
||||
type InboundProtocol = RPCProtocol;
|
||||
type OutboundProtocol = RPCRequest;
|
||||
type OutboundOpenInfo = u64; // request_id
|
||||
type OutboundOpenInfo = usize; // request_id
|
||||
|
||||
#[inline]
|
||||
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol> {
|
||||
@ -134,35 +135,43 @@ impl<TSubstream> ProtocolsHandler
|
||||
#[inline]
|
||||
fn inject_fully_negotiated_inbound(
|
||||
&mut self,
|
||||
out: RPCProtocol::Output,
|
||||
out: <RPCProtocol as InboundUpgrade<TSubstream>>::Output,
|
||||
) {
|
||||
let (stream, req) = out;
|
||||
// drop the stream and return a 0 id for goodbye "requests"
|
||||
if let req @ RPCRequest::Goodbye(_) = req {
|
||||
self.events_out.push(RPCEvent::Request(0, req));
|
||||
return;
|
||||
}
|
||||
let (substream, req, negotiated_protocol) = out;
|
||||
// drop the stream and return a 0 id for goodbye "requests"
|
||||
if let r @ RPCRequest::Goodbye(_) = req {
|
||||
self.events_out.push(RPCEvent::Request(0, r));
|
||||
return;
|
||||
}
|
||||
|
||||
// New inbound request. Store the stream and tag the output.
|
||||
let awaiting_stream = SubstreamState::WaitingResponse { stream, timeout: Instant::now() + Duration::from_secs(RESPONSE_TIMEOUT) };
|
||||
self.waiting_substreams.insert(self.current_substream_id, awaiting_stream);
|
||||
let awaiting_stream = SubstreamState::WaitingResponse {
|
||||
substream,
|
||||
negotiated_protocol,
|
||||
timeout: Instant::now() + Duration::from_secs(RESPONSE_TIMEOUT),
|
||||
};
|
||||
self.waiting_substreams
|
||||
.insert(self.current_substream_id, awaiting_stream);
|
||||
|
||||
self.events_out.push(RPCEvent::Request(self.current_substream_id, req));
|
||||
self.events_out
|
||||
.push(RPCEvent::Request(self.current_substream_id, req));
|
||||
self.current_substream_id += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inject_fully_negotiated_outbound(
|
||||
&mut self,
|
||||
out: RPCResponse,
|
||||
request_id : Self::OutboundOpenInfo,
|
||||
out: <RPCRequest as OutboundUpgrade<TSubstream>>::Output,
|
||||
request_id: Self::OutboundOpenInfo,
|
||||
) {
|
||||
self.dial_negotiated -= 1;
|
||||
|
||||
if self.dial_negotiated == 0 && self.dial_queue.is_empty() && self.waiting_substreams.is_empty() {
|
||||
if self.dial_negotiated == 0
|
||||
&& self.dial_queue.is_empty()
|
||||
&& self.waiting_substreams.is_empty()
|
||||
{
|
||||
self.keep_alive = KeepAlive::Until(Instant::now() + self.inactive_timeout);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
self.keep_alive = KeepAlive::Yes;
|
||||
}
|
||||
|
||||
@ -177,10 +186,19 @@ impl<TSubstream> ProtocolsHandler
|
||||
RPCEvent::Request(rpc_id, req) => self.send_request(rpc_id, req),
|
||||
RPCEvent::Response(rpc_id, res) => {
|
||||
// check if the stream matching the response still exists
|
||||
if let Some(mut waiting_stream) = self.waiting_substreams.get_mut(&rpc_id) {
|
||||
// only send one response per stream. This must be in the waiting state.
|
||||
if let SubstreamState::WaitingResponse {substream, .. } = waiting_stream {
|
||||
waiting_stream = SubstreamState::PendingWrite(upgrade::write_one(substream, res));
|
||||
if let Some(waiting_stream) = self.waiting_substreams.get_mut(&rpc_id) {
|
||||
// only send one response per stream. This must be in the waiting state.
|
||||
if let SubstreamState::WaitingResponse {
|
||||
substream,
|
||||
negotiated_protocol,
|
||||
..
|
||||
} = *waiting_stream
|
||||
{
|
||||
*waiting_stream = SubstreamState::PendingWrite(upgrade::write_one(
|
||||
substream,
|
||||
res.encode(negotiated_protocol)
|
||||
.expect("Response should always be encodeable"),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,6 +213,7 @@ impl<TSubstream> ProtocolsHandler
|
||||
<Self::OutboundProtocol as OutboundUpgrade<Self::Substream>>::Error,
|
||||
>,
|
||||
) {
|
||||
dbg!(error);
|
||||
if self.pending_error.is_none() {
|
||||
self.pending_error = Some(error);
|
||||
}
|
||||
@ -217,20 +236,24 @@ impl<TSubstream> ProtocolsHandler
|
||||
|
||||
// prioritise sending responses for waiting substreams
|
||||
self.waiting_substreams.retain(|_k, mut waiting_stream| {
|
||||
match waiting_stream => {
|
||||
match waiting_stream {
|
||||
SubstreamState::PendingWrite(write_one) => {
|
||||
match write_one.poll() => {
|
||||
match write_one.poll() {
|
||||
Ok(Async::Ready(_socket)) => false,
|
||||
Ok(Async::NotReady()) => true,
|
||||
Err(_e) => {
|
||||
Ok(Async::NotReady) => true,
|
||||
Err(_e) => {
|
||||
//TODO: Add logging
|
||||
// throw away streams that error
|
||||
false
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
SubstreamState::WaitingResponse { timeout, .. } => {
|
||||
if Instant::now() > *timeout {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
},
|
||||
SubstreamState::WaitingResponse { timeout, .. } => {
|
||||
if Instant::now() > timeout { false} else { true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -4,23 +4,6 @@ use ssz::{impl_decode_via_from, impl_encode_via_from};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use types::{Epoch, Hash256, Slot};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RPCResponse {
|
||||
Hello(HelloMessage),
|
||||
Goodbye, // empty value - required for protocol handler
|
||||
BeaconBlockRoots(BeaconBlockRootsResponse),
|
||||
BeaconBlockHeaders(BeaconBlockHeadersResponse),
|
||||
BeaconBlockBodies(BeaconBlockBodiesResponse),
|
||||
BeaconChainState(BeaconChainStateResponse),
|
||||
}
|
||||
|
||||
pub enum ResponseCode {
|
||||
Success = 0,
|
||||
EncodingError = 1,
|
||||
InvalidRequest = 2,
|
||||
ServerError = 3,
|
||||
}
|
||||
|
||||
/* Request/Response data structures for RPC methods */
|
||||
|
||||
/* Requests */
|
||||
@ -78,7 +61,6 @@ impl From<u64> for Goodbye {
|
||||
}
|
||||
}
|
||||
|
||||
impl_encode_via_from!(Goodbye, u64);
|
||||
impl_decode_via_from!(Goodbye, u64);
|
||||
|
||||
/// Request a number of beacon block roots from a peer.
|
||||
@ -108,7 +90,7 @@ pub struct BlockRootSlot {
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
/// The response of a beacl block roots request.
|
||||
/// The response of a beacon block roots request.
|
||||
impl BeaconBlockRootsResponse {
|
||||
/// Returns `true` if each `self.roots.slot[i]` is higher than the preceding `i`.
|
||||
pub fn slots_are_ascending(&self) -> bool {
|
||||
|
@ -1,39 +1,41 @@
|
||||
/// The Ethereum 2.0 Wire Protocol
|
||||
///
|
||||
/// This protocol is a purpose built Ethereum 2.0 libp2p protocol. It's role is to facilitate
|
||||
/// direct peer-to-peer communication primarily for sending/receiving chain information for
|
||||
/// syncing.
|
||||
///
|
||||
pub mod methods;
|
||||
mod protocol;
|
||||
|
||||
///! The Ethereum 2.0 Wire Protocol
|
||||
///!
|
||||
///! This protocol is a purpose built Ethereum 2.0 libp2p protocol. It's role is to facilitate
|
||||
///! direct peer-to-peer communication primarily for sending/receiving chain information for
|
||||
///! syncing.
|
||||
use futures::prelude::*;
|
||||
use libp2p::core::protocols_handler::{OneShotHandler, ProtocolsHandler};
|
||||
use handler::RPCHandler;
|
||||
use libp2p::core::protocols_handler::ProtocolsHandler;
|
||||
use libp2p::core::swarm::{
|
||||
ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters,
|
||||
};
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
pub use methods::{HelloMessage, RPCResponse};
|
||||
pub use protocol::{RPCProtocol, RPCRequest};
|
||||
pub use methods::HelloMessage;
|
||||
pub use protocol::{RPCProtocol, RPCRequest, RPCResponse};
|
||||
use slog::o;
|
||||
use std::marker::PhantomData;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
mod handler;
|
||||
pub mod methods;
|
||||
mod protocol;
|
||||
mod request_response;
|
||||
|
||||
/// The return type used in the behaviour and the resultant event from the protocols handler.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RPCEvent {
|
||||
/// A request that was received from the RPC protocol. The first parameter is a sequential
|
||||
/// id which tracks an awaiting substream for the response.
|
||||
Request(u64, RPCRequest),
|
||||
Request(usize, RPCRequest),
|
||||
|
||||
/// A response that has been received from the RPC protocol. The first parameter returns
|
||||
/// that which was sent with the corresponding request.
|
||||
Response(u64, RPCResponse),
|
||||
Response(usize, RPCResponse),
|
||||
}
|
||||
|
||||
/// Rpc implements the libp2p `NetworkBehaviour` trait and therefore manages network-level
|
||||
/// Implements the libp2p `NetworkBehaviour` trait and therefore manages network-level
|
||||
/// logic.
|
||||
pub struct Rpc<TSubstream> {
|
||||
pub struct RPC<TSubstream> {
|
||||
/// Queue of events to processed.
|
||||
events: Vec<NetworkBehaviourAction<RPCEvent, RPCMessage>>,
|
||||
/// Pins the generic substream.
|
||||
@ -42,10 +44,10 @@ pub struct Rpc<TSubstream> {
|
||||
_log: slog::Logger,
|
||||
}
|
||||
|
||||
impl<TSubstream> Rpc<TSubstream> {
|
||||
impl<TSubstream> RPC<TSubstream> {
|
||||
pub fn new(log: &slog::Logger) -> Self {
|
||||
let log = log.new(o!("Service" => "Libp2p-RPC"));
|
||||
Rpc {
|
||||
RPC {
|
||||
events: Vec::new(),
|
||||
marker: PhantomData,
|
||||
_log: log,
|
||||
@ -63,7 +65,7 @@ impl<TSubstream> Rpc<TSubstream> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSubstream> NetworkBehaviour for Rpc<TSubstream>
|
||||
impl<TSubstream> NetworkBehaviour for RPC<TSubstream>
|
||||
where
|
||||
TSubstream: AsyncRead + AsyncWrite,
|
||||
{
|
||||
@ -95,12 +97,6 @@ where
|
||||
source: PeerId,
|
||||
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
|
||||
) {
|
||||
// ignore successful send events
|
||||
let event = match event {
|
||||
HandlerEvent::Rx(event) => event,
|
||||
HandlerEvent::Sent => return,
|
||||
};
|
||||
|
||||
// send the event to the user
|
||||
self.events
|
||||
.push(NetworkBehaviourAction::GenerateEvent(RPCMessage::RPC(
|
||||
@ -129,26 +125,3 @@ pub enum RPCMessage {
|
||||
RPC(PeerId, RPCEvent),
|
||||
PeerDialed(PeerId),
|
||||
}
|
||||
|
||||
/// The output type received from the `OneShotHandler`.
|
||||
#[derive(Debug)]
|
||||
pub enum HandlerEvent {
|
||||
/// An RPC was received from a remote.
|
||||
Rx(RPCEvent),
|
||||
/// An RPC was sent.
|
||||
Sent,
|
||||
}
|
||||
|
||||
impl From<RPCEvent> for HandlerEvent {
|
||||
#[inline]
|
||||
fn from(rpc: RPCEvent) -> HandlerEvent {
|
||||
HandlerEvent::Rx(rpc)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for HandlerEvent {
|
||||
#[inline]
|
||||
fn from(_: ()) -> HandlerEvent {
|
||||
HandlerEvent::Sent
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
use super::methods::*;
|
||||
use super::request_response::{rpc_request_response, RPCRequestResponse};
|
||||
use futures::future::Future;
|
||||
use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo};
|
||||
use ssz::{Decode, Encode};
|
||||
use std::hash::Hasher;
|
||||
use std::io;
|
||||
use std::iter;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::prelude::future::MapErr;
|
||||
use tokio::util::FutureExt;
|
||||
|
||||
/// The maximum bytes that can be sent across the RPC.
|
||||
const MAX_RPC_SIZE: usize = 4_194_304; // 4M
|
||||
@ -33,42 +36,6 @@ impl UpgradeInfo for RPCProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/* Inbound upgrade */
|
||||
|
||||
// The inbound protocol reads the request, decodes it and returns the stream to the protocol
|
||||
// handler to respond to once ready.
|
||||
|
||||
type FnDecodeRPCEvent<TSocket> = fn(
|
||||
upgrade::Negotiated<TSocket>,
|
||||
Vec<u8>,
|
||||
(),
|
||||
) -> Result<(upgrade::Negotiated<TSocket>, RPCEvent), RPCError>;
|
||||
|
||||
impl<TSocket> InboundUpgrade<TSocket> for RPCProtocol
|
||||
where
|
||||
TSocket: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Output = (upgrade::Negotiated<TSocket>, RPCEvent);
|
||||
type Error = RPCError;
|
||||
type Future = upgrade::ReadRespond<upgrade::Negotiated<TSocket>, (), FnDecodeRPCEvent<TSocket>>;
|
||||
|
||||
fn upgrade_inbound(
|
||||
self,
|
||||
socket: upgrade::Negotiated<TSocket>,
|
||||
protocol: Self::Info,
|
||||
) -> Self::Future {
|
||||
upgrade::read_respond(socket, MAX_RPC_SIZE, (), |socket, packet, ()| {
|
||||
Ok((socket, decode_request(packet, protocol)?))
|
||||
})
|
||||
.timeout(Duration::from_secs(RESPONSE_TIMEOUT))
|
||||
}
|
||||
}
|
||||
|
||||
/* Outbound request */
|
||||
|
||||
// Combines all the RPC requests into a single enum to implement `UpgradeInfo` and
|
||||
// `OutboundUpgrade`
|
||||
|
||||
/// The raw protocol id sent over the wire.
|
||||
type RawProtocolId = Vec<u8>;
|
||||
|
||||
@ -86,38 +53,100 @@ pub struct ProtocolId {
|
||||
|
||||
/// An RPC protocol ID.
|
||||
impl ProtocolId {
|
||||
pub fn new(message_name: String, version: usize, encoding: String) -> Self {
|
||||
pub fn new(message_name: &str, version: usize, encoding: &str) -> Self {
|
||||
ProtocolId {
|
||||
message_name,
|
||||
message_name: message_name.into(),
|
||||
version,
|
||||
encoding,
|
||||
encoding: encoding.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a raw RPC protocol id string into an `RPCProtocolId`
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, RPCError> {
|
||||
let protocol_string = String::from_utf8(bytes.as_vec())
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, RPCError> {
|
||||
let protocol_string = String::from_utf8(bytes.to_vec())
|
||||
.map_err(|_| RPCError::InvalidProtocol("Invalid protocol Id"))?;
|
||||
let protocol_string = protocol_string.as_str().split('/');
|
||||
let protocol_list: Vec<&str> = protocol_string.as_str().split('/').take(5).collect();
|
||||
|
||||
if protocol_list.len() != 5 {
|
||||
return Err(RPCError::InvalidProtocol("Not enough '/'"));
|
||||
}
|
||||
|
||||
Ok(ProtocolId {
|
||||
message_name: protocol_string[3],
|
||||
version: protocol_string[4],
|
||||
encoding: protocol_string[5],
|
||||
message_name: protocol_list[3].into(),
|
||||
version: protocol_list[4]
|
||||
.parse()
|
||||
.map_err(|_| RPCError::InvalidProtocol("Invalid version"))?,
|
||||
encoding: protocol_list[5].into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<RawProtocolId> for ProtocolId {
|
||||
fn into(&self) -> [u8] {
|
||||
&format!(
|
||||
fn into(self) -> RawProtocolId {
|
||||
format!(
|
||||
"{}/{}/{}/{}",
|
||||
PROTOCOL_PREFIX, self.message_name, self.version, self.encoding
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/* Inbound upgrade */
|
||||
|
||||
// The inbound protocol reads the request, decodes it and returns the stream to the protocol
|
||||
// handler to respond to once ready.
|
||||
|
||||
type FnDecodeRPCEvent<TSocket> =
|
||||
fn(
|
||||
upgrade::Negotiated<TSocket>,
|
||||
Vec<u8>,
|
||||
&'static [u8], // protocol id
|
||||
) -> Result<(upgrade::Negotiated<TSocket>, RPCRequest, ProtocolId), RPCError>;
|
||||
|
||||
impl<TSocket> InboundUpgrade<TSocket> for RPCProtocol
|
||||
where
|
||||
TSocket: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Output = (upgrade::Negotiated<TSocket>, RPCRequest, ProtocolId);
|
||||
type Error = RPCError;
|
||||
type Future = MapErr<
|
||||
tokio_timer::Timeout<
|
||||
upgrade::ReadRespond<
|
||||
upgrade::Negotiated<TSocket>,
|
||||
Self::Info,
|
||||
FnDecodeRPCEvent<TSocket>,
|
||||
>,
|
||||
>,
|
||||
fn(tokio::timer::timeout::Error<RPCError>) -> RPCError,
|
||||
>;
|
||||
|
||||
fn upgrade_inbound(
|
||||
self,
|
||||
socket: upgrade::Negotiated<TSocket>,
|
||||
protocol: &'static [u8],
|
||||
) -> Self::Future {
|
||||
upgrade::read_respond(socket, MAX_RPC_SIZE, protocol, {
|
||||
|socket, packet, protocol| {
|
||||
let protocol_id = ProtocolId::from_bytes(protocol)?;
|
||||
Ok((
|
||||
socket,
|
||||
RPCRequest::decode(packet, protocol_id)?,
|
||||
protocol_id,
|
||||
))
|
||||
}
|
||||
}
|
||||
as FnDecodeRPCEvent<TSocket>)
|
||||
.timeout(Duration::from_secs(RESPONSE_TIMEOUT))
|
||||
.map_err(RPCError::from)
|
||||
}
|
||||
}
|
||||
|
||||
/* Outbound request */
|
||||
|
||||
// Combines all the RPC requests into a single enum to implement `UpgradeInfo` and
|
||||
// `OutboundUpgrade`
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RPCRequest {
|
||||
Hello(HelloMessage),
|
||||
@ -154,18 +183,16 @@ impl RPCRequest {
|
||||
RPCRequest::BeaconBlockBodies(_) => {
|
||||
vec![ProtocolId::new("beacon_block_bodies", 1, "ssz").into()]
|
||||
}
|
||||
RPCRequest::BeaconBlockState(_) => {
|
||||
RPCRequest::BeaconChainState(_) => {
|
||||
vec![ProtocolId::new("beacon_block_state", 1, "ssz").into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the Request object based on the negotiated protocol.
|
||||
pub fn encode(&self, protocol: RawProtocolId) -> Result<Vec<u8>, io::Error> {
|
||||
// Assume select has given a supported protocol.
|
||||
let protocol = ProtocolId::from_bytes(protocol)?;
|
||||
pub fn encode(&self, protocol: ProtocolId) -> Result<Vec<u8>, RPCError> {
|
||||
// Match on the encoding and in the future, the version
|
||||
match protocol.encoding {
|
||||
match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(self.ssz_encode()),
|
||||
_ => {
|
||||
return Err(RPCError::Custom(format!(
|
||||
@ -176,7 +203,7 @@ impl RPCRequest {
|
||||
}
|
||||
}
|
||||
|
||||
fn ssz_encode(&self) {
|
||||
fn ssz_encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
RPCRequest::Hello(req) => req.as_ssz_bytes(),
|
||||
RPCRequest::Goodbye(req) => req.as_ssz_bytes(),
|
||||
@ -186,177 +213,263 @@ impl RPCRequest {
|
||||
RPCRequest::BeaconChainState(req) => req.as_ssz_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
// This function can be extended to provide further logic for supporting various protocol versions/encoding
|
||||
/// Decodes a request received from our peer.
|
||||
pub fn decode(packet: Vec<u8>, protocol: ProtocolId, response_code: ResponseCode) -> Result<Self, RPCError> {
|
||||
|
||||
match response_code {
|
||||
ResponseCode::
|
||||
|
||||
|
||||
|
||||
match protocol.message_name.as_str() {
|
||||
"hello" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)),
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO encoding")),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO version")),
|
||||
},
|
||||
"goodbye" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)),
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown GOODBYE encoding")),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown GOODBYE version")),
|
||||
},
|
||||
"beacon_block_roots" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCRequest::BeaconBlockRoots(
|
||||
BeaconBlockRootsRequest::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_headers" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCRequest::BeaconBlockHeaders(
|
||||
BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_bodies" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCRequest::BeaconBlockBodies(
|
||||
BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES version",
|
||||
)),
|
||||
},
|
||||
"beacon_chain_state" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCRequest::BeaconChainState(
|
||||
BeaconChainStateRequest::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE version",
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Response Type */
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RPCResponse {
|
||||
/// A HELLO message.
|
||||
Hello(HelloMessage),
|
||||
/// An empty field returned from sending a GOODBYE request.
|
||||
Goodbye, // empty value - required for protocol handler
|
||||
/// A response to a get BEACON_BLOCK_ROOTS request.
|
||||
BeaconBlockRoots(BeaconBlockRootsResponse),
|
||||
/// A response to a get BEACON_BLOCK_HEADERS request.
|
||||
BeaconBlockHeaders(BeaconBlockHeadersResponse),
|
||||
/// A response to a get BEACON_BLOCK_BODIES request.
|
||||
BeaconBlockBodies(BeaconBlockBodiesResponse),
|
||||
/// A response to a get BEACON_CHAIN_STATE request.
|
||||
BeaconChainState(BeaconChainStateResponse),
|
||||
/// The Error returned from the peer during a request.
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub enum ResponseCode {
|
||||
Success = 0,
|
||||
EncodingError = 1,
|
||||
InvalidRequest = 2,
|
||||
ServerError = 3,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
||||
impl From<u64> for ResponseCode {
|
||||
fn from(val: u64) -> ResponseCode {
|
||||
match val {
|
||||
0 => ResponseCode::Success,
|
||||
1 => ResponseCode::EncodingError,
|
||||
2 => ResponseCode::InvalidRequest,
|
||||
3 => ResponseCode::ServerError,
|
||||
_ => ResponseCode::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RPCResponse {
|
||||
/// Decodes a response that was received on the same stream as a request. The response type should
|
||||
/// therefore match the request protocol type.
|
||||
fn decode(packet: Vec<u8>, protocol: ProtocolId) -> Result<Self, RPCError> {
|
||||
match protocol.message_name.as_str() {
|
||||
"hello" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCResponse::Hello(HelloMessage::from_ssz_bytes(&packet)?)),
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO encoding")),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO version")),
|
||||
},
|
||||
"goodbye" => Err(RPCError::Custom(
|
||||
"GOODBYE should not have a response".into(),
|
||||
)),
|
||||
"beacon_block_roots" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCResponse::BeaconBlockRoots(
|
||||
BeaconBlockRootsResponse::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_headers" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCResponse::BeaconBlockHeaders(
|
||||
BeaconBlockHeadersResponse { headers: packet },
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_bodies" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCResponse::BeaconBlockBodies(BeaconBlockBodiesResponse {
|
||||
block_bodies: packet,
|
||||
})),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES version",
|
||||
)),
|
||||
},
|
||||
"beacon_chain_state" => match protocol.version {
|
||||
1 => match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(RPCResponse::BeaconChainState(
|
||||
BeaconChainStateResponse::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE version",
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the Response object based on the negotiated protocol.
|
||||
pub fn encode(&self, protocol: ProtocolId) -> Result<Vec<u8>, RPCError> {
|
||||
// Match on the encoding and in the future, the version
|
||||
match protocol.encoding.as_str() {
|
||||
"ssz" => Ok(self.ssz_encode()),
|
||||
_ => {
|
||||
return Err(RPCError::Custom(format!(
|
||||
"Unknown Encoding: {}",
|
||||
protocol.encoding
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ssz_encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
RPCResponse::Hello(res) => res.as_ssz_bytes(),
|
||||
RPCResponse::Goodbye => unreachable!(),
|
||||
RPCResponse::BeaconBlockRoots(res) => res.as_ssz_bytes(),
|
||||
RPCResponse::BeaconBlockHeaders(res) => res.headers, // already raw bytes
|
||||
RPCResponse::BeaconBlockBodies(res) => res.block_bodies, // already raw bytes
|
||||
RPCResponse::BeaconChainState(res) => res.as_ssz_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Outbound upgrades */
|
||||
|
||||
impl<TSocket> OutboundUpgrade<TSocket> for RPCRequest
|
||||
where
|
||||
TSocket: AsyncWrite,
|
||||
TSocket: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Output = RPCResponse;
|
||||
type Error = RPCResponse;
|
||||
type Future = upgrade::RequestResponse<upgrade::Negotiated<TSocket>>;
|
||||
type Error = RPCError;
|
||||
type Future = MapErr<
|
||||
tokio_timer::Timeout<RPCRequestResponse<upgrade::Negotiated<TSocket>, Vec<u8>>>,
|
||||
fn(tokio::timer::timeout::Error<RPCError>) -> RPCError,
|
||||
>;
|
||||
|
||||
fn upgrade_outbound(
|
||||
self,
|
||||
socket: upgrade::Negotiated<TSocket>,
|
||||
protocol: Self::Info,
|
||||
) -> Self::Future {
|
||||
let bytes = self.encode(protocol);
|
||||
wait_for_response = if let RPCRequest::Goodbye(_) = self {
|
||||
false
|
||||
let protocol_id = ProtocolId::from_bytes(&protocol)
|
||||
.expect("Protocol ID must be valid for outbound requests");
|
||||
|
||||
let request_bytes = self
|
||||
.encode(protocol_id)
|
||||
.expect("Should be able to encode a supported protocol");
|
||||
// if sending a goodbye, drop the stream and return an empty GOODBYE response
|
||||
let short_circuit_return = if let RPCRequest::Goodbye(_) = self {
|
||||
Some(RPCResponse::Goodbye)
|
||||
} else {
|
||||
true
|
||||
None
|
||||
};
|
||||
// TODO: Reimplement request_response
|
||||
upgrade::request_response(socket, bytes, MAX_RPC_SIZE, protocol, |packet, protocol| {
|
||||
Ok(decode_response(packet, protocol)?)
|
||||
})
|
||||
rpc_request_response(
|
||||
socket,
|
||||
request_bytes,
|
||||
MAX_RPC_SIZE,
|
||||
short_circuit_return,
|
||||
protocol_id,
|
||||
)
|
||||
.timeout(Duration::from_secs(RESPONSE_TIMEOUT))
|
||||
}
|
||||
}
|
||||
|
||||
/* Decoding for Requests/Responses */
|
||||
|
||||
// This function can be extended to provide further logic for supporting various protocol versions/encoding
|
||||
fn decode_request(packet: Vec<u8>, protocol: ProtocolId) -> Result<RPCRequest, io::Error> {
|
||||
let protocol_id = ProtocolId::from_bytes(protocol);
|
||||
|
||||
match protocol_id.message_name {
|
||||
"hello" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)),
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO encoding")),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO version")),
|
||||
},
|
||||
"goodbye" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)),
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown GOODBYE encoding")),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown GOODBYE version")),
|
||||
},
|
||||
"beacon_block_roots" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCRequest::BeaconBlockRooots(
|
||||
BeaconBlockRootsRequest::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_headers" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCRequest::BeaconBlockHeaders(
|
||||
BeaconBlockHeadersRequest::from_ssz_bytes(&packet),
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_bodies" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCRequest::BeaconBlockBodies(
|
||||
BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES version",
|
||||
)),
|
||||
},
|
||||
"beacon_chain_state" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCRequest::BeaconChainState(
|
||||
BeaconChainStateRequest::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE version",
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a response that was received on the same stream as a request. The response type should
|
||||
/// therefore match the request protocol type.
|
||||
fn decode_response(packet: Vec<u8>, protocol: RawProtocolId) -> Result<RPCResponse, RPCError> {
|
||||
let protocol_id = ProtocolId::from_bytes(protocol)?;
|
||||
|
||||
match protocol_id.message_name {
|
||||
"hello" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCResponse::Hello(HelloMessage::from_ssz_bytes(&packet)?)),
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO encoding")),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol("Unknown HELLO version")),
|
||||
},
|
||||
"goodbye" => Err(RPCError::Custom("GOODBYE should not have a response")),
|
||||
"beacon_block_roots" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCResponse::BeaconBlockRoots(
|
||||
BeaconBlockRootsResponse::from_ssz_bytes(&packet)?,
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_ROOTS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_headers" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCResponse::BeaconBlockHeaders(
|
||||
BeaconBlockHeadersResponse { headers: packet },
|
||||
)),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_HEADERS version",
|
||||
)),
|
||||
},
|
||||
"beacon_block_bodies" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(RPCResponse::BeaconBlockBodies(BeaconBlockBodiesResponse {
|
||||
block_bodies: packet,
|
||||
})),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_BLOCK_BODIES version",
|
||||
)),
|
||||
},
|
||||
"beacon_chain_state" => match protocol_id.version {
|
||||
"1" => match protocol_id.encoding {
|
||||
"ssz" => Ok(BeaconChainStateRequest::from_ssz_bytes(&packet)?),
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE encoding",
|
||||
)),
|
||||
},
|
||||
_ => Err(RPCError::InvalidProtocol(
|
||||
"Unknown BEACON_CHAIN_STATE version",
|
||||
)),
|
||||
},
|
||||
.map_err(RPCError::from)
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,8 +480,12 @@ pub enum RPCError {
|
||||
ReadError(upgrade::ReadOneError),
|
||||
/// Error when decoding the raw buffer from ssz.
|
||||
SSZDecodeError(ssz::DecodeError),
|
||||
/// Invalid Protocol ID
|
||||
/// Invalid Protocol ID.
|
||||
InvalidProtocol(&'static str),
|
||||
/// IO Error.
|
||||
IoError(io::Error),
|
||||
/// Waiting for a request/response timed out, or timer error'd.
|
||||
StreamTimeout,
|
||||
/// Custom message.
|
||||
Custom(String),
|
||||
}
|
||||
@ -386,3 +503,46 @@ impl From<ssz::DecodeError> for RPCError {
|
||||
RPCError::SSZDecodeError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<tokio::timer::timeout::Error<T>> for RPCError {
|
||||
fn from(err: tokio::timer::timeout::Error<T>) -> Self {
|
||||
if err.is_elapsed() {
|
||||
RPCError::StreamTimeout
|
||||
} else {
|
||||
RPCError::Custom("Stream timer failed".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for RPCError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
RPCError::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Error trait is required for `ProtocolsHandler`
|
||||
impl std::fmt::Display for RPCError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
RPCError::ReadError(ref err) => write!(f, "Error while reading from socket: {}", err),
|
||||
RPCError::SSZDecodeError(ref err) => write!(f, "Error while decoding ssz: {:?}", err),
|
||||
RPCError::InvalidProtocol(ref err) => write!(f, "Invalid Protocol: {}", err),
|
||||
RPCError::IoError(ref err) => write!(f, "IO Error: {}", err),
|
||||
RPCError::StreamTimeout => write!(f, "Stream Timeout"),
|
||||
RPCError::Custom(ref err) => write!(f, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RPCError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
RPCError::ReadError(ref err) => Some(err),
|
||||
RPCError::SSZDecodeError(ref err) => None,
|
||||
RPCError::InvalidProtocol(ref err) => None,
|
||||
RPCError::IoError(ref err) => Some(err),
|
||||
RPCError::StreamTimeout => None,
|
||||
RPCError::Custom(ref err) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
239
beacon_node/eth2-libp2p/src/rpc/request_response.rs
Normal file
239
beacon_node/eth2-libp2p/src/rpc/request_response.rs
Normal file
@ -0,0 +1,239 @@
|
||||
use super::protocol::{ProtocolId, RPCError, RPCResponse, ResponseCode};
|
||||
use futures::prelude::*;
|
||||
use futures::try_ready;
|
||||
use libp2p::core::upgrade::{read_one, ReadOne, ReadOneError};
|
||||
use std::mem;
|
||||
use tokio_io::{io, AsyncRead, AsyncWrite};
|
||||
|
||||
/// Sends a message over a socket, waits for a response code, then optionally waits for a response.
|
||||
///
|
||||
/// The response code is a 1-byte code which determines whether the request succeeded or not.
|
||||
/// Depending on the response-code, an error may be returned. On success, a response is then
|
||||
/// retrieved if required.
|
||||
|
||||
/// This function also gives an option to terminate the socket and return a default value, allowing for
|
||||
/// one-shot requests.
|
||||
///
|
||||
/// The `short_circuit_return` parameter, if specified, returns the value without awaiting for a
|
||||
/// response to a request and performing the logic in `then`.
|
||||
#[inline]
|
||||
pub fn rpc_request_response<TSocket, TData>(
|
||||
socket: TSocket,
|
||||
data: TData, // data sent as a request
|
||||
max_size: usize, // maximum bytes to read in a response
|
||||
short_circuit_return: Option<RPCResponse>, // default value to return right after a request, do not wait for a response
|
||||
protocol: ProtocolId, // the protocol being negotiated
|
||||
) -> RPCRequestResponse<TSocket, TData>
|
||||
where
|
||||
TSocket: AsyncRead + AsyncWrite,
|
||||
TData: AsRef<[u8]>,
|
||||
{
|
||||
RPCRequestResponse {
|
||||
protocol,
|
||||
inner: RPCRequestResponseInner::Write(
|
||||
write_one(socket, data).inner,
|
||||
max_size,
|
||||
short_circuit_return,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that makes `rpc_request_response` work.
|
||||
pub struct RPCRequestResponse<TSocket, TData = Vec<u8>> {
|
||||
protocol: ProtocolId,
|
||||
inner: RPCRequestResponseInner<TSocket, TData>,
|
||||
}
|
||||
|
||||
enum RPCRequestResponseInner<TSocket, TData> {
|
||||
// We need to write data to the socket.
|
||||
Write(WriteOneInner<TSocket, TData>, usize, Option<RPCResponse>),
|
||||
// We need to read the response code.
|
||||
ReadResponseCode(io::ReadExact<TSocket, io::Window<Vec<u8>>>, usize),
|
||||
// We need to read a final data packet. The second parameter is the response code
|
||||
Read(ReadOne<TSocket>, ResponseCode),
|
||||
// An error happened during the processing.
|
||||
Poisoned,
|
||||
}
|
||||
|
||||
impl<TSocket, TData> Future for RPCRequestResponse<TSocket, TData>
|
||||
where
|
||||
TSocket: AsyncRead + AsyncWrite,
|
||||
TData: AsRef<[u8]>,
|
||||
{
|
||||
type Item = RPCResponse;
|
||||
type Error = RPCError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
match mem::replace(&mut self.inner, RPCRequestResponseInner::Poisoned) {
|
||||
RPCRequestResponseInner::Write(mut inner, max_size, sc_return) => {
|
||||
match inner.poll().map_err(ReadOneError::Io)? {
|
||||
Async::Ready(socket) => {
|
||||
// short-circuit the future if `short_circuit_return` is specified
|
||||
if let Some(return_val) = sc_return {
|
||||
return Ok(Async::Ready(return_val));
|
||||
}
|
||||
|
||||
// begin reading the 1-byte response code
|
||||
let mut data_buf = vec![0; 1];
|
||||
let mut data_buf = io::Window::new(data_buf);
|
||||
self.inner = RPCRequestResponseInner::ReadResponseCode(
|
||||
io::read_exact(socket, data_buf),
|
||||
max_size,
|
||||
);
|
||||
}
|
||||
Async::NotReady => {
|
||||
self.inner = RPCRequestResponseInner::Write(inner, max_size, sc_return);
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
}
|
||||
RPCRequestResponseInner::ReadResponseCode(mut inner, max_size) => {
|
||||
match inner.poll()? {
|
||||
Async::Ready((socket, data)) => {
|
||||
let response_code =
|
||||
ResponseCode::from(u64::from_be_bytes(data.into_inner()));
|
||||
// known response codes
|
||||
match response_code {
|
||||
ResponseCode::Success
|
||||
| ResponseCode::InvalidRequest
|
||||
| ResponseCode::ServerError => {
|
||||
// need to read another packet
|
||||
self.inner = RPCRequestResponseInner::Read(
|
||||
read_one(socket, max_size),
|
||||
response_code,
|
||||
)
|
||||
}
|
||||
ResponseCode::EncodingError => {
|
||||
// invalid encoding
|
||||
let response = RPCResponse::Error("Invalid Encoding".into());
|
||||
return Ok(Async::Ready(response));
|
||||
}
|
||||
ResponseCode::Unknown => {
|
||||
// unknown response code
|
||||
let response = RPCResponse::Error(format!(
|
||||
"Unknown response code: {}",
|
||||
response_code
|
||||
));
|
||||
return Ok(Async::Ready(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
Async::NotReady => {
|
||||
self.inner = RPCRequestResponseInner::ReadResponseCode(inner, max_size);
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
}
|
||||
RPCRequestResponseInner::Read(mut inner, response_code) => match inner.poll()? {
|
||||
Async::Ready(packet) => {
|
||||
return Ok(Async::Ready(RPCResponse::decode(
|
||||
packet,
|
||||
self.protocol,
|
||||
response_code,
|
||||
)?))
|
||||
}
|
||||
Async::NotReady => {
|
||||
self.inner = RPCRequestResponseInner::Read(inner, response_code);
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
},
|
||||
RPCRequestResponseInner::Poisoned => panic!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copied from rust-libp2p (https://github.com/libp2p/rust-libp2p) to access private members */
|
||||
|
||||
/// Send a message to the given socket, then shuts down the writing side.
|
||||
///
|
||||
/// > **Note**: Prepends a variable-length prefix indicate the length of the message. This is
|
||||
/// > compatible with what `read_one` expects.
|
||||
#[inline]
|
||||
pub fn write_one<TSocket, TData>(socket: TSocket, data: TData) -> WriteOne<TSocket, TData>
|
||||
where
|
||||
TSocket: AsyncWrite,
|
||||
TData: AsRef<[u8]>,
|
||||
{
|
||||
let len_data = build_int_buffer(data.as_ref().len());
|
||||
WriteOne {
|
||||
inner: WriteOneInner::WriteLen(io::write_all(socket, len_data), data),
|
||||
}
|
||||
}
|
||||
|
||||
enum WriteOneInner<TSocket, TData> {
|
||||
/// We need to write the data length to the socket.
|
||||
WriteLen(io::WriteAll<TSocket, io::Window<[u8; 10]>>, TData),
|
||||
/// We need to write the actual data to the socket.
|
||||
Write(io::WriteAll<TSocket, TData>),
|
||||
/// We need to shut down the socket.
|
||||
Shutdown(io::Shutdown<TSocket>),
|
||||
/// A problem happened during the processing.
|
||||
Poisoned,
|
||||
}
|
||||
|
||||
impl<TSocket, TData> Future for WriteOneInner<TSocket, TData>
|
||||
where
|
||||
TSocket: AsyncWrite,
|
||||
TData: AsRef<[u8]>,
|
||||
{
|
||||
type Item = TSocket;
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
match mem::replace(self, WriteOneInner::Poisoned) {
|
||||
WriteOneInner::WriteLen(mut inner, data) => match inner.poll()? {
|
||||
Async::Ready((socket, _)) => {
|
||||
*self = WriteOneInner::Write(io::write_all(socket, data));
|
||||
}
|
||||
Async::NotReady => {
|
||||
*self = WriteOneInner::WriteLen(inner, data);
|
||||
}
|
||||
},
|
||||
WriteOneInner::Write(mut inner) => match inner.poll()? {
|
||||
Async::Ready((socket, _)) => {
|
||||
*self = WriteOneInner::Shutdown(tokio_io::io::shutdown(socket));
|
||||
}
|
||||
Async::NotReady => {
|
||||
*self = WriteOneInner::Write(inner);
|
||||
}
|
||||
},
|
||||
WriteOneInner::Shutdown(ref mut inner) => {
|
||||
let socket = try_ready!(inner.poll());
|
||||
return Ok(Async::Ready(socket));
|
||||
}
|
||||
WriteOneInner::Poisoned => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a buffer that contains the given integer encoded as variable-length.
|
||||
fn build_int_buffer(num: usize) -> io::Window<[u8; 10]> {
|
||||
let mut len_data = unsigned_varint::encode::u64_buffer();
|
||||
let encoded_len = unsigned_varint::encode::u64(num as u64, &mut len_data).len();
|
||||
let mut len_data = io::Window::new(len_data);
|
||||
len_data.set_end(encoded_len);
|
||||
len_data
|
||||
}
|
||||
|
||||
/// Future that makes `write_one` work.
|
||||
struct WriteOne<TSocket, TData = Vec<u8>> {
|
||||
inner: WriteOneInner<TSocket, TData>,
|
||||
}
|
||||
|
||||
impl<TSocket, TData> Future for WriteOne<TSocket, TData>
|
||||
where
|
||||
TSocket: AsyncWrite,
|
||||
TData: AsRef<[u8]>,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = std::io::Error;
|
||||
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
Ok(self.inner.poll()?.map(|_socket| ()))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user