Stable futures (#879)

* Port eth1 lib to use stable futures

* Port eth1_test_rig to stable futures

* Port eth1 tests to stable futures

* Port genesis service to stable futures

* Port genesis tests to stable futures

* Port beacon_chain to stable futures

* Port lcli to stable futures

* Fix eth1_test_rig (#1014)

* Fix lcli

* Port timer to stable futures

* Fix timer

* Port websocket_server to stable futures

* Port notifier to stable futures

* Add TODOS

* Update hashmap hashset to stable futures

* Adds panic test to hashset delay

* Port remote_beacon_node to stable futures

* Fix lcli merge conflicts

* Non rpc stuff compiles

* protocol.rs compiles

* Port websockets, timer and notifier to stable futures (#1035)

* Fix lcli

* Port timer to stable futures

* Fix timer

* Port websocket_server to stable futures

* Port notifier to stable futures

* Add TODOS

* Port remote_beacon_node to stable futures

* Partial eth2-libp2p stable future upgrade

* Finished first round of fighting RPC types

* Further progress towards porting eth2-libp2p adds caching to discovery

* Update behaviour

* RPC handler to stable futures

* Update RPC to master libp2p

* Network service additions

* Fix the fallback transport construction (#1102)

* Correct warning

* Remove hashmap delay

* Compiling version of eth2-libp2p

* Update all crates versions

* Fix conversion function and add tests (#1113)

* Port validator_client to stable futures (#1114)

* Add PH & MS slot clock changes

* Account for genesis time

* Add progress on duties refactor

* Add simple is_aggregator bool to val subscription

* Start work on attestation_verification.rs

* Add progress on ObservedAttestations

* Progress with ObservedAttestations

* Fix tests

* Add observed attestations to the beacon chain

* Add attestation observation to processing code

* Add progress on attestation verification

* Add first draft of ObservedAttesters

* Add more tests

* Add observed attesters to beacon chain

* Add observers to attestation processing

* Add more attestation verification

* Create ObservedAggregators map

* Remove commented-out code

* Add observed aggregators into chain

* Add progress

* Finish adding features to attestation verification

* Ensure beacon chain compiles

* Link attn verification into chain

* Integrate new attn verification in chain

* Remove old attestation processing code

* Start trying to fix beacon_chain tests

* Split adding into pools into two functions

* Add aggregation to harness

* Get test harness working again

* Adjust the number of aggregators for test harness

* Fix edge-case in harness

* Integrate new attn processing in network

* Fix compile bug in validator_client

* Update validator API endpoints

* Fix aggreagation in test harness

* Fix enum thing

* Fix attestation observation bug:

* Patch failing API tests

* Start adding comments to attestation verification

* Remove unused attestation field

* Unify "is block known" logic

* Update comments

* Supress fork choice errors for network processing

* Add todos

* Tidy

* Add gossip attn tests

* Disallow test harness to produce old attns

* Comment out in-progress tests

* Partially address pruning tests

* Fix failing store test

* Add aggregate tests

* Add comments about which spec conditions we check

* Dont re-aggregate

* Split apart test harness attn production

* Fix compile error in network

* Make progress on commented-out test

* Fix skipping attestation test

* Add fork choice verification tests

* Tidy attn tests, remove dead code

* Remove some accidentally added code

* Fix clippy lint

* Rename test file

* Add block tests, add cheap block proposer check

* Rename block testing file

* Add observed_block_producers

* Tidy

* Switch around block signature verification

* Finish block testing

* Remove gossip from signature tests

* First pass of self review

* Fix deviation in spec

* Update test spec tags

* Start moving over to hashset

* Finish moving observed attesters to hashmap

* Move aggregation pool over to hashmap

* Make fc attn borrow again

* Fix rest_api compile error

* Fix missing comments

* Fix monster test

* Uncomment increasing slots test

* Address remaining comments

* Remove unsafe, use cfg test

* Remove cfg test flag

* Fix dodgy comment

* Revert "Update hashmap hashset to stable futures"

This reverts commit d432378a3c.

* Revert "Adds panic test to hashset delay"

This reverts commit 281502396f.

* Ported attestation_service

* Ported duties_service

* Ported fork_service

* More ports

* Port block_service

* Minor fixes

* VC compiles

* Update TODOS

* Borrow self where possible

* Ignore aggregates that are already known.

* Unify aggregator modulo logic

* Fix typo in logs

* Refactor validator subscription logic

* Avoid reproducing selection proof

* Skip HTTP call if no subscriptions

* Rename DutyAndState -> DutyAndProof

* Tidy logs

* Print root as dbg

* Fix compile errors in tests

* Fix compile error in test

* Re-Fix attestation and duties service

* Minor fixes

Co-authored-by: Paul Hauner <paul@paulhauner.com>

* Network crate update to stable futures

* Port account_manager to stable futures (#1121)

* Port account_manager to stable futures

* Run async fns in tokio environment

* Port rest_api crate to stable futures (#1118)

* Port rest_api lib to stable futures

* Reduce tokio features

* Update notifier to stable futures

* Builder update

* Further updates

* Convert self referential async functions

* stable futures fixes (#1124)

* Fix eth1 update functions

* Fix genesis and client

* Fix beacon node lib

* Return appropriate runtimes from environment

* Fix test rig

* Refactor eth1 service update

* Upgrade simulator to stable futures

* Lighthouse compiles on stable futures

* Remove println debugging statement

* Update libp2p service, start rpc test upgrade

* Update network crate for new libp2p

* Update tokio::codec to futures_codec (#1128)

* Further work towards RPC corrections

* Correct http timeout and network service select

* Use tokio runtime for libp2p

* Revert "Update tokio::codec to futures_codec (#1128)"

This reverts commit e57aea924a.

* Upgrade RPC libp2p tests

* Upgrade secio fallback test

* Upgrade gossipsub examples

* Clean up RPC protocol

* Test fixes (#1133)

* Correct websocket timeout and run on os thread

* Fix network test

* Clean up PR

* Correct tokio tcp move attestation service tests

* Upgrade attestation service tests

* Correct network test

* Correct genesis test

* Test corrections

* Log info when block is received

* Modify logs and update attester service events

* Stable futures: fixes to vc, eth1 and account manager (#1142)

* Add local testnet scripts

* Remove whiteblock script

* Rename local testnet script

* Move spawns onto handle

* Fix VC panic

* Initial fix to block production issue

* Tidy block producer fix

* Tidy further

* Add local testnet clean script

* Run cargo fmt

* Tidy duties service

* Tidy fork service

* Tidy ForkService

* Tidy AttestationService

* Tidy notifier

* Ensure await is not suppressed in eth1

* Ensure await is not suppressed in account_manager

* Use .ok() instead of .unwrap_or(())

* RPC decoding test for proto

* Update discv5 and eth2-libp2p deps

* Fix lcli double runtime issue (#1144)

* Handle stream termination and dialing peer errors

* Correct peer_info variant types

* Remove unnecessary warnings

* Handle subnet unsubscription removal and improve logigng

* Add logs around ping

* Upgrade discv5 and improve logging

* Handle peer connection status for multiple connections

* Improve network service logging

* Improve logging around peer manager

* Upgrade swarm poll centralise peer management

* Identify clients on error

* Fix `remove_peer` in sync (#1150)

* remove_peer removes from all chains

* Remove logs

* Fix early return from loop

* Improved logging, fix panic

* Partially correct tests

* Stable futures: Vc sync (#1149)

* Improve syncing heuristic

* Add comments

* Use safer method for tolerance

* Fix tests

* Stable futures: Fix VC bug, update agg pool, add more metrics (#1151)

* Expose epoch processing summary

* Expose participation metrics to prometheus

* Switch to f64

* Reduce precision

* Change precision

* Expose observed attesters metrics

* Add metrics for agg/unagg attn counts

* Add metrics for gossip rx

* Add metrics for gossip tx

* Adds ignored attns to prom

* Add attestation timing

* Add timer for aggregation pool sig agg

* Add write lock timer for agg pool

* Add more metrics to agg pool

* Change map lock code

* Add extra metric to agg pool

* Change lock handling in agg pool

* Change .write() to .read()

* Add another agg pool timer

* Fix for is_aggregator

* Fix pruning bug

Co-authored-by: pawan <pawandhananjay@gmail.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
Age Manning
2020-05-17 21:16:48 +10:00
committed by GitHub
parent 21901b1615
commit b6408805a2
165 changed files with 7924 additions and 7733 deletions

View File

@@ -5,38 +5,47 @@ authors = ["Age Manning <Age@AgeManning.com>"]
edition = "2018"
[dependencies]
hex = "0.3"
# rust-libp2p is presently being sourced from a Sigma Prime fork of the
# `libp2p/rust-libp2p` repository.
libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "71cf486b4d992862f5a05f9f4ef5e5c1631f4add" }
hex = "0.4.2"
types = { path = "../../eth2/types" }
hashmap_delay = { path = "../../eth2/utils/hashmap_delay" }
hashset_delay = { path = "../../eth2/utils/hashset_delay" }
eth2_ssz_types = { path = "../../eth2/utils/ssz_types" }
serde = { version = "1.0.102", features = ["derive"] }
serde_derive = "1.0.102"
serde = { version = "1.0.110", features = ["derive"] }
serde_derive = "1.0.110"
eth2_ssz = "0.1.2"
eth2_ssz_derive = "0.1.0"
slog = { version = "2.5.2", features = ["max_level_trace"] }
version = { path = "../version" }
tokio = "0.1.22"
futures = "0.1.29"
error-chain = "0.12.1"
tokio = { version = "0.2.20", features = ["time"] }
futures = "0.3.5"
error-chain = "0.12.2"
dirs = "2.0.2"
fnv = "1.0.6"
unsigned-varint = "0.2.3"
unsigned-varint = { git = "https://github.com/sigp/unsigned-varint", branch = "latest-codecs", features = ["codec"] }
lazy_static = "1.4.0"
lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" }
tokio-io-timeout = "0.3.1"
smallvec = "1.0.0"
smallvec = "1.4.0"
lru = "0.4.3"
parking_lot = "0.9.0"
sha2 = "0.8.0"
base64 = "0.11.0"
snap = "1"
parking_lot = "0.10.2"
sha2 = "0.8.1"
base64 = "0.12.1"
snap = "1.0.0"
void = "1.0.2"
tokio-io-timeout = "0.4.0"
tokio-util = { version = "0.3.1", features = ["codec", "compat"] }
# Patched for quick updates
discv5 = { git = "https://github.com/sigp/discv5", rev = "7b3bd40591b62b8c002ffdb85de008aa9f82e2e5" }
tiny-keccak = "2.0.2"
libp2p-tcp = { version = "0.18.0", default-features = false, features = ["tokio"] }
[dependencies.libp2p]
version = "0.18.1"
default-features = false
features = ["websocket", "identify", "mplex", "yamux", "noise", "secio", "gossipsub", "dns"]
[dev-dependencies]
tokio = { version = "0.2.20", features = ["full"] }
slog-stdlog = "4.0.0"
slog-term = "2.4.2"
slog-async = "2.3.0"
tempdir = "0.3"
slog-term = "2.5.0"
slog-async = "2.5.0"
tempdir = "0.3.7"

View File

@@ -3,20 +3,22 @@ use crate::peer_manager::{PeerManager, PeerManagerEvent};
use crate::rpc::*;
use crate::types::{GossipEncoding, GossipKind, GossipTopic};
use crate::{error, Enr, NetworkConfig, NetworkGlobals, PubsubMessage, TopicHash};
use discv5::Discv5Event;
use futures::prelude::*;
use libp2p::{
core::{identity::Keypair, ConnectedPoint},
discv5::Discv5Event,
core::identity::Keypair,
gossipsub::{Gossipsub, GossipsubEvent, MessageId},
identify::{Identify, IdentifyEvent},
swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess},
tokio_io::{AsyncRead, AsyncWrite},
swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters},
NetworkBehaviour, PeerId,
};
use lru::LruCache;
use slog::{crit, debug, o, warn};
use std::marker::PhantomData;
use std::sync::Arc;
use slog::{crit, debug, o};
use std::{
marker::PhantomData,
sync::Arc,
task::{Context, Poll},
};
use types::{EnrForkId, EthSpec, SubnetId};
const MAX_IDENTIFY_ADDRESSES: usize = 10;
@@ -26,17 +28,17 @@ const MAX_IDENTIFY_ADDRESSES: usize = 10;
/// behaviours.
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "BehaviourEvent<TSpec>", poll_method = "poll")]
pub struct Behaviour<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> {
pub struct Behaviour<TSpec: EthSpec> {
/// The routing pub-sub mechanism for eth2.
gossipsub: Gossipsub<TSubstream>,
gossipsub: Gossipsub,
/// The Eth2 RPC specified in the wire-0 protocol.
eth2_rpc: RPC<TSubstream, TSpec>,
eth2_rpc: RPC<TSpec>,
/// Keep regular connection to peers and disconnect if absent.
// TODO: Using id for initial interop. This will be removed by mainnet.
/// Provides IP addresses and peer information.
identify: Identify<TSubstream>,
identify: Identify,
/// Discovery behaviour.
discovery: Discovery<TSubstream, TSpec>,
discovery: Discovery<TSpec>,
/// The peer manager that keeps track of peer's reputation and status.
#[behaviour(ignore)]
peer_manager: PeerManager<TSpec>,
@@ -65,7 +67,7 @@ pub struct Behaviour<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> {
}
/// Implements the combined behaviour for the libp2p service.
impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, TSpec> {
impl<TSpec: EthSpec> Behaviour<TSpec> {
pub fn new(
local_key: &Keypair,
net_conf: &NetworkConfig,
@@ -114,12 +116,12 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, T
}
/// Obtain a reference to the discovery protocol.
pub fn discovery(&self) -> &Discovery<TSubstream, TSpec> {
pub fn discovery(&self) -> &Discovery<TSpec> {
&self.discovery
}
/// Obtain a reference to the gossipsub protocol.
pub fn gs(&self) -> &Gossipsub<TSubstream> {
pub fn gs(&self) -> &Gossipsub {
&self.gossipsub
}
@@ -304,8 +306,10 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, T
};
let event = if is_request {
debug!(self.log, "Sending Ping"; "request_id" => id, "peer_id" => peer_id.to_string());
RPCEvent::Request(id, RPCRequest::Ping(ping))
} else {
debug!(self.log, "Sending Pong"; "request_id" => id, "peer_id" => peer_id.to_string());
RPCEvent::Response(id, RPCCodedResponse::Success(RPCResponse::Pong(ping)))
};
self.send_rpc(peer_id, event);
@@ -326,12 +330,50 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, T
);
self.send_rpc(peer_id, metadata_response);
}
/// Returns a reference to the peer manager to allow the swarm to notify the manager of peer
/// status
pub fn peer_manager(&mut self) -> &mut PeerManager<TSpec> {
&mut self.peer_manager
}
/* Address in the new behaviour. Connections are now maintained at the swarm level.
/// Notifies the behaviour that a peer has connected.
pub fn notify_peer_connect(&mut self, peer_id: PeerId, endpoint: ConnectedPoint) {
match endpoint {
ConnectedPoint::Dialer { .. } => self.peer_manager.connect_outgoing(&peer_id),
ConnectedPoint::Listener { .. } => self.peer_manager.connect_ingoing(&peer_id),
};
// Find ENR info about a peer if possible.
if let Some(enr) = self.discovery.enr_of_peer(&peer_id) {
let bitfield = match enr.bitfield::<TSpec>() {
Ok(v) => v,
Err(e) => {
warn!(self.log, "Peer has invalid ENR bitfield";
"peer_id" => format!("{}", peer_id),
"error" => format!("{:?}", e));
return;
}
};
// use this as a baseline, until we get the actual meta-data
let meta_data = MetaData {
seq_number: 0,
attnets: bitfield,
};
// TODO: Shift to the peer manager
self.network_globals
.peers
.write()
.add_metadata(&peer_id, meta_data);
}
}
*/
}
// Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for Behaviour
impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>
NetworkBehaviourEventProcess<GossipsubEvent> for Behaviour<TSubstream, TSpec>
{
impl<TSpec: EthSpec> NetworkBehaviourEventProcess<GossipsubEvent> for Behaviour<TSpec> {
fn inject_event(&mut self, event: GossipsubEvent) {
match event {
GossipsubEvent::Message(propagation_source, id, gs_msg) => {
@@ -358,7 +400,7 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>
debug!(self.log, "Could not decode gossipsub message"; "error" => format!("{}", e))
}
Ok(msg) => {
crit!(self.log, "A duplicate gossipsub message was received"; "message_source" => format!("{}", gs_msg.source), "propagated_peer" => format!("{}",propagation_source), "message" => format!("{}", msg));
debug!(self.log, "A duplicate gossipsub message was received"; "message_source" => format!("{}", gs_msg.source), "propagated_peer" => format!("{}",propagation_source), "message" => format!("{}", msg));
}
}
}
@@ -372,112 +414,66 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>
}
}
impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>
NetworkBehaviourEventProcess<RPCMessage<TSpec>> for Behaviour<TSubstream, TSpec>
{
fn inject_event(&mut self, event: RPCMessage<TSpec>) {
match event {
// TODO: These are temporary methods to give access to injected behaviour
// events to the
// peer manager. After a behaviour re-write remove these:
RPCMessage::PeerConnectedHack(peer_id, connected_point) => {
match connected_point {
ConnectedPoint::Dialer { .. } => self.peer_manager.connect_outgoing(&peer_id),
ConnectedPoint::Listener { .. } => self.peer_manager.connect_ingoing(&peer_id),
};
// Find ENR info about a peer if possible.
if let Some(enr) = self.discovery.enr_of_peer(&peer_id) {
let bitfield = match enr.bitfield::<TSpec>() {
Ok(v) => v,
Err(e) => {
warn!(self.log, "Peer has invalid ENR bitfield";
"peer_id" => format!("{}", peer_id),
"error" => format!("{:?}", e));
return;
}
};
// use this as a baseline, until we get the actual meta-data
let meta_data = MetaData {
seq_number: 0,
attnets: bitfield,
};
// TODO: Shift to the peer manager
self.network_globals
.peers
.write()
.add_metadata(&peer_id, meta_data);
}
impl<TSpec: EthSpec> NetworkBehaviourEventProcess<RPCMessage<TSpec>> for Behaviour<TSpec> {
fn inject_event(&mut self, message: RPCMessage<TSpec>) {
let peer_id = message.peer_id;
// The METADATA and PING RPC responses are handled within the behaviour and not
// propagated
// TODO: Improve the RPC types to better handle this logic discrepancy
match message.event {
RPCEvent::Request(id, RPCRequest::Ping(ping)) => {
// inform the peer manager and send the response
self.peer_manager.ping_request(&peer_id, ping.data);
// send a ping response
self.send_ping(id, peer_id, false);
}
RPCMessage::PeerDisconnectedHack(peer_id, _connected_point) => {
self.peer_manager.notify_disconnect(&peer_id)
RPCEvent::Request(id, RPCRequest::MetaData(_)) => {
// send the requested meta-data
self.send_meta_data_response(id, peer_id);
}
RPCMessage::PeerDialed(peer_id) => {
self.events.push(BehaviourEvent::PeerDialed(peer_id))
RPCEvent::Response(_, RPCCodedResponse::Success(RPCResponse::Pong(ping))) => {
self.peer_manager.pong_response(&peer_id, ping.data);
}
RPCMessage::PeerDisconnected(peer_id) => {
self.events.push(BehaviourEvent::PeerDisconnected(peer_id))
RPCEvent::Response(_, RPCCodedResponse::Success(RPCResponse::MetaData(meta_data))) => {
self.peer_manager.meta_data_response(&peer_id, meta_data);
}
RPCMessage::RPC(peer_id, rpc_event) => {
// The METADATA and PING RPC responses are handled within the behaviour and not
// propagated
// TODO: Improve the RPC types to better handle this logic discrepancy
match rpc_event {
RPCEvent::Request(id, RPCRequest::Ping(ping)) => {
// inform the peer manager and send the response
self.peer_manager.ping_request(&peer_id, ping.data);
// send a ping response
self.send_ping(id, peer_id, false);
}
RPCEvent::Request(id, RPCRequest::MetaData(_)) => {
// send the requested meta-data
self.send_meta_data_response(id, peer_id);
}
RPCEvent::Response(_, RPCCodedResponse::Success(RPCResponse::Pong(ping))) => {
self.peer_manager.pong_response(&peer_id, ping.data);
}
RPCEvent::Response(
_,
RPCCodedResponse::Success(RPCResponse::MetaData(meta_data)),
) => {
self.peer_manager.meta_data_response(&peer_id, meta_data);
}
RPCEvent::Request(_, RPCRequest::Status(_))
| RPCEvent::Response(_, RPCCodedResponse::Success(RPCResponse::Status(_))) => {
// inform the peer manager that we have received a status from a peer
self.peer_manager.peer_statusd(&peer_id);
// propagate the STATUS message upwards
self.events.push(BehaviourEvent::RPC(peer_id, rpc_event));
}
RPCEvent::Error(_, protocol, ref err) => {
self.peer_manager.handle_rpc_error(&peer_id, protocol, err);
self.events.push(BehaviourEvent::RPC(peer_id, rpc_event));
}
_ => {
// propagate all other RPC messages upwards
self.events.push(BehaviourEvent::RPC(peer_id, rpc_event))
}
}
RPCEvent::Request(_, RPCRequest::Status(_))
| RPCEvent::Response(_, RPCCodedResponse::Success(RPCResponse::Status(_))) => {
// inform the peer manager that we have received a status from a peer
self.peer_manager.peer_statusd(&peer_id);
// propagate the STATUS message upwards
self.events
.push(BehaviourEvent::RPC(peer_id, message.event));
}
RPCEvent::Error(_, protocol, ref err) => {
self.peer_manager.handle_rpc_error(&peer_id, protocol, err);
self.events
.push(BehaviourEvent::RPC(peer_id, message.event));
}
_ => {
// propagate all other RPC messages upwards
self.events
.push(BehaviourEvent::RPC(peer_id, message.event))
}
}
}
}
impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, TSpec> {
impl<TSpec: EthSpec> Behaviour<TSpec> {
/// Consumes the events list when polled.
fn poll<TBehaviourIn>(
&mut self,
) -> Async<NetworkBehaviourAction<TBehaviourIn, BehaviourEvent<TSpec>>> {
cx: &mut Context,
_: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<TBehaviourIn, BehaviourEvent<TSpec>>> {
// check the peer manager for events
loop {
match self.peer_manager.poll() {
Ok(Async::Ready(Some(event))) => match event {
match self.peer_manager.poll_next_unpin(cx) {
Poll::Ready(Some(event)) => match event {
PeerManagerEvent::Status(peer_id) => {
// it's time to status. We don't keep a beacon chain reference here, so we inform
// the network to send a status to this peer
return Async::Ready(NetworkBehaviourAction::GenerateEvent(
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(
BehaviourEvent::StatusPeer(peer_id),
));
}
@@ -495,25 +491,20 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, T
//TODO: Implement
}
},
Ok(Async::NotReady) => break,
Ok(Async::Ready(None)) | Err(_) => {
crit!(self.log, "Error polling peer manager");
break;
}
Poll::Pending => break,
Poll::Ready(None) => break, // peer manager ended
}
}
if !self.events.is_empty() {
return Async::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0)));
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0)));
}
Async::NotReady
Poll::Pending
}
}
impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> NetworkBehaviourEventProcess<IdentifyEvent>
for Behaviour<TSubstream, TSpec>
{
impl<TSpec: EthSpec> NetworkBehaviourEventProcess<IdentifyEvent> for Behaviour<TSpec> {
fn inject_event(&mut self, event: IdentifyEvent) {
match event {
IdentifyEvent::Received {
@@ -545,9 +536,7 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> NetworkBehaviourEventPr
}
}
impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> NetworkBehaviourEventProcess<Discv5Event>
for Behaviour<TSubstream, TSpec>
{
impl<TSpec: EthSpec> NetworkBehaviourEventProcess<Discv5Event> for Behaviour<TSpec> {
fn inject_event(&mut self, _event: Discv5Event) {
// discv5 has no events to inject
}
@@ -558,11 +547,6 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> NetworkBehaviourEventPr
pub enum BehaviourEvent<TSpec: EthSpec> {
/// A received RPC event and the peer that it was received from.
RPC(PeerId, RPCEvent<TSpec>),
/// We have completed an initial connection to a new peer.
PeerDialed(PeerId),
/// A peer has disconnected.
PeerDisconnected(PeerId),
/// A gossipsub message has been received.
PubsubMessage {
/// The gossipsub message id. Used when propagating blocks after validation.
id: MessageId,

View File

@@ -1,6 +1,6 @@
use crate::types::GossipKind;
use crate::Enr;
use libp2p::discv5::{Discv5Config, Discv5ConfigBuilder};
use discv5::{Discv5Config, Discv5ConfigBuilder};
use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder, GossipsubMessage, MessageId};
use libp2p::Multiaddr;
use serde_derive::{Deserialize, Serialize};

View File

@@ -1,15 +1,15 @@
//! Helper functions and an extension trait for Ethereum 2 ENRs.
pub use libp2p::{core::identity::Keypair, discv5::enr::CombinedKey};
pub use discv5::enr::{self, CombinedKey, EnrBuilder};
pub use libp2p::core::identity::Keypair;
use super::ENR_FILENAME;
use crate::types::{Enr, EnrBitfield};
use crate::CombinedKeyExt;
use crate::NetworkConfig;
use libp2p::discv5::enr::EnrBuilder;
use slog::{debug, warn};
use ssz::{Decode, Encode};
use ssz_types::BitVector;
use std::convert::TryInto;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
@@ -62,10 +62,7 @@ pub fn build_or_load_enr<T: EthSpec>(
// 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, if the CLI doesn't expressly forbid it.
let enr_key: CombinedKey = local_key
.try_into()
.map_err(|_| "Invalid key type for ENR records")?;
let enr_key = CombinedKey::from_libp2p(&local_key)?;
let mut local_enr = build_enr::<T>(&enr_key, config, enr_fork_id)?;
let enr_f = config.network_dir.join(ENR_FILENAME);

View File

@@ -0,0 +1,190 @@
//! ENR extension trait to support libp2p integration.
use crate::{Enr, Multiaddr, PeerId};
use discv5::enr::{CombinedKey, CombinedPublicKey};
use libp2p::core::{identity::Keypair, identity::PublicKey, multiaddr::Protocol};
use tiny_keccak::{Hasher, Keccak};
/// Extend ENR for libp2p types.
pub trait EnrExt {
/// The libp2p `PeerId` for the record.
fn peer_id(&self) -> PeerId;
/// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp` or `udp` key **or** an `ip6` and either a `tcp6` or `udp6`.
/// The vector remains empty if these fields are not defined.
fn multiaddr(&self) -> Vec<Multiaddr>;
}
/// Extend ENR CombinedPublicKey for libp2p types.
pub trait CombinedKeyPublicExt {
/// Converts the publickey into a peer id, without consuming the key.
fn into_peer_id(&self) -> PeerId;
}
/// Extend ENR CombinedKey for conversion to libp2p keys.
pub trait CombinedKeyExt {
/// Converts a libp2p key into an ENR combined key.
fn from_libp2p(key: &libp2p::core::identity::Keypair) -> Result<CombinedKey, &'static str>;
}
impl EnrExt for Enr {
/// The libp2p `PeerId` for the record.
fn peer_id(&self) -> PeerId {
self.public_key().into_peer_id()
}
/// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp` or `udp` key **or** an `ip6` and either a `tcp6` or `udp6`.
/// The vector remains empty if these fields are not defined.
///
/// Note: Only available with the `libp2p` feature flag.
fn multiaddr(&self) -> Vec<Multiaddr> {
let mut multiaddrs: Vec<Multiaddr> = Vec::new();
if let Some(ip) = self.ip() {
if let Some(udp) = self.udp() {
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Udp(udp));
multiaddrs.push(multiaddr);
}
if let Some(tcp) = self.tcp() {
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Tcp(tcp));
multiaddrs.push(multiaddr);
}
}
if let Some(ip6) = self.ip6() {
if let Some(udp6) = self.udp6() {
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Udp(udp6));
multiaddrs.push(multiaddr);
}
if let Some(tcp6) = self.tcp6() {
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Tcp(tcp6));
multiaddrs.push(multiaddr);
}
}
multiaddrs
}
}
impl CombinedKeyPublicExt for CombinedPublicKey {
/// Converts the publickey into a peer id, without consuming the key.
///
/// This is only available with the `libp2p` feature flag.
fn into_peer_id(&self) -> PeerId {
match self {
Self::Secp256k1(pk) => {
let pk_bytes = pk.serialize_compressed();
let libp2p_pk = libp2p::core::PublicKey::Secp256k1(
libp2p::core::identity::secp256k1::PublicKey::decode(&pk_bytes)
.expect("valid public key"),
);
PeerId::from_public_key(libp2p_pk)
}
Self::Ed25519(pk) => {
let pk_bytes = pk.to_bytes();
let libp2p_pk = libp2p::core::PublicKey::Ed25519(
libp2p::core::identity::ed25519::PublicKey::decode(&pk_bytes)
.expect("valid public key"),
);
PeerId::from_public_key(libp2p_pk)
}
}
}
}
impl CombinedKeyExt for CombinedKey {
fn from_libp2p(key: &libp2p::core::identity::Keypair) -> Result<CombinedKey, &'static str> {
match key {
Keypair::Secp256k1(key) => {
let secret = discv5::enr::secp256k1::SecretKey::parse(&key.secret().to_bytes())
.expect("libp2p key must be valid");
Ok(CombinedKey::Secp256k1(secret))
}
Keypair::Ed25519(key) => {
let ed_keypair =
discv5::enr::ed25519_dalek::SecretKey::from_bytes(&key.encode()[..32])
.expect("libp2p key must be valid");
Ok(CombinedKey::from(ed_keypair))
}
_ => Err("ENR: Unsupported libp2p key type"),
}
}
}
// helper function to convert a peer_id to a node_id. This is only possible for secp256k1/ed25519 libp2p
// peer_ids
pub fn peer_id_to_node_id(peer_id: &PeerId) -> Result<discv5::enr::NodeId, String> {
// A libp2p peer id byte representation should be 2 length bytes + 4 protobuf bytes + compressed pk bytes
// if generated from a PublicKey with Identity multihash.
let pk_bytes = &peer_id.as_bytes()[2..];
match PublicKey::from_protobuf_encoding(pk_bytes).map_err(|e| {
format!(
" Cannot parse libp2p public key public key from peer id: {}",
e
)
})? {
PublicKey::Secp256k1(pk) => {
let uncompressed_key_bytes = &pk.encode_uncompressed()[1..];
let mut output = [0_u8; 32];
let mut hasher = Keccak::v256();
hasher.update(&uncompressed_key_bytes);
hasher.finalize(&mut output);
return Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length"));
}
PublicKey::Ed25519(pk) => {
let uncompressed_key_bytes = pk.encode();
let mut output = [0_u8; 32];
let mut hasher = Keccak::v256();
hasher.update(&uncompressed_key_bytes);
hasher.finalize(&mut output);
return Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length"));
}
_ => return Err("Unsupported public key".into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_secp256k1_peer_id_conversion() {
let sk_hex = "df94a73d528434ce2309abb19c16aedb535322797dbd59c157b1e04095900f48";
let sk_bytes = hex::decode(sk_hex).unwrap();
let secret_key = discv5::enr::secp256k1::SecretKey::parse_slice(&sk_bytes).unwrap();
let libp2p_sk = libp2p::identity::secp256k1::SecretKey::from_bytes(sk_bytes).unwrap();
let secp256k1_kp: libp2p::identity::secp256k1::Keypair = libp2p_sk.into();
let libp2p_kp = Keypair::Secp256k1(secp256k1_kp);
let peer_id = libp2p_kp.public().into_peer_id();
let enr = discv5::enr::EnrBuilder::new("v4")
.build(&secret_key)
.unwrap();
let node_id = peer_id_to_node_id(&peer_id).unwrap();
assert_eq!(enr.node_id(), node_id);
}
#[test]
fn test_ed25519_peer_conversion() {
let sk_hex = "4dea8a5072119927e9d243a7d953f2f4bc95b70f110978e2f9bc7a9000e4b261";
let sk_bytes = hex::decode(sk_hex).unwrap();
let secret = discv5::enr::ed25519_dalek::SecretKey::from_bytes(&sk_bytes).unwrap();
let public = discv5::enr::ed25519_dalek::PublicKey::from(&secret);
let keypair = discv5::enr::ed25519_dalek::Keypair { public, secret };
let libp2p_sk = libp2p::identity::ed25519::SecretKey::from_bytes(sk_bytes).unwrap();
let ed25519_kp: libp2p::identity::ed25519::Keypair = libp2p_sk.into();
let libp2p_kp = Keypair::Ed25519(ed25519_kp);
let peer_id = libp2p_kp.public().into_peer_id();
let enr = discv5::enr::EnrBuilder::new("v4").build(&keypair).unwrap();
let node_id = peer_id_to_node_id(&peer_id).unwrap();
assert_eq!(enr.node_id(), node_id);
}
}

View File

@@ -1,28 +1,35 @@
///! This manages the discovery and management of peers.
pub(crate) mod enr;
pub mod enr_ext;
// Allow external use of the lighthouse ENR builder
pub use enr::{build_enr, CombinedKey, Keypair};
pub use enr_ext::{CombinedKeyExt, EnrExt};
use crate::metrics;
use crate::{error, Enr, NetworkConfig, NetworkGlobals};
use discv5::{enr::NodeId, Discv5, Discv5Event};
use enr::{Eth2Enr, BITFIELD_ENR_KEY, ETH2_ENR_KEY};
use futures::prelude::*;
use libp2p::core::{ConnectedPoint, Multiaddr, PeerId};
use libp2p::discv5::enr::NodeId;
use libp2p::discv5::{Discv5, Discv5Event};
use libp2p::core::{connection::ConnectionId, Multiaddr, PeerId};
use libp2p::multiaddr::Protocol;
use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters, ProtocolsHandler};
use libp2p::swarm::{
protocols_handler::DummyProtocolsHandler, DialPeerCondition, NetworkBehaviour,
NetworkBehaviourAction, PollParameters, ProtocolsHandler,
};
use lru::LruCache;
use slog::{crit, debug, info, warn};
use ssz::{Decode, Encode};
use ssz_types::BitVector;
use std::collections::{HashSet, VecDeque};
use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::timer::Delay;
use std::{
collections::{HashSet, VecDeque},
net::SocketAddr,
path::Path,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use tokio::time::{delay_until, Delay, Instant};
use types::{EnrForkId, EthSpec, SubnetId};
/// Maximum seconds before searching for extra peers.
@@ -36,10 +43,13 @@ const TARGET_SUBNET_PEERS: u64 = 3;
/// Lighthouse discovery behaviour. This provides peer management and discovery using the Discv5
/// libp2p protocol.
pub struct Discovery<TSubstream, TSpec: EthSpec> {
pub struct Discovery<TSpec: EthSpec> {
/// Events to be processed by the behaviour.
events: VecDeque<NetworkBehaviourAction<void::Void, Discv5Event>>,
/// A collection of seen live ENRs for quick lookup and to map peer-id's to ENRs.
cached_enrs: LruCache<PeerId, Enr>,
/// The currently banned peers.
banned_peers: HashSet<PeerId>,
@@ -62,7 +72,7 @@ pub struct Discovery<TSubstream, TSpec: EthSpec> {
tcp_port: u16,
/// The discovery behaviour used to discover new peers.
discovery: Discv5<TSubstream>,
discovery: Discv5,
/// A collection of network constants that can be read from other threads.
network_globals: Arc<NetworkGlobals<TSpec>>,
@@ -71,7 +81,7 @@ pub struct Discovery<TSubstream, TSpec: EthSpec> {
log: slog::Logger,
}
impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
impl<TSpec: EthSpec> Discovery<TSpec> {
pub fn new(
local_key: &Keypair,
config: &NetworkConfig,
@@ -91,9 +101,12 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
let listen_socket = SocketAddr::new(config.listen_address, config.discovery_port);
// convert the keypair into an ENR key
let enr_key: CombinedKey = CombinedKey::from_libp2p(&local_key)?;
let mut discovery = Discv5::new(
local_enr,
local_key.clone(),
enr_key,
config.discv5_config.clone(),
listen_socket,
)
@@ -121,9 +134,10 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
Ok(Self {
events: VecDeque::with_capacity(16),
cached_enrs: LruCache::new(50),
banned_peers: HashSet::new(),
max_peers: config.max_peers,
peer_discovery_delay: Delay::new(Instant::now()),
peer_discovery_delay: delay_until(Instant::now()),
past_discovery_delay: INITIAL_SEARCH_DELAY,
tcp_port: config.libp2p_port,
discovery,
@@ -147,6 +161,9 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
/// Add an ENR to the routing table of the discovery mechanism.
pub fn add_enr(&mut self, enr: Enr) {
// add the enr to seen caches
self.cached_enrs.put(enr.peer_id(), enr.clone());
let _ = self.discovery.add_enr(enr).map_err(|e| {
warn!(
self.log,
@@ -174,7 +191,18 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
/// Returns the ENR of a known peer if it exists.
pub fn enr_of_peer(&mut self, peer_id: &PeerId) -> Option<Enr> {
self.discovery.enr_of_peer(peer_id)
// first search the local cache
if let Some(enr) = self.cached_enrs.get(peer_id) {
return Some(enr.clone());
}
// not in the local cache, look in the routing table
if let Ok(_node_id) = enr_ext::peer_id_to_node_id(peer_id) {
// TODO: Need to update discv5
// self.discovery.find_enr(&node_id)
return None;
} else {
return None;
}
}
/// Adds/Removes a subnet from the ENR Bitfield
@@ -342,48 +370,58 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
}
}
// Redirect all behaviour events to underlying discovery behaviour.
impl<TSubstream, TSpec: EthSpec> NetworkBehaviour for Discovery<TSubstream, TSpec>
where
TSubstream: AsyncRead + AsyncWrite,
{
type ProtocolsHandler = <Discv5<TSubstream> as NetworkBehaviour>::ProtocolsHandler;
type OutEvent = <Discv5<TSubstream> as NetworkBehaviour>::OutEvent;
// Build a dummy Network behaviour around the discv5 server
impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
type ProtocolsHandler = DummyProtocolsHandler;
type OutEvent = Discv5Event;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
NetworkBehaviour::new_handler(&mut self.discovery)
DummyProtocolsHandler::default()
}
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
// Let discovery track possible known peers.
self.discovery.addresses_of_peer(peer_id)
if let Some(enr) = self.enr_of_peer(peer_id) {
// ENR's may have multiple Multiaddrs. The multi-addr associated with the UDP
// port is removed, which is assumed to be associated with the discv5 protocol (and
// therefore irrelevant for other libp2p components).
let mut out_list = enr.multiaddr();
out_list.retain(|addr| {
addr.iter()
.find(|v| match v {
Protocol::Udp(_) => true,
_ => false,
})
.is_none()
});
out_list
} else {
// PeerId is not known
Vec::new()
}
}
fn inject_connected(&mut self, _peer_id: PeerId, _endpoint: ConnectedPoint) {}
// ignore libp2p connections/streams
fn inject_connected(&mut self, _: &PeerId) {}
fn inject_disconnected(&mut self, _peer_id: &PeerId, _endpoint: ConnectedPoint) {}
// ignore libp2p connections/streams
fn inject_disconnected(&mut self, _: &PeerId) {}
fn inject_replaced(
// no libp2p discv5 events - event originate from the session_service.
fn inject_event(
&mut self,
_peer_id: PeerId,
_closed: ConnectedPoint,
_opened: ConnectedPoint,
) {
// discv5 doesn't implement
}
fn inject_node_event(
&mut self,
_peer_id: PeerId,
_: PeerId,
_: ConnectionId,
_event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) {
// discv5 doesn't implement
void::unreachable(_event)
}
fn poll(
&mut self,
params: &mut impl PollParameters,
) -> Async<
cx: &mut Context,
_: &mut impl PollParameters,
) -> Poll<
NetworkBehaviourAction<
<Self::ProtocolsHandler as ProtocolsHandler>::InEvent,
Self::OutEvent,
@@ -391,8 +429,8 @@ where
> {
// search for peers if it is time
loop {
match self.peer_discovery_delay.poll() {
Ok(Async::Ready(_)) => {
match self.peer_discovery_delay.poll_unpin(cx) {
Poll::Ready(_) => {
if self.network_globals.connected_peers() < self.max_peers {
self.find_peers();
}
@@ -401,17 +439,14 @@ where
Instant::now() + Duration::from_secs(MAX_TIME_BETWEEN_PEER_SEARCHES),
);
}
Ok(Async::NotReady) => break,
Err(e) => {
warn!(self.log, "Discovery peer search failed"; "error" => format!("{:?}", e));
}
Poll::Pending => break,
}
}
// Poll discovery
loop {
match self.discovery.poll(params) {
Async::Ready(NetworkBehaviourAction::GenerateEvent(event)) => {
match self.discovery.poll_next_unpin(cx) {
Poll::Ready(Some(event)) => {
match event {
Discv5Event::Discovered(_enr) => {
// peers that get discovered during a query but are not contactable or
@@ -434,7 +469,7 @@ where
let enr = self.discovery.local_enr();
enr::save_enr_to_disk(Path::new(&self.enr_dir), enr, &self.log);
return Async::Ready(NetworkBehaviourAction::ReportObservedAddr {
return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr {
address,
});
}
@@ -451,9 +486,12 @@ where
self.peer_discovery_delay
.reset(Instant::now() + Duration::from_secs(delay));
for peer_id in closer_peers {
// if we need more peers, attempt a connection
for enr in closer_peers {
// cache known peers
let peer_id = enr.peer_id();
self.cached_enrs.put(enr.peer_id(), enr);
// if we need more peers, attempt a connection
if self.network_globals.connected_or_dialing_peers()
< self.max_peers
&& !self
@@ -463,10 +501,18 @@ where
.is_connected_or_dialing(&peer_id)
&& !self.banned_peers.contains(&peer_id)
{
debug!(self.log, "Connecting to discovered peer"; "peer_id"=> format!("{:?}", peer_id));
self.network_globals.peers.write().dialing_peer(&peer_id);
self.events
.push_back(NetworkBehaviourAction::DialPeer { peer_id });
// TODO: Debugging only
// NOTE: The peer manager will get updated by the global swarm.
let connection_status = self
.network_globals
.peers
.read()
.connection_status(&peer_id);
debug!(self.log, "Connecting to discovered peer"; "peer_id"=> peer_id.to_string(), "status" => format!("{:?}", connection_status));
self.events.push_back(NetworkBehaviourAction::DialPeer {
peer_id,
condition: DialPeerCondition::Disconnected,
});
}
}
}
@@ -474,16 +520,16 @@ where
}
}
// discv5 does not output any other NetworkBehaviourAction
Async::Ready(_) => {}
Async::NotReady => break,
Poll::Ready(_) => {}
Poll::Pending => break,
}
}
// process any queued events
if let Some(event) = self.events.pop_front() {
return Async::Ready(event);
return Poll::Ready(event);
}
Async::NotReady
Poll::Pending
}
}

View File

@@ -17,9 +17,10 @@ pub mod types;
pub use crate::types::{error, Enr, GossipTopic, NetworkGlobals, PubsubMessage};
pub use behaviour::BehaviourEvent;
pub use config::Config as NetworkConfig;
pub use discovery::enr_ext::{CombinedKeyExt, EnrExt};
pub use libp2p::gossipsub::{MessageId, Topic, TopicHash};
pub use libp2p::{core::ConnectedPoint, PeerId, Swarm};
pub use libp2p::{multiaddr, Multiaddr};
pub use libp2p::{PeerId, Swarm};
pub use peer_manager::{PeerDB, PeerInfo, PeerSyncStatus, SyncInfo};
pub use peer_manager::{client::Client, PeerDB, PeerInfo, PeerSyncStatus, SyncInfo};
pub use rpc::RPCEvent;
pub use service::{Service, NETWORK_KEY_FILENAME};
pub use service::{Libp2pEvent, Service, NETWORK_KEY_FILENAME};

View File

@@ -131,6 +131,18 @@ fn client_from_agent_version(agent_version: &str) -> (ClientKind, String, String
let unknown = String::from("unknown");
(kind, unknown.clone(), unknown)
}
Some("nim-libp2p") => {
let kind = ClientKind::Nimbus;
let mut version = String::from("unknown");
let mut os_version = version.clone();
if let Some(agent_version) = agent_split.next() {
version = agent_version.into();
if let Some(agent_os_version) = agent_split.next() {
os_version = agent_os_version.into();
}
}
(kind, version, os_version)
}
_ => {
let unknown = String::from("unknown");
(ClientKind::Unknown, unknown.clone(), unknown)

View File

@@ -6,16 +6,18 @@ use crate::rpc::{MetaData, Protocol, RPCError, RPCResponseErrorCode};
use crate::{NetworkGlobals, PeerId};
use futures::prelude::*;
use futures::Stream;
use hashmap_delay::HashSetDelay;
use hashset_delay::HashSetDelay;
use libp2p::identify::IdentifyInfo;
use slog::{crit, debug, error, warn};
use smallvec::SmallVec;
use std::convert::TryInto;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use types::EthSpec;
mod client;
pub mod client;
mod peer_info;
mod peer_sync_status;
mod peerdb;
@@ -24,7 +26,7 @@ pub use peer_info::{PeerConnectionStatus::*, PeerInfo};
pub use peer_sync_status::{PeerSyncStatus, SyncInfo};
/// The minimum reputation before a peer is disconnected.
// Most likely this needs tweaking.
const MIN_REP_BEFORE_BAN: Rep = 10;
const _MIN_REP_BEFORE_BAN: Rep = 10;
/// The time in seconds between re-status's peers.
const STATUS_INTERVAL: u64 = 300;
/// The time in seconds between PING events. We do not send a ping if the other peer as PING'd us within
@@ -42,7 +44,7 @@ pub struct PeerManager<TSpec: EthSpec> {
/// A collection of peers awaiting to be Status'd.
status_peers: HashSetDelay<PeerId>,
/// Last updated moment.
last_updated: Instant,
_last_updated: Instant,
/// The logger associated with the `PeerManager`.
log: slog::Logger,
}
@@ -104,7 +106,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
PeerManager {
network_globals,
events: SmallVec::new(),
last_updated: Instant::now(),
_last_updated: Instant::now(),
ping_peers: HashSetDelay::new(Duration::from_secs(PING_INTERVAL)),
status_peers: HashSetDelay::new(Duration::from_secs(STATUS_INTERVAL)),
log: log.clone(),
@@ -123,7 +125,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
debug!(self.log, "Received a ping request"; "peer_id" => peer_id.to_string(), "seq_no" => seq);
self.ping_peers.insert(peer_id.clone());
// if the sequence number is unknown send update the meta data of the peer.
// if the sequence number is unknown send an update the meta data of the peer.
if let Some(meta_data) = &peer_info.meta_data {
if meta_data.seq_number < seq {
debug!(self.log, "Requesting new metadata from peer";
@@ -180,9 +182,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
"peer_id" => peer_id.to_string(), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
peer_info.meta_data = Some(meta_data);
} else {
// TODO: isn't this malicious/random behaviour? What happens if the seq_number
// is the same but the contents differ?
warn!(self.log, "Received old metadata";
debug!(self.log, "Received old metadata";
"peer_id" => peer_id.to_string(), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
}
} else {
@@ -204,11 +204,8 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
/// Updates the state of the peer as disconnected.
pub fn notify_disconnect(&mut self, peer_id: &PeerId) {
self.update_reputations();
{
let mut peerdb = self.network_globals.peers.write();
peerdb.disconnect(peer_id);
}
//self.update_reputations();
self.network_globals.peers.write().disconnect(peer_id);
// remove the ping and status timer for the peer
self.ping_peers.remove(peer_id);
@@ -223,25 +220,31 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
/// Sets a peer as connected as long as their reputation allows it
/// Informs if the peer was accepted
pub fn connect_ingoing(&mut self, peer_id: &PeerId) -> bool {
self.connect_peer(peer_id, false)
self.connect_peer(peer_id, ConnectingType::IngoingConnected)
}
/// Sets a peer as connected as long as their reputation allows it
/// Informs if the peer was accepted
pub fn connect_outgoing(&mut self, peer_id: &PeerId) -> bool {
self.connect_peer(peer_id, true)
self.connect_peer(peer_id, ConnectingType::OutgoingConnected)
}
/// Updates the database informing that a peer is being dialed.
pub fn dialing_peer(&mut self, peer_id: &PeerId) -> bool {
self.connect_peer(peer_id, ConnectingType::Dialing)
}
/// Reports a peer for some action.
///
/// If the peer doesn't exist, log a warning and insert defaults.
pub fn report_peer(&mut self, peer_id: &PeerId, action: PeerAction) {
self.update_reputations();
//TODO: Check these. There are double disconnects for example
// self.update_reputations();
self.network_globals
.peers
.write()
.add_reputation(peer_id, action.rep_change());
self.update_reputations();
// self.update_reputations();
}
/// Updates `PeerInfo` with `identify` information.
@@ -255,7 +258,14 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
}
pub fn handle_rpc_error(&mut self, peer_id: &PeerId, protocol: Protocol, err: &RPCError) {
debug!(self.log, "RPCError"; "protocol" => protocol.to_string(), "err" => err.to_string());
let client = self
.network_globals
.peers
.read()
.peer_info(peer_id)
.map(|info| info.client.clone())
.unwrap_or_default();
debug!(self.log, "RPCError"; "protocol" => protocol.to_string(), "err" => err.to_string(), "client" => client.to_string());
// Map this error to a `PeerAction` (if any)
let peer_action = match err {
@@ -321,21 +331,23 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
///
/// This informs if the peer was accepted in to the db or not.
// TODO: Drop peers if over max_peer limit
fn connect_peer(&mut self, peer_id: &PeerId, outgoing: bool) -> bool {
fn connect_peer(&mut self, peer_id: &PeerId, connection: ConnectingType) -> bool {
// TODO: remove after timed updates
self.update_reputations();
//self.update_reputations();
{
let mut peerdb = self.network_globals.peers.write();
if peerdb.connection_status(peer_id).map(|c| c.is_banned()) == Some(true) {
// don't connect if the peer is banned
return false;
// TODO: Handle this case. If peer is banned this shouldn't be reached. It will put
// our connection/disconnection out of sync with libp2p
// return false;
}
if outgoing {
peerdb.connect_outgoing(peer_id);
} else {
peerdb.connect_ingoing(peer_id);
match connection {
ConnectingType::Dialing => peerdb.dialing_peer(peer_id),
ConnectingType::IngoingConnected => peerdb.connect_outgoing(peer_id),
ConnectingType::OutgoingConnected => peerdb.connect_ingoing(peer_id),
}
}
@@ -366,10 +378,10 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
///
/// A banned(disconnected) peer that gets its rep above(below) MIN_REP_BEFORE_BAN is
/// now considered a disconnected(banned) peer.
fn update_reputations(&mut self) {
fn _update_reputations(&mut self) {
// avoid locking the peerdb too often
// TODO: call this on a timer
if self.last_updated.elapsed().as_secs() < 30 {
if self._last_updated.elapsed().as_secs() < 30 {
return;
}
@@ -382,7 +394,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
/* Check how long have peers been in this state and update their reputations if needed */
let mut pdb = self.network_globals.peers.write();
for (id, info) in pdb.peers_mut() {
for (id, info) in pdb._peers_mut() {
// Update reputations
match info.connection_status {
Connected { .. } => {
@@ -398,7 +410,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
.as_secs()
/ 3600;
let last_dc_hours = self
.last_updated
._last_updated
.checked_duration_since(since)
.unwrap_or_else(|| Duration::from_secs(0))
.as_secs()
@@ -423,12 +435,13 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
// TODO: decide how to handle this
}
}
Unknown => {} //TODO: Handle this case
}
// Check if the peer gets banned or unbanned and if it should be disconnected
if info.reputation < MIN_REP_BEFORE_BAN && !info.connection_status.is_banned() {
if info.reputation < _MIN_REP_BEFORE_BAN && !info.connection_status.is_banned() {
// This peer gets banned. Check if we should request disconnection
ban_queue.push(id.clone());
} else if info.reputation >= MIN_REP_BEFORE_BAN && info.connection_status.is_banned() {
} else if info.reputation >= _MIN_REP_BEFORE_BAN && info.connection_status.is_banned() {
// This peer gets unbanned
unban_queue.push(id.clone());
}
@@ -444,57 +457,56 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
pdb.disconnect(&id);
}
self.last_updated = Instant::now();
self._last_updated = Instant::now();
}
}
impl<TSpec: EthSpec> Stream for PeerManager<TSpec> {
type Item = PeerManagerEvent;
type Error = ();
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// poll the timeouts for pings and status'
// TODO: Remove task notifies and temporary vecs for stable futures
// These exist to handle a bug in delayqueue
let mut peers_to_add = Vec::new();
while let Async::Ready(Some(peer_id)) = self.ping_peers.poll().map_err(|e| {
error!(self.log, "Failed to check for peers to ping"; "error" => e.to_string());
})? {
debug!(self.log, "Pinging peer"; "peer_id" => peer_id.to_string());
// add the ping timer back
peers_to_add.push(peer_id.clone());
self.events.push(PeerManagerEvent::Ping(peer_id));
loop {
match self.ping_peers.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(peer_id))) => {
self.ping_peers.insert(peer_id.clone());
self.events.push(PeerManagerEvent::Ping(peer_id));
}
Poll::Ready(Some(Err(e))) => {
error!(self.log, "Failed to check for peers to ping"; "error" => format!("{}",e))
}
Poll::Ready(None) | Poll::Pending => break,
}
}
if !peers_to_add.is_empty() {
futures::task::current().notify();
}
while let Some(peer) = peers_to_add.pop() {
self.ping_peers.insert(peer);
}
while let Async::Ready(Some(peer_id)) = self.status_peers.poll().map_err(|e| {
error!(self.log, "Failed to check for peers to status"; "error" => e.to_string());
})? {
debug!(self.log, "Sending Status to peer"; "peer_id" => peer_id.to_string());
// add the status timer back
peers_to_add.push(peer_id.clone());
self.events.push(PeerManagerEvent::Status(peer_id));
}
if !peers_to_add.is_empty() {
futures::task::current().notify();
}
while let Some(peer) = peers_to_add.pop() {
self.status_peers.insert(peer);
loop {
match self.status_peers.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(peer_id))) => {
self.status_peers.insert(peer_id.clone());
self.events.push(PeerManagerEvent::Status(peer_id))
}
Poll::Ready(Some(Err(e))) => {
error!(self.log, "Failed to check for peers to ping"; "error" => format!("{}",e))
}
Poll::Ready(None) | Poll::Pending => break,
}
}
if !self.events.is_empty() {
return Ok(Async::Ready(Some(self.events.remove(0))));
return Poll::Ready(Some(self.events.remove(0)));
} else {
self.events.shrink_to_fit();
}
Ok(Async::NotReady)
Poll::Pending
}
}
enum ConnectingType {
/// We are in the process of dialing this peer.
Dialing,
/// A peer has dialed us.
IngoingConnected,
/// We have successfully dialed a peer.
OutgoingConnected,
}

View File

@@ -100,6 +100,8 @@ pub enum PeerConnectionStatus {
/// time since we last communicated with the peer.
since: Instant,
},
/// The connection status has not been specified.
Unknown,
}
/// Serialization for http requests.
@@ -127,15 +129,14 @@ impl Serialize for PeerConnectionStatus {
s.serialize_field("since", &since.elapsed().as_secs())?;
s.end()
}
Unknown => serializer.serialize_unit_variant("", 4, "Unknown"),
}
}
}
impl Default for PeerConnectionStatus {
fn default() -> Self {
PeerConnectionStatus::Dialing {
since: Instant::now(),
}
PeerConnectionStatus::Unknown
}
}
@@ -177,7 +178,7 @@ impl PeerConnectionStatus {
pub fn connect_ingoing(&mut self) {
match self {
Connected { n_in, .. } => *n_in += 1,
Disconnected { .. } | Banned { .. } | Dialing { .. } => {
Disconnected { .. } | Banned { .. } | Dialing { .. } | Unknown => {
*self = Connected { n_in: 1, n_out: 0 }
}
}
@@ -188,7 +189,7 @@ impl PeerConnectionStatus {
pub fn connect_outgoing(&mut self) {
match self {
Connected { n_out, .. } => *n_out += 1,
Disconnected { .. } | Banned { .. } | Dialing { .. } => {
Disconnected { .. } | Banned { .. } | Dialing { .. } | Unknown => {
*self = Connected { n_in: 0, n_out: 1 }
}
}

View File

@@ -2,8 +2,8 @@ use super::peer_info::{PeerConnectionStatus, PeerInfo};
use super::peer_sync_status::PeerSyncStatus;
use crate::rpc::methods::MetaData;
use crate::PeerId;
use slog::{crit, warn};
use std::collections::HashMap;
use slog::{crit, debug, warn};
use std::collections::{hash_map::Entry, HashMap};
use std::time::Instant;
use types::{EthSpec, SubnetId};
@@ -77,7 +77,7 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
}
/// Returns an iterator over all peers in the db.
pub(super) fn peers_mut(&mut self) -> impl Iterator<Item = (&PeerId, &mut PeerInfo<TSpec>)> {
pub(super) fn _peers_mut(&mut self) -> impl Iterator<Item = (&PeerId, &mut PeerInfo<TSpec>)> {
self.peers.iter_mut()
}
@@ -228,11 +228,12 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
let info = self.peers.entry(peer_id.clone()).or_default();
if info.connection_status.is_disconnected() {
self.n_dc -= 1;
self.n_dc = self.n_dc.saturating_sub(1);
}
info.connection_status = PeerConnectionStatus::Dialing {
since: Instant::now(),
};
debug!(self.log, "Peer dialing in db"; "peer_id" => peer_id.to_string(), "n_dc" => self.n_dc);
}
/// Sets a peer as connected with an ingoing connection.
@@ -240,9 +241,10 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
let info = self.peers.entry(peer_id.clone()).or_default();
if info.connection_status.is_disconnected() {
self.n_dc -= 1;
self.n_dc = self.n_dc.saturating_sub(1);
}
info.connection_status.connect_ingoing();
debug!(self.log, "Peer connected to db"; "peer_id" => peer_id.to_string(), "n_dc" => self.n_dc);
}
/// Sets a peer as connected with an outgoing connection.
@@ -250,9 +252,10 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
let info = self.peers.entry(peer_id.clone()).or_default();
if info.connection_status.is_disconnected() {
self.n_dc -= 1;
self.n_dc = self.n_dc.saturating_sub(1);
}
info.connection_status.connect_outgoing();
debug!(self.log, "Peer connected to db"; "peer_id" => peer_id.to_string(), "n_dc" => self.n_dc);
}
/// Sets the peer as disconnected. A banned peer remains banned
@@ -263,11 +266,11 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
"peer_id" => peer_id.to_string());
PeerInfo::default()
});
if !info.connection_status.is_disconnected() && !info.connection_status.is_banned() {
info.connection_status.disconnect();
self.n_dc += 1;
}
debug!(self.log, "Peer disconnected from db"; "peer_id" => peer_id.to_string(), "n_dc" => self.n_dc);
self.shrink_to_fit();
}
@@ -284,7 +287,7 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
.map(|(id, _)| id.clone())
.unwrap(); // should be safe since n_dc > MAX_DC_PEERS > 0
self.peers.remove(&to_drop);
self.n_dc -= 1;
self.n_dc = self.n_dc.saturating_sub(1);
}
}
@@ -297,8 +300,9 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
PeerInfo::default()
});
if info.connection_status.is_disconnected() {
self.n_dc -= 1;
self.n_dc = self.n_dc.saturating_sub(1);
}
debug!(self.log, "Peer banned"; "peer_id" => peer_id.to_string(), "n_dc" => self.n_dc);
info.connection_status.ban();
}
@@ -334,11 +338,14 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
/// upper (lower) bounds, it stays at the maximum (minimum) value.
pub(super) fn add_reputation(&mut self, peer_id: &PeerId, change: RepChange) {
let log_ref = &self.log;
let info = self.peers.entry(peer_id.clone()).or_insert_with(|| {
warn!(log_ref, "Adding to the reputation of an unknown peer";
"peer_id" => peer_id.to_string());
PeerInfo::default()
});
let info = match self.peers.entry(peer_id.clone()) {
Entry::Vacant(_) => {
warn!(log_ref, "Peer is unknown, no reputation change made";
"peer_id" => peer_id.to_string());
return;
}
Entry::Occupied(e) => e.into_mut(),
};
info.reputation = if change.is_good {
info.reputation.saturating_add(change.diff)

View File

@@ -4,10 +4,10 @@ use crate::rpc::{ErrorMessage, RPCCodedResponse, RPCRequest, RPCResponse};
use libp2p::bytes::BufMut;
use libp2p::bytes::BytesMut;
use std::marker::PhantomData;
use tokio::codec::{Decoder, Encoder};
use tokio_util::codec::{Decoder, Encoder};
use types::EthSpec;
pub trait OutboundCodec: Encoder + Decoder {
pub trait OutboundCodec<TItem>: Encoder<TItem> + Decoder {
type ErrorType;
fn decode_error(
@@ -21,7 +21,7 @@ pub trait OutboundCodec: Encoder + Decoder {
pub struct BaseInboundCodec<TCodec, TSpec>
where
TCodec: Encoder + Decoder,
TCodec: Encoder<RPCCodedResponse<TSpec>> + Decoder,
TSpec: EthSpec,
{
/// Inner codec for handling various encodings
@@ -31,7 +31,7 @@ where
impl<TCodec, TSpec> BaseInboundCodec<TCodec, TSpec>
where
TCodec: Encoder + Decoder,
TCodec: Encoder<RPCCodedResponse<TSpec>> + Decoder,
TSpec: EthSpec,
{
pub fn new(codec: TCodec) -> Self {
@@ -46,7 +46,7 @@ where
// This deals with Decoding RPC Responses from other peers and encoding our requests
pub struct BaseOutboundCodec<TOutboundCodec, TSpec>
where
TOutboundCodec: OutboundCodec,
TOutboundCodec: OutboundCodec<RPCRequest<TSpec>>,
TSpec: EthSpec,
{
/// Inner codec for handling various encodings.
@@ -59,7 +59,7 @@ where
impl<TOutboundCodec, TSpec> BaseOutboundCodec<TOutboundCodec, TSpec>
where
TSpec: EthSpec,
TOutboundCodec: OutboundCodec,
TOutboundCodec: OutboundCodec<RPCRequest<TSpec>>,
{
pub fn new(codec: TOutboundCodec) -> Self {
BaseOutboundCodec {
@@ -75,15 +75,18 @@ where
/* Base Inbound Codec */
// This Encodes RPC Responses sent to external peers
impl<TCodec, TSpec> Encoder for BaseInboundCodec<TCodec, TSpec>
impl<TCodec, TSpec> Encoder<RPCCodedResponse<TSpec>> for BaseInboundCodec<TCodec, TSpec>
where
TSpec: EthSpec,
TCodec: Decoder + Encoder<Item = RPCCodedResponse<TSpec>>,
TCodec: Decoder + Encoder<RPCCodedResponse<TSpec>>,
{
type Item = RPCCodedResponse<TSpec>;
type Error = <TCodec as Encoder>::Error;
type Error = <TCodec as Encoder<RPCCodedResponse<TSpec>>>::Error;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(
&mut self,
item: RPCCodedResponse<TSpec>,
dst: &mut BytesMut,
) -> Result<(), Self::Error> {
dst.clear();
dst.reserve(1);
dst.put_u8(
@@ -98,7 +101,7 @@ where
impl<TCodec, TSpec> Decoder for BaseInboundCodec<TCodec, TSpec>
where
TSpec: EthSpec,
TCodec: Encoder + Decoder<Item = RPCRequest<TSpec>>,
TCodec: Encoder<RPCCodedResponse<TSpec>> + Decoder<Item = RPCRequest<TSpec>>,
{
type Item = RPCRequest<TSpec>;
type Error = <TCodec as Decoder>::Error;
@@ -111,15 +114,14 @@ where
/* Base Outbound Codec */
// This Encodes RPC Requests sent to external peers
impl<TCodec, TSpec> Encoder for BaseOutboundCodec<TCodec, TSpec>
impl<TCodec, TSpec> Encoder<RPCRequest<TSpec>> for BaseOutboundCodec<TCodec, TSpec>
where
TSpec: EthSpec,
TCodec: OutboundCodec + Encoder<Item = RPCRequest<TSpec>>,
TCodec: OutboundCodec<RPCRequest<TSpec>> + Encoder<RPCRequest<TSpec>>,
{
type Item = RPCRequest<TSpec>;
type Error = <TCodec as Encoder>::Error;
type Error = <TCodec as Encoder<RPCRequest<TSpec>>>::Error;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(&mut self, item: RPCRequest<TSpec>, dst: &mut BytesMut) -> Result<(), Self::Error> {
self.inner.encode(item, dst)
}
}
@@ -128,7 +130,8 @@ where
impl<TCodec, TSpec> Decoder for BaseOutboundCodec<TCodec, TSpec>
where
TSpec: EthSpec,
TCodec: OutboundCodec<ErrorType = ErrorMessage> + Decoder<Item = RPCResponse<TSpec>>,
TCodec: OutboundCodec<RPCRequest<TSpec>, ErrorType = ErrorMessage>
+ Decoder<Item = RPCResponse<TSpec>>,
{
type Item = RPCCodedResponse<TSpec>;
type Error = <TCodec as Decoder>::Error;
@@ -168,3 +171,47 @@ where
inner_result
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::*;
use super::super::ssz_snappy::*;
use super::*;
use crate::rpc::protocol::*;
#[test]
fn test_decode_status_message() {
let message = hex::decode("ff060000734e615070590032000006e71e7b54989925efd6c9cbcb8ceb9b5f71216f5137282bf6a1e3b50f64e42d6c7fb347abe07eb0db8200000005029e2800").unwrap();
let mut buf = BytesMut::new();
buf.extend_from_slice(&message);
type Spec = types::MainnetEthSpec;
let snappy_protocol_id =
ProtocolId::new(Protocol::Status, Version::V1, Encoding::SSZSnappy);
let ssz_protocol_id = ProtocolId::new(Protocol::Status, Version::V1, Encoding::SSZ);
let mut snappy_outbound_codec =
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, 1_048_576);
let mut ssz_outbound_codec = SSZOutboundCodec::<Spec>::new(ssz_protocol_id, 1_048_576);
// decode message just as snappy message
let snappy_decoded_message = snappy_outbound_codec.decode(&mut buf.clone());
// decode message just a ssz message
let ssz_decoded_message = ssz_outbound_codec.decode(&mut buf.clone());
// build codecs for entire chunk
let mut snappy_base_outbound_codec = BaseOutboundCodec::new(snappy_outbound_codec);
let mut ssz_base_outbound_codec = BaseOutboundCodec::new(ssz_outbound_codec);
// decode message as ssz snappy chunk
let snappy_decoded_chunk = snappy_base_outbound_codec.decode(&mut buf.clone());
// decode message just a ssz chunk
let ssz_decoded_chunk = ssz_base_outbound_codec.decode(&mut buf.clone());
let _ = dbg!(snappy_decoded_message);
let _ = dbg!(ssz_decoded_message);
let _ = dbg!(snappy_decoded_chunk);
let _ = dbg!(ssz_decoded_chunk);
}
}

View File

@@ -8,7 +8,7 @@ use self::ssz_snappy::{SSZSnappyInboundCodec, SSZSnappyOutboundCodec};
use crate::rpc::protocol::RPCError;
use crate::rpc::{RPCCodedResponse, RPCRequest};
use libp2p::bytes::BytesMut;
use tokio::codec::{Decoder, Encoder};
use tokio_util::codec::{Decoder, Encoder};
use types::EthSpec;
// Known types of codecs
@@ -22,11 +22,10 @@ pub enum OutboundCodec<TSpec: EthSpec> {
SSZ(BaseOutboundCodec<SSZOutboundCodec<TSpec>, TSpec>),
}
impl<T: EthSpec> Encoder for InboundCodec<T> {
type Item = RPCCodedResponse<T>;
impl<T: EthSpec> Encoder<RPCCodedResponse<T>> for InboundCodec<T> {
type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(&mut self, item: RPCCodedResponse<T>, dst: &mut BytesMut) -> Result<(), Self::Error> {
match self {
InboundCodec::SSZ(codec) => codec.encode(item, dst),
InboundCodec::SSZSnappy(codec) => codec.encode(item, dst),
@@ -46,11 +45,10 @@ impl<TSpec: EthSpec> Decoder for InboundCodec<TSpec> {
}
}
impl<TSpec: EthSpec> Encoder for OutboundCodec<TSpec> {
type Item = RPCRequest<TSpec>;
impl<TSpec: EthSpec> Encoder<RPCRequest<TSpec>> for OutboundCodec<TSpec> {
type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(&mut self, item: RPCRequest<TSpec>, dst: &mut BytesMut) -> Result<(), Self::Error> {
match self {
OutboundCodec::SSZ(codec) => codec.encode(item, dst),
OutboundCodec::SSZSnappy(codec) => codec.encode(item, dst),

View File

@@ -7,7 +7,7 @@ use crate::rpc::{ErrorMessage, RPCCodedResponse, RPCRequest, RPCResponse};
use libp2p::bytes::{BufMut, Bytes, BytesMut};
use ssz::{Decode, Encode};
use std::marker::PhantomData;
use tokio::codec::{Decoder, Encoder};
use tokio_util::codec::{Decoder, Encoder};
use types::{EthSpec, SignedBeaconBlock};
use unsigned_varint::codec::UviBytes;
@@ -19,7 +19,7 @@ pub struct SSZInboundCodec<TSpec: EthSpec> {
phantom: PhantomData<TSpec>,
}
impl<T: EthSpec> SSZInboundCodec<T> {
impl<TSpec: EthSpec> SSZInboundCodec<TSpec> {
pub fn new(protocol: ProtocolId, max_packet_size: usize) -> Self {
let mut uvi_codec = UviBytes::default();
uvi_codec.set_max_len(max_packet_size);
@@ -36,11 +36,14 @@ impl<T: EthSpec> SSZInboundCodec<T> {
}
// Encoder for inbound streams: Encodes RPC Responses sent to peers.
impl<TSpec: EthSpec> Encoder for SSZInboundCodec<TSpec> {
type Item = RPCCodedResponse<TSpec>;
impl<TSpec: EthSpec> Encoder<RPCCodedResponse<TSpec>> for SSZInboundCodec<TSpec> {
type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(
&mut self,
item: RPCCodedResponse<TSpec>,
dst: &mut BytesMut,
) -> Result<(), Self::Error> {
let bytes = match item {
RPCCodedResponse::Success(resp) => match resp {
RPCResponse::Status(res) => res.as_ssz_bytes(),
@@ -145,11 +148,10 @@ impl<TSpec: EthSpec> SSZOutboundCodec<TSpec> {
}
// Encoder for outbound streams: Encodes RPC Requests to peers
impl<TSpec: EthSpec> Encoder for SSZOutboundCodec<TSpec> {
type Item = RPCRequest<TSpec>;
impl<TSpec: EthSpec> Encoder<RPCRequest<TSpec>> for SSZOutboundCodec<TSpec> {
type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(&mut self, item: RPCRequest<TSpec>, dst: &mut BytesMut) -> Result<(), Self::Error> {
let bytes = match item {
RPCRequest::Status(req) => req.as_ssz_bytes(),
RPCRequest::Goodbye(req) => req.as_ssz_bytes(),
@@ -201,7 +203,7 @@ impl<TSpec: EthSpec> Decoder for SSZOutboundCodec<TSpec> {
match self.inner.decode(src).map_err(RPCError::from) {
Ok(Some(mut packet)) => {
// take the bytes from the buffer
let raw_bytes = packet.take();
let raw_bytes = packet.split();
match self.protocol.message_name {
Protocol::Status => match self.protocol.version {
@@ -239,7 +241,7 @@ impl<TSpec: EthSpec> Decoder for SSZOutboundCodec<TSpec> {
}
}
impl<TSpec: EthSpec> OutboundCodec for SSZOutboundCodec<TSpec> {
impl<TSpec: EthSpec> OutboundCodec<RPCRequest<TSpec>> for SSZOutboundCodec<TSpec> {
type ErrorType = ErrorMessage;
fn decode_error(&mut self, src: &mut BytesMut) -> Result<Option<Self::ErrorType>, RPCError> {

View File

@@ -12,7 +12,7 @@ use std::io::Cursor;
use std::io::ErrorKind;
use std::io::{Read, Write};
use std::marker::PhantomData;
use tokio::codec::{Decoder, Encoder};
use tokio_util::codec::{Decoder, Encoder};
use types::{EthSpec, SignedBeaconBlock};
use unsigned_varint::codec::Uvi;
@@ -44,11 +44,14 @@ impl<T: EthSpec> SSZSnappyInboundCodec<T> {
}
// Encoder for inbound streams: Encodes RPC Responses sent to peers.
impl<TSpec: EthSpec> Encoder for SSZSnappyInboundCodec<TSpec> {
type Item = RPCCodedResponse<TSpec>;
impl<TSpec: EthSpec> Encoder<RPCCodedResponse<TSpec>> for SSZSnappyInboundCodec<TSpec> {
type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(
&mut self,
item: RPCCodedResponse<TSpec>,
dst: &mut BytesMut,
) -> Result<(), Self::Error> {
let bytes = match item {
RPCCodedResponse::Success(resp) => match resp {
RPCResponse::Status(res) => res.as_ssz_bytes(),
@@ -116,7 +119,7 @@ impl<TSpec: EthSpec> Decoder for SSZSnappyInboundCodec<TSpec> {
// `n` is how many bytes the reader read in the compressed stream
let n = reader.get_ref().position();
self.len = None;
src.split_to(n as usize);
let _read_bytes = src.split_to(n as usize);
match self.protocol.message_name {
Protocol::Status => match self.protocol.version {
Version::V1 => Ok(Some(RPCRequest::Status(StatusMessage::from_ssz_bytes(
@@ -193,11 +196,10 @@ impl<TSpec: EthSpec> SSZSnappyOutboundCodec<TSpec> {
}
// Encoder for outbound streams: Encodes RPC Requests to peers
impl<TSpec: EthSpec> Encoder for SSZSnappyOutboundCodec<TSpec> {
type Item = RPCRequest<TSpec>;
impl<TSpec: EthSpec> Encoder<RPCRequest<TSpec>> for SSZSnappyOutboundCodec<TSpec> {
type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(&mut self, item: RPCRequest<TSpec>, dst: &mut BytesMut) -> Result<(), Self::Error> {
let bytes = match item {
RPCRequest::Status(req) => req.as_ssz_bytes(),
RPCRequest::Goodbye(req) => req.as_ssz_bytes(),
@@ -262,7 +264,7 @@ impl<TSpec: EthSpec> Decoder for SSZSnappyOutboundCodec<TSpec> {
// `n` is how many bytes the reader read in the compressed stream
let n = reader.get_ref().position();
self.len = None;
src.split_to(n as usize);
let _read_byts = src.split_to(n as usize);
match self.protocol.message_name {
Protocol::Status => match self.protocol.version {
Version::V1 => Ok(Some(RPCResponse::Status(
@@ -307,7 +309,7 @@ impl<TSpec: EthSpec> Decoder for SSZSnappyOutboundCodec<TSpec> {
}
}
impl<TSpec: EthSpec> OutboundCodec for SSZSnappyOutboundCodec<TSpec> {
impl<TSpec: EthSpec> OutboundCodec<RPCRequest<TSpec>> for SSZSnappyOutboundCodec<TSpec> {
type ErrorType = ErrorMessage;
fn decode_error(&mut self, src: &mut BytesMut) -> Result<Option<Self::ErrorType>, RPCError> {
@@ -334,7 +336,7 @@ impl<TSpec: EthSpec> OutboundCodec for SSZSnappyOutboundCodec<TSpec> {
// `n` is how many bytes the reader read in the compressed stream
let n = reader.get_ref().position();
self.len = None;
src.split_to(n as usize);
let _read_bytes = src.split_to(n as usize);
Ok(Some(ErrorMessage::from_ssz_bytes(&decoded_buffer)?))
}
Err(e) => match e.kind() {

View File

@@ -5,7 +5,6 @@ use super::methods::{ErrorMessage, RPCCodedResponse, RequestId, ResponseTerminat
use super::protocol::{Protocol, RPCError, RPCProtocol, RPCRequest};
use super::RPCEvent;
use crate::rpc::protocol::{InboundFramed, OutboundFramed};
use core::marker::PhantomData;
use fnv::FnvHashMap;
use futures::prelude::*;
use libp2p::core::upgrade::{
@@ -14,15 +13,18 @@ use libp2p::core::upgrade::{
use libp2p::swarm::protocols_handler::{
KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol,
};
use libp2p::swarm::NegotiatedSubstream;
use slog::{crit, debug, error, trace, warn};
use smallvec::SmallVec;
use std::collections::hash_map::Entry;
use std::time::{Duration, Instant};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::timer::{delay_queue, DelayQueue};
use std::{
collections::hash_map::Entry,
pin::Pin,
task::{Context, Poll},
time::{Duration, Instant},
};
use tokio::time::{delay_queue, DelayQueue};
use types::EthSpec;
//TODO: Implement close() on the substream types to improve the poll code.
//TODO: Implement check_timeout() on the substream types
/// The time (in seconds) before a substream that is awaiting a response from the user times out.
@@ -39,9 +41,8 @@ type InboundRequestId = RequestId;
type OutboundRequestId = RequestId;
/// Implementation of `ProtocolsHandler` for the RPC protocol.
pub struct RPCHandler<TSubstream, TSpec>
pub struct RPCHandler<TSpec>
where
TSubstream: AsyncRead + AsyncWrite,
TSpec: EthSpec,
{
/// The upgrade for inbound substreams.
@@ -63,7 +64,7 @@ where
inbound_substreams: FnvHashMap<
InboundRequestId,
(
InboundSubstreamState<TSubstream, TSpec>,
InboundSubstreamState<TSpec>,
Option<delay_queue::Key>,
Protocol,
),
@@ -74,14 +75,8 @@ where
/// Map of outbound substreams that need to be driven to completion. The `RequestId` is
/// maintained by the application sending the request.
outbound_substreams: FnvHashMap<
OutboundRequestId,
(
OutboundSubstreamState<TSubstream, TSpec>,
delay_queue::Key,
Protocol,
),
>,
outbound_substreams:
FnvHashMap<OutboundRequestId, (OutboundSubstreamState<TSpec>, delay_queue::Key, Protocol)>,
/// Inbound substream `DelayQueue` which keeps track of when an inbound substream will timeout.
outbound_substreams_delay: DelayQueue<OutboundRequestId>,
@@ -107,21 +102,27 @@ where
/// Logger for handling RPC streams
log: slog::Logger,
/// Marker to pin the generic stream.
_phantom: PhantomData<TSubstream>,
}
/// State of an outbound substream. Either waiting for a response, or in the process of sending.
pub enum InboundSubstreamState<TSubstream, TSpec>
pub enum InboundSubstreamState<TSpec>
where
TSubstream: AsyncRead + AsyncWrite,
TSpec: EthSpec,
{
/// A response has been sent, pending writing and flush.
/// A response has been sent, pending writing.
ResponsePendingSend {
/// The substream used to send the response
substream: futures::sink::Send<InboundFramed<TSubstream, TSpec>>,
substream: InboundFramed<NegotiatedSubstream, TSpec>,
/// The message that is attempting to be sent.
message: RPCCodedResponse<TSpec>,
/// Whether a stream termination is requested. If true the stream will be closed after
/// this send. Otherwise it will transition to an idle state until a stream termination is
/// requested or a timeout is reached.
closing: bool,
},
/// A response has been sent, pending flush.
ResponsePendingFlush {
/// The substream used to send the response
substream: InboundFramed<NegotiatedSubstream, TSpec>,
/// Whether a stream termination is requested. If true the stream will be closed after
/// this send. Otherwise it will transition to an idle state until a stream termination is
/// requested or a timeout is reached.
@@ -129,31 +130,31 @@ where
},
/// The response stream is idle and awaiting input from the application to send more chunked
/// responses.
ResponseIdle(InboundFramed<TSubstream, TSpec>),
ResponseIdle(InboundFramed<NegotiatedSubstream, TSpec>),
/// The substream is attempting to shutdown.
Closing(InboundFramed<TSubstream, TSpec>),
Closing(InboundFramed<NegotiatedSubstream, TSpec>),
/// Temporary state during processing
Poisoned,
}
pub enum OutboundSubstreamState<TSubstream, TSpec: EthSpec> {
/// State of an outbound substream. Either waiting for a response, or in the process of sending.
pub enum OutboundSubstreamState<TSpec: EthSpec> {
/// 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<TSubstream, TSpec>,
substream: OutboundFramed<NegotiatedSubstream, TSpec>,
/// Keeps track of the actual request sent.
request: RPCRequest<TSpec>,
},
/// Closing an outbound substream>
Closing(OutboundFramed<TSubstream, TSpec>),
Closing(OutboundFramed<NegotiatedSubstream, TSpec>),
/// Temporary state during processing
Poisoned,
}
impl<TSubstream, TSpec> InboundSubstreamState<TSubstream, TSpec>
impl<TSpec> InboundSubstreamState<TSpec>
where
TSubstream: AsyncRead + AsyncWrite,
TSpec: EthSpec,
{
/// Moves the substream state to closing and informs the connected peer. The
@@ -172,18 +173,37 @@ where
RPCCodedResponse::StreamTermination(ResponseTermination::BlocksByRange);
match std::mem::replace(self, InboundSubstreamState::Poisoned) {
InboundSubstreamState::ResponsePendingSend { substream, closing } => {
// if we are busy awaiting a send/flush add the termination to the queue
InboundSubstreamState::ResponsePendingSend {
substream,
message,
closing,
} => {
if !closing {
outbound_queue.push(error);
outbound_queue.push(stream_termination);
}
// if the stream is closing after the send, allow it to finish
*self = InboundSubstreamState::ResponsePendingSend { substream, closing }
*self = InboundSubstreamState::ResponsePendingSend {
substream,
message,
closing,
}
}
// if we are busy awaiting a send/flush add the termination to the queue
InboundSubstreamState::ResponsePendingFlush { substream, closing } => {
if !closing {
outbound_queue.push(error);
outbound_queue.push(stream_termination);
}
// if the stream is closing after the send, allow it to finish
*self = InboundSubstreamState::ResponsePendingFlush { substream, closing }
}
InboundSubstreamState::ResponseIdle(substream) => {
*self = InboundSubstreamState::ResponsePendingSend {
substream: substream.send(error),
substream: substream,
message: error,
closing: true,
};
}
@@ -198,9 +218,8 @@ where
}
}
impl<TSubstream, TSpec> RPCHandler<TSubstream, TSpec>
impl<TSpec> RPCHandler<TSpec>
where
TSubstream: AsyncRead + AsyncWrite,
TSpec: EthSpec,
{
pub fn new(
@@ -225,7 +244,6 @@ where
inactive_timeout,
outbound_io_error_retries: 0,
log: log.clone(),
_phantom: PhantomData,
}
}
@@ -258,15 +276,13 @@ where
}
}
impl<TSubstream, TSpec> ProtocolsHandler for RPCHandler<TSubstream, TSpec>
impl<TSpec> ProtocolsHandler for RPCHandler<TSpec>
where
TSubstream: AsyncRead + AsyncWrite,
TSpec: EthSpec,
{
type InEvent = RPCEvent<TSpec>;
type OutEvent = RPCEvent<TSpec>;
type Error = ProtocolsHandlerUpgrErr<RPCError>;
type Substream = TSubstream;
type Error = RPCError;
type InboundProtocol = RPCProtocol<TSpec>;
type OutboundProtocol = RPCRequest<TSpec>;
type OutboundOpenInfo = (RequestId, RPCRequest<TSpec>); // Keep track of the id and the request
@@ -277,14 +293,14 @@ where
fn inject_fully_negotiated_inbound(
&mut self,
out: <RPCProtocol<TSpec> as InboundUpgrade<TSubstream>>::Output,
substream: <Self::InboundProtocol as InboundUpgrade<NegotiatedSubstream>>::Output,
) {
// update the keep alive timeout if there are no more remaining outbound streams
if let KeepAlive::Until(_) = self.keep_alive {
self.keep_alive = KeepAlive::Until(Instant::now() + self.inactive_timeout);
}
let (req, substream) = out;
let (req, substream) = substream;
// 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));
@@ -309,7 +325,7 @@ where
fn inject_fully_negotiated_outbound(
&mut self,
out: <RPCRequest<TSpec> as OutboundUpgrade<TSubstream>>::Output,
out: <Self::OutboundProtocol as OutboundUpgrade<NegotiatedSubstream>>::Output,
request_info: Self::OutboundOpenInfo,
) {
self.dial_negotiated -= 1;
@@ -394,15 +410,18 @@ where
// if it's a single rpc request or an error, close the stream after
*substream_state =
InboundSubstreamState::ResponsePendingSend {
substream: substream.send(response),
substream: substream,
message: response,
closing: !res_is_multiple | res_is_error, // close if an error or we are not expecting more responses
};
}
}
}
InboundSubstreamState::ResponsePendingSend { substream, closing }
if res_is_multiple =>
{
InboundSubstreamState::ResponsePendingSend {
substream,
message,
closing,
} if res_is_multiple => {
// the stream is in use, add the request to a pending queue
self.queued_outbound_items
.entry(rpc_id)
@@ -411,6 +430,22 @@ where
// return the state
*substream_state = InboundSubstreamState::ResponsePendingSend {
substream,
message,
closing,
};
}
InboundSubstreamState::ResponsePendingFlush { substream, closing }
if res_is_multiple =>
{
// the stream is in use, add the request to a pending queue
self.queued_outbound_items
.entry(rpc_id)
.or_insert_with(Vec::new)
.push(response);
// return the state
*substream_state = InboundSubstreamState::ResponsePendingFlush {
substream,
closing,
};
@@ -419,8 +454,20 @@ where
*substream_state = InboundSubstreamState::Closing(substream);
debug!(self.log, "Response not sent. Stream is closing"; "response" => format!("{}",response));
}
InboundSubstreamState::ResponsePendingSend { substream, .. } => {
InboundSubstreamState::ResponsePendingSend {
substream,
message,
..
} => {
*substream_state = InboundSubstreamState::ResponsePendingSend {
substream,
message,
closing: true,
};
error!(self.log, "Attempted sending multiple responses to a single response request");
}
InboundSubstreamState::ResponsePendingFlush { substream, .. } => {
*substream_state = InboundSubstreamState::ResponsePendingFlush {
substream,
closing: true,
};
@@ -433,7 +480,7 @@ where
}
}
None => {
warn!(self.log, "Stream has expired. Response not sent"; "response" => format!("{}", response));
warn!(self.log, "Stream has expired. Response not sent"; "response" => response.to_string(), "id" => rpc_id);
}
};
}
@@ -446,7 +493,7 @@ where
&mut self,
request_info: Self::OutboundOpenInfo,
error: ProtocolsHandlerUpgrErr<
<Self::OutboundProtocol as OutboundUpgrade<Self::Substream>>::Error,
<Self::OutboundProtocol as OutboundUpgrade<NegotiatedSubstream>>::Error,
>,
) {
let (id, req) = request_info;
@@ -470,7 +517,7 @@ where
ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(
NegotiationError::ProtocolError(e),
)) => match e {
ProtocolError::IoError(io_err) => RPCError::IoError(io_err),
ProtocolError::IoError(io_err) => RPCError::IoError(io_err.to_string()),
ProtocolError::InvalidProtocol => {
RPCError::InternalError("Protocol was deemed invalid")
}
@@ -490,64 +537,82 @@ where
fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Poll<
ProtocolsHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::OutEvent>,
Self::Error,
ProtocolsHandlerEvent<
Self::OutboundProtocol,
Self::OutboundOpenInfo,
Self::OutEvent,
Self::Error,
>,
> {
if !self.pending_error.is_empty() {
let (id, protocol, err) = self.pending_error.remove(0);
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(id, protocol, err),
return Poll::Ready(ProtocolsHandlerEvent::Custom(RPCEvent::Error(
id, protocol, err,
)));
}
// 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),
)));
return Poll::Ready(ProtocolsHandlerEvent::Custom(self.events_out.remove(0)));
} else {
self.events_out.shrink_to_fit();
}
// purge expired inbound substreams and send an error
while let Async::Ready(Some(stream_id)) =
self.inbound_substreams_delay.poll().map_err(|e| {
warn!(self.log, "Inbound substream poll failed"; "error" => format!("{:?}", e));
ProtocolsHandlerUpgrErr::Timer
})?
{
let rpc_id = stream_id.get_ref();
loop {
match self.inbound_substreams_delay.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(stream_id))) => {
// handle a stream timeout for various states
if let Some((substream_state, delay_key, _)) =
self.inbound_substreams.get_mut(stream_id.get_ref())
{
// the delay has been removed
*delay_key = None;
// handle a stream timeout for various states
if let Some((substream_state, delay_key, _)) = self.inbound_substreams.get_mut(rpc_id) {
// the delay has been removed
*delay_key = None;
let outbound_queue = self
.queued_outbound_items
.entry(*rpc_id)
.or_insert_with(Vec::new);
substream_state.close(outbound_queue);
let outbound_queue = self
.queued_outbound_items
.entry(stream_id.into_inner())
.or_insert_with(Vec::new);
substream_state.close(outbound_queue);
}
}
Poll::Ready(Some(Err(e))) => {
warn!(self.log, "Inbound substream poll failed"; "error" => format!("{:?}", e));
// drops the peer if we cannot read the delay queue
return Poll::Ready(ProtocolsHandlerEvent::Close(RPCError::InternalError(
"Could not poll inbound stream timer",
)));
}
Poll::Pending | Poll::Ready(None) => break,
}
}
// purge expired outbound substreams
if let Async::Ready(Some(stream_id)) =
self.outbound_substreams_delay.poll().map_err(|e| {
warn!(self.log, "Outbound substream poll failed"; "error" => format!("{:?}", e));
ProtocolsHandlerUpgrErr::Timer
})?
{
if let Some((_id, _stream, protocol)) =
self.outbound_substreams.remove(stream_id.get_ref())
{
// notify the user
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(*stream_id.get_ref(), protocol, RPCError::StreamTimeout),
)));
} else {
crit!(self.log, "timed out substream not in the books"; "stream_id" => stream_id.get_ref());
loop {
match self.outbound_substreams_delay.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(stream_id))) => {
if let Some((_id, _stream, protocol)) =
self.outbound_substreams.remove(stream_id.get_ref())
{
// notify the user
return Poll::Ready(ProtocolsHandlerEvent::Custom(RPCEvent::Error(
*stream_id.get_ref(),
protocol,
RPCError::StreamTimeout,
)));
} else {
crit!(self.log, "timed out substream not in the books"; "stream_id" => stream_id.get_ref());
}
}
Poll::Ready(Some(Err(e))) => {
warn!(self.log, "Outbound substream poll failed"; "error" => format!("{:?}", e));
return Poll::Ready(ProtocolsHandlerEvent::Close(RPCError::InternalError(
"Could not poll outbound stream timer",
)));
}
Poll::Pending | Poll::Ready(None) => break,
}
}
@@ -566,20 +631,75 @@ where
) {
InboundSubstreamState::ResponsePendingSend {
mut substream,
message,
closing,
} => {
match substream.poll() {
Ok(Async::Ready(raw_substream)) => {
// completed the send
// close the stream if required
match Sink::poll_ready(Pin::new(&mut substream), cx) {
Poll::Ready(Ok(())) => {
// stream is ready to send data
match Sink::start_send(Pin::new(&mut substream), message) {
Ok(()) => {
// await flush
entry.get_mut().0 =
InboundSubstreamState::ResponsePendingFlush {
substream,
closing,
}
}
Err(e) => {
// error with sending in the codec
warn!(self.log, "Error sending RPC message"; "error" => e.to_string());
// keep connection with the peer and return the
// stream to awaiting response if this message
// wasn't closing the stream
// TODO: Duplicate code
if closing {
entry.get_mut().0 =
InboundSubstreamState::Closing(substream)
} else {
// check for queued chunks and update the stream
entry.get_mut().0 = apply_queued_responses(
substream,
&mut self
.queued_outbound_items
.get_mut(&request_id),
&mut new_items_to_send,
);
}
}
}
}
Poll::Ready(Err(e)) => {
error!(self.log, "Outbound substream error while sending RPC message: {:?}", e);
entry.remove();
return Poll::Ready(ProtocolsHandlerEvent::Close(e));
}
Poll::Pending => {
// the stream is not yet ready, continue waiting
entry.get_mut().0 =
InboundSubstreamState::ResponsePendingSend {
substream,
message,
closing,
};
}
}
}
InboundSubstreamState::ResponsePendingFlush {
mut substream,
closing,
} => {
match Sink::poll_flush(Pin::new(&mut substream), cx) {
Poll::Ready(Ok(())) => {
// finished flushing
// TODO: Duplicate code
if closing {
entry.get_mut().0 =
InboundSubstreamState::Closing(raw_substream)
InboundSubstreamState::Closing(substream)
} else {
// check for queued chunks and update the stream
entry.get_mut().0 = apply_queued_responses(
raw_substream,
substream,
&mut self
.queued_outbound_items
.get_mut(&request_id),
@@ -587,24 +707,34 @@ where
);
}
}
Ok(Async::NotReady) => {
Poll::Ready(Err(e)) => {
// error during flush
trace!(self.log, "Error sending flushing RPC message"; "error" => e.to_string());
// we drop the stream on error and inform the user, remove
// any pending requests
// TODO: Duplicate code
if let Some(delay_key) = &entry.get().1 {
self.inbound_substreams_delay.remove(delay_key);
}
self.queued_outbound_items.remove(&request_id);
entry.remove();
if self.outbound_substreams.is_empty()
&& self.inbound_substreams.is_empty()
{
self.keep_alive = KeepAlive::Until(
Instant::now() + self.inactive_timeout,
);
}
}
Poll::Pending => {
entry.get_mut().0 =
InboundSubstreamState::ResponsePendingSend {
InboundSubstreamState::ResponsePendingFlush {
substream,
closing,
};
}
Err(e) => {
if let Some(delay_key) = &entry.get().1 {
self.inbound_substreams_delay.remove(delay_key);
}
let protocol = entry.get().2;
entry.remove_entry();
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(0, protocol, e),
)));
}
};
}
}
InboundSubstreamState::ResponseIdle(substream) => {
entry.get_mut().0 = apply_queued_responses(
@@ -614,9 +744,8 @@ where
);
}
InboundSubstreamState::Closing(mut substream) => {
match substream.close() {
Ok(Async::Ready(())) | Err(_) => {
//trace!(self.log, "Inbound stream dropped");
match Sink::poll_close(Pin::new(&mut substream), cx) {
Poll::Ready(Ok(())) => {
if let Some(delay_key) = &entry.get().1 {
self.inbound_substreams_delay.remove(delay_key);
}
@@ -631,7 +760,25 @@ where
);
}
} // drop the stream
Ok(Async::NotReady) => {
Poll::Ready(Err(e)) => {
error!(self.log, "Error closing inbound stream"; "error" => e.to_string());
// drop the stream anyway
// TODO: Duplicate code
if let Some(delay_key) = &entry.get().1 {
self.inbound_substreams_delay.remove(delay_key);
}
self.queued_outbound_items.remove(&request_id);
entry.remove();
if self.outbound_substreams.is_empty()
&& self.inbound_substreams.is_empty()
{
self.keep_alive = KeepAlive::Until(
Instant::now() + self.inactive_timeout,
);
}
}
Poll::Pending => {
entry.get_mut().0 =
InboundSubstreamState::Closing(substream);
}
@@ -641,7 +788,7 @@ where
crit!(self.log, "Poisoned outbound substream");
unreachable!("Coding Error: Inbound Substream is poisoned");
}
};
}
}
Entry::Vacant(_) => unreachable!(),
}
@@ -659,8 +806,8 @@ where
OutboundSubstreamState::RequestPendingResponse {
mut substream,
request,
} => match substream.poll() {
Ok(Async::Ready(Some(response))) => {
} => match substream.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(response))) => {
if request.multiple_responses() && !response.is_error() {
entry.get_mut().0 =
OutboundSubstreamState::RequestPendingResponse {
@@ -678,11 +825,11 @@ where
entry.get_mut().0 = OutboundSubstreamState::Closing(substream);
}
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
return Poll::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Response(request_id, response),
)));
));
}
Ok(Async::Ready(None)) => {
Poll::Ready(None) => {
// stream closed
// if we expected multiple streams send a stream termination,
// else report the stream terminating only.
@@ -694,59 +841,62 @@ where
// notify the application error
if request.multiple_responses() {
// return an end of stream result
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
return Poll::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Response(
request_id,
RPCCodedResponse::StreamTermination(
request.stream_termination(),
),
),
)));
));
} // else we return an error, stream should not have closed early.
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
return Poll::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(
request_id,
request.protocol(),
RPCError::IncompleteStream,
),
)));
));
}
Ok(Async::NotReady) => {
Poll::Pending => {
entry.get_mut().0 = OutboundSubstreamState::RequestPendingResponse {
substream,
request,
}
}
Err(e) => {
Poll::Ready(Some(Err(e))) => {
// drop the stream
let delay_key = &entry.get().1;
self.outbound_substreams_delay.remove(delay_key);
let protocol = entry.get().2;
entry.remove_entry();
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
return Poll::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(request_id, protocol, e),
)));
));
}
},
OutboundSubstreamState::Closing(mut substream) => match substream.close() {
Ok(Async::Ready(())) | Err(_) => {
//trace!(self.log, "Outbound stream dropped");
// drop the stream
let delay_key = &entry.get().1;
self.outbound_substreams_delay.remove(delay_key);
entry.remove_entry();
OutboundSubstreamState::Closing(mut substream) => {
match Sink::poll_close(Pin::new(&mut substream), cx) {
// TODO: check if this is supposed to be a stream
Poll::Ready(_) => {
// drop the stream - including if there is an error
let delay_key = &entry.get().1;
self.outbound_substreams_delay.remove(delay_key);
entry.remove_entry();
if self.outbound_substreams.is_empty()
&& self.inbound_substreams.is_empty()
{
self.keep_alive =
KeepAlive::Until(Instant::now() + self.inactive_timeout);
if self.outbound_substreams.is_empty()
&& self.inbound_substreams.is_empty()
{
self.keep_alive = KeepAlive::Until(
Instant::now() + self.inactive_timeout,
);
}
}
Poll::Pending => {
entry.get_mut().0 = OutboundSubstreamState::Closing(substream);
}
}
Ok(Async::NotReady) => {
entry.get_mut().0 = OutboundSubstreamState::Closing(substream);
}
},
}
OutboundSubstreamState::Poisoned => {
crit!(self.log, "Poisoned outbound substream");
unreachable!("Coding Error: Outbound substream is poisoned")
@@ -762,23 +912,21 @@ where
self.dial_negotiated += 1;
let (id, req) = self.dial_queue.remove(0);
self.dial_queue.shrink_to_fit();
return Ok(Async::Ready(
ProtocolsHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(req.clone()),
info: (id, req),
},
));
return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(req.clone()),
info: (id, req),
});
}
Ok(Async::NotReady)
Poll::Pending
}
}
// Check for new items to send to the peer and update the underlying stream
fn apply_queued_responses<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>(
raw_substream: InboundFramed<TSubstream, TSpec>,
fn apply_queued_responses<TSpec: EthSpec>(
substream: InboundFramed<NegotiatedSubstream, TSpec>,
queued_outbound_items: &mut Option<&mut Vec<RPCCodedResponse<TSpec>>>,
new_items_to_send: &mut bool,
) -> InboundSubstreamState<TSubstream, TSpec> {
) -> InboundSubstreamState<TSpec> {
match queued_outbound_items {
Some(ref mut queue) if !queue.is_empty() => {
*new_items_to_send = true;
@@ -786,17 +934,18 @@ fn apply_queued_responses<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>(
match queue.remove(0) {
RPCCodedResponse::StreamTermination(_) => {
// close the stream if this is a stream termination
InboundSubstreamState::Closing(raw_substream)
InboundSubstreamState::Closing(substream)
}
chunk => InboundSubstreamState::ResponsePendingSend {
substream: raw_substream.send(chunk),
substream: substream,
message: chunk,
closing: false,
},
}
}
_ => {
// no items queued set to idle
InboundSubstreamState::ResponseIdle(raw_substream)
InboundSubstreamState::ResponseIdle(substream)
}
}
}

View File

@@ -164,7 +164,7 @@ pub enum RPCResponse<T: EthSpec> {
}
/// Indicates which response is being terminated by a stream termination response.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum ResponseTermination {
/// Blocks by range stream termination.
BlocksByRange,
@@ -175,7 +175,7 @@ pub enum ResponseTermination {
/// The structured response containing a result/code indicating success or failure
/// and the contents of the response
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum RPCCodedResponse<T: EthSpec> {
/// The response is a successful.
Success(RPCResponse<T>),
@@ -194,7 +194,7 @@ pub enum RPCCodedResponse<T: EthSpec> {
}
/// The code assigned to an erroneous `RPCResponse`.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum RPCResponseErrorCode {
InvalidRequest,
ServerError,
@@ -268,14 +268,14 @@ impl<T: EthSpec> RPCCodedResponse<T> {
}
}
#[derive(Encode, Decode, Debug)]
#[derive(Encode, Decode, Debug, Clone)]
pub struct ErrorMessage {
/// The UTF-8 encoded Error message string.
pub error_message: Vec<u8>,
}
impl ErrorMessage {
pub fn as_string(&self) -> String {
impl std::string::ToString for ErrorMessage {
fn to_string(&self) -> String {
String::from_utf8(self.error_message.clone()).unwrap_or_else(|_| "".into())
}
}

View File

@@ -4,12 +4,11 @@
//! direct peer-to-peer communication primarily for sending/receiving chain information for
//! syncing.
use futures::prelude::*;
use handler::RPCHandler;
use libp2p::core::ConnectedPoint;
use libp2p::core::{connection::ConnectionId, ConnectedPoint};
use libp2p::swarm::{
protocols_handler::ProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters,
SubstreamProtocol,
protocols_handler::ProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler,
PollParameters, SubstreamProtocol,
};
use libp2p::{Multiaddr, PeerId};
pub use methods::{
@@ -19,8 +18,8 @@ pub use methods::{
pub use protocol::{Protocol, RPCError, RPCProtocol, RPCRequest};
use slog::{debug, o};
use std::marker::PhantomData;
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite};
use types::EthSpec;
pub(crate) mod codec;
@@ -29,7 +28,7 @@ pub mod methods;
mod protocol;
/// The return type used in the behaviour and the resultant event from the protocols handler.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum RPCEvent<T: EthSpec> {
/// An inbound/outbound request for RPC protocol. The first parameter is a sequential
/// id which tracks an awaiting substream for the response.
@@ -42,6 +41,14 @@ pub enum RPCEvent<T: EthSpec> {
Error(RequestId, Protocol, RPCError),
}
/// Messages sent to the user from the RPC protocol.
pub struct RPCMessage<TSpec: EthSpec> {
/// The peer that sent the message.
pub peer_id: PeerId,
/// The message that was sent.
pub event: RPCEvent<TSpec>,
}
impl<T: EthSpec> RPCEvent<T> {
pub fn id(&self) -> usize {
match *self {
@@ -68,21 +75,18 @@ impl<T: EthSpec> std::fmt::Display for RPCEvent<T> {
/// Implements the libp2p `NetworkBehaviour` trait and therefore manages network-level
/// logic.
pub struct RPC<TSubstream, TSpec: EthSpec> {
pub struct RPC<TSpec: EthSpec> {
/// Queue of events to processed.
events: Vec<NetworkBehaviourAction<RPCEvent<TSpec>, RPCMessage<TSpec>>>,
/// Pins the generic substream.
marker: PhantomData<TSubstream>,
/// Slog logger for RPC behaviour.
log: slog::Logger,
}
impl<TSubstream, TSpec: EthSpec> RPC<TSubstream, TSpec> {
impl<TSpec: EthSpec> RPC<TSpec> {
pub fn new(log: slog::Logger) -> Self {
let log = log.new(o!("service" => "libp2p_rpc"));
RPC {
events: Vec::new(),
marker: PhantomData,
log,
}
}
@@ -91,19 +95,19 @@ impl<TSubstream, TSpec: EthSpec> RPC<TSubstream, TSpec> {
///
/// The peer must be connected for this to succeed.
pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent<TSpec>) {
self.events.push(NetworkBehaviourAction::SendEvent {
self.events.push(NetworkBehaviourAction::NotifyHandler {
peer_id,
handler: NotifyHandler::Any,
event: rpc_event,
});
}
}
impl<TSubstream, TSpec> NetworkBehaviour for RPC<TSubstream, TSpec>
impl<TSpec> NetworkBehaviour for RPC<TSpec>
where
TSubstream: AsyncRead + AsyncWrite,
TSpec: EthSpec,
{
type ProtocolsHandler = RPCHandler<TSubstream, TSpec>;
type ProtocolsHandler = RPCHandler<TSpec>;
type OutEvent = RPCMessage<TSpec>;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
@@ -121,75 +125,64 @@ where
Vec::new()
}
fn inject_connected(&mut self, peer_id: PeerId, connected_point: ConnectedPoint) {
// TODO: Remove this on proper peer discovery
self.events.push(NetworkBehaviourAction::GenerateEvent(
RPCMessage::PeerConnectedHack(peer_id.clone(), connected_point.clone()),
));
// if initialised the connection, report this upwards to send the HELLO request
if let ConnectedPoint::Dialer { .. } = connected_point {
self.events.push(NetworkBehaviourAction::GenerateEvent(
RPCMessage::PeerDialed(peer_id.clone()),
));
}
// Use connection established/closed instead of these currently
fn inject_connected(&mut self, peer_id: &PeerId) {
// find the peer's meta-data
debug!(self.log, "Requesting new peer's metadata"; "peer_id" => format!("{}",peer_id));
let rpc_event =
RPCEvent::Request(RequestId::from(0usize), RPCRequest::MetaData(PhantomData));
self.events.push(NetworkBehaviourAction::SendEvent {
peer_id,
self.events.push(NetworkBehaviourAction::NotifyHandler {
peer_id: peer_id.clone(),
handler: NotifyHandler::Any,
event: rpc_event,
});
}
fn inject_disconnected(&mut self, peer_id: &PeerId, connected_point: ConnectedPoint) {
// TODO: Remove this on proper peer discovery
self.events.push(NetworkBehaviourAction::GenerateEvent(
RPCMessage::PeerDisconnectedHack(peer_id.clone(), connected_point.clone()),
));
fn inject_disconnected(&mut self, _peer_id: &PeerId) {}
// inform the rpc handler that the peer has disconnected
self.events.push(NetworkBehaviourAction::GenerateEvent(
RPCMessage::PeerDisconnected(peer_id.clone()),
));
fn inject_connection_established(
&mut self,
_peer_id: &PeerId,
_: &ConnectionId,
_connected_point: &ConnectedPoint,
) {
}
fn inject_node_event(
fn inject_connection_closed(
&mut self,
_peer_id: &PeerId,
_: &ConnectionId,
_connected_point: &ConnectedPoint,
) {
}
fn inject_event(
&mut self,
source: PeerId,
_: ConnectionId,
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) {
// send the event to the user
self.events
.push(NetworkBehaviourAction::GenerateEvent(RPCMessage::RPC(
source, event,
)));
.push(NetworkBehaviourAction::GenerateEvent(RPCMessage {
peer_id: source,
event,
}));
}
fn poll(
&mut self,
_cx: &mut Context,
_: &mut impl PollParameters,
) -> Async<
) -> Poll<
NetworkBehaviourAction<
<Self::ProtocolsHandler as ProtocolsHandler>::InEvent,
Self::OutEvent,
>,
> {
if !self.events.is_empty() {
return Async::Ready(self.events.remove(0));
return Poll::Ready(self.events.remove(0));
}
Async::NotReady
Poll::Pending
}
}
/// Messages sent to the user from the RPC protocol.
pub enum RPCMessage<TSpec: EthSpec> {
RPC(PeerId, RPCEvent<TSpec>),
PeerDialed(PeerId),
PeerDisconnected(PeerId),
// TODO: This is a hack to give access to connections to peer manager. Remove this once
// behaviour is re-written
PeerConnectedHack(PeerId, ConnectedPoint),
PeerDisconnectedHack(PeerId, ConnectedPoint),
}

View File

@@ -10,17 +10,19 @@ use crate::rpc::{
},
methods::ResponseTermination,
};
use futures::future::*;
use futures::{future, sink, stream, Sink, Stream};
use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, ProtocolName, UpgradeInfo};
use futures::future::Ready;
use futures::prelude::*;
use futures::prelude::{AsyncRead, AsyncWrite};
use libp2p::core::{InboundUpgrade, OutboundUpgrade, ProtocolName, UpgradeInfo};
use std::io;
use std::marker::PhantomData;
use std::pin::Pin;
use std::time::Duration;
use tokio::codec::Framed;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::timer::timeout;
use tokio::util::FutureExt;
use tokio_io_timeout::TimeoutStream;
use tokio_util::{
codec::Framed,
compat::{Compat, FuturesAsyncReadCompatExt},
};
use types::EthSpec;
/// The maximum bytes that can be sent across the RPC.
@@ -171,45 +173,28 @@ impl ProtocolName for ProtocolId {
pub type InboundOutput<TSocket, TSpec> = (RPCRequest<TSpec>, InboundFramed<TSocket, TSpec>);
pub type InboundFramed<TSocket, TSpec> =
Framed<TimeoutStream<upgrade::Negotiated<TSocket>>, InboundCodec<TSpec>>;
// Auxiliary types
// The type of the socket timeout in the `InboundUpgrade` type `Future`
type TTimeout<TSocket, TSpec> =
timeout::Timeout<stream::StreamFuture<InboundFramed<TSocket, TSpec>>>;
// The type of the socket timeout error in the `InboundUpgrade` type `Future`
type TTimeoutErr<TSocket, TSpec> = timeout::Error<(RPCError, InboundFramed<TSocket, TSpec>)>;
// `TimeoutErr` to `RPCError` mapping function
type FnMapErr<TSocket, TSpec> = fn(TTimeoutErr<TSocket, TSpec>) -> RPCError;
Framed<TimeoutStream<Compat<TSocket>>, InboundCodec<TSpec>>;
type FnAndThen<TSocket, TSpec> = fn(
(Option<RPCRequest<TSpec>>, InboundFramed<TSocket, TSpec>),
) -> FutureResult<InboundOutput<TSocket, TSpec>, RPCError>;
(
Option<Result<RPCRequest<TSpec>, RPCError>>,
InboundFramed<TSocket, TSpec>,
),
) -> Ready<Result<InboundOutput<TSocket, TSpec>, RPCError>>;
type FnMapErr = fn(tokio::time::Elapsed) -> RPCError;
impl<TSocket, TSpec> InboundUpgrade<TSocket> for RPCProtocol<TSpec>
where
TSocket: AsyncRead + AsyncWrite,
TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static,
TSpec: EthSpec,
{
type Output = InboundOutput<TSocket, TSpec>;
type Error = RPCError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
type Future = future::Either<
FutureResult<InboundOutput<TSocket, TSpec>, RPCError>,
future::AndThen<
future::MapErr<TTimeout<TSocket, TSpec>, FnMapErr<TSocket, TSpec>>,
FutureResult<InboundOutput<TSocket, TSpec>, RPCError>,
FnAndThen<TSocket, TSpec>,
>,
>;
fn upgrade_inbound(
self,
socket: upgrade::Negotiated<TSocket>,
protocol: ProtocolId,
) -> Self::Future {
fn upgrade_inbound(self, socket: TSocket, protocol: ProtocolId) -> Self::Future {
let protocol_name = protocol.message_name;
// convert the socket to tokio compatible socket
let socket = socket.compat();
let codec = match protocol.encoding {
Encoding::SSZSnappy => {
let ssz_snappy_codec =
@@ -226,32 +211,23 @@ where
let socket = Framed::new(timed_socket, codec);
match protocol_name {
// `MetaData` requests should be empty, return the stream
// MetaData requests should be empty, return the stream
Box::pin(match protocol_name {
Protocol::MetaData => {
future::Either::A(future::ok((RPCRequest::MetaData(PhantomData), socket)))
future::Either::Left(future::ok((RPCRequest::MetaData(PhantomData), socket)))
}
_ => future::Either::B({
socket
.into_future()
.timeout(Duration::from_secs(REQUEST_TIMEOUT))
.map_err({
|err| {
if err.is_elapsed() {
RPCError::StreamTimeout
} else {
RPCError::InternalError("Stream timer failed")
}
}
} as FnMapErr<TSocket, TSpec>)
_ => future::Either::Right(
tokio::time::timeout(Duration::from_secs(REQUEST_TIMEOUT), socket.into_future())
.map_err(RPCError::from as FnMapErr)
.and_then({
|(req, stream)| match req {
Some(request) => future::ok((request, stream)),
None => future::err(RPCError::IncompleteStream),
Some(Ok(request)) => future::ok((request, stream)),
Some(Err(_)) | None => future::err(RPCError::IncompleteStream),
}
} as FnAndThen<TSocket, TSpec>)
}),
}
} as FnAndThen<TSocket, TSpec>),
),
})
}
}
@@ -371,23 +347,20 @@ impl<TSpec: EthSpec> RPCRequest<TSpec> {
/* Outbound upgrades */
pub type OutboundFramed<TSocket, TSpec> =
Framed<upgrade::Negotiated<TSocket>, OutboundCodec<TSpec>>;
pub type OutboundFramed<TSocket, TSpec> = Framed<Compat<TSocket>, OutboundCodec<TSpec>>;
impl<TSocket, TSpec> OutboundUpgrade<TSocket> for RPCRequest<TSpec>
where
TSpec: EthSpec,
TSocket: AsyncRead + AsyncWrite,
TSpec: EthSpec + Send + 'static,
TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Output = OutboundFramed<TSocket, TSpec>;
type Error = RPCError;
type Future = sink::Send<OutboundFramed<TSocket, TSpec>>;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_outbound(
self,
socket: upgrade::Negotiated<TSocket>,
protocol: Self::Info,
) -> Self::Future {
fn upgrade_outbound(self, socket: TSocket, protocol: Self::Info) -> Self::Future {
// convert to a tokio compatible socket
let socket = socket.compat();
let codec = match protocol.encoding {
Encoding::SSZSnappy => {
let ssz_snappy_codec =
@@ -400,18 +373,22 @@ where
OutboundCodec::SSZ(ssz_codec)
}
};
Framed::new(socket, codec).send(self)
let mut socket = Framed::new(socket, codec);
let future = async { socket.send(self).await.map(|_| socket) };
Box::pin(future)
}
}
/// Error in RPC Encoding/Decoding.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum RPCError {
/// Error when decoding the raw buffer from ssz.
// NOTE: in the future a ssz::DecodeError should map to an InvalidData error
SSZDecodeError(ssz::DecodeError),
/// IO Error.
IoError(io::Error),
IoError(String),
/// The peer returned a valid response but the response indicated an error.
ErrorResponse(RPCResponseErrorCode),
/// Timed out waiting for a response.
@@ -434,10 +411,15 @@ impl From<ssz::DecodeError> for RPCError {
RPCError::SSZDecodeError(err)
}
}
impl From<tokio::time::Elapsed> for RPCError {
fn from(_: tokio::time::Elapsed) -> Self {
RPCError::StreamTimeout
}
}
impl From<io::Error> for RPCError {
fn from(err: io::Error) -> Self {
RPCError::IoError(err)
RPCError::IoError(err.to_string())
}
}
@@ -463,7 +445,7 @@ impl std::error::Error for RPCError {
match *self {
// NOTE: this does have a source
RPCError::SSZDecodeError(_) => None,
RPCError::IoError(ref err) => Some(err),
RPCError::IoError(_) => None,
RPCError::StreamTimeout => None,
RPCError::UnsupportedProtocol => None,
RPCError::IncompleteStream => None,

View File

@@ -2,45 +2,76 @@ use crate::behaviour::{Behaviour, BehaviourEvent};
use crate::discovery::enr;
use crate::multiaddr::Protocol;
use crate::types::{error, GossipKind};
use crate::EnrExt;
use crate::{NetworkConfig, NetworkGlobals};
use futures::prelude::*;
use futures::Stream;
use libp2p::core::{
identity::Keypair,
multiaddr::Multiaddr,
muxing::StreamMuxerBox,
nodes::Substream,
transport::boxed::Boxed,
upgrade::{InboundUpgradeExt, OutboundUpgradeExt},
ConnectedPoint,
};
use libp2p::{core, noise, secio, swarm::NetworkBehaviour, PeerId, Swarm, Transport};
use slog::{crit, debug, error, info, trace, warn};
use libp2p::{
core, noise, secio,
swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent},
PeerId, Swarm, Transport,
};
use slog::{crit, debug, error, info, o, trace, warn};
use std::fs::File;
use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use tokio::timer::DelayQueue;
use tokio::time::DelayQueue;
use types::{EnrForkId, EthSpec};
type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>;
type Libp2pBehaviour<TSpec> = Behaviour<Substream<StreamMuxerBox>, TSpec>;
pub const NETWORK_KEY_FILENAME: &str = "key";
/// The time in milliseconds to wait before banning a peer. This allows for any Goodbye messages to be
/// flushed and protocols to be negotiated.
const BAN_PEER_WAIT_TIMEOUT: u64 = 200;
/// The maximum simultaneous libp2p connections per peer.
const MAX_CONNECTIONS_PER_PEER: usize = 1;
/// The types of events than can be obtained from polling the libp2p service.
///
/// This is a subset of the events that a libp2p swarm emits.
#[derive(Debug)]
pub enum Libp2pEvent<TSpec: EthSpec> {
/// A behaviour event
Behaviour(BehaviourEvent<TSpec>),
/// A new listening address has been established.
NewListenAddr(Multiaddr),
/// A peer has established at least one connection.
PeerConnected {
/// The peer that connected.
peer_id: PeerId,
/// Whether the peer was a dialer or listener.
endpoint: ConnectedPoint,
},
/// A peer no longer has any connections, i.e is disconnected.
PeerDisconnected {
/// The peer the disconnected.
peer_id: PeerId,
/// Whether the peer was a dialer or a listener.
endpoint: ConnectedPoint,
},
}
/// The configuration and state of the libp2p components for the beacon node.
pub struct Service<TSpec: EthSpec> {
/// The libp2p Swarm handler.
//TODO: Make this private
pub swarm: Swarm<Libp2pStream, Libp2pBehaviour<TSpec>>,
pub swarm: Swarm<Behaviour<TSpec>>,
/// This node's PeerId.
pub local_peer_id: PeerId,
/// Used for managing the state of peers.
network_globals: Arc<NetworkGlobals<TSpec>>,
/// A current list of peers to ban after a given timeout.
peers_to_ban: DelayQueue<PeerId>,
@@ -55,8 +86,9 @@ impl<TSpec: EthSpec> Service<TSpec> {
pub fn new(
config: &NetworkConfig,
enr_fork_id: EnrForkId,
log: slog::Logger,
log: &slog::Logger,
) -> error::Result<(Arc<NetworkGlobals<TSpec>>, Self)> {
let log = log.new(o!("service"=> "libp2p"));
trace!(log, "Libp2p Service starting");
// initialise the node's ID
@@ -84,10 +116,22 @@ impl<TSpec: EthSpec> Service<TSpec> {
let mut swarm = {
// Set up the transport - tcp/ws with noise/secio and mplex/yamux
let transport = build_transport(local_keypair.clone());
let transport = build_transport(local_keypair.clone())
.map_err(|e| format!("Failed to build transport: {:?}", e))?;
// Lighthouse network behaviour
let behaviour = Behaviour::new(&local_keypair, config, network_globals.clone(), &log)?;
Swarm::new(transport, behaviour, local_peer_id.clone())
// use the executor for libp2p
struct Executor(tokio::runtime::Handle);
impl libp2p::core::Executor for Executor {
fn exec(&self, f: Pin<Box<dyn Future<Output = ()> + Send>>) {
self.0.spawn(f);
}
}
SwarmBuilder::new(transport, behaviour, local_peer_id.clone())
.peer_connection_limit(MAX_CONNECTIONS_PER_PEER)
.executor(Box::new(Executor(tokio::runtime::Handle::current())))
.build()
};
// listen on the specified address
@@ -131,19 +175,24 @@ impl<TSpec: EthSpec> Service<TSpec> {
}
// attempt to connect to any specified boot-nodes
for bootnode_enr in &config.boot_nodes {
let mut boot_nodes = config.boot_nodes.clone();
boot_nodes.dedup();
for bootnode_enr in boot_nodes {
for multiaddr in &bootnode_enr.multiaddr() {
// ignore udp multiaddr if it exists
let components = multiaddr.iter().collect::<Vec<_>>();
if let Protocol::Udp(_) = components[1] {
continue;
}
// inform the peer manager that we are currently dialing this peer
network_globals
if !network_globals
.peers
.write()
.dialing_peer(&bootnode_enr.peer_id());
dial_addr(multiaddr);
.read()
.is_connected_or_dialing(&bootnode_enr.peer_id())
{
dial_addr(multiaddr);
}
}
}
@@ -160,6 +209,7 @@ impl<TSpec: EthSpec> Service<TSpec> {
let service = Service {
local_peer_id,
swarm,
network_globals: network_globals.clone(),
peers_to_ban: DelayQueue::new(),
peer_ban_timeout: DelayQueue::new(),
log,
@@ -177,76 +227,132 @@ impl<TSpec: EthSpec> Service<TSpec> {
);
self.peer_ban_timeout.insert(peer_id, timeout);
}
}
impl<TSpec: EthSpec> Stream for Service<TSpec> {
type Item = BehaviourEvent<TSpec>;
type Error = error::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
pub async fn next_event(&mut self) -> Libp2pEvent<TSpec> {
loop {
match self.swarm.poll() {
Ok(Async::Ready(Some(event))) => {
return Ok(Async::Ready(Some(event)));
}
Ok(Async::Ready(None)) => unreachable!("Swarm stream shouldn't end"),
Ok(Async::NotReady) => break,
_ => break,
}
}
tokio::select! {
event = self.swarm.next_event() => {
match event {
SwarmEvent::Behaviour(behaviour) => {
return Libp2pEvent::Behaviour(behaviour)
}
SwarmEvent::ConnectionEstablished {
peer_id,
endpoint,
num_established,
} => {
debug!(self.log, "Connection established"; "peer_id"=> peer_id.to_string(), "connections" => num_established.get());
// if this is the first connection inform the network layer a new connection
// has been established and update the db
if num_established.get() == 1 {
// update the peerdb
match endpoint {
ConnectedPoint::Listener { .. } => {
self.swarm.peer_manager().connect_ingoing(&peer_id);
}
ConnectedPoint::Dialer { .. } => self
.network_globals
.peers
.write()
.connect_outgoing(&peer_id),
}
return Libp2pEvent::PeerConnected { peer_id, endpoint };
}
}
SwarmEvent::ConnectionClosed {
peer_id,
cause,
endpoint,
num_established,
} => {
debug!(self.log, "Connection closed"; "peer_id"=> peer_id.to_string(), "cause" => cause.to_string(), "connections" => num_established);
if num_established == 0 {
// update the peer_db
self.swarm.peer_manager().notify_disconnect(&peer_id);
// the peer has disconnected
return Libp2pEvent::PeerDisconnected {
peer_id,
endpoint,
};
}
}
SwarmEvent::NewListenAddr(multiaddr) => {
return Libp2pEvent::NewListenAddr(multiaddr)
}
// check if peers need to be banned
loop {
match self.peers_to_ban.poll() {
Ok(Async::Ready(Some(peer_id))) => {
let peer_id = peer_id.into_inner();
Swarm::ban_peer_id(&mut self.swarm, peer_id.clone());
// TODO: Correctly notify protocols of the disconnect
// TODO: Also remove peer from the DHT: https://github.com/sigp/lighthouse/issues/629
let dummy_connected_point = ConnectedPoint::Dialer {
address: "/ip4/0.0.0.0"
.parse::<Multiaddr>()
.expect("valid multiaddr"),
};
self.swarm
.inject_disconnected(&peer_id, dummy_connected_point);
// inform the behaviour that the peer has been banned
self.swarm.peer_banned(peer_id);
}
Ok(Async::NotReady) | Ok(Async::Ready(None)) => break,
Err(e) => {
warn!(self.log, "Peer banning queue failed"; "error" => format!("{:?}", e));
SwarmEvent::IncomingConnection {
local_addr,
send_back_addr,
} => {
debug!(self.log, "Incoming connection"; "our_addr" => local_addr.to_string(), "from" => send_back_addr.to_string())
}
SwarmEvent::IncomingConnectionError {
local_addr,
send_back_addr,
error,
} => {
debug!(self.log, "Failed incoming connection"; "our_addr" => local_addr.to_string(), "from" => send_back_addr.to_string(), "error" => error.to_string())
}
SwarmEvent::BannedPeer {
peer_id,
endpoint: _,
} => {
debug!(self.log, "Attempted to dial a banned peer"; "peer_id" => peer_id.to_string())
}
SwarmEvent::UnreachableAddr {
peer_id,
address,
error,
attempts_remaining,
} => {
debug!(self.log, "Failed to dial address"; "peer_id" => peer_id.to_string(), "address" => address.to_string(), "error" => error.to_string(), "attempts_remaining" => attempts_remaining);
self.swarm.peer_manager().notify_disconnect(&peer_id);
}
SwarmEvent::UnknownPeerUnreachableAddr { address, error } => {
debug!(self.log, "Peer not known at dialed address"; "address" => address.to_string(), "error" => error.to_string());
}
SwarmEvent::ExpiredListenAddr(multiaddr) => {
debug!(self.log, "Listen address expired"; "multiaddr" => multiaddr.to_string())
}
SwarmEvent::ListenerClosed { addresses, reason } => {
debug!(self.log, "Listener closed"; "addresses" => format!("{:?}", addresses), "reason" => format!("{:?}", reason))
}
SwarmEvent::ListenerError { error } => {
debug!(self.log, "Listener error"; "error" => format!("{:?}", error.to_string()))
}
SwarmEvent::Dialing(peer_id) => {
debug!(self.log, "Dialing peer"; "peer" => peer_id.to_string());
self.swarm.peer_manager().dialing_peer(&peer_id);
}
}
}
}
// un-ban peer if it's timeout has expired
loop {
match self.peer_ban_timeout.poll() {
Ok(Async::Ready(Some(peer_id))) => {
let peer_id = peer_id.into_inner();
debug!(self.log, "Peer has been unbanned"; "peer" => format!("{:?}", peer_id));
self.swarm.peer_unbanned(&peer_id);
Swarm::unban_peer_id(&mut self.swarm, peer_id);
}
Ok(Async::NotReady) | Ok(Async::Ready(None)) => break,
Err(e) => {
warn!(self.log, "Peer banning timeout queue failed"; "error" => format!("{:?}", e));
}
Some(Ok(peer_to_ban)) = self.peers_to_ban.next() => {
let peer_id = peer_to_ban.into_inner();
Swarm::ban_peer_id(&mut self.swarm, peer_id.clone());
// TODO: Correctly notify protocols of the disconnect
// TODO: Also remove peer from the DHT: https://github.com/sigp/lighthouse/issues/629
self.swarm.inject_disconnected(&peer_id);
// inform the behaviour that the peer has been banned
self.swarm.peer_banned(peer_id);
}
Some(Ok(peer_to_unban)) = self.peer_ban_timeout.next() => {
debug!(self.log, "Peer has been unbanned"; "peer" => format!("{:?}", peer_to_unban));
let unban_peer = peer_to_unban.into_inner();
self.swarm.peer_unbanned(&unban_peer);
Swarm::unban_peer_id(&mut self.swarm, unban_peer);
}
}
}
Ok(Async::NotReady)
}
}
/// The implementation supports TCP/IP, WebSockets over TCP/IP, noise/secio as the encryption layer, and
/// mplex or yamux as the multiplexing layer.
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().nodelay(true);
let transport = libp2p::dns::DnsConfig::new(transport);
fn build_transport(
local_private_key: Keypair,
) -> Result<Boxed<(PeerId, StreamMuxerBox), Error>, Error> {
let transport = libp2p_tcp::TokioTcpConfig::new().nodelay(true);
let transport = libp2p::dns::DnsConfig::new(transport)?;
#[cfg(feature = "libp2p-websocket")]
let transport = {
let trans_clone = transport.clone();
@@ -260,7 +366,7 @@ fn build_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox)
secio::SecioConfig::new(local_private_key),
);
core::upgrade::apply(stream, upgrade, endpoint, core::upgrade::Version::V1).and_then(
move |out| {
|out| async move {
match out {
// Noise was negotiated
core::either::EitherOutput::First((remote_id, out)) => {
@@ -288,12 +394,12 @@ fn build_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox)
.map_outbound(move |muxer| (peer_id2, muxer));
core::upgrade::apply(stream, upgrade, endpoint, core::upgrade::Version::V1)
.map(|(id, muxer)| (id, core::muxing::StreamMuxerBox::new(muxer)))
.map_ok(|(id, muxer)| (id, core::muxing::StreamMuxerBox::new(muxer)))
})
.timeout(Duration::from_secs(20))
.map_err(|err| Error::new(ErrorKind::Other, err))
.boxed();
transport
Ok(transport)
}
fn keypair_from_hex(hex_bytes: &str) -> error::Result<Keypair> {

View File

@@ -2,6 +2,7 @@
use crate::peer_manager::PeerDB;
use crate::rpc::methods::MetaData;
use crate::types::SyncState;
use crate::EnrExt;
use crate::{discovery::enr::Eth2Enr, Enr, GossipTopic, Multiaddr, PeerId};
use parking_lot::RwLock;
use std::collections::HashSet;

View File

@@ -9,7 +9,7 @@ use types::{BitVector, EthSpec};
#[allow(type_alias_bounds)]
pub type EnrBitfield<T: EthSpec> = BitVector<T::SubnetBitfieldLength>;
pub type Enr = libp2p::discv5::enr::Enr<libp2p::discv5::enr::CombinedKey>;
pub type Enr = discv5::enr::Enr<discv5::enr::CombinedKey>;
pub use globals::NetworkGlobals;
pub use pubsub::PubsubMessage;

View File

@@ -1,8 +1,9 @@
#![cfg(test)]
use eth2_libp2p::Enr;
use eth2_libp2p::EnrExt;
use eth2_libp2p::Multiaddr;
use eth2_libp2p::NetworkConfig;
use eth2_libp2p::Service as LibP2PService;
use eth2_libp2p::{Libp2pEvent, NetworkConfig};
use slog::{debug, error, o, Drain};
use std::net::{TcpListener, UdpSocket};
use std::time::Duration;
@@ -85,7 +86,7 @@ pub fn build_libp2p_instance(
let port = unused_port("tcp").unwrap();
let config = build_config(port, boot_nodes, secret_key);
// launch libp2p service
LibP2PService::new(&config, EnrForkId::default(), log.clone())
LibP2PService::new(&config, EnrForkId::default(), &log)
.expect("should build libp2p instance")
.1
}
@@ -93,7 +94,6 @@ pub fn build_libp2p_instance(
#[allow(dead_code)]
pub fn get_enr(node: &LibP2PService<E>) -> Enr {
let enr = node.swarm.discovery().local_enr().clone();
dbg!(enr.multiaddr());
enr
}
@@ -121,19 +121,46 @@ pub fn build_full_mesh(log: slog::Logger, n: usize) -> Vec<LibP2PService<E>> {
nodes
}
// Constructs a pair of nodes with seperate loggers. The sender dials the receiver.
// Constructs a pair of nodes with separate loggers. The sender dials the receiver.
// This returns a (sender, receiver) pair.
#[allow(dead_code)]
pub fn build_node_pair(log: &slog::Logger) -> (LibP2PService<E>, LibP2PService<E>) {
pub async fn build_node_pair(log: &slog::Logger) -> (LibP2PService<E>, LibP2PService<E>) {
let sender_log = log.new(o!("who" => "sender"));
let receiver_log = log.new(o!("who" => "receiver"));
let mut sender = build_libp2p_instance(vec![], None, sender_log);
let receiver = build_libp2p_instance(vec![], None, receiver_log);
let mut receiver = build_libp2p_instance(vec![], None, receiver_log);
let receiver_multiaddr = receiver.swarm.discovery().local_enr().clone().multiaddr()[1].clone();
match libp2p::Swarm::dial_addr(&mut sender.swarm, receiver_multiaddr) {
Ok(()) => debug!(log, "Sender dialed receiver"),
// let the two nodes set up listeners
let sender_fut = async {
loop {
if let Libp2pEvent::NewListenAddr(_) = sender.next_event().await {
return;
}
}
};
let receiver_fut = async {
loop {
if let Libp2pEvent::NewListenAddr(_) = receiver.next_event().await {
return;
}
}
};
let joined = futures::future::join(sender_fut, receiver_fut);
// wait for either both nodes to listen or a timeout
tokio::select! {
_ = tokio::time::delay_for(Duration::from_millis(500)) => {}
_ = joined => {}
}
match libp2p::Swarm::dial_addr(&mut sender.swarm, receiver_multiaddr.clone()) {
Ok(()) => {
debug!(log, "Sender dialed receiver"; "address" => format!("{:?}", receiver_multiaddr))
}
Err(_) => error!(log, "Dialing failed"),
};
(sender, receiver)

View File

@@ -2,7 +2,6 @@
use crate::types::GossipEncoding;
use ::types::{BeaconBlock, EthSpec, MinimalEthSpec, Signature, SignedBeaconBlock};
use eth2_libp2p::*;
use futures::prelude::*;
use slog::{debug, Level};
type E = MinimalEthSpec;
@@ -19,8 +18,8 @@ mod common;
//
// node1 <-> node2 <-> node3 ..... <-> node(n-1) <-> node(n)
#[test]
fn test_gossipsub_forward() {
#[tokio::test]
async fn test_gossipsub_forward() {
// set up the logging. The level and enabled or not
let log = common::build_log(Level::Info, false);
@@ -41,55 +40,64 @@ fn test_gossipsub_forward() {
.clone()
.into();
let mut subscribed_count = 0;
tokio::run(futures::future::poll_fn(move || -> Result<_, ()> {
let fut = async move {
for node in nodes.iter_mut() {
loop {
match node.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::PubsubMessage {
topics,
message,
source,
id,
})) => {
assert_eq!(topics.len(), 1);
// Assert topic is the published topic
assert_eq!(
topics.first().unwrap(),
&TopicHash::from_raw(publishing_topic.clone())
);
// Assert message received is the correct one
assert_eq!(message, pubsub_message.clone());
received_count += 1;
// Since `propagate_message` is false, need to propagate manually
node.swarm.propagate_message(&source, id);
// Test should succeed if all nodes except the publisher receive the message
if received_count == num_nodes - 1 {
debug!(log.clone(), "Received message at {} nodes", num_nodes - 1);
return Ok(Async::Ready(()));
}
}
Async::Ready(Some(BehaviourEvent::PeerSubscribed(_, topic))) => {
// Publish on beacon block topic
if topic == TopicHash::from_raw(publishing_topic.clone()) {
subscribed_count += 1;
// Every node except the corner nodes are connected to 2 nodes.
if subscribed_count == (num_nodes * 2) - 2 {
node.swarm.publish(vec![pubsub_message.clone()]);
match node.next_event().await {
Libp2pEvent::Behaviour(b) => match b {
BehaviourEvent::PubsubMessage {
topics,
message,
source,
id,
} => {
assert_eq!(topics.len(), 1);
// Assert topic is the published topic
assert_eq!(
topics.first().unwrap(),
&TopicHash::from_raw(publishing_topic.clone())
);
// Assert message received is the correct one
assert_eq!(message, pubsub_message.clone());
received_count += 1;
// Since `propagate_message` is false, need to propagate manually
node.swarm.propagate_message(&source, id);
// Test should succeed if all nodes except the publisher receive the message
if received_count == num_nodes - 1 {
debug!(log.clone(), "Received message at {} nodes", num_nodes - 1);
return;
}
}
}
BehaviourEvent::PeerSubscribed(_, topic) => {
// Publish on beacon block topic
if topic == TopicHash::from_raw(publishing_topic.clone()) {
subscribed_count += 1;
// Every node except the corner nodes are connected to 2 nodes.
if subscribed_count == (num_nodes * 2) - 2 {
node.swarm.publish(vec![pubsub_message.clone()]);
}
}
}
_ => break,
},
_ => break,
}
}
}
Ok(Async::NotReady)
}))
};
tokio::select! {
_ = fut => {}
_ = tokio::time::delay_for(tokio::time::Duration::from_millis(800)) => {
panic!("Future timed out");
}
}
}
// Test publishing of a message with a full mesh for the topic
// Not very useful but this is the bare minimum functionality.
#[test]
fn test_gossipsub_full_mesh_publish() {
#[tokio::test]
async fn test_gossipsub_full_mesh_publish() {
// set up the logging. The level and enabled or not
let log = common::build_log(Level::Debug, false);
@@ -115,11 +123,13 @@ fn test_gossipsub_full_mesh_publish() {
.into();
let mut subscribed_count = 0;
let mut received_count = 0;
tokio::run(futures::future::poll_fn(move || -> Result<_, ()> {
let fut = async move {
for node in nodes.iter_mut() {
while let Async::Ready(Some(BehaviourEvent::PubsubMessage {
topics, message, ..
})) = node.poll().unwrap()
while let Libp2pEvent::Behaviour(BehaviourEvent::PubsubMessage {
topics,
message,
..
}) = node.next_event().await
{
assert_eq!(topics.len(), 1);
// Assert topic is the published topic
@@ -131,12 +141,12 @@ fn test_gossipsub_full_mesh_publish() {
assert_eq!(message, pubsub_message.clone());
received_count += 1;
if received_count == num_nodes - 1 {
return Ok(Async::Ready(()));
return;
}
}
}
while let Async::Ready(Some(BehaviourEvent::PeerSubscribed(_, topic))) =
publishing_node.poll().unwrap()
while let Libp2pEvent::Behaviour(BehaviourEvent::PeerSubscribed(_, topic)) =
publishing_node.next_event().await
{
// Publish on beacon block topic
if topic == TopicHash::from_raw(publishing_topic.clone()) {
@@ -146,6 +156,11 @@ fn test_gossipsub_full_mesh_publish() {
}
}
}
Ok(Async::NotReady)
}))
};
tokio::select! {
_ = fut => {}
_ = tokio::time::delay_for(tokio::time::Duration::from_millis(800)) => {
panic!("Future timed out");
}
}
}

View File

@@ -1,39 +1,39 @@
#![cfg(test)]
use crate::behaviour::{Behaviour, BehaviourEvent};
use crate::behaviour::Behaviour;
use crate::multiaddr::Protocol;
use ::types::{EnrForkId, MinimalEthSpec};
use eth2_libp2p::discovery::build_enr;
use eth2_libp2p::discovery::{build_enr, CombinedKey, CombinedKeyExt};
use eth2_libp2p::*;
use futures::prelude::*;
use libp2p::core::identity::Keypair;
use libp2p::{
core,
core::{muxing::StreamMuxerBox, nodes::Substream, transport::boxed::Boxed},
secio, PeerId, Swarm, Transport,
core::{muxing::StreamMuxerBox, transport::boxed::Boxed},
secio,
swarm::{SwarmBuilder, SwarmEvent},
PeerId, Swarm, Transport,
};
use slog::{crit, debug, info, Level};
use std::convert::TryInto;
use std::io::{Error, ErrorKind};
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use tokio::prelude::*;
type TSpec = MinimalEthSpec;
mod common;
type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>;
type Libp2pBehaviour = Behaviour<Substream<StreamMuxerBox>, TSpec>;
type Libp2pBehaviour = Behaviour<TSpec>;
/// Build and return a eth2_libp2p Swarm with only secio support.
fn build_secio_swarm(
config: &NetworkConfig,
log: slog::Logger,
) -> error::Result<Swarm<Libp2pStream, Libp2pBehaviour>> {
) -> error::Result<Swarm<Libp2pBehaviour>> {
let local_keypair = Keypair::generate_secp256k1();
let local_peer_id = PeerId::from(local_keypair.public());
let enr_key: libp2p::discv5::enr::CombinedKey = local_keypair.clone().try_into().unwrap();
let enr_key = CombinedKey::from_libp2p(&local_keypair).unwrap();
let enr = build_enr::<TSpec>(&enr_key, config, EnrForkId::default()).unwrap();
let network_globals = Arc::new(NetworkGlobals::new(
enr,
@@ -47,7 +47,16 @@ fn build_secio_swarm(
let transport = build_secio_transport(local_keypair.clone());
// Lighthouse network behaviour
let behaviour = Behaviour::new(&local_keypair, config, network_globals.clone(), &log)?;
Swarm::new(transport, behaviour, local_peer_id.clone())
// requires a tokio runtime
struct Executor(tokio::runtime::Handle);
impl libp2p::core::Executor for Executor {
fn exec(&self, f: Pin<Box<dyn Future<Output = ()> + Send>>) {
self.0.spawn(f);
}
}
SwarmBuilder::new(transport, behaviour, local_peer_id.clone())
.executor(Box::new(Executor(tokio::runtime::Handle::current())))
.build()
};
// listen on the specified address
@@ -101,7 +110,7 @@ fn build_secio_swarm(
/// Build a simple TCP transport with secio, mplex/yamux.
fn build_secio_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox), Error> {
let transport = libp2p::tcp::TcpConfig::new().nodelay(true);
let transport = libp2p_tcp::TokioTcpConfig::new().nodelay(true);
transport
.upgrade(core::upgrade::Version::V1)
.authenticate(secio::SecioConfig::new(local_private_key))
@@ -117,8 +126,8 @@ fn build_secio_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMux
}
/// Test if the encryption falls back to secio if noise isn't available
#[test]
fn test_secio_noise_fallback() {
#[tokio::test]
async fn test_secio_noise_fallback() {
// set up the logging. The level and enabled logging or not
let log_level = Level::Trace;
let enable_logging = false;
@@ -127,7 +136,7 @@ fn test_secio_noise_fallback() {
let port = common::unused_port("tcp").unwrap();
let noisy_config = common::build_config(port, vec![], None);
let mut noisy_node = Service::new(&noisy_config, EnrForkId::default(), log.clone())
let mut noisy_node = Service::new(&noisy_config, EnrForkId::default(), &log)
.expect("should build a libp2p instance")
.1;
@@ -142,40 +151,31 @@ fn test_secio_noise_fallback() {
let secio_log = log.clone();
let noisy_future = future::poll_fn(move || -> Poll<bool, ()> {
let noisy_future = async {
loop {
match noisy_node.poll().unwrap() {
_ => return Ok(Async::NotReady),
}
noisy_node.next_event().await;
}
});
};
let secio_future = future::poll_fn(move || -> Poll<bool, ()> {
let secio_future = async {
loop {
match secio_swarm.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id))) => {
match secio_swarm.next_event().await {
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
// secio node negotiated a secio transport with
// the noise compatible node
info!(secio_log, "Connected to peer {}", peer_id);
return Ok(Async::Ready(true));
return;
}
_ => return Ok(Async::NotReady),
_ => {} // Ignore all other events
}
}
});
};
// execute the futures and check the result
let test_result = Arc::new(AtomicBool::new(false));
let error_result = test_result.clone();
let thread_result = test_result.clone();
tokio::run(
noisy_future
.select(secio_future)
.timeout(Duration::from_millis(1000))
.map_err(move |_| error_result.store(false, Relaxed))
.map(move |result| {
thread_result.store(result.0, Relaxed);
}),
);
assert!(test_result.load(Relaxed));
tokio::select! {
_ = noisy_future => {}
_ = secio_future => {}
_ = tokio::time::delay_for(Duration::from_millis(800)) => {
panic!("Future timed out");
}
}
}

View File

@@ -1,12 +1,10 @@
#![cfg(test)]
use eth2_libp2p::rpc::methods::*;
use eth2_libp2p::rpc::*;
use eth2_libp2p::{BehaviourEvent, RPCEvent};
use slog::{warn, Level};
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
use std::sync::{Arc, Mutex};
use eth2_libp2p::{BehaviourEvent, Libp2pEvent, RPCEvent};
use slog::{debug, warn, Level};
use std::time::Duration;
use tokio::prelude::*;
use tokio::time::delay_for;
use types::{
BeaconBlock, Epoch, EthSpec, Hash256, MinimalEthSpec, Signature, SignedBeaconBlock, Slot,
};
@@ -15,17 +13,17 @@ mod common;
type E = MinimalEthSpec;
#[test]
#[tokio::test]
// Tests the STATUS RPC message
fn test_status_rpc() {
async fn test_status_rpc() {
// set up the logging. The level and enabled logging or not
let log_level = Level::Trace;
let log_level = Level::Debug;
let enable_logging = false;
let log = common::build_log(log_level, enable_logging);
// get sender/receiver
let (mut sender, mut receiver) = common::build_node_pair(&log);
let (mut sender, mut receiver) = common::build_node_pair(&log).await;
// Dummy STATUS RPC message
let rpc_request = RPCRequest::Status(StatusMessage {
@@ -45,92 +43,80 @@ fn test_status_rpc() {
head_slot: Slot::new(1),
});
let sender_request = rpc_request.clone();
let sender_log = log.clone();
let sender_response = rpc_response.clone();
// build the sender future
let sender_future = future::poll_fn(move || -> Poll<bool, ()> {
let sender_future = async {
loop {
match sender.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id))) => {
match sender.next_event().await {
Libp2pEvent::PeerConnected { peer_id, .. } => {
// Send a STATUS message
warn!(sender_log, "Sending RPC");
debug!(log, "Sending RPC");
sender
.swarm
.send_rpc(peer_id, RPCEvent::Request(1, sender_request.clone()));
.send_rpc(peer_id, RPCEvent::Request(10, rpc_request.clone()));
}
Async::Ready(Some(BehaviourEvent::RPC(_, event))) => match event {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(_, event)) => match event {
// Should receive the RPC response
RPCEvent::Response(id, response @ RPCCodedResponse::Success(_)) => {
if id == 1 {
warn!(sender_log, "Sender Received");
if id == 10 {
debug!(log, "Sender Received");
let response = {
match response {
RPCCodedResponse::Success(r) => r,
_ => unreachable!(),
}
};
assert_eq!(response, sender_response.clone());
warn!(sender_log, "Sender Completed");
return Ok(Async::Ready(true));
assert_eq!(response, rpc_response.clone());
debug!(log, "Sender Completed");
return;
}
}
e => panic!("Received invalid RPC message {}", e),
_ => {} // Ignore other RPC messages
},
Async::Ready(Some(_)) => (),
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
};
}
});
// build the receiver future
let receiver_future = future::poll_fn(move || -> Poll<bool, ()> {
loop {
match receiver.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::RPC(peer_id, event))) => match event {
// Should receive sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
warn!(log, "Receiver Received");
receiver.swarm.send_rpc(
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
),
);
}
}
e => panic!("Received invalid RPC message {}", e),
},
Async::Ready(Some(_)) => (),
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
_ => {}
}
}
});
};
// execute the futures and check the result
let test_result = Arc::new(AtomicBool::new(false));
let error_result = test_result.clone();
let thread_result = test_result.clone();
tokio::run(
sender_future
.select(receiver_future)
.timeout(Duration::from_millis(1000))
.map_err(move |_| error_result.store(false, Relaxed))
.map(move |result| {
thread_result.store(result.0, Relaxed);
}),
);
assert!(test_result.load(Relaxed));
// build the receiver future
let receiver_future = async {
loop {
match receiver.next_event().await {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(peer_id, event)) => {
match event {
// Should receive sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
debug!(log, "Receiver Received");
receiver.swarm.send_rpc(
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
),
);
}
}
_ => {} // Ignore other RPC requests
}
}
_ => {} // Ignore other events
}
}
};
tokio::select! {
_ = sender_future => {}
_ = receiver_future => {}
_ = delay_for(Duration::from_millis(800)) => {
panic!("Future timed out");
}
}
}
#[test]
#[tokio::test]
// Tests a streamed BlocksByRange RPC Message
fn test_blocks_by_range_chunked_rpc() {
async fn test_blocks_by_range_chunked_rpc() {
// set up the logging. The level and enabled logging or not
let log_level = Level::Trace;
let enable_logging = false;
@@ -140,7 +126,7 @@ fn test_blocks_by_range_chunked_rpc() {
let log = common::build_log(log_level, enable_logging);
// get sender/receiver
let (mut sender, mut receiver) = common::build_node_pair(&log);
let (mut sender, mut receiver) = common::build_node_pair(&log).await;
// BlocksByRange Request
let rpc_request = RPCRequest::BlocksByRange(BlocksByRangeRequest {
@@ -158,116 +144,100 @@ fn test_blocks_by_range_chunked_rpc() {
};
let rpc_response = RPCResponse::BlocksByRange(Box::new(empty_signed));
let sender_request = rpc_request.clone();
let sender_log = log.clone();
let sender_response = rpc_response.clone();
// keep count of the number of messages received
let messages_received = Arc::new(Mutex::new(0));
let mut messages_received = 0;
// build the sender future
let sender_future = future::poll_fn(move || -> Poll<bool, ()> {
let sender_future = async {
loop {
match sender.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id))) => {
// Send a BlocksByRange request
warn!(sender_log, "Sender sending RPC request");
match sender.next_event().await {
Libp2pEvent::PeerConnected { peer_id, .. } => {
// Send a STATUS message
debug!(log, "Sending RPC");
sender
.swarm
.send_rpc(peer_id, RPCEvent::Request(1, sender_request.clone()));
.send_rpc(peer_id, RPCEvent::Request(10, rpc_request.clone()));
}
Async::Ready(Some(BehaviourEvent::RPC(_, event))) => match event {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(_, event)) => match event {
// Should receive the RPC response
RPCEvent::Response(id, response) => {
if id == 1 {
warn!(sender_log, "Sender received a response");
if id == 10 {
warn!(log, "Sender received a response");
match response {
RPCCodedResponse::Success(res) => {
assert_eq!(res, sender_response.clone());
*messages_received.lock().unwrap() += 1;
warn!(sender_log, "Chunk received");
assert_eq!(res, rpc_response.clone());
messages_received += 1;
warn!(log, "Chunk received");
}
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
) => {
RPCCodedResponse::StreamTermination(_) => {
// should be exactly 10 messages before terminating
assert_eq!(
*messages_received.lock().unwrap(),
messages_to_send
);
assert_eq!(messages_received, messages_to_send);
// end the test
return Ok(Async::Ready(true));
return;
}
_ => panic!("Invalid RPC received"),
}
}
}
_ => panic!("Received invalid RPC message"),
_ => {} // Ignore other RPC messages
},
Async::Ready(Some(_)) => {}
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
};
_ => {} // Ignore other behaviour events
}
}
});
};
// build the receiver future
let receiver_future = future::poll_fn(move || -> Poll<bool, ()> {
let receiver_future = async {
loop {
match receiver.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::RPC(peer_id, event))) => match event {
// Should receive the sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
warn!(log, "Receiver got request");
match receiver.next_event().await {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(peer_id, event)) => {
match event {
// Should receive sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
warn!(log, "Receiver got request");
for _ in 1..=messages_to_send {
for _ in 1..=messages_to_send {
receiver.swarm.send_rpc(
peer_id.clone(),
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
),
);
}
// send the stream termination
receiver.swarm.send_rpc(
peer_id.clone(),
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
),
),
);
}
// send the stream termination
receiver.swarm.send_rpc(
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
),
),
);
}
_ => {} // Ignore other events
}
_ => panic!("Received invalid RPC message"),
},
Async::Ready(Some(_)) => (),
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
}
_ => {} // Ignore other events
}
}
});
};
// execute the futures and check the result
let test_result = Arc::new(AtomicBool::new(false));
let error_result = test_result.clone();
let thread_result = test_result.clone();
tokio::run(
sender_future
.select(receiver_future)
.timeout(Duration::from_millis(1000))
.map_err(move |_| error_result.store(false, Relaxed))
.map(move |result| {
thread_result.store(result.0, Relaxed);
}),
);
assert!(test_result.load(Relaxed));
tokio::select! {
_ = sender_future => {}
_ = receiver_future => {}
_ = delay_for(Duration::from_millis(800)) => {
panic!("Future timed out");
}
}
}
#[test]
#[tokio::test]
// Tests an empty response to a BlocksByRange RPC Message
fn test_blocks_by_range_single_empty_rpc() {
async fn test_blocks_by_range_single_empty_rpc() {
// set up the logging. The level and enabled logging or not
let log_level = Level::Trace;
let enable_logging = false;
@@ -275,7 +245,7 @@ fn test_blocks_by_range_single_empty_rpc() {
let log = common::build_log(log_level, enable_logging);
// get sender/receiver
let (mut sender, mut receiver) = common::build_node_pair(&log);
let (mut sender, mut receiver) = common::build_node_pair(&log).await;
// BlocksByRange Request
let rpc_request = RPCRequest::BlocksByRange(BlocksByRangeRequest {
@@ -293,116 +263,106 @@ fn test_blocks_by_range_single_empty_rpc() {
};
let rpc_response = RPCResponse::BlocksByRange(Box::new(empty_signed));
let sender_request = rpc_request.clone();
let sender_log = log.clone();
let sender_response = rpc_response.clone();
let messages_to_send = 1;
// keep count of the number of messages received
let messages_received = Arc::new(Mutex::new(0));
let mut messages_received = 0;
// build the sender future
let sender_future = future::poll_fn(move || -> Poll<bool, ()> {
let sender_future = async {
loop {
match sender.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id))) => {
// Send a BlocksByRange request
warn!(sender_log, "Sender sending RPC request");
match sender.next_event().await {
Libp2pEvent::PeerConnected { peer_id, .. } => {
// Send a STATUS message
debug!(log, "Sending RPC");
sender
.swarm
.send_rpc(peer_id, RPCEvent::Request(1, sender_request.clone()));
.send_rpc(peer_id, RPCEvent::Request(10, rpc_request.clone()));
}
Async::Ready(Some(BehaviourEvent::RPC(_, event))) => match event {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(_, event)) => match event {
// Should receive the RPC response
RPCEvent::Response(id, response) => {
if id == 1 {
warn!(sender_log, "Sender received a response");
if id == 10 {
warn!(log, "Sender received a response");
match response {
RPCCodedResponse::Success(res) => {
assert_eq!(res, sender_response.clone());
*messages_received.lock().unwrap() += 1;
warn!(sender_log, "Chunk received");
assert_eq!(res, rpc_response.clone());
messages_received += 1;
warn!(log, "Chunk received");
}
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
) => {
// should be exactly 1 messages before terminating
assert_eq!(*messages_received.lock().unwrap(), 1);
RPCCodedResponse::StreamTermination(_) => {
// should be exactly 10 messages before terminating
assert_eq!(messages_received, messages_to_send);
// end the test
return Ok(Async::Ready(true));
return;
}
_ => panic!("Invalid RPC received"),
}
}
}
m => panic!("Received invalid RPC message: {}", m),
_ => {} // Ignore other RPC messages
},
Async::Ready(Some(_)) => {}
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
};
}
});
// build the receiver future
let receiver_future = future::poll_fn(move || -> Poll<bool, ()> {
loop {
match receiver.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::RPC(peer_id, event))) => match event {
// Should receive the sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
warn!(log, "Receiver got request");
receiver.swarm.send_rpc(
peer_id.clone(),
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
),
);
// send the stream termination
receiver.swarm.send_rpc(
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
),
),
);
}
}
_ => panic!("Received invalid RPC message"),
},
Async::Ready(Some(_)) => (),
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
_ => {} // Ignore other behaviour events
}
}
});
};
// execute the futures and check the result
let test_result = Arc::new(AtomicBool::new(false));
let error_result = test_result.clone();
let thread_result = test_result.clone();
tokio::run(
sender_future
.select(receiver_future)
.timeout(Duration::from_millis(1000))
.map_err(move |_| error_result.store(false, Relaxed))
.map(move |result| {
thread_result.store(result.0, Relaxed);
}),
);
assert!(test_result.load(Relaxed));
// build the receiver future
let receiver_future = async {
loop {
match receiver.next_event().await {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(peer_id, event)) => {
match event {
// Should receive sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
warn!(log, "Receiver got request");
for _ in 1..=messages_to_send {
receiver.swarm.send_rpc(
peer_id.clone(),
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
),
);
}
// send the stream termination
receiver.swarm.send_rpc(
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
),
),
);
}
}
_ => {} // Ignore other events
}
}
_ => {} // Ignore other events
}
}
};
tokio::select! {
_ = sender_future => {}
_ = receiver_future => {}
_ = delay_for(Duration::from_millis(800)) => {
panic!("Future timed out");
}
}
}
#[test]
#[tokio::test]
// Tests a streamed, chunked BlocksByRoot RPC Message
// The size of the reponse is a full `BeaconBlock`
// which is greater than the Snappy frame size. Hence, this test
// serves to test the snappy framing format as well.
fn test_blocks_by_root_chunked_rpc() {
async fn test_blocks_by_root_chunked_rpc() {
// set up the logging. The level and enabled logging or not
let log_level = Level::Trace;
let log_level = Level::Debug;
let enable_logging = false;
let messages_to_send = 3;
@@ -411,7 +371,7 @@ fn test_blocks_by_root_chunked_rpc() {
let spec = E::default_spec();
// get sender/receiver
let (mut sender, mut receiver) = common::build_node_pair(&log);
let (mut sender, mut receiver) = common::build_node_pair(&log).await;
// BlocksByRoot Request
let rpc_request = RPCRequest::BlocksByRoot(BlocksByRootRequest {
@@ -426,112 +386,101 @@ fn test_blocks_by_root_chunked_rpc() {
};
let rpc_response = RPCResponse::BlocksByRoot(Box::new(signed_full_block));
let sender_request = rpc_request.clone();
let sender_log = log.clone();
let sender_response = rpc_response.clone();
// keep count of the number of messages received
let messages_received = Arc::new(Mutex::new(0));
let mut messages_received = 0;
// build the sender future
let sender_future = future::poll_fn(move || -> Poll<bool, ()> {
let sender_future = async {
loop {
match sender.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id))) => {
// Send a BlocksByRoot request
warn!(sender_log, "Sender sending RPC request");
match sender.next_event().await {
Libp2pEvent::PeerConnected { peer_id, .. } => {
// Send a STATUS message
debug!(log, "Sending RPC");
sender
.swarm
.send_rpc(peer_id, RPCEvent::Request(1, sender_request.clone()));
.send_rpc(peer_id, RPCEvent::Request(10, rpc_request.clone()));
}
Async::Ready(Some(BehaviourEvent::RPC(_, event))) => match event {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(_, event)) => match event {
// Should receive the RPC response
RPCEvent::Response(id, response) => {
warn!(sender_log, "Sender received a response");
assert_eq!(id, 1);
match response {
RPCCodedResponse::Success(res) => {
assert_eq!(res, sender_response.clone());
*messages_received.lock().unwrap() += 1;
warn!(sender_log, "Chunk received");
if id == 10 {
debug!(log, "Sender received a response");
match response {
RPCCodedResponse::Success(res) => {
assert_eq!(res, rpc_response.clone());
messages_received += 1;
debug!(log, "Chunk received");
}
RPCCodedResponse::StreamTermination(_) => {
// should be exactly messages_to_send
assert_eq!(messages_received, messages_to_send);
// end the test
return;
}
_ => {} // Ignore other RPC messages
}
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRoot,
) => {
// should be exactly 10 messages before terminating
assert_eq!(*messages_received.lock().unwrap(), messages_to_send);
// end the test
return Ok(Async::Ready(true));
}
m => panic!("Invalid RPC received: {}", m),
}
}
m => panic!("Received invalid RPC message: {}", m),
_ => {} // Ignore other RPC messages
},
Async::Ready(Some(_)) => {}
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
};
}
});
// build the receiver future
let receiver_future = future::poll_fn(move || -> Poll<bool, ()> {
loop {
match receiver.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::RPC(peer_id, event))) => match event {
// Should receive the sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
warn!(log, "Receiver got request");
for _ in 1..=messages_to_send {
receiver.swarm.send_rpc(
peer_id.clone(),
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
),
);
}
// send the stream termination
receiver.swarm.send_rpc(
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
),
),
);
}
}
_ => panic!("Received invalid RPC message"),
},
Async::Ready(Some(_)) => (),
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
_ => {} // Ignore other behaviour events
}
}
});
};
// execute the futures and check the result
let test_result = Arc::new(AtomicBool::new(false));
let error_result = test_result.clone();
let thread_result = test_result.clone();
tokio::run(
sender_future
.select(receiver_future)
.timeout(Duration::from_millis(1000))
.map_err(move |_| error_result.store(false, Relaxed))
.map(move |result| {
thread_result.store(result.0, Relaxed);
}),
);
assert!(test_result.load(Relaxed));
// build the receiver future
let receiver_future = async {
loop {
match receiver.next_event().await {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(peer_id, event)) => {
match event {
// Should receive sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
// send the response
debug!(log, "Receiver got request");
for _ in 1..=messages_to_send {
receiver.swarm.send_rpc(
peer_id.clone(),
RPCEvent::Response(
id,
RPCCodedResponse::Success(rpc_response.clone()),
),
);
debug!(log, "Sending message");
}
// send the stream termination
receiver.swarm.send_rpc(
peer_id,
RPCEvent::Response(
id,
RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange,
),
),
);
debug!(log, "Send stream term");
}
}
_ => {} // Ignore other events
}
}
_ => {} // Ignore other events
}
}
};
tokio::select! {
_ = sender_future => {}
_ = receiver_future => {}
_ = delay_for(Duration::from_millis(1000)) => {
panic!("Future timed out");
}
}
}
#[test]
#[tokio::test]
// Tests a Goodbye RPC message
fn test_goodbye_rpc() {
async fn test_goodbye_rpc() {
// set up the logging. The level and enabled logging or not
let log_level = Level::Trace;
let enable_logging = false;
@@ -539,65 +488,54 @@ fn test_goodbye_rpc() {
let log = common::build_log(log_level, enable_logging);
// get sender/receiver
let (mut sender, mut receiver) = common::build_node_pair(&log);
let (mut sender, mut receiver) = common::build_node_pair(&log).await;
// Goodbye Request
let rpc_request = RPCRequest::Goodbye(GoodbyeReason::ClientShutdown);
let sender_request = rpc_request.clone();
let sender_log = log.clone();
// build the sender future
let sender_future = future::poll_fn(move || -> Poll<bool, ()> {
let sender_future = async {
loop {
match sender.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id))) => {
// Send a Goodbye request
warn!(sender_log, "Sender sending RPC request");
match sender.next_event().await {
Libp2pEvent::PeerConnected { peer_id, .. } => {
// Send a STATUS message
debug!(log, "Sending RPC");
sender
.swarm
.send_rpc(peer_id, RPCEvent::Request(1, sender_request.clone()));
.send_rpc(peer_id, RPCEvent::Request(10, rpc_request.clone()));
}
Async::Ready(Some(_)) => {}
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
};
}
});
// build the receiver future
let receiver_future = future::poll_fn(move || -> Poll<bool, ()> {
loop {
match receiver.poll().unwrap() {
Async::Ready(Some(BehaviourEvent::RPC(_, event))) => match event {
// Should receive the sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
assert_eq!(id, 0);
assert_eq!(rpc_request.clone(), request);
// receives the goodbye. Nothing left to do
return Ok(Async::Ready(true));
}
}
_ => panic!("Received invalid RPC message"),
},
Async::Ready(Some(_)) => (),
Async::Ready(None) | Async::NotReady => return Ok(Async::NotReady),
_ => {} // Ignore other RPC messages
}
}
});
};
// execute the futures and check the result
let test_result = Arc::new(AtomicBool::new(false));
let error_result = test_result.clone();
let thread_result = test_result.clone();
tokio::run(
sender_future
.select(receiver_future)
.timeout(Duration::from_millis(1000))
.map_err(move |_| error_result.store(false, Relaxed))
.map(move |result| {
thread_result.store(result.0, Relaxed);
}),
);
assert!(test_result.load(Relaxed));
// build the receiver future
let receiver_future = async {
loop {
match receiver.next_event().await {
Libp2pEvent::Behaviour(BehaviourEvent::RPC(_peer_id, event)) => {
match event {
// Should receive sent RPC request
RPCEvent::Request(id, request) => {
if request == rpc_request {
assert_eq!(id, 0);
assert_eq!(rpc_request.clone(), request); // receives the goodbye. Nothing left to do
return;
}
}
_ => {} // Ignore other events
}
}
_ => {} // Ignore other events
}
}
};
tokio::select! {
_ = sender_future => {}
_ = receiver_future => {}
_ = delay_for(Duration::from_millis(1000)) => {
panic!("Future timed out");
}
}
}