From 95ce8ca6e6a26301b49156429e288ea3c804ce0c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 3 Apr 2019 11:20:13 +1100 Subject: [PATCH 01/39] Propogate valid attestations accross the network --- beacon_node/rpc/src/attestation.rs | 7 +++---- beacon_node/rpc/src/lib.rs | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index 0f585b7e7..a64ec620f 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -136,11 +136,10 @@ impl AttestationService for AttestationServiceInstance { "type" => "valid_attestation", ); - // TODO: Obtain topics from the network service properly. - let topic = types::TopicBuilder::new("beacon_chain".to_string()).build(); + // valid attestation, propagate to the network + let topic = types::TopicBuilder::new("attestations".to_string()).build(); let message = PubsubMessage::Attestation(attestation); - // Publish the attestation to the p2p network via gossipsub. self.network_chan .send(NetworkMessage::Publish { topics: vec![topic], @@ -150,7 +149,7 @@ impl AttestationService for AttestationServiceInstance { error!( self.log, "PublishAttestation"; - "type" => "failed to publish to gossipsub", + "type" => "failed to publish attestation to gossipsub", "error" => format!("{:?}", e) ); }); diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index f2f1b2abf..4506d90fc 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -60,6 +60,7 @@ pub fn start_server( }; let attestation_service = { let instance = AttestationServiceInstance { + network_chan, chain: beacon_chain.clone(), network_chan, log: log.clone(), From e9181e120c8d4cdf0e1831e2458d9378f0028a1b Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 3 Apr 2019 12:25:05 +1100 Subject: [PATCH 02/39] Add topics to chain id --- beacon_node/client/src/client_config.rs | 15 +++++++++++++++ beacon_node/eth2-libp2p/src/config.rs | 7 +++++-- eth2/types/src/chain_spec.rs | 9 ++++++--- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index 166725b61..9ed2a7c6e 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -18,6 +18,21 @@ pub struct ClientConfig { impl Default for ClientConfig { fn default() -> Self { + let data_dir = { + let home = dirs::home_dir().expect("Unable to determine home dir."); + home.join(".lighthouse/") + }; + fs::create_dir_all(&data_dir) + .unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir)); + + let default_spec = ChainSpec::lighthouse_testnet(); + let default_pubsub_topics = vec![ + default_spec.beacon_chain_topic.clone(), + default_spec.shard_topic_prefix.clone(), + ]; // simple singular attestation topic for now. + let default_net_conf = + NetworkConfig::new(default_spec.boot_nodes.clone(), default_pubsub_topics); + Self { data_dir: PathBuf::from(".lighthouse"), db_type: "disk".to_string(), diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index ee2add75e..97559343a 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -2,6 +2,7 @@ use clap::ArgMatches; use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; use serde_derive::{Deserialize, Serialize}; use types::multiaddr::{Error as MultiaddrError, Multiaddr}; +//use std::time::Duration; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] @@ -30,19 +31,21 @@ impl Default for Config { listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000".to_string()], gs_config: GossipsubConfigBuilder::new() .max_gossip_size(4_000_000) + // .inactivity_timeout(Duration::from_secs(90)) .build(), identify_config: IdentifyConfig::default(), boot_nodes: vec![], client_version: version::version(), - topics: vec![String::from("beacon_chain")], + topics: Vec::new(), } } } impl Config { - pub fn new(boot_nodes: Vec) -> Self { + pub fn new(boot_nodes: Vec, topics: Vec) -> Self { let mut conf = Config::default(); conf.boot_nodes = boot_nodes; + conf.topics = topics; conf } diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 74ce40671..4fa79adcd 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -106,10 +106,11 @@ pub struct ChainSpec { /* * Network specific parameters - * */ pub boot_nodes: Vec, pub chain_id: u8, + pub beacon_chain_topic: String, + pub shard_topic_prefix: String, } impl ChainSpec { @@ -216,10 +217,12 @@ impl ChainSpec { domain_transfer: 5, /* - * Boot nodes + * Network specific */ boot_nodes: vec![], - chain_id: 1, // mainnet chain id + chain_id: 1, // foundation chain id + beacon_chain_topic: String::from("beacon_chain"), + shard_topic_prefix: String::from("attestations"), // simple single attestation topic for now } } From f54bd79f841d9495ed4112578bbc69e75a281019 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 3 Apr 2019 13:50:11 +1100 Subject: [PATCH 03/39] RPC methods get pubsub topics from chain spec --- beacon_node/network/src/sync/simple_sync.rs | 3 ++- beacon_node/rpc/src/attestation.rs | 5 ++++- beacon_node/rpc/src/beacon_block.rs | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 0a082afcf..1b5a6aae9 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -4,7 +4,8 @@ use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome}; use eth2_libp2p::rpc::methods::*; use eth2_libp2p::rpc::{RPCRequest, RPCResponse, RequestId}; use eth2_libp2p::PeerId; -use slog::{debug, error, info, o, warn}; +use slog::{debug, error, info, o, trace, warn}; +use ssz::TreeHash; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index a64ec620f..c89cbbbb0 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -136,8 +136,11 @@ impl AttestationService for AttestationServiceInstance { "type" => "valid_attestation", ); + // get the network topic to send on + let topic_string = self.chain.get_spec().shard_topic_prefix.clone(); + // valid attestation, propagate to the network - let topic = types::TopicBuilder::new("attestations".to_string()).build(); + let topic = types::TopicBuilder::new(topic_string).build(); let message = PubsubMessage::Attestation(attestation); self.network_chan diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index 533fd285a..791c5008d 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -104,9 +104,9 @@ impl BeaconBlockService for BeaconBlockServiceInstance { "block_root" => format!("{}", block_root), ); - // TODO: Obtain topics from the network service properly. - let topic = - types::TopicBuilder::new("beacon_chain".to_string()).build(); + // get the network topic to send on + let topic_string = self.chain.get_spec().beacon_chain_topic.clone(); + let topic = types::TopicBuilder::new(topic_string).build(); let message = PubsubMessage::Block(block); // Publish the block to the p2p network via gossipsub. From dd3a4f0b438babf474f71afe6e87bab1f10731eb Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 3 Apr 2019 16:00:09 +1100 Subject: [PATCH 04/39] Removes network parameters from chain spec --- beacon_node/client/src/client_config.rs | 9 ++-- beacon_node/eth2-libp2p/src/behaviour.rs | 2 +- beacon_node/eth2-libp2p/src/config.rs | 57 +++++++++++++++++++++--- beacon_node/eth2-libp2p/src/lib.rs | 5 ++- beacon_node/eth2-libp2p/src/service.rs | 14 ++++-- beacon_node/network/src/service.rs | 2 +- beacon_node/rpc/src/attestation.rs | 3 +- eth2/types/src/chain_spec.rs | 6 --- eth2/types/src/lib.rs | 3 -- 9 files changed, 71 insertions(+), 30 deletions(-) diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index 9ed2a7c6e..685f58c61 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -25,13 +25,10 @@ impl Default for ClientConfig { fs::create_dir_all(&data_dir) .unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir)); + // currently lighthouse spec let default_spec = ChainSpec::lighthouse_testnet(); - let default_pubsub_topics = vec![ - default_spec.beacon_chain_topic.clone(), - default_spec.shard_topic_prefix.clone(), - ]; // simple singular attestation topic for now. - let default_net_conf = - NetworkConfig::new(default_spec.boot_nodes.clone(), default_pubsub_topics); + // builds a chain-specific network config + let net_conf = NetworkConfig::from(default_spec.chain_id); Self { data_dir: PathBuf::from(".lighthouse"), diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 10b140c3b..f362f5795 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -1,5 +1,6 @@ use crate::rpc::{RPCEvent, RPCMessage, Rpc}; use crate::NetworkConfig; +use crate::{Topic, TopicHash}; use futures::prelude::*; use libp2p::{ core::{ @@ -15,7 +16,6 @@ use libp2p::{ use slog::{debug, o, trace, warn}; use ssz::{ssz_encode, Decode, DecodeError, Encode}; use types::{Attestation, BeaconBlock}; -use types::{Topic, TopicHash}; /// Builds the network behaviour for the libp2p Swarm. /// Implements gossipsub message routing. diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index 97559343a..88f315d0c 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -20,8 +20,12 @@ pub struct Config { boot_nodes: Vec, /// Client version pub client_version: String, - /// List of topics to subscribe to as strings + /// List of extra topics to initially subscribe to as strings. pub topics: Vec, + /// Shard pubsub topic prefix. + pub shard_prefix: String, + /// The main beacon chain topic to subscribe to. + pub beacon_chain_topic: String, } impl Default for Config { @@ -37,17 +41,16 @@ impl Default for Config { boot_nodes: vec![], client_version: version::version(), topics: Vec::new(), + beacon_chain_topic: String::from("beacon_chain"), + shard_prefix: String::from("attestations"), // single topic for all attestation for the moment. } } } +/// Generates a default Config. impl Config { - pub fn new(boot_nodes: Vec, topics: Vec) -> Self { - let mut conf = Config::default(); - conf.boot_nodes = boot_nodes; - conf.topics = topics; - - conf + pub fn new() -> Self { + Config::default() } pub fn listen_addresses(&self) -> Result, MultiaddrError> { @@ -90,3 +93,43 @@ impl Default for IdentifyConfig { } } } + +/// Creates a standard network config from a chain_id. +/// +/// This creates specified network parameters for each chain type. +impl From for Config { + fn from(chain_type: ChainType) -> Self { + match chain_type { + ChainType::Foundation => Config::default(), + + ChainType::LighthouseTestnet => { + let boot_nodes = vec!["/ip4/127.0.0.1/tcp/9000" + .parse() + .expect("correct multiaddr")]; + Self { + boot_nodes, + ..Config::default() + } + } + + ChainType::Other => Config::default(), + } + } +} + +pub enum ChainType { + Foundation, + LighthouseTestnet, + Other, +} + +/// Maps a chain id to a ChainType. +impl From for ChainType { + fn from(chain_id: u8) -> Self { + match chain_id { + 1 => ChainType::Foundation, + 2 => ChainType::LighthouseTestnet, + _ => ChainType::Other, + } + } +} diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index 659d6b01c..4bd775802 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -10,6 +10,9 @@ mod service; pub use behaviour::PubsubMessage; pub use config::Config as NetworkConfig; +pub use libp2p::floodsub::{Topic, TopicBuilder, TopicHash}; +pub use libp2p::multiaddr; +pub use libp2p::Multiaddr; pub use libp2p::{ gossipsub::{GossipsubConfig, GossipsubConfigBuilder}, PeerId, @@ -17,5 +20,3 @@ pub use libp2p::{ pub use rpc::RPCEvent; pub use service::Libp2pEvent; pub use service::Service; -pub use types::multiaddr; -pub use types::Multiaddr; diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 18f7ca98c..99de38de6 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -3,6 +3,7 @@ use crate::error; use crate::multiaddr::Protocol; use crate::rpc::RPCEvent; use crate::NetworkConfig; +use crate::{TopicBuilder, TopicHash}; use futures::prelude::*; use futures::Stream; use libp2p::core::{ @@ -17,7 +18,6 @@ use libp2p::{core, secio, PeerId, Swarm, Transport}; use slog::{debug, info, trace, warn}; use std::io::{Error, ErrorKind}; use std::time::Duration; -use types::{TopicBuilder, TopicHash}; type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>; type Libp2pBehaviour = Behaviour>; @@ -85,9 +85,17 @@ impl Service { } // subscribe to default gossipsub topics + let mut topics = vec![]; + //TODO: Handle multiple shard attestations. For now we simply use a separate topic for + //attestations + topics.push(config.shard_prefix); + topics.push(config.beacon_chain_topic); + + topics.append(&mut config.topics.clone()); + let mut subscribed_topics = vec![]; - for topic in config.topics { - let t = TopicBuilder::new(topic.to_string()).build(); + for topic in topics { + let t = TopicBuilder::new(topic.clone()).build(); if swarm.subscribe(t) { trace!(log, "Subscribed to topic: {:?}", topic); subscribed_topics.push(topic); diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 9c71a60f7..c19aef004 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -4,6 +4,7 @@ use crate::NetworkConfig; use beacon_chain::{BeaconChain, BeaconChainTypes}; use crossbeam_channel::{unbounded as channel, Sender, TryRecvError}; use eth2_libp2p::Service as LibP2PService; +use eth2_libp2p::Topic; use eth2_libp2p::{Libp2pEvent, PeerId}; use eth2_libp2p::{PubsubMessage, RPCEvent}; use futures::prelude::*; @@ -13,7 +14,6 @@ use slog::{debug, info, o, trace}; use std::marker::PhantomData; use std::sync::Arc; use tokio::runtime::TaskExecutor; -use types::Topic; /// Service that handles communication between internal services and the eth2_libp2p network service. pub struct Service { diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index c89cbbbb0..b9b05b7cd 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -1,5 +1,6 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use eth2_libp2p::PubsubMessage; +use eth2_libp2p::TopicBuilder; use futures::Future; use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink}; use network::NetworkMessage; @@ -140,7 +141,7 @@ impl AttestationService for AttestationServiceInstance { let topic_string = self.chain.get_spec().shard_topic_prefix.clone(); // valid attestation, propagate to the network - let topic = types::TopicBuilder::new(topic_string).build(); + let topic = TopicBuilder::new(topic_string).build(); let message = PubsubMessage::Attestation(attestation); self.network_chan diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 4fa79adcd..8e4bd9c9c 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -107,10 +107,7 @@ pub struct ChainSpec { /* * Network specific parameters */ - pub boot_nodes: Vec, pub chain_id: u8, - pub beacon_chain_topic: String, - pub shard_topic_prefix: String, } impl ChainSpec { @@ -219,10 +216,7 @@ impl ChainSpec { /* * Network specific */ - boot_nodes: vec![], chain_id: 1, // foundation chain id - beacon_chain_topic: String::from("beacon_chain"), - shard_topic_prefix: String::from("attestations"), // simple single attestation topic for now } } diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 4d0ec5fae..2406c3a18 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -82,6 +82,3 @@ pub type ProposerMap = HashMap; pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature}; pub use fixed_len_vec::{typenum, typenum::Unsigned, FixedLenVec}; -pub use libp2p::floodsub::{Topic, TopicBuilder, TopicHash}; -pub use libp2p::multiaddr; -pub use libp2p::Multiaddr; From c1d609902a9468ab3da6c8a1436212f9c2ece390 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 3 Apr 2019 16:33:12 +1100 Subject: [PATCH 05/39] Complete moving network logc into beacon node --- beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/client_config.rs | 9 ++++++++- beacon_node/eth2-libp2p/src/config.rs | 10 ++++------ beacon_node/eth2-libp2p/src/lib.rs | 2 +- beacon_node/eth2-libp2p/src/service.rs | 5 +++-- beacon_node/network/src/lib.rs | 2 +- beacon_node/rpc/src/attestation.rs | 6 ++---- beacon_node/rpc/src/beacon_block.rs | 6 +++--- 8 files changed, 23 insertions(+), 18 deletions(-) diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index f97302a7c..8c5c44949 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -9,6 +9,7 @@ beacon_chain = { path = "../beacon_chain" } network = { path = "../network" } store = { path = "../store" } http_server = { path = "../http_server" } +eth2-libp2p = { path = "../eth2-libp2p" } rpc = { path = "../rpc" } prometheus = "^0.6" types = { path = "../../eth2/types" } diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index 685f58c61..c533cbcc8 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -1,7 +1,13 @@ use clap::ArgMatches; +use eth2_libp2p::multiaddr::Protocol; +use eth2_libp2p::multiaddr::ToMultiaddr; +use eth2_libp2p::Multiaddr; +use fork_choice::ForkChoiceAlgorithm; use http_server::HttpServerConfig; use network::NetworkConfig; +use network::{ChainType, NetworkConfig}; use serde_derive::{Deserialize, Serialize}; +use slog::error; use std::fs; use std::path::PathBuf; @@ -27,8 +33,9 @@ impl Default for ClientConfig { // currently lighthouse spec let default_spec = ChainSpec::lighthouse_testnet(); + let chain_type = ChainType::from(default_spec.chain_id); // builds a chain-specific network config - let net_conf = NetworkConfig::from(default_spec.chain_id); + let net_conf = NetworkConfig::from(chain_type); Self { data_dir: PathBuf::from(".lighthouse"), diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index 88f315d0c..0757f1cbb 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -4,6 +4,10 @@ use serde_derive::{Deserialize, Serialize}; use types::multiaddr::{Error as MultiaddrError, Multiaddr}; //use std::time::Duration; +/// The beacon node topic string to subscribe to. +pub const BEACON_PUBSUB_TOPIC: &str = "beacon_node"; +pub const SHARD_TOPIC_PREFIX: &str = "attestations"; // single topic for all attestation for the moment. + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] /// Network configuration for lighthouse. @@ -22,10 +26,6 @@ pub struct Config { pub client_version: String, /// List of extra topics to initially subscribe to as strings. pub topics: Vec, - /// Shard pubsub topic prefix. - pub shard_prefix: String, - /// The main beacon chain topic to subscribe to. - pub beacon_chain_topic: String, } impl Default for Config { @@ -41,8 +41,6 @@ impl Default for Config { boot_nodes: vec![], client_version: version::version(), topics: Vec::new(), - beacon_chain_topic: String::from("beacon_chain"), - shard_prefix: String::from("attestations"), // single topic for all attestation for the moment. } } } diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index 4bd775802..5597f9107 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -9,7 +9,7 @@ pub mod rpc; mod service; pub use behaviour::PubsubMessage; -pub use config::Config as NetworkConfig; +pub use config::{ChainType, Config as NetworkConfig, BEACON_PUBSUB_TOPIC, SHARD_TOPIC_PREFIX}; pub use libp2p::floodsub::{Topic, TopicBuilder, TopicHash}; pub use libp2p::multiaddr; pub use libp2p::Multiaddr; diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 99de38de6..9cbceda8d 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -4,6 +4,7 @@ use crate::multiaddr::Protocol; use crate::rpc::RPCEvent; use crate::NetworkConfig; use crate::{TopicBuilder, TopicHash}; +use crate::{BEACON_PUBSUB_TOPIC, SHARD_TOPIC_PREFIX}; use futures::prelude::*; use futures::Stream; use libp2p::core::{ @@ -88,8 +89,8 @@ impl Service { let mut topics = vec![]; //TODO: Handle multiple shard attestations. For now we simply use a separate topic for //attestations - topics.push(config.shard_prefix); - topics.push(config.beacon_chain_topic); + topics.push(SHARD_TOPIC_PREFIX.to_string()); + topics.push(BEACON_PUBSUB_TOPIC.to_string()); topics.append(&mut config.topics.clone()); diff --git a/beacon_node/network/src/lib.rs b/beacon_node/network/src/lib.rs index b805c1d75..d00c16292 100644 --- a/beacon_node/network/src/lib.rs +++ b/beacon_node/network/src/lib.rs @@ -4,6 +4,6 @@ pub mod message_handler; pub mod service; pub mod sync; -pub use eth2_libp2p::NetworkConfig; +pub use eth2_libp2p::{ChainType, NetworkConfig}; pub use service::NetworkMessage; pub use service::Service; diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index b9b05b7cd..86f4331f1 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -1,6 +1,7 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use eth2_libp2p::PubsubMessage; use eth2_libp2p::TopicBuilder; +use eth2_libp2p::SHARD_TOPIC_PREFIX; use futures::Future; use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink}; use network::NetworkMessage; @@ -137,11 +138,8 @@ impl AttestationService for AttestationServiceInstance { "type" => "valid_attestation", ); - // get the network topic to send on - let topic_string = self.chain.get_spec().shard_topic_prefix.clone(); - // valid attestation, propagate to the network - let topic = TopicBuilder::new(topic_string).build(); + let topic = TopicBuilder::new(SHARD_TOPIC_PREFIX).build(); let message = PubsubMessage::Attestation(attestation); self.network_chan diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index 791c5008d..cdf46a1ab 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -1,6 +1,7 @@ use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome}; use crossbeam_channel; -use eth2_libp2p::PubsubMessage; +use eth2_libp2p::BEACON_PUBSUB_TOPIC; +use eth2_libp2p::{PubsubMessage, TopicBuilder}; use futures::Future; use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink}; use network::NetworkMessage; @@ -105,8 +106,7 @@ impl BeaconBlockService for BeaconBlockServiceInstance { ); // get the network topic to send on - let topic_string = self.chain.get_spec().beacon_chain_topic.clone(); - let topic = types::TopicBuilder::new(topic_string).build(); + let topic = TopicBuilder::new(BEACON_PUBSUB_TOPIC).build(); let message = PubsubMessage::Block(block); // Publish the block to the p2p network via gossipsub. From 7ad9805f02ee14b6fa608760307a182a1e7dca0c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 3 Apr 2019 17:13:55 +1100 Subject: [PATCH 06/39] Update to latest libp2p --- beacon_node/eth2-libp2p/Cargo.toml | 2 +- beacon_node/network/src/sync/simple_sync.rs | 25 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index cc6393e38..b8cf1aaef 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" # SigP repository until PR is merged -libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "b3c32d9a821ae6cc89079499cc6e8a6bab0bffc3" } +libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "fb852bcc2b9b3935555cc93930e913cbec2b0688" } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 1b5a6aae9..ff08b26d1 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -522,9 +522,30 @@ impl SimpleSync { BlockProcessingOutcome::ParentUnknown { .. } => { self.import_queue .enqueue_full_blocks(vec![block], peer_id.clone()); + trace!( + self.log, + "NewGossipBlock"; + "peer" => format!("{:?}", peer_id), + ); - SHOULD_FORWARD_GOSSIP_BLOCK - } + // Ignore any block from a finalized slot. + if self.slot_is_finalized(block.slot) { + warn!( + self.log, "NewGossipBlock"; + "msg" => "new block slot is finalized.", + "block_slot" => block.slot, + ); + return false; + } + + let block_root = Hash256::from_slice(&block.hash_tree_root()); + + // Ignore any block that the chain already knows about. + if self.chain_has_seen_block(&block_root) { + println!("this happened"); + // TODO: Age confirm that we shouldn't forward a block if we already know of it. + return false; + } BlockProcessingOutcome::FutureSlot { present_slot, block_slot, From dea6ac7923c1f3972b0ca958db4aa71eacff441c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 3 Apr 2019 17:16:32 +1100 Subject: [PATCH 07/39] Add custom inactivity timeout to gossipsub --- beacon_node/eth2-libp2p/src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index 0757f1cbb..1a3f3ad3d 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -1,8 +1,8 @@ use clap::ArgMatches; use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; use serde_derive::{Deserialize, Serialize}; +use std::time::Duration; use types::multiaddr::{Error as MultiaddrError, Multiaddr}; -//use std::time::Duration; /// The beacon node topic string to subscribe to. pub const BEACON_PUBSUB_TOPIC: &str = "beacon_node"; @@ -35,7 +35,7 @@ impl Default for Config { listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000".to_string()], gs_config: GossipsubConfigBuilder::new() .max_gossip_size(4_000_000) - // .inactivity_timeout(Duration::from_secs(90)) + .inactivity_timeout(Duration::from_secs(90)) .build(), identify_config: IdentifyConfig::default(), boot_nodes: vec![], From 8d5d2282703312333076acfe0309317629a639a1 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 9 Apr 2019 09:15:11 +1000 Subject: [PATCH 08/39] Adds Kademlia for peer discovery --- beacon_node/eth2-libp2p/Cargo.toml | 1 + beacon_node/eth2-libp2p/src/behaviour.rs | 72 ++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index b8cf1aaef..9732b8fcc 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -19,3 +19,4 @@ version = { path = "../version" } tokio = "0.1.16" futures = "0.1.25" error-chain = "0.12.0" +tokio-timer = "0.2.10" diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index f362f5795..591d93bb5 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -9,14 +9,21 @@ use libp2p::{ }, gossipsub::{Gossipsub, GossipsubEvent}, identify::{protocol::IdentifyInfo, Identify, IdentifyEvent}, + kad::{Kademlia, KademliaOut}, ping::{Ping, PingEvent}, tokio_io::{AsyncRead, AsyncWrite}, NetworkBehaviour, PeerId, }; use slog::{debug, o, trace, warn}; use ssz::{ssz_encode, Decode, DecodeError, Encode}; +use std::time::{Duration, Instant}; +use tokio_timer::Delay; +>>>>>>> Adds Kademlia for peer discovery use types::{Attestation, BeaconBlock}; +//TODO: Make this dynamic +const TIME_BETWEEN_KAD_REQUESTS: Duration = Duration::from_secs(30); + /// Builds the network behaviour for the libp2p Swarm. /// Implements gossipsub message routing. #[derive(NetworkBehaviour)] @@ -24,17 +31,20 @@ use types::{Attestation, BeaconBlock}; pub struct Behaviour { /// The routing pub-sub mechanism for eth2. gossipsub: Gossipsub, - // TODO: Add Kademlia for peer discovery /// The events generated by this behaviour to be consumed in the swarm poll. serenity_rpc: Rpc, /// Allows discovery of IP addresses for peers on the network. identify: Identify, /// Keep regular connection to peers and disconnect if absent. - // TODO: Keepalive, likely remove this later. - // TODO: Make the ping time customizeable. ping: Ping, + /// Kademlia for peer discovery. + kad: Kademlia, + /// Queue of behaviour events to be processed. #[behaviour(ignore)] events: Vec, + /// The delay until we next search for more peers. + #[behaviour(ignore)] + kad_delay: Delay, /// Logger for behaviour actions. #[behaviour(ignore)] log: slog::Logger, @@ -121,6 +131,33 @@ impl NetworkBehaviourEventProcess } } +// implement the kademlia behaviour +impl NetworkBehaviourEventProcess + for Behaviour +{ + fn inject_event(&mut self, out: KademliaOut) { + match out { + KademliaOut::Discovered { .. } => { + // send this to our topology behaviour + } + KademliaOut::KBucketAdded { .. } => { + // send this to our topology behaviour + } + KademliaOut::FindNodeResult { closer_peers, .. } => { + debug!( + self.log, + "Kademlia query found {} peers", + closer_peers.len() + ); + if closer_peers.is_empty() { + warn!(self.log, "Kademlia random query yielded empty results"); + } + } + KademliaOut::GetProvidersResult { .. } => (), + } + } +} + impl Behaviour { pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self { let local_peer_id = local_public_key.clone().into_peer_id(); @@ -128,8 +165,9 @@ impl Behaviour { let behaviour_log = log.new(o!()); Behaviour { - gossipsub: Gossipsub::new(local_peer_id, net_conf.gs_config.clone()), serenity_rpc: Rpc::new(log), + gossipsub: Gossipsub::new(local_peer_id.clone(), net_conf.gs_config.clone()), + kad: Kademlia::new(local_peer_id), identify: Identify::new( identify_config.version, identify_config.user_agent, @@ -137,6 +175,7 @@ impl Behaviour { ), ping: Ping::new(), events: Vec::new(), + kad_delay: Delay::new(Instant::now()), log: behaviour_log, } } @@ -149,6 +188,19 @@ impl Behaviour { return Async::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0))); } + // check to see if it's time to search for me peers with kademlia + loop { + match self.kad_delay.poll() { + Ok(Async::Ready(_)) => { + self.get_kad_peers(); + } + Ok(Async::NotReady) => break, + Err(e) => { + warn!(self.log, "Error getting peers from Kademlia. Err: {:?}", e); + } + } + } + Async::NotReady } } @@ -172,6 +224,18 @@ impl Behaviour { self.gossipsub.publish(topic, message_bytes.clone()); } } + + /// Queries for more peers randomly using Kademlia. + pub fn get_kad_peers(&mut self) { + // pick a random PeerId + let random_peer = PeerId::random(); + debug!(self.log, "Running kademlia random peer query"); + self.kad.find_node(random_peer); + + // update the kademlia timeout + self.kad_delay + .reset(Instant::now() + TIME_BETWEEN_KAD_REQUESTS); + } } /// The types of events than can be obtained from polling the behaviour. From ad1438db538b7f50a9b624f352f81db807090fba Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 15 Apr 2019 11:29:08 +1000 Subject: [PATCH 09/39] Adds verbosity cli flag --- beacon_node/Cargo.toml | 1 + beacon_node/client/Cargo.toml | 4 +++- beacon_node/client/src/client_config.rs | 11 ++------- beacon_node/eth2-libp2p/src/behaviour.rs | 3 ++- beacon_node/src/main.rs | 30 +++++++++++++++++------- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 309f162e5..0bc7de4cc 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -22,3 +22,4 @@ tokio-timer = "0.2.10" futures = "0.1.25" exit-future = "0.1.3" state_processing = { path = "../eth2/state_processing" } +slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 8c5c44949..87150b4dc 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -19,7 +19,9 @@ slot_clock = { path = "../../eth2/utils/slot_clock" } serde = "1.0" serde_derive = "1.0" error-chain = "0.12.0" -slog = "^2.2.3" +slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } +slog-term = "^2.4.0" +slog-async = "^2.3.0" ssz = { path = "../../eth2/utils/ssz" } tokio = "0.1.15" clap = "2.32.0" diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index c533cbcc8..4d0e286b0 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -7,7 +7,7 @@ use http_server::HttpServerConfig; use network::NetworkConfig; use network::{ChainType, NetworkConfig}; use serde_derive::{Deserialize, Serialize}; -use slog::error; +use slog::{error, o, Drain, Level}; use std::fs; use std::path::PathBuf; @@ -57,13 +57,6 @@ impl ClientConfig { .and_then(|path| Some(path.join(&self.db_name))) } - /// Returns the core path for the client. - pub fn data_dir(&self) -> Option { - let path = dirs::home_dir()?.join(&self.data_dir); - fs::create_dir_all(&path).ok()?; - Some(path) - } - /// Apply the following arguments to `self`, replacing values if they are specified in `args`. /// /// Returns an error if arguments are obviously invalid. May succeed even if some values are @@ -81,6 +74,6 @@ impl ClientConfig { self.rpc.apply_cli_args(args)?; self.http.apply_cli_args(args)?; - Ok(()) + Ok(log) } } diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 591d93bb5..b80881853 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -137,7 +137,8 @@ impl NetworkBehaviourEventProcess { + KademliaOut::Discovered { peer_id, .. } => { + debug!(self.log, "Kademlia peer discovered: {:?}", peer_id); // send this to our topology behaviour } KademliaOut::KBucketAdded { .. } => { diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index d6274befc..60b51303a 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -1,5 +1,3 @@ -extern crate slog; - mod run; use clap::{App, Arg}; @@ -14,11 +12,6 @@ pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml"; pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml"; fn main() { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::CompactFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - let logger = slog::Logger::root(drain, o!()); - let matches = App::new("Lighthouse") .version(version::version().as_str()) .author("Sigma Prime ") @@ -116,8 +109,29 @@ fn main() { .short("r") .help("When present, genesis will be within 30 minutes prior. Only for testing"), ) + .arg( + Arg::with_name("verbosity") + .short("v") + .multiple(true) + .help("Sets the verbosity level") + .takes_value(true), + ) .get_matches(); + // build the initial logger + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::CompactFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build(); + + let drain = match matches.occurrences_of("verbosity") { + 0 => drain.filter_level(Level::Info), + 1 => drain.filter_level(Level::Debug), + 2 => drain.filter_level(Level::Trace), + _ => drain.filter_level(Level::Info), + }; + + let logger = slog::Logger::root(drain.fuse(), o!()); + let data_dir = match get_data_dir(&matches, PathBuf::from(DEFAULT_DATA_DIR)) { Ok(dir) => dir, Err(e) => { @@ -128,7 +142,7 @@ fn main() { let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME); - // Attempt to lead the `ClientConfig` from disk. + // Attempt to load the `ClientConfig` from disk. // // If file doesn't exist, create a new, default one. let mut client_config = match read_from_file::(client_config_path.clone()) { From 89282c1e09336db743f92274b66f7dd861bccdd5 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 15 Apr 2019 13:58:41 +1000 Subject: [PATCH 10/39] Adds env logger to output libp2p logs --- beacon_node/Cargo.toml | 1 + beacon_node/src/main.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 0bc7de4cc..43e75d0a6 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -23,3 +23,4 @@ futures = "0.1.25" exit-future = "0.1.3" state_processing = { path = "../eth2/state_processing" } slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } +env_logger = "0.6.1" diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 60b51303a..96353ddf9 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -2,6 +2,7 @@ mod run; use clap::{App, Arg}; use client::{ClientConfig, Eth2Config}; +use env_logger::{Builder, Env}; use eth2_config::{get_data_dir, read_from_file, write_to_file}; use slog::{crit, o, Drain}; use std::path::PathBuf; @@ -12,6 +13,9 @@ pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml"; pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml"; fn main() { + // debugging output for libp2p and external crates + Builder::from_env(Env::default()).init(); + let matches = App::new("Lighthouse") .version(version::version().as_str()) .author("Sigma Prime ") From 25f37ad96703ebba6240db43654166cccee22d7f Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 15 Apr 2019 18:29:49 +1000 Subject: [PATCH 11/39] Builds on discovery. Adds identify to discovery --- beacon_node/eth2-libp2p/Cargo.toml | 3 +- beacon_node/eth2-libp2p/src/behaviour.rs | 71 ++------- beacon_node/eth2-libp2p/src/config.rs | 1 + beacon_node/eth2-libp2p/src/discovery.rs | 182 +++++++++++++++++++++++ beacon_node/eth2-libp2p/src/lib.rs | 1 + 5 files changed, 199 insertions(+), 59 deletions(-) create mode 100644 beacon_node/eth2-libp2p/src/discovery.rs diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 9732b8fcc..754efb83d 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -8,7 +8,8 @@ edition = "2018" beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" # SigP repository until PR is merged -libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "fb852bcc2b9b3935555cc93930e913cbec2b0688" } +#libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "fb852bcc2b9b3935555cc93930e913cbec2b0688" } +libp2p = { path = "../../../sharding/rust-libp2p" } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index b80881853..e952d1f81 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -1,3 +1,4 @@ +use crate::discovery::Discovery; use crate::rpc::{RPCEvent, RPCMessage, Rpc}; use crate::NetworkConfig; use crate::{Topic, TopicHash}; @@ -9,7 +10,7 @@ use libp2p::{ }, gossipsub::{Gossipsub, GossipsubEvent}, identify::{protocol::IdentifyInfo, Identify, IdentifyEvent}, - kad::{Kademlia, KademliaOut}, + kad::KademliaOut, ping::{Ping, PingEvent}, tokio_io::{AsyncRead, AsyncWrite}, NetworkBehaviour, PeerId, @@ -18,12 +19,8 @@ use slog::{debug, o, trace, warn}; use ssz::{ssz_encode, Decode, DecodeError, Encode}; use std::time::{Duration, Instant}; use tokio_timer::Delay; ->>>>>>> Adds Kademlia for peer discovery use types::{Attestation, BeaconBlock}; -//TODO: Make this dynamic -const TIME_BETWEEN_KAD_REQUESTS: Duration = Duration::from_secs(30); - /// Builds the network behaviour for the libp2p Swarm. /// Implements gossipsub message routing. #[derive(NetworkBehaviour)] @@ -38,13 +35,10 @@ pub struct Behaviour { /// Keep regular connection to peers and disconnect if absent. ping: Ping, /// Kademlia for peer discovery. - kad: Kademlia, + discovery: Discovery, /// Queue of behaviour events to be processed. #[behaviour(ignore)] events: Vec, - /// The delay until we next search for more peers. - #[behaviour(ignore)] - kad_delay: Delay, /// Logger for behaviour actions. #[behaviour(ignore)] log: slog::Logger, @@ -116,6 +110,12 @@ impl NetworkBehaviourEventProcess format!("{:?}", peer_id), "Addresses" => format!("{:?}", info.listen_addrs)); + // inject the found addresses into our discovery behaviour + for address in &info.listen_addrs { + self.discovery + .add_connected_address(&peer_id, address.clone()); + } } IdentifyEvent::Error { .. } => {} IdentifyEvent::SendBack { .. } => {} @@ -131,31 +131,12 @@ impl NetworkBehaviourEventProcess } } -// implement the kademlia behaviour +// implement the discovery behaviour (currently kademlia) impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, out: KademliaOut) { - match out { - KademliaOut::Discovered { peer_id, .. } => { - debug!(self.log, "Kademlia peer discovered: {:?}", peer_id); - // send this to our topology behaviour - } - KademliaOut::KBucketAdded { .. } => { - // send this to our topology behaviour - } - KademliaOut::FindNodeResult { closer_peers, .. } => { - debug!( - self.log, - "Kademlia query found {} peers", - closer_peers.len() - ); - if closer_peers.is_empty() { - warn!(self.log, "Kademlia random query yielded empty results"); - } - } - KademliaOut::GetProvidersResult { .. } => (), - } + fn inject_event(&mut self, _out: KademliaOut) { + // not interested in kademlia results at the moment } } @@ -168,7 +149,7 @@ impl Behaviour { Behaviour { serenity_rpc: Rpc::new(log), gossipsub: Gossipsub::new(local_peer_id.clone(), net_conf.gs_config.clone()), - kad: Kademlia::new(local_peer_id), + discovery: Discovery::new(local_peer_id, log), identify: Identify::new( identify_config.version, identify_config.user_agent, @@ -176,7 +157,6 @@ impl Behaviour { ), ping: Ping::new(), events: Vec::new(), - kad_delay: Delay::new(Instant::now()), log: behaviour_log, } } @@ -189,19 +169,6 @@ impl Behaviour { return Async::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0))); } - // check to see if it's time to search for me peers with kademlia - loop { - match self.kad_delay.poll() { - Ok(Async::Ready(_)) => { - self.get_kad_peers(); - } - Ok(Async::NotReady) => break, - Err(e) => { - warn!(self.log, "Error getting peers from Kademlia. Err: {:?}", e); - } - } - } - Async::NotReady } } @@ -225,18 +192,6 @@ impl Behaviour { self.gossipsub.publish(topic, message_bytes.clone()); } } - - /// Queries for more peers randomly using Kademlia. - pub fn get_kad_peers(&mut self) { - // pick a random PeerId - let random_peer = PeerId::random(); - debug!(self.log, "Running kademlia random peer query"); - self.kad.find_node(random_peer); - - // update the kademlia timeout - self.kad_delay - .reset(Instant::now() + TIME_BETWEEN_KAD_REQUESTS); - } } /// The types of events than can be obtained from polling the behaviour. diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index 1a3f3ad3d..b6857cd37 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -36,6 +36,7 @@ impl Default for Config { gs_config: GossipsubConfigBuilder::new() .max_gossip_size(4_000_000) .inactivity_timeout(Duration::from_secs(90)) + .heartbeat_interval(Duration::from_secs(20)) .build(), identify_config: IdentifyConfig::default(), boot_nodes: vec![], diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs new file mode 100644 index 000000000..232590c05 --- /dev/null +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -0,0 +1,182 @@ +/// This manages the discovery and management of peers. +/// +/// Currently using Kademlia for peer discovery. +/// +use futures::prelude::*; +use libp2p::core::swarm::{ + ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters, +}; +use libp2p::core::{Multiaddr, PeerId, ProtocolsHandler}; +use libp2p::kad::{Kademlia, KademliaOut}; +use slog::{debug, o, warn}; +use std::collections::HashMap; +use std::time::{Duration, Instant}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_timer::Delay; + +//TODO: Make this dynamic +const TIME_BETWEEN_KAD_REQUESTS: Duration = Duration::from_secs(30); + +/// Maintains a list of discovered peers and implements the discovery protocol to discover new +/// peers. +pub struct Discovery { + /// Queue of events to processed. + // TODO: Re-implement as discovery protocol grows + // events: Vec>, + /// The discovery behaviour used to discover new peers. + discovery: Kademlia, + /// The delay between peer discovery searches. + peer_discovery_delay: Delay, + /// Mapping of known addresses for peer ids. + known_peers: HashMap>, + /// Logger for the discovery behaviour. + log: slog::Logger, +} + +impl Discovery { + pub fn new(local_peer_id: PeerId, log: &slog::Logger) -> Self { + let log = log.new(o!("Service" => "Libp2p-Discovery")); + Self { + // events: Vec::new(), + discovery: Kademlia::new(local_peer_id), + peer_discovery_delay: Delay::new(Instant::now()), + known_peers: HashMap::new(), + log, + } + } + + /// Uses discovery to search for new peers. + pub fn find_peers(&mut self) { + // pick a random PeerId + let random_peer = PeerId::random(); + debug!(self.log, "Searching for peers..."); + self.discovery.find_node(random_peer); + + // update the kademlia timeout + self.peer_discovery_delay + .reset(Instant::now() + TIME_BETWEEN_KAD_REQUESTS); + } + + /// We have discovered an address for a peer, add it to known peers. + pub fn add_connected_address(&mut self, peer_id: &PeerId, address: Multiaddr) { + let known_peers = self + .known_peers + .entry(peer_id.clone()) + .or_insert_with(|| vec![]); + if !known_peers.contains(&address) { + known_peers.push(address.clone()); + } + // pass the address on to kademlia + self.discovery.add_connected_address(peer_id, address); + } +} + +// Redirect all behaviour event to underlying discovery behaviour. +impl NetworkBehaviour for Discovery +where + TSubstream: AsyncRead + AsyncWrite, +{ + type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; + type OutEvent = as NetworkBehaviour>::OutEvent; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + NetworkBehaviour::new_handler(&mut self.discovery) + } + + // TODO: we store all peers in known_peers, when upgrading to discv5 we will avoid duplication + // of peer storage. + fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { + if let Some(addresses) = self.known_peers.get(peer_id) { + addresses.clone() + } else { + debug!( + self.log, + "Tried to dial: {:?} but no address stored", peer_id + ); + Vec::new() + } + } + + fn inject_connected(&mut self, peer_id: PeerId, endpoint: ConnectedPoint) { + NetworkBehaviour::inject_connected(&mut self.discovery, peer_id, endpoint) + } + + fn inject_disconnected(&mut self, peer_id: &PeerId, endpoint: ConnectedPoint) { + NetworkBehaviour::inject_disconnected(&mut self.discovery, peer_id, endpoint) + } + + fn inject_replaced(&mut self, peer_id: PeerId, closed: ConnectedPoint, opened: ConnectedPoint) { + NetworkBehaviour::inject_replaced(&mut self.discovery, peer_id, closed, opened) + } + + fn inject_node_event( + &mut self, + peer_id: PeerId, + event: ::OutEvent, + ) { + // TODO: Upgrade to discv5 + NetworkBehaviour::inject_node_event(&mut self.discovery, peer_id, event) + } + + fn poll( + &mut self, + params: &mut PollParameters, + ) -> Async< + NetworkBehaviourAction< + ::InEvent, + Self::OutEvent, + >, + > { + // check to see if it's time to search for peers + loop { + match self.peer_discovery_delay.poll() { + Ok(Async::Ready(_)) => { + self.find_peers(); + } + Ok(Async::NotReady) => break, + Err(e) => { + warn!( + self.log, + "Error getting peers from discovery behaviour. Err: {:?}", e + ); + } + } + } + // Poll discovery + match self.discovery.poll(params) { + Async::Ready(action) => { + match &action { + NetworkBehaviourAction::GenerateEvent(disc_output) => match disc_output { + KademliaOut::Discovered { + peer_id, addresses, .. + } => { + debug!(self.log, "Kademlia peer discovered"; "Peer"=> format!("{:?}", peer_id), "Addresses" => format!("{:?}", addresses)); + (*self + .known_peers + .entry(peer_id.clone()) + .or_insert_with(|| vec![])) + .extend(addresses.clone()); + } + KademliaOut::FindNodeResult { closer_peers, .. } => { + debug!( + self.log, + "Kademlia query found {} peers", + closer_peers.len() + ); + if closer_peers.is_empty() { + debug!(self.log, "Kademlia random query yielded empty results"); + } + return Async::Ready(action); + } + _ => {} + }, + _ => {} + }; + return Async::Ready(action); + } + Async::NotReady => (), + } + + Async::NotReady + } +} diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index 5597f9107..197c074df 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -4,6 +4,7 @@ /// This crate builds and manages the libp2p services required by the beacon node. pub mod behaviour; mod config; +mod discovery; pub mod error; pub mod rpc; mod service; From 75959cc9a2a06e95d6f22bc01e1bd311c8f379b7 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Thu, 18 Apr 2019 15:26:30 +1000 Subject: [PATCH 12/39] Discovery and gossip bug fixes --- beacon_node/Cargo.toml | 2 +- beacon_node/client/Cargo.toml | 2 +- beacon_node/eth2-libp2p/Cargo.toml | 2 +- beacon_node/eth2-libp2p/src/discovery.rs | 20 ++++---------------- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 43e75d0a6..783cdcda3 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -22,5 +22,5 @@ tokio-timer = "0.2.10" futures = "0.1.25" exit-future = "0.1.3" state_processing = { path = "../eth2/state_processing" } -slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } +slog = "^2.2.3" env_logger = "0.6.1" diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 87150b4dc..9e4474644 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -19,7 +19,7 @@ slot_clock = { path = "../../eth2/utils/slot_clock" } serde = "1.0" serde_derive = "1.0" error-chain = "0.12.0" -slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } +slog = "^2.2.3" slog-term = "^2.4.0" slog-async = "^2.3.0" ssz = { path = "../../eth2/utils/ssz" } diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 754efb83d..13dfcdbe8 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -15,7 +15,7 @@ serde = "1.0" serde_derive = "1.0" ssz = { path = "../../eth2/utils/ssz" } ssz_derive = { path = "../../eth2/utils/ssz_derive" } -slog = "2.4.1" +slog = "^2.2.3" version = { path = "../version" } tokio = "0.1.16" futures = "0.1.25" diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 232590c05..d6fd43ef4 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -83,18 +83,9 @@ where NetworkBehaviour::new_handler(&mut self.discovery) } - // TODO: we store all peers in known_peers, when upgrading to discv5 we will avoid duplication - // of peer storage. fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { - if let Some(addresses) = self.known_peers.get(peer_id) { - addresses.clone() - } else { - debug!( - self.log, - "Tried to dial: {:?} but no address stored", peer_id - ); - Vec::new() - } + // Let discovery track possible known peers. + self.discovery.addresses_of_peer(peer_id) } fn inject_connected(&mut self, peer_id: PeerId, endpoint: ConnectedPoint) { @@ -151,11 +142,6 @@ where peer_id, addresses, .. } => { debug!(self.log, "Kademlia peer discovered"; "Peer"=> format!("{:?}", peer_id), "Addresses" => format!("{:?}", addresses)); - (*self - .known_peers - .entry(peer_id.clone()) - .or_insert_with(|| vec![])) - .extend(addresses.clone()); } KademliaOut::FindNodeResult { closer_peers, .. } => { debug!( @@ -163,6 +149,8 @@ where "Kademlia query found {} peers", closer_peers.len() ); + debug!(self.log, "Kademlia peers discovered"; "Peer"=> format!("{:?}", closer_peers)); + if closer_peers.is_empty() { debug!(self.log, "Kademlia random query yielded empty results"); } From f7c2e4c5af318a5f064d2b350eddd534048a06c7 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 30 Apr 2019 15:12:57 +1000 Subject: [PATCH 13/39] Initial core grouping of libp2p behaviours --- beacon_node/client/Cargo.toml | 2 +- beacon_node/eth2-libp2p/Cargo.toml | 2 +- beacon_node/eth2-libp2p/src/behaviour.rs | 48 +-- beacon_node/eth2-libp2p/src/core-behaviour.rs | 279 ++++++++++++++++++ beacon_node/eth2-libp2p/src/discovery.rs | 13 +- beacon_node/network/Cargo.toml | 2 +- 6 files changed, 311 insertions(+), 35 deletions(-) create mode 100644 beacon_node/eth2-libp2p/src/core-behaviour.rs diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 9e4474644..f97173bea 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -19,7 +19,7 @@ slot_clock = { path = "../../eth2/utils/slot_clock" } serde = "1.0" serde_derive = "1.0" error-chain = "0.12.0" -slog = "^2.2.3" +slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_trace"] } slog-term = "^2.4.0" slog-async = "^2.3.0" ssz = { path = "../../eth2/utils/ssz" } diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 13dfcdbe8..2deeaf5f0 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -15,7 +15,7 @@ serde = "1.0" serde_derive = "1.0" ssz = { path = "../../eth2/utils/ssz" } ssz_derive = { path = "../../eth2/utils/ssz_derive" } -slog = "^2.2.3" +slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_trace"] } version = { path = "../version" } tokio = "0.1.16" futures = "0.1.25" diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index e952d1f81..7ddbd95b7 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -3,6 +3,7 @@ use crate::rpc::{RPCEvent, RPCMessage, Rpc}; use crate::NetworkConfig; use crate::{Topic, TopicHash}; use futures::prelude::*; +use libp2p::Multiaddr; use libp2p::{ core::{ swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess}, @@ -19,16 +20,18 @@ use slog::{debug, o, trace, warn}; use ssz::{ssz_encode, Decode, DecodeError, Encode}; use std::time::{Duration, Instant}; use tokio_timer::Delay; +use std::collections::HashMap; use types::{Attestation, BeaconBlock}; -/// Builds the network behaviour for the libp2p Swarm. -/// Implements gossipsub message routing. +/// Builds the network behaviour that manages the core protocols of eth2. +/// This core behaviour is managed by `Behaviour` which adds peer management to all core +/// behaviours. #[derive(NetworkBehaviour)] -#[behaviour(out_event = "BehaviourEvent", poll_method = "poll")] -pub struct Behaviour { +#[behaviour(out_event = "CoreBehaviourEvent", poll_method = "poll")] +pub struct CoreCoreBehaviourTSubstream: AsyncRead + AsyncWrite> { /// The routing pub-sub mechanism for eth2. gossipsub: Gossipsub, - /// The events generated by this behaviour to be consumed in the swarm poll. + /// The serenity RPC specified in the wire-0 protocol. serenity_rpc: Rpc, /// Allows discovery of IP addresses for peers on the network. identify: Identify, @@ -36,9 +39,9 @@ pub struct Behaviour { ping: Ping, /// Kademlia for peer discovery. discovery: Discovery, - /// Queue of behaviour events to be processed. #[behaviour(ignore)] - events: Vec, + /// The events generated by this behaviour to be consumed in the swarm poll. + events: Vec, /// Logger for behaviour actions. #[behaviour(ignore)] log: slog::Logger, @@ -46,7 +49,7 @@ pub struct Behaviour { // Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for Behaviour impl NetworkBehaviourEventProcess - for Behaviour + for CoreBehaviourTSubstream> { fn inject_event(&mut self, event: GossipsubEvent) { match event { @@ -79,7 +82,7 @@ impl NetworkBehaviourEventProcess NetworkBehaviourEventProcess - for Behaviour + for CoreBehaviourTSubstream> { fn inject_event(&mut self, event: RPCMessage) { match event { @@ -94,7 +97,7 @@ impl NetworkBehaviourEventProcess NetworkBehaviourEventProcess - for Behaviour + for CoreBehaviourTSubstream> { fn inject_event(&mut self, event: IdentifyEvent) { match event { @@ -124,7 +127,7 @@ impl NetworkBehaviourEventProcess NetworkBehaviourEventProcess - for Behaviour + for CoreBehaviourTSubstream> { fn inject_event(&mut self, _event: PingEvent) { // not interested in ping responses at the moment. @@ -133,14 +136,14 @@ impl NetworkBehaviourEventProcess // implement the discovery behaviour (currently kademlia) impl NetworkBehaviourEventProcess - for Behaviour + for CoreBehaviourTSubstream> { fn inject_event(&mut self, _out: KademliaOut) { // not interested in kademlia results at the moment } } -impl Behaviour { +impl CoreBehaviourTSubstream> { pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self { let local_peer_id = local_public_key.clone().into_peer_id(); let identify_config = net_conf.identify_config.clone(); @@ -174,17 +177,14 @@ impl Behaviour { } /// Implements the combined behaviour for the libp2p service. -impl Behaviour { +impl CoreBehaviourTSubstream> { + /* Pubsub behaviour functions */ + /// Subscribes to a gossipsub topic. pub fn subscribe(&mut self, topic: Topic) -> bool { self.gossipsub.subscribe(topic) } - /// Sends an RPC Request/Response via the RPC protocol. - pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { - self.serenity_rpc.send_rpc(peer_id, rpc_event); - } - /// Publishes a message on the pubsub (gossipsub) behaviour. pub fn publish(&mut self, topics: Vec, message: PubsubMessage) { let message_bytes = ssz_encode(&message); @@ -192,10 +192,18 @@ impl Behaviour { self.gossipsub.publish(topic, message_bytes.clone()); } } + + /* Eth2 RPC behaviour functions */ + + /// Sends an RPC Request/Response via the RPC protocol. + pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { + self.serenity_rpc.send_rpc(peer_id, rpc_event); + } + } /// The types of events than can be obtained from polling the behaviour. -pub enum BehaviourEvent { +pub enum CoreBehaviourEvent { RPC(PeerId, RPCEvent), PeerDialed(PeerId), Identified(PeerId, Box), diff --git a/beacon_node/eth2-libp2p/src/core-behaviour.rs b/beacon_node/eth2-libp2p/src/core-behaviour.rs new file mode 100644 index 000000000..e59183b4c --- /dev/null +++ b/beacon_node/eth2-libp2p/src/core-behaviour.rs @@ -0,0 +1,279 @@ +use crate::discovery::Discovery; +use crate::rpc::{RPCEvent, RPCMessage, Rpc}; +use crate::NetworkConfig; +use crate::{Topic, TopicHash}; +use futures::prelude::*; +use libp2p::Multiaddr; +use libp2p::{ + core::{ + swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess}, + PublicKey, + }, + gossipsub::{Gossipsub, GossipsubEvent}, + identify::{protocol::IdentifyInfo, Identify, IdentifyEvent}, + kad::KademliaOut, + ping::{Ping, PingEvent}, + tokio_io::{AsyncRead, AsyncWrite}, + NetworkBehaviour, PeerId, +}; +use slog::{debug, o, trace, warn}; +use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream}; +use std::collections::HashMap; +use types::{Attestation, BeaconBlock}; + +/// Builds the network behaviour that manages the core protocols of eth2. +/// This core behaviour is managed by `Behaviour` which adds peer management to all core +/// behaviours. +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "CoreBehaviourEvent", poll_method = "poll")] +pub struct CoreBehaviour { + /// The routing pub-sub mechanism for eth2. + gossipsub: Gossipsub, + /// The serenity RPC specified in the wire-0 protocol. + serenity_rpc: Rpc, + /// Allows discovery of IP addresses for peers on the network. + identify: Identify, + /// Keep regular connection to peers and disconnect if absent. + ping: Ping, + /// Kademlia for peer discovery. + discovery: Discovery, + #[behaviour(ignore)] + /// The events generated by this behaviour to be consumed by the global behaviour. + events: Vec, + /// Logger for behaviour actions. + #[behaviour(ignore)] + log: slog::Logger, +} + +impl CoreBehaviour { + pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self { + let local_peer_id = local_public_key.clone().into_peer_id(); + let identify_config = net_conf.identify_config.clone(); + let behaviour_log = log.new(o!()); + + CoreBehaviour { + serenity_rpc: Rpc::new(log), + gossipsub: Gossipsub::new(local_peer_id.clone(), net_conf.gs_config.clone()), + discovery: Discovery::new(local_peer_id, log), + identify: Identify::new( + identify_config.version, + identify_config.user_agent, + local_public_key, + ), + ping: Ping::new(), + events: Vec::new(), + log: behaviour_log, + } + } + + /// Consumes the events list when polled. + fn poll( + &mut self, + ) -> Async> { + if !self.events.is_empty() { + return Async::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0))); + } + + Async::NotReady + } +} + +// Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for CoreBehaviour +impl NetworkBehaviourEventProcess + for CoreBehaviour +{ + fn inject_event(&mut self, event: GossipsubEvent) { + match event { + GossipsubEvent::Message(gs_msg) => { + trace!(self.log, "Received GossipEvent"; "msg" => format!("{:?}", gs_msg)); + + let pubsub_message = match PubsubMessage::ssz_decode(&gs_msg.data, 0) { + //TODO: Punish peer on error + Err(e) => { + warn!( + self.log, + "Received undecodable message from Peer {:?} error", gs_msg.source; + "error" => format!("{:?}", e) + ); + return; + } + Ok((msg, _index)) => msg, + }; + + self.events.push(BehaviourEvent::GossipMessage { + source: gs_msg.source, + topics: gs_msg.topics, + message: pubsub_message, + }); + } + GossipsubEvent::Subscribed { + peer_id: _, + topic: _, + } + | GossipsubEvent::Unsubscribed { + peer_id: _, + topic: _, + } => {} + } + } +} + +impl NetworkBehaviourEventProcess + for CoreBehaviour +{ + fn inject_event(&mut self, event: RPCMessage) { + match event { + RPCMessage::PeerDialed(peer_id) => { + self.events.push(BehaviourEvent::PeerDialed(peer_id)) + } + RPCMessage::RPC(peer_id, rpc_event) => { + self.events.push(BehaviourEvent::RPC(peer_id, rpc_event)) + } + } + } +} + +impl NetworkBehaviourEventProcess + for CoreBehaviour +{ + fn inject_event(&mut self, event: IdentifyEvent) { + match event { + IdentifyEvent::Identified { + peer_id, mut info, .. + } => { + if info.listen_addrs.len() > 20 { + debug!( + self.log, + "More than 20 peers have been identified, truncating" + ); + info.listen_addrs.truncate(20); + } + trace!(self.log, "Found addresses"; "Peer Id" => format!("{:?}", peer_id), "Addresses" => format!("{:?}", info.listen_addrs)); + // inject the found addresses into our discovery behaviour + for address in &info.listen_addrs { + self.discovery + .add_connected_address(&peer_id, address.clone()); + } + self.events.push(BehaviourEvent::Identified(peer_id, info)); + } + IdentifyEvent::Error { .. } => {} + IdentifyEvent::SendBack { .. } => {} + } + } +} + +impl NetworkBehaviourEventProcess + for CoreBehaviour +{ + fn inject_event(&mut self, _event: PingEvent) { + // not interested in ping responses at the moment. + } +} + +// implement the discovery behaviour (currently kademlia) +impl NetworkBehaviourEventProcess + for CoreBehaviour +{ + fn inject_event(&mut self, _out: KademliaOut) { + // not interested in kademlia results at the moment + } +} + +/// Implements the combined behaviour for the libp2p service. +impl CoreBehaviour { + /* Pubsub behaviour functions */ + + /// Subscribes to a gossipsub topic. + pub fn subscribe(&mut self, topic: Topic) -> bool { + self.gossipsub.subscribe(topic) + } + + /// Publishes a message on the pubsub (gossipsub) behaviour. + pub fn publish(&mut self, topics: Vec, message: PubsubMessage) { + let message_bytes = ssz_encode(&message); + for topic in topics { + self.gossipsub.publish(topic, message_bytes.clone()); + } + } + + /* Eth2 RPC behaviour functions */ + + /// Sends an RPC Request/Response via the RPC protocol. + pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { + self.serenity_rpc.send_rpc(peer_id, rpc_event); + } +} + +/// The types of events than can be obtained from polling the behaviour. +pub enum CoreBehaviourEvent { + RPC(PeerId, RPCEvent), + PeerDialed(PeerId), + Identified(PeerId, IdentifyInfo), + // TODO: This is a stub at the moment + GossipMessage { + source: PeerId, + topics: Vec, + message: PubsubMessage, + }, +} + +/// Messages that are passed to and from the pubsub (Gossipsub) behaviour. +#[derive(Debug, Clone, PartialEq)] +pub enum PubsubMessage { + /// Gossipsub message providing notification of a new block. + Block(BeaconBlock), + /// Gossipsub message providing notification of a new attestation. + Attestation(Attestation), +} + +//TODO: Correctly encode/decode enums. Prefixing with integer for now. +impl Encodable for PubsubMessage { + fn ssz_append(&self, s: &mut SszStream) { + match self { + PubsubMessage::Block(block_gossip) => { + 0u32.ssz_append(s); + block_gossip.ssz_append(s); + } + PubsubMessage::Attestation(attestation_gossip) => { + 1u32.ssz_append(s); + attestation_gossip.ssz_append(s); + } + } + } +} + +impl Decodable for PubsubMessage { + fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError> { + let (id, index) = u32::ssz_decode(bytes, index)?; + match id { + 0 => { + let (block, index) = BeaconBlock::ssz_decode(bytes, index)?; + Ok((PubsubMessage::Block(block), index)) + } + 1 => { + let (attestation, index) = Attestation::ssz_decode(bytes, index)?; + Ok((PubsubMessage::Attestation(attestation), index)) + } + _ => Err(DecodeError::Invalid), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use types::*; + + #[test] + fn ssz_encoding() { + let original = PubsubMessage::Block(BeaconBlock::empty(&ChainSpec::foundation())); + + let encoded = ssz_encode(&original); + + println!("{:?}", encoded); + + let (decoded, _i) = PubsubMessage::ssz_decode(&encoded, 0).unwrap(); + + assert_eq!(original, decoded); + } +} diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index d6fd43ef4..dc91b487c 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -9,7 +9,6 @@ use libp2p::core::swarm::{ use libp2p::core::{Multiaddr, PeerId, ProtocolsHandler}; use libp2p::kad::{Kademlia, KademliaOut}; use slog::{debug, o, warn}; -use std::collections::HashMap; use std::time::{Duration, Instant}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -27,8 +26,6 @@ pub struct Discovery { discovery: Kademlia, /// The delay between peer discovery searches. peer_discovery_delay: Delay, - /// Mapping of known addresses for peer ids. - known_peers: HashMap>, /// Logger for the discovery behaviour. log: slog::Logger, } @@ -37,10 +34,8 @@ impl Discovery { pub fn new(local_peer_id: PeerId, log: &slog::Logger) -> Self { let log = log.new(o!("Service" => "Libp2p-Discovery")); Self { - // events: Vec::new(), discovery: Kademlia::new(local_peer_id), peer_discovery_delay: Delay::new(Instant::now()), - known_peers: HashMap::new(), log, } } @@ -59,13 +54,6 @@ impl Discovery { /// We have discovered an address for a peer, add it to known peers. pub fn add_connected_address(&mut self, peer_id: &PeerId, address: Multiaddr) { - let known_peers = self - .known_peers - .entry(peer_id.clone()) - .or_insert_with(|| vec![]); - if !known_peers.contains(&address) { - known_peers.push(address.clone()); - } // pass the address on to kademlia self.discovery.add_connected_address(peer_id, address); } @@ -160,6 +148,7 @@ where }, _ => {} }; + // propagate result upwards return Async::Ready(action); } Async::NotReady => (), diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index ebf71aa4e..cd8ba0820 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -13,7 +13,7 @@ store = { path = "../store" } eth2-libp2p = { path = "../eth2-libp2p" } version = { path = "../version" } types = { path = "../../eth2/types" } -slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } +slog = { version = "^2.2.3" } ssz = { path = "../../eth2/utils/ssz" } tree_hash = { path = "../../eth2/utils/tree_hash" } futures = "0.1.25" From fc8dc6dfa7728950dc1460df69182cbfd62125f1 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 13 May 2019 17:50:11 +1000 Subject: [PATCH 14/39] Update to lastest libp2p --- beacon_node/client/src/client_config.rs | 2 +- beacon_node/eth2-libp2p/src/behaviour.rs | 42 +-- beacon_node/eth2-libp2p/src/core-behaviour.rs | 279 ------------------ beacon_node/eth2-libp2p/src/service.rs | 9 +- 4 files changed, 31 insertions(+), 301 deletions(-) delete mode 100644 beacon_node/eth2-libp2p/src/core-behaviour.rs diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index 4d0e286b0..74115c547 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -1,6 +1,5 @@ use clap::ArgMatches; use eth2_libp2p::multiaddr::Protocol; -use eth2_libp2p::multiaddr::ToMultiaddr; use eth2_libp2p::Multiaddr; use fork_choice::ForkChoiceAlgorithm; use http_server::HttpServerConfig; @@ -51,6 +50,7 @@ impl Default for ClientConfig { } impl ClientConfig { +<<<<<<< HEAD /// Returns the path to which the client may initialize an on-disk database. pub fn db_path(&self) -> Option { self.data_dir() diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 7ddbd95b7..58f603276 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -3,7 +3,6 @@ use crate::rpc::{RPCEvent, RPCMessage, Rpc}; use crate::NetworkConfig; use crate::{Topic, TopicHash}; use futures::prelude::*; -use libp2p::Multiaddr; use libp2p::{ core::{ swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess}, @@ -12,23 +11,24 @@ use libp2p::{ gossipsub::{Gossipsub, GossipsubEvent}, identify::{protocol::IdentifyInfo, Identify, IdentifyEvent}, kad::KademliaOut, - ping::{Ping, PingEvent}, + ping::{Ping, PingConfig, PingEvent}, tokio_io::{AsyncRead, AsyncWrite}, NetworkBehaviour, PeerId, }; use slog::{debug, o, trace, warn}; use ssz::{ssz_encode, Decode, DecodeError, Encode}; +use std::collections::HashMap; +use std::num::NonZeroU32; use std::time::{Duration, Instant}; use tokio_timer::Delay; -use std::collections::HashMap; use types::{Attestation, BeaconBlock}; /// Builds the network behaviour that manages the core protocols of eth2. /// This core behaviour is managed by `Behaviour` which adds peer management to all core /// behaviours. #[derive(NetworkBehaviour)] -#[behaviour(out_event = "CoreBehaviourEvent", poll_method = "poll")] -pub struct CoreCoreBehaviourTSubstream: AsyncRead + AsyncWrite> { +#[behaviour(out_event = "BehaviourEvent", poll_method = "poll")] +pub struct Behaviour { /// The routing pub-sub mechanism for eth2. gossipsub: Gossipsub, /// The serenity RPC specified in the wire-0 protocol. @@ -41,7 +41,7 @@ pub struct CoreCoreBehaviourTSubstream: AsyncRead + AsyncWrite> { discovery: Discovery, #[behaviour(ignore)] /// The events generated by this behaviour to be consumed in the swarm poll. - events: Vec, + events: Vec, /// Logger for behaviour actions. #[behaviour(ignore)] log: slog::Logger, @@ -49,7 +49,7 @@ pub struct CoreCoreBehaviourTSubstream: AsyncRead + AsyncWrite> { // Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for Behaviour impl NetworkBehaviourEventProcess - for CoreBehaviourTSubstream> + for Behaviour { fn inject_event(&mut self, event: GossipsubEvent) { match event { @@ -82,7 +82,7 @@ impl NetworkBehaviourEventProcess NetworkBehaviourEventProcess - for CoreBehaviourTSubstream> + for Behaviour { fn inject_event(&mut self, event: RPCMessage) { match event { @@ -97,7 +97,7 @@ impl NetworkBehaviourEventProcess NetworkBehaviourEventProcess - for CoreBehaviourTSubstream> + for Behaviour { fn inject_event(&mut self, event: IdentifyEvent) { match event { @@ -127,7 +127,7 @@ impl NetworkBehaviourEventProcess NetworkBehaviourEventProcess - for CoreBehaviourTSubstream> + for Behaviour { fn inject_event(&mut self, _event: PingEvent) { // not interested in ping responses at the moment. @@ -136,19 +136,28 @@ impl NetworkBehaviourEventProcess // implement the discovery behaviour (currently kademlia) impl NetworkBehaviourEventProcess - for CoreBehaviourTSubstream> + for Behaviour { fn inject_event(&mut self, _out: KademliaOut) { // not interested in kademlia results at the moment } } -impl CoreBehaviourTSubstream> { +impl Behaviour { pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self { let local_peer_id = local_public_key.clone().into_peer_id(); - let identify_config = net_conf.identify_config.clone(); let behaviour_log = log.new(o!()); + // identify configuration + let identify_config = net_conf.identify_config.clone(); + + // ping configuration + let ping_config = PingConfig::new() + .with_timeout(Duration::from_secs(30)) + .with_interval(Duration::from_secs(20)) + .with_max_failures(NonZeroU32::new(2).expect("2 != 0")) + .with_keep_alive(false); + Behaviour { serenity_rpc: Rpc::new(log), gossipsub: Gossipsub::new(local_peer_id.clone(), net_conf.gs_config.clone()), @@ -158,7 +167,7 @@ impl CoreBehaviourTSubstream> { identify_config.user_agent, local_public_key, ), - ping: Ping::new(), + ping: Ping::new(ping_config), events: Vec::new(), log: behaviour_log, } @@ -177,7 +186,7 @@ impl CoreBehaviourTSubstream> { } /// Implements the combined behaviour for the libp2p service. -impl CoreBehaviourTSubstream> { +impl Behaviour { /* Pubsub behaviour functions */ /// Subscribes to a gossipsub topic. @@ -199,11 +208,10 @@ impl CoreBehaviourTSubstream> { pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { self.serenity_rpc.send_rpc(peer_id, rpc_event); } - } /// The types of events than can be obtained from polling the behaviour. -pub enum CoreBehaviourEvent { +pub enum BehaviourEvent { RPC(PeerId, RPCEvent), PeerDialed(PeerId), Identified(PeerId, Box), diff --git a/beacon_node/eth2-libp2p/src/core-behaviour.rs b/beacon_node/eth2-libp2p/src/core-behaviour.rs deleted file mode 100644 index e59183b4c..000000000 --- a/beacon_node/eth2-libp2p/src/core-behaviour.rs +++ /dev/null @@ -1,279 +0,0 @@ -use crate::discovery::Discovery; -use crate::rpc::{RPCEvent, RPCMessage, Rpc}; -use crate::NetworkConfig; -use crate::{Topic, TopicHash}; -use futures::prelude::*; -use libp2p::Multiaddr; -use libp2p::{ - core::{ - swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess}, - PublicKey, - }, - gossipsub::{Gossipsub, GossipsubEvent}, - identify::{protocol::IdentifyInfo, Identify, IdentifyEvent}, - kad::KademliaOut, - ping::{Ping, PingEvent}, - tokio_io::{AsyncRead, AsyncWrite}, - NetworkBehaviour, PeerId, -}; -use slog::{debug, o, trace, warn}; -use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream}; -use std::collections::HashMap; -use types::{Attestation, BeaconBlock}; - -/// Builds the network behaviour that manages the core protocols of eth2. -/// This core behaviour is managed by `Behaviour` which adds peer management to all core -/// behaviours. -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "CoreBehaviourEvent", poll_method = "poll")] -pub struct CoreBehaviour { - /// The routing pub-sub mechanism for eth2. - gossipsub: Gossipsub, - /// The serenity RPC specified in the wire-0 protocol. - serenity_rpc: Rpc, - /// Allows discovery of IP addresses for peers on the network. - identify: Identify, - /// Keep regular connection to peers and disconnect if absent. - ping: Ping, - /// Kademlia for peer discovery. - discovery: Discovery, - #[behaviour(ignore)] - /// The events generated by this behaviour to be consumed by the global behaviour. - events: Vec, - /// Logger for behaviour actions. - #[behaviour(ignore)] - log: slog::Logger, -} - -impl CoreBehaviour { - pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self { - let local_peer_id = local_public_key.clone().into_peer_id(); - let identify_config = net_conf.identify_config.clone(); - let behaviour_log = log.new(o!()); - - CoreBehaviour { - serenity_rpc: Rpc::new(log), - gossipsub: Gossipsub::new(local_peer_id.clone(), net_conf.gs_config.clone()), - discovery: Discovery::new(local_peer_id, log), - identify: Identify::new( - identify_config.version, - identify_config.user_agent, - local_public_key, - ), - ping: Ping::new(), - events: Vec::new(), - log: behaviour_log, - } - } - - /// Consumes the events list when polled. - fn poll( - &mut self, - ) -> Async> { - if !self.events.is_empty() { - return Async::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0))); - } - - Async::NotReady - } -} - -// Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for CoreBehaviour -impl NetworkBehaviourEventProcess - for CoreBehaviour -{ - fn inject_event(&mut self, event: GossipsubEvent) { - match event { - GossipsubEvent::Message(gs_msg) => { - trace!(self.log, "Received GossipEvent"; "msg" => format!("{:?}", gs_msg)); - - let pubsub_message = match PubsubMessage::ssz_decode(&gs_msg.data, 0) { - //TODO: Punish peer on error - Err(e) => { - warn!( - self.log, - "Received undecodable message from Peer {:?} error", gs_msg.source; - "error" => format!("{:?}", e) - ); - return; - } - Ok((msg, _index)) => msg, - }; - - self.events.push(BehaviourEvent::GossipMessage { - source: gs_msg.source, - topics: gs_msg.topics, - message: pubsub_message, - }); - } - GossipsubEvent::Subscribed { - peer_id: _, - topic: _, - } - | GossipsubEvent::Unsubscribed { - peer_id: _, - topic: _, - } => {} - } - } -} - -impl NetworkBehaviourEventProcess - for CoreBehaviour -{ - fn inject_event(&mut self, event: RPCMessage) { - match event { - RPCMessage::PeerDialed(peer_id) => { - self.events.push(BehaviourEvent::PeerDialed(peer_id)) - } - RPCMessage::RPC(peer_id, rpc_event) => { - self.events.push(BehaviourEvent::RPC(peer_id, rpc_event)) - } - } - } -} - -impl NetworkBehaviourEventProcess - for CoreBehaviour -{ - fn inject_event(&mut self, event: IdentifyEvent) { - match event { - IdentifyEvent::Identified { - peer_id, mut info, .. - } => { - if info.listen_addrs.len() > 20 { - debug!( - self.log, - "More than 20 peers have been identified, truncating" - ); - info.listen_addrs.truncate(20); - } - trace!(self.log, "Found addresses"; "Peer Id" => format!("{:?}", peer_id), "Addresses" => format!("{:?}", info.listen_addrs)); - // inject the found addresses into our discovery behaviour - for address in &info.listen_addrs { - self.discovery - .add_connected_address(&peer_id, address.clone()); - } - self.events.push(BehaviourEvent::Identified(peer_id, info)); - } - IdentifyEvent::Error { .. } => {} - IdentifyEvent::SendBack { .. } => {} - } - } -} - -impl NetworkBehaviourEventProcess - for CoreBehaviour -{ - fn inject_event(&mut self, _event: PingEvent) { - // not interested in ping responses at the moment. - } -} - -// implement the discovery behaviour (currently kademlia) -impl NetworkBehaviourEventProcess - for CoreBehaviour -{ - fn inject_event(&mut self, _out: KademliaOut) { - // not interested in kademlia results at the moment - } -} - -/// Implements the combined behaviour for the libp2p service. -impl CoreBehaviour { - /* Pubsub behaviour functions */ - - /// Subscribes to a gossipsub topic. - pub fn subscribe(&mut self, topic: Topic) -> bool { - self.gossipsub.subscribe(topic) - } - - /// Publishes a message on the pubsub (gossipsub) behaviour. - pub fn publish(&mut self, topics: Vec, message: PubsubMessage) { - let message_bytes = ssz_encode(&message); - for topic in topics { - self.gossipsub.publish(topic, message_bytes.clone()); - } - } - - /* Eth2 RPC behaviour functions */ - - /// Sends an RPC Request/Response via the RPC protocol. - pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { - self.serenity_rpc.send_rpc(peer_id, rpc_event); - } -} - -/// The types of events than can be obtained from polling the behaviour. -pub enum CoreBehaviourEvent { - RPC(PeerId, RPCEvent), - PeerDialed(PeerId), - Identified(PeerId, IdentifyInfo), - // TODO: This is a stub at the moment - GossipMessage { - source: PeerId, - topics: Vec, - message: PubsubMessage, - }, -} - -/// Messages that are passed to and from the pubsub (Gossipsub) behaviour. -#[derive(Debug, Clone, PartialEq)] -pub enum PubsubMessage { - /// Gossipsub message providing notification of a new block. - Block(BeaconBlock), - /// Gossipsub message providing notification of a new attestation. - Attestation(Attestation), -} - -//TODO: Correctly encode/decode enums. Prefixing with integer for now. -impl Encodable for PubsubMessage { - fn ssz_append(&self, s: &mut SszStream) { - match self { - PubsubMessage::Block(block_gossip) => { - 0u32.ssz_append(s); - block_gossip.ssz_append(s); - } - PubsubMessage::Attestation(attestation_gossip) => { - 1u32.ssz_append(s); - attestation_gossip.ssz_append(s); - } - } - } -} - -impl Decodable for PubsubMessage { - fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError> { - let (id, index) = u32::ssz_decode(bytes, index)?; - match id { - 0 => { - let (block, index) = BeaconBlock::ssz_decode(bytes, index)?; - Ok((PubsubMessage::Block(block), index)) - } - 1 => { - let (attestation, index) = Attestation::ssz_decode(bytes, index)?; - Ok((PubsubMessage::Attestation(attestation), index)) - } - _ => Err(DecodeError::Invalid), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use types::*; - - #[test] - fn ssz_encoding() { - let original = PubsubMessage::Block(BeaconBlock::empty(&ChainSpec::foundation())); - - let encoded = ssz_encode(&original); - - println!("{:?}", encoded); - - let (decoded, _i) = PubsubMessage::ssz_decode(&encoded, 0).unwrap(); - - assert_eq!(original, decoded); - } -} diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 9cbceda8d..68ca72620 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -63,11 +63,12 @@ impl Service { .map_err(|e| format!("Invalid listen multiaddr: {}", e))? { match Swarm::listen_on(&mut swarm, address.clone()) { - Ok(mut listen_addr) => { - listen_addr.append(Protocol::P2p(local_peer_id.clone().into())); - info!(log, "Listening on: {}", listen_addr); + Ok(_) => { + let mut log_address = address.clone(); + log_address.push(Protocol::P2p(local_peer_id.clone().into())); + info!(log, "Listening on: {}", log_address); } - Err(err) => warn!(log, "Cannot listen on: {} : {:?}", address, err), + Err(err) => warn!(log, "Cannot listen on: {} because: {:?}", address, err), }; } // connect to boot nodes - these are currently stored as multiaddrs From cf459b60a9b0bfbbc59c2dd1aac2be55906c4207 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Thu, 20 Jun 2019 22:43:50 +1000 Subject: [PATCH 15/39] Updates for latest master --- beacon_node/client/src/client_config.rs | 23 ++++++---------- beacon_node/eth2-libp2p/Cargo.toml | 7 +++-- beacon_node/eth2-libp2p/src/behaviour.rs | 4 --- beacon_node/network/src/sync/simple_sync.rs | 29 +++++---------------- eth2/types/src/chain_spec.rs | 4 ++- 5 files changed, 21 insertions(+), 46 deletions(-) diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index 74115c547..93ff5f7eb 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -23,19 +23,6 @@ pub struct ClientConfig { impl Default for ClientConfig { fn default() -> Self { - let data_dir = { - let home = dirs::home_dir().expect("Unable to determine home dir."); - home.join(".lighthouse/") - }; - fs::create_dir_all(&data_dir) - .unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir)); - - // currently lighthouse spec - let default_spec = ChainSpec::lighthouse_testnet(); - let chain_type = ChainType::from(default_spec.chain_id); - // builds a chain-specific network config - let net_conf = NetworkConfig::from(chain_type); - Self { data_dir: PathBuf::from(".lighthouse"), db_type: "disk".to_string(), @@ -50,13 +37,19 @@ impl Default for ClientConfig { } impl ClientConfig { -<<<<<<< HEAD /// Returns the path to which the client may initialize an on-disk database. pub fn db_path(&self) -> Option { self.data_dir() .and_then(|path| Some(path.join(&self.db_name))) } + /// Returns the core path for the client. + pub fn data_dir(&self) -> Option { + let path = dirs::home_dir()?.join(&self.data_dir); + fs::create_dir_all(&path).ok()?; + Some(path) + } + /// Apply the following arguments to `self`, replacing values if they are specified in `args`. /// /// Returns an error if arguments are obviously invalid. May succeed even if some values are @@ -74,6 +67,6 @@ impl ClientConfig { self.rpc.apply_cli_args(args)?; self.http.apply_cli_args(args)?; - Ok(log) + Ok(()) } } diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 2deeaf5f0..b6c62255b 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -7,15 +7,14 @@ edition = "2018" [dependencies] beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" -# SigP repository until PR is merged -#libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "fb852bcc2b9b3935555cc93930e913cbec2b0688" } -libp2p = { path = "../../../sharding/rust-libp2p" } +# SigP repository +libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "8d5e5bbbe32d07ad271d6a2e15fde0347894061a" } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" ssz = { path = "../../eth2/utils/ssz" } ssz_derive = { path = "../../eth2/utils/ssz_derive" } -slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_trace"] } +slog = { version = "2.4.1" , features = ["max_level_trace", "release_max_level_trace"] } version = { path = "../version" } tokio = "0.1.16" futures = "0.1.25" diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 58f603276..ed466bb75 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -147,11 +147,7 @@ impl Behaviour { pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self { let local_peer_id = local_public_key.clone().into_peer_id(); let behaviour_log = log.new(o!()); - - // identify configuration let identify_config = net_conf.identify_config.clone(); - - // ping configuration let ping_config = PingConfig::new() .with_timeout(Duration::from_secs(30)) .with_interval(Duration::from_secs(20)) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index ff08b26d1..67bf97948 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -522,30 +522,15 @@ impl SimpleSync { BlockProcessingOutcome::ParentUnknown { .. } => { self.import_queue .enqueue_full_blocks(vec![block], peer_id.clone()); - trace!( - self.log, - "NewGossipBlock"; - "peer" => format!("{:?}", peer_id), - ); + trace!( + self.log, + "NewGossipBlock"; + "peer" => format!("{:?}", peer_id), + ); - // Ignore any block from a finalized slot. - if self.slot_is_finalized(block.slot) { - warn!( - self.log, "NewGossipBlock"; - "msg" => "new block slot is finalized.", - "block_slot" => block.slot, - ); - return false; - } + SHOULD_FORWARD_GOSSIP_BLOCK + } - let block_root = Hash256::from_slice(&block.hash_tree_root()); - - // Ignore any block that the chain already knows about. - if self.chain_has_seen_block(&block_root) { - println!("this happened"); - // TODO: Age confirm that we shouldn't forward a block if we already know of it. - return false; - } BlockProcessingOutcome::FutureSlot { present_slot, block_slot, diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 8e4bd9c9c..d35f696b9 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -107,6 +107,7 @@ pub struct ChainSpec { /* * Network specific parameters */ + pub boot_nodes: Vec, pub chain_id: u8, } @@ -216,7 +217,8 @@ impl ChainSpec { /* * Network specific */ - chain_id: 1, // foundation chain id + boot_nodes: vec![], + chain_id: 1, // mainnet chain id } } From 84ea5adffe31b511070e96aa4536f24d5e4e158c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 23 Jun 2019 12:34:00 +1000 Subject: [PATCH 16/39] Complete merging of network addition branch --- beacon_node/Cargo.toml | 1 - beacon_node/client/src/client_config.rs | 3 +-- beacon_node/eth2-libp2p/Cargo.toml | 2 +- beacon_node/eth2-libp2p/src/behaviour.rs | 6 ++++-- beacon_node/eth2-libp2p/src/config.rs | 2 +- beacon_node/eth2-libp2p/src/discovery.rs | 2 +- beacon_node/network/src/sync/simple_sync.rs | 1 - beacon_node/rpc/src/lib.rs | 1 - beacon_node/src/main.rs | 2 +- eth2/types/Cargo.toml | 1 - eth2/types/src/chain_spec.rs | 13 +++---------- 11 files changed, 12 insertions(+), 22 deletions(-) diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 783cdcda3..7e43a13df 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -22,5 +22,4 @@ tokio-timer = "0.2.10" futures = "0.1.25" exit-future = "0.1.3" state_processing = { path = "../eth2/state_processing" } -slog = "^2.2.3" env_logger = "0.6.1" diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index 93ff5f7eb..f2f356daf 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -3,7 +3,6 @@ use eth2_libp2p::multiaddr::Protocol; use eth2_libp2p::Multiaddr; use fork_choice::ForkChoiceAlgorithm; use http_server::HttpServerConfig; -use network::NetworkConfig; use network::{ChainType, NetworkConfig}; use serde_derive::{Deserialize, Serialize}; use slog::{error, o, Drain, Level}; @@ -29,7 +28,7 @@ impl Default for ClientConfig { db_name: "chain_db".to_string(), // Note: there are no default bootnodes specified. // Once bootnodes are established, add them here. - network: NetworkConfig::new(vec![]), + network: NetworkConfig::new(), rpc: rpc::RPCConfig::default(), http: HttpServerConfig::default(), } diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index b6c62255b..7206b90d9 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" # SigP repository -libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "8d5e5bbbe32d07ad271d6a2e15fde0347894061a" } +libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "71744d4090ebd93a993d1b390787919add4098fd" } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index ed466bb75..c711a2134 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -111,14 +111,16 @@ impl NetworkBehaviourEventProcess format!("{:?}", peer_id), "Addresses" => format!("{:?}", info.listen_addrs)); // inject the found addresses into our discovery behaviour + for address in &info.listen_addrs { self.discovery .add_connected_address(&peer_id, address.clone()); } + + self.events + .push(BehaviourEvent::Identified(peer_id, Box::new(info))); } IdentifyEvent::Error { .. } => {} IdentifyEvent::SendBack { .. } => {} diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index b6857cd37..baa1bf47f 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -1,8 +1,8 @@ use clap::ArgMatches; use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; +use libp2p::multiaddr::{Error as MultiaddrError, Multiaddr}; use serde_derive::{Deserialize, Serialize}; use std::time::Duration; -use types::multiaddr::{Error as MultiaddrError, Multiaddr}; /// The beacon node topic string to subscribe to. pub const BEACON_PUBSUB_TOPIC: &str = "beacon_node"; diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index dc91b487c..c3a02b16f 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -55,7 +55,7 @@ impl Discovery { /// We have discovered an address for a peer, add it to known peers. pub fn add_connected_address(&mut self, peer_id: &PeerId, address: Multiaddr) { // pass the address on to kademlia - self.discovery.add_connected_address(peer_id, address); + self.discovery.add_address(peer_id, address); } } diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 67bf97948..925221673 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -5,7 +5,6 @@ use eth2_libp2p::rpc::methods::*; use eth2_libp2p::rpc::{RPCRequest, RPCResponse, RequestId}; use eth2_libp2p::PeerId; use slog::{debug, error, info, o, trace, warn}; -use ssz::TreeHash; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 4506d90fc..3e6fd3e73 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -62,7 +62,6 @@ pub fn start_server( let instance = AttestationServiceInstance { network_chan, chain: beacon_chain.clone(), - network_chan, log: log.clone(), }; create_attestation_service(instance) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 96353ddf9..07215119f 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -4,7 +4,7 @@ use clap::{App, Arg}; use client::{ClientConfig, Eth2Config}; use env_logger::{Builder, Env}; use eth2_config::{get_data_dir, read_from_file, write_to_file}; -use slog::{crit, o, Drain}; +use slog::{crit, o, Drain, Level}; use std::path::PathBuf; pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index fa1fe6a6d..bc36a8502 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -32,7 +32,6 @@ swap_or_not_shuffle = { path = "../utils/swap_or_not_shuffle" } test_random_derive = { path = "../utils/test_random_derive" } tree_hash = { path = "../utils/tree_hash" } tree_hash_derive = { path = "../utils/tree_hash_derive" } -libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "b3c32d9a821ae6cc89079499cc6e8a6bab0bffc3" } [dev-dependencies] env_logger = "0.6.0" diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index d35f696b9..6073fb32e 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -104,10 +104,7 @@ pub struct ChainSpec { domain_voluntary_exit: u32, domain_transfer: u32, - /* - * Network specific parameters - */ - pub boot_nodes: Vec, + pub boot_nodes: Vec, pub chain_id: u8, } @@ -230,12 +227,8 @@ impl ChainSpec { pub fn minimal() -> Self { let genesis_slot = Slot::new(0); - // Note: these bootnodes are placeholders. - // - // Should be updated once static bootnodes exist. - let boot_nodes = vec!["/ip4/127.0.0.1/tcp/9000" - .parse() - .expect("correct multiaddr")]; + // Note: bootnodes to be updated when static nodes exist. + let boot_nodes = vec![]; Self { target_committee_size: 4, From 81f0b6c238ab336f177021d60f5c1b38a96bf5f8 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 25 Jun 2019 14:51:45 +1000 Subject: [PATCH 17/39] Integrate discv5 into lighthouse --- beacon_node/client/Cargo.toml | 2 +- beacon_node/client/src/client_config.rs | 71 ------- beacon_node/client/src/lib.rs | 4 +- beacon_node/eth2-libp2p/Cargo.toml | 3 +- beacon_node/eth2-libp2p/src/behaviour.rs | 116 ++++------- beacon_node/eth2-libp2p/src/config.rs | 143 ++++++------- beacon_node/eth2-libp2p/src/discovery.rs | 246 ++++++++++++++++------- beacon_node/eth2-libp2p/src/lib.rs | 4 +- beacon_node/eth2-libp2p/src/service.rs | 46 +---- beacon_node/network/src/lib.rs | 2 +- beacon_node/src/main.rs | 24 ++- beacon_node/src/run.rs | 2 +- 12 files changed, 312 insertions(+), 351 deletions(-) delete mode 100644 beacon_node/client/src/client_config.rs diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index f97173bea..64dbf9c24 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -16,7 +16,7 @@ types = { path = "../../eth2/types" } tree_hash = { path = "../../eth2/utils/tree_hash" } eth2_config = { path = "../../eth2/utils/eth2_config" } slot_clock = { path = "../../eth2/utils/slot_clock" } -serde = "1.0" +serde = "1.0.93" serde_derive = "1.0" error-chain = "0.12.0" slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_trace"] } diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs deleted file mode 100644 index f2f356daf..000000000 --- a/beacon_node/client/src/client_config.rs +++ /dev/null @@ -1,71 +0,0 @@ -use clap::ArgMatches; -use eth2_libp2p::multiaddr::Protocol; -use eth2_libp2p::Multiaddr; -use fork_choice::ForkChoiceAlgorithm; -use http_server::HttpServerConfig; -use network::{ChainType, NetworkConfig}; -use serde_derive::{Deserialize, Serialize}; -use slog::{error, o, Drain, Level}; -use std::fs; -use std::path::PathBuf; - -/// The core configuration of a Lighthouse beacon node. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ClientConfig { - pub data_dir: PathBuf, - pub db_type: String, - db_name: String, - pub network: network::NetworkConfig, - pub rpc: rpc::RPCConfig, - pub http: HttpServerConfig, -} - -impl Default for ClientConfig { - fn default() -> Self { - Self { - data_dir: PathBuf::from(".lighthouse"), - db_type: "disk".to_string(), - db_name: "chain_db".to_string(), - // Note: there are no default bootnodes specified. - // Once bootnodes are established, add them here. - network: NetworkConfig::new(), - rpc: rpc::RPCConfig::default(), - http: HttpServerConfig::default(), - } - } -} - -impl ClientConfig { - /// Returns the path to which the client may initialize an on-disk database. - pub fn db_path(&self) -> Option { - self.data_dir() - .and_then(|path| Some(path.join(&self.db_name))) - } - - /// Returns the core path for the client. - pub fn data_dir(&self) -> Option { - let path = dirs::home_dir()?.join(&self.data_dir); - fs::create_dir_all(&path).ok()?; - Some(path) - } - - /// Apply the following arguments to `self`, replacing values if they are specified in `args`. - /// - /// Returns an error if arguments are obviously invalid. May succeed even if some values are - /// invalid. - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { - if let Some(dir) = args.value_of("datadir") { - self.data_dir = PathBuf::from(dir); - }; - - if let Some(dir) = args.value_of("db") { - self.db_type = dir.to_string(); - } - - self.network.apply_cli_args(args)?; - self.rpc.apply_cli_args(args)?; - self.http.apply_cli_args(args)?; - - Ok(()) - } -} diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 18ddef7bb..7eee8ac0a 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -1,7 +1,7 @@ extern crate slog; mod beacon_chain_types; -mod client_config; +mod config; pub mod error; pub mod notifier; @@ -21,7 +21,7 @@ use tokio::timer::Interval; pub use beacon_chain::BeaconChainTypes; pub use beacon_chain_types::ClientType; pub use beacon_chain_types::InitialiseBeaconChain; -pub use client_config::ClientConfig; +pub use config::Config as ClientConfig; pub use eth2_config::Eth2Config; /// Main beacon node client service. This provides the connection and initialisation of the clients diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 7206b90d9..c47a4389f 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -8,7 +8,8 @@ edition = "2018" beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" # SigP repository -libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "71744d4090ebd93a993d1b390787919add4098fd" } +libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "f018f5c443ed5a93de890048dbc6755393373e72" } +enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "f018f5c443ed5a93de890048dbc6755393373e72", features = ["serde"] } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index c711a2134..4e4cf24f3 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -1,26 +1,23 @@ use crate::discovery::Discovery; use crate::rpc::{RPCEvent, RPCMessage, Rpc}; -use crate::NetworkConfig; +use crate::{error, NetworkConfig}; use crate::{Topic, TopicHash}; use futures::prelude::*; use libp2p::{ core::{ + identity::Keypair, swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess}, - PublicKey, }, + discv5::Discv5Event, gossipsub::{Gossipsub, GossipsubEvent}, - identify::{protocol::IdentifyInfo, Identify, IdentifyEvent}, - kad::KademliaOut, ping::{Ping, PingConfig, PingEvent}, tokio_io::{AsyncRead, AsyncWrite}, NetworkBehaviour, PeerId, }; -use slog::{debug, o, trace, warn}; +use slog::{o, trace, warn}; use ssz::{ssz_encode, Decode, DecodeError, Encode}; -use std::collections::HashMap; use std::num::NonZeroU32; -use std::time::{Duration, Instant}; -use tokio_timer::Delay; +use std::time::Duration; use types::{Attestation, BeaconBlock}; /// Builds the network behaviour that manages the core protocols of eth2. @@ -33,8 +30,6 @@ pub struct Behaviour { gossipsub: Gossipsub, /// The serenity RPC specified in the wire-0 protocol. serenity_rpc: Rpc, - /// Allows discovery of IP addresses for peers on the network. - identify: Identify, /// Keep regular connection to peers and disconnect if absent. ping: Ping, /// Kademlia for peer discovery. @@ -47,6 +42,31 @@ pub struct Behaviour { log: slog::Logger, } +impl Behaviour { + pub fn new( + local_key: &Keypair, + net_conf: &NetworkConfig, + log: &slog::Logger, + ) -> error::Result { + let local_peer_id = local_key.public().clone().into_peer_id(); + let behaviour_log = log.new(o!()); + let ping_config = PingConfig::new() + .with_timeout(Duration::from_secs(30)) + .with_interval(Duration::from_secs(20)) + .with_max_failures(NonZeroU32::new(2).expect("2 != 0")) + .with_keep_alive(false); + + Ok(Behaviour { + 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), + events: Vec::new(), + log: behaviour_log, + }) + } +} + // Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for Behaviour impl NetworkBehaviourEventProcess for Behaviour @@ -96,38 +116,6 @@ impl NetworkBehaviourEventProcess NetworkBehaviourEventProcess - for Behaviour -{ - fn inject_event(&mut self, event: IdentifyEvent) { - match event { - IdentifyEvent::Identified { - peer_id, mut info, .. - } => { - if info.listen_addrs.len() > 20 { - debug!( - self.log, - "More than 20 peers have been identified, truncating" - ); - info.listen_addrs.truncate(20); - } - trace!(self.log, "Found addresses"; "Peer Id" => format!("{:?}", peer_id), "Addresses" => format!("{:?}", info.listen_addrs)); - // inject the found addresses into our discovery behaviour - - for address in &info.listen_addrs { - self.discovery - .add_connected_address(&peer_id, address.clone()); - } - - self.events - .push(BehaviourEvent::Identified(peer_id, Box::new(info))); - } - IdentifyEvent::Error { .. } => {} - IdentifyEvent::SendBack { .. } => {} - } - } -} - impl NetworkBehaviourEventProcess for Behaviour { @@ -136,41 +124,7 @@ impl NetworkBehaviourEventProcess } } -// implement the discovery behaviour (currently kademlia) -impl NetworkBehaviourEventProcess - for Behaviour -{ - fn inject_event(&mut self, _out: KademliaOut) { - // not interested in kademlia results at the moment - } -} - impl Behaviour { - pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self { - let local_peer_id = local_public_key.clone().into_peer_id(); - let behaviour_log = log.new(o!()); - let identify_config = net_conf.identify_config.clone(); - let ping_config = PingConfig::new() - .with_timeout(Duration::from_secs(30)) - .with_interval(Duration::from_secs(20)) - .with_max_failures(NonZeroU32::new(2).expect("2 != 0")) - .with_keep_alive(false); - - Behaviour { - serenity_rpc: Rpc::new(log), - gossipsub: Gossipsub::new(local_peer_id.clone(), net_conf.gs_config.clone()), - discovery: Discovery::new(local_peer_id, log), - identify: Identify::new( - identify_config.version, - identify_config.user_agent, - local_public_key, - ), - ping: Ping::new(ping_config), - events: Vec::new(), - log: behaviour_log, - } - } - /// Consumes the events list when polled. fn poll( &mut self, @@ -183,6 +137,14 @@ impl Behaviour { } } +impl NetworkBehaviourEventProcess + for Behaviour +{ + fn inject_event(&mut self, _event: Discv5Event) { + // discv5 has no events to inject + } +} + /// Implements the combined behaviour for the libp2p service. impl Behaviour { /* Pubsub behaviour functions */ @@ -212,8 +174,6 @@ impl Behaviour { pub enum BehaviourEvent { RPC(PeerId, RPCEvent), PeerDialed(PeerId), - Identified(PeerId, Box), - // TODO: This is a stub at the moment GossipMessage { source: PeerId, topics: Vec, diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index baa1bf47f..00a8ed51e 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -1,29 +1,44 @@ use clap::ArgMatches; -use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; -use libp2p::multiaddr::{Error as MultiaddrError, Multiaddr}; +use enr::Enr; +use libp2p::{ + gossipsub::{GossipsubConfig, GossipsubConfigBuilder}, + multiaddr::Multiaddr, +}; use serde_derive::{Deserialize, Serialize}; use std::time::Duration; /// The beacon node topic string to subscribe to. -pub const BEACON_PUBSUB_TOPIC: &str = "beacon_node"; -pub const SHARD_TOPIC_PREFIX: &str = "attestations"; // single topic for all attestation for the moment. +pub const BEACON_PUBSUB_TOPIC: &str = "beacon_block"; +pub const BEACON_ATTESTATION_TOPIC: &str = "beacon_attestation"; +//TODO: Implement shard subnets +pub const SHARD_TOPIC_PREFIX: &str = "shard"; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] /// Network configuration for lighthouse. pub struct Config { /// IP address to listen on. - listen_addresses: Vec, + pub listen_addresses: Vec, + + /// Specifies the IP address that the discovery protocol will listen on. + pub discovery_address: std::net::IpAddr, + + /// UDP port that discovery listens on. + pub discovery_port: u16, + + /// Target number of connected peers. + pub max_peers: usize, + /// Gossipsub configuration parameters. #[serde(skip)] pub gs_config: GossipsubConfig, - /// Configuration parameters for node identification protocol. - #[serde(skip)] - pub identify_config: IdentifyConfig, + /// List of nodes to initially connect to. - boot_nodes: Vec, + pub boot_nodes: Vec, + /// Client version pub client_version: String, + /// List of extra topics to initially subscribe to as strings. pub topics: Vec, } @@ -32,13 +47,16 @@ impl Default for Config { /// Generate a default network configuration. fn default() -> Self { Config { - listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000".to_string()], + listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000".parse().expect("vaild multiaddr")], + discovery_address: "0.0.0.0".parse().expect("valid ip address"), + discovery_port: 9000, + max_peers: 10, + //TODO: Set realistic values for production gs_config: GossipsubConfigBuilder::new() .max_gossip_size(4_000_000) .inactivity_timeout(Duration::from_secs(90)) .heartbeat_interval(Duration::from_secs(20)) .build(), - identify_config: IdentifyConfig::default(), boot_nodes: vec![], client_version: version::version(), topics: Vec::new(), @@ -52,83 +70,42 @@ impl Config { Config::default() } - pub fn listen_addresses(&self) -> Result, MultiaddrError> { - self.listen_addresses.iter().map(|s| s.parse()).collect() - } - - pub fn boot_nodes(&self) -> Result, MultiaddrError> { - self.boot_nodes.iter().map(|s| s.parse()).collect() - } - - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { + pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { if let Some(listen_address_str) = args.value_of("listen-address") { - let listen_addresses = listen_address_str.split(',').map(Into::into).collect(); - self.listen_addresses = listen_addresses; + self.listen_addresses = listen_address_str + .split(',') + .map(|a| { + a.parse::() + .map_err(|_| format!("Invalid Listen address: {:?}", a)) + }) + .collect::, _>>()?; } - if let Some(boot_addresses_str) = args.value_of("boot-nodes") { - let boot_addresses = boot_addresses_str.split(',').map(Into::into).collect(); - self.boot_nodes = boot_addresses; + if let Some(max_peers_str) = args.value_of("maxpeers") { + self.max_peers = max_peers_str + .parse::() + .map_err(|_| format!("Invalid number of max peers: {}", max_peers_str))?; + } + + if let Some(discovery_address_str) = args.value_of("disc-listen-address") { + self.discovery_address = discovery_address_str + .parse::() + .map_err(|_| format!("Invalid discovery address: {:?}", discovery_address_str))?; + } + + if let Some(boot_enr_str) = args.value_of("boot-nodes") { + self.boot_nodes = boot_enr_str + .split(',') + .map(|enr| enr.parse().map_err(|_| format!("Invalid ENR: {}", enr))) + .collect::, _>>()?; + } + + if let Some(disc_port_str) = args.value_of("disc-port") { + self.discovery_port = disc_port_str + .parse::() + .map_err(|_| format!("Invalid discovery port: {}", disc_port_str))?; } Ok(()) } } - -/// The configuration parameters for the Identify protocol -#[derive(Debug, Clone)] -pub struct IdentifyConfig { - /// The protocol version to listen on. - pub version: String, - /// The client's name and version for identification. - pub user_agent: String, -} - -impl Default for IdentifyConfig { - fn default() -> Self { - Self { - version: "/eth/serenity/1.0".to_string(), - user_agent: version::version(), - } - } -} - -/// Creates a standard network config from a chain_id. -/// -/// This creates specified network parameters for each chain type. -impl From for Config { - fn from(chain_type: ChainType) -> Self { - match chain_type { - ChainType::Foundation => Config::default(), - - ChainType::LighthouseTestnet => { - let boot_nodes = vec!["/ip4/127.0.0.1/tcp/9000" - .parse() - .expect("correct multiaddr")]; - Self { - boot_nodes, - ..Config::default() - } - } - - ChainType::Other => Config::default(), - } - } -} - -pub enum ChainType { - Foundation, - LighthouseTestnet, - Other, -} - -/// Maps a chain id to a ChainType. -impl From for ChainType { - fn from(chain_id: u8) -> Self { - match chain_id { - 1 => ChainType::Foundation, - 2 => ChainType::LighthouseTestnet, - _ => ChainType::Other, - } - } -} diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index c3a02b16f..9a1e75691 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -1,3 +1,4 @@ +use crate::{error, NetworkConfig}; /// This manages the discovery and management of peers. /// /// Currently using Kademlia for peer discovery. @@ -6,66 +7,154 @@ use futures::prelude::*; use libp2p::core::swarm::{ ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; -use libp2p::core::{Multiaddr, PeerId, ProtocolsHandler}; -use libp2p::kad::{Kademlia, KademliaOut}; -use slog::{debug, o, warn}; +use libp2p::core::{identity::Keypair, Multiaddr, PeerId, ProtocolsHandler}; +use libp2p::discv5::{Discv5, Discv5Event}; +use libp2p::enr::{Enr, EnrBuilder, NodeId}; +use libp2p::multiaddr::Protocol; +use slog::{debug, error, info, o, warn}; +use std::collections::HashSet; use std::time::{Duration, Instant}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -//TODO: Make this dynamic -const TIME_BETWEEN_KAD_REQUESTS: Duration = Duration::from_secs(30); +/// Maximum seconds before searching for extra peers. +const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 60; -/// Maintains a list of discovered peers and implements the discovery protocol to discover new -/// peers. +/// Lighthouse discovery behaviour. This provides peer management and discovery using the Discv5 +/// libp2p protocol. pub struct Discovery { - /// Queue of events to processed. - // TODO: Re-implement as discovery protocol grows - // events: Vec>, - /// The discovery behaviour used to discover new peers. - discovery: Kademlia, + /// The peers currently connected to libp2p streams. + connected_peers: HashSet, + + /// The target number of connected peers on the libp2p interface. + max_peers: usize, + /// The delay between peer discovery searches. peer_discovery_delay: Delay, + + /// Tracks the last discovery delay. The delay is doubled each round until the max + /// time is reached. + past_discovery_delay: u64, + + /// The TCP port for libp2p. Used to convert an updated IP address to a multiaddr. Note: This + /// assumes that the external TCP port is the same as the internal TCP port if behind a NAT. + //TODO: Improve NAT handling limit the above restriction + tcp_port: u16, + + /// The discovery behaviour used to discover new peers. + discovery: Discv5, + /// Logger for the discovery behaviour. log: slog::Logger, } impl Discovery { - pub fn new(local_peer_id: PeerId, log: &slog::Logger) -> Self { + pub fn new( + local_key: &Keypair, + net_conf: &NetworkConfig, + log: &slog::Logger, + ) -> error::Result { let log = log.new(o!("Service" => "Libp2p-Discovery")); - Self { - discovery: Kademlia::new(local_peer_id), - peer_discovery_delay: Delay::new(Instant::now()), - log, + + // Build the local ENR. + // The first TCP listening address is used for the ENR record. This will inform our peers to + // connect to this TCP port and establish libp2p streams. + // Note: Discovery should update the ENR record's IP to the external IP as seen by the + // majority of our peers. + let tcp_multiaddr = net_conf + .listen_addresses + .iter() + .filter(|a| { + if let Some(Protocol::Tcp(_)) = a.iter().last() { + true + } else { + false + } + }) + .next() + .ok_or_else(|| "No valid TCP addresses")?; + + let ip: std::net::IpAddr = match tcp_multiaddr.iter().next() { + Some(Protocol::Ip4(ip)) => ip.into(), + Some(Protocol::Ip6(ip)) => ip.into(), + _ => { + error!(log, "Multiaddr has an invalid IP address"); + return Err(format!("Invalid IP Address: {}", tcp_multiaddr).into()); + } + }; + + let tcp_port = match tcp_multiaddr.iter().last() { + Some(Protocol::Tcp(tcp)) => tcp, + _ => unreachable!(), + }; + + let local_enr = EnrBuilder::new() + .ip(ip.into()) + .tcp(tcp_port) + .udp(net_conf.discovery_port) + .build(&local_key) + .map_err(|e| format!("Could not build Local ENR: {:?}", e))?; + info!(log, "Local ENR: {}", local_enr.to_base64()); + + let mut discovery = Discv5::new(local_enr, local_key.clone(), net_conf.discovery_address) + .map_err(|e| format!("Discv5 service failed: {:?}", e))?; + + // Add bootnodes to routing table + for bootnode_enr in net_conf.boot_nodes.clone() { + discovery.add_enr(bootnode_enr); } + + Ok(Self { + connected_peers: HashSet::new(), + max_peers: net_conf.max_peers, + peer_discovery_delay: Delay::new(Instant::now()), + past_discovery_delay: 1, + tcp_port, + discovery, + log, + }) } - /// Uses discovery to search for new peers. - pub fn find_peers(&mut self) { - // pick a random PeerId - let random_peer = PeerId::random(); + /// Manually search for peers. This restarts the discovery round, sparking multiple rapid + /// queries. + pub fn discover_peers(&mut self) { + self.past_discovery_delay = 1; + self.find_peers(); + } + + /// Add an Enr to the routing table of the discovery mechanism. + pub fn add_enr(&mut self, enr: Enr) { + self.discovery.add_enr(enr); + } + + /// Search for new peers using the underlying discovery mechanism. + fn find_peers(&mut self) { + // pick a random NodeId + let random_node = NodeId::random(); debug!(self.log, "Searching for peers..."); - self.discovery.find_node(random_peer); + self.discovery.find_node(random_node); - // update the kademlia timeout + // update the time until next discovery + let delay = { + if self.past_discovery_delay < MAX_TIME_BETWEEN_PEER_SEARCHES { + self.past_discovery_delay *= 2; + self.past_discovery_delay + } else { + MAX_TIME_BETWEEN_PEER_SEARCHES + } + }; self.peer_discovery_delay - .reset(Instant::now() + TIME_BETWEEN_KAD_REQUESTS); - } - - /// We have discovered an address for a peer, add it to known peers. - pub fn add_connected_address(&mut self, peer_id: &PeerId, address: Multiaddr) { - // pass the address on to kademlia - self.discovery.add_address(peer_id, address); + .reset(Instant::now() + Duration::from_secs(delay)); } } -// Redirect all behaviour event to underlying discovery behaviour. +// Redirect all behaviour events to underlying discovery behaviour. impl NetworkBehaviour for Discovery where TSubstream: AsyncRead + AsyncWrite, { - type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; - type OutEvent = as NetworkBehaviour>::OutEvent; + type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; + type OutEvent = as NetworkBehaviour>::OutEvent; fn new_handler(&mut self) -> Self::ProtocolsHandler { NetworkBehaviour::new_handler(&mut self.discovery) @@ -76,25 +165,29 @@ where self.discovery.addresses_of_peer(peer_id) } - fn inject_connected(&mut self, peer_id: PeerId, endpoint: ConnectedPoint) { - NetworkBehaviour::inject_connected(&mut self.discovery, peer_id, endpoint) + fn inject_connected(&mut self, peer_id: PeerId, _endpoint: ConnectedPoint) { + self.connected_peers.insert(peer_id); } - fn inject_disconnected(&mut self, peer_id: &PeerId, endpoint: ConnectedPoint) { - NetworkBehaviour::inject_disconnected(&mut self.discovery, peer_id, endpoint) + fn inject_disconnected(&mut self, peer_id: &PeerId, _endpoint: ConnectedPoint) { + self.connected_peers.remove(peer_id); } - fn inject_replaced(&mut self, peer_id: PeerId, closed: ConnectedPoint, opened: ConnectedPoint) { - NetworkBehaviour::inject_replaced(&mut self.discovery, peer_id, closed, opened) + fn inject_replaced( + &mut self, + _peer_id: PeerId, + _closed: ConnectedPoint, + _opened: ConnectedPoint, + ) { + // discv5 doesn't implement } fn inject_node_event( &mut self, - peer_id: PeerId, - event: ::OutEvent, + _peer_id: PeerId, + _event: ::OutEvent, ) { - // TODO: Upgrade to discv5 - NetworkBehaviour::inject_node_event(&mut self.discovery, peer_id, event) + // discv5 doesn't implement } fn poll( @@ -106,7 +199,7 @@ where Self::OutEvent, >, > { - // check to see if it's time to search for peers + // search of peers if it is time loop { match self.peer_discovery_delay.poll() { Ok(Async::Ready(_)) => { @@ -114,46 +207,49 @@ where } Ok(Async::NotReady) => break, Err(e) => { - warn!( - self.log, - "Error getting peers from discovery behaviour. Err: {:?}", e - ); + warn!(self.log, "Discovery peer search failed: {:?}", e); } } } - // Poll discovery - match self.discovery.poll(params) { - Async::Ready(action) => { - match &action { - NetworkBehaviourAction::GenerateEvent(disc_output) => match disc_output { - KademliaOut::Discovered { - peer_id, addresses, .. - } => { - debug!(self.log, "Kademlia peer discovered"; "Peer"=> format!("{:?}", peer_id), "Addresses" => format!("{:?}", addresses)); - } - KademliaOut::FindNodeResult { closer_peers, .. } => { - debug!( - self.log, - "Kademlia query found {} peers", - closer_peers.len() - ); - debug!(self.log, "Kademlia peers discovered"; "Peer"=> format!("{:?}", closer_peers)); - if closer_peers.is_empty() { - debug!(self.log, "Kademlia random query yielded empty results"); + // Poll discovery + loop { + match self.discovery.poll(params) { + Async::Ready(NetworkBehaviourAction::GenerateEvent(event)) => { + match event { + Discv5Event::Discovered(enr) => { + debug!(self.log, "Discv5: Peer discovered"; "Peer"=> format!("{:?}", enr.peer_id()), "Addresses" => format!("{:?}", enr.multiaddr())); + + let peer_id = enr.peer_id(); + // if we need more peers, attempt a connection + if self.connected_peers.len() < self.max_peers + && self.connected_peers.get(&peer_id).is_none() + { + return Async::Ready(NetworkBehaviourAction::DialPeer { peer_id }); + } + } + Discv5Event::SocketUpdated(socket) => { + info!(self.log, "Address updated"; "IP" => format!("{}",socket.ip())); + let mut address = Multiaddr::from(socket.ip()); + address.push(Protocol::Tcp(self.tcp_port)); + return Async::Ready(NetworkBehaviourAction::ReportObservedAddr { + address, + }); + } + Discv5Event::FindNodeResult { closer_peers, .. } => { + debug!(self.log, "Discv5 query found {} peers", closer_peers.len()); + if closer_peers.is_empty() { + debug!(self.log, "Discv5 random query yielded empty results"); } - return Async::Ready(action); } _ => {} - }, - _ => {} - }; - // propagate result upwards - return Async::Ready(action); + } + } + // discv5 does not output any other NetworkBehaviourAction + Async::Ready(_) => {} + Async::NotReady => break, } - Async::NotReady => (), } - Async::NotReady } } diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index 197c074df..7a3b2e632 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -10,7 +10,9 @@ pub mod rpc; mod service; pub use behaviour::PubsubMessage; -pub use config::{ChainType, Config as NetworkConfig, BEACON_PUBSUB_TOPIC, SHARD_TOPIC_PREFIX}; +pub use config::{ + Config as NetworkConfig, BEACON_ATTESTATION_TOPIC, BEACON_PUBSUB_TOPIC, SHARD_TOPIC_PREFIX, +}; pub use libp2p::floodsub::{Topic, TopicBuilder, TopicHash}; pub use libp2p::multiaddr; pub use libp2p::Multiaddr; diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 68ca72620..780b1453f 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -4,7 +4,7 @@ use crate::multiaddr::Protocol; use crate::rpc::RPCEvent; use crate::NetworkConfig; use crate::{TopicBuilder, TopicHash}; -use crate::{BEACON_PUBSUB_TOPIC, SHARD_TOPIC_PREFIX}; +use crate::{BEACON_ATTESTATION_TOPIC, BEACON_PUBSUB_TOPIC}; use futures::prelude::*; use futures::Stream; use libp2p::core::{ @@ -36,32 +36,24 @@ pub struct Service { impl Service { pub fn new(config: NetworkConfig, log: slog::Logger) -> error::Result { - debug!(log, "Libp2p Service starting"); + debug!(log, "Network-libp2p Service starting"); - // TODO: Currently using secp256k1 key pairs. Wire protocol specifies RSA. Waiting for this - // PR to be merged to generate RSA keys: https://github.com/briansmith/ring/pull/733 // TODO: Save and recover node key from disk + // TODO: Currently using secp256k1 keypairs - currently required for discv5 let local_private_key = identity::Keypair::generate_secp256k1(); - - let local_public_key = local_private_key.public(); let local_peer_id = PeerId::from(local_private_key.public()); info!(log, "Local peer id: {:?}", local_peer_id); let mut swarm = { - // Set up the transport - let transport = build_transport(local_private_key); - // Set up gossipsub routing - let behaviour = Behaviour::new(local_public_key.clone(), &config, &log); - // Set up Topology - let topology = local_peer_id.clone(); - Swarm::new(transport, behaviour, topology) + // Set up the transport - tcp/ws with secio and mplex/yamux + let transport = build_transport(local_private_key.clone()); + // Lighthouse network behaviour + let behaviour = Behaviour::new(&local_private_key, &config, &log)?; + Swarm::new(transport, behaviour, local_peer_id.clone()) }; // listen on all addresses - for address in config - .listen_addresses() - .map_err(|e| format!("Invalid listen multiaddr: {}", e))? - { + for address in config.listen_addresses { match Swarm::listen_on(&mut swarm, address.clone()) { Ok(_) => { let mut log_address = address.clone(); @@ -71,28 +63,13 @@ impl Service { Err(err) => warn!(log, "Cannot listen on: {} because: {:?}", address, err), }; } - // connect to boot nodes - these are currently stored as multiaddrs - // Once we have discovery, can set to peerId - for bootnode in config - .boot_nodes() - .map_err(|e| format!("Invalid boot node multiaddr: {:?}", e))? - { - match Swarm::dial_addr(&mut swarm, bootnode.clone()) { - Ok(()) => debug!(log, "Dialing bootnode: {}", bootnode), - Err(err) => debug!( - log, - "Could not connect to bootnode: {} error: {:?}", bootnode, err - ), - }; - } // subscribe to default gossipsub topics let mut topics = vec![]; //TODO: Handle multiple shard attestations. For now we simply use a separate topic for //attestations - topics.push(SHARD_TOPIC_PREFIX.to_string()); + topics.push(BEACON_ATTESTATION_TOPIC.to_string()); topics.push(BEACON_PUBSUB_TOPIC.to_string()); - topics.append(&mut config.topics.clone()); let mut subscribed_topics = vec![]; @@ -145,9 +122,6 @@ impl Stream for Service { BehaviourEvent::PeerDialed(peer_id) => { return Ok(Async::Ready(Some(Libp2pEvent::PeerDialed(peer_id)))); } - BehaviourEvent::Identified(peer_id, info) => { - return Ok(Async::Ready(Some(Libp2pEvent::Identified(peer_id, info)))); - } }, Ok(Async::Ready(None)) => unreachable!("Swarm stream shouldn't end"), Ok(Async::NotReady) => break, diff --git a/beacon_node/network/src/lib.rs b/beacon_node/network/src/lib.rs index d00c16292..b805c1d75 100644 --- a/beacon_node/network/src/lib.rs +++ b/beacon_node/network/src/lib.rs @@ -4,6 +4,6 @@ pub mod message_handler; pub mod service; pub mod sync; -pub use eth2_libp2p::{ChainType, NetworkConfig}; +pub use eth2_libp2p::NetworkConfig; pub use service::NetworkMessage; pub use service::Service; diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 07215119f..51d3a58f9 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -37,11 +37,33 @@ fn main() { .help("One or more comma-delimited multi-addresses to listen for p2p connections.") .takes_value(true), ) + .arg( + Arg::with_name("maxpeers") + .long("maxpeers") + .value_name("Max Peers") + .help("The maximum number of peers (default 10)") + .takes_value(true), + ) .arg( Arg::with_name("boot-nodes") .long("boot-nodes") + .allow_hyphen_values(true) .value_name("BOOTNODES") - .help("One or more comma-delimited multi-addresses to bootstrap the p2p network.") + .help("One or more comma-delimited base64-encoded ENR's to bootstrap the p2p network.") + .takes_value(true), + ) + .arg( + Arg::with_name("disc-listen-address") + .long("disc-listen_address") + .value_name("DISCPORT") + .help("The IP address that the discovery protocol will listen on. Defaults to 0.0.0.0") + .takes_value(true), + ) + .arg( + Arg::with_name("discovery-port") + .long("disc-port") + .value_name("DISCPORT") + .help("Listen UDP port for the discovery process") .takes_value(true), ) // rpc related arguments diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index 834f9a428..15883d974 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -84,7 +84,7 @@ pub fn run_beacon_node( info!( log, "Started beacon node"; - "p2p_listen_addresses" => format!("{:?}", &other_client_config.network.listen_addresses()), + "p2p_listen_addresses" => format!("{:?}", &other_client_config.network.listen_addresses), "data_dir" => format!("{:?}", other_client_config.data_dir()), "spec_constants" => &spec_constants, "db_type" => &other_client_config.db_type, From a64a6c7d3a6f06d0a2f030b1e40c9a0de35844b7 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 25 Jun 2019 18:02:11 +1000 Subject: [PATCH 18/39] Initial integration of discovery v5 --- beacon_node/client/src/config.rs | 67 +++++++++++++++++++++ beacon_node/eth2-libp2p/Cargo.toml | 4 +- beacon_node/eth2-libp2p/src/config.rs | 45 ++++++++------ beacon_node/eth2-libp2p/src/discovery.rs | 77 ++++++++++-------------- beacon_node/eth2-libp2p/src/service.rs | 30 +++++---- beacon_node/src/main.rs | 33 +++++----- beacon_node/src/run.rs | 20 +++--- 7 files changed, 172 insertions(+), 104 deletions(-) create mode 100644 beacon_node/client/src/config.rs diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs new file mode 100644 index 000000000..415ef0ec9 --- /dev/null +++ b/beacon_node/client/src/config.rs @@ -0,0 +1,67 @@ +use clap::ArgMatches; +use http_server::HttpServerConfig; +use network::NetworkConfig; +use serde_derive::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; + +/// The core configuration of a Lighthouse beacon node. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub data_dir: PathBuf, + pub db_type: String, + db_name: String, + pub network: network::NetworkConfig, + pub rpc: rpc::RPCConfig, + pub http: HttpServerConfig, +} + +impl Default for Config { + fn default() -> Self { + Self { + data_dir: PathBuf::from(".lighthouse"), + db_type: "disk".to_string(), + db_name: "chain_db".to_string(), + // Note: there are no default bootnodes specified. + // Once bootnodes are established, add them here. + network: NetworkConfig::new(), + rpc: rpc::RPCConfig::default(), + http: HttpServerConfig::default(), + } + } +} + +impl Config { + /// Returns the path to which the client may initialize an on-disk database. + pub fn db_path(&self) -> Option { + self.data_dir() + .and_then(|path| Some(path.join(&self.db_name))) + } + + /// Returns the core path for the client. + pub fn data_dir(&self) -> Option { + let path = dirs::home_dir()?.join(&self.data_dir); + fs::create_dir_all(&path).ok()?; + Some(path) + } + + /// Apply the following arguments to `self`, replacing values if they are specified in `args`. + /// + /// Returns an error if arguments are obviously invalid. May succeed even if some values are + /// invalid. + pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { + if let Some(dir) = args.value_of("datadir") { + self.data_dir = PathBuf::from(dir); + }; + + if let Some(dir) = args.value_of("db") { + self.db_type = dir.to_string(); + } + + self.network.apply_cli_args(args)?; + self.rpc.apply_cli_args(args)?; + self.http.apply_cli_args(args)?; + + Ok(()) + } +} diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index c47a4389f..36b0a3d29 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" # SigP repository -libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "f018f5c443ed5a93de890048dbc6755393373e72" } -enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "f018f5c443ed5a93de890048dbc6755393373e72", features = ["serde"] } + libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "1c2aa97a338fc9dfd8e804cc47497fb9b8e7ad04" } +enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "1c2aa97a338fc9dfd8e804cc47497fb9b8e7ad04", features = ["serde"] } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index 00a8ed51e..cf9422520 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -1,9 +1,6 @@ use clap::ArgMatches; use enr::Enr; -use libp2p::{ - gossipsub::{GossipsubConfig, GossipsubConfigBuilder}, - multiaddr::Multiaddr, -}; +use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; use serde_derive::{Deserialize, Serialize}; use std::time::Duration; @@ -18,9 +15,12 @@ pub const SHARD_TOPIC_PREFIX: &str = "shard"; /// Network configuration for lighthouse. pub struct Config { /// IP address to listen on. - pub listen_addresses: Vec, + pub listen_address: std::net::IpAddr, - /// Specifies the IP address that the discovery protocol will listen on. + /// The TCP port that libp2p listens on. + pub libp2p_port: u16, + + /// The address to broadcast to peers about which address we are listening on. pub discovery_address: std::net::IpAddr, /// UDP port that discovery listens on. @@ -47,8 +47,9 @@ impl Default for Config { /// Generate a default network configuration. fn default() -> Self { Config { - listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000".parse().expect("vaild multiaddr")], - discovery_address: "0.0.0.0".parse().expect("valid ip address"), + listen_address: "127.0.0.1".parse().expect("vaild ip address"), + libp2p_port: 9000, + discovery_address: "127.0.0.1".parse().expect("valid ip address"), discovery_port: 9000, max_peers: 10, //TODO: Set realistic values for production @@ -72,13 +73,11 @@ impl Config { pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { if let Some(listen_address_str) = args.value_of("listen-address") { - self.listen_addresses = listen_address_str - .split(',') - .map(|a| { - a.parse::() - .map_err(|_| format!("Invalid Listen address: {:?}", a)) - }) - .collect::, _>>()?; + let listen_address = listen_address_str + .parse() + .map_err(|_| format!("Invalid listen address: {:?}", listen_address_str))?; + self.listen_address = listen_address; + self.discovery_address = listen_address; } if let Some(max_peers_str) = args.value_of("maxpeers") { @@ -87,10 +86,12 @@ impl Config { .map_err(|_| format!("Invalid number of max peers: {}", max_peers_str))?; } - if let Some(discovery_address_str) = args.value_of("disc-listen-address") { - self.discovery_address = discovery_address_str - .parse::() - .map_err(|_| format!("Invalid discovery address: {:?}", discovery_address_str))?; + if let Some(port_str) = args.value_of("port") { + let port = port_str + .parse::() + .map_err(|_| format!("Invalid port: {}", port_str))?; + self.libp2p_port = port; + self.discovery_port = port; } if let Some(boot_enr_str) = args.value_of("boot-nodes") { @@ -100,6 +101,12 @@ impl Config { .collect::, _>>()?; } + if let Some(discovery_address_str) = args.value_of("discovery-address") { + self.discovery_address = discovery_address_str + .parse() + .map_err(|_| format!("Invalid discovery address: {:?}", discovery_address_str))? + } + if let Some(disc_port_str) = args.value_of("disc-port") { self.discovery_port = disc_port_str .parse::() diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 9a1e75691..1d4563552 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -11,7 +11,7 @@ use libp2p::core::{identity::Keypair, Multiaddr, PeerId, ProtocolsHandler}; use libp2p::discv5::{Discv5, Discv5Event}; use libp2p::enr::{Enr, EnrBuilder, NodeId}; use libp2p::multiaddr::Protocol; -use slog::{debug, error, info, o, warn}; +use slog::{debug, info, o, warn}; use std::collections::HashSet; use std::time::{Duration, Instant}; use tokio::io::{AsyncRead, AsyncWrite}; @@ -19,6 +19,8 @@ use tokio_timer::Delay; /// Maximum seconds before searching for extra peers. const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 60; +/// Initial delay between peer searches. +const INITIAL_SEARCH_DELAY: u64 = 5; /// Lighthouse discovery behaviour. This provides peer management and discovery using the Discv5 /// libp2p protocol. @@ -57,50 +59,27 @@ impl Discovery { let log = log.new(o!("Service" => "Libp2p-Discovery")); // Build the local ENR. - // The first TCP listening address is used for the ENR record. This will inform our peers to - // connect to this TCP port and establish libp2p streams. // Note: Discovery should update the ENR record's IP to the external IP as seen by the // majority of our peers. - let tcp_multiaddr = net_conf - .listen_addresses - .iter() - .filter(|a| { - if let Some(Protocol::Tcp(_)) = a.iter().last() { - true - } else { - false - } - }) - .next() - .ok_or_else(|| "No valid TCP addresses")?; - - let ip: std::net::IpAddr = match tcp_multiaddr.iter().next() { - Some(Protocol::Ip4(ip)) => ip.into(), - Some(Protocol::Ip6(ip)) => ip.into(), - _ => { - error!(log, "Multiaddr has an invalid IP address"); - return Err(format!("Invalid IP Address: {}", tcp_multiaddr).into()); - } - }; - - let tcp_port = match tcp_multiaddr.iter().last() { - Some(Protocol::Tcp(tcp)) => tcp, - _ => unreachable!(), - }; let local_enr = EnrBuilder::new() - .ip(ip.into()) - .tcp(tcp_port) + .ip(net_conf.discovery_address.into()) + .tcp(net_conf.libp2p_port) .udp(net_conf.discovery_port) .build(&local_key) .map_err(|e| format!("Could not build Local ENR: {:?}", e))?; info!(log, "Local ENR: {}", local_enr.to_base64()); - let mut discovery = Discv5::new(local_enr, local_key.clone(), net_conf.discovery_address) + let mut discovery = Discv5::new(local_enr, local_key.clone(), net_conf.listen_address) .map_err(|e| format!("Discv5 service failed: {:?}", e))?; // Add bootnodes to routing table for bootnode_enr in net_conf.boot_nodes.clone() { + debug!( + log, + "Adding node to routing table: {}", + bootnode_enr.node_id() + ); discovery.add_enr(bootnode_enr); } @@ -108,8 +87,8 @@ impl Discovery { connected_peers: HashSet::new(), max_peers: net_conf.max_peers, peer_discovery_delay: Delay::new(Instant::now()), - past_discovery_delay: 1, - tcp_port, + past_discovery_delay: INITIAL_SEARCH_DELAY, + tcp_port: net_conf.libp2p_port, discovery, log, }) @@ -118,7 +97,7 @@ impl Discovery { /// Manually search for peers. This restarts the discovery round, sparking multiple rapid /// queries. pub fn discover_peers(&mut self) { - self.past_discovery_delay = 1; + self.past_discovery_delay = INITIAL_SEARCH_DELAY; self.find_peers(); } @@ -203,7 +182,9 @@ where loop { match self.peer_discovery_delay.poll() { Ok(Async::Ready(_)) => { - self.find_peers(); + if self.connected_peers.len() < self.max_peers { + self.find_peers(); + } } Ok(Async::NotReady) => break, Err(e) => { @@ -217,16 +198,9 @@ where match self.discovery.poll(params) { Async::Ready(NetworkBehaviourAction::GenerateEvent(event)) => { match event { - Discv5Event::Discovered(enr) => { - debug!(self.log, "Discv5: Peer discovered"; "Peer"=> format!("{:?}", enr.peer_id()), "Addresses" => format!("{:?}", enr.multiaddr())); - - let peer_id = enr.peer_id(); - // if we need more peers, attempt a connection - if self.connected_peers.len() < self.max_peers - && self.connected_peers.get(&peer_id).is_none() - { - return Async::Ready(NetworkBehaviourAction::DialPeer { peer_id }); - } + Discv5Event::Discovered(_enr) => { + // not concerned about FINDNODE results, rather the result of an entire + // query. } Discv5Event::SocketUpdated(socket) => { info!(self.log, "Address updated"; "IP" => format!("{}",socket.ip())); @@ -241,6 +215,17 @@ where if closer_peers.is_empty() { debug!(self.log, "Discv5 random query yielded empty results"); } + for peer_id in closer_peers { + // if we need more peers, attempt a connection + if self.connected_peers.len() < self.max_peers + && self.connected_peers.get(&peer_id).is_none() + { + debug!(self.log, "Discv5: Peer discovered"; "Peer"=> format!("{:?}", peer_id)); + return Async::Ready(NetworkBehaviourAction::DialPeer { + peer_id, + }); + } + } } _ => {} } diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 780b1453f..1db855cd4 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -9,6 +9,7 @@ use futures::prelude::*; use futures::Stream; use libp2p::core::{ identity, + multiaddr::Multiaddr, muxing::StreamMuxerBox, nodes::Substream, transport::boxed::Boxed, @@ -52,17 +53,24 @@ impl Service { Swarm::new(transport, behaviour, local_peer_id.clone()) }; - // listen on all addresses - for address in config.listen_addresses { - match Swarm::listen_on(&mut swarm, address.clone()) { - Ok(_) => { - let mut log_address = address.clone(); - log_address.push(Protocol::P2p(local_peer_id.clone().into())); - info!(log, "Listening on: {}", log_address); - } - Err(err) => warn!(log, "Cannot listen on: {} because: {:?}", address, err), - }; - } + // listen on the specified address + let listen_multiaddr = { + let mut m = Multiaddr::from(config.listen_address); + m.push(Protocol::Tcp(config.libp2p_port)); + m + }; + + match Swarm::listen_on(&mut swarm, listen_multiaddr.clone()) { + Ok(_) => { + let mut log_address = listen_multiaddr; + log_address.push(Protocol::P2p(local_peer_id.clone().into())); + info!(log, "Listening on: {}", log_address); + } + Err(err) => warn!( + log, + "Cannot listen on: {} because: {:?}", listen_multiaddr, err + ), + }; // subscribe to default gossipsub topics let mut topics = vec![]; diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 51d3a58f9..f7b92275a 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -33,15 +33,14 @@ fn main() { .arg( Arg::with_name("listen-address") .long("listen-address") - .value_name("Listen Address") - .help("One or more comma-delimited multi-addresses to listen for p2p connections.") + .value_name("Address") + .help("The address lighthouse will listen for UDP and TCP connections. (default 127.0.0.1).") .takes_value(true), ) .arg( Arg::with_name("maxpeers") .long("maxpeers") - .value_name("Max Peers") - .help("The maximum number of peers (default 10)") + .help("The maximum number of peers (default 10).") .takes_value(true), ) .arg( @@ -53,17 +52,24 @@ fn main() { .takes_value(true), ) .arg( - Arg::with_name("disc-listen-address") - .long("disc-listen_address") - .value_name("DISCPORT") - .help("The IP address that the discovery protocol will listen on. Defaults to 0.0.0.0") + Arg::with_name("port") + .long("port") + .value_name("Lighthouse Port") + .help("The TCP/UDP port to listen on. The UDP port can be modified by the --discovery-port flag.") .takes_value(true), ) .arg( Arg::with_name("discovery-port") .long("disc-port") - .value_name("DISCPORT") - .help("Listen UDP port for the discovery process") + .value_name("DiscoveryPort") + .help("The discovery UDP port.") + .takes_value(true), + ) + .arg( + Arg::with_name("discovery-address") + .long("discovery-address") + .value_name("Address") + .help("The address to broadcast to other peers on how to reach this node.") .takes_value(true), ) // rpc related arguments @@ -77,14 +83,13 @@ fn main() { .arg( Arg::with_name("rpc-address") .long("rpc-address") - .value_name("RPCADDRESS") + .value_name("Address") .help("Listen address for RPC endpoint.") .takes_value(true), ) .arg( Arg::with_name("rpc-port") .long("rpc-port") - .value_name("RPCPORT") .help("Listen port for RPC endpoint.") .takes_value(true), ) @@ -92,21 +97,19 @@ fn main() { .arg( Arg::with_name("http") .long("http") - .value_name("HTTP") .help("Enable the HTTP server.") .takes_value(false), ) .arg( Arg::with_name("http-address") .long("http-address") - .value_name("HTTPADDRESS") + .value_name("Address") .help("Listen address for the HTTP server.") .takes_value(true), ) .arg( Arg::with_name("http-port") .long("http-port") - .value_name("HTTPPORT") .help("Listen port for the HTTP server.") .takes_value(true), ) diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index 15883d974..51fa16154 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -41,6 +41,15 @@ pub fn run_beacon_node( "This software is EXPERIMENTAL and provides no guarantees or warranties." ); + info!( + log, + "Starting beacon node"; + "p2p_listen_address" => format!("{:?}", &other_client_config.network.listen_address), + "data_dir" => format!("{:?}", other_client_config.data_dir()), + "spec_constants" => &spec_constants, + "db_type" => &other_client_config.db_type, + ); + let result = match (db_type.as_str(), spec_constants.as_str()) { ("disk", "minimal") => run::>( &db_path, @@ -80,17 +89,6 @@ pub fn run_beacon_node( } }; - if result.is_ok() { - info!( - log, - "Started beacon node"; - "p2p_listen_addresses" => format!("{:?}", &other_client_config.network.listen_addresses), - "data_dir" => format!("{:?}", other_client_config.data_dir()), - "spec_constants" => &spec_constants, - "db_type" => &other_client_config.db_type, - ); - } - result } From 955574f46945c7b0f29f93763441533837bf2784 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 25 Jun 2019 18:57:11 +1000 Subject: [PATCH 19/39] Update to latest libp2p --- beacon_node/eth2-libp2p/Cargo.toml | 4 ++-- beacon_node/eth2-libp2p/src/discovery.rs | 2 +- beacon_node/eth2-libp2p/src/rpc/mod.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 36b0a3d29..6d6422c4d 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" # SigP repository - libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "1c2aa97a338fc9dfd8e804cc47497fb9b8e7ad04" } -enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "1c2aa97a338fc9dfd8e804cc47497fb9b8e7ad04", features = ["serde"] } + libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "8ff9f2001de0aea1175c1442f22bfbf382dc0e8b" } +enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "8ff9f2001de0aea1175c1442f22bfbf382dc0e8b", features = ["serde"] } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 1d4563552..b69f45be7 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -171,7 +171,7 @@ where fn poll( &mut self, - params: &mut PollParameters, + params: &mut impl PollParameters, ) -> Async< NetworkBehaviourAction< ::InEvent, diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index 57d7dadbe..2d303469c 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -94,7 +94,7 @@ where fn poll( &mut self, - _: &mut PollParameters<'_>, + _: &mut impl PollParameters, ) -> Async< NetworkBehaviourAction< ::InEvent, From 5521c53d360508fb474936fe5d734f6a165924c6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 1 Jul 2019 16:38:42 +1000 Subject: [PATCH 20/39] Add persistent network identification --- account_manager/Cargo.toml | 1 + account_manager/src/main.rs | 19 +++-- beacon_node/Cargo.toml | 1 + beacon_node/eth2-libp2p/Cargo.toml | 7 +- beacon_node/eth2-libp2p/src/config.rs | 14 +++ beacon_node/eth2-libp2p/src/discovery.rs | 103 +++++++++++++++++++---- beacon_node/eth2-libp2p/src/service.rs | 65 ++++++++++++-- beacon_node/network/src/service.rs | 6 -- beacon_node/src/main.rs | 41 +++++---- eth2/utils/eth2_config/src/lib.rs | 13 --- validator_client/Cargo.toml | 1 + validator_client/src/main.rs | 20 +++-- 12 files changed, 220 insertions(+), 71 deletions(-) diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index 48504d89a..b3c687eef 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -13,3 +13,4 @@ slog-async = "^2.3.0" validator_client = { path = "../validator_client" } types = { path = "../eth2/types" } eth2_config = { path = "../eth2/utils/eth2_config" } +dirs = "2.0.1" diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index 1c8cc8819..e242e8ae4 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -1,7 +1,7 @@ use bls::Keypair; use clap::{App, Arg, SubCommand}; -use eth2_config::get_data_dir; use slog::{crit, debug, info, o, Drain}; +use std::fs; use std::path::PathBuf; use types::test_utils::generate_deterministic_keypair; use validator_client::Config as ValidatorClientConfig; @@ -61,13 +61,22 @@ fn main() { ) .get_matches(); - let data_dir = match get_data_dir(&matches, PathBuf::from(DEFAULT_DATA_DIR)) { - Ok(dir) => dir, + let mut default_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + default_dir.push(DEFAULT_DATA_DIR); + + let data_dir = &matches + .value_of("datadir") + .and_then(|v| Some(PathBuf::from(v))) + .unwrap_or_else(|| PathBuf::from(default_dir)); + + // create the directory if needed + match fs::create_dir_all(&data_dir) { + Ok(_) => {} Err(e) => { - crit!(log, "Failed to initialize data dir"; "error" => format!("{:?}", e)); + crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e)); return; } - }; + } let mut client_config = ValidatorClientConfig::default(); diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 7e43a13df..9e96f8484 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -23,3 +23,4 @@ futures = "0.1.25" exit-future = "0.1.3" state_processing = { path = "../eth2/state_processing" } env_logger = "0.6.1" +dirs = "2.0.1" diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 6d6422c4d..82d218241 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -7,9 +7,9 @@ edition = "2018" [dependencies] beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" -# SigP repository - libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "8ff9f2001de0aea1175c1442f22bfbf382dc0e8b" } -enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "8ff9f2001de0aea1175c1442f22bfbf382dc0e8b", features = ["serde"] } +#SigP repository +libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "be5710bbde69d8c5be732c13ba64239e2f370a7b" } +enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "be5710bbde69d8c5be732c13ba64239e2f370a7b", features = ["serde"] } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" @@ -21,3 +21,4 @@ tokio = "0.1.16" futures = "0.1.25" error-chain = "0.12.0" tokio-timer = "0.2.10" +dirs = "2.0.1" diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index cf9422520..e881408ea 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -2,6 +2,7 @@ use clap::ArgMatches; use enr::Enr; use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; use serde_derive::{Deserialize, Serialize}; +use std::path::PathBuf; use std::time::Duration; /// The beacon node topic string to subscribe to. @@ -14,6 +15,9 @@ pub const SHARD_TOPIC_PREFIX: &str = "shard"; #[serde(default)] /// Network configuration for lighthouse. pub struct Config { + /// Data directory where node's keyfile is stored + pub network_dir: PathBuf, + /// IP address to listen on. pub listen_address: std::net::IpAddr, @@ -46,7 +50,11 @@ pub struct Config { impl Default for Config { /// Generate a default network configuration. fn default() -> Self { + let mut network_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + network_dir.push(".lighthouse"); + network_dir.push("network"); Config { + network_dir, listen_address: "127.0.0.1".parse().expect("vaild ip address"), libp2p_port: 9000, discovery_address: "127.0.0.1".parse().expect("valid ip address"), @@ -72,6 +80,12 @@ impl Config { } pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { + dbg!(self.network_dir.clone()); + if let Some(dir) = args.value_of("datadir") { + self.network_dir = PathBuf::from(dir).join("network"); + }; + dbg!(self.network_dir.clone()); + if let Some(listen_address_str) = args.value_of("listen-address") { let listen_address = listen_address_str .parse() diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index b69f45be7..104cd0285 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -1,7 +1,7 @@ use crate::{error, NetworkConfig}; /// This manages the discovery and management of peers. /// -/// Currently using Kademlia for peer discovery. +/// Currently using discv5 for peer discovery. /// use futures::prelude::*; use libp2p::core::swarm::{ @@ -13,6 +13,9 @@ use libp2p::enr::{Enr, EnrBuilder, NodeId}; use libp2p::multiaddr::Protocol; use slog::{debug, info, o, warn}; use std::collections::HashSet; +use std::fs::File; +use std::io::prelude::*; +use std::str::FromStr; use std::time::{Duration, Instant}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -21,6 +24,8 @@ use tokio_timer::Delay; const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 60; /// Initial delay between peer searches. const INITIAL_SEARCH_DELAY: u64 = 5; +/// Local ENR storage filename. +const ENR_FILENAME: &str = "enr.dat"; /// Lighthouse discovery behaviour. This provides peer management and discovery using the Discv5 /// libp2p protocol. @@ -53,28 +58,22 @@ pub struct Discovery { impl Discovery { pub fn new( local_key: &Keypair, - net_conf: &NetworkConfig, + config: &NetworkConfig, log: &slog::Logger, ) -> error::Result { let log = log.new(o!("Service" => "Libp2p-Discovery")); - // Build the local ENR. - // Note: Discovery should update the ENR record's IP to the external IP as seen by the - // majority of our peers. + // checks if current ENR matches that found on disk + let local_enr = load_enr(local_key, config, &log)?; - let local_enr = EnrBuilder::new() - .ip(net_conf.discovery_address.into()) - .tcp(net_conf.libp2p_port) - .udp(net_conf.discovery_port) - .build(&local_key) - .map_err(|e| format!("Could not build Local ENR: {:?}", e))?; info!(log, "Local ENR: {}", local_enr.to_base64()); + debug!(log, "Local Node Id: {}", local_enr.node_id()); - let mut discovery = Discv5::new(local_enr, local_key.clone(), net_conf.listen_address) + let mut discovery = Discv5::new(local_enr, local_key.clone(), config.listen_address) .map_err(|e| format!("Discv5 service failed: {:?}", e))?; // Add bootnodes to routing table - for bootnode_enr in net_conf.boot_nodes.clone() { + for bootnode_enr in config.boot_nodes.clone() { debug!( log, "Adding node to routing table: {}", @@ -85,10 +84,10 @@ impl Discovery { Ok(Self { connected_peers: HashSet::new(), - max_peers: net_conf.max_peers, + max_peers: config.max_peers, peer_discovery_delay: Delay::new(Instant::now()), past_discovery_delay: INITIAL_SEARCH_DELAY, - tcp_port: net_conf.libp2p_port, + tcp_port: config.libp2p_port, discovery, log, }) @@ -238,3 +237,77 @@ where Async::NotReady } } + +/// Loads an ENR from file if it exists and matches the current NodeId and sequence number. If none +/// exists, generates a new one. +/// +/// If an ENR exists, with the same NodeId and IP addresses, we use the disk-generated one as it's +/// ENR sequence will be equal or higher than a newly generated one. +fn load_enr( + local_key: &Keypair, + config: &NetworkConfig, + log: &slog::Logger, +) -> Result { + // Build the local ENR. + // Note: Discovery should update the ENR record's IP to the external IP as seen by the + // majority of our peers. + let mut local_enr = EnrBuilder::new() + .ip(config.discovery_address.into()) + .tcp(config.libp2p_port) + .udp(config.discovery_port) + .build(&local_key) + .map_err(|e| format!("Could not build Local ENR: {:?}", e))?; + + let enr_f = config.network_dir.join(ENR_FILENAME); + if let Ok(mut enr_file) = File::open(enr_f.clone()) { + let mut enr_string = String::new(); + match enr_file.read_to_string(&mut enr_string) { + Err(_) => debug!(log, "Could not read ENR from file"), + Ok(_) => { + match Enr::from_str(&enr_string) { + Ok(enr) => { + debug!(log, "ENR found in file: {:?}", enr_f); + + if enr.node_id() == local_enr.node_id() { + if enr.ip() == config.discovery_address.into() + && enr.tcp() == Some(config.libp2p_port) + && enr.udp() == Some(config.discovery_port) + { + debug!(log, "ENR loaded from file"); + // the stored ENR has the same configuration, use it + return Ok(enr); + } + + // same node id, different configuration - update the sequence number + let new_seq_no = enr.seq().checked_add(1).ok_or_else(|| "ENR sequence number on file is too large. Remove it to generate a new NodeId")?; + local_enr.set_seq(new_seq_no, local_key).map_err(|e| { + format!("Could not update ENR sequence number: {:?}", e) + })?; + debug!(log, "ENR sequence number increased to: {}", new_seq_no); + } + } + Err(e) => { + warn!(log, "ENR from file could not be decoded: {:?}", e); + } + } + } + } + } + + // write ENR to disk + let _ = std::fs::create_dir_all(&config.network_dir); + match File::create(enr_f.clone()) + .and_then(|mut f| f.write_all(&local_enr.to_base64().as_bytes())) + { + Ok(_) => { + debug!(log, "ENR written to disk"); + } + Err(e) => { + warn!( + log, + "Could not write ENR to file: {:?}. Error: {}", enr_f, e + ); + } + } + Ok(local_enr) +} diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 1db855cd4..69f8a1ca5 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -8,22 +8,25 @@ use crate::{BEACON_ATTESTATION_TOPIC, BEACON_PUBSUB_TOPIC}; use futures::prelude::*; use futures::Stream; use libp2p::core::{ - identity, + identity::Keypair, multiaddr::Multiaddr, muxing::StreamMuxerBox, nodes::Substream, transport::boxed::Boxed, upgrade::{InboundUpgradeExt, OutboundUpgradeExt}, }; -use libp2p::identify::protocol::IdentifyInfo; use libp2p::{core, secio, PeerId, Swarm, Transport}; use slog::{debug, info, trace, warn}; +use std::fs::File; +use std::io::prelude::*; use std::io::{Error, ErrorKind}; use std::time::Duration; type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>; type Libp2pBehaviour = Behaviour>; +const NETWORK_KEY_FILENAME: &str = "key"; + /// The configuration and state of the libp2p components for the beacon node. pub struct Service { /// The libp2p Swarm handler. @@ -39,9 +42,9 @@ impl Service { pub fn new(config: NetworkConfig, log: slog::Logger) -> error::Result { debug!(log, "Network-libp2p Service starting"); - // TODO: Save and recover node key from disk - // TODO: Currently using secp256k1 keypairs - currently required for discv5 - let local_private_key = identity::Keypair::generate_secp256k1(); + // load the private key from CLI flag, disk or generate a new one + let local_private_key = load_private_key(&config, &log); + let local_peer_id = PeerId::from(local_private_key.public()); info!(log, "Local peer id: {:?}", local_peer_id); @@ -142,7 +145,7 @@ impl Stream for Service { /// The implementation supports TCP/IP, WebSockets over TCP/IP, secio as the encryption layer, and /// mplex or yamux as the multiplexing layer. -fn build_transport(local_private_key: identity::Keypair) -> Boxed<(PeerId, StreamMuxerBox), Error> { +fn build_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox), Error> { // TODO: The Wire protocol currently doesn't specify encryption and this will need to be customised // in the future. let transport = libp2p::tcp::TcpConfig::new(); @@ -179,8 +182,6 @@ pub enum Libp2pEvent { RPC(PeerId, RPCEvent), /// Initiated the connection to a new peer. PeerDialed(PeerId), - /// Received information about a peer on the network. - Identified(PeerId, Box), /// Received pubsub message. PubsubMessage { source: PeerId, @@ -188,3 +189,51 @@ pub enum Libp2pEvent { message: Box, }, } + +/// Loads a private key from disk. If this fails, a new key is +/// generated and is then saved to disk. +/// +/// Currently only secp256k1 keys are allowed, as these are the only keys supported by discv5. +fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair { + // TODO: Currently using secp256k1 keypairs - currently required for discv5 + // check for key from disk + let network_key_f = config.network_dir.join(NETWORK_KEY_FILENAME); + if let Ok(mut network_key_file) = File::open(network_key_f.clone()) { + let mut key_bytes: Vec = Vec::with_capacity(36); + match network_key_file.read_to_end(&mut key_bytes) { + Err(_) => debug!(log, "Could not read network key file"), + Ok(_) => { + // only accept secp256k1 keys for now + if let Ok(secret_key) = + libp2p::core::identity::secp256k1::SecretKey::from_bytes(&mut key_bytes) + { + let kp: libp2p::core::identity::secp256k1::Keypair = secret_key.into(); + debug!(log, "Loaded network key from disk."); + return Keypair::Secp256k1(kp); + } else { + debug!(log, "Network key file is not a valid secp256k1 key"); + } + } + } + } + + // if a key could not be loaded from disk, generate a new one and save it + let local_private_key = Keypair::generate_secp256k1(); + if let Keypair::Secp256k1(key) = local_private_key.clone() { + let _ = std::fs::create_dir_all(&config.network_dir); + match File::create(network_key_f.clone()) + .and_then(|mut f| f.write_all(&key.secret().to_bytes())) + { + Ok(_) => { + debug!(log, "New network key generated and written to disk"); + } + Err(e) => { + warn!( + log, + "Could not write node key to file: {:?}. Error: {}", network_key_f, e + ); + } + } + } + local_private_key +} diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index c19aef004..b2ecc1a0b 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -126,12 +126,6 @@ fn network_service( .send(HandlerMessage::PeerDialed(peer_id)) .map_err(|_| "failed to send rpc to handler")?; } - Libp2pEvent::Identified(peer_id, info) => { - debug!( - log, - "We have identified peer: {:?} with {:?}", peer_id, info - ); - } Libp2pEvent::PubsubMessage { source, message, .. } => { diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index f7b92275a..651ad8e0c 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -3,8 +3,9 @@ mod run; use clap::{App, Arg}; use client::{ClientConfig, Eth2Config}; use env_logger::{Builder, Env}; -use eth2_config::{get_data_dir, read_from_file, write_to_file}; +use eth2_config::{read_from_file, write_to_file}; use slog::{crit, o, Drain, Level}; +use std::fs; use std::path::PathBuf; pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; @@ -27,7 +28,6 @@ fn main() { .value_name("DIR") .help("Data directory for keys and databases.") .takes_value(true) - .default_value(DEFAULT_DATA_DIR), ) // network related arguments .arg( @@ -69,7 +69,7 @@ fn main() { Arg::with_name("discovery-address") .long("discovery-address") .value_name("Address") - .help("The address to broadcast to other peers on how to reach this node.") + .help("The IP address to broadcast to other peers on how to reach this node.") .takes_value(true), ) // rpc related arguments @@ -159,15 +159,24 @@ fn main() { _ => drain.filter_level(Level::Info), }; - let logger = slog::Logger::root(drain.fuse(), o!()); + let log = slog::Logger::root(drain.fuse(), o!()); - let data_dir = match get_data_dir(&matches, PathBuf::from(DEFAULT_DATA_DIR)) { - Ok(dir) => dir, + let mut default_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + default_dir.push(DEFAULT_DATA_DIR); + + let data_dir = &matches + .value_of("datadir") + .and_then(|v| Some(PathBuf::from(v))) + .unwrap_or_else(|| PathBuf::from(default_dir)); + + // create the directory if needed + match fs::create_dir_all(&data_dir) { + Ok(_) => {} Err(e) => { - crit!(logger, "Failed to initialize data dir"; "error" => format!("{:?}", e)); + crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e)); return; } - }; + } let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME); @@ -179,13 +188,13 @@ fn main() { Ok(None) => { let default = ClientConfig::default(); if let Err(e) = write_to_file(client_config_path, &default) { - crit!(logger, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e)); + crit!(log, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e)); return; } default } Err(e) => { - crit!(logger, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e)); + crit!(log, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e)); return; } }; @@ -197,7 +206,7 @@ fn main() { match client_config.apply_cli_args(&matches) { Ok(()) => (), Err(s) => { - crit!(logger, "Failed to parse ClientConfig CLI arguments"; "error" => s); + crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s); return; } }; @@ -216,13 +225,13 @@ fn main() { _ => unreachable!(), // Guarded by slog. }; if let Err(e) = write_to_file(eth2_config_path, &default) { - crit!(logger, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e)); + crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e)); return; } default } Err(e) => { - crit!(logger, "Failed to load/generate an Eth2Config"; "error" => format!("{:?}", e)); + crit!(log, "Failed to load/generate an Eth2Config"; "error" => format!("{:?}", e)); return; } }; @@ -231,13 +240,13 @@ fn main() { match eth2_config.apply_cli_args(&matches) { Ok(()) => (), Err(s) => { - crit!(logger, "Failed to parse Eth2Config CLI arguments"; "error" => s); + crit!(log, "Failed to parse Eth2Config CLI arguments"; "error" => s); return; } }; - match run::run_beacon_node(client_config, eth2_config, &logger) { + match run::run_beacon_node(client_config, eth2_config, &log) { Ok(_) => {} - Err(e) => crit!(logger, "Beacon node failed to start"; "reason" => format!("{:}", e)), + Err(e) => crit!(log, "Beacon node failed to start"; "reason" => format!("{:}", e)), } } diff --git a/eth2/utils/eth2_config/src/lib.rs b/eth2/utils/eth2_config/src/lib.rs index 9d50a95c1..f6ad54c21 100644 --- a/eth2/utils/eth2_config/src/lib.rs +++ b/eth2/utils/eth2_config/src/lib.rs @@ -1,6 +1,5 @@ use clap::ArgMatches; use serde_derive::{Deserialize, Serialize}; -use std::fs; use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; @@ -105,15 +104,3 @@ where Ok(None) } } - -pub fn get_data_dir(args: &ArgMatches, default_data_dir: PathBuf) -> Result { - if let Some(data_dir) = args.value_of("data_dir") { - Ok(PathBuf::from(data_dir)) - } else { - let path = dirs::home_dir() - .ok_or_else(|| "Unable to locate home directory")? - .join(&default_data_dir); - fs::create_dir_all(&path).map_err(|_| "Unable to create data_dir")?; - Ok(path) - } -} diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 1784bdcb1..5a3776968 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -34,3 +34,4 @@ toml = "^0.5" error-chain = "0.12.0" bincode = "^1.1.2" futures = "0.1.25" +dirs = "2.0.1" diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index f74915438..43ef2f994 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -9,9 +9,10 @@ mod signer; use crate::config::Config as ValidatorClientConfig; use crate::service::Service as ValidatorService; use clap::{App, Arg}; -use eth2_config::{get_data_dir, read_from_file, write_to_file, Eth2Config}; +use eth2_config::{read_from_file, write_to_file, Eth2Config}; use protos::services_grpc::ValidatorServiceClient; use slog::{crit, error, info, o, Drain}; +use std::fs; use std::path::PathBuf; use types::{Keypair, MainnetEthSpec, MinimalEthSpec}; @@ -66,13 +67,22 @@ fn main() { ) .get_matches(); - let data_dir = match get_data_dir(&matches, PathBuf::from(DEFAULT_DATA_DIR)) { - Ok(dir) => dir, + let mut default_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + default_dir.push(DEFAULT_DATA_DIR); + + let data_dir = &matches + .value_of("datadir") + .and_then(|v| Some(PathBuf::from(v))) + .unwrap_or_else(|| PathBuf::from(default_dir)); + + // create the directory if needed + match fs::create_dir_all(&data_dir) { + Ok(_) => {} Err(e) => { - crit!(log, "Failed to initialize data dir"; "error" => format!("{:?}", e)); + crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e)); return; } - }; + } let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME); From f8e20d802744ae2320ec66b71cc063b5362d554a Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 1 Jul 2019 16:40:35 +1000 Subject: [PATCH 21/39] Typo fixes --- beacon_node/eth2-libp2p/src/discovery.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 104cd0285..44b4e655b 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -177,7 +177,7 @@ where Self::OutEvent, >, > { - // search of peers if it is time + // search for peers if it is time loop { match self.peer_discovery_delay.poll() { Ok(Async::Ready(_)) => { @@ -241,7 +241,7 @@ where /// Loads an ENR from file if it exists and matches the current NodeId and sequence number. If none /// exists, generates a new one. /// -/// If an ENR exists, with the same NodeId and IP addresses, we use the disk-generated one as it's +/// If an ENR exists, with the same NodeId and IP address, we use the disk-generated one as its /// ENR sequence will be equal or higher than a newly generated one. fn load_enr( local_key: &Keypair, From 1f622212358be4012e5cf70b1811919e910e2749 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 1 Jul 2019 17:23:14 +1000 Subject: [PATCH 22/39] Removes left-over debugging statements --- beacon_node/eth2-libp2p/src/config.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index e881408ea..c5e02d5e3 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -80,11 +80,9 @@ impl Config { } pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { - dbg!(self.network_dir.clone()); if let Some(dir) = args.value_of("datadir") { self.network_dir = PathBuf::from(dir).join("network"); }; - dbg!(self.network_dir.clone()); if let Some(listen_address_str) = args.value_of("listen-address") { let listen_address = listen_address_str From 6c18b417c3f918910caa4727396961a6ba3f5b64 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 2 Jul 2019 11:15:35 +1000 Subject: [PATCH 23/39] Remove Phase 1 TODO --- beacon_node/eth2-libp2p/src/config.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index c5e02d5e3..ea87075b7 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -8,7 +8,6 @@ use std::time::Duration; /// The beacon node topic string to subscribe to. pub const BEACON_PUBSUB_TOPIC: &str = "beacon_block"; pub const BEACON_ATTESTATION_TOPIC: &str = "beacon_attestation"; -//TODO: Implement shard subnets pub const SHARD_TOPIC_PREFIX: &str = "shard"; #[derive(Clone, Debug, Serialize, Deserialize)] From 76371659e92dc87746e24dfe2d1ba056efae26a5 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 2 Jul 2019 17:32:14 +1000 Subject: [PATCH 24/39] Improve error handling of default directory --- account_manager/src/main.rs | 21 ++++++++++++++++----- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 1 - beacon_node/src/main.rs | 21 ++++++++++++++++----- validator_client/src/main.rs | 21 ++++++++++++++++----- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index e242e8ae4..ee0e86d60 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -61,13 +61,24 @@ fn main() { ) .get_matches(); - let mut default_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); - default_dir.push(DEFAULT_DATA_DIR); - - let data_dir = &matches + let data_dir = match matches .value_of("datadir") .and_then(|v| Some(PathBuf::from(v))) - .unwrap_or_else(|| PathBuf::from(default_dir)); + { + Some(v) => v, + None => { + // use the default + let mut default_dir = match dirs::home_dir() { + Some(v) => v, + None => { + crit!(log, "Failed to find a home directory"); + return; + } + }; + default_dir.push(DEFAULT_DATA_DIR); + PathBuf::from(default_dir) + } + }; // create the directory if needed match fs::create_dir_all(&data_dir) { diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index 2f461988a..7afded3ac 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -11,7 +11,6 @@ use tokio::io::{AsyncRead, AsyncWrite}; const MAX_READ_SIZE: usize = 4_194_304; // 4M /// Implementation of the `ConnectionUpgrade` for the rpc protocol. - #[derive(Debug, Clone)] pub struct RPCProtocol; diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 651ad8e0c..791feae54 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -161,13 +161,24 @@ fn main() { let log = slog::Logger::root(drain.fuse(), o!()); - let mut default_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); - default_dir.push(DEFAULT_DATA_DIR); - - let data_dir = &matches + let data_dir = match matches .value_of("datadir") .and_then(|v| Some(PathBuf::from(v))) - .unwrap_or_else(|| PathBuf::from(default_dir)); + { + Some(v) => v, + None => { + // use the default + let mut default_dir = match dirs::home_dir() { + Some(v) => v, + None => { + crit!(log, "Failed to find a home directory"); + return; + } + }; + default_dir.push(DEFAULT_DATA_DIR); + PathBuf::from(default_dir) + } + }; // create the directory if needed match fs::create_dir_all(&data_dir) { diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 43ef2f994..e37b6530e 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -67,13 +67,24 @@ fn main() { ) .get_matches(); - let mut default_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); - default_dir.push(DEFAULT_DATA_DIR); - - let data_dir = &matches + let data_dir = match matches .value_of("datadir") .and_then(|v| Some(PathBuf::from(v))) - .unwrap_or_else(|| PathBuf::from(default_dir)); + { + Some(v) => v, + None => { + // use the default + let mut default_dir = match dirs::home_dir() { + Some(v) => v, + None => { + crit!(log, "Failed to find a home directory"); + return; + } + }; + default_dir.push(DEFAULT_DATA_DIR); + PathBuf::from(default_dir) + } + }; // create the directory if needed match fs::create_dir_all(&data_dir) { From 746935fa41f5786fefe583c2541fe30314adc9e3 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Thu, 4 Jul 2019 14:05:01 +1000 Subject: [PATCH 25/39] Initial sub-protocol implementation --- beacon_node/eth2-libp2p/src/rpc/methods.rs | 57 +------ beacon_node/eth2-libp2p/src/rpc/mod.rs | 44 +++--- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 165 +++++++++----------- 3 files changed, 103 insertions(+), 163 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index b752b74cb..76f62f23a 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -23,34 +23,8 @@ pub enum RPCMethod { Unknown, } -impl From for RPCMethod { - fn from(method_id: u16) -> Self { - match method_id { - 0 => RPCMethod::Hello, - 1 => RPCMethod::Goodbye, - 10 => RPCMethod::BeaconBlockRoots, - 11 => RPCMethod::BeaconBlockHeaders, - 12 => RPCMethod::BeaconBlockBodies, - 13 => RPCMethod::BeaconChainState, +pub enum RawRPCRequest - _ => RPCMethod::Unknown, - } - } -} - -impl Into for RPCMethod { - fn into(self) -> u16 { - match self { - RPCMethod::Hello => 0, - RPCMethod::Goodbye => 1, - RPCMethod::BeaconBlockRoots => 10, - RPCMethod::BeaconBlockHeaders => 11, - RPCMethod::BeaconBlockBodies => 12, - RPCMethod::BeaconChainState => 13, - _ => 0, - } - } -} #[derive(Debug, Clone)] pub enum RPCRequest { @@ -62,20 +36,6 @@ pub enum RPCRequest { BeaconChainState(BeaconChainStateRequest), } -impl RPCRequest { - pub fn method_id(&self) -> u16 { - let method = match self { - RPCRequest::Hello(_) => RPCMethod::Hello, - RPCRequest::Goodbye(_) => RPCMethod::Goodbye, - RPCRequest::BeaconBlockRoots(_) => RPCMethod::BeaconBlockRoots, - RPCRequest::BeaconBlockHeaders(_) => RPCMethod::BeaconBlockHeaders, - RPCRequest::BeaconBlockBodies(_) => RPCMethod::BeaconBlockBodies, - RPCRequest::BeaconChainState(_) => RPCMethod::BeaconChainState, - }; - method.into() - } -} - #[derive(Debug, Clone)] pub enum RPCResponse { Hello(HelloMessage), @@ -85,19 +45,6 @@ pub enum RPCResponse { BeaconChainState(BeaconChainStateResponse), } -impl RPCResponse { - pub fn method_id(&self) -> u16 { - let method = match self { - RPCResponse::Hello(_) => RPCMethod::Hello, - RPCResponse::BeaconBlockRoots(_) => RPCMethod::BeaconBlockRoots, - RPCResponse::BeaconBlockHeaders(_) => RPCMethod::BeaconBlockHeaders, - RPCResponse::BeaconBlockBodies(_) => RPCMethod::BeaconBlockBodies, - RPCResponse::BeaconChainState(_) => RPCMethod::BeaconChainState, - }; - method.into() - } -} - /* Request/Response data structures for RPC methods */ /// The HELLO request/response handshake message. @@ -170,7 +117,7 @@ pub struct BeaconBlockRootsResponse { } impl BeaconBlockRootsResponse { - /// Returns `true` if each `self.roots.slot[i]` is higher than the preceeding `i`. + /// Returns `true` if each `self.roots.slot[i]` is higher than the preceding `i`. pub fn slots_are_ascending(&self) -> bool { for window in self.roots.windows(2) { if window[0].slot >= window[1].slot { diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index 2d303469c..59015a86e 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -1,7 +1,9 @@ -/// RPC Protocol over libp2p. +/// 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. /// -/// This is purpose built for Ethereum 2.0 serenity and the protocol listens on -/// `/eth/serenity/rpc/1.0.0` pub mod methods; mod protocol; @@ -17,9 +19,8 @@ use slog::o; use std::marker::PhantomData; use tokio::io::{AsyncRead, AsyncWrite}; -/// The network behaviour handles RPC requests/responses as specified in the Eth 2.0 phase 0 -/// specification. - +/// This struct implements the libp2p `NetworkBehaviour` trait and therefore manages network-level +/// logic. pub struct Rpc { /// Queue of events to processed. events: Vec>, @@ -39,7 +40,9 @@ impl Rpc { } } - /// Submits and RPC request. + /// Submits an RPC request. + /// + /// The peer must be connected for this to succeed. pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { self.events.push(NetworkBehaviourAction::SendEvent { peer_id, @@ -52,13 +55,14 @@ impl NetworkBehaviour for Rpc where TSubstream: AsyncRead + AsyncWrite, { - type ProtocolsHandler = OneShotHandler; + type ProtocolsHandler = OneShotHandler; type OutEvent = RPCMessage; fn new_handler(&mut self) -> Self::ProtocolsHandler { Default::default() } + // handled by discovery fn addresses_of_peer(&mut self, _peer_id: &PeerId) -> Vec { Vec::new() } @@ -81,8 +85,8 @@ where ) { // ignore successful send events let event = match event { - OneShotEvent::Rx(event) => event, - OneShotEvent::Sent => return, + HandlerEvent::Rx(event) => event, + HandlerEvent::Sent => return, }; // send the event to the user @@ -114,25 +118,25 @@ pub enum RPCMessage { PeerDialed(PeerId), } -/// Transmission between the `OneShotHandler` and the `RPCEvent`. +/// The output type received from the `OneShotHandler`. #[derive(Debug)] -pub enum OneShotEvent { - /// We received an RPC from a remote. +pub enum HandlerEvent { + /// An RPC was received from a remote. Rx(RPCEvent), - /// We successfully sent an RPC request. + /// An RPC was sent. Sent, } -impl From for OneShotEvent { +impl From for HandlerEvent { #[inline] - fn from(rpc: RPCEvent) -> OneShotEvent { - OneShotEvent::Rx(rpc) + fn from(rpc: RPCEvent) -> HandlerEvent { + HandlerEvent::Rx(rpc) } } -impl From<()> for OneShotEvent { +impl From<()> for HandlerEvent { #[inline] - fn from(_: ()) -> OneShotEvent { - OneShotEvent::Sent + fn from(_: ()) -> HandlerEvent { + HandlerEvent::Sent } } diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index 7afded3ac..136b9cc26 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -16,106 +16,109 @@ pub struct RPCProtocol; impl UpgradeInfo for RPCProtocol { type Info = &'static [u8]; - type InfoIter = iter::Once; + type InfoIter = iter::Iter; - #[inline] fn protocol_info(&self) -> Self::InfoIter { - iter::once(b"/eth/serenity/rpc/1.0.0") + vec![b"/eth/serenity/rpc/hello/1/ssz", + b"/eth/serenity/rpc/goodbye/1/ssz", + b"/eth/serenity/rpc/beacon_block_roots/1/ssz", + b"/eth/serenity/rpc/beacon_block_headers/1/ssz", + b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", + b"/eth/serenity/rpc/beacon_chain_state/1/ssz"].into_iter() } } -impl Default for RPCProtocol { - fn default() -> Self { - RPCProtocol - } -} - -/// A monotonic counter for ordering `RPCRequest`s. -#[derive(Debug, Clone, Copy, Default)] -pub struct RequestId(u64); - -impl RequestId { - /// Increment the request id. - pub fn increment(&mut self) { - self.0 += 1 - } - - /// Return the previous id. - pub fn previous(self) -> Self { - Self(self.0 - 1) - } -} - -impl Eq for RequestId {} - -impl PartialEq for RequestId { - fn eq(&self, other: &RequestId) -> bool { - self.0 == other.0 - } -} - -impl Hash for RequestId { - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl From for RequestId { - fn from(x: u64) -> RequestId { - RequestId(x) - } -} - -impl Into for RequestId { - fn into(self) -> u64 { - self.0 - } -} - -impl_encode_via_from!(RequestId, u64); -impl_decode_via_from!(RequestId, u64); - -/// The RPC types which are sent/received in this protocol. +/// The outbound RPC type as well as the return type used in the behaviour. #[derive(Debug, Clone)] pub enum RPCEvent { - Request { - id: RequestId, - method_id: u16, - body: RPCRequest, - }, - Response { - id: RequestId, - method_id: u16, //TODO: Remove and process decoding upstream - result: RPCResponse, - }, + Request ( RPCRequest ), + Response ( RPCResponse ), } +// outbound protocol supports the same as the inbound. impl UpgradeInfo for RPCEvent { type Info = &'static [u8]; - type InfoIter = iter::Once; + type InfoIter = iter::Iter; - #[inline] fn protocol_info(&self) -> Self::InfoIter { - iter::once(b"/eth/serenity/rpc/1.0.0") + vec![b"/eth/serenity/rpc/hello/1/ssz", + b"/eth/serenity/rpc/goodbye/1/ssz", + b"/eth/serenity/rpc/beacon_block_roots/1/ssz", + b"/eth/serenity/rpc/beacon_block_headers/1/ssz", + b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", + b"/eth/serenity/rpc/beacon_chain_state/1/ssz"].into_iter() } } -type FnDecodeRPCEvent = fn(Vec, ()) -> Result; +/* 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 = fn(upgrade::Negotiated, Vec, ()) -> Result<(upgrade::Negotiated,RPCEvent), DecodeError>; impl InboundUpgrade for RPCProtocol where TSocket: AsyncRead + AsyncWrite, { - type Output = RPCEvent; + type Socket = upgrade::Negotiated, + type Output = (Self::Socket, RPCEvent), type Error = DecodeError; - type Future = upgrade::ReadOneThen, (), FnDecodeRPCEvent>; + type Future = upgrade::ReadRespond; - fn upgrade_inbound(self, socket: upgrade::Negotiated, _: Self::Info) -> Self::Future { - upgrade::read_one_then(socket, MAX_READ_SIZE, (), |packet, ()| Ok(decode(packet)?)) + fn upgrade_inbound(self, socket: upgrade::Negotiated, protocol: Self::Info) -> Self::Future { + upgrade::read_respond(socket, MAX_READ_SIZE, (), |socket, packet, ()| Ok((socket, decode_request(packet, protocol)?))) } } -/// A helper structed used to obtain SSZ serialization for RPC messages. + +/* Outbound upgrade */ + +impl OutboundUpgrade for RPCEvent +where + TSocket: AsyncWrite, +{ + type Output = (); + type Error = io::Error; + type Future = upgrade::WriteOne>; + + #[inline] + fn upgrade_outbound(self, socket: upgrade::Negotiated, protocol: Self::Info) -> Self::Future { + let bytes = ssz_encode(&self); + upgrade::request_response(socket, + upgrade::write_one(socket, bytes) + } +} + +// This function can be extended to provide further logic for supporting various protocol versions/encoding +fn decode_request(packet: Vec, protocol: &'static[u8]) { + match protocol { + b"/eth/serenity/rpc/hello/1/ssz" => { } + b"/eth/serenity/rpc/goodbye/1/ssz", + b"/eth/serenity/rpc/beacon_block_roots/1/ssz", + b"/eth/serenity/rpc/beacon_block_headers/1/ssz", + b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", + b"/eth/serenity/rpc/beacon_chain_state/1/ssz", + _=> { // Other protocols are not supported. + return Err(DecodeError::UnknownProtocol); + } + } +} + + + + + + + + + + + + + + +/// A helper struct used to obtain SSZ serialization for RPC messages. #[derive(Encode, Decode, Default)] struct SszContainer { /// Note: the `is_request` field is not included in the spec. @@ -186,20 +189,6 @@ fn decode(packet: Vec) -> Result { } } -impl OutboundUpgrade for RPCEvent -where - TSocket: AsyncWrite, -{ - type Output = (); - type Error = io::Error; - type Future = upgrade::WriteOne>; - - #[inline] - fn upgrade_outbound(self, socket: upgrade::Negotiated, _: Self::Info) -> Self::Future { - let bytes = ssz_encode(&self); - upgrade::write_one(socket, bytes) - } -} impl Encode for RPCEvent { fn is_ssz_fixed_len() -> bool { From cda61c1577c0e937ae1a53684993bccb5dbbbbca Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 5 Jul 2019 18:59:53 +1000 Subject: [PATCH 26/39] Adds basic inbound/outbound upgrades for eth2 rpc --- beacon_node/eth2-libp2p/src/rpc/methods.rs | 132 +++-- beacon_node/eth2-libp2p/src/rpc/mod.rs | 2 +- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 530 +++++++++++++------- 3 files changed, 403 insertions(+), 261 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index 76f62f23a..dfc3121c1 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -2,39 +2,7 @@ use ssz::{impl_decode_via_from, impl_encode_via_from}; use ssz_derive::{Decode, Encode}; -use types::{BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; - -#[derive(Debug)] -/// Available Serenity Libp2p RPC methods -pub enum RPCMethod { - /// Initialise handshake between connecting peers. - Hello, - /// Terminate a connection providing a reason. - Goodbye, - /// Requests a number of beacon block roots. - BeaconBlockRoots, - /// Requests a number of beacon block headers. - BeaconBlockHeaders, - /// Requests a number of beacon block bodies. - BeaconBlockBodies, - /// Requests values for a merkle proof for the current blocks state root. - BeaconChainState, // Note: experimental, not complete. - /// Unknown method received. - Unknown, -} - -pub enum RawRPCRequest - - -#[derive(Debug, Clone)] -pub enum RPCRequest { - Hello(HelloMessage), - Goodbye(GoodbyeReason), - BeaconBlockRoots(BeaconBlockRootsRequest), - BeaconBlockHeaders(BeaconBlockHeadersRequest), - BeaconBlockBodies(BeaconBlockBodiesRequest), - BeaconChainState(BeaconChainStateRequest), -} +use types::{Epoch, Hash256, Slot}; #[derive(Debug, Clone)] pub enum RPCResponse { @@ -45,19 +13,35 @@ pub enum RPCResponse { BeaconChainState(BeaconChainStateResponse), } +pub enum ResponseCode { + Success = 0, + EncodingError = 1, + InvalidRequest = 2, + ServerError = 3, +} + /* Request/Response data structures for RPC methods */ +/* Requests */ + /// The HELLO request/response handshake message. #[derive(Encode, Decode, Clone, Debug)] pub struct HelloMessage { /// The network ID of the peer. pub network_id: u8, + + /// The chain id for the HELLO request. + pub chain_id: u64, + /// The peers last finalized root. pub latest_finalized_root: Hash256, + /// The peers last finalized epoch. pub latest_finalized_epoch: Epoch, + /// The peers last block root. pub best_root: Hash256, + /// The peers last slot. pub best_slot: Slot, } @@ -68,43 +52,40 @@ pub struct HelloMessage { /// however `GoodbyeReason::Unknown.into()` will go into `0_u64`. Therefore de-serializing then /// re-serializing may not return the same bytes. #[derive(Debug, Clone)] -pub enum GoodbyeReason { - ClientShutdown, - IrreleventNetwork, - Fault, - Unknown, +pub enum Goodbye { + /// This node has shutdown. + ClientShutdown = 1, + + /// Incompatible networks. + IrreleventNetwork = 2, + + /// Error/fault in the RPC. + Fault = 3, + + /// Unknown reason. + Unknown = 0, } -impl From for GoodbyeReason { - fn from(id: u64) -> GoodbyeReason { +impl From for Goodbye { + fn from(id: u64) -> Goodbye { match id { - 1 => GoodbyeReason::ClientShutdown, - 2 => GoodbyeReason::IrreleventNetwork, - 3 => GoodbyeReason::Fault, - _ => GoodbyeReason::Unknown, + 1 => Goodbye::ClientShutdown, + 2 => Goodbye::IrreleventNetwork, + 3 => Goodbye::Fault, + _ => Goodbye::Unknown, } } } -impl Into for GoodbyeReason { - fn into(self) -> u64 { - match self { - GoodbyeReason::Unknown => 0, - GoodbyeReason::ClientShutdown => 1, - GoodbyeReason::IrreleventNetwork => 2, - GoodbyeReason::Fault => 3, - } - } -} - -impl_encode_via_from!(GoodbyeReason, u64); -impl_decode_via_from!(GoodbyeReason, u64); +impl_encode_via_from!(Goodbye, u64); +impl_decode_via_from!(Goodbye, u64); /// Request a number of beacon block roots from a peer. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BeaconBlockRootsRequest { /// The starting slot of the requested blocks. pub start_slot: Slot, + /// The number of blocks from the start slot. pub count: u64, // this must be less than 32768. //TODO: Enforce this in the lower layers } @@ -116,6 +97,17 @@ pub struct BeaconBlockRootsResponse { pub roots: Vec, } +/// Contains a block root and associated slot. +#[derive(Encode, Decode, Clone, Debug, PartialEq)] +pub struct BlockRootSlot { + /// The block root. + pub block_root: Hash256, + + /// The block slot. + pub slot: Slot, +} + +/// The response of a beacl 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 { @@ -129,33 +121,27 @@ impl BeaconBlockRootsResponse { } } -/// Contains a block root and associated slot. -#[derive(Encode, Decode, Clone, Debug, PartialEq)] -pub struct BlockRootSlot { - /// The block root. - pub block_root: Hash256, - /// The block slot. - pub slot: Slot, -} - /// Request a number of beacon block headers from a peer. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BeaconBlockHeadersRequest { /// The starting header hash of the requested headers. pub start_root: Hash256, + /// The starting slot of the requested headers. pub start_slot: Slot, + /// The maximum number of headers than can be returned. pub max_headers: u64, + /// The maximum number of slots to skip between blocks. pub skip_slots: u64, } /// Response containing requested block headers. -#[derive(Encode, Decode, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct BeaconBlockHeadersResponse { - /// The list of requested beacon block headers. - pub headers: Vec, + /// The list of ssz-encoded requested beacon block headers. + pub headers: Vec, } /// Request a number of beacon block bodies from a peer. @@ -166,10 +152,10 @@ pub struct BeaconBlockBodiesRequest { } /// Response containing the list of requested beacon block bodies. -#[derive(Encode, Decode, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct BeaconBlockBodiesResponse { - /// The list of beacon block bodies being requested. - pub block_bodies: Vec, + /// The list of ssz-encoded beacon block bodies being requested. + pub block_bodies: Vec, } /// Request values for tree hashes which yield a blocks `state_root`. @@ -184,5 +170,5 @@ pub struct BeaconChainStateRequest { #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BeaconChainStateResponse { /// The values corresponding the to the requested tree hashes. - pub values: bool, //TBD - stubbed with encodeable bool + pub values: bool, //TBD - stubbed with encodable bool } diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index 59015a86e..0ccb6c1e3 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -19,7 +19,7 @@ use slog::o; use std::marker::PhantomData; use tokio::io::{AsyncRead, AsyncWrite}; -/// This struct implements the libp2p `NetworkBehaviour` trait and therefore manages network-level +/// Rpc implements the libp2p `NetworkBehaviour` trait and therefore manages network-level /// logic. pub struct Rpc { /// Queue of events to processed. diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index 136b9cc26..e0b4ca1a4 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -1,14 +1,15 @@ use super::methods::*; use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use ssz::{impl_decode_via_from, impl_encode_via_from, ssz_encode, Decode, Encode}; -use ssz_derive::{Decode, Encode}; -use std::hash::{Hash, Hasher}; +use ssz::{Decode, Encode}; +use std::hash::Hasher; use std::io; use std::iter; use tokio::io::{AsyncRead, AsyncWrite}; /// The maximum bytes that can be sent across the RPC. -const MAX_READ_SIZE: usize = 4_194_304; // 4M +const MAX_RPC_SIZE: usize = 4_194_304; // 4M +/// The protocol prefix the RPC protocol id. +const PROTOCOL_PREFIX: &str = "/eth/serenity/rpc/"; /// Implementation of the `ConnectionUpgrade` for the rpc protocol. #[derive(Debug, Clone)] @@ -16,65 +17,195 @@ pub struct RPCProtocol; impl UpgradeInfo for RPCProtocol { type Info = &'static [u8]; - type InfoIter = iter::Iter; + type InfoIter = Vec; fn protocol_info(&self) -> Self::InfoIter { - vec![b"/eth/serenity/rpc/hello/1/ssz", - b"/eth/serenity/rpc/goodbye/1/ssz", - b"/eth/serenity/rpc/beacon_block_roots/1/ssz", - b"/eth/serenity/rpc/beacon_block_headers/1/ssz", - b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", - b"/eth/serenity/rpc/beacon_chain_state/1/ssz"].into_iter() + vec![ + b"/eth/serenity/rpc/hello/1/ssz", + b"/eth/serenity/rpc/goodbye/1/ssz", + b"/eth/serenity/rpc/beacon_block_roots/1/ssz", + b"/eth/serenity/rpc/beacon_block_headers/1/ssz", + b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", + b"/eth/serenity/rpc/beacon_chain_state/1/ssz", + ] } } /// The outbound RPC type as well as the return type used in the behaviour. #[derive(Debug, Clone)] pub enum RPCEvent { - Request ( RPCRequest ), - Response ( RPCResponse ), + Request(RPCRequest), + Response(RPCResponse), } -// outbound protocol supports the same as the inbound. -impl UpgradeInfo for RPCEvent { - type Info = &'static [u8]; - type InfoIter = iter::Iter; - - fn protocol_info(&self) -> Self::InfoIter { - vec![b"/eth/serenity/rpc/hello/1/ssz", - b"/eth/serenity/rpc/goodbye/1/ssz", - b"/eth/serenity/rpc/beacon_block_roots/1/ssz", - b"/eth/serenity/rpc/beacon_block_headers/1/ssz", - b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", - b"/eth/serenity/rpc/beacon_chain_state/1/ssz"].into_iter() - } -} - -/* Inbound upgrade */ +/* 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 = fn(upgrade::Negotiated, Vec, ()) -> Result<(upgrade::Negotiated,RPCEvent), DecodeError>; +type FnDecodeRPCEvent = fn( + upgrade::Negotiated, + Vec, + (), +) -> Result<(upgrade::Negotiated, RPCEvent), RPCError>; impl InboundUpgrade for RPCProtocol where TSocket: AsyncRead + AsyncWrite, { - type Socket = upgrade::Negotiated, - type Output = (Self::Socket, RPCEvent), - type Error = DecodeError; - type Future = upgrade::ReadRespond; + type Output = (upgrade::Negotiated, RPCEvent); + type Error = RPCError; + type Future = upgrade::ReadRespond, (), FnDecodeRPCEvent>; - fn upgrade_inbound(self, socket: upgrade::Negotiated, protocol: Self::Info) -> Self::Future { - upgrade::read_respond(socket, MAX_READ_SIZE, (), |socket, packet, ()| Ok((socket, decode_request(packet, protocol)?))) + fn upgrade_inbound( + self, + socket: upgrade::Negotiated, + protocol: Self::Info, + ) -> Self::Future { + upgrade::read_respond(socket, MAX_RPC_SIZE, (), |socket, packet, ()| { + Ok((socket, decode_request(packet, protocol)?)) + }) } } +/* Outbound request */ -/* Outbound upgrade */ +// Combines all the RPC requests into a single enum to implement `UpgradeInfo` and +// `OutboundUpgrade` -impl OutboundUpgrade for RPCEvent +/// The raw protocol id sent over the wire. +type RawProtocolId = Vec; + +/// Tracks the types in a protocol id. +pub struct ProtocolId { + /// The rpc message type/name. + pub message_name: String, + + /// The version of the RPC. + pub version: usize, + + /// The encoding of the RPC. + pub encoding: String, +} + +/// An RPC protocol ID. +impl ProtocolId { + pub fn new(message_name: String, version: usize, encoding: String) -> Self { + ProtocolId { + message_name, + version, + encoding, + } + } + + /// Converts a raw RPC protocol id string into an `RPCProtocolId` + pub fn from_bytes(bytes: Vec) -> Result { + let protocol_string = String::from_utf8(bytes.as_vec()) + .map_err(|_| RPCError::InvalidProtocol("Invalid protocol Id"))?; + let protocol_string = protocol_string.as_str().split('/'); + + Ok(ProtocolId { + message_name: protocol_string[3], + version: protocol_string[4], + encoding: protocol_string[5], + }) + } +} + +impl Into for ProtocolId { + fn into(&self) -> [u8] { + &format!( + "{}/{}/{}/{}", + PROTOCOL_PREFIX, self.message_name, self.version, self.encoding + ) + .as_bytes() + } +} + +#[derive(Debug, Clone)] +pub enum RPCRequest { + Hello(HelloMessage), + Goodbye(Goodbye), + BeaconBlockRoots(BeaconBlockRootsRequest), + BeaconBlockHeaders(BeaconBlockHeadersRequest), + BeaconBlockBodies(BeaconBlockBodiesRequest), + BeaconChainState(BeaconChainStateRequest), +} + +impl UpgradeInfo for RPCRequest { + type Info = RawProtocolId; + type InfoIter = Vec; + + // add further protocols as we support more encodings/versions + fn protocol_info(&self) -> Self::InfoIter { + self.supported_protocols() + } +} + +// GOODBYE RPC has it's own upgrade as it doesn't expect a response +impl UpgradeInfo for Goodbye { + type Info = RawProtocolId; + type InfoIter = iter::Once; + + // add further protocols as we support more encodings/versions + fn protocol_info(&self) -> Self::InfoIter { + iter::once(ProtocolId::new("goodbye", 1, "ssz").into()) + } +} + +/// Implements the encoding per supported protocol for RPCRequest. +impl RPCRequest { + pub fn supported_protocols(&self) -> Vec { + match self { + // add more protocols when versions/encodings are supported + RPCRequest::Hello(_) => vec![ProtocolId::new("hello", 1, "ssz").into()], + RPCRequest::Goodbye(_) => vec![ProtocolId::new("goodbye", 1, "ssz").into()], + RPCRequest::BeaconBlockRoots(_) => { + vec![ProtocolId::new("beacon_block_roots", 1, "ssz").into()] + } + RPCRequest::BeaconBlockHeaders(_) => { + vec![ProtocolId::new("beacon_block_headers", 1, "ssz").into()] + } + RPCRequest::BeaconBlockBodies(_) => { + vec![ProtocolId::new("beacon_block_bodies", 1, "ssz").into()] + } + RPCRequest::BeaconBlockState(_) => { + 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, io::Error> { + // Assume select has given a supported protocol. + let protocol = ProtocolId::from_bytes(protocol)?; + // Match on the encoding and in the future, the version + match protocol.encoding { + "ssz" => Ok(self.ssz_encode()), + _ => { + return Err(RPCError::Custom(format!( + "Unknown Encoding: {}", + protocol.encoding + ))) + } + } + } + + fn ssz_encode(&self) { + match self { + RPCRequest::Hello(req) => req.as_ssz_bytes(), + RPCRequest::Goodbye(req) => req.as_ssz_bytes(), + RPCRequest::BeaconBlockRoots(req) => req.as_ssz_bytes(), + RPCRequest::BeaconBlockHeaders(req) => req.as_ssz_bytes(), + RPCRequest::BeaconBlockBodies(req) => req.as_ssz_bytes(), + RPCRequest::BeaconChainState(req) => req.as_ssz_bytes(), + } + } +} + +/* Outbound upgrades */ + +impl OutboundUpgrade for RPCRequest where TSocket: AsyncWrite, { @@ -82,177 +213,202 @@ where type Error = io::Error; type Future = upgrade::WriteOne>; - #[inline] - fn upgrade_outbound(self, socket: upgrade::Negotiated, protocol: Self::Info) -> Self::Future { - let bytes = ssz_encode(&self); - upgrade::request_response(socket, + fn upgrade_outbound( + self, + socket: upgrade::Negotiated, + protocol: Self::Info, + ) -> Self::Future { + let bytes = self.encode(protocol); + upgrade::request_response(socket, bytes, MAX_RPC_SIZE, protocol, |packet, protocol| { + Ok(decode_response(packet, protocol)?) + }) + } +} + +impl OutboundUpgrade for Goodbye +where + TSocket: AsyncWrite, +{ + type Output = (); + type Error = io::Error; + type Future = upgrade::WriteOne>; + + fn upgrade_outbound( + self, + socket: upgrade::Negotiated, + protocol: Self::Info, + ) -> Self::Future { + let bytes = self.as_ssz_bytes(); upgrade::write_one(socket, bytes) } } -// This function can be extended to provide further logic for supporting various protocol versions/encoding -fn decode_request(packet: Vec, protocol: &'static[u8]) { - match protocol { - b"/eth/serenity/rpc/hello/1/ssz" => { } - b"/eth/serenity/rpc/goodbye/1/ssz", - b"/eth/serenity/rpc/beacon_block_roots/1/ssz", - b"/eth/serenity/rpc/beacon_block_headers/1/ssz", - b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", - b"/eth/serenity/rpc/beacon_chain_state/1/ssz", - _=> { // Other protocols are not supported. - return Err(DecodeError::UnknownProtocol); - } - } -} +/* Decoding for Requests/Responses */ +// This function can be extended to provide further logic for supporting various protocol versions/encoding +fn decode_request(packet: Vec, protocol: ProtocolId) -> Result { + let protocol_id = ProtocolId::from_bytes(protocol); - - - - - - - - - - - - -/// A helper struct used to obtain SSZ serialization for RPC messages. -#[derive(Encode, Decode, Default)] -struct SszContainer { - /// Note: the `is_request` field is not included in the spec. - /// - /// We are unable to determine a request from a response unless we add some flag to the - /// packet. Here we have added a bool (encoded as 1 byte) which is set to `1` if the - /// message is a request. - is_request: bool, - id: u64, - other: u16, - bytes: Vec, -} - -fn decode(packet: Vec) -> Result { - let msg = SszContainer::from_ssz_bytes(&packet)?; - - if msg.is_request { - let body = match RPCMethod::from(msg.other) { - RPCMethod::Hello => RPCRequest::Hello(HelloMessage::from_ssz_bytes(&msg.bytes)?), - RPCMethod::Goodbye => RPCRequest::Goodbye(GoodbyeReason::from_ssz_bytes(&msg.bytes)?), - RPCMethod::BeaconBlockRoots => { - RPCRequest::BeaconBlockRoots(BeaconBlockRootsRequest::from_ssz_bytes(&msg.bytes)?) - } - RPCMethod::BeaconBlockHeaders => RPCRequest::BeaconBlockHeaders( - BeaconBlockHeadersRequest::from_ssz_bytes(&msg.bytes)?, - ), - RPCMethod::BeaconBlockBodies => { - RPCRequest::BeaconBlockBodies(BeaconBlockBodiesRequest::from_ssz_bytes(&msg.bytes)?) - } - RPCMethod::BeaconChainState => { - RPCRequest::BeaconChainState(BeaconChainStateRequest::from_ssz_bytes(&msg.bytes)?) - } - RPCMethod::Unknown => return Err(DecodeError::UnknownRPCMethod), - }; - - Ok(RPCEvent::Request { - id: RequestId::from(msg.id), - method_id: msg.other, - body, - }) - } - // we have received a response - else { - let result = match RPCMethod::from(msg.other) { - RPCMethod::Hello => RPCResponse::Hello(HelloMessage::from_ssz_bytes(&msg.bytes)?), - RPCMethod::BeaconBlockRoots => { - RPCResponse::BeaconBlockRoots(BeaconBlockRootsResponse::from_ssz_bytes(&msg.bytes)?) - } - RPCMethod::BeaconBlockHeaders => RPCResponse::BeaconBlockHeaders( - BeaconBlockHeadersResponse::from_ssz_bytes(&msg.bytes)?, - ), - RPCMethod::BeaconBlockBodies => RPCResponse::BeaconBlockBodies( - BeaconBlockBodiesResponse::from_ssz_bytes(&msg.bytes)?, - ), - RPCMethod::BeaconChainState => { - RPCResponse::BeaconChainState(BeaconChainStateResponse::from_ssz_bytes(&msg.bytes)?) - } - // We should never receive a goodbye response; it is invalid. - RPCMethod::Goodbye => return Err(DecodeError::UnknownRPCMethod), - RPCMethod::Unknown => return Err(DecodeError::UnknownRPCMethod), - }; - - Ok(RPCEvent::Response { - id: RequestId::from(msg.id), - method_id: msg.other, - result, - }) - } -} - - -impl Encode for RPCEvent { - fn is_ssz_fixed_len() -> bool { - false - } - - fn ssz_append(&self, buf: &mut Vec) { - let container = match self { - RPCEvent::Request { - id, - method_id, - body, - } => SszContainer { - is_request: true, - id: (*id).into(), - other: *method_id, - bytes: match body { - RPCRequest::Hello(body) => body.as_ssz_bytes(), - RPCRequest::Goodbye(body) => body.as_ssz_bytes(), - RPCRequest::BeaconBlockRoots(body) => body.as_ssz_bytes(), - RPCRequest::BeaconBlockHeaders(body) => body.as_ssz_bytes(), - RPCRequest::BeaconBlockBodies(body) => body.as_ssz_bytes(), - RPCRequest::BeaconChainState(body) => body.as_ssz_bytes(), - }, + 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")), }, - RPCEvent::Response { - id, - method_id, - result, - } => SszContainer { - is_request: false, - id: (*id).into(), - other: *method_id, - bytes: match result { - RPCResponse::Hello(response) => response.as_ssz_bytes(), - RPCResponse::BeaconBlockRoots(response) => response.as_ssz_bytes(), - RPCResponse::BeaconBlockHeaders(response) => response.as_ssz_bytes(), - RPCResponse::BeaconBlockBodies(response) => response.as_ssz_bytes(), - RPCResponse::BeaconChainState(response) => response.as_ssz_bytes(), - }, + _ => 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")), }, - }; - - container.ssz_append(buf) + _ => 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, protocol: RawProtocolId) -> Result { + 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", + )), + }, + } +} + +/// Error in RPC Encoding/Decoding. #[derive(Debug)] -pub enum DecodeError { +pub enum RPCError { + /// Error when reading the packet from the socket. ReadError(upgrade::ReadOneError), + /// Error when decoding the raw buffer from ssz. SSZDecodeError(ssz::DecodeError), - UnknownRPCMethod, + /// Invalid Protocol ID + InvalidProtocol(&'static str), + /// Custom message. + Custom(String), } -impl From for DecodeError { +impl From for RPCError { #[inline] fn from(err: upgrade::ReadOneError) -> Self { - DecodeError::ReadError(err) + RPCError::ReadError(err) } } -impl From for DecodeError { +impl From for RPCError { #[inline] fn from(err: ssz::DecodeError) -> Self { - DecodeError::SSZDecodeError(err) + RPCError::SSZDecodeError(err) } } From f1127e4e0dc05039f33846e92901a85f2835bcbe Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 6 Jul 2019 21:32:32 +1000 Subject: [PATCH 27/39] Initial addition of an RPC Protocol Handler --- beacon_node/eth2-libp2p/src/rpc/handler.rs | 225 ++++++++++++++++++++ beacon_node/eth2-libp2p/src/rpc/mod.rs | 16 +- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 19 +- 3 files changed, 248 insertions(+), 12 deletions(-) create mode 100644 beacon_node/eth2-libp2p/src/rpc/handler.rs diff --git a/beacon_node/eth2-libp2p/src/rpc/handler.rs b/beacon_node/eth2-libp2p/src/rpc/handler.rs new file mode 100644 index 000000000..fe5fba05f --- /dev/null +++ b/beacon_node/eth2-libp2p/src/rpc/handler.rs @@ -0,0 +1,225 @@ + +use libp2p::core::protocols_handler::{ + KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, + SubstreamProtocol +}; +use libp2p::core::upgrade::{InboundUpgrade, OutboundUpgrade}; +use futures::prelude::*; +use smallvec::SmallVec; +use std::{error, marker::PhantomData, time::Duration}; +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 + + /// The upgrade for inbound substreams. + listen_protocol: SubstreamProtocol, + + /// If `Some`, something bad happened and we should shut down the handler with an error. + pending_error: Option>, + + /// Queue of events to produce in `poll()`. + events_out: SmallVec<[TOutEvent; 4]>, + + /// Queue of outbound substreams to open. + dial_queue: SmallVec<[TOutProto; 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 + + /// Sequential Id for waiting substreams. + current_substream_id: usize, + + /// Maximum number of concurrent outbound substreams being opened. Value is never modified. + max_dial_negotiated: u32, + + /// Value to return from `connection_keep_alive`. + keep_alive: KeepAlive, + + /// After the given duration has elapsed, an inactive connection will shutdown. + inactive_timeout: Duration, +} + +struct WaitingStream { + stream: TSubstream, + timeout: Duration, +} + +impl + RPCHandler +{ + pub fn new( + listen_protocol: SubstreamProtocol, + inactive_timeout: Duration + ) -> Self { + RPCHandler { + listen_protocol, + pending_error: None, + events_out: SmallVec::new(), + dial_queue: SmallVec::new(), + dial_negotiated: 0, + waiting_substreams: FnvHashMap::default(), + curent_substream_id: 0, + max_dial_negotiated: 8, + keep_alive: KeepAlive::Yes, + inactive_timeout, + } + } + + /// Returns the number of pending requests. + pub fn pending_requests(&self) -> u32 { + self.dial_negotiated + self.dial_queue.len() as u32 + } + + /// Returns a reference to the listen protocol configuration. + /// + /// > **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 { + &self.listen_protocol + } + + /// Returns a mutable reference to the listen protocol configuration. + /// + /// > **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 { + &mut self.listen_protocol + } + + /// Opens an outbound substream with `upgrade`. + #[inline] + pub fn send_request(&mut self, upgrade: RPCRequest) { + self.keep_alive = KeepAlive::Yes; + self.dial_queue.push(upgrade); + } +} + +impl Default + for RPCHandler +{ + fn default() -> Self { + RPCHandler::new(SubstreamProtocol::new(RPCProtocol), Duration::from_secs(10)) + } +} + +impl ProtocolsHandler + for RPCHandler +{ + type InEvent = RPCRequest; + type OutEvent = RPCEvent; + type Error = ProtocolsHandlerUpgrErr; + type Substream = TSubstream; + type InboundProtocol = RPCProtocol; + type OutboundProtocol = RPCRequest; + type OutboundOpenInfo = (); + + #[inline] + fn listen_protocol(&self) -> SubstreamProtocol { + self.listen_protocol.clone() + } + + #[inline] + fn inject_fully_negotiated_inbound( + &mut self, + out: RPCProtocol::Output, + ) { + if !self.keep_alive.is_yes() { + self.keep_alive = KeepAlive::Until(Instant::now() + self.inactive_timeout); + } + + 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; + } + + // New inbound request. Store the stream and tag the output. + let awaiting_stream = WaitingStream { stream, 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.current_substream_id += 1; + } + + #[inline] + fn inject_fully_negotiated_outbound( + &mut self, + out: RPCResponse, + _: Self::OutboundOpenInfo, + ) { + self.dial_negotiated -= 1; + + if self.dial_negotiated == 0 && self.dial_queue.is_empty() { + self.keep_alive = KeepAlive::Until(Instant::now() + self.inactive_timeout); + } + + self.events_out.push(out.into()); + } + + #[inline] + fn inject_event(&mut self, event: Self::InEvent) { + self.send_request(event); + } + + #[inline] + fn inject_dial_upgrade_error( + &mut self, + _: Self::OutboundOpenInfo, + error: ProtocolsHandlerUpgrErr< + >::Error, + >, + ) { + if self.pending_error.is_none() { + self.pending_error = Some(error); + } + } + + #[inline] + fn connection_keep_alive(&self) -> KeepAlive { + self.keep_alive + } + + fn poll( + &mut self, + ) -> Poll< + ProtocolsHandlerEvent, + Self::Error, + > { + if let Some(err) = self.pending_error.take() { + return Err(err); + } + + if !self.events_out.is_empty() { + return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( + self.events_out.remove(0), + ))); + } else { + self.events_out.shrink_to_fit(); + } + + if !self.dial_queue.is_empty() { + if self.dial_negotiated < self.max_dial_negotiated { + self.dial_negotiated += 1; + return Ok(Async::Ready( + ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(self.dial_queue.remove(0)), + info: (), + }, + )); + } + } else { + self.dial_queue.shrink_to_fit(); + } + + Ok(Async::NotReady) + } +} diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index 0ccb6c1e3..8914e18d5 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -13,12 +13,24 @@ use libp2p::core::swarm::{ ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; use libp2p::{Multiaddr, PeerId}; -pub use methods::{HelloMessage, RPCMethod, RPCRequest, RPCResponse}; -pub use protocol::{RPCEvent, RPCProtocol, RequestId}; +pub use methods::{HelloMessage, RPCResponse}; +pub use protocol::{RPCProtocol, RPCRequest}; use slog::o; use std::marker::PhantomData; use tokio::io::{AsyncRead, AsyncWrite}; +/// 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), + + /// 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), +} + /// Rpc implements the libp2p `NetworkBehaviour` trait and therefore manages network-level /// logic. pub struct Rpc { diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index e0b4ca1a4..fb4a2b603 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -31,13 +31,6 @@ impl UpgradeInfo for RPCProtocol { } } -/// The outbound RPC type as well as the return type used in the behaviour. -#[derive(Debug, Clone)] -pub enum RPCEvent { - Request(RPCRequest), - Response(RPCResponse), -} - /* Inbound upgrade */ // The inbound protocol reads the request, decodes it and returns the stream to the protocol @@ -209,9 +202,9 @@ impl OutboundUpgrade for RPCRequest where TSocket: AsyncWrite, { - type Output = (); - type Error = io::Error; - type Future = upgrade::WriteOne>; + type Output = RPCResponse; + type Error = RPCResponse; + type Future = upgrade::RequestResponse>; fn upgrade_outbound( self, @@ -219,6 +212,12 @@ where protocol: Self::Info, ) -> Self::Future { let bytes = self.encode(protocol); + wait_for_response = if let RPCRequest::Goodbye(_) = self { + false + } else { + true + }; + // TODO: Reimplement request_response upgrade::request_response(socket, bytes, MAX_RPC_SIZE, protocol, |packet, protocol| { Ok(decode_response(packet, protocol)?) }) From bb0e28b8e3381d6763b35d1ccddec2ef2cf68ea3 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 6 Jul 2019 23:43:44 +1000 Subject: [PATCH 28/39] Improved rpc protocols handler. WIP --- beacon_node/eth2-libp2p/src/rpc/handler.rs | 88 +++++++++++++++------ beacon_node/eth2-libp2p/src/rpc/methods.rs | 1 + beacon_node/eth2-libp2p/src/rpc/mod.rs | 4 +- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 33 +------- 4 files changed, 70 insertions(+), 56 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/handler.rs b/beacon_node/eth2-libp2p/src/rpc/handler.rs index fe5fba05f..ff9f4ebdb 100644 --- a/beacon_node/eth2-libp2p/src/rpc/handler.rs +++ b/beacon_node/eth2-libp2p/src/rpc/handler.rs @@ -1,4 +1,3 @@ - use libp2p::core::protocols_handler::{ KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol @@ -14,7 +13,7 @@ use wasm_timer::Instant; pub const RESPONSE_TIMEOUT: u64 = 9; /// Implementation of `ProtocolsHandler` for the RPC protocol. -pub struct RPCHandler +pub struct RPCHandler { /// The upgrade for inbound substreams. listen_protocol: SubstreamProtocol, @@ -26,13 +25,13 @@ pub struct RPCHandler events_out: SmallVec<[TOutEvent; 4]>, /// Queue of outbound substreams to open. - dial_queue: SmallVec<[TOutProto; 4]>, + dial_queue: SmallVec<[(usize,TOutProto); 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 + waiting_substreams: FnvHashMap /// Sequential Id for waiting substreams. current_substream_id: usize, @@ -47,9 +46,15 @@ pub struct RPCHandler inactive_timeout: Duration, } -struct WaitingStream { - stream: TSubstream, - timeout: Duration, +/// State of an outbound substream. Either waiting for a response, or in the process of sending. +pub enum SubstreamState { + /// An outbound substream is waiting a response from the user. + WaitingResponse { + stream: , + timeout: Duration, + } + /// A response has been sent and we are waiting for the stream to close. + ResponseSent(WriteOne) } impl @@ -96,9 +101,9 @@ impl /// Opens an outbound substream with `upgrade`. #[inline] - pub fn send_request(&mut self, upgrade: RPCRequest) { + pub fn send_request(&mut self, request_id, u64, upgrade: RPCRequest) { self.keep_alive = KeepAlive::Yes; - self.dial_queue.push(upgrade); + self.dial_queue.push((request_id, upgrade)); } } @@ -106,20 +111,20 @@ impl Default for RPCHandler { fn default() -> Self { - RPCHandler::new(SubstreamProtocol::new(RPCProtocol), Duration::from_secs(10)) + RPCHandler::new(SubstreamProtocol::new(RPCProtocol), Duration::from_secs(30)) } } impl ProtocolsHandler for RPCHandler { - type InEvent = RPCRequest; + type InEvent = RPCEvent; type OutEvent = RPCEvent; type Error = ProtocolsHandlerUpgrErr; type Substream = TSubstream; type InboundProtocol = RPCProtocol; type OutboundProtocol = RPCRequest; - type OutboundOpenInfo = (); + type OutboundOpenInfo = u64; // request_id #[inline] fn listen_protocol(&self) -> SubstreamProtocol { @@ -131,10 +136,6 @@ impl ProtocolsHandler &mut self, out: RPCProtocol::Output, ) { - if !self.keep_alive.is_yes() { - self.keep_alive = KeepAlive::Until(Instant::now() + self.inactive_timeout); - } - let (stream, req) = out; // drop the stream and return a 0 id for goodbye "requests" if let req @ RPCRequest::Goodbye(_) = req { @@ -143,7 +144,7 @@ impl ProtocolsHandler } // New inbound request. Store the stream and tag the output. - let awaiting_stream = WaitingStream { stream, timeout: Instant::now() + Duration::from_secs(RESPONSE_TIMEOUT) }; + let awaiting_stream = SubstreamState::WaitingResponse { stream, 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)); @@ -154,20 +155,36 @@ impl ProtocolsHandler fn inject_fully_negotiated_outbound( &mut self, out: RPCResponse, - _: Self::OutboundOpenInfo, + request_id : Self::OutboundOpenInfo, ) { self.dial_negotiated -= 1; - if self.dial_negotiated == 0 && self.dial_queue.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 { + self.keep_alive = KeepAlive::Yes; + } - self.events_out.push(out.into()); + self.events_out.push(RPCEvent::Response(request_id, out)); } + // Note: If the substream has closed due to inactivity, or the substream is in the + // wrong state a response will fail silently. #[inline] - fn inject_event(&mut self, event: Self::InEvent) { - self.send_request(event); + fn inject_event(&mut self, rpc_event: Self::InEvent) { + match rpc_event { + 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)); + } + } + } + } } #[inline] @@ -198,6 +215,26 @@ impl ProtocolsHandler return Err(err); } + // prioritise sending responses for waiting substreams + self.waiting_substreams.retain(|_k, mut waiting_stream| { + match waiting_stream => { + SubstreamState::PendingWrite(write_one) => { + match write_one.poll() => { + Ok(Async::Ready(_socket)) => false, + Ok(Async::NotReady()) => true, + Err(_e) => { + //TODO: Add logging + // throw away streams that error + false + } + } + }, + SubstreamState::WaitingResponse { timeout, .. } => { + if Instant::now() > timeout { false} else { true } + } + } + }); + if !self.events_out.is_empty() { return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( self.events_out.remove(0), @@ -206,20 +243,21 @@ impl ProtocolsHandler self.events_out.shrink_to_fit(); } + // establish outbound substreams if !self.dial_queue.is_empty() { if self.dial_negotiated < self.max_dial_negotiated { self.dial_negotiated += 1; + let (request_id, req) = self.dial_queue.remove(0); return Ok(Async::Ready( ProtocolsHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new(self.dial_queue.remove(0)), - info: (), + protocol: SubstreamProtocol::new(req), + info: request_id, }, )); } } else { self.dial_queue.shrink_to_fit(); } - Ok(Async::NotReady) } } diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index dfc3121c1..90784f929 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -7,6 +7,7 @@ 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), diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index 8914e18d5..222e1de8d 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -1,6 +1,6 @@ /// The Ethereum 2.0 Wire Protocol /// -/// This protocol is a purpose built ethereum 2.0 libp2p protocol. It's role is to facilitate +/// 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. /// @@ -67,7 +67,7 @@ impl NetworkBehaviour for Rpc where TSubstream: AsyncRead + AsyncWrite, { - type ProtocolsHandler = OneShotHandler; + type ProtocolsHandler = RPCHandler; type OutEvent = RPCMessage; fn new_handler(&mut self) -> Self::ProtocolsHandler { diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index fb4a2b603..9b7e88184 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -10,6 +10,8 @@ use tokio::io::{AsyncRead, AsyncWrite}; const MAX_RPC_SIZE: usize = 4_194_304; // 4M /// The protocol prefix the RPC protocol id. const PROTOCOL_PREFIX: &str = "/eth/serenity/rpc/"; +/// The number of seconds to wait for a response before the stream is terminated. +const RESPONSE_TIMEOUT: u64 = 10; /// Implementation of the `ConnectionUpgrade` for the rpc protocol. #[derive(Debug, Clone)] @@ -58,6 +60,7 @@ where upgrade::read_respond(socket, MAX_RPC_SIZE, (), |socket, packet, ()| { Ok((socket, decode_request(packet, protocol)?)) }) + .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) } } @@ -135,17 +138,6 @@ impl UpgradeInfo for RPCRequest { } } -// GOODBYE RPC has it's own upgrade as it doesn't expect a response -impl UpgradeInfo for Goodbye { - type Info = RawProtocolId; - type InfoIter = iter::Once; - - // add further protocols as we support more encodings/versions - fn protocol_info(&self) -> Self::InfoIter { - iter::once(ProtocolId::new("goodbye", 1, "ssz").into()) - } -} - /// Implements the encoding per supported protocol for RPCRequest. impl RPCRequest { pub fn supported_protocols(&self) -> Vec { @@ -221,24 +213,7 @@ where upgrade::request_response(socket, bytes, MAX_RPC_SIZE, protocol, |packet, protocol| { Ok(decode_response(packet, protocol)?) }) - } -} - -impl OutboundUpgrade for Goodbye -where - TSocket: AsyncWrite, -{ - type Output = (); - type Error = io::Error; - type Future = upgrade::WriteOne>; - - fn upgrade_outbound( - self, - socket: upgrade::Negotiated, - protocol: Self::Info, - ) -> Self::Future { - let bytes = self.as_ssz_bytes(); - upgrade::write_one(socket, bytes) + .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) } } From 4a84b2f7cc4b602dc0bb5802fa9539583de65cd9 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 9 Jul 2019 15:44:23 +1000 Subject: [PATCH 29/39] Improved RPC handling. WIP --- beacon_node/eth2-libp2p/Cargo.toml | 4 + beacon_node/eth2-libp2p/src/behaviour.rs | 6 +- beacon_node/eth2-libp2p/src/rpc/handler.rs | 141 +++-- beacon_node/eth2-libp2p/src/rpc/methods.rs | 20 +- beacon_node/eth2-libp2p/src/rpc/mod.rs | 69 +-- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 580 +++++++++++------- .../eth2-libp2p/src/rpc/request_response.rs | 239 ++++++++ 7 files changed, 720 insertions(+), 339 deletions(-) create mode 100644 beacon_node/eth2-libp2p/src/rpc/request_response.rs diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 82d218241..c1059d415 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -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" diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 4e4cf24f3..542060014 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -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 { /// The routing pub-sub mechanism for eth2. gossipsub: Gossipsub, /// The serenity RPC specified in the wire-0 protocol. - serenity_rpc: Rpc, + serenity_rpc: RPC, /// Keep regular connection to peers and disconnect if absent. ping: Ping, /// Kademlia for peer discovery. @@ -57,7 +57,7 @@ impl Behaviour { .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), diff --git a/beacon_node/eth2-libp2p/src/rpc/handler.rs b/beacon_node/eth2-libp2p/src/rpc/handler.rs index ff9f4ebdb..02b82adea 100644 --- a/beacon_node/eth2-libp2p/src/rpc/handler.rs +++ b/beacon_node/eth2-libp2p/src/rpc/handler.rs @@ -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 { - /// The upgrade for inbound substreams. listen_protocol: SubstreamProtocol, /// If `Some`, something bad happened and we should shut down the handler with an error. - pending_error: Option>, + pending_error: Option>, /// 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 + waiting_substreams: FnvHashMap>, /// Sequential Id for waiting substreams. current_substream_id: usize, @@ -50,19 +50,21 @@ pub struct RPCHandler { pub enum SubstreamState { /// An outbound substream is waiting a response from the user. WaitingResponse { - stream: , - timeout: Duration, - } + /// The negotiated substream. + substream: upgrade::Negotiated, + /// 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) + PendingWrite(WriteOne, Vec>), } -impl - RPCHandler -{ +impl RPCHandler { pub fn new( listen_protocol: SubstreamProtocol, - inactive_timeout: Duration + inactive_timeout: Duration, ) -> Self { RPCHandler { listen_protocol, @@ -71,7 +73,7 @@ impl 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 /// /// > **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 { + pub fn listen_protocol_ref(&self) -> &SubstreamProtocol { &self.listen_protocol } @@ -95,36 +97,35 @@ impl /// /// > **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 { + pub fn listen_protocol_mut(&mut self) -> &mut SubstreamProtocol { &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 Default - for RPCHandler -{ +impl Default for RPCHandler { fn default() -> Self { RPCHandler::new(SubstreamProtocol::new(RPCProtocol), Duration::from_secs(30)) } } -impl ProtocolsHandler - for RPCHandler +impl ProtocolsHandler for RPCHandler +where + TSubstream: AsyncRead + AsyncWrite, { type InEvent = RPCEvent; type OutEvent = RPCEvent; - type Error = ProtocolsHandlerUpgrErr; + type Error = ProtocolsHandlerUpgrErr; 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 { @@ -134,35 +135,43 @@ impl ProtocolsHandler #[inline] fn inject_fully_negotiated_inbound( &mut self, - out: RPCProtocol::Output, + out: >::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: >::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 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 ProtocolsHandler >::Error, >, ) { + dbg!(error); if self.pending_error.is_none() { self.pending_error = Some(error); } @@ -217,20 +236,24 @@ impl 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 } } } }); diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index 90784f929..b1897f9f5 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -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 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 { diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index 222e1de8d..e6ac74d9a 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -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 { +pub struct RPC { /// Queue of events to processed. events: Vec>, /// Pins the generic substream. @@ -42,10 +44,10 @@ pub struct Rpc { _log: slog::Logger, } -impl Rpc { +impl RPC { 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 Rpc { } } -impl NetworkBehaviour for Rpc +impl NetworkBehaviour for RPC where TSubstream: AsyncRead + AsyncWrite, { @@ -95,12 +97,6 @@ where source: PeerId, event: ::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 for HandlerEvent { - #[inline] - fn from(rpc: RPCEvent) -> HandlerEvent { - HandlerEvent::Rx(rpc) - } -} - -impl From<()> for HandlerEvent { - #[inline] - fn from(_: ()) -> HandlerEvent { - HandlerEvent::Sent - } -} diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index 9b7e88184..a1c948b2a 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -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 = fn( - upgrade::Negotiated, - Vec, - (), -) -> Result<(upgrade::Negotiated, RPCEvent), RPCError>; - -impl InboundUpgrade for RPCProtocol -where - TSocket: AsyncRead + AsyncWrite, -{ - type Output = (upgrade::Negotiated, RPCEvent); - type Error = RPCError; - type Future = upgrade::ReadRespond, (), FnDecodeRPCEvent>; - - fn upgrade_inbound( - self, - socket: upgrade::Negotiated, - 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; @@ -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) -> Result { - let protocol_string = String::from_utf8(bytes.as_vec()) + pub fn from_bytes(bytes: &[u8]) -> Result { + 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 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 = + fn( + upgrade::Negotiated, + Vec, + &'static [u8], // protocol id + ) -> Result<(upgrade::Negotiated, RPCRequest, ProtocolId), RPCError>; + +impl InboundUpgrade for RPCProtocol +where + TSocket: AsyncRead + AsyncWrite, +{ + type Output = (upgrade::Negotiated, RPCRequest, ProtocolId); + type Error = RPCError; + type Future = MapErr< + tokio_timer::Timeout< + upgrade::ReadRespond< + upgrade::Negotiated, + Self::Info, + FnDecodeRPCEvent, + >, + >, + fn(tokio::timer::timeout::Error) -> RPCError, + >; + + fn upgrade_inbound( + self, + socket: upgrade::Negotiated, + 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) + .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, io::Error> { - // Assume select has given a supported protocol. - let protocol = ProtocolId::from_bytes(protocol)?; + pub fn encode(&self, protocol: ProtocolId) -> Result, 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 { 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, protocol: ProtocolId, response_code: ResponseCode) -> Result { + + 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 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, protocol: ProtocolId) -> Result { + 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, 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 { + 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 OutboundUpgrade for RPCRequest where - TSocket: AsyncWrite, + TSocket: AsyncRead + AsyncWrite, { type Output = RPCResponse; - type Error = RPCResponse; - type Future = upgrade::RequestResponse>; + type Error = RPCError; + type Future = MapErr< + tokio_timer::Timeout, Vec>>, + fn(tokio::timer::timeout::Error) -> RPCError, + >; fn upgrade_outbound( self, socket: upgrade::Negotiated, 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, protocol: ProtocolId) -> Result { - 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, protocol: RawProtocolId) -> Result { - 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 for RPCError { RPCError::SSZDecodeError(err) } } + +impl From> for RPCError { + fn from(err: tokio::timer::timeout::Error) -> Self { + if err.is_elapsed() { + RPCError::StreamTimeout + } else { + RPCError::Custom("Stream timer failed".into()) + } + } +} + +impl From 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, + } + } +} diff --git a/beacon_node/eth2-libp2p/src/rpc/request_response.rs b/beacon_node/eth2-libp2p/src/rpc/request_response.rs new file mode 100644 index 000000000..cd8ed9280 --- /dev/null +++ b/beacon_node/eth2-libp2p/src/rpc/request_response.rs @@ -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( + socket: TSocket, + data: TData, // data sent as a request + max_size: usize, // maximum bytes to read in a response + short_circuit_return: Option, // default value to return right after a request, do not wait for a response + protocol: ProtocolId, // the protocol being negotiated +) -> RPCRequestResponse +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> { + protocol: ProtocolId, + inner: RPCRequestResponseInner, +} + +enum RPCRequestResponseInner { + // We need to write data to the socket. + Write(WriteOneInner, usize, Option), + // We need to read the response code. + ReadResponseCode(io::ReadExact>>, usize), + // We need to read a final data packet. The second parameter is the response code + Read(ReadOne, ResponseCode), + // An error happened during the processing. + Poisoned, +} + +impl Future for RPCRequestResponse +where + TSocket: AsyncRead + AsyncWrite, + TData: AsRef<[u8]>, +{ + type Item = RPCResponse; + type Error = RPCError; + + fn poll(&mut self) -> Poll { + 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(socket: TSocket, data: TData) -> WriteOne +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 { + /// We need to write the data length to the socket. + WriteLen(io::WriteAll>, TData), + /// We need to write the actual data to the socket. + Write(io::WriteAll), + /// We need to shut down the socket. + Shutdown(io::Shutdown), + /// A problem happened during the processing. + Poisoned, +} + +impl Future for WriteOneInner +where + TSocket: AsyncWrite, + TData: AsRef<[u8]>, +{ + type Item = TSocket; + type Error = std::io::Error; + + fn poll(&mut self) -> Poll { + 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> { + inner: WriteOneInner, +} + +impl Future for WriteOne +where + TSocket: AsyncWrite, + TData: AsRef<[u8]>, +{ + type Item = (); + type Error = std::io::Error; + + #[inline] + fn poll(&mut self) -> Poll { + Ok(self.inner.poll()?.map(|_socket| ())) + } +} From 0292679f27608cbecbf17b22092ccca8dfea4fde Mon Sep 17 00:00:00 2001 From: Age Manning Date: Thu, 11 Jul 2019 15:11:31 +1000 Subject: [PATCH 30/39] Improved error handling. Switching to codecs for easier encoding support --- beacon_node/eth2-libp2p/src/rpc/methods.rs | 11 +- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 229 ++++++++++-------- .../eth2-libp2p/src/rpc/request_response.rs | 7 +- 3 files changed, 144 insertions(+), 103 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index b1897f9f5..066da1351 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -32,8 +32,8 @@ pub struct HelloMessage { /// The reason given for a `Goodbye` message. /// -/// Note: any unknown `u64::into(n)` will resolve to `GoodbyeReason::Unknown` for any unknown `n`, -/// however `GoodbyeReason::Unknown.into()` will go into `0_u64`. Therefore de-serializing then +/// Note: any unknown `u64::into(n)` will resolve to `Goodbye::Unknown` for any unknown `n`, +/// however `Goodbye::Unknown.into()` will go into `0_u64`. Therefore de-serializing then /// re-serializing may not return the same bytes. #[derive(Debug, Clone)] pub enum Goodbye { @@ -61,6 +61,13 @@ impl From for Goodbye { } } +impl Into for Goodbye { + fn into(self) -> u64 { + self as u64 + } +} + +impl_encode_via_from!(Goodbye, u64); impl_decode_via_from!(Goodbye, u64); /// Request a number of beacon block roots from a peer. diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index a1c948b2a..4d1f0b021 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -3,6 +3,7 @@ use super::request_response::{rpc_request_response, RPCRequestResponse}; use futures::future::Future; use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; use std::io; use std::time::Duration; use tokio::io::{AsyncRead, AsyncWrite}; @@ -26,12 +27,12 @@ impl UpgradeInfo for RPCProtocol { fn protocol_info(&self) -> Self::InfoIter { vec![ - b"/eth/serenity/rpc/hello/1/ssz", - b"/eth/serenity/rpc/goodbye/1/ssz", - b"/eth/serenity/rpc/beacon_block_roots/1/ssz", - b"/eth/serenity/rpc/beacon_block_headers/1/ssz", - b"/eth/serenity/rpc/beacon_block_bodies/1/ssz", - b"/eth/serenity/rpc/beacon_chain_state/1/ssz", + b"/eth/serenity/rpc/hello/1.0.0/ssz", + b"/eth/serenity/rpc/goodbye/1.0.0/ssz", + b"/eth/serenity/rpc/beacon_block_roots/1.0.0/ssz", + b"/eth/serenity/rpc/beacon_block_headers/1.0.0/ssz", + b"/eth/serenity/rpc/beacon_block_bodies/1.0.0/ssz", + b"/eth/serenity/rpc/beacon_chain_state/1.0.0/ssz", ] } } @@ -45,7 +46,7 @@ pub struct ProtocolId { pub message_name: String, /// The version of the RPC. - pub version: usize, + pub version: String, /// The encoding of the RPC. pub encoding: String, @@ -53,10 +54,10 @@ pub struct ProtocolId { /// An RPC protocol ID. impl ProtocolId { - pub fn new(message_name: &str, version: usize, encoding: &str) -> Self { + pub fn new(message_name: &str, version: &str, encoding: &str) -> Self { ProtocolId { message_name: message_name.into(), - version, + version: version.into(), encoding: encoding.into(), } } @@ -73,9 +74,7 @@ impl ProtocolId { Ok(ProtocolId { message_name: protocol_list[3].into(), - version: protocol_list[4] - .parse() - .map_err(|_| RPCError::InvalidProtocol("Invalid version"))?, + version: protocol_list[4].into(), encoding: protocol_list[5].into(), }) } @@ -172,19 +171,19 @@ impl RPCRequest { pub fn supported_protocols(&self) -> Vec { match self { // add more protocols when versions/encodings are supported - RPCRequest::Hello(_) => vec![ProtocolId::new("hello", 1, "ssz").into()], - RPCRequest::Goodbye(_) => vec![ProtocolId::new("goodbye", 1, "ssz").into()], + RPCRequest::Hello(_) => vec![ProtocolId::new("hello", "1.0.0", "ssz").into()], + RPCRequest::Goodbye(_) => vec![ProtocolId::new("goodbye", "1.0.0", "ssz").into()], RPCRequest::BeaconBlockRoots(_) => { - vec![ProtocolId::new("beacon_block_roots", 1, "ssz").into()] + vec![ProtocolId::new("beacon_block_roots", "1.0.0", "ssz").into()] } RPCRequest::BeaconBlockHeaders(_) => { - vec![ProtocolId::new("beacon_block_headers", 1, "ssz").into()] + vec![ProtocolId::new("beacon_block_headers", "1.0.0", "ssz").into()] } RPCRequest::BeaconBlockBodies(_) => { - vec![ProtocolId::new("beacon_block_bodies", 1, "ssz").into()] + vec![ProtocolId::new("beacon_block_bodies", "1.0.0", "ssz").into()] } RPCRequest::BeaconChainState(_) => { - vec![ProtocolId::new("beacon_block_state", 1, "ssz").into()] + vec![ProtocolId::new("beacon_block_state", "1.0.0", "ssz").into()] } } } @@ -216,30 +215,26 @@ impl RPCRequest { // 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, protocol: ProtocolId, response_code: ResponseCode) -> Result { - - match response_code { - ResponseCode:: - - - + pub fn decode(packet: Vec, protocol: ProtocolId) -> Result { match protocol.message_name.as_str() { - "hello" => match protocol.version { - 1 => match protocol.encoding.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => 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() { + "goodbye" => match protocol.version.as_str() { + "1.0.0" => 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")), + _ => Err(RPCError::InvalidProtocol( + "Unknown GOODBYE version.as_str()", + )), }, - "beacon_block_roots" => match protocol.version { - 1 => match protocol.encoding.as_str() { + "beacon_block_roots" => match protocol.version.as_str() { + "1.0.0" => match protocol.encoding.as_str() { "ssz" => Ok(RPCRequest::BeaconBlockRoots( BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, )), @@ -248,11 +243,11 @@ impl RPCRequest { )), }, _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_ROOTS version", + "Unknown BEACON_BLOCK_ROOTS version.", )), }, - "beacon_block_headers" => match protocol.version { - 1 => match protocol.encoding.as_str() { + "beacon_block_headers" => match protocol.version.as_str() { + "1.0.0" => match protocol.encoding.as_str() { "ssz" => Ok(RPCRequest::BeaconBlockHeaders( BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, )), @@ -261,11 +256,11 @@ impl RPCRequest { )), }, _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_HEADERS version", + "Unknown BEACON_BLOCK_HEADERS version.", )), }, - "beacon_block_bodies" => match protocol.version { - 1 => match protocol.encoding.as_str() { + "beacon_block_bodies" => match protocol.version.as_str() { + "1.0.0" => match protocol.encoding.as_str() { "ssz" => Ok(RPCRequest::BeaconBlockBodies( BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, )), @@ -274,11 +269,11 @@ impl RPCRequest { )), }, _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_BODIES version", + "Unknown BEACON_BLOCK_BODIES version.", )), }, - "beacon_chain_state" => match protocol.version { - 1 => match protocol.encoding.as_str() { + "beacon_chain_state" => match protocol.version.as_str() { + "1.0.0" => match protocol.encoding.as_str() { "ssz" => Ok(RPCRequest::BeaconChainState( BeaconChainStateRequest::from_ssz_bytes(&packet)?, )), @@ -287,7 +282,7 @@ impl RPCRequest { )), }, _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_CHAIN_STATE version", + "Unknown BEACON_CHAIN_STATE version.", )), }, } @@ -319,12 +314,11 @@ pub enum ResponseCode { EncodingError = 1, InvalidRequest = 2, ServerError = 3, - Unknown, + Unknown = 255, } - -impl From for ResponseCode { - fn from(val: u64) -> ResponseCode { +impl From for ResponseCode { + fn from(val: u8) -> ResponseCode { match val { 0 => ResponseCode::Success, 1 => ResponseCode::EncodingError, @@ -335,72 +329,110 @@ impl From for ResponseCode { } } +impl Into for ResponseCode { + fn into(self) -> u8 { + self as u8 + } +} + +#[derive(Encode, Decode)] +struct ErrorResponse { + error_message: String, +} + 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, protocol: ProtocolId) -> Result { - 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")), + pub fn decode( + packet: Vec, + protocol: ProtocolId, + response_code: ResponseCode, + ) -> Result { + match response_code { + ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), + ResponseCode::InvalidRequest => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Invalid Request: {}", + response.error_message + ))) + } + ResponseCode::ServerError => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Remote Server Error: {}", + response.error_message + ))) + } + ResponseCode::Success => match protocol.message_name.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => 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.")), }, - _ => 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", + "goodbye" => Err(RPCError::Custom( + "GOODBYE should not have a response".into(), )), - }, - "beacon_block_headers" => match protocol.version { - 1 => match protocol.encoding.as_str() { - "ssz" => Ok(RPCResponse::BeaconBlockHeaders( - BeaconBlockHeadersResponse { headers: packet }, - )), + "beacon_block_roots" => match protocol.version.as_str() { + "1.0.0" => 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_HEADERS encoding", + "Unknown BEACON_BLOCK_ROOTS version.", )), }, - _ => 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, - })), + "beacon_block_headers" => match protocol.version.as_str() { + "1.0.0" => 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_BODIES encoding", + "Unknown BEACON_BLOCK_HEADERS version.", )), }, - _ => 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)?, - )), + "beacon_block_bodies" => match protocol.version.as_str() { + "1.0.0" => 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_CHAIN_STATE encoding", + "Unknown BEACON_BLOCK_BODIES version.", + )), + }, + "beacon_chain_state" => match protocol.version.as_str() { + "1.0.0" => 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.", )), }, - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_CHAIN_STATE version", - )), }, } } @@ -503,7 +535,6 @@ impl From for RPCError { RPCError::SSZDecodeError(err) } } - impl From> for RPCError { fn from(err: tokio::timer::timeout::Error) -> Self { if err.is_elapsed() { diff --git a/beacon_node/eth2-libp2p/src/rpc/request_response.rs b/beacon_node/eth2-libp2p/src/rpc/request_response.rs index cd8ed9280..e511bfe9f 100644 --- a/beacon_node/eth2-libp2p/src/rpc/request_response.rs +++ b/beacon_node/eth2-libp2p/src/rpc/request_response.rs @@ -91,8 +91,11 @@ where RPCRequestResponseInner::ReadResponseCode(mut inner, max_size) => { match inner.poll()? { Async::Ready((socket, data)) => { + let resp_code_byte = [0; 1]; + // data must be only 1-byte - this cannot panic + resp_code_byte.copy_from_slice(&data.into_inner()); let response_code = - ResponseCode::from(u64::from_be_bytes(data.into_inner())); + ResponseCode::from(u8::from_be_bytes(resp_code_byte)); // known response codes match response_code { ResponseCode::Success @@ -113,7 +116,7 @@ where // unknown response code let response = RPCResponse::Error(format!( "Unknown response code: {}", - response_code + (response_code as u8) )); return Ok(Async::Ready(response)); } From d84780a3395ee49f338848d6019e566bfd26fc0a Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 13 Jul 2019 18:35:33 +1000 Subject: [PATCH 31/39] Initial base codec --- .../eth2-libp2p/src/rpc/codecs/base.rs | 442 +++++++++++++++++ beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs | 0 beacon_node/eth2-libp2p/src/rpc/protocol.rs | 261 ++-------- codec.rs | 461 ++++++++++++++++++ 4 files changed, 945 insertions(+), 219 deletions(-) create mode 100644 beacon_node/eth2-libp2p/src/rpc/codecs/base.rs create mode 100644 beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs create mode 100644 codec.rs diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs b/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs new file mode 100644 index 000000000..5fe1aad8a --- /dev/null +++ b/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs @@ -0,0 +1,442 @@ + +///! This handles the various supported encoding mechanism for the Eth 2.0 RPC. +///! +///! Currently supported encodings are: +///! - ssz - Varint length-prefixed SSZ-encoding. + + +pub struct BaseCodec { + /// Inner codec for handling various encodings + inner: TCodec, + /// Optimisation for decoding. True if the response code has been read and we are awaiting a + /// response. + read_response_code: bool, +} + + +impl Encoder for BaseOutboundCodec +where + TCodec: Encoder +{ + type Item = RPCRequest; + type Error = ::Error; + + fn encode( + &mut self, + item: Self::Item, + dst: &mut BytesMut + ) -> Result<(), Self::Error> { + self.inner.encode(item, dst) + } +} + + +impl Encoder for BaseInboundCodec +where + TCodec: Encoder +{ + type Item = RPCResponse; + type Error = ::Error; + + fn encode( + &mut self, + item: Self::Item, + dst: &mut BytesMut + ) -> Result<(), Self::Error> { + + match item { + RPCResponse::Error(response) => { + match response = { + ErrorResponse::EncodingError => { + dst.clear(); + dst.reserve(1); + dst.put(response as u8); + return; + } + ErrorResponse:: + self.inner.encode(item, dst) + } +} + + +impl Decoder for BaseCodec +where + TCodec: Decoder, + ::Error: From, +{ + + type Item = RPCResponse; + type Error = ::Error; + + fn decode( + &mut self, + src: &mut BytesMut + ) -> Result, Self::Error> { + + if !self.read_response_code { + if src.len() < 1 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "no bytes received")); + } + + let resp_code_byte = [0; 1]; + // data must be only 1-byte - this cannot panic + resp_code_byte.copy_from_slice(&src); + let response_code = + ResponseCode::from(u8::from_be_bytes(resp_code_byte)); + match response_code { + ResponseCode::EncodingError => { + // invalid encoding + let response = RPCResponse::Error("Invalid Encoding".into()); + return Ok(Async::Ready(response)); + } + ResponseCode::Success + | ResponseCode::InvalidRequest + | ResponseCode::ServerError => { + // need to read another packet + self.inner = RPCRequestResponseInner::Read( + read_one(socket, max_size), + response_code, + ) + } + ResponseCode::Unknown => { + // unknown response code + let response = RPCResponse::Error(format!( + "Unknown response code: {}", + (response_code as u8) + )); + return Ok(Async::Ready(response)); + + + + + + } + +} + + + + + +/// SSZ Input stream +pub struct SSZInboundSink { + inner: + protocol: ProtocolId + +impl for SSZInputStream +where + TSocket: AsyncRead + AsyncWrite +{ + + /// Set up the initial input stream object. + pub fn new(incomming: TSocket, protocol: ProtocolId, max_size: usize) -> Self { + + // this type of stream should only apply to ssz protocols + debug_assert!(protocol.encoding.as_str() == "ssz"); + + let mut uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_size); + + let inner = Framed::new(incomming, uvi_codec).from_err() + .with(|response| { + self.encode(response) + }) + .and_then(|bytes| { + self.decode(request) + }).into_future(); + + //TODO: add timeout + + SSZInputStream { + inner, + protocol + } + } + + /// Decodes an SSZ-encoded RPCRequest. + fn decode(&self, request: RPCRequest) { + + match self.protocol.message_name.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)), + _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), + }, + "goodbye" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)), + _ => Err(RPCError::InvalidProtocol( + "Unknown GOODBYE version.as_str()", + )), + }, + "beacon_block_roots" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockRoots( + BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_ROOTS version.", + )), + }, + "beacon_block_headers" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockHeaders( + BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_HEADERS version.", + )), + }, + "beacon_block_bodies" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockBodies( + BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_BODIES version.", + )), + }, + "beacon_chain_state" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconChainState( + BeaconChainStateRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_CHAIN_STATE version.", + )), + }, + } + } + + fn encode(&self, response: RPCResponse) { + + // TODO: Add error code + + match response { + 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(), + } + } + +} + +type SSZInboundOutput = stream::AndThen>>, RPCError>, + RPCResponse, + fn(RPCResponse) -> Result, RPCError>, + Result, RPCError>, + >, + fn(BytesMut) -> Result, + Result + >; + +impl Sink for SSZInputStreamSink { + + type SinkItem = RPCResponse; + type SinkError = RPCError; + + fn start_send( + &mut self, + item: Self::SinkItem +) -> Result, Self::SinkError> { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Result, Self::SinkError> { + self.inner.poll_complete() + } +} + +/* Outbound specific stream */ + +// Implement our own decoder to handle the response byte + +struct SSZOutboundCodec + + + +pub struct SSZOutboundStreamSink { + inner: + protocol: ProtocolId + +impl for SSZOutboundStreamSink +where + TSocket: AsyncRead + AsyncWrite +{ + + /// Set up the initial outbound stream object. + pub fn new(socket: TSocket, protocol: ProtocolId, max_size: usize) -> Self { + + // this type of stream should only apply to ssz protocols + debug_assert!(protocol.encoding.as_str() == "ssz"); + + let mut uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_size); + + let inner = Framed::new(socket, uvi_codec).from_err() + .with(|request| { + self.encode(request) + }) + .and_then(|bytes| { + self.decode(response) + }); + + SSZOutboundStream { + inner, + protocol + } + } + + + + + + /// Decodes a response that was received on the same stream as a request. The response type should + /// therefore match the request protocol type. + pub fn decode(&self, response: Vec, + protocol: ProtocolId, + response_code: ResponseCode, + ) -> Result { + match response_code { + ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), + ResponseCode::InvalidRequest => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Invalid Request: {}", + response.error_message + ))) + } + ResponseCode::ServerError => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Remote Server Error: {}", + response.error_message + ))) + } + ResponseCode::Success => match protocol.message_name.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.", + )), + }, + }, + } + } + + fn encode(&self, response: RPCResponse) { + + match response { + 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(), + } + } + +} + +type SSZOutboundStream = stream::AndThen>>, RPCError>, + RPCResponse, + fn(RPCResponse) -> Result, RPCError>, + Result, RPCError>, + >, + fn(BytesMut) -> Result, + Result + >; + + +impl Stream for SSZInputStreamSink { + + type Item = SSZInboundOutput; + type Error = RPCError; + + fn poll(&mut self) -> Result>, Self::Error> { + self.inner.poll() + } +} + +impl Sink for SSZInputStreamSink { + + type SinkItem = RPCResponse; + type SinkError = RPCError; + + fn start_send( + &mut self, + item: Self::SinkItem +) -> Result, Self::SinkError> { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Result, Self::SinkError> { + self.inner.poll_complete() + } +} + + + + + + + diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs b/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs new file mode 100644 index 000000000..e69de29bb diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index 4d1f0b021..ce58c5607 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -212,81 +212,6 @@ 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, protocol: ProtocolId) -> Result { - match protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str()", - )), - }, - "beacon_block_roots" => match protocol.version.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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 */ @@ -305,163 +230,61 @@ pub enum RPCResponse { 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 = 255, +pub enum RPCErrorResponse { + Success(RPCResponse), + EncodingError, + InvalidRequest(ErrorMessage), + ServerError(ErrorMessage), + Unknown(ErrorMessage), } -impl From for ResponseCode { - fn from(val: u8) -> ResponseCode { - match val { - 0 => ResponseCode::Success, - 1 => ResponseCode::EncodingError, - 2 => ResponseCode::InvalidRequest, - 3 => ResponseCode::ServerError, - _ => ResponseCode::Unknown, +impl RPCErrorResponse { + /// If a response has no payload, returns the variant corresponding to the code. + pub fn internal_data(response_code: u8) -> Option { + match response_code { + // EncodingError + 1 => Some(RPCErrorResponse::EncodingError), + // All others require further data + _ => None, } } -} -impl Into for ResponseCode { - fn into(self) -> u8 { - self as u8 + pub fn as_u8(&self) -> u8 { + match self { + RPCErrorResponse::Success(_) => 0, + RPCErrorResponse::EncodingError => 1, + RPCErrorResponse::InvalidRequest(_) => 2, + RPCErrorResponse::ServerError(_) => 3, + RPCErrorResponse::Unknown(_) => 255, + } } + + /// Tells the codec whether to decode as an RPCResponse or an error. + pub fn is_response(response_code:u8) -> bool { + match response_code { + 0 => true, + _ => false, + } + + /// Builds an RPCErrorResponse from a response code and an ErrorMessage + pub fn from_error(response_code:u8, err: ErrorMessage) -> Self { + match response_code { + 2 => RPCErrorResponse::InvalidRequest(err), + 3 => RPCErrorResponse::ServerError(err), + _ => RPCErrorResponse::Unknown(err), + } } #[derive(Encode, Decode)] -struct ErrorResponse { - error_message: String, +struct ErrorMessage { + /// The UTF-8 encoded Error message string. + error_message: Vec, } -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. - pub fn decode( - packet: Vec, - protocol: ProtocolId, - response_code: ResponseCode, - ) -> Result { - match response_code { - ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), - ResponseCode::InvalidRequest => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Invalid Request: {}", - response.error_message - ))) - } - ResponseCode::ServerError => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Remote Server Error: {}", - response.error_message - ))) - } - ResponseCode::Success => match protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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, 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 { - 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(), - } - } -} +// todo: SSZ-Encode +impl RPCResponse {} /* Outbound upgrades */ diff --git a/codec.rs b/codec.rs new file mode 100644 index 000000000..a4e81b5f0 --- /dev/null +++ b/codec.rs @@ -0,0 +1,461 @@ + +///! This handles the various supported encoding mechanism for the Eth 2.0 RPC. +///! +///! Currently supported encodings are: +///! - ssz - Varint length-prefixed SSZ-encoding. + + +pub trait InnerCodec: Encoder + Decoder { + type Error; + + pub fn decode_error(&mut self, &mut BytesMut) -> Result, ::Error>; +} + +pub struct BaseInboundCodec { + /// Inner codec for handling various encodings + inner: TCodec, +} + +impl Encoder for BaseInboundCodec +where + TCodec: Encoder, + ::Item = RPCResponse, +{ + type Item = RPCResponse; + type Error = ::Error; + + fn encode( + &mut self, + item: Self::Item, + dst: &mut BytesMut + ) -> Result<(), Self::Error> { + dst.clear(); + dst.reserve(1); + dst.put_u8(item.as_u8); + return self.inner.encode(); + } +} + + +impl Decoder for BaseInboundCodec +where + TCodec: Decoder, + ::Item: RPCrequest, + ::Error: From, +{ + + type Item = RPCRequest; + type Error = ::Error; + + fn decode( + &mut self, + src: &mut BytesMut + ) -> Result, Self::Error> { + self.inner.decode(src) + } +} + +pub struct BaseOutboundCodec +where + TCodec: InnerCodec, + ::Item = RPCResponse, + ::ErrorItem = ErrorMessage, + { + /// Inner codec for handling various encodings + inner: TCodec, + /// Optimisation for decoding. True if the response code has been read and we are awaiting a + /// response. + response_code: Option, +} + +impl Encoder for BaseOutboundCodec +where + TCodec: Encoder +{ + type Item = RPCRequest; + type Error = ::Error; + + fn encode( + &mut self, + item: Self::Item, + dst: &mut BytesMut + ) -> Result<(), Self::Error> { + self.inner.encode(item, dst) + } +} + + +impl Decoder for BaseOutboundCodec +where + TCodec: InnerCodec, + ::Error: From, +{ + + type Item = RPCResponse; + type Error = ::Error; + + fn decode( + &mut self, + src: &mut BytesMut + ) -> Result, Self::Error> { + + + let response_code = { + if let Some(resp_code) = self.response_code { + resp_code; + } + else { + if src.is_empty() { + return Err(io::Error::new(io::ErrorKind::InvalidData, "no bytes received")); + } + let resp_byte = src.split_to(1); + let resp_code_byte = [0; 1]; + resp_code_byte.copy_from_slice(&resp_byte); + + let resp_code = u8::from_be_bytes(resp_code_byte); + + if let Some(response) = RPCErrorResponse::internal_data(resp_code) { + self.response_code = None; + return response; + } + resp_code + } + }; + + if RPCErrorResponse::is_response(response_code) { + // decode an actual response + return self.inner.decode(src).map(|r| r.map(|resp| RPCErrorResponse::Success(resp))); + } + else { + // decode an error + return self.inner.decode_error(src).map(|r| r.map(|resp| RPCErrorResponse::from_error(response_code, resp))); + } + + } + +} + + + +/// SSZ Input stream +pub struct SSZInboundSink { + inner: + protocol: ProtocolId + +impl for SSZInputStream +where + TSocket: AsyncRead + AsyncWrite +{ + + /// Set up the initial input stream object. + pub fn new(incomming: TSocket, protocol: ProtocolId, max_size: usize) -> Self { + + // this type of stream should only apply to ssz protocols + debug_assert!(protocol.encoding.as_str() == "ssz"); + + let mut uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_size); + + let inner = Framed::new(incomming, uvi_codec).from_err() + .with(|response| { + self.encode(response) + }) + .and_then(|bytes| { + self.decode(request) + }).into_future(); + + //TODO: add timeout + + SSZInputStream { + inner, + protocol + } + } + + /// Decodes an SSZ-encoded RPCRequest. + fn decode(&self, request: RPCRequest) { + + match self.protocol.message_name.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)), + _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), + }, + "goodbye" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)), + _ => Err(RPCError::InvalidProtocol( + "Unknown GOODBYE version.as_str()", + )), + }, + "beacon_block_roots" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockRoots( + BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_ROOTS version.", + )), + }, + "beacon_block_headers" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockHeaders( + BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_HEADERS version.", + )), + }, + "beacon_block_bodies" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockBodies( + BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_BODIES version.", + )), + }, + "beacon_chain_state" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconChainState( + BeaconChainStateRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_CHAIN_STATE version.", + )), + }, + } + } + + fn encode(&self, response: RPCResponse) { + + // TODO: Add error code + + match response { + 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(), + } + } + +} + +type SSZInboundOutput = stream::AndThen>>, RPCError>, + RPCResponse, + fn(RPCResponse) -> Result, RPCError>, + Result, RPCError>, + >, + fn(BytesMut) -> Result, + Result + >; + +impl Sink for SSZInputStreamSink { + + type SinkItem = RPCResponse; + type SinkError = RPCError; + + fn start_send( + &mut self, + item: Self::SinkItem +) -> Result, Self::SinkError> { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Result, Self::SinkError> { + self.inner.poll_complete() + } +} + +/* Outbound specific stream */ + +// Implement our own decoder to handle the response byte + +struct SSZOutboundCodec + + + +pub struct SSZOutboundStreamSink { + inner: + protocol: ProtocolId + +impl for SSZOutboundStreamSink +where + TSocket: AsyncRead + AsyncWrite +{ + + /// Set up the initial outbound stream object. + pub fn new(socket: TSocket, protocol: ProtocolId, max_size: usize) -> Self { + + // this type of stream should only apply to ssz protocols + debug_assert!(protocol.encoding.as_str() == "ssz"); + + let mut uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_size); + + let inner = Framed::new(socket, uvi_codec).from_err() + .with(|request| { + self.encode(request) + }) + .and_then(|bytes| { + self.decode(response) + }); + + SSZOutboundStream { + inner, + protocol + } + } + + + + + + /// Decodes a response that was received on the same stream as a request. The response type should + /// therefore match the request protocol type. + pub fn decode(&self, response: Vec, + protocol: ProtocolId, + response_code: ResponseCode, + ) -> Result { + match response_code { + ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), + ResponseCode::InvalidRequest => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Invalid Request: {}", + response.error_message + ))) + } + ResponseCode::ServerError => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Remote Server Error: {}", + response.error_message + ))) + } + ResponseCode::Success => match protocol.message_name.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.", + )), + }, + }, + } + } + + fn encode(&self, response: RPCResponse) { + + match response { + 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(), + } + } + +} + +type SSZOutboundStream = stream::AndThen>>, RPCError>, + RPCResponse, + fn(RPCResponse) -> Result, RPCError>, + Result, RPCError>, + >, + fn(BytesMut) -> Result, + Result + >; + + +impl Stream for SSZInputStreamSink { + + type Item = SSZInboundOutput; + type Error = RPCError; + + fn poll(&mut self) -> Result>, Self::Error> { + self.inner.poll() + } +} + +impl Sink for SSZInputStreamSink { + + type SinkItem = RPCResponse; + type SinkError = RPCError; + + fn start_send( + &mut self, + item: Self::SinkItem +) -> Result, Self::SinkError> { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Result, Self::SinkError> { + self.inner.poll_complete() + } +} + + + + + + + From 15cdd2afb95f985ca73c40813d360e7474b0432f Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 13 Jul 2019 18:56:08 +1000 Subject: [PATCH 32/39] Initial codec module --- .../eth2-libp2p/src/rpc/codecs/base.rs | 501 ++++-------------- beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs | 1 + beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs | 323 +++++++++++ beacon_node/eth2-libp2p/src/rpc/mod.rs | 1 + beacon_node/eth2-libp2p/src/rpc/protocol.rs | 1 + codec.rs | 461 ---------------- 6 files changed, 415 insertions(+), 873 deletions(-) create mode 100644 beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs delete mode 100644 codec.rs diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs b/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs index 5fe1aad8a..6957ad180 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs @@ -1,442 +1,119 @@ +///! This handles the various supported encoding mechanism for the Eth 2.0 RPC. -///! This handles the various supported encoding mechanism for the Eth 2.0 RPC. -///! -///! Currently supported encodings are: -///! - ssz - Varint length-prefixed SSZ-encoding. +pub trait InnerCodec: Encoder + Decoder { + type Error; + fn decode_error( + &mut self, + &mut BytesMut, + ) -> Result, ::Error>; +} -pub struct BaseCodec { +pub struct BaseInboundCodec { + /// Inner codec for handling various encodings + inner: TCodec, +} + +pub struct BaseOutboundCodec +where + TCodec: InnerCodec, + ::Item = RPCResponse, + ::ErrorItem = ErrorMessage, +{ /// Inner codec for handling various encodings inner: TCodec, /// Optimisation for decoding. True if the response code has been read and we are awaiting a /// response. - read_response_code: bool, + response_code: Option, } +impl Encoder for BaseInboundCodec +where + TCodec: Encoder, + ::Item = RPCResponse, +{ + type Item = RPCResponse; + type Error = ::Error; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + dst.clear(); + dst.reserve(1); + dst.put_u8(item.as_u8); + return self.inner.encode(); + } +} + +impl Decoder for BaseInboundCodec +where + TCodec: Decoder, + ::Item: RPCrequest, + ::Error: From, +{ + type Item = RPCRequest; + type Error = ::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + self.inner.decode(src) + } +} impl Encoder for BaseOutboundCodec -where - TCodec: Encoder +where + TCodec: Encoder, { type Item = RPCRequest; type Error = ::Error; - fn encode( - &mut self, - item: Self::Item, - dst: &mut BytesMut - ) -> Result<(), Self::Error> { + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { self.inner.encode(item, dst) } } - -impl Encoder for BaseInboundCodec -where - TCodec: Encoder +impl Decoder for BaseOutboundCodec +where + TCodec: InnerCodec, + ::Error: From, { - type Item = RPCResponse; - type Error = ::Error; - - fn encode( - &mut self, - item: Self::Item, - dst: &mut BytesMut - ) -> Result<(), Self::Error> { - - match item { - RPCResponse::Error(response) => { - match response = { - ErrorResponse::EncodingError => { - dst.clear(); - dst.reserve(1); - dst.put(response as u8); - return; - } - ErrorResponse:: - self.inner.encode(item, dst) - } -} - - -impl Decoder for BaseCodec -where - TCodec: Decoder, - ::Error: From, -{ - type Item = RPCResponse; type Error = ::Error; - fn decode( - &mut self, - src: &mut BytesMut - ) -> Result, Self::Error> { - - if !self.read_response_code { - if src.len() < 1 { - return Err(io::Error::new(io::ErrorKind::InvalidData, "no bytes received")); - } - - let resp_code_byte = [0; 1]; - // data must be only 1-byte - this cannot panic - resp_code_byte.copy_from_slice(&src); - let response_code = - ResponseCode::from(u8::from_be_bytes(resp_code_byte)); - match response_code { - ResponseCode::EncodingError => { - // invalid encoding - let response = RPCResponse::Error("Invalid Encoding".into()); - return Ok(Async::Ready(response)); - } - ResponseCode::Success - | ResponseCode::InvalidRequest - | ResponseCode::ServerError => { - // need to read another packet - self.inner = RPCRequestResponseInner::Read( - read_one(socket, max_size), - response_code, - ) - } - ResponseCode::Unknown => { - // unknown response code - let response = RPCResponse::Error(format!( - "Unknown response code: {}", - (response_code as u8) + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + let response_code = { + if let Some(resp_code) = self.response_code { + resp_code; + } else { + if src.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "no bytes received", )); - return Ok(Async::Ready(response)); + } + let resp_byte = src.split_to(1); + let resp_code_byte = [0; 1]; + resp_code_byte.copy_from_slice(&resp_byte); + let resp_code = u8::from_be_bytes(resp_code_byte); - - - - } - -} - - - - - -/// SSZ Input stream -pub struct SSZInboundSink { - inner: - protocol: ProtocolId - -impl for SSZInputStream -where - TSocket: AsyncRead + AsyncWrite -{ - - /// Set up the initial input stream object. - pub fn new(incomming: TSocket, protocol: ProtocolId, max_size: usize) -> Self { - - // this type of stream should only apply to ssz protocols - debug_assert!(protocol.encoding.as_str() == "ssz"); - - let mut uvi_codec = UviBytes::default(); - uvi_codec.set_max_len(max_size); - - let inner = Framed::new(incomming, uvi_codec).from_err() - .with(|response| { - self.encode(response) - }) - .and_then(|bytes| { - self.decode(request) - }).into_future(); - - //TODO: add timeout - - SSZInputStream { - inner, - protocol - } - } - - /// Decodes an SSZ-encoded RPCRequest. - fn decode(&self, request: RPCRequest) { - - match self.protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)), - _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), - }, - "goodbye" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)), - _ => Err(RPCError::InvalidProtocol( - "Unknown GOODBYE version.as_str()", - )), - }, - "beacon_block_roots" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockRoots( - BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_ROOTS version.", - )), - }, - "beacon_block_headers" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockHeaders( - BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_HEADERS version.", - )), - }, - "beacon_block_bodies" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockBodies( - BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_BODIES version.", - )), - }, - "beacon_chain_state" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconChainState( - BeaconChainStateRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_CHAIN_STATE version.", - )), - }, - } - } - - fn encode(&self, response: RPCResponse) { - - // TODO: Add error code - - match response { - 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(), - } - } - -} - -type SSZInboundOutput = stream::AndThen>>, RPCError>, - RPCResponse, - fn(RPCResponse) -> Result, RPCError>, - Result, RPCError>, - >, - fn(BytesMut) -> Result, - Result - >; - -impl Sink for SSZInputStreamSink { - - type SinkItem = RPCResponse; - type SinkError = RPCError; - - fn start_send( - &mut self, - item: Self::SinkItem -) -> Result, Self::SinkError> { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Result, Self::SinkError> { - self.inner.poll_complete() - } -} - -/* Outbound specific stream */ - -// Implement our own decoder to handle the response byte - -struct SSZOutboundCodec - - - -pub struct SSZOutboundStreamSink { - inner: - protocol: ProtocolId - -impl for SSZOutboundStreamSink -where - TSocket: AsyncRead + AsyncWrite -{ - - /// Set up the initial outbound stream object. - pub fn new(socket: TSocket, protocol: ProtocolId, max_size: usize) -> Self { - - // this type of stream should only apply to ssz protocols - debug_assert!(protocol.encoding.as_str() == "ssz"); - - let mut uvi_codec = UviBytes::default(); - uvi_codec.set_max_len(max_size); - - let inner = Framed::new(socket, uvi_codec).from_err() - .with(|request| { - self.encode(request) - }) - .and_then(|bytes| { - self.decode(response) - }); - - SSZOutboundStream { - inner, - protocol - } - } - - - - - - /// Decodes a response that was received on the same stream as a request. The response type should - /// therefore match the request protocol type. - pub fn decode(&self, response: Vec, - protocol: ProtocolId, - response_code: ResponseCode, - ) -> Result { - match response_code { - ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), - ResponseCode::InvalidRequest => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Invalid Request: {}", - response.error_message - ))) + if let Some(response) = RPCErrorResponse::internal_data(resp_code) { + self.response_code = None; + return response; + } + resp_code } - ResponseCode::ServerError => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Remote Server Error: {}", - response.error_message - ))) - } - ResponseCode::Success => match protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.", - )), - }, - }, + }; + + if RPCErrorResponse::is_response(response_code) { + // decode an actual response + return self + .inner + .decode(src) + .map(|r| r.map(|resp| RPCErrorResponse::Success(resp))); + } else { + // decode an error + return self + .inner + .decode_error(src) + .map(|r| r.map(|resp| RPCErrorResponse::from_error(response_code, resp))); } } - - fn encode(&self, response: RPCResponse) { - - match response { - 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(), - } - } - } - -type SSZOutboundStream = stream::AndThen>>, RPCError>, - RPCResponse, - fn(RPCResponse) -> Result, RPCError>, - Result, RPCError>, - >, - fn(BytesMut) -> Result, - Result - >; - - -impl Stream for SSZInputStreamSink { - - type Item = SSZInboundOutput; - type Error = RPCError; - - fn poll(&mut self) -> Result>, Self::Error> { - self.inner.poll() - } -} - -impl Sink for SSZInputStreamSink { - - type SinkItem = RPCResponse; - type SinkError = RPCError; - - fn start_send( - &mut self, - item: Self::SinkItem -) -> Result, Self::SinkError> { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Result, Self::SinkError> { - self.inner.poll_complete() - } -} - - - - - - - diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs b/beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs new file mode 100644 index 000000000..77ed8456d --- /dev/null +++ b/beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs @@ -0,0 +1 @@ +mod base; diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs b/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs index e69de29bb..fd20ae57e 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs @@ -0,0 +1,323 @@ + +/// SSZ Input stream +pub struct SSZInboundSink { + inner: + protocol: ProtocolId + +impl for SSZInputStream +where + TSocket: AsyncRead + AsyncWrite +{ + + /// Set up the initial input stream object. + pub fn new(incomming: TSocket, protocol: ProtocolId, max_size: usize) -> Self { + + // this type of stream should only apply to ssz protocols + debug_assert!(protocol.encoding.as_str() == "ssz"); + + let mut uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_size); + + let inner = Framed::new(incomming, uvi_codec).from_err() + .with(|response| { + self.encode(response) + }) + .and_then(|bytes| { + self.decode(request) + }).into_future(); + + //TODO: add timeout + + SSZInputStream { + inner, + protocol + } + } + + /// Decodes an SSZ-encoded RPCRequest. + fn decode(&self, request: RPCRequest) { + + match self.protocol.message_name.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)), + _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), + }, + "goodbye" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)), + _ => Err(RPCError::InvalidProtocol( + "Unknown GOODBYE version.as_str()", + )), + }, + "beacon_block_roots" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockRoots( + BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_ROOTS version.", + )), + }, + "beacon_block_headers" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockHeaders( + BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_HEADERS version.", + )), + }, + "beacon_block_bodies" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconBlockBodies( + BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_BODIES version.", + )), + }, + "beacon_chain_state" => match protocol.version.as_str() { + "1.0.0" => Ok(RPCRequest::BeaconChainState( + BeaconChainStateRequest::from_ssz_bytes(&packet)?, + )), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_CHAIN_STATE version.", + )), + }, + } + } + + fn encode(&self, response: RPCResponse) { + + // TODO: Add error code + + match response { + 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(), + } + } + +} + +type SSZInboundOutput = stream::AndThen>>, RPCError>, + RPCResponse, + fn(RPCResponse) -> Result, RPCError>, + Result, RPCError>, + >, + fn(BytesMut) -> Result, + Result + >; + +impl Sink for SSZInputStreamSink { + + type SinkItem = RPCResponse; + type SinkError = RPCError; + + fn start_send( + &mut self, + item: Self::SinkItem +) -> Result, Self::SinkError> { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Result, Self::SinkError> { + self.inner.poll_complete() + } +} + +/* Outbound specific stream */ + +// Implement our own decoder to handle the response byte + +struct SSZOutboundCodec + + + +pub struct SSZOutboundStreamSink { + inner: + protocol: ProtocolId + +impl for SSZOutboundStreamSink +where + TSocket: AsyncRead + AsyncWrite +{ + + /// Set up the initial outbound stream object. + pub fn new(socket: TSocket, protocol: ProtocolId, max_size: usize) -> Self { + + // this type of stream should only apply to ssz protocols + debug_assert!(protocol.encoding.as_str() == "ssz"); + + let mut uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_size); + + let inner = Framed::new(socket, uvi_codec).from_err() + .with(|request| { + self.encode(request) + }) + .and_then(|bytes| { + self.decode(response) + }); + + SSZOutboundStream { + inner, + protocol + } + } + + + + + + /// Decodes a response that was received on the same stream as a request. The response type should + /// therefore match the request protocol type. + pub fn decode(&self, response: Vec, + protocol: ProtocolId, + response_code: ResponseCode, + ) -> Result { + match response_code { + ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), + ResponseCode::InvalidRequest => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Invalid Request: {}", + response.error_message + ))) + } + ResponseCode::ServerError => { + let response = match protocol.encoding.as_str() { + "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, + _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), + }; + Ok(RPCResponse::Error(format!( + "Remote Server Error: {}", + response.error_message + ))) + } + ResponseCode::Success => match protocol.message_name.as_str() { + "hello" => match protocol.version.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.as_str() { + "1.0.0" => 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.", + )), + }, + }, + } + } + + fn encode(&self, response: RPCResponse) { + + match response { + 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(), + } + } + +} + +type SSZOutboundStream = stream::AndThen>>, RPCError>, + RPCResponse, + fn(RPCResponse) -> Result, RPCError>, + Result, RPCError>, + >, + fn(BytesMut) -> Result, + Result + >; + + +impl Stream for SSZInputStreamSink { + + type Item = SSZInboundOutput; + type Error = RPCError; + + fn poll(&mut self) -> Result>, Self::Error> { + self.inner.poll() + } +} + +impl Sink for SSZInputStreamSink { + + type SinkItem = RPCResponse; + type SinkError = RPCError; + + fn start_send( + &mut self, + item: Self::SinkItem +) -> Result, Self::SinkError> { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Result, Self::SinkError> { + self.inner.poll_complete() + } +} + + + + + + + diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index e6ac74d9a..fc7560eff 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -16,6 +16,7 @@ use slog::o; use std::marker::PhantomData; use tokio::io::{AsyncRead, AsyncWrite}; +mod codecs; mod handler; pub mod methods; mod protocol; diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index ce58c5607..bb593f3c1 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -251,6 +251,7 @@ impl RPCErrorResponse { } } + /// Used to encode the response. pub fn as_u8(&self) -> u8 { match self { RPCErrorResponse::Success(_) => 0, diff --git a/codec.rs b/codec.rs deleted file mode 100644 index a4e81b5f0..000000000 --- a/codec.rs +++ /dev/null @@ -1,461 +0,0 @@ - -///! This handles the various supported encoding mechanism for the Eth 2.0 RPC. -///! -///! Currently supported encodings are: -///! - ssz - Varint length-prefixed SSZ-encoding. - - -pub trait InnerCodec: Encoder + Decoder { - type Error; - - pub fn decode_error(&mut self, &mut BytesMut) -> Result, ::Error>; -} - -pub struct BaseInboundCodec { - /// Inner codec for handling various encodings - inner: TCodec, -} - -impl Encoder for BaseInboundCodec -where - TCodec: Encoder, - ::Item = RPCResponse, -{ - type Item = RPCResponse; - type Error = ::Error; - - fn encode( - &mut self, - item: Self::Item, - dst: &mut BytesMut - ) -> Result<(), Self::Error> { - dst.clear(); - dst.reserve(1); - dst.put_u8(item.as_u8); - return self.inner.encode(); - } -} - - -impl Decoder for BaseInboundCodec -where - TCodec: Decoder, - ::Item: RPCrequest, - ::Error: From, -{ - - type Item = RPCRequest; - type Error = ::Error; - - fn decode( - &mut self, - src: &mut BytesMut - ) -> Result, Self::Error> { - self.inner.decode(src) - } -} - -pub struct BaseOutboundCodec -where - TCodec: InnerCodec, - ::Item = RPCResponse, - ::ErrorItem = ErrorMessage, - { - /// Inner codec for handling various encodings - inner: TCodec, - /// Optimisation for decoding. True if the response code has been read and we are awaiting a - /// response. - response_code: Option, -} - -impl Encoder for BaseOutboundCodec -where - TCodec: Encoder -{ - type Item = RPCRequest; - type Error = ::Error; - - fn encode( - &mut self, - item: Self::Item, - dst: &mut BytesMut - ) -> Result<(), Self::Error> { - self.inner.encode(item, dst) - } -} - - -impl Decoder for BaseOutboundCodec -where - TCodec: InnerCodec, - ::Error: From, -{ - - type Item = RPCResponse; - type Error = ::Error; - - fn decode( - &mut self, - src: &mut BytesMut - ) -> Result, Self::Error> { - - - let response_code = { - if let Some(resp_code) = self.response_code { - resp_code; - } - else { - if src.is_empty() { - return Err(io::Error::new(io::ErrorKind::InvalidData, "no bytes received")); - } - let resp_byte = src.split_to(1); - let resp_code_byte = [0; 1]; - resp_code_byte.copy_from_slice(&resp_byte); - - let resp_code = u8::from_be_bytes(resp_code_byte); - - if let Some(response) = RPCErrorResponse::internal_data(resp_code) { - self.response_code = None; - return response; - } - resp_code - } - }; - - if RPCErrorResponse::is_response(response_code) { - // decode an actual response - return self.inner.decode(src).map(|r| r.map(|resp| RPCErrorResponse::Success(resp))); - } - else { - // decode an error - return self.inner.decode_error(src).map(|r| r.map(|resp| RPCErrorResponse::from_error(response_code, resp))); - } - - } - -} - - - -/// SSZ Input stream -pub struct SSZInboundSink { - inner: - protocol: ProtocolId - -impl for SSZInputStream -where - TSocket: AsyncRead + AsyncWrite -{ - - /// Set up the initial input stream object. - pub fn new(incomming: TSocket, protocol: ProtocolId, max_size: usize) -> Self { - - // this type of stream should only apply to ssz protocols - debug_assert!(protocol.encoding.as_str() == "ssz"); - - let mut uvi_codec = UviBytes::default(); - uvi_codec.set_max_len(max_size); - - let inner = Framed::new(incomming, uvi_codec).from_err() - .with(|response| { - self.encode(response) - }) - .and_then(|bytes| { - self.decode(request) - }).into_future(); - - //TODO: add timeout - - SSZInputStream { - inner, - protocol - } - } - - /// Decodes an SSZ-encoded RPCRequest. - fn decode(&self, request: RPCRequest) { - - match self.protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)), - _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), - }, - "goodbye" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)), - _ => Err(RPCError::InvalidProtocol( - "Unknown GOODBYE version.as_str()", - )), - }, - "beacon_block_roots" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockRoots( - BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_ROOTS version.", - )), - }, - "beacon_block_headers" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockHeaders( - BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_HEADERS version.", - )), - }, - "beacon_block_bodies" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockBodies( - BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_BODIES version.", - )), - }, - "beacon_chain_state" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconChainState( - BeaconChainStateRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_CHAIN_STATE version.", - )), - }, - } - } - - fn encode(&self, response: RPCResponse) { - - // TODO: Add error code - - match response { - 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(), - } - } - -} - -type SSZInboundOutput = stream::AndThen>>, RPCError>, - RPCResponse, - fn(RPCResponse) -> Result, RPCError>, - Result, RPCError>, - >, - fn(BytesMut) -> Result, - Result - >; - -impl Sink for SSZInputStreamSink { - - type SinkItem = RPCResponse; - type SinkError = RPCError; - - fn start_send( - &mut self, - item: Self::SinkItem -) -> Result, Self::SinkError> { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Result, Self::SinkError> { - self.inner.poll_complete() - } -} - -/* Outbound specific stream */ - -// Implement our own decoder to handle the response byte - -struct SSZOutboundCodec - - - -pub struct SSZOutboundStreamSink { - inner: - protocol: ProtocolId - -impl for SSZOutboundStreamSink -where - TSocket: AsyncRead + AsyncWrite -{ - - /// Set up the initial outbound stream object. - pub fn new(socket: TSocket, protocol: ProtocolId, max_size: usize) -> Self { - - // this type of stream should only apply to ssz protocols - debug_assert!(protocol.encoding.as_str() == "ssz"); - - let mut uvi_codec = UviBytes::default(); - uvi_codec.set_max_len(max_size); - - let inner = Framed::new(socket, uvi_codec).from_err() - .with(|request| { - self.encode(request) - }) - .and_then(|bytes| { - self.decode(response) - }); - - SSZOutboundStream { - inner, - protocol - } - } - - - - - - /// Decodes a response that was received on the same stream as a request. The response type should - /// therefore match the request protocol type. - pub fn decode(&self, response: Vec, - protocol: ProtocolId, - response_code: ResponseCode, - ) -> Result { - match response_code { - ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), - ResponseCode::InvalidRequest => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Invalid Request: {}", - response.error_message - ))) - } - ResponseCode::ServerError => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Remote Server Error: {}", - response.error_message - ))) - } - ResponseCode::Success => match protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.", - )), - }, - }, - } - } - - fn encode(&self, response: RPCResponse) { - - match response { - 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(), - } - } - -} - -type SSZOutboundStream = stream::AndThen>>, RPCError>, - RPCResponse, - fn(RPCResponse) -> Result, RPCError>, - Result, RPCError>, - >, - fn(BytesMut) -> Result, - Result - >; - - -impl Stream for SSZInputStreamSink { - - type Item = SSZInboundOutput; - type Error = RPCError; - - fn poll(&mut self) -> Result>, Self::Error> { - self.inner.poll() - } -} - -impl Sink for SSZInputStreamSink { - - type SinkItem = RPCResponse; - type SinkError = RPCError; - - fn start_send( - &mut self, - item: Self::SinkItem -) -> Result, Self::SinkError> { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Result, Self::SinkError> { - self.inner.poll_complete() - } -} - - - - - - - From 15c99b5f370232c7d0b7df34280b8eb7038e0b51 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 15 Jul 2019 17:07:23 +1000 Subject: [PATCH 33/39] RPC Update. WIP --- beacon_node/eth2-libp2p/Cargo.toml | 1 + .../src/rpc/{codecs => codec}/base.rs | 89 +++-- beacon_node/eth2-libp2p/src/rpc/codec/mod.rs | 2 + beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs | 239 +++++++++++++ beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs | 1 - beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs | 323 ------------------ beacon_node/eth2-libp2p/src/rpc/methods.rs | 73 ++++ beacon_node/eth2-libp2p/src/rpc/mod.rs | 19 +- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 196 +++++------ 9 files changed, 465 insertions(+), 478 deletions(-) rename beacon_node/eth2-libp2p/src/rpc/{codecs => codec}/base.rs (58%) create mode 100644 beacon_node/eth2-libp2p/src/rpc/codec/mod.rs create mode 100644 beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs delete mode 100644 beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs delete mode 100644 beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index c1059d415..220948cd5 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -26,3 +26,4 @@ tokio-io = "0.1.12" smallvec = "0.6.10" fnv = "1.0.6" unsigned-varint = "0.2.2" +bytes = "0.4.12" diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs b/beacon_node/eth2-libp2p/src/rpc/codec/base.rs similarity index 58% rename from beacon_node/eth2-libp2p/src/rpc/codecs/base.rs rename to beacon_node/eth2-libp2p/src/rpc/codec/base.rs index 6957ad180..466fc5c3d 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codecs/base.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codec/base.rs @@ -1,53 +1,77 @@ -///! This handles the various supported encoding mechanism for the Eth 2.0 RPC. +//! This handles the various supported encoding mechanism for the Eth 2.0 RPC. -pub trait InnerCodec: Encoder + Decoder { - type Error; +use crate::rpc::{ErrorMessage, RPCErrorResponse, RPCRequest, RPCResponse}; +use bytes::BufMut; +use bytes::BytesMut; +use tokio::codec::{Decoder, Encoder}; + +pub(crate) trait OutboundCodec: Encoder + Decoder { + type ErrorType; fn decode_error( &mut self, - &mut BytesMut, - ) -> Result, ::Error>; + src: &mut BytesMut, + ) -> Result, ::Error>; } -pub struct BaseInboundCodec { - /// Inner codec for handling various encodings - inner: TCodec, -} - -pub struct BaseOutboundCodec +pub(crate) struct BaseInboundCodec where - TCodec: InnerCodec, - ::Item = RPCResponse, - ::ErrorItem = ErrorMessage, + TCodec: Encoder + Decoder, { /// Inner codec for handling various encodings inner: TCodec, +} + +impl BaseInboundCodec +where + TCodec: Encoder + Decoder, +{ + pub fn new(codec: TCodec) -> Self { + BaseInboundCodec { inner: codec } + } +} + +pub(crate) struct BaseOutboundCodec +where + TOutboundCodec: OutboundCodec, +{ + /// Inner codec for handling various encodings + inner: TOutboundCodec, /// Optimisation for decoding. True if the response code has been read and we are awaiting a /// response. response_code: Option, } +impl BaseOutboundCodec +where + TOutboundCodec: OutboundCodec, +{ + pub fn new(codec: TOutboundCodec) -> Self { + BaseOutboundCodec { + inner: codec, + response_code: None, + } + } +} + impl Encoder for BaseInboundCodec where - TCodec: Encoder, - ::Item = RPCResponse, + TCodec: Decoder + Encoder, { - type Item = RPCResponse; + type Item = RPCErrorResponse; type Error = ::Error; fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { dst.clear(); dst.reserve(1); - dst.put_u8(item.as_u8); - return self.inner.encode(); + dst.put_u8(item.as_u8()); + return self.inner.encode(item, dst); } } impl Decoder for BaseInboundCodec where - TCodec: Decoder, - ::Item: RPCrequest, - ::Error: From, + TCodec: Encoder + Decoder, { type Item = RPCRequest; type Error = ::Error; @@ -59,7 +83,7 @@ where impl Encoder for BaseOutboundCodec where - TCodec: Encoder, + TCodec: OutboundCodec + Encoder, { type Item = RPCRequest; type Error = ::Error; @@ -71,23 +95,19 @@ where impl Decoder for BaseOutboundCodec where - TCodec: InnerCodec, - ::Error: From, + TCodec: OutboundCodec + Decoder, { - type Item = RPCResponse; + type Item = RPCErrorResponse; type Error = ::Error; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { let response_code = { if let Some(resp_code) = self.response_code { - resp_code; + resp_code } else { - if src.is_empty() { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "no bytes received", - )); - } + // buffer should not be empty + debug_assert!(!src.is_empty()); + let resp_byte = src.split_to(1); let resp_code_byte = [0; 1]; resp_code_byte.copy_from_slice(&resp_byte); @@ -96,8 +116,9 @@ where if let Some(response) = RPCErrorResponse::internal_data(resp_code) { self.response_code = None; - return response; + return Ok(Some(response)); } + self.response_code = Some(resp_code); resp_code } }; diff --git a/beacon_node/eth2-libp2p/src/rpc/codec/mod.rs b/beacon_node/eth2-libp2p/src/rpc/codec/mod.rs new file mode 100644 index 000000000..68dcd4650 --- /dev/null +++ b/beacon_node/eth2-libp2p/src/rpc/codec/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod base; +pub(crate) mod ssz; diff --git a/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs b/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs new file mode 100644 index 000000000..e339007d7 --- /dev/null +++ b/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs @@ -0,0 +1,239 @@ +use crate::rpc::methods::*; +use crate::rpc::{ + codec::base::OutboundCodec, + protocol::{ProtocolId, RPCError}, +}; +use crate::rpc::{ErrorMessage, RPCErrorResponse, RPCRequest, RPCResponse}; +use bytes::{Bytes, BytesMut}; +use ssz::{Decode, Encode}; +use tokio::codec::{Decoder, Encoder}; +use unsigned_varint::codec::UviBytes; + +/* Inbound Codec */ + +pub struct SSZInboundCodec { + inner: UviBytes, + protocol: ProtocolId, +} + +impl SSZInboundCodec { + pub fn new(protocol: ProtocolId, max_packet_size: usize) -> Self { + let uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_packet_size); + + // this encoding only applies to ssz. + debug_assert!(protocol.encoding.as_str() == "ssz"); + + SSZInboundCodec { + inner: uvi_codec, + protocol, + } + } +} + +// Encoder for inbound +impl Encoder for SSZInboundCodec { + type Item = RPCErrorResponse; + type Error = RPCError; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + let bytes = match item { + RPCErrorResponse::Success(resp) => { + match resp { + 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(), + } + } + RPCErrorResponse::EncodingError => vec![], + RPCErrorResponse::InvalidRequest(err) => err.as_ssz_bytes(), + RPCErrorResponse::ServerError(err) => err.as_ssz_bytes(), + RPCErrorResponse::Unknown(err) => err.as_ssz_bytes(), + }; + + if !bytes.is_empty() { + // length-prefix and return + return self + .inner + .encode(Bytes::from(bytes), dst) + .map_err(RPCError::from); + } + Ok(()) + } +} + +// Decoder for inbound +impl Decoder for SSZInboundCodec { + type Item = RPCRequest; + type Error = RPCError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + match self.inner.decode(src).map_err(RPCError::from) { + Ok(Some(packet)) => match self.protocol.message_name.as_str() { + "hello" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCRequest::Hello(HelloMessage::from_ssz_bytes( + &packet, + )?))), + _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), + }, + "goodbye" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?))), + _ => Err(RPCError::InvalidProtocol( + "Unknown GOODBYE version.as_str()", + )), + }, + "beacon_block_roots" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCRequest::BeaconBlockRoots( + BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_ROOTS version.", + )), + }, + "beacon_block_headers" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCRequest::BeaconBlockHeaders( + BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_HEADERS version.", + )), + }, + "beacon_block_bodies" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCRequest::BeaconBlockBodies( + BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_BODIES version.", + )), + }, + "beacon_chain_state" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCRequest::BeaconChainState( + BeaconChainStateRequest::from_ssz_bytes(&packet)?, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_CHAIN_STATE version.", + )), + }, + }, + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} + +/* Outbound Codec */ + +pub struct SSZOutboundCodec { + inner: UviBytes, + protocol: ProtocolId, +} + +impl SSZOutboundCodec { + pub fn new(protocol: ProtocolId, max_packet_size: usize) -> Self { + let uvi_codec = UviBytes::default(); + uvi_codec.set_max_len(max_packet_size); + + // this encoding only applies to ssz. + debug_assert!(protocol.encoding.as_str() == "ssz"); + + SSZOutboundCodec { + inner: uvi_codec, + protocol, + } + } +} + +// Encoder for outbound +impl Encoder for SSZOutboundCodec { + type Item = RPCRequest; + type Error = RPCError; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + let bytes = match item { + RPCRequest::Hello(req) => req.as_ssz_bytes(), + RPCRequest::Goodbye(req) => req.as_ssz_bytes(), + RPCRequest::BeaconBlockRoots(req) => req.as_ssz_bytes(), + RPCRequest::BeaconBlockHeaders(req) => req.as_ssz_bytes(), + RPCRequest::BeaconBlockBodies(req) => req.as_ssz_bytes(), + RPCRequest::BeaconChainState(req) => req.as_ssz_bytes(), + }; + // length-prefix + self.inner + .encode(bytes::Bytes::from(bytes), dst) + .map_err(RPCError::from) + } +} + +// Decoder for outbound +impl Decoder for SSZOutboundCodec { + type Item = RPCResponse; + type Error = RPCError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + match self.inner.decode(src).map_err(RPCError::from) { + Ok(Some(packet)) => match self.protocol.message_name.as_str() { + "hello" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCResponse::Hello(HelloMessage::from_ssz_bytes( + &packet, + )?))), + _ => Err(RPCError::InvalidProtocol("Unknown HELLO version.")), + }, + "goodbye" => Err(RPCError::InvalidProtocol("GOODBYE doesn't have a response")), + "beacon_block_roots" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCResponse::BeaconBlockRoots( + BeaconBlockRootsResponse::from_ssz_bytes(&packet)?, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_ROOTS version.", + )), + }, + "beacon_block_headers" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCResponse::BeaconBlockHeaders( + BeaconBlockHeadersResponse { + headers: packet.to_vec(), + }, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_HEADERS version.", + )), + }, + "beacon_block_bodies" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCResponse::BeaconBlockBodies( + BeaconBlockBodiesResponse { + block_bodies: packet.to_vec(), + }, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_BLOCK_BODIES version.", + )), + }, + "beacon_chain_state" => match self.protocol.version.as_str() { + "1.0.0" => Ok(Some(RPCResponse::BeaconChainState( + BeaconChainStateResponse::from_ssz_bytes(&packet)?, + ))), + _ => Err(RPCError::InvalidProtocol( + "Unknown BEACON_CHAIN_STATE version.", + )), + }, + _ => Err(RPCError::InvalidProtocol("Unknown method")), + }, + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} + +impl OutboundCodec for SSZOutboundCodec { + type ErrorType = ErrorMessage; + + fn decode_error(&mut self, src: &mut BytesMut) -> Result, RPCError> { + match self.inner.decode(src).map_err(RPCError::from) { + Ok(Some(packet)) => Ok(Some(ErrorMessage::from_ssz_bytes(&packet)?)), + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs b/beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs deleted file mode 100644 index 77ed8456d..000000000 --- a/beacon_node/eth2-libp2p/src/rpc/codecs/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod base; diff --git a/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs b/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs deleted file mode 100644 index fd20ae57e..000000000 --- a/beacon_node/eth2-libp2p/src/rpc/codecs/ssz.rs +++ /dev/null @@ -1,323 +0,0 @@ - -/// SSZ Input stream -pub struct SSZInboundSink { - inner: - protocol: ProtocolId - -impl for SSZInputStream -where - TSocket: AsyncRead + AsyncWrite -{ - - /// Set up the initial input stream object. - pub fn new(incomming: TSocket, protocol: ProtocolId, max_size: usize) -> Self { - - // this type of stream should only apply to ssz protocols - debug_assert!(protocol.encoding.as_str() == "ssz"); - - let mut uvi_codec = UviBytes::default(); - uvi_codec.set_max_len(max_size); - - let inner = Framed::new(incomming, uvi_codec).from_err() - .with(|response| { - self.encode(response) - }) - .and_then(|bytes| { - self.decode(request) - }).into_future(); - - //TODO: add timeout - - SSZInputStream { - inner, - protocol - } - } - - /// Decodes an SSZ-encoded RPCRequest. - fn decode(&self, request: RPCRequest) { - - match self.protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::Hello(HelloMessage::from_ssz_bytes(&packet)?)), - _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), - }, - "goodbye" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?)), - _ => Err(RPCError::InvalidProtocol( - "Unknown GOODBYE version.as_str()", - )), - }, - "beacon_block_roots" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockRoots( - BeaconBlockRootsRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_ROOTS version.", - )), - }, - "beacon_block_headers" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockHeaders( - BeaconBlockHeadersRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_HEADERS version.", - )), - }, - "beacon_block_bodies" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconBlockBodies( - BeaconBlockBodiesRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_BLOCK_BODIES version.", - )), - }, - "beacon_chain_state" => match protocol.version.as_str() { - "1.0.0" => Ok(RPCRequest::BeaconChainState( - BeaconChainStateRequest::from_ssz_bytes(&packet)?, - )), - _ => Err(RPCError::InvalidProtocol( - "Unknown BEACON_CHAIN_STATE version.", - )), - }, - } - } - - fn encode(&self, response: RPCResponse) { - - // TODO: Add error code - - match response { - 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(), - } - } - -} - -type SSZInboundOutput = stream::AndThen>>, RPCError>, - RPCResponse, - fn(RPCResponse) -> Result, RPCError>, - Result, RPCError>, - >, - fn(BytesMut) -> Result, - Result - >; - -impl Sink for SSZInputStreamSink { - - type SinkItem = RPCResponse; - type SinkError = RPCError; - - fn start_send( - &mut self, - item: Self::SinkItem -) -> Result, Self::SinkError> { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Result, Self::SinkError> { - self.inner.poll_complete() - } -} - -/* Outbound specific stream */ - -// Implement our own decoder to handle the response byte - -struct SSZOutboundCodec - - - -pub struct SSZOutboundStreamSink { - inner: - protocol: ProtocolId - -impl for SSZOutboundStreamSink -where - TSocket: AsyncRead + AsyncWrite -{ - - /// Set up the initial outbound stream object. - pub fn new(socket: TSocket, protocol: ProtocolId, max_size: usize) -> Self { - - // this type of stream should only apply to ssz protocols - debug_assert!(protocol.encoding.as_str() == "ssz"); - - let mut uvi_codec = UviBytes::default(); - uvi_codec.set_max_len(max_size); - - let inner = Framed::new(socket, uvi_codec).from_err() - .with(|request| { - self.encode(request) - }) - .and_then(|bytes| { - self.decode(response) - }); - - SSZOutboundStream { - inner, - protocol - } - } - - - - - - /// Decodes a response that was received on the same stream as a request. The response type should - /// therefore match the request protocol type. - pub fn decode(&self, response: Vec, - protocol: ProtocolId, - response_code: ResponseCode, - ) -> Result { - match response_code { - ResponseCode::EncodingError => Ok(RPCResponse::Error("Encoding error".into())), - ResponseCode::InvalidRequest => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Invalid Request: {}", - response.error_message - ))) - } - ResponseCode::ServerError => { - let response = match protocol.encoding.as_str() { - "ssz" => ErrorResponse::from_ssz_bytes(&packet)?, - _ => return Err(RPCError::InvalidProtocol("Unknown Encoding")), - }; - Ok(RPCResponse::Error(format!( - "Remote Server Error: {}", - response.error_message - ))) - } - ResponseCode::Success => match protocol.message_name.as_str() { - "hello" => match protocol.version.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.as_str() { - "1.0.0" => 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.", - )), - }, - }, - } - } - - fn encode(&self, response: RPCResponse) { - - match response { - 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(), - } - } - -} - -type SSZOutboundStream = stream::AndThen>>, RPCError>, - RPCResponse, - fn(RPCResponse) -> Result, RPCError>, - Result, RPCError>, - >, - fn(BytesMut) -> Result, - Result - >; - - -impl Stream for SSZInputStreamSink { - - type Item = SSZInboundOutput; - type Error = RPCError; - - fn poll(&mut self) -> Result>, Self::Error> { - self.inner.poll() - } -} - -impl Sink for SSZInputStreamSink { - - type SinkItem = RPCResponse; - type SinkError = RPCError; - - fn start_send( - &mut self, - item: Self::SinkItem -) -> Result, Self::SinkError> { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Result, Self::SinkError> { - self.inner.poll_complete() - } -} - - - - - - - diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index 066da1351..9e9087f9e 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -162,3 +162,76 @@ pub struct BeaconChainStateResponse { /// The values corresponding the to the requested tree hashes. pub values: bool, //TBD - stubbed with encodable bool } + +/* RPC Handling and Grouping */ +// Collection of enums and structs used by the Codecs to encode/decode RPC messages + +#[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), +} + +pub enum RPCErrorResponse { + Success(RPCResponse), + EncodingError, + InvalidRequest(ErrorMessage), + ServerError(ErrorMessage), + Unknown(ErrorMessage), +} + +impl RPCErrorResponse { + /// If a response has no payload, returns the variant corresponding to the code. + pub fn internal_data(response_code: u8) -> Option { + match response_code { + // EncodingError + 1 => Some(RPCErrorResponse::EncodingError), + // All others require further data + _ => None, + } + } + + /// Used to encode the response. + pub fn as_u8(&self) -> u8 { + match self { + RPCErrorResponse::Success(_) => 0, + RPCErrorResponse::EncodingError => 1, + RPCErrorResponse::InvalidRequest(_) => 2, + RPCErrorResponse::ServerError(_) => 3, + RPCErrorResponse::Unknown(_) => 255, + } + } + + /// Tells the codec whether to decode as an RPCResponse or an error. + pub fn is_response(response_code: u8) -> bool { + match response_code { + 0 => true, + _ => false, + } + } + + /// Builds an RPCErrorResponse from a response code and an ErrorMessage + pub fn from_error(response_code: u8, err: ErrorMessage) -> Self { + match response_code { + 2 => RPCErrorResponse::InvalidRequest(err), + 3 => RPCErrorResponse::ServerError(err), + _ => RPCErrorResponse::Unknown(err), + } + } +} + +#[derive(Encode, Decode)] +pub struct ErrorMessage { + /// The UTF-8 encoded Error message string. + error_message: Vec, +} diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index fc7560eff..ab47b4362 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -1,8 +1,9 @@ -///! 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. +//! 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 handler::RPCHandler; use libp2p::core::protocols_handler::ProtocolsHandler; @@ -10,17 +11,17 @@ use libp2p::core::swarm::{ ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; use libp2p::{Multiaddr, PeerId}; -pub use methods::HelloMessage; -pub use protocol::{RPCProtocol, RPCRequest, RPCResponse}; +pub use methods::{ErrorMessage, HelloMessage, RPCErrorResponse, RPCResponse}; +pub use protocol::{RPCProtocol, RPCRequest}; use slog::o; use std::marker::PhantomData; use tokio::io::{AsyncRead, AsyncWrite}; -mod codecs; +pub(crate) mod codec; mod handler; pub mod methods; mod protocol; -mod request_response; +// mod request_response; /// The return type used in the behaviour and the resultant event from the protocols handler. #[derive(Debug, Clone)] diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index bb593f3c1..af94e476d 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -1,13 +1,18 @@ use super::methods::*; -use super::request_response::{rpc_request_response, RPCRequestResponse}; +use crate::rpc::codec::{ + base::{BaseInboundCodec, BaseOutboundCodec}, + ssz::{SSZInboundCodec, SSZOutboundCodec}, +}; use futures::future::Future; use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use ssz::{Decode, Encode}; +use ssz::Encode; use ssz_derive::{Decode, Encode}; use std::io; use std::time::Duration; +use tokio::codec::Framed; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::prelude::future::MapErr; +use tokio::prelude::*; use tokio::util::FutureExt; /// The maximum bytes that can be sent across the RPC. @@ -17,7 +22,7 @@ const PROTOCOL_PREFIX: &str = "/eth/serenity/rpc/"; /// The number of seconds to wait for a response before the stream is terminated. const RESPONSE_TIMEOUT: u64 = 10; -/// Implementation of the `ConnectionUpgrade` for the rpc protocol. +/// Implementation of the `ConnectionUpgrade` for the RPC protocol. #[derive(Debug, Clone)] pub struct RPCProtocol; @@ -96,6 +101,10 @@ impl Into for ProtocolId { // The inbound protocol reads the request, decodes it and returns the stream to the protocol // handler to respond to once ready. +enum InboundCodec { + SSZ(BaseInboundCodec), +} + type FnDecodeRPCEvent = fn( upgrade::Negotiated, @@ -107,8 +116,11 @@ impl InboundUpgrade for RPCProtocol where TSocket: AsyncRead + AsyncWrite, { - type Output = (upgrade::Negotiated, RPCRequest, ProtocolId); + type Output = RPCRequest; type Error = RPCError; + + type Future = Box>; + /* type Future = MapErr< tokio_timer::Timeout< upgrade::ReadRespond< @@ -119,25 +131,48 @@ where >, fn(tokio::timer::timeout::Error) -> RPCError, >; + */ fn upgrade_inbound( self, socket: upgrade::Negotiated, 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, - )) + let protocol_id = match ProtocolId::from_bytes(protocol) { + Ok(v) => v, + Err(e) => return Box::new(futures::future::err(e)), + }; + + match protocol_id.encoding.as_str() { + "ssz" | _ => { + let codec = BaseInboundCodec::new(SSZInboundCodec::new(protocol_id, 4096)); + Box::new( + Framed::new(socket, codec) + .into_future() + .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) + .map_err(RPCError::from) + .and_then(|(madouby, _)| match madouby { + Some(x) => futures::future::ok(x), + None => futures::future::err(RPCError::Custom("Go home".into())), + }), + ) } } - as FnDecodeRPCEvent) - .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) - .map_err(RPCError::from) + + /* + 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) + } + */ } } @@ -214,78 +249,7 @@ impl RPCRequest { } } -/* 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), -} - -pub enum RPCErrorResponse { - Success(RPCResponse), - EncodingError, - InvalidRequest(ErrorMessage), - ServerError(ErrorMessage), - Unknown(ErrorMessage), -} - -impl RPCErrorResponse { - /// If a response has no payload, returns the variant corresponding to the code. - pub fn internal_data(response_code: u8) -> Option { - match response_code { - // EncodingError - 1 => Some(RPCErrorResponse::EncodingError), - // All others require further data - _ => None, - } - } - - /// Used to encode the response. - pub fn as_u8(&self) -> u8 { - match self { - RPCErrorResponse::Success(_) => 0, - RPCErrorResponse::EncodingError => 1, - RPCErrorResponse::InvalidRequest(_) => 2, - RPCErrorResponse::ServerError(_) => 3, - RPCErrorResponse::Unknown(_) => 255, - } - } - - /// Tells the codec whether to decode as an RPCResponse or an error. - pub fn is_response(response_code:u8) -> bool { - match response_code { - 0 => true, - _ => false, - } - - /// Builds an RPCErrorResponse from a response code and an ErrorMessage - pub fn from_error(response_code:u8, err: ErrorMessage) -> Self { - match response_code { - 2 => RPCErrorResponse::InvalidRequest(err), - 3 => RPCErrorResponse::ServerError(err), - _ => RPCErrorResponse::Unknown(err), - } -} - -#[derive(Encode, Decode)] -struct ErrorMessage { - /// The UTF-8 encoded Error message string. - error_message: Vec, -} - -// todo: SSZ-Encode -impl RPCResponse {} +/* RPC Response type - used for outbound upgrades */ /* Outbound upgrades */ @@ -295,37 +259,47 @@ where { type Output = RPCResponse; type Error = RPCError; - type Future = MapErr< - tokio_timer::Timeout, Vec>>, - fn(tokio::timer::timeout::Error) -> RPCError, - >; - + type Future = Box>; fn upgrade_outbound( self, socket: upgrade::Negotiated, protocol: Self::Info, ) -> Self::Future { - let protocol_id = ProtocolId::from_bytes(&protocol) - .expect("Protocol ID must be valid for outbound requests"); + panic!() - 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 { - None + /* + let protocol_id = match ProtocolId::from_bytes(&protocol) { + Ok(v) => v, + Err(e) => return futures::future::err(e), }; - rpc_request_response( - socket, - request_bytes, - MAX_RPC_SIZE, - short_circuit_return, - protocol_id, - ) - .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) - .map_err(RPCError::from) + + // select which codec to use + let inbound_stream = match protocol_id.encoding.as_str() { + "ssz" => { + let codec = BaseInboundCodec::new(SSZCodec::new()); + Framed::new(socket, codec).send(self) + } + _ => futures::future::err(RPCError::InvalidProtocol("Unsupported encoding")), + }; + + // do not wait for a timeout if we send a GOODBYE request + match protocol_id.message_name.as_str() { + // goodbye messages do not have a response + "goodbye" => inbound_stream.and_then(|| { + RPCErrorResponse::Unknown(ErrorMessage { + error_message: String::from("goodbye response").as_bytes(), + }) + }), + // get a response for all other requests + _ => inbound_stream.and_then(|stream| { + stream + .into_future() + .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) + .map(|resp, _| resp) + .map_err(RPCError::from) + }), + } + */ } } From 704263e35f500cd08655150951fd017b7c8a9f8e Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 15 Jul 2019 18:41:05 +1000 Subject: [PATCH 34/39] Clean up Protocol types --- beacon_node/eth2-libp2p/src/rpc/codec/mod.rs | 60 +++++++++ beacon_node/eth2-libp2p/src/rpc/protocol.rs | 122 ++++++------------- 2 files changed, 99 insertions(+), 83 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/codec/mod.rs b/beacon_node/eth2-libp2p/src/rpc/codec/mod.rs index 68dcd4650..d0d1b650b 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codec/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codec/mod.rs @@ -1,2 +1,62 @@ pub(crate) mod base; pub(crate) mod ssz; + +use self::base::{BaseInboundCodec, BaseOutboundCodec}; +use self::ssz::{SSZInboundCodec, SSZOutboundCodec}; +use crate::rpc::protocol::RPCError; +use crate::rpc::{RPCErrorResponse, RPCRequest}; +use bytes::BytesMut; +use tokio::codec::{Decoder, Encoder}; + +// Known types of codecs +pub enum InboundCodec { + SSZ(BaseInboundCodec), +} + +pub enum OutboundCodec { + SSZ(BaseOutboundCodec), +} + +impl Encoder for InboundCodec { + type Item = RPCErrorResponse; + type Error = RPCError; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + match self { + InboundCodec::SSZ(codec) => codec.encode(item, dst), + } + } +} + +impl Decoder for InboundCodec { + type Item = RPCRequest; + type Error = RPCError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + match self { + InboundCodec::SSZ(codec) => codec.decode(src), + } + } +} + +impl Encoder for OutboundCodec { + type Item = RPCRequest; + type Error = RPCError; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + match self { + OutboundCodec::SSZ(codec) => codec.encode(item, dst), + } + } +} + +impl Decoder for OutboundCodec { + type Item = RPCErrorResponse; + type Error = RPCError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + match self { + OutboundCodec::SSZ(codec) => codec.decode(src), + } + } +} diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index af94e476d..7d24105f5 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -2,8 +2,12 @@ use super::methods::*; use crate::rpc::codec::{ base::{BaseInboundCodec, BaseOutboundCodec}, ssz::{SSZInboundCodec, SSZOutboundCodec}, + InboundCodec, OutboundCodec, +}; +use futures::{ + future::{self, FutureResult}, + sink, stream, Sink, Stream, }; -use futures::future::Future; use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use ssz::Encode; use ssz_derive::{Decode, Encode}; @@ -13,6 +17,7 @@ use tokio::codec::Framed; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::prelude::future::MapErr; use tokio::prelude::*; +use tokio::timer::timeout; use tokio::util::FutureExt; /// The maximum bytes that can be sent across the RPC. @@ -101,16 +106,10 @@ impl Into for ProtocolId { // The inbound protocol reads the request, decodes it and returns the stream to the protocol // handler to respond to once ready. -enum InboundCodec { - SSZ(BaseInboundCodec), -} - -type FnDecodeRPCEvent = - fn( - upgrade::Negotiated, - Vec, - &'static [u8], // protocol id - ) -> Result<(upgrade::Negotiated, RPCRequest, ProtocolId), RPCError>; +type InboundFramed = Framed, InboundCodec>; +type FnAndThen = + fn((Option, InboundFramed)) -> FutureResult; +type FnMapErr = fn(timeout::Error<(RPCError, InboundFramed)>) -> RPCError; impl InboundUpgrade for RPCProtocol where @@ -119,60 +118,40 @@ where type Output = RPCRequest; type Error = RPCError; - type Future = Box>; - /* - type Future = MapErr< - tokio_timer::Timeout< - upgrade::ReadRespond< - upgrade::Negotiated, - Self::Info, - FnDecodeRPCEvent, - >, + type Future = future::AndThen< + future::MapErr< + timeout::Timeout>>, + FnMapErr, >, - fn(tokio::timer::timeout::Error) -> RPCError, + FutureResult, + FnAndThen, >; - */ fn upgrade_inbound( self, socket: upgrade::Negotiated, protocol: &'static [u8], ) -> Self::Future { - let protocol_id = match ProtocolId::from_bytes(protocol) { - Ok(v) => v, - Err(e) => return Box::new(futures::future::err(e)), - }; + // TODO: Verify this + let protocol_id = + ProtocolId::from_bytes(protocol).expect("Can decode all supported protocols"); match protocol_id.encoding.as_str() { "ssz" | _ => { - let codec = BaseInboundCodec::new(SSZInboundCodec::new(protocol_id, 4096)); - Box::new( - Framed::new(socket, codec) - .into_future() - .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) - .map_err(RPCError::from) - .and_then(|(madouby, _)| match madouby { + let ssz_codec = BaseInboundCodec::new(SSZInboundCodec::new(protocol_id, 4096)); + let codec = InboundCodec::SSZ(ssz_codec); + Framed::new(socket, codec) + .into_future() + .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) + .map_err(RPCError::from as FnMapErr) + .and_then({ + |(madouby, _)| match madouby { Some(x) => futures::future::ok(x), None => futures::future::err(RPCError::Custom("Go home".into())), - }), - ) + } + } as FnAndThen) } } - - /* - 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) - } - */ } } @@ -253,53 +232,30 @@ impl RPCRequest { /* Outbound upgrades */ +type OutboundFramed = Framed, OutboundCodec>; + impl OutboundUpgrade for RPCRequest where TSocket: AsyncRead + AsyncWrite, { - type Output = RPCResponse; + type Output = OutboundFramed; type Error = RPCError; - type Future = Box>; + type Future = sink::Send>; fn upgrade_outbound( self, socket: upgrade::Negotiated, protocol: Self::Info, ) -> Self::Future { - panic!() + let protocol_id = + ProtocolId::from_bytes(&protocol).expect("Can decode all supported protocols"); - /* - let protocol_id = match ProtocolId::from_bytes(&protocol) { - Ok(v) => v, - Err(e) => return futures::future::err(e), - }; - - // select which codec to use - let inbound_stream = match protocol_id.encoding.as_str() { - "ssz" => { - let codec = BaseInboundCodec::new(SSZCodec::new()); + match protocol_id.encoding.as_str() { + "ssz" | _ => { + let ssz_codec = BaseOutboundCodec::new(SSZOutboundCodec::new(protocol_id, 4096)); + let codec = OutboundCodec::SSZ(ssz_codec); Framed::new(socket, codec).send(self) } - _ => futures::future::err(RPCError::InvalidProtocol("Unsupported encoding")), - }; - - // do not wait for a timeout if we send a GOODBYE request - match protocol_id.message_name.as_str() { - // goodbye messages do not have a response - "goodbye" => inbound_stream.and_then(|| { - RPCErrorResponse::Unknown(ErrorMessage { - error_message: String::from("goodbye response").as_bytes(), - }) - }), - // get a response for all other requests - _ => inbound_stream.and_then(|stream| { - stream - .into_future() - .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) - .map(|resp, _| resp) - .map_err(RPCError::from) - }), } - */ } } From 414d41cb57e745e10f97215b3396f02258604a9c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 16 Jul 2019 22:32:37 +1000 Subject: [PATCH 35/39] Shift changes into message handler and simple sync for rpc-rewrite --- beacon_node/eth2-libp2p/src/behaviour.rs | 4 + beacon_node/eth2-libp2p/src/rpc/codec/base.rs | 8 +- beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs | 12 +- beacon_node/eth2-libp2p/src/rpc/handler.rs | 259 ++++++++++++------ beacon_node/eth2-libp2p/src/rpc/methods.rs | 62 +++-- beacon_node/eth2-libp2p/src/rpc/mod.rs | 30 +- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 86 +++--- beacon_node/eth2-libp2p/src/service.rs | 5 + beacon_node/network/src/message_handler.rs | 193 ++++++++----- beacon_node/network/src/service.rs | 14 +- beacon_node/network/src/sync/simple_sync.rs | 40 ++- 11 files changed, 486 insertions(+), 227 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 542060014..1bff58ecd 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -109,6 +109,9 @@ impl NetworkBehaviourEventProcess { self.events.push(BehaviourEvent::PeerDialed(peer_id)) } + RPCMessage::PeerDisconnected(peer_id) => { + self.events.push(BehaviourEvent::PeerDisconnected(peer_id)) + } RPCMessage::RPC(peer_id, rpc_event) => { self.events.push(BehaviourEvent::RPC(peer_id, rpc_event)) } @@ -174,6 +177,7 @@ impl Behaviour { pub enum BehaviourEvent { RPC(PeerId, RPCEvent), PeerDialed(PeerId), + PeerDisconnected(PeerId), GossipMessage { source: PeerId, topics: Vec, diff --git a/beacon_node/eth2-libp2p/src/rpc/codec/base.rs b/beacon_node/eth2-libp2p/src/rpc/codec/base.rs index 466fc5c3d..f2f4c085b 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codec/base.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codec/base.rs @@ -5,7 +5,7 @@ use bytes::BufMut; use bytes::BytesMut; use tokio::codec::{Decoder, Encoder}; -pub(crate) trait OutboundCodec: Encoder + Decoder { +pub trait OutboundCodec: Encoder + Decoder { type ErrorType; fn decode_error( @@ -14,7 +14,7 @@ pub(crate) trait OutboundCodec: Encoder + Decoder { ) -> Result, ::Error>; } -pub(crate) struct BaseInboundCodec +pub struct BaseInboundCodec where TCodec: Encoder + Decoder, { @@ -31,7 +31,7 @@ where } } -pub(crate) struct BaseOutboundCodec +pub struct BaseOutboundCodec where TOutboundCodec: OutboundCodec, { @@ -109,7 +109,7 @@ where debug_assert!(!src.is_empty()); let resp_byte = src.split_to(1); - let resp_code_byte = [0; 1]; + let mut resp_code_byte = [0; 1]; resp_code_byte.copy_from_slice(&resp_byte); let resp_code = u8::from_be_bytes(resp_code_byte); diff --git a/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs b/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs index e339007d7..a78ac8f7f 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs @@ -18,7 +18,7 @@ pub struct SSZInboundCodec { impl SSZInboundCodec { pub fn new(protocol: ProtocolId, max_packet_size: usize) -> Self { - let uvi_codec = UviBytes::default(); + let mut uvi_codec = UviBytes::default(); uvi_codec.set_max_len(max_packet_size); // this encoding only applies to ssz. @@ -41,7 +41,6 @@ impl Encoder for SSZInboundCodec { RPCErrorResponse::Success(resp) => { match resp { 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 @@ -80,7 +79,9 @@ impl Decoder for SSZInboundCodec { _ => Err(RPCError::InvalidProtocol("Unknown HELLO version")), }, "goodbye" => match self.protocol.version.as_str() { - "1.0.0" => Ok(Some(RPCRequest::Goodbye(Goodbye::from_ssz_bytes(&packet)?))), + "1.0.0" => Ok(Some(RPCRequest::Goodbye(GoodbyeReason::from_ssz_bytes( + &packet, + )?))), _ => Err(RPCError::InvalidProtocol( "Unknown GOODBYE version.as_str()", )), @@ -117,6 +118,7 @@ impl Decoder for SSZInboundCodec { "Unknown BEACON_CHAIN_STATE version.", )), }, + _ => Err(RPCError::InvalidProtocol("Unknown message name.")), }, Ok(None) => Ok(None), Err(e) => Err(e), @@ -133,7 +135,7 @@ pub struct SSZOutboundCodec { impl SSZOutboundCodec { pub fn new(protocol: ProtocolId, max_packet_size: usize) -> Self { - let uvi_codec = UviBytes::default(); + let mut uvi_codec = UviBytes::default(); uvi_codec.set_max_len(max_packet_size); // this encoding only applies to ssz. @@ -204,6 +206,8 @@ impl Decoder for SSZOutboundCodec { "1.0.0" => Ok(Some(RPCResponse::BeaconBlockBodies( BeaconBlockBodiesResponse { block_bodies: packet.to_vec(), + // this gets filled in the protocol handler + block_roots: None, }, ))), _ => Err(RPCError::InvalidProtocol( diff --git a/beacon_node/eth2-libp2p/src/rpc/handler.rs b/beacon_node/eth2-libp2p/src/rpc/handler.rs index 02b82adea..df8769122 100644 --- a/beacon_node/eth2-libp2p/src/rpc/handler.rs +++ b/beacon_node/eth2-libp2p/src/rpc/handler.rs @@ -1,11 +1,13 @@ -use super::protocol::{ProtocolId, RPCError, RPCProtocol, RPCRequest}; +use super::methods::{RPCErrorResponse, RPCResponse, RequestId}; +use super::protocol::{RPCError, RPCProtocol, RPCRequest}; use super::RPCEvent; +use crate::rpc::protocol::{InboundFramed, OutboundFramed}; 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 libp2p::core::upgrade::{InboundUpgrade, OutboundUpgrade}; use smallvec::SmallVec; use std::time::{Duration, Instant}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -14,7 +16,10 @@ use tokio_io::{AsyncRead, AsyncWrite}; pub const RESPONSE_TIMEOUT: u64 = 9; /// Implementation of `ProtocolsHandler` for the RPC protocol. -pub struct RPCHandler { +pub struct RPCHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ /// The upgrade for inbound substreams. listen_protocol: SubstreamProtocol, @@ -25,16 +30,19 @@ pub struct RPCHandler { events_out: SmallVec<[RPCEvent; 4]>, /// Queue of outbound substreams to open. - dial_queue: SmallVec<[(usize, RPCRequest); 4]>, + dial_queue: SmallVec<[RPCEvent; 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>, + waiting_substreams: FnvHashMap>, + + /// List of outbound substreams that need to be driven to completion. + substreams: Vec>, /// Sequential Id for waiting substreams. - current_substream_id: usize, + current_substream_id: RequestId, /// Maximum number of concurrent outbound substreams being opened. Value is never modified. max_dial_negotiated: u32, @@ -46,22 +54,40 @@ pub struct RPCHandler { inactive_timeout: Duration, } -/// State of an outbound substream. Either waiting for a response, or in the process of sending. -pub enum SubstreamState { - /// An outbound substream is waiting a response from the user. - WaitingResponse { - /// The negotiated substream. - substream: upgrade::Negotiated, - /// 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. - PendingWrite(WriteOne, Vec>), +/// An outbound substream is waiting a response from the user. +struct WaitingResponse { + /// The framed negotiated substream. + substream: InboundFramed, + /// The time when the substream is closed. + timeout: Instant, } -impl RPCHandler { +/// State of an outbound substream. Either waiting for a response, or in the process of sending. +pub enum SubstreamState +where + TSubstream: AsyncRead + AsyncWrite, +{ + /// A response has been sent, pending writing and flush. + ResponsePendingSend { + substream: futures::sink::Send>, + }, + /// A request has been sent, and we are awaiting a response. This future is driven in the + /// handler because GOODBYE requests can be handled and responses dropped instantly. + RequestPendingResponse { + /// The framed negotiated substream. + substream: OutboundFramed, + /// Keeps track of the request id and the request to permit forming advanced responses which require + /// data from the request. + rpc_event: RPCEvent, + /// The time when the substream is closed. + timeout: Instant, + }, +} + +impl RPCHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ pub fn new( listen_protocol: SubstreamProtocol, inactive_timeout: Duration, @@ -73,7 +99,8 @@ impl RPCHandler { dial_queue: SmallVec::new(), dial_negotiated: 0, waiting_substreams: FnvHashMap::default(), - current_substream_id: 0, + substreams: Vec::new(), + current_substream_id: 1, max_dial_negotiated: 8, keep_alive: KeepAlive::Yes, inactive_timeout, @@ -101,15 +128,19 @@ impl RPCHandler { &mut self.listen_protocol } - /// Opens an outbound substream with `upgrade`. + /// Opens an outbound substream with a request. #[inline] - pub fn send_request(&mut self, request_id: usize, upgrade: RPCRequest) { + pub fn send_request(&mut self, rpc_event: RPCEvent) { self.keep_alive = KeepAlive::Yes; - self.dial_queue.push((request_id, upgrade)); + + self.dial_queue.push(rpc_event); } } -impl Default for RPCHandler { +impl Default for RPCHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ fn default() -> Self { RPCHandler::new(SubstreamProtocol::new(RPCProtocol), Duration::from_secs(30)) } @@ -125,7 +156,7 @@ where type Substream = TSubstream; type InboundProtocol = RPCProtocol; type OutboundProtocol = RPCRequest; - type OutboundOpenInfo = usize; // request_id + type OutboundOpenInfo = RPCEvent; // Keep track of the id and the request #[inline] fn listen_protocol(&self) -> SubstreamProtocol { @@ -137,7 +168,7 @@ where &mut self, out: >::Output, ) { - let (substream, req, negotiated_protocol) = out; + let (req, substream) = 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)); @@ -145,9 +176,8 @@ where } // New inbound request. Store the stream and tag the output. - let awaiting_stream = SubstreamState::WaitingResponse { + let awaiting_stream = WaitingResponse { substream, - negotiated_protocol, timeout: Instant::now() + Duration::from_secs(RESPONSE_TIMEOUT), }; self.waiting_substreams @@ -162,7 +192,7 @@ where fn inject_fully_negotiated_outbound( &mut self, out: >::Output, - request_id: Self::OutboundOpenInfo, + rpc_event: Self::OutboundOpenInfo, ) { self.dial_negotiated -= 1; @@ -175,7 +205,18 @@ where self.keep_alive = KeepAlive::Yes; } - self.events_out.push(RPCEvent::Response(request_id, out)); + // add the stream to substreams if we expect a response, otherwise drop the stream. + if let RPCEvent::Request(id, req) = rpc_event { + if req.expect_response() { + let awaiting_stream = SubstreamState::RequestPendingResponse { + substream: out, + rpc_event: RPCEvent::Request(id, req), + timeout: Instant::now() + Duration::from_secs(RESPONSE_TIMEOUT), + }; + + self.substreams.push(awaiting_stream); + } + } } // Note: If the substream has closed due to inactivity, or the substream is in the @@ -183,25 +224,17 @@ where #[inline] fn inject_event(&mut self, rpc_event: Self::InEvent) { match rpc_event { - RPCEvent::Request(rpc_id, req) => self.send_request(rpc_id, req), + RPCEvent::Request(_, _) => self.send_request(rpc_event), RPCEvent::Response(rpc_id, res) => { // check if the stream matching the response still exists - if let Some(waiting_stream) = self.waiting_substreams.get_mut(&rpc_id) { + if let Some(waiting_stream) = self.waiting_substreams.remove(&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"), - )); - } + self.substreams.push(SubstreamState::ResponsePendingSend { + substream: waiting_stream.substream.send(res), + }); } } + RPCEvent::Error(_, _) => {} } } @@ -213,7 +246,6 @@ where >::Error, >, ) { - dbg!(error); if self.pending_error.is_none() { self.pending_error = Some(error); } @@ -234,30 +266,7 @@ where return Err(err); } - // prioritise sending responses for waiting substreams - self.waiting_substreams.retain(|_k, mut waiting_stream| { - match waiting_stream { - SubstreamState::PendingWrite(write_one) => { - match write_one.poll() { - Ok(Async::Ready(_socket)) => false, - Ok(Async::NotReady) => true, - Err(_e) => { - //TODO: Add logging - // throw away streams that error - false - } - } - } - SubstreamState::WaitingResponse { timeout, .. } => { - if Instant::now() > *timeout { - false - } else { - true - } - } - } - }); - + // return any events that need to be reported if !self.events_out.is_empty() { return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( self.events_out.remove(0), @@ -266,17 +275,85 @@ where self.events_out.shrink_to_fit(); } + // remove any streams that have expired + self.waiting_substreams.retain(|_k, waiting_stream| { + if Instant::now() > waiting_stream.timeout { + false + } else { + true + } + }); + + // drive streams that need to be processed + for n in (0..self.substreams.len()).rev() { + let stream = self.substreams.swap_remove(n); + match stream { + SubstreamState::ResponsePendingSend { mut substream } => { + match substream.poll() { + Ok(Async::Ready(_substream)) => {} // sent and flushed + Ok(Async::NotReady) => { + self.substreams + .push(SubstreamState::ResponsePendingSend { substream }); + } + Err(e) => { + return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( + RPCEvent::Error(0, e), + ))) + } + } + } + SubstreamState::RequestPendingResponse { + mut substream, + rpc_event, + timeout, + } => match substream.poll() { + Ok(Async::Ready(response)) => { + if let Some(response) = response { + return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( + build_response(rpc_event, response), + ))); + } else { + // stream closed early + return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( + RPCEvent::Error( + rpc_event.id(), + RPCError::Custom("Stream Closed Early".into()), + ), + ))); + } + } + Ok(Async::NotReady) => { + if Instant::now() < timeout { + self.substreams + .push(SubstreamState::RequestPendingResponse { + substream, + rpc_event, + timeout, + }); + } + } + Err(e) => { + return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( + RPCEvent::Error(rpc_event.id(), e.into()), + ))) + } + }, + } + } + // establish outbound substreams if !self.dial_queue.is_empty() { if self.dial_negotiated < self.max_dial_negotiated { self.dial_negotiated += 1; - let (request_id, req) = self.dial_queue.remove(0); - return Ok(Async::Ready( - ProtocolsHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new(req), - info: request_id, - }, - )); + let rpc_event = self.dial_queue.remove(0); + if let RPCEvent::Request(id, req) = rpc_event { + return Ok(Async::Ready( + ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(req.clone()), + info: RPCEvent::Request(id, req), + }, + )); + } } } else { self.dial_queue.shrink_to_fit(); @@ -284,3 +361,31 @@ where Ok(Async::NotReady) } } + +/// Given a response back from a peer and the request that sent it, construct a response to send +/// back to the user. This allows for some data manipulation of responses given requests. +fn build_response(rpc_event: RPCEvent, rpc_response: RPCErrorResponse) -> RPCEvent { + let id = rpc_event.id(); + + // handle the types of responses + match rpc_response { + RPCErrorResponse::Success(response) => { + match response { + // if the response is block roots, tag on the extra request data + RPCResponse::BeaconBlockBodies(mut resp) => { + if let RPCEvent::Request(_id, RPCRequest::BeaconBlockBodies(bodies_req)) = + rpc_event + { + resp.block_roots = Some(bodies_req.block_roots); + } + RPCEvent::Response( + id, + RPCErrorResponse::Success(RPCResponse::BeaconBlockBodies(resp)), + ) + } + _ => RPCEvent::Response(id, RPCErrorResponse::Success(response)), + } + } + _ => RPCEvent::Response(id, rpc_response), + } +} diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index 9e9087f9e..02dec8025 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -2,12 +2,14 @@ use ssz::{impl_decode_via_from, impl_encode_via_from}; use ssz_derive::{Decode, Encode}; -use types::{Epoch, Hash256, Slot}; +use types::{BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; /* Request/Response data structures for RPC methods */ /* Requests */ +pub type RequestId = usize; + /// The HELLO request/response handshake message. #[derive(Encode, Decode, Clone, Debug)] pub struct HelloMessage { @@ -33,10 +35,10 @@ pub struct HelloMessage { /// The reason given for a `Goodbye` message. /// /// Note: any unknown `u64::into(n)` will resolve to `Goodbye::Unknown` for any unknown `n`, -/// however `Goodbye::Unknown.into()` will go into `0_u64`. Therefore de-serializing then +/// however `GoodbyeReason::Unknown.into()` will go into `0_u64`. Therefore de-serializing then /// re-serializing may not return the same bytes. #[derive(Debug, Clone)] -pub enum Goodbye { +pub enum GoodbyeReason { /// This node has shutdown. ClientShutdown = 1, @@ -50,25 +52,25 @@ pub enum Goodbye { Unknown = 0, } -impl From for Goodbye { - fn from(id: u64) -> Goodbye { +impl From for GoodbyeReason { + fn from(id: u64) -> GoodbyeReason { match id { - 1 => Goodbye::ClientShutdown, - 2 => Goodbye::IrreleventNetwork, - 3 => Goodbye::Fault, - _ => Goodbye::Unknown, + 1 => GoodbyeReason::ClientShutdown, + 2 => GoodbyeReason::IrreleventNetwork, + 3 => GoodbyeReason::Fault, + _ => GoodbyeReason::Unknown, } } } -impl Into for Goodbye { +impl Into for GoodbyeReason { fn into(self) -> u64 { self as u64 } } -impl_encode_via_from!(Goodbye, u64); -impl_decode_via_from!(Goodbye, u64); +impl_encode_via_from!(GoodbyeReason, u64); +impl_decode_via_from!(GoodbyeReason, u64); /// Request a number of beacon block roots from a peer. #[derive(Encode, Decode, Clone, Debug, PartialEq)] @@ -134,6 +136,11 @@ pub struct BeaconBlockHeadersResponse { pub headers: Vec, } +#[derive(Encode, Decode, Debug)] +pub struct EncodeableBeaconBlockHeadersResponse { + pub headers: Vec, +} + /// Request a number of beacon block bodies from a peer. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BeaconBlockBodiesRequest { @@ -144,10 +151,28 @@ pub struct BeaconBlockBodiesRequest { /// Response containing the list of requested beacon block bodies. #[derive(Clone, Debug, PartialEq)] pub struct BeaconBlockBodiesResponse { + /// The list of hashes that were sent in the request and match these roots response. None when + /// sending outbound. + pub block_roots: Option>, /// The list of ssz-encoded beacon block bodies being requested. pub block_bodies: Vec, } +/// The decoded version of `BeaconBlockBodiesResponse` which is expected in `SimpleSync`. +pub struct DecodedBeaconBlockBodiesResponse { + /// The list of hashes sent in the request to get this response. + pub block_roots: Vec, + /// The valid decoded block bodies. + pub block_bodies: Vec, +} + +//TODO: Build a cleaner API for this encoding/decoding +/// This only exists to encode/decode beacon block bodies according to the wire protocol. +#[derive(Encode, Decode)] +pub struct EncodeableBeaconBlockBodiesResponse { + pub block_bodies: Vec, +} + /// Request values for tree hashes which yield a blocks `state_root`. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BeaconChainStateRequest { @@ -170,8 +195,6 @@ pub struct BeaconChainStateResponse { 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. @@ -182,6 +205,7 @@ pub enum RPCResponse { BeaconChainState(BeaconChainStateResponse), } +#[derive(Debug)] pub enum RPCErrorResponse { Success(RPCResponse), EncodingError, @@ -230,8 +254,14 @@ impl RPCErrorResponse { } } -#[derive(Encode, Decode)] +#[derive(Encode, Decode, Debug)] pub struct ErrorMessage { /// The UTF-8 encoded Error message string. - error_message: Vec, + pub error_message: Vec, +} + +impl ErrorMessage { + pub fn as_string(&self) -> String { + String::from_utf8(self.error_message.clone()).unwrap_or_else(|_| "".into()) + } } diff --git a/beacon_node/eth2-libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs index ab47b4362..f1f341908 100644 --- a/beacon_node/eth2-libp2p/src/rpc/mod.rs +++ b/beacon_node/eth2-libp2p/src/rpc/mod.rs @@ -11,8 +11,8 @@ use libp2p::core::swarm::{ ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; use libp2p::{Multiaddr, PeerId}; -pub use methods::{ErrorMessage, HelloMessage, RPCErrorResponse, RPCResponse}; -pub use protocol::{RPCProtocol, RPCRequest}; +pub use methods::{ErrorMessage, HelloMessage, RPCErrorResponse, RPCResponse, RequestId}; +pub use protocol::{RPCError, RPCProtocol, RPCRequest}; use slog::o; use std::marker::PhantomData; use tokio::io::{AsyncRead, AsyncWrite}; @@ -24,15 +24,27 @@ mod protocol; // mod request_response; /// The return type used in the behaviour and the resultant event from the protocols handler. -#[derive(Debug, Clone)] +#[derive(Debug)] 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(usize, RPCRequest), + Request(RequestId, RPCRequest), /// A response that has been received from the RPC protocol. The first parameter returns /// that which was sent with the corresponding request. - Response(usize, RPCResponse), + Response(RequestId, RPCErrorResponse), + /// An Error occurred. + Error(RequestId, RPCError), +} + +impl RPCEvent { + pub fn id(&self) -> usize { + match *self { + RPCEvent::Request(id, _) => id, + RPCEvent::Response(id, _) => id, + RPCEvent::Error(id, _) => id, + } + } } /// Implements the libp2p `NetworkBehaviour` trait and therefore manages network-level @@ -92,7 +104,12 @@ where } } - fn inject_disconnected(&mut self, _: &PeerId, _: ConnectedPoint) {} + fn inject_disconnected(&mut self, peer_id: &PeerId, _: ConnectedPoint) { + // inform the rpc handler that the peer has disconnected + self.events.push(NetworkBehaviourAction::GenerateEvent( + RPCMessage::PeerDisconnected(peer_id.clone()), + )); + } fn inject_node_event( &mut self, @@ -126,4 +143,5 @@ where pub enum RPCMessage { RPC(PeerId, RPCEvent), PeerDialed(PeerId), + PeerDisconnected(PeerId), } diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index 7d24105f5..5ef2cd3c6 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -9,13 +9,10 @@ use futures::{ sink, stream, Sink, Stream, }; use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use ssz::Encode; -use ssz_derive::{Decode, Encode}; use std::io; use std::time::Duration; use tokio::codec::Framed; use tokio::io::{AsyncRead, AsyncWrite}; -use tokio::prelude::future::MapErr; use tokio::prelude::*; use tokio::timer::timeout; use tokio::util::FutureExt; @@ -24,25 +21,25 @@ use tokio::util::FutureExt; const MAX_RPC_SIZE: usize = 4_194_304; // 4M /// The protocol prefix the RPC protocol id. const PROTOCOL_PREFIX: &str = "/eth/serenity/rpc/"; -/// The number of seconds to wait for a response before the stream is terminated. -const RESPONSE_TIMEOUT: u64 = 10; +/// The number of seconds to wait for a request once a protocol has been established before the stream is terminated. +const REQUEST_TIMEOUT: u64 = 3; /// Implementation of the `ConnectionUpgrade` for the RPC protocol. #[derive(Debug, Clone)] pub struct RPCProtocol; impl UpgradeInfo for RPCProtocol { - type Info = &'static [u8]; + type Info = RawProtocolId; type InfoIter = Vec; fn protocol_info(&self) -> Self::InfoIter { vec![ - b"/eth/serenity/rpc/hello/1.0.0/ssz", - b"/eth/serenity/rpc/goodbye/1.0.0/ssz", - b"/eth/serenity/rpc/beacon_block_roots/1.0.0/ssz", - b"/eth/serenity/rpc/beacon_block_headers/1.0.0/ssz", - b"/eth/serenity/rpc/beacon_block_bodies/1.0.0/ssz", - b"/eth/serenity/rpc/beacon_chain_state/1.0.0/ssz", + ProtocolId::new("hello", "1.0.0", "ssz").into(), + ProtocolId::new("goodbye", "1.0.0", "ssz").into(), + ProtocolId::new("beacon_block_roots", "1.0.0", "ssz").into(), + ProtocolId::new("beacon_block_headers", "1.0.0", "ssz").into(), + ProtocolId::new("beacon_block_bodies", "1.0.0", "ssz").into(), + ProtocolId::new("beacon_chain_state", "1.0.0", "ssz").into(), ] } } @@ -106,16 +103,18 @@ impl Into for ProtocolId { // The inbound protocol reads the request, decodes it and returns the stream to the protocol // handler to respond to once ready. -type InboundFramed = Framed, InboundCodec>; -type FnAndThen = - fn((Option, InboundFramed)) -> FutureResult; +pub type InboundOutput = (RPCRequest, InboundFramed); +pub type InboundFramed = Framed, InboundCodec>; +type FnAndThen = fn( + (Option, InboundFramed), +) -> FutureResult, RPCError>; type FnMapErr = fn(timeout::Error<(RPCError, InboundFramed)>) -> RPCError; impl InboundUpgrade for RPCProtocol where TSocket: AsyncRead + AsyncWrite, { - type Output = RPCRequest; + type Output = InboundOutput; type Error = RPCError; type Future = future::AndThen< @@ -123,31 +122,34 @@ where timeout::Timeout>>, FnMapErr, >, - FutureResult, + FutureResult, RPCError>, FnAndThen, >; fn upgrade_inbound( self, socket: upgrade::Negotiated, - protocol: &'static [u8], + protocol: RawProtocolId, ) -> Self::Future { // TODO: Verify this let protocol_id = - ProtocolId::from_bytes(protocol).expect("Can decode all supported protocols"); + ProtocolId::from_bytes(&protocol).expect("Can decode all supported protocols"); match protocol_id.encoding.as_str() { "ssz" | _ => { - let ssz_codec = BaseInboundCodec::new(SSZInboundCodec::new(protocol_id, 4096)); + let ssz_codec = + BaseInboundCodec::new(SSZInboundCodec::new(protocol_id, MAX_RPC_SIZE)); let codec = InboundCodec::SSZ(ssz_codec); Framed::new(socket, codec) .into_future() - .timeout(Duration::from_secs(RESPONSE_TIMEOUT)) + .timeout(Duration::from_secs(REQUEST_TIMEOUT)) .map_err(RPCError::from as FnMapErr) .and_then({ - |(madouby, _)| match madouby { - Some(x) => futures::future::ok(x), - None => futures::future::err(RPCError::Custom("Go home".into())), + |(req, stream)| match req { + Some(req) => futures::future::ok((req, stream)), + None => futures::future::err(RPCError::Custom( + "Stream terminated early".into(), + )), } } as FnAndThen) } @@ -163,7 +165,7 @@ where #[derive(Debug, Clone)] pub enum RPCRequest { Hello(HelloMessage), - Goodbye(Goodbye), + Goodbye(GoodbyeReason), BeaconBlockRoots(BeaconBlockRootsRequest), BeaconBlockHeaders(BeaconBlockHeadersRequest), BeaconBlockBodies(BeaconBlockBodiesRequest), @@ -202,28 +204,12 @@ impl RPCRequest { } } - /// Encodes the Request object based on the negotiated protocol. - pub fn encode(&self, protocol: ProtocolId) -> Result, 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 { + /// This specifies whether a stream should remain open and await a response, given a request. + /// A GOODBYE request has no response. + pub fn expect_response(&self) -> bool { match self { - RPCRequest::Hello(req) => req.as_ssz_bytes(), - RPCRequest::Goodbye(req) => req.as_ssz_bytes(), - RPCRequest::BeaconBlockRoots(req) => req.as_ssz_bytes(), - RPCRequest::BeaconBlockHeaders(req) => req.as_ssz_bytes(), - RPCRequest::BeaconBlockBodies(req) => req.as_ssz_bytes(), - RPCRequest::BeaconChainState(req) => req.as_ssz_bytes(), + RPCRequest::Goodbye(_) => false, + _ => true, } } } @@ -232,7 +218,7 @@ impl RPCRequest { /* Outbound upgrades */ -type OutboundFramed = Framed, OutboundCodec>; +pub type OutboundFramed = Framed, OutboundCodec>; impl OutboundUpgrade for RPCRequest where @@ -323,11 +309,11 @@ 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::SSZDecodeError(_) => None, + RPCError::InvalidProtocol(_) => None, RPCError::IoError(ref err) => Some(err), RPCError::StreamTimeout => None, - RPCError::Custom(ref err) => None, + RPCError::Custom(_) => None, } } } diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 69f8a1ca5..79f92c36a 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -133,6 +133,9 @@ impl Stream for Service { BehaviourEvent::PeerDialed(peer_id) => { return Ok(Async::Ready(Some(Libp2pEvent::PeerDialed(peer_id)))); } + BehaviourEvent::PeerDisconnected(peer_id) => { + return Ok(Async::Ready(Some(Libp2pEvent::PeerDisconnected(peer_id)))); + } }, Ok(Async::Ready(None)) => unreachable!("Swarm stream shouldn't end"), Ok(Async::NotReady) => break, @@ -182,6 +185,8 @@ pub enum Libp2pEvent { RPC(PeerId, RPCEvent), /// Initiated the connection to a new peer. PeerDialed(PeerId), + /// A peer has disconnected. + PeerDisconnected(PeerId), /// Received pubsub message. PubsubMessage { source: PeerId, diff --git a/beacon_node/network/src/message_handler.rs b/beacon_node/network/src/message_handler.rs index 40a396c3b..e2ea4b0e5 100644 --- a/beacon_node/network/src/message_handler.rs +++ b/beacon_node/network/src/message_handler.rs @@ -3,22 +3,19 @@ use crate::service::{NetworkMessage, OutgoingMessage}; use crate::sync::SimpleSync; use beacon_chain::{BeaconChain, BeaconChainTypes}; use crossbeam_channel::{unbounded as channel, Sender}; +use eth2_libp2p::rpc::methods::*; use eth2_libp2p::{ behaviour::PubsubMessage, - rpc::{methods::GoodbyeReason, RPCRequest, RPCResponse, RequestId}, + rpc::{RPCError, RPCErrorResponse, RPCRequest, RPCResponse, RequestId}, PeerId, RPCEvent, }; use futures::future; -use slog::{debug, warn}; +use slog::{debug, error, warn}; +use ssz::Decode; use std::collections::HashMap; use std::sync::Arc; use std::time::Instant; -/// Timeout for RPC requests. -// const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); -/// Timeout before banning a peer for non-identification. -// const HELLO_TIMEOUT: Duration = Duration::from_secs(30); - /// Handles messages received from the network and client and organises syncing. pub struct MessageHandler { /// Currently loaded and initialised beacon chain. @@ -32,7 +29,7 @@ pub struct MessageHandler { } /// Types of messages the handler can receive. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum HandlerMessage { /// We have initiated a connection to a new peer. PeerDialed(PeerId), @@ -87,6 +84,10 @@ impl MessageHandler { HandlerMessage::PeerDialed(peer_id) => { self.sync.on_connect(peer_id, &mut self.network_context); } + // A peer has disconnected + HandlerMessage::PeerDisconnected(peer_id) => { + self.sync.on_disconnect(peer_id); + } // we have received an RPC message request/response HandlerMessage::RPC(peer_id, rpc_event) => { self.handle_rpc_message(peer_id, rpc_event); @@ -105,9 +106,9 @@ impl MessageHandler { /// Handle RPC messages fn handle_rpc_message(&mut self, peer_id: PeerId, rpc_message: RPCEvent) { match rpc_message { - RPCEvent::Request { id, body, .. // TODO: Clean up RPC Message types, have a cleaner type by this point. - } => self.handle_rpc_request(peer_id, id, body), - RPCEvent::Response { id, result, .. } => self.handle_rpc_response(peer_id, id, result), + RPCEvent::Request(id, req) => self.handle_rpc_request(peer_id, id, req), + RPCEvent::Response(id, resp) => self.handle_rpc_response(peer_id, id, resp), + RPCEvent::Error(id, error) => self.handle_rpc_error(peer_id, id, error), } } @@ -150,58 +151,137 @@ impl MessageHandler { /// An RPC response has been received from the network. // we match on id and ignore responses past the timeout. - fn handle_rpc_response(&mut self, peer_id: PeerId, id: RequestId, response: RPCResponse) { - // if response id is not related to a request, ignore (likely RPC timeout) + fn handle_rpc_response( + &mut self, + peer_id: PeerId, + id: RequestId, + error_response: RPCErrorResponse, + ) { + //TODO: Potentially do not need to keep track of this at all. This has all been shifted + //into libp2p stack. Tracking Id's will only be necessary if a response is important + //relative to a specific request. Note: BeaconBlockBodies already returns with the data + //associated with its request. + // Currently leave this here for testing, to ensure it is redundant. if self .network_context .outstanding_outgoing_request_ids .remove(&(peer_id.clone(), id)) .is_none() { - warn!( + // This should never happen. The RPC layer handles all timeouts and ensures a response + // matches a request. + debug_assert!(false); + + error!( self.log, "Unknown ResponseId for incoming RPCRequest"; "peer" => format!("{:?}", peer_id), - "request_id" => format!("{:?}", id) + "request_id" => format!("{}", id) ); return; } - match response { - RPCResponse::Hello(hello_message) => { - self.sync - .on_hello_response(peer_id, hello_message, &mut self.network_context); + // an error could have occurred. + // TODO: Handle Error gracefully + match error_response { + RPCErrorResponse::EncodingError => { + warn!(self.log, "Encoding Error"; "peer" => format!("{:?}", peer_id), "request_id" => format!("{}",id)) } - RPCResponse::BeaconBlockRoots(response) => { - self.sync.on_beacon_block_roots_response( - peer_id, - response, - &mut self.network_context, - ); + RPCErrorResponse::InvalidRequest(error) => { + warn!(self.log, "";"peer" => format!("{:?}", peer_id), "Invalid Request" => error.as_string()) } - RPCResponse::BeaconBlockHeaders(response) => { - self.sync.on_beacon_block_headers_response( - peer_id, - response, - &mut self.network_context, - ); + RPCErrorResponse::ServerError(error) => { + warn!(self.log, "";"peer" => format!("{:?}", peer_id), "Server Error" => error.as_string()) } - RPCResponse::BeaconBlockBodies(response) => { - self.sync.on_beacon_block_bodies_response( - peer_id, - response, - &mut self.network_context, - ); + RPCErrorResponse::Unknown(error) => { + warn!(self.log, "";"peer" => format!("{:?}", peer_id), "Unknown Error" => error.as_string()) } - RPCResponse::BeaconChainState(_) => { - // We do not implement this endpoint, it is not required and will only likely be - // useful for light-client support in later phases. - // - // Theoretically, we shouldn't reach this code because we should never send a - // beacon state RPC request. - warn!(self.log, "BeaconChainState RPC call is not supported."); + RPCErrorResponse::Success(response) => { + match response { + RPCResponse::Hello(hello_message) => { + self.sync.on_hello_response( + peer_id, + hello_message, + &mut self.network_context, + ); + } + RPCResponse::BeaconBlockRoots(response) => { + self.sync.on_beacon_block_roots_response( + peer_id, + response, + &mut self.network_context, + ); + } + RPCResponse::BeaconBlockHeaders(response) => { + if let Some(decoded_block_headers) = self.decode_block_headers(response) { + self.sync.on_beacon_block_headers_response( + peer_id, + decoded_block_headers, + &mut self.network_context, + ); + } else { + warn!(self.log, "Peer sent invalid block headers";"peer" => format!("{:?}", peer_id)) + } + } + RPCResponse::BeaconBlockBodies(response) => { + if let Some(decoded_block_bodies) = self.decode_block_bodies(response) { + self.sync.on_beacon_block_bodies_response( + peer_id, + decoded_block_bodies, + &mut self.network_context, + ); + } else { + warn!(self.log, "Peer sent invalid block bodies";"peer" => format!("{:?}", peer_id)) + } + } + RPCResponse::BeaconChainState(_) => { + // We do not implement this endpoint, it is not required and will only likely be + // useful for light-client support in later phases. + // + // Theoretically, we shouldn't reach this code because we should never send a + // beacon state RPC request. + warn!(self.log, "BeaconChainState RPC call is not supported."); + } + } } - }; + } + } + + /// Verifies and decodes the ssz-encoded block bodies received from peers. + fn decode_block_bodies( + &self, + bodies_response: BeaconBlockBodiesResponse, + ) -> Option { + //TODO: Implement faster block verification before decoding entirely + let simple_decoded_bodies = + EncodeableBeaconBlockBodiesResponse::from_ssz_bytes(&bodies_response.block_bodies); + + //TODO: Potentially improve the types used here for SSZ encoding/decoding + if let Ok(simple_decoded_bodies) = simple_decoded_bodies { + Some(DecodedBeaconBlockBodiesResponse { + block_roots: bodies_response + .block_roots + .expect("Responses must have associated roots"), + block_bodies: simple_decoded_bodies.block_bodies, + }) + } else { + None + } + } + + /// Verifies and decodes the ssz-encoded block headers received from peers. + fn decode_block_headers( + &self, + headers_response: BeaconBlockHeadersResponse, + ) -> Option { + //TODO: Implement faster header verification before decoding entirely + EncodeableBeaconBlockHeadersResponse::from_ssz_bytes(&headers_response.headers).ok() + } + + /// Handle various RPC errors + fn handle_rpc_error(&mut self, peer_id: PeerId, request_id: RequestId, error: RPCError) { + //TODO: Handle error correctly + warn!(self.log, "RPC Error"; "Peer" => format!("{:?}", peer_id), "Request Id" => format!("{}", request_id), "Error" => format!("{:?}", error)); } /// Handle RPC messages @@ -252,16 +332,10 @@ impl NetworkContext { self.outstanding_outgoing_request_ids .insert((peer_id.clone(), id), Instant::now()); - self.send_rpc_event( - peer_id, - RPCEvent::Request { - id, - method_id: rpc_request.method_id(), - body: rpc_request, - }, - ); + self.send_rpc_event(peer_id, RPCEvent::Request(id, rpc_request)); } + //TODO: Handle Error responses pub fn send_rpc_response( &mut self, peer_id: PeerId, @@ -270,11 +344,7 @@ impl NetworkContext { ) { self.send_rpc_event( peer_id, - RPCEvent::Response { - id: request_id, - method_id: rpc_response.method_id(), - result: rpc_response, - }, + RPCEvent::Response(request_id, RPCErrorResponse::Success(rpc_response)), ); } @@ -291,7 +361,6 @@ impl NetworkContext { "Could not send RPC message to the network service" ) }); - // } /// Returns the next `RequestId` for sending an `RPCRequest` to the `peer_id`. @@ -299,9 +368,9 @@ impl NetworkContext { let next_id = self .outgoing_request_ids .entry(peer_id.clone()) - .and_modify(RequestId::increment) - .or_insert_with(|| RequestId::from(1)); + .and_modify(|id| *id += 1) + .or_insert_with(|| 0); - next_id.previous() + *next_id } } diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index b2ecc1a0b..0b8b70651 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -118,13 +118,19 @@ fn network_service( trace!(log, "RPC Event: RPC message received: {:?}", rpc_event); message_handler_send .send(HandlerMessage::RPC(peer_id, rpc_event)) - .map_err(|_| "failed to send rpc to handler")?; + .map_err(|_| "Failed to send rpc to handler")?; } Libp2pEvent::PeerDialed(peer_id) => { debug!(log, "Peer Dialed: {:?}", peer_id); message_handler_send .send(HandlerMessage::PeerDialed(peer_id)) - .map_err(|_| "failed to send rpc to handler")?; + .map_err(|_| "Failed to send PeerDialed to handler")?; + } + Libp2pEvent::PeerDisconnected(peer_id) => { + debug!(log, "Peer Disconnected: {:?}", peer_id); + message_handler_send + .send(HandlerMessage::PeerDisconnected(peer_id)) + .map_err(|_| "Failed to send PeerDisconnected to handler")?; } Libp2pEvent::PubsubMessage { source, message, .. @@ -176,7 +182,7 @@ fn network_service( } /// Types of messages that the network service can receive. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum NetworkMessage { /// Send a message to libp2p service. //TODO: Define typing for messages across the wire @@ -189,7 +195,7 @@ pub enum NetworkMessage { } /// Type of outgoing messages that can be sent through the network service. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum OutgoingMessage { /// Send an RPC request/response. RPC(RPCEvent), diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 925221673..fd79c10f0 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -5,6 +5,7 @@ use eth2_libp2p::rpc::methods::*; use eth2_libp2p::rpc::{RPCRequest, RPCResponse, RequestId}; use eth2_libp2p::PeerId; use slog::{debug, error, info, o, trace, warn}; +use ssz::Encode; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -30,6 +31,7 @@ const SHOULD_NOT_FORWARD_GOSSIP_BLOCK: bool = false; #[derive(Clone, Copy, Debug)] pub struct PeerSyncInfo { network_id: u8, + chain_id: u64, latest_finalized_root: Hash256, latest_finalized_epoch: Epoch, best_root: Hash256, @@ -40,6 +42,7 @@ impl From for PeerSyncInfo { fn from(hello: HelloMessage) -> PeerSyncInfo { PeerSyncInfo { network_id: hello.network_id, + chain_id: hello.chain_id, latest_finalized_root: hello.latest_finalized_root, latest_finalized_epoch: hello.latest_finalized_epoch, best_root: hello.best_root, @@ -107,6 +110,17 @@ impl SimpleSync { self.known_peers.remove(&peer_id); } + /// Handle a peer disconnect. + /// + /// Removes the peer from `known_peers`. + pub fn on_disconnect(&mut self, peer_id: PeerId) { + info!( + self.log, "Peer Disconnected"; + "peer" => format!("{:?}", peer_id), + ); + self.known_peers.remove(&peer_id); + } + /// Handle the connection of a new peer. /// /// Sends a `Hello` message to the peer. @@ -200,7 +214,7 @@ impl SimpleSync { // If we have equal or better finalized epochs and best slots, we require nothing else from // this peer. // - // We make an exception when our best slot is 0. Best slot does not indicate wether or + // We make an exception when our best slot is 0. Best slot does not indicate whether or // not there is a block at slot zero. if (remote.latest_finalized_epoch <= local.latest_finalized_epoch) && (remote.best_slot <= local.best_slot) @@ -398,6 +412,13 @@ impl SimpleSync { }) .collect(); + // ssz-encode the headers + //TODO: Make this more elegant + let headers = { + let resp = EncodeableBeaconBlockHeadersResponse { headers }; + resp.as_ssz_bytes() + }; + network.send_rpc_response( peer_id, request_id, @@ -409,7 +430,7 @@ impl SimpleSync { pub fn on_beacon_block_headers_response( &mut self, peer_id: PeerId, - res: BeaconBlockHeadersResponse, + res: EncodeableBeaconBlockHeadersResponse, network: &mut NetworkContext, ) { debug!( @@ -471,10 +492,19 @@ impl SimpleSync { "returned" => block_bodies.len(), ); + //TODO: Elegant ssz encoding. Either here or in the message handler + let bytes = { + let resp = EncodeableBeaconBlockBodiesResponse { block_bodies }; + resp.as_ssz_bytes() + }; + network.send_rpc_response( peer_id, request_id, - RPCResponse::BeaconBlockBodies(BeaconBlockBodiesResponse { block_bodies }), + RPCResponse::BeaconBlockBodies(BeaconBlockBodiesResponse { + block_bodies: bytes, + block_roots: None, + }), ) } @@ -482,7 +512,7 @@ impl SimpleSync { pub fn on_beacon_block_bodies_response( &mut self, peer_id: PeerId, - res: BeaconBlockBodiesResponse, + res: DecodedBeaconBlockBodiesResponse, network: &mut NetworkContext, ) { debug!( @@ -802,7 +832,9 @@ fn hello_message(beacon_chain: &BeaconChain) -> HelloMes let state = &beacon_chain.head().beacon_state; HelloMessage { + //TODO: Correctly define the chain/network id network_id: spec.chain_id, + chain_id: spec.chain_id as u64, latest_finalized_root: state.finalized_root, latest_finalized_epoch: state.finalized_epoch, best_root: beacon_chain.head().beacon_block_root, From b350a78fec6994083625c8cdc4b2e607139f730a Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 22 Jul 2019 14:13:18 +1000 Subject: [PATCH 36/39] Update RPC. WIP --- beacon_node/eth2-libp2p/src/rpc/methods.rs | 7 ------- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 3 --- beacon_node/network/src/message_handler.rs | 6 +----- beacon_node/network/src/sync/simple_sync.rs | 6 +----- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index 02dec8025..8a62b87c5 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -166,13 +166,6 @@ pub struct DecodedBeaconBlockBodiesResponse { pub block_bodies: Vec, } -//TODO: Build a cleaner API for this encoding/decoding -/// This only exists to encode/decode beacon block bodies according to the wire protocol. -#[derive(Encode, Decode)] -pub struct EncodeableBeaconBlockBodiesResponse { - pub block_bodies: Vec, -} - /// Request values for tree hashes which yield a blocks `state_root`. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BeaconChainStateRequest { diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index caa2aacf9..6664b1d5c 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -24,9 +24,6 @@ const PROTOCOL_PREFIX: &str = "/eth/serenity/rpc/"; /// The number of seconds to wait for a request once a protocol has been established before the stream is terminated. const REQUEST_TIMEOUT: u64 = 3; -/// Implementation of the `ConnectionUpgrade` for the RPC protocol. -const MAX_READ_SIZE: usize = 4_194_304; // 4M - #[derive(Debug, Clone)] pub struct RPCProtocol; diff --git a/beacon_node/network/src/message_handler.rs b/beacon_node/network/src/message_handler.rs index d255d4a8f..0f67f63a9 100644 --- a/beacon_node/network/src/message_handler.rs +++ b/beacon_node/network/src/message_handler.rs @@ -254,16 +254,12 @@ impl MessageHandler { bodies_response: BeaconBlockBodiesResponse, ) -> Option { //TODO: Implement faster block verification before decoding entirely - let simple_decoded_bodies = - EncodeableBeaconBlockBodiesResponse::from_ssz_bytes(&bodies_response.block_bodies); - - //TODO: Potentially improve the types used here for SSZ encoding/decoding if let Ok(simple_decoded_bodies) = simple_decoded_bodies { Some(DecodedBeaconBlockBodiesResponse { block_roots: bodies_response .block_roots .expect("Responses must have associated roots"), - block_bodies: simple_decoded_bodies.block_bodies, + block_bodies: Vec::from_ssz_bytes(&bodies_response.block_bodies).unwrap(), }) } else { None diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index d8f419520..e4115ff37 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -503,11 +503,7 @@ impl SimpleSync { "returned" => block_bodies.len(), ); - //TODO: Elegant ssz encoding. Either here or in the message handler - let bytes = { - let resp = EncodeableBeaconBlockBodiesResponse { block_bodies }; - resp.as_ssz_bytes() - }; + let bytes = block_bodes.as_ssz_bytes(); network.send_rpc_response( peer_id, From 89ff7fb6b8fd97b3411f8863d979084ae9b6dd51 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 23 Jul 2019 22:45:42 +1000 Subject: [PATCH 37/39] Complete initial testing of new RPC --- beacon_node/eth2-libp2p/src/discovery.rs | 1 + beacon_node/eth2-libp2p/src/rpc/codec/base.rs | 5 - beacon_node/eth2-libp2p/src/rpc/methods.rs | 17 +-- beacon_node/eth2-libp2p/src/rpc/protocol.rs | 12 +- beacon_node/network/src/message_handler.rs | 127 ++++++------------ beacon_node/network/src/sync/simple_sync.rs | 18 +-- 6 files changed, 54 insertions(+), 126 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 44b4e655b..49e5dbeb5 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -68,6 +68,7 @@ impl Discovery { info!(log, "Local ENR: {}", local_enr.to_base64()); debug!(log, "Local Node Id: {}", local_enr.node_id()); + debug!(log, "Local ENR seq: {}", local_enr.seq()); let mut discovery = Discv5::new(local_enr, local_key.clone(), config.listen_address) .map_err(|e| format!("Discv5 service failed: {:?}", e))?; diff --git a/beacon_node/eth2-libp2p/src/rpc/codec/base.rs b/beacon_node/eth2-libp2p/src/rpc/codec/base.rs index f2f4c085b..639a8a730 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codec/base.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codec/base.rs @@ -113,11 +113,6 @@ where resp_code_byte.copy_from_slice(&resp_byte); let resp_code = u8::from_be_bytes(resp_code_byte); - - if let Some(response) = RPCErrorResponse::internal_data(resp_code) { - self.response_code = None; - return Ok(Some(response)); - } self.response_code = Some(resp_code); resp_code } diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index 8a62b87c5..c73435a9f 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -2,7 +2,7 @@ use ssz::{impl_decode_via_from, impl_encode_via_from}; use ssz_derive::{Decode, Encode}; -use types::{BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; +use types::{BeaconBlockBody, Epoch, Hash256, Slot}; /* Request/Response data structures for RPC methods */ @@ -136,11 +136,6 @@ pub struct BeaconBlockHeadersResponse { pub headers: Vec, } -#[derive(Encode, Decode, Debug)] -pub struct EncodeableBeaconBlockHeadersResponse { - pub headers: Vec, -} - /// Request a number of beacon block bodies from a peer. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BeaconBlockBodiesRequest { @@ -208,16 +203,6 @@ pub enum RPCErrorResponse { } impl RPCErrorResponse { - /// If a response has no payload, returns the variant corresponding to the code. - pub fn internal_data(response_code: u8) -> Option { - match response_code { - // EncodingError - 1 => Some(RPCErrorResponse::EncodingError), - // All others require further data - _ => None, - } - } - /// Used to encode the response. pub fn as_u8(&self) -> u8 { match self { diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index 6664b1d5c..bae9618bd 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -20,7 +20,7 @@ use tokio::util::FutureExt; /// The maximum bytes that can be sent across the RPC. const MAX_RPC_SIZE: usize = 4_194_304; // 4M /// The protocol prefix the RPC protocol id. -const PROTOCOL_PREFIX: &str = "/eth/serenity/rpc/"; +const PROTOCOL_PREFIX: &str = "/eth2/beacon_node/rpc"; /// The number of seconds to wait for a request once a protocol has been established before the stream is terminated. const REQUEST_TIMEOUT: u64 = 3; @@ -72,16 +72,16 @@ impl ProtocolId { pub fn from_bytes(bytes: &[u8]) -> Result { let protocol_string = String::from_utf8(bytes.to_vec()) .map_err(|_| RPCError::InvalidProtocol("Invalid protocol Id"))?; - let protocol_list: Vec<&str> = protocol_string.as_str().split('/').take(5).collect(); + let protocol_list: Vec<&str> = protocol_string.as_str().split('/').take(7).collect(); - if protocol_list.len() != 5 { + if protocol_list.len() != 7 { return Err(RPCError::InvalidProtocol("Not enough '/'")); } Ok(ProtocolId { - message_name: protocol_list[3].into(), - version: protocol_list[4].into(), - encoding: protocol_list[5].into(), + message_name: protocol_list[4].into(), + version: protocol_list[5].into(), + encoding: protocol_list[6].into(), }) } } diff --git a/beacon_node/network/src/message_handler.rs b/beacon_node/network/src/message_handler.rs index 0f67f63a9..239547078 100644 --- a/beacon_node/network/src/message_handler.rs +++ b/beacon_node/network/src/message_handler.rs @@ -10,12 +10,11 @@ use eth2_libp2p::{ }; use futures::future::Future; use futures::stream::Stream; -use slog::{debug, error, warn}; -use ssz::Decode; -use std::collections::HashMap; +use slog::{debug, warn}; +use ssz::{Decode, DecodeError}; use std::sync::Arc; -use std::time::Instant; use tokio::sync::mpsc; +use types::BeaconBlockHeader; /// Handles messages received from the network and client and organises syncing. pub struct MessageHandler { @@ -97,8 +96,6 @@ impl MessageHandler { HandlerMessage::PubsubMessage(peer_id, gossip) => { self.handle_gossip(peer_id, *gossip); } - //TODO: Handle all messages - _ => {} } } @@ -115,7 +112,6 @@ impl MessageHandler { /// A new RPC request has been received from the network. fn handle_rpc_request(&mut self, peer_id: PeerId, request_id: RequestId, request: RPCRequest) { - // TODO: process the `id`. match request { RPCRequest::Hello(hello_message) => self.sync.on_hello_request( peer_id, @@ -158,30 +154,6 @@ impl MessageHandler { id: RequestId, error_response: RPCErrorResponse, ) { - //TODO: Potentially do not need to keep track of this at all. This has all been shifted - //into libp2p stack. Tracking Id's will only be necessary if a response is important - //relative to a specific request. Note: BeaconBlockBodies already returns with the data - //associated with its request. - // Currently leave this here for testing, to ensure it is redundant. - if self - .network_context - .outstanding_outgoing_request_ids - .remove(&(peer_id.clone(), id)) - .is_none() - { - // This should never happen. The RPC layer handles all timeouts and ensures a response - // matches a request. - debug_assert!(false); - - error!( - self.log, - "Unknown ResponseId for incoming RPCRequest"; - "peer" => format!("{:?}", peer_id), - "request_id" => format!("{}", id) - ); - return; - } - // an error could have occurred. // TODO: Handle Error gracefully match error_response { @@ -214,25 +186,31 @@ impl MessageHandler { ); } RPCResponse::BeaconBlockHeaders(response) => { - if let Some(decoded_block_headers) = self.decode_block_headers(response) { - self.sync.on_beacon_block_headers_response( - peer_id, - decoded_block_headers, - &mut self.network_context, - ); - } else { - warn!(self.log, "Peer sent invalid block headers";"peer" => format!("{:?}", peer_id)) + match self.decode_block_headers(response) { + Ok(decoded_block_headers) => { + self.sync.on_beacon_block_headers_response( + peer_id, + decoded_block_headers, + &mut self.network_context, + ); + } + Err(_e) => { + warn!(self.log, "Peer sent invalid block headers";"peer" => format!("{:?}", peer_id)) + } } } RPCResponse::BeaconBlockBodies(response) => { - if let Some(decoded_block_bodies) = self.decode_block_bodies(response) { - self.sync.on_beacon_block_bodies_response( - peer_id, - decoded_block_bodies, - &mut self.network_context, - ); - } else { - warn!(self.log, "Peer sent invalid block bodies";"peer" => format!("{:?}", peer_id)) + match self.decode_block_bodies(response) { + Ok(decoded_block_bodies) => { + self.sync.on_beacon_block_bodies_response( + peer_id, + decoded_block_bodies, + &mut self.network_context, + ); + } + Err(_e) => { + warn!(self.log, "Peer sent invalid block bodies";"peer" => format!("{:?}", peer_id)) + } } } RPCResponse::BeaconChainState(_) => { @@ -252,27 +230,24 @@ impl MessageHandler { fn decode_block_bodies( &self, bodies_response: BeaconBlockBodiesResponse, - ) -> Option { + ) -> Result { //TODO: Implement faster block verification before decoding entirely - if let Ok(simple_decoded_bodies) = simple_decoded_bodies { - Some(DecodedBeaconBlockBodiesResponse { - block_roots: bodies_response - .block_roots - .expect("Responses must have associated roots"), - block_bodies: Vec::from_ssz_bytes(&bodies_response.block_bodies).unwrap(), - }) - } else { - None - } + let block_bodies = Vec::from_ssz_bytes(&bodies_response.block_bodies)?; + Ok(DecodedBeaconBlockBodiesResponse { + block_roots: bodies_response + .block_roots + .expect("Responses must have associated roots"), + block_bodies, + }) } /// Verifies and decodes the ssz-encoded block headers received from peers. fn decode_block_headers( &self, headers_response: BeaconBlockHeadersResponse, - ) -> Option { + ) -> Result, DecodeError> { //TODO: Implement faster header verification before decoding entirely - EncodeableBeaconBlockHeadersResponse::from_ssz_bytes(&headers_response.headers).ok() + Vec::from_ssz_bytes(&headers_response.headers) } /// Handle various RPC errors @@ -297,25 +272,17 @@ impl MessageHandler { } } +// TODO: RPC Rewrite makes this struct fairly pointless pub struct NetworkContext { /// The network channel to relay messages to the Network service. network_send: mpsc::UnboundedSender, - /// A mapping of peers and the RPC id we have sent an RPC request to. - outstanding_outgoing_request_ids: HashMap<(PeerId, RequestId), Instant>, - /// Stores the next `RequestId` we should include on an outgoing `RPCRequest` to a `PeerId`. - outgoing_request_ids: HashMap, /// The `MessageHandler` logger. log: slog::Logger, } impl NetworkContext { pub fn new(network_send: mpsc::UnboundedSender, log: slog::Logger) -> Self { - Self { - network_send, - outstanding_outgoing_request_ids: HashMap::new(), - outgoing_request_ids: HashMap::new(), - log, - } + Self { network_send, log } } pub fn disconnect(&mut self, peer_id: PeerId, reason: GoodbyeReason) { @@ -324,12 +291,9 @@ impl NetworkContext { } pub fn send_rpc_request(&mut self, peer_id: PeerId, rpc_request: RPCRequest) { - let id = self.generate_request_id(&peer_id); - - self.outstanding_outgoing_request_ids - .insert((peer_id.clone(), id), Instant::now()); - - self.send_rpc_event(peer_id, RPCEvent::Request(id, rpc_request)); + // Note: There is currently no use of keeping track of requests. However the functionality + // is left here for future revisions. + self.send_rpc_event(peer_id, RPCEvent::Request(0, rpc_request)); } //TODO: Handle Error responses @@ -359,15 +323,4 @@ impl NetworkContext { ) }); } - - /// Returns the next `RequestId` for sending an `RPCRequest` to the `peer_id`. - fn generate_request_id(&mut self, peer_id: &PeerId) -> RequestId { - let next_id = self - .outgoing_request_ids - .entry(peer_id.clone()) - .and_modify(|id| *id += 1) - .or_insert_with(|| 0); - - *next_id - } } diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index e4115ff37..91594b999 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -422,11 +422,7 @@ impl SimpleSync { .collect(); // ssz-encode the headers - //TODO: Make this more elegant - let headers = { - let resp = EncodeableBeaconBlockHeadersResponse { headers }; - resp.as_ssz_bytes() - }; + let headers = headers.as_ssz_bytes(); network.send_rpc_response( peer_id, @@ -439,17 +435,17 @@ impl SimpleSync { pub fn on_beacon_block_headers_response( &mut self, peer_id: PeerId, - res: EncodeableBeaconBlockHeadersResponse, + headers: Vec, network: &mut NetworkContext, ) { debug!( self.log, "BlockHeadersResponse"; "peer" => format!("{:?}", peer_id), - "count" => res.headers.len(), + "count" => headers.len(), ); - if res.headers.is_empty() { + if headers.is_empty() { warn!( self.log, "Peer returned empty block headers response. PeerId: {:?}", peer_id @@ -459,9 +455,7 @@ impl SimpleSync { // Enqueue the headers, obtaining a list of the roots of the headers which were newly added // to the queue. - let block_roots = self - .import_queue - .enqueue_headers(res.headers, peer_id.clone()); + let block_roots = self.import_queue.enqueue_headers(headers, peer_id.clone()); if !block_roots.is_empty() { self.request_block_bodies(peer_id, BeaconBlockBodiesRequest { block_roots }, network); @@ -503,7 +497,7 @@ impl SimpleSync { "returned" => block_bodies.len(), ); - let bytes = block_bodes.as_ssz_bytes(); + let bytes = block_bodies.as_ssz_bytes(); network.send_rpc_response( peer_id, From 7d38cba25231d48c337ca48b42900a3edbe17d7a Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 24 Jul 2019 15:17:44 +1000 Subject: [PATCH 38/39] Remove encoding error and redundant code --- beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs | 1 - beacon_node/eth2-libp2p/src/rpc/methods.rs | 2 - beacon_node/eth2-libp2p/src/rpc/protocol.rs | 1 - .../eth2-libp2p/src/rpc/request_response.rs | 242 ------------------ 4 files changed, 246 deletions(-) delete mode 100644 beacon_node/eth2-libp2p/src/rpc/request_response.rs diff --git a/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs b/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs index a78ac8f7f..8e2bdaa64 100644 --- a/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs +++ b/beacon_node/eth2-libp2p/src/rpc/codec/ssz.rs @@ -47,7 +47,6 @@ impl Encoder for SSZInboundCodec { RPCResponse::BeaconChainState(res) => res.as_ssz_bytes(), } } - RPCErrorResponse::EncodingError => vec![], RPCErrorResponse::InvalidRequest(err) => err.as_ssz_bytes(), RPCErrorResponse::ServerError(err) => err.as_ssz_bytes(), RPCErrorResponse::Unknown(err) => err.as_ssz_bytes(), diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index c73435a9f..0d6311d9d 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -196,7 +196,6 @@ pub enum RPCResponse { #[derive(Debug)] pub enum RPCErrorResponse { Success(RPCResponse), - EncodingError, InvalidRequest(ErrorMessage), ServerError(ErrorMessage), Unknown(ErrorMessage), @@ -207,7 +206,6 @@ impl RPCErrorResponse { pub fn as_u8(&self) -> u8 { match self { RPCErrorResponse::Success(_) => 0, - RPCErrorResponse::EncodingError => 1, RPCErrorResponse::InvalidRequest(_) => 2, RPCErrorResponse::ServerError(_) => 3, RPCErrorResponse::Unknown(_) => 255, diff --git a/beacon_node/eth2-libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs index bae9618bd..8729de3a7 100644 --- a/beacon_node/eth2-libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2-libp2p/src/rpc/protocol.rs @@ -38,7 +38,6 @@ impl UpgradeInfo for RPCProtocol { ProtocolId::new("beacon_block_roots", "1.0.0", "ssz").into(), ProtocolId::new("beacon_block_headers", "1.0.0", "ssz").into(), ProtocolId::new("beacon_block_bodies", "1.0.0", "ssz").into(), - ProtocolId::new("beacon_chain_state", "1.0.0", "ssz").into(), ] } } diff --git a/beacon_node/eth2-libp2p/src/rpc/request_response.rs b/beacon_node/eth2-libp2p/src/rpc/request_response.rs deleted file mode 100644 index e511bfe9f..000000000 --- a/beacon_node/eth2-libp2p/src/rpc/request_response.rs +++ /dev/null @@ -1,242 +0,0 @@ -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( - socket: TSocket, - data: TData, // data sent as a request - max_size: usize, // maximum bytes to read in a response - short_circuit_return: Option, // default value to return right after a request, do not wait for a response - protocol: ProtocolId, // the protocol being negotiated -) -> RPCRequestResponse -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> { - protocol: ProtocolId, - inner: RPCRequestResponseInner, -} - -enum RPCRequestResponseInner { - // We need to write data to the socket. - Write(WriteOneInner, usize, Option), - // We need to read the response code. - ReadResponseCode(io::ReadExact>>, usize), - // We need to read a final data packet. The second parameter is the response code - Read(ReadOne, ResponseCode), - // An error happened during the processing. - Poisoned, -} - -impl Future for RPCRequestResponse -where - TSocket: AsyncRead + AsyncWrite, - TData: AsRef<[u8]>, -{ - type Item = RPCResponse; - type Error = RPCError; - - fn poll(&mut self) -> Poll { - 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 resp_code_byte = [0; 1]; - // data must be only 1-byte - this cannot panic - resp_code_byte.copy_from_slice(&data.into_inner()); - let response_code = - ResponseCode::from(u8::from_be_bytes(resp_code_byte)); - // 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 as u8) - )); - 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(socket: TSocket, data: TData) -> WriteOne -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 { - /// We need to write the data length to the socket. - WriteLen(io::WriteAll>, TData), - /// We need to write the actual data to the socket. - Write(io::WriteAll), - /// We need to shut down the socket. - Shutdown(io::Shutdown), - /// A problem happened during the processing. - Poisoned, -} - -impl Future for WriteOneInner -where - TSocket: AsyncWrite, - TData: AsRef<[u8]>, -{ - type Item = TSocket; - type Error = std::io::Error; - - fn poll(&mut self) -> Poll { - 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> { - inner: WriteOneInner, -} - -impl Future for WriteOne -where - TSocket: AsyncWrite, - TData: AsRef<[u8]>, -{ - type Item = (); - type Error = std::io::Error; - - #[inline] - fn poll(&mut self) -> Poll { - Ok(self.inner.poll()?.map(|_socket| ())) - } -} From ae96325c8184ae69edbf35c01db975bafeb21b56 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 24 Jul 2019 17:45:31 +1000 Subject: [PATCH 39/39] Allows Libp2p service to be read outside network thread --- beacon_node/client/src/notifier.rs | 19 ++++--- beacon_node/eth2-libp2p/src/behaviour.rs | 5 ++ beacon_node/eth2-libp2p/src/discovery.rs | 5 ++ beacon_node/network/Cargo.toml | 1 + beacon_node/network/src/message_handler.rs | 12 +---- beacon_node/network/src/service.rs | 61 ++++++++-------------- 6 files changed, 47 insertions(+), 56 deletions(-) diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 987f064a4..ff6c1b230 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -8,7 +8,7 @@ use tokio::runtime::TaskExecutor; use tokio::timer::Interval; /// The interval between heartbeat events. -pub const HEARTBEAT_INTERVAL_SECONDS: u64 = 5; +pub const HEARTBEAT_INTERVAL_SECONDS: u64 = 15; /// Spawns a thread that can be used to run code periodically, on `HEARTBEAT_INTERVAL_SECONDS` /// durations. @@ -25,19 +25,22 @@ pub fn run( Duration::from_secs(HEARTBEAT_INTERVAL_SECONDS), ); - let _log = client.log.new(o!("Service" => "Notifier")); + let log = client.log.new(o!("Service" => "Notifier")); + + let libp2p = client.network.libp2p_service(); + + let heartbeat = move |_| { + // Notify the number of connected nodes + // Panic if libp2p is poisoned + debug!(log, ""; "Connected Peers" => libp2p.lock().swarm.connected_peers()); - let heartbeat = |_| { - // There is not presently any heartbeat logic. - // - // We leave this function empty for future use. Ok(()) }; // map error and spawn - let log = client.log.clone(); + let err_log = client.log.clone(); let heartbeat_interval = interval - .map_err(move |e| debug!(log, "Timer error {}", e)) + .map_err(move |e| debug!(err_log, "Timer error {}", e)) .for_each(heartbeat); executor.spawn(exit.until(heartbeat_interval).map(|_| ())); diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 1bff58ecd..9a30a60b9 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -171,6 +171,11 @@ impl Behaviour { pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { self.serenity_rpc.send_rpc(peer_id, rpc_event); } + + /* Discovery / Peer management functions */ + pub fn connected_peers(&self) -> usize { + self.discovery.connected_peers() + } } /// The types of events than can be obtained from polling the behaviour. diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 49e5dbeb5..29725ff36 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -106,6 +106,11 @@ impl Discovery { self.discovery.add_enr(enr); } + /// The current number of connected libp2p peers. + pub fn connected_peers(&self) -> usize { + self.connected_peers.len() + } + /// Search for new peers using the underlying discovery mechanism. fn find_peers(&mut self) { // pick a random NodeId diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index 1499ac580..9eadede76 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -19,3 +19,4 @@ tree_hash = { path = "../../eth2/utils/tree_hash" } futures = "0.1.25" error-chain = "0.12.0" tokio = "0.1.16" +parking_lot = "0.9.0" diff --git a/beacon_node/network/src/message_handler.rs b/beacon_node/network/src/message_handler.rs index 239547078..b1d88415c 100644 --- a/beacon_node/network/src/message_handler.rs +++ b/beacon_node/network/src/message_handler.rs @@ -105,7 +105,7 @@ impl MessageHandler { fn handle_rpc_message(&mut self, peer_id: PeerId, rpc_message: RPCEvent) { match rpc_message { RPCEvent::Request(id, req) => self.handle_rpc_request(peer_id, id, req), - RPCEvent::Response(id, resp) => self.handle_rpc_response(peer_id, id, resp), + RPCEvent::Response(_id, resp) => self.handle_rpc_response(peer_id, resp), RPCEvent::Error(id, error) => self.handle_rpc_error(peer_id, id, error), } } @@ -148,18 +148,10 @@ impl MessageHandler { /// An RPC response has been received from the network. // we match on id and ignore responses past the timeout. - fn handle_rpc_response( - &mut self, - peer_id: PeerId, - id: RequestId, - error_response: RPCErrorResponse, - ) { + fn handle_rpc_response(&mut self, peer_id: PeerId, error_response: RPCErrorResponse) { // an error could have occurred. // TODO: Handle Error gracefully match error_response { - RPCErrorResponse::EncodingError => { - warn!(self.log, "Encoding Error"; "peer" => format!("{:?}", peer_id), "request_id" => format!("{}",id)) - } RPCErrorResponse::InvalidRequest(error) => { warn!(self.log, "";"peer" => format!("{:?}", peer_id), "Invalid Request" => error.as_string()) } diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 0c0af367a..a771f8add 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -8,6 +8,7 @@ use eth2_libp2p::{Libp2pEvent, PeerId}; use eth2_libp2p::{PubsubMessage, RPCEvent}; use futures::prelude::*; use futures::Stream; +use parking_lot::Mutex; use slog::{debug, info, o, trace}; use std::marker::PhantomData; use std::sync::Arc; @@ -16,9 +17,9 @@ use tokio::sync::{mpsc, oneshot}; /// Service that handles communication between internal services and the eth2_libp2p network service. pub struct Service { - //libp2p_service: Arc>, + libp2p_service: Arc>, _libp2p_exit: oneshot::Sender<()>, - network_send: mpsc::UnboundedSender, + _network_send: mpsc::UnboundedSender, _phantom: PhantomData, //message_handler: MessageHandler, //message_handler_send: Sender } @@ -43,38 +44,33 @@ impl Service { // launch libp2p service let libp2p_log = log.new(o!("Service" => "Libp2p")); - let libp2p_service = LibP2PService::new(config.clone(), libp2p_log)?; + let libp2p_service = Arc::new(Mutex::new(LibP2PService::new(config.clone(), libp2p_log)?)); // TODO: Spawn thread to handle libp2p messages and pass to message handler thread. let libp2p_exit = spawn_service( - libp2p_service, + libp2p_service.clone(), network_recv, message_handler_send, executor, log, )?; let network_service = Service { + libp2p_service, _libp2p_exit: libp2p_exit, - network_send: network_send.clone(), + _network_send: network_send.clone(), _phantom: PhantomData, }; Ok((Arc::new(network_service), network_send)) } - // TODO: Testing only - pub fn send_message(&mut self) { - self.network_send - .try_send(NetworkMessage::Send( - PeerId::random(), - OutgoingMessage::NotifierTest, - )) - .unwrap(); + pub fn libp2p_service(&self) -> Arc> { + self.libp2p_service.clone() } } fn spawn_service( - libp2p_service: LibP2PService, + libp2p_service: Arc>, network_recv: mpsc::UnboundedReceiver, message_handler_send: mpsc::UnboundedSender, executor: &TaskExecutor, @@ -103,7 +99,7 @@ fn spawn_service( //TODO: Potentially handle channel errors fn network_service( - mut libp2p_service: LibP2PService, + libp2p_service: Arc>, mut network_recv: mpsc::UnboundedReceiver, mut message_handler_send: mpsc::UnboundedSender, log: slog::Logger, @@ -115,28 +111,18 @@ fn network_service( not_ready_count = 0; // poll the network channel match network_recv.poll() { - Ok(Async::Ready(Some(message))) => { - match message { - // TODO: Testing message - remove - NetworkMessage::Send(peer_id, outgoing_message) => { - match outgoing_message { - OutgoingMessage::RPC(rpc_event) => { - trace!(log, "Sending RPC Event: {:?}", rpc_event); - //TODO: Make swarm private - //TODO: Implement correct peer id topic message handling - libp2p_service.swarm.send_rpc(peer_id, rpc_event); - } - OutgoingMessage::NotifierTest => { - // debug!(log, "Received message from notifier"); - } - }; - } - NetworkMessage::Publish { topics, message } => { - debug!(log, "Sending pubsub message"; "topics" => format!("{:?}",topics)); - libp2p_service.swarm.publish(topics, *message); + Ok(Async::Ready(Some(message))) => match message { + NetworkMessage::Send(peer_id, outgoing_message) => match outgoing_message { + OutgoingMessage::RPC(rpc_event) => { + trace!(log, "Sending RPC Event: {:?}", rpc_event); + libp2p_service.lock().swarm.send_rpc(peer_id, rpc_event); } + }, + NetworkMessage::Publish { topics, message } => { + debug!(log, "Sending pubsub message"; "topics" => format!("{:?}",topics)); + libp2p_service.lock().swarm.publish(topics, *message); } - } + }, Ok(Async::NotReady) => not_ready_count += 1, Ok(Async::Ready(None)) => { return Err(eth2_libp2p::error::Error::from("Network channel closed")); @@ -147,7 +133,7 @@ fn network_service( } // poll the swarm - match libp2p_service.poll() { + match libp2p_service.lock().poll() { Ok(Async::Ready(Some(event))) => match event { Libp2pEvent::RPC(peer_id, rpc_event) => { trace!(log, "RPC Event: RPC message received: {:?}", rpc_event); @@ -182,6 +168,7 @@ fn network_service( Err(_) => not_ready_count += 1, } } + Ok(Async::NotReady) }) } @@ -204,6 +191,4 @@ pub enum NetworkMessage { pub enum OutgoingMessage { /// Send an RPC request/response. RPC(RPCEvent), - //TODO: Remove - NotifierTest, }