mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-25 09:25:46 +00:00
80616f6d03
[litep2p](https://github.com/altonen/litep2p) is a libp2p-compatible P2P networking library. It supports all of the features of `rust-libp2p` that are currently being utilized by Polkadot SDK. Compared to `rust-libp2p`, `litep2p` has a quite different architecture which is why the new `litep2p` network backend is only able to use a little of the existing code in `sc-network`. The design has been mainly influenced by how we'd wish to structure our networking-related code in Polkadot SDK: independent higher-levels protocols directly communicating with the network over links that support bidirectional backpressure. A good example would be `NotificationHandle`/`RequestResponseHandle` abstractions which allow, e.g., `SyncingEngine` to directly communicate with peers to announce/request blocks. I've tried running `polkadot --network-backend litep2p` with a few different peer configurations and there is a noticeable reduction in networking CPU usage. For high load (`--out-peers 200`), networking CPU usage goes down from ~110% to ~30% (80 pp) and for normal load (`--out-peers 40`), the usage goes down from ~55% to ~18% (37 pp). These should not be taken as final numbers because: a) there are still some low-hanging optimization fruits, such as enabling [receive window auto-tuning](https://github.com/libp2p/rust-yamux/pull/176), integrating `Peerset` more closely with `litep2p` or improving memory usage of the WebSocket transport b) fixing bugs/instabilities that incorrectly cause `litep2p` to do less work will increase the networking CPU usage c) verification in a more diverse set of tests/conditions is needed Nevertheless, these numbers should give an early estimate for CPU usage of the new networking backend. This PR consists of three separate changes: * introduce a generic `PeerId` (wrapper around `Multihash`) so that we don't have use `NetworkService::PeerId` in every part of the code that uses a `PeerId` * introduce `NetworkBackend` trait, implement it for the libp2p network stack and make Polkadot SDK generic over `NetworkBackend` * implement `NetworkBackend` for litep2p The new library should be considered experimental which is why `rust-libp2p` will remain as the default option for the time being. This PR currently depends on the master branch of `litep2p` but I'll cut a new release for the library once all review comments have been addresses. --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: Dmitry Markin <dmitry@markin.tech> Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Alexandru Vasile <alexandru.vasile@parity.io>
306 lines
9.9 KiB
Rust
306 lines
9.9 KiB
Rust
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// This file is part of Polkadot.
|
|
|
|
// Polkadot is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Polkadot is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use crate::{
|
|
approval::{ApprovalsOptions, BlockTestData, CandidateTestData},
|
|
configuration::TestAuthorities,
|
|
};
|
|
use itertools::Itertools;
|
|
use parity_scale_codec::{Decode, Encode};
|
|
use polkadot_node_network_protocol::v3 as protocol_v3;
|
|
use polkadot_primitives::{CandidateIndex, Hash, ValidatorIndex};
|
|
use sc_network_types::PeerId;
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
|
pub struct TestMessageInfo {
|
|
/// The actual message
|
|
pub msg: protocol_v3::ApprovalDistributionMessage,
|
|
/// The list of peers that would sends this message in a real topology.
|
|
/// It includes both the peers that would send the message because of the topology
|
|
/// or because of randomly choosing so.
|
|
pub sent_by: Vec<ValidatorIndex>,
|
|
/// The tranche at which this message should be sent.
|
|
pub tranche: u32,
|
|
/// The block hash this message refers to.
|
|
pub block_hash: Hash,
|
|
}
|
|
|
|
impl std::hash::Hash for TestMessageInfo {
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
match &self.msg {
|
|
protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => {
|
|
for (assignment, candidates) in assignments {
|
|
(assignment.block_hash, assignment.validator).hash(state);
|
|
candidates.hash(state);
|
|
}
|
|
},
|
|
protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => {
|
|
for approval in approvals {
|
|
(approval.block_hash, approval.validator).hash(state);
|
|
approval.candidate_indices.hash(state);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
|
/// A list of messages that depend of each-other, approvals cover one of the assignments and
|
|
/// vice-versa.
|
|
pub struct MessagesBundle {
|
|
pub assignments: Vec<TestMessageInfo>,
|
|
pub approvals: Vec<TestMessageInfo>,
|
|
}
|
|
|
|
impl MessagesBundle {
|
|
/// The tranche when this bundle can be sent correctly, so no assignments or approvals will be
|
|
/// from the future.
|
|
pub fn tranche_to_send(&self) -> u32 {
|
|
self.assignments
|
|
.iter()
|
|
.chain(self.approvals.iter())
|
|
.max_by(|a, b| a.tranche.cmp(&b.tranche))
|
|
.unwrap()
|
|
.tranche
|
|
}
|
|
|
|
/// The min tranche in the bundle.
|
|
pub fn min_tranche(&self) -> u32 {
|
|
self.assignments
|
|
.iter()
|
|
.chain(self.approvals.iter())
|
|
.min_by(|a, b| a.tranche.cmp(&b.tranche))
|
|
.unwrap()
|
|
.tranche
|
|
}
|
|
|
|
/// Tells if the bundle is needed for sending.
|
|
/// We either send it because we need more assignments and approvals to approve the candidates
|
|
/// or because we configured the test to send messages until a given tranche.
|
|
pub fn should_send(
|
|
&self,
|
|
candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
options: &ApprovalsOptions,
|
|
) -> bool {
|
|
self.needed_for_approval(candidates_test_data) ||
|
|
(!options.stop_when_approved &&
|
|
self.min_tranche() <= options.last_considered_tranche)
|
|
}
|
|
|
|
/// Tells if the bundle is needed because we need more messages to approve the candidates.
|
|
pub fn needed_for_approval(
|
|
&self,
|
|
candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
) -> bool {
|
|
self.assignments
|
|
.iter()
|
|
.any(|message| message.needed_for_approval(candidates_test_data))
|
|
}
|
|
|
|
/// Mark the assignments in the bundle as sent.
|
|
pub fn record_sent_assignment(
|
|
&self,
|
|
candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
) {
|
|
self.assignments
|
|
.iter()
|
|
.for_each(|assignment| assignment.record_sent_assignment(candidates_test_data));
|
|
}
|
|
}
|
|
|
|
impl TestMessageInfo {
|
|
/// Tells if the message is an approval.
|
|
fn is_approval(&self) -> bool {
|
|
match self.msg {
|
|
protocol_v3::ApprovalDistributionMessage::Assignments(_) => false,
|
|
protocol_v3::ApprovalDistributionMessage::Approvals(_) => true,
|
|
}
|
|
}
|
|
|
|
/// Records an approval.
|
|
/// We use this to check after all messages have been processed that we didn't loose any
|
|
/// message.
|
|
pub fn record_vote(&self, state: &BlockTestData) {
|
|
if self.is_approval() {
|
|
match &self.msg {
|
|
protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(),
|
|
protocol_v3::ApprovalDistributionMessage::Approvals(approvals) =>
|
|
for approval in approvals {
|
|
for candidate_index in approval.candidate_indices.iter_ones() {
|
|
state
|
|
.votes
|
|
.get(approval.validator.0 as usize)
|
|
.unwrap()
|
|
.get(candidate_index)
|
|
.unwrap()
|
|
.store(true, std::sync::atomic::Ordering::SeqCst);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Mark the assignments in the message as sent.
|
|
pub fn record_sent_assignment(
|
|
&self,
|
|
candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
) {
|
|
match &self.msg {
|
|
protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => {
|
|
for (assignment, candidate_indices) in assignments {
|
|
for candidate_index in candidate_indices.iter_ones() {
|
|
let candidate_test_data = candidates_test_data
|
|
.get_mut(&(assignment.block_hash, candidate_index as CandidateIndex))
|
|
.unwrap();
|
|
candidate_test_data.mark_sent_assignment(self.tranche)
|
|
}
|
|
}
|
|
},
|
|
protocol_v3::ApprovalDistributionMessage::Approvals(_approvals) => todo!(),
|
|
}
|
|
}
|
|
|
|
/// Returns a list of candidates indices in this message
|
|
pub fn candidate_indices(&self) -> HashSet<usize> {
|
|
let mut unique_candidate_indices = HashSet::new();
|
|
match &self.msg {
|
|
protocol_v3::ApprovalDistributionMessage::Assignments(assignments) =>
|
|
for (_assignment, candidate_indices) in assignments {
|
|
for candidate_index in candidate_indices.iter_ones() {
|
|
unique_candidate_indices.insert(candidate_index);
|
|
}
|
|
},
|
|
protocol_v3::ApprovalDistributionMessage::Approvals(approvals) =>
|
|
for approval in approvals {
|
|
for candidate_index in approval.candidate_indices.iter_ones() {
|
|
unique_candidate_indices.insert(candidate_index);
|
|
}
|
|
},
|
|
}
|
|
unique_candidate_indices
|
|
}
|
|
|
|
/// Marks this message as no-shows if the number of configured no-shows is above the registered
|
|
/// no-shows.
|
|
/// Returns true if the message is a no-show.
|
|
pub fn no_show_if_required(
|
|
&self,
|
|
assignments: &[TestMessageInfo],
|
|
candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
) -> bool {
|
|
let mut should_no_show = false;
|
|
if self.is_approval() {
|
|
let covered_candidates = assignments
|
|
.iter()
|
|
.map(|assignment| (assignment, assignment.candidate_indices()))
|
|
.collect_vec();
|
|
|
|
match &self.msg {
|
|
protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(),
|
|
protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => {
|
|
assert_eq!(approvals.len(), 1);
|
|
|
|
for approval in approvals {
|
|
should_no_show = should_no_show ||
|
|
approval.candidate_indices.iter_ones().all(|candidate_index| {
|
|
let candidate_test_data = candidates_test_data
|
|
.get_mut(&(
|
|
approval.block_hash,
|
|
candidate_index as CandidateIndex,
|
|
))
|
|
.unwrap();
|
|
let assignment = covered_candidates
|
|
.iter()
|
|
.find(|(_assignment, candidates)| {
|
|
candidates.contains(&candidate_index)
|
|
})
|
|
.unwrap();
|
|
candidate_test_data.should_no_show(assignment.0.tranche)
|
|
});
|
|
|
|
if should_no_show {
|
|
for candidate_index in approval.candidate_indices.iter_ones() {
|
|
let candidate_test_data = candidates_test_data
|
|
.get_mut(&(
|
|
approval.block_hash,
|
|
candidate_index as CandidateIndex,
|
|
))
|
|
.unwrap();
|
|
let assignment = covered_candidates
|
|
.iter()
|
|
.find(|(_assignment, candidates)| {
|
|
candidates.contains(&candidate_index)
|
|
})
|
|
.unwrap();
|
|
candidate_test_data.record_no_show(assignment.0.tranche)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
should_no_show
|
|
}
|
|
|
|
/// Tells if a message is needed for approval
|
|
pub fn needed_for_approval(
|
|
&self,
|
|
candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
) -> bool {
|
|
match &self.msg {
|
|
protocol_v3::ApprovalDistributionMessage::Assignments(assignments) =>
|
|
assignments.iter().any(|(assignment, candidate_indices)| {
|
|
candidate_indices.iter_ones().any(|candidate_index| {
|
|
candidates_test_data
|
|
.get(&(assignment.block_hash, candidate_index as CandidateIndex))
|
|
.map(|data| data.should_send_tranche(self.tranche))
|
|
.unwrap_or_default()
|
|
})
|
|
}),
|
|
protocol_v3::ApprovalDistributionMessage::Approvals(approvals) =>
|
|
approvals.iter().any(|approval| {
|
|
approval.candidate_indices.iter_ones().any(|candidate_index| {
|
|
candidates_test_data
|
|
.get(&(approval.block_hash, candidate_index as CandidateIndex))
|
|
.map(|data| data.should_send_tranche(self.tranche))
|
|
.unwrap_or_default()
|
|
})
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Splits a message into multiple messages based on what peers should send this message.
|
|
/// It build a HashMap of messages that should be sent by each peer.
|
|
pub fn split_by_peer_id(
|
|
self,
|
|
authorities: &TestAuthorities,
|
|
) -> HashMap<(ValidatorIndex, PeerId), Vec<TestMessageInfo>> {
|
|
let mut result: HashMap<(ValidatorIndex, PeerId), Vec<TestMessageInfo>> = HashMap::new();
|
|
|
|
for validator_index in &self.sent_by {
|
|
let peer = authorities.peer_ids.get(validator_index.0 as usize).unwrap();
|
|
result.entry((*validator_index, *peer)).or_default().push(TestMessageInfo {
|
|
msg: self.msg.clone(),
|
|
sent_by: Default::default(),
|
|
tranche: self.tranche,
|
|
block_hash: self.block_hash,
|
|
});
|
|
}
|
|
result
|
|
}
|
|
}
|