Files
pezkuwi-subxt/polkadot/node/network/statement-distribution/src/lib.rs
T
Andrei Sandu 0570b6fa9e approval-voting improvement: include all tranche0 assignments in one certificate (#1178)
**_PR migrated from https://github.com/paritytech/polkadot/pull/6782_** 

This PR will upgrade the network protocol to version 3 -> VStaging which
will later be renamed to V3. This version introduces a new kind of
assignment certificate that will be used for tranche0 assignments.
Instead of issuing/importing one tranche0 assignment per candidate,
there will be just one certificate per relay chain block per validator.
However, we will not be sending out the new assignment certificates,
yet. So everything should work exactly as before. Once the majority of
the validators have been upgraded to the new protocol version we will
enable the new certificates (starting at a specific relay chain block)
with a new client update.

There are still a few things that need to be done:

- [x] Use bitfield instead of Vec<CandidateIndex>:
https://github.com/paritytech/polkadot/pull/6802
  - [x] Fix existing approval-distribution and approval-voting tests
  - [x] Fix bitfield-distribution and statement-distribution tests
  - [x] Fix network bridge tests
  - [x] Implement todos in the code
  - [x] Add tests to cover new code
  - [x] Update metrics
  - [x] Remove the approval distribution aggression levels: TBD PR
  - [x] Parachains DB migration 
  - [x] Test network protocol upgrade on Versi
  - [x] Versi Load test
  - [x] Add Zombienet test
  - [x] Documentation updates
- [x] Fix for sending DistributeAssignment for each candidate claimed by
a v2 assignment (warning: Importing locally an already known assignment)
 - [x]  Fix AcceptedDuplicate
 - [x] Fix DB migration so that we can still keep old data.
 - [x] Final Versi burn in

---------

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Signed-off-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io>
Co-authored-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io>
2023-11-06 15:21:32 +02:00

438 lines
13 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/>.
//! The Statement Distribution Subsystem.
//!
//! This is responsible for distributing signed statements about candidate
//! validity among validators.
// #![deny(unused_crate_dependencies)]
#![warn(missing_docs)]
use error::{log_error, FatalResult};
use std::time::Duration;
use polkadot_node_network_protocol::{
request_response::{v1 as request_v1, v2::AttestedCandidateRequest, IncomingRequestReceiver},
v2 as protocol_v2, vstaging as protocol_vstaging, Versioned,
};
use polkadot_node_primitives::StatementWithPVD;
use polkadot_node_subsystem::{
messages::{NetworkBridgeEvent, StatementDistributionMessage},
overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError,
};
use polkadot_node_subsystem_util::{
rand,
reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL},
runtime::{prospective_parachains_mode, ProspectiveParachainsMode},
};
use futures::{channel::mpsc, prelude::*};
use sp_keystore::KeystorePtr;
use fatality::Nested;
mod error;
pub use error::{Error, FatalError, JfyiError, Result};
/// Metrics for the statement distribution
pub(crate) mod metrics;
use metrics::Metrics;
mod legacy_v1;
use legacy_v1::{
respond as v1_respond_task, RequesterMessage as V1RequesterMessage,
ResponderMessage as V1ResponderMessage,
};
mod v2;
const LOG_TARGET: &str = "parachain::statement-distribution";
/// The statement distribution subsystem.
pub struct StatementDistributionSubsystem<R> {
/// Pointer to a keystore, which is required for determining this node's validator index.
keystore: KeystorePtr,
/// Receiver for incoming large statement requests.
v1_req_receiver: Option<IncomingRequestReceiver<request_v1::StatementFetchingRequest>>,
/// Receiver for incoming candidate requests.
req_receiver: Option<IncomingRequestReceiver<AttestedCandidateRequest>>,
/// Prometheus metrics
metrics: Metrics,
/// Pseudo-random generator for peers selection logic
rng: R,
/// Aggregated reputation change
reputation: ReputationAggregator,
}
#[overseer::subsystem(StatementDistribution, error=SubsystemError, prefix=self::overseer)]
impl<Context, R: rand::Rng + Send + Sync + 'static> StatementDistributionSubsystem<R> {
fn start(self, ctx: Context) -> SpawnedSubsystem {
// Swallow error because failure is fatal to the node and we log with more precision
// within `run`.
SpawnedSubsystem {
name: "statement-distribution-subsystem",
future: self
.run(ctx)
.map_err(|e| SubsystemError::with_origin("statement-distribution", e))
.boxed(),
}
}
}
/// Messages to be handled in this subsystem.
enum MuxedMessage {
/// Messages from other subsystems.
Subsystem(FatalResult<FromOrchestra<StatementDistributionMessage>>),
/// Messages from spawned v1 (legacy) requester background tasks.
V1Requester(Option<V1RequesterMessage>),
/// Messages from spawned v1 (legacy) responder background task.
V1Responder(Option<V1ResponderMessage>),
/// Messages from candidate responder background task.
Responder(Option<v2::ResponderMessage>),
/// Messages from answered requests.
Response(v2::UnhandledResponse),
/// Message that a request is ready to be retried. This just acts as a signal that we should
/// dispatch all pending requests again.
RetryRequest(()),
}
#[overseer::contextbounds(StatementDistribution, prefix = self::overseer)]
impl MuxedMessage {
async fn receive<Context>(
ctx: &mut Context,
state: &mut v2::State,
from_v1_requester: &mut mpsc::Receiver<V1RequesterMessage>,
from_v1_responder: &mut mpsc::Receiver<V1ResponderMessage>,
from_responder: &mut mpsc::Receiver<v2::ResponderMessage>,
) -> MuxedMessage {
let (request_manager, response_manager) = state.request_and_response_managers();
// We are only fusing here to make `select` happy, in reality we will quit if one of those
// streams end:
let from_orchestra = ctx.recv().fuse();
let from_v1_requester = from_v1_requester.next();
let from_v1_responder = from_v1_responder.next();
let from_responder = from_responder.next();
let receive_response = v2::receive_response(response_manager).fuse();
let retry_request = v2::next_retry(request_manager).fuse();
futures::pin_mut!(
from_orchestra,
from_v1_requester,
from_v1_responder,
from_responder,
receive_response,
retry_request,
);
futures::select! {
msg = from_orchestra => MuxedMessage::Subsystem(msg.map_err(FatalError::SubsystemReceive)),
msg = from_v1_requester => MuxedMessage::V1Requester(msg),
msg = from_v1_responder => MuxedMessage::V1Responder(msg),
msg = from_responder => MuxedMessage::Responder(msg),
msg = receive_response => MuxedMessage::Response(msg),
msg = retry_request => MuxedMessage::RetryRequest(msg),
}
}
}
#[overseer::contextbounds(StatementDistribution, prefix = self::overseer)]
impl<R: rand::Rng> StatementDistributionSubsystem<R> {
/// Create a new Statement Distribution Subsystem
pub fn new(
keystore: KeystorePtr,
v1_req_receiver: IncomingRequestReceiver<request_v1::StatementFetchingRequest>,
req_receiver: IncomingRequestReceiver<AttestedCandidateRequest>,
metrics: Metrics,
rng: R,
) -> Self {
Self {
keystore,
v1_req_receiver: Some(v1_req_receiver),
req_receiver: Some(req_receiver),
metrics,
rng,
reputation: Default::default(),
}
}
async fn run<Context>(self, ctx: Context) -> std::result::Result<(), FatalError> {
self.run_inner(ctx, REPUTATION_CHANGE_INTERVAL).await
}
async fn run_inner<Context>(
mut self,
mut ctx: Context,
reputation_interval: Duration,
) -> std::result::Result<(), FatalError> {
let new_reputation_delay = || futures_timer::Delay::new(reputation_interval).fuse();
let mut reputation_delay = new_reputation_delay();
let mut legacy_v1_state = crate::legacy_v1::State::new(self.keystore.clone());
let mut state = crate::v2::State::new(self.keystore.clone());
// Sender/Receiver for getting news from our statement fetching tasks.
let (v1_req_sender, mut v1_req_receiver) = mpsc::channel(1);
// Sender/Receiver for getting news from our responder task.
let (v1_res_sender, mut v1_res_receiver) = mpsc::channel(1);
let mut warn_freq = gum::Freq::new();
ctx.spawn(
"large-statement-responder",
v1_respond_task(
self.v1_req_receiver.take().expect("Mandatory argument to new. qed"),
v1_res_sender.clone(),
)
.boxed(),
)
.map_err(FatalError::SpawnTask)?;
// Sender/receiver for getting news from our candidate responder task.
let (res_sender, mut res_receiver) = mpsc::channel(1);
ctx.spawn(
"candidate-responder",
v2::respond_task(
self.req_receiver.take().expect("Mandatory argument to new. qed"),
res_sender.clone(),
)
.boxed(),
)
.map_err(FatalError::SpawnTask)?;
loop {
// Wait for the next message.
let message = futures::select! {
_ = reputation_delay => {
self.reputation.send(ctx.sender()).await;
reputation_delay = new_reputation_delay();
continue
},
message = MuxedMessage::receive(
&mut ctx,
&mut state,
&mut v1_req_receiver,
&mut v1_res_receiver,
&mut res_receiver,
).fuse() => {
message
}
};
match message {
MuxedMessage::Subsystem(result) => {
let result = self
.handle_subsystem_message(
&mut ctx,
&mut state,
&mut legacy_v1_state,
&v1_req_sender,
result?,
)
.await;
match result.into_nested()? {
Ok(true) => break,
Ok(false) => {},
Err(jfyi) => gum::debug!(target: LOG_TARGET, error = ?jfyi),
}
},
MuxedMessage::V1Requester(result) => {
let result = crate::legacy_v1::handle_requester_message(
&mut ctx,
&mut legacy_v1_state,
&v1_req_sender,
&mut self.rng,
result.ok_or(FatalError::RequesterReceiverFinished)?,
&self.metrics,
&mut self.reputation,
)
.await;
log_error(
result.map_err(From::from),
"handle_requester_message",
&mut warn_freq,
)?;
},
MuxedMessage::V1Responder(result) => {
let result = crate::legacy_v1::handle_responder_message(
&mut legacy_v1_state,
result.ok_or(FatalError::ResponderReceiverFinished)?,
)
.await;
log_error(
result.map_err(From::from),
"handle_responder_message",
&mut warn_freq,
)?;
},
MuxedMessage::Responder(result) => {
v2::answer_request(
&mut state,
result.ok_or(FatalError::RequesterReceiverFinished)?,
);
},
MuxedMessage::Response(result) => {
v2::handle_response(&mut ctx, &mut state, result, &mut self.reputation).await;
},
MuxedMessage::RetryRequest(()) => {
// A pending request is ready to retry. This is only a signal to call
// `dispatch_requests` again.
()
},
};
v2::dispatch_requests(&mut ctx, &mut state).await;
}
Ok(())
}
async fn handle_subsystem_message<Context>(
&mut self,
ctx: &mut Context,
state: &mut v2::State,
legacy_v1_state: &mut legacy_v1::State,
v1_req_sender: &mpsc::Sender<V1RequesterMessage>,
message: FromOrchestra<StatementDistributionMessage>,
) -> Result<bool> {
let metrics = &self.metrics;
match message {
FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
activated,
deactivated,
})) => {
let _timer = metrics.time_active_leaves_update();
// v2 should handle activated first because of implicit view.
if let Some(ref activated) = activated {
let mode = prospective_parachains_mode(ctx.sender(), activated.hash).await?;
if let ProspectiveParachainsMode::Enabled { .. } = mode {
v2::handle_active_leaves_update(ctx, state, activated, mode).await?;
v2::handle_deactivate_leaves(state, &deactivated);
} else if let ProspectiveParachainsMode::Disabled = mode {
for deactivated in &deactivated {
crate::legacy_v1::handle_deactivate_leaf(legacy_v1_state, *deactivated);
}
crate::legacy_v1::handle_activated_leaf(
ctx,
legacy_v1_state,
activated.clone(),
)
.await?;
}
} else {
for deactivated in &deactivated {
crate::legacy_v1::handle_deactivate_leaf(legacy_v1_state, *deactivated);
}
v2::handle_deactivate_leaves(state, &deactivated);
}
},
FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {
// do nothing
},
FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(true),
FromOrchestra::Communication { msg } => match msg {
StatementDistributionMessage::Share(relay_parent, statement) => {
let _timer = metrics.time_share();
// pass to legacy if legacy state contains head.
if legacy_v1_state.contains_relay_parent(&relay_parent) {
crate::legacy_v1::share_local_statement(
ctx,
legacy_v1_state,
relay_parent,
StatementWithPVD::drop_pvd_from_signed(statement),
&mut self.rng,
metrics,
)
.await?;
} else {
v2::share_local_statement(
ctx,
state,
relay_parent,
statement,
&mut self.reputation,
)
.await?;
}
},
StatementDistributionMessage::NetworkBridgeUpdate(event) => {
// pass all events to both protocols except for messages,
// which are filtered.
enum VersionTarget {
Legacy,
Current,
Both,
}
impl VersionTarget {
fn targets_legacy(&self) -> bool {
match self {
&VersionTarget::Legacy | &VersionTarget::Both => true,
_ => false,
}
}
fn targets_current(&self) -> bool {
match self {
&VersionTarget::Current | &VersionTarget::Both => true,
_ => false,
}
}
}
let target = match &event {
NetworkBridgeEvent::PeerMessage(_, message) => match message {
Versioned::V2(
protocol_v2::StatementDistributionMessage::V1Compatibility(_),
) |
Versioned::VStaging(
protocol_vstaging::StatementDistributionMessage::V1Compatibility(_),
) => VersionTarget::Legacy,
Versioned::V1(_) => VersionTarget::Legacy,
Versioned::V2(_) | Versioned::VStaging(_) => VersionTarget::Current,
},
_ => VersionTarget::Both,
};
if target.targets_legacy() {
crate::legacy_v1::handle_network_update(
ctx,
legacy_v1_state,
v1_req_sender,
event.clone(),
&mut self.rng,
metrics,
&mut self.reputation,
)
.await;
}
if target.targets_current() {
// pass to v2.
v2::handle_network_update(ctx, state, event, &mut self.reputation).await;
}
},
StatementDistributionMessage::Backed(candidate_hash) => {
crate::v2::handle_backed_candidate_message(ctx, state, candidate_hash).await;
},
},
}
Ok(false)
}
}