Get rid of Peerset compatibility layer (#14337)

* Move bootnodes from individual `SetConfig`s to `PeersetConfig`

* Move `SetId` & `SetConfig` from `peerset` to `protocol_controller`

* Remove unused `DropReason`

* Move `Message` & `IncomingIndex` from `peerset` to `protocol_controller`

* Restore running fuzz test

* Get rid of `Peerset` in `fuzz` test

* Spawn runners instead of manual polling in `fuzz` test

* Migrate `Protocol` from `Peerset` to `PeerStore` & `ProtocolController`

* Migrate `NetworkService` from `Peerset` to `PeerStore` & `ProtocolController`

* Migrate `Notifications` from `Peerset` to `ProtocolController`s

* Migrate `Notifications` tests from `Peerset` to `ProtocolController`

* Fix compilation of `NetworkService` & `Protocol`

* Fix borrowing issues in `Notifications`

* Migrate `RequestResponse`from `Peerset` to `PeerStore`

* rustfmt

* Migrate request-response tests from `Peerset` to `PeerStore`

* Migrate `reconnect_after_disconnect` test to `PeerStore` & `ProtocolController`

* Fix `Notifications` tests

* Remove `Peerset` completely

* Fix bug with counting sync peers in `Protocol`

* Eliminate indirect calls to `PeerStore` via `Protocol`

* Eliminate indirect calls to `ProtocolController` via `Protocol`

* Handle `Err` outcome from `remove_peers_from_reserved_set`

* Add note about disconnecting sync peers in `Protocol`

* minor: remove unneeded `clone()`

* minor: extra comma removed

* minor: use `Stream` API of `from_protocol_controllers` channel

* minor: remove TODO

* minor: replace `.map().flatten()` with `.flat_map()`

* minor: update `ProtocolController` docs

* rustfmt

* Apply suggestions from code review

Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com>

* Extract `MockPeerStore` to `mock.rs`

* Move `PeerStore` initialization to `build_network`

* minor: remove unused import

* minor: clarify error message

* Convert `syncs_header_only_forks` test into single-threaded

---------

Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com>
This commit is contained in:
Dmitry Markin
2023-08-02 16:01:35 +03:00
committed by GitHub
parent 85f9931e4f
commit 8dc3bd729b
27 changed files with 857 additions and 1213 deletions
+66 -169
View File
@@ -35,7 +35,9 @@
//! is used to handle incoming requests.
use crate::{
peer_store::BANNED_THRESHOLD, peerset::PeersetHandle, types::ProtocolName, ReputationChange,
peer_store::{PeerStoreProvider, BANNED_THRESHOLD},
types::ProtocolName,
ReputationChange,
};
use futures::{channel::oneshot, prelude::*};
@@ -279,28 +281,7 @@ pub struct RequestResponsesBehaviour {
send_feedback: HashMap<ProtocolRequestId, oneshot::Sender<()>>,
/// Primarily used to get a reputation of a node.
peerset: PeersetHandle,
/// Pending message request, holds `MessageRequest` as a Future state to poll it
/// until we get a response from `Peerset`
message_request: Option<MessageRequest>,
}
// This is a state of processing incoming request Message.
// The main reason of this struct is to hold `get_peer_reputation` as a Future state.
struct MessageRequest {
peer: PeerId,
request_id: RequestId,
request: Vec<u8>,
channel: ResponseChannel<Result<Vec<u8>, ()>>,
protocol: ProtocolName,
// A builder used for building responses for incoming requests. Note that we use
// `async_channel` and not `mpsc` on purpose, because `mpsc::channel` allocates an extra
// message slot for every cloned `Sender` and this breaks a back-pressure mechanism.
resp_builder: Option<async_channel::Sender<IncomingRequest>>,
// Once we get incoming request we save all params, create an async call to Peerset
// to get the reputation of the peer.
get_peer_reputation: Pin<Box<dyn Future<Output = Result<i32, ()>> + Send>>,
peer_store: Box<dyn PeerStoreProvider>,
}
/// Generated by the response builder and waiting to be processed.
@@ -317,7 +298,7 @@ impl RequestResponsesBehaviour {
/// the same protocol is passed twice.
pub fn new(
list: impl Iterator<Item = ProtocolConfig>,
peerset: PeersetHandle,
peer_store: Box<dyn PeerStoreProvider>,
) -> Result<Self, RegisterError> {
let mut protocols = HashMap::new();
for protocol in list {
@@ -354,8 +335,7 @@ impl RequestResponsesBehaviour {
pending_responses: Default::default(),
pending_responses_arrival_time: Default::default(),
send_feedback: Default::default(),
peerset,
message_request: None,
peer_store,
})
}
@@ -576,96 +556,6 @@ impl NetworkBehaviour for RequestResponsesBehaviour {
params: &mut impl PollParameters,
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
'poll_all: loop {
if let Some(message_request) = self.message_request.take() {
// Now we can can poll `MessageRequest` until we get the reputation
let MessageRequest {
peer,
request_id,
request,
channel,
protocol,
resp_builder,
mut get_peer_reputation,
} = message_request;
let reputation = Future::poll(Pin::new(&mut get_peer_reputation), cx);
match reputation {
Poll::Pending => {
// Save the state to poll it again next time.
self.message_request = Some(MessageRequest {
peer,
request_id,
request,
channel,
protocol,
resp_builder,
get_peer_reputation,
});
return Poll::Pending
},
Poll::Ready(reputation) => {
// Once we get the reputation we can continue processing the request.
let reputation = reputation.expect(
"The channel can only be closed if the peerset no longer exists; qed",
);
if reputation < BANNED_THRESHOLD {
log::debug!(
target: "sub-libp2p",
"Cannot handle requests from a node with a low reputation {}: {}",
peer,
reputation,
);
continue 'poll_all
}
log::trace!(target: "sub-libp2p", "request received from {peer} ({protocol:?}), {} bytes", request.len());
let (tx, rx) = oneshot::channel();
// Submit the request to the "response builder" passed by the user at
// initialization.
if let Some(resp_builder) = resp_builder {
// If the response builder is too busy, silently drop `tx`. This
// will be reported by the corresponding request-response [`Behaviour`]
// through an `InboundFailure::Omission` event.
// Note that we use `async_channel::bounded` and not `mpsc::channel`
// because the latter allocates an extra slot for every cloned sender.
let _ = resp_builder.try_send(IncomingRequest {
peer,
payload: request,
pending_response: tx,
});
} else {
debug_assert!(false, "Received message on outbound-only protocol.");
}
self.pending_responses.push(Box::pin(async move {
// The `tx` created above can be dropped if we are not capable of
// processing this request, which is reflected as a
// `InboundFailure::Omission` event.
if let Ok(response) = rx.await {
Some(RequestProcessingOutcome {
peer,
request_id,
protocol,
inner_channel: channel,
response,
})
} else {
None
}
}));
// This `continue` makes sure that `pending_responses` gets polled
// after we have added the new element.
continue 'poll_all
},
}
}
// Poll to see if any response is ready to be sent back.
while let Poll::Ready(Some(outcome)) = self.pending_responses.poll_next_unpin(cx) {
let RequestProcessingOutcome {
@@ -712,7 +602,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour {
// Poll request-responses protocols.
for (protocol, (behaviour, resp_builder)) in &mut self.protocols {
while let Poll::Ready(ev) = behaviour.poll(cx, params) {
'poll_protocol: while let Poll::Ready(ev) = behaviour.poll(cx, params) {
let ev = match ev {
// Main events we are interested in.
ToSwarm::GenerateEvent(ev) => ev,
@@ -756,23 +646,56 @@ impl NetworkBehaviour for RequestResponsesBehaviour {
self.pending_responses_arrival_time
.insert((protocol.clone(), request_id).into(), Instant::now());
let get_peer_reputation = self.peerset.clone().peer_reputation(peer);
let get_peer_reputation = Box::pin(get_peer_reputation);
let reputation = self.peer_store.peer_reputation(&peer);
// Save the Future-like state with params to poll `get_peer_reputation`
// and to continue processing the request once we get the reputation of
// the peer.
self.message_request = Some(MessageRequest {
peer,
request_id,
request,
channel,
protocol: protocol.clone(),
resp_builder: resp_builder.clone(),
get_peer_reputation,
});
if reputation < BANNED_THRESHOLD {
log::debug!(
target: "sub-libp2p",
"Cannot handle requests from a node with a low reputation {}: {}",
peer,
reputation,
);
continue 'poll_protocol
}
// This `continue` makes sure that `message_request` gets polled
let (tx, rx) = oneshot::channel();
// Submit the request to the "response builder" passed by the user at
// initialization.
if let Some(resp_builder) = resp_builder {
// If the response builder is too busy, silently drop `tx`. This
// will be reported by the corresponding request-response
// [`Behaviour`] through an `InboundFailure::Omission` event.
// Note that we use `async_channel::bounded` and not `mpsc::channel`
// because the latter allocates an extra slot for every cloned
// sender.
let _ = resp_builder.try_send(IncomingRequest {
peer,
payload: request,
pending_response: tx,
});
} else {
debug_assert!(false, "Received message on outbound-only protocol.");
}
let protocol = protocol.clone();
self.pending_responses.push(Box::pin(async move {
// The `tx` created above can be dropped if we are not capable of
// processing this request, which is reflected as a
// `InboundFailure::Omission` event.
rx.await.map_or(None, |response| {
Some(RequestProcessingOutcome {
peer,
request_id,
protocol,
inner_channel: channel,
response,
})
})
}));
// This `continue` makes sure that `pending_responses` gets polled
// after we have added the new element.
continue 'poll_all
},
@@ -1064,7 +987,7 @@ impl Codec for GenericCodec {
mod tests {
use super::*;
use crate::peerset::{Peerset, PeersetConfig, SetConfig};
use crate::mock::MockPeerStore;
use futures::{channel::oneshot, executor::LocalPool, task::Spawn};
use libp2p::{
core::{
@@ -1087,7 +1010,7 @@ mod tests {
fn build_swarm(
list: impl Iterator<Item = ProtocolConfig>,
) -> (Swarm<RequestResponsesBehaviour>, Multiaddr, Peerset) {
) -> (Swarm<RequestResponsesBehaviour>, Multiaddr) {
let keypair = Keypair::generate_ed25519();
let transport = MemoryTransport::new()
@@ -1096,19 +1019,7 @@ mod tests {
.multiplex(libp2p::yamux::Config::default())
.boxed();
let config = PeersetConfig {
sets: vec![SetConfig {
in_peers: u32::max_value(),
out_peers: u32::max_value(),
bootnodes: vec![],
reserved_nodes: Default::default(),
reserved_only: false,
}],
};
let (peerset, handle) = Peerset::from_config(config);
let behaviour = RequestResponsesBehaviour::new(list, handle).unwrap();
let behaviour = RequestResponsesBehaviour::new(list, Box::new(MockPeerStore {})).unwrap();
let runtime = tokio::runtime::Runtime::new().unwrap();
let mut swarm = SwarmBuilder::with_executor(
@@ -1121,11 +1032,7 @@ mod tests {
let listen_addr: Multiaddr = format!("/memory/{}", rand::random::<u64>()).parse().unwrap();
swarm.listen_on(listen_addr.clone()).unwrap();
(swarm, listen_addr, peerset)
}
async fn loop_peerset(peerset: Peerset) {
let _: Vec<_> = peerset.collect().await;
(swarm, listen_addr)
}
#[test]
@@ -1177,9 +1084,7 @@ mod tests {
Swarm::dial(&mut swarms[0].0, dial_addr).unwrap();
}
let (mut swarm, _, peerset) = swarms.remove(0);
// Process every peerset event in the background.
pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap();
let (mut swarm, _) = swarms.remove(0);
// Running `swarm[0]` in the background.
pool.spawner()
.spawn_obj({
@@ -1199,9 +1104,7 @@ mod tests {
.unwrap();
// Remove and run the remaining swarm.
let (mut swarm, _, peerset) = swarms.remove(0);
// Process every peerset event in the background.
pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap();
let (mut swarm, _) = swarms.remove(0);
pool.run_until(async move {
let mut response_receiver = None;
@@ -1280,9 +1183,7 @@ mod tests {
// Running `swarm[0]` in the background until a `InboundRequest` event happens,
// which is a hint about the test having ended.
let (mut swarm, _, peerset) = swarms.remove(0);
// Process every peerset event in the background.
pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap();
let (mut swarm, _) = swarms.remove(0);
pool.spawner()
.spawn_obj({
async move {
@@ -1302,9 +1203,7 @@ mod tests {
.unwrap();
// Remove and run the remaining swarm.
let (mut swarm, _, peerset) = swarms.remove(0);
// Process every peerset event in the background.
pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap();
let (mut swarm, _) = swarms.remove(0);
pool.run_until(async move {
let mut response_receiver = None;
@@ -1376,7 +1275,7 @@ mod tests {
build_swarm(protocol_configs.into_iter()).0
};
let (mut swarm_2, mut swarm_2_handler_1, mut swarm_2_handler_2, listen_add_2, peerset) = {
let (mut swarm_2, mut swarm_2_handler_1, mut swarm_2_handler_2, listen_add_2) = {
let (tx_1, rx_1) = async_channel::bounded(64);
let (tx_2, rx_2) = async_channel::bounded(64);
@@ -1399,12 +1298,10 @@ mod tests {
},
];
let (swarm, listen_addr, peerset) = build_swarm(protocol_configs.into_iter());
let (swarm, listen_addr) = build_swarm(protocol_configs.into_iter());
(swarm, rx_1, rx_2, listen_addr, peerset)
(swarm, rx_1, rx_2, listen_addr)
};
// Process every peerset event in the background.
pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap();
// Ask swarm 1 to dial swarm 2. There isn't any discovery mechanism in place in this test,
// so they wouldn't connect to each other.